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 }