Skip to content

Commit

Permalink
Entryway: plivo integration (#2155)
Browse files Browse the repository at this point in the history
* hook up plivo to entryway

* fix up test

* build branch

* comment

* fix up dev-env

* explicit on conflict col

* Entryway: multi verifier (#2156)

* add multi verifier

* add second try flag

* fix
  • Loading branch information
dholms authored Feb 8, 2024
1 parent 59494a5 commit e3b9eb6
Show file tree
Hide file tree
Showing 20 changed files with 406 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-push-pds-aws.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
push:
branches:
- main
- multi-pds-auth
- plivo-entryway
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }}
Expand Down
5 changes: 3 additions & 2 deletions packages/dev-env/src/bin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import './env'
import { generateMockSetup } from './mock'
import { TestNetwork } from './network'
import { mockMailer, mockTwilio } from './util'
import { mockMailer, mockPhoneVerifier } from './util'

const run = async () => {
console.log(`
Expand All @@ -21,6 +21,7 @@ const run = async () => {
dbPostgresSchema: 'pds',
enableDidDocWithSession: true,
phoneVerificationRequired: true,
phoneVerificationProvider: 'twilio',
twilioAccountSid: 'ACXXXXXXX',
twilioAuthToken: 'AUTH',
twilioServiceSid: 'VAXXXXXXXX',
Expand All @@ -31,7 +32,7 @@ const run = async () => {
plc: { port: 2582 },
})
mockMailer(network.pds)
mockTwilio(network.pds)
mockPhoneVerifier(network.pds)
await generateMockSetup(network)

console.log(
Expand Down
2 changes: 1 addition & 1 deletion packages/dev-env/src/mock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export async function generateMockSetup(env: TestNetwork) {
for (const user of users) {
let verificationCode: string | undefined = undefined
let verificationPhone: string | undefined = undefined
if (env.pds.ctx.twilio) {
if (env.pds.ctx.phoneVerifier) {
verificationPhone = `+1111111111${_i}`
await clients.loggedout.api.com.atproto.temp.requestPhoneVerification({
phoneNumber: verificationPhone,
Expand Down
8 changes: 4 additions & 4 deletions packages/dev-env/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ export const uniqueLockId = () => {
return lockId
}

export const mockTwilio = (pds: TestPds) => {
if (!pds.ctx.twilio) return
export const mockPhoneVerifier = (pds: TestPds) => {
if (!pds.ctx.phoneVerifier) return

pds.ctx.twilio.sendCode = async (number: string) => {
pds.ctx.phoneVerifier.sendCode = async (number: string) => {
if (!pds.mockedPhoneCodes[number]) {
const code = crypto.randomStr(4, 'base10').slice(0, 6)
pds.mockedPhoneCodes[number] = code
Expand All @@ -90,7 +90,7 @@ export const mockTwilio = (pds: TestPds) => {
console.log(`☎️ Phone verification code sent to ${number}: ${code}`)
}

pds.ctx.twilio.verifyCode = async (number: string, code: string) => {
pds.ctx.phoneVerifier.verifyCode = async (number: string, code: string) => {
if (pds.mockedPhoneCodes[number] === code) {
delete pds.mockedPhoneCodes[number]
return true
Expand Down
7 changes: 4 additions & 3 deletions packages/pds/src/api/com/atproto/server/createAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { didDocForSession } from './util'
import { getPdsEndpoint } from '../../../../pds-agents'
import { isThisPds } from '../../../proxy'
import { dbLogger as log } from '../../../../logger'
import { normalizePhoneNumber } from '../../../../phone-verification/util'

export default function (server: Server, ctx: AppContext) {
server.com.atproto.server.createAccount({
Expand Down Expand Up @@ -483,7 +484,7 @@ const ensurePhoneVerification = async (
phone?: string,
code?: string,
): Promise<string | undefined> => {
if (!ctx.cfg.phoneVerification.required || !ctx.twilio) {
if (!ctx.cfg.phoneVerification.required || !ctx.phoneVerifier) {
return
}

Expand All @@ -503,8 +504,8 @@ const ensurePhoneVerification = async (
'InvalidPhoneVerification',
)
}
const normalizedPhone = ctx.twilio.normalizePhoneNumber(phone)
const verified = await ctx.twilio.verifyCode(normalizedPhone, code)
const normalizedPhone = normalizePhoneNumber(phone)
const verified = await ctx.phoneVerifier.verifyCode(normalizedPhone, code)
if (!verified) {
throw new InvalidRequestError(
'Could not verify phone number. Please try again.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import AppContext from '../../../../context'
import { InvalidRequestError } from '@atproto/xrpc-server'
import { HOUR, MINUTE } from '@atproto/common'
import { countAll } from '../../../../db/util'
import { normalizePhoneNumber } from '../../../../phone-verification/util'

export default function (server: Server, ctx: AppContext) {
server.com.atproto.temp.requestPhoneVerification({
Expand All @@ -17,7 +18,7 @@ export default function (server: Server, ctx: AppContext) {
},
],
handler: async ({ input }) => {
if (!ctx.twilio || !ctx.cfg.phoneVerification.required) {
if (!ctx.phoneVerifier || !ctx.cfg.phoneVerification.required) {
throw new InvalidRequestError('phone verification not enabled')
}
if (
Expand All @@ -29,9 +30,7 @@ export default function (server: Server, ctx: AppContext) {
}
const accountsPerPhoneNumber =
ctx.cfg.phoneVerification.accountsPerPhoneNumber
const phoneNumber = ctx.twilio.normalizePhoneNumber(
input.body.phoneNumber,
)
const phoneNumber = normalizePhoneNumber(input.body.phoneNumber)

const res = await ctx.db.db
.selectFrom('phone_verification')
Expand All @@ -44,7 +43,7 @@ export default function (server: Server, ctx: AppContext) {
)
}

await ctx.twilio.sendCode(phoneNumber)
await ctx.phoneVerifier.sendCode(phoneNumber)
},
})
}
66 changes: 60 additions & 6 deletions packages/pds/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,49 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
required: false,
}
if (env.phoneVerificationRequired) {
assert(env.twilioAccountSid)
assert(env.twilioServiceSid)
const provider = env.phoneVerificationProvider
let providerCfg: TwilioConfig | PlivoConfig | MultiVerifierConfig
if (provider === 'twilio') {
assert(env.twilioAccountSid)
assert(env.twilioServiceSid)
providerCfg = {
provider,
accountSid: env.twilioAccountSid,
serviceSid: env.twilioServiceSid,
}
} else if (provider === 'plivo') {
assert(env.plivoAuthId)
assert(env.plivoAppId)
providerCfg = {
provider,
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}`)
}
phoneVerificationCfg = {
required: true,
twilioAccountSid: env.twilioAccountSid,
twilioServiceSid: env.twilioServiceSid,
provider: providerCfg,
accountsPerPhoneNumber: env.accountsPerPhoneNumber ?? 3,
bypassPhoneNumber: env.bypassPhoneNumber,
}
Expand Down Expand Up @@ -332,15 +369,32 @@ export type InvitesConfig =
export type PhoneVerificationConfig =
| {
required: true
twilioAccountSid: string
twilioServiceSid: string
provider: TwilioConfig | PlivoConfig | MultiVerifierConfig
accountsPerPhoneNumber: number
bypassPhoneNumber?: string
}
| {
required: false
}

export type TwilioConfig = {
provider: 'twilio'
accountSid: string
serviceSid: string
}

export type PlivoConfig = {
provider: 'plivo'
authId: string
appId: string
}

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

export type EmailConfig = {
smtpUrl: string
fromAddress: string
Expand Down
8 changes: 8 additions & 0 deletions packages/pds/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ export const readEnv = (): ServerEnvironment => {

// phone verification
phoneVerificationRequired: envBool('PDS_PHONE_VERIFICATION_REQUIRED'),
phoneVerificationProvider: envStr('PDS_PHONE_VERIFICATION_PROVIDER'),
accountsPerPhoneNumber: envInt('PDS_ACCOUNTS_PER_PHONE_NUMBER'),
bypassPhoneNumber: envStr('PDS_BYPASS_PHONE_NUMBER'),
twilioAccountSid: envStr('PDS_TWILIO_ACCOUNT_SID'),
twilioAuthToken: envStr('PDS_TWILIO_AUTH_TOKEN'),
twilioServiceSid: envStr('PDS_TWILIO_SERVICE_SID'),
plivoAuthId: envStr('PDS_PLIVO_AUTH_ID'),
plivoAuthToken: envStr('PDS_PLIVO_AUTH_TOKEN'),
plivoAppId: envStr('PDS_PLIVO_APP_ID'),

// email
emailSmtpUrl: envStr('PDS_EMAIL_SMTP_URL'),
Expand Down Expand Up @@ -173,11 +177,15 @@ export type ServerEnvironment = {

// phone verification
phoneVerificationRequired?: boolean
phoneVerificationProvider?: string
accountsPerPhoneNumber?: number
bypassPhoneNumber?: string
twilioAccountSid?: string
twilioAuthToken?: string
twilioServiceSid?: string
plivoAuthId?: string
plivoAuthToken?: string
plivoAppId?: string

// email
emailSmtpUrl?: string
Expand Down
2 changes: 2 additions & 0 deletions packages/pds/src/config/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const envToSecrets = (env: ServerEnvironment): ServerSecrets => {
triagePassword:
env.triagePassword ?? env.moderatorPassword ?? env.adminPassword,
twilioAuthToken: env.twilioAuthToken,
plivoAuthToken: env.plivoAuthToken,
repoSigningKey,
plcRotationKey,
}
Expand All @@ -79,6 +80,7 @@ export type ServerSecrets = {
moderatorPassword: string
triagePassword: string
twilioAuthToken?: string
plivoAuthToken?: string
repoSigningKey: SigningKeyKms | SigningKeyMemory
plcRotationKey: SigningKeyKms | SigningKeyMemory
}
Expand Down
50 changes: 38 additions & 12 deletions packages/pds/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ import { DiskBlobStore } from './storage'
import { getRedisClient } from './redis'
import { RuntimeFlags } from './runtime-flags'
import { PdsAgents } from './pds-agents'
import { TwilioClient } from './twilio'
import assert from 'assert'
import { SignupLimiter } from './signup-queue/limiter'
import { SignupActivator } from './signup-queue/activator'
import { createCourierClient, authWithApiKey as courierAuth } from './courier'
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 All @@ -50,7 +53,7 @@ export type AppContextOptions = {
pdsAgents: PdsAgents
repoSigningKey: crypto.Keypair
plcRotationKey: crypto.Keypair
twilio?: TwilioClient
phoneVerifier?: PhoneVerifier
signupLimiter: SignupLimiter
signupActivator: SignupActivator
cfg: ServerConfig
Expand All @@ -77,7 +80,7 @@ export class AppContext {
public pdsAgents: PdsAgents
public repoSigningKey: crypto.Keypair
public plcRotationKey: crypto.Keypair
public twilio?: TwilioClient
public phoneVerifier?: PhoneVerifier
public signupLimiter: SignupLimiter
public signupActivator: SignupActivator
public cfg: ServerConfig
Expand All @@ -103,7 +106,7 @@ export class AppContext {
this.pdsAgents = opts.pdsAgents
this.repoSigningKey = opts.repoSigningKey
this.plcRotationKey = opts.plcRotationKey
this.twilio = opts.twilio
this.phoneVerifier = opts.phoneVerifier
this.signupLimiter = opts.signupLimiter
this.signupActivator = opts.signupActivator
this.cfg = opts.cfg
Expand Down Expand Up @@ -223,14 +226,37 @@ export class AppContext {
crawlers,
})

let twilio: TwilioClient | undefined = undefined
let phoneVerifier: PhoneVerifier | undefined = undefined
if (cfg.phoneVerification.required) {
assert(secrets.twilioAuthToken)
twilio = new TwilioClient({
accountSid: cfg.phoneVerification.twilioAccountSid,
serviceSid: cfg.phoneVerification.twilioServiceSid,
authToken: secrets.twilioAuthToken,
})
if (cfg.phoneVerification.provider.provider === 'twilio') {
assert(secrets.twilioAuthToken, 'expected twilio auth token')
phoneVerifier = new TwilioClient({
accountSid: cfg.phoneVerification.provider.accountSid,
serviceSid: cfg.phoneVerification.provider.serviceSid,
authToken: secrets.twilioAuthToken,
})
} else if (cfg.phoneVerification.provider.provider === 'plivo') {
assert(secrets.plivoAuthToken, 'expected plivo auth token')
phoneVerifier = new PlivoClient(db, {
authId: cfg.phoneVerification.provider.authId,
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)
}
}

const signupLimiter = new SignupLimiter(db)
Expand Down Expand Up @@ -285,7 +311,7 @@ export class AppContext {
repoSigningKey,
plcRotationKey,
pdsAgents,
twilio,
phoneVerifier,
signupLimiter,
signupActivator,
cfg,
Expand Down
4 changes: 3 additions & 1 deletion packages/pds/src/db/database-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as repoSeq from './tables/repo-seq'
import * as appMigration from './tables/app-migration'
import * as runtimeFlag from './tables/runtime-flag'
import * as phoneVerification from './tables/phone-verification'
import * as plivoSession from './tables/plivo-session'

export type DatabaseSchemaType = appMigration.PartialDB &
runtimeFlag.PartialDB &
Expand All @@ -39,7 +40,8 @@ export type DatabaseSchemaType = appMigration.PartialDB &
emailToken.PartialDB &
moderation.PartialDB &
repoSeq.PartialDB &
phoneVerification.PartialDB
phoneVerification.PartialDB &
plivoSession.PartialDB

export type DatabaseSchema = Kysely<DatabaseSchemaType>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Kysely } from 'kysely'

export async function up(db: Kysely<unknown>): Promise<void> {
await db.schema
.createTable('plivo_session')
.addColumn('phoneNumber', 'varchar', (col) => col.primaryKey())
.addColumn('sessionId', 'varchar', (col) => col.notNull())
.addColumn('createdAt', 'varchar', (col) => col.notNull())
.execute()
}

export async function down(db: Kysely<unknown>): Promise<void> {
await db.schema.dropTable('plivo_session').execute()
}
Loading

0 comments on commit e3b9eb6

Please sign in to comment.