Files
internal-mail-manager/apps/api/src/index.ts
T
2026-03-30 14:03:44 +00:00

176 lines
4.2 KiB
TypeScript

import { Elysia } from "elysia";
import { loginAdmin, logoutAdmin, requireAdminSession, getSessionFromRequest, seedAdminAccount } from "./auth/service";
import { cleanupDatabase } from "./lib/db";
import { errorResponse, jsonResponse, emptyResponse, optionsResponse, parseJson, assertAllowedOrigin } from "./lib/http";
import { config } from "./lib/config";
import { serveFrontendBuild } from "./lib/frontend";
import {
createUserSchema,
loginSchema,
parseRouteEmail,
resetPasswordSchema,
resetTokenSchema,
updatePasswordSchema
} from "./lib/validation";
import {
consumePasswordReset,
createUser,
deleteUser,
inspectPasswordReset,
listUsers,
triggerPasswordReset,
updateUserPassword
} from "./users/service";
const app = new Elysia();
const route = (
handler: (context: {
request: Request;
params: Record<string, string>;
}) => Promise<Response> | Response
) =>
async ({ request, params }: { request: Request; params: Record<string, string> }) => {
if (request.method === "OPTIONS") {
return optionsResponse(request);
}
try {
assertAllowedOrigin(request);
cleanupDatabase();
return await handler({ request, params });
} catch (error) {
return errorResponse(request, error);
}
};
await seedAdminAccount();
app.get(
"/api/health",
route(async ({ request }) =>
jsonResponse(request, 200, {
ok: true
})
)
);
app.get(
"/api/auth/session",
route(async ({ request }) =>
jsonResponse(request, 200, {
session: getSessionFromRequest(request)
})
)
);
app.post(
"/api/auth/login",
route(async ({ request }) => {
const body = await parseJson(request, loginSchema);
const result = await loginAdmin(body.email, body.password);
return jsonResponse(
request,
200,
{ session: result.session },
{ "set-cookie": result.cookie }
);
})
);
app.post(
"/api/auth/logout",
route(async ({ request }) =>
jsonResponse(
request,
200,
{ session: { authenticated: false, adminEmail: null } },
{ "set-cookie": logoutAdmin(request) }
)
)
);
app.get(
"/api/users",
route(async ({ request }) => {
requireAdminSession(request);
return jsonResponse(request, 200, { items: await listUsers() });
})
);
app.post(
"/api/users",
route(async ({ request }) => {
const session = requireAdminSession(request);
const body = await parseJson(request, createUserSchema);
const result = await createUser(body.email, session.adminEmail);
return jsonResponse(request, 201, result);
})
);
app.patch(
"/api/users/:email/password",
route(async ({ request, params }) => {
requireAdminSession(request);
const email = parseRouteEmail(params.email);
const body = await parseJson(request, updatePasswordSchema);
await updateUserPassword(email, body.password);
return emptyResponse(request, 204);
})
);
app.delete(
"/api/users/:email",
route(async ({ request, params }) => {
requireAdminSession(request);
const email = parseRouteEmail(params.email);
await deleteUser(email);
return emptyResponse(request, 204);
})
);
app.post(
"/api/users/:email/password-reset",
route(async ({ request, params }) => {
const session = requireAdminSession(request);
const email = parseRouteEmail(params.email);
const result = await triggerPasswordReset(email, session.adminEmail);
return jsonResponse(request, 200, result);
})
);
app.post(
"/api/password-resets/inspect",
route(async ({ request }) => {
const body = await parseJson(request, resetTokenSchema);
return jsonResponse(request, 200, await inspectPasswordReset(body.token));
})
);
app.post(
"/api/password-resets/consume",
route(async ({ request }) => {
const body = await parseJson(request, resetPasswordSchema);
await consumePasswordReset(body.token, body.password);
return emptyResponse(request, 204);
})
);
app.get(
"/",
route(async ({ request }) => serveFrontendBuild(request))
);
app.get(
"/*",
route(async ({ request }) => serveFrontendBuild(request))
);
app.listen({
hostname: config.host,
port: config.port
});
console.log(`Mail admin API listening on http://${config.host}:${config.port}`);