@_mustachio/openauth 0.11.0 → 0.13.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/dist/esm/adapters/d1/index.js +18 -9
- package/dist/esm/adapters/d1/migrations.js +7 -2
- package/dist/esm/adapters/d1/session-store.js +39 -0
- package/dist/esm/adapters/d1/session.js +2 -2
- package/dist/esm/adapters/durable-object/index.js +4 -0
- package/dist/esm/adapters/durable-object/session-store.js +42 -0
- package/dist/esm/adapters/dynamo/index.js +24 -7
- package/dist/esm/adapters/dynamo/session-store.js +49 -0
- package/dist/esm/adapters/kms/index.js +6 -2
- package/dist/esm/adapters/kv/index.js +6 -2
- package/dist/esm/adapters/memory/index.js +24 -7
- package/dist/esm/adapters/memory/session-store.js +23 -0
- package/dist/esm/adapters/postgres/executor.js +2 -2
- package/dist/esm/adapters/postgres/index.js +33 -10
- package/dist/esm/adapters/postgres/migrations.js +8 -2
- package/dist/esm/adapters/postgres/session-store.js +42 -0
- package/dist/esm/client.js +27 -27
- package/dist/esm/domain/authorize.js +2 -2
- package/dist/esm/domain/callback.js +121 -7
- package/dist/esm/domain/client-auth.js +2 -2
- package/dist/esm/domain/crypto.js +14 -14
- package/dist/esm/domain/discovery.js +2 -2
- package/dist/esm/domain/dpop.js +4 -4
- package/dist/esm/domain/id-token.js +5 -5
- package/dist/esm/domain/jwt.js +4 -4
- package/dist/esm/domain/method-dispatch.js +27 -1
- package/dist/esm/domain/method-route.js +80 -1
- package/dist/esm/domain/par.js +2 -2
- package/dist/esm/domain/pkce.js +2 -2
- package/dist/esm/domain/revoke.js +2 -2
- package/dist/esm/domain/scim/discovery.js +206 -0
- package/dist/esm/domain/scim/filter.js +177 -0
- package/dist/esm/domain/scim/handle.js +338 -0
- package/dist/esm/domain/scim/patch.js +568 -0
- package/dist/esm/domain/scim/resource.js +256 -0
- package/dist/esm/domain/state-envelope.js +2 -2
- package/dist/esm/domain/token.js +5 -5
- package/dist/esm/error.js +8 -8
- package/dist/esm/http/cookies.js +19 -2
- package/dist/esm/http/errors.js +4 -4
- package/dist/esm/http/handlers/authorize.js +3 -19
- package/dist/esm/http/handlers/callback.js +7 -1
- package/dist/esm/http/handlers/metadata.js +2 -2
- package/dist/esm/http/handlers/method-route.js +47 -2
- package/dist/esm/http/handlers/revocation.js +2 -2
- package/dist/esm/http/handlers/scim.js +59 -0
- package/dist/esm/http/middleware/tenant.js +31 -6
- package/dist/esm/http/router.js +3 -0
- package/dist/esm/http/schemas/authorize.js +2 -2
- package/dist/esm/http/schemas/revocation.js +2 -2
- package/dist/esm/http/schemas/token.js +7 -7
- package/dist/esm/index.js +34 -32
- package/dist/esm/methods/oauth2-factory.js +2 -2
- package/dist/esm/methods/password-hash.js +2 -2
- package/dist/esm/methods/providers/index.js +30 -14
- package/dist/esm/methods/saml-sp/acs.js +232 -0
- package/dist/esm/methods/saml-sp/attributes.js +77 -0
- package/dist/esm/methods/saml-sp/authnrequest.js +63 -0
- package/dist/esm/methods/saml-sp/cache-provider.js +33 -0
- package/dist/esm/methods/saml-sp/cert-rotation.js +13 -0
- package/dist/esm/methods/saml-sp/factory.js +81 -0
- package/dist/esm/methods/saml-sp/index.js +7 -0
- package/dist/esm/methods/saml-sp/metadata.js +71 -0
- package/dist/esm/methods/saml-sp/method.js +29 -0
- package/dist/esm/methods/saml-sp/parse-idp-metadata.js +102 -0
- package/dist/esm/methods/saml-sp/saml-instance.js +59 -0
- package/dist/esm/methods/saml-sp/slo-initiate.js +84 -0
- package/dist/esm/methods/saml-sp/sls.js +154 -0
- package/dist/esm/methods/saml-sp/types.js +0 -0
- package/dist/esm/ports/scim-directory.js +0 -0
- package/dist/esm/types/error.js +5 -0
- package/dist/esm/types/result.js +3 -3
- package/dist/esm/types/scim.js +0 -0
- package/dist/esm/ui/forms.js +3 -3
- package/dist/esm/ui/index.js +8 -5
- package/dist/esm/ui/picker.js +2 -2
- package/dist/types/adapters/d1/migrations.d.ts.map +1 -1
- package/dist/types/adapters/d1/session-store.d.ts +3 -0
- package/dist/types/adapters/d1/session-store.d.ts.map +1 -1
- package/dist/types/adapters/durable-object/session-store.d.ts +3 -0
- package/dist/types/adapters/durable-object/session-store.d.ts.map +1 -1
- package/dist/types/adapters/dynamo/session-store.d.ts +3 -0
- package/dist/types/adapters/dynamo/session-store.d.ts.map +1 -1
- package/dist/types/adapters/memory/session-store.d.ts +3 -0
- package/dist/types/adapters/memory/session-store.d.ts.map +1 -1
- package/dist/types/adapters/postgres/migrations.d.ts +1 -1
- package/dist/types/adapters/postgres/migrations.d.ts.map +1 -1
- package/dist/types/adapters/postgres/session-store.d.ts +3 -0
- package/dist/types/adapters/postgres/session-store.d.ts.map +1 -1
- package/dist/types/domain/callback.d.ts +16 -1
- package/dist/types/domain/callback.d.ts.map +1 -1
- package/dist/types/domain/method-dispatch.d.ts.map +1 -1
- package/dist/types/domain/method-route.d.ts +57 -1
- package/dist/types/domain/method-route.d.ts.map +1 -1
- package/dist/types/domain/scim/discovery.d.ts +11 -0
- package/dist/types/domain/scim/discovery.d.ts.map +1 -0
- package/dist/types/domain/scim/filter.d.ts +51 -0
- package/dist/types/domain/scim/filter.d.ts.map +1 -0
- package/dist/types/domain/scim/handle.d.ts +35 -0
- package/dist/types/domain/scim/handle.d.ts.map +1 -0
- package/dist/types/domain/scim/patch.d.ts +73 -0
- package/dist/types/domain/scim/patch.d.ts.map +1 -0
- package/dist/types/domain/scim/resource.d.ts +56 -0
- package/dist/types/domain/scim/resource.d.ts.map +1 -0
- package/dist/types/http/context.d.ts +6 -1
- package/dist/types/http/context.d.ts.map +1 -1
- package/dist/types/http/cookies.d.ts +9 -1
- package/dist/types/http/cookies.d.ts.map +1 -1
- package/dist/types/http/handlers/callback.d.ts.map +1 -1
- package/dist/types/http/handlers/method-route.d.ts.map +1 -1
- package/dist/types/http/handlers/scim.d.ts +3 -0
- package/dist/types/http/handlers/scim.d.ts.map +1 -0
- package/dist/types/http/middleware/tenant.d.ts.map +1 -1
- package/dist/types/http/router.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/methods/saml-sp/acs.d.ts +4 -0
- package/dist/types/methods/saml-sp/acs.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/attributes.d.ts +45 -0
- package/dist/types/methods/saml-sp/attributes.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/authnrequest.d.ts +4 -0
- package/dist/types/methods/saml-sp/authnrequest.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/cache-provider.d.ts +38 -0
- package/dist/types/methods/saml-sp/cache-provider.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/cert-rotation.d.ts +23 -0
- package/dist/types/methods/saml-sp/cert-rotation.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/factory.d.ts +920 -0
- package/dist/types/methods/saml-sp/factory.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/index.d.ts +24 -0
- package/dist/types/methods/saml-sp/index.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/metadata.d.ts +50 -0
- package/dist/types/methods/saml-sp/metadata.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/method.d.ts +17 -0
- package/dist/types/methods/saml-sp/method.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/parse-idp-metadata.d.ts +4 -0
- package/dist/types/methods/saml-sp/parse-idp-metadata.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/saml-instance.d.ts +93 -0
- package/dist/types/methods/saml-sp/saml-instance.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/slo-initiate.d.ts +4 -0
- package/dist/types/methods/saml-sp/slo-initiate.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/sls.d.ts +4 -0
- package/dist/types/methods/saml-sp/sls.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/types.d.ts +299 -0
- package/dist/types/methods/saml-sp/types.d.ts.map +1 -0
- package/dist/types/ports/audit-log.d.ts +18 -5
- package/dist/types/ports/audit-log.d.ts.map +1 -1
- package/dist/types/ports/scim-directory.d.ts +104 -0
- package/dist/types/ports/scim-directory.d.ts.map +1 -0
- package/dist/types/ports/session-store.d.ts +23 -0
- package/dist/types/ports/session-store.d.ts.map +1 -1
- package/dist/types/types/error.d.ts +5 -0
- package/dist/types/types/error.d.ts.map +1 -1
- package/dist/types/types/idp.d.ts +83 -0
- package/dist/types/types/idp.d.ts.map +1 -1
- package/dist/types/types/method.d.ts +109 -0
- package/dist/types/types/method.d.ts.map +1 -1
- package/dist/types/types/scim.d.ts +269 -0
- package/dist/types/types/scim.d.ts.map +1 -0
- package/dist/types/types/tenant.d.ts +10 -0
- package/dist/types/types/tenant.d.ts.map +1 -1
- package/package.json +9 -1
- package/src/adapters/d1/migrations.ts +5 -0
- package/src/adapters/d1/session-store.ts +68 -0
- package/src/adapters/durable-object/session-store.ts +61 -0
- package/src/adapters/dynamo/session-store.ts +63 -0
- package/src/adapters/memory/session-store.ts +39 -0
- package/src/adapters/postgres/migrations.ts +6 -0
- package/src/adapters/postgres/session-store.ts +69 -0
- package/src/domain/callback.ts +215 -7
- package/src/domain/method-dispatch.ts +53 -1
- package/src/domain/method-route.ts +190 -1
- package/src/domain/scim/discovery.ts +262 -0
- package/src/domain/scim/filter.ts +295 -0
- package/src/domain/scim/handle.ts +597 -0
- package/src/domain/scim/patch.ts +807 -0
- package/src/domain/scim/resource.ts +358 -0
- package/src/http/context.ts +9 -0
- package/src/http/cookies.ts +19 -1
- package/src/http/handlers/authorize.ts +2 -22
- package/src/http/handlers/callback.ts +9 -1
- package/src/http/handlers/method-route.ts +72 -2
- package/src/http/handlers/scim.ts +107 -0
- package/src/http/middleware/tenant.ts +47 -1
- package/src/http/router.ts +9 -0
- package/src/index.ts +23 -0
- package/src/methods/saml-sp/acs.ts +449 -0
- package/src/methods/saml-sp/attributes.ts +146 -0
- package/src/methods/saml-sp/authnrequest.ts +129 -0
- package/src/methods/saml-sp/cache-provider.ts +76 -0
- package/src/methods/saml-sp/cert-rotation.ts +34 -0
- package/src/methods/saml-sp/factory.ts +139 -0
- package/src/methods/saml-sp/index.ts +34 -0
- package/src/methods/saml-sp/metadata.ts +215 -0
- package/src/methods/saml-sp/method.ts +65 -0
- package/src/methods/saml-sp/parse-idp-metadata.ts +158 -0
- package/src/methods/saml-sp/saml-instance.ts +210 -0
- package/src/methods/saml-sp/slo-initiate.ts +180 -0
- package/src/methods/saml-sp/sls.ts +307 -0
- package/src/methods/saml-sp/types.ts +298 -0
- package/src/ports/CONSISTENCY.md +52 -24
- package/src/ports/audit-log.ts +18 -5
- package/src/ports/scim-directory.ts +166 -0
- package/src/ports/session-store.ts +24 -0
- package/src/types/error.ts +11 -0
- package/src/types/idp.ts +86 -0
- package/src/types/method.ts +110 -0
- package/src/types/scim.ts +280 -0
- package/src/types/tenant.ts +10 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCIM 2.0 — public types.
|
|
3
|
+
*
|
|
4
|
+
* The library is the SCIM **Service Provider** (the system being
|
|
5
|
+
* provisioned into); corporate IdPs are the SCIM clients. Inbound only —
|
|
6
|
+
* we never originate provisioning traffic. See `SCIM-AD1` in
|
|
7
|
+
* `docs/plans/claude/scim-plan.md`.
|
|
8
|
+
*
|
|
9
|
+
* These types are the whole contract between the library's protocol
|
|
10
|
+
* layer and the host's data layer. No SCIM JSON shape, path expression,
|
|
11
|
+
* or filter string reaches the host: it receives validated, normalized
|
|
12
|
+
* values and returns records. See `SCIM-AD2`.
|
|
13
|
+
*
|
|
14
|
+
* Plain TypeScript, no third-party types, no Node APIs — SCIM is JSON
|
|
15
|
+
* over HTTP, so unlike SAML this surface lives on the root entry and
|
|
16
|
+
* stays edge-clean.
|
|
17
|
+
*/
|
|
18
|
+
/** RFC 7643 §4.1.2 — a multi-valued attribute entry. */
|
|
19
|
+
export type ScimMultiValue = {
|
|
20
|
+
value: string;
|
|
21
|
+
/** e.g. `"work"` / `"home"` / `"mobile"`. */
|
|
22
|
+
type?: string;
|
|
23
|
+
/** At most one entry per attribute should be primary. */
|
|
24
|
+
primary?: boolean;
|
|
25
|
+
};
|
|
26
|
+
/** RFC 7643 §4.1.1 — the `name` complex attribute. */
|
|
27
|
+
export type ScimName = {
|
|
28
|
+
formatted?: string;
|
|
29
|
+
familyName?: string;
|
|
30
|
+
givenName?: string;
|
|
31
|
+
middleName?: string;
|
|
32
|
+
honorificPrefix?: string;
|
|
33
|
+
honorificSuffix?: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* RFC 7643 §4.3 — the enterprise user extension
|
|
37
|
+
* (`urn:ietf:params:scim:schemas:extension:enterprise:2.0:User`).
|
|
38
|
+
* Okta and Entra both populate parts of this; `department` and
|
|
39
|
+
* `manager` are the ones hosts usually care about.
|
|
40
|
+
*/
|
|
41
|
+
export type ScimEnterpriseUser = {
|
|
42
|
+
employeeNumber?: string;
|
|
43
|
+
costCenter?: string;
|
|
44
|
+
organization?: string;
|
|
45
|
+
division?: string;
|
|
46
|
+
department?: string;
|
|
47
|
+
/** `manager.value` is the manager's SCIM `id`. */
|
|
48
|
+
manager?: {
|
|
49
|
+
value?: string;
|
|
50
|
+
displayName?: string;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* A user as the **host** stores it. Returned from every `ScimDirectory`
|
|
55
|
+
* read and write.
|
|
56
|
+
*
|
|
57
|
+
* `id` is the host's stable identifier and becomes the SCIM resource id
|
|
58
|
+
* in URLs — it must be opaque, stable, and URL-safe.
|
|
59
|
+
*
|
|
60
|
+
* `externalId` is the **IdP's** identifier for the same person. It is
|
|
61
|
+
* what reconciliation depends on in practice (a user can change
|
|
62
|
+
* `userName`; `externalId` survives), so hosts should persist and index
|
|
63
|
+
* it even though SCIM marks it optional.
|
|
64
|
+
*/
|
|
65
|
+
export type ScimUserRecord = {
|
|
66
|
+
id: string;
|
|
67
|
+
externalId?: string;
|
|
68
|
+
userName: string;
|
|
69
|
+
/** `false` is the normal deprovisioning signal — see `SCIM-AD8`. */
|
|
70
|
+
active: boolean;
|
|
71
|
+
name?: ScimName;
|
|
72
|
+
displayName?: string;
|
|
73
|
+
emails?: ScimMultiValue[];
|
|
74
|
+
phoneNumbers?: ScimMultiValue[];
|
|
75
|
+
enterprise?: ScimEnterpriseUser;
|
|
76
|
+
/** Unix ms. Surfaced as `meta.created` when present. */
|
|
77
|
+
createdAt?: number;
|
|
78
|
+
/** Unix ms. Surfaced as `meta.lastModified` when present. */
|
|
79
|
+
updatedAt?: number;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* A validated create / replace payload. Same shape as a record minus
|
|
83
|
+
* the host-assigned `id` and timestamps.
|
|
84
|
+
*
|
|
85
|
+
* `active` is always present: SCIM defaults it to `true` on create, and
|
|
86
|
+
* resolving that default in the library rather than the host keeps the
|
|
87
|
+
* semantics in one place.
|
|
88
|
+
*
|
|
89
|
+
* Note there is no `password` field, deliberately — see `SCIM-AD1`
|
|
90
|
+
* non-goals. A `password` in the payload is refused, not silently
|
|
91
|
+
* dropped.
|
|
92
|
+
*/
|
|
93
|
+
export type ScimUserWrite = {
|
|
94
|
+
externalId?: string;
|
|
95
|
+
userName: string;
|
|
96
|
+
active: boolean;
|
|
97
|
+
name?: ScimName;
|
|
98
|
+
displayName?: string;
|
|
99
|
+
emails?: ScimMultiValue[];
|
|
100
|
+
phoneNumbers?: ScimMultiValue[];
|
|
101
|
+
enterprise?: ScimEnterpriseUser;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* A **normalized** PATCH delta (`SCIM-AD6`).
|
|
105
|
+
*
|
|
106
|
+
* The library resolves `urn:ietf:params:scim:api:messages:2.0:PatchOp`
|
|
107
|
+
* operations — including path expressions like
|
|
108
|
+
* `emails[type eq "work"].value` and the different shapes Okta and Entra
|
|
109
|
+
* emit for the same intent — into this flat delta. The host never parses
|
|
110
|
+
* a SCIM path.
|
|
111
|
+
*
|
|
112
|
+
* Present key ⇒ set to that value. Absent key ⇒ leave untouched.
|
|
113
|
+
* `null` ⇒ the attribute was removed and should be cleared.
|
|
114
|
+
*/
|
|
115
|
+
export type ScimUserPatch = {
|
|
116
|
+
externalId?: string | null;
|
|
117
|
+
userName?: string;
|
|
118
|
+
active?: boolean;
|
|
119
|
+
name?: ScimName | null;
|
|
120
|
+
displayName?: string | null;
|
|
121
|
+
emails?: ScimMultiValue[] | null;
|
|
122
|
+
phoneNumbers?: ScimMultiValue[] | null;
|
|
123
|
+
enterprise?: ScimEnterpriseUser | null;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Attributes the filter subset may reference (`SCIM-AD3`).
|
|
127
|
+
*
|
|
128
|
+
* `emails.value` is the normalized form of the complex path Entra emits
|
|
129
|
+
* (`emails[type eq "work"].value`) — the host matches on any email
|
|
130
|
+
* value and need not model the `type` qualifier.
|
|
131
|
+
*/
|
|
132
|
+
export type ScimFilterAttribute = "id" | "userName" | "externalId" | "active" | "emails.value"
|
|
133
|
+
/** Groups only. */
|
|
134
|
+
| "displayName";
|
|
135
|
+
/**
|
|
136
|
+
* The parsed filter, as a small typed tree. The host never receives the
|
|
137
|
+
* raw filter string — parsing is the library's job, and an expression
|
|
138
|
+
* outside the supported subset is rejected with `400 invalidFilter`
|
|
139
|
+
* before the port is called.
|
|
140
|
+
*/
|
|
141
|
+
export type ScimFilter = {
|
|
142
|
+
op: "eq";
|
|
143
|
+
attribute: ScimFilterAttribute;
|
|
144
|
+
value: string | boolean;
|
|
145
|
+
} | {
|
|
146
|
+
op: "and";
|
|
147
|
+
left: ScimFilter;
|
|
148
|
+
right: ScimFilter;
|
|
149
|
+
};
|
|
150
|
+
/** Input to `ScimDirectory.findUsers`. */
|
|
151
|
+
export type ScimUserQuery = {
|
|
152
|
+
/** Absent ⇒ unfiltered list. */
|
|
153
|
+
filter?: ScimFilter;
|
|
154
|
+
/**
|
|
155
|
+
* **1-based**, per RFC 7644 §3.4.2.4 — not zero. Already clamped to
|
|
156
|
+
* `>= 1` by the library.
|
|
157
|
+
*/
|
|
158
|
+
startIndex: number;
|
|
159
|
+
/** Page size, already clamped to the connection's configured maximum. */
|
|
160
|
+
count: number;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* One page of results. `totalResults` is the count of everything
|
|
164
|
+
* matching the filter, not the size of `resources` — SCIM clients use it
|
|
165
|
+
* to drive pagination, so a host that returns the page size here will
|
|
166
|
+
* make Okta loop or stop early.
|
|
167
|
+
*/
|
|
168
|
+
export type ScimPage<T> = {
|
|
169
|
+
resources: T[];
|
|
170
|
+
totalResults: number;
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* One group membership entry. `value` is the member's SCIM `id` — for
|
|
174
|
+
* us always a User id, since we do not support nested groups.
|
|
175
|
+
*/
|
|
176
|
+
export type ScimGroupMember = {
|
|
177
|
+
value: string;
|
|
178
|
+
/** Human label the IdP supplied. Advisory; not authoritative. */
|
|
179
|
+
display?: string;
|
|
180
|
+
};
|
|
181
|
+
/** A group as the **host** stores it. */
|
|
182
|
+
export type ScimGroupRecord = {
|
|
183
|
+
id: string;
|
|
184
|
+
externalId?: string;
|
|
185
|
+
displayName: string;
|
|
186
|
+
/**
|
|
187
|
+
* Omit when the caller asked for `excludedAttributes=members`, which
|
|
188
|
+
* Okta does while enumerating groups. Distinguish "not requested"
|
|
189
|
+
* (omit) from "no members" (`[]`) — returning `[]` for the former
|
|
190
|
+
* tells the client the group was emptied.
|
|
191
|
+
*/
|
|
192
|
+
members?: ScimGroupMember[];
|
|
193
|
+
/** Unix ms → `meta.created`. */
|
|
194
|
+
createdAt?: number;
|
|
195
|
+
/** Unix ms → `meta.lastModified`. */
|
|
196
|
+
updatedAt?: number;
|
|
197
|
+
};
|
|
198
|
+
/** A validated group create / replace payload. */
|
|
199
|
+
export type ScimGroupWrite = {
|
|
200
|
+
externalId?: string;
|
|
201
|
+
displayName: string;
|
|
202
|
+
/** Full membership for a create or replace. */
|
|
203
|
+
members?: ScimGroupMember[];
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* A normalized group PATCH.
|
|
207
|
+
*
|
|
208
|
+
* Membership is handled differently from every user attribute, and
|
|
209
|
+
* deliberately so (`SCIM-AD9`). For a user, the library resolves a
|
|
210
|
+
* targeted patch into the complete new value because the lists involved
|
|
211
|
+
* are small and bounded. Group membership is neither: resolving
|
|
212
|
+
* "add one member" against a 20,000-member group would mean reading all
|
|
213
|
+
* 20,000 rows and handing them back on every single change.
|
|
214
|
+
*
|
|
215
|
+
* So the client's *intent* is preserved instead. `addMembers` /
|
|
216
|
+
* `removeMembers` are incremental and let the host issue one insert or
|
|
217
|
+
* delete; `members` is a full replacement. They are mutually exclusive
|
|
218
|
+
* — the library never emits `members` alongside either incremental
|
|
219
|
+
* field, so a host can branch on which is present without ordering
|
|
220
|
+
* concerns.
|
|
221
|
+
*/
|
|
222
|
+
export type ScimGroupPatch = {
|
|
223
|
+
displayName?: string;
|
|
224
|
+
externalId?: string | null;
|
|
225
|
+
/** Replace the entire membership with exactly these members. */
|
|
226
|
+
members?: ScimGroupMember[];
|
|
227
|
+
/** Add these members, leaving existing ones alone. Idempotent. */
|
|
228
|
+
addMembers?: ScimGroupMember[];
|
|
229
|
+
/** Remove these member ids. Removing a non-member is not an error. */
|
|
230
|
+
removeMembers?: string[];
|
|
231
|
+
};
|
|
232
|
+
/** Input to `ScimDirectory.findGroups`. */
|
|
233
|
+
export type ScimGroupQuery = {
|
|
234
|
+
filter?: ScimFilter;
|
|
235
|
+
/** 1-based. */
|
|
236
|
+
startIndex: number;
|
|
237
|
+
count: number;
|
|
238
|
+
/**
|
|
239
|
+
* `true` when the client sent `excludedAttributes=members`. The host
|
|
240
|
+
* may skip loading membership entirely — Okta sets this while
|
|
241
|
+
* enumerating groups, and honouring it is the difference between a
|
|
242
|
+
* cheap listing and a fan-out read per group.
|
|
243
|
+
*/
|
|
244
|
+
excludeMembers: boolean;
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Per-tenant SCIM connection config, carried on `TenantConfig.scim`.
|
|
248
|
+
*
|
|
249
|
+
* SCIM is not an `AuthMethod` — no `/authorize`, no flow, no user agent
|
|
250
|
+
* — so it is tenant-level config rather than a `MethodConfig`
|
|
251
|
+
* (`SCIM-AD5`).
|
|
252
|
+
*/
|
|
253
|
+
export type ScimConfig = {
|
|
254
|
+
/** `false` (or absent config) ⇒ `/scim/v2/*` responds 403 for this tenant. */
|
|
255
|
+
enabled: boolean;
|
|
256
|
+
/**
|
|
257
|
+
* Hash of the bearer token issued to the IdP, produced with
|
|
258
|
+
* `hashClientSecret` — the same treatment as
|
|
259
|
+
* `ClientConfig.secretHash`. Never store the raw token.
|
|
260
|
+
*/
|
|
261
|
+
tokenHash: string;
|
|
262
|
+
/**
|
|
263
|
+
* Largest page this connection will return, whatever the client asks
|
|
264
|
+
* for. Defaults to 100; hard-capped at 500 so a hostile or
|
|
265
|
+
* misconfigured `count` cannot ask the host for an unbounded read.
|
|
266
|
+
*/
|
|
267
|
+
maxPageSize?: number;
|
|
268
|
+
};
|
|
269
|
+
//# sourceMappingURL=scim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scim.d.ts","sourceRoot":"","sources":["../../../src/types/scim.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* providers carries only `{ tenantId, flowId, nonce, kid }`. Everything else
|
|
12
12
|
* lives in the server-side `FlowRecord` (see `./flow.ts`).
|
|
13
13
|
*/
|
|
14
|
+
import type { ScimConfig } from "./scim";
|
|
14
15
|
/**
|
|
15
16
|
* Branded tenant id — **opaque to the framework**.
|
|
16
17
|
*
|
|
@@ -181,6 +182,15 @@ export type TenantConfig = {
|
|
|
181
182
|
refreshTtl?: number;
|
|
182
183
|
/** Override default access-token TTL (seconds). */
|
|
183
184
|
accessTtl?: number;
|
|
185
|
+
/**
|
|
186
|
+
* SCIM 2.0 provisioning for this tenant. Absent or `enabled: false` ⇒
|
|
187
|
+
* `/scim/v2/*` answers 403 for this tenant.
|
|
188
|
+
*
|
|
189
|
+
* Tenant-level rather than a `MethodConfig` because SCIM is not an
|
|
190
|
+
* auth method — no `/authorize`, no flow, no user agent. See
|
|
191
|
+
* `SCIM-AD5` in `docs/plans/claude/scim-plan.md`.
|
|
192
|
+
*/
|
|
193
|
+
scim?: ScimConfig;
|
|
184
194
|
};
|
|
185
195
|
/**
|
|
186
196
|
* Per-request tenant context handed to methods and the user-supplied
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../../src/types/tenant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../../src/types/tenant.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@_mustachio/openauth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,6 +37,12 @@
|
|
|
37
37
|
"import": "./dist/esm/adapters/*/index.js",
|
|
38
38
|
"types": "./dist/types/adapters/*/index.d.ts"
|
|
39
39
|
},
|
|
40
|
+
"./methods/saml-sp": {
|
|
41
|
+
"node": {
|
|
42
|
+
"import": "./dist/esm/methods/saml-sp/index.js",
|
|
43
|
+
"types": "./dist/types/methods/saml-sp/index.d.ts"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
40
46
|
"./*": {
|
|
41
47
|
"import": "./dist/esm/*.js",
|
|
42
48
|
"types": "./dist/types/*.d.ts"
|
|
@@ -73,8 +79,10 @@
|
|
|
73
79
|
},
|
|
74
80
|
"dependencies": {
|
|
75
81
|
"@noble/hashes": "2.2.0",
|
|
82
|
+
"@node-saml/node-saml": "5.1.0",
|
|
76
83
|
"@simplewebauthn/server": "13.1.1",
|
|
77
84
|
"@standard-schema/spec": "1.0.0-beta.3",
|
|
85
|
+
"@xmldom/xmldom": "0.8.13",
|
|
78
86
|
"aws4fetch": "1.0.20",
|
|
79
87
|
"jose": "5.9.6",
|
|
80
88
|
"oauth4webapi": "3.8.6",
|
|
@@ -67,6 +67,11 @@ export const INITIAL_SCHEMA_SQL = [
|
|
|
67
67
|
payload TEXT NOT NULL,
|
|
68
68
|
expires_at INTEGER NOT NULL
|
|
69
69
|
)`,
|
|
70
|
+
`CREATE TABLE IF NOT EXISTS openauth_scratch (
|
|
71
|
+
scratch_key TEXT PRIMARY KEY,
|
|
72
|
+
value TEXT NOT NULL,
|
|
73
|
+
expires_at INTEGER NOT NULL
|
|
74
|
+
)`,
|
|
70
75
|
`CREATE TABLE IF NOT EXISTS openauth_audit_events (
|
|
71
76
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
72
77
|
ts INTEGER NOT NULL,
|
|
@@ -227,4 +227,72 @@ export class D1SessionStore implements SessionStore {
|
|
|
227
227
|
}
|
|
228
228
|
return ok(undefined)
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
async saveScratch(
|
|
232
|
+
key: string,
|
|
233
|
+
value: string,
|
|
234
|
+
ttlMs: number,
|
|
235
|
+
): Promise<Result<void>> {
|
|
236
|
+
if (ttlMs <= 0) {
|
|
237
|
+
return err(
|
|
238
|
+
authError.internalError(
|
|
239
|
+
`saveScratch: ttlMs must be positive, got ${ttlMs}`,
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
const expiresAt = this.#clock() + ttlMs
|
|
244
|
+
try {
|
|
245
|
+
// Pinned to the primary — scratch backs InResponseTo replay
|
|
246
|
+
// protection, which is read-after-write sensitive at the ACS.
|
|
247
|
+
await primarySession(this.#db)
|
|
248
|
+
.prepare(
|
|
249
|
+
`INSERT INTO openauth_scratch (scratch_key, value, expires_at) VALUES (?1, ?2, ?3)
|
|
250
|
+
ON CONFLICT(scratch_key) DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at`,
|
|
251
|
+
)
|
|
252
|
+
.bind(key, value, expiresAt)
|
|
253
|
+
.run()
|
|
254
|
+
} catch (e) {
|
|
255
|
+
return err(authError.internalError("saveScratch: upsert failed", e))
|
|
256
|
+
}
|
|
257
|
+
return ok(undefined)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async readScratch(key: string): Promise<Result<string>> {
|
|
261
|
+
let row: { value: string; expires_at: number } | null
|
|
262
|
+
try {
|
|
263
|
+
row = await primarySession(this.#db)
|
|
264
|
+
.prepare(
|
|
265
|
+
`SELECT value, expires_at FROM openauth_scratch WHERE scratch_key = ?1`,
|
|
266
|
+
)
|
|
267
|
+
.bind(key)
|
|
268
|
+
.first<{ value: string; expires_at: number }>()
|
|
269
|
+
} catch (e) {
|
|
270
|
+
return err(authError.internalError("readScratch: query failed", e))
|
|
271
|
+
}
|
|
272
|
+
if (!row) {
|
|
273
|
+
return err(authError.unknownState(`scratch "${key}" unknown`))
|
|
274
|
+
}
|
|
275
|
+
if (this.#clock() >= Number(row.expires_at)) {
|
|
276
|
+
try {
|
|
277
|
+
await primarySession(this.#db)
|
|
278
|
+
.prepare(`DELETE FROM openauth_scratch WHERE scratch_key = ?1`)
|
|
279
|
+
.bind(key)
|
|
280
|
+
.run()
|
|
281
|
+
} catch {}
|
|
282
|
+
return err(authError.unknownState(`scratch "${key}" expired`))
|
|
283
|
+
}
|
|
284
|
+
return ok(row.value)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async deleteScratch(key: string): Promise<Result<void>> {
|
|
288
|
+
try {
|
|
289
|
+
await primarySession(this.#db)
|
|
290
|
+
.prepare(`DELETE FROM openauth_scratch WHERE scratch_key = ?1`)
|
|
291
|
+
.bind(key)
|
|
292
|
+
.run()
|
|
293
|
+
} catch (e) {
|
|
294
|
+
return err(authError.internalError("deleteScratch: delete failed", e))
|
|
295
|
+
}
|
|
296
|
+
return ok(undefined)
|
|
297
|
+
}
|
|
230
298
|
}
|
|
@@ -51,6 +51,11 @@ type StoredSession = {
|
|
|
51
51
|
expiresAt: number
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
type StoredScratch = {
|
|
55
|
+
value: string
|
|
56
|
+
expiresAt: number
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
export type DurableObjectSessionStoreOptions = {
|
|
55
60
|
storage: DurableObjectStorageLike
|
|
56
61
|
clock?: () => number
|
|
@@ -218,10 +223,66 @@ export class DurableObjectSessionStore implements SessionStore {
|
|
|
218
223
|
}
|
|
219
224
|
return ok(undefined)
|
|
220
225
|
}
|
|
226
|
+
|
|
227
|
+
async saveScratch(
|
|
228
|
+
key: string,
|
|
229
|
+
value: string,
|
|
230
|
+
ttlMs: number,
|
|
231
|
+
): Promise<Result<void>> {
|
|
232
|
+
if (ttlMs <= 0) {
|
|
233
|
+
return err(
|
|
234
|
+
authError.internalError(
|
|
235
|
+
`saveScratch: ttlMs must be positive, got ${ttlMs}`,
|
|
236
|
+
),
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
const stored: StoredScratch = {
|
|
240
|
+
value,
|
|
241
|
+
expiresAt: this.#clock() + ttlMs,
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
// Plain put — scratch overwrites any prior value for the key. No
|
|
245
|
+
// transaction needed: it is a single write, and the DO serializes
|
|
246
|
+
// all operations on this instance.
|
|
247
|
+
await this.#storage.put(scratchKey(key), stored)
|
|
248
|
+
} catch (e) {
|
|
249
|
+
return err(authError.internalError("saveScratch: put failed", e))
|
|
250
|
+
}
|
|
251
|
+
return ok(undefined)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async readScratch(key: string): Promise<Result<string>> {
|
|
255
|
+
let stored: StoredScratch | undefined
|
|
256
|
+
try {
|
|
257
|
+
stored = await this.#storage.get<StoredScratch>(scratchKey(key))
|
|
258
|
+
} catch (e) {
|
|
259
|
+
return err(authError.internalError("readScratch: get failed", e))
|
|
260
|
+
}
|
|
261
|
+
if (!stored) {
|
|
262
|
+
return err(authError.unknownState(`scratch "${key}" unknown`))
|
|
263
|
+
}
|
|
264
|
+
if (this.#clock() >= stored.expiresAt) {
|
|
265
|
+
try {
|
|
266
|
+
await this.#storage.delete(scratchKey(key))
|
|
267
|
+
} catch {}
|
|
268
|
+
return err(authError.unknownState(`scratch "${key}" expired`))
|
|
269
|
+
}
|
|
270
|
+
return ok(stored.value)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async deleteScratch(key: string): Promise<Result<void>> {
|
|
274
|
+
try {
|
|
275
|
+
await this.#storage.delete(scratchKey(key))
|
|
276
|
+
} catch (e) {
|
|
277
|
+
return err(authError.internalError("deleteScratch: delete failed", e))
|
|
278
|
+
}
|
|
279
|
+
return ok(undefined)
|
|
280
|
+
}
|
|
221
281
|
}
|
|
222
282
|
|
|
223
283
|
const flowKey = (flowId: string) => `flow:${flowId}`
|
|
224
284
|
const sessionKey = (sessionId: string) => `session:${sessionId}`
|
|
285
|
+
const scratchKey = (key: string) => `scratch:${key}`
|
|
225
286
|
|
|
226
287
|
// Re-exported for adapter users wiring `storage.transaction` callbacks.
|
|
227
288
|
export type { DurableObjectStorageLike, DurableObjectTransactionLike }
|
|
@@ -211,6 +211,69 @@ export class DynamoSessionStore implements SessionStore {
|
|
|
211
211
|
}
|
|
212
212
|
return ok(undefined)
|
|
213
213
|
}
|
|
214
|
+
|
|
215
|
+
async saveScratch(
|
|
216
|
+
key: string,
|
|
217
|
+
value: string,
|
|
218
|
+
ttlMs: number,
|
|
219
|
+
): Promise<Result<void>> {
|
|
220
|
+
if (ttlMs <= 0) {
|
|
221
|
+
return err(
|
|
222
|
+
authError.internalError(
|
|
223
|
+
`saveScratch: ttlMs must be positive, got ${ttlMs}`,
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
const expiresAtMs = this.#clock() + ttlMs
|
|
228
|
+
try {
|
|
229
|
+
// Unconditional put — scratch overwrites any prior value for the key.
|
|
230
|
+
await this.#exec.put({
|
|
231
|
+
item: {
|
|
232
|
+
pk: "scratch",
|
|
233
|
+
sk: key,
|
|
234
|
+
value,
|
|
235
|
+
expires_at: expiresAtMs,
|
|
236
|
+
ttl: Math.floor(expiresAtMs / 1000),
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
} catch (e) {
|
|
240
|
+
return err(authError.internalError("saveScratch: put failed", e))
|
|
241
|
+
}
|
|
242
|
+
return ok(undefined)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async readScratch(key: string): Promise<Result<string>> {
|
|
246
|
+
let row: Record<string, unknown> | undefined
|
|
247
|
+
try {
|
|
248
|
+
row = await this.#exec.get({
|
|
249
|
+
key: { pk: "scratch", sk: key },
|
|
250
|
+
consistentRead: true,
|
|
251
|
+
})
|
|
252
|
+
} catch (e) {
|
|
253
|
+
return err(authError.internalError("readScratch: get failed", e))
|
|
254
|
+
}
|
|
255
|
+
if (!row) {
|
|
256
|
+
return err(authError.unknownState(`scratch "${key}" unknown`))
|
|
257
|
+
}
|
|
258
|
+
// Native DynamoDB TTL eviction is best-effort (can lag up to ~48h), so
|
|
259
|
+
// filter on the adapter clock for correctness.
|
|
260
|
+
if (this.#clock() >= Number(row.expires_at)) {
|
|
261
|
+
try {
|
|
262
|
+
await this.#exec.delete({ key: { pk: "scratch", sk: key } })
|
|
263
|
+
} catch {}
|
|
264
|
+
return err(authError.unknownState(`scratch "${key}" expired`))
|
|
265
|
+
}
|
|
266
|
+
return ok(String(row.value))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async deleteScratch(key: string): Promise<Result<void>> {
|
|
270
|
+
try {
|
|
271
|
+
await this.#exec.delete({ key: { pk: "scratch", sk: key } })
|
|
272
|
+
} catch (e) {
|
|
273
|
+
return err(authError.internalError("deleteScratch: delete failed", e))
|
|
274
|
+
}
|
|
275
|
+
return ok(undefined)
|
|
276
|
+
}
|
|
214
277
|
}
|
|
215
278
|
|
|
216
279
|
function parseFlowPayload(raw: unknown): FlowRecord {
|
|
@@ -28,6 +28,11 @@ type StoredPar = {
|
|
|
28
28
|
expiresAt: number
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
type StoredScratch = {
|
|
32
|
+
value: string
|
|
33
|
+
expiresAt: number
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
export type MemorySessionStoreOptions = {
|
|
32
37
|
clock?: Clock
|
|
33
38
|
}
|
|
@@ -37,6 +42,7 @@ export class MemorySessionStore implements SessionStore {
|
|
|
37
42
|
#flows = new Map<string, StoredFlow>()
|
|
38
43
|
#sessions = new Map<string, SessionRecord>()
|
|
39
44
|
#par = new Map<string, StoredPar>()
|
|
45
|
+
#scratch = new Map<string, StoredScratch>()
|
|
40
46
|
|
|
41
47
|
constructor(opts: MemorySessionStoreOptions = {}) {
|
|
42
48
|
this.#clock = opts.clock ?? realClock
|
|
@@ -168,4 +174,37 @@ export class MemorySessionStore implements SessionStore {
|
|
|
168
174
|
}
|
|
169
175
|
return ok(stored.record)
|
|
170
176
|
}
|
|
177
|
+
|
|
178
|
+
async saveScratch(
|
|
179
|
+
key: string,
|
|
180
|
+
value: string,
|
|
181
|
+
ttlMs: number,
|
|
182
|
+
): Promise<Result<void>> {
|
|
183
|
+
if (ttlMs <= 0) {
|
|
184
|
+
return err(
|
|
185
|
+
authError.internalError(
|
|
186
|
+
`saveScratch: ttlMs must be positive, got ${ttlMs}`,
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
this.#scratch.set(key, { value, expiresAt: this.#clock() + ttlMs })
|
|
191
|
+
return ok(undefined)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async readScratch(key: string): Promise<Result<string>> {
|
|
195
|
+
const stored = this.#scratch.get(key)
|
|
196
|
+
if (!stored) {
|
|
197
|
+
return err(authError.unknownState(`scratch "${key}" unknown`))
|
|
198
|
+
}
|
|
199
|
+
if (this.#clock() >= stored.expiresAt) {
|
|
200
|
+
this.#scratch.delete(key)
|
|
201
|
+
return err(authError.unknownState(`scratch "${key}" expired`))
|
|
202
|
+
}
|
|
203
|
+
return ok(stored.value)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async deleteScratch(key: string): Promise<Result<void>> {
|
|
207
|
+
this.#scratch.delete(key)
|
|
208
|
+
return ok(undefined)
|
|
209
|
+
}
|
|
171
210
|
}
|
|
@@ -89,6 +89,12 @@ CREATE TABLE IF NOT EXISTS openauth_sessions (
|
|
|
89
89
|
expires_at bigint NOT NULL
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
+
CREATE TABLE IF NOT EXISTS openauth_scratch (
|
|
93
|
+
scratch_key text PRIMARY KEY,
|
|
94
|
+
value text NOT NULL,
|
|
95
|
+
expires_at bigint NOT NULL
|
|
96
|
+
);
|
|
97
|
+
|
|
92
98
|
CREATE TABLE IF NOT EXISTS openauth_audit_events (
|
|
93
99
|
id bigserial PRIMARY KEY,
|
|
94
100
|
ts bigint NOT NULL,
|