Browse Source

feat(server): errors, rate limit stub, verification policy, noop mailer

Made-with: Cursor
feat/auth-user
npmrun 6 days ago
parent
commit
3c09e11c3a
  1. 9
      server/utils/errors.ts
  2. 9
      server/utils/mailer.ts
  3. 22
      server/utils/rate-limit.ts
  4. 5
      server/utils/verification-policy.ts
  5. 11
      test/unit/verification-policy.test.ts

9
server/utils/errors.ts

@ -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 } },
});
}

9
server/utils/mailer.ts

@ -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() {},
};

22
server/utils/rate-limit.ts

@ -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: "请求过于频繁,请稍后再试" },
},
});
}
}

5
server/utils/verification-policy.ts

@ -0,0 +1,5 @@
const NEEDS_VERIFIED = new Set<string>(["patch-me"]);
export function needsEmailVerified(handlerId: string): boolean {
return NEEDS_VERIFIED.has(handlerId);
}

11
test/unit/verification-policy.test.ts

@ -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…
Cancel
Save