@owlmeans/oidc 0.1.18-rc.50 → 0.1.18-rc.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/oidc/SKILL.md +37 -9
- package/build/consts.d.ts +18 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +18 -0
- package/build/consts.js.map +1 -1
- package/build/entrypoints.d.ts +6 -0
- package/build/entrypoints.d.ts.map +1 -1
- package/build/entrypoints.js +7 -2
- package/build/entrypoints.js.map +1 -1
- package/build/guard.d.ts.map +1 -1
- package/build/guard.js +6 -0
- package/build/guard.js.map +1 -1
- package/build/models.d.ts +2 -1
- package/build/models.d.ts.map +1 -1
- package/build/models.js +10 -1
- package/build/models.js.map +1 -1
- package/build/types.d.ts +52 -2
- package/build/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/consts.ts +25 -0
- package/src/entrypoints.ts +19 -2
- package/src/guard.ts +10 -3
- package/src/models.ts +16 -2
- package/src/types.ts +58 -2
- package/tests/entrypoints.spec.ts +29 -0
- package/tests/guard.spec.ts +64 -8
- package/tests/models.spec.ts +42 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Shared OIDC protocol abstractions — guard/gate aliases, models, and immutable
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
bun add @owlmeans/oidc@^0.1.18-rc.
|
|
15
|
+
bun add @owlmeans/oidc@^0.1.18-rc.52
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
@@ -95,7 +95,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
95
95
|
your project's skill store (`.agents/skills/`):
|
|
96
96
|
|
|
97
97
|
```sh
|
|
98
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
98
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.46
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/oidc",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "0.1.18-rc.52",
|
|
5
|
+
"generatedAt": "2026-10-03T11:19:50.782Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oidc
|
|
3
|
-
description: How to use @owlmeans/oidc — the OIDC names both sides share — the OIDC_GATE alias, the guard, the requested-scope contract, provider descriptors, the dispatcher entrypoints and the error query params. Auto-invoked when importing OIDC types or constants, wiring OIDC into a protocol gate, or declaring an identity provider in configuration.
|
|
3
|
+
description: How to use @owlmeans/oidc — the OIDC names both sides share — the OIDC_GATE alias, the guard, the requested-scope contract, provider descriptors, the dispatcher entrypoints, the organizations scope and switch protocols, and the error query params. Auto-invoked when importing OIDC types or constants, wiring OIDC into a protocol gate, or declaring an identity provider in configuration.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/oidc
|
|
9
9
|
|
|
10
10
|
**Layer:** Core
|
|
11
|
-
**Install:** `"@owlmeans/oidc": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/oidc": "^0.1.18-rc.52"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
The shared half of the OIDC stack: no transport, no library. It owns the names, the shapes and the
|
|
14
14
|
guard that the server relying party (`@owlmeans/server-oidc-rp`), the browser relying party
|
|
@@ -23,13 +23,16 @@ guard that the server relying party (`@owlmeans/server-oidc-rp`), the browser re
|
|
|
23
23
|
| `OIDC_GUARD_CACHE` | A resource-alias constant nothing reads — the guard has no cache lookup, so registering a resource under it changes no behaviour |
|
|
24
24
|
| `makeOidcGuard(opts?)` / `appendOidcGuard(context, opts?)` | The wrapped-token guard, and its registration. The target-specific packages wrap these |
|
|
25
25
|
| `withOidcGuard(protocolTree, coguards?)` | Returns a decorated immutable protocol tree, prepending `OIDC_GUARD` to selected guarded declarations (default `DEFAULT_GUARD`) |
|
|
26
|
-
| `oidcProtocols` | Shared
|
|
27
|
-
| `DISPATCHER_OIDC_INIT`, `DISPATCHER_OIDC` | Their aliases |
|
|
26
|
+
| `oidcProtocols` | Shared declarations: the sign-in pair `init` (`POST /authenticate/oidc/init`) and `authenticate` (`POST /authenticate/oidc/process`), and the organization switch `organizations` (`GET /authenticate/oidc/organizations`) and `organization` (`POST /authenticate/oidc/organization`, body `{ entitySlug }`), both guarded by `OIDC_GUARD` |
|
|
27
|
+
| `DISPATCHER_OIDC_INIT`, `DISPATCHER_OIDC`, `DISPATCHER_OIDC_ORGANIZATIONS`, `DISPATCHER_OIDC_ORGANIZATION` | Their aliases |
|
|
28
28
|
| `OIDC_WRAPPED_TOKEN`, `WRAPPED_OIDC` | The authorization-header scheme for an OIDC-wrapped token, and the service alias that refreshes one |
|
|
29
29
|
| `OIDC_CLIENT_AUTH`, `GOOGLE_CLIENT_AUTH` | Authentication plugin types — the generic OIDC one and the Google one |
|
|
30
30
|
| `GOOGLE_SERVICE` | Provider service value for Google (`'google'`) |
|
|
31
31
|
| `OIDC_RP_BASE_SCOPES` / `OIDC_RP_BASE_SCOPE` | The scopes every OwlMeans RP requests, as array / space-delimited string |
|
|
32
32
|
| `EMAIL_SCOPE`, `PERMISSIONS_SCOPE`, `PERMISSIONS_CLAIM` | The standard email scope; the integrated-IAM grant scope and the claim it produces |
|
|
33
|
+
| `ORGANIZATIONS_SCOPE`, `ORGANIZATIONS_CLAIM` | `'organizations'` — the tenanted-client scope and the claim carrying `OidcOrganizationClaim[]` |
|
|
34
|
+
| `ORGANIZATION_REFUSAL`, `ORGANIZATION_OWNER_REFUSAL` | `AuthForbidden` reasons: not one of the subject's organizations; the action needs its owner |
|
|
35
|
+
| `IAM_API_METADATA` | `'owlmeans_iam_api'` — the discovery-document field naming the provider's runtime IAM API |
|
|
33
36
|
| `OIDC_CODE_QUERY`, `OIDC_ERROR_QUERY`, `OIDC_ERROR_DESCRIPTION_QUERY` | Redirect-URI params an authorization server sets on success and on failure |
|
|
34
37
|
| `INTERACTION`, `INTERACTION_PATH`, `INTERACTION_UID` | Interaction screen alias, path, and the uid path param the path must declare |
|
|
35
38
|
| `PROVIDER_INTERACTION`, `OIDC_AUTHEN_MODULE`, `OIDC_FLOW` | The provider-side interaction alias, the IAM authentication entrypoint alias, and the flow name |
|
|
@@ -39,10 +42,14 @@ guard that the server relying party (`@owlmeans/server-oidc-rp`), the browser re
|
|
|
39
42
|
| `OidcSharedConfig` / `WithSharedConfig` | `cfg.oidc`: `providers`, `restrictedProviders`, `clientCookie.interaction` |
|
|
40
43
|
| `OidcIamConfig` | `{ iamMode?: 'keycloak' \| 'integrated' }` — the IAM-mode seam, kept here to avoid a cycle with `@owlmeans/iam` |
|
|
41
44
|
| `OidcGuard`, `OidcGuardOptions` | The guard service type, and `{ coguards, cache?, tokenService? }` — only `coguards` (its **first** entry, default `DEFAULT_GUARD`) and `tokenService` are read; `cache` is declared and never used |
|
|
42
|
-
| `OIDCAuthInitParams`, `OIDCClientAuthPayload`, `OIDCTokenUpdate`, `CommonTokenSetParams` | The dispatcher wire shapes |
|
|
43
|
-
| `OIDCAuthInitParamsSchema`, `OIDCClientAuthPayloadSchema`, `OIDCTokenUpdateSchema`, `ProviderProfileDetailsSchema` | Their AJV schemas |
|
|
45
|
+
| `OIDCAuthInitParams`, `OIDCClientAuthPayload`, `OIDCTokenUpdate`, `CommonTokenSetParams` | The dispatcher wire shapes; init's `entitySlug?` is the organization the person asks to act in |
|
|
46
|
+
| `OIDCAuthInitParamsSchema`, `OIDCClientAuthPayloadSchema`, `OIDCTokenUpdateSchema`, `ProviderProfileDetailsSchema`, `OidcOrganizationSwitchSchema` | Their AJV schemas — closed (the server's AJV strips an undeclared field silently, so every field a caller sends is declared), except `OIDCClientAuthPayloadSchema`, which admits any further string param of the provider's redirect |
|
|
47
|
+
| `OidcOrganizationClaim` | `{ entitySlug, entityKey, title?, owner, groups?, home? }` — one organization of the subject as the provider claims it; `entityKey` is the frozen IAM key and never leaves a server |
|
|
48
|
+
| `OidcPermissionSetClaim` | `PermissionSet` + `entitySlug?` — a set bound to that organization |
|
|
49
|
+
| `OidcOrganizationItem`, `OidcOrganizationList`, `OidcOrganizationSwitch` | The switch's wire shapes: `{ entitySlug, title?, owner, groups?, home?, acting }` items (no key), the `{ items }` list, the `{ entitySlug }` body |
|
|
50
|
+
| `WrappedOIDCUpdate` | `AuthToken` + `entity?: ResolvedEntity` — what `WrappedOIDCService.update` answers |
|
|
44
51
|
| `ProviderProfileDetails`, `OidcUserDetails`, `OidcProviderSettings` | What an identity provider reports about a subject |
|
|
45
|
-
| `WrappedOIDCService` | The `AuthorizationService` that keeps a wrapped token fresh |
|
|
52
|
+
| `WrappedOIDCService` | The `AuthorizationService` that keeps a wrapped token fresh; `update` answers a `WrappedOIDCUpdate` |
|
|
46
53
|
|
|
47
54
|
## Usage
|
|
48
55
|
|
|
@@ -61,8 +68,7 @@ protocol(
|
|
|
61
68
|
|
|
62
69
|
The verification itself happens in `@owlmeans/server-oidc-rp` (server) and `@owlmeans/web-oidc-rp`
|
|
63
70
|
(browser); this package gives both sides one name to refer to. `OIDC_GATE` is for OIDC/UMA-style
|
|
64
|
-
authorization
|
|
65
|
-
resources declares its own gate alias instead.
|
|
71
|
+
authorization only (`server-oidc-rp` → Rules).
|
|
66
72
|
|
|
67
73
|
## Scope names are a cross-package contract
|
|
68
74
|
|
|
@@ -77,6 +83,28 @@ server for, and two sides read it from here:
|
|
|
77
83
|
Never hardcode a scope string on either side. Adding one to `OIDC_RP_BASE_SCOPES` must widen every
|
|
78
84
|
client allowlist derived from it in the same change.
|
|
79
85
|
|
|
86
|
+
## Organizations of a tenanted client
|
|
87
|
+
|
|
88
|
+
A client that asks for `ORGANIZATIONS_SCOPE` gets two claims from the integrated provider: the
|
|
89
|
+
`organizations` claim (`OidcOrganizationClaim[]`) and the FULL `permissions` claim, in which a set
|
|
90
|
+
bound to an organization carries `entitySlug`. A client that never asks gets the flattened claim of
|
|
91
|
+
the one organization it acts in and no `organizations` claim — so the scope is what turns tenancy
|
|
92
|
+
on, and a single-organization client never sees an `organizations` claim or a set carrying
|
|
93
|
+
`entitySlug`.
|
|
94
|
+
|
|
95
|
+
Only the relying party's server sees the claims; what the session keeps and what the browser token
|
|
96
|
+
carries is governed by `server-oidc-rp` → "Organizations".
|
|
97
|
+
|
|
98
|
+
The guard attaches `updated.entity` to the request whenever the wrapping service answers one. A
|
|
99
|
+
relying party of a tenanted client has no organization registry of its own, so this is the only
|
|
100
|
+
way `req.entity` gets set there; `attachEntity` (`@owlmeans/auth-common`) keeps it only while its
|
|
101
|
+
`slug` is exactly the token's.
|
|
102
|
+
|
|
103
|
+
The switch is two protocols guarded by `OIDC_GUARD` alone — they act on the session the wrapped
|
|
104
|
+
token names, after the guard has validated it against the provider. There is no per-request
|
|
105
|
+
organization selector: the server's AJV strips unknown query keys, and the session, not the
|
|
106
|
+
request, decides which organization a request acts in.
|
|
107
|
+
|
|
80
108
|
## Provider descriptors
|
|
81
109
|
|
|
82
110
|
`cfg.oidc.providers` is the list. `discoveryUrl` is the canonical form — the fully-qualified issuer,
|
package/build/consts.d.ts
CHANGED
|
@@ -14,6 +14,10 @@ export declare const OIDC_GUARD = "guard:oidc";
|
|
|
14
14
|
export declare const OIDC_GUARD_CACHE = "resource:oidc-guard:cache";
|
|
15
15
|
export declare const DISPATCHER_OIDC_INIT = "dispatcher:oidc:init";
|
|
16
16
|
export declare const DISPATCHER_OIDC = "dispatcher:oidc:authenticate";
|
|
17
|
+
/** Lists the organizations the session's subject belongs to. */
|
|
18
|
+
export declare const DISPATCHER_OIDC_ORGANIZATIONS = "dispatcher:oidc:organizations";
|
|
19
|
+
/** Switches the organization the session acts in. */
|
|
20
|
+
export declare const DISPATCHER_OIDC_ORGANIZATION = "dispatcher:oidc:organization";
|
|
17
21
|
export declare const OIDC_CODE_QUERY = "code";
|
|
18
22
|
/**
|
|
19
23
|
* Query params an authorization server sets on the redirect URI when the request failed
|
|
@@ -42,4 +46,18 @@ export declare const OIDC_RP_BASE_SCOPES: string[];
|
|
|
42
46
|
export declare const OIDC_RP_BASE_SCOPE: string;
|
|
43
47
|
/** Token/userinfo claim carrying the subject's PermissionSet[] for the requesting client. */
|
|
44
48
|
export declare const PERMISSIONS_CLAIM = "permissions";
|
|
49
|
+
/**
|
|
50
|
+
* OIDC scope of a tenanted client: the provider answers it with the subject's organizations and
|
|
51
|
+
* the full permission claim, bound sets included. A client that never asks for it gets the
|
|
52
|
+
* flattened claim of the one organization it acts in, exactly as before the scope existed.
|
|
53
|
+
*/
|
|
54
|
+
export declare const ORGANIZATIONS_SCOPE = "organizations";
|
|
55
|
+
/** Token/userinfo claim carrying the subject's `OidcOrganizationClaim[]`. */
|
|
56
|
+
export declare const ORGANIZATIONS_CLAIM = "organizations";
|
|
57
|
+
/** `AuthForbidden` reason: the organization named is not one the subject belongs to. */
|
|
58
|
+
export declare const ORGANIZATION_REFUSAL = "organization";
|
|
59
|
+
/** `AuthForbidden` reason: the action needs the organization's owner. */
|
|
60
|
+
export declare const ORGANIZATION_OWNER_REFUSAL = "organization:owner";
|
|
61
|
+
/** Discovery-document field carrying the base URL of the provider's runtime IAM API. */
|
|
62
|
+
export declare const IAM_API_METADATA = "owlmeans_iam_api";
|
|
45
63
|
//# sourceMappingURL=consts.d.ts.map
|
package/build/consts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,YAAY,SAAS,CAAA;AAElC,eAAO,MAAM,aAAa,gBAAgB,CAAA;AAE1C,6FAA6F;AAC7F,eAAO,MAAM,eAAe,QAAQ,CAAA;AAEpC,eAAO,MAAM,gBAAgB,sBAA4C,CAAA;AAEzE,eAAO,MAAM,WAAW,qBAAqB,CAAA;AAE7C,eAAO,MAAM,SAAS,SAAS,CAAA;AAE/B,eAAO,MAAM,kBAAkB,oBAAoB,CAAA;AAEnD,eAAO,MAAM,oBAAoB,4BAA4B,CAAA;AAE7D,eAAO,MAAM,gBAAgB,gBAAgB,CAAA;AAE7C,eAAO,MAAM,kBAAkB,iBAAiB,CAAA;AAGhD,eAAO,MAAM,cAAc,WAAW,CAAA;AAEtC,eAAO,MAAM,UAAU,eAAe,CAAA;AAEtC,eAAO,MAAM,gBAAgB,8BAA8B,CAAA;AAE3D,eAAO,MAAM,oBAAoB,yBAAyB,CAAA;AAE1D,eAAO,MAAM,eAAe,iCAAiC,CAAA;AAE7D,eAAO,MAAM,eAAe,SAAS,CAAA;AAErC;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,UAAU,CAAA;AAEvC,eAAO,MAAM,4BAA4B,sBAAsB,CAAA;AAE/D,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD,eAAO,MAAM,YAAY,uBAAuB,CAAA;AAEhD,eAAO,MAAM,SAAS,cAAc,CAAA;AAEpC,+EAA+E;AAC/E,eAAO,MAAM,WAAW,UAAU,CAAA;AAElC,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,gBAAgB,CAAA;AAE9C;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,UAAqC,CAAA;AAErE,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,QAAgC,CAAA;AAE/D,6FAA6F;AAC7F,eAAO,MAAM,iBAAiB,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,YAAY,SAAS,CAAA;AAElC,eAAO,MAAM,aAAa,gBAAgB,CAAA;AAE1C,6FAA6F;AAC7F,eAAO,MAAM,eAAe,QAAQ,CAAA;AAEpC,eAAO,MAAM,gBAAgB,sBAA4C,CAAA;AAEzE,eAAO,MAAM,WAAW,qBAAqB,CAAA;AAE7C,eAAO,MAAM,SAAS,SAAS,CAAA;AAE/B,eAAO,MAAM,kBAAkB,oBAAoB,CAAA;AAEnD,eAAO,MAAM,oBAAoB,4BAA4B,CAAA;AAE7D,eAAO,MAAM,gBAAgB,gBAAgB,CAAA;AAE7C,eAAO,MAAM,kBAAkB,iBAAiB,CAAA;AAGhD,eAAO,MAAM,cAAc,WAAW,CAAA;AAEtC,eAAO,MAAM,UAAU,eAAe,CAAA;AAEtC,eAAO,MAAM,gBAAgB,8BAA8B,CAAA;AAE3D,eAAO,MAAM,oBAAoB,yBAAyB,CAAA;AAE1D,eAAO,MAAM,eAAe,iCAAiC,CAAA;AAE7D,gEAAgE;AAChE,eAAO,MAAM,6BAA6B,kCAAkC,CAAA;AAE5E,qDAAqD;AACrD,eAAO,MAAM,4BAA4B,iCAAiC,CAAA;AAE1E,eAAO,MAAM,eAAe,SAAS,CAAA;AAErC;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,UAAU,CAAA;AAEvC,eAAO,MAAM,4BAA4B,sBAAsB,CAAA;AAE/D,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD,eAAO,MAAM,YAAY,uBAAuB,CAAA;AAEhD,eAAO,MAAM,SAAS,cAAc,CAAA;AAEpC,+EAA+E;AAC/E,eAAO,MAAM,WAAW,UAAU,CAAA;AAElC,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,gBAAgB,CAAA;AAE9C;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,UAAqC,CAAA;AAErE,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,QAAgC,CAAA;AAE/D,6FAA6F;AAC7F,eAAO,MAAM,iBAAiB,gBAAgB,CAAA;AAE9C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,kBAAkB,CAAA;AAElD,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,kBAAkB,CAAA;AAElD,wFAAwF;AACxF,eAAO,MAAM,oBAAoB,iBAAiB,CAAA;AAElD,yEAAyE;AACzE,eAAO,MAAM,0BAA0B,uBAAuB,CAAA;AAE9D,wFAAwF;AACxF,eAAO,MAAM,gBAAgB,qBAAqB,CAAA"}
|
package/build/consts.js
CHANGED
|
@@ -16,6 +16,10 @@ export const OIDC_GUARD = 'guard:oidc';
|
|
|
16
16
|
export const OIDC_GUARD_CACHE = 'resource:oidc-guard:cache';
|
|
17
17
|
export const DISPATCHER_OIDC_INIT = 'dispatcher:oidc:init';
|
|
18
18
|
export const DISPATCHER_OIDC = 'dispatcher:oidc:authenticate';
|
|
19
|
+
/** Lists the organizations the session's subject belongs to. */
|
|
20
|
+
export const DISPATCHER_OIDC_ORGANIZATIONS = 'dispatcher:oidc:organizations';
|
|
21
|
+
/** Switches the organization the session acts in. */
|
|
22
|
+
export const DISPATCHER_OIDC_ORGANIZATION = 'dispatcher:oidc:organization';
|
|
19
23
|
export const OIDC_CODE_QUERY = 'code';
|
|
20
24
|
/**
|
|
21
25
|
* Query params an authorization server sets on the redirect URI when the request failed
|
|
@@ -44,4 +48,18 @@ export const OIDC_RP_BASE_SCOPES = ['openid', 'profile', EMAIL_SCOPE];
|
|
|
44
48
|
export const OIDC_RP_BASE_SCOPE = OIDC_RP_BASE_SCOPES.join(' ');
|
|
45
49
|
/** Token/userinfo claim carrying the subject's PermissionSet[] for the requesting client. */
|
|
46
50
|
export const PERMISSIONS_CLAIM = 'permissions';
|
|
51
|
+
/**
|
|
52
|
+
* OIDC scope of a tenanted client: the provider answers it with the subject's organizations and
|
|
53
|
+
* the full permission claim, bound sets included. A client that never asks for it gets the
|
|
54
|
+
* flattened claim of the one organization it acts in, exactly as before the scope existed.
|
|
55
|
+
*/
|
|
56
|
+
export const ORGANIZATIONS_SCOPE = 'organizations';
|
|
57
|
+
/** Token/userinfo claim carrying the subject's `OidcOrganizationClaim[]`. */
|
|
58
|
+
export const ORGANIZATIONS_CLAIM = 'organizations';
|
|
59
|
+
/** `AuthForbidden` reason: the organization named is not one the subject belongs to. */
|
|
60
|
+
export const ORGANIZATION_REFUSAL = 'organization';
|
|
61
|
+
/** `AuthForbidden` reason: the action needs the organization's owner. */
|
|
62
|
+
export const ORGANIZATION_OWNER_REFUSAL = 'organization:owner';
|
|
63
|
+
/** Discovery-document field carrying the base URL of the provider's runtime IAM API. */
|
|
64
|
+
export const IAM_API_METADATA = 'owlmeans_iam_api';
|
|
47
65
|
//# sourceMappingURL=consts.js.map
|
package/build/consts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAEvC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAA;AAElC,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAA;AAE1C,6FAA6F;AAC7F,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAA;AAEpC,MAAM,CAAC,MAAM,gBAAgB,GAAG,gBAAgB,KAAK,GAAG,eAAe,EAAE,CAAA;AAEzE,MAAM,CAAC,MAAM,WAAW,GAAG,kBAAkB,CAAA;AAE7C,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAA;AAE/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAA;AAEnD,MAAM,CAAC,MAAM,oBAAoB,GAAG,yBAAyB,CAAA;AAE7D,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAA;AAE7C,MAAM,CAAC,MAAM,kBAAkB,GAAG,cAAc,CAAA;AAEhD,2GAA2G;AAC3G,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAA;AAEtC,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAA;AAEtC,MAAM,CAAC,MAAM,gBAAgB,GAAG,2BAA2B,CAAA;AAE3D,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAA;AAE1D,MAAM,CAAC,MAAM,eAAe,GAAG,8BAA8B,CAAA;AAE7D,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAA;AAErC;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAA;AAEvC,MAAM,CAAC,MAAM,4BAA4B,GAAG,mBAAmB,CAAA;AAE/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAA;AAEtD,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAA;AAEhD,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAA;AAEpC,+EAA+E;AAC/E,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAA;AAElC,mFAAmF;AACnF,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAAA;AAE9C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,CAAA;AAErE,4FAA4F;AAC5F,MAAM,CAAC,MAAM,kBAAkB,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE/D,6FAA6F;AAC7F,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAEvC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAA;AAElC,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAA;AAE1C,6FAA6F;AAC7F,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAA;AAEpC,MAAM,CAAC,MAAM,gBAAgB,GAAG,gBAAgB,KAAK,GAAG,eAAe,EAAE,CAAA;AAEzE,MAAM,CAAC,MAAM,WAAW,GAAG,kBAAkB,CAAA;AAE7C,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAA;AAE/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAA;AAEnD,MAAM,CAAC,MAAM,oBAAoB,GAAG,yBAAyB,CAAA;AAE7D,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAA;AAE7C,MAAM,CAAC,MAAM,kBAAkB,GAAG,cAAc,CAAA;AAEhD,2GAA2G;AAC3G,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAA;AAEtC,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAA;AAEtC,MAAM,CAAC,MAAM,gBAAgB,GAAG,2BAA2B,CAAA;AAE3D,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAA;AAE1D,MAAM,CAAC,MAAM,eAAe,GAAG,8BAA8B,CAAA;AAE7D,gEAAgE;AAChE,MAAM,CAAC,MAAM,6BAA6B,GAAG,+BAA+B,CAAA;AAE5E,qDAAqD;AACrD,MAAM,CAAC,MAAM,4BAA4B,GAAG,8BAA8B,CAAA;AAE1E,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAA;AAErC;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAA;AAEvC,MAAM,CAAC,MAAM,4BAA4B,GAAG,mBAAmB,CAAA;AAE/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAA;AAEtD,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAA;AAEhD,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAA;AAEpC,+EAA+E;AAC/E,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAA;AAElC,mFAAmF;AACnF,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAAA;AAE9C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,CAAA;AAErE,4FAA4F;AAC5F,MAAM,CAAC,MAAM,kBAAkB,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE/D,6FAA6F;AAC7F,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAAA;AAE9C;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,eAAe,CAAA;AAElD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,mBAAmB,GAAG,eAAe,CAAA;AAElD,wFAAwF;AACxF,MAAM,CAAC,MAAM,oBAAoB,GAAG,cAAc,CAAA;AAElD,yEAAyE;AACzE,MAAM,CAAC,MAAM,0BAA0B,GAAG,oBAAoB,CAAA;AAE9D,wFAAwF;AACxF,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAA"}
|
package/build/entrypoints.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AuthToken } from '@owlmeans/auth';
|
|
2
|
+
import type { OidcOrganizationList } from './types.js';
|
|
1
3
|
/** Shared OIDC browser-to-server protocols, bound by the relying-party packages. */
|
|
2
4
|
export declare const oidcProtocols: {
|
|
3
5
|
init: import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
@@ -6,5 +8,9 @@ export declare const oidcProtocols: {
|
|
|
6
8
|
authenticate: import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
7
9
|
body: import("./types.js").OIDCClientAuthPayload;
|
|
8
10
|
}, any>;
|
|
11
|
+
organizations: import("@owlmeans/entrypoint").EntrypointProtocol<{}, OidcOrganizationList>;
|
|
12
|
+
organization: import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
13
|
+
body: import("./types.js").OidcOrganizationSwitch;
|
|
14
|
+
}, AuthToken>;
|
|
9
15
|
};
|
|
10
16
|
//# sourceMappingURL=entrypoints.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAK/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEtD,oFAAoF;AACpF,eAAO,MAAM,aAAa;IACxB,IAAI;;;IAIJ,YAAY;;;IAOZ,aAAa;IAKb,YAAY;;;CAKb,CAAA"}
|
package/build/entrypoints.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { contract, protocol, typed } from '@owlmeans/entrypoint';
|
|
2
2
|
import { route, RouteMethod, backend } from '@owlmeans/route';
|
|
3
|
-
import { DISPATCHER_OIDC, DISPATCHER_OIDC_INIT } from './consts.js';
|
|
4
|
-
import { OIDCAuthInitParamsSchema, OIDCClientAuthPayloadSchema } from './models.js';
|
|
3
|
+
import { DISPATCHER_OIDC, DISPATCHER_OIDC_INIT, DISPATCHER_OIDC_ORGANIZATION, DISPATCHER_OIDC_ORGANIZATIONS, OIDC_GUARD, } from './consts.js';
|
|
4
|
+
import { OIDCAuthInitParamsSchema, OIDCClientAuthPayloadSchema, OidcOrganizationSwitchSchema } from './models.js';
|
|
5
5
|
/** Shared OIDC browser-to-server protocols, bound by the relying-party packages. */
|
|
6
6
|
export const oidcProtocols = {
|
|
7
7
|
init: protocol(route(DISPATCHER_OIDC_INIT, '/authenticate/oidc/init', backend(null, RouteMethod.POST)), contract.request({ body: typed(OIDCAuthInitParamsSchema) }, typed())),
|
|
8
8
|
authenticate: protocol(route(DISPATCHER_OIDC, '/authenticate/oidc/process', backend(null, RouteMethod.POST)), contract.request({ body: typed(OIDCClientAuthPayloadSchema) }, typed())),
|
|
9
|
+
// The organization switch acts on the session the wrapped token names, so only that token's own
|
|
10
|
+
// guard can reach it — and it runs after the guard has refreshed the session against the
|
|
11
|
+
// provider, so the list it answers from is the provider's current one.
|
|
12
|
+
organizations: protocol(route(DISPATCHER_OIDC_ORGANIZATIONS, '/authenticate/oidc/organizations', backend(null, RouteMethod.GET)), contract(typed()), { guards: OIDC_GUARD }),
|
|
13
|
+
organization: protocol(route(DISPATCHER_OIDC_ORGANIZATION, '/authenticate/oidc/organization', backend(null, RouteMethod.POST)), contract.request({ body: typed(OidcOrganizationSwitchSchema) }, typed()), { guards: OIDC_GUARD }),
|
|
9
14
|
};
|
|
10
15
|
//# sourceMappingURL=entrypoints.js.map
|
package/build/entrypoints.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entrypoints.js","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAChE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"entrypoints.js","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAChE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAE7D,OAAO,EACL,eAAe,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,6BAA6B,EAAE,UAAU,GAC/G,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,wBAAwB,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAA;AAGjH,oFAAoF;AACpF,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,IAAI,EAAE,QAAQ,CACZ,KAAK,CAAC,oBAAoB,EAAE,yBAAyB,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,EACvF,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,wBAAwB,CAAC,EAAE,EAAE,KAAK,EAAO,CAAC,CAC1E;IACD,YAAY,EAAE,QAAQ,CACpB,KAAK,CAAC,eAAe,EAAE,4BAA4B,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,EACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,2BAA2B,CAAC,EAAE,EAAE,KAAK,EAAO,CAAC,CAC7E;IACD,gGAAgG;IAChG,yFAAyF;IACzF,uEAAuE;IACvE,aAAa,EAAE,QAAQ,CACrB,KAAK,CAAC,6BAA6B,EAAE,kCAAkC,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,EACxG,QAAQ,CAAC,KAAK,EAAwB,CAAC,EACvC,EAAE,MAAM,EAAE,UAAU,EAAE,CACvB;IACD,YAAY,EAAE,QAAQ,CACpB,KAAK,CAAC,4BAA4B,EAAE,iCAAiC,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,EACvG,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,4BAA4B,CAAC,EAAE,EAAE,KAAK,EAAa,CAAC,EACnF,EAAE,MAAM,EAAE,UAAU,EAAE,CACvB;CACF,CAAA"}
|
package/build/guard.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAyC,MAAM,YAAY,CAAA;AAGrH,OAAO,KAAK,EAAqC,cAAc,EAAgB,MAAM,sBAAsB,CAAA;AAQ3G,eAAO,MAAM,aAAa,UAAW,gBAAgB,KAAG,SA4GvD,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,WAC3D,CAAC,SAAS,gBAAgB,MAKpC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,IAAI,SAAS,cAAc,QACjD,IAAI,aAAa,MAAM,GAAG,SAAS,MAAM,EAAE,KAChD,IAcF,CAAA"}
|
package/build/guard.js
CHANGED
|
@@ -77,6 +77,12 @@ export const makeOidcGuard = (opts) => {
|
|
|
77
77
|
if (updated == null) {
|
|
78
78
|
return false;
|
|
79
79
|
}
|
|
80
|
+
// The provider names the organization this session acts in, and a relying party of a
|
|
81
|
+
// tenanted client has no registry to resolve it from by slug. `attachEntity` keeps this one
|
|
82
|
+
// only while its slug is the token's, so a stale attachment can never stand in for it.
|
|
83
|
+
if (updated.entity != null) {
|
|
84
|
+
req.entity = updated.entity;
|
|
85
|
+
}
|
|
80
86
|
if (updated.token !== token) {
|
|
81
87
|
const [, newAuth] = updated.token.split(' ');
|
|
82
88
|
const envelope = makeEnvelopeModel(newAuth, EnvelopeKind.Token);
|
package/build/guard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1E,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAE/E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
|
package/build/models.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { JSONSchemaType } from 'ajv';
|
|
2
|
-
import type { OIDCAuthInitParams, OIDCClientAuthPayload, OIDCTokenUpdate, ProviderProfileDetails } from './types.js';
|
|
2
|
+
import type { OIDCAuthInitParams, OIDCClientAuthPayload, OidcOrganizationSwitch, OIDCTokenUpdate, ProviderProfileDetails } from './types.js';
|
|
3
3
|
export declare const OIDCAuthInitParamsSchema: JSONSchemaType<OIDCAuthInitParams>;
|
|
4
4
|
export declare const OIDCClientAuthPayloadSchema: JSONSchemaType<OIDCClientAuthPayload>;
|
|
5
|
+
export declare const OidcOrganizationSwitchSchema: JSONSchemaType<OidcOrganizationSwitch>;
|
|
5
6
|
export declare const OIDCTokenUpdateSchema: JSONSchemaType<OIDCTokenUpdate>;
|
|
6
7
|
export declare const ProviderProfileDetailsSchema: JSONSchemaType<ProviderProfileDetails>;
|
|
7
8
|
//# sourceMappingURL=models.d.ts.map
|
package/build/models.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAA;AACzC,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAA;AACzC,OAAO,KAAK,EACV,kBAAkB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,eAAe,EAAE,sBAAsB,EAC3G,MAAM,YAAY,CAAA;AAKnB,eAAO,MAAM,wBAAwB,EAAE,cAAc,CAAC,kBAAkB,CASvE,CAAA;AAED,eAAO,MAAM,2BAA2B,EAAE,cAAc,CAAC,qBAAqB,CAQ7E,CAAA;AAED,eAAO,MAAM,4BAA4B,EAAE,cAAc,CAAC,sBAAsB,CAO/E,CAAA;AAED,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,eAAe,CAqBjE,CAAA;AAED,eAAO,MAAM,4BAA4B,EAAE,cAAc,CAAC,sBAAsB,CAe/E,CAAA"}
|
package/build/models.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { AuthTokenSchema, EntityValueSchema, IdValueSchema, ScopeValueSchema, TypeNameSchema } from '@owlmeans/auth';
|
|
1
|
+
import { AuthTokenSchema, EntitySlugValueSchema, EntityValueSchema, IdValueSchema, ScopeValueSchema, TypeNameSchema, } from '@owlmeans/auth';
|
|
2
2
|
export const OIDCAuthInitParamsSchema = {
|
|
3
3
|
type: 'object',
|
|
4
4
|
properties: {
|
|
5
5
|
entity: { ...EntityValueSchema, nullable: true },
|
|
6
6
|
profile: { ...IdValueSchema, nullable: true },
|
|
7
|
+
entitySlug: { ...EntitySlugValueSchema, nullable: true },
|
|
7
8
|
},
|
|
8
9
|
required: [],
|
|
9
10
|
additionalProperties: false,
|
|
@@ -17,6 +18,14 @@ export const OIDCClientAuthPayloadSchema = {
|
|
|
17
18
|
additionalProperties: { type: 'string', minLength: 0, maxLength: 512 },
|
|
18
19
|
required: ['code', 'authUrl'],
|
|
19
20
|
};
|
|
21
|
+
export const OidcOrganizationSwitchSchema = {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
entitySlug: { ...EntitySlugValueSchema },
|
|
25
|
+
},
|
|
26
|
+
required: ['entitySlug'],
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
};
|
|
20
29
|
export const OIDCTokenUpdateSchema = {
|
|
21
30
|
type: 'object',
|
|
22
31
|
properties: {
|
package/build/models.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { AuthToken } from '@owlmeans/auth';
|
|
1
|
+
import type { AuthToken, PermissionSet } from '@owlmeans/auth';
|
|
2
2
|
import type { AuthorizationService } from '@owlmeans/auth-common';
|
|
3
3
|
import type { BasicConfig, BasicContext } from '@owlmeans/context';
|
|
4
|
-
import type { GuardService } from '@owlmeans/entrypoint';
|
|
4
|
+
import type { GuardService, ResolvedEntity } from '@owlmeans/entrypoint';
|
|
5
5
|
export interface Config extends BasicConfig {
|
|
6
6
|
}
|
|
7
7
|
export interface Context<C extends Config = Config> extends BasicContext<C> {
|
|
@@ -73,12 +73,62 @@ export interface OidcGuardOptions {
|
|
|
73
73
|
export interface OIDCAuthInitParams {
|
|
74
74
|
entity?: string;
|
|
75
75
|
profile?: string;
|
|
76
|
+
/**
|
|
77
|
+
* The organization the person asked to act in after sign-in. Honoured only when the provider
|
|
78
|
+
* names it among the subject's organizations; otherwise the session starts in the home one.
|
|
79
|
+
*/
|
|
80
|
+
entitySlug?: string;
|
|
81
|
+
}
|
|
82
|
+
/** One organization of the subject, as the provider claims it under `ORGANIZATIONS_SCOPE`. */
|
|
83
|
+
export interface OidcOrganizationClaim {
|
|
84
|
+
entitySlug: string;
|
|
85
|
+
/**
|
|
86
|
+
* The organization's frozen IAM key — stable across renames, which is why a session remembers
|
|
87
|
+
* its acting organization by it. Server-side only: never copied into a browser token or into a
|
|
88
|
+
* response.
|
|
89
|
+
*/
|
|
90
|
+
entityKey: string;
|
|
91
|
+
title?: string;
|
|
92
|
+
owner: boolean;
|
|
93
|
+
groups?: string[];
|
|
94
|
+
home?: boolean;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A permission set as the provider claims it. A set carrying `entitySlug` is bound to that
|
|
98
|
+
* organization and applies only while the session acts in it; one without applies everywhere.
|
|
99
|
+
*/
|
|
100
|
+
export interface OidcPermissionSetClaim extends PermissionSet {
|
|
101
|
+
entitySlug?: string;
|
|
102
|
+
}
|
|
103
|
+
/** One organization of the session, as the switch lists it — the key never leaves the server. */
|
|
104
|
+
export interface OidcOrganizationItem {
|
|
105
|
+
entitySlug: string;
|
|
106
|
+
title?: string;
|
|
107
|
+
owner: boolean;
|
|
108
|
+
groups?: string[];
|
|
109
|
+
home?: boolean;
|
|
110
|
+
acting: boolean;
|
|
111
|
+
}
|
|
112
|
+
export interface OidcOrganizationList {
|
|
113
|
+
items: OidcOrganizationItem[];
|
|
114
|
+
}
|
|
115
|
+
export interface OidcOrganizationSwitch {
|
|
116
|
+
entitySlug: string;
|
|
76
117
|
}
|
|
77
118
|
export interface OIDCClientAuthPayload extends Record<string, string> {
|
|
78
119
|
code: string;
|
|
79
120
|
authUrl: string;
|
|
80
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* A refreshed wrapped token, plus the organization the session acts in when the provider names
|
|
124
|
+
* one. The guard attaches that entity to the request, because a relying party of a tenanted
|
|
125
|
+
* client has no resolver of its own that could find it by slug.
|
|
126
|
+
*/
|
|
127
|
+
export interface WrappedOIDCUpdate extends AuthToken {
|
|
128
|
+
entity?: ResolvedEntity;
|
|
129
|
+
}
|
|
81
130
|
export interface WrappedOIDCService extends AuthorizationService {
|
|
131
|
+
update: (token?: string | AuthToken, thr?: boolean) => Promise<WrappedOIDCUpdate | null>;
|
|
82
132
|
}
|
|
83
133
|
export interface OIDCTokenUpdate extends AuthToken {
|
|
84
134
|
tokenSet: CommonTokenSetParams;
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/oidc",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.52",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@owlmeans/auth": "^0.1.18-rc.
|
|
25
|
-
"@owlmeans/auth-common": "^0.1.18-rc.
|
|
26
|
-
"@owlmeans/basic-envelope": "^0.1.18-rc.
|
|
27
|
-
"@owlmeans/config": "^0.1.18-rc.
|
|
24
|
+
"@owlmeans/auth": "^0.1.18-rc.38",
|
|
25
|
+
"@owlmeans/auth-common": "^0.1.18-rc.44",
|
|
26
|
+
"@owlmeans/basic-envelope": "^0.1.18-rc.43",
|
|
27
|
+
"@owlmeans/config": "^0.1.18-rc.41",
|
|
28
28
|
"@owlmeans/context": "^0.1.18-rc.32",
|
|
29
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
29
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.39",
|
|
30
30
|
"@owlmeans/resource": "^0.1.18-rc.37",
|
|
31
31
|
"@owlmeans/route": "^0.1.18-rc.33"
|
|
32
32
|
},
|
package/src/consts.ts
CHANGED
|
@@ -33,6 +33,12 @@ export const DISPATCHER_OIDC_INIT = 'dispatcher:oidc:init'
|
|
|
33
33
|
|
|
34
34
|
export const DISPATCHER_OIDC = 'dispatcher:oidc:authenticate'
|
|
35
35
|
|
|
36
|
+
/** Lists the organizations the session's subject belongs to. */
|
|
37
|
+
export const DISPATCHER_OIDC_ORGANIZATIONS = 'dispatcher:oidc:organizations'
|
|
38
|
+
|
|
39
|
+
/** Switches the organization the session acts in. */
|
|
40
|
+
export const DISPATCHER_OIDC_ORGANIZATION = 'dispatcher:oidc:organization'
|
|
41
|
+
|
|
36
42
|
export const OIDC_CODE_QUERY = 'code'
|
|
37
43
|
|
|
38
44
|
/**
|
|
@@ -71,3 +77,22 @@ export const OIDC_RP_BASE_SCOPE = OIDC_RP_BASE_SCOPES.join(' ')
|
|
|
71
77
|
|
|
72
78
|
/** Token/userinfo claim carrying the subject's PermissionSet[] for the requesting client. */
|
|
73
79
|
export const PERMISSIONS_CLAIM = 'permissions'
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* OIDC scope of a tenanted client: the provider answers it with the subject's organizations and
|
|
83
|
+
* the full permission claim, bound sets included. A client that never asks for it gets the
|
|
84
|
+
* flattened claim of the one organization it acts in, exactly as before the scope existed.
|
|
85
|
+
*/
|
|
86
|
+
export const ORGANIZATIONS_SCOPE = 'organizations'
|
|
87
|
+
|
|
88
|
+
/** Token/userinfo claim carrying the subject's `OidcOrganizationClaim[]`. */
|
|
89
|
+
export const ORGANIZATIONS_CLAIM = 'organizations'
|
|
90
|
+
|
|
91
|
+
/** `AuthForbidden` reason: the organization named is not one the subject belongs to. */
|
|
92
|
+
export const ORGANIZATION_REFUSAL = 'organization'
|
|
93
|
+
|
|
94
|
+
/** `AuthForbidden` reason: the action needs the organization's owner. */
|
|
95
|
+
export const ORGANIZATION_OWNER_REFUSAL = 'organization:owner'
|
|
96
|
+
|
|
97
|
+
/** Discovery-document field carrying the base URL of the provider's runtime IAM API. */
|
|
98
|
+
export const IAM_API_METADATA = 'owlmeans_iam_api'
|
package/src/entrypoints.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { contract, protocol, typed } from '@owlmeans/entrypoint'
|
|
2
2
|
import { route, RouteMethod, backend } from '@owlmeans/route'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import type { AuthToken } from '@owlmeans/auth'
|
|
4
|
+
import {
|
|
5
|
+
DISPATCHER_OIDC, DISPATCHER_OIDC_INIT, DISPATCHER_OIDC_ORGANIZATION, DISPATCHER_OIDC_ORGANIZATIONS, OIDC_GUARD,
|
|
6
|
+
} from './consts.js'
|
|
7
|
+
import { OIDCAuthInitParamsSchema, OIDCClientAuthPayloadSchema, OidcOrganizationSwitchSchema } from './models.js'
|
|
8
|
+
import type { OidcOrganizationList } from './types.js'
|
|
5
9
|
|
|
6
10
|
/** Shared OIDC browser-to-server protocols, bound by the relying-party packages. */
|
|
7
11
|
export const oidcProtocols = {
|
|
@@ -13,4 +17,17 @@ export const oidcProtocols = {
|
|
|
13
17
|
route(DISPATCHER_OIDC, '/authenticate/oidc/process', backend(null, RouteMethod.POST)),
|
|
14
18
|
contract.request({ body: typed(OIDCClientAuthPayloadSchema) }, typed<any>()),
|
|
15
19
|
),
|
|
20
|
+
// The organization switch acts on the session the wrapped token names, so only that token's own
|
|
21
|
+
// guard can reach it — and it runs after the guard has refreshed the session against the
|
|
22
|
+
// provider, so the list it answers from is the provider's current one.
|
|
23
|
+
organizations: protocol(
|
|
24
|
+
route(DISPATCHER_OIDC_ORGANIZATIONS, '/authenticate/oidc/organizations', backend(null, RouteMethod.GET)),
|
|
25
|
+
contract(typed<OidcOrganizationList>()),
|
|
26
|
+
{ guards: OIDC_GUARD },
|
|
27
|
+
),
|
|
28
|
+
organization: protocol(
|
|
29
|
+
route(DISPATCHER_OIDC_ORGANIZATION, '/authenticate/oidc/organization', backend(null, RouteMethod.POST)),
|
|
30
|
+
contract.request({ body: typed(OidcOrganizationSwitchSchema) }, typed<AuthToken>()),
|
|
31
|
+
{ guards: OIDC_GUARD },
|
|
32
|
+
),
|
|
16
33
|
}
|
package/src/guard.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createService } from '@owlmeans/context'
|
|
2
|
-
import type { Config, Context, OidcGuard, OidcGuardOptions, WrappedOIDCService } from './types.js'
|
|
2
|
+
import type { Config, Context, OidcGuard, OidcGuardOptions, WrappedOIDCService, WrappedOIDCUpdate } from './types.js'
|
|
3
3
|
import { OIDC_GUARD, OIDC_WRAPPED_TOKEN, WRAPPED_OIDC } from './consts.js'
|
|
4
4
|
import { decorateEntrypoint, isEntrypointProtocol } from '@owlmeans/entrypoint'
|
|
5
5
|
import type { AbstractRequest, AbstractResponse, EntrypointTree, GuardService } from '@owlmeans/entrypoint'
|
|
6
6
|
import { DEFAULT_GUARD, TOKEN_UPDATE } from '@owlmeans/auth-common'
|
|
7
|
-
import { AUTH_HEADER, type Auth,
|
|
7
|
+
import { AUTH_HEADER, type Auth, AuthorizationError } from '@owlmeans/auth'
|
|
8
8
|
import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope'
|
|
9
9
|
import { trust } from '@owlmeans/auth-common/utils'
|
|
10
10
|
import { TRUSTED } from '@owlmeans/config'
|
|
@@ -77,7 +77,7 @@ export const makeOidcGuard = (opts?: OidcGuardOptions): OidcGuard => {
|
|
|
77
77
|
|
|
78
78
|
const token = extractAuthToken(req, OIDC_WRAPPED_TOKEN, false)!
|
|
79
79
|
|
|
80
|
-
let updated:
|
|
80
|
+
let updated: WrappedOIDCUpdate | null = null
|
|
81
81
|
|
|
82
82
|
try {
|
|
83
83
|
updated = await wrapper(ctx).update({ token }, true)
|
|
@@ -94,6 +94,13 @@ export const makeOidcGuard = (opts?: OidcGuardOptions): OidcGuard => {
|
|
|
94
94
|
return false as T
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// The provider names the organization this session acts in, and a relying party of a
|
|
98
|
+
// tenanted client has no registry to resolve it from by slug. `attachEntity` keeps this one
|
|
99
|
+
// only while its slug is the token's, so a stale attachment can never stand in for it.
|
|
100
|
+
if (updated.entity != null) {
|
|
101
|
+
req.entity = updated.entity
|
|
102
|
+
}
|
|
103
|
+
|
|
97
104
|
if (updated.token !== token) {
|
|
98
105
|
const [, newAuth] = updated.token.split(' ')
|
|
99
106
|
const envelope = makeEnvelopeModel<Auth>(newAuth, EnvelopeKind.Token)
|
package/src/models.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import type { JSONSchemaType } from 'ajv'
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type {
|
|
3
|
+
OIDCAuthInitParams, OIDCClientAuthPayload, OidcOrganizationSwitch, OIDCTokenUpdate, ProviderProfileDetails,
|
|
4
|
+
} from './types.js'
|
|
5
|
+
import {
|
|
6
|
+
AuthTokenSchema, EntitySlugValueSchema, EntityValueSchema, IdValueSchema, ScopeValueSchema, TypeNameSchema,
|
|
7
|
+
} from '@owlmeans/auth'
|
|
4
8
|
|
|
5
9
|
export const OIDCAuthInitParamsSchema: JSONSchemaType<OIDCAuthInitParams> = {
|
|
6
10
|
type: 'object',
|
|
7
11
|
properties: {
|
|
8
12
|
entity: { ...EntityValueSchema, nullable: true },
|
|
9
13
|
profile: { ...IdValueSchema, nullable: true },
|
|
14
|
+
entitySlug: { ...EntitySlugValueSchema, nullable: true },
|
|
10
15
|
},
|
|
11
16
|
required: [],
|
|
12
17
|
additionalProperties: false,
|
|
@@ -22,6 +27,15 @@ export const OIDCClientAuthPayloadSchema: JSONSchemaType<OIDCClientAuthPayload>
|
|
|
22
27
|
required: ['code', 'authUrl'],
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
export const OidcOrganizationSwitchSchema: JSONSchemaType<OidcOrganizationSwitch> = {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
entitySlug: { ...EntitySlugValueSchema },
|
|
34
|
+
},
|
|
35
|
+
required: ['entitySlug'],
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
export const OIDCTokenUpdateSchema: JSONSchemaType<OIDCTokenUpdate> = {
|
|
26
40
|
type: 'object',
|
|
27
41
|
properties: {
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { AuthToken } from '@owlmeans/auth'
|
|
1
|
+
import type { AuthToken, PermissionSet } from '@owlmeans/auth'
|
|
2
2
|
import type { AuthorizationService } from '@owlmeans/auth-common'
|
|
3
3
|
import type { BasicConfig, BasicContext } from '@owlmeans/context'
|
|
4
|
-
import type { GuardService } from '@owlmeans/entrypoint'
|
|
4
|
+
import type { GuardService, ResolvedEntity } from '@owlmeans/entrypoint'
|
|
5
5
|
|
|
6
6
|
export interface Config extends BasicConfig { }
|
|
7
7
|
export interface Context<C extends Config = Config> extends BasicContext<C> { }
|
|
@@ -84,6 +84,52 @@ export interface OidcGuardOptions {
|
|
|
84
84
|
export interface OIDCAuthInitParams {
|
|
85
85
|
entity?: string
|
|
86
86
|
profile?: string
|
|
87
|
+
/**
|
|
88
|
+
* The organization the person asked to act in after sign-in. Honoured only when the provider
|
|
89
|
+
* names it among the subject's organizations; otherwise the session starts in the home one.
|
|
90
|
+
*/
|
|
91
|
+
entitySlug?: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** One organization of the subject, as the provider claims it under `ORGANIZATIONS_SCOPE`. */
|
|
95
|
+
export interface OidcOrganizationClaim {
|
|
96
|
+
entitySlug: string
|
|
97
|
+
/**
|
|
98
|
+
* The organization's frozen IAM key — stable across renames, which is why a session remembers
|
|
99
|
+
* its acting organization by it. Server-side only: never copied into a browser token or into a
|
|
100
|
+
* response.
|
|
101
|
+
*/
|
|
102
|
+
entityKey: string
|
|
103
|
+
title?: string
|
|
104
|
+
owner: boolean
|
|
105
|
+
groups?: string[]
|
|
106
|
+
home?: boolean
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A permission set as the provider claims it. A set carrying `entitySlug` is bound to that
|
|
111
|
+
* organization and applies only while the session acts in it; one without applies everywhere.
|
|
112
|
+
*/
|
|
113
|
+
export interface OidcPermissionSetClaim extends PermissionSet {
|
|
114
|
+
entitySlug?: string
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** One organization of the session, as the switch lists it — the key never leaves the server. */
|
|
118
|
+
export interface OidcOrganizationItem {
|
|
119
|
+
entitySlug: string
|
|
120
|
+
title?: string
|
|
121
|
+
owner: boolean
|
|
122
|
+
groups?: string[]
|
|
123
|
+
home?: boolean
|
|
124
|
+
acting: boolean
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface OidcOrganizationList {
|
|
128
|
+
items: OidcOrganizationItem[]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface OidcOrganizationSwitch {
|
|
132
|
+
entitySlug: string
|
|
87
133
|
}
|
|
88
134
|
|
|
89
135
|
// @TODO replace arbitarary Record params with specific possible params for OIDC
|
|
@@ -92,7 +138,17 @@ export interface OIDCClientAuthPayload extends Record<string, string> {
|
|
|
92
138
|
authUrl: string
|
|
93
139
|
}
|
|
94
140
|
|
|
141
|
+
/**
|
|
142
|
+
* A refreshed wrapped token, plus the organization the session acts in when the provider names
|
|
143
|
+
* one. The guard attaches that entity to the request, because a relying party of a tenanted
|
|
144
|
+
* client has no resolver of its own that could find it by slug.
|
|
145
|
+
*/
|
|
146
|
+
export interface WrappedOIDCUpdate extends AuthToken {
|
|
147
|
+
entity?: ResolvedEntity
|
|
148
|
+
}
|
|
149
|
+
|
|
95
150
|
export interface WrappedOIDCService extends AuthorizationService {
|
|
151
|
+
update: (token?: string | AuthToken, thr?: boolean) => Promise<WrappedOIDCUpdate | null>
|
|
96
152
|
}
|
|
97
153
|
|
|
98
154
|
export interface OIDCTokenUpdate extends AuthToken {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { RouteMethod } from '@owlmeans/route'
|
|
3
|
+
import { oidcProtocols } from '../src/entrypoints.js'
|
|
4
|
+
import { DISPATCHER_OIDC_ORGANIZATION, DISPATCHER_OIDC_ORGANIZATIONS, OIDC_GUARD } from '../src/consts.js'
|
|
5
|
+
import { OidcOrganizationSwitchSchema } from '../src/models.js'
|
|
6
|
+
|
|
7
|
+
describe('oidcProtocols — organization switch', () => {
|
|
8
|
+
test('lists organizations on GET, behind the wrapped-token guard alone', () => {
|
|
9
|
+
const { organizations } = oidcProtocols
|
|
10
|
+
expect(organizations.alias).toBe(DISPATCHER_OIDC_ORGANIZATIONS)
|
|
11
|
+
expect(organizations.route.route.path).toBe('/authenticate/oidc/organizations')
|
|
12
|
+
expect(organizations.route.route.method).toBe(RouteMethod.GET)
|
|
13
|
+
expect(organizations.guards).toEqual([OIDC_GUARD])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('switches on POST with a closed slug body, behind the same guard', () => {
|
|
17
|
+
const { organization } = oidcProtocols
|
|
18
|
+
expect(organization.alias).toBe(DISPATCHER_OIDC_ORGANIZATION)
|
|
19
|
+
expect(organization.route.route.path).toBe('/authenticate/oidc/organization')
|
|
20
|
+
expect(organization.route.route.method).toBe(RouteMethod.POST)
|
|
21
|
+
expect(organization.guards).toEqual([OIDC_GUARD])
|
|
22
|
+
expect(organization.contract?.requestSchemas.body).toEqual(OidcOrganizationSwitchSchema)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('the sign-in pair stays unguarded', () => {
|
|
26
|
+
expect(oidcProtocols.init.guards).toEqual([])
|
|
27
|
+
expect(oidcProtocols.authenticate.guards).toEqual([])
|
|
28
|
+
})
|
|
29
|
+
})
|
package/tests/guard.spec.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { describe, expect, mock, test } from 'bun:test'
|
|
2
|
+
import { AuthRole } from '@owlmeans/auth'
|
|
3
|
+
import type { Auth } from '@owlmeans/auth'
|
|
4
|
+
import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope'
|
|
5
|
+
import type { AbstractRequest, ResolvedEntity } from '@owlmeans/entrypoint'
|
|
6
|
+
import type { WrappedOIDCUpdate } from '../src/types.js'
|
|
7
|
+
|
|
8
|
+
/** Whether the trusted key accepts a signature — off for the undecodable-token cases. */
|
|
9
|
+
let accept = false
|
|
2
10
|
|
|
3
11
|
/**
|
|
4
12
|
* A bearer that cannot be decoded is a credential the server does not accept — the guard answers
|
|
@@ -6,7 +14,7 @@ import { describe, expect, mock, test } from 'bun:test'
|
|
|
6
14
|
* never throws the decoder's own error (which the API layer answers with 500).
|
|
7
15
|
*/
|
|
8
16
|
mock.module('@owlmeans/auth-common/utils', () => ({
|
|
9
|
-
trust: async () => ({ key: { verify: async () =>
|
|
17
|
+
trust: async () => ({ key: { verify: async () => accept } }),
|
|
10
18
|
extractAuthToken: (req: { headers: Record<string, string> }, type: string, strip = true) => {
|
|
11
19
|
const header = req.headers.authorization
|
|
12
20
|
if (header == null || !header.toUpperCase().startsWith(type.toUpperCase())) return null
|
|
@@ -15,21 +23,69 @@ mock.module('@owlmeans/auth-common/utils', () => ({
|
|
|
15
23
|
}))
|
|
16
24
|
|
|
17
25
|
const { makeOidcGuard } = await import('../src/guard.js')
|
|
26
|
+
const { OIDC_WRAPPED_TOKEN, WRAPPED_OIDC } = await import('../src/consts.js')
|
|
18
27
|
|
|
19
|
-
const call = async (authorization: string) => {
|
|
28
|
+
const call = async (authorization: string, update?: (token: string) => WrappedOIDCUpdate) => {
|
|
20
29
|
const guard = makeOidcGuard() as any
|
|
21
|
-
guard.assertCtx = () => ({
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
guard.assertCtx = () => ({
|
|
31
|
+
cfg: { alias: 'app' },
|
|
32
|
+
hasService: (alias: string) => alias === WRAPPED_OIDC && update != null,
|
|
33
|
+
service: () => ({ update: async ({ token }: { token: string }) => update!(token) }),
|
|
34
|
+
})
|
|
35
|
+
const req = { headers: { authorization } } as unknown as AbstractRequest
|
|
36
|
+
const res = { responseProvider: { header: () => undefined }, resolve: () => undefined }
|
|
37
|
+
return { outcome: await guard.handle(req, res), req }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const wrapped = async (auth: Partial<Auth>): Promise<string> => {
|
|
41
|
+
const envelope = makeEnvelopeModel<Auth>(OIDC_WRAPPED_TOKEN).send({
|
|
42
|
+
type: OIDC_WRAPPED_TOKEN, token: 'session', userId: 'sub', role: AuthRole.Guest, scopes: [],
|
|
43
|
+
isUser: true, createdAt: new Date(), ...auth,
|
|
44
|
+
} as Auth, null)
|
|
45
|
+
const token = await envelope.sign({ sign: async () => 'signature' } as never, EnvelopeKind.Token)
|
|
46
|
+
|
|
47
|
+
return `${OIDC_WRAPPED_TOKEN.toUpperCase()} ${token}`
|
|
24
48
|
}
|
|
25
49
|
|
|
26
50
|
describe('oidc guard', () => {
|
|
27
51
|
test('an undecodable wrapped token is unauthenticated, not a server error', async () => {
|
|
28
|
-
|
|
52
|
+
accept = false
|
|
53
|
+
expect((await call('OIDC-WRAPPED-TOKEN bogus.token')).outcome).toBe(false)
|
|
29
54
|
})
|
|
30
55
|
|
|
31
56
|
test('garbage of every shape stays unauthenticated', async () => {
|
|
32
|
-
|
|
33
|
-
expect(await call('OIDC-WRAPPED-TOKEN ')).toBe(false)
|
|
57
|
+
accept = false
|
|
58
|
+
expect((await call('OIDC-WRAPPED-TOKEN %%%')).outcome).toBe(false)
|
|
59
|
+
expect((await call('OIDC-WRAPPED-TOKEN ')).outcome).toBe(false)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('oidc guard — the acting organization', () => {
|
|
64
|
+
const acme: ResolvedEntity = { id: 'iam-key-acme', slug: 'acme', iamKey: 'iam-key-acme' }
|
|
65
|
+
|
|
66
|
+
test('attaches the entity the wrapper resolved', async () => {
|
|
67
|
+
accept = true
|
|
68
|
+
const authorization = await wrapped({ entitySlug: 'acme' })
|
|
69
|
+
const { outcome, req } = await call(authorization, token => ({ token, entity: acme }))
|
|
70
|
+
|
|
71
|
+
expect(outcome).toBe(true)
|
|
72
|
+
expect(req.entity).toEqual(acme)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('attaches nothing when the wrapper names no organization', async () => {
|
|
76
|
+
accept = true
|
|
77
|
+
const authorization = await wrapped({ entitySlug: 'acme' })
|
|
78
|
+
const { outcome, req } = await call(authorization, token => ({ token }))
|
|
79
|
+
|
|
80
|
+
expect(outcome).toBe(true)
|
|
81
|
+
expect(req.entity).toBeUndefined()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('without a wrapping service there is nothing to attach', async () => {
|
|
85
|
+
accept = true
|
|
86
|
+
const { outcome, req } = await call(await wrapped({ entitySlug: 'acme' }))
|
|
87
|
+
|
|
88
|
+
expect(outcome).toBe(true)
|
|
89
|
+
expect(req.entity).toBeUndefined()
|
|
34
90
|
})
|
|
35
91
|
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import Ajv from 'ajv'
|
|
3
|
+
import { OIDCAuthInitParamsSchema, OidcOrganizationSwitchSchema } from '../src/models.js'
|
|
4
|
+
|
|
5
|
+
const strict = new Ajv()
|
|
6
|
+
// The server's own options: a closed schema STRIPS what it does not declare instead of failing,
|
|
7
|
+
// so an undeclared field would vanish without an error ever reaching the caller.
|
|
8
|
+
const server = new Ajv({ removeAdditional: true, useDefaults: true, coerceTypes: true, allErrors: true, strict: false })
|
|
9
|
+
|
|
10
|
+
describe('OIDCAuthInitParamsSchema', () => {
|
|
11
|
+
const validate = strict.compile(OIDCAuthInitParamsSchema)
|
|
12
|
+
|
|
13
|
+
test('accepts the requested organization next to the provider entity', () => {
|
|
14
|
+
expect(validate({ entity: 'acme-client', entitySlug: 'acme-team' })).toBe(true)
|
|
15
|
+
expect(validate({ entitySlug: 'acme-team' })).toBe(true)
|
|
16
|
+
expect(validate({})).toBe(true)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('refuses a slug too short to name an organization', () => {
|
|
20
|
+
expect(validate({ entitySlug: 'ab' })).toBe(false)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('stays closed to anything it does not declare', () => {
|
|
24
|
+
expect(validate({ entitySlug: 'acme-team', entityId: '0123456789abcdef01234567' })).toBe(false)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('the server keeps the requested slug rather than stripping it', () => {
|
|
28
|
+
const body: Record<string, unknown> = { entity: 'acme-client', entitySlug: 'acme-team', stray: 'x' }
|
|
29
|
+
expect(server.compile(OIDCAuthInitParamsSchema)(body)).toBe(true)
|
|
30
|
+
expect(body).toEqual({ entity: 'acme-client', entitySlug: 'acme-team' })
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('OidcOrganizationSwitchSchema', () => {
|
|
35
|
+
const validate = strict.compile(OidcOrganizationSwitchSchema)
|
|
36
|
+
|
|
37
|
+
test('names the organization by slug and nothing else', () => {
|
|
38
|
+
expect(validate({ entitySlug: 'acme-team' })).toBe(true)
|
|
39
|
+
expect(validate({})).toBe(false)
|
|
40
|
+
expect(validate({ entitySlug: 'acme-team', entityKey: 'frozen-key' })).toBe(false)
|
|
41
|
+
})
|
|
42
|
+
})
|