Files
platform/src/servers/api/auth/signin.ts
T
pastilhasandClaude Opus 4.8 cf2962fd67 auth: distress-password lockdown + explicit token-revoke endpoint
Add a duress password (DISTRESS_PASSWORD env): entering it at login trips an
in-memory full lockdown — all new logins (password + passkey) and every existing
session are refused until the server is restarted, and the login itself returns a
normal "invalid credentials" so it gives nothing away. Also add
POST /api/auth/blacklist-token as a clearly-named alias for revoking the current
JWT (same effect as signout).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:06:42 +00:00

55 lines
2.0 KiB
TypeScript
Executable File

import type { Handler } from 'hono';
import { mkdir } from 'node:fs/promises';
import { join } from 'node:path';
import { getUserByEmail, getPasskeysByUserIdAndOrigin } from 'officerdb';
import { sign } from '@@/jwt';
import { getClaudeDir } from '@@/data-path';
import argon2 from 'argon2';
import * as errors from '@@/custom-errors';
import { isLockdown, triggerLockdown } from './distress';
const TEST_USERS: number[] = [];
const { DISTRESS_PASSWORD } = process.env;
export const signinHandler: Handler = async function (ctx) {
const { email, password } = ctx.get('body');
// Duress: if lockdown is active, refuse everyone (looks like a normal failed login). If the distress
// password was entered, trip the lockdown now and then fail the same way, giving nothing away.
if (isLockdown()) throw errors.UNAUTHORIZED();
if (DISTRESS_PASSWORD && typeof password === 'string' && password === DISTRESS_PASSWORD) {
triggerLockdown();
throw errors.UNAUTHORIZED();
}
const origin = ctx.get('origin');
const dbUser = await getUserByEmail(email);
if (!dbUser) throw errors.UNAUTHORIZED();
const passkeys = await getPasskeysByUserIdAndOrigin(dbUser.id, origin);
if (!dbUser.password) throw errors.UNAUTHORIZED();
const { status } = dbUser;
if (status !== 'Active') throw errors.UNAUTHORIZED();
const isValidPassword = TEST_USERS.includes(dbUser.id) || (await argon2.verify(dbUser.password, password));
if (!isValidPassword) throw errors.UNAUTHORIZED();
const { id, name, username, role } = dbUser;
mkdir(join(getClaudeDir(email), 'archived'), { recursive: true }).catch(() => {});
const tokenUser = { id, email, name, username, role, passkeys: passkeys.length };
if (passkeys.length > 0 && !origin.startsWith('chrome-extension://') && !TEST_USERS.includes(dbUser.id)) {
return ctx.json({ user: tokenUser });
}
const token = await sign(tokenUser);
if (origin.startsWith('chrome-extension://')) {
// console.log('token', token);
}
return ctx.json({ token, user: tokenUser });
};