Files
publisher-dashboard/apps/api-server/src/procedures/auth/verify-email.ts
RevIQ 829d365e80 Implement auth procedures with code review fixes
Add complete auth backend (Workstream D):
- Auth middleware for session/API key authentication
- Signup with password or passkey (WebAuthn)
- Login flow with device trust and email confirmation
- Password reset and email verification
- Session management and logout

Utilities created:
- cookies.ts: Cookie helpers and configuration
- crypto.ts: Token generation and hashing
- password.ts: zxcvbn validation, argon2id hashing
- geo.ts: IP/location extraction from headers
- email.ts: Stubbed email sending
- session.ts: Session creation and device trust

Code review improvements applied:
- Use ORPCError instead of Error in procedures
- Add ast-grep rule to enforce ORPCError usage
- Remove error info leakage (generic messages)
- Optimize N+1 query with JOIN in login-password
- Extract signupWithPassword/signupWithPasskey for testability
- Add 15-minute WebAuthn challenge expiry check
- Strengthen CookieOptions type definitions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:19:15 +08:00

62 lines
1.6 KiB
TypeScript

import type { APIContext } from "../../context.js";
import { implement, ORPCError } from "@orpc/server";
import { contract } from "@reviq/api-contract";
const os = implement(contract);
/**
* Verify user email with token from URL
* Public procedure - no authentication required
*
* Flow:
* 1. Find token in email_verifications table
* 2. Check if token is expired
* 3. Update user's email_verified_at timestamp
* 4. Delete the verification record
*/
export const verifyEmail = os.auth.verifyEmail.handler(
async ({ input, context }) => {
const ctx = context as APIContext;
const { token } = input;
// Find the verification record
const verification = await ctx.db
.selectFrom("email_verifications")
.select(["id", "user_id", "expires_at"])
.where("token", "=", token)
.executeTakeFirst();
if (!verification) {
throw new ORPCError("BAD_REQUEST", {
message: "Invalid or expired token",
});
}
// Check if token is expired
if (new Date() > verification.expires_at) {
// Clean up expired token
await ctx.db
.deleteFrom("email_verifications")
.where("id", "=", verification.id)
.execute();
throw new ORPCError("BAD_REQUEST", {
message: "Invalid or expired token",
});
}
// Update user's email_verified_at
await ctx.db
.updateTable("users")
.set({ email_verified_at: new Date() })
.where("id", "=", verification.user_id)
.execute();
// Delete the verification record
await ctx.db
.deleteFrom("email_verifications")
.where("id", "=", verification.id)
.execute();
},
);