mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 01:01:09 +02:00
refactor(auth): extract sso-origins SSOT + harden drift test
TRUSTED_ORIGINS was defined inside better-auth.config.ts, which pulls in the whole Better Auth stack just to read a list of hostnames. Anyone who wants to consume the list (infra tooling, compose-env generators, monitoring) had to either duplicate it or pay the import cost. - New `sso-origins.ts` — zero-dep module exposing `PRODUCTION_TRUSTED_ORIGINS` + `LOCAL_TRUSTED_ORIGINS` + the combined `TRUSTED_ORIGINS` list. This is now the canonical place to add a new top-level SSO origin. - `better-auth.config.ts` imports + re-exports so existing consumers keep working without a touch. - `sso-config.spec.ts` imports directly from `./sso-origins` (cleaner coupling) and now HARD-FAILS when mana-auth CORS_ORIGINS contains a production origin that isn't in trustedOrigins. Previously this was a `console.warn` only, meaning dead-drift could silently accumulate and then surface as a confusing runtime auth rejection. - Root CLAUDE.md "Adding an app to SSO" updated to point at the SSOT and mention the new hard-fail direction. No current drift — the mana-auth CORS_ORIGINS already match. The hardened assertion is defensive for future changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2bcc3954ea
commit
a7fe828d32
4 changed files with 51 additions and 41 deletions
|
|
@ -112,9 +112,9 @@ Default new user-typed fields to **encrypt**; default new IDs/timestamps/sort-ke
|
|||
All servers use `@mana/shared-hono` with `authMiddleware()`. Tokens are EdDSA JWTs issued by `mana-auth` with claims `{sub, email, role, sid, tier, exp, iss, aud}`. Cross-app SSO works across `*.mana.how`. See [`.claude/guidelines/authentication.md`](.claude/guidelines/authentication.md) and `services/mana-auth/`.
|
||||
|
||||
**Adding an app to SSO** requires updating *all three*:
|
||||
1. `trustedOrigins` in `services/mana-auth/src/auth/better-auth.config.ts`
|
||||
1. `PRODUCTION_TRUSTED_ORIGINS` in `services/mana-auth/src/auth/sso-origins.ts` (the SSOT — better-auth.config.ts re-exports from here)
|
||||
2. `CORS_ORIGINS` for mana-auth in `docker-compose.macmini.yml`
|
||||
3. Run `pnpm test -- src/auth/sso-config.spec.ts` from `services/mana-auth/`
|
||||
3. Run `bun test src/auth/sso-config.spec.ts` from `services/mana-auth/` — now hard-fails on drift in either direction
|
||||
|
||||
### Access tiers
|
||||
|
||||
|
|
|
|||
|
|
@ -37,33 +37,11 @@ import {
|
|||
sendMagicLinkEmail,
|
||||
} from '../email/send';
|
||||
import { sourceAppStore, passwordResetRedirectStore } from './stores';
|
||||
import { TRUSTED_ORIGINS } from './sso-origins';
|
||||
|
||||
/**
|
||||
* Single source of truth for SSO trusted origins.
|
||||
*
|
||||
* Better Auth rejects any cross-origin auth request whose Origin header
|
||||
* isn't in this list — silent login failure on mis-configured apps. When
|
||||
* adding a new top-level domain (NOT a path under mana.how), update both:
|
||||
*
|
||||
* 1. This array
|
||||
* 2. The `mana-auth` `CORS_ORIGINS` env var in
|
||||
* `docker-compose.macmini.yml` (must be a superset of this list)
|
||||
*
|
||||
* `sso-config.spec.ts` enforces both invariants. The unified app under
|
||||
* `mana.how` does NOT need per-module subdomains here — modules are routed
|
||||
* by path on the same origin.
|
||||
*/
|
||||
export const TRUSTED_ORIGINS: string[] = [
|
||||
// Unified app — all productivity apps live under mana.how
|
||||
'https://mana.how',
|
||||
'https://auth.mana.how',
|
||||
// Separate apps (not part of the unified app)
|
||||
'https://arcade.mana.how', // Games
|
||||
'https://whopxl.mana.how', // Games
|
||||
// Local development
|
||||
'http://localhost:3001',
|
||||
'http://localhost:5173',
|
||||
];
|
||||
// Re-export so existing imports (`import { TRUSTED_ORIGINS } from './better-auth.config'`)
|
||||
// keep working. New code should import from './sso-origins' directly.
|
||||
export { TRUSTED_ORIGINS };
|
||||
|
||||
/**
|
||||
* JWT Custom Payload Interface
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
import { describe, it, expect } from 'bun:test';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { TRUSTED_ORIGINS } from './better-auth.config';
|
||||
import { TRUSTED_ORIGINS } from './sso-origins';
|
||||
|
||||
const REPO_ROOT = join(import.meta.dir, '../../../..');
|
||||
const COMPOSE_FILE = join(REPO_ROOT, 'docker-compose.macmini.yml');
|
||||
|
|
@ -106,21 +106,18 @@ describe('SSO ↔ docker-compose CORS_ORIGINS consistency', () => {
|
|||
expect(missing).toEqual([]);
|
||||
});
|
||||
|
||||
it('reports CORS_ORIGINS entries that are NOT in trustedOrigins (dead drift)', () => {
|
||||
// This is a soft assertion — extra entries don't break the SSO
|
||||
// loop, but they're a sign of stale config that should be cleaned
|
||||
// up. We log them so they're visible in CI without failing the
|
||||
// build. Convert to a hard expect() once the cleanup ships.
|
||||
it('mana-auth CORS_ORIGINS contains NO entries outside trustedOrigins (no dead drift)', () => {
|
||||
// Hard-fail on extras: if CORS lists an origin Better Auth doesn't
|
||||
// trust, the server accepts the preflight but then silently rejects
|
||||
// the auth request — worst-of-both-worlds. Tightened from a warning
|
||||
// to a hard assertion on 2026-04-19 per audit.
|
||||
// Fix: either add the origin to TRUSTED_ORIGINS (in sso-origins.ts)
|
||||
// or remove it from the mana-auth CORS_ORIGINS in
|
||||
// docker-compose.macmini.yml.
|
||||
const extras = corsOrigins.filter(
|
||||
(o) =>
|
||||
o.startsWith('https://') && !TRUSTED_ORIGINS.includes(o as (typeof TRUSTED_ORIGINS)[number])
|
||||
);
|
||||
if (extras.length > 0) {
|
||||
console.warn(
|
||||
`[sso-config.spec] mana-auth CORS_ORIGINS contains ${extras.length} origin(s) not in trustedOrigins (likely stale post-consolidation): ${extras.join(', ')}`
|
||||
);
|
||||
}
|
||||
// No assertion — see comment above. Will be tightened once
|
||||
// audit item from REFACTORING_AUDIT_2026_04.md lands.
|
||||
expect(extras).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
35
services/mana-auth/src/auth/sso-origins.ts
Normal file
35
services/mana-auth/src/auth/sso-origins.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Single source of truth for SSO trusted origins.
|
||||
*
|
||||
* Extracted into a standalone module (no Better Auth imports) so it can
|
||||
* also be consumed by infra tooling (compose env generators, monitoring
|
||||
* jobs, etc.) without pulling in the full auth stack.
|
||||
*
|
||||
* Better Auth rejects any cross-origin auth request whose Origin header
|
||||
* isn't in this list — silent login failure on mis-configured apps. When
|
||||
* adding a new top-level domain (NOT a path under mana.how), update both:
|
||||
*
|
||||
* 1. `PRODUCTION_TRUSTED_ORIGINS` below
|
||||
* 2. The `mana-auth` `CORS_ORIGINS` env var in
|
||||
* `docker-compose.macmini.yml` (must be a superset of this list)
|
||||
*
|
||||
* `sso-config.spec.ts` enforces both invariants. The unified app under
|
||||
* `mana.how` does NOT need per-module subdomains here — modules are routed
|
||||
* by path on the same origin.
|
||||
*/
|
||||
|
||||
/** HTTPS origins Better Auth accepts in production. */
|
||||
export const PRODUCTION_TRUSTED_ORIGINS = [
|
||||
// Unified app — all productivity apps live under mana.how
|
||||
'https://mana.how',
|
||||
'https://auth.mana.how',
|
||||
// Separate apps (not part of the unified app)
|
||||
'https://arcade.mana.how', // Games
|
||||
'https://whopxl.mana.how', // Games
|
||||
] as const;
|
||||
|
||||
/** Local dev origins — web dev server + the auth server itself. */
|
||||
export const LOCAL_TRUSTED_ORIGINS = ['http://localhost:3001', 'http://localhost:5173'] as const;
|
||||
|
||||
/** Full trusted-origins list passed to Better Auth. */
|
||||
export const TRUSTED_ORIGINS: string[] = [...PRODUCTION_TRUSTED_ORIGINS, ...LOCAL_TRUSTED_ORIGINS];
|
||||
Loading…
Add table
Add a link
Reference in a new issue