feat(notifications): allow auth bypass for local debug and ngrok testing

Add shouldBypassNotificationAuth() when test mode or a backend URL override
is set so register/refresh can proceed without DID/Bearer headers. Production
paths still require auth when bypass is off; log bypass vs authenticated
request modes for easier WAKEUP_PING and panel smoke testing.
This commit is contained in:
Jose Olarte III
2026-05-20 19:34:46 +08:00
parent 5bc030125a
commit 0d7586865c
7 changed files with 59 additions and 11 deletions
@@ -30,7 +30,6 @@ import {
import { import {
getNotificationApiHeaders, getNotificationApiHeaders,
httpAuthErrorMessage, httpAuthErrorMessage,
logNotificationRequestAuthenticated,
logSkippingRefreshDueToMissingAuth, logSkippingRefreshDueToMissingAuth,
} from "./notificationApiAuth"; } from "./notificationApiAuth";
import { logNotification } from "./NotificationDebugEvents"; import { logNotification } from "./NotificationDebugEvents";
@@ -591,7 +590,7 @@ export async function refreshNotificationsWithDiagnostics(options?: {
} }
try { try {
const auth = await getNotificationApiHeaders(); const auth = await getNotificationApiHeaders("refresh");
if (!auth.ok) { if (!auth.ok) {
logSkippingRefreshDueToMissingAuth(); logSkippingRefreshDueToMissingAuth();
logRefreshFailure(startedAt, auth.message); logRefreshFailure(startedAt, auth.message);
@@ -601,7 +600,6 @@ export async function refreshNotificationsWithDiagnostics(options?: {
errorMessage: auth.message, errorMessage: auth.message,
}; };
} }
logNotificationRequestAuthenticated("refresh");
const baseUrl = getNotificationApiBaseUrl(); const baseUrl = getNotificationApiBaseUrl();
const res = await fetch(`${baseUrl}/notifications/refresh`, { const res = await fetch(`${baseUrl}/notifications/refresh`, {
@@ -24,7 +24,6 @@ import {
getNotificationApiHeaders, getNotificationApiHeaders,
httpAuthErrorMessage, httpAuthErrorMessage,
logNotificationAuthFailure, logNotificationAuthFailure,
logNotificationRequestAuthenticated,
} from "./notificationApiAuth"; } from "./notificationApiAuth";
import { import {
logTokenRegistrationFailure, logTokenRegistrationFailure,
@@ -42,12 +41,11 @@ export async function registerToken(fcmToken: string): Promise<void> {
const deviceId = await getOrCreateDeviceId(); const deviceId = await getOrCreateDeviceId();
const baseUrl = getNotificationApiBaseUrl(); const baseUrl = getNotificationApiBaseUrl();
try { try {
const auth = await getNotificationApiHeaders(); const auth = await getNotificationApiHeaders("register");
if (!auth.ok) { if (!auth.ok) {
logNotificationAuthFailure("register", auth.message); logNotificationAuthFailure("register", auth.message);
throw new Error(`registerToken auth unavailable: ${auth.message}`); throw new Error(`registerToken auth unavailable: ${auth.message}`);
} }
logNotificationRequestAuthenticated("register");
const res = await fetch(`${baseUrl}/notifications/register`, { const res = await fetch(`${baseUrl}/notifications/register`, {
method: "POST", method: "POST",
@@ -54,7 +54,7 @@ async function registerRetrievedToken(
return; return;
} }
const auth = await getNotificationApiHeaders(); const auth = await getNotificationApiHeaders("register");
if (!auth.ok) { if (!auth.ok) {
if (options?.force) { if (options?.force) {
throw new Error(`FCM registration auth unavailable: ${auth.message}`); throw new Error(`FCM registration auth unavailable: ${auth.message}`);
+1
View File
@@ -21,6 +21,7 @@ export {
setBackendBaseUrl, setBackendBaseUrl,
setTestMode, setTestMode,
} from "./NotificationDebugConfig"; } from "./NotificationDebugConfig";
export { shouldBypassNotificationAuth } from "./notificationApiDebugMode";
export { export {
appendLog, appendLog,
clearNotificationDebugLogs, clearNotificationDebugLogs,
@@ -1,23 +1,33 @@
/** /**
* Authenticated headers for notification backend API calls (`/notifications/*`). * Authenticated headers for notification backend API calls (`/notifications/*`).
* Uses the same `getHeaders` + active DID flow as the rest of the app. * 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 { getHeaders } from "@/libs/endorserServer";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { logger } from "@/utils/logger"; import { logger } from "@/utils/logger";
import { shouldBypassNotificationAuth } from "./notificationApiDebugMode";
import { logNotification } from "./NotificationDebugEvents"; import { logNotification } from "./NotificationDebugEvents";
export type NotificationRequestKind = "register" | "refresh"; export type NotificationRequestKind = "register" | "refresh";
export type NotificationApiHeadersResult = export type NotificationApiHeadersResult =
| { ok: true; headers: { "Content-Type": string; Authorization: string } } | {
ok: true;
authenticated: boolean;
headers: Record<string, string>;
}
| { | {
ok: false; ok: false;
reason: "no_active_did" | "missing_token"; reason: "no_active_did" | "missing_token";
message: string; message: string;
}; };
const DEBUG_HEADERS: Record<string, string> = {
"Content-Type": "application/json",
};
async function resolveActiveDid(): Promise<string | null> { async function resolveActiveDid(): Promise<string | null> {
try { try {
const service = PlatformServiceFactory.getInstance(); const service = PlatformServiceFactory.getInstance();
@@ -46,8 +56,33 @@ function hasBearerToken(headers: {
); );
} }
/** Resolve `Authorization: Bearer …` headers for notification API requests. */ function logAuthBypassEnabled(): void {
export async function getNotificationApiHeaders(): Promise<NotificationApiHeadersResult> { 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.
*/
export async function getNotificationApiHeaders(
kind?: NotificationRequestKind,
): Promise<NotificationApiHeadersResult> {
if (shouldBypassNotificationAuth()) {
logAuthBypassEnabled();
if (kind) {
logDebugUnauthenticatedNotificationRequest();
}
return { ok: true, authenticated: false, headers: { ...DEBUG_HEADERS } };
}
const did = await resolveActiveDid(); const did = await resolveActiveDid();
if (!did) { if (!did) {
return { return {
@@ -66,8 +101,13 @@ export async function getNotificationApiHeaders(): Promise<NotificationApiHeader
}; };
} }
if (kind) {
logAuthenticatedNotificationRequest();
}
return { return {
ok: true, ok: true,
authenticated: true,
headers: { headers: {
"Content-Type": headers["Content-Type"], "Content-Type": headers["Content-Type"],
Authorization: headers.Authorization, Authorization: headers.Authorization,
@@ -0,0 +1,11 @@
/**
* Debug/local notification API auth bypass (ngrok, Notification Debug Panel).
* Production paths leave bypass off unless test mode or backend override is set.
*/
import { getBackendBaseUrl, getTestMode } from "./NotificationDebugConfig";
/** True when local notification debug config allows unauthenticated API calls. */
export function shouldBypassNotificationAuth(): boolean {
return getTestMode() || getBackendBaseUrl() !== null;
}
@@ -78,7 +78,7 @@ export async function flushDeferredFcmRegistration(
return; return;
} }
const auth = await getNotificationApiHeaders(); const auth = await getNotificationApiHeaders("register");
if (!auth.ok) { if (!auth.ok) {
scheduleDeferredRegistrationRetry(); scheduleDeferredRegistrationRetry();
return; return;