All files / realm/blong-access/adapter/db accessCredentialCheck.ts

91.75% Statements 89/97
37.5% Branches 3/8
100% Functions 1/1
91.75% Lines 89/97

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 981x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x     28x 28x     28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x     28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x     28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 1x  
import {handler} from '@feasibleone/blong';
 
import * as account from './account.ts';
 
export default handler(
    ({
        errors,
        lib: {crockfordEncode, verifyPassword},
        handler: {accessPermissionList},
    }) =>
        async function accessCredentialCheck(
            params: {username: string; password: string},
            $meta: Record<string, unknown>,
        ): Promise<{
            userId: string;
            /** Base64 of the raw binary(16) user key — for session creation. */
            userKey: string;
            /** Active credential id — for session creation. */
            credentialId: number;
            permissionMap: string;
            actions: string[];
        }> {
            const queryBuilder = this.config?.context?.queryBuilder;
            if (!queryBuilder) throw new Error('Database not available');
 
            // 1. Find the user by username (resourceName) and type alias 'access.user'
            const user = await queryBuilder
                .select('r.resourceId', 'u.userId', 'u.isActive')
                .from('core_resource as r')
                .join('access_user as u', 'u.userId', 'r.resourceId')
                .join('core_type as t', 't.typeId', 'r.typeId')
                .where('r.resourceName', params.username)
                .where('t.typeAlias', 'access.user')
                .first();
 
            if (!user) {
                throw errors.userNotFound();
            }
 
            if (!user.isActive) {
                throw errors.userInactive();
            }
 
            // 2. Find the active password credential for this user
            const credential = await queryBuilder
                .select(
                    'credentialId',
                    'credentialHash',
                    'credentialSalt',
                    'credentialParamsJSON',
                )
                .from('access_credential')
                .where('userId', user.userId)
                .where('credentialType', 'password')
                .where('isActive', 1)
                .where(function () {
                    this.whereNull('expiresAt').orWhere('expiresAt', '>', new Date());
                })
                .first();
 
            if (!credential) {
                throw errors.credentialNotFound();
            }
 
            // 3. Verify password using the credential parameters stored on the row
            // (`credentialParamsJSON` is parsed to an object by the knex adapter;
            // fall back to config.password when it was not persisted).
            if (
                !verifyPassword(
                    params.password,
                    credential.credentialHash,
                    credential.credentialSalt,
                    credential.credentialParamsJSON,
                )
            ) {
                throw errors.credentialsMismatch();
            }
 
            // 4. Resolve effective role bits + action names from the materialized core_path
            const {permissionMap, actions: actionNames} = await accessPermissionList<{
                roleBits: number[];
                actions: string[];
                permissionMap: string;
            }>(
                {userId: account.bufToUuid(user.userId)},
                $meta,
            );
 
            return {
                userId: crockfordEncode(user.userId),
                userKey: Buffer.from(user.userId).toString('base64'),
                credentialId: credential.credentialId,
                permissionMap,
                actions: actionNames,
            };
        },
);