@hivemagnus/hive-common 0.2.0 → 0.3.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/CHANGELOG.md +39 -1
- package/README.md +59 -4
- package/dist/oathweave/claims.d.ts +10 -2
- package/dist/oathweave/claims.js +6 -3
- package/dist/oathweave/conformance.d.ts +7 -0
- package/dist/oathweave/conformance.js +2 -0
- package/dist/oathweave/index.d.ts +2 -2
- package/dist/oathweave/index.js +1 -1
- package/dist/oathweave/verifier.d.ts +28 -3
- package/dist/oathweave/verifier.js +45 -6
- package/package.json +1 -1
- package/test-vectors/README.md +7 -3
- package/test-vectors/oathweave/jwks.json +1 -1
- package/test-vectors/oathweave/vectors.json +56 -12
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,43 @@ All notable changes to `@hivemagnus/hive-common` are recorded here. The format f
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.0] - 2026-11-12
|
|
10
|
+
|
|
11
|
+
Native-issuer service tokens. Oathweave signs client-credentials tokens with its native issuer
|
|
12
|
+
(its `JWT_ISSUER`, for example `hive-iam`), which is a name, not an https URL, so 0.2.0 refused the
|
|
13
|
+
configuration and products kept a local jose check for those tokens. Every change is additive:
|
|
14
|
+
without the new options a verifier behaves as in 0.2.0.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `/oathweave`: `nativeIssuers` accepts tokens whose `iss` is one of the listed native
|
|
19
|
+
identifiers. Each must match `OATHWEAVE_NATIVE_ISSUER_PATTERN` (`^[a-z][a-z0-9-]{0,63}$`), so a
|
|
20
|
+
URL or anything else is a startup error. They are verified with the same `jwksUrl` (still https)
|
|
21
|
+
or `keys` as the https issuer, because Oathweave signs both with one key. A native issuer is never
|
|
22
|
+
accepted unless listed. `verifier.nativeIssuers` lists them.
|
|
23
|
+
- `/oathweave`: `nativeIssuerAccept` (default `'service'`) limits which principals a native issuer
|
|
24
|
+
may sign for. A user token from a native issuer is refused with `invalid_issuer` by default,
|
|
25
|
+
before any userinfo call.
|
|
26
|
+
- `/oathweave`: `maxLifetimeSec` also takes `{ user?, service? }`, one limit per principal kind;
|
|
27
|
+
each defaults to 900. `ClaimPolicy.maxLifetimeSec` and `principalFromClaims()` take the same
|
|
28
|
+
shape. The `OathweaveLifetimeLimits` type is exported.
|
|
29
|
+
- `/oathweave/conformance`: `ConformanceVectors.nativeIssuers` and `ConformanceContext.nativeIssuers`.
|
|
30
|
+
`hiveCommonConformanceVerify()` configures them.
|
|
31
|
+
- Conformance vectors: `valid-native-service` (accept), `unconfigured-native-issuer` (401
|
|
32
|
+
`invalid_issuer`) and `native-service-lifetime-too-long` (401 `invalid_lifetime`). There is no
|
|
33
|
+
vector for a native-issuer user token, because Oathweave does issue those (to registered clients
|
|
34
|
+
that are not suite browser clients); refusing them is a product setting, not a contract.
|
|
35
|
+
- README: "Native issuers", and native-issuer and per-kind lifetime rows in "Choosing settings for
|
|
36
|
+
an existing product".
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- The conformance vectors were regenerated with `npm run vectors:generate` to sign the new tokens,
|
|
41
|
+
so every token and the test JWKS changed (the signing keys are discarded after each run). The
|
|
42
|
+
existing vectors keep their names, claims and expected outcomes. A product verifier that is not
|
|
43
|
+
configured with `ctx.nativeIssuers` now fails `valid-native-service`; configure it, or exclude
|
|
44
|
+
that vector with `only` until the product accepts native issuers.
|
|
45
|
+
|
|
9
46
|
## [0.2.0] - 2026-11-12
|
|
10
47
|
|
|
11
48
|
Options that let a product keep its existing behaviour without wrapping the shared helpers. Every
|
|
@@ -60,6 +97,7 @@ verifier and its own error, health and request-ID conventions.
|
|
|
60
97
|
pagination and agent identity.
|
|
61
98
|
- `/hono` and `/node-http` adapters for auth, request context, the error envelope and health.
|
|
62
99
|
|
|
63
|
-
[Unreleased]: https://github.com/ChefTaco/HiveCommon/compare/v0.
|
|
100
|
+
[Unreleased]: https://github.com/ChefTaco/HiveCommon/compare/v0.3.0...HEAD
|
|
101
|
+
[0.3.0]: https://github.com/ChefTaco/HiveCommon/compare/v0.2.0...v0.3.0
|
|
64
102
|
[0.2.0]: https://github.com/ChefTaco/HiveCommon/compare/v0.1.0...v0.2.0
|
|
65
103
|
[0.1.0]: https://github.com/ChefTaco/HiveCommon/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -100,7 +100,8 @@ What it checks, in order: a compact JWT of at most 16 KiB; header `alg` is `RS25
|
|
|
100
100
|
HMAC key confusion never reach a key); signature against the JWKS (cached for 10 minutes and
|
|
101
101
|
refetched, at most every 30 s, when a token names an unknown `kid`); `iss` is one of the issuers;
|
|
102
102
|
`aud` is exactly the audience; `sub`, `iat`, `exp`, `jti` present; `exp`, `nbf` and `iat` within
|
|
103
|
-
30 s of clock tolerance; `exp - iat` and token age at most 900 s
|
|
103
|
+
30 s of clock tolerance; `exp - iat` and token age at most 900 s (`maxLifetimeSec`, one number or
|
|
104
|
+
`{ user, service }`); then the principal rules:
|
|
104
105
|
|
|
105
106
|
- **User:** `actorType: "USER"`, a `sessionId`, a `clientId` other than the subject, string-array
|
|
106
107
|
`permissions` and (optional) `roles`. With `userinfoUrl`, the session is confirmed online; the
|
|
@@ -138,17 +139,48 @@ Two options make the claim rules stricter for products that always required more
|
|
|
138
139
|
`'client_id'` (RFC 9068) or `'either'` (the default). When both claims are present they must
|
|
139
140
|
agree in every mode.
|
|
140
141
|
|
|
142
|
+
#### Native issuers
|
|
143
|
+
|
|
144
|
+
Oathweave signs browser-client tokens with its https issuer, but client-credentials tokens with its
|
|
145
|
+
*native* issuer (its `JWT_ISSUER`, for example `hive-iam`) unless the client's machine policy selects
|
|
146
|
+
the browser issuer. Both are signed by the same key and published in the same JWKS. List the native
|
|
147
|
+
identifiers in `nativeIssuers` to accept those tokens too:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const verifier = createOathweaveVerifier({
|
|
151
|
+
issuer: process.env.OATHWEAVE_ISSUER!, // still required, still https
|
|
152
|
+
nativeIssuers: ['hive-iam'], // exact `iss` values; never accepted unless listed
|
|
153
|
+
audience: 'hivemem',
|
|
154
|
+
jwksUrl: process.env.OATHWEAVE_JWKS_URL!, // the one https JWKS serves both issuers
|
|
155
|
+
userinfoUrl: process.env.OATHWEAVE_USERINFO_URL,
|
|
156
|
+
accept: 'service',
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- Each entry must match `OATHWEAVE_NATIVE_ISSUER_PATTERN` (`^[a-z][a-z0-9-]{0,63}$`), so a URL or
|
|
161
|
+
any other shape is a startup error. There is no separate JWKS setting: `jwksUrl` (https, or
|
|
162
|
+
loopback http with `allowInsecureLoopback`) or `keys` verify both kinds of issuer.
|
|
163
|
+
- A native issuer signs service principals only, by default: a user token from a native issuer is
|
|
164
|
+
refused with `invalid_issuer` before any session check. Suite user tokens come from browser
|
|
165
|
+
clients, which Oathweave always signs with the https issuer. `nativeIssuerAccept: 'any'` (or
|
|
166
|
+
`'user'`) changes that for a product that really takes native user tokens.
|
|
167
|
+
- Every other rule (audience, lifetime, principal rules) is unchanged. `principal.issuer` is the
|
|
168
|
+
native identifier, so identity and service links keyed by `(issuer, clientId)` keep working.
|
|
169
|
+
`verifier.nativeIssuers` lists them; `verifier.issuers` still lists only the https issuers.
|
|
170
|
+
|
|
141
171
|
Identity links name the local account `localUserId`. `localId`, the name older deployment configs
|
|
142
172
|
use, is accepted as an alias; an entry that sets both to different values is refused. Parsed links
|
|
143
173
|
always carry `localUserId`.
|
|
144
174
|
|
|
145
175
|
Endpoints must be `https`. For tests against a loopback server, pass `allowInsecureLoopback: true`.
|
|
176
|
+
Native issuer identifiers are names, not endpoints, and are never fetched.
|
|
146
177
|
|
|
147
178
|
### Conformance vectors
|
|
148
179
|
|
|
149
|
-
`test-vectors/oathweave/vectors.json` ships a test JWKS and
|
|
180
|
+
`test-vectors/oathweave/vectors.json` ships a test JWKS and fifteen pre-signed tokens (valid user,
|
|
150
181
|
valid service, expired, not yet valid, wrong audience, wrong issuer, bad signature, unknown `kid`,
|
|
151
|
-
`alg: none`, HS256 key confusion, overlong lifetime, user without session
|
|
182
|
+
`alg: none`, HS256 key confusion, overlong lifetime, user without session, valid native-issuer
|
|
183
|
+
service, unconfigured native issuer, overlong native-issuer service token), evaluated at a fixed
|
|
152
184
|
clock so they never expire. Run them against your product's verifier:
|
|
153
185
|
|
|
154
186
|
```ts
|
|
@@ -157,13 +189,15 @@ import { runOathweaveConformance } from '@hivemagnus/hive-common/oathweave/confo
|
|
|
157
189
|
it('meets the Oathweave conformance vectors', async () => {
|
|
158
190
|
await runOathweaveConformance((token, ctx) => createProductVerifier({
|
|
159
191
|
issuer: ctx.issuer, audience: ctx.audience, jwksUrl: ctx.jwksUrl, userinfoUrl: ctx.userinfoUrl,
|
|
192
|
+
nativeIssuers: ctx.nativeIssuers, // native issuers the vectors use, for service tokens
|
|
160
193
|
fetch: ctx.fetch, // serves the vectors' JWKS and userinfo
|
|
161
194
|
now: () => ctx.nowMs, // the fixed clock
|
|
162
195
|
})(token));
|
|
163
196
|
});
|
|
164
197
|
```
|
|
165
198
|
|
|
166
|
-
|
|
199
|
+
A verifier that does not accept native issuers fails `valid-native-service`; pass `only` to run
|
|
200
|
+
the other vectors until it does. It throws a readable error listing failed vectors. A refusal passes when the verifier rejects
|
|
167
201
|
(and any numeric `status` on the error matches); `strictReasons: true` also compares `reason`.
|
|
168
202
|
|
|
169
203
|
### errors
|
|
@@ -406,6 +440,8 @@ existing behaviour with these settings instead of wrapping the shared helpers:
|
|
|
406
440
|
| `sessionCacheTtlMs: 0` (verifier) | 10 000 | an online session check on every request, so sign-out is immediate |
|
|
407
441
|
| `requireRoles: true` (verifier) | `false` | refusing tokens without a `roles` array |
|
|
408
442
|
| `clientIdClaim: 'clientId'` (verifier) | `'either'` | requiring the Oathweave `clientId` claim; RFC 9068 `client_id` alone is refused |
|
|
443
|
+
| `nativeIssuers: ['hive-iam']` (verifier) | none | accepting client-credentials tokens signed with Oathweave's native issuer, which a local jose check used to handle |
|
|
444
|
+
| `maxLifetimeSec: { user, service }` (verifier) | 900 for both | a different limit per principal kind, for example `{ user: 900, service: 300 }` |
|
|
409
445
|
| `trustInboundRequestId: false` with `trustInboundTrace: true` (context, `/hono`, `/node-http`) | both `trustInbound` | generating every request ID on an internet-facing service while continuing callers' traces |
|
|
410
446
|
| `liveBody` (health, `/hono`, `/node-http`) | the standard body | a published `/health/live` body such as `{ status: 'ok' }` |
|
|
411
447
|
| `localId` in identity links | `localUserId` | existing link configs, unchanged |
|
|
@@ -423,6 +459,25 @@ registerHealthRoutes(app, { service: 'chattermost', liveBody: { status: 'ok' } }
|
|
|
423
459
|
Every refusal, including 503 `auth_unavailable`, is still an `OathweaveError`; a product that
|
|
424
460
|
answers 401 for all of them maps the error in its own handler.
|
|
425
461
|
|
|
462
|
+
Service tokens from a native issuer. Products that kept a local jose verifier for Oathweave's
|
|
463
|
+
native issuer replace it with a second shared verifier on the same key source, so one JWKS cache
|
|
464
|
+
serves both:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
const keys = createRemoteJWKSet(new URL(jwksUrl)); // or let each verifier fetch jwksUrl itself
|
|
468
|
+
const users = createOathweaveVerifier({ issuer, audience: 'hivemem', keys, userinfoUrl, /* user settings */ });
|
|
469
|
+
const services = createOathweaveVerifier({
|
|
470
|
+
issuer, // https; browser-issued machine clients use it
|
|
471
|
+
nativeIssuers: ['hive-iam'], // Oathweave's JWT_ISSUER, from configuration
|
|
472
|
+
audience: 'hivemem', keys, accept: 'service',
|
|
473
|
+
clientIdClaim: 'clientId', // Oathweave service tokens always carry clientId
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
The fifteen-minute limit applies to service tokens as before (`maxLifetimeSec` sets it per kind).
|
|
478
|
+
Native identifiers are exact: configure the value Oathweave's `JWT_ISSUER` is set to, and keep the
|
|
479
|
+
service links that grant access keyed by that issuer.
|
|
480
|
+
|
|
426
481
|
## Development
|
|
427
482
|
|
|
428
483
|
```bash
|
|
@@ -58,10 +58,18 @@ export declare function extractScopes(payload: JWTPayload): string[];
|
|
|
58
58
|
export declare function extractRoles(payload: JWTPayload, required?: boolean): string[] | undefined;
|
|
59
59
|
/** A service token is one whose client ID equals its subject. */
|
|
60
60
|
export declare function principalKindOf(payload: JWTPayload, claim?: ClientIdClaim): PrincipalKind;
|
|
61
|
+
/** A lifetime limit in seconds for each principal kind. */
|
|
62
|
+
export interface OathweaveLifetimeLimits {
|
|
63
|
+
readonly user?: number;
|
|
64
|
+
readonly service?: number;
|
|
65
|
+
}
|
|
61
66
|
export interface ClaimPolicy {
|
|
62
67
|
readonly accept: AcceptPrincipal;
|
|
63
|
-
/**
|
|
64
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Largest allowed `exp - iat`, in seconds: one limit for every token, or one per principal
|
|
70
|
+
* kind. A kind missing from the object gets `OATHWEAVE_MAX_LIFETIME_SEC` (900).
|
|
71
|
+
*/
|
|
72
|
+
readonly maxLifetimeSec: number | OathweaveLifetimeLimits;
|
|
65
73
|
/** Refuse a token without a `roles` claim. Defaults to false: an absent claim is no roles. */
|
|
66
74
|
readonly requireRoles?: boolean;
|
|
67
75
|
/** Which claim names supply the client ID. Defaults to `either`. */
|
package/dist/oathweave/claims.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { OathweaveError } from './errors.js';
|
|
2
|
+
/** Mirrors `OATHWEAVE_MAX_LIFETIME_SEC`, which lives in the verifier module. */
|
|
3
|
+
const MAX_LIFETIME_SEC = 900;
|
|
2
4
|
function stringArray(value) {
|
|
3
5
|
return Array.isArray(value) && value.every(item => typeof item === 'string');
|
|
4
6
|
}
|
|
@@ -52,18 +54,19 @@ function validAuthTime(value) {
|
|
|
52
54
|
*/
|
|
53
55
|
export function principalFromClaims(payload, policy) {
|
|
54
56
|
const { iat, exp, sub, jti, iss } = payload;
|
|
55
|
-
|
|
57
|
+
const clientIdClaim = policy.clientIdClaim ?? 'either';
|
|
58
|
+
const kind = principalKindOf(payload, clientIdClaim);
|
|
59
|
+
const maxLifetimeSec = typeof policy.maxLifetimeSec === 'number' ? policy.maxLifetimeSec : policy.maxLifetimeSec[kind] ?? MAX_LIFETIME_SEC;
|
|
60
|
+
if (typeof iat !== 'number' || typeof exp !== 'number' || exp <= iat || exp - iat > maxLifetimeSec) {
|
|
56
61
|
throw new OathweaveError('invalid_lifetime');
|
|
57
62
|
}
|
|
58
63
|
if (!nonEmpty(sub) || !nonEmpty(jti) || !nonEmpty(iss))
|
|
59
64
|
throw new OathweaveError('invalid_claims');
|
|
60
65
|
const permissions = payload['permissions'];
|
|
61
|
-
const clientIdClaim = policy.clientIdClaim ?? 'either';
|
|
62
66
|
const roles = extractRoles(payload, policy.requireRoles ?? false);
|
|
63
67
|
const clientId = clientIdOf(payload, clientIdClaim);
|
|
64
68
|
if (!stringArray(permissions) || roles === undefined || clientId === undefined)
|
|
65
69
|
throw new OathweaveError('invalid_claims');
|
|
66
|
-
const kind = principalKindOf(payload, clientIdClaim);
|
|
67
70
|
if (policy.accept !== 'any' && policy.accept !== kind)
|
|
68
71
|
throw new OathweaveError('wrong_principal_kind', { details: { kind } });
|
|
69
72
|
const base = {
|
|
@@ -25,6 +25,8 @@ export interface ConformanceVector {
|
|
|
25
25
|
export interface ConformanceVectors {
|
|
26
26
|
readonly schema: 1;
|
|
27
27
|
readonly issuer: string;
|
|
28
|
+
/** Native issuer identifiers the verifier under test must be configured to accept. */
|
|
29
|
+
readonly nativeIssuers?: readonly string[];
|
|
28
30
|
readonly audience: string;
|
|
29
31
|
readonly jwksUrl: string;
|
|
30
32
|
readonly userinfoUrl: string;
|
|
@@ -35,6 +37,11 @@ export interface ConformanceVectors {
|
|
|
35
37
|
}
|
|
36
38
|
export interface ConformanceContext {
|
|
37
39
|
readonly issuer: string;
|
|
40
|
+
/**
|
|
41
|
+
* Native issuer identifiers (such as `hive-iam`) to configure alongside `issuer`, for service
|
|
42
|
+
* tokens. Empty when the vectors have none.
|
|
43
|
+
*/
|
|
44
|
+
readonly nativeIssuers: readonly string[];
|
|
38
45
|
readonly audience: string;
|
|
39
46
|
readonly jwks: JSONWebKeySet;
|
|
40
47
|
readonly jwksUrl: string;
|
|
@@ -76,6 +76,7 @@ export async function runOathweaveConformance(verify, options = {}) {
|
|
|
76
76
|
continue;
|
|
77
77
|
const context = {
|
|
78
78
|
issuer: vectors.issuer,
|
|
79
|
+
nativeIssuers: vectors.nativeIssuers ?? [],
|
|
79
80
|
audience: vectors.audience,
|
|
80
81
|
jwks: vectors.jwks,
|
|
81
82
|
jwksUrl: vectors.jwksUrl,
|
|
@@ -116,6 +117,7 @@ export async function runOathweaveConformance(verify, options = {}) {
|
|
|
116
117
|
export function hiveCommonConformanceVerify() {
|
|
117
118
|
return (token, context) => createOathweaveVerifier({
|
|
118
119
|
issuer: context.issuer,
|
|
120
|
+
nativeIssuers: context.nativeIssuers,
|
|
119
121
|
audience: context.audience,
|
|
120
122
|
keys: context.jwks,
|
|
121
123
|
now: () => context.nowMs,
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* `@hivemagnus/hive-common/oathweave`: Oathweave access-token verification for suite services.
|
|
3
3
|
* Framework-free; see `/hono` and `/node-http` for middleware.
|
|
4
4
|
*/
|
|
5
|
-
export { createOathweaveVerifier, parseBearerToken, OATHWEAVE_DEFAULT_CLOCK_TOLERANCE_SEC, OATHWEAVE_FAILED_CHECK_TTL_MS, OATHWEAVE_MAX_LIFETIME_SEC, OATHWEAVE_MAX_TOKEN_LENGTH, OATHWEAVE_REQUEST_TIMEOUT_MS, OATHWEAVE_SESSION_CACHE_MAX, OATHWEAVE_SESSION_CACHE_TTL_MS, OATHWEAVE_STREAM_RECHECK_MS, type OathweaveVerifier, type OathweaveVerifierOptions, type VerifyOptions, } from './verifier.js';
|
|
6
|
-
export { CLIENT_ID_CLAIMS, clientIdOf, extractRoles, extractScopes, principalFromClaims, principalKindOf, type AcceptPrincipal, type ClaimPolicy, type ClientIdClaim, type OathweavePrincipal, type OathweaveServicePrincipal, type OathweaveUserPrincipal, type PrincipalKind, } from './claims.js';
|
|
5
|
+
export { createOathweaveVerifier, parseBearerToken, OATHWEAVE_DEFAULT_CLOCK_TOLERANCE_SEC, OATHWEAVE_FAILED_CHECK_TTL_MS, OATHWEAVE_MAX_LIFETIME_SEC, OATHWEAVE_MAX_TOKEN_LENGTH, OATHWEAVE_NATIVE_ISSUER_PATTERN, OATHWEAVE_REQUEST_TIMEOUT_MS, OATHWEAVE_SESSION_CACHE_MAX, OATHWEAVE_SESSION_CACHE_TTL_MS, OATHWEAVE_STREAM_RECHECK_MS, type OathweaveVerifier, type OathweaveVerifierOptions, type VerifyOptions, } from './verifier.js';
|
|
6
|
+
export { CLIENT_ID_CLAIMS, clientIdOf, extractRoles, extractScopes, principalFromClaims, principalKindOf, type AcceptPrincipal, type ClaimPolicy, type ClientIdClaim, type OathweaveLifetimeLimits, type OathweavePrincipal, type OathweaveServicePrincipal, type OathweaveUserPrincipal, type PrincipalKind, } from './claims.js';
|
|
7
7
|
export { OathweaveError, isOathweaveError, statusForReason, type OathweaveFailureReason } from './errors.js';
|
|
8
8
|
export { assertOathweaveEndpoint, endpointProblem, isLoopbackHost, isOathweaveEndpoint, type EndpointOptions } from './endpoint.js';
|
|
9
9
|
export { effectivePermissions, hasAnyRole, hasPermissions, hasScopes, IdentityLinkError, parseIdentityLinks, permissionMatches, requireIdentityLink, requirePermissions, uniqueIdentityLink, type OathweaveIdentityLink, type ParseIdentityLinksOptions, type PermissionOptions, } from './links.js';
|
package/dist/oathweave/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `@hivemagnus/hive-common/oathweave`: Oathweave access-token verification for suite services.
|
|
3
3
|
* Framework-free; see `/hono` and `/node-http` for middleware.
|
|
4
4
|
*/
|
|
5
|
-
export { createOathweaveVerifier, parseBearerToken, OATHWEAVE_DEFAULT_CLOCK_TOLERANCE_SEC, OATHWEAVE_FAILED_CHECK_TTL_MS, OATHWEAVE_MAX_LIFETIME_SEC, OATHWEAVE_MAX_TOKEN_LENGTH, OATHWEAVE_REQUEST_TIMEOUT_MS, OATHWEAVE_SESSION_CACHE_MAX, OATHWEAVE_SESSION_CACHE_TTL_MS, OATHWEAVE_STREAM_RECHECK_MS, } from './verifier.js';
|
|
5
|
+
export { createOathweaveVerifier, parseBearerToken, OATHWEAVE_DEFAULT_CLOCK_TOLERANCE_SEC, OATHWEAVE_FAILED_CHECK_TTL_MS, OATHWEAVE_MAX_LIFETIME_SEC, OATHWEAVE_MAX_TOKEN_LENGTH, OATHWEAVE_NATIVE_ISSUER_PATTERN, OATHWEAVE_REQUEST_TIMEOUT_MS, OATHWEAVE_SESSION_CACHE_MAX, OATHWEAVE_SESSION_CACHE_TTL_MS, OATHWEAVE_STREAM_RECHECK_MS, } from './verifier.js';
|
|
6
6
|
export { CLIENT_ID_CLAIMS, clientIdOf, extractRoles, extractScopes, principalFromClaims, principalKindOf, } from './claims.js';
|
|
7
7
|
export { OathweaveError, isOathweaveError, statusForReason } from './errors.js';
|
|
8
8
|
export { assertOathweaveEndpoint, endpointProblem, isLoopbackHost, isOathweaveEndpoint } from './endpoint.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type JSONWebKeySet, type JWTVerifyGetKey } from 'jose';
|
|
2
|
-
import { type AcceptPrincipal, type ClientIdClaim, type OathweavePrincipal, type OathweaveServicePrincipal, type OathweaveUserPrincipal } from './claims.js';
|
|
2
|
+
import { type AcceptPrincipal, type ClientIdClaim, type OathweaveLifetimeLimits, type OathweavePrincipal, type OathweaveServicePrincipal, type OathweaveUserPrincipal } from './claims.js';
|
|
3
3
|
import { type EndpointOptions } from './endpoint.js';
|
|
4
4
|
/** How long a userinfo answer is reused, measured from when its request started. */
|
|
5
5
|
export declare const OATHWEAVE_SESSION_CACHE_TTL_MS = 10000;
|
|
@@ -14,9 +14,28 @@ export declare const OATHWEAVE_MAX_LIFETIME_SEC = 900;
|
|
|
14
14
|
export declare const OATHWEAVE_DEFAULT_CLOCK_TOLERANCE_SEC = 30;
|
|
15
15
|
/** Longest accepted compact token. Real Oathweave tokens are well under 4 KiB. */
|
|
16
16
|
export declare const OATHWEAVE_MAX_TOKEN_LENGTH = 16384;
|
|
17
|
+
/**
|
|
18
|
+
* What a native issuer identifier must look like: a lowercase name such as `hive-iam`, never a
|
|
19
|
+
* URL, so it cannot be confused with an https issuer.
|
|
20
|
+
*/
|
|
21
|
+
export declare const OATHWEAVE_NATIVE_ISSUER_PATTERN: RegExp;
|
|
17
22
|
export interface OathweaveVerifierOptions extends EndpointOptions {
|
|
18
23
|
/** Accepted issuer, or issuers. Each must be an https URL. */
|
|
19
24
|
readonly issuer: string | readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* Oathweave's native issuer identifiers (its `JWT_ISSUER`, for example `hive-iam`) to accept
|
|
27
|
+
* as well. Oathweave signs client-credentials tokens with its native issuer unless the
|
|
28
|
+
* client's policy selects the browser issuer, and with the same keys, so these tokens are
|
|
29
|
+
* verified against the same `jwksUrl` or `keys`. Each must match
|
|
30
|
+
* `OATHWEAVE_NATIVE_ISSUER_PATTERN`. Defaults to none: a native issuer is never accepted
|
|
31
|
+
* unless it is listed here.
|
|
32
|
+
*/
|
|
33
|
+
readonly nativeIssuers?: readonly string[];
|
|
34
|
+
/**
|
|
35
|
+
* Which principals a native issuer may sign for. Defaults to `service`: suite user tokens
|
|
36
|
+
* come from browser clients, which Oathweave always signs with the https issuer.
|
|
37
|
+
*/
|
|
38
|
+
readonly nativeIssuerAccept?: AcceptPrincipal;
|
|
20
39
|
/** This service's resource audience, for example `hivemem`. Matched exactly. */
|
|
21
40
|
readonly audience: string;
|
|
22
41
|
/** The issuer's JWKS endpoint. Required unless `keys` is given. */
|
|
@@ -32,8 +51,11 @@ export interface OathweaveVerifierOptions extends EndpointOptions {
|
|
|
32
51
|
readonly accept?: AcceptPrincipal;
|
|
33
52
|
/** Seconds of clock skew tolerated on `exp`, `nbf` and `iat`. Defaults to 30. */
|
|
34
53
|
readonly clockToleranceSec?: number;
|
|
35
|
-
/**
|
|
36
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Largest `exp - iat` and token age in seconds: one limit for every token, or one per
|
|
56
|
+
* principal kind (`{ user: 900, service: 300 }`). Each defaults to 900.
|
|
57
|
+
*/
|
|
58
|
+
readonly maxLifetimeSec?: number | OathweaveLifetimeLimits;
|
|
37
59
|
readonly fetch?: typeof fetch;
|
|
38
60
|
/** Clock in epoch milliseconds. */
|
|
39
61
|
readonly now?: () => number;
|
|
@@ -64,7 +86,10 @@ export interface VerifyOptions {
|
|
|
64
86
|
readonly accept?: AcceptPrincipal;
|
|
65
87
|
}
|
|
66
88
|
export interface OathweaveVerifier {
|
|
89
|
+
/** The accepted https issuers. */
|
|
67
90
|
readonly issuers: readonly string[];
|
|
91
|
+
/** The accepted native issuer identifiers; empty unless configured. */
|
|
92
|
+
readonly nativeIssuers: readonly string[];
|
|
68
93
|
readonly audience: string;
|
|
69
94
|
verify(token: string, options?: VerifyOptions): Promise<OathweavePrincipal>;
|
|
70
95
|
verifyUser(token: string): Promise<OathweaveUserPrincipal>;
|
|
@@ -23,6 +23,11 @@ export const OATHWEAVE_MAX_LIFETIME_SEC = 900;
|
|
|
23
23
|
export const OATHWEAVE_DEFAULT_CLOCK_TOLERANCE_SEC = 30;
|
|
24
24
|
/** Longest accepted compact token. Real Oathweave tokens are well under 4 KiB. */
|
|
25
25
|
export const OATHWEAVE_MAX_TOKEN_LENGTH = 16_384;
|
|
26
|
+
/**
|
|
27
|
+
* What a native issuer identifier must look like: a lowercase name such as `hive-iam`, never a
|
|
28
|
+
* URL, so it cannot be confused with an https issuer.
|
|
29
|
+
*/
|
|
30
|
+
export const OATHWEAVE_NATIVE_ISSUER_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
26
31
|
const BEARER = /^Bearer[ ]+([A-Za-z0-9\-._~+/]+=*)[ ]*$/i;
|
|
27
32
|
/**
|
|
28
33
|
* The token in an `Authorization: Bearer` header (RFC 6750). Undefined when there is no
|
|
@@ -91,6 +96,31 @@ function mapVerifyError(error) {
|
|
|
91
96
|
// JOSEAlgNotAllowed cannot occur: the header precheck already refused every algorithm but RS256.
|
|
92
97
|
return new OathweaveError('malformed_token');
|
|
93
98
|
}
|
|
99
|
+
const ACCEPT_VALUES = ['user', 'service', 'any'];
|
|
100
|
+
/** Validate the configured native issuer identifiers. */
|
|
101
|
+
function nativeIssuerList(value) {
|
|
102
|
+
if (value === undefined)
|
|
103
|
+
return [];
|
|
104
|
+
if (!Array.isArray(value))
|
|
105
|
+
throw new TypeError('Oathweave nativeIssuers must be an array');
|
|
106
|
+
for (const issuer of value) {
|
|
107
|
+
if (typeof issuer !== 'string' || !OATHWEAVE_NATIVE_ISSUER_PATTERN.test(issuer)) {
|
|
108
|
+
throw new TypeError('Oathweave nativeIssuers entries must be lowercase identifiers such as hive-iam');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [...new Set(value)];
|
|
112
|
+
}
|
|
113
|
+
/** Resolve `maxLifetimeSec` into one limit per principal kind. */
|
|
114
|
+
function lifetimeLimits(value) {
|
|
115
|
+
const limits = typeof value === 'object' && value !== null
|
|
116
|
+
? { user: value.user ?? OATHWEAVE_MAX_LIFETIME_SEC, service: value.service ?? OATHWEAVE_MAX_LIFETIME_SEC }
|
|
117
|
+
: { user: value ?? OATHWEAVE_MAX_LIFETIME_SEC, service: value ?? OATHWEAVE_MAX_LIFETIME_SEC };
|
|
118
|
+
for (const limit of [limits.user, limits.service]) {
|
|
119
|
+
if (!(typeof limit === 'number' && limit > 0 && limit <= 86_400))
|
|
120
|
+
throw new TypeError('maxLifetimeSec must be 1-86400');
|
|
121
|
+
}
|
|
122
|
+
return limits;
|
|
123
|
+
}
|
|
94
124
|
/** Create a verifier. Throws a TypeError at startup when the configuration is unsafe. */
|
|
95
125
|
export function createOathweaveVerifier(options) {
|
|
96
126
|
const issuers = typeof options.issuer === 'string' ? [options.issuer] : [...options.issuer];
|
|
@@ -98,6 +128,10 @@ export function createOathweaveVerifier(options) {
|
|
|
98
128
|
throw new TypeError('At least one Oathweave issuer is required');
|
|
99
129
|
for (const issuer of issuers)
|
|
100
130
|
assertOathweaveEndpoint(issuer, 'issuer', options);
|
|
131
|
+
const nativeIssuers = nativeIssuerList(options.nativeIssuers);
|
|
132
|
+
const nativeIssuerAccept = options.nativeIssuerAccept ?? 'service';
|
|
133
|
+
if (!ACCEPT_VALUES.includes(nativeIssuerAccept))
|
|
134
|
+
throw new TypeError("nativeIssuerAccept must be 'user', 'service' or 'any'");
|
|
101
135
|
if (options.jwksUrl !== undefined)
|
|
102
136
|
assertOathweaveEndpoint(options.jwksUrl, 'jwksUrl', options);
|
|
103
137
|
if (options.userinfoUrl !== undefined)
|
|
@@ -105,11 +139,9 @@ export function createOathweaveVerifier(options) {
|
|
|
105
139
|
if (typeof options.audience !== 'string' || !options.audience)
|
|
106
140
|
throw new TypeError('Oathweave audience is required');
|
|
107
141
|
const clockTolerance = options.clockToleranceSec ?? OATHWEAVE_DEFAULT_CLOCK_TOLERANCE_SEC;
|
|
108
|
-
const
|
|
142
|
+
const lifetimes = lifetimeLimits(options.maxLifetimeSec);
|
|
109
143
|
if (!(clockTolerance >= 0 && clockTolerance <= 300))
|
|
110
144
|
throw new TypeError('clockToleranceSec must be 0-300');
|
|
111
|
-
if (!(maxLifetimeSec > 0 && maxLifetimeSec <= 86_400))
|
|
112
|
-
throw new TypeError('maxLifetimeSec must be 1-86400');
|
|
113
145
|
const requireRoles = options.requireRoles ?? false;
|
|
114
146
|
const clientIdClaim = options.clientIdClaim ?? 'either';
|
|
115
147
|
if (typeof requireRoles !== 'boolean')
|
|
@@ -121,6 +153,7 @@ export function createOathweaveVerifier(options) {
|
|
|
121
153
|
const request = options.fetch ?? ((input, init) => fetch(input, init));
|
|
122
154
|
const now = options.now ?? Date.now;
|
|
123
155
|
const keys = keySource(options, request, timeoutMs);
|
|
156
|
+
const acceptedIssuers = [...issuers, ...nativeIssuers];
|
|
124
157
|
const sessionTtl = options.sessionCacheTtlMs ?? OATHWEAVE_SESSION_CACHE_TTL_MS;
|
|
125
158
|
const sessionMax = options.sessionCacheMax ?? OATHWEAVE_SESSION_CACHE_MAX;
|
|
126
159
|
// Holds only tokens whose signature verified, so garbage can neither fill it nor reach userinfo.
|
|
@@ -194,10 +227,11 @@ export function createOathweaveVerifier(options) {
|
|
|
194
227
|
try {
|
|
195
228
|
({ payload } = await jwtVerify(token, keys, {
|
|
196
229
|
algorithms: ['RS256'],
|
|
197
|
-
issuer:
|
|
230
|
+
issuer: acceptedIssuers,
|
|
198
231
|
audience: options.audience,
|
|
199
232
|
requiredClaims: ['sub', 'iat', 'exp', 'jti'],
|
|
200
|
-
|
|
233
|
+
// The per-kind limit is checked on `exp - iat` below, which also bounds the age.
|
|
234
|
+
maxTokenAge: Math.max(lifetimes.user, lifetimes.service),
|
|
201
235
|
clockTolerance,
|
|
202
236
|
currentDate: new Date(now()),
|
|
203
237
|
}));
|
|
@@ -210,10 +244,14 @@ export function createOathweaveVerifier(options) {
|
|
|
210
244
|
throw new OathweaveError('invalid_audience');
|
|
211
245
|
const principal = principalFromClaims(payload, {
|
|
212
246
|
accept: verifyOptions.accept ?? defaultAccept,
|
|
213
|
-
maxLifetimeSec,
|
|
247
|
+
maxLifetimeSec: lifetimes,
|
|
214
248
|
requireRoles,
|
|
215
249
|
clientIdClaim,
|
|
216
250
|
});
|
|
251
|
+
// A native issuer signs only the principals it was configured for.
|
|
252
|
+
if (nativeIssuers.includes(principal.issuer) && nativeIssuerAccept !== 'any' && nativeIssuerAccept !== principal.kind) {
|
|
253
|
+
throw new OathweaveError('invalid_issuer');
|
|
254
|
+
}
|
|
217
255
|
if (principal.kind === 'user' && options.userinfoUrl) {
|
|
218
256
|
const displayName = await sessionCheck(token, principal.subject);
|
|
219
257
|
return { ...principal, displayName, sessionChecked: true };
|
|
@@ -222,6 +260,7 @@ export function createOathweaveVerifier(options) {
|
|
|
222
260
|
}
|
|
223
261
|
return {
|
|
224
262
|
issuers,
|
|
263
|
+
nativeIssuers,
|
|
225
264
|
audience: options.audience,
|
|
226
265
|
verify,
|
|
227
266
|
verifyUser: token => verify(token, { accept: 'user' }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hivemagnus/hive-common",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shared server kit for the Hive:Magnus suite: Oathweave token verification, error envelope, health, request context, registry client, logging, events and framework adapters",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hive",
|
package/test-vectors/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
`oathweave/vectors.json` holds a test JWKS and pre-signed Oathweave access tokens with the
|
|
4
4
|
outcome every suite verifier must produce. `oathweave/jwks.json` is the same JWKS on its own.
|
|
5
5
|
|
|
6
|
-
All tokens are synthetic: the issuer is `https://oathweave.example.test`, the
|
|
7
|
-
`conformance`, and the signing keys were discarded after generation. Evaluate them at the fixed
|
|
6
|
+
All tokens are synthetic: the issuer is `https://oathweave.example.test`, the native issuer
|
|
7
|
+
(`nativeIssuers`) is `conformance-iam`, the audience is `conformance`, and the signing keys were discarded after generation. Evaluate them at the fixed
|
|
8
8
|
time in `now` (`2026-01-01T00:00:00Z`), because Oathweave tokens live at most fifteen minutes.
|
|
9
9
|
|
|
10
10
|
| Vector | Expected |
|
|
@@ -21,7 +21,11 @@ time in `now` (`2026-01-01T00:00:00Z`), because Oathweave tokens live at most fi
|
|
|
21
21
|
| `hs256-confusion` | 401 `algorithm_not_allowed` |
|
|
22
22
|
| `lifetime-too-long` | 401 `invalid_lifetime` |
|
|
23
23
|
| `user-without-session` | 401 `invalid_claims` |
|
|
24
|
+
| `valid-native-service` | accept, service principal signed with the native issuer |
|
|
25
|
+
| `unconfigured-native-issuer` | 401 `invalid_issuer` |
|
|
26
|
+
| `native-service-lifetime-too-long` | 401 `invalid_lifetime` |
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
Configure the verifier under test with `nativeIssuers` from the file (the runner passes them as
|
|
29
|
+
`ctx.nativeIssuers`). Run them with `runOathweaveConformance()` from `@hivemagnus/hive-common/oathweave/conformance`.
|
|
26
30
|
Regenerate with `npm run vectors:generate` in the HiveCommon repository; that replaces every
|
|
27
31
|
token and key, so it is a changelog-worthy change.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"keys": [
|
|
3
3
|
{
|
|
4
4
|
"kty": "RSA",
|
|
5
|
-
"n": "
|
|
5
|
+
"n": "yqMr7bkXQ5W1vFW-cIyvEdkdZNAdJ6S3JsmE88aLNyNTDSM8cPskt-1ZntDBFqVc2SyhMtpPHr6zQHo8EkG6VU8vmg0u-X7whM63Pnj3xM-NNJlMCyUfH66imW2N-rITu-cyCOMmWN6WprFzhsXAR4bTw8qnuVfBHQ19fKwRhOHZuTFEQbK_OUj0k_GgzOWVyaoRnCW3_AnesQuBwp68f2JEwllYRYX2vv75Q9ckl8pstGS3E1AAtgPrGp7nAxvbmQHoMlS1xYppZuWjoUttb7qVttduuMESpFva5oDqol5xKe9J0_dP9ZPEWInpzV7LfWgkry21asEIwojglTFgaQ",
|
|
6
6
|
"e": "AQAB",
|
|
7
7
|
"kid": "conformance-1",
|
|
8
8
|
"alg": "RS256",
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"description": "Oathweave access-token conformance vectors. Synthetic: the signing keys were discarded after generation.",
|
|
4
4
|
"issuer": "https://oathweave.example.test",
|
|
5
|
+
"nativeIssuers": [
|
|
6
|
+
"conformance-iam"
|
|
7
|
+
],
|
|
5
8
|
"audience": "conformance",
|
|
6
9
|
"jwksUrl": "https://oathweave.example.test/jwks",
|
|
7
10
|
"userinfoUrl": "https://oathweave.example.test/userinfo",
|
|
@@ -10,7 +13,7 @@
|
|
|
10
13
|
"keys": [
|
|
11
14
|
{
|
|
12
15
|
"kty": "RSA",
|
|
13
|
-
"n": "
|
|
16
|
+
"n": "yqMr7bkXQ5W1vFW-cIyvEdkdZNAdJ6S3JsmE88aLNyNTDSM8cPskt-1ZntDBFqVc2SyhMtpPHr6zQHo8EkG6VU8vmg0u-X7whM63Pnj3xM-NNJlMCyUfH66imW2N-rITu-cyCOMmWN6WprFzhsXAR4bTw8qnuVfBHQ19fKwRhOHZuTFEQbK_OUj0k_GgzOWVyaoRnCW3_AnesQuBwp68f2JEwllYRYX2vv75Q9ckl8pstGS3E1AAtgPrGp7nAxvbmQHoMlS1xYppZuWjoUttb7qVttduuMESpFva5oDqol5xKe9J0_dP9ZPEWInpzV7LfWgkry21asEIwojglTFgaQ",
|
|
14
17
|
"e": "AQAB",
|
|
15
18
|
"kid": "conformance-1",
|
|
16
19
|
"alg": "RS256",
|
|
@@ -23,7 +26,7 @@
|
|
|
23
26
|
"name": "valid-user",
|
|
24
27
|
"description": "A user access token with a session, signed by the trusted key.",
|
|
25
28
|
"principal": "user",
|
|
26
|
-
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjU1NDAtMTc2NzIyNjQ0MCIsImlhdCI6MTc2NzIyNTU0MCwiZXhwIjoxNzY3MjI2NDQwfQ.
|
|
29
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjU1NDAtMTc2NzIyNjQ0MCIsImlhdCI6MTc2NzIyNTU0MCwiZXhwIjoxNzY3MjI2NDQwfQ.h_xGzySHyRl_bLLqz7bRQfI6FXeeoFGX7WVhauLK9LUZG0tn5mtc89p6muVppqStzCpo9NtYJC-ZD1UpF_SU3CAUC7vBU8yRkVPHHGVR26nI5n29jjr1zocAHsKg0Aia7k8hWBRDdxkfxNC6clj0fzLRR2BHNlM0m4UrE29nyQItI5hYt49ngLl74ieFStYYCo1QxJR4_x0AjDcAOUtT-hATSalaBjuS9ASrGoHjhUmal-teXcUbzXV5LXQseY1IspAtx3ujIliFp4qVhCgD2DjGf0j-0vy6tD2NBNSZL6BRJfU8ODhVy9jw1drHj9HncPPgYbr9TT96qaFRklFMKw",
|
|
27
30
|
"expect": {
|
|
28
31
|
"outcome": "accept",
|
|
29
32
|
"kind": "user",
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
"name": "valid-service",
|
|
46
49
|
"description": "A client-credentials token whose client ID equals its subject.",
|
|
47
50
|
"principal": "service",
|
|
48
|
-
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJTRVJWSUNFX0FDQ09VTlQiLCJjbGllbnRJZCI6ImNvbmZvcm1hbmNlLXNlcnZpY2UiLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwicm9sZXMiOltdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTp3cml0ZSJdLCJzY29wZSI6ImNvbmZvcm1hbmNlOndyaXRlIiwiaXNzIjoiaHR0cHM6Ly9vYXRod2VhdmUuZXhhbXBsZS50ZXN0IiwiYXVkIjoiY29uZm9ybWFuY2UiLCJzdWIiOiJjb25mb3JtYW5jZS1zZXJ2aWNlIiwianRpIjoiY29uZm9ybWFuY2Utc2VydmljZS0xNzY3MjI1NTcwLTE3NjcyMjU4NzAiLCJpYXQiOjE3NjcyMjU1NzAsImV4cCI6MTc2NzIyNTg3MH0.
|
|
51
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJTRVJWSUNFX0FDQ09VTlQiLCJjbGllbnRJZCI6ImNvbmZvcm1hbmNlLXNlcnZpY2UiLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwicm9sZXMiOltdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTp3cml0ZSJdLCJzY29wZSI6ImNvbmZvcm1hbmNlOndyaXRlIiwiaXNzIjoiaHR0cHM6Ly9vYXRod2VhdmUuZXhhbXBsZS50ZXN0IiwiYXVkIjoiY29uZm9ybWFuY2UiLCJzdWIiOiJjb25mb3JtYW5jZS1zZXJ2aWNlIiwianRpIjoiY29uZm9ybWFuY2Utc2VydmljZS0xNzY3MjI1NTcwLTE3NjcyMjU4NzAiLCJpYXQiOjE3NjcyMjU1NzAsImV4cCI6MTc2NzIyNTg3MH0.OX7smVqC33kIdki0cnQvYIS3TTV2Hn9Pwdgh2jBJ4SHLq_ns3StMOqaDt9uz6QB7f52JM08a-e_JitdhbqPy6J7W2JIixaqV9mSC1_T8PEKRNdJ6TbkJ_zdnw4wG20VyFBOQqXz3uL8Y9hi4rk_qCmG3iXnRBTmTFw2Ra0J9ndS4Gd6oQH8VNkjBQzS6En-yNQpOEN0kn-ssYeidPJmvOkZ2Kglp4s8sri3OtF19clwcChrbChONJSITLpg6Ee3PBJIiye_h2HONCGeYbnEkKAuqUsnkmwR37rS-Eb58nOeR6A2tornF0iX1dXUShC-siuhalYOP03ijrsLh4Th0qw",
|
|
49
52
|
"expect": {
|
|
50
53
|
"outcome": "accept",
|
|
51
54
|
"kind": "service",
|
|
@@ -64,7 +67,7 @@
|
|
|
64
67
|
"name": "expired",
|
|
65
68
|
"description": "Expired fifteen minutes before the evaluation time.",
|
|
66
69
|
"principal": "user",
|
|
67
|
-
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjM4MDAtMTc2NzIyNDcwMCIsImlhdCI6MTc2NzIyMzgwMCwiZXhwIjoxNzY3MjI0NzAwfQ.
|
|
70
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjM4MDAtMTc2NzIyNDcwMCIsImlhdCI6MTc2NzIyMzgwMCwiZXhwIjoxNzY3MjI0NzAwfQ.vRA51HlCDqr-pfDhUKoDIO50zsykGJM-ob1umb5ByYj2uDOLPl800zyu9v5PJrGPDEQMGsxKgsEDTL5vVFKTDMGQyqYVs3JUnrJhVaIV9C8Ua_e1IZqa2V7qa-sKjiXDgLnMW0s-DQGNvoL2QKf1N9kevht-UtdfW7V9OGnSloo2wJyTN1a4j9qslV5l5PK0hXV_IWcI4oQINFhOUvI5Kt7NYUkVuOqotd-TcKNYcjwmI8v-w_2CP6fG86EP07cipUBHadpxjrA6IT8r21UAa8-Oqh5FnUE6-GY1sUZRl49fNDWrY2UDZqdYnOaGSun3cJuX1sLghStSwTXU6Gm0lA",
|
|
68
71
|
"expect": {
|
|
69
72
|
"outcome": "reject",
|
|
70
73
|
"status": 401,
|
|
@@ -75,7 +78,7 @@
|
|
|
75
78
|
"name": "not-yet-valid",
|
|
76
79
|
"description": "nbf is ten minutes after the evaluation time, beyond any clock tolerance.",
|
|
77
80
|
"principal": "user",
|
|
78
|
-
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjU1NDAtMTc2NzIyNjQ0MCIsImlhdCI6MTc2NzIyNTU0MCwiZXhwIjoxNzY3MjI2NDQwLCJuYmYiOjE3NjcyMjYyMDB9.
|
|
81
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjU1NDAtMTc2NzIyNjQ0MCIsImlhdCI6MTc2NzIyNTU0MCwiZXhwIjoxNzY3MjI2NDQwLCJuYmYiOjE3NjcyMjYyMDB9.VqgN6KtelOkvYszNnlXsZmpHVx_TnuiEBt6e9Lqb2YvPiZvZvYZLS8VMXkujQtV9dQK2q3rEOWK7WpzwsW7ejdCQVX6aixsW5W4PdsG0xPUL4NO5_Sw5f9Uk2wNccEb5fTLqm2_3As-PXe59AXtX_o7JLLsZAkqr0lycsaysp9bO53KshB04WDHWpY5jL-fmDuKPurMD3XPMQ4oe-TFvpqAMlOMLeaH3JgdT6Xiv97NtYAfWU_3vzp0O-GLxxTirRlwL9nMkXZ1lj5Bboyp4rOlwu6OBxNEo69LEgMm7Cmify2KpDotPxnqWJXCW6knwROlInIefqnkRAopF1QhkUQ",
|
|
79
82
|
"expect": {
|
|
80
83
|
"outcome": "reject",
|
|
81
84
|
"status": 401,
|
|
@@ -86,7 +89,7 @@
|
|
|
86
89
|
"name": "wrong-audience",
|
|
87
90
|
"description": "Issued for another product.",
|
|
88
91
|
"principal": "user",
|
|
89
|
-
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImFub3RoZXItcHJvZHVjdCIsInN1YiI6ImNvbmZvcm1hbmNlLXVzZXItc3ViamVjdCIsImp0aSI6ImNvbmZvcm1hbmNlLXVzZXItc3ViamVjdC0xNzY3MjI1NTQwLTE3NjcyMjY0NDAiLCJpYXQiOjE3NjcyMjU1NDAsImV4cCI6MTc2NzIyNjQ0MH0.
|
|
92
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImFub3RoZXItcHJvZHVjdCIsInN1YiI6ImNvbmZvcm1hbmNlLXVzZXItc3ViamVjdCIsImp0aSI6ImNvbmZvcm1hbmNlLXVzZXItc3ViamVjdC0xNzY3MjI1NTQwLTE3NjcyMjY0NDAiLCJpYXQiOjE3NjcyMjU1NDAsImV4cCI6MTc2NzIyNjQ0MH0.XXFWK7gQfhV-zF0GhBm3RrgqOeQyQIpVfCXLfle2EERERF5DvLIEg4YZo6YoVR7oo-NfS9AAr4bQOom-WEGY_vrqA678LbS3626K2jE2WiPSvgj1wJnhrDPT0NoSXF05zr5FhhnVbOGF9-SCcv6hSm8K8nyftdLBG8GykqUPhwmiUDlAFJXwcLdI5vIEPKQbZLqijq-JlMngcbPq54CzqkB6xw1Fi1N791zw7uXRkwlzji55Ld6wUHpqU-lk0pLF6iKzM5xkhuCXgTS7KXK0HUaIYIGxzldCzfVNq3SVkj8dRd6UF0v7CnJxGXzNyZm9cAiwwJ1J46Nl3dsn2XnXWA",
|
|
90
93
|
"expect": {
|
|
91
94
|
"outcome": "reject",
|
|
92
95
|
"status": 401,
|
|
@@ -97,7 +100,7 @@
|
|
|
97
100
|
"name": "wrong-issuer",
|
|
98
101
|
"description": "Signed by the trusted key but naming another issuer.",
|
|
99
102
|
"principal": "user",
|
|
100
|
-
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vaW1wb3N0b3IuZXhhbXBsZS50ZXN0IiwiYXVkIjoiY29uZm9ybWFuY2UiLCJzdWIiOiJjb25mb3JtYW5jZS11c2VyLXN1YmplY3QiLCJqdGkiOiJjb25mb3JtYW5jZS11c2VyLXN1YmplY3QtMTc2NzIyNTU0MC0xNzY3MjI2NDQwIiwiaWF0IjoxNzY3MjI1NTQwLCJleHAiOjE3NjcyMjY0NDB9.
|
|
103
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vaW1wb3N0b3IuZXhhbXBsZS50ZXN0IiwiYXVkIjoiY29uZm9ybWFuY2UiLCJzdWIiOiJjb25mb3JtYW5jZS11c2VyLXN1YmplY3QiLCJqdGkiOiJjb25mb3JtYW5jZS11c2VyLXN1YmplY3QtMTc2NzIyNTU0MC0xNzY3MjI2NDQwIiwiaWF0IjoxNzY3MjI1NTQwLCJleHAiOjE3NjcyMjY0NDB9.XJq-_kga-yWW33-wyjYjevshWgeLOgMK2QuHMQncUV5-FGE8qEZZDXpGRYi_qd40EdlzxMdZJLFXqf38Ub6pZZoo9lKmLN-zfwdLZgiujoMgruTdJzkk0JUe6_kbtVbiQIFepm7VfLmnhFeN4o0p9npmXINE3FDDMMXKBrI8APkJA1JX_zOIdamvNRZqMOF_r6lJLr2flIaPM1zAsP45AdUgNY0NHiK4jZqO5rNCMJgvwPDpBuU78zNzW5ZH5iYbdKvG_QE_AVROg9Enxg4cfH2zbrxl377AfqwbqAz1f65AaWgPx5rG_1-OWcoA8FRA2p38-Z4ZBXohlk7vERd26A",
|
|
101
104
|
"expect": {
|
|
102
105
|
"outcome": "reject",
|
|
103
106
|
"status": 401,
|
|
@@ -108,7 +111,7 @@
|
|
|
108
111
|
"name": "bad-signature",
|
|
109
112
|
"description": "Names the trusted key ID but is signed by another key.",
|
|
110
113
|
"principal": "user",
|
|
111
|
-
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjU1NDAtMTc2NzIyNjQ0MCIsImlhdCI6MTc2NzIyNTU0MCwiZXhwIjoxNzY3MjI2NDQwfQ.
|
|
114
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjU1NDAtMTc2NzIyNjQ0MCIsImlhdCI6MTc2NzIyNTU0MCwiZXhwIjoxNzY3MjI2NDQwfQ.PonwzwBFqrqWQitpXSoAaTbqsRaPS-XfF-QJkIsZG2vRJVkoJaoL6LSqEMvZs0lbGjFfqliLIJgJXsWiheGzz922AQvbsOIAeJbiGuec9yZ5E8qe2NTsdutUMZkSnC1XMhw4n7n1EihjqJXQhTxqwmuFHRKYPjRFfObIX8VMlKw-cEP0DtZZrjWBO1YXaOwg5NgzgQf4z6ZMhfAkAngPHHAcjx50Fwkje79YxfTlXFMwSbcTrBJm4Dfd81mQFpFN3mEUkJyNcGM-Nypk1HGcxSO7hiq7qI7_wohxk05EcW47Lb4sj5hxgnEoxKD2otOo5M5Tv7Ao14YOjLj2Hk-r2w",
|
|
112
115
|
"expect": {
|
|
113
116
|
"outcome": "reject",
|
|
114
117
|
"status": 401,
|
|
@@ -119,7 +122,7 @@
|
|
|
119
122
|
"name": "unknown-kid",
|
|
120
123
|
"description": "Signed by a key the JWKS does not contain.",
|
|
121
124
|
"principal": "user",
|
|
122
|
-
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLXVua25vd24iLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjU1NDAtMTc2NzIyNjQ0MCIsImlhdCI6MTc2NzIyNTU0MCwiZXhwIjoxNzY3MjI2NDQwfQ.
|
|
125
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLXVua25vd24iLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjU1NDAtMTc2NzIyNjQ0MCIsImlhdCI6MTc2NzIyNTU0MCwiZXhwIjoxNzY3MjI2NDQwfQ.Gc9towTbaDvWaUZ54DPl4Qil36n0ajn0Ql6-uopacIHucHhD5zfQAXHNvwI6BAppRegDWewR1n764TM4W-UESIWxrReLNlEdPi_vSlWOm4ft-z5IzfGX9y-INDEUsRq2L4Ps2lRz4HPQUIW57239c_nl9UOeYHzbrg4jswbRg_Ld2d2Qc5PhUTYKIzsNCeV3UtQQQFbASnfGNEGUe-9OodPwtACmDoMfm_ZqMTZxCWEnwPdH1KeL3i7oGV2rGmRLQj3458RBY6xrGLx1CIQ1iS73jKsxuMxBWtzCOkZOPfsYo4nDAUzdyGwBXNwuP4PjDJy3W8iVyA6_HNuFRr4cPw",
|
|
123
126
|
"expect": {
|
|
124
127
|
"outcome": "reject",
|
|
125
128
|
"status": 401,
|
|
@@ -141,7 +144,7 @@
|
|
|
141
144
|
"name": "hs256-confusion",
|
|
142
145
|
"description": "HS256, keyed with the trusted public key, to exploit key-type confusion.",
|
|
143
146
|
"principal": "user",
|
|
144
|
-
"token": "eyJhbGciOiJIUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiZm9yZ2VkIiwiaWF0IjoxNzY3MjI1NTQwLCJleHAiOjE3NjcyMjY0NDB9.
|
|
147
|
+
"token": "eyJhbGciOiJIUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiZm9yZ2VkIiwiaWF0IjoxNzY3MjI1NTQwLCJleHAiOjE3NjcyMjY0NDB9.khNhRMAyBq4h1BGtn5ElcDfoPkmDMA4eFYTXeR3UMk4",
|
|
145
148
|
"expect": {
|
|
146
149
|
"outcome": "reject",
|
|
147
150
|
"status": 401,
|
|
@@ -152,7 +155,7 @@
|
|
|
152
155
|
"name": "lifetime-too-long",
|
|
153
156
|
"description": "A one-hour token; Oathweave access tokens live at most fifteen minutes.",
|
|
154
157
|
"principal": "user",
|
|
155
|
-
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjU1NDAtMTc2NzIyOTE0MCIsImlhdCI6MTc2NzIyNTU0MCwiZXhwIjoxNzY3MjI5MTQwfQ.
|
|
158
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwic2Vzc2lvbklkIjoiY29uZm9ybWFuY2Utc2Vzc2lvbiIsInVzZXJuYW1lIjoiY29uZm9ybWFuY2UtdXNlciIsInJvbGVzIjpbIlZJRVdFUiJdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTpyZWFkIl0sInNjb3BlIjoib3BlbmlkIGNvbmZvcm1hbmNlOnJlYWQiLCJhdXRoX3RpbWUiOjE3NjcyMjU0ODAsImlzcyI6Imh0dHBzOi8vb2F0aHdlYXZlLmV4YW1wbGUudGVzdCIsImF1ZCI6ImNvbmZvcm1hbmNlIiwic3ViIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0IiwianRpIjoiY29uZm9ybWFuY2UtdXNlci1zdWJqZWN0LTE3NjcyMjU1NDAtMTc2NzIyOTE0MCIsImlhdCI6MTc2NzIyNTU0MCwiZXhwIjoxNzY3MjI5MTQwfQ.Bse6FyPkiHXCRdtFFuMKxUpCoSVRp0-WrKTaD3FFo8bKcewHe67hyFKpXHJNulnrtExvDzRD1V0tJKhTQPnyzxGp-GarlwPKIrQuu6aMOOTkSdFdVzgZSCK8yhvL5xZPbtkD8EiX5o7t7aWKZ4XadvAWL0B-FKXTaPCOTIMhNMbi3OX3l0xgiRWZcfPiSLgJQ2ExAzuYYyh-ick6fqY9raLjuDnp4Kms3X2VxYvPzoiV5h8HfPsa_sPJDnlYOx4n1UKWpIyaN1tR0wuKo_1pBGnOLwpN4_yTgbMWo3vLI-99ONmDEQc7iVYyOGjdKWqT0cNPhee0XcpMGfzty4DRmg",
|
|
156
159
|
"expect": {
|
|
157
160
|
"outcome": "reject",
|
|
158
161
|
"status": 401,
|
|
@@ -163,12 +166,53 @@
|
|
|
163
166
|
"name": "user-without-session",
|
|
164
167
|
"description": "A USER token with no session cannot be revoked and is refused.",
|
|
165
168
|
"principal": "user",
|
|
166
|
-
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwidXNlcm5hbWUiOiJjb25mb3JtYW5jZS11c2VyIiwicm9sZXMiOlsiVklFV0VSIl0sInBlcm1pc3Npb25zIjpbImNvbmZvcm1hbmNlOnJlYWQiXSwic2NvcGUiOiJvcGVuaWQgY29uZm9ybWFuY2U6cmVhZCIsImF1dGhfdGltZSI6MTc2NzIyNTQ4MCwiaXNzIjoiaHR0cHM6Ly9vYXRod2VhdmUuZXhhbXBsZS50ZXN0IiwiYXVkIjoiY29uZm9ybWFuY2UiLCJzdWIiOiJjb25mb3JtYW5jZS11c2VyLXN1YmplY3QiLCJqdGkiOiJjb25mb3JtYW5jZS11c2VyLXN1YmplY3QtMTc2NzIyNTU0MC0xNzY3MjI2NDQwIiwiaWF0IjoxNzY3MjI1NTQwLCJleHAiOjE3NjcyMjY0NDB9.
|
|
169
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJVU0VSIiwiY2xpZW50SWQiOiJoaXZleHVpIiwidXNlcm5hbWUiOiJjb25mb3JtYW5jZS11c2VyIiwicm9sZXMiOlsiVklFV0VSIl0sInBlcm1pc3Npb25zIjpbImNvbmZvcm1hbmNlOnJlYWQiXSwic2NvcGUiOiJvcGVuaWQgY29uZm9ybWFuY2U6cmVhZCIsImF1dGhfdGltZSI6MTc2NzIyNTQ4MCwiaXNzIjoiaHR0cHM6Ly9vYXRod2VhdmUuZXhhbXBsZS50ZXN0IiwiYXVkIjoiY29uZm9ybWFuY2UiLCJzdWIiOiJjb25mb3JtYW5jZS11c2VyLXN1YmplY3QiLCJqdGkiOiJjb25mb3JtYW5jZS11c2VyLXN1YmplY3QtMTc2NzIyNTU0MC0xNzY3MjI2NDQwIiwiaWF0IjoxNzY3MjI1NTQwLCJleHAiOjE3NjcyMjY0NDB9.QoN7my4VGDehZNulCrZCXdq4d01trKuSzKYofoZdzsDOlP5tjCHPxlnKVDmvIh6NXahlV3Yxauj4w6_Q_7WPytXTvJqAHF7VlqZihvMk7Qt0YPD12vjkMHC7yJSRqOiCilvAhH2nzYV6gwbHUCx_dXLhsrH3X5mWFinllLj_yrvqR7n0IXLB8oUzBNii6tcDwVXSsAtyIm0pd876XjvSfOQcAZ2kHznUtRgIybX_w3kWTnfGAEdF6qE9ksWY6RNr063pv__5UFfXDOQdMaqxpO6TA6Vonoq0e0X0vW8DIWKyxAt_rZCE1ju_pX2wP29Lt5Ja6wMRTZWs6K2yRoy8-A",
|
|
167
170
|
"expect": {
|
|
168
171
|
"outcome": "reject",
|
|
169
172
|
"status": 401,
|
|
170
173
|
"reason": "invalid_claims"
|
|
171
174
|
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"name": "valid-native-service",
|
|
178
|
+
"description": "A client-credentials token signed with the configured native issuer and the same keys.",
|
|
179
|
+
"principal": "service",
|
|
180
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJTRVJWSUNFX0FDQ09VTlQiLCJjbGllbnRJZCI6ImNvbmZvcm1hbmNlLXNlcnZpY2UiLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwicm9sZXMiOltdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTp3cml0ZSJdLCJzY29wZSI6ImNvbmZvcm1hbmNlOndyaXRlIiwiaXNzIjoiY29uZm9ybWFuY2UtaWFtIiwiYXVkIjoiY29uZm9ybWFuY2UiLCJzdWIiOiJjb25mb3JtYW5jZS1zZXJ2aWNlIiwianRpIjoiY29uZm9ybWFuY2Utc2VydmljZS0xNzY3MjI1NTcwLTE3NjcyMjU4NzAiLCJpYXQiOjE3NjcyMjU1NzAsImV4cCI6MTc2NzIyNTg3MH0.tzDv8M9EYauZzS1hVvgoW5miWyRdnlatpGh6QfIxvxn2b8qG90FMQmLdPZffcXiD5T4gAq0JGO_LGiaPeY8rp7Jq_mz4RZXIAvVvQ_2hjC_o7nh8mu6KWtbqAfK1UB6ZerMo3w_9T6Dcll1-irNzlSTGvjItY1dJJHMvq-y_rKY5c_GpWOEy6BIkqAZXbl5D2If_zZIJgonJ0S65fVbG-rHUxHzlL7G4UrDO6KplCqVJbU_wBhmS7Tcgrpb2RX_tUGXZkr1B5xkSnJ-gAlm5-1YEOw9V64wK94fRn6V2hdHP6xp3fQs444GRpnhl9YytlW5bwzV9Il7h5lGvoW_Tgg",
|
|
181
|
+
"expect": {
|
|
182
|
+
"outcome": "accept",
|
|
183
|
+
"kind": "service",
|
|
184
|
+
"subject": "conformance-service",
|
|
185
|
+
"clientId": "conformance-service",
|
|
186
|
+
"roles": [],
|
|
187
|
+
"permissions": [
|
|
188
|
+
"conformance:write"
|
|
189
|
+
],
|
|
190
|
+
"scopes": [
|
|
191
|
+
"conformance:write"
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"name": "unconfigured-native-issuer",
|
|
197
|
+
"description": "A client-credentials token from a native issuer that is not configured.",
|
|
198
|
+
"principal": "service",
|
|
199
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJTRVJWSUNFX0FDQ09VTlQiLCJjbGllbnRJZCI6ImNvbmZvcm1hbmNlLXNlcnZpY2UiLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwicm9sZXMiOltdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTp3cml0ZSJdLCJzY29wZSI6ImNvbmZvcm1hbmNlOndyaXRlIiwiaXNzIjoiaW1wb3N0b3ItaWFtIiwiYXVkIjoiY29uZm9ybWFuY2UiLCJzdWIiOiJjb25mb3JtYW5jZS1zZXJ2aWNlIiwianRpIjoiY29uZm9ybWFuY2Utc2VydmljZS0xNzY3MjI1NTcwLTE3NjcyMjU4NzAiLCJpYXQiOjE3NjcyMjU1NzAsImV4cCI6MTc2NzIyNTg3MH0.au69pXP0GSRHLRlcE3mF3_pquzd7OK2_2xkfZNJPmvBzdkgDG2-xTk3oInpgYdEqBS2gHyi9P2ZbkEM3UhhD0sgDNudQYZQsYSzaptX17oQYEJAk_3fnPL9WMUlmEqt0ifpOaXmIkSq-gPu9G3GXP1VMtZPpueuMBRshQQncj7bs5amNTck-48MaR8IY7LY95ghzkczgVOdO783lqjPu0u1tvYKwLe7MP8I8DxKzYwEhn7aHYlL_f6a1i6QAmrq_D55_M39CEff-AZC6koqo7SlaeQcJpj6dJSpZIDwbbHUdlzCmpzwWDLlEqnJOEoRdw_teSP0223Xd-LwNkJfSnw",
|
|
200
|
+
"expect": {
|
|
201
|
+
"outcome": "reject",
|
|
202
|
+
"status": 401,
|
|
203
|
+
"reason": "invalid_issuer"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"name": "native-service-lifetime-too-long",
|
|
208
|
+
"description": "A one-hour client-credentials token from the native issuer.",
|
|
209
|
+
"principal": "service",
|
|
210
|
+
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvbmZvcm1hbmNlLTEiLCJ0eXAiOiJKV1QifQ.eyJhY3RvclR5cGUiOiJTRVJWSUNFX0FDQ09VTlQiLCJjbGllbnRJZCI6ImNvbmZvcm1hbmNlLXNlcnZpY2UiLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwicm9sZXMiOltdLCJwZXJtaXNzaW9ucyI6WyJjb25mb3JtYW5jZTp3cml0ZSJdLCJzY29wZSI6ImNvbmZvcm1hbmNlOndyaXRlIiwiaXNzIjoiY29uZm9ybWFuY2UtaWFtIiwiYXVkIjoiY29uZm9ybWFuY2UiLCJzdWIiOiJjb25mb3JtYW5jZS1zZXJ2aWNlIiwianRpIjoiY29uZm9ybWFuY2Utc2VydmljZS0xNzY3MjI1NTcwLTE3NjcyMjkxNzAiLCJpYXQiOjE3NjcyMjU1NzAsImV4cCI6MTc2NzIyOTE3MH0.d0o46Nuw0c29NsGRaeCrdaIN2tUoUX5tLech0f1sIwMw3qxGYPWIyVkhrefmGOWkOW2gP3HMF7UhNiUo26suDSnIRB5qwlZQ48rqA4ymRYWbeT_cfArXU_F-7tUOTgnaMSe8r6TqbvaGfQjJciuv_NULkta_Q7mrFz6hYHxu2beDoFP1xrlBOIqUxzYpBznAUro08cjIumN-kMP29D_v3MaQac0ea4_C1ZBtdUd-VISe756pIv9qygtTal3QG3nU00OrKTKNEgNNmmjMmX6C3On5rSD4LFZhsFXfkvIBS6KJWHSHs2fLTC9V2m0EIJI5oHtOYYN6ij4YEaVE9Vi1gw",
|
|
211
|
+
"expect": {
|
|
212
|
+
"outcome": "reject",
|
|
213
|
+
"status": 401,
|
|
214
|
+
"reason": "invalid_lifetime"
|
|
215
|
+
}
|
|
172
216
|
}
|
|
173
217
|
]
|
|
174
218
|
}
|