realm.test.js (6139B)
1 import test from 'node:test'; 2 import assert from 'node:assert/strict'; 3 import crypto from 'node:crypto'; 4 import fs from 'node:fs/promises'; 5 import os from 'node:os'; 6 import path from 'node:path'; 7 import { findManagedProviderForUser, resolveLogin } from '../src/lib/login.js'; 8 import { 9 addClientRealm, 10 addOidcClientHost, 11 addRealmEmailDomain, 12 closeRealms, 13 createRealm, 14 findClientRealm, 15 findOidcClientHost, 16 findRealmByEmailDomain, 17 getSystemDatabase, 18 initRealms, 19 openRealmDatabase, 20 realmMetadata, 21 resolveRealm, 22 } from '../src/lib/realm.js'; 23 import { 24 addUserEmail, 25 assignHomeRealmForEmail, 26 createUser, 27 findUserById, 28 } from '../src/lib/users.js'; 29 30 async function tempConfig() { 31 const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'auth-realm-')); 32 return { 33 dir, 34 cfg: { 35 database: { 36 dialect: 'sqlite', 37 path: path.join(dir, 'system.sqlite'), 38 realms: path.join(dir, 'realms'), 39 }, 40 }, 41 }; 42 } 43 44 test('realm resolution prefers hostname and rejects a mismatched slug', async () => { 45 const { dir, cfg } = await tempConfig(); 46 try { 47 await initRealms(cfg); 48 await createRealm({ slug: 'acme', displayName: 'Acme', hosts: ['acme.localhost'] }); 49 50 const byHost = await resolveRealm({ host: 'acme.localhost' }); 51 assert.equal(byHost.slug, 'acme'); 52 53 const bySlug = await resolveRealm({ host: 'main.test:3000', slug: 'acme' }); 54 assert.equal(bySlug.slug, 'acme'); 55 56 await assert.rejects( 57 () => resolveRealm({ host: 'acme.localhost', slug: 'system' }), 58 /Unknown realm/ 59 ); 60 await assert.rejects(() => resolveRealm({ host: 'main.test:3000', slug: 'nope' }), /Unknown realm/); 61 62 const metadata = await realmMetadata(byHost); 63 assert.equal(metadata.display_name, 'Acme'); 64 assert.equal(metadata.allow_session_duration_choice, true); 65 assert.deepEqual(metadata.branding, {}); 66 } finally { 67 await closeRealms(); 68 await fs.rm(dir, { recursive: true, force: true }); 69 } 70 }); 71 72 test('client IDs are globally unique and client hosts resolve through mappings', async () => { 73 const { dir, cfg } = await tempConfig(); 74 try { 75 await initRealms(cfg); 76 const acme = await createRealm({ slug: 'acme', displayName: 'Acme' }); 77 const globex = await createRealm({ slug: 'globex', displayName: 'Globex' }); 78 79 await addClientRealm('shared-app', acme.id); 80 const mapping = await findClientRealm('shared-app'); 81 assert.equal(mapping.realm_id, acme.id); 82 assert.equal(mapping.slug, 'acme'); 83 84 await assert.rejects( 85 () => addClientRealm('shared-app', globex.id), 86 /already assigned/ 87 ); 88 await assert.rejects(() => addClientRealm('Bad App', acme.id), /invalid client id/); 89 90 await addOidcClientHost('shared-app', 'app.acme.localhost'); 91 const clientHost = await findOidcClientHost('app.acme.localhost:3000'); 92 assert.equal(clientHost.client_id, 'shared-app'); 93 assert.equal(clientHost.realm_id, acme.id); 94 assert.equal(clientHost.slug, 'acme'); 95 96 const realm = await resolveRealm({ host: 'app.acme.localhost' }); 97 assert.equal(realm.id, acme.id); 98 99 await assert.rejects( 100 () => addOidcClientHost('missing-app', 'app.missing.localhost'), 101 /unknown client/ 102 ); 103 } finally { 104 await closeRealms(); 105 await fs.rm(dir, { recursive: true, force: true }); 106 } 107 }); 108 109 test('owned email domains select their realm provider mapping', async () => { 110 const { dir, cfg } = await tempConfig(); 111 try { 112 await initRealms(cfg); 113 const realm = await createRealm({ slug: 'acme', displayName: 'Acme' }); 114 await addRealmEmailDomain(realm.id, 'example.com', { verified: true }); 115 const db = await openRealmDatabase(realm); 116 117 const local = await resolveLogin({ identifier: 'alice@local.test' }); 118 assert.equal(local.next, 'password'); 119 const username = await resolveLogin({ identifier: 'alice' }); 120 assert.equal(username.next, 'password'); 121 122 const providerId = crypto.randomUUID(); 123 const now = Date.now(); 124 await db.run( 125 `INSERT INTO identity_providers 126 (id, slug, display_name, issuer, client_id, client_secret, scopes, enabled, created_at, updated_at) 127 VALUES 128 ({id}, 'example-sso', 'Example SSO', 'https://idp.example.com', 'demo', NULL, 'openid email profile', 1, {now}, {now})`, 129 { id: providerId, now } 130 ); 131 await db.run( 132 'INSERT INTO email_domain_providers (domain, provider_id, created_at) VALUES ({domain}, {providerId}, {now})', 133 { domain: 'example.com', providerId, now } 134 ); 135 136 const owner = await findRealmByEmailDomain('example.com'); 137 assert.equal(owner.id, realm.id); 138 139 const upstream = await resolveLogin({ identifier: 'someone@example.com' }); 140 assert.equal(upstream.next, 'oidc'); 141 assert.equal(upstream.provider.display_name, 'Example SSO'); 142 143 const systemDb = getSystemDatabase(); 144 const userId = await createUser(systemDb, { username: 'someone' }); 145 await addUserEmail(systemDb, userId, 'someone@example.com', { verified: true, primary: true }); 146 await assignHomeRealmForEmail(systemDb, userId, 'someone@example.com'); 147 const user = await findUserById(systemDb, userId); 148 assert.equal(user.home_realm_id, realm.id); 149 150 const managed = await findManagedProviderForUser(systemDb, user); 151 assert.equal(managed.display_name, 'Example SSO'); 152 153 await db.run('UPDATE identity_providers SET enabled = 0 WHERE id = {id}', { id: providerId }); 154 await assert.rejects(() => resolveLogin({ identifier: 'someone@example.com' }), /unavailable/); 155 } finally { 156 await closeRealms(); 157 await fs.rm(dir, { recursive: true, force: true }); 158 } 159 }); 160 161 test('a realm can disable the long-session choice', async () => { 162 const { dir, cfg } = await tempConfig(); 163 try { 164 await initRealms(cfg); 165 const realm = await createRealm({ 166 slug: 'acme', 167 displayName: 'Acme', 168 allowSessionDurationChoice: false, 169 }); 170 const metadata = await realmMetadata(realm); 171 assert.equal(metadata.allow_session_duration_choice, false); 172 } finally { 173 await closeRealms(); 174 await fs.rm(dir, { recursive: true, force: true }); 175 } 176 });