@warlock.js/access 4.8.1 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/index.cjs +9 -9
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/access-resolver.d.mts +1 -1
- package/esm/contracts/access-resolver.d.mts.map +1 -1
- package/esm/contracts/types.d.mts +1 -1
- package/esm/contracts/types.d.mts.map +1 -1
- package/esm/middleware/gate.middleware.d.mts +1 -1
- package/esm/middleware/gate.middleware.d.mts.map +1 -1
- package/esm/middleware/gate.middleware.mjs +1 -1
- package/esm/middleware/gate.middleware.mjs.map +1 -1
- package/esm/services/access-config.d.mts +1 -1
- package/esm/services/access-config.d.mts.map +1 -1
- package/esm/services/access-config.mjs +1 -1
- package/esm/services/access-config.mjs.map +1 -1
- package/esm/services/access.d.mts +1 -1
- package/esm/services/access.d.mts.map +1 -1
- package/esm/services/access.mjs +1 -1
- package/esm/services/access.mjs.map +1 -1
- package/esm/services/default-resolver.d.mts +1 -1
- package/esm/services/default-resolver.d.mts.map +1 -1
- package/esm/services/default-resolver.mjs +1 -1
- package/esm/services/default-resolver.mjs.map +1 -1
- package/esm/services/engine.d.mts +1 -1
- package/esm/services/engine.d.mts.map +1 -1
- package/esm/services/engine.mjs +1 -1
- package/esm/services/engine.mjs.map +1 -1
- package/esm/services/matcher.mjs +1 -1
- package/esm/services/matcher.mjs.map +1 -1
- package/esm/services/policies.d.mts +1 -1
- package/esm/services/policies.d.mts.map +1 -1
- package/esm/services/policies.mjs +1 -1
- package/esm/services/policies.mjs.map +1 -1
- package/esm/utils/access-config-error.d.mts +1 -1
- package/esm/utils/access-config-error.d.mts.map +1 -1
- package/esm/utils/access-config-error.mjs +1 -1
- package/esm/utils/access-config-error.mjs.map +1 -1
- package/esm/utils/access-error-codes.d.mts +1 -1
- package/esm/utils/access-error-codes.d.mts.map +1 -1
- package/esm/utils/access-error-codes.mjs +1 -1
- package/esm/utils/access-error-codes.mjs.map +1 -1
- package/package.json +7 -7
package/cjs/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@ let _warlock_js_core = require("@warlock.js/core");
|
|
|
3
3
|
let _warlock_js_cache = require("@warlock.js/cache");
|
|
4
4
|
let _warlock_js_logger = require("@warlock.js/logger");
|
|
5
5
|
|
|
6
|
-
//#region
|
|
6
|
+
//#region ../access/src/utils/access-error-codes.ts
|
|
7
7
|
/**
|
|
8
8
|
* Wire-stable error codes emitted by `@warlock.js/access`. Map these in your
|
|
9
9
|
* error transformer the way you map `AuthErrorCodes`. A value change here is a
|
|
@@ -16,7 +16,7 @@ let AccessErrorCodes = /* @__PURE__ */ function(AccessErrorCodes) {
|
|
|
16
16
|
}({});
|
|
17
17
|
|
|
18
18
|
//#endregion
|
|
19
|
-
//#region
|
|
19
|
+
//#region ../access/src/utils/access-config-error.ts
|
|
20
20
|
/**
|
|
21
21
|
* Thrown when `@warlock.js/access` is misconfigured (e.g. no resolver). Unlike a
|
|
22
22
|
* runtime resolution failure — which fails CLOSED (denies) — a config error is a
|
|
@@ -30,7 +30,7 @@ var AccessConfigError = class extends Error {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
//#endregion
|
|
33
|
-
//#region
|
|
33
|
+
//#region ../access/src/services/access-config.ts
|
|
34
34
|
/**
|
|
35
35
|
* The active configuration, fed once at boot by the framework's access connector
|
|
36
36
|
* via {@link setAccessConfig} (the connector reads `src/config/access.ts`).
|
|
@@ -76,7 +76,7 @@ const accessConfig = {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
//#endregion
|
|
79
|
-
//#region
|
|
79
|
+
//#region ../access/src/services/matcher.ts
|
|
80
80
|
/**
|
|
81
81
|
* Whether a set of granted permission patterns covers a requested permission.
|
|
82
82
|
*
|
|
@@ -96,7 +96,7 @@ function matchesPermission(granted, permission) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
//#endregion
|
|
99
|
-
//#region
|
|
99
|
+
//#region ../access/src/services/policies.ts
|
|
100
100
|
const policies = /* @__PURE__ */ new Map();
|
|
101
101
|
/**
|
|
102
102
|
* Register an ABAC condition for a permission. It runs ON TOP of the RBAC grant
|
|
@@ -119,7 +119,7 @@ function clearPolicies() {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
//#endregion
|
|
122
|
-
//#region
|
|
122
|
+
//#region ../access/src/services/engine.ts
|
|
123
123
|
const PERMISSIONS_PREFIX = "access.perms.";
|
|
124
124
|
const ROLES_PREFIX = "access.roles.";
|
|
125
125
|
/** Resolve the effective tenant for a check (explicit → ambient → undefined). */
|
|
@@ -197,7 +197,7 @@ async function check(user, permission, context = {}) {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
//#endregion
|
|
200
|
-
//#region
|
|
200
|
+
//#region ../access/src/services/access.ts
|
|
201
201
|
/** Whether the user holds `permission` (and passes its policy when a resource is given). */
|
|
202
202
|
function can(user, permission, context) {
|
|
203
203
|
return check(user, permission, context);
|
|
@@ -271,7 +271,7 @@ const access = {
|
|
|
271
271
|
};
|
|
272
272
|
|
|
273
273
|
//#endregion
|
|
274
|
-
//#region
|
|
274
|
+
//#region ../access/src/middleware/gate.middleware.ts
|
|
275
275
|
function deny(response) {
|
|
276
276
|
return response.forbidden({
|
|
277
277
|
error: (0, _warlock_js_core.t)("access.errors.forbidden"),
|
|
@@ -309,7 +309,7 @@ function gateAll(permissions) {
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
//#endregion
|
|
312
|
-
//#region
|
|
312
|
+
//#region ../access/src/services/default-resolver.ts
|
|
313
313
|
/** Coerce a roles value (`undefined` / single string / array) into a string array. */
|
|
314
314
|
function toRoleList(value) {
|
|
315
315
|
if (Array.isArray(value)) return value;
|
package/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["cache","ForbiddenError","flushUser"],"sources":["../../../../../../@warlock.js/access/src/utils/access-error-codes.ts","../../../../../../@warlock.js/access/src/utils/access-config-error.ts","../../../../../../@warlock.js/access/src/services/access-config.ts","../../../../../../@warlock.js/access/src/services/matcher.ts","../../../../../../@warlock.js/access/src/services/policies.ts","../../../../../../@warlock.js/access/src/services/engine.ts","../../../../../../@warlock.js/access/src/services/access.ts","../../../../../../@warlock.js/access/src/middleware/gate.middleware.ts","../../../../../../@warlock.js/access/src/services/default-resolver.ts"],"sourcesContent":["/**\n * Wire-stable error codes emitted by `@warlock.js/access`. Map these in your\n * error transformer the way you map `AuthErrorCodes`. A value change here is a\n * breaking change for any client that reacts to the code.\n */\nexport enum AccessErrorCodes {\n /** EC100 — authenticated, but missing the required permission. */\n Forbidden = \"EC100\",\n}\n","/**\n * Thrown when `@warlock.js/access` is misconfigured (e.g. no resolver). Unlike a\n * runtime resolution failure — which fails CLOSED (denies) — a config error is a\n * developer mistake, so the engine re-throws it LOUD instead of silently denying.\n */\nexport class AccessConfigError extends Error {\n public constructor(message: string) {\n super(message);\n this.name = \"AccessConfigError\";\n }\n}\n","import type { Auth } from \"@warlock.js/auth\";\nimport type { AccessResolver } from \"../contracts/access-resolver\";\nimport type { AccessConfigurations } from \"../contracts/types\";\nimport { AccessConfigError } from \"../utils/access-config-error\";\n\n/**\n * The active configuration, fed once at boot by the framework's access connector\n * via {@link setAccessConfig} (the connector reads `src/config/access.ts`).\n * Reading it before it's set throws {@link AccessConfigError} — a loud\n * misconfiguration, never a silent deny.\n */\nlet current: AccessConfigurations | undefined;\n\n/**\n * Register the access configuration. The framework's access connector calls this\n * at boot with the default export of `src/config/access.ts`. It validates that a\n * `resolver` is present, so a misconfigured authorization layer fails at STARTUP\n * rather than on the first protected request.\n *\n * @example\n * setAccessConfig({ resolver: new DefaultAccessResolver({ admin: [\"*\"] }) });\n */\nexport function setAccessConfig(configuration: AccessConfigurations): void {\n if (!configuration?.resolver) {\n throw new AccessConfigError(\n \"No access resolver configured. Set `resolver` in src/config/access.ts to a class \" +\n \"implementing AccessResolver — the ejected DatabaseAccessResolver, or \" +\n \"`new DefaultAccessResolver(rolesMap)` for a fixed-role catalog.\",\n );\n }\n\n current = configuration;\n}\n\n/** Clear the active config. Used by the connector on dev-restart and by tests. */\nexport function resetAccessConfig(): void {\n current = undefined;\n}\n\n/** Accessors over the active access configuration. */\nexport const accessConfig = {\n /**\n * The configured resolver. Throws {@link AccessConfigError} if access was never\n * configured (no `src/config/access.ts`, or the connector hasn't run).\n */\n resolver(): AccessResolver {\n if (!current) {\n throw new AccessConfigError(\n \"@warlock.js/access is not configured. Add src/config/access.ts (exporting a `resolver`) \" +\n \"— it is wired at boot by the access connector.\",\n );\n }\n\n return current.resolver;\n },\n\n /** Cache TTL for resolved permission sets. */\n cacheTtl(): string | number {\n return current?.cache?.ttl ?? \"10m\";\n },\n\n /** Ambient tenant for this user, delegated to the resolver's optional hook. */\n tenant(user: Auth): string | undefined {\n return this.resolver().resolveTenant?.(user);\n },\n};\n","/**\n * Whether a set of granted permission patterns covers a requested permission.\n *\n * Supports three forms:\n * - exact — `\"orders.update\"` covers `\"orders.update\"`\n * - global — `\"*\"` covers everything (the super-grant)\n * - prefix wildcard — `\"orders.*\"` covers `\"orders.update\"` and `\"orders.update.status\"`\n *\n * `\"orders.*\"` does NOT cover the bare `\"orders\"` (no trailing segment).\n */\nexport function matchesPermission(granted: string[], permission: string): boolean {\n for (const pattern of granted) {\n if (pattern === \"*\" || pattern === permission) {\n return true;\n }\n\n // \"orders.*\" → prefix \"orders.\" — matches any nested permission.\n if (pattern.endsWith(\".*\") && permission.startsWith(pattern.slice(0, -1))) {\n return true;\n }\n }\n\n return false;\n}\n","import type { PolicyFn } from \"../contracts/types\";\n\nconst policies = new Map<string, PolicyFn>();\n\n/**\n * Register an ABAC condition for a permission. It runs ON TOP of the RBAC grant\n * whenever an authorization check carries a `resource` — letting you express\n * \"...but only their own / only in their tenant / only while pending\".\n *\n * @example\n * definePolicy(\"orders.update\", (user, order) => order.get(\"customer_id\") === user.id);\n */\nexport function definePolicy(permission: string, policy: PolicyFn): void {\n policies.set(permission, policy);\n}\n\n/** The policy registered for a permission, if any. */\nexport function getPolicy(permission: string): PolicyFn | undefined {\n return policies.get(permission);\n}\n\n/** Remove every registered policy. Primarily for tests. */\nexport function clearPolicies(): void {\n policies.clear();\n}\n","import type { Auth } from \"@warlock.js/auth\";\nimport { cache } from \"@warlock.js/cache\";\nimport { log } from \"@warlock.js/logger\";\nimport type { AccessContext } from \"../contracts/types\";\nimport { AccessConfigError } from \"../utils/access-config-error\";\nimport { accessConfig } from \"./access-config\";\nimport { matchesPermission } from \"./matcher\";\nimport { getPolicy } from \"./policies\";\n\nconst PERMISSIONS_PREFIX = \"access.perms.\";\nconst ROLES_PREFIX = \"access.roles.\";\n\n/** Resolve the effective tenant for a check (explicit → ambient → undefined). */\nfunction tenantFor(user: Auth, context: AccessContext): string | undefined {\n return context.tenant ?? accessConfig.tenant(user);\n}\n\nfunction cacheKey(prefix: string, user: Auth, tenant?: string): string {\n // Encode each segment so a `.` in an id or tenant can't collide two distinct\n // principals onto the same cache key (and serve one's grants to the other).\n const segment = (value: unknown) => encodeURIComponent(String(value));\n\n return `${prefix}${segment(user.userType)}.${segment(user.id)}.${segment(tenant ?? \"_\")}`;\n}\n\n/**\n * Read a list through the cache, falling back to the loader.\n *\n * The cache is **best-effort**: a cache failure degrades to the loader (the\n * source of truth) and is logged, never denied. A loader failure propagates —\n * so the DECISION fails closed in {@link check}.\n */\nasync function cached(\n key: string,\n loader: () => Promise<string[]>,\n): Promise<string[]> {\n try {\n const hit = await cache.get<string[]>(key);\n\n if (hit) return hit;\n } catch (error) {\n log.error(\"access\", \"cache\", error);\n }\n\n const value = await loader();\n\n try {\n await cache.set(key, value, { ttl: accessConfig.cacheTtl() });\n } catch (error) {\n log.error(\"access\", \"cache\", error);\n }\n\n return value;\n}\n\n/** The user's effective permission set for a tenant (cached). */\nexport function permissionsFor(user: Auth, tenant?: string): Promise<string[]> {\n return cached(cacheKey(PERMISSIONS_PREFIX, user, tenant), () =>\n accessConfig.resolver().resolvePermissions(user, tenant),\n );\n}\n\n/** The user's roles for a tenant (cached). */\nexport function rolesFor(user: Auth, tenant?: string): Promise<string[]> {\n return cached(cacheKey(ROLES_PREFIX, user, tenant), () =>\n accessConfig.resolver().resolveRoles(user, tenant),\n );\n}\n\n/** Drop the cached role/permission sets for a user (after any out-of-band role change). */\nexport async function flush(user: Auth, tenant?: string): Promise<void> {\n try {\n await cache.remove(cacheKey(PERMISSIONS_PREFIX, user, tenant));\n await cache.remove(cacheKey(ROLES_PREFIX, user, tenant));\n } catch (error) {\n log.error(\"access\", \"cache\", error);\n }\n}\n\n/**\n * The core decision: does the user hold `permission`, and — when a `resource`\n * is supplied — does its policy pass too?\n *\n * Fails CLOSED: any error while resolving the decision denies (and logs).\n */\nexport async function check(\n user: Auth,\n permission: string,\n context: AccessContext = {},\n): Promise<boolean> {\n try {\n const tenant = tenantFor(user, context);\n const permissions = await permissionsFor(user, tenant);\n\n if (!matchesPermission(permissions, permission)) return false;\n\n const policy = getPolicy(permission);\n\n // A policy only runs on an instance check (a resource was supplied);\n // class-level checks (`gate`) stop at the grant.\n if (policy === undefined || context.resource === undefined) return true;\n\n const roles = await rolesFor(user, tenant);\n\n return Boolean(\n await policy(user, context.resource, {\n ...context,\n tenant,\n hasRole: (role) => roles.includes(role),\n hasPermission: (perm) => matchesPermission(permissions, perm),\n }),\n );\n } catch (error) {\n if (error instanceof AccessConfigError) throw error; // misconfig is loud, not a silent deny\n\n log.error(\"access\", \"check\", error);\n\n return false;\n }\n}\n","import type { Auth } from \"@warlock.js/auth\";\nimport { ForbiddenError, t } from \"@warlock.js/core\";\nimport type { AccessContext } from \"../contracts/types\";\nimport { AccessErrorCodes } from \"../utils/access-error-codes\";\nimport { check, flush as flushUser, rolesFor } from \"./engine\";\n\n/** Whether the user holds `permission` (and passes its policy when a resource is given). */\nexport function can(\n user: Auth,\n permission: string,\n context?: AccessContext,\n): Promise<boolean> {\n return check(user, permission, context);\n}\n\n/** Inverse of {@link can}. */\nexport async function cannot(\n user: Auth,\n permission: string,\n context?: AccessContext,\n): Promise<boolean> {\n return !(await can(user, permission, context));\n}\n\n/** Whether the user holds EVERY listed permission. Short-circuits on the first miss. */\nexport async function canAll(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<boolean> {\n for (const permission of permissions) {\n if (!(await can(user, permission, context))) return false;\n }\n\n return true;\n}\n\n/** Whether the user holds ANY listed permission. Short-circuits on the first hit. */\nexport async function canAny(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<boolean> {\n for (const permission of permissions) {\n if (await can(user, permission, context)) return true;\n }\n\n return false;\n}\n\nfunction forbidden(): never {\n throw new ForbiddenError(t(\"access.errors.forbidden\"), {\n errorCode: AccessErrorCodes.Forbidden,\n });\n}\n\n/** Assert the user holds `permission`; throw `ForbiddenError` (403) otherwise. */\nexport async function authorize(\n user: Auth,\n permission: string,\n context?: AccessContext,\n): Promise<void> {\n if (!(await can(user, permission, context))) forbidden();\n}\n\n/** Assert the user holds EVERY listed permission; throw otherwise. */\nexport async function authorizeAll(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<void> {\n if (!(await canAll(user, permissions, context))) forbidden();\n}\n\n/** Assert the user holds ANY listed permission; throw otherwise. */\nexport async function authorizeAny(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<void> {\n if (!(await canAny(user, permissions, context))) forbidden();\n}\n\n/** Whether the user has the given role. */\nexport async function hasRole(\n user: Auth,\n role: string,\n tenant?: string,\n): Promise<boolean> {\n return (await rolesFor(user, tenant)).includes(role);\n}\n\n/** Whether the user has ANY of the given roles. */\nexport async function hasAnyRole(\n user: Auth,\n roles: string[],\n tenant?: string,\n): Promise<boolean> {\n const held = await rolesFor(user, tenant);\n\n return roles.some((role) => held.includes(role));\n}\n\n/** Whether the user has ALL of the given roles. */\nexport async function hasAllRoles(\n user: Auth,\n roles: string[],\n tenant?: string,\n): Promise<boolean> {\n const held = await rolesFor(user, tenant);\n\n return roles.every((role) => held.includes(role));\n}\n\n/**\n * The access facade — every check plus `flush` to drop a user's cached set.\n * Role assignment lives on the ejected `UserRole` model in app-land; callers do\n * `UserRole.assign(...)` then `access.flush(user, tenant)`.\n *\n * @example\n * import { access } from \"@warlock.js/access\";\n * if (await access.can(user, \"orders.update\", { resource: order })) { ... }\n * await access.flush(user, \"tenant-1\");\n */\nexport const access = {\n can,\n cannot,\n canAll,\n canAny,\n authorize,\n authorizeAll,\n authorizeAny,\n hasRole,\n hasAnyRole,\n hasAllRoles,\n /** Drop the user's cached role/permission set (call after you mutate role rows). */\n flush: flushUser,\n};\n","import { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\nimport { can, canAll, canAny } from \"../services/access\";\nimport { AccessErrorCodes } from \"../utils/access-error-codes\";\n\nfunction deny(response: Response) {\n return response.forbidden({\n error: t(\"access.errors.forbidden\"),\n errorCode: AccessErrorCodes.Forbidden,\n });\n}\n\n/**\n * Gate a route on a single permission (class-level). Stack it AFTER\n * `authMiddleware`, which sets `request.user`.\n *\n * @example\n * router.post(\"/orders\", createOrder, {\n * middleware: [authMiddleware([]), gate(\"orders.create\")],\n * });\n */\nexport function gate(permission: string): Middleware {\n return async (request: Request, response: Response) => {\n if (!request.user) return deny(response);\n\n if (!(await can(request.user, permission))) return deny(response);\n };\n}\n\n/** Gate a route on holding ANY of the listed permissions. */\nexport function gateAny(permissions: string[]): Middleware {\n return async (request: Request, response: Response) => {\n if (!request.user) return deny(response);\n\n if (!(await canAny(request.user, permissions))) return deny(response);\n };\n}\n\n/** Gate a route on holding ALL of the listed permissions. */\nexport function gateAll(permissions: string[]): Middleware {\n return async (request: Request, response: Response) => {\n if (!request.user) return deny(response);\n\n if (!(await canAll(request.user, permissions))) return deny(response);\n };\n}\n","import type { Auth } from \"@warlock.js/auth\";\nimport type { AccessResolver } from \"../contracts/access-resolver\";\nimport type { RolesMap } from \"../contracts/types\";\n\n/** Coerce a roles value (`undefined` / single string / array) into a string array. */\nfunction toRoleList(value: unknown): string[] {\n if (Array.isArray(value)) return value as string[];\n\n if (typeof value === \"string\" && value.length > 0) return [value];\n\n return [];\n}\n\n/**\n * The zero-config resolver: reads a user's roles from a model field and maps\n * them to permissions through your `roles` catalog. Works out of the box for\n * both a `roles` array column and a single `role` column.\n *\n * Swap `readRoles` to read from anywhere else (a relation, a token claim); for\n * a different storage shape entirely, implement {@link AccessResolver} directly.\n *\n * @example\n * new DefaultAccessResolver({ editor: [\"orders.*\"], viewer: [\"orders.view\"] });\n * new DefaultAccessResolver(rolesMap, (user) => user.get(\"memberRoles\"));\n */\nexport class DefaultAccessResolver implements AccessResolver {\n public constructor(\n private readonly roles: RolesMap,\n private readonly readRoles: (user: Auth) => unknown = (user) =>\n user.get(\"roles\") ?? user.get(\"role\"),\n ) {}\n\n public async resolveRoles(user: Auth): Promise<string[]> {\n return toRoleList(await this.readRoles(user));\n }\n\n public async resolvePermissions(user: Auth): Promise<string[]> {\n const roles = await this.resolveRoles(user);\n\n // Guard against a role named `__proto__` / `constructor` resolving to an\n // inherited (non-array) value.\n return roles.flatMap((role) =>\n Object.prototype.hasOwnProperty.call(this.roles, role)\n ? this.roles[role]\n : [],\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["cache","ForbiddenError","flushUser"],"sources":["../../../../../../access/src/utils/access-error-codes.ts","../../../../../../access/src/utils/access-config-error.ts","../../../../../../access/src/services/access-config.ts","../../../../../../access/src/services/matcher.ts","../../../../../../access/src/services/policies.ts","../../../../../../access/src/services/engine.ts","../../../../../../access/src/services/access.ts","../../../../../../access/src/middleware/gate.middleware.ts","../../../../../../access/src/services/default-resolver.ts"],"sourcesContent":["/**\n * Wire-stable error codes emitted by `@warlock.js/access`. Map these in your\n * error transformer the way you map `AuthErrorCodes`. A value change here is a\n * breaking change for any client that reacts to the code.\n */\nexport enum AccessErrorCodes {\n /** EC100 — authenticated, but missing the required permission. */\n Forbidden = \"EC100\",\n}\n","/**\n * Thrown when `@warlock.js/access` is misconfigured (e.g. no resolver). Unlike a\n * runtime resolution failure — which fails CLOSED (denies) — a config error is a\n * developer mistake, so the engine re-throws it LOUD instead of silently denying.\n */\nexport class AccessConfigError extends Error {\n public constructor(message: string) {\n super(message);\n this.name = \"AccessConfigError\";\n }\n}\n","import type { Auth } from \"@warlock.js/auth\";\nimport type { AccessResolver } from \"../contracts/access-resolver\";\nimport type { AccessConfigurations } from \"../contracts/types\";\nimport { AccessConfigError } from \"../utils/access-config-error\";\n\n/**\n * The active configuration, fed once at boot by the framework's access connector\n * via {@link setAccessConfig} (the connector reads `src/config/access.ts`).\n * Reading it before it's set throws {@link AccessConfigError} — a loud\n * misconfiguration, never a silent deny.\n */\nlet current: AccessConfigurations | undefined;\n\n/**\n * Register the access configuration. The framework's access connector calls this\n * at boot with the default export of `src/config/access.ts`. It validates that a\n * `resolver` is present, so a misconfigured authorization layer fails at STARTUP\n * rather than on the first protected request.\n *\n * @example\n * setAccessConfig({ resolver: new DefaultAccessResolver({ admin: [\"*\"] }) });\n */\nexport function setAccessConfig(configuration: AccessConfigurations): void {\n if (!configuration?.resolver) {\n throw new AccessConfigError(\n \"No access resolver configured. Set `resolver` in src/config/access.ts to a class \" +\n \"implementing AccessResolver — the ejected DatabaseAccessResolver, or \" +\n \"`new DefaultAccessResolver(rolesMap)` for a fixed-role catalog.\",\n );\n }\n\n current = configuration;\n}\n\n/** Clear the active config. Used by the connector on dev-restart and by tests. */\nexport function resetAccessConfig(): void {\n current = undefined;\n}\n\n/** Accessors over the active access configuration. */\nexport const accessConfig = {\n /**\n * The configured resolver. Throws {@link AccessConfigError} if access was never\n * configured (no `src/config/access.ts`, or the connector hasn't run).\n */\n resolver(): AccessResolver {\n if (!current) {\n throw new AccessConfigError(\n \"@warlock.js/access is not configured. Add src/config/access.ts (exporting a `resolver`) \" +\n \"— it is wired at boot by the access connector.\",\n );\n }\n\n return current.resolver;\n },\n\n /** Cache TTL for resolved permission sets. */\n cacheTtl(): string | number {\n return current?.cache?.ttl ?? \"10m\";\n },\n\n /** Ambient tenant for this user, delegated to the resolver's optional hook. */\n tenant(user: Auth): string | undefined {\n return this.resolver().resolveTenant?.(user);\n },\n};\n","/**\n * Whether a set of granted permission patterns covers a requested permission.\n *\n * Supports three forms:\n * - exact — `\"orders.update\"` covers `\"orders.update\"`\n * - global — `\"*\"` covers everything (the super-grant)\n * - prefix wildcard — `\"orders.*\"` covers `\"orders.update\"` and `\"orders.update.status\"`\n *\n * `\"orders.*\"` does NOT cover the bare `\"orders\"` (no trailing segment).\n */\nexport function matchesPermission(granted: string[], permission: string): boolean {\n for (const pattern of granted) {\n if (pattern === \"*\" || pattern === permission) {\n return true;\n }\n\n // \"orders.*\" → prefix \"orders.\" — matches any nested permission.\n if (pattern.endsWith(\".*\") && permission.startsWith(pattern.slice(0, -1))) {\n return true;\n }\n }\n\n return false;\n}\n","import type { PolicyFn } from \"../contracts/types\";\n\nconst policies = new Map<string, PolicyFn>();\n\n/**\n * Register an ABAC condition for a permission. It runs ON TOP of the RBAC grant\n * whenever an authorization check carries a `resource` — letting you express\n * \"...but only their own / only in their tenant / only while pending\".\n *\n * @example\n * definePolicy(\"orders.update\", (user, order) => order.get(\"customer_id\") === user.id);\n */\nexport function definePolicy(permission: string, policy: PolicyFn): void {\n policies.set(permission, policy);\n}\n\n/** The policy registered for a permission, if any. */\nexport function getPolicy(permission: string): PolicyFn | undefined {\n return policies.get(permission);\n}\n\n/** Remove every registered policy. Primarily for tests. */\nexport function clearPolicies(): void {\n policies.clear();\n}\n","import type { Auth } from \"@warlock.js/auth\";\nimport { cache } from \"@warlock.js/cache\";\nimport { log } from \"@warlock.js/logger\";\nimport type { AccessContext } from \"../contracts/types\";\nimport { AccessConfigError } from \"../utils/access-config-error\";\nimport { accessConfig } from \"./access-config\";\nimport { matchesPermission } from \"./matcher\";\nimport { getPolicy } from \"./policies\";\n\nconst PERMISSIONS_PREFIX = \"access.perms.\";\nconst ROLES_PREFIX = \"access.roles.\";\n\n/** Resolve the effective tenant for a check (explicit → ambient → undefined). */\nfunction tenantFor(user: Auth, context: AccessContext): string | undefined {\n return context.tenant ?? accessConfig.tenant(user);\n}\n\nfunction cacheKey(prefix: string, user: Auth, tenant?: string): string {\n // Encode each segment so a `.` in an id or tenant can't collide two distinct\n // principals onto the same cache key (and serve one's grants to the other).\n const segment = (value: unknown) => encodeURIComponent(String(value));\n\n return `${prefix}${segment(user.userType)}.${segment(user.id)}.${segment(tenant ?? \"_\")}`;\n}\n\n/**\n * Read a list through the cache, falling back to the loader.\n *\n * The cache is **best-effort**: a cache failure degrades to the loader (the\n * source of truth) and is logged, never denied. A loader failure propagates —\n * so the DECISION fails closed in {@link check}.\n */\nasync function cached(\n key: string,\n loader: () => Promise<string[]>,\n): Promise<string[]> {\n try {\n const hit = await cache.get<string[]>(key);\n\n if (hit) return hit;\n } catch (error) {\n log.error(\"access\", \"cache\", error);\n }\n\n const value = await loader();\n\n try {\n await cache.set(key, value, { ttl: accessConfig.cacheTtl() });\n } catch (error) {\n log.error(\"access\", \"cache\", error);\n }\n\n return value;\n}\n\n/** The user's effective permission set for a tenant (cached). */\nexport function permissionsFor(user: Auth, tenant?: string): Promise<string[]> {\n return cached(cacheKey(PERMISSIONS_PREFIX, user, tenant), () =>\n accessConfig.resolver().resolvePermissions(user, tenant),\n );\n}\n\n/** The user's roles for a tenant (cached). */\nexport function rolesFor(user: Auth, tenant?: string): Promise<string[]> {\n return cached(cacheKey(ROLES_PREFIX, user, tenant), () =>\n accessConfig.resolver().resolveRoles(user, tenant),\n );\n}\n\n/** Drop the cached role/permission sets for a user (after any out-of-band role change). */\nexport async function flush(user: Auth, tenant?: string): Promise<void> {\n try {\n await cache.remove(cacheKey(PERMISSIONS_PREFIX, user, tenant));\n await cache.remove(cacheKey(ROLES_PREFIX, user, tenant));\n } catch (error) {\n log.error(\"access\", \"cache\", error);\n }\n}\n\n/**\n * The core decision: does the user hold `permission`, and — when a `resource`\n * is supplied — does its policy pass too?\n *\n * Fails CLOSED: any error while resolving the decision denies (and logs).\n */\nexport async function check(\n user: Auth,\n permission: string,\n context: AccessContext = {},\n): Promise<boolean> {\n try {\n const tenant = tenantFor(user, context);\n const permissions = await permissionsFor(user, tenant);\n\n if (!matchesPermission(permissions, permission)) return false;\n\n const policy = getPolicy(permission);\n\n // A policy only runs on an instance check (a resource was supplied);\n // class-level checks (`gate`) stop at the grant.\n if (policy === undefined || context.resource === undefined) return true;\n\n const roles = await rolesFor(user, tenant);\n\n return Boolean(\n await policy(user, context.resource, {\n ...context,\n tenant,\n hasRole: (role) => roles.includes(role),\n hasPermission: (perm) => matchesPermission(permissions, perm),\n }),\n );\n } catch (error) {\n if (error instanceof AccessConfigError) throw error; // misconfig is loud, not a silent deny\n\n log.error(\"access\", \"check\", error);\n\n return false;\n }\n}\n","import type { Auth } from \"@warlock.js/auth\";\nimport { ForbiddenError, t } from \"@warlock.js/core\";\nimport type { AccessContext } from \"../contracts/types\";\nimport { AccessErrorCodes } from \"../utils/access-error-codes\";\nimport { check, flush as flushUser, rolesFor } from \"./engine\";\n\n/** Whether the user holds `permission` (and passes its policy when a resource is given). */\nexport function can(\n user: Auth,\n permission: string,\n context?: AccessContext,\n): Promise<boolean> {\n return check(user, permission, context);\n}\n\n/** Inverse of {@link can}. */\nexport async function cannot(\n user: Auth,\n permission: string,\n context?: AccessContext,\n): Promise<boolean> {\n return !(await can(user, permission, context));\n}\n\n/** Whether the user holds EVERY listed permission. Short-circuits on the first miss. */\nexport async function canAll(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<boolean> {\n for (const permission of permissions) {\n if (!(await can(user, permission, context))) return false;\n }\n\n return true;\n}\n\n/** Whether the user holds ANY listed permission. Short-circuits on the first hit. */\nexport async function canAny(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<boolean> {\n for (const permission of permissions) {\n if (await can(user, permission, context)) return true;\n }\n\n return false;\n}\n\nfunction forbidden(): never {\n throw new ForbiddenError(t(\"access.errors.forbidden\"), {\n errorCode: AccessErrorCodes.Forbidden,\n });\n}\n\n/** Assert the user holds `permission`; throw `ForbiddenError` (403) otherwise. */\nexport async function authorize(\n user: Auth,\n permission: string,\n context?: AccessContext,\n): Promise<void> {\n if (!(await can(user, permission, context))) forbidden();\n}\n\n/** Assert the user holds EVERY listed permission; throw otherwise. */\nexport async function authorizeAll(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<void> {\n if (!(await canAll(user, permissions, context))) forbidden();\n}\n\n/** Assert the user holds ANY listed permission; throw otherwise. */\nexport async function authorizeAny(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<void> {\n if (!(await canAny(user, permissions, context))) forbidden();\n}\n\n/** Whether the user has the given role. */\nexport async function hasRole(\n user: Auth,\n role: string,\n tenant?: string,\n): Promise<boolean> {\n return (await rolesFor(user, tenant)).includes(role);\n}\n\n/** Whether the user has ANY of the given roles. */\nexport async function hasAnyRole(\n user: Auth,\n roles: string[],\n tenant?: string,\n): Promise<boolean> {\n const held = await rolesFor(user, tenant);\n\n return roles.some((role) => held.includes(role));\n}\n\n/** Whether the user has ALL of the given roles. */\nexport async function hasAllRoles(\n user: Auth,\n roles: string[],\n tenant?: string,\n): Promise<boolean> {\n const held = await rolesFor(user, tenant);\n\n return roles.every((role) => held.includes(role));\n}\n\n/**\n * The access facade — every check plus `flush` to drop a user's cached set.\n * Role assignment lives on the ejected `UserRole` model in app-land; callers do\n * `UserRole.assign(...)` then `access.flush(user, tenant)`.\n *\n * @example\n * import { access } from \"@warlock.js/access\";\n * if (await access.can(user, \"orders.update\", { resource: order })) { ... }\n * await access.flush(user, \"tenant-1\");\n */\nexport const access = {\n can,\n cannot,\n canAll,\n canAny,\n authorize,\n authorizeAll,\n authorizeAny,\n hasRole,\n hasAnyRole,\n hasAllRoles,\n /** Drop the user's cached role/permission set (call after you mutate role rows). */\n flush: flushUser,\n};\n","import { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\nimport { can, canAll, canAny } from \"../services/access\";\nimport { AccessErrorCodes } from \"../utils/access-error-codes\";\n\nfunction deny(response: Response) {\n return response.forbidden({\n error: t(\"access.errors.forbidden\"),\n errorCode: AccessErrorCodes.Forbidden,\n });\n}\n\n/**\n * Gate a route on a single permission (class-level). Stack it AFTER\n * `authMiddleware`, which sets `request.user`.\n *\n * @example\n * router.post(\"/orders\", createOrder, {\n * middleware: [authMiddleware([]), gate(\"orders.create\")],\n * });\n */\nexport function gate(permission: string): Middleware {\n return async (request: Request, response: Response) => {\n if (!request.user) return deny(response);\n\n if (!(await can(request.user, permission))) return deny(response);\n };\n}\n\n/** Gate a route on holding ANY of the listed permissions. */\nexport function gateAny(permissions: string[]): Middleware {\n return async (request: Request, response: Response) => {\n if (!request.user) return deny(response);\n\n if (!(await canAny(request.user, permissions))) return deny(response);\n };\n}\n\n/** Gate a route on holding ALL of the listed permissions. */\nexport function gateAll(permissions: string[]): Middleware {\n return async (request: Request, response: Response) => {\n if (!request.user) return deny(response);\n\n if (!(await canAll(request.user, permissions))) return deny(response);\n };\n}\n","import type { Auth } from \"@warlock.js/auth\";\nimport type { AccessResolver } from \"../contracts/access-resolver\";\nimport type { RolesMap } from \"../contracts/types\";\n\n/** Coerce a roles value (`undefined` / single string / array) into a string array. */\nfunction toRoleList(value: unknown): string[] {\n if (Array.isArray(value)) return value as string[];\n\n if (typeof value === \"string\" && value.length > 0) return [value];\n\n return [];\n}\n\n/**\n * The zero-config resolver: reads a user's roles from a model field and maps\n * them to permissions through your `roles` catalog. Works out of the box for\n * both a `roles` array column and a single `role` column.\n *\n * Swap `readRoles` to read from anywhere else (a relation, a token claim); for\n * a different storage shape entirely, implement {@link AccessResolver} directly.\n *\n * @example\n * new DefaultAccessResolver({ editor: [\"orders.*\"], viewer: [\"orders.view\"] });\n * new DefaultAccessResolver(rolesMap, (user) => user.get(\"memberRoles\"));\n */\nexport class DefaultAccessResolver implements AccessResolver {\n public constructor(\n private readonly roles: RolesMap,\n private readonly readRoles: (user: Auth) => unknown = (user) =>\n user.get(\"roles\") ?? user.get(\"role\"),\n ) {}\n\n public async resolveRoles(user: Auth): Promise<string[]> {\n return toRoleList(await this.readRoles(user));\n }\n\n public async resolvePermissions(user: Auth): Promise<string[]> {\n const roles = await this.resolveRoles(user);\n\n // Guard against a role named `__proto__` / `constructor` resolving to an\n // inherited (non-array) value.\n return roles.flatMap((role) =>\n Object.prototype.hasOwnProperty.call(this.roles, role)\n ? this.roles[role]\n : [],\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Auth } from "@warlock.js/auth";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../access/src/contracts/access-resolver.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* The one adapter the engine depends on. Implement it to connect the engine to
|
|
6
6
|
* however YOUR app stores roles and permissions — a user column, a pivot table,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-resolver.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"access-resolver.d.mts","names":[],"sources":["../../../../../../../access/src/contracts/access-resolver.ts"],"mappings":";;;;;AAuBA;;;;;;;;;;;;;;;;;;;UAAiB,cAAA;EAaf;EAXA,YAAA,CAAa,IAAA,EAAM,IAAA,EAAM,MAAA,YAAkB,OAAA;EAW5B;EARf,kBAAA,CAAmB,IAAA,EAAM,IAAA,EAAM,MAAA,YAAkB,OAAA;EAQxB;;;;;;EAAzB,aAAA,EAAe,IAAA,EAAM,IAAA;AAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AccessResolver } from "./access-resolver.mjs";
|
|
2
2
|
import { Auth } from "@warlock.js/auth";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../access/src/contracts/types.d.ts
|
|
5
5
|
/** A role name mapped to the permission strings it grants. */
|
|
6
6
|
type RolesMap = Record<string, string[]>;
|
|
7
7
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../access/src/contracts/types.ts"],"mappings":";;;;;KAIY,QAAA,GAAW,MAAM;AAA7B;;;;AAA6B;AAA7B,KAOY,aAAA;EAAa,iFAEvB,QAAA,YAFuB;EAIvB,MAAA;EAAA,CACC,GAAA;AAAA;;KAIS,aAAA,GAAgB,aAAa;EACvC,MAAA,WADuB;EAGvB,OAAA,GAAU,IAAA,sBAH6B;EAKvC,aAAA,GAAgB,UAAA;AAAA;;KAIN,QAAA,IACV,IAAA,EAAM,IAAA,EACN,QAAA,WACA,OAAA,EAAS,aAAA,eACI,OAAA;AAAA,KAEH,oBAAA;EAVM;;AAAkB;AAIpC;;EAYE,QAAA,EAAU,cAAc,EAXlB;EAcN,KAAA;IAXa,2CAaX,GAAA;EAAA;AAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Middleware } from "@warlock.js/core";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../access/src/middleware/gate.middleware.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Gate a route on a single permission (class-level). Stack it AFTER
|
|
6
6
|
* `authMiddleware`, which sets `request.user`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.middleware.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"gate.middleware.d.mts","names":[],"sources":["../../../../../../../access/src/middleware/gate.middleware.ts"],"mappings":";;;;;AAoBA;;;;AAAoD;AASpD;;iBATgB,IAAA,CAAK,UAAA,WAAqB,UAAU;;iBASpC,OAAA,CAAQ,WAAA,aAAwB,UAAU;AAS1D;AAAA,iBAAgB,OAAA,CAAQ,WAAA,aAAwB,UAAU"}
|
|
@@ -2,7 +2,7 @@ import { AccessErrorCodes } from "../utils/access-error-codes.mjs";
|
|
|
2
2
|
import { can, canAll, canAny } from "../services/access.mjs";
|
|
3
3
|
import { t } from "@warlock.js/core";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../access/src/middleware/gate.middleware.ts
|
|
6
6
|
function deny(response) {
|
|
7
7
|
return response.forbidden({
|
|
8
8
|
error: t("access.errors.forbidden"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.middleware.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"gate.middleware.mjs","names":[],"sources":["../../../../../../../access/src/middleware/gate.middleware.ts"],"sourcesContent":["import { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\nimport { can, canAll, canAny } from \"../services/access\";\nimport { AccessErrorCodes } from \"../utils/access-error-codes\";\n\nfunction deny(response: Response) {\n return response.forbidden({\n error: t(\"access.errors.forbidden\"),\n errorCode: AccessErrorCodes.Forbidden,\n });\n}\n\n/**\n * Gate a route on a single permission (class-level). Stack it AFTER\n * `authMiddleware`, which sets `request.user`.\n *\n * @example\n * router.post(\"/orders\", createOrder, {\n * middleware: [authMiddleware([]), gate(\"orders.create\")],\n * });\n */\nexport function gate(permission: string): Middleware {\n return async (request: Request, response: Response) => {\n if (!request.user) return deny(response);\n\n if (!(await can(request.user, permission))) return deny(response);\n };\n}\n\n/** Gate a route on holding ANY of the listed permissions. */\nexport function gateAny(permissions: string[]): Middleware {\n return async (request: Request, response: Response) => {\n if (!request.user) return deny(response);\n\n if (!(await canAny(request.user, permissions))) return deny(response);\n };\n}\n\n/** Gate a route on holding ALL of the listed permissions. */\nexport function gateAll(permissions: string[]): Middleware {\n return async (request: Request, response: Response) => {\n if (!request.user) return deny(response);\n\n if (!(await canAll(request.user, permissions))) return deny(response);\n };\n}\n"],"mappings":";;;;;AAIA,SAAS,KAAK,UAAoB;CAChC,OAAO,SAAS,UAAU;EACxB,OAAO,EAAE,yBAAyB;EAClC;CACF,CAAC;AACH;;;;;;;;;;AAWA,SAAgB,KAAK,YAAgC;CACnD,OAAO,OAAO,SAAkB,aAAuB;EACrD,IAAI,CAAC,QAAQ,MAAM,OAAO,KAAK,QAAQ;EAEvC,IAAI,CAAE,MAAM,IAAI,QAAQ,MAAM,UAAU,GAAI,OAAO,KAAK,QAAQ;CAClE;AACF;;AAGA,SAAgB,QAAQ,aAAmC;CACzD,OAAO,OAAO,SAAkB,aAAuB;EACrD,IAAI,CAAC,QAAQ,MAAM,OAAO,KAAK,QAAQ;EAEvC,IAAI,CAAE,MAAM,OAAO,QAAQ,MAAM,WAAW,GAAI,OAAO,KAAK,QAAQ;CACtE;AACF;;AAGA,SAAgB,QAAQ,aAAmC;CACzD,OAAO,OAAO,SAAkB,aAAuB;EACrD,IAAI,CAAC,QAAQ,MAAM,OAAO,KAAK,QAAQ;EAEvC,IAAI,CAAE,MAAM,OAAO,QAAQ,MAAM,WAAW,GAAI,OAAO,KAAK,QAAQ;CACtE;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AccessConfigurations } from "../contracts/types.mjs";
|
|
2
2
|
import { Auth } from "@warlock.js/auth";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../access/src/services/access-config.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Register the access configuration. The framework's access connector calls this
|
|
7
7
|
* at boot with the default export of `src/config/access.ts`. It validates that a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-config.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"access-config.d.mts","names":[],"sources":["../../../../../../../access/src/services/access-config.ts"],"mappings":";;;;;;AAsBA;;;;AAAmE;AAanE;;iBAbgB,eAAA,CAAgB,aAAmC,EAApB,oBAAoB;;iBAanD,iBAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AccessConfigError } from "../utils/access-config-error.mjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../access/src/services/access-config.ts
|
|
4
4
|
/**
|
|
5
5
|
* The active configuration, fed once at boot by the framework's access connector
|
|
6
6
|
* via {@link setAccessConfig} (the connector reads `src/config/access.ts`).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-config.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"access-config.mjs","names":[],"sources":["../../../../../../../access/src/services/access-config.ts"],"sourcesContent":["import type { Auth } from \"@warlock.js/auth\";\nimport type { AccessResolver } from \"../contracts/access-resolver\";\nimport type { AccessConfigurations } from \"../contracts/types\";\nimport { AccessConfigError } from \"../utils/access-config-error\";\n\n/**\n * The active configuration, fed once at boot by the framework's access connector\n * via {@link setAccessConfig} (the connector reads `src/config/access.ts`).\n * Reading it before it's set throws {@link AccessConfigError} — a loud\n * misconfiguration, never a silent deny.\n */\nlet current: AccessConfigurations | undefined;\n\n/**\n * Register the access configuration. The framework's access connector calls this\n * at boot with the default export of `src/config/access.ts`. It validates that a\n * `resolver` is present, so a misconfigured authorization layer fails at STARTUP\n * rather than on the first protected request.\n *\n * @example\n * setAccessConfig({ resolver: new DefaultAccessResolver({ admin: [\"*\"] }) });\n */\nexport function setAccessConfig(configuration: AccessConfigurations): void {\n if (!configuration?.resolver) {\n throw new AccessConfigError(\n \"No access resolver configured. Set `resolver` in src/config/access.ts to a class \" +\n \"implementing AccessResolver — the ejected DatabaseAccessResolver, or \" +\n \"`new DefaultAccessResolver(rolesMap)` for a fixed-role catalog.\",\n );\n }\n\n current = configuration;\n}\n\n/** Clear the active config. Used by the connector on dev-restart and by tests. */\nexport function resetAccessConfig(): void {\n current = undefined;\n}\n\n/** Accessors over the active access configuration. */\nexport const accessConfig = {\n /**\n * The configured resolver. Throws {@link AccessConfigError} if access was never\n * configured (no `src/config/access.ts`, or the connector hasn't run).\n */\n resolver(): AccessResolver {\n if (!current) {\n throw new AccessConfigError(\n \"@warlock.js/access is not configured. Add src/config/access.ts (exporting a `resolver`) \" +\n \"— it is wired at boot by the access connector.\",\n );\n }\n\n return current.resolver;\n },\n\n /** Cache TTL for resolved permission sets. */\n cacheTtl(): string | number {\n return current?.cache?.ttl ?? \"10m\";\n },\n\n /** Ambient tenant for this user, delegated to the resolver's optional hook. */\n tenant(user: Auth): string | undefined {\n return this.resolver().resolveTenant?.(user);\n },\n};\n"],"mappings":";;;;;;;;;AAWA,IAAI;;;;;;;;;;AAWJ,SAAgB,gBAAgB,eAA2C;CACzE,IAAI,CAAC,eAAe,UAClB,MAAM,IAAI,kBACR,uNAGF;CAGF,UAAU;AACZ;;AAGA,SAAgB,oBAA0B;CACxC,UAAU;AACZ;;AAGA,MAAa,eAAe;;;;;CAK1B,WAA2B;EACzB,IAAI,CAAC,SACH,MAAM,IAAI,kBACR,wIAEF;EAGF,OAAO,QAAQ;CACjB;;CAGA,WAA4B;EAC1B,OAAO,SAAS,OAAO,OAAO;CAChC;;CAGA,OAAO,MAAgC;EACrC,OAAO,KAAK,SAAS,CAAC,CAAC,gBAAgB,IAAI;CAC7C;AACF"}
|
|
@@ -2,7 +2,7 @@ import { AccessContext } from "../contracts/types.mjs";
|
|
|
2
2
|
import { flush } from "./engine.mjs";
|
|
3
3
|
import { Auth } from "@warlock.js/auth";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../access/src/services/access.d.ts
|
|
6
6
|
/** Whether the user holds `permission` (and passes its policy when a resource is given). */
|
|
7
7
|
declare function can(user: Auth, permission: string, context?: AccessContext): Promise<boolean>;
|
|
8
8
|
/** Inverse of {@link can}. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"access.d.mts","names":[],"sources":["../../../../../../../access/src/services/access.ts"],"mappings":";;;;;;iBAOgB,GAAA,CACd,IAAA,EAAM,IAAA,EACN,UAAA,UACA,OAAA,GAAU,aAAA,GACT,OAAA;AAJH;AAAA,iBASsB,MAAA,CACpB,IAAA,EAAM,IAAA,EACN,UAAA,UACA,OAAA,GAAU,aAAA,GACT,OAAA;;iBAKmB,MAAA,CACpB,IAAA,EAAM,IAAA,EACN,WAAA,YACA,OAAA,GAAU,aAAA,GACT,OAAA;;iBASmB,MAAA,CACpB,IAAA,EAAM,IAAA,EACN,WAAA,YACA,OAAA,GAAU,aAAA,GACT,OAAA;;iBAemB,SAAA,CACpB,IAAA,EAAM,IAAA,EACN,UAAA,UACA,OAAA,GAAU,aAAA,GACT,OAAA;;iBAKmB,YAAA,CACpB,IAAA,EAAM,IAAA,EACN,WAAA,YACA,OAAA,GAAU,aAAA,GACT,OAAA;;iBAKmB,YAAA,CACpB,IAAA,EAAM,IAAA,EACN,WAAA,YACA,OAAA,GAAU,aAAA,GACT,OAAA;;iBAKmB,OAAA,CACpB,IAAA,EAAM,IAAA,EACN,IAAA,UACA,MAAA,YACC,OAAO;;iBAKY,UAAA,CACpB,IAAA,EAAM,IAAA,EACN,KAAA,YACA,MAAA,YACC,OAAO;AAjFV;AAAA,iBAwFsB,WAAA,CACpB,IAAA,EAAM,IAAA,EACN,KAAA,YACA,MAAA,YACC,OAAO;;;;;;;;;;;cAgBG,MAAA;;;;;;;;;;mCA/FH"}
|
package/esm/services/access.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { AccessErrorCodes } from "../utils/access-error-codes.mjs";
|
|
|
2
2
|
import { check, flush, rolesFor } from "./engine.mjs";
|
|
3
3
|
import { ForbiddenError, t } from "@warlock.js/core";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../access/src/services/access.ts
|
|
6
6
|
/** Whether the user holds `permission` (and passes its policy when a resource is given). */
|
|
7
7
|
function can(user, permission, context) {
|
|
8
8
|
return check(user, permission, context);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.mjs","names":["flushUser"],"sources":["
|
|
1
|
+
{"version":3,"file":"access.mjs","names":["flushUser"],"sources":["../../../../../../../access/src/services/access.ts"],"sourcesContent":["import type { Auth } from \"@warlock.js/auth\";\nimport { ForbiddenError, t } from \"@warlock.js/core\";\nimport type { AccessContext } from \"../contracts/types\";\nimport { AccessErrorCodes } from \"../utils/access-error-codes\";\nimport { check, flush as flushUser, rolesFor } from \"./engine\";\n\n/** Whether the user holds `permission` (and passes its policy when a resource is given). */\nexport function can(\n user: Auth,\n permission: string,\n context?: AccessContext,\n): Promise<boolean> {\n return check(user, permission, context);\n}\n\n/** Inverse of {@link can}. */\nexport async function cannot(\n user: Auth,\n permission: string,\n context?: AccessContext,\n): Promise<boolean> {\n return !(await can(user, permission, context));\n}\n\n/** Whether the user holds EVERY listed permission. Short-circuits on the first miss. */\nexport async function canAll(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<boolean> {\n for (const permission of permissions) {\n if (!(await can(user, permission, context))) return false;\n }\n\n return true;\n}\n\n/** Whether the user holds ANY listed permission. Short-circuits on the first hit. */\nexport async function canAny(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<boolean> {\n for (const permission of permissions) {\n if (await can(user, permission, context)) return true;\n }\n\n return false;\n}\n\nfunction forbidden(): never {\n throw new ForbiddenError(t(\"access.errors.forbidden\"), {\n errorCode: AccessErrorCodes.Forbidden,\n });\n}\n\n/** Assert the user holds `permission`; throw `ForbiddenError` (403) otherwise. */\nexport async function authorize(\n user: Auth,\n permission: string,\n context?: AccessContext,\n): Promise<void> {\n if (!(await can(user, permission, context))) forbidden();\n}\n\n/** Assert the user holds EVERY listed permission; throw otherwise. */\nexport async function authorizeAll(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<void> {\n if (!(await canAll(user, permissions, context))) forbidden();\n}\n\n/** Assert the user holds ANY listed permission; throw otherwise. */\nexport async function authorizeAny(\n user: Auth,\n permissions: string[],\n context?: AccessContext,\n): Promise<void> {\n if (!(await canAny(user, permissions, context))) forbidden();\n}\n\n/** Whether the user has the given role. */\nexport async function hasRole(\n user: Auth,\n role: string,\n tenant?: string,\n): Promise<boolean> {\n return (await rolesFor(user, tenant)).includes(role);\n}\n\n/** Whether the user has ANY of the given roles. */\nexport async function hasAnyRole(\n user: Auth,\n roles: string[],\n tenant?: string,\n): Promise<boolean> {\n const held = await rolesFor(user, tenant);\n\n return roles.some((role) => held.includes(role));\n}\n\n/** Whether the user has ALL of the given roles. */\nexport async function hasAllRoles(\n user: Auth,\n roles: string[],\n tenant?: string,\n): Promise<boolean> {\n const held = await rolesFor(user, tenant);\n\n return roles.every((role) => held.includes(role));\n}\n\n/**\n * The access facade — every check plus `flush` to drop a user's cached set.\n * Role assignment lives on the ejected `UserRole` model in app-land; callers do\n * `UserRole.assign(...)` then `access.flush(user, tenant)`.\n *\n * @example\n * import { access } from \"@warlock.js/access\";\n * if (await access.can(user, \"orders.update\", { resource: order })) { ... }\n * await access.flush(user, \"tenant-1\");\n */\nexport const access = {\n can,\n cannot,\n canAll,\n canAny,\n authorize,\n authorizeAll,\n authorizeAny,\n hasRole,\n hasAnyRole,\n hasAllRoles,\n /** Drop the user's cached role/permission set (call after you mutate role rows). */\n flush: flushUser,\n};\n"],"mappings":";;;;;;AAOA,SAAgB,IACd,MACA,YACA,SACkB;CAClB,OAAO,MAAM,MAAM,YAAY,OAAO;AACxC;;AAGA,eAAsB,OACpB,MACA,YACA,SACkB;CAClB,OAAO,CAAE,MAAM,IAAI,MAAM,YAAY,OAAO;AAC9C;;AAGA,eAAsB,OACpB,MACA,aACA,SACkB;CAClB,KAAK,MAAM,cAAc,aACvB,IAAI,CAAE,MAAM,IAAI,MAAM,YAAY,OAAO,GAAI,OAAO;CAGtD,OAAO;AACT;;AAGA,eAAsB,OACpB,MACA,aACA,SACkB;CAClB,KAAK,MAAM,cAAc,aACvB,IAAI,MAAM,IAAI,MAAM,YAAY,OAAO,GAAG,OAAO;CAGnD,OAAO;AACT;AAEA,SAAS,YAAmB;CAC1B,MAAM,IAAI,eAAe,EAAE,yBAAyB,GAAG,EACrD,mBACF,CAAC;AACH;;AAGA,eAAsB,UACpB,MACA,YACA,SACe;CACf,IAAI,CAAE,MAAM,IAAI,MAAM,YAAY,OAAO,GAAI,UAAU;AACzD;;AAGA,eAAsB,aACpB,MACA,aACA,SACe;CACf,IAAI,CAAE,MAAM,OAAO,MAAM,aAAa,OAAO,GAAI,UAAU;AAC7D;;AAGA,eAAsB,aACpB,MACA,aACA,SACe;CACf,IAAI,CAAE,MAAM,OAAO,MAAM,aAAa,OAAO,GAAI,UAAU;AAC7D;;AAGA,eAAsB,QACpB,MACA,MACA,QACkB;CAClB,QAAQ,MAAM,SAAS,MAAM,MAAM,EAAC,CAAE,SAAS,IAAI;AACrD;;AAGA,eAAsB,WACpB,MACA,OACA,QACkB;CAClB,MAAM,OAAO,MAAM,SAAS,MAAM,MAAM;CAExC,OAAO,MAAM,MAAM,SAAS,KAAK,SAAS,IAAI,CAAC;AACjD;;AAGA,eAAsB,YACpB,MACA,OACA,QACkB;CAClB,MAAM,OAAO,MAAM,SAAS,MAAM,MAAM;CAExC,OAAO,MAAM,OAAO,SAAS,KAAK,SAAS,IAAI,CAAC;AAClD;;;;;;;;;;;AAYA,MAAa,SAAS;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;CAEOA;AACT"}
|
|
@@ -2,7 +2,7 @@ import { AccessResolver } from "../contracts/access-resolver.mjs";
|
|
|
2
2
|
import { RolesMap } from "../contracts/types.mjs";
|
|
3
3
|
import { Auth } from "@warlock.js/auth";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../access/src/services/default-resolver.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* The zero-config resolver: reads a user's roles from a model field and maps
|
|
8
8
|
* them to permissions through your `roles` catalog. Works out of the box for
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-resolver.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"default-resolver.d.mts","names":[],"sources":["../../../../../../../access/src/services/default-resolver.ts"],"mappings":";;;;;;;AAyBA;;;;;;;;;;cAAa,qBAAA,YAAiC,cAAA;EAAA,iBAEzB,KAAA;EAAA,iBACA,SAAA;cADA,KAAA,EAAO,QAAA,EACP,SAAA,IAAY,IAAA,EAAM,IAAA;EAIxB,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,OAAA;EAI1B,kBAAA,CAAmB,IAAA,EAAM,IAAA,GAAO,OAAA;AAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../access/src/services/default-resolver.ts
|
|
2
2
|
/** Coerce a roles value (`undefined` / single string / array) into a string array. */
|
|
3
3
|
function toRoleList(value) {
|
|
4
4
|
if (Array.isArray(value)) return value;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-resolver.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"default-resolver.mjs","names":[],"sources":["../../../../../../../access/src/services/default-resolver.ts"],"sourcesContent":["import type { Auth } from \"@warlock.js/auth\";\nimport type { AccessResolver } from \"../contracts/access-resolver\";\nimport type { RolesMap } from \"../contracts/types\";\n\n/** Coerce a roles value (`undefined` / single string / array) into a string array. */\nfunction toRoleList(value: unknown): string[] {\n if (Array.isArray(value)) return value as string[];\n\n if (typeof value === \"string\" && value.length > 0) return [value];\n\n return [];\n}\n\n/**\n * The zero-config resolver: reads a user's roles from a model field and maps\n * them to permissions through your `roles` catalog. Works out of the box for\n * both a `roles` array column and a single `role` column.\n *\n * Swap `readRoles` to read from anywhere else (a relation, a token claim); for\n * a different storage shape entirely, implement {@link AccessResolver} directly.\n *\n * @example\n * new DefaultAccessResolver({ editor: [\"orders.*\"], viewer: [\"orders.view\"] });\n * new DefaultAccessResolver(rolesMap, (user) => user.get(\"memberRoles\"));\n */\nexport class DefaultAccessResolver implements AccessResolver {\n public constructor(\n private readonly roles: RolesMap,\n private readonly readRoles: (user: Auth) => unknown = (user) =>\n user.get(\"roles\") ?? user.get(\"role\"),\n ) {}\n\n public async resolveRoles(user: Auth): Promise<string[]> {\n return toRoleList(await this.readRoles(user));\n }\n\n public async resolvePermissions(user: Auth): Promise<string[]> {\n const roles = await this.resolveRoles(user);\n\n // Guard against a role named `__proto__` / `constructor` resolving to an\n // inherited (non-array) value.\n return roles.flatMap((role) =>\n Object.prototype.hasOwnProperty.call(this.roles, role)\n ? this.roles[role]\n : [],\n );\n }\n}\n"],"mappings":";;AAKA,SAAS,WAAW,OAA0B;CAC5C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO;CAEjC,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK;CAEhE,OAAO,CAAC;AACV;;;;;;;;;;;;;AAcA,IAAa,wBAAb,MAA6D;CAC3D,AAAO,YACL,AAAiB,OACjB,AAAiB,aAAsC,SACrD,KAAK,IAAI,OAAO,KAAK,KAAK,IAAI,MAAM,GACtC;EAHiB;EACA;CAEhB;CAEH,MAAa,aAAa,MAA+B;EACvD,OAAO,WAAW,MAAM,KAAK,UAAU,IAAI,CAAC;CAC9C;CAEA,MAAa,mBAAmB,MAA+B;EAK7D,QAAO,MAJa,KAAK,aAAa,IAAI,EAI9B,CAAC,SAAS,SACpB,OAAO,UAAU,eAAe,KAAK,KAAK,OAAO,IAAI,IACjD,KAAK,MAAM,QACX,CAAC,CACP;CACF;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Auth } from "@warlock.js/auth";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../access/src/services/engine.d.ts
|
|
4
4
|
/** Drop the cached role/permission sets for a user (after any out-of-band role change). */
|
|
5
5
|
declare function flush(user: Auth, tenant?: string): Promise<void>;
|
|
6
6
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"engine.d.mts","names":[],"sources":["../../../../../../../access/src/services/engine.ts"],"mappings":";;;;iBAsEsB,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,MAAA,YAAkB,OAAO"}
|
package/esm/services/engine.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { getPolicy } from "./policies.mjs";
|
|
|
5
5
|
import { cache } from "@warlock.js/cache";
|
|
6
6
|
import { log } from "@warlock.js/logger";
|
|
7
7
|
|
|
8
|
-
//#region
|
|
8
|
+
//#region ../access/src/services/engine.ts
|
|
9
9
|
const PERMISSIONS_PREFIX = "access.perms.";
|
|
10
10
|
const ROLES_PREFIX = "access.roles.";
|
|
11
11
|
/** Resolve the effective tenant for a check (explicit → ambient → undefined). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"engine.mjs","names":[],"sources":["../../../../../../../access/src/services/engine.ts"],"sourcesContent":["import type { Auth } from \"@warlock.js/auth\";\nimport { cache } from \"@warlock.js/cache\";\nimport { log } from \"@warlock.js/logger\";\nimport type { AccessContext } from \"../contracts/types\";\nimport { AccessConfigError } from \"../utils/access-config-error\";\nimport { accessConfig } from \"./access-config\";\nimport { matchesPermission } from \"./matcher\";\nimport { getPolicy } from \"./policies\";\n\nconst PERMISSIONS_PREFIX = \"access.perms.\";\nconst ROLES_PREFIX = \"access.roles.\";\n\n/** Resolve the effective tenant for a check (explicit → ambient → undefined). */\nfunction tenantFor(user: Auth, context: AccessContext): string | undefined {\n return context.tenant ?? accessConfig.tenant(user);\n}\n\nfunction cacheKey(prefix: string, user: Auth, tenant?: string): string {\n // Encode each segment so a `.` in an id or tenant can't collide two distinct\n // principals onto the same cache key (and serve one's grants to the other).\n const segment = (value: unknown) => encodeURIComponent(String(value));\n\n return `${prefix}${segment(user.userType)}.${segment(user.id)}.${segment(tenant ?? \"_\")}`;\n}\n\n/**\n * Read a list through the cache, falling back to the loader.\n *\n * The cache is **best-effort**: a cache failure degrades to the loader (the\n * source of truth) and is logged, never denied. A loader failure propagates —\n * so the DECISION fails closed in {@link check}.\n */\nasync function cached(\n key: string,\n loader: () => Promise<string[]>,\n): Promise<string[]> {\n try {\n const hit = await cache.get<string[]>(key);\n\n if (hit) return hit;\n } catch (error) {\n log.error(\"access\", \"cache\", error);\n }\n\n const value = await loader();\n\n try {\n await cache.set(key, value, { ttl: accessConfig.cacheTtl() });\n } catch (error) {\n log.error(\"access\", \"cache\", error);\n }\n\n return value;\n}\n\n/** The user's effective permission set for a tenant (cached). */\nexport function permissionsFor(user: Auth, tenant?: string): Promise<string[]> {\n return cached(cacheKey(PERMISSIONS_PREFIX, user, tenant), () =>\n accessConfig.resolver().resolvePermissions(user, tenant),\n );\n}\n\n/** The user's roles for a tenant (cached). */\nexport function rolesFor(user: Auth, tenant?: string): Promise<string[]> {\n return cached(cacheKey(ROLES_PREFIX, user, tenant), () =>\n accessConfig.resolver().resolveRoles(user, tenant),\n );\n}\n\n/** Drop the cached role/permission sets for a user (after any out-of-band role change). */\nexport async function flush(user: Auth, tenant?: string): Promise<void> {\n try {\n await cache.remove(cacheKey(PERMISSIONS_PREFIX, user, tenant));\n await cache.remove(cacheKey(ROLES_PREFIX, user, tenant));\n } catch (error) {\n log.error(\"access\", \"cache\", error);\n }\n}\n\n/**\n * The core decision: does the user hold `permission`, and — when a `resource`\n * is supplied — does its policy pass too?\n *\n * Fails CLOSED: any error while resolving the decision denies (and logs).\n */\nexport async function check(\n user: Auth,\n permission: string,\n context: AccessContext = {},\n): Promise<boolean> {\n try {\n const tenant = tenantFor(user, context);\n const permissions = await permissionsFor(user, tenant);\n\n if (!matchesPermission(permissions, permission)) return false;\n\n const policy = getPolicy(permission);\n\n // A policy only runs on an instance check (a resource was supplied);\n // class-level checks (`gate`) stop at the grant.\n if (policy === undefined || context.resource === undefined) return true;\n\n const roles = await rolesFor(user, tenant);\n\n return Boolean(\n await policy(user, context.resource, {\n ...context,\n tenant,\n hasRole: (role) => roles.includes(role),\n hasPermission: (perm) => matchesPermission(permissions, perm),\n }),\n );\n } catch (error) {\n if (error instanceof AccessConfigError) throw error; // misconfig is loud, not a silent deny\n\n log.error(\"access\", \"check\", error);\n\n return false;\n }\n}\n"],"mappings":";;;;;;;;AASA,MAAM,qBAAqB;AAC3B,MAAM,eAAe;;AAGrB,SAAS,UAAU,MAAY,SAA4C;CACzE,OAAO,QAAQ,UAAU,aAAa,OAAO,IAAI;AACnD;AAEA,SAAS,SAAS,QAAgB,MAAY,QAAyB;CAGrE,MAAM,WAAW,UAAmB,mBAAmB,OAAO,KAAK,CAAC;CAEpE,OAAO,GAAG,SAAS,QAAQ,KAAK,QAAQ,EAAE,GAAG,QAAQ,KAAK,EAAE,EAAE,GAAG,QAAQ,UAAU,GAAG;AACxF;;;;;;;;AASA,eAAe,OACb,KACA,QACmB;CACnB,IAAI;EACF,MAAM,MAAM,MAAM,MAAM,IAAc,GAAG;EAEzC,IAAI,KAAK,OAAO;CAClB,SAAS,OAAO;EACd,IAAI,MAAM,UAAU,SAAS,KAAK;CACpC;CAEA,MAAM,QAAQ,MAAM,OAAO;CAE3B,IAAI;EACF,MAAM,MAAM,IAAI,KAAK,OAAO,EAAE,KAAK,aAAa,SAAS,EAAE,CAAC;CAC9D,SAAS,OAAO;EACd,IAAI,MAAM,UAAU,SAAS,KAAK;CACpC;CAEA,OAAO;AACT;;AAGA,SAAgB,eAAe,MAAY,QAAoC;CAC7E,OAAO,OAAO,SAAS,oBAAoB,MAAM,MAAM,SACrD,aAAa,SAAS,CAAC,CAAC,mBAAmB,MAAM,MAAM,CACzD;AACF;;AAGA,SAAgB,SAAS,MAAY,QAAoC;CACvE,OAAO,OAAO,SAAS,cAAc,MAAM,MAAM,SAC/C,aAAa,SAAS,CAAC,CAAC,aAAa,MAAM,MAAM,CACnD;AACF;;AAGA,eAAsB,MAAM,MAAY,QAAgC;CACtE,IAAI;EACF,MAAM,MAAM,OAAO,SAAS,oBAAoB,MAAM,MAAM,CAAC;EAC7D,MAAM,MAAM,OAAO,SAAS,cAAc,MAAM,MAAM,CAAC;CACzD,SAAS,OAAO;EACd,IAAI,MAAM,UAAU,SAAS,KAAK;CACpC;AACF;;;;;;;AAQA,eAAsB,MACpB,MACA,YACA,UAAyB,CAAC,GACR;CAClB,IAAI;EACF,MAAM,SAAS,UAAU,MAAM,OAAO;EACtC,MAAM,cAAc,MAAM,eAAe,MAAM,MAAM;EAErD,IAAI,CAAC,kBAAkB,aAAa,UAAU,GAAG,OAAO;EAExD,MAAM,SAAS,UAAU,UAAU;EAInC,IAAI,WAAW,UAAa,QAAQ,aAAa,QAAW,OAAO;EAEnE,MAAM,QAAQ,MAAM,SAAS,MAAM,MAAM;EAEzC,OAAO,QACL,MAAM,OAAO,MAAM,QAAQ,UAAU;GACnC,GAAG;GACH;GACA,UAAU,SAAS,MAAM,SAAS,IAAI;GACtC,gBAAgB,SAAS,kBAAkB,aAAa,IAAI;EAC9D,CAAC,CACH;CACF,SAAS,OAAO;EACd,IAAI,iBAAiB,mBAAmB,MAAM;EAE9C,IAAI,MAAM,UAAU,SAAS,KAAK;EAElC,OAAO;CACT;AACF"}
|
package/esm/services/matcher.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matcher.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"matcher.mjs","names":[],"sources":["../../../../../../../access/src/services/matcher.ts"],"sourcesContent":["/**\n * Whether a set of granted permission patterns covers a requested permission.\n *\n * Supports three forms:\n * - exact — `\"orders.update\"` covers `\"orders.update\"`\n * - global — `\"*\"` covers everything (the super-grant)\n * - prefix wildcard — `\"orders.*\"` covers `\"orders.update\"` and `\"orders.update.status\"`\n *\n * `\"orders.*\"` does NOT cover the bare `\"orders\"` (no trailing segment).\n */\nexport function matchesPermission(granted: string[], permission: string): boolean {\n for (const pattern of granted) {\n if (pattern === \"*\" || pattern === permission) {\n return true;\n }\n\n // \"orders.*\" → prefix \"orders.\" — matches any nested permission.\n if (pattern.endsWith(\".*\") && permission.startsWith(pattern.slice(0, -1))) {\n return true;\n }\n }\n\n return false;\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAgB,kBAAkB,SAAmB,YAA6B;CAChF,KAAK,MAAM,WAAW,SAAS;EAC7B,IAAI,YAAY,OAAO,YAAY,YACjC,OAAO;EAIT,IAAI,QAAQ,SAAS,IAAI,KAAK,WAAW,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC,GACtE,OAAO;CAEX;CAEA,OAAO;AACT"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PolicyFn } from "../contracts/types.mjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../access/src/services/policies.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Register an ABAC condition for a permission. It runs ON TOP of the RBAC grant
|
|
6
6
|
* whenever an authorization check carries a `resource` — letting you express
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policies.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"policies.d.mts","names":[],"sources":["../../../../../../../access/src/services/policies.ts"],"mappings":";;;;;AAYA;;;;;;iBAAgB,YAAA,CAAa,UAAA,UAAoB,MAAA,EAAQ,QAAQ;AAUjE;AAAA,iBAAgB,aAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policies.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"policies.mjs","names":[],"sources":["../../../../../../../access/src/services/policies.ts"],"sourcesContent":["import type { PolicyFn } from \"../contracts/types\";\n\nconst policies = new Map<string, PolicyFn>();\n\n/**\n * Register an ABAC condition for a permission. It runs ON TOP of the RBAC grant\n * whenever an authorization check carries a `resource` — letting you express\n * \"...but only their own / only in their tenant / only while pending\".\n *\n * @example\n * definePolicy(\"orders.update\", (user, order) => order.get(\"customer_id\") === user.id);\n */\nexport function definePolicy(permission: string, policy: PolicyFn): void {\n policies.set(permission, policy);\n}\n\n/** The policy registered for a permission, if any. */\nexport function getPolicy(permission: string): PolicyFn | undefined {\n return policies.get(permission);\n}\n\n/** Remove every registered policy. Primarily for tests. */\nexport function clearPolicies(): void {\n policies.clear();\n}\n"],"mappings":";AAEA,MAAM,2BAAW,IAAI,IAAsB;;;;;;;;;AAU3C,SAAgB,aAAa,YAAoB,QAAwB;CACvE,SAAS,IAAI,YAAY,MAAM;AACjC;;AAGA,SAAgB,UAAU,YAA0C;CAClE,OAAO,SAAS,IAAI,UAAU;AAChC;;AAGA,SAAgB,gBAAsB;CACpC,SAAS,MAAM;AACjB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../access/src/utils/access-config-error.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* Thrown when `@warlock.js/access` is misconfigured (e.g. no resolver). Unlike a
|
|
4
4
|
* runtime resolution failure — which fails CLOSED (denies) — a config error is a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-config-error.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"access-config-error.d.mts","names":[],"sources":["../../../../../../../access/src/utils/access-config-error.ts"],"mappings":";;AAKA;;;;cAAa,iBAAA,SAA0B,KAAK;cACvB,OAAA;AAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../access/src/utils/access-config-error.ts
|
|
2
2
|
/**
|
|
3
3
|
* Thrown when `@warlock.js/access` is misconfigured (e.g. no resolver). Unlike a
|
|
4
4
|
* runtime resolution failure — which fails CLOSED (denies) — a config error is a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-config-error.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"access-config-error.mjs","names":[],"sources":["../../../../../../../access/src/utils/access-config-error.ts"],"sourcesContent":["/**\n * Thrown when `@warlock.js/access` is misconfigured (e.g. no resolver). Unlike a\n * runtime resolution failure — which fails CLOSED (denies) — a config error is a\n * developer mistake, so the engine re-throws it LOUD instead of silently denying.\n */\nexport class AccessConfigError extends Error {\n public constructor(message: string) {\n super(message);\n this.name = \"AccessConfigError\";\n }\n}\n"],"mappings":";;;;;;AAKA,IAAa,oBAAb,cAAuC,MAAM;CAC3C,AAAO,YAAY,SAAiB;EAClC,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../access/src/utils/access-error-codes.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* Wire-stable error codes emitted by `@warlock.js/access`. Map these in your
|
|
4
4
|
* error transformer the way you map `AuthErrorCodes`. A value change here is a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-error-codes.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"access-error-codes.d.mts","names":[],"sources":["../../../../../../../access/src/utils/access-error-codes.ts"],"mappings":";;AAKA;;;;aAAY,gBAAA;;EAEV,SAAS;AAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../access/src/utils/access-error-codes.ts
|
|
2
2
|
/**
|
|
3
3
|
* Wire-stable error codes emitted by `@warlock.js/access`. Map these in your
|
|
4
4
|
* error transformer the way you map `AuthErrorCodes`. A value change here is a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-error-codes.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"access-error-codes.mjs","names":[],"sources":["../../../../../../../access/src/utils/access-error-codes.ts"],"sourcesContent":["/**\n * Wire-stable error codes emitted by `@warlock.js/access`. Map these in your\n * error transformer the way you map `AuthErrorCodes`. A value change here is a\n * breaking change for any client that reacts to the code.\n */\nexport enum AccessErrorCodes {\n /** EC100 — authenticated, but missing the required permission. */\n Forbidden = \"EC100\",\n}\n"],"mappings":";;;;;;AAKA,IAAY,mBAAL;;CAEL;;AACF"}
|
package/package.json
CHANGED
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
"url": "https://github.com/warlockjs/access"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@warlock.js/auth": "4.
|
|
21
|
-
"@warlock.js/cache": "4.
|
|
22
|
-
"@warlock.js/cascade": "4.
|
|
23
|
-
"@warlock.js/core": "4.
|
|
24
|
-
"@warlock.js/logger": "4.
|
|
25
|
-
"@warlock.js/seal": "4.
|
|
20
|
+
"@warlock.js/auth": "4.9.0",
|
|
21
|
+
"@warlock.js/cache": "4.9.0",
|
|
22
|
+
"@warlock.js/cascade": "4.9.0",
|
|
23
|
+
"@warlock.js/core": "4.9.0",
|
|
24
|
+
"@warlock.js/logger": "4.9.0",
|
|
25
|
+
"@warlock.js/seal": "4.9.0"
|
|
26
26
|
},
|
|
27
|
-
"version": "4.
|
|
27
|
+
"version": "4.9.0",
|
|
28
28
|
"main": "./cjs/index.cjs",
|
|
29
29
|
"module": "./esm/index.mjs",
|
|
30
30
|
"types": "./esm/index.d.mts",
|