From aaee2d0dc212e622b123f25d9770a10e4edcd9c4 Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Sat, 3 Feb 2024 19:06:07 -0600 Subject: [PATCH] Email rate limits followup (#2133) email rate limits followup --- .../atproto/server/requestAccountDelete.ts | 13 +++++ .../server/requestEmailConfirmation.ts | 13 +++++ .../com/atproto/server/requestEmailUpdate.ts | 13 +++++ .../atproto/server/requestPasswordReset.ts | 53 ++++++++++++------- 4 files changed, 72 insertions(+), 20 deletions(-) diff --git a/packages/pds/src/api/com/atproto/server/requestAccountDelete.ts b/packages/pds/src/api/com/atproto/server/requestAccountDelete.ts index 2566682d244..e6ab60ce74b 100644 --- a/packages/pds/src/api/com/atproto/server/requestAccountDelete.ts +++ b/packages/pds/src/api/com/atproto/server/requestAccountDelete.ts @@ -1,3 +1,4 @@ +import { DAY, HOUR } from '@atproto/common' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' @@ -5,6 +6,18 @@ import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { server.com.atproto.server.requestAccountDelete({ + rateLimit: [ + { + durationMs: DAY, + points: 15, + calcKey: ({ auth }) => auth.credentials.did, + }, + { + durationMs: HOUR, + points: 5, + calcKey: ({ auth }) => auth.credentials.did, + }, + ], auth: ctx.authVerifier.accessCheckTakedown, handler: async ({ auth, req }) => { const did = auth.credentials.did diff --git a/packages/pds/src/api/com/atproto/server/requestEmailConfirmation.ts b/packages/pds/src/api/com/atproto/server/requestEmailConfirmation.ts index 76703d0d6d2..ba031efab3f 100644 --- a/packages/pds/src/api/com/atproto/server/requestEmailConfirmation.ts +++ b/packages/pds/src/api/com/atproto/server/requestEmailConfirmation.ts @@ -1,3 +1,4 @@ +import { DAY, HOUR } from '@atproto/common' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' @@ -5,6 +6,18 @@ import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { server.com.atproto.server.requestEmailConfirmation({ + rateLimit: [ + { + durationMs: DAY, + points: 15, + calcKey: ({ auth }) => auth.credentials.did, + }, + { + durationMs: HOUR, + points: 5, + calcKey: ({ auth }) => auth.credentials.did, + }, + ], auth: ctx.authVerifier.accessCheckTakedown, handler: async ({ auth, req }) => { const did = auth.credentials.did diff --git a/packages/pds/src/api/com/atproto/server/requestEmailUpdate.ts b/packages/pds/src/api/com/atproto/server/requestEmailUpdate.ts index 696a7d2c018..a2f7675576d 100644 --- a/packages/pds/src/api/com/atproto/server/requestEmailUpdate.ts +++ b/packages/pds/src/api/com/atproto/server/requestEmailUpdate.ts @@ -1,3 +1,4 @@ +import { DAY, HOUR } from '@atproto/common' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' @@ -5,6 +6,18 @@ import { authPassthru, resultPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { server.com.atproto.server.requestEmailUpdate({ + rateLimit: [ + { + durationMs: DAY, + points: 15, + calcKey: ({ auth }) => auth.credentials.did, + }, + { + durationMs: HOUR, + points: 5, + calcKey: ({ auth }) => auth.credentials.did, + }, + ], auth: ctx.authVerifier.accessCheckTakedown, handler: async ({ auth, req }) => { const did = auth.credentials.did diff --git a/packages/pds/src/api/com/atproto/server/requestPasswordReset.ts b/packages/pds/src/api/com/atproto/server/requestPasswordReset.ts index 4afdb0add83..0248f923f4c 100644 --- a/packages/pds/src/api/com/atproto/server/requestPasswordReset.ts +++ b/packages/pds/src/api/com/atproto/server/requestPasswordReset.ts @@ -1,32 +1,45 @@ +import { DAY, HOUR } from '@atproto/common' import { InvalidRequestError } from '@atproto/xrpc-server' import AppContext from '../../../../context' import { Server } from '../../../../lexicon' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { - server.com.atproto.server.requestPasswordReset(async ({ input, req }) => { - const email = input.body.email.toLowerCase() + server.com.atproto.server.requestPasswordReset({ + rateLimit: [ + { + durationMs: DAY, + points: 50, + }, + { + durationMs: HOUR, + points: 15, + }, + ], + handler: async ({ input, req }) => { + const email = input.body.email.toLowerCase() - const account = await ctx.accountManager.getAccountByEmail(email) + const account = await ctx.accountManager.getAccountByEmail(email) - if (!account?.email) { - if (ctx.entrywayAgent) { - await ctx.entrywayAgent.com.atproto.server.requestPasswordReset( - input.body, - authPassthru(req, true), - ) - return + if (!account?.email) { + if (ctx.entrywayAgent) { + await ctx.entrywayAgent.com.atproto.server.requestPasswordReset( + input.body, + authPassthru(req, true), + ) + return + } + throw new InvalidRequestError('account does not have an email address') } - throw new InvalidRequestError('account does not have an email address') - } - const token = await ctx.accountManager.createEmailToken( - account.did, - 'reset_password', - ) - await ctx.mailer.sendResetPassword( - { identifier: account.handle ?? account.email, token }, - { to: account.email }, - ) + const token = await ctx.accountManager.createEmailToken( + account.did, + 'reset_password', + ) + await ctx.mailer.sendResetPassword( + { identifier: account.handle ?? account.email, token }, + { to: account.email }, + ) + }, }) }