forked from jappel/leistungsbilanz-ts
Add AUTH_DEV_AUTOLOGIN to switch off the login on development instances
With AUTH_DEV_AUTOLOGIN=<username>, every /api request without a valid session runs as that existing, active user and skips the forced password change. Unset keeps the normal login; unknown or deactivated users still get 401, real sessions take precedence, the CSRF application header stays mandatory and the API logs a warning at startup. The switch is not limited to localhost because the development instance is reached from other machines and through a reverse proxy (THREATMODEL D2/R15). It must never be set on an instance with real project data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4677b1685c
commit
084002bea9
11 changed files with 211 additions and 8 deletions
|
|
@ -50,6 +50,31 @@ export async function identityFromRequest(
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Session identity, or on a development instance with AUTH_DEV_AUTOLOGIN the
|
||||
* configured active user. The auto-login user skips the forced password
|
||||
* change because nobody signs in interactively.
|
||||
*/
|
||||
export async function resolveRequestIdentity(
|
||||
req: { headers: { cookie?: string | undefined } },
|
||||
authConfig: Pick<AuthConfig, "devAutoLogin">,
|
||||
sessions: SessionStore,
|
||||
users: Pick<UserStore, "findById" | "findByUsername">,
|
||||
slideExpiry = true
|
||||
): Promise<AuthUser | undefined> {
|
||||
const identity = await identityFromRequest(req, sessions, users, slideExpiry);
|
||||
if (identity || !authConfig.devAutoLogin) return identity;
|
||||
const user = await users.findByUsername(authConfig.devAutoLogin);
|
||||
if (!user || !user.active) return undefined;
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
role: user.role as UserRole,
|
||||
mustChangePassword: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function cookieOptions(authConfig: AuthConfig, maxAgeMs: number) {
|
||||
return {
|
||||
httpOnly: true,
|
||||
|
|
@ -107,7 +132,7 @@ export function requireSameOriginForWrites(): RequestHandler {
|
|||
export function createAuthGuard(
|
||||
authConfig: AuthConfig,
|
||||
sessions: SessionStore,
|
||||
users: Pick<UserStore, "findById">,
|
||||
users: Pick<UserStore, "findById" | "findByUsername">,
|
||||
openPaths: readonly string[] = ["/auth/login", "/auth/status", "/auth/logout"]
|
||||
): RequestHandler {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
|
|
@ -115,7 +140,7 @@ export function createAuthGuard(
|
|||
next();
|
||||
return;
|
||||
}
|
||||
identityFromRequest(req, sessions, users)
|
||||
resolveRequestIdentity(req, authConfig, sessions, users)
|
||||
.then((identity) => {
|
||||
if (!identity) {
|
||||
res.status(401).json({ error: "Nicht angemeldet." });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue