diff --git a/keyserver/src/creators/account-creator.js b/keyserver/src/creators/account-creator.js index 6b427b340..ae72d3f66 100644 --- a/keyserver/src/creators/account-creator.js +++ b/keyserver/src/creators/account-creator.js @@ -1,376 +1,378 @@ // @flow import { getRustAPI } from 'rust-node-addon'; import bcrypt from 'twin-bcrypt'; -import ashoat from 'lib/facts/ashoat.js'; import bots from 'lib/facts/bots.js'; import genesis from 'lib/facts/genesis.js'; import { policyTypes } from 'lib/facts/policies.js'; import { validUsernameRegex } from 'lib/shared/account-utils.js'; import type { RegisterResponse, RegisterRequest, } from 'lib/types/account-types.js'; import type { UserDetail, ReservedUsernameMessage, SignedIdentityKeysBlob, } from 'lib/types/crypto-types.js'; import type { PlatformDetails, DeviceTokenUpdateRequest, } from 'lib/types/device-types.js'; import type { CalendarQuery } from 'lib/types/entry-types.js'; import { messageTypes } from 'lib/types/message-types-enum.js'; import type { SIWESocialProof } from 'lib/types/siwe-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { ServerError } from 'lib/utils/errors.js'; import { values } from 'lib/utils/objects.js'; import { ignorePromiseRejections } from 'lib/utils/promises.js'; import { reservedUsernamesSet } from 'lib/utils/reserved-users.js'; import { isValidEthereumAddress } from 'lib/utils/siwe-utils.js'; import createIDs from './id-creator.js'; import createMessages from './message-creator.js'; import { createAndPersistOlmSession } from './olm-session-creator.js'; import { createThread, createPrivateThread, privateThreadDescription, } from './thread-creator.js'; import { dbQuery, SQL } from '../database/database.js'; import { deleteCookie } from '../deleters/cookie-deleters.js'; import { fetchThreadInfos } from '../fetchers/thread-fetchers.js'; import { fetchLoggedInUserInfo, fetchKnownUserInfos, } from '../fetchers/user-fetchers.js'; import { verifyCalendarQueryThreadIDs } from '../responders/entry-responders.js'; import { searchForUser } from '../search/users.js'; import { createNewUserCookie, setNewSession } from '../session/cookies.js'; import { createScriptViewer } from '../session/scripts.js'; import type { Viewer } from '../session/viewer.js'; import { fetchOlmAccount } from '../updaters/olm-account-updater.js'; import { updateThread } from '../updaters/thread-updaters.js'; import { viewerAcknowledgmentUpdater } from '../updaters/viewer-acknowledgment-updater.js'; +import { thisKeyserverAdmin } from '../user/identity.js'; const { commbot } = bots; const ashoatMessages = [ 'welcome to Comm!', 'as you inevitably discover bugs, have feature requests, or design ' + 'suggestions, feel free to message them to me in the app.', ]; const privateMessages = [privateThreadDescription]; async function createAccount( viewer: Viewer, request: RegisterRequest, ): Promise { if (request.password.trim() === '') { throw new ServerError('empty_password'); } if (request.username.search(validUsernameRegex) === -1) { throw new ServerError('invalid_username'); } - const promises = [searchForUser(request.username)]; + const promises = [searchForUser(request.username), thisKeyserverAdmin()]; const { calendarQuery, signedIdentityKeysBlob, initialNotificationsEncryptedMessage, } = request; if (calendarQuery) { promises.push(verifyCalendarQueryThreadIDs(calendarQuery)); } - const [existingUser] = await Promise.all(promises); + const [existingUser, admin] = await Promise.all(promises); if ( reservedUsernamesSet.has(request.username.toLowerCase()) || isValidEthereumAddress(request.username.toLowerCase()) ) { throw new ServerError('username_reserved'); } if (existingUser) { throw new ServerError('username_taken'); } const hash = bcrypt.hashSync(request.password); const time = Date.now(); const deviceToken = request.deviceTokenUpdateRequest ? request.deviceTokenUpdateRequest.deviceToken : viewer.deviceToken; const [id] = await createIDs('users', 1); const newUserRow = [id, request.username, hash, time]; const newUserQuery = SQL` INSERT INTO users(id, username, hash, creation_time) VALUES ${[newUserRow]} `; const [userViewerData] = await Promise.all([ createNewUserCookie(id, { platformDetails: request.platformDetails, deviceToken, signedIdentityKeysBlob, }), deleteCookie(viewer.cookieID), dbQuery(newUserQuery), ]); viewer.setNewCookie(userViewerData); if (calendarQuery) { await setNewSession(viewer, calendarQuery, 0); } const olmSessionPromise = (async () => { if (userViewerData.cookieID && initialNotificationsEncryptedMessage) { await createAndPersistOlmSession( initialNotificationsEncryptedMessage, 'notifications', userViewerData.cookieID, ); } })(); await Promise.all([ updateThread( - createScriptViewer(ashoat.id), + createScriptViewer(admin.id), { threadID: genesis().id, changes: { newMemberIDs: [id] }, }, { forceAddMembers: true, silenceMessages: true, ignorePermissions: true }, ), viewerAcknowledgmentUpdater(viewer, policyTypes.tosAndPrivacyPolicy), olmSessionPromise, ]); const [privateThreadResult, ashoatThreadResult] = await Promise.all([ createPrivateThread(viewer), createThread( viewer, { type: threadTypes.PERSONAL, - initialMemberIDs: [ashoat.id], + initialMemberIDs: [admin.id], }, { forceAddMembers: true }, ), ]); const ashoatThreadID = ashoatThreadResult.newThreadID; const privateThreadID = privateThreadResult.newThreadID; let messageTime = Date.now(); const ashoatMessageDatas = ashoatMessages.map(message => ({ type: messageTypes.TEXT, threadID: ashoatThreadID, - creatorID: ashoat.id, + creatorID: admin.id, time: messageTime++, text: message, })); const privateMessageDatas = privateMessages.map(message => ({ type: messageTypes.TEXT, threadID: privateThreadID, creatorID: commbot.userID, time: messageTime++, text: message, })); const messageDatas = [...ashoatMessageDatas, ...privateMessageDatas]; const [messageInfos, threadsResult, userInfos, currentUserInfo] = await Promise.all([ createMessages(viewer, messageDatas), fetchThreadInfos(viewer), fetchKnownUserInfos(viewer), fetchLoggedInUserInfo(viewer), ]); const rawMessageInfos = [ ...ashoatThreadResult.newMessageInfos, ...privateThreadResult.newMessageInfos, ...messageInfos, ]; ignorePromiseRejections( createAndSendReservedUsernameMessage([ { username: request.username, userID: id }, ]), ); return { id, rawMessageInfos, currentUserInfo, cookieChange: { threadInfos: threadsResult.threadInfos, userInfos: values(userInfos), }, }; } export type ProcessSIWEAccountCreationRequest = { +address: string, +calendarQuery: CalendarQuery, +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, +platformDetails: PlatformDetails, +socialProof: SIWESocialProof, +signedIdentityKeysBlob?: ?SignedIdentityKeysBlob, }; // Note: `processSIWEAccountCreation(...)` assumes that the validity of // `ProcessSIWEAccountCreationRequest` was checked at call site. async function processSIWEAccountCreation( viewer: Viewer, request: ProcessSIWEAccountCreationRequest, ): Promise { const { calendarQuery, signedIdentityKeysBlob } = request; await verifyCalendarQueryThreadIDs(calendarQuery); const time = Date.now(); const deviceToken = request.deviceTokenUpdateRequest ? request.deviceTokenUpdateRequest.deviceToken : viewer.deviceToken; const [id] = await createIDs('users', 1); const newUserRow = [id, request.address, request.address, time]; const newUserQuery = SQL` INSERT INTO users(id, username, ethereum_address, creation_time) VALUES ${[newUserRow]} `; const [userViewerData] = await Promise.all([ createNewUserCookie(id, { platformDetails: request.platformDetails, deviceToken, socialProof: request.socialProof, signedIdentityKeysBlob, }), deleteCookie(viewer.cookieID), dbQuery(newUserQuery), ]); viewer.setNewCookie(userViewerData); await setNewSession(viewer, calendarQuery, 0); await processAccountCreationCommon(viewer); ignorePromiseRejections( createAndSendReservedUsernameMessage([ { username: request.address, userID: id }, ]), ); return id; } export type ProcessOLMAccountCreationRequest = { +userID: string, +username: string, +walletAddress?: ?string, +calendarQuery: CalendarQuery, +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, +platformDetails: PlatformDetails, +signedIdentityKeysBlob: SignedIdentityKeysBlob, }; // Note: `processOLMAccountCreation(...)` assumes that the validity of // `ProcessOLMAccountCreationRequest` was checked at call site. async function processOLMAccountCreation( viewer: Viewer, request: ProcessOLMAccountCreationRequest, ): Promise { const { calendarQuery, signedIdentityKeysBlob } = request; await verifyCalendarQueryThreadIDs(calendarQuery); const time = Date.now(); const deviceToken = request.deviceTokenUpdateRequest ? request.deviceTokenUpdateRequest.deviceToken : viewer.deviceToken; const newUserRow = [ request.userID, request.username, request.walletAddress, time, ]; const newUserQuery = SQL` INSERT INTO users(id, username, ethereum_address, creation_time) VALUES ${[newUserRow]} `; const [userViewerData] = await Promise.all([ createNewUserCookie(request.userID, { platformDetails: request.platformDetails, deviceToken, signedIdentityKeysBlob, }), deleteCookie(viewer.cookieID), dbQuery(newUserQuery), ]); viewer.setNewCookie(userViewerData); await setNewSession(viewer, calendarQuery, 0); await processAccountCreationCommon(viewer); } async function processAccountCreationCommon(viewer: Viewer) { + const admin = await thisKeyserverAdmin(); + await Promise.all([ updateThread( - createScriptViewer(ashoat.id), + createScriptViewer(admin.id), { threadID: genesis().id, changes: { newMemberIDs: [viewer.userID] }, }, { forceAddMembers: true, silenceMessages: true, ignorePermissions: true }, ), viewerAcknowledgmentUpdater(viewer, policyTypes.tosAndPrivacyPolicy), ]); const [privateThreadResult, ashoatThreadResult] = await Promise.all([ createPrivateThread(viewer), createThread( viewer, { type: threadTypes.PERSONAL, - initialMemberIDs: [ashoat.id], + initialMemberIDs: [admin.id], }, { forceAddMembers: true }, ), ]); const ashoatThreadID = ashoatThreadResult.newThreadID; const privateThreadID = privateThreadResult.newThreadID; let messageTime = Date.now(); const ashoatMessageDatas = ashoatMessages.map(message => ({ type: messageTypes.TEXT, threadID: ashoatThreadID, - creatorID: ashoat.id, + creatorID: admin.id, time: messageTime++, text: message, })); const privateMessageDatas = privateMessages.map(message => ({ type: messageTypes.TEXT, threadID: privateThreadID, creatorID: commbot.userID, time: messageTime++, text: message, })); const messageDatas = [...ashoatMessageDatas, ...privateMessageDatas]; await Promise.all([createMessages(viewer, messageDatas)]); } async function createAndSendReservedUsernameMessage( payload: $ReadOnlyArray, ) { const issuedAt = new Date().toISOString(); const reservedUsernameMessage: ReservedUsernameMessage = { statement: 'Add the following usernames to reserved list', payload, issuedAt, }; const stringifiedMessage = JSON.stringify(reservedUsernameMessage); const [rustAPI, accountInfo] = await Promise.all([ getRustAPI(), fetchOlmAccount('content'), ]); const signature = accountInfo.account.sign(stringifiedMessage); await rustAPI.addReservedUsernames(stringifiedMessage, signature); } export { createAccount, processSIWEAccountCreation, processOLMAccountCreation }; diff --git a/keyserver/src/database/setup-db.js b/keyserver/src/database/setup-db.js index 238b46e1e..6c18ef2e2 100644 --- a/keyserver/src/database/setup-db.js +++ b/keyserver/src/database/setup-db.js @@ -1,484 +1,494 @@ // @flow -import ashoat from 'lib/facts/ashoat.js'; import bots from 'lib/facts/bots.js'; import genesis from 'lib/facts/genesis.js'; import { usernameMaxLength } from 'lib/shared/account-utils.js'; import { sortUserIDs } from 'lib/shared/relationship-utils.js'; import { undirectedStatus } from 'lib/types/relationship-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { createThread } from '../creators/thread-creator.js'; import { dbQuery, SQL } from '../database/database.js'; import { updateDBVersion } from '../database/db-version.js'; import { newDatabaseVersion, createOlmAccounts, } from '../database/migration-config.js'; import { createScriptViewer } from '../session/scripts.js'; import { ensureUserCredentials } from '../user/checks.js'; +import { thisKeyserverAdmin } from '../user/identity.js'; +import { verifyUserLoggedIn } from '../user/login.js'; async function setupDB() { await ensureUserCredentials(); await createTables(); + await createOlmAccounts(); + await verifyUserLoggedIn(); await createUsers(); await createThreads(); await setUpMetadataTable(); - await createOlmAccounts(); } async function createTables() { await dbQuery( SQL` CREATE TABLE cookies ( id bigint(20) NOT NULL, hash char(64) NOT NULL, user varchar(255) CHARSET latin1 COLLATE latin1_bin DEFAULT NULL, platform varchar(255) DEFAULT NULL, creation_time bigint(20) NOT NULL, last_used bigint(20) NOT NULL, device_token mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, versions json DEFAULT NULL, device_id varchar(255) DEFAULT NULL, signed_identity_keys mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, social_proof mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, \`primary\` TINYINT(1) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE days ( id bigint(20) NOT NULL, date date NOT NULL, thread bigint(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE entries ( id bigint(20) NOT NULL, day bigint(20) NOT NULL, text mediumtext COLLATE utf8mb4_bin NOT NULL, creator varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, creation_time bigint(20) NOT NULL, last_update bigint(20) NOT NULL, deleted tinyint(1) UNSIGNED NOT NULL, creation varchar(255) COLLATE utf8mb4_bin DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE focused ( user varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, session bigint(20) NOT NULL, thread bigint(20) NOT NULL, time bigint(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE ids ( id bigint(20) NOT NULL, table_name varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE memberships ( thread bigint(20) NOT NULL, user varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, role bigint(20) NOT NULL, permissions json DEFAULT NULL, permissions_for_children json DEFAULT NULL, creation_time bigint(20) NOT NULL, subscription json NOT NULL, last_message bigint(20) NOT NULL DEFAULT 0, last_read_message bigint(20) NOT NULL DEFAULT 0, sender tinyint(1) UNSIGNED NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE messages ( id bigint(20) NOT NULL, thread bigint(20) NOT NULL, user varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, type tinyint(3) UNSIGNED NOT NULL, content mediumtext COLLATE utf8mb4_bin, time bigint(20) NOT NULL, creation varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, target_message bigint(20) DEFAULT NULL, pinned tinyint(1) UNSIGNED NOT NULL DEFAULT 0, pin_time bigint(20) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE notifications ( id bigint(20) NOT NULL, user varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, thread bigint(20) DEFAULT NULL, message bigint(20) DEFAULT NULL, collapse_key varchar(255) DEFAULT NULL, delivery json NOT NULL, rescinded tinyint(1) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE reports ( id bigint(20) NOT NULL, user varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, type tinyint(3) UNSIGNED NOT NULL, platform varchar(255) NOT NULL, report json NOT NULL, creation_time bigint(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE revisions ( id bigint(20) NOT NULL, entry bigint(20) NOT NULL, author varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, text mediumtext COLLATE utf8mb4_bin NOT NULL, creation_time bigint(20) NOT NULL, session bigint(20) NOT NULL, last_update bigint(20) NOT NULL, deleted tinyint(1) UNSIGNED NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE roles ( id bigint(20) NOT NULL, thread bigint(20) NOT NULL, name varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, permissions json NOT NULL, creation_time bigint(20) NOT NULL, special_role tinyint(2) UNSIGNED DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE sessions ( id bigint(20) NOT NULL, user varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, cookie bigint(20) NOT NULL, query json NOT NULL, creation_time bigint(20) NOT NULL, last_update bigint(20) NOT NULL, last_validated bigint(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE threads ( id bigint(20) NOT NULL, type tinyint(3) NOT NULL, name varchar(191) COLLATE utf8mb4_bin DEFAULT NULL, description mediumtext COLLATE utf8mb4_bin, parent_thread_id bigint(20) DEFAULT NULL, containing_thread_id bigint(20) DEFAULT NULL, community bigint(20) DEFAULT NULL, depth int UNSIGNED NOT NULL DEFAULT 0, creator varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, creation_time bigint(20) NOT NULL, color char(6) COLLATE utf8mb4_bin NOT NULL, source_message bigint(20) DEFAULT NULL UNIQUE, replies_count int UNSIGNED NOT NULL DEFAULT 0, avatar varchar(191) COLLATE utf8mb4_bin DEFAULT NULL, pinned_count int UNSIGNED NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE updates ( id bigint(20) NOT NULL, user varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, type tinyint(3) UNSIGNED NOT NULL, \`key\` varchar(255) CHARSET latin1 COLLATE latin1_bin DEFAULT NULL, updater bigint(20) DEFAULT NULL, target bigint(20) DEFAULT NULL, content mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, time bigint(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE uploads ( id bigint(20) NOT NULL, thread bigint(20) DEFAULT NULL, uploader varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, container bigint(20) DEFAULT NULL, type varchar(255) CHARSET latin1 COLLATE latin1_swedish_ci NOT NULL, filename varchar(255) NOT NULL, mime varchar(255) CHARSET latin1 COLLATE latin1_swedish_ci NOT NULL, content longblob NOT NULL, secret varchar(255) CHARSET latin1 COLLATE latin1_swedish_ci NOT NULL, creation_time bigint(20) NOT NULL, extra json DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE users ( id varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, username varchar(${usernameMaxLength}) COLLATE utf8mb4_bin NOT NULL, hash char(60) COLLATE utf8mb4_bin DEFAULT NULL, avatar varchar(191) COLLATE utf8mb4_bin DEFAULT NULL, ethereum_address char(42) DEFAULT NULL, creation_time bigint(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE relationships_undirected ( user1 varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, user2 varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, status tinyint(1) UNSIGNED NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE relationships_directed ( user1 varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, user2 varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, status tinyint(1) UNSIGNED NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE one_time_keys ( session bigint(20) NOT NULL, one_time_key char(43) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE user_messages ( recipient varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, thread bigint(20) NOT NULL, message bigint(20) NOT NULL, time bigint(20) NOT NULL, data mediumtext COLLATE utf8mb4_bin DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE settings ( user varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, name varchar(255) NOT NULL, data mediumtext COLLATE utf8mb4_bin DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE metadata ( name varchar(255) NOT NULL, data varchar(1023) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE policy_acknowledgments ( user varchar(255) CHARSET latin1 COLLATE latin1_bin NOT NULL, policy varchar(255) NOT NULL, date bigint(20) NOT NULL, confirmed tinyint(1) UNSIGNED NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE siwe_nonces ( nonce char(17) NOT NULL, creation_time bigint(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE message_search ( original_message_id bigint(20) NOT NULL, message_id bigint(20) NOT NULL, processed_content mediumtext COLLATE utf8mb4_bin ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE invite_links ( id bigint(20) NOT NULL, name varchar(255) CHARSET latin1 NOT NULL, \`primary\` tinyint(1) UNSIGNED NOT NULL DEFAULT 0, role bigint(20) NOT NULL, community bigint(20) NOT NULL, expiration_time bigint(20), limit_of_uses int UNSIGNED, number_of_uses int UNSIGNED NOT NULL DEFAULT 0, blob_holder char(36) CHARSET latin1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE olm_sessions ( cookie_id bigint(20) NOT NULL, is_content tinyint(1) NOT NULL, version bigint(20) NOT NULL, pickled_olm_session text CHARACTER SET latin1 COLLATE latin1_bin NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin; CREATE TABLE olm_accounts ( is_content tinyint(1) NOT NULL, version bigint(20) NOT NULL, pickling_key text CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, pickled_olm_account text CHARACTER SET latin1 COLLATE latin1_bin NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin; ALTER TABLE cookies ADD PRIMARY KEY (id), ADD UNIQUE KEY device_token (device_token(512)), ADD KEY user_device_token (user,device_token(512)); ALTER TABLE days ADD PRIMARY KEY (id), ADD UNIQUE KEY date_thread (date,thread) USING BTREE; ALTER TABLE entries ADD PRIMARY KEY (id), ADD UNIQUE KEY creator_creation (creator,creation), ADD KEY day (day); ALTER TABLE focused ADD UNIQUE KEY user_cookie_thread (user,session,thread), ADD KEY thread_user (thread,user); ALTER TABLE ids ADD PRIMARY KEY (id); ALTER TABLE memberships ADD UNIQUE KEY thread_user (thread, user) USING BTREE, ADD KEY role (role) USING BTREE, ADD KEY user_role_thread (user, role, thread); ALTER TABLE messages ADD PRIMARY KEY (id), ADD UNIQUE KEY user_creation (user,creation), ADD KEY thread (thread), ADD INDEX target_message (target_message), ADD INDEX thread_pinned (thread, pinned); ALTER TABLE notifications ADD PRIMARY KEY (id), ADD KEY rescinded_user_collapse_key (rescinded,user,collapse_key) USING BTREE, ADD KEY thread (thread), ADD KEY rescinded_user_thread_message (rescinded,user,thread,message) USING BTREE; ALTER TABLE notifications ADD INDEX user (user); ALTER TABLE reports ADD PRIMARY KEY (id), ADD INDEX user_type_platform_creation_time (user, type, platform, creation_time); ALTER TABLE revisions ADD PRIMARY KEY (id), ADD KEY entry (entry); ALTER TABLE roles ADD PRIMARY KEY (id), ADD KEY thread_special_role (thread, special_role), ADD UNIQUE KEY thread_name (thread, name); ALTER TABLE sessions ADD PRIMARY KEY (id), ADD KEY user (user); ALTER TABLE threads ADD PRIMARY KEY (id), ADD INDEX parent_thread_id (parent_thread_id), ADD INDEX containing_thread_id (containing_thread_id), ADD INDEX community (community); ALTER TABLE updates ADD PRIMARY KEY (id), ADD INDEX user_time (user,time), ADD INDEX target_time (target, time), ADD INDEX user_key_target_type_time (user, \`key\`, target, type, time), ADD INDEX user_key_type_time (user, \`key\`, type, time), ADD INDEX user_key_time (user, \`key\`, time); ALTER TABLE uploads ADD PRIMARY KEY (id), ADD INDEX container (container), ADD INDEX thread (thread); ALTER TABLE users ADD PRIMARY KEY (id), ADD UNIQUE KEY username (username); ALTER TABLE relationships_undirected ADD UNIQUE KEY user1_user2 (user1,user2), ADD UNIQUE KEY user2_user1 (user2,user1); ALTER TABLE relationships_directed ADD UNIQUE KEY user1_user2 (user1,user2), ADD UNIQUE KEY user2_user1 (user2,user1); ALTER TABLE one_time_keys ADD PRIMARY KEY (session, one_time_key); ALTER TABLE user_messages ADD INDEX recipient_time (recipient, time), ADD INDEX recipient_thread_time (recipient, thread, time), ADD INDEX thread (thread), ADD PRIMARY KEY (recipient, message); ALTER TABLE ids MODIFY id bigint(20) NOT NULL AUTO_INCREMENT; ALTER TABLE settings ADD PRIMARY KEY (user, name); ALTER TABLE metadata ADD PRIMARY KEY (name); ALTER TABLE policy_acknowledgments ADD PRIMARY KEY (user, policy); ALTER TABLE siwe_nonces ADD PRIMARY KEY (nonce); ALTER TABLE message_search ADD PRIMARY KEY (original_message_id), ADD FULLTEXT INDEX processed_content (processed_content); ALTER TABLE invite_links ADD PRIMARY KEY (id), ADD UNIQUE KEY (name), ADD INDEX community_primary (community, \`primary\`); ALTER TABLE olm_sessions ADD PRIMARY KEY (cookie_id, is_content); ALTER TABLE olm_accounts ADD PRIMARY KEY (is_content); `, { multipleStatements: true }, ); } async function createUsers() { - const [user1, user2] = sortUserIDs(bots.commbot.userID, ashoat.id); - await dbQuery( - SQL` + const admin = await thisKeyserverAdmin(); + + const [user1, user2] = sortUserIDs(bots.commbot.userID, admin.id); + const query = SQL` INSERT INTO ids (id, table_name) VALUES - (${bots.commbot.userID}, 'users'), - (${ashoat.id}, 'users'); + (${bots.commbot.userID}, 'users'); INSERT INTO users (id, username, hash, avatar, creation_time) VALUES (${bots.commbot.userID}, 'commbot', '', NULL, 1530049900980), - (${ashoat.id}, 'ashoat', '', NULL, 1463588881886); + (${admin.id}, ${admin.username}, '', NULL, 1463588881886); INSERT INTO relationships_undirected (user1, user2, status) VALUES (${user1}, ${user2}, ${undirectedStatus.KNOW_OF}); - `, - { multipleStatements: true }, - ); + `; + + if (!isNaN(Number(admin.id))) { + query.append(SQL` + INSERT INTO ids (id, table_name) + VALUES (${admin.id}, 'users'); + `); + } + await dbQuery(query, { multipleStatements: true }); } const createThreadOptions = { forceAddMembers: true }; async function createThreads() { const insertIDsPromise = dbQuery(SQL` INSERT INTO ids (id, table_name) VALUES (${genesis().id}, 'threads'), (${bots.commbot.staffThreadID}, 'threads') `); - const ashoatViewer = createScriptViewer(ashoat.id); + const admin = await thisKeyserverAdmin(); + + const ashoatViewer = createScriptViewer(admin.id); const createGenesisPromise = createThread( ashoatViewer, { id: genesis().id, type: threadTypes.GENESIS, name: genesis().name, description: genesis().description, initialMemberIDs: [bots.commbot.userID], }, createThreadOptions, ); await Promise.all([insertIDsPromise, createGenesisPromise]); const commbotViewer = createScriptViewer(bots.commbot.userID); await createThread( commbotViewer, { id: bots.commbot.staffThreadID, type: threadTypes.COMMUNITY_SECRET_SUBTHREAD, - initialMemberIDs: [ashoat.id], + initialMemberIDs: [admin.id], }, createThreadOptions, ); } async function setUpMetadataTable() { await updateDBVersion(newDatabaseVersion); } export { setupDB }; diff --git a/keyserver/src/scripts/create-community.js b/keyserver/src/scripts/create-community.js index a959d5eba..05b1eab67 100644 --- a/keyserver/src/scripts/create-community.js +++ b/keyserver/src/scripts/create-community.js @@ -1,20 +1,21 @@ // @flow -import ashoat from 'lib/facts/ashoat.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { main } from './utils.js'; import { createThread } from '../creators/thread-creator.js'; import { createScriptViewer } from '../session/scripts.js'; +import { thisKeyserverAdmin } from '../user/identity.js'; const communityName = 'New community'; async function createCommunity() { - const ashoatViewer = createScriptViewer(ashoat.id); - await createThread(ashoatViewer, { + const admin = await thisKeyserverAdmin(); + const adminViewer = createScriptViewer(admin.id); + await createThread(adminViewer, { type: threadTypes.COMMUNITY_ROOT, name: communityName, }); } main([createCommunity]); diff --git a/keyserver/src/scripts/create-many-threads-to-trigger-crash-loop.js b/keyserver/src/scripts/create-many-threads-to-trigger-crash-loop.js index ac5e3b916..72a7445b3 100644 --- a/keyserver/src/scripts/create-many-threads-to-trigger-crash-loop.js +++ b/keyserver/src/scripts/create-many-threads-to-trigger-crash-loop.js @@ -1,42 +1,43 @@ // @flow -import ashoat from 'lib/facts/ashoat.js'; import { main } from './utils.js'; import { createThread } from '../creators/thread-creator.js'; import { createScriptViewer } from '../session/scripts.js'; +import { thisKeyserverAdmin } from '../user/identity.js'; const testUserID = ''; const numOfThreads = 1000; async function createThreads( n: number, spammedUserID: string, spammingUserID: string, ): Promise<$ReadOnlyArray> { const threads = []; const viewer = createScriptViewer(spammingUserID); const initialMemberIDs = [spammedUserID]; const threadRequest = { type: 3, initialMemberIDs, parentThreadID: '1' }; for (let i = 0; i < n; i++) { const threadResponse = await createThread(viewer, threadRequest); if (threadResponse.newThreadID) { const threadID: string = threadResponse.newThreadID; threads.push(threadID); } } return threads; } // This script is used to trigger socket crash loop // Linear issue: https://linear.app/comm/issue/ENG-2075/reproduce-socket-crash-loop-in-production-with-artificial-test-data // Usage: set testUserID to the user you wish to trigger the crash loop for, // set iOS physical device networking profile to 3G and run the script, // open comm on physical device once the script has finnished // the app should be in a crash loop async function createManyThreadsToTriggerCrashLoop() { - await createThreads(numOfThreads, testUserID, ashoat.id); + const admin = await thisKeyserverAdmin(); + await createThreads(numOfThreads, testUserID, admin.id); } main([createManyThreadsToTriggerCrashLoop]); diff --git a/keyserver/src/scripts/make-channel-private.js b/keyserver/src/scripts/make-channel-private.js index 5b8d7d2d2..bae629dae 100644 --- a/keyserver/src/scripts/make-channel-private.js +++ b/keyserver/src/scripts/make-channel-private.js @@ -1,20 +1,21 @@ // @flow -import ashoat from 'lib/facts/ashoat.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { main } from './utils.js'; import { createScriptViewer } from '../session/scripts.js'; import { updateThread } from '../updaters/thread-updaters.js'; +import { thisKeyserverAdmin } from '../user/identity.js'; const channelID = '-1'; async function makeChannelPrivate() { - const viewer = createScriptViewer(ashoat.id); + const admin = await thisKeyserverAdmin(); + const viewer = createScriptViewer(admin.id); await updateThread(viewer, { threadID: channelID, changes: { type: threadTypes.COMMUNITY_SECRET_SUBTHREAD }, }); } main([makeChannelPrivate]); diff --git a/keyserver/src/scripts/move-threads.js b/keyserver/src/scripts/move-threads.js index 6a8e53411..b7d7cd41a 100644 --- a/keyserver/src/scripts/move-threads.js +++ b/keyserver/src/scripts/move-threads.js @@ -1,36 +1,37 @@ // @flow -import ashoat from 'lib/facts/ashoat.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { main } from './utils.js'; import { createScriptViewer } from '../session/scripts.js'; import { updateThread } from '../updaters/thread-updaters.js'; +import { thisKeyserverAdmin } from '../user/identity.js'; async function moveThreads() { - const viewer = createScriptViewer(ashoat.id); + const admin = await thisKeyserverAdmin(); + const viewer = createScriptViewer(admin.id); await updateThread( viewer, { threadID: '1251682', // comm global hq changes: { type: threadTypes.COMMUNITY_SECRET_SUBTHREAD, parentThreadID: '311733', // Comm }, }, { ignorePermissions: true }, ); await updateThread( viewer, { threadID: '1512796', // Bird App changes: { type: threadTypes.COMMUNITY_OPEN_SUBTHREAD, parentThreadID: '311733', // Comm }, }, { ignorePermissions: true }, ); } main([moveThreads]); diff --git a/keyserver/src/user/identity.js b/keyserver/src/user/identity.js index 1004eacfe..6de3e36b3 100644 --- a/keyserver/src/user/identity.js +++ b/keyserver/src/user/identity.js @@ -1,69 +1,100 @@ // @flow import type { QueryResults } from 'mysql'; +import ashoat from 'lib/facts/ashoat.js'; import { getCommConfig } from 'lib/utils/comm-config.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; import type { UserCredentials } from './checks'; import { SQL, dbQuery } from '../database/database.js'; const userIDMetadataKey = 'user_id'; const accessTokenMetadataKey = 'access_token'; // This information is minted when registering with identity service // Naming should reflect the rust-bindings: userId -> user_id export type IdentityInfo = { +userId: string, +accessToken: string }; async function fetchIdentityInfo(): Promise { const versionQuery = SQL` SELECT name, data FROM metadata WHERE name IN (${userIDMetadataKey}, ${accessTokenMetadataKey}) `; const [result] = await dbQuery(versionQuery); let userID, accessToken; for (const row of result) { if (row.name === userIDMetadataKey) { userID = row.data; } else if (row.name === accessTokenMetadataKey) { accessToken = row.data; } } if (!userID || !accessToken) { return null; } return { userId: userID, accessToken }; } let cachedKeyserverID: ?string; async function thisKeyserverID(): Promise { if (cachedKeyserverID) { return cachedKeyserverID; } const config = await getCommConfig({ folder: 'secrets', name: 'user_credentials', }); if (!config?.usingIdentityCredentials) { return ashoatKeyserverID; } const identityInfo = await fetchIdentityInfo(); cachedKeyserverID = identityInfo?.userId ?? ashoatKeyserverID; return cachedKeyserverID; } +export type AdminData = { + +username: string, + +id: string, +}; + +async function thisKeyserverAdmin(): Promise { + const config = await getCommConfig({ + folder: 'secrets', + name: 'user_credentials', + }); + + if (!config) { + return { + id: ashoat.id, + username: ashoat.username, + }; + } + const id = await thisKeyserverID(); + + return { + id, + username: config.username, + }; +} + function saveIdentityInfo(userInfo: IdentityInfo): Promise { const updateQuery = SQL` REPLACE INTO metadata (name, data) VALUES (${userIDMetadataKey}, ${userInfo.userId}), (${accessTokenMetadataKey}, ${userInfo.accessToken}) `; return dbQuery(updateQuery); } -export { fetchIdentityInfo, thisKeyserverID, saveIdentityInfo }; +export { + fetchIdentityInfo, + thisKeyserverID, + thisKeyserverAdmin, + saveIdentityInfo, +}; diff --git a/lib/facts/ashoat.js b/lib/facts/ashoat.js index 51ead0689..8f5cbc76a 100644 --- a/lib/facts/ashoat.js +++ b/lib/facts/ashoat.js @@ -1,15 +1,17 @@ // @flow type Ashoat = { +id: string, +email: string, +landing_email: string, + +username: string, }; const ashoat: Ashoat = { id: '256', email: 'ashoat@gmail.com', landing_email: 'postmaster@comm.app', + username: 'ashoat', }; export default ashoat;