conductor

CI task system
git clone git://git.finwo.net/app/conductor
Log | Files | Refs | README | LICENSE

index.js (2809B)


      1 // src/lib/auth/index.js - request authentication
      2 //
      3 // OIDC is used when auth.oidc.discovery_url is configured, built-in accounts
      4 // otherwise. The two do not mix: with OIDC the provider owns identity and
      5 // local accounts cannot sign in at all.
      6 
      7 import { verifyToken, parseCookies, SESSION_COOKIE } from './token.js';
      8 import { createOidc } from './oidc.js';
      9 
     10 export { issueToken, verifyToken, sessionCookie, clearedCookie, SESSION_COOKIE } from './token.js';
     11 export { hashPassword, verifyPassword, generatePassword, validatePassword } from './password.js';
     12 
     13 export function createAuth({ cfg, users, logger = console }) {
     14   const oidc = cfg.auth.mode === 'oidc' ? createOidc(cfg) : null;
     15   const localLogin = cfg.auth.mode !== 'oidc';
     16 
     17   // The session cookie is the only credential the conductor itself accepts.
     18   // Workers and triggers authenticate on their own routes.
     19   function credentialFrom(req) {
     20     const cookies = parseCookies(req.headers.cookie);
     21     return cookies[SESSION_COOKIE] ?? null;
     22   }
     23 
     24   return {
     25     mode: cfg.auth.mode,
     26     localLogin,
     27     // The provider client, or null for built-in accounts. The interface
     28     // uses it to start the browser login and to end the provider session.
     29     oidc,
     30 
     31     // Resolves a request to a user, or null. Never throws: an unparseable
     32     // credential is simply not authenticated.
     33     async identify(req) {
     34       const credential = credentialFrom(req);
     35       if (!credential) return null;
     36 
     37       if (oidc) {
     38         try {
     39           const profile = await oidc.verify(credential);
     40 
     41           // The provider vouched for them, but they still need a local row
     42           // to own anything, since projects and worker tokens reference
     43           // users(id). Created on first sight, refreshed after that.
     44           const account = await users.upsertExternal({
     45             externalId: `${profile.issuer}|${profile.subject}`,
     46             username: profile.username,
     47             role: profile.role,
     48           });
     49 
     50           // An administrator can still lock out an account the provider
     51           // would happily keep admitting.
     52           if (account.disabled === 1) return null;
     53 
     54           return { id: account.id, username: account.username, role: account.role, source: 'oidc' };
     55         } catch (e) {
     56           logger.debug?.(`oidc verification failed: ${e.message}`);
     57           return null;
     58         }
     59       }
     60 
     61       const claims = verifyToken(cfg.auth.session_secret, credential);
     62       if (!claims) return null;
     63 
     64       // Re-read the account, so disabling a user takes effect immediately
     65       // rather than when their token happens to expire.
     66       const user = await users.get(claims.sub);
     67       if (!user || user.disabled === 1) return null;
     68 
     69       return { id: user.id, username: user.username, role: user.role, source: 'local' };
     70     },
     71   };
     72 }