All files / realm/blong-gateway/adapter/db gatewayApplicationRegister.ts

100% Statements 86/86
60% Branches 3/5
100% Functions 1/1
100% Lines 86/86

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 871x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x  
import {type IMeta, handler} from '@feasibleone/blong';
 
import {newUuid, uuidBuf} from './gatewayUuid.ts';
 
type KnexQb = any;
 
/**
 * Register an OAuth application for a developer.
 *
 * Wire: `gateway.application.register` — creates the application resource
 * (clientId = resourceName) plus its `clientSecret` credential (via
 * `access.credential.add`).  The client secret is returned ONCE; it cannot be
 * retrieved later (only re-issued by rotating the credential).
 *
 * Uses the shared `core.resource.ensure` helper (find-or-create by name) so
 * re-registering the same clientId is idempotent — the existing resource is
 * reused and `access.credential.add` rotates the active secret.
 */
export default handler(
    ({
        errors,
        lib: {crockfordDecode, crockfordEncode},
        handler: {'db/coreResourceEnsure': coreResourceEnsure, accessCredentialAdd},
    }) =>
        async function gatewayApplicationRegister(
            params: {
                /** ClientId / application display name. */
                clientId: string;
                /** Owner (developer) resource id (hex UUID); defaults to the authenticated user. */
                ownerUserId?: string;
                applicationType?: string;
                description?: string;
                isActive?: boolean;
            },
            $meta: IMeta,
        ): Promise<{
            applicationId: string;
            clientId: string;
            clientSecret: string;
        }> {
            const qb: KnexQb = this.config?.context?.queryBuilder;
            if (!qb) throw new Error('Database not available');
 
            // Resolve the owner from the authenticated developer when not supplied.
            const ownerUserId =
                params.ownerUserId ??
                (() => {
                    const actorId = ($meta?.auth as {actorId?: string} | undefined)?.actorId;
                    if (!actorId) throw errors.applicationNotFound();
                    return Buffer.from(crockfordDecode(actorId) as Uint8Array).toString('hex');
                })();
 
            const {resourceId: applicationId} = await coreResourceEnsure<{resourceId: string}>(
                {
                    name: params.clientId,
                    typeAlias: 'gateway.application',
                    table: 'gateway_application',
                    extraColumns: {
                        ownerUserId: uuidBuf(ownerUserId),
                        applicationType: params.applicationType ?? 'oauth2_client',
                        description: params.description ?? 'Registered application',
                        isActive: params.isActive ?? 1,
                    },
                    keyName: 'applicationId',
                },
                $meta,
            );
 
            const clientSecret = newUuid();
            await accessCredentialAdd<{success: boolean}>(
                {
                    subjectResourceId: applicationId,
                    credentialType: 'clientSecret',
                    secret: clientSecret,
                    isActive: params.isActive ?? 1,
                },
                $meta,
            );
 
            return {
                applicationId: crockfordEncode(uuidBuf(applicationId)),
                clientId: params.clientId,
                clientSecret,
            };
        },
);