feat(debug): allow local testMode auth on send-wakeup

Move requireAuthOrNotificationLocalTest into shared auth middleware and
apply it only to POST /debug/send-wakeup so local testing matches
register/refresh without changing JWT-authenticated behavior.
This commit is contained in:
Jose Olarte III
2026-06-11 17:24:06 +08:00
parent bb0927ad92
commit 6261f1baa0
3 changed files with 36 additions and 36 deletions

View File

@@ -13,6 +13,21 @@ type ClientErrorBody = {
};
};
/** Synthetic userId for unauthenticated local debug registrations (testMode). */
const LOCAL_TEST_USER_ID = "__notification_local_test__";
function isNotificationLocalTestBypass(req: Request): boolean {
if (req.headers.authorization?.startsWith("Bearer ")) {
return false;
}
const body = req.body;
return (
body !== null &&
typeof body === "object" &&
(body as { testMode?: unknown }).testMode === true
);
}
function clientErrorMessage(err: unknown): string | undefined {
if (err && typeof err === "object" && "clientError" in err) {
const message = (err as ClientErrorBody).clientError?.message;
@@ -86,3 +101,17 @@ export async function requireAuth(
});
}
}
export async function requireAuthOrNotificationLocalTest(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
if (isNotificationLocalTestBypass(req)) {
req.did = LOCAL_TEST_USER_ID;
console.log("[Auth] Local notification test bypass");
next();
return;
}
return requireAuth(req, res, next);
}

View File

@@ -1,6 +1,9 @@
import { Router } from "express";
import { db } from "../db/fcmTokens.js";
import { requireAuth } from "../middleware/auth.js";
import {
requireAuth,
requireAuthOrNotificationLocalTest,
} from "../middleware/auth.js";
import {
computeNextEligibleAt,
sendPushToDevice,
@@ -11,8 +14,6 @@ import { maskToken } from "../util/maskToken.js";
// TODO: Protect this endpoint before production deployment
export const debugRouter = Router();
debugRouter.use(requireAuth);
function deviceDebugPayload(row: {
id: string;
deviceId: string;
@@ -47,7 +48,7 @@ function sendWakeupFailureReason(
}
// TODO: Protect this endpoint before production deployment
debugRouter.get("/device/:token", async (req, res) => {
debugRouter.get("/device/:token", requireAuth, async (req, res) => {
const started = Date.now();
const userId = req.did;
if (userId === undefined) {
@@ -81,7 +82,7 @@ debugRouter.get("/device/:token", async (req, res) => {
});
// TODO: Protect this endpoint before production deployment
debugRouter.post("/send-wakeup", async (req, res) => {
debugRouter.post("/send-wakeup", requireAuthOrNotificationLocalTest, async (req, res) => {
const started = Date.now();
const userId = req.did;
if (userId === undefined) {

View File

@@ -1,39 +1,9 @@
import type { NextFunction, Request, Response } from "express";
import { Router } from "express";
import { db } from "../db/fcmTokens.js";
import { requireAuth } from "../middleware/auth.js";
import { requireAuthOrNotificationLocalTest } from "../middleware/auth.js";
import { errorMessage, formatElapsedMs } from "../util/formatElapsed.js";
import { maskToken } from "../util/maskToken.js";
/** Synthetic userId for unauthenticated local debug registrations (testMode). */
const LOCAL_TEST_USER_ID = "__notification_local_test__";
function isNotificationLocalTestBypass(req: Request): boolean {
if (req.headers.authorization?.startsWith("Bearer ")) {
return false;
}
const body = req.body;
return (
body !== null &&
typeof body === "object" &&
(body as { testMode?: unknown }).testMode === true
);
}
async function requireAuthOrNotificationLocalTest(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
if (isNotificationLocalTestBypass(req)) {
req.did = LOCAL_TEST_USER_ID;
console.log("[Auth] Local notification test bypass");
next();
return;
}
return requireAuth(req, res, next);
}
export const notificationsRouter = Router();
notificationsRouter.get("/", (_req, res) => {