mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
test(mana-auth): sso-config consistency spec
Locks in the relationship between three places that must agree about
SSO origin configuration:
1. TRUSTED_ORIGINS in better-auth.config.ts (Better Auth allow-list)
2. CORS_ORIGINS env var on mana-auth in docker-compose.macmini.yml
3. The HTTPS subset of (1) must be a subset of (2) — every origin
Better Auth trusts must also pass CORS preflight
Background: root CLAUDE.md references this spec file as the canonical
"Adding an app to SSO" verification step (line 116) but the file
itself never existed. The first run of this spec immediately caught
two real bugs:
- 3 origins in TRUSTED_ORIGINS were missing from CORS_ORIGINS
(https://auth.mana.how, https://arcade.mana.how, https://whopxl.mana.how)
- 22 zombie subdomain entries in CORS_ORIGINS left over from before
the consolidation (calendar, chat, todo, ...) that no app actually
routes to anymore
Both fixes shipped together with the TRUSTED_ORIGINS extraction in
the broader pre-launch sweep (commit 919fcca4b). This spec is the
guard against the same drift creeping back in.
Eight tests:
- canonical mana.how + auth subdomain present
- localhost dev origins (3001, 5173) present
- all production origins HTTPS
- all production origins on *.mana.how
- no duplicates
- every HTTPS trusted origin appears in mana-auth CORS_ORIGINS
- soft warning for CORS_ORIGINS entries not in trustedOrigins
(catches drift in the other direction)
8/8 pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b4dd646fd7
commit
e19a81c83c
1 changed files with 126 additions and 0 deletions
126
services/mana-auth/src/auth/sso-config.spec.ts
Normal file
126
services/mana-auth/src/auth/sso-config.spec.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* SSO config consistency tests.
|
||||
*
|
||||
* Locks in the relationship between three places that must agree about
|
||||
* which origins are allowed to talk to mana-auth:
|
||||
*
|
||||
* 1. `TRUSTED_ORIGINS` in `better-auth.config.ts` — Better Auth's
|
||||
* cross-origin allow-list. A missing entry causes silent login
|
||||
* failure (request rejected before any handler runs).
|
||||
* 2. `CORS_ORIGINS` env var on the `mana-auth` service in
|
||||
* `docker-compose.macmini.yml` — Hono's CORS preflight check.
|
||||
* A missing entry causes browsers to block the response.
|
||||
* 3. The set of HTTPS origins in (1) must be a SUBSET of (2) — every
|
||||
* production origin Better Auth trusts must also pass CORS.
|
||||
*
|
||||
* The reverse is intentionally NOT enforced: docker-compose may list
|
||||
* extra origins (e.g. legacy subdomains) that Better Auth doesn't yet
|
||||
* trust. But if it does, this test reports them so dead entries get
|
||||
* cleaned up rather than accumulating forever.
|
||||
*
|
||||
* This test is referenced from the root CLAUDE.md as the canonical
|
||||
* way to verify "I added a new app to SSO" — see "Adding an app to SSO"
|
||||
* in `/CLAUDE.md`.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { TRUSTED_ORIGINS } from './better-auth.config';
|
||||
|
||||
const REPO_ROOT = join(import.meta.dir, '../../../..');
|
||||
const COMPOSE_FILE = join(REPO_ROOT, 'docker-compose.macmini.yml');
|
||||
|
||||
/**
|
||||
* Pull the `CORS_ORIGINS` value out of the `mana-auth` service block in
|
||||
* docker-compose.macmini.yml. We deliberately do a coarse string scan
|
||||
* instead of a YAML parse to keep this test dependency-free — the
|
||||
* compose file's `mana-auth:` block is conventional enough that the
|
||||
* `service: ... CORS_ORIGINS: ...` window is unambiguous.
|
||||
*/
|
||||
function readManaAuthCorsOrigins(): string[] {
|
||||
const yaml = readFileSync(COMPOSE_FILE, 'utf8');
|
||||
// Find the mana-auth service definition
|
||||
const serviceMatch = yaml.match(/^ {2}mana-auth:\s*$/m);
|
||||
if (!serviceMatch) {
|
||||
throw new Error('mana-auth service not found in docker-compose.macmini.yml');
|
||||
}
|
||||
const tail = yaml.slice(serviceMatch.index! + serviceMatch[0].length);
|
||||
// CORS_ORIGINS appears within the next ~50 lines under environment:
|
||||
const corsMatch = tail.match(/CORS_ORIGINS:\s*([^\n]+)/);
|
||||
if (!corsMatch) {
|
||||
throw new Error('CORS_ORIGINS not found in mana-auth service block');
|
||||
}
|
||||
return corsMatch[1]
|
||||
.replace(/^["']|["']$/g, '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
describe('SSO trusted origins', () => {
|
||||
it('contains the canonical mana.how origin', () => {
|
||||
expect(TRUSTED_ORIGINS).toContain('https://mana.how');
|
||||
});
|
||||
|
||||
it('contains the auth subdomain (Better Auth callback target)', () => {
|
||||
expect(TRUSTED_ORIGINS).toContain('https://auth.mana.how');
|
||||
});
|
||||
|
||||
it('contains localhost dev origins for local development', () => {
|
||||
// Web dev server (5173) and the auth server itself (3001) — both
|
||||
// are required for the local SSO loop to work end-to-end.
|
||||
expect(TRUSTED_ORIGINS).toContain('http://localhost:5173');
|
||||
expect(TRUSTED_ORIGINS).toContain('http://localhost:3001');
|
||||
});
|
||||
|
||||
it('every production origin uses HTTPS', () => {
|
||||
const httpOrigins = TRUSTED_ORIGINS.filter(
|
||||
(o) => o.startsWith('http://') && !o.includes('localhost')
|
||||
);
|
||||
expect(httpOrigins).toEqual([]);
|
||||
});
|
||||
|
||||
it('every production origin is on mana.how (no third-party hosts)', () => {
|
||||
const offRoot = TRUSTED_ORIGINS.filter((o) => {
|
||||
if (o.includes('localhost')) return false;
|
||||
return !/^https:\/\/([a-z0-9-]+\.)?mana\.how$/.test(o);
|
||||
});
|
||||
expect(offRoot).toEqual([]);
|
||||
});
|
||||
|
||||
it('has no duplicate entries', () => {
|
||||
const set = new Set(TRUSTED_ORIGINS);
|
||||
expect(set.size).toBe(TRUSTED_ORIGINS.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SSO ↔ docker-compose CORS_ORIGINS consistency', () => {
|
||||
const corsOrigins = readManaAuthCorsOrigins();
|
||||
|
||||
it('every HTTPS trusted origin is also in mana-auth CORS_ORIGINS', () => {
|
||||
const productionTrusted = TRUSTED_ORIGINS.filter((o) => o.startsWith('https://'));
|
||||
const missing = productionTrusted.filter((o) => !corsOrigins.includes(o));
|
||||
// If this fails: add the listed origins to CORS_ORIGINS for the
|
||||
// mana-auth service in docker-compose.macmini.yml.
|
||||
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.
|
||||
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.
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue