Add PocketBase admin auth to garmin-sync cron job
All checks were successful
Deploy / deploy (push) Successful in 2m28s
All checks were successful
Deploy / deploy (push) Successful in 2m28s
The cron job needs to list all users, but the users collection doesn't have a public listRule (for security). Added admin authentication so the job can access user records. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ vi.mock("@/lib/pocketbase", () => ({
|
|||||||
}),
|
}),
|
||||||
create: mockPbCreate,
|
create: mockPbCreate,
|
||||||
update: mockPbUpdate,
|
update: mockPbUpdate,
|
||||||
|
authWithPassword: vi.fn().mockResolvedValue({ token: "admin-token" }),
|
||||||
})),
|
})),
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
@@ -133,6 +134,8 @@ describe("POST /api/cron/garmin-sync", () => {
|
|||||||
mockDaysUntilExpiry.mockReturnValue(30); // Default to 30 days remaining
|
mockDaysUntilExpiry.mockReturnValue(30); // Default to 30 days remaining
|
||||||
mockSendTokenExpirationWarning.mockResolvedValue(undefined); // Reset mock implementation
|
mockSendTokenExpirationWarning.mockResolvedValue(undefined); // Reset mock implementation
|
||||||
process.env.CRON_SECRET = validSecret;
|
process.env.CRON_SECRET = validSecret;
|
||||||
|
process.env.POCKETBASE_ADMIN_EMAIL = "admin@test.com";
|
||||||
|
process.env.POCKETBASE_ADMIN_PASSWORD = "test-password";
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Authentication", () => {
|
describe("Authentication", () => {
|
||||||
@@ -159,6 +162,26 @@ describe("POST /api/cron/garmin-sync", () => {
|
|||||||
|
|
||||||
expect(response.status).toBe(401);
|
expect(response.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns 500 when POCKETBASE_ADMIN_EMAIL is not set", async () => {
|
||||||
|
process.env.POCKETBASE_ADMIN_EMAIL = "";
|
||||||
|
|
||||||
|
const response = await POST(createMockRequest(`Bearer ${validSecret}`));
|
||||||
|
|
||||||
|
expect(response.status).toBe(500);
|
||||||
|
const body = await response.json();
|
||||||
|
expect(body.error).toBe("Server misconfiguration");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns 500 when POCKETBASE_ADMIN_PASSWORD is not set", async () => {
|
||||||
|
process.env.POCKETBASE_ADMIN_PASSWORD = "";
|
||||||
|
|
||||||
|
const response = await POST(createMockRequest(`Bearer ${validSecret}`));
|
||||||
|
|
||||||
|
expect(response.status).toBe(500);
|
||||||
|
const body = await response.json();
|
||||||
|
expect(body.error).toBe("Server misconfiguration");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("User fetching", () => {
|
describe("User fetching", () => {
|
||||||
|
|||||||
@@ -58,6 +58,32 @@ export async function POST(request: Request) {
|
|||||||
|
|
||||||
const pb = createPocketBaseClient();
|
const pb = createPocketBaseClient();
|
||||||
|
|
||||||
|
// Authenticate as admin to bypass API rules and list all users
|
||||||
|
const adminEmail = process.env.POCKETBASE_ADMIN_EMAIL;
|
||||||
|
const adminPassword = process.env.POCKETBASE_ADMIN_PASSWORD;
|
||||||
|
if (!adminEmail || !adminPassword) {
|
||||||
|
logger.error("Missing POCKETBASE_ADMIN_EMAIL or POCKETBASE_ADMIN_PASSWORD");
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Server misconfiguration" },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pb
|
||||||
|
.collection("_superusers")
|
||||||
|
.authWithPassword(adminEmail, adminPassword);
|
||||||
|
} catch (authError) {
|
||||||
|
logger.error(
|
||||||
|
{ err: authError },
|
||||||
|
"Failed to authenticate as PocketBase admin",
|
||||||
|
);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Database authentication failed" },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch all users (we'll filter garminConnected in code to avoid PocketBase query syntax issues)
|
// Fetch all users (we'll filter garminConnected in code to avoid PocketBase query syntax issues)
|
||||||
// Also filter out users without required date fields (garminTokenExpiresAt, lastPeriodDate)
|
// Also filter out users without required date fields (garminTokenExpiresAt, lastPeriodDate)
|
||||||
const allUsers = await pb.collection("users").getFullList<User>();
|
const allUsers = await pb.collection("users").getFullList<User>();
|
||||||
|
|||||||
Reference in New Issue
Block a user