auth

Main authentication module for my projects
git clone git://git.finwo.net/app/auth
Log | Files | Refs

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 });