Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Signup queue notifications #2115

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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-and-push-pds-aws.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
push:
branches:
- main
- signup-queueing-take2
- signup-queue-notify
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }}
Expand Down
12 changes: 12 additions & 0 deletions packages/pds/buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: v1
plugins:
- plugin: es
opt:
- target=ts
- import_extension=.ts
out: src/proto
- plugin: connect-es
opt:
- target=ts
- import_extension=.ts
out: src/proto
10 changes: 9 additions & 1 deletion packages/pds/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"test:sqlite": "jest --testPathIgnorePatterns /tests/proxied/*",
"test:log": "tail -50 test.log | pino-pretty",
"test:updateSnapshot": "jest --updateSnapshot",
"migration:create": "ts-node ./bin/migration-create.ts"
"migration:create": "ts-node ./bin/migration-create.ts",
"buf:gen": "buf generate ./proto"
},
"dependencies": {
"@atproto/api": "workspace:^",
Expand All @@ -42,6 +43,9 @@
"@atproto/syntax": "workspace:^",
"@atproto/xrpc": "workspace:^",
"@atproto/xrpc-server": "workspace:^",
"@bufbuild/protobuf": "^1.5.0",
"@connectrpc/connect": "^1.1.4",
"@connectrpc/connect-node": "^1.1.4",
"@did-plc/lib": "^0.0.1",
"better-sqlite3": "^7.6.2",
"bytes": "^3.1.2",
Expand All @@ -66,6 +70,7 @@
"pg": "^8.10.0",
"pino": "^8.15.0",
"pino-http": "^8.2.1",
"rate-limiter-flexible": "^2.4.1",
"sharp": "^0.32.6",
"twilio": "^4.20.1",
"typed-emitter": "^2.1.0",
Expand All @@ -77,6 +82,9 @@
"@atproto/bsky": "workspace:^",
"@atproto/dev-env": "workspace:^",
"@atproto/lex-cli": "workspace:^",
"@bufbuild/buf": "^1.28.1",
"@bufbuild/protoc-gen-es": "^1.5.0",
"@connectrpc/protoc-gen-connect-es": "^1.1.4",
"@did-plc/server": "^0.0.1",
"@types/cors": "^2.8.12",
"@types/disposable-email": "^0.2.0",
Expand Down
56 changes: 56 additions & 0 deletions packages/pds/proto/courier.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
syntax = "proto3";

package courier;
option go_package = "./;courier";

import "google/protobuf/struct.proto";
import "google/protobuf/timestamp.proto";

//
// Messages
//

// Ping
message PingRequest {}
message PingResponse {}

// Notifications

enum AppPlatform {
APP_PLATFORM_UNSPECIFIED = 0;
APP_PLATFORM_IOS = 1;
APP_PLATFORM_ANDROID = 2;
APP_PLATFORM_WEB = 3;
}

message Notification {
string id = 1;
string recipient_did = 2;
string title = 3;
string message = 4;
string collapse_key = 5;
bool always_deliver = 6;
google.protobuf.Timestamp timestamp = 7;
google.protobuf.Struct additional = 8;
}

message PushNotificationsRequest {
repeated Notification notifications = 1;
}

message PushNotificationsResponse {}

message RegisterDeviceTokenRequest {
string did = 1;
string token = 2;
string app_id = 3;
AppPlatform platform = 4;
}

message RegisterDeviceTokenResponse {}

service Service {
rpc Ping(PingRequest) returns (PingResponse);
rpc PushNotifications(PushNotificationsRequest) returns (PushNotificationsResponse);
rpc RegisterDeviceToken(RegisterDeviceTokenRequest) returns (RegisterDeviceTokenResponse);
}
20 changes: 20 additions & 0 deletions packages/pds/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,16 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
}
: { enabled: false }

const courierHttpVersion = env.courierHttpVersion ?? '2'
assert(courierHttpVersion === '1.1' || courierHttpVersion === '2')
const activatorCfg: ServerConfig['activator'] = {
courierUrl: env.courierUrl,
courierHttpVersion,
courierIgnoreBadTls: env.courierIgnoreBadTls,
courierApiKey: env.courierApiKey,
emailsPerDay: env.activatorEmailsPerDay,
}

const crawlersCfg: ServerConfig['crawlers'] = env.crawlers ?? []

return {
Expand All @@ -227,6 +237,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
modService: modServiceCfg,
redis: redisCfg,
rateLimits: rateLimitsCfg,
activator: activatorCfg,
crawlers: crawlersCfg,
}
}
Expand All @@ -245,6 +256,7 @@ export type ServerConfig = {
modService: ModServiceConfig
redis: RedisScratchConfig | null
rateLimits: RateLimitsConfig
activator: ActivatorConfig
crawlers: string[]
}

Expand Down Expand Up @@ -355,6 +367,14 @@ export type RateLimitsConfig =
}
| { enabled: false }

export type ActivatorConfig = {
courierUrl?: string
courierHttpVersion?: '1.1' | '2'
courierIgnoreBadTls?: boolean
courierApiKey?: string
emailsPerDay?: number
}

export type BksyAppViewConfig = {
url: string
did: string
Expand Down
14 changes: 14 additions & 0 deletions packages/pds/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ export const readEnv = (): ServerEnvironment => {
redisScratchAddress: envStr('PDS_REDIS_SCRATCH_ADDRESS'),
redisScratchPassword: envStr('PDS_REDIS_SCRATCH_PASSWORD'),

// activator
courierUrl: envStr('PDS_COURIER_URL'),
courierHttpVersion: envStr('PDS_COURIER_HTTP_VERSION'),
courierIgnoreBadTls: envBool('PDS_COURIER_IGNORE_BAD_TLS'),
courierApiKey: envStr('PDS_COURIER_API_KEY'),
activatorEmailsPerDay: envInt('PDS_ACTIVATOR_EMAILS_PER_DAY'),

// crawlers
crawlers: envList('PDS_CRAWLERS'),

Expand Down Expand Up @@ -202,6 +209,13 @@ export type ServerEnvironment = {
redisScratchAddress?: string
redisScratchPassword?: string

// activator
courierUrl?: string
courierHttpVersion?: string
courierIgnoreBadTls?: boolean
courierApiKey?: string
activatorEmailsPerDay?: number

// crawler
crawlers?: string[]

Expand Down
31 changes: 30 additions & 1 deletion packages/pds/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as nodemailer from 'nodemailer'
import { Redis } from 'ioredis'
import { RateLimiterRedis } from 'rate-limiter-flexible'
import * as plc from '@did-plc/lib'
import * as crypto from '@atproto/crypto'
import { IdResolver } from '@atproto/identity'
Expand All @@ -25,6 +26,8 @@ import { TwilioClient } from './twilio'
import assert from 'assert'
import { SignupLimiter } from './signup-queue/limiter'
import { SignupActivator } from './signup-queue/activator'
import { createCourierClient, authWithApiKey as courierAuth } from './courier'
import { DAY } from '@atproto/common'

export type AppContextOptions = {
db: Database
Expand Down Expand Up @@ -231,7 +234,33 @@ export class AppContext {
}

const signupLimiter = new SignupLimiter(db)
const signupActivator = new SignupActivator(db)
const courierClient = cfg.activator.courierUrl
? createCourierClient({
baseUrl: cfg.activator.courierUrl,
httpVersion: cfg.activator.courierHttpVersion ?? '2',
nodeOptions: {
rejectUnauthorized: !cfg.activator.courierIgnoreBadTls,
},
interceptors: cfg.activator.courierApiKey
? [courierAuth(cfg.activator.courierApiKey)]
: [],
})
: undefined
const limiter =
cfg.activator.emailsPerDay && redisScratch
? new RateLimiterRedis({
storeClient: redisScratch,
duration: DAY / 1000,
points: cfg.activator.emailsPerDay,
})
: undefined

const signupActivator = new SignupActivator({
db,
mailer,
courierClient,
limiter,
})

const pdsAgents = new PdsAgents()

Expand Down
41 changes: 41 additions & 0 deletions packages/pds/src/courier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Service } from './proto/courier_connect'
import {
Code,
ConnectError,
PromiseClient,
createPromiseClient,
Interceptor,
} from '@connectrpc/connect'
import {
createConnectTransport,
ConnectTransportOptions,
} from '@connectrpc/connect-node'

export type CourierClient = PromiseClient<typeof Service>

export const createCourierClient = (
opts: ConnectTransportOptions,
): CourierClient => {
const transport = createConnectTransport(opts)
return createPromiseClient(Service, transport)
}

export { Code }

export const isCourierError = (
err: unknown,
code?: Code,
): err is ConnectError => {
if (err instanceof ConnectError) {
return !code || err.code === code
}
return false
}

export const authWithApiKey =
(apiKey: string): Interceptor =>
(next) =>
(req) => {
req.header.set('authorization', `Bearer ${apiKey}`)
return next(req)
}
11 changes: 11 additions & 0 deletions packages/pds/src/mailer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class ServerMailer {
deleteAccount: this.compile('delete-account'),
confirmEmail: this.compile('confirm-email'),
updateEmail: this.compile('update-email'),
accountActivated: this.compile('account-activated'),
}
}

Expand Down Expand Up @@ -65,6 +66,16 @@ export class ServerMailer {
})
}

async sendAccountActivated(
params: { handle: string },
mailOpts: Mail.Options,
) {
return this.sendTemplate('accountActivated', params, {
subject: 'Your Bluesky Account is Activated!',
...mailOpts,
})
}

private async sendTemplate(templateName, params, mailOpts: Mail.Options) {
const html = this.templates[templateName]({
...params,
Expand Down
Loading
Loading