@plantops/auth-kit 0.1.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/README.md +7 -0
- package/dist/adapters/fetch/index.d.ts +38 -0
- package/dist/adapters/fetch/index.d.ts.map +1 -0
- package/dist/adapters/fetch/index.js +52 -0
- package/dist/adapters/nestjs/auth.guard.d.ts +120 -0
- package/dist/adapters/nestjs/auth.guard.d.ts.map +1 -0
- package/dist/adapters/nestjs/auth.guard.js +165 -0
- package/dist/adapters/nestjs/index.d.ts +10 -0
- package/dist/adapters/nestjs/index.d.ts.map +1 -0
- package/dist/adapters/nestjs/index.js +12 -0
- package/dist/adapters/nestjs/permission.guard.d.ts +115 -0
- package/dist/adapters/nestjs/permission.guard.d.ts.map +1 -0
- package/dist/adapters/nestjs/permission.guard.js +167 -0
- package/dist/adapters/nestjs/require-permission.decorator.d.ts +31 -0
- package/dist/adapters/nestjs/require-permission.decorator.d.ts.map +1 -0
- package/dist/adapters/nestjs/require-permission.decorator.js +41 -0
- package/dist/adapters/nestjs/scope-resolver.d.ts +12 -0
- package/dist/adapters/nestjs/scope-resolver.d.ts.map +1 -0
- package/dist/adapters/nestjs/scope-resolver.js +24 -0
- package/dist/core/claims.d.ts +114 -0
- package/dist/core/claims.d.ts.map +1 -0
- package/dist/core/claims.js +183 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +15 -0
- package/dist/core/jwks-verifier.d.ts +78 -0
- package/dist/core/jwks-verifier.d.ts.map +1 -0
- package/dist/core/jwks-verifier.js +183 -0
- package/dist/core/jws.d.ts +96 -0
- package/dist/core/jws.d.ts.map +1 -0
- package/dist/core/jws.js +183 -0
- package/dist/core/revocation-cache.d.ts +79 -0
- package/dist/core/revocation-cache.d.ts.map +1 -0
- package/dist/core/revocation-cache.js +68 -0
- package/dist/core/scope-resolver.d.ts +235 -0
- package/dist/core/scope-resolver.d.ts.map +1 -0
- package/dist/core/scope-resolver.js +206 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The guard that enforces `@RequirePermission` (Doc 04 §8, Doc 08 §4).
|
|
4
|
+
*
|
|
5
|
+
* `AuthGuard` establishes **who** is calling. This one establishes **whether
|
|
6
|
+
* they may** — the WHO × WHAT × WHERE question of Doc 04 §1, asked once per
|
|
7
|
+
* request, before the handler and before the transaction that would carry out
|
|
8
|
+
* whatever it was going to do.
|
|
9
|
+
*
|
|
10
|
+
* Four steps, and each one is a line of Doc 04 §8's contract:
|
|
11
|
+
*
|
|
12
|
+
* 1. Read the route's requirement. No requirement and no explicit opt-out is a
|
|
13
|
+
* refusal — see {@link RequirePermission} for why the direction is that way
|
|
14
|
+
* round.
|
|
15
|
+
* 2. Load the subject's resolved grants, through {@link GrantsSource}.
|
|
16
|
+
* 3. Confirm the permission is held, and — where the route names a scope node —
|
|
17
|
+
* that the subject's grants cover it.
|
|
18
|
+
* 4. Refuse with `PERMISSION_DENIED` or `SCOPE_DENIED`, and audit the attempt.
|
|
19
|
+
*
|
|
20
|
+
* Steps 2 and 3 are {@link ScopeResolver.decide}, which is where the rule lives
|
|
21
|
+
* and where it is unit-tested. What is left here is the framework adapter: read
|
|
22
|
+
* metadata, read the request, translate an outcome into an exception. That
|
|
23
|
+
* split is deliberate — a guard is the one place a test has to build an
|
|
24
|
+
* `ExecutionContext` to reach, and authorization logic should not be behind
|
|
25
|
+
* that.
|
|
26
|
+
*
|
|
27
|
+
* ## Which connection step 2 runs on
|
|
28
|
+
*
|
|
29
|
+
* Not this file's decision, and deliberately not this file's problem.
|
|
30
|
+
* `docs/adr/0001-permission-guard-connection-strategy.md` settles it: a guard
|
|
31
|
+
* runs *before* the per-request transaction exists (Nest runs guards ahead of
|
|
32
|
+
* interceptors), so on a grants-cache miss the IAM's {@link GrantsSource} opens
|
|
33
|
+
* its own `QueryRunner`, applies the RLS context from the verified claims,
|
|
34
|
+
* resolves on it, and commits and releases in a `finally`. It does **not** open,
|
|
35
|
+
* reuse or leave open the request transaction: a guard has no "after" phase in
|
|
36
|
+
* which to close one, so `TenantContextInterceptor` stays its sole owner.
|
|
37
|
+
*
|
|
38
|
+
* `auth-kit` may depend on `@plantops/contracts` and nothing else (Doc 08 §2),
|
|
39
|
+
* so it could not name a `DataSource` here in any case — which is why the port
|
|
40
|
+
* exists and why a future module can satisfy it with a cached HTTP call instead.
|
|
41
|
+
*
|
|
42
|
+
* ## Every denial is audited, and a failure to audit is not a failure to deny
|
|
43
|
+
*
|
|
44
|
+
* Doc 04 §8 step 5 and Doc 10 §3: the attempt is recorded with the permission
|
|
45
|
+
* that was wanted and the target that was named. The IAM binds
|
|
46
|
+
* {@link DENIAL_AUDITOR} to `AuditService.recordDenial`, which commits on its
|
|
47
|
+
* own connection precisely because the request it accompanies is about to be
|
|
48
|
+
* rolled back by its own 403 — and which never throws, because turning a lost
|
|
49
|
+
* audit row into a 500 would tell a caller which requests were refused for which
|
|
50
|
+
* reason.
|
|
51
|
+
*
|
|
52
|
+
* The auditor is optional: a downstream module has no `audit_trail` table and
|
|
53
|
+
* records nothing. Binding it is the IAM's business.
|
|
54
|
+
*
|
|
55
|
+
* ## The two refusals say different things, and neither says whether the target
|
|
56
|
+
* exists
|
|
57
|
+
*
|
|
58
|
+
* `PERMISSION_DENIED` means the subject does not hold it anywhere;
|
|
59
|
+
* `SCOPE_DENIED` means they hold it, but not over the node they named. A node
|
|
60
|
+
* belonging to another tenant produces `SCOPE_DENIED` too — indistinguishable
|
|
61
|
+
* from one they simply do not cover, because RLS makes it so and Doc 06 §2
|
|
62
|
+
* requires that a denial never reveal cross-tenant existence.
|
|
63
|
+
*/
|
|
64
|
+
var PermissionGuard_1;
|
|
65
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
66
|
+
exports.PermissionGuard = exports.DENIAL_AUDITOR = exports.VERIFIED_CLAIMS_SOURCE = exports.AuthorizationDeniedException = void 0;
|
|
67
|
+
const tslib_1 = require("tslib");
|
|
68
|
+
const common_1 = require("@nestjs/common");
|
|
69
|
+
const core_1 = require("@nestjs/core");
|
|
70
|
+
const contracts_1 = require("@plantops/contracts");
|
|
71
|
+
const auth_guard_1 = require("./auth.guard");
|
|
72
|
+
const require_permission_decorator_1 = require("./require-permission.decorator");
|
|
73
|
+
const scope_resolver_1 = require("./scope-resolver");
|
|
74
|
+
const scope_resolver_2 = require("../../core/scope-resolver");
|
|
75
|
+
/**
|
|
76
|
+
* A 403 that knows which of Doc 06 §2's two codes it is.
|
|
77
|
+
*
|
|
78
|
+
* `ForbiddenException` alone would come back as `PERMISSION_DENIED`, because
|
|
79
|
+
* that is the less specific of the two and the only one a bare status can imply
|
|
80
|
+
* (`http-exception.filter.ts`). The distinction is worth carrying: a client that
|
|
81
|
+
* sees `SCOPE_DENIED` can tell its user *where* they lack access, which is the
|
|
82
|
+
* one actionable thing about a refusal.
|
|
83
|
+
*
|
|
84
|
+
* `IamErrorCode` is a `@plantops/contracts` export, so naming it here crosses no
|
|
85
|
+
* boundary — the code table is part of the published contract, not of the IAM.
|
|
86
|
+
*/
|
|
87
|
+
class AuthorizationDeniedException extends common_1.ForbiddenException {
|
|
88
|
+
constructor(code, message) {
|
|
89
|
+
super(message);
|
|
90
|
+
this.code = code;
|
|
91
|
+
this.name = 'AuthorizationDeniedException';
|
|
92
|
+
}
|
|
93
|
+
/** The subject does not hold the permission at all. */
|
|
94
|
+
static permission() {
|
|
95
|
+
return new AuthorizationDeniedException(contracts_1.IamErrorCode.PERMISSION_DENIED, 'You do not have permission to perform this action');
|
|
96
|
+
}
|
|
97
|
+
/** They hold it, but not over the node they named. */
|
|
98
|
+
static scope() {
|
|
99
|
+
return new AuthorizationDeniedException(contracts_1.IamErrorCode.SCOPE_DENIED, 'You do not have permission at the requested scope');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.AuthorizationDeniedException = AuthorizationDeniedException;
|
|
103
|
+
exports.VERIFIED_CLAIMS_SOURCE = Symbol('auth-kit:VerifiedClaimsSource');
|
|
104
|
+
exports.DENIAL_AUDITOR = Symbol('auth-kit:DenialAuditor');
|
|
105
|
+
let PermissionGuard = PermissionGuard_1 = class PermissionGuard {
|
|
106
|
+
constructor(reflector, resolver, claims, auditor = null) {
|
|
107
|
+
this.reflector = reflector;
|
|
108
|
+
this.resolver = resolver;
|
|
109
|
+
this.claims = claims;
|
|
110
|
+
this.auditor = auditor;
|
|
111
|
+
this.logger = new common_1.Logger(PermissionGuard_1.name);
|
|
112
|
+
}
|
|
113
|
+
async canActivate(context) {
|
|
114
|
+
if (context.getType() !== 'http')
|
|
115
|
+
return true;
|
|
116
|
+
const targets = [context.getHandler(), context.getClass()];
|
|
117
|
+
// A `@Public()` route has no subject, so there are no grants to check. It
|
|
118
|
+
// is also the one case where stepping aside cannot widen anything: the
|
|
119
|
+
// request reaches the handler with no RLS context, and every tenant policy
|
|
120
|
+
// matches nothing (`tenant-context.interceptor.ts`).
|
|
121
|
+
if (this.reflector.getAllAndOverride(auth_guard_1.IS_PUBLIC_METADATA, targets)) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const requirement = this.reflector.getAllAndOverride(require_permission_decorator_1.REQUIRE_PERMISSION_METADATA, targets);
|
|
125
|
+
if (requirement === undefined) {
|
|
126
|
+
const exemption = this.reflector.getAllAndOverride(require_permission_decorator_1.NO_PERMISSION_METADATA, targets);
|
|
127
|
+
if (exemption !== undefined)
|
|
128
|
+
return true;
|
|
129
|
+
// Deny-by-default, and loudly. Reaching this is a wiring mistake rather
|
|
130
|
+
// than a caller's, and it must not be silent: the alternative reading —
|
|
131
|
+
// let an undeclared route through — is the one that produces a working,
|
|
132
|
+
// ungated endpoint nobody notices (`require-permission.decorator.ts`).
|
|
133
|
+
this.logger.error(`${context.getClass().name}.${context.getHandler().name} declares neither ` +
|
|
134
|
+
'@RequirePermission() nor @NoPermissionRequired(); refusing the request');
|
|
135
|
+
throw AuthorizationDeniedException.permission();
|
|
136
|
+
}
|
|
137
|
+
const request = context.switchToHttp().getRequest();
|
|
138
|
+
const claims = this.claims.claimsOf(request);
|
|
139
|
+
// Unreachable through `AuthGuard`, which refuses an unauthenticated request
|
|
140
|
+
// on a non-`@Public()` route. It is the backstop for a route that ever loses
|
|
141
|
+
// that guard, and refusing is the only safe reading of "no subject".
|
|
142
|
+
if (claims === undefined)
|
|
143
|
+
throw AuthorizationDeniedException.permission();
|
|
144
|
+
const scopeNodeId = requirement.scopeFrom === undefined
|
|
145
|
+
? undefined
|
|
146
|
+
: (0, require_permission_decorator_1.readScopeTarget)(request, requirement.scopeFrom);
|
|
147
|
+
const decision = await this.resolver.decide(claims, requirement, scopeNodeId);
|
|
148
|
+
if (decision.outcome === scope_resolver_2.AuthorizationOutcome.ALLOWED)
|
|
149
|
+
return true;
|
|
150
|
+
// Best-effort and awaited: the row commits on its own connection, so it
|
|
151
|
+
// survives the rollback this 403 is about to cause, and the auditor's own
|
|
152
|
+
// contract is that it never throws (Doc 10 §3).
|
|
153
|
+
await this.auditor?.recordDenial(claims, decision.outcome, decision.permissions, decision.scopeNodeId);
|
|
154
|
+
throw decision.outcome === scope_resolver_2.AuthorizationOutcome.PERMISSION_DENIED
|
|
155
|
+
? AuthorizationDeniedException.permission()
|
|
156
|
+
: AuthorizationDeniedException.scope();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
exports.PermissionGuard = PermissionGuard;
|
|
160
|
+
exports.PermissionGuard = PermissionGuard = PermissionGuard_1 = tslib_1.__decorate([
|
|
161
|
+
(0, common_1.Injectable)(),
|
|
162
|
+
tslib_1.__param(2, (0, common_1.Inject)(exports.VERIFIED_CLAIMS_SOURCE)),
|
|
163
|
+
tslib_1.__param(3, (0, common_1.Optional)()),
|
|
164
|
+
tslib_1.__param(3, (0, common_1.Inject)(exports.DENIAL_AUDITOR)),
|
|
165
|
+
tslib_1.__metadata("design:paramtypes", [core_1.Reflector,
|
|
166
|
+
scope_resolver_1.ScopeResolver, Object, Object])
|
|
167
|
+
], PermissionGuard);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@RequirePermission` — the WHAT, and optionally the WHERE, of a route
|
|
3
|
+
* (Doc 04 §8, Doc 08 §4).
|
|
4
|
+
*
|
|
5
|
+
* The decision vocabulary — {@link PermissionRequirement},
|
|
6
|
+
* {@link RequirePermissionOptions}, `readScopeTarget` — lives in the
|
|
7
|
+
* framework-free core (`core/scope-resolver.ts`); this adapter adds only what
|
|
8
|
+
* Nest needs: `SetMetadata` for the two decorators, and nothing else.
|
|
9
|
+
*/
|
|
10
|
+
import type { PermissionKey } from '@plantops/contracts';
|
|
11
|
+
import { type RequirePermissionOptions } from '../../core/scope-resolver';
|
|
12
|
+
export { NO_PERMISSION_METADATA, REQUIRE_PERMISSION_METADATA, type PermissionRequirement, type RequirePermissionOptions, readScopeTarget, } from '../../core/scope-resolver';
|
|
13
|
+
/**
|
|
14
|
+
* Gates a route on `permission`, optionally at the scope node the request
|
|
15
|
+
* names.
|
|
16
|
+
*
|
|
17
|
+
* Applicable to a method or to a whole controller; the method wins where both
|
|
18
|
+
* carry one, which is Nest getAllAndOverride order and lets a controller state
|
|
19
|
+
* the common case once.
|
|
20
|
+
*
|
|
21
|
+
* Pass an array to admit any one of several keys — see Doc 04 §8 for when that
|
|
22
|
+
* is legitimate, and for why the others must not.
|
|
23
|
+
*/
|
|
24
|
+
export declare const RequirePermission: (permission: PermissionKey | readonly PermissionKey[], options?: RequirePermissionOptions) => import("@nestjs/common").CustomDecorator<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Opts a route out of permission gating, with the reason recorded at the call
|
|
27
|
+
* site. The reason is not decoration — it separates a considered exemption from
|
|
28
|
+
* a forgotten decorator.
|
|
29
|
+
*/
|
|
30
|
+
export declare const NoPermissionRequired: (reason: string) => import("@nestjs/common").CustomDecorator<string>;
|
|
31
|
+
//# sourceMappingURL=require-permission.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-permission.decorator.d.ts","sourceRoot":"","sources":["../../../src/adapters/nestjs/require-permission.decorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC5B,YAAY,aAAa,GAAG,SAAS,aAAa,EAAE,EACpD,UAAS,wBAA6B,qDAKpC,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,qDACN,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@RequirePermission` — the WHAT, and optionally the WHERE, of a route
|
|
4
|
+
* (Doc 04 §8, Doc 08 §4).
|
|
5
|
+
*
|
|
6
|
+
* The decision vocabulary — {@link PermissionRequirement},
|
|
7
|
+
* {@link RequirePermissionOptions}, `readScopeTarget` — lives in the
|
|
8
|
+
* framework-free core (`core/scope-resolver.ts`); this adapter adds only what
|
|
9
|
+
* Nest needs: `SetMetadata` for the two decorators, and nothing else.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.NoPermissionRequired = exports.RequirePermission = exports.readScopeTarget = exports.REQUIRE_PERMISSION_METADATA = exports.NO_PERMISSION_METADATA = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const scope_resolver_1 = require("../../core/scope-resolver");
|
|
15
|
+
var scope_resolver_2 = require("../../core/scope-resolver");
|
|
16
|
+
Object.defineProperty(exports, "NO_PERMISSION_METADATA", { enumerable: true, get: function () { return scope_resolver_2.NO_PERMISSION_METADATA; } });
|
|
17
|
+
Object.defineProperty(exports, "REQUIRE_PERMISSION_METADATA", { enumerable: true, get: function () { return scope_resolver_2.REQUIRE_PERMISSION_METADATA; } });
|
|
18
|
+
Object.defineProperty(exports, "readScopeTarget", { enumerable: true, get: function () { return scope_resolver_2.readScopeTarget; } });
|
|
19
|
+
/**
|
|
20
|
+
* Gates a route on `permission`, optionally at the scope node the request
|
|
21
|
+
* names.
|
|
22
|
+
*
|
|
23
|
+
* Applicable to a method or to a whole controller; the method wins where both
|
|
24
|
+
* carry one, which is Nest getAllAndOverride order and lets a controller state
|
|
25
|
+
* the common case once.
|
|
26
|
+
*
|
|
27
|
+
* Pass an array to admit any one of several keys — see Doc 04 §8 for when that
|
|
28
|
+
* is legitimate, and for why the others must not.
|
|
29
|
+
*/
|
|
30
|
+
const RequirePermission = (permission, options = {}) => (0, common_1.SetMetadata)(scope_resolver_1.REQUIRE_PERMISSION_METADATA, {
|
|
31
|
+
permissions: typeof permission === 'string' ? [permission] : [...permission],
|
|
32
|
+
...options,
|
|
33
|
+
});
|
|
34
|
+
exports.RequirePermission = RequirePermission;
|
|
35
|
+
/**
|
|
36
|
+
* Opts a route out of permission gating, with the reason recorded at the call
|
|
37
|
+
* site. The reason is not decoration — it separates a considered exemption from
|
|
38
|
+
* a forgotten decorator.
|
|
39
|
+
*/
|
|
40
|
+
const NoPermissionRequired = (reason) => (0, common_1.SetMetadata)(scope_resolver_1.NO_PERMISSION_METADATA, reason);
|
|
41
|
+
exports.NoPermissionRequired = NoPermissionRequired;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Nest binding of the framework-free {@link ScopeResolverCore}: the only
|
|
3
|
+
* thing this class adds is receiving the `GrantsSource` port through Nest\u2019s
|
|
4
|
+
* injector (roadmap Session 50). Every decision method is inherited, so the
|
|
5
|
+
* rule lives in exactly one place — core/scope-resolver.ts — while remaining
|
|
6
|
+
* injectable anywhere a Nest token is.
|
|
7
|
+
*/
|
|
8
|
+
import { ScopeResolverCore, type GrantsSource } from '../../core/scope-resolver';
|
|
9
|
+
export declare class ScopeResolver extends ScopeResolverCore {
|
|
10
|
+
constructor(source: GrantsSource);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=scope-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-resolver.d.ts","sourceRoot":"","sources":["../../../src/adapters/nestjs/scope-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAiB,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEhG,qBACa,aAAc,SAAQ,iBAAiB;gBACf,MAAM,EAAE,YAAY;CAGxD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The Nest binding of the framework-free {@link ScopeResolverCore}: the only
|
|
4
|
+
* thing this class adds is receiving the `GrantsSource` port through Nest\u2019s
|
|
5
|
+
* injector (roadmap Session 50). Every decision method is inherited, so the
|
|
6
|
+
* rule lives in exactly one place — core/scope-resolver.ts — while remaining
|
|
7
|
+
* injectable anywhere a Nest token is.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.ScopeResolver = void 0;
|
|
11
|
+
const tslib_1 = require("tslib");
|
|
12
|
+
const common_1 = require("@nestjs/common");
|
|
13
|
+
const scope_resolver_1 = require("../../core/scope-resolver");
|
|
14
|
+
let ScopeResolver = class ScopeResolver extends scope_resolver_1.ScopeResolverCore {
|
|
15
|
+
constructor(source) {
|
|
16
|
+
super(source);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
exports.ScopeResolver = ScopeResolver;
|
|
20
|
+
exports.ScopeResolver = ScopeResolver = tslib_1.__decorate([
|
|
21
|
+
(0, common_1.Injectable)(),
|
|
22
|
+
tslib_1.__param(0, (0, common_1.Inject)(scope_resolver_1.GRANTS_SOURCE)),
|
|
23
|
+
tslib_1.__metadata("design:paramtypes", [Object])
|
|
24
|
+
], ScopeResolver);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed claim set, and the checks every verifier must apply identically
|
|
3
|
+
* (Doc 03 §2, §6).
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is not in the IAM
|
|
6
|
+
*
|
|
7
|
+
* The IAM signs; `admin-web`, `iam-client` and every future operational module
|
|
8
|
+
* verify — locally, from the published JWKS, without calling the IAM (Doc 06
|
|
9
|
+
* §11). "Verify the same way" is therefore a property of *separate processes*,
|
|
10
|
+
* and the only way to hold it is for all of them to run this code. A module
|
|
11
|
+
* that reimplements the expiry check with a different leeway rejects tokens the
|
|
12
|
+
* IAM considers live for a whole minute at every token edge; one that skips the
|
|
13
|
+
* issuer check accepts another deployment's tokens outright.
|
|
14
|
+
*
|
|
15
|
+
* ## The claim set is closed, in both directions
|
|
16
|
+
*
|
|
17
|
+
* Doc 03 §2 lists exactly seven claims: `iss, sub, sty, cid, sid, iat, exp`.
|
|
18
|
+
* Not "at least" — permissions, roles and scope nodes are *deliberately* absent
|
|
19
|
+
* so that a grant change takes effect on cache invalidation (Doc 04 §7) rather
|
|
20
|
+
* than on token expiry. So {@link assertExactClaims} refuses to sign an eighth,
|
|
21
|
+
* and {@link readAccessTokenClaims} refuses to accept one: a token this IAM
|
|
22
|
+
* issued cannot carry extras, so their presence means the payload came from
|
|
23
|
+
* somewhere else, and silently reading the seven we recognise is how a second
|
|
24
|
+
* issuer goes unnoticed.
|
|
25
|
+
*
|
|
26
|
+
* ## The one exception, and why it is not a hole
|
|
27
|
+
*
|
|
28
|
+
* `JWT_TOLERATED_CLAIM_KEYS` — today just `aud` — is accepted on the way in and
|
|
29
|
+
* dropped, never required and never read (ADR 0005 §5.1). Signing is unchanged,
|
|
30
|
+
* so this IAM still mints exactly seven claims.
|
|
31
|
+
*
|
|
32
|
+
* The asymmetry is deliberate, and it is about which half can be migrated.
|
|
33
|
+
* Every consumer verifies locally with this published code; the signer is a
|
|
34
|
+
* single deployment. A claim the IAM adds later is therefore free on the
|
|
35
|
+
* signing side and catastrophic on the verifying side — an older verifier
|
|
36
|
+
* rejects **every** token rather than ignoring the field it does not know. So
|
|
37
|
+
* the tolerance has to be in the field *before* the first publish or it is
|
|
38
|
+
* worthless, which is the deadline ADR 0005 §5.1 set against #72.
|
|
39
|
+
*
|
|
40
|
+
* What it costs is the tamper signal, for exactly one registered claim. What it
|
|
41
|
+
* buys is that reconsidering ADR 0005 §3.3 does not require a strictly-ordered
|
|
42
|
+
* upgrade across repositories this one does not control.
|
|
43
|
+
*/
|
|
44
|
+
import { type JwtClaims } from '@plantops/contracts';
|
|
45
|
+
/** Why a token was refused. Never returned to a caller — see the note below. */
|
|
46
|
+
export declare const TokenRejection: {
|
|
47
|
+
/** Not a compact JWS, or its segments do not decode. */
|
|
48
|
+
readonly MALFORMED: "malformed";
|
|
49
|
+
/** Header `alg` is not RS256 (Doc 03 §1). */
|
|
50
|
+
readonly UNSUPPORTED_ALGORITHM: "unsupported_algorithm";
|
|
51
|
+
/** `kid` is not in the published set — refetch JWKS, then reject. */
|
|
52
|
+
readonly UNKNOWN_KEY: "unknown_key";
|
|
53
|
+
/** Signature does not verify under the selected key. */
|
|
54
|
+
readonly BAD_SIGNATURE: "bad_signature";
|
|
55
|
+
/** `exp` has passed, allowing the 60 s leeway. */
|
|
56
|
+
readonly EXPIRED: "expired";
|
|
57
|
+
/** `iat` is in the future beyond the leeway — a clock or a forgery. */
|
|
58
|
+
readonly NOT_YET_VALID: "not_yet_valid";
|
|
59
|
+
/** `iss` is not the expected issuer. */
|
|
60
|
+
readonly WRONG_ISSUER: "wrong_issuer";
|
|
61
|
+
/** Claim set is not the Doc 03 §2 shape. */
|
|
62
|
+
readonly BAD_CLAIMS: "bad_claims";
|
|
63
|
+
/** The session behind `sid` has been revoked (Doc 03 §6). */
|
|
64
|
+
readonly REVOKED: "revoked";
|
|
65
|
+
};
|
|
66
|
+
export type TokenRejection = (typeof TokenRejection)[keyof typeof TokenRejection];
|
|
67
|
+
/**
|
|
68
|
+
* A refused token, with the reason kept **server-side**.
|
|
69
|
+
*
|
|
70
|
+
* The distinction matters at the HTTP boundary: an `unknown_key` and an
|
|
71
|
+
* `expired` must look identical to the caller (both are a bare 401
|
|
72
|
+
* `AUTH_REQUIRED`), because the difference tells an attacker whether a forged
|
|
73
|
+
* `kid` was a near miss. The reason is here for logs, metrics and the
|
|
74
|
+
* refetch-then-reject decision — not for the response body.
|
|
75
|
+
*/
|
|
76
|
+
export declare class TokenVerificationError extends Error {
|
|
77
|
+
readonly reason: TokenRejection;
|
|
78
|
+
constructor(reason: TokenRejection, message: string);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Guards the closed claim set at signing time (Doc 03 §2).
|
|
82
|
+
*
|
|
83
|
+
* Typed input alone does not cover this: a structurally-typed object may carry
|
|
84
|
+
* extra properties at runtime, and `JSON.stringify` would faithfully sign them.
|
|
85
|
+
*
|
|
86
|
+
* Not relaxed by ADR 0005 §5.1: the tolerated claims are accepted on the way
|
|
87
|
+
* **in** only. This IAM mints seven claims, and a token of its own carrying an
|
|
88
|
+
* `aud` would mean something upstream put it there.
|
|
89
|
+
*/
|
|
90
|
+
export declare function assertExactClaims(claims: JwtClaims): Record<string, unknown>;
|
|
91
|
+
/**
|
|
92
|
+
* Validates a **signature-verified** payload against the Doc 03 §2 shape.
|
|
93
|
+
*
|
|
94
|
+
* Call this only after the signature checks out. A token signed by a legitimate
|
|
95
|
+
* key is still attacker-influenced if any upstream path ever let a caller
|
|
96
|
+
* choose a claim value, which is why every field is re-validated on the way in
|
|
97
|
+
* and not merely on the way out.
|
|
98
|
+
*
|
|
99
|
+
* @throws {TokenVerificationError}
|
|
100
|
+
*/
|
|
101
|
+
export declare function readAccessTokenClaims(payload: Record<string, unknown>): JwtClaims;
|
|
102
|
+
/**
|
|
103
|
+
* Checks issuer and the time window, with the shared 60 s leeway.
|
|
104
|
+
*
|
|
105
|
+
* The leeway is {@link CLOCK_SKEW_LEEWAY_SECONDS}, not a local number: the IAM
|
|
106
|
+
* and every consuming module must agree, or a token is live in one process and
|
|
107
|
+
* expired in the next at the edges (Doc 03 §6). Note that the issuer is a claim
|
|
108
|
+
* like any other and is worthless until the document is known to be genuine —
|
|
109
|
+
* so this runs after signature verification, never before it.
|
|
110
|
+
*
|
|
111
|
+
* @throws {TokenVerificationError}
|
|
112
|
+
*/
|
|
113
|
+
export declare function assertClaimsAcceptable(claims: JwtClaims, expectedIssuer: string, now?: Date): void;
|
|
114
|
+
//# sourceMappingURL=claims.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.d.ts","sourceRoot":"","sources":["../../src/core/claims.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAE7B,gFAAgF;AAChF,eAAO,MAAM,cAAc;IACzB,wDAAwD;;IAExD,6CAA6C;;IAE7C,qEAAqE;;IAErE,wDAAwD;;IAExD,kDAAkD;;IAElD,uEAAuE;;IAEvE,wCAAwC;;IAExC,4CAA4C;;IAE5C,6DAA6D;;CAErD,CAAC;AACX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAElF;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAE7C,QAAQ,CAAC,MAAM,EAAE,cAAc;gBAAtB,MAAM,EAAE,cAAc,EAC/B,OAAO,EAAE,MAAM;CAKlB;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAW5E;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CA0DjF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,cAAc,EAAE,MAAM,EACtB,GAAG,GAAE,IAAiB,GACrB,IAAI,CAkBN"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The closed claim set, and the checks every verifier must apply identically
|
|
4
|
+
* (Doc 03 §2, §6).
|
|
5
|
+
*
|
|
6
|
+
* ## Why this is not in the IAM
|
|
7
|
+
*
|
|
8
|
+
* The IAM signs; `admin-web`, `iam-client` and every future operational module
|
|
9
|
+
* verify — locally, from the published JWKS, without calling the IAM (Doc 06
|
|
10
|
+
* §11). "Verify the same way" is therefore a property of *separate processes*,
|
|
11
|
+
* and the only way to hold it is for all of them to run this code. A module
|
|
12
|
+
* that reimplements the expiry check with a different leeway rejects tokens the
|
|
13
|
+
* IAM considers live for a whole minute at every token edge; one that skips the
|
|
14
|
+
* issuer check accepts another deployment's tokens outright.
|
|
15
|
+
*
|
|
16
|
+
* ## The claim set is closed, in both directions
|
|
17
|
+
*
|
|
18
|
+
* Doc 03 §2 lists exactly seven claims: `iss, sub, sty, cid, sid, iat, exp`.
|
|
19
|
+
* Not "at least" — permissions, roles and scope nodes are *deliberately* absent
|
|
20
|
+
* so that a grant change takes effect on cache invalidation (Doc 04 §7) rather
|
|
21
|
+
* than on token expiry. So {@link assertExactClaims} refuses to sign an eighth,
|
|
22
|
+
* and {@link readAccessTokenClaims} refuses to accept one: a token this IAM
|
|
23
|
+
* issued cannot carry extras, so their presence means the payload came from
|
|
24
|
+
* somewhere else, and silently reading the seven we recognise is how a second
|
|
25
|
+
* issuer goes unnoticed.
|
|
26
|
+
*
|
|
27
|
+
* ## The one exception, and why it is not a hole
|
|
28
|
+
*
|
|
29
|
+
* `JWT_TOLERATED_CLAIM_KEYS` — today just `aud` — is accepted on the way in and
|
|
30
|
+
* dropped, never required and never read (ADR 0005 §5.1). Signing is unchanged,
|
|
31
|
+
* so this IAM still mints exactly seven claims.
|
|
32
|
+
*
|
|
33
|
+
* The asymmetry is deliberate, and it is about which half can be migrated.
|
|
34
|
+
* Every consumer verifies locally with this published code; the signer is a
|
|
35
|
+
* single deployment. A claim the IAM adds later is therefore free on the
|
|
36
|
+
* signing side and catastrophic on the verifying side — an older verifier
|
|
37
|
+
* rejects **every** token rather than ignoring the field it does not know. So
|
|
38
|
+
* the tolerance has to be in the field *before* the first publish or it is
|
|
39
|
+
* worthless, which is the deadline ADR 0005 §5.1 set against #72.
|
|
40
|
+
*
|
|
41
|
+
* What it costs is the tamper signal, for exactly one registered claim. What it
|
|
42
|
+
* buys is that reconsidering ADR 0005 §3.3 does not require a strictly-ordered
|
|
43
|
+
* upgrade across repositories this one does not control.
|
|
44
|
+
*/
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.TokenVerificationError = exports.TokenRejection = void 0;
|
|
47
|
+
exports.assertExactClaims = assertExactClaims;
|
|
48
|
+
exports.readAccessTokenClaims = readAccessTokenClaims;
|
|
49
|
+
exports.assertClaimsAcceptable = assertClaimsAcceptable;
|
|
50
|
+
const contracts_1 = require("@plantops/contracts");
|
|
51
|
+
/** Why a token was refused. Never returned to a caller — see the note below. */
|
|
52
|
+
exports.TokenRejection = {
|
|
53
|
+
/** Not a compact JWS, or its segments do not decode. */
|
|
54
|
+
MALFORMED: 'malformed',
|
|
55
|
+
/** Header `alg` is not RS256 (Doc 03 §1). */
|
|
56
|
+
UNSUPPORTED_ALGORITHM: 'unsupported_algorithm',
|
|
57
|
+
/** `kid` is not in the published set — refetch JWKS, then reject. */
|
|
58
|
+
UNKNOWN_KEY: 'unknown_key',
|
|
59
|
+
/** Signature does not verify under the selected key. */
|
|
60
|
+
BAD_SIGNATURE: 'bad_signature',
|
|
61
|
+
/** `exp` has passed, allowing the 60 s leeway. */
|
|
62
|
+
EXPIRED: 'expired',
|
|
63
|
+
/** `iat` is in the future beyond the leeway — a clock or a forgery. */
|
|
64
|
+
NOT_YET_VALID: 'not_yet_valid',
|
|
65
|
+
/** `iss` is not the expected issuer. */
|
|
66
|
+
WRONG_ISSUER: 'wrong_issuer',
|
|
67
|
+
/** Claim set is not the Doc 03 §2 shape. */
|
|
68
|
+
BAD_CLAIMS: 'bad_claims',
|
|
69
|
+
/** The session behind `sid` has been revoked (Doc 03 §6). */
|
|
70
|
+
REVOKED: 'revoked',
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* A refused token, with the reason kept **server-side**.
|
|
74
|
+
*
|
|
75
|
+
* The distinction matters at the HTTP boundary: an `unknown_key` and an
|
|
76
|
+
* `expired` must look identical to the caller (both are a bare 401
|
|
77
|
+
* `AUTH_REQUIRED`), because the difference tells an attacker whether a forged
|
|
78
|
+
* `kid` was a near miss. The reason is here for logs, metrics and the
|
|
79
|
+
* refetch-then-reject decision — not for the response body.
|
|
80
|
+
*/
|
|
81
|
+
class TokenVerificationError extends Error {
|
|
82
|
+
constructor(reason, message) {
|
|
83
|
+
super(message);
|
|
84
|
+
this.reason = reason;
|
|
85
|
+
this.name = 'TokenVerificationError';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.TokenVerificationError = TokenVerificationError;
|
|
89
|
+
/**
|
|
90
|
+
* Guards the closed claim set at signing time (Doc 03 §2).
|
|
91
|
+
*
|
|
92
|
+
* Typed input alone does not cover this: a structurally-typed object may carry
|
|
93
|
+
* extra properties at runtime, and `JSON.stringify` would faithfully sign them.
|
|
94
|
+
*
|
|
95
|
+
* Not relaxed by ADR 0005 §5.1: the tolerated claims are accepted on the way
|
|
96
|
+
* **in** only. This IAM mints seven claims, and a token of its own carrying an
|
|
97
|
+
* `aud` would mean something upstream put it there.
|
|
98
|
+
*/
|
|
99
|
+
function assertExactClaims(claims) {
|
|
100
|
+
const allowed = new Set(contracts_1.JWT_CLAIM_KEYS);
|
|
101
|
+
const extra = Object.keys(claims).filter((key) => !allowed.has(key));
|
|
102
|
+
if (extra.length > 0) {
|
|
103
|
+
throw new Error(`Access token would carry claims outside Doc 03 §2: ${extra.join(', ')}. ` +
|
|
104
|
+
'Permissions, roles and scopes are resolved separately and must not be ' +
|
|
105
|
+
'embedded in a token.');
|
|
106
|
+
}
|
|
107
|
+
return { ...claims };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Validates a **signature-verified** payload against the Doc 03 §2 shape.
|
|
111
|
+
*
|
|
112
|
+
* Call this only after the signature checks out. A token signed by a legitimate
|
|
113
|
+
* key is still attacker-influenced if any upstream path ever let a caller
|
|
114
|
+
* choose a claim value, which is why every field is re-validated on the way in
|
|
115
|
+
* and not merely on the way out.
|
|
116
|
+
*
|
|
117
|
+
* @throws {TokenVerificationError}
|
|
118
|
+
*/
|
|
119
|
+
function readAccessTokenClaims(payload) {
|
|
120
|
+
const missing = contracts_1.JWT_CLAIM_KEYS.filter((key) => payload[key] === undefined);
|
|
121
|
+
if (missing.length > 0) {
|
|
122
|
+
throw new TokenVerificationError(exports.TokenRejection.BAD_CLAIMS, `Token is missing required claims: ${missing.join(', ')}`);
|
|
123
|
+
}
|
|
124
|
+
// Accepted, not required, and not returned — see the header. Every other
|
|
125
|
+
// unrecognised claim is still a refusal, so this widens the door by exactly
|
|
126
|
+
// one registered name rather than opening it.
|
|
127
|
+
const allowed = new Set([
|
|
128
|
+
...contracts_1.JWT_CLAIM_KEYS,
|
|
129
|
+
...contracts_1.JWT_TOLERATED_CLAIM_KEYS,
|
|
130
|
+
]);
|
|
131
|
+
const extra = Object.keys(payload).filter((key) => !allowed.has(key));
|
|
132
|
+
if (extra.length > 0) {
|
|
133
|
+
throw new TokenVerificationError(exports.TokenRejection.BAD_CLAIMS, `Token carries claims outside Doc 03 §2: ${extra.join(', ')}`);
|
|
134
|
+
}
|
|
135
|
+
for (const key of ['iss', 'sub', 'cid', 'sid']) {
|
|
136
|
+
if (typeof payload[key] !== 'string' || payload[key] === '') {
|
|
137
|
+
throw new TokenVerificationError(exports.TokenRejection.BAD_CLAIMS, `Token claim "${key}" is not a non-empty string`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const key of ['iat', 'exp']) {
|
|
141
|
+
if (typeof payload[key] !== 'number' || !Number.isInteger(payload[key])) {
|
|
142
|
+
throw new TokenVerificationError(exports.TokenRejection.BAD_CLAIMS, `Token claim "${key}" is not an integer`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (payload['sty'] !== contracts_1.SubjectType.USER && payload['sty'] !== contracts_1.SubjectType.SERVICE) {
|
|
146
|
+
throw new TokenVerificationError(exports.TokenRejection.BAD_CLAIMS, 'Token claim "sty" is not a known subject type');
|
|
147
|
+
}
|
|
148
|
+
// The seven, and only the seven. A tolerated claim is dropped here rather
|
|
149
|
+
// than passed through, so no caller downstream can come to depend on a value
|
|
150
|
+
// this system does not mint and does not validate.
|
|
151
|
+
return {
|
|
152
|
+
iss: payload['iss'],
|
|
153
|
+
sub: payload['sub'],
|
|
154
|
+
sty: payload['sty'],
|
|
155
|
+
cid: payload['cid'],
|
|
156
|
+
sid: payload['sid'],
|
|
157
|
+
iat: payload['iat'],
|
|
158
|
+
exp: payload['exp'],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Checks issuer and the time window, with the shared 60 s leeway.
|
|
163
|
+
*
|
|
164
|
+
* The leeway is {@link CLOCK_SKEW_LEEWAY_SECONDS}, not a local number: the IAM
|
|
165
|
+
* and every consuming module must agree, or a token is live in one process and
|
|
166
|
+
* expired in the next at the edges (Doc 03 §6). Note that the issuer is a claim
|
|
167
|
+
* like any other and is worthless until the document is known to be genuine —
|
|
168
|
+
* so this runs after signature verification, never before it.
|
|
169
|
+
*
|
|
170
|
+
* @throws {TokenVerificationError}
|
|
171
|
+
*/
|
|
172
|
+
function assertClaimsAcceptable(claims, expectedIssuer, now = new Date()) {
|
|
173
|
+
if (claims.iss !== expectedIssuer) {
|
|
174
|
+
throw new TokenVerificationError(exports.TokenRejection.WRONG_ISSUER, 'Token was issued by a different issuer');
|
|
175
|
+
}
|
|
176
|
+
const seconds = Math.floor(now.getTime() / 1000);
|
|
177
|
+
if (claims.exp + contracts_1.CLOCK_SKEW_LEEWAY_SECONDS <= seconds) {
|
|
178
|
+
throw new TokenVerificationError(exports.TokenRejection.EXPIRED, 'Token has expired');
|
|
179
|
+
}
|
|
180
|
+
if (claims.iat - contracts_1.CLOCK_SKEW_LEEWAY_SECONDS > seconds) {
|
|
181
|
+
throw new TokenVerificationError(exports.TokenRejection.NOT_YET_VALID, 'Token was issued in the future');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The framework-free core of `auth-kit` (roadmap Session 50): token
|
|
3
|
+
* verification (JWS primitives + the JWKS verifier), the closed claim set,
|
|
4
|
+
* revocation caching, and the pure scope-coverage vocabulary. Nothing in this
|
|
5
|
+
* directory may import the Nest packages — enforced by
|
|
6
|
+
* `core/core-is-framework-free.spec.ts`.
|
|
7
|
+
*/
|
|
8
|
+
export * from './claims';
|
|
9
|
+
export * from './jws';
|
|
10
|
+
export * from './jwks-verifier';
|
|
11
|
+
export * from './revocation-cache';
|
|
12
|
+
export * from './scope-resolver';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
/**
|
|
5
|
+
* The framework-free core of `auth-kit` (roadmap Session 50): token
|
|
6
|
+
* verification (JWS primitives + the JWKS verifier), the closed claim set,
|
|
7
|
+
* revocation caching, and the pure scope-coverage vocabulary. Nothing in this
|
|
8
|
+
* directory may import the Nest packages — enforced by
|
|
9
|
+
* `core/core-is-framework-free.spec.ts`.
|
|
10
|
+
*/
|
|
11
|
+
tslib_1.__exportStar(require("./claims"), exports);
|
|
12
|
+
tslib_1.__exportStar(require("./jws"), exports);
|
|
13
|
+
tslib_1.__exportStar(require("./jwks-verifier"), exports);
|
|
14
|
+
tslib_1.__exportStar(require("./revocation-cache"), exports);
|
|
15
|
+
tslib_1.__exportStar(require("./scope-resolver"), exports);
|