auth

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

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