Skip to content

Commit

Permalink
Entryway: multi verifier (#2156)
Browse files Browse the repository at this point in the history
* add multi verifier

* add second try flag
  • Loading branch information
dholms authored Feb 8, 2024
1 parent 4cb84a7 commit e78faa5
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 2 deletions.
29 changes: 27 additions & 2 deletions packages/pds/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
}
if (env.phoneVerificationRequired) {
const provider = env.phoneVerificationProvider
let providerCfg: TwilioConfig | PlivoConfig
let providerCfg: TwilioConfig | PlivoConfig | MultiVerifierConfig
if (provider === 'twilio') {
assert(env.twilioAccountSid)
assert(env.twilioServiceSid)
Expand All @@ -148,6 +148,25 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
authId: env.plivoAuthId,
appId: env.plivoAppId,
}
} else if (provider === 'multi') {
assert(env.twilioAccountSid)
assert(env.twilioServiceSid)
assert(env.plivoAuthId)
assert(env.plivoAppId)

providerCfg = {
provider,
twilio: {
provider: 'twilio',
accountSid: env.twilioAccountSid,
serviceSid: env.twilioServiceSid,
},
plivo: {
provider: 'plivo',
authId: env.plivoAuthId,
appId: env.plivoAppId,
},
}
} else {
throw new Error(`invalid phone verification provider: ${provider}`)
}
Expand Down Expand Up @@ -350,7 +369,7 @@ export type InvitesConfig =
export type PhoneVerificationConfig =
| {
required: true
provider: TwilioConfig | PlivoConfig
provider: TwilioConfig | PlivoConfig | MultiVerifierConfig
accountsPerPhoneNumber: number
bypassPhoneNumber?: string
}
Expand All @@ -370,6 +389,12 @@ export type PlivoConfig = {
appId: string
}

export type MultiVerifierConfig = {
provider: 'multi'
twilio: TwilioConfig
plivo: PlivoConfig
}

export type EmailConfig = {
smtpUrl: string
fromAddress: string
Expand Down
15 changes: 15 additions & 0 deletions packages/pds/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { DAY } from '@atproto/common'
import { PhoneVerifier } from './phone-verification/util'
import { TwilioClient } from './phone-verification/twilio'
import { PlivoClient } from './phone-verification/plivo'
import { MultiVerifier } from './phone-verification/multi'

export type AppContextOptions = {
db: Database
Expand Down Expand Up @@ -241,6 +242,20 @@ export class AppContext {
appId: cfg.phoneVerification.provider.appId,
authToken: secrets.plivoAuthToken,
})
} else if (cfg.phoneVerification.provider.provider === 'multi') {
assert(secrets.twilioAuthToken, 'expected twilio auth token')
assert(secrets.plivoAuthToken, 'expected plivo auth token')
const twilio = new TwilioClient({
accountSid: cfg.phoneVerification.provider.twilio.accountSid,
serviceSid: cfg.phoneVerification.provider.twilio.serviceSid,
authToken: secrets.twilioAuthToken,
})
const plivo = new PlivoClient(db, {
authId: cfg.phoneVerification.provider.plivo.authId,
appId: cfg.phoneVerification.provider.plivo.appId,
authToken: secrets.plivoAuthToken,
})
phoneVerifier = new MultiVerifier(db, twilio, plivo)
}
}

Expand Down
85 changes: 85 additions & 0 deletions packages/pds/src/phone-verification/multi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Database from '../db'
import { PlivoClient } from './plivo'
import { TwilioClient } from './twilio'
import { SECOND } from '@atproto/common'
import { randomIntFromSeed } from '@atproto/crypto'
import { PhoneVerifier } from './util'

const PLIVO_RATIO_FLAG = 'phone-verification:plivoRatio'
const SECOND_TRY_FLAG = 'phone-verification:attemptSecondTry'

export class MultiVerifier implements PhoneVerifier {
plivoRatio = 0
attemptSecondTry = false
lastRefreshed = 0

constructor(
public db: Database,
public twilio: TwilioClient,
public plivo: PlivoClient,
) {}

async checkRefreshRatio() {
if (Date.now() - this.lastRefreshed > 30 * SECOND) {
await this.refreshRatio()
}
}

async refreshRatio() {
const res = await this.db.db
.selectFrom('runtime_flag')
.where('name', '=', PLIVO_RATIO_FLAG)
.orWhere('name', '=', SECOND_TRY_FLAG)
.selectAll()
.execute()

this.plivoRatio = parseMaybeInt(
res.find((val) => val.name === PLIVO_RATIO_FLAG)?.value,
)
this.attemptSecondTry =
res.find((val) => val.name === SECOND_TRY_FLAG)?.value === 'true'

this.lastRefreshed = Date.now()
}

async sendCode(phoneNumber: string) {
await this.checkRefreshRatio()
const id = await randomIntFromSeed(phoneNumber, 10, 0)
if (id < this.plivoRatio) {
await this.plivo.sendCode(phoneNumber)
} else {
await this.twilio.sendCode(phoneNumber)
}
}

async verifyCode(phoneNumber: string, code: string) {
await this.checkRefreshRatio()
const id = await randomIntFromSeed(phoneNumber, 10, 0)
const firstTry =
id < this.plivoRatio
? () => this.plivo.verifyCode(phoneNumber, code)
: () => this.twilio.verifyCode(phoneNumber, code)
const secondTry =
id < this.plivoRatio
? () => this.plivo.verifyCode(phoneNumber, code)
: () => this.twilio.verifyCode(phoneNumber, code)
try {
return await firstTry()
} catch (err) {
if (this.attemptSecondTry) {
return await secondTry()
} else {
throw err
}
}
}
}

const parseMaybeInt = (str?: string): number => {
if (!str) return 0
const parsed = parseInt(str)
if (isNaN(parsed) || parsed < 0) {
return 0
}
return parsed
}

0 comments on commit e78faa5

Please sign in to comment.