Files
crowd-funder-from-jason/src/services/notifications/notificationApiAuth.ts
T
Jose Olarte III 34a8c51d2f Add a manual debug-panel action to mint and PUT the 100-day AlertSearch authorization batch.
Reuse the existing delegated JWT minting and notification API auth so a signed-in ethr identity can upload the batch to the configured notify backend without changing production defaults or sending it automatically.
2026-09-02 15:34:03 +08:00

162 lines
4.3 KiB
TypeScript

/**
* Authenticated headers for notification backend API calls (`/notifications/*`).
* Uses the same `getHeaders` + active DID flow as the rest of the app.
* Debug/local config can bypass auth for ngrok and panel testing.
*/
import { getHeaders } from "@/libs/endorserServer";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { logger } from "@/utils/logger";
import { shouldBypassNotificationAuth } from "./notificationApiDebugMode";
import { logNotification } from "./NotificationDebugEvents";
export type NotificationRequestKind = "register" | "refresh";
export type NotificationApiHeadersResult =
| {
ok: true;
authenticated: boolean;
headers: Record<string, string>;
}
| {
ok: false;
reason: "no_active_did" | "missing_token" | "identity_changed";
message: string;
};
const DEBUG_HEADERS: Record<string, string> = {
"Content-Type": "application/json",
};
export async function getActiveNotificationDid(): Promise<string | null> {
try {
const service = PlatformServiceFactory.getInstance();
const row = await service.dbGetOneRow(
"SELECT activeDid FROM active_identity WHERE id = 1",
);
if (!row?.[0]) {
return null;
}
const did = String(row[0]).trim();
return did || null;
} catch (err) {
logger.warn("[notificationApiAuth] Failed to read active DID", err);
return null;
}
}
function hasBearerToken(headers: {
Authorization?: string;
}): headers is { Authorization: string } {
const auth = headers.Authorization;
return (
typeof auth === "string" &&
auth.startsWith("Bearer ") &&
auth.length > "Bearer ".length
);
}
function logAuthBypassEnabled(): void {
logNotification("Auth bypass enabled for debug/testing");
}
function logAuthenticatedNotificationRequest(): void {
logNotification("Using authenticated notification request");
}
function logDebugUnauthenticatedNotificationRequest(): void {
logNotification("Using debug unauthenticated notification request");
}
/**
* Resolve headers for notification API requests.
* @param kind Optional request kind for structured logs.
* @param expectedDid Reject if the active identity changed before token minting.
*/
export async function getNotificationApiHeaders(
kind?: NotificationRequestKind,
expectedDid?: string,
): Promise<NotificationApiHeadersResult> {
if (shouldBypassNotificationAuth()) {
logAuthBypassEnabled();
if (kind) {
logDebugUnauthenticatedNotificationRequest();
}
return { ok: true, authenticated: false, headers: { ...DEBUG_HEADERS } };
}
const did = await getActiveNotificationDid();
if (!did) {
return {
ok: false,
reason: "no_active_did",
message: "no active identity (cannot authenticate)",
};
}
if (expectedDid && did !== expectedDid) {
return {
ok: false,
reason: "identity_changed",
message: "active identity changed before authentication",
};
}
const headers = await getHeaders(did);
if (!hasBearerToken(headers)) {
return {
ok: false,
reason: "missing_token",
message: "missing or empty Authorization token",
};
}
if (kind) {
logAuthenticatedNotificationRequest();
}
return {
ok: true,
authenticated: true,
headers: {
"Content-Type": headers["Content-Type"],
Authorization: headers.Authorization,
},
};
}
export function logNotificationRequestAuthenticated(
kind: NotificationRequestKind,
): void {
logNotification(
kind === "register"
? "Register request authenticated"
: "Refresh request authenticated",
);
}
export function logNotificationAuthFailure(
kind: NotificationRequestKind,
message: string,
): void {
const verb = kind === "register" ? "Register" : "Refresh";
logNotification(`${verb} auth unavailable: ${message}`);
}
export function logWaitingForAuthBeforeRegistration(): void {
logNotification("Waiting for auth before registration");
}
export function logSkippingRefreshDueToMissingAuth(): void {
logNotification("Skipping refresh due to missing auth");
}
export function httpAuthErrorMessage(status: number): string {
if (status === 401) {
return "unauthorized (expired or invalid auth)";
}
if (status === 403) {
return "forbidden (not authorized)";
}
return `HTTP ${status}`;
}