packages/fxa-shared/db/models/auth/base-auth.ts (141 lines of code) (raw):

/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import crypto from 'crypto'; import { BaseModel } from '../base'; import { Knex } from 'knex'; export enum Proc { AccountRecord = 'accountRecord_10', AccountResetToken = 'accountResetToken_2', AccountDevices = 'accountDevices_17', ConsumeRecoveryCode = 'consumeRecoveryCode_3', ConsumeSigninCode = 'consumeSigninCode_4', ConsumeUnblockCode = 'consumeUnblockCode_4', CreateAccount = 'createAccount_12', CreateDevice = 'createDevice_5', CreateEmail = 'createEmail_2', CreateEmailBounce = 'createEmailBounce_3', CreateKeyFetchToken = 'createKeyFetchToken_2', CreatePasswordChangeToken = 'createPasswordChangeToken_2', CreatePasswordForgotToken = 'createPasswordForgotToken_2', CreateRecoveryCode = 'createRecoveryCode_3', CreateRecoveryKey = 'createRecoveryKey_4', CreateSecurityEvent = 'createSecurityEvent_5', CreateSessionToken = 'createSessionToken_10', CreateSigninCode = 'createSigninCode_2', CreateTotpToken = 'createTotpToken_1', CreateUnblockCode = 'createUnblockCode_1', DeleteAccount = 'deleteAccount_20', DeleteAccountResetToken = 'deleteAccountResetToken_1', DeleteDevice = 'deleteDevice_4', DeleteEmail = 'deleteEmail_5', DeleteKeyFetchToken = 'deleteKeyFetchToken_2', DeletePasswordChangeToken = 'deletePasswordChangeToken_1', DeletePasswordForgotToken = 'deletePasswordForgotToken_1', DeleteRecoveryCodes = 'deleteRecoveryCodes_1', DeleteRecoveryKey = 'deleteRecoveryKey_2', DeleteSessionToken = 'deleteSessionToken_4', DeleteTotpToken = 'deleteTotpToken_4', Device = 'device_3', DeviceFromTokenVerificationId = 'deviceFromTokenVerificationId_6', EmailBounces = 'fetchEmailBounces_3', FindLargeAccounts = 'findLargeAccounts_1', ForgotPasswordVerified = 'forgotPasswordVerified_9', KeyFetchToken = 'keyFetchToken_1', KeyFetchTokenWithVerificationStatus = 'keyFetchTokenWithVerificationStatus_2', LimitSessions = 'limitSessions_3', PasswordChangeToken = 'passwordChangeToken_3', PasswordForgotToken = 'passwordForgotToken_3', PurgeAvailableCommands = 'purgeAvailableCommands_1', Prune = 'prune_10', RecoveryCodes = 'recoveryCodes_1', RecoveryKey = 'getRecoveryKey_4', ResetAccount = 'resetAccount_19', ResetAccountTokens = 'resetAccountTokens_1', SessionWithDevice = 'sessionWithDevice_19', Sessions = 'sessions_13', SetPrimaryEmail = 'setPrimaryEmail_6', TotpToken = 'totpToken_2', UpdateDevice = 'updateDevice_6', UpdateRecoveryKey = 'updateRecoveryKey_1', UpdateSessionToken = 'updateSessionToken_3', UpdateTotpToken = 'updateTotpToken_4', UpsertAvailableCommands = 'upsertAvailableCommand_1', VerifyEmail = 'verifyEmail_9', VerifyToken = 'verifyToken_3', VerifyTokenWithMethod = 'verifyTokensWithMethod_3', } function callString(name: Proc, argCount: number, outputs?: string[]) { const qs = new Array(argCount) .fill('?') .concat(outputs || []) .join(','); return `Call ${name}(${qs})`; } export type QueryStatus = { fieldCount: number; affectedRows: number; insertId: number; serverStatus: number; warningCount: number; message: string; protocol41: boolean; changedRows: number; }; export abstract class BaseAuthModel extends BaseModel { static async callProcedure( name: Proc, txn: Knex.Transaction, ...args: any[] ): Promise<{ status: QueryStatus; rows: any[] }>; static async callProcedure( name: Proc, ...args: any[] ): Promise<{ status: QueryStatus; rows: any[] }>; static async callProcedure(name: Proc, ...args: any[]) { let [txn, ...rest] = args; const knex = this.knex() as Knex; const query = txn && typeof txn.commit === 'function' ? knex.raw(callString(name, rest.length), rest).transacting(txn) : knex.raw(callString(name, args.length), args); const [result] = await query; if (Array.isArray(result)) { return { status: result.pop(), rows: result.shift() }; } return { status: result, rows: [] }; } static async callProcedureWithOutputs( name: Proc, args: any[], outputs: string[] ) { const { query, knex } = this.callProcedureRaw(args, name, outputs); await query; return await knex.select(knex.raw(outputs.join(','))).first(); } static async callProcedureWithOutputsAndQueryResults( name: Proc, args: any[], outputs: string[] ) { const { query, knex } = this.callProcedureRaw(args, name, outputs); const [result] = await query; return { outputs: await knex.select(knex.raw(outputs.join(','))).first(), results: { status: result.pop(), rows: result }, }; } private static callProcedureRaw(args: any[], name: Proc, outputs: string[]) { let [txn, ...rest] = args; const knex = this.knex() as Knex; const query = txn && typeof txn.commit === 'function' ? knex .raw(callString(name, rest.length, outputs), rest) .transacting(txn) : knex.raw(callString(name, args.length, outputs), args); return { query, knex }; } static sha256(hex: string | Buffer) { const data = typeof hex === 'string' ? Buffer.from(hex, 'hex') : hex; return crypto.createHash('sha256').update(data).digest(); } }