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

80% Statements 72/90
26.31% Branches 5/19
50% Functions 1/2
80% Lines 72/90

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 911x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x               1x 1x 1x 1x 1x 1x 1x 1x                       1x 1x 1x 1x 1x 1x 1x 1x 7x 1x  
import {type IMeta, handler} from '@feasibleone/blong';
 
import * as model from './accessModel.ts';
import * as account from './account.ts';
 
type KnexQb = any;
 
/**
 * `access.user.add` — create a user (resource + `access_user` row), plus
 * optional credentials and granted roles.
 *
 * Reuses `core.resource.ensure` to create the resource-backed row (the generic
 * knex `add` cannot — the PK is `uidNotNull`, not `uuid()`), and
 * `core.triple.merge` for the `hasRole` edges. Credentials are hashed via the
 * shared password library. A submitted `matrix` seeds the user's own
 * scope-level `access_acl` rules.
 */
export default handler(
    ({
        handler: {
            'db/coreResourceEnsure': coreResourceEnsure,
            'db/coreTripleMerge': coreTripleMerge,
        },
        lib: {hashPassword, credentialPolicyParams, ulid, crockfordDecode},
    }) => ({
        async accessUserAdd(
            params: {
                user?: {emailAddress?: string; isActive?: boolean; userName?: string};
                credential?: Array<Record<string, unknown>>;
                role?: Array<{roleId?: string; roleName?: string; granted?: boolean}>;
                matrix?: model.AclMatrixRow[];
            },
            $meta: IMeta,
        ): Promise<Record<string, unknown>> {
            const qb: KnexQb = this.config?.context?.queryBuilder;
            if (!qb) throw new Error('Database not available');
            const user = params.user ?? {};
            const name = user.emailAddress || user.userName || `user-${Date.now()}`;
            const {resourceId} = await coreResourceEnsure<{resourceId: string}>(
                {
                    name,
                    typeAlias: 'access.user',
                    table: 'access_user',
                    extraColumns: {
                        emailAddress: user.emailAddress ?? null,
                        isActive: user.isActive ?? 1,
                    },
                    keyName: 'userId',
                },
                $meta,
            );
            const userIdHex = model.binHex(resourceId);
            if (!userIdHex) throw new Error('Could not resolve user resource id');
            if (Array.isArray(params.credential) && params.credential.length) {
                await model.syncCredentials(
                    qb,
                    {hashPassword, credentialPolicyParams},
                    userIdHex,
                    params.credential,
                );
            }
            const roleIds = (params.role ?? [])
                .filter(r => r.granted !== false)
                .map(r => model.binHex(r.roleId))
                .filter((x): x is string => !!x);
            if (roleIds.length) {
                await model.syncEdges(qb, coreTripleMerge, userIdHex, 'hasRole', roleIds, $meta);
            }
            if (Array.isArray(params.matrix) && params.matrix.length) {
                await model.syncAclMatrix(
                    qb,
                    {
                        coreResourceEnsure,
                        newAclId: () => Buffer.from(crockfordDecode(ulid())),
                    },
                    userIdHex,
                    params.matrix,
                    $meta,
                );
            }
            return {
                user: {
                    userId: model.bufToBase64(account.uuidBuf(resourceId)),
                    emailAddress: user.emailAddress ?? null,
                    isActive: user.isActive ?? 1,
                },
            };
        },
    }),
);