openvibers/openvibe.live Commits
Live: users.password_hash nullable; nothing writes email or password placeholders (expand step) (#81)
Changes
nnew file mode 100644@@@ -0,0 +1,6 @@++-- phase: expand++-- WS-B task 2: Live keeps no passwords and no email addresses; the OpenVibe account (openvibe.network) keeps them.++-- users.password_hash was NOT NULL, so every new user row carried a random '$sso$…' placeholder. It becomes nullable++-- and nothing writes it any more (server/db/database.js createUser); users.email is already written as NULL only.++-- A later contract migration drops both columns once no release that writes them can be rolled back to.++ALTER TABLE users ALTER COLUMN password_hash DROP NOT NULL;@@@ -169,7 +169,7 @@ async function eraseUsers(d, userIds, { now = new Date().toISOString() } = {}) {await d.prepare('UPDATE transactions SET message = NULL WHERE from_user_id = ? AND message IS NOT NULL').run(uid);}// The tombstone: the kept rows point at nobody; the username is free again.-- const set = { username: `deleted-${uid}`, email: null, password_hash: '!deleted', display_name: null, avatar_url: null, bio: null, stream_key: null,++ const set = { username: `deleted-${uid}`, email: null, password_hash: null, display_name: null, avatar_url: null, bio: null, stream_key: null,profile_color: null, theme_id: null, avatar_paste_id: null, ban_reason: null, deleted_at: now };const keys = Object.keys(set).filter((k) => usersCols.includes(k));await d.prepare(`UPDATE users SET ${keys.map((k) => `${q(k)} = ?`).join(', ')} WHERE id = ?`).run(...keys.map((k) => set[k]), uid);@@@ -160,8 +160,6 @@ async function resolveNetworkUser(decoded) {const stream_key = uuidv4().replace(/-/g, '');const result = await db.createUser({username: decoded.username,-- email: null,-- password_hash: '$sso$' + require('crypto').randomBytes(32).toString('hex'),display_name: decoded.display_name || decoded.username,stream_key,});@@@ -364,8 +364,6 @@ async function establishNetworkSession(req, res, tokenData) {const stream_key = uuidv4().replace(/-/g, '');const result = await db.createUser({username: ssoUser.username,-- email: null, // the OpenVibe account keeps it (WS-B task 2)-- password_hash: '$sso$' + require('crypto').randomBytes(32).toString('hex'), // placeholder, can't login with passworddisplay_name: ssoUser.display_name || ssoUser.username,stream_key,});@@@ -230,15 +230,15 @@ async function getUserByStreamKey(key) {return await get('SELECT * FROM users WHERE stream_key = ?', [key]);}--async function createUser({ username, email, password_hash, display_name, stream_key }) {++async function createUser({ username, password_hash, display_name, stream_key }) {// Identity is the OpenVibe account's (WS-B task 2): Live stores no password and no email. A real password-- // hash here is a bug, refused before it reaches the table; an email address is never stored.++ // hash here is a bug, refused before anything is written; neither column is written at all (migration 0003++ // made password_hash nullable, and a contract migration drops both).if (/^\$(2[abxy]?|argon2|scrypt|pbkdf2)/.test(String(password_hash || ''))) throw new Error('Live stores no passwords: accounts sign in through openvibe.network');-- email = null;return await run(-- `INSERT INTO users (username, email, password_hash, display_name, stream_key)-- VALUES (?, ?, ?, ?, ?) RETURNING id`,-- [username, email || null, password_hash, display_name || username, stream_key]++ `INSERT INTO users (username, display_name, stream_key)++ VALUES (?, ?, ?) RETURNING id`,++ [username, display_name || username, stream_key]);}@@@ -1187,10 +1187,8 @@ async function start() {const adminUser = config.adminUsername || 'admin';await db.createUser({username: adminUser,-- email: null,// Live keeps no passwords (sign-in is the OpenVibe account's): this account signs in through SSO once// an openvibe.network account with its username exists; ADMIN_PASSWORD is not used.-- password_hash: '$sso$' + require('crypto').randomBytes(32).toString('hex'),display_name: adminUser,stream_key: uuidv4().replace(/-/g, ''),});
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+-- phase: expand
+-- WS-B task 2: Live keeps no passwords and no email addresses; the OpenVibe account (openvibe.network) keeps them.
+-- users.password_hash was NOT NULL, so every new user row carried a random '$sso$…' placeholder. It becomes nullable
+-- and nothing writes it any more (server/db/database.js createUser); users.email is already written as NULL only.
+-- A later contract migration drops both columns once no release that writes them can be rolled back to.
+ALTER TABLE users ALTER COLUMN password_hash DROP NOT NULL;
@@ -169,7 +169,7 @@ async function eraseUsers(d, userIds, { now = new Date().toISOString() } = {}) {
@@ -169,7 +169,7 @@ async function eraseUsers(d, userIds, { now = new Date().toISOString() } = {}) {
await d.prepare('UPDATE transactions SET message = NULL WHERE from_user_id = ? AND message IS NOT NULL').run(uid);
await d.prepare('UPDATE transactions SET message = NULL WHERE from_user_id = ? AND message IS NOT NULL').run(uid);
}
}
// The tombstone: the kept rows point at nobody; the username is free again.
// The tombstone: the kept rows point at nobody; the username is free again.
- const set = { username: `deleted-${uid}`, email: null, password_hash: '!deleted', display_name: null, avatar_url: null, bio: null, stream_key: null,
+ const set = { username: `deleted-${uid}`, email: null, password_hash: null, display_name: null, avatar_url: null, bio: null, stream_key: null,
profile_color: null, theme_id: null, avatar_paste_id: null, ban_reason: null, deleted_at: now };
profile_color: null, theme_id: null, avatar_paste_id: null, ban_reason: null, deleted_at: now };
const keys = Object.keys(set).filter((k) => usersCols.includes(k));
const keys = Object.keys(set).filter((k) => usersCols.includes(k));
await d.prepare(`UPDATE users SET ${keys.map((k) => `${q(k)} = ?`).join(', ')} WHERE id = ?`).run(...keys.map((k) => set[k]), uid);
await d.prepare(`UPDATE users SET ${keys.map((k) => `${q(k)} = ?`).join(', ')} WHERE id = ?`).run(...keys.map((k) => set[k]), uid);
@@ -160,8 +160,6 @@ async function resolveNetworkUser(decoded) {
@@ -160,8 +160,6 @@ async function resolveNetworkUser(decoded) {
const stream_key = uuidv4().replace(/-/g, '');
const stream_key = uuidv4().replace(/-/g, '');
const result = await db.createUser({
const result = await db.createUser({
username: decoded.username,
username: decoded.username,
- email: null,
- password_hash: '$sso$' + require('crypto').randomBytes(32).toString('hex'),
display_name: decoded.display_name || decoded.username,
display_name: decoded.display_name || decoded.username,
stream_key,
stream_key,
});
});
@@ -364,8 +364,6 @@ async function establishNetworkSession(req, res, tokenData) {
@@ -364,8 +364,6 @@ async function establishNetworkSession(req, res, tokenData) {
const stream_key = uuidv4().replace(/-/g, '');
const stream_key = uuidv4().replace(/-/g, '');
const result = await db.createUser({
const result = await db.createUser({
username: ssoUser.username,
username: ssoUser.username,
- email: null, // the OpenVibe account keeps it (WS-B task 2)
- password_hash: '$sso$' + require('crypto').randomBytes(32).toString('hex'), // placeholder, can't login with password
display_name: ssoUser.display_name || ssoUser.username,
display_name: ssoUser.display_name || ssoUser.username,
stream_key,
stream_key,
});
});
@@ -230,15 +230,15 @@ async function getUserByStreamKey(key) {
@@ -230,15 +230,15 @@ async function getUserByStreamKey(key) {
return await get('SELECT * FROM users WHERE stream_key = ?', [key]);
return await get('SELECT * FROM users WHERE stream_key = ?', [key]);
}
}
-async function createUser({ username, email, password_hash, display_name, stream_key }) {
+async function createUser({ username, password_hash, display_name, stream_key }) {
// Identity is the OpenVibe account's (WS-B task 2): Live stores no password and no email. A real password
// Identity is the OpenVibe account's (WS-B task 2): Live stores no password and no email. A real password
- // hash here is a bug, refused before it reaches the table; an email address is never stored.
+ // hash here is a bug, refused before anything is written; neither column is written at all (migration 0003
+ // made password_hash nullable, and a contract migration drops both).
if (/^\$(2[abxy]?|argon2|scrypt|pbkdf2)/.test(String(password_hash || ''))) throw new Error('Live stores no passwords: accounts sign in through openvibe.network');
if (/^\$(2[abxy]?|argon2|scrypt|pbkdf2)/.test(String(password_hash || ''))) throw new Error('Live stores no passwords: accounts sign in through openvibe.network');
- email = null;
return await run(
return await run(
- `INSERT INTO users (username, email, password_hash, display_name, stream_key)
- VALUES (?, ?, ?, ?, ?) RETURNING id`,
- [username, email || null, password_hash, display_name || username, stream_key]
+ `INSERT INTO users (username, display_name, stream_key)
+ VALUES (?, ?, ?) RETURNING id`,
+ [username, display_name || username, stream_key]
);
);
}
}
@@ -1187,10 +1187,8 @@ async function start() {
@@ -1187,10 +1187,8 @@ async function start() {
const adminUser = config.adminUsername || 'admin';
const adminUser = config.adminUsername || 'admin';
await db.createUser({
await db.createUser({
username: adminUser,
username: adminUser,
- email: null,
// Live keeps no passwords (sign-in is the OpenVibe account's): this account signs in through SSO once
// Live keeps no passwords (sign-in is the OpenVibe account's): this account signs in through SSO once
// an openvibe.network account with its username exists; ADMIN_PASSWORD is not used.
// an openvibe.network account with its username exists; ADMIN_PASSWORD is not used.
- password_hash: '$sso$' + require('crypto').randomBytes(32).toString('hex'),
display_name: adminUser,
display_name: adminUser,
stream_key: uuidv4().replace(/-/g, ''),
stream_key: uuidv4().replace(/-/g, ''),
});
});