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:
@@ -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}`);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user