feat(auth): implement user authentication and email verification

Add registration, login, and logout flows with session management
Integrate Resend for email verification tokens
Create frontend auth views and update topbar state
This commit is contained in:
2026-04-30 11:32:46 +08:00
parent 193b4e3fd5
commit 9af8c98401
13 changed files with 898 additions and 11 deletions

View File

@@ -1,5 +1,6 @@
import cors from '@fastify/cors';
import Fastify from 'fastify';
import { getUserBySessionToken, loginUser, logoutSession, registerUser, verifyEmail } from './auth.ts';
import { initializeDatabase, pool } from './db.ts';
import {
createConfig,
@@ -35,6 +36,7 @@ const app = Fastify({
});
await app.register(cors, {
allowedHeaders: ['Authorization', 'Content-Type'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
origin: process.env.FRONTEND_ORIGIN ?? true
});
@@ -64,6 +66,39 @@ app.setErrorHandler(async (error, _request, reply) => {
app.get('/health', async () => ({ ok: true }));
function getBearerToken(authorization: string | undefined): string | null {
const [scheme, token] = authorization?.split(' ') ?? [];
return scheme === 'Bearer' && token ? token : null;
}
app.post('/api/auth/register', async (request, reply) =>
reply.code(201).send(await registerUser(request.body as Record<string, unknown>))
);
app.post('/api/auth/verify-email', async (request) => verifyEmail(request.body as Record<string, unknown>));
app.post('/api/auth/login', async (request) => loginUser(request.body as Record<string, unknown>));
app.get('/api/auth/me', async (request, reply) => {
const token = getBearerToken(request.headers.authorization);
const user = token ? await getUserBySessionToken(token) : null;
if (!user) {
return reply.code(401).send({ message: '请先登录' });
}
return { user };
});
app.post('/api/auth/logout', async (request, reply) => {
const token = getBearerToken(request.headers.authorization);
if (token) {
await logoutSession(token);
}
return reply.code(204).send();
});
app.get('/api/options', async () => getOptions());
app.get('/api/pokemon', async (request) => listPokemon(request.query as Record<string, string | string[] | undefined>));