forked from jappel/leistungsbilanz-ts
Add login-required user management (admin/user roles, Config->Benutzer)
No self-signup: admins create every account in Config -> Benutzer with a typed start password; a forced password change gates first use. Session- cookie auth protects all /api/* routes; /api/users additionally requires the admin role. See docs/authentication.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
b50c00c0ac
commit
c56cfb30ad
36 changed files with 4928 additions and 38 deletions
88
src/server/controllers/auth.controller.ts
Normal file
88
src/server/controllers/auth.controller.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import type { Request, Response } from "express";
|
||||
import { changePasswordSchema, loginSchema } from "../../shared/validation/auth.schemas.js";
|
||||
import { InvalidCredentialsError, WrongPasswordError } from "../../domain/services/user.service.js";
|
||||
import { authConfig, sessionStore, userService } from "../composition/auth.js";
|
||||
import {
|
||||
cookieOptions,
|
||||
identityFromRequest,
|
||||
sessionIdFromRequest,
|
||||
} from "../auth/auth.middleware.js";
|
||||
import { SESSION_COOKIE } from "../auth/auth-config.js";
|
||||
import { userRepository } from "../composition/application-repositories.js";
|
||||
|
||||
export async function getAuthStatus(req: Request, res: Response) {
|
||||
const identity = await identityFromRequest(req, sessionStore, userRepository);
|
||||
const sessionId = sessionIdFromRequest(req);
|
||||
if (identity && sessionId) {
|
||||
res.cookie(SESSION_COOKIE, sessionId, cookieOptions(authConfig));
|
||||
}
|
||||
res.json({
|
||||
signedIn: Boolean(identity),
|
||||
mustChangePassword: identity?.mustChangePassword ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
export async function login(req: Request, res: Response) {
|
||||
const parsed = loginSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(401).json({ error: "Anmeldung fehlgeschlagen." });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const user = await userService.authenticate(parsed.data.username, parsed.data.password);
|
||||
const session = sessionStore.create(user.id);
|
||||
res.cookie(SESSION_COOKIE, session.id, cookieOptions(authConfig));
|
||||
res.json({ signedIn: true, mustChangePassword: user.mustChangePassword });
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidCredentialsError) {
|
||||
res.status(401).json({ error: error.message });
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function getMe(req: Request, res: Response) {
|
||||
const identity = req.authUser;
|
||||
if (!identity) {
|
||||
res.status(401).json({ error: "Nicht angemeldet." });
|
||||
return;
|
||||
}
|
||||
res.json(identity);
|
||||
}
|
||||
|
||||
export async function changePassword(req: Request, res: Response) {
|
||||
const identity = req.authUser;
|
||||
if (!identity) {
|
||||
res.status(401).json({ error: "Nicht angemeldet." });
|
||||
return;
|
||||
}
|
||||
const parsed = changePasswordSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ error: parsed.error.flatten() });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await userService.changeOwnPassword(
|
||||
identity.id,
|
||||
parsed.data.currentPassword,
|
||||
parsed.data.newPassword
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof WrongPasswordError) {
|
||||
res.status(400).json({ error: error.message });
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
// A password change invalidates every other session of this account; the
|
||||
// current one stays so the change doesn't log the user out themselves.
|
||||
sessionStore.destroyAllForUser(identity.id, sessionIdFromRequest(req));
|
||||
res.json({ changed: true });
|
||||
}
|
||||
|
||||
export function logout(req: Request, res: Response) {
|
||||
sessionStore.destroy(sessionIdFromRequest(req));
|
||||
res.clearCookie(SESSION_COOKIE, { ...cookieOptions(authConfig), maxAge: undefined });
|
||||
res.json({ signedOut: true });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue