Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entryway: plivo integration #2155

Merged
merged 8 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading