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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | 1x 1x 1x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x | import {schema} from '@feasibleone/blong';
export default schema(async ({lib: {type}}) => ({
/**
* User profiles attached to core.resource records.
*
* `core.resource.typeId` discriminates between 'person', 'organization', and 'system'
* profiles. The PK `userId` is also a FK to core.resource.resourceId,
* so every user corresponds to a resource entity.
*/
user: type.Object(
{
userId: type.uidNotNull(),
emailAddress: type.stringNull(),
isActive: type.booleanNotNull(),
},
{
constraints: {
primaryKey: 'userId',
foreign: {
userId: 'core.resource.resourceId',
},
},
},
),
/**
* Authenticator records bound to a user or an application.
*
* `credentialType` is 'password', 'clientSecret' or 'google'. The secret is
* hashed with PBKDF2 (or a stronger algorithm) before storage. The FK is
* to `core.resource.resourceId` so a credential can be bound to either an
* `access.user` or a `gateway.application` (both are core.resource-backed).
*/
credential: type.Object(
{
credentialId: type.increment(),
userId: type.uidNotNull(),
credentialType: type.stringNotNull(),
credentialHash: type.stringNotNull(),
credentialSalt: type.stringNotNull(),
/**
* Credential function parameters as a JSON document (string column,
* auto-(de)serialized by the knex adapter for `*JSON` columns).
* Contains the function name and its parameters, e.g.
* `{"function":"hash","algorithm":"pbkdf2","iterations":100000,
* "keyLength":64,"digest":"sha512"}` — stored so verification
* re-uses the exact parameters that produced `credentialHash`.
*/
credentialParamsJSON: type.stringNull(),
isActive: type.booleanNotNull(),
expiresAt: type.dateTimeNull(),
},
{
constraints: {
foreign: {
userId: 'core.resource.resourceId',
},
},
},
),
/**
* Registered actions that the backend supports.
*
* The action name is stored in core.resource.resourceName and follows
* the semantic-triple naming convention (subjectObjectPredicate) to
* match API methods.
*/
action: type.Object(
{
actionId: type.uidNotNull(),
description: type.stringNull(),
},
{
constraints: {
primaryKey: 'actionId',
foreign: {
actionId: 'core.resource.resourceId',
},
},
},
),
/**
* Named business capabilities that group low-level actions.
*
* Capabilities are the "what" — they bundle related actions into
* a meaningful concept (e.g. "userManagement", "reportViewing").
* The capability name is stored in core.resource.resourceName.
*/
capability: type.Object(
{
capabilityId: type.uidNotNull(),
description: type.stringNull(),
},
{
constraints: {
primaryKey: 'capabilityId',
foreign: {
capabilityId: 'core.resource.resourceId',
},
},
},
),
/**
* Named roles that group capabilities.
*
* Roles are the intermediate link between users and capabilities.
* Users are assigned roles, roles carry capabilities.
* The role name is stored in core.resource.resourceName.
*/
role: type.Object(
{
roleId: type.uidNotNull(),
roleBit: type.integerNotNull({min: 0, max: 1023}),
description: type.stringNull(),
},
{
constraints: {
primaryKey: 'roleId',
unique: {
roleBit: {},
},
foreign: {
roleId: 'core.resource.resourceId',
},
},
},
),
/**
* Access rules applicable to the system.
*
* Each access record defines conditions that must be satisfied
* for access to be granted — e.g. time restrictions, IP allow
* lists, geo-fencing. `accessRule` holds the rule configuration
* as a JSON string. The access rule name is stored in
* core.resource.resourceName.
*/
access: type.Object(
{
accessId: type.uidNotNull(),
accessType: type.stringNotNull(),
accessRule: type.stringNotNull(),
isActive: type.booleanNotNull(),
},
{
constraints: {
primaryKey: 'accessId',
foreign: {
accessId: 'core.resource.resourceId',
},
},
},
),
/**
* Credential policy rules.
*
* Defines complexity requirements and lifecycle constraints per
* credential type (e.g. minimum password length, required
* character classes, max age, max failed attempts).
* The policy name is stored in core.resource.resourceName.
*/
policy: type.Object(
{
policyId: type.uidNotNull(),
credentialType: type.stringNotNull(),
minLength: type.integerNull(),
requireSpecialChar: type.booleanNull(),
requireNumber: type.booleanNull(),
requireUppercase: type.booleanNull(),
maxAgeDays: type.integerNull(),
maxAttempts: type.integerNull(),
/**
* Credential function parameters the policy dictates for new
* credentials of this type (JSON document, `*JSON` column — see
* `credential.credentialParamsJSON`).
*/
credentialParamsJSON: type.stringNull(),
isActive: type.booleanNotNull(),
},
{
constraints: {
primaryKey: 'policyId',
foreign: {
policyId: 'core.resource.resourceId',
},
},
},
),
/**
* Authentication flows that define multi-factor login steps.
*
* `flowSteps` is a JSON array of step definitions — e.g.
* `["password", "totp"]` for a two-factor flow.
* The flow name is stored in core.resource.resourceName.
*/
flow: type.Object(
{
flowId: type.uidNotNull(),
flowSteps: type.stringNotNull(),
isActive: type.booleanNotNull(),
},
{
constraints: {
primaryKey: 'flowId',
foreign: {
flowId: 'core.resource.resourceId',
},
},
},
),
/**
* Active user sessions (ephemeral runtime records).
*
* Session records are created upon successful authentication and
* are not persisted as core.resource entries — the PK is a
* standalone uid.
*/
session: type.Object(
{
sessionId: type.uidNotNull(),
userId: type.uidNotNull(),
credentialId: type.bigIntNotNull(),
/**
* SHA-256 hex digest of the current refresh token. Rotated on
* every token renewal so a stolen refresh token can be detected
* and the session revoked.
*/
tokenHash: type.stringNotNull(),
issuedAt: type.dateTimeNotNull(),
/**
* Absolute expiry of the refresh token (issuedAt + refresh TTL).
* Renewal is refused once this passes.
*/
expiresAt: type.dateTimeNotNull(),
/**
* Last activity timestamp. Updated on token renewal and by
* explicit `access.session.verify` (critical ops). Sessions whose
* last activity is older than the inactivity timeout are refused
* renewal and reported as inactive by `verify`.
*/
lastActivityAt: type.dateTimeNotNull(),
ipAddress: type.stringNull(),
isRevoked: type.booleanNotNull(),
/** When the session was revoked (logout / security action). */
revokedAt: type.dateTimeNull(),
/**
* SHA-256 hex digest of the opaque restore-cookie handle (set on
* login, cleared on revoke, rotated on restore). Only the restore
* endpoint (path-scoped cookie) can use it.
*/
cookieHash: type.stringNull(),
},
{
constraints: {
foreign: {
userId: 'access.user.userId',
credentialId: 'access.credential.credentialId',
},
},
},
),
/**
* Append-only audit log for authentication events.
*
* Each entry records an attempt (success or failure) with context
* for security analysis. Uses a ULID PK to avoid
* core.resource overhead.
*/
audit: type.Object({
auditId: type.ulid(),
/** Binary user key (base64 on the wire) — used by login events. */
userId: type.uidNull(),
/**
* Readable actor reference (crockford/base64 actorId from the JWT)
* — used by gateway access-check events where the raw binary key is
* not available.
*/
actorId: type.stringNull(),
/** Session id (dashed UUID string) the event belongs to, if any. */
sessionId: type.stringNull(),
/**
* The operation recorded — a semantic-triple method name for
* access-check/DML events, or a label like 'login' / 'logout'.
*/
actionName: type.stringNotNull(),
credentialType: type.stringNull(),
ipAddress: type.stringNull(),
isSuccess: type.booleanNotNull(),
failureReason: type.stringNull(),
/** HTTP status code for access-check events (200 allow / 403 deny). */
statusCode: type.integerNull(),
/**
* Optional JSON context — for access-table DML events a sanitised
* summary of the affected entity key(s), never full params.
*/
detail: type.stringNull(),
occurredAt: type.dateTimeNotNull(),
}),
/**
* Record-level access control list.
*
* One row grants or refuses one action on one target for one principal:
*
* - `principalId` — a user, role, unit or capability resource. A row on the
* **user** is an *explicit* grant; a row on a role/unit the user inherits is
* the *implicit* half of the ACL (the hierarchy-derived grant).
* - `actionId` — the `access_action` resource, i.e. the guarded method.
* - `targetKind` — `record` (a single row of the guarded table) or `scope`
* (every record linked to that scope through the table's declared scope
* predicates, e.g. `belongsTo` / `isPartOf`).
* - `effect` — `allow` / `deny`. **A deny always wins**, which is how an
* implicitly enabled record is explicitly forbidden.
*
* The effective ACL is evaluated in SQL at query time (no materialized
* effective table) — see `ISchemaTable.acl` and the adapter's `acl` helpers.
* A ULID PK is used because the row is a rule, not a named entity.
*/
acl: type.Object(
{
aclId: type.ulid(),
/** The principal the rule applies to (user / role / unit / capability). */
principalId: type.uidNotNull(),
/** The guarded action resource (`access_action`). */
actionId: type.uidNotNull(),
/** The guarded record, or a scope node when `targetKind` is `scope`. */
targetId: type.uidNotNull(),
/** `record` — a single record; `scope` — every record in that scope. */
targetKind: type.stringNotNull({maxLength: 8}),
/** `allow` or `deny`; a deny always wins over every allow. */
effect: type.stringNotNull({maxLength: 8}),
isActive: type.booleanNotNull(),
},
{
constraints: {
foreign: {
principalId: 'core.resource.resourceId',
actionId: 'access.action.actionId',
targetId: 'core.resource.resourceId',
},
unique: {
principalActionTarget: {
columns: ['principalId', 'actionId', 'targetKind', 'targetId'],
},
},
index: {
target: {columns: ['actionId', 'targetKind', 'targetId']},
},
},
},
),
}));
|