scimmy-hono-routers 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Clifton Cunningham
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # scimmy-hono-routers
2
+
3
+ [SCIMMY](https://github.com/scimmyjs/scimmy) router middleware for [Hono](https://hono.dev/) — add [SCIM 2.0](https://datatracker.ietf.org/doc/html/rfc7644) server endpoints to any Hono application.
4
+
5
+ This is the Hono equivalent of [scimmy-routers](https://github.com/scimmyjs/scimmy-routers) (Express). It provides the same SCIM 2.0 endpoint coverage using Hono's native routing and async handling, in a single `scimmyHono()` factory function.
6
+
7
+ The routers leverage work done in the [SCIMMY](https://github.com/scimmyjs/scimmy) package, which handles all the hard parts of the SCIM 2.0 protocol: filter parsing, PATCH operations, schema validation, and response formatting.
8
+
9
+ > For details on how to use SCIMMY Resources, [visit the SCIMMY documentation](https://scimmyjs.github.io)!
10
+
11
+ ## Requirements
12
+
13
+ - [Node.js](https://nodejs.org) v24+
14
+ - [Hono](https://hono.dev/) v4+
15
+ - [SCIMMY](https://github.com/scimmyjs/scimmy) v1.x
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install scimmy-hono-routers scimmy hono
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```ts
26
+ import { Hono } from "hono";
27
+ import { scimmyHono, SCIMMY } from "scimmy-hono-routers";
28
+
29
+ // 1. Declare your SCIM resources with handlers (see SCIMMY docs for full details)
30
+ SCIMMY.Resources.declare(SCIMMY.Resources.User)
31
+ .ingress(async (resource, data, ctx) => {
32
+ if (resource.id) {
33
+ return await db.users.update(resource.id, data);
34
+ }
35
+ return await db.users.create(data);
36
+ })
37
+ .egress(async (resource, ctx) => {
38
+ if (resource.id) {
39
+ return await db.users.findById(resource.id);
40
+ }
41
+ return await db.users.list(resource.filter);
42
+ })
43
+ .degress(async (resource, ctx) => {
44
+ await db.users.delete(resource.id);
45
+ });
46
+
47
+ SCIMMY.Resources.declare(SCIMMY.Resources.Group, {
48
+ /* Your handlers for group resource type */
49
+ });
50
+
51
+ // 2. Create the SCIM router
52
+ const scim = scimmyHono({
53
+ type: "bearer",
54
+ handler: async (c) => {
55
+ const token = c.req.header("Authorization")?.replace("Bearer ", "");
56
+ if (!token || !isValidToken(token)) throw new Error("Unauthorized");
57
+ return getUserIdFromToken(token); // Return user ID string for /Me endpoint
58
+ },
59
+ context: async (c) => ({
60
+ tenantId: c.get("tenantId"),
61
+ }),
62
+ baseUri: (c) => new URL(c.req.url).origin,
63
+ });
64
+
65
+ // 3. Mount at your SCIM base path
66
+ const app = new Hono();
67
+ app.route("/scim/v2", scim);
68
+
69
+ export default app;
70
+ ```
71
+
72
+ ## API
73
+
74
+ `scimmyHono()` returns a standard Hono app instance that can be mounted at any path using `app.route()`. It is recommended to mount at `/scim/v2` or `/scim` to follow SCIM conventions.
75
+
76
+ ### Options
77
+
78
+ ```ts
79
+ interface SCIMMYHonoOptions {
80
+ type: string;
81
+ handler: AuthenticationHandler;
82
+ context?: AuthenticationContext;
83
+ baseUri?: AuthenticationBaseUri;
84
+ docUri?: string;
85
+ }
86
+ ```
87
+
88
+ - **`type`** (required) — SCIM service provider authentication scheme type.
89
+ Supported values: `"bearer"`, `"oauth"`, `"basic"`, `"digest"`, which map to SCIM authentication scheme types `oauthbearertoken`, `oauth2`, `httpbasic`, and `httpdigest` respectively.
90
+
91
+ - **`handler`** (required) — Function invoked to authenticate each SCIM request. Receives the Hono `Context` object.
92
+ - Throw an `Error` to reject the request (returns 401 with the error message).
93
+ - Return a user ID string to identify the authenticated user (used by the `/Me` endpoint).
94
+
95
+ - **`context`** (optional) — Function invoked to provide additional context to each request. The returned value is passed directly to SCIMMY's `ingress`/`egress`/`degress` handler methods. Useful for multi-tenancy, passing database connections, etc.
96
+
97
+ - **`baseUri`** (optional) — Function invoked to determine the base URI for `meta.location` properties in SCIM responses. Must return a valid URL string (e.g., `"https://example.com"`). If omitted, locations are derived from the request URL.
98
+
99
+ - **`docUri`** (optional) — URL string for the documentation URI of the authentication scheme, included in the `ServiceProviderConfig` response.
100
+
101
+ ### Type Signatures
102
+
103
+ ```ts
104
+ type AuthenticationHandler = (c: Context) => string | Promise<string>;
105
+ type AuthenticationContext = (c: Context) => unknown | Promise<unknown>;
106
+ type AuthenticationBaseUri = (c: Context) => string | Promise<string>;
107
+ ```
108
+
109
+ ## Endpoints
110
+
111
+ All standard SCIM 2.0 endpoints ([RFC 7644](https://datatracker.ietf.org/doc/html/rfc7644)) are supported:
112
+
113
+ | Endpoint | Methods | Description |
114
+ |---|---|---|
115
+ | `/ServiceProviderConfig` | GET | Server capabilities and configuration |
116
+ | `/Schemas` | GET | Schema definitions |
117
+ | `/Schemas/:id` | GET | Single schema by URN |
118
+ | `/ResourceTypes` | GET | Resource type definitions |
119
+ | `/ResourceTypes/:id` | GET | Single resource type |
120
+ | `/Users` | GET, POST | List/create users |
121
+ | `/Users/:id` | GET, PUT, PATCH, DELETE | Read/replace/update/delete a user |
122
+ | `/Groups` | GET, POST | List/create groups |
123
+ | `/Groups/:id` | GET, PUT, PATCH, DELETE | Read/replace/update/delete a group |
124
+ | `/Me` | GET | Currently authenticated user |
125
+ | `/.search` | POST | Cross-resource search ([RFC 7644 §3.4.3](https://datatracker.ietf.org/doc/html/rfc7644#section-3.4.3)) |
126
+ | `/Bulk` | POST | Bulk operations ([RFC 7644 §3.7](https://datatracker.ietf.org/doc/html/rfc7644#section-3.7)) |
127
+
128
+ Resource-scoped `/.search` endpoints (e.g., `/Users/.search`) are also supported.
129
+
130
+ All responses use `Content-Type: application/scim+json`.
131
+
132
+ ## How It Works
133
+
134
+ This package is a thin adapter (~280 lines of TypeScript) that maps Hono routes to SCIMMY's resource operations:
135
+
136
+ | HTTP Method | SCIMMY Operation |
137
+ |---|---|
138
+ | `GET /Resource` | `new Resource(query).read(context)` |
139
+ | `GET /Resource/:id` | `new Resource(id, query).read(context)` |
140
+ | `POST /Resource` | `new Resource(query).write(body, context)` |
141
+ | `PUT /Resource/:id` | `new Resource(id, query).write(body, context)` |
142
+ | `PATCH /Resource/:id` | `new Resource(id, query).patch(body, context)` |
143
+ | `DELETE /Resource/:id` | `new Resource(id).dispose(context)` |
144
+
145
+ SCIMMY handles all the protocol complexity: filter parsing, PATCH operation application, schema validation, `ListResponse` pagination, error formatting, and more.
146
+
147
+ ## Comparison with scimmy-routers (Express)
148
+
149
+ | | scimmy-routers | scimmy-hono-routers |
150
+ |---|---|---|
151
+ | Framework | Express 4+ | Hono 4+ |
152
+ | Language | JavaScript | TypeScript |
153
+ | Pattern | Class extending `Router` | Factory function returning `Hono` |
154
+ | Auth handler receives | `express.Request` | `hono.Context` |
155
+ | Test framework | Mocha + Sinon + Supertest | Vitest + Sinon |
156
+
157
+ The API surface and SCIM compliance are identical — both packages use the same SCIMMY core for all protocol logic.
158
+
159
+ ## Development
160
+
161
+ ### Prerequisites
162
+
163
+ ```bash
164
+ nvm use # Uses Node.js 24+ (see .nvmrc)
165
+ npm install
166
+ ```
167
+
168
+ ### Commands
169
+
170
+ ```bash
171
+ npm run build # Build with tsup (ESM + .d.ts)
172
+ npm run dev # Build in watch mode
173
+ npm test # Run tests (vitest)
174
+ npm run test:watch # Run tests in watch mode
175
+ npm run typecheck # TypeScript type check (tsc --noEmit)
176
+ ```
177
+
178
+ ### Project Structure
179
+
180
+ ```
181
+ src/
182
+ index.ts # Single-file implementation (~280 lines)
183
+ test/
184
+ index.test.ts # Full test suite (48 tests) using sinon stubs
185
+ helpers.ts # Test utilities (request helpers, assertion helpers)
186
+ ```
187
+
188
+ ### Testing Approach
189
+
190
+ Tests mirror the [scimmy-routers test suite](https://github.com/scimmyjs/scimmy-routers/tree/main/test) — using sinon stubs on SCIMMY resource prototypes to test routes in isolation. This validates that the Hono adapter correctly maps HTTP requests to SCIMMY operations without requiring a real database backend.
191
+
192
+ ## Related Projects
193
+
194
+ - [SCIMMY](https://github.com/scimmyjs/scimmy) — SCIM 2.0 protocol implementation for Node.js (the core library)
195
+ - [scimmy-routers](https://github.com/scimmyjs/scimmy-routers) — SCIMMY Express routers (the Express equivalent of this package)
196
+ - [Hono](https://hono.dev/) — Ultrafast web framework for the Edges
197
+
198
+ ## License
199
+
200
+ MIT
@@ -0,0 +1,63 @@
1
+ import { Context, Hono } from 'hono';
2
+ export { default as SCIMMY } from 'scimmy';
3
+
4
+ /**
5
+ * Method invoked to authenticate a SCIM request.
6
+ * Should throw an error if the request is not authenticated.
7
+ * @returns The ID of the currently authenticated user (consumed by /Me endpoint)
8
+ */
9
+ type AuthenticationHandler = (c: Context) => string | Promise<string>;
10
+ /**
11
+ * Method invoked to provide authentication context to a SCIM request.
12
+ * @returns Any information to pass through to a Resource's handler methods
13
+ */
14
+ type AuthenticationContext = (c: Context) => unknown | Promise<unknown>;
15
+ /**
16
+ * Method invoked to determine a base URI for location properties in a SCIM response.
17
+ * @returns The base URI to use for location properties in SCIM responses
18
+ */
19
+ type AuthenticationBaseUri = (c: Context) => string | Promise<string>;
20
+ interface SCIMMYHonoOptions {
21
+ /** SCIM authentication scheme type: "oauth", "bearer", "basic", or "digest" */
22
+ type: string;
23
+ /** Method to invoke to authenticate SCIM requests */
24
+ handler: AuthenticationHandler;
25
+ /** Method to invoke to evaluate context passed to SCIMMY handlers */
26
+ context?: AuthenticationContext;
27
+ /** Method to invoke to determine the base URI for location properties */
28
+ baseUri?: AuthenticationBaseUri;
29
+ /** URL to use as documentation URI for the authentication scheme */
30
+ docUri?: string;
31
+ }
32
+ /**
33
+ * Create a Hono app with SCIM 2.0 endpoints powered by SCIMMY.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { Hono } from "hono";
38
+ * import { scimmyHono, SCIMMY } from "scimmy-hono-routers";
39
+ *
40
+ * // Declare resources and handlers
41
+ * SCIMMY.Resources.declare(SCIMMY.Resources.User)
42
+ * .ingress((resource, data, ctx) => { ... })
43
+ * .egress((resource, ctx) => { ... })
44
+ * .degress((resource, ctx) => { ... });
45
+ *
46
+ * const scim = scimmyHono({
47
+ * type: "bearer",
48
+ * handler: async (c) => {
49
+ * const token = c.req.header("Authorization")?.replace("Bearer ", "");
50
+ * if (!token) throw new Error("Unauthorized");
51
+ * return userId;
52
+ * },
53
+ * context: async (c) => ({ tenantId: c.get("tenantId") }),
54
+ * baseUri: (c) => `${new URL(c.req.url).origin}`,
55
+ * });
56
+ *
57
+ * const app = new Hono();
58
+ * app.route("/scim/v2", scim);
59
+ * ```
60
+ */
61
+ declare function scimmyHono(options: SCIMMYHonoOptions): Hono;
62
+
63
+ export { type AuthenticationBaseUri, type AuthenticationContext, type AuthenticationHandler, type SCIMMYHonoOptions, scimmyHono as default, scimmyHono };
package/dist/index.js ADDED
@@ -0,0 +1,350 @@
1
+ // src/index.ts
2
+ import { Hono } from "hono";
3
+ import SCIMMY from "scimmy";
4
+ var authSchemeTypes = {
5
+ oauth: {
6
+ type: "oauth2",
7
+ name: "OAuth 2.0 Authorization Framework",
8
+ description: "Authentication scheme using the OAuth 2.0 Authorization Framework Standard",
9
+ specUri: "https://datatracker.ietf.org/doc/html/rfc6749"
10
+ },
11
+ bearer: {
12
+ type: "oauthbearertoken",
13
+ name: "OAuth Bearer Token",
14
+ description: "Authentication scheme using the OAuth Bearer Token Standard",
15
+ specUri: "https://datatracker.ietf.org/doc/html/rfc6750"
16
+ },
17
+ basic: {
18
+ type: "httpbasic",
19
+ name: "HTTP Basic",
20
+ description: "Authentication scheme using the HTTP Basic Standard",
21
+ specUri: "https://datatracker.ietf.org/doc/html/rfc2617"
22
+ },
23
+ digest: {
24
+ type: "httpdigest",
25
+ name: "HTTP Digest",
26
+ description: "Authentication scheme using the HTTP Digest Standard",
27
+ specUri: "https://datatracker.ietf.org/doc/html/rfc2617"
28
+ }
29
+ };
30
+ function scimJson(c, body, status = 200) {
31
+ return c.json(body, status, {
32
+ "Content-Type": "application/scim+json"
33
+ });
34
+ }
35
+ function scimError(c, ex) {
36
+ const status = ex.status ?? 500;
37
+ return scimJson(c, new SCIMMY.Messages.Error(ex), status);
38
+ }
39
+ function castPaginationParams(query) {
40
+ const result = { ...query };
41
+ for (const param of ["startIndex", "count"]) {
42
+ if (result[param] && typeof result[param] === "string" && !Number.isNaN(+result[param])) {
43
+ result[param] = +result[param];
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ function scimmyHono(options) {
49
+ const {
50
+ type,
51
+ handler,
52
+ context = () => {
53
+ },
54
+ baseUri = () => "",
55
+ docUri
56
+ } = options;
57
+ if (!type) {
58
+ throw new TypeError(
59
+ "Missing required parameter 'type' from authentication scheme in scimmyHono"
60
+ );
61
+ }
62
+ if (!handler) {
63
+ throw new TypeError(
64
+ "Missing required parameter 'handler' from authentication scheme in scimmyHono"
65
+ );
66
+ }
67
+ if (typeof handler !== "function") {
68
+ throw new TypeError(
69
+ "Parameter 'handler' must be of type 'function' in scimmyHono"
70
+ );
71
+ }
72
+ if (!authSchemeTypes[type]) {
73
+ throw new TypeError(
74
+ `Unknown authentication scheme type '${type}' in scimmyHono`
75
+ );
76
+ }
77
+ if (typeof context !== "function") {
78
+ throw new TypeError(
79
+ "Parameter 'context' must be of type 'function' in scimmyHono"
80
+ );
81
+ }
82
+ if (typeof baseUri !== "function") {
83
+ throw new TypeError(
84
+ "Parameter 'baseUri' must be of type 'function' in scimmyHono"
85
+ );
86
+ }
87
+ SCIMMY.Config.set({
88
+ patch: true,
89
+ filter: true,
90
+ sort: true,
91
+ bulk: true,
92
+ authenticationSchemes: [
93
+ { ...authSchemeTypes[type], documentationUri: docUri }
94
+ ]
95
+ });
96
+ const app = new Hono();
97
+ app.use("*", async (c, next) => {
98
+ try {
99
+ const basepath = await baseUri(c) ?? "";
100
+ if (!basepath || typeof basepath === "string" && basepath.match(/^https?:\/\//)) {
101
+ const url = new URL(c.req.url);
102
+ const routePath = url.pathname.replace(/\/(Schemas|ResourceTypes|ServiceProviderConfig|Users|Groups|Bulk|Me|\.search).*$/, "");
103
+ const location = basepath ? basepath.replace(/\/$/, "") + routePath : `${url.origin}${routePath}`;
104
+ SCIMMY.Resources.Schema.basepath(location);
105
+ SCIMMY.Resources.ResourceType.basepath(location);
106
+ SCIMMY.Resources.ServiceProviderConfig.basepath(location);
107
+ for (const Resource of Object.values(SCIMMY.Resources.declared())) {
108
+ Resource.basepath(location);
109
+ }
110
+ await next();
111
+ } else {
112
+ throw new TypeError(
113
+ "Method 'baseUri' must return a URL string in scimmyHono"
114
+ );
115
+ }
116
+ } catch (ex) {
117
+ return scimError(c, ex);
118
+ }
119
+ });
120
+ app.use("*", async (c, next) => {
121
+ try {
122
+ await handler(c);
123
+ await next();
124
+ } catch (ex) {
125
+ return scimJson(
126
+ c,
127
+ new SCIMMY.Messages.Error({ status: 401, message: ex.message }),
128
+ 401
129
+ );
130
+ }
131
+ });
132
+ app.get("/Schemas", async (c) => {
133
+ try {
134
+ const query = castPaginationParams(c.req.query());
135
+ return scimJson(c, await new SCIMMY.Resources.Schema(query).read());
136
+ } catch (ex) {
137
+ return scimError(c, ex);
138
+ }
139
+ });
140
+ app.get("/Schemas/:id", async (c) => {
141
+ try {
142
+ const query = castPaginationParams(c.req.query());
143
+ return scimJson(
144
+ c,
145
+ await new SCIMMY.Resources.Schema(c.req.param("id"), query).read()
146
+ );
147
+ } catch (ex) {
148
+ return scimError(c, ex);
149
+ }
150
+ });
151
+ app.get("/ResourceTypes", async (c) => {
152
+ try {
153
+ const query = castPaginationParams(c.req.query());
154
+ return scimJson(
155
+ c,
156
+ await new SCIMMY.Resources.ResourceType(query).read()
157
+ );
158
+ } catch (ex) {
159
+ return scimError(c, ex);
160
+ }
161
+ });
162
+ app.get("/ResourceTypes/:id", async (c) => {
163
+ try {
164
+ const query = castPaginationParams(c.req.query());
165
+ return scimJson(
166
+ c,
167
+ await new SCIMMY.Resources.ResourceType(c.req.param("id"), query).read()
168
+ );
169
+ } catch (ex) {
170
+ return scimError(c, ex);
171
+ }
172
+ });
173
+ app.get("/ServiceProviderConfig", async (c) => {
174
+ try {
175
+ const query = castPaginationParams(c.req.query());
176
+ return scimJson(
177
+ c,
178
+ await new SCIMMY.Resources.ServiceProviderConfig(query).read()
179
+ );
180
+ } catch (ex) {
181
+ return scimError(c, ex);
182
+ }
183
+ });
184
+ app.post("/.search", async (c) => {
185
+ try {
186
+ const body = await c.req.json();
187
+ const ctx = await context(c);
188
+ return scimJson(
189
+ c,
190
+ await new SCIMMY.Messages.SearchRequest(body).apply(void 0, ctx)
191
+ );
192
+ } catch (ex) {
193
+ return scimError(c, ex);
194
+ }
195
+ });
196
+ app.post("/Bulk", async (c) => {
197
+ try {
198
+ const { supported, maxPayloadSize, maxOperations } = SCIMMY.Config.get()?.bulk ?? {};
199
+ if (!supported) {
200
+ return scimJson(
201
+ c,
202
+ new SCIMMY.Messages.Error({
203
+ status: 501,
204
+ message: "Endpoint Not Implemented"
205
+ }),
206
+ 501
207
+ );
208
+ }
209
+ const contentLength = Number(c.req.header("content-length") ?? 0);
210
+ if (contentLength > maxPayloadSize) {
211
+ return scimJson(
212
+ c,
213
+ new SCIMMY.Messages.Error({
214
+ status: 413,
215
+ message: `The size of the bulk operation exceeds maxPayloadSize limit (${maxPayloadSize})`
216
+ }),
217
+ 413
218
+ );
219
+ }
220
+ const body = await c.req.json();
221
+ const ctx = await context(c);
222
+ return scimJson(
223
+ c,
224
+ await new SCIMMY.Messages.BulkRequest(body, maxOperations).apply(
225
+ void 0,
226
+ ctx
227
+ )
228
+ );
229
+ } catch (ex) {
230
+ return scimError(c, ex);
231
+ }
232
+ });
233
+ app.get("/Me", async (c) => {
234
+ try {
235
+ const id = await handler(c);
236
+ const isDeclared = SCIMMY.Resources.declared(SCIMMY.Resources.User);
237
+ const user = isDeclared && typeof id === "string" ? await new SCIMMY.Resources.User(id).read(await context(c)) : false;
238
+ if (user && user?.meta?.location) {
239
+ return scimJson(c, user);
240
+ }
241
+ return scimJson(
242
+ c,
243
+ new SCIMMY.Messages.Error({
244
+ status: 501,
245
+ message: "Endpoint Not Implemented"
246
+ }),
247
+ 501
248
+ );
249
+ } catch (ex) {
250
+ return scimError(c, ex);
251
+ }
252
+ });
253
+ for (const Resource of Object.values(SCIMMY.Resources.declared())) {
254
+ const endpoint = Resource.endpoint;
255
+ app.post(`${endpoint}/.search`, async (c) => {
256
+ try {
257
+ const body = await c.req.json();
258
+ const ctx = await context(c);
259
+ return scimJson(
260
+ c,
261
+ await new SCIMMY.Messages.SearchRequest(body).apply([Resource], ctx)
262
+ );
263
+ } catch (ex) {
264
+ return scimError(c, ex);
265
+ }
266
+ });
267
+ app.get(endpoint, async (c) => {
268
+ try {
269
+ const query = castPaginationParams(c.req.query());
270
+ const ctx = await context(c);
271
+ return scimJson(c, await new Resource(query).read(ctx));
272
+ } catch (ex) {
273
+ return scimError(c, ex);
274
+ }
275
+ });
276
+ app.get(`${endpoint}/:id`, async (c) => {
277
+ try {
278
+ const query = castPaginationParams(c.req.query());
279
+ const ctx = await context(c);
280
+ return scimJson(
281
+ c,
282
+ await new Resource(c.req.param("id"), query).read(ctx)
283
+ );
284
+ } catch (ex) {
285
+ return scimError(c, ex);
286
+ }
287
+ });
288
+ app.post(endpoint, async (c) => {
289
+ try {
290
+ const query = castPaginationParams(c.req.query());
291
+ const body = await c.req.json();
292
+ const ctx = await context(c);
293
+ return scimJson(c, await new Resource(query).write(body, ctx), 201);
294
+ } catch (ex) {
295
+ return scimError(c, ex);
296
+ }
297
+ });
298
+ app.put(`${endpoint}/:id`, async (c) => {
299
+ try {
300
+ const query = castPaginationParams(c.req.query());
301
+ const body = await c.req.json();
302
+ const ctx = await context(c);
303
+ return scimJson(
304
+ c,
305
+ await new Resource(c.req.param("id"), query).write(body, ctx)
306
+ );
307
+ } catch (ex) {
308
+ return scimError(c, ex);
309
+ }
310
+ });
311
+ app.patch(`${endpoint}/:id`, async (c) => {
312
+ try {
313
+ const query = castPaginationParams(c.req.query());
314
+ const body = await c.req.json();
315
+ const ctx = await context(c);
316
+ const value = await new Resource(c.req.param("id"), query).patch(
317
+ body,
318
+ ctx
319
+ );
320
+ return value ? scimJson(c, value) : c.body(null, 204);
321
+ } catch (ex) {
322
+ return scimError(c, ex);
323
+ }
324
+ });
325
+ app.delete(`${endpoint}/:id`, async (c) => {
326
+ try {
327
+ const ctx = await context(c);
328
+ await new Resource(c.req.param("id")).dispose(ctx);
329
+ return c.body(null, 204);
330
+ } catch (ex) {
331
+ return scimError(c, ex);
332
+ }
333
+ });
334
+ }
335
+ app.all("*", (c) => {
336
+ return scimJson(
337
+ c,
338
+ new SCIMMY.Messages.Error({ status: 404, message: "Endpoint Not Found" }),
339
+ 404
340
+ );
341
+ });
342
+ return app;
343
+ }
344
+ var index_default = scimmyHono;
345
+ export {
346
+ SCIMMY,
347
+ index_default as default,
348
+ scimmyHono
349
+ };
350
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport SCIMMY from \"scimmy\";\n\n// Re-export SCIMMY for consumption by dependent packages\nexport { SCIMMY };\n\n/**\n * Predefined SCIM Service Provider Config authentication scheme types\n */\nconst authSchemeTypes: Record<\n string,\n { type: string; name: string; description: string; specUri: string }\n> = {\n oauth: {\n type: \"oauth2\",\n name: \"OAuth 2.0 Authorization Framework\",\n description:\n \"Authentication scheme using the OAuth 2.0 Authorization Framework Standard\",\n specUri: \"https://datatracker.ietf.org/doc/html/rfc6749\",\n },\n bearer: {\n type: \"oauthbearertoken\",\n name: \"OAuth Bearer Token\",\n description:\n \"Authentication scheme using the OAuth Bearer Token Standard\",\n specUri: \"https://datatracker.ietf.org/doc/html/rfc6750\",\n },\n basic: {\n type: \"httpbasic\",\n name: \"HTTP Basic\",\n description: \"Authentication scheme using the HTTP Basic Standard\",\n specUri: \"https://datatracker.ietf.org/doc/html/rfc2617\",\n },\n digest: {\n type: \"httpdigest\",\n name: \"HTTP Digest\",\n description: \"Authentication scheme using the HTTP Digest Standard\",\n specUri: \"https://datatracker.ietf.org/doc/html/rfc2617\",\n },\n};\n\n/**\n * Method invoked to authenticate a SCIM request.\n * Should throw an error if the request is not authenticated.\n * @returns The ID of the currently authenticated user (consumed by /Me endpoint)\n */\nexport type AuthenticationHandler = (c: Context) => string | Promise<string>;\n\n/**\n * Method invoked to provide authentication context to a SCIM request.\n * @returns Any information to pass through to a Resource's handler methods\n */\nexport type AuthenticationContext = (c: Context) => unknown | Promise<unknown>;\n\n/**\n * Method invoked to determine a base URI for location properties in a SCIM response.\n * @returns The base URI to use for location properties in SCIM responses\n */\nexport type AuthenticationBaseUri = (c: Context) => string | Promise<string>;\n\nexport interface SCIMMYHonoOptions {\n /** SCIM authentication scheme type: \"oauth\", \"bearer\", \"basic\", or \"digest\" */\n type: string;\n /** Method to invoke to authenticate SCIM requests */\n handler: AuthenticationHandler;\n /** Method to invoke to evaluate context passed to SCIMMY handlers */\n context?: AuthenticationContext;\n /** Method to invoke to determine the base URI for location properties */\n baseUri?: AuthenticationBaseUri;\n /** URL to use as documentation URI for the authentication scheme */\n docUri?: string;\n}\n\n/**\n * Helper to send a SCIM JSON response\n */\nfunction scimJson(c: Context, body: unknown, status: number = 200): Response {\n return c.json(body as object, status as any, {\n \"Content-Type\": \"application/scim+json\",\n });\n}\n\n/**\n * Helper to send a SCIM error response\n */\nfunction scimError(\n c: Context,\n ex: any\n): Response {\n const status = ex.status ?? 500;\n return scimJson(c, new SCIMMY.Messages.Error(ex), status);\n}\n\n/**\n * Cast pagination query parameters from strings to numbers\n */\nfunction castPaginationParams(query: Record<string, string>): Record<string, any> {\n const result: Record<string, any> = { ...query };\n for (const param of [\"startIndex\", \"count\"]) {\n if (result[param] && typeof result[param] === \"string\" && !Number.isNaN(+result[param])) {\n result[param] = +result[param];\n }\n }\n return result;\n}\n\n/**\n * Create a Hono app with SCIM 2.0 endpoints powered by SCIMMY.\n *\n * @example\n * ```ts\n * import { Hono } from \"hono\";\n * import { scimmyHono, SCIMMY } from \"scimmy-hono-routers\";\n *\n * // Declare resources and handlers\n * SCIMMY.Resources.declare(SCIMMY.Resources.User)\n * .ingress((resource, data, ctx) => { ... })\n * .egress((resource, ctx) => { ... })\n * .degress((resource, ctx) => { ... });\n *\n * const scim = scimmyHono({\n * type: \"bearer\",\n * handler: async (c) => {\n * const token = c.req.header(\"Authorization\")?.replace(\"Bearer \", \"\");\n * if (!token) throw new Error(\"Unauthorized\");\n * return userId;\n * },\n * context: async (c) => ({ tenantId: c.get(\"tenantId\") }),\n * baseUri: (c) => `${new URL(c.req.url).origin}`,\n * });\n *\n * const app = new Hono();\n * app.route(\"/scim/v2\", scim);\n * ```\n */\nexport function scimmyHono(options: SCIMMYHonoOptions): Hono {\n const {\n type,\n handler,\n context = () => {},\n baseUri = () => \"\",\n docUri,\n } = options;\n\n // Validate options\n if (!type) {\n throw new TypeError(\n \"Missing required parameter 'type' from authentication scheme in scimmyHono\"\n );\n }\n if (!handler) {\n throw new TypeError(\n \"Missing required parameter 'handler' from authentication scheme in scimmyHono\"\n );\n }\n if (typeof handler !== \"function\") {\n throw new TypeError(\n \"Parameter 'handler' must be of type 'function' in scimmyHono\"\n );\n }\n if (!authSchemeTypes[type]) {\n throw new TypeError(\n `Unknown authentication scheme type '${type}' in scimmyHono`\n );\n }\n if (typeof context !== \"function\") {\n throw new TypeError(\n \"Parameter 'context' must be of type 'function' in scimmyHono\"\n );\n }\n if (typeof baseUri !== \"function\") {\n throw new TypeError(\n \"Parameter 'baseUri' must be of type 'function' in scimmyHono\"\n );\n }\n\n // Register the authentication scheme and SCIM config\n SCIMMY.Config.set({\n patch: true,\n filter: true,\n sort: true,\n bulk: true,\n authenticationSchemes: [\n { ...authSchemeTypes[type], documentationUri: docUri },\n ],\n });\n\n const app = new Hono();\n\n // Middleware: set basepath for all resource types\n app.use(\"*\", async (c, next) => {\n try {\n const basepath = (await baseUri(c)) ?? \"\";\n\n if (\n !basepath ||\n (typeof basepath === \"string\" && basepath.match(/^https?:\\/\\//))\n ) {\n // Construct location from basepath + mount path\n const url = new URL(c.req.url);\n const routePath = url.pathname.replace(/\\/(Schemas|ResourceTypes|ServiceProviderConfig|Users|Groups|Bulk|Me|\\.search).*$/, \"\");\n const location = basepath\n ? basepath.replace(/\\/$/, \"\") + routePath\n : `${url.origin}${routePath}`;\n\n SCIMMY.Resources.Schema.basepath(location);\n SCIMMY.Resources.ResourceType.basepath(location);\n SCIMMY.Resources.ServiceProviderConfig.basepath(location);\n for (const Resource of Object.values(SCIMMY.Resources.declared()) as any[]) {\n Resource.basepath(location);\n }\n\n await next();\n } else {\n throw new TypeError(\n \"Method 'baseUri' must return a URL string in scimmyHono\"\n );\n }\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // Middleware: authenticate requests\n app.use(\"*\", async (c, next) => {\n try {\n await handler(c);\n await next();\n } catch (ex: any) {\n return scimJson(\n c,\n new SCIMMY.Messages.Error({ status: 401, message: ex.message }),\n 401\n );\n }\n });\n\n // --- Discovery endpoints ---\n\n // Schemas\n app.get(\"/Schemas\", async (c) => {\n try {\n const query = castPaginationParams(c.req.query());\n return scimJson(c, await new (SCIMMY.Resources.Schema as any)(query).read());\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n app.get(\"/Schemas/:id\", async (c) => {\n try {\n const query = castPaginationParams(c.req.query());\n return scimJson(\n c,\n await new (SCIMMY.Resources.Schema as any)(c.req.param(\"id\"), query).read()\n );\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // ResourceTypes\n app.get(\"/ResourceTypes\", async (c) => {\n try {\n const query = castPaginationParams(c.req.query());\n return scimJson(\n c,\n await new (SCIMMY.Resources.ResourceType as any)(query).read()\n );\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n app.get(\"/ResourceTypes/:id\", async (c) => {\n try {\n const query = castPaginationParams(c.req.query());\n return scimJson(\n c,\n await new (SCIMMY.Resources.ResourceType as any)(c.req.param(\"id\"), query).read()\n );\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // ServiceProviderConfig\n app.get(\"/ServiceProviderConfig\", async (c) => {\n try {\n const query = castPaginationParams(c.req.query());\n return scimJson(\n c,\n await new (SCIMMY.Resources.ServiceProviderConfig as any)(query).read()\n );\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // --- Search endpoint (global) ---\n app.post(\"/.search\", async (c) => {\n try {\n const body = await c.req.json();\n const ctx = await context(c);\n return scimJson(\n c,\n await new SCIMMY.Messages.SearchRequest(body).apply(undefined, ctx)\n );\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // --- Bulk endpoint ---\n app.post(\"/Bulk\", async (c) => {\n try {\n const { supported, maxPayloadSize, maxOperations } =\n SCIMMY.Config.get()?.bulk ?? {};\n\n if (!supported) {\n return scimJson(\n c,\n new SCIMMY.Messages.Error({\n status: 501,\n message: \"Endpoint Not Implemented\",\n }),\n 501\n );\n }\n\n const contentLength = Number(c.req.header(\"content-length\") ?? 0);\n if (contentLength > maxPayloadSize) {\n return scimJson(\n c,\n new SCIMMY.Messages.Error({\n status: 413,\n message: `The size of the bulk operation exceeds maxPayloadSize limit (${maxPayloadSize})`,\n }),\n 413\n );\n }\n\n const body = await c.req.json();\n const ctx = await context(c);\n return scimJson(\n c,\n await new SCIMMY.Messages.BulkRequest(body, maxOperations).apply(\n undefined,\n ctx\n )\n );\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // --- Me endpoint ---\n app.get(\"/Me\", async (c) => {\n try {\n const id = await handler(c);\n const isDeclared = SCIMMY.Resources.declared(SCIMMY.Resources.User);\n const user: any =\n isDeclared && typeof id === \"string\"\n ? await new SCIMMY.Resources.User(id).read(await context(c))\n : false;\n\n if (user && user?.meta?.location) {\n return scimJson(c, user);\n }\n return scimJson(\n c,\n new SCIMMY.Messages.Error({\n status: 501,\n message: \"Endpoint Not Implemented\",\n }),\n 501\n );\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // --- Resource endpoints (Users, Groups, and any declared resource types) ---\n for (const Resource of Object.values(SCIMMY.Resources.declared()) as any[]) {\n const endpoint = Resource.endpoint; // e.g. \"/Users\", \"/Groups\"\n\n // Resource-scoped /.search\n app.post(`${endpoint}/.search`, async (c) => {\n try {\n const body = await c.req.json();\n const ctx = await context(c);\n return scimJson(\n c,\n await new SCIMMY.Messages.SearchRequest(body).apply([Resource], ctx)\n );\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // List resources\n app.get(endpoint, async (c) => {\n try {\n const query = castPaginationParams(c.req.query());\n const ctx = await context(c);\n return scimJson(c, await new Resource(query).read(ctx));\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // Get single resource\n app.get(`${endpoint}/:id`, async (c) => {\n try {\n const query = castPaginationParams(c.req.query());\n const ctx = await context(c);\n return scimJson(\n c,\n await new Resource(c.req.param(\"id\"), query).read(ctx)\n );\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // Create resource\n app.post(endpoint, async (c) => {\n try {\n const query = castPaginationParams(c.req.query());\n const body = await c.req.json();\n const ctx = await context(c);\n return scimJson(c, await new Resource(query).write(body, ctx), 201);\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // Replace resource\n app.put(`${endpoint}/:id`, async (c) => {\n try {\n const query = castPaginationParams(c.req.query());\n const body = await c.req.json();\n const ctx = await context(c);\n return scimJson(\n c,\n await new Resource(c.req.param(\"id\"), query).write(body, ctx)\n );\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // Patch resource\n app.patch(`${endpoint}/:id`, async (c) => {\n try {\n const query = castPaginationParams(c.req.query());\n const body = await c.req.json();\n const ctx = await context(c);\n const value = await new Resource(c.req.param(\"id\"), query).patch(\n body,\n ctx\n );\n return value ? scimJson(c, value) : c.body(null, 204);\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n\n // Delete resource\n app.delete(`${endpoint}/:id`, async (c) => {\n try {\n const ctx = await context(c);\n await new Resource(c.req.param(\"id\")).dispose(ctx);\n return c.body(null, 204);\n } catch (ex) {\n return scimError(c, ex);\n }\n });\n }\n\n // 404 for unmatched routes\n app.all(\"*\", (c) => {\n return scimJson(\n c,\n new SCIMMY.Messages.Error({ status: 404, message: \"Endpoint Not Found\" }),\n 404\n );\n });\n\n return app;\n}\n\nexport default scimmyHono;\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "scimmy-hono-routers",
3
+ "description": "SCIMMY Hono Routers — SCIM 2.0 server middleware for Hono",
4
+ "version": "0.1.0",
5
+ "author": "cliftonc",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=24"
10
+ },
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ "import": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "typecheck": "tsc --noEmit",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "files": [
28
+ "dist/**"
29
+ ],
30
+ "keywords": [
31
+ "SCIM",
32
+ "SCIM2",
33
+ "hono",
34
+ "provisioning",
35
+ "identity",
36
+ "rfc7643",
37
+ "rfc7644",
38
+ "scimmy"
39
+ ],
40
+ "homepage": "https://github.com/cliftonc/scimmy-hono-routers#readme",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/cliftonc/scimmy-hono-routers.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/cliftonc/scimmy-hono-routers/issues"
47
+ },
48
+ "peerDependencies": {
49
+ "hono": ">=4.0.0",
50
+ "scimmy": "1.x"
51
+ },
52
+ "devDependencies": {
53
+ "@types/sinon": "^21.0.0",
54
+ "hono": "^4.7.0",
55
+ "scimmy": "^1.3.5",
56
+ "sinon": "^21.0.3",
57
+ "tsup": "^8.0.0",
58
+ "typescript": "^5.7.0",
59
+ "vitest": "^3.0.0"
60
+ }
61
+ }