import "server-only"; import type { ExternalUserId } from "@/lib/auth/types"; export type UserServiceProfile = { id: ExternalUserId; login: string; fullName: string; position: string; email: string; lastName: string; firstName: string; middleName: string; roles: string[]; createdAt: string; updatedAt: string; }; export type UserServiceAuthErrorReason = | "invalid_credentials" | "unavailable"; export class UserServiceAuthError extends Error { constructor(readonly reason: UserServiceAuthErrorReason) { super(reason === "invalid_credentials" ? "Invalid credentials" : "User service unavailable"); this.name = "UserServiceAuthError"; } } export async function authenticateWithUserService( identifier: string, password: string, signal?: AbortSignal, ): Promise { try { const response = await fetch(`${userServiceUrl()}/api/user/auth`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify({ identifier, password }), signal: AbortSignal.any([ signal ?? new AbortController().signal, AbortSignal.timeout(5_000), ]), cache: "no-store", }); if (response.status === 401) { throw new UserServiceAuthError("invalid_credentials"); } if (!response.ok) { throw new UserServiceAuthError("unavailable"); } return parseProfile(await response.json()); } catch (error) { if (error instanceof UserServiceAuthError) { throw error; } throw new UserServiceAuthError("unavailable"); } } function userServiceUrl(): string { const value = process.env.USER_SERVICE_URL?.trim(); if (!value) { throw new Error("Missing required environment variable: USER_SERVICE_URL"); } return value.replace(/\/+$/, ""); } function parseProfile(value: unknown): UserServiceProfile { if (!isRecord(value) || !isPositiveSafeInteger(value.id) || value.is_active !== true) { throw new Error("Invalid user-service profile"); } const fields = [ ["login", "login"], ["full_name", "fullName"], ["position", "position"], ["email", "email"], ["last_name", "lastName"], ["first_name", "firstName"], ["middle_name", "middleName"], ["created_at", "createdAt"], ["updated_at", "updatedAt"], ] as const; if (!Array.isArray(value.roles) || !value.roles.every(isString)) { throw new Error("Invalid user-service profile"); } const profile = { id: String(value.id), roles: value.roles } as Partial; for (const [source, target] of fields) { if (!isString(value[source])) { throw new Error("Invalid user-service profile"); } profile[target] = value[source]; } return profile as UserServiceProfile; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function isPositiveSafeInteger(value: unknown): value is number { return typeof value === "number" && Number.isSafeInteger(value) && value > 0; } function isString(value: unknown): value is string { return typeof value === "string"; }