From 43071d16ec8da16ab04f4b365ea72054bdb8f395 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 23 Jan 2024 11:25:50 -0600 Subject: [PATCH 01/21] schemas --- .../com/atproto/server/createAccount.json | 3 ++- .../atproto/temp/checkSignupAvailability.json | 27 +++++++++++++++++++ .../atproto/temp/sendSignupQueueEmails.json | 17 ++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 lexicons/com/atproto/temp/checkSignupAvailability.json create mode 100644 lexicons/com/atproto/temp/sendSignupQueueEmails.json diff --git a/lexicons/com/atproto/server/createAccount.json b/lexicons/com/atproto/server/createAccount.json index d1456e095ae..f998c9846a2 100644 --- a/lexicons/com/atproto/server/createAccount.json +++ b/lexicons/com/atproto/server/createAccount.json @@ -44,7 +44,8 @@ { "name": "HandleNotAvailable" }, { "name": "UnsupportedDomain" }, { "name": "UnresolvableDid" }, - { "name": "IncompatibleDidDoc" } + { "name": "IncompatibleDidDoc" }, + { "name": "SignupCapacity" } ] } } diff --git a/lexicons/com/atproto/temp/checkSignupAvailability.json b/lexicons/com/atproto/temp/checkSignupAvailability.json new file mode 100644 index 00000000000..b7ec92fcee4 --- /dev/null +++ b/lexicons/com/atproto/temp/checkSignupAvailability.json @@ -0,0 +1,27 @@ +{ + "lexicon": 1, + "id": "com.atproto.temp.checkSignupAvailability", + "defs": { + "main": { + "type": "query", + "description": "Check if the service has availability for new accounts and submit email for waitlist queue.", + "parameters": { + "type": "params", + "required": ["email"], + "properties": { + "email": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["hasAvailability"], + "properties": { + "hasAvailability": { "type": "boolean" } + } + } + } + } + } +} diff --git a/lexicons/com/atproto/temp/sendSignupQueueEmails.json b/lexicons/com/atproto/temp/sendSignupQueueEmails.json new file mode 100644 index 00000000000..9e8092fa7f9 --- /dev/null +++ b/lexicons/com/atproto/temp/sendSignupQueueEmails.json @@ -0,0 +1,17 @@ +{ + "lexicon": 1, + "id": "com.atproto.temp.sendSignupQueueEmails", + "defs": { + "main": { + "type": "query", + "description": "Fetch all labels from a labeler created after a certain date.", + "parameters": { + "type": "params", + "required": ["count"], + "properties": { + "count": { "type": "integer" } + } + } + } + } +} From 413e24f774f27ccf797ffacbad13699df2809f72 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 23 Jan 2024 12:00:21 -0600 Subject: [PATCH 02/21] gen code --- packages/api/src/client/index.ts | 128 +++++++++++------- packages/api/src/client/lexicons.ts | 58 ++++++++ .../types/com/atproto/server/createAccount.ts | 7 + .../atproto/temp/checkSignupAvailability.ts | 35 +++++ .../com/atproto/temp/sendSignupQueueEmails.ts | 29 ++++ packages/bsky/src/lexicon/index.ts | 128 +++++++++++------- packages/bsky/src/lexicon/lexicons.ts | 58 ++++++++ .../types/com/atproto/server/createAccount.ts | 1 + .../atproto/temp/checkSignupAvailability.ts | 45 ++++++ .../com/atproto/temp/sendSignupQueueEmails.ts | 33 +++++ packages/pds/src/lexicon/index.ts | 128 +++++++++++------- packages/pds/src/lexicon/lexicons.ts | 58 ++++++++ .../types/com/atproto/server/createAccount.ts | 1 + .../atproto/temp/checkSignupAvailability.ts | 45 ++++++ .../com/atproto/temp/sendSignupQueueEmails.ts | 33 +++++ 15 files changed, 634 insertions(+), 153 deletions(-) create mode 100644 packages/api/src/client/types/com/atproto/temp/checkSignupAvailability.ts create mode 100644 packages/api/src/client/types/com/atproto/temp/sendSignupQueueEmails.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index 2bff31ccc27..a6f5da1b80d 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -81,10 +81,12 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos' import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate' import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl' import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' +import * as ComAtprotoTempCheckSignupAvailability from './types/com/atproto/temp/checkSignupAvailability' import * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' import * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' import * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' import * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' +import * as ComAtprotoTempSendSignupQueueEmails from './types/com/atproto/temp/sendSignupQueueEmails' import * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' import * as AppBskyActorDefs from './types/app/bsky/actor/defs' import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' @@ -226,10 +228,12 @@ export * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos' export * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate' export * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl' export * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' +export * as ComAtprotoTempCheckSignupAvailability from './types/com/atproto/temp/checkSignupAvailability' export * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' export * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' export * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' export * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' +export * as ComAtprotoTempSendSignupQueueEmails from './types/com/atproto/temp/sendSignupQueueEmails' export * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' export * as AppBskyActorDefs from './types/app/bsky/actor/defs' export * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' @@ -348,39 +352,39 @@ export class AtpServiceClient { export class ComNS { _service: AtpServiceClient - atproto: AtprotoNS + atproto: ComAtprotoNS constructor(service: AtpServiceClient) { this._service = service - this.atproto = new AtprotoNS(service) + this.atproto = new ComAtprotoNS(service) } } -export class AtprotoNS { +export class ComAtprotoNS { _service: AtpServiceClient - admin: AdminNS - identity: IdentityNS - label: LabelNS - moderation: ModerationNS - repo: RepoNS - server: ServerNS - sync: SyncNS - temp: TempNS + admin: ComAtprotoAdminNS + identity: ComAtprotoIdentityNS + label: ComAtprotoLabelNS + moderation: ComAtprotoModerationNS + repo: ComAtprotoRepoNS + server: ComAtprotoServerNS + sync: ComAtprotoSyncNS + temp: ComAtprotoTempNS constructor(service: AtpServiceClient) { this._service = service - this.admin = new AdminNS(service) - this.identity = new IdentityNS(service) - this.label = new LabelNS(service) - this.moderation = new ModerationNS(service) - this.repo = new RepoNS(service) - this.server = new ServerNS(service) - this.sync = new SyncNS(service) - this.temp = new TempNS(service) + this.admin = new ComAtprotoAdminNS(service) + this.identity = new ComAtprotoIdentityNS(service) + this.label = new ComAtprotoLabelNS(service) + this.moderation = new ComAtprotoModerationNS(service) + this.repo = new ComAtprotoRepoNS(service) + this.server = new ComAtprotoServerNS(service) + this.sync = new ComAtprotoSyncNS(service) + this.temp = new ComAtprotoTempNS(service) } } -export class AdminNS { +export class ComAtprotoAdminNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -666,7 +670,7 @@ export class AdminNS { } } -export class IdentityNS { +export class ComAtprotoIdentityNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -696,7 +700,7 @@ export class IdentityNS { } } -export class LabelNS { +export class ComAtprotoLabelNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -715,7 +719,7 @@ export class LabelNS { } } -export class ModerationNS { +export class ComAtprotoModerationNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -734,7 +738,7 @@ export class ModerationNS { } } -export class RepoNS { +export class ComAtprotoRepoNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -830,7 +834,7 @@ export class RepoNS { } } -export class ServerNS { +export class ComAtprotoServerNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -1069,7 +1073,7 @@ export class ServerNS { } } -export class SyncNS { +export class ComAtprotoSyncNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -1198,13 +1202,24 @@ export class SyncNS { } } -export class TempNS { +export class ComAtprotoTempNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { this._service = service } + checkSignupAvailability( + params?: ComAtprotoTempCheckSignupAvailability.QueryParams, + opts?: ComAtprotoTempCheckSignupAvailability.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.temp.checkSignupAvailability', params, undefined, opts) + .catch((e) => { + throw ComAtprotoTempCheckSignupAvailability.toKnownErr(e) + }) + } + fetchLabels( params?: ComAtprotoTempFetchLabels.QueryParams, opts?: ComAtprotoTempFetchLabels.CallOptions, @@ -1249,6 +1264,17 @@ export class TempNS { }) } + sendSignupQueueEmails( + params?: ComAtprotoTempSendSignupQueueEmails.QueryParams, + opts?: ComAtprotoTempSendSignupQueueEmails.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.temp.sendSignupQueueEmails', params, undefined, opts) + .catch((e) => { + throw ComAtprotoTempSendSignupQueueEmails.toKnownErr(e) + }) + } + transferAccount( data?: ComAtprotoTempTransferAccount.InputSchema, opts?: ComAtprotoTempTransferAccount.CallOptions, @@ -1263,37 +1289,37 @@ export class TempNS { export class AppNS { _service: AtpServiceClient - bsky: BskyNS + bsky: AppBskyNS constructor(service: AtpServiceClient) { this._service = service - this.bsky = new BskyNS(service) + this.bsky = new AppBskyNS(service) } } -export class BskyNS { +export class AppBskyNS { _service: AtpServiceClient - actor: ActorNS - embed: EmbedNS - feed: FeedNS - graph: GraphNS - notification: NotificationNS - richtext: RichtextNS - unspecced: UnspeccedNS + actor: AppBskyActorNS + embed: AppBskyEmbedNS + feed: AppBskyFeedNS + graph: AppBskyGraphNS + notification: AppBskyNotificationNS + richtext: AppBskyRichtextNS + unspecced: AppBskyUnspeccedNS constructor(service: AtpServiceClient) { this._service = service - this.actor = new ActorNS(service) - this.embed = new EmbedNS(service) - this.feed = new FeedNS(service) - this.graph = new GraphNS(service) - this.notification = new NotificationNS(service) - this.richtext = new RichtextNS(service) - this.unspecced = new UnspeccedNS(service) + this.actor = new AppBskyActorNS(service) + this.embed = new AppBskyEmbedNS(service) + this.feed = new AppBskyFeedNS(service) + this.graph = new AppBskyGraphNS(service) + this.notification = new AppBskyNotificationNS(service) + this.richtext = new AppBskyRichtextNS(service) + this.unspecced = new AppBskyUnspeccedNS(service) } } -export class ActorNS { +export class AppBskyActorNS { _service: AtpServiceClient profile: ProfileRecord @@ -1441,7 +1467,7 @@ export class ProfileRecord { } } -export class EmbedNS { +export class AppBskyEmbedNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -1449,7 +1475,7 @@ export class EmbedNS { } } -export class FeedNS { +export class AppBskyFeedNS { _service: AtpServiceClient generator: GeneratorRecord like: LikeRecord @@ -1952,7 +1978,7 @@ export class ThreadgateRecord { } } -export class GraphNS { +export class AppBskyGraphNS { _service: AtpServiceClient block: BlockRecord follow: FollowRecord @@ -2427,7 +2453,7 @@ export class ListitemRecord { } } -export class NotificationNS { +export class AppBskyNotificationNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -2479,7 +2505,7 @@ export class NotificationNS { } } -export class RichtextNS { +export class AppBskyRichtextNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -2487,7 +2513,7 @@ export class RichtextNS { } } -export class UnspeccedNS { +export class AppBskyUnspeccedNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 95fd1bcf549..ea33e3d8666 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -2921,6 +2921,9 @@ export const schemaDict = { { name: 'IncompatibleDidDoc', }, + { + name: 'SignupCapacity', + }, ], }, }, @@ -4246,6 +4249,38 @@ export const schemaDict = { }, }, }, + ComAtprotoTempCheckSignupAvailability: { + lexicon: 1, + id: 'com.atproto.temp.checkSignupAvailability', + defs: { + main: { + type: 'query', + description: + 'Check if the service has availability for new accounts and submit email for waitlist queue.', + parameters: { + type: 'params', + required: ['email'], + properties: { + email: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['hasAvailability'], + properties: { + hasAvailability: { + type: 'boolean', + }, + }, + }, + }, + }, + }, + }, ComAtprotoTempFetchLabels: { lexicon: 1, id: 'com.atproto.temp.fetchLabels', @@ -4363,6 +4398,26 @@ export const schemaDict = { }, }, }, + ComAtprotoTempSendSignupQueueEmails: { + lexicon: 1, + id: 'com.atproto.temp.sendSignupQueueEmails', + defs: { + main: { + type: 'query', + description: + 'Fetch all labels from a labeler created after a certain date.', + parameters: { + type: 'params', + required: ['count'], + properties: { + count: { + type: 'integer', + }, + }, + }, + }, + }, + }, ComAtprotoTempTransferAccount: { lexicon: 1, id: 'com.atproto.temp.transferAccount', @@ -8220,11 +8275,14 @@ export const ids = { ComAtprotoSyncNotifyOfUpdate: 'com.atproto.sync.notifyOfUpdate', ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl', ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', + ComAtprotoTempCheckSignupAvailability: + 'com.atproto.temp.checkSignupAvailability', ComAtprotoTempFetchLabels: 'com.atproto.temp.fetchLabels', ComAtprotoTempImportRepo: 'com.atproto.temp.importRepo', ComAtprotoTempPushBlob: 'com.atproto.temp.pushBlob', ComAtprotoTempRequestPhoneVerification: 'com.atproto.temp.requestPhoneVerification', + ComAtprotoTempSendSignupQueueEmails: 'com.atproto.temp.sendSignupQueueEmails', ComAtprotoTempTransferAccount: 'com.atproto.temp.transferAccount', AppBskyActorDefs: 'app.bsky.actor.defs', AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', diff --git a/packages/api/src/client/types/com/atproto/server/createAccount.ts b/packages/api/src/client/types/com/atproto/server/createAccount.ts index b62adf97cb1..b702623385a 100644 --- a/packages/api/src/client/types/com/atproto/server/createAccount.ts +++ b/packages/api/src/client/types/com/atproto/server/createAccount.ts @@ -85,6 +85,12 @@ export class IncompatibleDidDocError extends XRPCError { } } +export class SignupCapacityError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers) + } +} + export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'InvalidHandle') return new InvalidHandleError(e) @@ -94,6 +100,7 @@ export function toKnownErr(e: any) { if (e.error === 'UnsupportedDomain') return new UnsupportedDomainError(e) if (e.error === 'UnresolvableDid') return new UnresolvableDidError(e) if (e.error === 'IncompatibleDidDoc') return new IncompatibleDidDocError(e) + if (e.error === 'SignupCapacity') return new SignupCapacityError(e) } return e } diff --git a/packages/api/src/client/types/com/atproto/temp/checkSignupAvailability.ts b/packages/api/src/client/types/com/atproto/temp/checkSignupAvailability.ts new file mode 100644 index 00000000000..39baf9b3a05 --- /dev/null +++ b/packages/api/src/client/types/com/atproto/temp/checkSignupAvailability.ts @@ -0,0 +1,35 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams { + email: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + hasAvailability: boolean + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/temp/sendSignupQueueEmails.ts b/packages/api/src/client/types/com/atproto/temp/sendSignupQueueEmails.ts new file mode 100644 index 00000000000..9e5f7073088 --- /dev/null +++ b/packages/api/src/client/types/com/atproto/temp/sendSignupQueueEmails.ts @@ -0,0 +1,29 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams { + count: number +} + +export type InputSchema = undefined + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/bsky/src/lexicon/index.ts b/packages/bsky/src/lexicon/index.ts index eb9dfd23f2d..1c9caa30336 100644 --- a/packages/bsky/src/lexicon/index.ts +++ b/packages/bsky/src/lexicon/index.ts @@ -78,10 +78,12 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos' import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate' import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl' import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' +import * as ComAtprotoTempCheckSignupAvailability from './types/com/atproto/temp/checkSignupAvailability' import * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' import * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' import * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' import * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' +import * as ComAtprotoTempSendSignupQueueEmails from './types/com/atproto/temp/sendSignupQueueEmails' import * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile' @@ -166,39 +168,39 @@ export class Server { export class ComNS { _server: Server - atproto: AtprotoNS + atproto: ComAtprotoNS constructor(server: Server) { this._server = server - this.atproto = new AtprotoNS(server) + this.atproto = new ComAtprotoNS(server) } } -export class AtprotoNS { +export class ComAtprotoNS { _server: Server - admin: AdminNS - identity: IdentityNS - label: LabelNS - moderation: ModerationNS - repo: RepoNS - server: ServerNS - sync: SyncNS - temp: TempNS + admin: ComAtprotoAdminNS + identity: ComAtprotoIdentityNS + label: ComAtprotoLabelNS + moderation: ComAtprotoModerationNS + repo: ComAtprotoRepoNS + server: ComAtprotoServerNS + sync: ComAtprotoSyncNS + temp: ComAtprotoTempNS constructor(server: Server) { this._server = server - this.admin = new AdminNS(server) - this.identity = new IdentityNS(server) - this.label = new LabelNS(server) - this.moderation = new ModerationNS(server) - this.repo = new RepoNS(server) - this.server = new ServerNS(server) - this.sync = new SyncNS(server) - this.temp = new TempNS(server) + this.admin = new ComAtprotoAdminNS(server) + this.identity = new ComAtprotoIdentityNS(server) + this.label = new ComAtprotoLabelNS(server) + this.moderation = new ComAtprotoModerationNS(server) + this.repo = new ComAtprotoRepoNS(server) + this.server = new ComAtprotoServerNS(server) + this.sync = new ComAtprotoSyncNS(server) + this.temp = new ComAtprotoTempNS(server) } } -export class AdminNS { +export class ComAtprotoAdminNS { _server: Server constructor(server: Server) { @@ -459,7 +461,7 @@ export class AdminNS { } } -export class IdentityNS { +export class ComAtprotoIdentityNS { _server: Server constructor(server: Server) { @@ -489,7 +491,7 @@ export class IdentityNS { } } -export class LabelNS { +export class ComAtprotoLabelNS { _server: Server constructor(server: Server) { @@ -519,7 +521,7 @@ export class LabelNS { } } -export class ModerationNS { +export class ComAtprotoModerationNS { _server: Server constructor(server: Server) { @@ -538,7 +540,7 @@ export class ModerationNS { } } -export class RepoNS { +export class ComAtprotoRepoNS { _server: Server constructor(server: Server) { @@ -634,7 +636,7 @@ export class RepoNS { } } -export class ServerNS { +export class ComAtprotoServerNS { _server: Server constructor(server: Server) { @@ -873,7 +875,7 @@ export class ServerNS { } } -export class SyncNS { +export class ComAtprotoSyncNS { _server: Server constructor(server: Server) { @@ -1013,13 +1015,24 @@ export class SyncNS { } } -export class TempNS { +export class ComAtprotoTempNS { _server: Server constructor(server: Server) { this._server = server } + checkSignupAvailability( + cfg: ConfigOf< + AV, + ComAtprotoTempCheckSignupAvailability.Handler>, + ComAtprotoTempCheckSignupAvailability.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.temp.checkSignupAvailability' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + fetchLabels( cfg: ConfigOf< AV, @@ -1064,6 +1077,17 @@ export class TempNS { return this._server.xrpc.method(nsid, cfg) } + sendSignupQueueEmails( + cfg: ConfigOf< + AV, + ComAtprotoTempSendSignupQueueEmails.Handler>, + ComAtprotoTempSendSignupQueueEmails.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.temp.sendSignupQueueEmails' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + transferAccount( cfg: ConfigOf< AV, @@ -1078,37 +1102,37 @@ export class TempNS { export class AppNS { _server: Server - bsky: BskyNS + bsky: AppBskyNS constructor(server: Server) { this._server = server - this.bsky = new BskyNS(server) + this.bsky = new AppBskyNS(server) } } -export class BskyNS { +export class AppBskyNS { _server: Server - actor: ActorNS - embed: EmbedNS - feed: FeedNS - graph: GraphNS - notification: NotificationNS - richtext: RichtextNS - unspecced: UnspeccedNS + actor: AppBskyActorNS + embed: AppBskyEmbedNS + feed: AppBskyFeedNS + graph: AppBskyGraphNS + notification: AppBskyNotificationNS + richtext: AppBskyRichtextNS + unspecced: AppBskyUnspeccedNS constructor(server: Server) { this._server = server - this.actor = new ActorNS(server) - this.embed = new EmbedNS(server) - this.feed = new FeedNS(server) - this.graph = new GraphNS(server) - this.notification = new NotificationNS(server) - this.richtext = new RichtextNS(server) - this.unspecced = new UnspeccedNS(server) + this.actor = new AppBskyActorNS(server) + this.embed = new AppBskyEmbedNS(server) + this.feed = new AppBskyFeedNS(server) + this.graph = new AppBskyGraphNS(server) + this.notification = new AppBskyNotificationNS(server) + this.richtext = new AppBskyRichtextNS(server) + this.unspecced = new AppBskyUnspeccedNS(server) } } -export class ActorNS { +export class AppBskyActorNS { _server: Server constructor(server: Server) { @@ -1193,7 +1217,7 @@ export class ActorNS { } } -export class EmbedNS { +export class AppBskyEmbedNS { _server: Server constructor(server: Server) { @@ -1201,7 +1225,7 @@ export class EmbedNS { } } -export class FeedNS { +export class AppBskyFeedNS { _server: Server constructor(server: Server) { @@ -1385,7 +1409,7 @@ export class FeedNS { } } -export class GraphNS { +export class AppBskyGraphNS { _server: Server constructor(server: Server) { @@ -1536,7 +1560,7 @@ export class GraphNS { } } -export class NotificationNS { +export class AppBskyNotificationNS { _server: Server constructor(server: Server) { @@ -1588,7 +1612,7 @@ export class NotificationNS { } } -export class RichtextNS { +export class AppBskyRichtextNS { _server: Server constructor(server: Server) { @@ -1596,7 +1620,7 @@ export class RichtextNS { } } -export class UnspeccedNS { +export class AppBskyUnspeccedNS { _server: Server constructor(server: Server) { @@ -1670,11 +1694,13 @@ type RouteRateLimitOpts = { calcKey?: (ctx: T) => string calcPoints?: (ctx: T) => number } +type HandlerOpts = { blobLimit?: number } type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts type ConfigOf = | Handler | { auth?: Auth + opts?: HandlerOpts rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[] handler: Handler } diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 95fd1bcf549..ea33e3d8666 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -2921,6 +2921,9 @@ export const schemaDict = { { name: 'IncompatibleDidDoc', }, + { + name: 'SignupCapacity', + }, ], }, }, @@ -4246,6 +4249,38 @@ export const schemaDict = { }, }, }, + ComAtprotoTempCheckSignupAvailability: { + lexicon: 1, + id: 'com.atproto.temp.checkSignupAvailability', + defs: { + main: { + type: 'query', + description: + 'Check if the service has availability for new accounts and submit email for waitlist queue.', + parameters: { + type: 'params', + required: ['email'], + properties: { + email: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['hasAvailability'], + properties: { + hasAvailability: { + type: 'boolean', + }, + }, + }, + }, + }, + }, + }, ComAtprotoTempFetchLabels: { lexicon: 1, id: 'com.atproto.temp.fetchLabels', @@ -4363,6 +4398,26 @@ export const schemaDict = { }, }, }, + ComAtprotoTempSendSignupQueueEmails: { + lexicon: 1, + id: 'com.atproto.temp.sendSignupQueueEmails', + defs: { + main: { + type: 'query', + description: + 'Fetch all labels from a labeler created after a certain date.', + parameters: { + type: 'params', + required: ['count'], + properties: { + count: { + type: 'integer', + }, + }, + }, + }, + }, + }, ComAtprotoTempTransferAccount: { lexicon: 1, id: 'com.atproto.temp.transferAccount', @@ -8220,11 +8275,14 @@ export const ids = { ComAtprotoSyncNotifyOfUpdate: 'com.atproto.sync.notifyOfUpdate', ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl', ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', + ComAtprotoTempCheckSignupAvailability: + 'com.atproto.temp.checkSignupAvailability', ComAtprotoTempFetchLabels: 'com.atproto.temp.fetchLabels', ComAtprotoTempImportRepo: 'com.atproto.temp.importRepo', ComAtprotoTempPushBlob: 'com.atproto.temp.pushBlob', ComAtprotoTempRequestPhoneVerification: 'com.atproto.temp.requestPhoneVerification', + ComAtprotoTempSendSignupQueueEmails: 'com.atproto.temp.sendSignupQueueEmails', ComAtprotoTempTransferAccount: 'com.atproto.temp.transferAccount', AppBskyActorDefs: 'app.bsky.actor.defs', AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts index bbf2c009bf5..4784a9f0df3 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts @@ -54,6 +54,7 @@ export interface HandlerError { | 'UnsupportedDomain' | 'UnresolvableDid' | 'IncompatibleDidDoc' + | 'SignupCapacity' } export type HandlerOutput = HandlerError | HandlerSuccess diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts new file mode 100644 index 00000000000..cd462bfee7d --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts @@ -0,0 +1,45 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams { + email: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + hasAvailability: boolean + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts new file mode 100644 index 00000000000..1cb21d8baee --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts @@ -0,0 +1,33 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams { + count: number +} + +export type InputSchema = undefined +export type HandlerInput = undefined + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/index.ts b/packages/pds/src/lexicon/index.ts index eb9dfd23f2d..1c9caa30336 100644 --- a/packages/pds/src/lexicon/index.ts +++ b/packages/pds/src/lexicon/index.ts @@ -78,10 +78,12 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos' import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate' import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl' import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' +import * as ComAtprotoTempCheckSignupAvailability from './types/com/atproto/temp/checkSignupAvailability' import * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' import * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' import * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' import * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' +import * as ComAtprotoTempSendSignupQueueEmails from './types/com/atproto/temp/sendSignupQueueEmails' import * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile' @@ -166,39 +168,39 @@ export class Server { export class ComNS { _server: Server - atproto: AtprotoNS + atproto: ComAtprotoNS constructor(server: Server) { this._server = server - this.atproto = new AtprotoNS(server) + this.atproto = new ComAtprotoNS(server) } } -export class AtprotoNS { +export class ComAtprotoNS { _server: Server - admin: AdminNS - identity: IdentityNS - label: LabelNS - moderation: ModerationNS - repo: RepoNS - server: ServerNS - sync: SyncNS - temp: TempNS + admin: ComAtprotoAdminNS + identity: ComAtprotoIdentityNS + label: ComAtprotoLabelNS + moderation: ComAtprotoModerationNS + repo: ComAtprotoRepoNS + server: ComAtprotoServerNS + sync: ComAtprotoSyncNS + temp: ComAtprotoTempNS constructor(server: Server) { this._server = server - this.admin = new AdminNS(server) - this.identity = new IdentityNS(server) - this.label = new LabelNS(server) - this.moderation = new ModerationNS(server) - this.repo = new RepoNS(server) - this.server = new ServerNS(server) - this.sync = new SyncNS(server) - this.temp = new TempNS(server) + this.admin = new ComAtprotoAdminNS(server) + this.identity = new ComAtprotoIdentityNS(server) + this.label = new ComAtprotoLabelNS(server) + this.moderation = new ComAtprotoModerationNS(server) + this.repo = new ComAtprotoRepoNS(server) + this.server = new ComAtprotoServerNS(server) + this.sync = new ComAtprotoSyncNS(server) + this.temp = new ComAtprotoTempNS(server) } } -export class AdminNS { +export class ComAtprotoAdminNS { _server: Server constructor(server: Server) { @@ -459,7 +461,7 @@ export class AdminNS { } } -export class IdentityNS { +export class ComAtprotoIdentityNS { _server: Server constructor(server: Server) { @@ -489,7 +491,7 @@ export class IdentityNS { } } -export class LabelNS { +export class ComAtprotoLabelNS { _server: Server constructor(server: Server) { @@ -519,7 +521,7 @@ export class LabelNS { } } -export class ModerationNS { +export class ComAtprotoModerationNS { _server: Server constructor(server: Server) { @@ -538,7 +540,7 @@ export class ModerationNS { } } -export class RepoNS { +export class ComAtprotoRepoNS { _server: Server constructor(server: Server) { @@ -634,7 +636,7 @@ export class RepoNS { } } -export class ServerNS { +export class ComAtprotoServerNS { _server: Server constructor(server: Server) { @@ -873,7 +875,7 @@ export class ServerNS { } } -export class SyncNS { +export class ComAtprotoSyncNS { _server: Server constructor(server: Server) { @@ -1013,13 +1015,24 @@ export class SyncNS { } } -export class TempNS { +export class ComAtprotoTempNS { _server: Server constructor(server: Server) { this._server = server } + checkSignupAvailability( + cfg: ConfigOf< + AV, + ComAtprotoTempCheckSignupAvailability.Handler>, + ComAtprotoTempCheckSignupAvailability.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.temp.checkSignupAvailability' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + fetchLabels( cfg: ConfigOf< AV, @@ -1064,6 +1077,17 @@ export class TempNS { return this._server.xrpc.method(nsid, cfg) } + sendSignupQueueEmails( + cfg: ConfigOf< + AV, + ComAtprotoTempSendSignupQueueEmails.Handler>, + ComAtprotoTempSendSignupQueueEmails.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.temp.sendSignupQueueEmails' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + transferAccount( cfg: ConfigOf< AV, @@ -1078,37 +1102,37 @@ export class TempNS { export class AppNS { _server: Server - bsky: BskyNS + bsky: AppBskyNS constructor(server: Server) { this._server = server - this.bsky = new BskyNS(server) + this.bsky = new AppBskyNS(server) } } -export class BskyNS { +export class AppBskyNS { _server: Server - actor: ActorNS - embed: EmbedNS - feed: FeedNS - graph: GraphNS - notification: NotificationNS - richtext: RichtextNS - unspecced: UnspeccedNS + actor: AppBskyActorNS + embed: AppBskyEmbedNS + feed: AppBskyFeedNS + graph: AppBskyGraphNS + notification: AppBskyNotificationNS + richtext: AppBskyRichtextNS + unspecced: AppBskyUnspeccedNS constructor(server: Server) { this._server = server - this.actor = new ActorNS(server) - this.embed = new EmbedNS(server) - this.feed = new FeedNS(server) - this.graph = new GraphNS(server) - this.notification = new NotificationNS(server) - this.richtext = new RichtextNS(server) - this.unspecced = new UnspeccedNS(server) + this.actor = new AppBskyActorNS(server) + this.embed = new AppBskyEmbedNS(server) + this.feed = new AppBskyFeedNS(server) + this.graph = new AppBskyGraphNS(server) + this.notification = new AppBskyNotificationNS(server) + this.richtext = new AppBskyRichtextNS(server) + this.unspecced = new AppBskyUnspeccedNS(server) } } -export class ActorNS { +export class AppBskyActorNS { _server: Server constructor(server: Server) { @@ -1193,7 +1217,7 @@ export class ActorNS { } } -export class EmbedNS { +export class AppBskyEmbedNS { _server: Server constructor(server: Server) { @@ -1201,7 +1225,7 @@ export class EmbedNS { } } -export class FeedNS { +export class AppBskyFeedNS { _server: Server constructor(server: Server) { @@ -1385,7 +1409,7 @@ export class FeedNS { } } -export class GraphNS { +export class AppBskyGraphNS { _server: Server constructor(server: Server) { @@ -1536,7 +1560,7 @@ export class GraphNS { } } -export class NotificationNS { +export class AppBskyNotificationNS { _server: Server constructor(server: Server) { @@ -1588,7 +1612,7 @@ export class NotificationNS { } } -export class RichtextNS { +export class AppBskyRichtextNS { _server: Server constructor(server: Server) { @@ -1596,7 +1620,7 @@ export class RichtextNS { } } -export class UnspeccedNS { +export class AppBskyUnspeccedNS { _server: Server constructor(server: Server) { @@ -1670,11 +1694,13 @@ type RouteRateLimitOpts = { calcKey?: (ctx: T) => string calcPoints?: (ctx: T) => number } +type HandlerOpts = { blobLimit?: number } type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts type ConfigOf = | Handler | { auth?: Auth + opts?: HandlerOpts rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[] handler: Handler } diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 95fd1bcf549..ea33e3d8666 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -2921,6 +2921,9 @@ export const schemaDict = { { name: 'IncompatibleDidDoc', }, + { + name: 'SignupCapacity', + }, ], }, }, @@ -4246,6 +4249,38 @@ export const schemaDict = { }, }, }, + ComAtprotoTempCheckSignupAvailability: { + lexicon: 1, + id: 'com.atproto.temp.checkSignupAvailability', + defs: { + main: { + type: 'query', + description: + 'Check if the service has availability for new accounts and submit email for waitlist queue.', + parameters: { + type: 'params', + required: ['email'], + properties: { + email: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['hasAvailability'], + properties: { + hasAvailability: { + type: 'boolean', + }, + }, + }, + }, + }, + }, + }, ComAtprotoTempFetchLabels: { lexicon: 1, id: 'com.atproto.temp.fetchLabels', @@ -4363,6 +4398,26 @@ export const schemaDict = { }, }, }, + ComAtprotoTempSendSignupQueueEmails: { + lexicon: 1, + id: 'com.atproto.temp.sendSignupQueueEmails', + defs: { + main: { + type: 'query', + description: + 'Fetch all labels from a labeler created after a certain date.', + parameters: { + type: 'params', + required: ['count'], + properties: { + count: { + type: 'integer', + }, + }, + }, + }, + }, + }, ComAtprotoTempTransferAccount: { lexicon: 1, id: 'com.atproto.temp.transferAccount', @@ -8220,11 +8275,14 @@ export const ids = { ComAtprotoSyncNotifyOfUpdate: 'com.atproto.sync.notifyOfUpdate', ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl', ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', + ComAtprotoTempCheckSignupAvailability: + 'com.atproto.temp.checkSignupAvailability', ComAtprotoTempFetchLabels: 'com.atproto.temp.fetchLabels', ComAtprotoTempImportRepo: 'com.atproto.temp.importRepo', ComAtprotoTempPushBlob: 'com.atproto.temp.pushBlob', ComAtprotoTempRequestPhoneVerification: 'com.atproto.temp.requestPhoneVerification', + ComAtprotoTempSendSignupQueueEmails: 'com.atproto.temp.sendSignupQueueEmails', ComAtprotoTempTransferAccount: 'com.atproto.temp.transferAccount', AppBskyActorDefs: 'app.bsky.actor.defs', AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts index bbf2c009bf5..4784a9f0df3 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts @@ -54,6 +54,7 @@ export interface HandlerError { | 'UnsupportedDomain' | 'UnresolvableDid' | 'IncompatibleDidDoc' + | 'SignupCapacity' } export type HandlerOutput = HandlerError | HandlerSuccess diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts b/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts new file mode 100644 index 00000000000..cd462bfee7d --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts @@ -0,0 +1,45 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams { + email: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + hasAvailability: boolean + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts b/packages/pds/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts new file mode 100644 index 00000000000..1cb21d8baee --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts @@ -0,0 +1,33 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams { + count: number +} + +export type InputSchema = undefined +export type HandlerInput = undefined + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput From e8495088035a7caed389751d5a701c732a80dba2 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 23 Jan 2024 13:05:30 -0600 Subject: [PATCH 03/21] sketching out signup limiter --- .../api/com/atproto/server/createAccount.ts | 8 ++++ .../atproto/temp/checkSignupAvailability.ts | 44 +++++++++++++++++++ .../pds/src/api/com/atproto/temp/index.ts | 4 ++ .../com/atproto/temp/sendSignupQueueEmails.ts | 12 +++++ packages/pds/src/context.ts | 7 +++ packages/pds/src/db/database-schema.ts | 4 +- packages/pds/src/db/tables/queued-email.ts | 11 +++++ packages/pds/src/signup-limiter.ts | 9 ++++ 8 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 packages/pds/src/api/com/atproto/temp/checkSignupAvailability.ts create mode 100644 packages/pds/src/api/com/atproto/temp/sendSignupQueueEmails.ts create mode 100644 packages/pds/src/db/tables/queued-email.ts create mode 100644 packages/pds/src/signup-limiter.ts diff --git a/packages/pds/src/api/com/atproto/server/createAccount.ts b/packages/pds/src/api/com/atproto/server/createAccount.ts index 5786ff92868..fa86d4ede0e 100644 --- a/packages/pds/src/api/com/atproto/server/createAccount.ts +++ b/packages/pds/src/api/com/atproto/server/createAccount.ts @@ -25,6 +25,14 @@ export default function (server: Server, ctx: AppContext) { points: 100, }, handler: async ({ input, req }) => { + const hasAvailability = ctx.signupLimiter.hasAvailability() + if (!hasAvailability) { + throw new InvalidRequestError( + 'Service at signup capacity, please check back later.', + 'SignupCapacity', + ) + } + const { did, handle, diff --git a/packages/pds/src/api/com/atproto/temp/checkSignupAvailability.ts b/packages/pds/src/api/com/atproto/temp/checkSignupAvailability.ts new file mode 100644 index 00000000000..56d5d04928e --- /dev/null +++ b/packages/pds/src/api/com/atproto/temp/checkSignupAvailability.ts @@ -0,0 +1,44 @@ +import disposable from 'disposable-email' +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { InvalidRequestError } from '@atproto/xrpc-server' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.temp.checkSignupAvailability({ + handler: async ({ params }) => { + const email = params.email.toLowerCase() + if (!disposable.validate(email)) { + throw new InvalidRequestError( + 'This email address is not supported, please use a different email.', + ) + } + + const alreadyExists = await ctx.db.db + .selectFrom('user_account') + .selectAll() + .where('email', '=', email) + .executeTakeFirst() + if (alreadyExists) { + throw new InvalidRequestError(`Email already taken: ${email}`) + } + + await ctx.db.db + .insertInto('queued_email') + .values({ + email, + registeredAt: new Date().toISOString(), + }) + .onConflict((oc) => oc.doNothing()) + .execute() + + const hasAvailability = ctx.signupLimiter.hasAvailability() + + return { + encoding: 'application/json', + body: { + hasAvailability, + }, + } + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/temp/index.ts b/packages/pds/src/api/com/atproto/temp/index.ts index db34f17bf29..90f2122101a 100644 --- a/packages/pds/src/api/com/atproto/temp/index.ts +++ b/packages/pds/src/api/com/atproto/temp/index.ts @@ -1,7 +1,11 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' import requestPhoneVerification from './requestPhoneVerification' +import checkSignupAvailability from './checkSignupAvailability' +import sendSignupQueueEmails from './sendSignupQueueEmails' export default function (server: Server, ctx: AppContext) { requestPhoneVerification(server, ctx) + checkSignupAvailability(server, ctx) + sendSignupQueueEmails(server, ctx) } diff --git a/packages/pds/src/api/com/atproto/temp/sendSignupQueueEmails.ts b/packages/pds/src/api/com/atproto/temp/sendSignupQueueEmails.ts new file mode 100644 index 00000000000..f0f06aafd6b --- /dev/null +++ b/packages/pds/src/api/com/atproto/temp/sendSignupQueueEmails.ts @@ -0,0 +1,12 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { countAll } from '../../../../db/util' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.temp.sendSignupQueueEmails({ + handler: async () => { + throw new Error('unimplemented') + }, + }) +} diff --git a/packages/pds/src/context.ts b/packages/pds/src/context.ts index 15ee684e28b..8311416b30e 100644 --- a/packages/pds/src/context.ts +++ b/packages/pds/src/context.ts @@ -23,6 +23,7 @@ import { RuntimeFlags } from './runtime-flags' import { PdsAgents } from './pds-agents' import { TwilioClient } from './twilio' import assert from 'assert' +import { SignupLimiter } from './signup-limiter' export type AppContextOptions = { db: Database @@ -46,6 +47,7 @@ export type AppContextOptions = { repoSigningKey: crypto.Keypair plcRotationKey: crypto.Keypair twilio?: TwilioClient + signupLimiter: SignupLimiter cfg: ServerConfig } @@ -71,6 +73,7 @@ export class AppContext { public repoSigningKey: crypto.Keypair public plcRotationKey: crypto.Keypair public twilio?: TwilioClient + public signupLimiter: SignupLimiter public cfg: ServerConfig constructor(opts: AppContextOptions) { @@ -95,6 +98,7 @@ export class AppContext { this.repoSigningKey = opts.repoSigningKey this.plcRotationKey = opts.plcRotationKey this.twilio = opts.twilio + this.signupLimiter = opts.signupLimiter this.cfg = opts.cfg } @@ -222,6 +226,8 @@ export class AppContext { }) } + const signupLimiter = new SignupLimiter() + const pdsAgents = new PdsAgents() return new AppContext({ @@ -246,6 +252,7 @@ export class AppContext { plcRotationKey, pdsAgents, twilio, + signupLimiter, cfg, ...(overrides ?? {}), }) diff --git a/packages/pds/src/db/database-schema.ts b/packages/pds/src/db/database-schema.ts index 57599aa12d9..727a774953b 100644 --- a/packages/pds/src/db/database-schema.ts +++ b/packages/pds/src/db/database-schema.ts @@ -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 queuedEmail from './tables/queued-email' export type DatabaseSchemaType = appMigration.PartialDB & runtimeFlag.PartialDB & @@ -39,7 +40,8 @@ export type DatabaseSchemaType = appMigration.PartialDB & emailToken.PartialDB & moderation.PartialDB & repoSeq.PartialDB & - phoneVerification.PartialDB + phoneVerification.PartialDB & + queuedEmail.PartialDB export type DatabaseSchema = Kysely diff --git a/packages/pds/src/db/tables/queued-email.ts b/packages/pds/src/db/tables/queued-email.ts new file mode 100644 index 00000000000..aff839df106 --- /dev/null +++ b/packages/pds/src/db/tables/queued-email.ts @@ -0,0 +1,11 @@ +export interface QueuedEmail { + email: string + registeredAt: string + lastEmailed: string | null +} + +export const tableName = 'queued_email' + +export type PartialDB = { + [tableName]: QueuedEmail +} diff --git a/packages/pds/src/signup-limiter.ts b/packages/pds/src/signup-limiter.ts new file mode 100644 index 00000000000..76ab318be61 --- /dev/null +++ b/packages/pds/src/signup-limiter.ts @@ -0,0 +1,9 @@ +import Database from './db' + +export class SignupLimiter { + constructor(public db: Database) {} + + hasAvailability(): boolean { + return true + } +} From d20afa1b0b50b7584c4c1db47f71188f6ff10c19 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 23 Jan 2024 13:30:41 -0600 Subject: [PATCH 04/21] flesh out polling singup limiter --- packages/pds/src/context.ts | 2 +- packages/pds/src/index.ts | 2 + packages/pds/src/logger.ts | 1 + packages/pds/src/signup-limiter.ts | 98 +++++++++++++++++++++++++++++- 4 files changed, 100 insertions(+), 3 deletions(-) diff --git a/packages/pds/src/context.ts b/packages/pds/src/context.ts index 8311416b30e..b5464a76866 100644 --- a/packages/pds/src/context.ts +++ b/packages/pds/src/context.ts @@ -226,7 +226,7 @@ export class AppContext { }) } - const signupLimiter = new SignupLimiter() + const signupLimiter = new SignupLimiter(db) const pdsAgents = new PdsAgents() diff --git a/packages/pds/src/index.ts b/packages/pds/src/index.ts index 42544eba492..0d7e4fbbc4b 100644 --- a/packages/pds/src/index.ts +++ b/packages/pds/src/index.ts @@ -163,6 +163,7 @@ export class PDS { await this.ctx.sequencer.start() await this.ctx.db.startListeningToChannels() await this.ctx.runtimeFlags.start() + await this.ctx.signupLimiter.start() const server = this.app.listen(this.ctx.cfg.service.port) this.server = server this.server.keepAliveTimeout = 90000 @@ -173,6 +174,7 @@ export class PDS { async destroy(): Promise { await this.ctx.runtimeFlags.destroy() + await this.ctx.signupLimiter.destroy() await this.ctx.sequencerLeader?.destroy() await this.terminator?.terminate() await this.ctx.backgroundQueue.destroy() diff --git a/packages/pds/src/logger.ts b/packages/pds/src/logger.ts index e8b663b567f..15cf4741922 100644 --- a/packages/pds/src/logger.ts +++ b/packages/pds/src/logger.ts @@ -8,6 +8,7 @@ export const dbLogger = subsystemLogger('pds:db') export const readStickyLogger = subsystemLogger('pds:read-sticky') export const redisLogger = subsystemLogger('pds:redis') export const seqLogger = subsystemLogger('pds:sequencer') +export const limiterLogger = subsystemLogger('pds:limiter') export const mailerLogger = subsystemLogger('pds:mailer') export const labelerLogger = subsystemLogger('pds:labler') export const crawlerLogger = subsystemLogger('pds:crawler') diff --git a/packages/pds/src/signup-limiter.ts b/packages/pds/src/signup-limiter.ts index 76ab318be61..c53c350afca 100644 --- a/packages/pds/src/signup-limiter.ts +++ b/packages/pds/src/signup-limiter.ts @@ -1,9 +1,103 @@ +import { SECOND } from '@atproto/common' +import { limiterLogger as log } from './logger' import Database from './db' +import { countAll } from './db/util' + +type LimiterFlags = { + disableSignups: boolean + periodAllowance: number + periodMs: number +} + +type LimiterStatus = LimiterFlags & { + accountsInPeriod: number +} export class SignupLimiter { - constructor(public db: Database) {} + destroyed = false + promise: Promise = Promise.resolve() + timer: NodeJS.Timer | undefined + status: LimiterStatus + + constructor(private db: Database) {} hasAvailability(): boolean { - return true + if (this.status.disableSignups) return false + return this.status.accountsInPeriod < this.status.periodAllowance + } + + async start() { + this.poll() + await this.promise + } + + poll() { + if (this.destroyed) return + this.promise = this.refresh() + .catch((err) => log.error({ err }, 'limiter refresh failed')) + .finally(() => { + this.timer = setTimeout(() => this.poll(), 30 * SECOND) + }) + } + + async destroy() { + this.destroyed = true + if (this.timer) { + clearTimeout(this.timer) + } + await this.promise + } + + async refresh() { + const flags = await this.getRuntimeFlags() + const accountsInPeriod = + flags.periodMs === 0 ? 0 : await this.accountsInPeriod(flags.periodMs) + + this.status = { + ...flags, + accountsInPeriod, + } + + log.info({ ...this.status }, 'limiter refresh') + } + + async getRuntimeFlags(): Promise { + const flagsRes = await this.db.db + .selectFrom('runtime_flag') + .selectAll() + .where('name', '=', DISABLE_SIGNUPS_FLAG) + .orWhere('name', '=', PERIOD_ALLOWANCE_FLAG) + .orWhere('name', '=', PERIOD_MS_FLAG) + .execute() + const disableSignups = + flagsRes.find((val) => val.name === DISABLE_SIGNUPS_FLAG)?.value ?? + 'false' + const periodAllowanceFlag = + flagsRes.find((val) => val.name === PERIOD_ALLOWANCE_FLAG)?.value ?? + '10000000' + const periodAllowance = parseInt(periodAllowanceFlag) + const periodMsFlag = + flagsRes.find((val) => val.name === PERIOD_MS_FLAG)?.value ?? '0' + const periodMs = parseInt(periodMsFlag) + + return { + disableSignups: disableSignups === 'true', + periodAllowance: isNaN(periodAllowance) ? 10000000 : periodAllowance, + periodMs: isNaN(periodMs) ? 10000000 : periodMs, + } + } + + async accountsInPeriod(period: number): Promise { + const hourAgo = new Date(Date.now() - period).toISOString() + const res = await this.db.db + .selectFrom('user_account') + .select(countAll.as('count')) + .where('createdAt', '>', hourAgo) + .executeTakeFirstOrThrow() + return res.count } } + +const DISABLE_SIGNUPS_FLAG = 'signup-limiter:disableSignups' +const PERIOD_ALLOWANCE_FLAG = 'signup-limiter:periodAllowance' +const PERIOD_MS_FLAG = 'signup-limiter:periodMs' From bec3d564d16df286f9a82c070f75c833da617255 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 23 Jan 2024 17:46:34 -0600 Subject: [PATCH 05/21] new schemas & fleshing out signup queue --- .../com/atproto/server/createAccount.json | 3 +- .../atproto/temp/checkSignupAvailability.json | 27 ----- .../com/atproto/temp/checkSignupQueue.json | 22 ++++ .../atproto/temp/sendSignupQueueEmails.json | 17 --- packages/api/src/client/index.ts | 29 ++--- packages/api/src/client/lexicons.ts | 53 ++------- .../types/com/atproto/server/createAccount.ts | 7 -- ...nupAvailability.ts => checkSignupQueue.ts} | 8 +- .../com/atproto/temp/sendSignupQueueEmails.ts | 29 ----- packages/bsky/src/lexicon/index.ts | 22 +--- packages/bsky/src/lexicon/lexicons.ts | 53 ++------- .../types/com/atproto/server/createAccount.ts | 1 - ...nupAvailability.ts => checkSignupQueue.ts} | 8 +- .../com/atproto/temp/sendSignupQueueEmails.ts | 33 ------ packages/pds/src/account-activator.ts | 111 ++++++++++++++++++ .../api/com/atproto/server/createAccount.ts | 8 +- .../api/com/atproto/server/createSession.ts | 1 + .../api/com/atproto/server/refreshSession.ts | 1 + .../atproto/temp/checkSignupAvailability.ts | 44 ------- .../api/com/atproto/temp/checkSignupQueue.ts | 49 ++++++++ .../pds/src/api/com/atproto/temp/index.ts | 6 +- .../com/atproto/temp/sendSignupQueueEmails.ts | 12 -- packages/pds/src/auth-verifier.ts | 9 ++ packages/pds/src/db/database-schema.ts | 4 +- packages/pds/src/db/tables/queued-account.ts | 10 ++ packages/pds/src/db/tables/queued-email.ts | 11 -- packages/pds/src/db/tables/user-account.ts | 1 + packages/pds/src/lexicon/index.ts | 22 +--- packages/pds/src/lexicon/lexicons.ts | 53 ++------- .../types/com/atproto/server/createAccount.ts | 1 - ...nupAvailability.ts => checkSignupQueue.ts} | 8 +- .../com/atproto/temp/sendSignupQueueEmails.ts | 33 ------ packages/pds/src/services/account/index.ts | 7 +- packages/pds/src/services/auth.ts | 23 +++- packages/pds/src/signup-limiter.ts | 5 +- 35 files changed, 303 insertions(+), 428 deletions(-) delete mode 100644 lexicons/com/atproto/temp/checkSignupAvailability.json create mode 100644 lexicons/com/atproto/temp/checkSignupQueue.json delete mode 100644 lexicons/com/atproto/temp/sendSignupQueueEmails.json rename packages/api/src/client/types/com/atproto/temp/{checkSignupAvailability.ts => checkSignupQueue.ts} (85%) delete mode 100644 packages/api/src/client/types/com/atproto/temp/sendSignupQueueEmails.ts rename packages/bsky/src/lexicon/types/com/atproto/temp/{checkSignupAvailability.ts => checkSignupQueue.ts} (90%) delete mode 100644 packages/bsky/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts create mode 100644 packages/pds/src/account-activator.ts delete mode 100644 packages/pds/src/api/com/atproto/temp/checkSignupAvailability.ts create mode 100644 packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts delete mode 100644 packages/pds/src/api/com/atproto/temp/sendSignupQueueEmails.ts create mode 100644 packages/pds/src/db/tables/queued-account.ts delete mode 100644 packages/pds/src/db/tables/queued-email.ts rename packages/pds/src/lexicon/types/com/atproto/temp/{checkSignupAvailability.ts => checkSignupQueue.ts} (90%) delete mode 100644 packages/pds/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts diff --git a/lexicons/com/atproto/server/createAccount.json b/lexicons/com/atproto/server/createAccount.json index f998c9846a2..d1456e095ae 100644 --- a/lexicons/com/atproto/server/createAccount.json +++ b/lexicons/com/atproto/server/createAccount.json @@ -44,8 +44,7 @@ { "name": "HandleNotAvailable" }, { "name": "UnsupportedDomain" }, { "name": "UnresolvableDid" }, - { "name": "IncompatibleDidDoc" }, - { "name": "SignupCapacity" } + { "name": "IncompatibleDidDoc" } ] } } diff --git a/lexicons/com/atproto/temp/checkSignupAvailability.json b/lexicons/com/atproto/temp/checkSignupAvailability.json deleted file mode 100644 index b7ec92fcee4..00000000000 --- a/lexicons/com/atproto/temp/checkSignupAvailability.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "lexicon": 1, - "id": "com.atproto.temp.checkSignupAvailability", - "defs": { - "main": { - "type": "query", - "description": "Check if the service has availability for new accounts and submit email for waitlist queue.", - "parameters": { - "type": "params", - "required": ["email"], - "properties": { - "email": { "type": "string" } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["hasAvailability"], - "properties": { - "hasAvailability": { "type": "boolean" } - } - } - } - } - } -} diff --git a/lexicons/com/atproto/temp/checkSignupQueue.json b/lexicons/com/atproto/temp/checkSignupQueue.json new file mode 100644 index 00000000000..b7d9e65d7cf --- /dev/null +++ b/lexicons/com/atproto/temp/checkSignupQueue.json @@ -0,0 +1,22 @@ +{ + "lexicon": 1, + "id": "com.atproto.temp.checkSignupQueue", + "defs": { + "main": { + "type": "query", + "description": "Check accounts location in signup queue.", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["activated"], + "properties": { + "activated": { "type": "boolean" }, + "placeInQueue": { "type": "integer" }, + "estimatedTimeMs": { "type": "integer" } + } + } + } + } + } +} diff --git a/lexicons/com/atproto/temp/sendSignupQueueEmails.json b/lexicons/com/atproto/temp/sendSignupQueueEmails.json deleted file mode 100644 index 9e8092fa7f9..00000000000 --- a/lexicons/com/atproto/temp/sendSignupQueueEmails.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "lexicon": 1, - "id": "com.atproto.temp.sendSignupQueueEmails", - "defs": { - "main": { - "type": "query", - "description": "Fetch all labels from a labeler created after a certain date.", - "parameters": { - "type": "params", - "required": ["count"], - "properties": { - "count": { "type": "integer" } - } - } - } - } -} diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index a6f5da1b80d..50e0fd6c0c9 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -81,12 +81,11 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos' import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate' import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl' import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' -import * as ComAtprotoTempCheckSignupAvailability from './types/com/atproto/temp/checkSignupAvailability' +import * as ComAtprotoTempCheckSignupQueue from './types/com/atproto/temp/checkSignupQueue' import * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' import * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' import * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' import * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' -import * as ComAtprotoTempSendSignupQueueEmails from './types/com/atproto/temp/sendSignupQueueEmails' import * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' import * as AppBskyActorDefs from './types/app/bsky/actor/defs' import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' @@ -228,12 +227,11 @@ export * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos' export * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate' export * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl' export * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' -export * as ComAtprotoTempCheckSignupAvailability from './types/com/atproto/temp/checkSignupAvailability' +export * as ComAtprotoTempCheckSignupQueue from './types/com/atproto/temp/checkSignupQueue' export * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' export * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' export * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' export * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' -export * as ComAtprotoTempSendSignupQueueEmails from './types/com/atproto/temp/sendSignupQueueEmails' export * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' export * as AppBskyActorDefs from './types/app/bsky/actor/defs' export * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' @@ -1209,14 +1207,14 @@ export class ComAtprotoTempNS { this._service = service } - checkSignupAvailability( - params?: ComAtprotoTempCheckSignupAvailability.QueryParams, - opts?: ComAtprotoTempCheckSignupAvailability.CallOptions, - ): Promise { + checkSignupQueue( + params?: ComAtprotoTempCheckSignupQueue.QueryParams, + opts?: ComAtprotoTempCheckSignupQueue.CallOptions, + ): Promise { return this._service.xrpc - .call('com.atproto.temp.checkSignupAvailability', params, undefined, opts) + .call('com.atproto.temp.checkSignupQueue', params, undefined, opts) .catch((e) => { - throw ComAtprotoTempCheckSignupAvailability.toKnownErr(e) + throw ComAtprotoTempCheckSignupQueue.toKnownErr(e) }) } @@ -1264,17 +1262,6 @@ export class ComAtprotoTempNS { }) } - sendSignupQueueEmails( - params?: ComAtprotoTempSendSignupQueueEmails.QueryParams, - opts?: ComAtprotoTempSendSignupQueueEmails.CallOptions, - ): Promise { - return this._service.xrpc - .call('com.atproto.temp.sendSignupQueueEmails', params, undefined, opts) - .catch((e) => { - throw ComAtprotoTempSendSignupQueueEmails.toKnownErr(e) - }) - } - transferAccount( data?: ComAtprotoTempTransferAccount.InputSchema, opts?: ComAtprotoTempTransferAccount.CallOptions, diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index ea33e3d8666..d15e52eb1ad 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -2921,9 +2921,6 @@ export const schemaDict = { { name: 'IncompatibleDidDoc', }, - { - name: 'SignupCapacity', - }, ], }, }, @@ -4249,32 +4246,28 @@ export const schemaDict = { }, }, }, - ComAtprotoTempCheckSignupAvailability: { + ComAtprotoTempCheckSignupQueue: { lexicon: 1, - id: 'com.atproto.temp.checkSignupAvailability', + id: 'com.atproto.temp.checkSignupQueue', defs: { main: { type: 'query', - description: - 'Check if the service has availability for new accounts and submit email for waitlist queue.', - parameters: { - type: 'params', - required: ['email'], - properties: { - email: { - type: 'string', - }, - }, - }, + description: 'Check accounts location in signup queue.', output: { encoding: 'application/json', schema: { type: 'object', - required: ['hasAvailability'], + required: ['activated'], properties: { - hasAvailability: { + activated: { type: 'boolean', }, + placeInQueue: { + type: 'integer', + }, + estimatedTimeMs: { + type: 'integer', + }, }, }, }, @@ -4398,26 +4391,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempSendSignupQueueEmails: { - lexicon: 1, - id: 'com.atproto.temp.sendSignupQueueEmails', - defs: { - main: { - type: 'query', - description: - 'Fetch all labels from a labeler created after a certain date.', - parameters: { - type: 'params', - required: ['count'], - properties: { - count: { - type: 'integer', - }, - }, - }, - }, - }, - }, ComAtprotoTempTransferAccount: { lexicon: 1, id: 'com.atproto.temp.transferAccount', @@ -8275,14 +8248,12 @@ export const ids = { ComAtprotoSyncNotifyOfUpdate: 'com.atproto.sync.notifyOfUpdate', ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl', ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', - ComAtprotoTempCheckSignupAvailability: - 'com.atproto.temp.checkSignupAvailability', + ComAtprotoTempCheckSignupQueue: 'com.atproto.temp.checkSignupQueue', ComAtprotoTempFetchLabels: 'com.atproto.temp.fetchLabels', ComAtprotoTempImportRepo: 'com.atproto.temp.importRepo', ComAtprotoTempPushBlob: 'com.atproto.temp.pushBlob', ComAtprotoTempRequestPhoneVerification: 'com.atproto.temp.requestPhoneVerification', - ComAtprotoTempSendSignupQueueEmails: 'com.atproto.temp.sendSignupQueueEmails', ComAtprotoTempTransferAccount: 'com.atproto.temp.transferAccount', AppBskyActorDefs: 'app.bsky.actor.defs', AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', diff --git a/packages/api/src/client/types/com/atproto/server/createAccount.ts b/packages/api/src/client/types/com/atproto/server/createAccount.ts index b702623385a..b62adf97cb1 100644 --- a/packages/api/src/client/types/com/atproto/server/createAccount.ts +++ b/packages/api/src/client/types/com/atproto/server/createAccount.ts @@ -85,12 +85,6 @@ export class IncompatibleDidDocError extends XRPCError { } } -export class SignupCapacityError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) - } -} - export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'InvalidHandle') return new InvalidHandleError(e) @@ -100,7 +94,6 @@ export function toKnownErr(e: any) { if (e.error === 'UnsupportedDomain') return new UnsupportedDomainError(e) if (e.error === 'UnresolvableDid') return new UnresolvableDidError(e) if (e.error === 'IncompatibleDidDoc') return new IncompatibleDidDocError(e) - if (e.error === 'SignupCapacity') return new SignupCapacityError(e) } return e } diff --git a/packages/api/src/client/types/com/atproto/temp/checkSignupAvailability.ts b/packages/api/src/client/types/com/atproto/temp/checkSignupQueue.ts similarity index 85% rename from packages/api/src/client/types/com/atproto/temp/checkSignupAvailability.ts rename to packages/api/src/client/types/com/atproto/temp/checkSignupQueue.ts index 39baf9b3a05..2f80322c82e 100644 --- a/packages/api/src/client/types/com/atproto/temp/checkSignupAvailability.ts +++ b/packages/api/src/client/types/com/atproto/temp/checkSignupQueue.ts @@ -7,14 +7,14 @@ import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -export interface QueryParams { - email: string -} +export interface QueryParams {} export type InputSchema = undefined export interface OutputSchema { - hasAvailability: boolean + activated: boolean + placeInQueue?: number + estimatedTimeMs?: number [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/temp/sendSignupQueueEmails.ts b/packages/api/src/client/types/com/atproto/temp/sendSignupQueueEmails.ts deleted file mode 100644 index 9e5f7073088..00000000000 --- a/packages/api/src/client/types/com/atproto/temp/sendSignupQueueEmails.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import { Headers, XRPCError } from '@atproto/xrpc' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' -import { CID } from 'multiformats/cid' - -export interface QueryParams { - count: number -} - -export type InputSchema = undefined - -export interface CallOptions { - headers?: Headers -} - -export interface Response { - success: boolean - headers: Headers -} - -export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } - return e -} diff --git a/packages/bsky/src/lexicon/index.ts b/packages/bsky/src/lexicon/index.ts index 1c9caa30336..88b2ad43465 100644 --- a/packages/bsky/src/lexicon/index.ts +++ b/packages/bsky/src/lexicon/index.ts @@ -78,12 +78,11 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos' import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate' import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl' import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' -import * as ComAtprotoTempCheckSignupAvailability from './types/com/atproto/temp/checkSignupAvailability' +import * as ComAtprotoTempCheckSignupQueue from './types/com/atproto/temp/checkSignupQueue' import * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' import * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' import * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' import * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' -import * as ComAtprotoTempSendSignupQueueEmails from './types/com/atproto/temp/sendSignupQueueEmails' import * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile' @@ -1022,14 +1021,14 @@ export class ComAtprotoTempNS { this._server = server } - checkSignupAvailability( + checkSignupQueue( cfg: ConfigOf< AV, - ComAtprotoTempCheckSignupAvailability.Handler>, - ComAtprotoTempCheckSignupAvailability.HandlerReqCtx> + ComAtprotoTempCheckSignupQueue.Handler>, + ComAtprotoTempCheckSignupQueue.HandlerReqCtx> >, ) { - const nsid = 'com.atproto.temp.checkSignupAvailability' // @ts-ignore + const nsid = 'com.atproto.temp.checkSignupQueue' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } @@ -1077,17 +1076,6 @@ export class ComAtprotoTempNS { return this._server.xrpc.method(nsid, cfg) } - sendSignupQueueEmails( - cfg: ConfigOf< - AV, - ComAtprotoTempSendSignupQueueEmails.Handler>, - ComAtprotoTempSendSignupQueueEmails.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.sendSignupQueueEmails' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - transferAccount( cfg: ConfigOf< AV, diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index ea33e3d8666..d15e52eb1ad 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -2921,9 +2921,6 @@ export const schemaDict = { { name: 'IncompatibleDidDoc', }, - { - name: 'SignupCapacity', - }, ], }, }, @@ -4249,32 +4246,28 @@ export const schemaDict = { }, }, }, - ComAtprotoTempCheckSignupAvailability: { + ComAtprotoTempCheckSignupQueue: { lexicon: 1, - id: 'com.atproto.temp.checkSignupAvailability', + id: 'com.atproto.temp.checkSignupQueue', defs: { main: { type: 'query', - description: - 'Check if the service has availability for new accounts and submit email for waitlist queue.', - parameters: { - type: 'params', - required: ['email'], - properties: { - email: { - type: 'string', - }, - }, - }, + description: 'Check accounts location in signup queue.', output: { encoding: 'application/json', schema: { type: 'object', - required: ['hasAvailability'], + required: ['activated'], properties: { - hasAvailability: { + activated: { type: 'boolean', }, + placeInQueue: { + type: 'integer', + }, + estimatedTimeMs: { + type: 'integer', + }, }, }, }, @@ -4398,26 +4391,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempSendSignupQueueEmails: { - lexicon: 1, - id: 'com.atproto.temp.sendSignupQueueEmails', - defs: { - main: { - type: 'query', - description: - 'Fetch all labels from a labeler created after a certain date.', - parameters: { - type: 'params', - required: ['count'], - properties: { - count: { - type: 'integer', - }, - }, - }, - }, - }, - }, ComAtprotoTempTransferAccount: { lexicon: 1, id: 'com.atproto.temp.transferAccount', @@ -8275,14 +8248,12 @@ export const ids = { ComAtprotoSyncNotifyOfUpdate: 'com.atproto.sync.notifyOfUpdate', ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl', ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', - ComAtprotoTempCheckSignupAvailability: - 'com.atproto.temp.checkSignupAvailability', + ComAtprotoTempCheckSignupQueue: 'com.atproto.temp.checkSignupQueue', ComAtprotoTempFetchLabels: 'com.atproto.temp.fetchLabels', ComAtprotoTempImportRepo: 'com.atproto.temp.importRepo', ComAtprotoTempPushBlob: 'com.atproto.temp.pushBlob', ComAtprotoTempRequestPhoneVerification: 'com.atproto.temp.requestPhoneVerification', - ComAtprotoTempSendSignupQueueEmails: 'com.atproto.temp.sendSignupQueueEmails', ComAtprotoTempTransferAccount: 'com.atproto.temp.transferAccount', AppBskyActorDefs: 'app.bsky.actor.defs', AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts index 4784a9f0df3..bbf2c009bf5 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts @@ -54,7 +54,6 @@ export interface HandlerError { | 'UnsupportedDomain' | 'UnresolvableDid' | 'IncompatibleDidDoc' - | 'SignupCapacity' } export type HandlerOutput = HandlerError | HandlerSuccess diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts similarity index 90% rename from packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts rename to packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts index cd462bfee7d..d2a431430a8 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts @@ -8,14 +8,14 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import { HandlerAuth } from '@atproto/xrpc-server' -export interface QueryParams { - email: string -} +export interface QueryParams {} export type InputSchema = undefined export interface OutputSchema { - hasAvailability: boolean + activated: boolean + placeInQueue?: number + estimatedTimeMs?: number [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts deleted file mode 100644 index 1cb21d8baee..00000000000 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' - -export interface QueryParams { - count: number -} - -export type InputSchema = undefined -export type HandlerInput = undefined - -export interface HandlerError { - status: number - message?: string -} - -export type HandlerOutput = HandlerError | void -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/pds/src/account-activator.ts b/packages/pds/src/account-activator.ts new file mode 100644 index 00000000000..8b1e1f13f04 --- /dev/null +++ b/packages/pds/src/account-activator.ts @@ -0,0 +1,111 @@ +import { SECOND } from '@atproto/common' +import { limiterLogger as log } from './logger' +import Database from './db' +import { countAll } from './db/util' +import { Leader } from './db/leader' + +type LimiterFlags = { + disableSignups: boolean + periodAllowance: number + periodMs: number +} + +type LimiterStatus = LimiterFlags & { + accountsInPeriod: number +} + +export const ACCOUNT_ACTIVATOR_ID = 1010 + +export class AccountActivator { + leader: Leader + + destroyed = false + promise: Promise = Promise.resolve() + timer: NodeJS.Timer | undefined + status: LimiterStatus + + constructor(private db: Database, lockId = ACCOUNT_ACTIVATOR_ID) { + this.leader = new Leader(lockId, this.db) + } + + hasAvailability(): boolean { + if (this.status.disableSignups) return false + return this.status.accountsInPeriod < this.status.periodAllowance + } + + async start() { + this.poll() + await this.promise + } + + poll() { + if (this.destroyed) return + this.promise = this.refresh() + .catch((err) => log.error({ err }, 'limiter refresh failed')) + .finally(() => { + this.timer = setTimeout(() => this.poll(), 30 * SECOND) + }) + } + + async destroy() { + this.destroyed = true + if (this.timer) { + clearTimeout(this.timer) + } + await this.promise + } + + async refresh() { + const flags = await this.getRuntimeFlags() + const accountsInPeriod = + flags.periodMs === 0 ? 0 : await this.accountsInPeriod(flags.periodMs) + + this.status = { + ...flags, + accountsInPeriod, + } + + log.info({ ...this.status }, 'limiter refresh') + } + + async getRuntimeFlags(): Promise { + const flagsRes = await this.db.db + .selectFrom('runtime_flag') + .selectAll() + .where('name', '=', DISABLE_SIGNUPS_FLAG) + .orWhere('name', '=', PERIOD_ALLOWANCE_FLAG) + .orWhere('name', '=', PERIOD_MS_FLAG) + .execute() + const disableSignups = + flagsRes.find((val) => val.name === DISABLE_SIGNUPS_FLAG)?.value ?? + 'false' + const periodAllowanceFlag = + flagsRes.find((val) => val.name === PERIOD_ALLOWANCE_FLAG)?.value ?? + '10000000' + const periodAllowance = parseInt(periodAllowanceFlag) + const periodMsFlag = + flagsRes.find((val) => val.name === PERIOD_MS_FLAG)?.value ?? '0' + const periodMs = parseInt(periodMsFlag) + + return { + disableSignups: disableSignups === 'true', + periodAllowance: isNaN(periodAllowance) ? 10000000 : periodAllowance, + periodMs: isNaN(periodMs) ? 10000000 : periodMs, + } + } + + async accountsInPeriod(period: number): Promise { + const periodStart = new Date(Date.now() - period).toISOString() + const res = await this.db.db + .selectFrom('user_account') + .select(countAll.as('count')) + .where('activatedAt', 'is not', null) + .where('activatedAt', '>', periodStart) + .executeTakeFirstOrThrow() + return res.count + } +} + +const DISABLE_SIGNUPS_FLAG = 'signup-limiter:disableSignups' +const PERIOD_ALLOWANCE_FLAG = 'signup-limiter:periodAllowance' +const PERIOD_MS_FLAG = 'signup-limiter:periodMs' diff --git a/packages/pds/src/api/com/atproto/server/createAccount.ts b/packages/pds/src/api/com/atproto/server/createAccount.ts index fa86d4ede0e..fd5a0ca14f4 100644 --- a/packages/pds/src/api/com/atproto/server/createAccount.ts +++ b/packages/pds/src/api/com/atproto/server/createAccount.ts @@ -26,12 +26,6 @@ export default function (server: Server, ctx: AppContext) { }, handler: async ({ input, req }) => { const hasAvailability = ctx.signupLimiter.hasAvailability() - if (!hasAvailability) { - throw new InvalidRequestError( - 'Service at signup capacity, please check back later.', - 'SignupCapacity', - ) - } const { did, @@ -95,6 +89,7 @@ export default function (server: Server, ctx: AppContext) { did, pdsId: entrywayAssignedPds?.id, passwordScrypt, + activated: hasAvailability, }) } catch (err) { if (err instanceof UserAlreadyExistsError) { @@ -149,6 +144,7 @@ export default function (server: Server, ctx: AppContext) { did, pdsDid: entrywayAssignedPds?.did ?? null, appPasswordName: null, + deactivated: !hasAvailability, }) if (entrywayAssignedPds) { diff --git a/packages/pds/src/api/com/atproto/server/createSession.ts b/packages/pds/src/api/com/atproto/server/createSession.ts index df1d2e2d68b..affd76db1a9 100644 --- a/packages/pds/src/api/com/atproto/server/createSession.ts +++ b/packages/pds/src/api/com/atproto/server/createSession.ts @@ -60,6 +60,7 @@ export default function (server: Server, ctx: AppContext) { did: user.did, pdsDid: user.pdsDid, appPasswordName, + deactivated: !user.activatedAt, }), didDocForSession(ctx, user), ]) diff --git a/packages/pds/src/api/com/atproto/server/refreshSession.ts b/packages/pds/src/api/com/atproto/server/refreshSession.ts index 1b0c190707b..3e7ab3a9f03 100644 --- a/packages/pds/src/api/com/atproto/server/refreshSession.ts +++ b/packages/pds/src/api/com/atproto/server/refreshSession.ts @@ -28,6 +28,7 @@ export default function (server: Server, ctx: AppContext) { return ctx.services.auth(dbTxn).rotateRefreshToken({ id: auth.credentials.tokenId, pdsDid: user.pdsDid, + deactived: !user.activatedAt, }) }), ]) diff --git a/packages/pds/src/api/com/atproto/temp/checkSignupAvailability.ts b/packages/pds/src/api/com/atproto/temp/checkSignupAvailability.ts deleted file mode 100644 index 56d5d04928e..00000000000 --- a/packages/pds/src/api/com/atproto/temp/checkSignupAvailability.ts +++ /dev/null @@ -1,44 +0,0 @@ -import disposable from 'disposable-email' -import { Server } from '../../../../lexicon' -import AppContext from '../../../../context' -import { InvalidRequestError } from '@atproto/xrpc-server' - -export default function (server: Server, ctx: AppContext) { - server.com.atproto.temp.checkSignupAvailability({ - handler: async ({ params }) => { - const email = params.email.toLowerCase() - if (!disposable.validate(email)) { - throw new InvalidRequestError( - 'This email address is not supported, please use a different email.', - ) - } - - const alreadyExists = await ctx.db.db - .selectFrom('user_account') - .selectAll() - .where('email', '=', email) - .executeTakeFirst() - if (alreadyExists) { - throw new InvalidRequestError(`Email already taken: ${email}`) - } - - await ctx.db.db - .insertInto('queued_email') - .values({ - email, - registeredAt: new Date().toISOString(), - }) - .onConflict((oc) => oc.doNothing()) - .execute() - - const hasAvailability = ctx.signupLimiter.hasAvailability() - - return { - encoding: 'application/json', - body: { - hasAvailability, - }, - } - }, - }) -} diff --git a/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts b/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts new file mode 100644 index 00000000000..36210d4aca9 --- /dev/null +++ b/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts @@ -0,0 +1,49 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { countAll } from '../../../../db/util' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.temp.checkSignupQueue({ + auth: ctx.authVerifier.accessDeactived, + handler: async ({ auth }) => { + const requester = auth.credentials.did + const account = await ctx.db.db + .selectFrom('user_account') + .selectAll() + .where('did', '=', requester) + .executeTakeFirstOrThrow() + const activated = !!account.activatedAt + let placeInQueue: number | undefined + if (!activated) { + const res = await ctx.db.db + .selectFrom('user_account') + .select(countAll.as('count')) + .where('user_account.activatedAt', 'is', null) + .where('user_account.createdAt', '<', account.createdAt) + .executeTakeFirst() + placeInQueue = res?.count + } + const limiterStatus = ctx.signupLimiter.status + let estimatedTimeMs: number | undefined + if ( + placeInQueue && + limiterStatus.disableSignups && + limiterStatus.accountsInPeriod > 0 + ) { + estimatedTimeMs = + (placeInQueue * limiterStatus.periodMs) / + limiterStatus.accountsInPeriod + } + + return { + encoding: 'application/json', + body: { + activated, + placeInQueue, + estimatedTimeMs, + }, + } + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/temp/index.ts b/packages/pds/src/api/com/atproto/temp/index.ts index 90f2122101a..f07a0d5a1c2 100644 --- a/packages/pds/src/api/com/atproto/temp/index.ts +++ b/packages/pds/src/api/com/atproto/temp/index.ts @@ -1,11 +1,9 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' +import checkSignupQueue from './checkSignupQueue' import requestPhoneVerification from './requestPhoneVerification' -import checkSignupAvailability from './checkSignupAvailability' -import sendSignupQueueEmails from './sendSignupQueueEmails' export default function (server: Server, ctx: AppContext) { requestPhoneVerification(server, ctx) - checkSignupAvailability(server, ctx) - sendSignupQueueEmails(server, ctx) + checkSignupQueue(server, ctx) } diff --git a/packages/pds/src/api/com/atproto/temp/sendSignupQueueEmails.ts b/packages/pds/src/api/com/atproto/temp/sendSignupQueueEmails.ts deleted file mode 100644 index f0f06aafd6b..00000000000 --- a/packages/pds/src/api/com/atproto/temp/sendSignupQueueEmails.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Server } from '../../../../lexicon' -import AppContext from '../../../../context' -import { InvalidRequestError } from '@atproto/xrpc-server' -import { countAll } from '../../../../db/util' - -export default function (server: Server, ctx: AppContext) { - server.com.atproto.temp.sendSignupQueueEmails({ - handler: async () => { - throw new Error('unimplemented') - }, - }) -} diff --git a/packages/pds/src/auth-verifier.ts b/packages/pds/src/auth-verifier.ts index cba9094fb34..647ef359e42 100644 --- a/packages/pds/src/auth-verifier.ts +++ b/packages/pds/src/auth-verifier.ts @@ -29,6 +29,7 @@ export enum AuthScope { Access = 'com.atproto.access', Refresh = 'com.atproto.refresh', AppPass = 'com.atproto.appPass', + Deactivated = 'com.atproto.deactived', } export enum RoleStatus { @@ -166,6 +167,14 @@ export class AuthVerifier { return this.validateAccessToken(ctx.req, [AuthScope.Access]) } + accessDeactived = (ctx: ReqCtx): Promise => { + return this.validateAccessToken(ctx.req, [ + AuthScope.Access, + AuthScope.AppPass, + AuthScope.Deactivated, + ]) + } + // @TODO additional check on aud when set refresh = async (ctx: ReqCtx): Promise => { const { did, scope, token, audience, payload } = diff --git a/packages/pds/src/db/database-schema.ts b/packages/pds/src/db/database-schema.ts index 727a774953b..89f94c73e63 100644 --- a/packages/pds/src/db/database-schema.ts +++ b/packages/pds/src/db/database-schema.ts @@ -19,7 +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 queuedEmail from './tables/queued-email' +import * as queuedAccount from './tables/queued-account' export type DatabaseSchemaType = appMigration.PartialDB & runtimeFlag.PartialDB & @@ -41,7 +41,7 @@ export type DatabaseSchemaType = appMigration.PartialDB & moderation.PartialDB & repoSeq.PartialDB & phoneVerification.PartialDB & - queuedEmail.PartialDB + queuedAccount.PartialDB export type DatabaseSchema = Kysely diff --git a/packages/pds/src/db/tables/queued-account.ts b/packages/pds/src/db/tables/queued-account.ts new file mode 100644 index 00000000000..524b21b001c --- /dev/null +++ b/packages/pds/src/db/tables/queued-account.ts @@ -0,0 +1,10 @@ +export interface QueuedAccount { + did: string + activatedAt: string | null +} + +export const tableName = 'queued_account' + +export type PartialDB = { + [tableName]: QueuedAccount +} diff --git a/packages/pds/src/db/tables/queued-email.ts b/packages/pds/src/db/tables/queued-email.ts deleted file mode 100644 index aff839df106..00000000000 --- a/packages/pds/src/db/tables/queued-email.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface QueuedEmail { - email: string - registeredAt: string - lastEmailed: string | null -} - -export const tableName = 'queued_email' - -export type PartialDB = { - [tableName]: QueuedEmail -} diff --git a/packages/pds/src/db/tables/user-account.ts b/packages/pds/src/db/tables/user-account.ts index 5c7c1b2dcea..0b2e7c55599 100644 --- a/packages/pds/src/db/tables/user-account.ts +++ b/packages/pds/src/db/tables/user-account.ts @@ -5,6 +5,7 @@ export interface UserAccount { email: string passwordScrypt: string createdAt: string + activatedAt: string | null emailConfirmedAt: string | null invitesDisabled: Generated<0 | 1> inviteNote: string | null diff --git a/packages/pds/src/lexicon/index.ts b/packages/pds/src/lexicon/index.ts index 1c9caa30336..88b2ad43465 100644 --- a/packages/pds/src/lexicon/index.ts +++ b/packages/pds/src/lexicon/index.ts @@ -78,12 +78,11 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos' import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate' import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl' import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' -import * as ComAtprotoTempCheckSignupAvailability from './types/com/atproto/temp/checkSignupAvailability' +import * as ComAtprotoTempCheckSignupQueue from './types/com/atproto/temp/checkSignupQueue' import * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' import * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' import * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' import * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' -import * as ComAtprotoTempSendSignupQueueEmails from './types/com/atproto/temp/sendSignupQueueEmails' import * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile' @@ -1022,14 +1021,14 @@ export class ComAtprotoTempNS { this._server = server } - checkSignupAvailability( + checkSignupQueue( cfg: ConfigOf< AV, - ComAtprotoTempCheckSignupAvailability.Handler>, - ComAtprotoTempCheckSignupAvailability.HandlerReqCtx> + ComAtprotoTempCheckSignupQueue.Handler>, + ComAtprotoTempCheckSignupQueue.HandlerReqCtx> >, ) { - const nsid = 'com.atproto.temp.checkSignupAvailability' // @ts-ignore + const nsid = 'com.atproto.temp.checkSignupQueue' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } @@ -1077,17 +1076,6 @@ export class ComAtprotoTempNS { return this._server.xrpc.method(nsid, cfg) } - sendSignupQueueEmails( - cfg: ConfigOf< - AV, - ComAtprotoTempSendSignupQueueEmails.Handler>, - ComAtprotoTempSendSignupQueueEmails.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.sendSignupQueueEmails' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - transferAccount( cfg: ConfigOf< AV, diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index ea33e3d8666..d15e52eb1ad 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -2921,9 +2921,6 @@ export const schemaDict = { { name: 'IncompatibleDidDoc', }, - { - name: 'SignupCapacity', - }, ], }, }, @@ -4249,32 +4246,28 @@ export const schemaDict = { }, }, }, - ComAtprotoTempCheckSignupAvailability: { + ComAtprotoTempCheckSignupQueue: { lexicon: 1, - id: 'com.atproto.temp.checkSignupAvailability', + id: 'com.atproto.temp.checkSignupQueue', defs: { main: { type: 'query', - description: - 'Check if the service has availability for new accounts and submit email for waitlist queue.', - parameters: { - type: 'params', - required: ['email'], - properties: { - email: { - type: 'string', - }, - }, - }, + description: 'Check accounts location in signup queue.', output: { encoding: 'application/json', schema: { type: 'object', - required: ['hasAvailability'], + required: ['activated'], properties: { - hasAvailability: { + activated: { type: 'boolean', }, + placeInQueue: { + type: 'integer', + }, + estimatedTimeMs: { + type: 'integer', + }, }, }, }, @@ -4398,26 +4391,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempSendSignupQueueEmails: { - lexicon: 1, - id: 'com.atproto.temp.sendSignupQueueEmails', - defs: { - main: { - type: 'query', - description: - 'Fetch all labels from a labeler created after a certain date.', - parameters: { - type: 'params', - required: ['count'], - properties: { - count: { - type: 'integer', - }, - }, - }, - }, - }, - }, ComAtprotoTempTransferAccount: { lexicon: 1, id: 'com.atproto.temp.transferAccount', @@ -8275,14 +8248,12 @@ export const ids = { ComAtprotoSyncNotifyOfUpdate: 'com.atproto.sync.notifyOfUpdate', ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl', ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', - ComAtprotoTempCheckSignupAvailability: - 'com.atproto.temp.checkSignupAvailability', + ComAtprotoTempCheckSignupQueue: 'com.atproto.temp.checkSignupQueue', ComAtprotoTempFetchLabels: 'com.atproto.temp.fetchLabels', ComAtprotoTempImportRepo: 'com.atproto.temp.importRepo', ComAtprotoTempPushBlob: 'com.atproto.temp.pushBlob', ComAtprotoTempRequestPhoneVerification: 'com.atproto.temp.requestPhoneVerification', - ComAtprotoTempSendSignupQueueEmails: 'com.atproto.temp.sendSignupQueueEmails', ComAtprotoTempTransferAccount: 'com.atproto.temp.transferAccount', AppBskyActorDefs: 'app.bsky.actor.defs', AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts index 4784a9f0df3..bbf2c009bf5 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts @@ -54,7 +54,6 @@ export interface HandlerError { | 'UnsupportedDomain' | 'UnresolvableDid' | 'IncompatibleDidDoc' - | 'SignupCapacity' } export type HandlerOutput = HandlerError | HandlerSuccess diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts b/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts similarity index 90% rename from packages/pds/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts rename to packages/pds/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts index cd462bfee7d..d2a431430a8 100644 --- a/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupAvailability.ts +++ b/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts @@ -8,14 +8,14 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import { HandlerAuth } from '@atproto/xrpc-server' -export interface QueryParams { - email: string -} +export interface QueryParams {} export type InputSchema = undefined export interface OutputSchema { - hasAvailability: boolean + activated: boolean + placeInQueue?: number + estimatedTimeMs?: number [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts b/packages/pds/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts deleted file mode 100644 index 1cb21d8baee..00000000000 --- a/packages/pds/src/lexicon/types/com/atproto/temp/sendSignupQueueEmails.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' - -export interface QueryParams { - count: number -} - -export type InputSchema = undefined -export type HandlerInput = undefined - -export interface HandlerError { - status: number - message?: string -} - -export type HandlerOutput = HandlerError | void -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/pds/src/services/account/index.ts b/packages/pds/src/services/account/index.ts index f52ef9d9bb7..f7fa571b64f 100644 --- a/packages/pds/src/services/account/index.ts +++ b/packages/pds/src/services/account/index.ts @@ -129,10 +129,12 @@ export class AccountService { did: string pdsId?: number passwordScrypt: string + activated: boolean }) { this.db.assertTransaction() - const { email, handle, did, pdsId, passwordScrypt } = opts + const { email, handle, did, pdsId, passwordScrypt, activated } = opts log.debug({ handle, email }, 'registering user') + const createdAt = new Date().toISOString() const registerUserAccnt = this.db.db .insertInto('user_account') .values({ @@ -140,7 +142,8 @@ export class AccountService { did, pdsId, passwordScrypt, - createdAt: new Date().toISOString(), + createdAt, + activatedAt: activated ? createdAt : null, }) .onConflict((oc) => oc.doNothing()) .returning('did') diff --git a/packages/pds/src/services/auth.ts b/packages/pds/src/services/auth.ts index 706aaa1b4db..9f493f9f97a 100644 --- a/packages/pds/src/services/auth.ts +++ b/packages/pds/src/services/auth.ts @@ -84,13 +84,14 @@ export class AuthService { did: string pdsDid: string | null appPasswordName: string | null + deactivated: boolean }) { - const { did, pdsDid, appPasswordName } = opts + const { did, pdsDid, appPasswordName, deactivated } = opts const [access, refresh] = await Promise.all([ this.createAccessToken({ did, pdsDid, - scope: appPasswordName === null ? AuthScope.Access : AuthScope.AppPass, + scope: determineScope(appPasswordName, deactivated), }), this.createRefreshToken({ did }), ]) @@ -115,7 +116,11 @@ export class AuthService { .executeTakeFirst() } - async rotateRefreshToken(opts: { id: string; pdsDid: string | null }) { + async rotateRefreshToken(opts: { + id: string + pdsDid: string | null + deactived: boolean + }) { this.db.assertTransaction() const { id, pdsDid } = opts const token = await this.db.db @@ -167,8 +172,7 @@ export class AuthService { this.createAccessToken({ did: token.did, pdsDid, - scope: - token.appPasswordName === null ? AuthScope.Access : AuthScope.AppPass, + scope: determineScope(token.appPasswordName, opts.deactived), }), ]) @@ -214,3 +218,12 @@ const decodeRefreshToken = (jwt: string) => { assert.ok(token.scope === AuthScope.Refresh, 'not a refresh token') return token as RefreshToken } + +const determineScope = ( + appPasswordName: string | null, + deactivated: boolean, +): AuthScope => { + if (deactivated) return AuthScope.Deactivated + if (appPasswordName !== null) return AuthScope.AppPass + return AuthScope.Access +} diff --git a/packages/pds/src/signup-limiter.ts b/packages/pds/src/signup-limiter.ts index c53c350afca..746a8cc0c63 100644 --- a/packages/pds/src/signup-limiter.ts +++ b/packages/pds/src/signup-limiter.ts @@ -88,11 +88,12 @@ export class SignupLimiter { } async accountsInPeriod(period: number): Promise { - const hourAgo = new Date(Date.now() - period).toISOString() + const periodStart = new Date(Date.now() - period).toISOString() const res = await this.db.db .selectFrom('user_account') .select(countAll.as('count')) - .where('createdAt', '>', hourAgo) + .where('activatedAt', 'is not', null) + .where('activatedAt', '>', periodStart) .executeTakeFirstOrThrow() return res.count } From 55d664235922035f9c6bfec4eb179332a50037a9 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 23 Jan 2024 18:51:30 -0600 Subject: [PATCH 06/21] refactor & hook up to ctx --- packages/pds/src/account-activator.ts | 111 ------------------ .../api/com/atproto/temp/checkSignupQueue.ts | 3 +- packages/pds/src/context.ts | 8 +- packages/pds/src/signup-limiter.ts | 104 ---------------- packages/pds/src/signup-queue/activator.ts | 104 ++++++++++++++++ packages/pds/src/signup-queue/limiter.ts | 46 ++++++++ packages/pds/src/signup-queue/util.ts | 66 +++++++++++ 7 files changed, 225 insertions(+), 217 deletions(-) delete mode 100644 packages/pds/src/account-activator.ts delete mode 100644 packages/pds/src/signup-limiter.ts create mode 100644 packages/pds/src/signup-queue/activator.ts create mode 100644 packages/pds/src/signup-queue/limiter.ts create mode 100644 packages/pds/src/signup-queue/util.ts diff --git a/packages/pds/src/account-activator.ts b/packages/pds/src/account-activator.ts deleted file mode 100644 index 8b1e1f13f04..00000000000 --- a/packages/pds/src/account-activator.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { SECOND } from '@atproto/common' -import { limiterLogger as log } from './logger' -import Database from './db' -import { countAll } from './db/util' -import { Leader } from './db/leader' - -type LimiterFlags = { - disableSignups: boolean - periodAllowance: number - periodMs: number -} - -type LimiterStatus = LimiterFlags & { - accountsInPeriod: number -} - -export const ACCOUNT_ACTIVATOR_ID = 1010 - -export class AccountActivator { - leader: Leader - - destroyed = false - promise: Promise = Promise.resolve() - timer: NodeJS.Timer | undefined - status: LimiterStatus - - constructor(private db: Database, lockId = ACCOUNT_ACTIVATOR_ID) { - this.leader = new Leader(lockId, this.db) - } - - hasAvailability(): boolean { - if (this.status.disableSignups) return false - return this.status.accountsInPeriod < this.status.periodAllowance - } - - async start() { - this.poll() - await this.promise - } - - poll() { - if (this.destroyed) return - this.promise = this.refresh() - .catch((err) => log.error({ err }, 'limiter refresh failed')) - .finally(() => { - this.timer = setTimeout(() => this.poll(), 30 * SECOND) - }) - } - - async destroy() { - this.destroyed = true - if (this.timer) { - clearTimeout(this.timer) - } - await this.promise - } - - async refresh() { - const flags = await this.getRuntimeFlags() - const accountsInPeriod = - flags.periodMs === 0 ? 0 : await this.accountsInPeriod(flags.periodMs) - - this.status = { - ...flags, - accountsInPeriod, - } - - log.info({ ...this.status }, 'limiter refresh') - } - - async getRuntimeFlags(): Promise { - const flagsRes = await this.db.db - .selectFrom('runtime_flag') - .selectAll() - .where('name', '=', DISABLE_SIGNUPS_FLAG) - .orWhere('name', '=', PERIOD_ALLOWANCE_FLAG) - .orWhere('name', '=', PERIOD_MS_FLAG) - .execute() - const disableSignups = - flagsRes.find((val) => val.name === DISABLE_SIGNUPS_FLAG)?.value ?? - 'false' - const periodAllowanceFlag = - flagsRes.find((val) => val.name === PERIOD_ALLOWANCE_FLAG)?.value ?? - '10000000' - const periodAllowance = parseInt(periodAllowanceFlag) - const periodMsFlag = - flagsRes.find((val) => val.name === PERIOD_MS_FLAG)?.value ?? '0' - const periodMs = parseInt(periodMsFlag) - - return { - disableSignups: disableSignups === 'true', - periodAllowance: isNaN(periodAllowance) ? 10000000 : periodAllowance, - periodMs: isNaN(periodMs) ? 10000000 : periodMs, - } - } - - async accountsInPeriod(period: number): Promise { - const periodStart = new Date(Date.now() - period).toISOString() - const res = await this.db.db - .selectFrom('user_account') - .select(countAll.as('count')) - .where('activatedAt', 'is not', null) - .where('activatedAt', '>', periodStart) - .executeTakeFirstOrThrow() - return res.count - } -} - -const DISABLE_SIGNUPS_FLAG = 'signup-limiter:disableSignups' -const PERIOD_ALLOWANCE_FLAG = 'signup-limiter:periodAllowance' -const PERIOD_MS_FLAG = 'signup-limiter:periodMs' diff --git a/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts b/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts index 36210d4aca9..6c6d201deff 100644 --- a/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts +++ b/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts @@ -1,6 +1,5 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { InvalidRequestError } from '@atproto/xrpc-server' import { countAll } from '../../../../db/util' export default function (server: Server, ctx: AppContext) { @@ -14,6 +13,7 @@ export default function (server: Server, ctx: AppContext) { .where('did', '=', requester) .executeTakeFirstOrThrow() const activated = !!account.activatedAt + let placeInQueue: number | undefined if (!activated) { const res = await ctx.db.db @@ -24,6 +24,7 @@ export default function (server: Server, ctx: AppContext) { .executeTakeFirst() placeInQueue = res?.count } + const limiterStatus = ctx.signupLimiter.status let estimatedTimeMs: number | undefined if ( diff --git a/packages/pds/src/context.ts b/packages/pds/src/context.ts index b5464a76866..75224e3c83a 100644 --- a/packages/pds/src/context.ts +++ b/packages/pds/src/context.ts @@ -23,7 +23,8 @@ import { RuntimeFlags } from './runtime-flags' import { PdsAgents } from './pds-agents' import { TwilioClient } from './twilio' import assert from 'assert' -import { SignupLimiter } from './signup-limiter' +import { SignupLimiter } from './signup-queue/limiter' +import { SignupActivator } from './signup-queue/activator' export type AppContextOptions = { db: Database @@ -48,6 +49,7 @@ export type AppContextOptions = { plcRotationKey: crypto.Keypair twilio?: TwilioClient signupLimiter: SignupLimiter + signupActivator: SignupActivator cfg: ServerConfig } @@ -74,6 +76,7 @@ export class AppContext { public plcRotationKey: crypto.Keypair public twilio?: TwilioClient public signupLimiter: SignupLimiter + public signupActivator: SignupActivator public cfg: ServerConfig constructor(opts: AppContextOptions) { @@ -99,6 +102,7 @@ export class AppContext { this.plcRotationKey = opts.plcRotationKey this.twilio = opts.twilio this.signupLimiter = opts.signupLimiter + this.signupActivator = opts.signupActivator this.cfg = opts.cfg } @@ -227,6 +231,7 @@ export class AppContext { } const signupLimiter = new SignupLimiter(db) + const signupActivator = new SignupActivator(db) const pdsAgents = new PdsAgents() @@ -253,6 +258,7 @@ export class AppContext { pdsAgents, twilio, signupLimiter, + signupActivator, cfg, ...(overrides ?? {}), }) diff --git a/packages/pds/src/signup-limiter.ts b/packages/pds/src/signup-limiter.ts deleted file mode 100644 index 746a8cc0c63..00000000000 --- a/packages/pds/src/signup-limiter.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { SECOND } from '@atproto/common' -import { limiterLogger as log } from './logger' -import Database from './db' -import { countAll } from './db/util' - -type LimiterFlags = { - disableSignups: boolean - periodAllowance: number - periodMs: number -} - -type LimiterStatus = LimiterFlags & { - accountsInPeriod: number -} - -export class SignupLimiter { - destroyed = false - promise: Promise = Promise.resolve() - timer: NodeJS.Timer | undefined - status: LimiterStatus - - constructor(private db: Database) {} - - hasAvailability(): boolean { - if (this.status.disableSignups) return false - return this.status.accountsInPeriod < this.status.periodAllowance - } - - async start() { - this.poll() - await this.promise - } - - poll() { - if (this.destroyed) return - this.promise = this.refresh() - .catch((err) => log.error({ err }, 'limiter refresh failed')) - .finally(() => { - this.timer = setTimeout(() => this.poll(), 30 * SECOND) - }) - } - - async destroy() { - this.destroyed = true - if (this.timer) { - clearTimeout(this.timer) - } - await this.promise - } - - async refresh() { - const flags = await this.getRuntimeFlags() - const accountsInPeriod = - flags.periodMs === 0 ? 0 : await this.accountsInPeriod(flags.periodMs) - - this.status = { - ...flags, - accountsInPeriod, - } - - log.info({ ...this.status }, 'limiter refresh') - } - - async getRuntimeFlags(): Promise { - const flagsRes = await this.db.db - .selectFrom('runtime_flag') - .selectAll() - .where('name', '=', DISABLE_SIGNUPS_FLAG) - .orWhere('name', '=', PERIOD_ALLOWANCE_FLAG) - .orWhere('name', '=', PERIOD_MS_FLAG) - .execute() - const disableSignups = - flagsRes.find((val) => val.name === DISABLE_SIGNUPS_FLAG)?.value ?? - 'false' - const periodAllowanceFlag = - flagsRes.find((val) => val.name === PERIOD_ALLOWANCE_FLAG)?.value ?? - '10000000' - const periodAllowance = parseInt(periodAllowanceFlag) - const periodMsFlag = - flagsRes.find((val) => val.name === PERIOD_MS_FLAG)?.value ?? '0' - const periodMs = parseInt(periodMsFlag) - - return { - disableSignups: disableSignups === 'true', - periodAllowance: isNaN(periodAllowance) ? 10000000 : periodAllowance, - periodMs: isNaN(periodMs) ? 10000000 : periodMs, - } - } - - async accountsInPeriod(period: number): Promise { - const periodStart = new Date(Date.now() - period).toISOString() - const res = await this.db.db - .selectFrom('user_account') - .select(countAll.as('count')) - .where('activatedAt', 'is not', null) - .where('activatedAt', '>', periodStart) - .executeTakeFirstOrThrow() - return res.count - } -} - -const DISABLE_SIGNUPS_FLAG = 'signup-limiter:disableSignups' -const PERIOD_ALLOWANCE_FLAG = 'signup-limiter:periodAllowance' -const PERIOD_MS_FLAG = 'signup-limiter:periodMs' diff --git a/packages/pds/src/signup-queue/activator.ts b/packages/pds/src/signup-queue/activator.ts new file mode 100644 index 00000000000..99cd05c1e4e --- /dev/null +++ b/packages/pds/src/signup-queue/activator.ts @@ -0,0 +1,104 @@ +import { SECOND, jitter, wait } from '@atproto/common' +import { limiterLogger as log } from '../logger' +import Database from '../db' +import { Leader } from '../db/leader' +import { DisconnectError } from '@atproto/xrpc-server' +import { getQueueStatus } from './util' + +type LimiterFlags = { + disableSignups: boolean + periodAllowance: number + periodMs: number +} + +type LimiterStatus = LimiterFlags & { + accountsInPeriod: number +} + +export const ACCOUNT_ACTIVATOR_ID = 1010 + +export class SignupActivator { + leader: Leader + + destroyed = false + promise: Promise = Promise.resolve() + timer: NodeJS.Timer | undefined + status: LimiterStatus + + constructor(private db: Database, lockId = ACCOUNT_ACTIVATOR_ID) { + this.leader = new Leader(lockId, this.db) + } + + async run() { + while (!this.destroyed) + try { + const { ran } = await this.leader.run(async ({ signal }) => { + this.poll() + await new Promise((resolve, reject) => { + signal.addEventListener('abort', () => { + const err = signal.reason + if (!err || err instanceof DisconnectError) { + resolve() + } else { + reject(err) + } + }) + }) + }) + if (ran && !this.destroyed) { + throw new Error( + 'Account activator leader completed, but should be persistent', + ) + } + } catch (err) { + log.error({ err }, 'account activator errored') + } finally { + if (!this.destroyed) { + await wait(1000 + jitter(500)) + } + } + } + + poll() { + if (this.destroyed) return + this.promise = this.activateBatch() + .catch((err) => log.error({ err }, 'limiter refresh failed')) + .finally(() => { + this.timer = setTimeout(() => this.poll(), 30 * SECOND) + }) + } + + async destroy() { + this.destroyed = true + if (this.timer) { + clearTimeout(this.timer) + } + this.leader.destroy(new DisconnectError()) + await this.promise + } + + async activateBatch() { + const status = await getQueueStatus(this.db) + if (status.disableSignups) return + const toAdmit = status.periodAllowance - status.accountsInPeriod + if (toAdmit < 1) return + + const activatedAt = new Date().toISOString() + const activated = await this.db.db + .updateTable('user_account') + .set({ activatedAt }) + .where('did', 'in', (qb) => + qb + .selectFrom('user_account') + .select('did') + .where('activatedAt', 'is', null) + .orderBy('createdAt', 'asc') + .limit(toAdmit), + ) + .returning('did') + .execute() + + log.info({ count: activated.length }, 'activated accounts') + // @TODO send mail/push notifs + } +} diff --git a/packages/pds/src/signup-queue/limiter.ts b/packages/pds/src/signup-queue/limiter.ts new file mode 100644 index 00000000000..934d04a73da --- /dev/null +++ b/packages/pds/src/signup-queue/limiter.ts @@ -0,0 +1,46 @@ +import { SECOND } from '@atproto/common' +import { limiterLogger as log } from '../logger' +import Database from '../db' +import { LimiterStatus, getQueueStatus } from './util' + +export class SignupLimiter { + destroyed = false + promise: Promise = Promise.resolve() + timer: NodeJS.Timer | undefined + status: LimiterStatus + + constructor(private db: Database) {} + + hasAvailability(): boolean { + if (this.status.disableSignups) return false + return this.status.accountsInPeriod < this.status.periodAllowance + } + + async start() { + this.poll() + await this.promise + } + + poll() { + if (this.destroyed) return + this.promise = this.refresh() + .catch((err) => log.error({ err }, 'limiter refresh failed')) + .finally(() => { + this.timer = setTimeout(() => this.poll(), 30 * SECOND) + }) + } + + async destroy() { + this.destroyed = true + if (this.timer) { + clearTimeout(this.timer) + } + await this.promise + } + + async refresh() { + this.status = await getQueueStatus(this.db) + + log.info({ ...this.status }, 'limiter refresh') + } +} diff --git a/packages/pds/src/signup-queue/util.ts b/packages/pds/src/signup-queue/util.ts new file mode 100644 index 00000000000..f9730c861b9 --- /dev/null +++ b/packages/pds/src/signup-queue/util.ts @@ -0,0 +1,66 @@ +import Database from '../db' +import { countAll } from '../db/util' + +export type LimiterFlags = { + disableSignups: boolean + periodAllowance: number + periodMs: number +} + +export type LimiterStatus = LimiterFlags & { + accountsInPeriod: number +} + +export const getRuntimeFlags = async (db: Database): Promise => { + const flagsRes = await db.db + .selectFrom('runtime_flag') + .selectAll() + .where('name', '=', DISABLE_SIGNUPS_FLAG) + .orWhere('name', '=', PERIOD_ALLOWANCE_FLAG) + .orWhere('name', '=', PERIOD_MS_FLAG) + .execute() + const disableSignups = + flagsRes.find((val) => val.name === DISABLE_SIGNUPS_FLAG)?.value ?? 'false' + const periodAllowanceFlag = + flagsRes.find((val) => val.name === PERIOD_ALLOWANCE_FLAG)?.value ?? + '10000000' + const periodAllowance = parseInt(periodAllowanceFlag) + const periodMsFlag = + flagsRes.find((val) => val.name === PERIOD_MS_FLAG)?.value ?? '0' + const periodMs = parseInt(periodMsFlag) + + return { + disableSignups: disableSignups === 'true', + periodAllowance: isNaN(periodAllowance) ? 10000000 : periodAllowance, + periodMs: isNaN(periodMs) ? 10000000 : periodMs, + } +} + +export const getAccountsInPeriod = async ( + db: Database, + periodMs: number, +): Promise => { + const periodStart = new Date(Date.now() - periodMs).toISOString() + const res = await db.db + .selectFrom('user_account') + .select(countAll.as('count')) + .where('activatedAt', 'is not', null) + .where('activatedAt', '>', periodStart) + .executeTakeFirstOrThrow() + return res.count +} + +export const getQueueStatus = async (db: Database) => { + const flags = await getRuntimeFlags(db) + const accountsInPeriod = + flags.periodMs === 0 ? 0 : await getAccountsInPeriod(db, flags.periodMs) + + return { + ...flags, + accountsInPeriod, + } +} + +export const DISABLE_SIGNUPS_FLAG = 'signup-limiter:disableSignups' +export const PERIOD_ALLOWANCE_FLAG = 'signup-limiter:periodAllowance' +export const PERIOD_MS_FLAG = 'signup-limiter:periodMs' From 0e56c9101eb5a491b7a33c06d6ea2e4dfd1c0733 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 23 Jan 2024 18:58:15 -0600 Subject: [PATCH 07/21] db migration --- packages/pds/src/db/database-schema.ts | 4 +--- .../20240124T005600811Z-signup-queue.ts | 18 ++++++++++++++++++ packages/pds/src/db/migrations/index.ts | 1 + packages/pds/src/db/tables/queued-account.ts | 10 ---------- 4 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 packages/pds/src/db/migrations/20240124T005600811Z-signup-queue.ts delete mode 100644 packages/pds/src/db/tables/queued-account.ts diff --git a/packages/pds/src/db/database-schema.ts b/packages/pds/src/db/database-schema.ts index 89f94c73e63..57599aa12d9 100644 --- a/packages/pds/src/db/database-schema.ts +++ b/packages/pds/src/db/database-schema.ts @@ -19,7 +19,6 @@ 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 queuedAccount from './tables/queued-account' export type DatabaseSchemaType = appMigration.PartialDB & runtimeFlag.PartialDB & @@ -40,8 +39,7 @@ export type DatabaseSchemaType = appMigration.PartialDB & emailToken.PartialDB & moderation.PartialDB & repoSeq.PartialDB & - phoneVerification.PartialDB & - queuedAccount.PartialDB + phoneVerification.PartialDB export type DatabaseSchema = Kysely diff --git a/packages/pds/src/db/migrations/20240124T005600811Z-signup-queue.ts b/packages/pds/src/db/migrations/20240124T005600811Z-signup-queue.ts new file mode 100644 index 00000000000..38fb5c6e399 --- /dev/null +++ b/packages/pds/src/db/migrations/20240124T005600811Z-signup-queue.ts @@ -0,0 +1,18 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('user_account') + .addColumn('activatedAt', 'varchar') + .execute() + await db.schema + .createIndex('user_account_activated_at_idx') + .on('user_account') + .columns(['activatedAt', 'createdAt']) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropIndex('user_account_activated_at_idx').execute() + await db.schema.alterTable('user_account').dropColumn('activatedAt').execute() +} diff --git a/packages/pds/src/db/migrations/index.ts b/packages/pds/src/db/migrations/index.ts index 8b1e10f89f7..a23b1c36275 100644 --- a/packages/pds/src/db/migrations/index.ts +++ b/packages/pds/src/db/migrations/index.ts @@ -9,3 +9,4 @@ export * as _20230929T213219699Z from './20230929T213219699Z-takedown-id-as-int' export * as _20231011T155513453Z from './20231011T155513453Z-takedown-ref' export * as _20231031T222409283Z from './20231031T222409283Z-user-account-pds' export * as _20240117T001106576Z from './20240117T001106576Z-phone-verification' +export * as _20240124T005600811Z from './20240124T005600811Z-signup-queue' diff --git a/packages/pds/src/db/tables/queued-account.ts b/packages/pds/src/db/tables/queued-account.ts deleted file mode 100644 index 524b21b001c..00000000000 --- a/packages/pds/src/db/tables/queued-account.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface QueuedAccount { - did: string - activatedAt: string | null -} - -export const tableName = 'queued_account' - -export type PartialDB = { - [tableName]: QueuedAccount -} From addeabde031bcbe1ca32b8ba0bd81388a8d8b0fd Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 23 Jan 2024 19:01:55 -0600 Subject: [PATCH 08/21] bad codegen --- packages/api/src/client/index.ts | 102 ++++++++++++++-------------- packages/bsky/src/lexicon/index.ts | 104 ++++++++++++++--------------- packages/pds/src/lexicon/index.ts | 104 ++++++++++++++--------------- 3 files changed, 153 insertions(+), 157 deletions(-) diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index 50e0fd6c0c9..673ce911fcc 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -350,39 +350,39 @@ export class AtpServiceClient { export class ComNS { _service: AtpServiceClient - atproto: ComAtprotoNS + atproto: AtprotoNS constructor(service: AtpServiceClient) { this._service = service - this.atproto = new ComAtprotoNS(service) + this.atproto = new AtprotoNS(service) } } -export class ComAtprotoNS { +export class AtprotoNS { _service: AtpServiceClient - admin: ComAtprotoAdminNS - identity: ComAtprotoIdentityNS - label: ComAtprotoLabelNS - moderation: ComAtprotoModerationNS - repo: ComAtprotoRepoNS - server: ComAtprotoServerNS - sync: ComAtprotoSyncNS - temp: ComAtprotoTempNS + admin: AdminNS + identity: IdentityNS + label: LabelNS + moderation: ModerationNS + repo: RepoNS + server: ServerNS + sync: SyncNS + temp: TempNS constructor(service: AtpServiceClient) { this._service = service - this.admin = new ComAtprotoAdminNS(service) - this.identity = new ComAtprotoIdentityNS(service) - this.label = new ComAtprotoLabelNS(service) - this.moderation = new ComAtprotoModerationNS(service) - this.repo = new ComAtprotoRepoNS(service) - this.server = new ComAtprotoServerNS(service) - this.sync = new ComAtprotoSyncNS(service) - this.temp = new ComAtprotoTempNS(service) + this.admin = new AdminNS(service) + this.identity = new IdentityNS(service) + this.label = new LabelNS(service) + this.moderation = new ModerationNS(service) + this.repo = new RepoNS(service) + this.server = new ServerNS(service) + this.sync = new SyncNS(service) + this.temp = new TempNS(service) } } -export class ComAtprotoAdminNS { +export class AdminNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -668,7 +668,7 @@ export class ComAtprotoAdminNS { } } -export class ComAtprotoIdentityNS { +export class IdentityNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -698,7 +698,7 @@ export class ComAtprotoIdentityNS { } } -export class ComAtprotoLabelNS { +export class LabelNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -717,7 +717,7 @@ export class ComAtprotoLabelNS { } } -export class ComAtprotoModerationNS { +export class ModerationNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -736,7 +736,7 @@ export class ComAtprotoModerationNS { } } -export class ComAtprotoRepoNS { +export class RepoNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -832,7 +832,7 @@ export class ComAtprotoRepoNS { } } -export class ComAtprotoServerNS { +export class ServerNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -1071,7 +1071,7 @@ export class ComAtprotoServerNS { } } -export class ComAtprotoSyncNS { +export class SyncNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -1200,7 +1200,7 @@ export class ComAtprotoSyncNS { } } -export class ComAtprotoTempNS { +export class TempNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -1276,37 +1276,37 @@ export class ComAtprotoTempNS { export class AppNS { _service: AtpServiceClient - bsky: AppBskyNS + bsky: BskyNS constructor(service: AtpServiceClient) { this._service = service - this.bsky = new AppBskyNS(service) + this.bsky = new BskyNS(service) } } -export class AppBskyNS { +export class BskyNS { _service: AtpServiceClient - actor: AppBskyActorNS - embed: AppBskyEmbedNS - feed: AppBskyFeedNS - graph: AppBskyGraphNS - notification: AppBskyNotificationNS - richtext: AppBskyRichtextNS - unspecced: AppBskyUnspeccedNS + actor: ActorNS + embed: EmbedNS + feed: FeedNS + graph: GraphNS + notification: NotificationNS + richtext: RichtextNS + unspecced: UnspeccedNS constructor(service: AtpServiceClient) { this._service = service - this.actor = new AppBskyActorNS(service) - this.embed = new AppBskyEmbedNS(service) - this.feed = new AppBskyFeedNS(service) - this.graph = new AppBskyGraphNS(service) - this.notification = new AppBskyNotificationNS(service) - this.richtext = new AppBskyRichtextNS(service) - this.unspecced = new AppBskyUnspeccedNS(service) + this.actor = new ActorNS(service) + this.embed = new EmbedNS(service) + this.feed = new FeedNS(service) + this.graph = new GraphNS(service) + this.notification = new NotificationNS(service) + this.richtext = new RichtextNS(service) + this.unspecced = new UnspeccedNS(service) } } -export class AppBskyActorNS { +export class ActorNS { _service: AtpServiceClient profile: ProfileRecord @@ -1454,7 +1454,7 @@ export class ProfileRecord { } } -export class AppBskyEmbedNS { +export class EmbedNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -1462,7 +1462,7 @@ export class AppBskyEmbedNS { } } -export class AppBskyFeedNS { +export class FeedNS { _service: AtpServiceClient generator: GeneratorRecord like: LikeRecord @@ -1965,7 +1965,7 @@ export class ThreadgateRecord { } } -export class AppBskyGraphNS { +export class GraphNS { _service: AtpServiceClient block: BlockRecord follow: FollowRecord @@ -2440,7 +2440,7 @@ export class ListitemRecord { } } -export class AppBskyNotificationNS { +export class NotificationNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -2492,7 +2492,7 @@ export class AppBskyNotificationNS { } } -export class AppBskyRichtextNS { +export class RichtextNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { @@ -2500,7 +2500,7 @@ export class AppBskyRichtextNS { } } -export class AppBskyUnspeccedNS { +export class UnspeccedNS { _service: AtpServiceClient constructor(service: AtpServiceClient) { diff --git a/packages/bsky/src/lexicon/index.ts b/packages/bsky/src/lexicon/index.ts index 88b2ad43465..a46c726bcfe 100644 --- a/packages/bsky/src/lexicon/index.ts +++ b/packages/bsky/src/lexicon/index.ts @@ -167,39 +167,39 @@ export class Server { export class ComNS { _server: Server - atproto: ComAtprotoNS + atproto: AtprotoNS constructor(server: Server) { this._server = server - this.atproto = new ComAtprotoNS(server) + this.atproto = new AtprotoNS(server) } } -export class ComAtprotoNS { +export class AtprotoNS { _server: Server - admin: ComAtprotoAdminNS - identity: ComAtprotoIdentityNS - label: ComAtprotoLabelNS - moderation: ComAtprotoModerationNS - repo: ComAtprotoRepoNS - server: ComAtprotoServerNS - sync: ComAtprotoSyncNS - temp: ComAtprotoTempNS + admin: AdminNS + identity: IdentityNS + label: LabelNS + moderation: ModerationNS + repo: RepoNS + server: ServerNS + sync: SyncNS + temp: TempNS constructor(server: Server) { this._server = server - this.admin = new ComAtprotoAdminNS(server) - this.identity = new ComAtprotoIdentityNS(server) - this.label = new ComAtprotoLabelNS(server) - this.moderation = new ComAtprotoModerationNS(server) - this.repo = new ComAtprotoRepoNS(server) - this.server = new ComAtprotoServerNS(server) - this.sync = new ComAtprotoSyncNS(server) - this.temp = new ComAtprotoTempNS(server) + this.admin = new AdminNS(server) + this.identity = new IdentityNS(server) + this.label = new LabelNS(server) + this.moderation = new ModerationNS(server) + this.repo = new RepoNS(server) + this.server = new ServerNS(server) + this.sync = new SyncNS(server) + this.temp = new TempNS(server) } } -export class ComAtprotoAdminNS { +export class AdminNS { _server: Server constructor(server: Server) { @@ -460,7 +460,7 @@ export class ComAtprotoAdminNS { } } -export class ComAtprotoIdentityNS { +export class IdentityNS { _server: Server constructor(server: Server) { @@ -490,7 +490,7 @@ export class ComAtprotoIdentityNS { } } -export class ComAtprotoLabelNS { +export class LabelNS { _server: Server constructor(server: Server) { @@ -520,7 +520,7 @@ export class ComAtprotoLabelNS { } } -export class ComAtprotoModerationNS { +export class ModerationNS { _server: Server constructor(server: Server) { @@ -539,7 +539,7 @@ export class ComAtprotoModerationNS { } } -export class ComAtprotoRepoNS { +export class RepoNS { _server: Server constructor(server: Server) { @@ -635,7 +635,7 @@ export class ComAtprotoRepoNS { } } -export class ComAtprotoServerNS { +export class ServerNS { _server: Server constructor(server: Server) { @@ -874,7 +874,7 @@ export class ComAtprotoServerNS { } } -export class ComAtprotoSyncNS { +export class SyncNS { _server: Server constructor(server: Server) { @@ -1014,7 +1014,7 @@ export class ComAtprotoSyncNS { } } -export class ComAtprotoTempNS { +export class TempNS { _server: Server constructor(server: Server) { @@ -1090,37 +1090,37 @@ export class ComAtprotoTempNS { export class AppNS { _server: Server - bsky: AppBskyNS + bsky: BskyNS constructor(server: Server) { this._server = server - this.bsky = new AppBskyNS(server) + this.bsky = new BskyNS(server) } } -export class AppBskyNS { +export class BskyNS { _server: Server - actor: AppBskyActorNS - embed: AppBskyEmbedNS - feed: AppBskyFeedNS - graph: AppBskyGraphNS - notification: AppBskyNotificationNS - richtext: AppBskyRichtextNS - unspecced: AppBskyUnspeccedNS + actor: ActorNS + embed: EmbedNS + feed: FeedNS + graph: GraphNS + notification: NotificationNS + richtext: RichtextNS + unspecced: UnspeccedNS constructor(server: Server) { this._server = server - this.actor = new AppBskyActorNS(server) - this.embed = new AppBskyEmbedNS(server) - this.feed = new AppBskyFeedNS(server) - this.graph = new AppBskyGraphNS(server) - this.notification = new AppBskyNotificationNS(server) - this.richtext = new AppBskyRichtextNS(server) - this.unspecced = new AppBskyUnspeccedNS(server) + this.actor = new ActorNS(server) + this.embed = new EmbedNS(server) + this.feed = new FeedNS(server) + this.graph = new GraphNS(server) + this.notification = new NotificationNS(server) + this.richtext = new RichtextNS(server) + this.unspecced = new UnspeccedNS(server) } } -export class AppBskyActorNS { +export class ActorNS { _server: Server constructor(server: Server) { @@ -1205,7 +1205,7 @@ export class AppBskyActorNS { } } -export class AppBskyEmbedNS { +export class EmbedNS { _server: Server constructor(server: Server) { @@ -1213,7 +1213,7 @@ export class AppBskyEmbedNS { } } -export class AppBskyFeedNS { +export class FeedNS { _server: Server constructor(server: Server) { @@ -1397,7 +1397,7 @@ export class AppBskyFeedNS { } } -export class AppBskyGraphNS { +export class GraphNS { _server: Server constructor(server: Server) { @@ -1548,7 +1548,7 @@ export class AppBskyGraphNS { } } -export class AppBskyNotificationNS { +export class NotificationNS { _server: Server constructor(server: Server) { @@ -1600,7 +1600,7 @@ export class AppBskyNotificationNS { } } -export class AppBskyRichtextNS { +export class RichtextNS { _server: Server constructor(server: Server) { @@ -1608,7 +1608,7 @@ export class AppBskyRichtextNS { } } -export class AppBskyUnspeccedNS { +export class UnspeccedNS { _server: Server constructor(server: Server) { @@ -1682,13 +1682,11 @@ type RouteRateLimitOpts = { calcKey?: (ctx: T) => string calcPoints?: (ctx: T) => number } -type HandlerOpts = { blobLimit?: number } type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts type ConfigOf = | Handler | { auth?: Auth - opts?: HandlerOpts rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[] handler: Handler } diff --git a/packages/pds/src/lexicon/index.ts b/packages/pds/src/lexicon/index.ts index 88b2ad43465..a46c726bcfe 100644 --- a/packages/pds/src/lexicon/index.ts +++ b/packages/pds/src/lexicon/index.ts @@ -167,39 +167,39 @@ export class Server { export class ComNS { _server: Server - atproto: ComAtprotoNS + atproto: AtprotoNS constructor(server: Server) { this._server = server - this.atproto = new ComAtprotoNS(server) + this.atproto = new AtprotoNS(server) } } -export class ComAtprotoNS { +export class AtprotoNS { _server: Server - admin: ComAtprotoAdminNS - identity: ComAtprotoIdentityNS - label: ComAtprotoLabelNS - moderation: ComAtprotoModerationNS - repo: ComAtprotoRepoNS - server: ComAtprotoServerNS - sync: ComAtprotoSyncNS - temp: ComAtprotoTempNS + admin: AdminNS + identity: IdentityNS + label: LabelNS + moderation: ModerationNS + repo: RepoNS + server: ServerNS + sync: SyncNS + temp: TempNS constructor(server: Server) { this._server = server - this.admin = new ComAtprotoAdminNS(server) - this.identity = new ComAtprotoIdentityNS(server) - this.label = new ComAtprotoLabelNS(server) - this.moderation = new ComAtprotoModerationNS(server) - this.repo = new ComAtprotoRepoNS(server) - this.server = new ComAtprotoServerNS(server) - this.sync = new ComAtprotoSyncNS(server) - this.temp = new ComAtprotoTempNS(server) + this.admin = new AdminNS(server) + this.identity = new IdentityNS(server) + this.label = new LabelNS(server) + this.moderation = new ModerationNS(server) + this.repo = new RepoNS(server) + this.server = new ServerNS(server) + this.sync = new SyncNS(server) + this.temp = new TempNS(server) } } -export class ComAtprotoAdminNS { +export class AdminNS { _server: Server constructor(server: Server) { @@ -460,7 +460,7 @@ export class ComAtprotoAdminNS { } } -export class ComAtprotoIdentityNS { +export class IdentityNS { _server: Server constructor(server: Server) { @@ -490,7 +490,7 @@ export class ComAtprotoIdentityNS { } } -export class ComAtprotoLabelNS { +export class LabelNS { _server: Server constructor(server: Server) { @@ -520,7 +520,7 @@ export class ComAtprotoLabelNS { } } -export class ComAtprotoModerationNS { +export class ModerationNS { _server: Server constructor(server: Server) { @@ -539,7 +539,7 @@ export class ComAtprotoModerationNS { } } -export class ComAtprotoRepoNS { +export class RepoNS { _server: Server constructor(server: Server) { @@ -635,7 +635,7 @@ export class ComAtprotoRepoNS { } } -export class ComAtprotoServerNS { +export class ServerNS { _server: Server constructor(server: Server) { @@ -874,7 +874,7 @@ export class ComAtprotoServerNS { } } -export class ComAtprotoSyncNS { +export class SyncNS { _server: Server constructor(server: Server) { @@ -1014,7 +1014,7 @@ export class ComAtprotoSyncNS { } } -export class ComAtprotoTempNS { +export class TempNS { _server: Server constructor(server: Server) { @@ -1090,37 +1090,37 @@ export class ComAtprotoTempNS { export class AppNS { _server: Server - bsky: AppBskyNS + bsky: BskyNS constructor(server: Server) { this._server = server - this.bsky = new AppBskyNS(server) + this.bsky = new BskyNS(server) } } -export class AppBskyNS { +export class BskyNS { _server: Server - actor: AppBskyActorNS - embed: AppBskyEmbedNS - feed: AppBskyFeedNS - graph: AppBskyGraphNS - notification: AppBskyNotificationNS - richtext: AppBskyRichtextNS - unspecced: AppBskyUnspeccedNS + actor: ActorNS + embed: EmbedNS + feed: FeedNS + graph: GraphNS + notification: NotificationNS + richtext: RichtextNS + unspecced: UnspeccedNS constructor(server: Server) { this._server = server - this.actor = new AppBskyActorNS(server) - this.embed = new AppBskyEmbedNS(server) - this.feed = new AppBskyFeedNS(server) - this.graph = new AppBskyGraphNS(server) - this.notification = new AppBskyNotificationNS(server) - this.richtext = new AppBskyRichtextNS(server) - this.unspecced = new AppBskyUnspeccedNS(server) + this.actor = new ActorNS(server) + this.embed = new EmbedNS(server) + this.feed = new FeedNS(server) + this.graph = new GraphNS(server) + this.notification = new NotificationNS(server) + this.richtext = new RichtextNS(server) + this.unspecced = new UnspeccedNS(server) } } -export class AppBskyActorNS { +export class ActorNS { _server: Server constructor(server: Server) { @@ -1205,7 +1205,7 @@ export class AppBskyActorNS { } } -export class AppBskyEmbedNS { +export class EmbedNS { _server: Server constructor(server: Server) { @@ -1213,7 +1213,7 @@ export class AppBskyEmbedNS { } } -export class AppBskyFeedNS { +export class FeedNS { _server: Server constructor(server: Server) { @@ -1397,7 +1397,7 @@ export class AppBskyFeedNS { } } -export class AppBskyGraphNS { +export class GraphNS { _server: Server constructor(server: Server) { @@ -1548,7 +1548,7 @@ export class AppBskyGraphNS { } } -export class AppBskyNotificationNS { +export class NotificationNS { _server: Server constructor(server: Server) { @@ -1600,7 +1600,7 @@ export class AppBskyNotificationNS { } } -export class AppBskyRichtextNS { +export class RichtextNS { _server: Server constructor(server: Server) { @@ -1608,7 +1608,7 @@ export class AppBskyRichtextNS { } } -export class AppBskyUnspeccedNS { +export class UnspeccedNS { _server: Server constructor(server: Server) { @@ -1682,13 +1682,11 @@ type RouteRateLimitOpts = { calcKey?: (ctx: T) => string calcPoints?: (ctx: T) => number } -type HandlerOpts = { blobLimit?: number } type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts type ConfigOf = | Handler | { auth?: Auth - opts?: HandlerOpts rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[] handler: Handler } From a2aa60e98db4e5b796912419f6a5e1c2384e5630 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 23 Jan 2024 20:00:50 -0600 Subject: [PATCH 09/21] tests --- .../api/com/atproto/temp/checkSignupQueue.ts | 2 +- packages/pds/src/signup-queue/index.ts | 3 + packages/pds/tests/signup-queue.test.ts | 226 ++++++++++++++++++ 3 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 packages/pds/src/signup-queue/index.ts create mode 100644 packages/pds/tests/signup-queue.test.ts diff --git a/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts b/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts index 6c6d201deff..cb58726b9a7 100644 --- a/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts +++ b/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts @@ -29,7 +29,7 @@ export default function (server: Server, ctx: AppContext) { let estimatedTimeMs: number | undefined if ( placeInQueue && - limiterStatus.disableSignups && + !limiterStatus.disableSignups && limiterStatus.accountsInPeriod > 0 ) { estimatedTimeMs = diff --git a/packages/pds/src/signup-queue/index.ts b/packages/pds/src/signup-queue/index.ts new file mode 100644 index 00000000000..10f307808ba --- /dev/null +++ b/packages/pds/src/signup-queue/index.ts @@ -0,0 +1,3 @@ +export * from './activator' +export * from './limiter' +export * from './util' diff --git a/packages/pds/tests/signup-queue.test.ts b/packages/pds/tests/signup-queue.test.ts new file mode 100644 index 00000000000..ff5f086c9f0 --- /dev/null +++ b/packages/pds/tests/signup-queue.test.ts @@ -0,0 +1,226 @@ +import { once } from 'events' +import AtpAgent, { ComAtprotoServerResetPassword } from '@atproto/api' +import * as crypto from '@atproto/crypto' +import { SeedClient, TestNetworkNoAppView } from '@atproto/dev-env' +import Mail from 'nodemailer/lib/mailer' +import { AppContext } from '../src' +import { + DISABLE_SIGNUPS_FLAG, + PERIOD_ALLOWANCE_FLAG, + PERIOD_MS_FLAG, +} from '../src/signup-queue' +import { DAY } from '@atproto/common' +import assert from 'assert' +import { create } from 'domain' + +describe('signup queue', () => { + let network: TestNetworkNoAppView + let ctx: AppContext + let agent: AtpAgent + let sc: SeedClient + + beforeAll(async () => { + network = await TestNetworkNoAppView.create({ + dbPostgresSchema: 'signup_queue', + }) + ctx = network.pds.ctx + agent = network.pds.getClient() + sc = network.getSeedClient() + + await ctx.db.db + .insertInto('runtime_flag') + .values([ + { + name: DISABLE_SIGNUPS_FLAG, + value: 'false', + }, + { + name: PERIOD_ALLOWANCE_FLAG, + value: '2', + }, + { + name: PERIOD_MS_FLAG, + value: DAY.toString(), + }, + ]) + .execute() + await ctx.signupLimiter.refresh() + }) + + afterAll(async () => { + await network.close() + }) + + const createAccount = (name: string) => { + return sc.createAccount(name, { + handle: `${name}.test`, + email: `${name}@test.com`, + password: `${name}-pass`, + }) + } + + let deactivated: string + + it('does not activate accounts that exceed signups', async () => { + const one = await createAccount('one') + await ctx.signupLimiter.refresh() + const two = await createAccount('two') + await ctx.signupLimiter.refresh() + const three = await createAccount('three') + await ctx.signupLimiter.refresh() + await ctx.signupActivator.activateBatch() + deactivated = three.did + + const accounts = await ctx.db.db + .selectFrom('user_account') + .selectAll() + .where('did', 'in', [one.did, two.did, three.did]) + .execute() + const oneRow = accounts.find((row) => row.did === one.did) + assert(oneRow) + const twoRow = accounts.find((row) => row.did === two.did) + assert(twoRow) + const threeRow = accounts.find((row) => row.did === three.did) + assert(threeRow) + + expect(oneRow.activatedAt).toEqual(oneRow.createdAt) + expect(twoRow.activatedAt).toEqual(twoRow.createdAt) + expect(threeRow.activatedAt).toBeNull() + }) + + it('allows an account to check their status in the queue', async () => { + const four = await createAccount('four') + await ctx.signupLimiter.refresh() + await ctx.signupActivator.activateBatch() + + const res = await agent.api.com.atproto.temp.checkSignupQueue(undefined, { + headers: sc.getHeaders(four.did), + }) + expect(res.data.activated).toBe(false) + expect(res.data.placeInQueue).toBe(1) + expect(res.data.estimatedTimeMs).toBe(0.5 * DAY) + }) + + it('does not allow an account to perform other actions on their pds', async () => { + const five = await createAccount('five') + await ctx.signupLimiter.refresh() + await ctx.signupActivator.activateBatch() + + await agent.api + const attempt1 = agent.api.app.bsky.feed.getTimeline( + {}, + { + headers: sc.getHeaders(five.did), + }, + ) + await expect(attempt1).rejects.toThrow('Bad token scope') + + const attempt2 = agent.api.app.bsky.actor.getPreferences( + {}, + { + headers: sc.getHeaders(five.did), + }, + ) + await expect(attempt2).rejects.toThrow('Bad token scope') + + const attempt3 = agent.api.app.bsky.feed.post.create( + { repo: five.did }, + { + text: 'test', + createdAt: new Date().toISOString(), + }, + sc.getHeaders(five.did), + ) + await expect(attempt3).rejects.toThrow('Bad token scope') + }) + + it('returns a deactived access token on refresh as well', async () => { + const six = await createAccount('six') + await ctx.signupLimiter.refresh() + await ctx.signupActivator.activateBatch() + + const refreshed = await agent.api.com.atproto.server.refreshSession( + undefined, + { + headers: { authorization: `Bearer ${six.refreshJwt}` }, + }, + ) + + const attempt = agent.api.app.bsky.feed.getTimeline( + {}, + { + headers: { authorization: `Bearer ${refreshed.data.accessJwt}` }, + }, + ) + await expect(attempt).rejects.toThrow('Bad token scope') + }) + + it('automatically activates accounts when its their time', async () => { + await ctx.db.db + .updateTable('runtime_flag') + .set({ value: '100' }) + .where('name', '=', PERIOD_ALLOWANCE_FLAG) + .execute() + await ctx.signupLimiter.refresh() + await ctx.signupActivator.activateBatch() + + const res = await ctx.db.db + .selectFrom('user_account') + .selectAll() + .where('activatedAt', 'is', null) + .execute() + expect(res.length).toBe(0) + }) + + it('returns a working access token on refresh after activation', async () => { + const deactivatedRefreshToken = sc.accounts[deactivated].refreshJwt + const refreshed = await agent.api.com.atproto.server.refreshSession( + undefined, + { + headers: { authorization: `Bearer ${deactivatedRefreshToken}` }, + }, + ) + + await agent.api.app.bsky.actor.getPreferences( + {}, + { + headers: { authorization: `Bearer ${refreshed.data.accessJwt}` }, + }, + ) + }) + + it('disables and reenables signups entirely', async () => { + await ctx.db.db + .updateTable('runtime_flag') + .set({ value: 'true' }) + .where('name', '=', DISABLE_SIGNUPS_FLAG) + .execute() + await ctx.signupLimiter.refresh() + const seven = await createAccount('seven') + await ctx.signupActivator.activateBatch() + + const accountRowBefore = await ctx.db.db + .selectFrom('user_account') + .selectAll() + .where('did', '=', seven.did) + .executeTakeFirst() + assert(accountRowBefore) + expect(accountRowBefore.activatedAt).toBeNull() + + await ctx.db.db + .updateTable('runtime_flag') + .set({ value: 'false' }) + .where('name', '=', DISABLE_SIGNUPS_FLAG) + .execute() + await ctx.signupLimiter.refresh() + await ctx.signupActivator.activateBatch() + + const accountRowAfter = await ctx.db.db + .selectFrom('user_account') + .selectAll() + .where('did', '=', seven.did) + .executeTakeFirst() + assert(accountRowAfter) + expect(typeof accountRowAfter.activatedAt).toBe('string') + }) +}) From 071d0fcbb4ed14130df8bb06f9a8e5a221a253b3 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 23 Jan 2024 20:22:03 -0600 Subject: [PATCH 10/21] tidy test + dev-env mock --- packages/dev-env/src/mock/index.ts | 17 +++++++++++++++++ packages/pds/tests/signup-queue.test.ts | 6 +----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/dev-env/src/mock/index.ts b/packages/dev-env/src/mock/index.ts index 93b2f4717c4..acd02732ef4 100644 --- a/packages/dev-env/src/mock/index.ts +++ b/packages/dev-env/src/mock/index.ts @@ -347,6 +347,23 @@ export async function generateMockSetup(env: TestNetwork) { createdAt: date.next().value, }, ) + + // uncomment to enable signup limits + // the following parameters for instance, will allow 2 signups after minute + // await env.pds.ctx.db.db + // .insertInto('runtime_flag') + // .values([ + // { + // name: 'signup-limiter:periodMs', + // value: (1000 * 60).toString(), + // }, + // { + // name: 'signup-limiter:periodAllowance', + // value: '2', + // }, + // ]) + // .execute() + // await env.pds.ctx.signupLimiter.refresh() } function ucfirst(str: string): string { diff --git a/packages/pds/tests/signup-queue.test.ts b/packages/pds/tests/signup-queue.test.ts index ff5f086c9f0..c2d0f082373 100644 --- a/packages/pds/tests/signup-queue.test.ts +++ b/packages/pds/tests/signup-queue.test.ts @@ -1,8 +1,5 @@ -import { once } from 'events' -import AtpAgent, { ComAtprotoServerResetPassword } from '@atproto/api' -import * as crypto from '@atproto/crypto' +import AtpAgent from '@atproto/api' import { SeedClient, TestNetworkNoAppView } from '@atproto/dev-env' -import Mail from 'nodemailer/lib/mailer' import { AppContext } from '../src' import { DISABLE_SIGNUPS_FLAG, @@ -11,7 +8,6 @@ import { } from '../src/signup-queue' import { DAY } from '@atproto/common' import assert from 'assert' -import { create } from 'domain' describe('signup queue', () => { let network: TestNetworkNoAppView From 65c7738fbd5695ad9921373e3602e0e62264fc19 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Wed, 24 Jan 2024 16:50:45 -0800 Subject: [PATCH 11/21] Expose refreshSession on the agent --- packages/api/src/agent.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/src/agent.ts b/packages/api/src/agent.ts index de80c9de07d..c6cc699cb0c 100644 --- a/packages/api/src/agent.ts +++ b/packages/api/src/agent.ts @@ -214,7 +214,7 @@ export class AtpAgent { // handle session-refreshes as needed if (isErrorResponse(res, ['ExpiredToken']) && this.session?.refreshJwt) { // attempt refresh - await this._refreshSession() + await this.refreshSession() // resend the request with the new access token res = await AtpAgent.fetch( @@ -233,7 +233,7 @@ export class AtpAgent { * - Wraps the actual implementation in a promise-guard to ensure only * one refresh is attempted at a time. */ - private async _refreshSession() { + async refreshSession() { if (this._refreshSessionPromise) { return this._refreshSessionPromise } From 52d5720171cc738590420a04051884c95c06e1e4 Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 24 Jan 2024 19:08:57 -0600 Subject: [PATCH 12/21] couple more routes for deactivated accounts --- packages/pds/src/api/app/bsky/notification/registerPush.ts | 2 +- packages/pds/src/api/com/atproto/server/getSession.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pds/src/api/app/bsky/notification/registerPush.ts b/packages/pds/src/api/app/bsky/notification/registerPush.ts index 97fd40a8b7b..6d4174a2202 100644 --- a/packages/pds/src/api/app/bsky/notification/registerPush.ts +++ b/packages/pds/src/api/app/bsky/notification/registerPush.ts @@ -8,7 +8,7 @@ import { authPassthru, proxy, proxyAppView } from '../../../proxy' export default function (server: Server, ctx: AppContext) { server.app.bsky.notification.registerPush({ - auth: ctx.authVerifier.access, + auth: ctx.authVerifier.accessDeactived, handler: async ({ auth, input, req }) => { const proxied = await proxy( ctx, diff --git a/packages/pds/src/api/com/atproto/server/getSession.ts b/packages/pds/src/api/com/atproto/server/getSession.ts index e798c48a1b9..312274eeea9 100644 --- a/packages/pds/src/api/com/atproto/server/getSession.ts +++ b/packages/pds/src/api/com/atproto/server/getSession.ts @@ -5,7 +5,7 @@ import { didDocForSession } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.server.getSession({ - auth: ctx.authVerifier.access, + auth: ctx.authVerifier.accessDeactived, handler: async ({ auth }) => { const did = auth.credentials.did const [account, didDoc] = await Promise.all([ From 48b07312bb247ab68e793996703c1c7fd596bf5d Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 24 Jan 2024 20:27:48 -0600 Subject: [PATCH 13/21] typo --- packages/pds/src/auth-verifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pds/src/auth-verifier.ts b/packages/pds/src/auth-verifier.ts index 647ef359e42..7640ce0d2f3 100644 --- a/packages/pds/src/auth-verifier.ts +++ b/packages/pds/src/auth-verifier.ts @@ -29,7 +29,7 @@ export enum AuthScope { Access = 'com.atproto.access', Refresh = 'com.atproto.refresh', AppPass = 'com.atproto.appPass', - Deactivated = 'com.atproto.deactived', + Deactivated = 'com.atproto.deactivated', } export enum RoleStatus { From 085bc87954769f2d71f3f7f4fb813b5d820def5c Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 30 Jan 2024 17:06:42 -0600 Subject: [PATCH 14/21] build branch --- .github/workflows/build-and-push-pds-aws.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-push-pds-aws.yaml b/.github/workflows/build-and-push-pds-aws.yaml index 097f782d88e..959dbfd7e94 100644 --- a/.github/workflows/build-and-push-pds-aws.yaml +++ b/.github/workflows/build-and-push-pds-aws.yaml @@ -3,6 +3,7 @@ on: push: branches: - main + - signup-queueing-take2 env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }} From 5f830cb585713e405c1aca92d24a50fe359cf49a Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 30 Jan 2024 17:21:34 -0600 Subject: [PATCH 15/21] run activator --- packages/pds/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/pds/src/index.ts b/packages/pds/src/index.ts index 0d7e4fbbc4b..818584bb03a 100644 --- a/packages/pds/src/index.ts +++ b/packages/pds/src/index.ts @@ -164,6 +164,7 @@ export class PDS { await this.ctx.db.startListeningToChannels() await this.ctx.runtimeFlags.start() await this.ctx.signupLimiter.start() + await this.ctx.signupActivator.run() const server = this.app.listen(this.ctx.cfg.service.port) this.server = server this.server.keepAliveTimeout = 90000 @@ -175,6 +176,7 @@ export class PDS { async destroy(): Promise { await this.ctx.runtimeFlags.destroy() await this.ctx.signupLimiter.destroy() + await this.ctx.signupActivator.run() await this.ctx.sequencerLeader?.destroy() await this.terminator?.terminate() await this.ctx.backgroundQueue.destroy() From 64ff83aa11af5de6acfb9e715dbcddd87a7c0e5e Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 30 Jan 2024 17:22:09 -0600 Subject: [PATCH 16/21] dont run activator in dev-env --- packages/dev-env/src/pds.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dev-env/src/pds.ts b/packages/dev-env/src/pds.ts index e9b7cd7c692..979f40e8048 100644 --- a/packages/dev-env/src/pds.ts +++ b/packages/dev-env/src/pds.ts @@ -57,6 +57,7 @@ export class TestPds { const secrets = pds.envToSecrets(env) const server = await pds.PDS.create(cfg, secrets) + await server.ctx.signupActivator.destroy() // Separate migration db on postgres in case migration changes some // connection state that we need in the tests, e.g. "alter database ... set ..." From e7bd3ff97d73ff0b7c1b850ee6389fb388e568bc Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 30 Jan 2024 17:36:51 -0600 Subject: [PATCH 17/21] run activator async --- packages/pds/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pds/src/index.ts b/packages/pds/src/index.ts index 818584bb03a..b384c165e40 100644 --- a/packages/pds/src/index.ts +++ b/packages/pds/src/index.ts @@ -164,7 +164,7 @@ export class PDS { await this.ctx.db.startListeningToChannels() await this.ctx.runtimeFlags.start() await this.ctx.signupLimiter.start() - await this.ctx.signupActivator.run() + this.ctx.signupActivator.run() const server = this.app.listen(this.ctx.cfg.service.port) this.server = server this.server.keepAliveTimeout = 90000 From 258734b51125a6827fe763a4b38e67d3d57cf4f2 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 30 Jan 2024 18:07:02 -0600 Subject: [PATCH 18/21] round estimated time --- packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts b/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts index cb58726b9a7..2a94c4fa558 100644 --- a/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts +++ b/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts @@ -32,9 +32,10 @@ export default function (server: Server, ctx: AppContext) { !limiterStatus.disableSignups && limiterStatus.accountsInPeriod > 0 ) { - estimatedTimeMs = + estimatedTimeMs = Math.ceil( (placeInQueue * limiterStatus.periodMs) / - limiterStatus.accountsInPeriod + limiterStatus.accountsInPeriod, + ) } return { From 6c0571d5029271400a822c4a0720d73a96a04dbc Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 30 Jan 2024 18:48:02 -0600 Subject: [PATCH 19/21] logging --- packages/pds/src/signup-queue/activator.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/pds/src/signup-queue/activator.ts b/packages/pds/src/signup-queue/activator.ts index 99cd05c1e4e..71842243725 100644 --- a/packages/pds/src/signup-queue/activator.ts +++ b/packages/pds/src/signup-queue/activator.ts @@ -79,8 +79,10 @@ export class SignupActivator { async activateBatch() { const status = await getQueueStatus(this.db) - if (status.disableSignups) return const toAdmit = status.periodAllowance - status.accountsInPeriod + log.info({ ...status, toAdmit }, 'activating accounts') + + if (status.disableSignups) return if (toAdmit < 1) return const activatedAt = new Date().toISOString() From c1bbd68f57a6830eb44d5fc72490c5b9740d72e0 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 30 Jan 2024 19:10:11 -0600 Subject: [PATCH 20/21] real time limiter --- .../api/com/atproto/server/createAccount.ts | 2 +- .../api/com/atproto/temp/checkSignupQueue.ts | 18 ++++++++---------- packages/pds/src/signup-queue/limiter.ts | 19 ++++++++++++------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/pds/src/api/com/atproto/server/createAccount.ts b/packages/pds/src/api/com/atproto/server/createAccount.ts index 1b322f76304..3064e6dd541 100644 --- a/packages/pds/src/api/com/atproto/server/createAccount.ts +++ b/packages/pds/src/api/com/atproto/server/createAccount.ts @@ -28,7 +28,7 @@ export default function (server: Server, ctx: AppContext) { points: 100, }, handler: async ({ input, req }) => { - const hasAvailability = ctx.signupLimiter.hasAvailability() + const hasAvailability = await ctx.signupLimiter.hasAvailability() const { did, diff --git a/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts b/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts index 2a94c4fa558..a71fe5d1dae 100644 --- a/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts +++ b/packages/pds/src/api/com/atproto/temp/checkSignupQueue.ts @@ -25,17 +25,15 @@ export default function (server: Server, ctx: AppContext) { placeInQueue = res?.count } - const limiterStatus = ctx.signupLimiter.status + const limiter = ctx.signupLimiter let estimatedTimeMs: number | undefined - if ( - placeInQueue && - !limiterStatus.disableSignups && - limiterStatus.accountsInPeriod > 0 - ) { - estimatedTimeMs = Math.ceil( - (placeInQueue * limiterStatus.periodMs) / - limiterStatus.accountsInPeriod, - ) + if (placeInQueue && !limiter.flags.disableSignups) { + const accountsInPeriod = await limiter.accountsInPeriod() + if (accountsInPeriod > 0) { + estimatedTimeMs = Math.ceil( + (placeInQueue * limiter.flags.periodMs) / accountsInPeriod, + ) + } } return { diff --git a/packages/pds/src/signup-queue/limiter.ts b/packages/pds/src/signup-queue/limiter.ts index 934d04a73da..d8a3945931e 100644 --- a/packages/pds/src/signup-queue/limiter.ts +++ b/packages/pds/src/signup-queue/limiter.ts @@ -1,19 +1,20 @@ import { SECOND } from '@atproto/common' import { limiterLogger as log } from '../logger' import Database from '../db' -import { LimiterStatus, getQueueStatus } from './util' +import { LimiterFlags, getAccountsInPeriod, getQueueStatus } from './util' export class SignupLimiter { destroyed = false promise: Promise = Promise.resolve() timer: NodeJS.Timer | undefined - status: LimiterStatus + flags: LimiterFlags constructor(private db: Database) {} - hasAvailability(): boolean { - if (this.status.disableSignups) return false - return this.status.accountsInPeriod < this.status.periodAllowance + async hasAvailability(): Promise { + if (this.flags.disableSignups) return false + const accountsInPeriod = await this.accountsInPeriod() + return accountsInPeriod < this.flags.periodAllowance } async start() { @@ -38,9 +39,13 @@ export class SignupLimiter { await this.promise } + async accountsInPeriod(): Promise { + return getAccountsInPeriod(this.db, this.flags.periodMs) + } + async refresh() { - this.status = await getQueueStatus(this.db) + this.flags = await getQueueStatus(this.db) - log.info({ ...this.status }, 'limiter refresh') + log.info({ ...this.flags }, 'limiter refresh') } } From 1f64e22b5ed521f4eb05aeba16386098ab55adc1 Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 31 Jan 2024 21:00:49 -0600 Subject: [PATCH 21/21] fix test --- packages/pds/tests/entryway.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/pds/tests/entryway.test.ts b/packages/pds/tests/entryway.test.ts index 40ef7b191d5..5643ec29fff 100644 --- a/packages/pds/tests/entryway.test.ts +++ b/packages/pds/tests/entryway.test.ts @@ -547,6 +547,7 @@ describe('entryway', () => { did: dan, pdsDid: pds.ctx.cfg.service.did, appPasswordName: null, + deactivated: false, }) const expiredAccessToken = await authService.createAccessToken({ did: dan, @@ -580,6 +581,7 @@ describe('entryway', () => { did: session.did, pdsDid: pds.ctx.cfg.service.did, // not eve's pds appPasswordName: null, + deactivated: false, }) const attempt = (agent: AtpAgent) => agent.api.app.bsky.actor.putPreferences(