feat(auth): add debug-login bypass endpoint secured by DEBUG_LOGIN_TOKEN
All checks were successful
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 1m10s
Release / Check ui (push) Successful in 26s
Release / Test backend (push) Successful in 41s
CI / Backend (pull_request) Successful in 25s
Release / Docker / caddy (push) Successful in 47s
CI / UI (pull_request) Successful in 41s
Release / Docker / ui (push) Successful in 2m32s
Release / Docker / backend (push) Successful in 3m57s
Release / Docker / runner (push) Successful in 4m8s
Release / Gitea Release (push) Successful in 12s
All checks were successful
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 1m10s
Release / Check ui (push) Successful in 26s
Release / Test backend (push) Successful in 41s
CI / Backend (pull_request) Successful in 25s
Release / Docker / caddy (push) Successful in 47s
CI / UI (pull_request) Successful in 41s
Release / Docker / ui (push) Successful in 2m32s
Release / Docker / backend (push) Successful in 3m57s
Release / Docker / runner (push) Successful in 4m8s
Release / Gitea Release (push) Successful in 12s
This commit is contained in:
@@ -290,6 +290,7 @@ services:
|
|||||||
POCKETBASE_ADMIN_EMAIL: "${POCKETBASE_ADMIN_EMAIL}"
|
POCKETBASE_ADMIN_EMAIL: "${POCKETBASE_ADMIN_EMAIL}"
|
||||||
POCKETBASE_ADMIN_PASSWORD: "${POCKETBASE_ADMIN_PASSWORD}"
|
POCKETBASE_ADMIN_PASSWORD: "${POCKETBASE_ADMIN_PASSWORD}"
|
||||||
AUTH_SECRET: "${AUTH_SECRET}"
|
AUTH_SECRET: "${AUTH_SECRET}"
|
||||||
|
DEBUG_LOGIN_TOKEN: "${DEBUG_LOGIN_TOKEN}"
|
||||||
PUBLIC_MINIO_PUBLIC_URL: "${MINIO_PUBLIC_ENDPOINT}"
|
PUBLIC_MINIO_PUBLIC_URL: "${MINIO_PUBLIC_ENDPOINT}"
|
||||||
# Valkey
|
# Valkey
|
||||||
VALKEY_ADDR: "valkey:6379"
|
VALKEY_ADDR: "valkey:6379"
|
||||||
|
|||||||
73
ui/src/routes/api/auth/debug-login/+server.ts
Normal file
73
ui/src/routes/api/auth/debug-login/+server.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { redirect, error } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
import { getUserByUsername, createUserSession } from '$lib/server/pocketbase';
|
||||||
|
import { createAuthToken } from '../../../../hooks.server';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
import { log } from '$lib/server/logger';
|
||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
|
|
||||||
|
const AUTH_COOKIE = 'libnovel_auth';
|
||||||
|
const ONE_YEAR = 60 * 60 * 24 * 365;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/auth/debug-login?token=<DEBUG_LOGIN_TOKEN>&username=<username>
|
||||||
|
*
|
||||||
|
* One-shot debug bypass: verifies a shared secret token, then mints a real
|
||||||
|
* auth cookie for the given user (defaults to the first admin account) and
|
||||||
|
* redirects to /.
|
||||||
|
*
|
||||||
|
* Requires DEBUG_LOGIN_TOKEN env var to be set. Disabled (404) when the var
|
||||||
|
* is absent or empty.
|
||||||
|
*/
|
||||||
|
export const GET: RequestHandler = async ({ url, cookies, request }) => {
|
||||||
|
const debugToken = env.DEBUG_LOGIN_TOKEN ?? '';
|
||||||
|
if (!debugToken) {
|
||||||
|
error(404, 'Not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const provided = url.searchParams.get('token') ?? '';
|
||||||
|
// Constant-time comparison to prevent timing attacks
|
||||||
|
if (provided.length !== debugToken.length) {
|
||||||
|
log.warn('api/auth/debug-login', 'bad token attempt');
|
||||||
|
error(401, 'Invalid token');
|
||||||
|
}
|
||||||
|
let diff = 0;
|
||||||
|
for (let i = 0; i < debugToken.length; i++) {
|
||||||
|
diff |= provided.charCodeAt(i) ^ debugToken.charCodeAt(i);
|
||||||
|
}
|
||||||
|
if (diff !== 0) {
|
||||||
|
log.warn('api/auth/debug-login', 'bad token attempt');
|
||||||
|
error(401, 'Invalid token');
|
||||||
|
}
|
||||||
|
|
||||||
|
const username = url.searchParams.get('username') ?? 'kamil_alekber_2e99';
|
||||||
|
const user = await getUserByUsername(username);
|
||||||
|
if (!user) {
|
||||||
|
error(404, `User '${username}' not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const authSessionId = randomBytes(16).toString('hex');
|
||||||
|
const userAgent = request.headers.get('user-agent') ?? '';
|
||||||
|
const ip =
|
||||||
|
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ??
|
||||||
|
request.headers.get('x-real-ip') ??
|
||||||
|
'debug';
|
||||||
|
|
||||||
|
createUserSession(user.id, authSessionId, userAgent, ip).catch((e) =>
|
||||||
|
log.warn('api/auth/debug-login', 'createUserSession failed (non-fatal)', { err: String(e) })
|
||||||
|
);
|
||||||
|
|
||||||
|
const token = createAuthToken(user.id, user.username, user.role ?? 'user', authSessionId);
|
||||||
|
|
||||||
|
cookies.set(AUTH_COOKIE, token, {
|
||||||
|
path: '/',
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'lax',
|
||||||
|
maxAge: ONE_YEAR
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info('api/auth/debug-login', 'debug login used', { username: user.username, ip });
|
||||||
|
|
||||||
|
const next = url.searchParams.get('next') ?? '/';
|
||||||
|
redirect(302, next);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user