password.js (5566B)
1 import { app } from '../../app.js'; 2 import { resolveClientForHost, resolveHostContext } from '../../lib/host-context.js'; 3 import { HttpError } from '../../lib/http/http-error.js'; 4 import { findManagedProviderForUser, resolveLogin } from '../../lib/login.js'; 5 import { getClient } from '../../lib/oidc/clients.js'; 6 import { 7 getAuthRequest, 8 readAuthRequestId, 9 selectAuthRequestAccount, 10 } from '../../lib/oidc/requests.js'; 11 import { verifyDummyPassword, verifyPassword } from '../../lib/password.js'; 12 import { canUseClient } from '../../lib/policy.js'; 13 import { getSystemDatabase, openRealmDatabase, realmMetadata } from '../../lib/realm.js'; 14 import { 15 LONG_SESSION_TTL, 16 SHORT_SESSION_TTL, 17 createHostSession, 18 purgeExpiredSessions, 19 requestIp, 20 requestToken, 21 setSessionCookie, 22 } from '../../lib/session.js'; 23 import { 24 findUserByIdentifier, 25 getMembership, 26 getPasswordCredential, 27 getUserRoles, 28 updateLastLogin, 29 } from '../../lib/users.js'; 30 31 export default { 32 method: 'post', 33 path: '/api/v1/login/password', 34 name: 'login.password', 35 async handler(req, res) { 36 const body = req.body || {}; 37 if (typeof body.identifier !== 'string') { 38 throw new HttpError(400, 'invalid_request', 'identifier is required'); 39 } 40 if (typeof body.password !== 'string') { 41 throw new HttpError(400, 'invalid_request', 'password is required'); 42 } 43 if (body.password.length > 1024) { 44 throw new HttpError(400, 'invalid_request', 'password is too long'); 45 } 46 if (typeof body.client_id !== 'string') { 47 throw new HttpError(400, 'invalid_request', 'client_id is required'); 48 } 49 if (body.long_session != null && typeof body.long_session !== 'boolean') { 50 throw new HttpError(400, 'invalid_request', 'long_session must be a boolean'); 51 } 52 53 const hostContext = await resolveHostContext(req, { 54 publicUrl: app.config.server.public_url, 55 }); 56 const { realm, clientId } = await resolveClientForHost(hostContext, body.client_id); 57 const metadata = await realmMetadata(realm); 58 const realmDb = await openRealmDatabase(realm); 59 const systemDb = getSystemDatabase(); 60 const client = await getClient(realmDb, clientId); 61 if (!client || !client.enabled) { 62 throw new HttpError(404, 'unknown_client', 'Unknown client'); 63 } 64 65 if (body.identifier.includes('@')) { 66 const resolution = await resolveLogin({ identifier: body.identifier }); 67 if (resolution.next !== 'password') { 68 throw new HttpError(403, 'provider_required', 'This account must sign in with its identity provider'); 69 } 70 } 71 72 const user = await findUserByIdentifier(systemDb, body.identifier); 73 const credential = user ? await getPasswordCredential(systemDb, user.id) : null; 74 const membership = user ? await getMembership(realmDb, user.id) : null; 75 const roles = user ? await getUserRoles(realmDb, user.id) : []; 76 const pendingId = readAuthRequestId(req, realm); 77 const pending = pendingId ? await getAuthRequest(realmDb, pendingId) : null; 78 const pendingClient = pending && !pending.completed_at && pending.client_id === client.client_id; 79 80 if (!credential) { 81 await verifyDummyPassword(body.password); 82 throw new HttpError(401, 'invalid_credentials', 'Invalid credentials'); 83 } 84 85 const valid = await verifyPassword(body.password, credential.secret_hash); 86 if (!valid) { 87 throw new HttpError(401, 'invalid_credentials', 'Invalid credentials'); 88 } 89 90 if (!user || user.status !== 'active') { 91 throw new HttpError(401, 'invalid_credentials', 'Invalid credentials'); 92 } 93 94 const allowedByPolicy = canUseClient({ 95 user, 96 membership, 97 roles, 98 client, 99 realm, 100 }); 101 const member = membership && membership.status === 'active'; 102 const publicNonMember = client.access_mode === 'public' && !client.virtual && pendingClient; 103 const allowed = 104 allowedByPolicy && 105 (member || client.virtual || publicNonMember || user.is_system_admin); 106 107 if (!allowed) { 108 throw new HttpError(401, 'invalid_credentials', 'Invalid credentials'); 109 } 110 111 if (!body.identifier.includes('@')) { 112 const provider = await findManagedProviderForUser(systemDb, user); 113 if (provider && !provider.enabled) { 114 throw new HttpError(409, 'provider_disabled', 'The identity provider for this account is unavailable'); 115 } 116 if (provider) { 117 throw new HttpError(403, 'provider_required', 'This account must sign in with its identity provider'); 118 } 119 } 120 121 const longSession = body.long_session === true && metadata.allow_session_duration_choice; 122 const ttl = longSession ? LONG_SESSION_TTL : SHORT_SESSION_TTL; 123 124 await purgeExpiredSessions(systemDb); 125 const { token, session } = await createHostSession(systemDb, { 126 context: hostContext, 127 token: requestToken(req), 128 userId: user.id, 129 ttl, 130 userAgent: req.headers['user-agent'] || null, 131 ip: requestIp(req), 132 }); 133 setSessionCookie(res, req, token, session.ttl); 134 if (pendingClient && pending) { 135 await selectAuthRequestAccount(realmDb, pending.id, { 136 userId: user.id, 137 sessionId: session.id, 138 authTime: session.auth_time, 139 }); 140 } 141 await updateLastLogin(systemDb, user.id); 142 143 return new app.HttpOk({ 144 realm: metadata, 145 user: { 146 id: user.id, 147 username: user.username, 148 display_name: user.display_name, 149 }, 150 expires_at: session.expires_at, 151 pending_auth_request: Boolean(pendingClient), 152 }); 153 }, 154 };