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>
This commit is contained in:
RevIQ
2026-01-09 15:19:15 +08:00
parent 8de88472b1
commit 829d365e80
24 changed files with 1739 additions and 47 deletions

View File

@@ -3,8 +3,18 @@ import type {
AuthenticatedContext,
LoginRequestContext,
} from "./context.js";
import { implement } from "@orpc/server";
import { implement, ORPCError } from "@orpc/server";
import { contract } from "@reviq/api-contract";
import { createLoginRequest as createLoginRequestHandler } from "./procedures/auth/create-login-request.js";
import { forgotPassword as forgotPasswordHandler } from "./procedures/auth/forgot-password.js";
import { loginIfRequestIsCompleted as loginIfRequestIsCompletedHandler } from "./procedures/auth/login-if-completed.js";
import { loginPassword as loginPasswordHandler } from "./procedures/auth/login-password.js";
import { loginPasswordConfirm as loginPasswordConfirmHandler } from "./procedures/auth/login-password-confirm.js";
import { logout as logoutHandler } from "./procedures/auth/logout.js";
import { resendVerificationEmail as resendVerificationHandler } from "./procedures/auth/resend-verification.js";
import { resetPassword as resetPasswordHandler } from "./procedures/auth/reset-password.js";
import { signup as signupHandler } from "./procedures/auth/signup.js";
import { verifyEmail as verifyEmailHandler } from "./procedures/auth/verify-email.js";
import {
createAuthenticationOptions as createAuthOptions,
createRegistrationOptions as createRegOptions,
@@ -17,49 +27,25 @@ import {
const os = implement(contract);
// Auth procedures
const signup = os.auth.signup.handler(async () => {
throw new Error("Not implemented");
});
const signup = signupHandler;
const verifyEmail = os.auth.verifyEmail.handler(async () => {
throw new Error("Not implemented");
});
const verifyEmail = verifyEmailHandler;
const resendVerificationEmail = os.auth.resendVerificationEmail.handler(
async () => {
throw new Error("Not implemented");
},
);
const resendVerificationEmail = resendVerificationHandler;
const createLoginRequest = os.auth.createLoginRequest.handler(async () => {
throw new Error("Not implemented");
});
const createLoginRequest = createLoginRequestHandler;
const loginPassword = os.auth.loginPassword.handler(async () => {
throw new Error("Not implemented");
});
const loginPassword = loginPasswordHandler;
const loginPasswordConfirm = os.auth.loginPasswordConfirm.handler(async () => {
throw new Error("Not implemented");
});
const loginPasswordConfirm = loginPasswordConfirmHandler;
const loginIfRequestIsCompleted = os.auth.loginIfRequestIsCompleted.handler(
async () => {
throw new Error("Not implemented");
},
);
const loginIfRequestIsCompleted = loginIfRequestIsCompletedHandler;
const forgotPassword = os.auth.forgotPassword.handler(async () => {
throw new Error("Not implemented");
});
const forgotPassword = forgotPasswordHandler;
const resetPassword = os.auth.resetPassword.handler(async () => {
throw new Error("Not implemented");
});
const resetPassword = resetPasswordHandler;
const logout = os.auth.logout.handler(async () => {
throw new Error("Not implemented");
});
const logout = logoutHandler;
// WebAuthn procedures
const createRegistrationOptions =
@@ -111,7 +97,9 @@ const verifyAuthentication = os.auth.webauthn.verifyAuthentication.handler(
);
if (!verified) {
throw new Error("Authentication failed");
throw new ORPCError("BAD_REQUEST", {
message: "Authentication failed",
});
}
},
);