openvibers/openvibe.network Commits
Names lookup: GET /api/v1/users/names/:name answers { current, network_id, renamed, previous_names } for a current or an old name (banned accounts and unknown names 404), so Live can redirect /@old and pick up a new name it has not seen yet; replaces /api/v1/users/renamed/:name (never deployed)
Changes
@@@ -6,7 +6,8 @@* Users → Rename). Every rename is kept in username_history, so:* - openvibe.live answers /@old with a 301 to /@new for renamed channels (never for a bare /old,* which stays a 404: channel URLs are /@username only);-- * - GET /api/v1/users/renamed/:name tells any site the current name for an old one;++ * - GET /api/v1/users/names/:name tells any site who holds a name now (current name, Network id,++ * past names), for an old name and for a new one it has not seen yet;* - an old name stays reserved for RESERVE_DAYS, so nobody can take it over and inherit the* person's links, mentions and reputation. Only the person who held it may take it back.*/@@@ -78,10 +79,30 @@ function renamedTo(db, oldName) {return row ? row.username : null;}++/**++ * The public record for a name: { current, network_id, renamed, previous_names } when it is someone's++ * current name or one they were renamed away from; null otherwise.++ */++function lookup(db, name) {++ ensureSchema(db);++ if (!NAME_RE.test(String(name || ''))) return null;++ let user = db.prepare('SELECT id, username FROM users WHERE LOWER(username) = LOWER(?) AND COALESCE(is_banned, 0) = 0').get(name);++ let renamed = false;++ if (!user) {++ const h = db.prepare('SELECT user_id FROM username_history WHERE old_username = ? COLLATE NOCASE ORDER BY id DESC LIMIT 1').get(name);++ if (!h) return null;++ user = db.prepare('SELECT id, username FROM users WHERE id = ? AND COALESCE(is_banned, 0) = 0').get(h.user_id);++ if (!user) return null;++ renamed = true;++ }++ const previous = db.prepare('SELECT old_username FROM username_history WHERE user_id = ? ORDER BY id DESC').all(user.id).map(r => r.old_username);++ return { current: user.username, network_id: user.id, renamed, previous_names: previous };++}++/** A user's past usernames, newest first. */function historyOf(db, userId) {ensureSchema(db);return db.prepare('SELECT old_username, new_username, changed_at FROM username_history WHERE user_id = ? ORDER BY id DESC').all(userId);}--module.exports = { ensureSchema, isReserved, problemWith, rename, renamedTo, historyOf, RESERVE_DAYS, NAME_RE };++module.exports = { ensureSchema, isReserved, problemWith, rename, renamedTo, lookup, historyOf, RESERVE_DAYS, NAME_RE };@@@ -384,12 +384,13 @@ const jwt = require('jsonwebtoken');const requireAuth = require('./auth/session').makeRequireAuth(() => ({ db, publicKey, config }), authRoutes.signToken);// The moderation audit log for staff (staff.moderation.logs): server/admin/moderation-audit.js.app.use('/api/v1/staff/moderation-audit', requireAuth, moderationAudit.router());--// Username history (WS-B task 6): the current name for an old one, so sites redirect /@old → /@new.--app.get('/api/v1/users/renamed/:name', rateLimit({ windowMs: 60_000, max: 120 }), (req, res) => {-- const to = require('./identity/usernames').renamedTo(db, req.params.name);-- res.set('Cache-Control', 'public, max-age=300');-- if (!to) return res.status(404).json({ error: 'not_renamed' });-- res.json({ username: to });++// Username history (WS-B task 6): who holds a name now, so sites redirect /@old → /@new and pick up a++// new name before they have seen it. Banned accounts and unknown names are 404.++app.get('/api/v1/users/names/:name', rateLimit({ windowMs: 60_000, max: 240 }), (req, res) => {++ const rec = require('./identity/usernames').lookup(db, req.params.name);++ res.set('Cache-Control', 'public, max-age=120');++ if (!rec) return res.status(404).json({ error: 'not_found' });++ res.json(rec);});// The GitHub token (admin → Settings → GitHub, or GITHUB_TOKEN): owner-only admin, and Blog's changelog reads it.app.use('/api/admin/integrations/github', requireAuth, require('./integrations/github').adminRouter(db));@@@ -51,7 +51,13 @@ try {assert.ok(/usernames'\)\.isReserved\(db, username\)/.test(auth));const admin = fs.readFileSync(path.join(__dirname, '../server/admin/routes.js'), 'utf8');assert.ok(/router\.put\('\/users\/:id\/username'/.test(admin) && /Only the owner renames the owner/.test(admin));-- assert.ok(/app\.get\('\/api\/v1\/users\/renamed\/:name'/.test(fs.readFileSync(path.join(__dirname, '../server/index.js'), 'utf8')));++ assert.ok(/app\.get\('\/api\/v1\/users\/names\/:name'/.test(fs.readFileSync(path.join(__dirname, '../server/index.js'), 'utf8')));++ // The public record: an old name and the current one both lead to the same person.++ assert.deepStrictEqual(u.lookup(db, 'ann_old'), { current: 'ann_third', network_id: ann, renamed: true, previous_names: ['ann_new', 'ann_old'] });++ assert.deepStrictEqual(u.lookup(db, 'ANN_THIRD'), { current: 'ann_third', network_id: ann, renamed: false, previous_names: ['ann_new', 'ann_old'] });++ assert.strictEqual(u.lookup(db, 'nobody_here'), null);++ db.prepare('UPDATE users SET is_banned = 1 WHERE id = ?').run(ann);++ assert.strictEqual(u.lookup(db, 'ann_old'), null, 'banned accounts are not looked up');} finally {fs.rmSync(dir, { recursive: true, force: true });}
@@ -6,7 +6,8 @@
@@ -6,7 +6,8 @@
* Users → Rename). Every rename is kept in username_history, so:
* Users → Rename). Every rename is kept in username_history, so:
* - openvibe.live answers /@old with a 301 to /@new for renamed channels (never for a bare /old,
* - openvibe.live answers /@old with a 301 to /@new for renamed channels (never for a bare /old,
* which stays a 404: channel URLs are /@username only);
* which stays a 404: channel URLs are /@username only);
- * - GET /api/v1/users/renamed/:name tells any site the current name for an old one;
+ * - GET /api/v1/users/names/:name tells any site who holds a name now (current name, Network id,
+ * past names), for an old name and for a new one it has not seen yet;
* - an old name stays reserved for RESERVE_DAYS, so nobody can take it over and inherit the
* - an old name stays reserved for RESERVE_DAYS, so nobody can take it over and inherit the
* person's links, mentions and reputation. Only the person who held it may take it back.
* person's links, mentions and reputation. Only the person who held it may take it back.
*/
*/
@@ -78,10 +79,30 @@ function renamedTo(db, oldName) {
@@ -78,10 +79,30 @@ function renamedTo(db, oldName) {
return row ? row.username : null;
return row ? row.username : null;
}
}
+/**
+ * The public record for a name: { current, network_id, renamed, previous_names } when it is someone's
+ * current name or one they were renamed away from; null otherwise.
+ */
+function lookup(db, name) {
+ ensureSchema(db);
+ if (!NAME_RE.test(String(name || ''))) return null;
+ let user = db.prepare('SELECT id, username FROM users WHERE LOWER(username) = LOWER(?) AND COALESCE(is_banned, 0) = 0').get(name);
+ let renamed = false;
+ if (!user) {
+ const h = db.prepare('SELECT user_id FROM username_history WHERE old_username = ? COLLATE NOCASE ORDER BY id DESC LIMIT 1').get(name);
+ if (!h) return null;
+ user = db.prepare('SELECT id, username FROM users WHERE id = ? AND COALESCE(is_banned, 0) = 0').get(h.user_id);
+ if (!user) return null;
+ renamed = true;
+ }
+ const previous = db.prepare('SELECT old_username FROM username_history WHERE user_id = ? ORDER BY id DESC').all(user.id).map(r => r.old_username);
+ return { current: user.username, network_id: user.id, renamed, previous_names: previous };
+}
+
/** A user's past usernames, newest first. */
/** A user's past usernames, newest first. */
function historyOf(db, userId) {
function historyOf(db, userId) {
ensureSchema(db);
ensureSchema(db);
return db.prepare('SELECT old_username, new_username, changed_at FROM username_history WHERE user_id = ? ORDER BY id DESC').all(userId);
return db.prepare('SELECT old_username, new_username, changed_at FROM username_history WHERE user_id = ? ORDER BY id DESC').all(userId);
}
}
-module.exports = { ensureSchema, isReserved, problemWith, rename, renamedTo, historyOf, RESERVE_DAYS, NAME_RE };
+module.exports = { ensureSchema, isReserved, problemWith, rename, renamedTo, lookup, historyOf, RESERVE_DAYS, NAME_RE };
@@ -384,12 +384,13 @@ const jwt = require('jsonwebtoken');
@@ -384,12 +384,13 @@ const jwt = require('jsonwebtoken');
const requireAuth = require('./auth/session').makeRequireAuth(() => ({ db, publicKey, config }), authRoutes.signToken);
const requireAuth = require('./auth/session').makeRequireAuth(() => ({ db, publicKey, config }), authRoutes.signToken);
// The moderation audit log for staff (staff.moderation.logs): server/admin/moderation-audit.js.
// The moderation audit log for staff (staff.moderation.logs): server/admin/moderation-audit.js.
app.use('/api/v1/staff/moderation-audit', requireAuth, moderationAudit.router());
app.use('/api/v1/staff/moderation-audit', requireAuth, moderationAudit.router());
-// Username history (WS-B task 6): the current name for an old one, so sites redirect /@old → /@new.
-app.get('/api/v1/users/renamed/:name', rateLimit({ windowMs: 60_000, max: 120 }), (req, res) => {
- const to = require('./identity/usernames').renamedTo(db, req.params.name);
- res.set('Cache-Control', 'public, max-age=300');
- if (!to) return res.status(404).json({ error: 'not_renamed' });
- res.json({ username: to });
+// Username history (WS-B task 6): who holds a name now, so sites redirect /@old → /@new and pick up a
+// new name before they have seen it. Banned accounts and unknown names are 404.
+app.get('/api/v1/users/names/:name', rateLimit({ windowMs: 60_000, max: 240 }), (req, res) => {
+ const rec = require('./identity/usernames').lookup(db, req.params.name);
+ res.set('Cache-Control', 'public, max-age=120');
+ if (!rec) return res.status(404).json({ error: 'not_found' });
+ res.json(rec);
});
});
// The GitHub token (admin → Settings → GitHub, or GITHUB_TOKEN): owner-only admin, and Blog's changelog reads it.
// The GitHub token (admin → Settings → GitHub, or GITHUB_TOKEN): owner-only admin, and Blog's changelog reads it.
app.use('/api/admin/integrations/github', requireAuth, require('./integrations/github').adminRouter(db));
app.use('/api/admin/integrations/github', requireAuth, require('./integrations/github').adminRouter(db));
@@ -51,7 +51,13 @@ try {
@@ -51,7 +51,13 @@ try {
assert.ok(/usernames'\)\.isReserved\(db, username\)/.test(auth));
assert.ok(/usernames'\)\.isReserved\(db, username\)/.test(auth));
const admin = fs.readFileSync(path.join(__dirname, '../server/admin/routes.js'), 'utf8');
const admin = fs.readFileSync(path.join(__dirname, '../server/admin/routes.js'), 'utf8');
assert.ok(/router\.put\('\/users\/:id\/username'/.test(admin) && /Only the owner renames the owner/.test(admin));
assert.ok(/router\.put\('\/users\/:id\/username'/.test(admin) && /Only the owner renames the owner/.test(admin));
- assert.ok(/app\.get\('\/api\/v1\/users\/renamed\/:name'/.test(fs.readFileSync(path.join(__dirname, '../server/index.js'), 'utf8')));
+ assert.ok(/app\.get\('\/api\/v1\/users\/names\/:name'/.test(fs.readFileSync(path.join(__dirname, '../server/index.js'), 'utf8')));
+ // The public record: an old name and the current one both lead to the same person.
+ assert.deepStrictEqual(u.lookup(db, 'ann_old'), { current: 'ann_third', network_id: ann, renamed: true, previous_names: ['ann_new', 'ann_old'] });
+ assert.deepStrictEqual(u.lookup(db, 'ANN_THIRD'), { current: 'ann_third', network_id: ann, renamed: false, previous_names: ['ann_new', 'ann_old'] });
+ assert.strictEqual(u.lookup(db, 'nobody_here'), null);
+ db.prepare('UPDATE users SET is_banned = 1 WHERE id = ?').run(ann);
+ assert.strictEqual(u.lookup(db, 'ann_old'), null, 'banned accounts are not looked up');
} finally {
} finally {
fs.rmSync(dir, { recursive: true, force: true });
fs.rmSync(dir, { recursive: true, force: true });
}
}