5 changed files with 56 additions and 0 deletions
@ -0,0 +1,9 @@ |
|||
import { createError } from "h3"; |
|||
|
|||
/** 与规格一致的错误体;依赖 Nitro 将 `createError` 的 `data` 序列化进 JSON。 */ |
|||
export function jsonError(status: number, code: string, message: string): never { |
|||
throw createError({ |
|||
statusCode: status, |
|||
data: { error: { code, message } }, |
|||
}); |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
export type Mailer = { |
|||
sendVerificationEmail(input: { to: string; token: string }): Promise<void>; |
|||
sendPasswordResetEmail(input: { to: string; token: string }): Promise<void>; |
|||
}; |
|||
|
|||
export const noopMailer: Mailer = { |
|||
async sendVerificationEmail() {}, |
|||
async sendPasswordResetEmail() {}, |
|||
}; |
|||
@ -0,0 +1,22 @@ |
|||
import { createError } from "h3"; |
|||
import { getRedis } from "./redis"; |
|||
|
|||
export async function rateLimitOrThrow( |
|||
key: string, |
|||
limit: number, |
|||
windowSeconds: number, |
|||
): Promise<void> { |
|||
const redis = getRedis(); |
|||
const n = await redis.incr(key); |
|||
if (n === 1) { |
|||
await redis.expire(key, windowSeconds); |
|||
} |
|||
if (n > limit) { |
|||
throw createError({ |
|||
statusCode: 429, |
|||
data: { |
|||
error: { code: "RATE_LIMITED", message: "请求过于频繁,请稍后再试" }, |
|||
}, |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
const NEEDS_VERIFIED = new Set<string>(["patch-me"]); |
|||
|
|||
export function needsEmailVerified(handlerId: string): boolean { |
|||
return NEEDS_VERIFIED.has(handlerId); |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
import { describe, expect, it } from "bun:test"; |
|||
import { needsEmailVerified } from "../../server/utils/verification-policy"; |
|||
|
|||
describe("verification-policy", () => { |
|||
it("patch-me requires verified", () => { |
|||
expect(needsEmailVerified("patch-me")).toBe(true); |
|||
}); |
|||
it("unknown defaults false", () => { |
|||
expect(needsEmailVerified("other")).toBe(false); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue