Skip to content

Commit

Permalink
chore: adding rate limit on device account creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Burtey committed Nov 18, 2023
1 parent 09de196 commit 3469677
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 18 deletions.
6 changes: 6 additions & 0 deletions core/api/dev/ory/oathkeeper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ mutators:
noop:
enabled: true

header:
enabled: true
config:
headers:
X-User: "{{ print .Subject }}"

errors:
fallback:
- json
Expand Down
7 changes: 7 additions & 0 deletions core/api/src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export const configSchema = {
invoiceCreateAttempt: rateLimitConfigSchema,
invoiceCreateForRecipientAttempt: rateLimitConfigSchema,
onChainAddressCreateAttempt: rateLimitConfigSchema,
deviceAccountCreateAttempt: rateLimitConfigSchema,
},
required: [
"requestCodePerLoginIdentifier",
Expand All @@ -272,6 +273,7 @@ export const configSchema = {
"invoiceCreateAttempt",
"invoiceCreateForRecipientAttempt",
"onChainAddressCreateAttempt",
"deviceAccountCreateAttempt",
],
additionalProperties: false,
default: {
Expand Down Expand Up @@ -310,6 +312,11 @@ export const configSchema = {
duration: 3600,
blockDuration: 14400,
},
deviceAccountCreateAttempt: {
points: 1,
duration: 86400,
blockDuration: 86400,
},
},
},
accounts: {
Expand Down
1 change: 1 addition & 0 deletions core/api/src/config/schema.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type YamlSchema = {
invoiceCreateAttempt: RateLimitInput
invoiceCreateForRecipientAttempt: RateLimitInput
onChainAddressCreateAttempt: RateLimitInput
deviceAccountCreateAttempt: RateLimitInput
}
accounts: {
initialStatus: string
Expand Down
3 changes: 3 additions & 0 deletions core/api/src/config/yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ export const getInvoiceCreateForRecipientAttemptLimits = () =>
export const getOnChainAddressCreateAttemptLimits = () =>
getRateLimits(yamlConfig.rateLimits.onChainAddressCreateAttempt)

export const getDeviceAccountCreateAttemptLimits = () =>
getRateLimits(yamlConfig.rateLimits.deviceAccountCreateAttempt)

export const getOnChainWalletConfig = () => ({
dustThreshold: yamlConfig.onChainWallet.dustThreshold,
})
Expand Down
1 change: 1 addition & 0 deletions core/api/src/domain/authentication/index.types.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
type SessionId = string & { readonly brand: unique symbol }
type AppcheckJti = string & { readonly brand: unique symbol }

type AuthenticationError = import("./errors").AuthenticationError

Expand Down
1 change: 1 addition & 0 deletions core/api/src/domain/rate-limit/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export class UserLoginIdentifierRateLimiterExceededError extends RateLimiterExce
export class InvoiceCreateRateLimiterExceededError extends RateLimiterExceededError {}
export class InvoiceCreateForRecipientRateLimiterExceededError extends RateLimiterExceededError {}
export class OnChainAddressCreateRateLimiterExceededError extends RateLimiterExceededError {}
export class DeviceAccountCreateRateLimiterExceededError extends RateLimiterExceededError {}
12 changes: 11 additions & 1 deletion core/api/src/domain/rate-limit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
UserLoginIdentifierRateLimiterExceededError,
UserCodeAttemptIpRateLimiterExceededError,
UserCodeAttemptIdentifierRateLimiterExceededError,
DeviceAccountCreateRateLimiterExceededError,
} from "./errors"

import {
getDeviceAccountCreateAttemptLimits,
getFailedLoginAttemptPerIpLimits,
getFailedLoginAttemptPerLoginIdentifierLimits,
getInvoiceCreateAttemptLimits,
Expand All @@ -26,9 +28,12 @@ export const RateLimitPrefix = {
invoiceCreate: "invoice_create",
invoiceCreateForRecipient: "invoice_create_for_recipient",
onChainAddressCreate: "onchain_address_create",
deviceAccountCreate: "device_account_create",
} as const

export const RateLimitConfig: { [key: string]: RateLimitConfig } = {
type RateLimitPrefixKey = keyof typeof RateLimitPrefix

export const RateLimitConfig: { [key in RateLimitPrefixKey]: RateLimitConfig } = {
requestCodeAttemptPerLoginIdentifier: {
key: RateLimitPrefix.requestCodeAttemptPerLoginIdentifier,
limits: getRequestCodePerLoginIdentifierLimits(),
Expand Down Expand Up @@ -64,4 +69,9 @@ export const RateLimitConfig: { [key: string]: RateLimitConfig } = {
limits: getOnChainAddressCreateAttemptLimits(),
error: OnChainAddressCreateRateLimiterExceededError,
},
deviceAccountCreate: {
key: RateLimitPrefix.deviceAccountCreate,
limits: getDeviceAccountCreateAttemptLimits(),
error: DeviceAccountCreateRateLimiterExceededError,
},
}
4 changes: 4 additions & 0 deletions core/api/src/graphql/error-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => {
"Too many onchain addresses creation, please wait for a while and try again."
return new TooManyRequestError({ message, logger: baseLogger })

case "DeviceAccountCreateRateLimiterExceededError":
message = "Too many device account creation, please wait for a while and try again."
return new TooManyRequestError({ message, logger: baseLogger })

case "UserCodeAttemptIdentifierRateLimiterExceededError":
message = "Too many request code attempts, please wait for a while and try again."
return new TooManyRequestError({ message, logger: baseLogger })
Expand Down
28 changes: 27 additions & 1 deletion core/api/src/servers/authentication/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@ import {
EmailValidationSubmittedTooOftenError,
} from "@/domain/authentication/errors"

import { UserLoginIpRateLimiterExceededError } from "@/domain/rate-limit/errors"
import {
RateLimiterExceededError,
UserLoginIpRateLimiterExceededError,
} from "@/domain/rate-limit/errors"

import { registerCaptchaGeetest } from "@/app/captcha"
import { consumeLimiter } from "@/services/rate-limit"
import { RateLimitConfig } from "@/domain/rate-limit"

const authRouter = express.Router({ caseSensitive: true })

Expand Down Expand Up @@ -75,6 +80,19 @@ authRouter.use((req: Request, res: Response, next: NextFunction) => {
})

authRouter.post("/create/device-account", async (req: Request, res: Response) => {
const appcheckJti = req.headers["x-appcheck-jti"]
if (!appcheckJti || typeof appcheckJti !== "string" || appcheckJti === "") {
return res.status(400).send({ error: "missing or invalid appcheck jti" })
}

const check = await checkDeviceLoginAttemptPerAppcheckJtiLimits(
appcheckJti as AppcheckJti,
)

if (check instanceof Error) {
return res.status(429).send({ error: "too many requests" })
}

const ip = req.originalIp
const user = basicAuth(req)

Expand Down Expand Up @@ -320,3 +338,11 @@ authRouter.post("/phone/login", async (req: Request, res: Response) => {
})

export default authRouter

const checkDeviceLoginAttemptPerAppcheckJtiLimits = async (
appcheckJti: AppcheckJti,
): Promise<true | RateLimiterExceededError> =>
consumeLimiter({
rateLimitConfig: RateLimitConfig.deviceAccountCreate,
keyToConsume: appcheckJti,
})
2 changes: 1 addition & 1 deletion core/api/src/services/rate-limit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const consumeLimiter = async ({
keyToConsume,
}: {
rateLimitConfig: RateLimitConfig
keyToConsume: IpAddress | LoginIdentifier | AccountId | ""
keyToConsume: IpAddress | LoginIdentifier | AccountId | AppcheckJti | ""
}) => {
const limiter = RedisRateLimitService({
keyPrefix: rateLimitConfig.key,
Expand Down
3 changes: 3 additions & 0 deletions dev/config/ory/oathkeeper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ mutators:
noop:
enabled: true

header:
enabled: true

errors:
fallback:
- json
Expand Down
21 changes: 6 additions & 15 deletions dev/config/ory/oathkeeper_rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,8 @@
upstream:
url: "http://bats-tests:4012"
match:
url: "<(http|https)>://<[a-zA-Z0-9-.:]+>/auth/<(clearCookies|login|logout|email/code|email/login|totp/validate|email/login/cookie|phone/captcha|phone/code|phone/login)>"
url: "<(http|https)>://<[a-zA-Z0-9-.:]+>/auth/<.*>"
methods: ["GET", "POST", "OPTIONS"]
authenticators:
- handler: anonymous
authorizer:
handler: allow
mutators:
- handler: noop

- id: device-login
upstream:
url: "http://bats-tests:4012"
match:
url: "<(http|https)>://<[a-zA-Z0-9-.:]+>/auth/create/device-account"
methods: ["POST"]
authenticators:
- handler: jwt
config:
Expand All @@ -29,10 +16,14 @@
- file:///home/ory/jwks.json # ONLY FOR DEV, DO NOT USE IN PRODUCTION
token_from:
header: Appcheck
- handler: anonymous
authorizer:
handler: allow
mutators:
- handler: noop
- handler: header
config:
headers:
X-Appcheck-Jti: "{{ print .Extra.jti }}"

- id: galoy-ws
upstream:
Expand Down

0 comments on commit 3469677

Please sign in to comment.