Files
TeleUploader/src/application/use-cases/authenticate.ts
T
asepharyana 5d01a9405f refactor(fase0): shared scaffold — crypto, file-url, schemas, test splits
- Add zod dep; new src/shared/validation/schemas.ts (BucketName,
  JsonUploadPayload, LoginBody, DeleteObjects, CompleteMultipart,
  clampMaxKeys, parseOrNull) + test/validation.test.ts
- Dedup timingSafeCompare -> src/shared/utils/crypto.ts (auth
  middleware, authenticate use-case, s3/auth now import it)
- Dedup AuthSession -> single type in dto/auth.ts
- Dedup telegram file URL builder + filename sanitizer to shared
  modules (file-controller now imports the canonical ones)
- Remove duplicate controllers/home.html (canonical: src/home.html)
- Fix stale bootstrap mocks to real module paths; bootstrap now
  asserts public GET vs guarded POST separately
- Fix health assertion to include version field
- package.json: test -> test:unit alias; new test:quarantine for
  network/live tests; register previously unlisted test files
2026-09-14 17:09:52 +07:00

97 lines
2.9 KiB
TypeScript

import { timingSafeCompare } from '../../shared/utils/crypto';
import type {
AuthSession,
LoginInput,
LoginResponse,
LogoutResponse,
UserInfoResponse,
} from '../dto/auth';
/** Subset of application configuration consumed by the authenticate use case. */
export interface AuthUseCaseConfig {
/** Admin API token used to authenticate login requests. */
adminApiToken: string;
/** Name of the session cookie. */
sessionCookieName: string;
/** Session lifetime in milliseconds. */
sessionMaxAgeMs: number;
}
/** Dependencies required by the authenticate use case factory. */
export interface AuthenticateUseCaseDeps {
/** Application configuration subset. */
config: AuthUseCaseConfig;
}
/**
* Checks whether authentication is enabled based on the configured token.
*
* @param adminApiToken - The admin API token value.
* @returns `true` if the token is non-empty (auth is enabled).
*/
const isAuthEnabled = (adminApiToken: string): boolean => adminApiToken.length > 0;
/**
* Creates a factory function for the login use case.
*
* Validates the provided admin API token and returns session metadata on
* success. The caller (controller/adapter) is responsible for translating
* the result into an HTTP response (e.g. setting a session cookie).
*
* @param deps - The injected dependencies.
* @returns An async function accepting login input and returning a login response.
*/
export function createLoginUseCase(deps: AuthenticateUseCaseDeps) {
return async (input: LoginInput): Promise<LoginResponse> => {
if (!isAuthEnabled(deps.config.adminApiToken)) {
return { username: 'admin' };
}
if (!timingSafeCompare(input.token, deps.config.adminApiToken)) {
throw new Error('Invalid token');
}
return { username: 'admin' };
};
}
/**
* Creates a factory function for the logout use case.
*
* Always succeeds — the caller is responsible for clearing the session cookie.
*
* @returns An async function returning a logout response.
*/
export function createLogoutUseCase() {
return async (): Promise<LogoutResponse> => {
return { success: true };
};
}
/**
* Creates a factory function for the current-user (me) use case.
*
* Accepts an already-parsed auth session (from cookie or bearer token) and
* returns the user info response. The caller (controller/adapter) is
* responsible for extracting the session from the raw HTTP request.
*
* @param deps - The injected dependencies.
* @returns An async function accepting an optional session and returning user info.
*/
export function createMeUseCase(deps: AuthenticateUseCaseDeps) {
return async (session: AuthSession | null): Promise<UserInfoResponse | null> => {
if (!isAuthEnabled(deps.config.adminApiToken)) {
return null;
}
if (!session) {
return null;
}
return {
username: session.username,
expiresAt: session.expiresAt?.toISOString() ?? null,
};
};
}