Skip to content

Commit

Permalink
Merge branch 'multi-pds-auth' into signup-queueing-take2
Browse files Browse the repository at this point in the history
  • Loading branch information
dholms committed Feb 1, 2024
2 parents c1bbd68 + 86655ea commit 8d719e4
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 50 deletions.
137 changes: 87 additions & 50 deletions packages/pds/src/api/com/atproto/server/createAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Database from '../../../../db'
import { didDocForSession } from './util'
import { getPdsEndpoint } from '../../../../pds-agents'
import { isThisPds } from '../../../proxy'
import { dbLogger as log } from '../../../../logger'

export default function (server: Server, ctx: AppContext) {
server.com.atproto.server.createAccount({
Expand All @@ -45,33 +46,11 @@ export default function (server: Server, ctx: AppContext) {
const now = new Date().toISOString()
const passwordScrypt = await scrypt.genSaltAndHash(password)

let verificationPhone: string | undefined = undefined
if (ctx.cfg.phoneVerification.required && ctx.twilio) {
if (!input.body.verificationPhone) {
throw new InvalidRequestError(
`Text verification is now required on this server. Please make sure you're using the latest version of the Bluesky app.`,
'InvalidPhoneVerification',
)
} else if (!input.body.verificationCode) {
throw new InvalidRequestError(
`Text verification is now required on this server. Please make sure you're using the latest version of the Bluesky app.`,
'InvalidPhoneVerification',
)
}
verificationPhone = ctx.twilio.normalizePhoneNumber(
input.body.verificationPhone,
)
const verified = await ctx.twilio.verifyCode(
verificationPhone,
input.body.verificationCode.trim(),
)
if (!verified) {
throw new InvalidRequestError(
'Could not verify phone number. Please try again.',
'InvalidPhoneVerification',
)
}
}
const verificationPhone = await ensurePhoneVerification(
ctx,
input.body.verificationPhone,
input.body.verificationCode?.trim(),
)

const result = await ctx.db.transaction(async (dbTxn) => {
const actorTxn = ctx.services.account(dbTxn)
Expand Down Expand Up @@ -99,19 +78,6 @@ export default function (server: Server, ctx: AppContext) {
throw err
}

// Generate a real did with PLC
if (plcOp && !entrywayAssignedPds) {
try {
await ctx.plcClient.sendOperation(did, plcOp)
} catch (err) {
req.log.error(
{ didKey: ctx.plcRotationKey.did(), handle },
'failed to create did:plc',
)
throw err
}
}

// insert invite code use
if (ctx.cfg.invites.required && inviteCode) {
await ensureCodeIsAvailable(dbTxn, inviteCode, true)
Expand All @@ -135,6 +101,10 @@ export default function (server: Server, ctx: AppContext) {
.execute()
}

if (!entrywayAssignedPds) {
await repoTxn.createRepo(did, [], now)
}

const { access, refresh } = await ctx.services
.auth(dbTxn)
.createSession({
Expand All @@ -144,6 +114,15 @@ export default function (server: Server, ctx: AppContext) {
deactivated: !hasAvailability,
})

return {
did,
pdsDid: entrywayAssignedPds?.did ?? null,
accessJwt: access,
refreshJwt: refresh,
}
})

try {
if (entrywayAssignedPds) {
const agent = ctx.pdsAgents.get(entrywayAssignedPds.host)
await agent.com.atproto.server.createAccount({
Expand All @@ -153,17 +132,21 @@ export default function (server: Server, ctx: AppContext) {
recoveryKey: input.body.recoveryKey,
})
} else {
// Setup repo root
await repoTxn.createRepo(did, [], now)
}

return {
did,
pdsDid: entrywayAssignedPds?.did ?? null,
accessJwt: access,
refreshJwt: refresh,
assert(plcOp)
try {
await ctx.plcClient.sendOperation(did, plcOp)
} catch (err) {
req.log.error(
{ didKey: ctx.plcRotationKey.did(), handle },
'failed to create did:plc',
)
throw err
}
}
})
} catch (err) {
await cleanupUncreatedAccount(ctx, did)
throw err
}

const didDoc = await didDocForSession(ctx, result)

Expand Down Expand Up @@ -490,6 +473,42 @@ const ensureUnusedHandleAndEmail = async (
}
}

const ensurePhoneVerification = async (
ctx: AppContext,
phone?: string,
code?: string,
): Promise<string | undefined> => {
if (!ctx.cfg.phoneVerification.required || !ctx.twilio) {
return
}

if (!phone) {
throw new InvalidRequestError(
`Text verification is now required on this server. Please make sure you're using the latest version of the Bluesky app.`,
'InvalidPhoneVerification',
)
}
if (ctx.cfg.phoneVerification.bypassPhoneNumber === phone) {
return undefined
}

if (!code) {
throw new InvalidRequestError(
`Text verification is now required on this server. Please make sure you're using the latest version of the Bluesky app.`,
'InvalidPhoneVerification',
)
}
const normalizedPhone = ctx.twilio.normalizePhoneNumber(phone)
const verified = await ctx.twilio.verifyCode(normalizedPhone, code)
if (!verified) {
throw new InvalidRequestError(
'Could not verify phone number. Please try again.',
'InvalidPhoneVerification',
)
}
return normalizedPhone
}

const randomIndexByWeight = (weights) => {
let sum = 0
const cumulative = weights.map((weight) => {
Expand All @@ -500,3 +519,21 @@ const randomIndexByWeight = (weights) => {
const rand = Math.random() * sum
return cumulative.findIndex((item) => item >= rand)
}

const cleanupUncreatedAccount = async (
ctx: AppContext,
did: string,
tries = 0,
) => {
if (tries > 3) return
try {
await Promise.all([
ctx.services.account(ctx.db).deleteAccount(did),
ctx.services.record(ctx.db).deleteForActor(did),
ctx.services.repo(ctx.db).deleteRepo(did),
])
} catch (err) {
log.error({ err, did, tries }, 'failed to clean up partially created user')
return cleanupUncreatedAccount(ctx, did, tries + 1)
}
}
2 changes: 2 additions & 0 deletions packages/pds/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
twilioAccountSid: env.twilioAccountSid,
twilioServiceSid: env.twilioServiceSid,
accountsPerPhoneNumber: env.accountsPerPhoneNumber ?? 3,
bypassPhoneNumber: env.bypassPhoneNumber,
}
}

Expand Down Expand Up @@ -322,6 +323,7 @@ export type PhoneVerificationConfig =
twilioAccountSid: string
twilioServiceSid: string
accountsPerPhoneNumber: number
bypassPhoneNumber?: string
}
| {
required: false
Expand Down
2 changes: 2 additions & 0 deletions packages/pds/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const readEnv = (): ServerEnvironment => {
// phone verification
phoneVerificationRequired: envBool('PDS_PHONE_VERIFICATION_REQUIRED'),
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'),
Expand Down Expand Up @@ -166,6 +167,7 @@ export type ServerEnvironment = {
// phone verification
phoneVerificationRequired?: boolean
accountsPerPhoneNumber?: number
bypassPhoneNumber?: string
twilioAccountSid?: string
twilioAuthToken?: string
twilioServiceSid?: string
Expand Down

0 comments on commit 8d719e4

Please sign in to comment.