@proof-holdings/delegation-verifier 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/LICENSE +21 -0
- package/README.md +273 -0
- package/dist/carriers.d.ts +73 -0
- package/dist/carriers.d.ts.map +1 -0
- package/dist/carriers.js +265 -0
- package/dist/carriers.js.map +1 -0
- package/dist/delegate.d.ts +34 -0
- package/dist/delegate.d.ts.map +1 -0
- package/dist/delegate.js +156 -0
- package/dist/delegate.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/jwks.d.ts +47 -0
- package/dist/jwks.d.ts.map +1 -0
- package/dist/jwks.js +185 -0
- package/dist/jwks.js.map +1 -0
- package/dist/jwt.d.ts +20 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +63 -0
- package/dist/jwt.js.map +1 -0
- package/dist/publication.d.ts +19 -0
- package/dist/publication.d.ts.map +1 -0
- package/dist/publication.js +39 -0
- package/dist/publication.js.map +1 -0
- package/dist/status.d.ts +86 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +321 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +11 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +240 -0
- package/dist/verify.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalization of typed delegate identifiers.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ SECOND LOCKSTEP COPY. The authority is the issuer-side implementation
|
|
5
|
+
* (`src/utils/delegateIdentifier.ts` in the proof.holdings backend), which produces the string
|
|
6
|
+
* that gets SIGNED into a token's `delegate` claim. This file must produce the same string for
|
|
7
|
+
* the same input, byte for byte, or a verifier reads a valid delegation as a mismatch. The
|
|
8
|
+
* equivalence is pinned by the drift suite `src/__tests__/drift/delegation-verifier.test.ts`
|
|
9
|
+
* in that repository, which runs one shared vector through both implementations.
|
|
10
|
+
*
|
|
11
|
+
* It is copied rather than imported for the reason the package exists at all: a gateway
|
|
12
|
+
* verifying someone's delegation must not have to install the issuer's backend.
|
|
13
|
+
*/
|
|
14
|
+
import type { DelegateInput, DelegateType } from './types.js';
|
|
15
|
+
export declare const DELEGATE_TYPES: readonly DelegateType[];
|
|
16
|
+
/** Cap on the CANONICAL delegate string. */
|
|
17
|
+
export declare const DELEGATE_MAX_LENGTH = 512;
|
|
18
|
+
export declare const DELEGATION_SCOPE_MAX_ENTRIES = 32;
|
|
19
|
+
export declare const DELEGATION_SCOPE_TOKEN_MAX_LENGTH = 64;
|
|
20
|
+
export declare class DelegateIdentifierError extends Error {
|
|
21
|
+
constructor(message: string);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validate and canonicalize a typed delegate. Throws `DelegateIdentifierError` on rejection —
|
|
25
|
+
* the caller turns that into an `invalid_options` failure, because an artifact identity that
|
|
26
|
+
* cannot be canonicalized is a bug in the CALL, not evidence about the token.
|
|
27
|
+
*/
|
|
28
|
+
export declare function canonicalizeDelegate(input: DelegateInput): string;
|
|
29
|
+
/**
|
|
30
|
+
* Validate and canonicalize a scope list: every entry a lowercase kebab-case token,
|
|
31
|
+
* deduplicated, sorted. Throws `DelegateIdentifierError` on any rejection.
|
|
32
|
+
*/
|
|
33
|
+
export declare function canonicalizeScopes(scopes: string[]): string[];
|
|
34
|
+
//# sourceMappingURL=delegate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegate.d.ts","sourceRoot":"","sources":["../src/delegate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE9D,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAA6B,CAAC;AAEhF,4CAA4C;AAC5C,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAC/C,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAQpD,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAIjE;AAqGD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAyB7D"}
|
package/dist/delegate.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalization of typed delegate identifiers.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ SECOND LOCKSTEP COPY. The authority is the issuer-side implementation
|
|
5
|
+
* (`src/utils/delegateIdentifier.ts` in the proof.holdings backend), which produces the string
|
|
6
|
+
* that gets SIGNED into a token's `delegate` claim. This file must produce the same string for
|
|
7
|
+
* the same input, byte for byte, or a verifier reads a valid delegation as a mismatch. The
|
|
8
|
+
* equivalence is pinned by the drift suite `src/__tests__/drift/delegation-verifier.test.ts`
|
|
9
|
+
* in that repository, which runs one shared vector through both implementations.
|
|
10
|
+
*
|
|
11
|
+
* It is copied rather than imported for the reason the package exists at all: a gateway
|
|
12
|
+
* verifying someone's delegation must not have to install the issuer's backend.
|
|
13
|
+
*/
|
|
14
|
+
export const DELEGATE_TYPES = ['url', 'purl'];
|
|
15
|
+
/** Cap on the CANONICAL delegate string. */
|
|
16
|
+
export const DELEGATE_MAX_LENGTH = 512;
|
|
17
|
+
export const DELEGATION_SCOPE_MAX_ENTRIES = 32;
|
|
18
|
+
export const DELEGATION_SCOPE_TOKEN_MAX_LENGTH = 64;
|
|
19
|
+
/** Lowercase kebab-case: `send-email`, `read-profile-v2`. */
|
|
20
|
+
const SCOPE_TOKEN_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
21
|
+
/** `pkg:type/[namespace/]name` — structure only; per-type case rules applied after. */
|
|
22
|
+
const PURL_RE = /^pkg:([a-z0-9.+-]+)\/(.+)$/i;
|
|
23
|
+
export class DelegateIdentifierError extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'DelegateIdentifierError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validate and canonicalize a typed delegate. Throws `DelegateIdentifierError` on rejection —
|
|
31
|
+
* the caller turns that into an `invalid_options` failure, because an artifact identity that
|
|
32
|
+
* cannot be canonicalized is a bug in the CALL, not evidence about the token.
|
|
33
|
+
*/
|
|
34
|
+
export function canonicalizeDelegate(input) {
|
|
35
|
+
if (input.type === 'purl')
|
|
36
|
+
return canonicalizePurl(input.value);
|
|
37
|
+
if (input.type === 'url')
|
|
38
|
+
return canonicalizeUrl(input.value);
|
|
39
|
+
throw new DelegateIdentifierError(`unknown delegate type: ${String(input.type)}`);
|
|
40
|
+
}
|
|
41
|
+
function canonicalizePurl(value) {
|
|
42
|
+
const raw = value.trim();
|
|
43
|
+
if (!raw)
|
|
44
|
+
throw new DelegateIdentifierError('delegate must not be empty');
|
|
45
|
+
if (/\s/.test(raw))
|
|
46
|
+
throw new DelegateIdentifierError('delegate must not contain whitespace');
|
|
47
|
+
if (raw.includes('?')) {
|
|
48
|
+
throw new DelegateIdentifierError('purl qualifiers are not allowed in a delegate');
|
|
49
|
+
}
|
|
50
|
+
if (raw.includes('#')) {
|
|
51
|
+
throw new DelegateIdentifierError('purl subpath is not allowed in a delegate');
|
|
52
|
+
}
|
|
53
|
+
const match = raw.match(PURL_RE);
|
|
54
|
+
if (!match) {
|
|
55
|
+
throw new DelegateIdentifierError('delegate must be a purl of the form pkg:type/name');
|
|
56
|
+
}
|
|
57
|
+
const type = match[1].toLowerCase();
|
|
58
|
+
let rest = match[2];
|
|
59
|
+
// `@` in an npm scope (`pkg:npm/@acme/tool`) is legitimate; a trailing `@version` is not.
|
|
60
|
+
if (findVersionSeparator(rest) !== -1) {
|
|
61
|
+
throw new DelegateIdentifierError('purl version is not allowed in a delegate — delegate the artifact, not a version');
|
|
62
|
+
}
|
|
63
|
+
const segments = rest.split('/');
|
|
64
|
+
if (segments.some((segment) => segment.length === 0)) {
|
|
65
|
+
throw new DelegateIdentifierError('purl namespace and name must not be empty');
|
|
66
|
+
}
|
|
67
|
+
// Per-type case rules (purl-spec: type is case-insensitive and canonically lowercase; name
|
|
68
|
+
// rules are per-type). npm and pypi are lowercase-only registries; pypi additionally treats
|
|
69
|
+
// `_` and `-` as equivalent, canonically `-` (PEP 503).
|
|
70
|
+
if (type === 'npm') {
|
|
71
|
+
rest = segments.map((segment) => segment.toLowerCase()).join('/');
|
|
72
|
+
}
|
|
73
|
+
else if (type === 'pypi') {
|
|
74
|
+
rest = segments.map((segment) => segment.toLowerCase().replace(/_/g, '-')).join('/');
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
rest = segments.join('/');
|
|
78
|
+
}
|
|
79
|
+
return enforceLength(`pkg:${type}/${rest}`);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Index of an `@version` separator, or -1. A leading `@` on a path segment is a scope/user
|
|
83
|
+
* marker (`@acme` in `pkg:npm/@acme/tool`), not a version.
|
|
84
|
+
*/
|
|
85
|
+
function findVersionSeparator(rest) {
|
|
86
|
+
for (let i = 0; i < rest.length; i++) {
|
|
87
|
+
if (rest[i] !== '@')
|
|
88
|
+
continue;
|
|
89
|
+
if (i === 0 || rest[i - 1] === '/')
|
|
90
|
+
continue; // scope marker
|
|
91
|
+
return i;
|
|
92
|
+
}
|
|
93
|
+
return -1;
|
|
94
|
+
}
|
|
95
|
+
function canonicalizeUrl(value) {
|
|
96
|
+
const raw = value.trim();
|
|
97
|
+
if (!raw)
|
|
98
|
+
throw new DelegateIdentifierError('delegate must not be empty');
|
|
99
|
+
let parsed;
|
|
100
|
+
try {
|
|
101
|
+
parsed = new URL(raw);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
throw new DelegateIdentifierError('delegate must be a valid absolute URL');
|
|
105
|
+
}
|
|
106
|
+
if (parsed.protocol !== 'https:') {
|
|
107
|
+
throw new DelegateIdentifierError('delegate URL must use https');
|
|
108
|
+
}
|
|
109
|
+
if (parsed.username || parsed.password) {
|
|
110
|
+
throw new DelegateIdentifierError('delegate URL must not carry credentials');
|
|
111
|
+
}
|
|
112
|
+
if (parsed.search) {
|
|
113
|
+
throw new DelegateIdentifierError('delegate URL must not carry a query string');
|
|
114
|
+
}
|
|
115
|
+
if (raw.includes('#')) {
|
|
116
|
+
throw new DelegateIdentifierError('delegate URL must not carry a fragment');
|
|
117
|
+
}
|
|
118
|
+
// URL() already lowercases the host and strips an explicit default port; pathname keeps its
|
|
119
|
+
// case. Trailing slash is canonicalized away except at the root, where it is always '/'.
|
|
120
|
+
let pathname = parsed.pathname;
|
|
121
|
+
if (pathname.length > 1 && pathname.endsWith('/')) {
|
|
122
|
+
pathname = pathname.replace(/\/+$/, '');
|
|
123
|
+
}
|
|
124
|
+
return enforceLength(`https://${parsed.host}${pathname}`);
|
|
125
|
+
}
|
|
126
|
+
function enforceLength(canonical) {
|
|
127
|
+
if (canonical.length > DELEGATE_MAX_LENGTH) {
|
|
128
|
+
throw new DelegateIdentifierError(`delegate is too long (max ${DELEGATE_MAX_LENGTH} characters)`);
|
|
129
|
+
}
|
|
130
|
+
return canonical;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Validate and canonicalize a scope list: every entry a lowercase kebab-case token,
|
|
134
|
+
* deduplicated, sorted. Throws `DelegateIdentifierError` on any rejection.
|
|
135
|
+
*/
|
|
136
|
+
export function canonicalizeScopes(scopes) {
|
|
137
|
+
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
138
|
+
throw new DelegateIdentifierError('scope must contain at least one entry');
|
|
139
|
+
}
|
|
140
|
+
if (scopes.length > DELEGATION_SCOPE_MAX_ENTRIES) {
|
|
141
|
+
throw new DelegateIdentifierError(`too many scope entries (max ${DELEGATION_SCOPE_MAX_ENTRIES})`);
|
|
142
|
+
}
|
|
143
|
+
for (const token of scopes) {
|
|
144
|
+
if (typeof token !== 'string' || token.length === 0) {
|
|
145
|
+
throw new DelegateIdentifierError('scope entries must be non-empty strings');
|
|
146
|
+
}
|
|
147
|
+
if (token.length > DELEGATION_SCOPE_TOKEN_MAX_LENGTH) {
|
|
148
|
+
throw new DelegateIdentifierError(`scope entry is too long (max ${DELEGATION_SCOPE_TOKEN_MAX_LENGTH} characters)`);
|
|
149
|
+
}
|
|
150
|
+
if (!SCOPE_TOKEN_RE.test(token)) {
|
|
151
|
+
throw new DelegateIdentifierError(`invalid scope entry "${token}" — lowercase kebab-case tokens only (e.g. send-email)`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return [...new Set(scopes)].sort();
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=delegate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegate.js","sourceRoot":"","sources":["../src/delegate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @proof-holdings/delegation-verifier — issuer-agnostic verification of Proof of Delegation.
|
|
3
|
+
*
|
|
4
|
+
* A delegation says: the domain in `principal` authorized the artifact in `delegate` for these
|
|
5
|
+
* capability scopes, and the issuer checked that domain's control at issuance. It says nothing
|
|
6
|
+
* about whether the artifact is safe, audited, or any good.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const result = await verifyPublishedDelegation(serverCard, {
|
|
10
|
+
* trustedIssuers: [{
|
|
11
|
+
* issuer: 'proof.holdings',
|
|
12
|
+
* jwksUri: 'https://api.proof.holdings/.well-known/jwks.json',
|
|
13
|
+
* statusEndpoint: 'https://api.proof.holdings/api/v1/proofs/validate',
|
|
14
|
+
* }],
|
|
15
|
+
* delegate: { type: 'purl', value: 'pkg:npm/postmark-mcp' }, // what YOU are installing
|
|
16
|
+
* expectedPrincipal: 'postmarkapp.com',
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export { verifyDelegation } from './verify.js';
|
|
21
|
+
export { verifyPublishedDelegation, extractDelegationPublications, DELEGATION_META_KEY, DELEGATION_EXTENSION_URI, REGISTRY_PUBLISHER_PROVIDED_KEY, } from './publication.js';
|
|
22
|
+
export type { FoundPublication } from './publication.js';
|
|
23
|
+
/**
|
|
24
|
+
* The fourth carrier: a `_mcp.<domain>` TXT record naming the card that holds the publication.
|
|
25
|
+
* Only the PARSE half is here — resolving it means a DNS query and a card fetch, and this package
|
|
26
|
+
* is dependency-free on purpose. A consumer does the lookup with whatever resolver it already has
|
|
27
|
+
* and hands the TXT values in.
|
|
28
|
+
*/
|
|
29
|
+
export { parseDelegationPointer, pointerRecordName, POINTER_VERSION, POINTER_RECORD_PREFIX, } from './publication.js';
|
|
30
|
+
export type { PointerParseResult, PointerRejection } from './publication.js';
|
|
31
|
+
export { canonicalizeDelegate, canonicalizeScopes, DelegateIdentifierError, DELEGATE_TYPES, DELEGATE_MAX_LENGTH, } from './delegate.js';
|
|
32
|
+
export { resetJwksCache, SUPPORTED_ALGORITHMS } from './jwks.js';
|
|
33
|
+
export type { SupportedAlgorithm } from './jwks.js';
|
|
34
|
+
export { readStatusSlot } from './status.js';
|
|
35
|
+
export { classifyFailureReason } from './types.js';
|
|
36
|
+
export type { DelegateInput, DelegateType, FailureOutcome, FailureReason, FetchLike, TrustedIssuer, VerificationFailure, VerificationResult, VerificationSuccess, VerifiedDelegation, VerifyOptions, } from './types.js';
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,mBAAmB,EACnB,wBAAwB,EACxB,+BAA+B,GAChC,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD;;;;;GAKG;AACH,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,cAAc,EACd,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjE,YAAY,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,aAAa,EACb,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @proof-holdings/delegation-verifier — issuer-agnostic verification of Proof of Delegation.
|
|
3
|
+
*
|
|
4
|
+
* A delegation says: the domain in `principal` authorized the artifact in `delegate` for these
|
|
5
|
+
* capability scopes, and the issuer checked that domain's control at issuance. It says nothing
|
|
6
|
+
* about whether the artifact is safe, audited, or any good.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const result = await verifyPublishedDelegation(serverCard, {
|
|
10
|
+
* trustedIssuers: [{
|
|
11
|
+
* issuer: 'proof.holdings',
|
|
12
|
+
* jwksUri: 'https://api.proof.holdings/.well-known/jwks.json',
|
|
13
|
+
* statusEndpoint: 'https://api.proof.holdings/api/v1/proofs/validate',
|
|
14
|
+
* }],
|
|
15
|
+
* delegate: { type: 'purl', value: 'pkg:npm/postmark-mcp' }, // what YOU are installing
|
|
16
|
+
* expectedPrincipal: 'postmarkapp.com',
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export { verifyDelegation } from './verify.js';
|
|
21
|
+
export { verifyPublishedDelegation, extractDelegationPublications, DELEGATION_META_KEY, DELEGATION_EXTENSION_URI, REGISTRY_PUBLISHER_PROVIDED_KEY, } from './publication.js';
|
|
22
|
+
/**
|
|
23
|
+
* The fourth carrier: a `_mcp.<domain>` TXT record naming the card that holds the publication.
|
|
24
|
+
* Only the PARSE half is here — resolving it means a DNS query and a card fetch, and this package
|
|
25
|
+
* is dependency-free on purpose. A consumer does the lookup with whatever resolver it already has
|
|
26
|
+
* and hands the TXT values in.
|
|
27
|
+
*/
|
|
28
|
+
export { parseDelegationPointer, pointerRecordName, POINTER_VERSION, POINTER_RECORD_PREFIX, } from './publication.js';
|
|
29
|
+
export { canonicalizeDelegate, canonicalizeScopes, DelegateIdentifierError, DELEGATE_TYPES, DELEGATE_MAX_LENGTH, } from './delegate.js';
|
|
30
|
+
export { resetJwksCache, SUPPORTED_ALGORITHMS } from './jwks.js';
|
|
31
|
+
export { readStatusSlot } from './status.js';
|
|
32
|
+
export { classifyFailureReason } from './types.js';
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,mBAAmB,EACnB,wBAAwB,EACxB,+BAA+B,GAChC,MAAM,kBAAkB,CAAC;AAE1B;;;;;GAKG;AACH,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,cAAc,EACd,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/jwks.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWKS retrieval and key cache.
|
|
3
|
+
*
|
|
4
|
+
* Three behaviours here are load-bearing rather than tidy, all learned from the hand-rolled Go
|
|
5
|
+
* verifier that shipped before this package:
|
|
6
|
+
*
|
|
7
|
+
* - The algorithm is chosen from THIS list, never from the token's own header. A token that
|
|
8
|
+
* asks for `none` or for an HMAC is refused before any key is looked up.
|
|
9
|
+
* - An unknown `kid` is indistinguishable from a rotated key, so a miss triggers at most one
|
|
10
|
+
* refetch per cooldown window. Without it every junk-kid token costs one outbound request
|
|
11
|
+
* and a busy verifier trips the issuer's rate limit — failing its GOOD tokens too.
|
|
12
|
+
* - Concurrent misses share ONE in-flight fetch per key set, with the cache re-checked after
|
|
13
|
+
* it resolves. Serialising is what makes the cooldown hold under concurrency.
|
|
14
|
+
*/
|
|
15
|
+
import type { FetchLike } from './types.js';
|
|
16
|
+
export declare const SUPPORTED_ALGORITHMS: readonly ["RS256", "ES256"];
|
|
17
|
+
export type SupportedAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number];
|
|
18
|
+
/** How long a `kid` miss suppresses further refetches of the same key set. */
|
|
19
|
+
export declare const KEY_MISS_COOLDOWN_MS = 30000;
|
|
20
|
+
/** How long a successfully fetched key set is reused without refetching. */
|
|
21
|
+
export declare const KEY_SET_TTL_MS: number;
|
|
22
|
+
/**
|
|
23
|
+
* A key set is kilobytes. Anything larger is not a JWKS, and the request is driven by input the
|
|
24
|
+
* verifier does not control, so it is capped rather than streamed to completion.
|
|
25
|
+
*/
|
|
26
|
+
export declare const MAX_REMOTE_BODY_BYTES: number;
|
|
27
|
+
/** Test seam — drops every cached key set. */
|
|
28
|
+
export declare function resetJwksCache(): void;
|
|
29
|
+
export declare class JwksUnavailableError extends Error {
|
|
30
|
+
constructor(message: string);
|
|
31
|
+
}
|
|
32
|
+
export declare function isSupportedAlgorithm(alg: unknown): alg is SupportedAlgorithm;
|
|
33
|
+
/**
|
|
34
|
+
* Candidate verification keys for `kid`, fetching the key set at most once per cooldown.
|
|
35
|
+
* Returns an empty array when the key is genuinely unknown; throws `JwksUnavailableError`
|
|
36
|
+
* when the key set could not be read at all ("I could not check" is not "the token is bad").
|
|
37
|
+
*/
|
|
38
|
+
export declare function getVerificationKeys(params: {
|
|
39
|
+
jwksUri: string;
|
|
40
|
+
kid?: string;
|
|
41
|
+
alg: SupportedAlgorithm;
|
|
42
|
+
fetch: FetchLike;
|
|
43
|
+
timeoutMs: number;
|
|
44
|
+
}): Promise<CryptoKey[]>;
|
|
45
|
+
/** Verify a JWS signature against one candidate key. */
|
|
46
|
+
export declare function verifySignature(key: CryptoKey, alg: SupportedAlgorithm, signingInput: string, signature: Uint8Array): Promise<boolean>;
|
|
47
|
+
//# sourceMappingURL=jwks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../src/jwks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,eAAO,MAAM,oBAAoB,6BAA8B,CAAC;AAChE,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAC3C,4EAA4E;AAC5E,eAAO,MAAM,cAAc,QAAc,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAa,CAAC;AAchD,8CAA8C;AAC9C,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAkBD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,kBAAkB,CAE5E;AA8ED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,kBAAkB,CAAC;IACxB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAmDvB;AAED,wDAAwD;AACxD,wBAAsB,eAAe,CACnC,GAAG,EAAE,SAAS,EACd,GAAG,EAAE,kBAAkB,EACvB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC,OAAO,CAAC,CAYlB"}
|
package/dist/jwks.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWKS retrieval and key cache.
|
|
3
|
+
*
|
|
4
|
+
* Three behaviours here are load-bearing rather than tidy, all learned from the hand-rolled Go
|
|
5
|
+
* verifier that shipped before this package:
|
|
6
|
+
*
|
|
7
|
+
* - The algorithm is chosen from THIS list, never from the token's own header. A token that
|
|
8
|
+
* asks for `none` or for an HMAC is refused before any key is looked up.
|
|
9
|
+
* - An unknown `kid` is indistinguishable from a rotated key, so a miss triggers at most one
|
|
10
|
+
* refetch per cooldown window. Without it every junk-kid token costs one outbound request
|
|
11
|
+
* and a busy verifier trips the issuer's rate limit — failing its GOOD tokens too.
|
|
12
|
+
* - Concurrent misses share ONE in-flight fetch per key set, with the cache re-checked after
|
|
13
|
+
* it resolves. Serialising is what makes the cooldown hold under concurrency.
|
|
14
|
+
*/
|
|
15
|
+
import { isRecord } from './jwt.js';
|
|
16
|
+
export const SUPPORTED_ALGORITHMS = ['RS256', 'ES256'];
|
|
17
|
+
/** How long a `kid` miss suppresses further refetches of the same key set. */
|
|
18
|
+
export const KEY_MISS_COOLDOWN_MS = 30_000;
|
|
19
|
+
/** How long a successfully fetched key set is reused without refetching. */
|
|
20
|
+
export const KEY_SET_TTL_MS = 10 * 60_000;
|
|
21
|
+
/**
|
|
22
|
+
* A key set is kilobytes. Anything larger is not a JWKS, and the request is driven by input the
|
|
23
|
+
* verifier does not control, so it is capped rather than streamed to completion.
|
|
24
|
+
*/
|
|
25
|
+
export const MAX_REMOTE_BODY_BYTES = 512 * 1024;
|
|
26
|
+
const cache = new Map();
|
|
27
|
+
/** Test seam — drops every cached key set. */
|
|
28
|
+
export function resetJwksCache() {
|
|
29
|
+
cache.clear();
|
|
30
|
+
}
|
|
31
|
+
export class JwksUnavailableError extends Error {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = 'JwksUnavailableError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function importParams(alg) {
|
|
38
|
+
if (alg === 'ES256') {
|
|
39
|
+
return {
|
|
40
|
+
algorithm: { name: 'ECDSA', namedCurve: 'P-256' },
|
|
41
|
+
verify: { name: 'ECDSA', hash: 'SHA-256' },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
algorithm: { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
|
|
46
|
+
verify: { name: 'RSASSA-PKCS1-v1_5' },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function isSupportedAlgorithm(alg) {
|
|
50
|
+
return typeof alg === 'string' && SUPPORTED_ALGORITHMS.includes(alg);
|
|
51
|
+
}
|
|
52
|
+
async function importJwk(jwk, alg) {
|
|
53
|
+
try {
|
|
54
|
+
const { algorithm } = importParams(alg);
|
|
55
|
+
return await crypto.subtle.importKey('jwk', jwk, algorithm, false, ['verify']);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function fetchKeySet(entry, jwksUri, alg, fetchImpl, timeoutMs) {
|
|
62
|
+
const response = await fetchImpl(jwksUri, {
|
|
63
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
64
|
+
headers: { accept: 'application/json' },
|
|
65
|
+
// A redirect would let any open redirect on the issuer's origin move this fetch off it.
|
|
66
|
+
redirect: 'error',
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new JwksUnavailableError(`JWKS fetch failed with HTTP ${response.status}`);
|
|
70
|
+
}
|
|
71
|
+
const declaredLength = Number(response.headers.get('content-length'));
|
|
72
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_REMOTE_BODY_BYTES) {
|
|
73
|
+
throw new JwksUnavailableError('JWKS response is too large');
|
|
74
|
+
}
|
|
75
|
+
const raw = await response.text();
|
|
76
|
+
if (raw.length > MAX_REMOTE_BODY_BYTES) {
|
|
77
|
+
throw new JwksUnavailableError('JWKS response is too large');
|
|
78
|
+
}
|
|
79
|
+
let body;
|
|
80
|
+
try {
|
|
81
|
+
body = JSON.parse(raw);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
throw new JwksUnavailableError('JWKS response is not JSON');
|
|
85
|
+
}
|
|
86
|
+
if (!isRecord(body) || !Array.isArray(body.keys)) {
|
|
87
|
+
throw new JwksUnavailableError('JWKS response has no `keys` array');
|
|
88
|
+
}
|
|
89
|
+
const keys = new Map();
|
|
90
|
+
const anonymousKeys = [];
|
|
91
|
+
for (const candidate of body.keys) {
|
|
92
|
+
if (!isRecord(candidate))
|
|
93
|
+
continue;
|
|
94
|
+
// A key set may legitimately mix algorithms; only the ones usable for THIS verification
|
|
95
|
+
// are imported, and an unusable entry is skipped rather than failing the whole set.
|
|
96
|
+
if (typeof candidate.alg === 'string' && candidate.alg !== alg)
|
|
97
|
+
continue;
|
|
98
|
+
const key = await importJwk(candidate, alg);
|
|
99
|
+
if (!key)
|
|
100
|
+
continue;
|
|
101
|
+
if (typeof candidate.kid === 'string' && candidate.kid.length > 0) {
|
|
102
|
+
keys.set(candidate.kid, key);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
anonymousKeys.push(key);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
entry.keys = keys;
|
|
109
|
+
entry.anonymousKeys = anonymousKeys;
|
|
110
|
+
entry.fetchedAt = Date.now();
|
|
111
|
+
}
|
|
112
|
+
function lookup(entry, kid) {
|
|
113
|
+
if (kid) {
|
|
114
|
+
const key = entry.keys.get(kid);
|
|
115
|
+
return key ? [key] : [];
|
|
116
|
+
}
|
|
117
|
+
// No `kid` in the header: every key in the set is a candidate.
|
|
118
|
+
return [...entry.anonymousKeys, ...entry.keys.values()];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Candidate verification keys for `kid`, fetching the key set at most once per cooldown.
|
|
122
|
+
* Returns an empty array when the key is genuinely unknown; throws `JwksUnavailableError`
|
|
123
|
+
* when the key set could not be read at all ("I could not check" is not "the token is bad").
|
|
124
|
+
*/
|
|
125
|
+
export async function getVerificationKeys(params) {
|
|
126
|
+
const cacheKey = `${params.jwksUri}|${params.alg}`;
|
|
127
|
+
let entry = cache.get(cacheKey);
|
|
128
|
+
if (!entry) {
|
|
129
|
+
entry = {
|
|
130
|
+
keys: new Map(),
|
|
131
|
+
anonymousKeys: [],
|
|
132
|
+
fetchedAt: 0,
|
|
133
|
+
lastMissAt: 0,
|
|
134
|
+
inFlight: null,
|
|
135
|
+
};
|
|
136
|
+
cache.set(cacheKey, entry);
|
|
137
|
+
}
|
|
138
|
+
const fresh = entry.fetchedAt > 0 && Date.now() - entry.fetchedAt < KEY_SET_TTL_MS;
|
|
139
|
+
const hit = lookup(entry, params.kid);
|
|
140
|
+
if (fresh && hit.length > 0)
|
|
141
|
+
return hit;
|
|
142
|
+
if (fresh && Date.now() - entry.lastMissAt < KEY_MISS_COOLDOWN_MS) {
|
|
143
|
+
return []; // Cooldown: a miss we already paid a request for.
|
|
144
|
+
}
|
|
145
|
+
const current = entry;
|
|
146
|
+
if (!current.inFlight) {
|
|
147
|
+
const running = fetchKeySet(current, params.jwksUri, params.alg, params.fetch, params.timeoutMs);
|
|
148
|
+
// The shared promise RESOLVES to the outcome rather than recording it on the entry: a
|
|
149
|
+
// waiter reading shared state after its await could be handed the state of the NEXT
|
|
150
|
+
// fetch. Carried, not stored — a waiter that only saw an empty key set would report
|
|
151
|
+
// `unknown_key` ("this token names a key that does not exist") for what is actually
|
|
152
|
+
// "I could not reach the issuer": a false forgery signal during an outage.
|
|
153
|
+
current.inFlight = running.then(() => null, (error) => error instanceof JwksUnavailableError
|
|
154
|
+
? error
|
|
155
|
+
: new JwksUnavailableError(error instanceof Error ? error.message : 'JWKS fetch failed'));
|
|
156
|
+
const settled = current.inFlight;
|
|
157
|
+
settled.finally(() => {
|
|
158
|
+
if (current.inFlight === settled)
|
|
159
|
+
current.inFlight = null;
|
|
160
|
+
});
|
|
161
|
+
const failure = await settled;
|
|
162
|
+
if (failure)
|
|
163
|
+
throw failure;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const failure = await current.inFlight;
|
|
167
|
+
if (failure)
|
|
168
|
+
throw failure;
|
|
169
|
+
}
|
|
170
|
+
const after = lookup(current, params.kid);
|
|
171
|
+
if (after.length === 0)
|
|
172
|
+
current.lastMissAt = Date.now();
|
|
173
|
+
return after;
|
|
174
|
+
}
|
|
175
|
+
/** Verify a JWS signature against one candidate key. */
|
|
176
|
+
export async function verifySignature(key, alg, signingInput, signature) {
|
|
177
|
+
const { verify } = importParams(alg);
|
|
178
|
+
try {
|
|
179
|
+
return await crypto.subtle.verify(verify, key, signature, new TextEncoder().encode(signingInput));
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=jwks.js.map
|
package/dist/jwks.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks.js","sourceRoot":"","sources":["../src/jwks.ts"],"names":[],"mappings":"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"}
|
package/dist/jwt.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal JWT surface: base64url decoding and an UNVERIFIED structural parse.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately dependency-free and deliberately small — everything here runs BEFORE any
|
|
5
|
+
* signature has been checked, so nothing it produces may be treated as a fact. The one thing
|
|
6
|
+
* the caller may read from an unverified token is `iss`, and only to select which trusted
|
|
7
|
+
* issuer's key set to check the signature against.
|
|
8
|
+
*/
|
|
9
|
+
export interface ParsedJwt {
|
|
10
|
+
header: Record<string, unknown>;
|
|
11
|
+
payload: Record<string, unknown>;
|
|
12
|
+
/** `<header>.<payload>` — the bytes the signature is over. */
|
|
13
|
+
signingInput: string;
|
|
14
|
+
signature: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
export declare function base64UrlToBytes(value: string): Uint8Array;
|
|
17
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
18
|
+
/** Structural parse. Null on anything that is not a three-part JWT with JSON object segments. */
|
|
19
|
+
export declare function parseJwt(token: string): ParsedJwt | null;
|
|
20
|
+
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAO1D;AAYD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,iGAAiG;AACjG,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA4BxD"}
|
package/dist/jwt.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal JWT surface: base64url decoding and an UNVERIFIED structural parse.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately dependency-free and deliberately small — everything here runs BEFORE any
|
|
5
|
+
* signature has been checked, so nothing it produces may be treated as a fact. The one thing
|
|
6
|
+
* the caller may read from an unverified token is `iss`, and only to select which trusted
|
|
7
|
+
* issuer's key set to check the signature against.
|
|
8
|
+
*/
|
|
9
|
+
export function base64UrlToBytes(value) {
|
|
10
|
+
const normalized = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
11
|
+
const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=');
|
|
12
|
+
const binary = atob(padded);
|
|
13
|
+
const bytes = new Uint8Array(binary.length);
|
|
14
|
+
for (let i = 0; i < binary.length; i++)
|
|
15
|
+
bytes[i] = binary.charCodeAt(i);
|
|
16
|
+
return bytes;
|
|
17
|
+
}
|
|
18
|
+
function decodeJsonSegment(segment) {
|
|
19
|
+
try {
|
|
20
|
+
const text = new TextDecoder().decode(base64UrlToBytes(segment));
|
|
21
|
+
const parsed = JSON.parse(text);
|
|
22
|
+
return isRecord(parsed) ? parsed : null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function isRecord(value) {
|
|
29
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
/** Structural parse. Null on anything that is not a three-part JWT with JSON object segments. */
|
|
32
|
+
export function parseJwt(token) {
|
|
33
|
+
if (typeof token !== 'string')
|
|
34
|
+
return null;
|
|
35
|
+
const parts = token.split('.');
|
|
36
|
+
if (parts.length !== 3)
|
|
37
|
+
return null;
|
|
38
|
+
const [headerSegment, payloadSegment, signatureSegment] = parts;
|
|
39
|
+
// An EMPTY signature segment is structurally legal (that is what an `alg: none` token looks
|
|
40
|
+
// like) and is deliberately parsed rather than rejected here: refusing it as "malformed"
|
|
41
|
+
// would hide the far more useful answer the algorithm gate gives one step later — that the
|
|
42
|
+
// verifier picks the algorithm, and this token asked for one that is not accepted.
|
|
43
|
+
if (!headerSegment || !payloadSegment)
|
|
44
|
+
return null;
|
|
45
|
+
const header = decodeJsonSegment(headerSegment);
|
|
46
|
+
const payload = decodeJsonSegment(payloadSegment);
|
|
47
|
+
if (!header || !payload)
|
|
48
|
+
return null;
|
|
49
|
+
let signature;
|
|
50
|
+
try {
|
|
51
|
+
signature = base64UrlToBytes(signatureSegment);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
header,
|
|
58
|
+
payload,
|
|
59
|
+
signingInput: `${headerSegment}.${payloadSegment}`,
|
|
60
|
+
signature,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=jwt.js.map
|
package/dist/jwt.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/F,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACxE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAe;IACxC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;QACjE,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,CAAC,aAAa,EAAE,cAAc,EAAE,gBAAgB,CAAC,GAAG,KAAK,CAAC;IAChE,4FAA4F;IAC5F,yFAAyF;IACzF,2FAA2F;IAC3F,mFAAmF;IACnF,IAAI,CAAC,aAAa,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAEnD,MAAM,MAAM,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,iBAAiB,CAAC,cAAc,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAErC,IAAI,SAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,SAAS,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,MAAM;QACN,OAAO;QACP,YAAY,EAAE,GAAG,aAAa,IAAI,cAAc,EAAE;QAClD,SAAS;KACV,CAAC;AACJ,CAAC"}
|