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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | 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 7x 7x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 2x 2x 3x 3x 3x 3x 3x 3x 5x 5x 5x 5x 3x 3x 3x 5x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 5x 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 1x | import {type IMeta, handler} from '@feasibleone/blong';
import {type EnsuredRole} from './accessModel.ts';
import * as account from './account.ts';
import {type PasswordParams} from './password.ts';
type KnexQb = any;
/**
* Low-level account creation for the access realm.
*
* Creates a `core_resource` (resourceName = email) + `access_user` row, one
* credential (PBKDF2 `password` or `google` subject id), the `hasRole` Guest
* edges, and refreshes the materialized `core_path`. Enforces email
* uniqueness. Returns the new `userId` (hex UUID string).
*/
export default handler(
({
errors,
handler: {
'db/accessRoleEnsure': accessRoleEnsure,
'db/coreResourceEnsure': coreResourceEnsure,
'db/coreTripleMerge': coreTripleMerge,
},
lib: {hashPassword, credentialPolicyParams},
}) =>
async function accessAccountAdd(
params: {
/** Resource name — the normalized email used as the login key. */
name: string;
/** Display email stored on `access_user` (defaults to `name`). */
emailAddress?: string;
/** Creates a PBKDF2 password credential when present. */
password?: string;
/** Creates a `google` credential storing the provider subject id. */
googleSubjectId?: string;
isActive?: boolean;
/** Comma-separated role names to grant (e.g. `Guest`). */
roles?: string;
},
$meta: IMeta,
): Promise<{userId: string}> {
const qb: KnexQb = this.config?.context?.queryBuilder;
if (!qb) throw new Error('Database not available');
const email = params.emailAddress ?? params.name;
// Uniqueness: no existing access.user resource with this resourceName
const existing = await qb
.select('core_resource.resourceId')
.from('core_resource')
.join('core_type', 'core_resource.typeId', 'core_type.typeId')
.where('core_type.typeAlias', 'access.user')
.where('core_resource.resourceName', params.name)
.first();
if (existing) {
throw errors.errorAccountExists({params: {emailAddress: email}});
}
const {resourceId: userId} = await coreResourceEnsure<{resourceId: string}>(
{
name: params.name,
typeAlias: 'access.user',
table: 'access_user',
extraColumns: {emailAddress: email, isActive: params.isActive ?? 1},
keyName: 'userId',
},
$meta,
);
// Credential — password (PBKDF2) or Google subject id
if (params.password) {
const salt = account.newUuid();
// Credential params come from the active policy; config.password is the fallback.
const policyParams = await credentialPolicyParams(qb, 'password');
const {hash, params: credentialParams} = hashPassword<{
hash: string;
params: PasswordParams;
}>(params.password, salt, policyParams);
await qb('access_credential')
.insert({
userId: account.uuidBuf(userId),
credentialType: 'password',
credentialHash: hash,
credentialSalt: salt,
// `*JSON` column — the knex adapter stores this object as JSON.
credentialParamsJSON: credentialParams,
isActive: 1,
})
.onConflict()
.ignore();
} else if (params.googleSubjectId) {
await qb('access_credential')
.insert({
userId: account.uuidBuf(userId),
credentialType: 'google',
credentialHash: params.googleSubjectId,
credentialSalt: '',
isActive: 1,
})
.onConflict()
.ignore();
}
// Role edges (e.g. Guest) — batched via the shared `core.triple.merge`
// helper (P3), which also refreshes `access_path` once.
const triples: Array<{subjectId: string; predicateName: string; objectId: string}> = [];
if (params.roles) {
const roleNames = account.splitNames(params.roles);
for (const roleName of roleNames) {
// Roles are never created here with a hardcoded bit any more:
// `access.role.ensure` allocates one when the role is new and
// returns the existing role — with its own bit — otherwise.
const {role} = await accessRoleEnsure<EnsuredRole>({role: {roleName}}, $meta);
triples.push({
subjectId: userId,
predicateName: 'hasRole',
objectId: role.roleId,
});
}
}
await coreTripleMerge({triples, refreshPath: true}, $meta);
return {userId};
},
);
|