feat(auth): 2.0.0-alpha.10 — Cloudflare Access SSO as owner auth

A cryptographically-verified CF Access JWT (signature vs team JWKS + audience +
email allow-list) now counts as the owner, so browser requests through the CF
tunnel don't need the owner token copied onto each device. Fails closed → owner
token remains the fallback (LAN-direct + dev/tests unaffected). Opt-in via
CF_ACCESS_TEAM_DOMAIN / CF_ACCESS_AUD / CF_ACCESS_OWNER_EMAILS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-03 10:25:58 +10:00
parent 925cb0d7d6
commit 4e943ada12
6 changed files with 172 additions and 2 deletions

View File

@@ -1,5 +1,6 @@
import * as agents from '../../db/repos/agents.js';
import { timingSafeStrEqual } from '../../auth/safe_compare.js';
import { accessOwnerEmail } from '../../auth/cf_access.js';
export async function agentOrOwner(req, res, next) {
const expectedOwner = process.env.OWNER_TOKEN;
@@ -8,6 +9,14 @@ export async function agentOrOwner(req, res, next) {
error: { code: 'no_owner_token', message: 'OWNER_TOKEN not configured' }
});
}
// A cryptographically-verified Cloudflare Access identity counts as the owner,
// so requests through the CF tunnel don't need the owner token. Fails closed
// (null on any miss/error) → falls through to bearer-token auth below.
const cfEmail = await accessOwnerEmail(req);
if (cfEmail) {
req.actor = { kind: 'user', id: null };
return next();
}
const auth = req.headers.authorization || '';
const [scheme, token] = auth.split(' ');
if (scheme !== 'Bearer' || !token) {