@soulcraft/sdk 1.0.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/client/index.d.ts +62 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +60 -0
- package/dist/client/index.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/ai/index.d.ts +55 -0
- package/dist/modules/ai/index.d.ts.map +1 -0
- package/dist/modules/ai/index.js +263 -0
- package/dist/modules/ai/index.js.map +1 -0
- package/dist/modules/ai/types.d.ts +216 -0
- package/dist/modules/ai/types.d.ts.map +1 -0
- package/dist/modules/ai/types.js +30 -0
- package/dist/modules/ai/types.js.map +1 -0
- package/dist/modules/auth/backchannel.d.ts +85 -0
- package/dist/modules/auth/backchannel.d.ts.map +1 -0
- package/dist/modules/auth/backchannel.js +168 -0
- package/dist/modules/auth/backchannel.js.map +1 -0
- package/dist/modules/auth/config.d.ts +122 -0
- package/dist/modules/auth/config.d.ts.map +1 -0
- package/dist/modules/auth/config.js +158 -0
- package/dist/modules/auth/config.js.map +1 -0
- package/dist/modules/auth/middleware.d.ts +146 -0
- package/dist/modules/auth/middleware.d.ts.map +1 -0
- package/dist/modules/auth/middleware.js +204 -0
- package/dist/modules/auth/middleware.js.map +1 -0
- package/dist/modules/auth/types.d.ts +162 -0
- package/dist/modules/auth/types.d.ts.map +1 -0
- package/dist/modules/auth/types.js +14 -0
- package/dist/modules/auth/types.js.map +1 -0
- package/dist/modules/billing/types.d.ts +7 -0
- package/dist/modules/billing/types.d.ts.map +1 -0
- package/dist/modules/billing/types.js +7 -0
- package/dist/modules/billing/types.js.map +1 -0
- package/dist/modules/brainy/auth.d.ts +104 -0
- package/dist/modules/brainy/auth.d.ts.map +1 -0
- package/dist/modules/brainy/auth.js +144 -0
- package/dist/modules/brainy/auth.js.map +1 -0
- package/dist/modules/brainy/errors.d.ts +118 -0
- package/dist/modules/brainy/errors.d.ts.map +1 -0
- package/dist/modules/brainy/errors.js +142 -0
- package/dist/modules/brainy/errors.js.map +1 -0
- package/dist/modules/brainy/events.d.ts +63 -0
- package/dist/modules/brainy/events.d.ts.map +1 -0
- package/dist/modules/brainy/events.js +14 -0
- package/dist/modules/brainy/events.js.map +1 -0
- package/dist/modules/brainy/proxy.d.ts +48 -0
- package/dist/modules/brainy/proxy.d.ts.map +1 -0
- package/dist/modules/brainy/proxy.js +95 -0
- package/dist/modules/brainy/proxy.js.map +1 -0
- package/dist/modules/brainy/types.d.ts +83 -0
- package/dist/modules/brainy/types.d.ts.map +1 -0
- package/dist/modules/brainy/types.js +21 -0
- package/dist/modules/brainy/types.js.map +1 -0
- package/dist/modules/events/index.d.ts +41 -0
- package/dist/modules/events/index.d.ts.map +1 -0
- package/dist/modules/events/index.js +53 -0
- package/dist/modules/events/index.js.map +1 -0
- package/dist/modules/events/types.d.ts +129 -0
- package/dist/modules/events/types.d.ts.map +1 -0
- package/dist/modules/events/types.js +32 -0
- package/dist/modules/events/types.js.map +1 -0
- package/dist/modules/formats/types.d.ts +7 -0
- package/dist/modules/formats/types.d.ts.map +1 -0
- package/dist/modules/formats/types.js +7 -0
- package/dist/modules/formats/types.js.map +1 -0
- package/dist/modules/hall/types.d.ts +56 -0
- package/dist/modules/hall/types.d.ts.map +1 -0
- package/dist/modules/hall/types.js +16 -0
- package/dist/modules/hall/types.js.map +1 -0
- package/dist/modules/kits/types.d.ts +7 -0
- package/dist/modules/kits/types.d.ts.map +1 -0
- package/dist/modules/kits/types.js +7 -0
- package/dist/modules/kits/types.js.map +1 -0
- package/dist/modules/license/types.d.ts +7 -0
- package/dist/modules/license/types.d.ts.map +1 -0
- package/dist/modules/license/types.js +7 -0
- package/dist/modules/license/types.js.map +1 -0
- package/dist/modules/notifications/types.d.ts +7 -0
- package/dist/modules/notifications/types.d.ts.map +1 -0
- package/dist/modules/notifications/types.js +7 -0
- package/dist/modules/notifications/types.js.map +1 -0
- package/dist/modules/skills/index.d.ts +60 -0
- package/dist/modules/skills/index.d.ts.map +1 -0
- package/dist/modules/skills/index.js +253 -0
- package/dist/modules/skills/index.js.map +1 -0
- package/dist/modules/skills/types.d.ts +127 -0
- package/dist/modules/skills/types.d.ts.map +1 -0
- package/dist/modules/skills/types.js +23 -0
- package/dist/modules/skills/types.js.map +1 -0
- package/dist/modules/versions/types.d.ts +31 -0
- package/dist/modules/versions/types.d.ts.map +1 -0
- package/dist/modules/versions/types.js +9 -0
- package/dist/modules/versions/types.js.map +1 -0
- package/dist/modules/vfs/types.d.ts +26 -0
- package/dist/modules/vfs/types.d.ts.map +1 -0
- package/dist/modules/vfs/types.js +11 -0
- package/dist/modules/vfs/types.js.map +1 -0
- package/dist/server/create-sdk.d.ts +70 -0
- package/dist/server/create-sdk.d.ts.map +1 -0
- package/dist/server/create-sdk.js +125 -0
- package/dist/server/create-sdk.js.map +1 -0
- package/dist/server/hall-handlers.d.ts +195 -0
- package/dist/server/hall-handlers.d.ts.map +1 -0
- package/dist/server/hall-handlers.js +239 -0
- package/dist/server/hall-handlers.js.map +1 -0
- package/dist/server/handlers.d.ts +216 -0
- package/dist/server/handlers.d.ts.map +1 -0
- package/dist/server/handlers.js +214 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/index.d.ts +52 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +50 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/instance-pool.d.ts +299 -0
- package/dist/server/instance-pool.d.ts.map +1 -0
- package/dist/server/instance-pool.js +359 -0
- package/dist/server/instance-pool.js.map +1 -0
- package/dist/transports/http.d.ts +86 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +134 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/transports/local.d.ts +76 -0
- package/dist/transports/local.d.ts.map +1 -0
- package/dist/transports/local.js +101 -0
- package/dist/transports/local.js.map +1 -0
- package/dist/transports/sse.d.ts +99 -0
- package/dist/transports/sse.d.ts.map +1 -0
- package/dist/transports/sse.js +192 -0
- package/dist/transports/sse.js.map +1 -0
- package/dist/transports/transport.d.ts +68 -0
- package/dist/transports/transport.d.ts.map +1 -0
- package/dist/transports/transport.js +14 -0
- package/dist/transports/transport.js.map +1 -0
- package/dist/transports/ws.d.ts +135 -0
- package/dist/transports/ws.d.ts.map +1 -0
- package/dist/transports/ws.js +331 -0
- package/dist/transports/ws.js.map +1 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/docs/ADR-001-sdk-design.md +282 -0
- package/docs/IMPLEMENTATION-PLAN.md +708 -0
- package/docs/USAGE.md +646 -0
- package/docs/kit-sdk-guide.md +474 -0
- package/package.json +61 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module modules/auth/config
|
|
3
|
+
* @description Shared constants, utilities, and environment helpers for Soulcraft
|
|
4
|
+
* platform authentication.
|
|
5
|
+
*
|
|
6
|
+
* Provides ONLY well-typed shared values — no `betterAuth()` wrappers. Each product
|
|
7
|
+
* (Workshop, Venue, Academy) assembles its own fully-typed `betterAuth()` configuration
|
|
8
|
+
* using these constants and utilities, keeping TypeScript inference intact end-to-end.
|
|
9
|
+
*
|
|
10
|
+
* ## Why no config factory functions?
|
|
11
|
+
*
|
|
12
|
+
* A factory returning a broad object type loses compile-time safety when passed to
|
|
13
|
+
* `betterAuth()`. The correct boundary is:
|
|
14
|
+
* - **This module** → exports typed field definitions, session config, and pure utilities
|
|
15
|
+
* - **Each product** → imports those exports and constructs its own `betterAuth()` call
|
|
16
|
+
*
|
|
17
|
+
* This absorbs and replaces `@soulcraft/auth/config`, which is deprecated.
|
|
18
|
+
*
|
|
19
|
+
* @example Workshop auth setup
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { betterAuth } from 'better-auth'
|
|
22
|
+
* import {
|
|
23
|
+
* SOULCRAFT_USER_FIELDS,
|
|
24
|
+
* SOULCRAFT_SESSION_CONFIG,
|
|
25
|
+
* computeEmailHash,
|
|
26
|
+
* } from '@soulcraft/sdk/server'
|
|
27
|
+
*
|
|
28
|
+
* export const auth = betterAuth({
|
|
29
|
+
* database: new Database('./auth.db'),
|
|
30
|
+
* secret: process.env.BETTER_AUTH_SECRET!,
|
|
31
|
+
* session: SOULCRAFT_SESSION_CONFIG,
|
|
32
|
+
* user: { additionalFields: SOULCRAFT_USER_FIELDS },
|
|
33
|
+
* databaseHooks: {
|
|
34
|
+
* user: { create: { before: async (user) => ({
|
|
35
|
+
* data: { ...user, emailHash: computeEmailHash(user.email), platformRole: 'creator' }
|
|
36
|
+
* })}}
|
|
37
|
+
* },
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
import { createHash } from 'node:crypto';
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
// Shared additional user fields
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Additional fields to register on the better-auth `user` table.
|
|
47
|
+
*
|
|
48
|
+
* Pass to `betterAuth({ user: { additionalFields: SOULCRAFT_USER_FIELDS } })`.
|
|
49
|
+
*
|
|
50
|
+
* `emailHash` must be populated at user-creation time via a
|
|
51
|
+
* `databaseHooks.user.create.before` hook. `platformRole` defaults to `'creator'`
|
|
52
|
+
* for Workshop; products override this in their own hook.
|
|
53
|
+
*/
|
|
54
|
+
export const SOULCRAFT_USER_FIELDS = {
|
|
55
|
+
platformRole: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
required: false,
|
|
58
|
+
defaultValue: 'creator',
|
|
59
|
+
},
|
|
60
|
+
emailHash: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
required: false,
|
|
63
|
+
defaultValue: '',
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
+
// Shared session configuration
|
|
68
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Session lifetime configuration shared across all Soulcraft products.
|
|
71
|
+
*
|
|
72
|
+
* Pass to `betterAuth({ session: SOULCRAFT_SESSION_CONFIG })`.
|
|
73
|
+
*
|
|
74
|
+
* - Sessions are valid for 30 days from issuance
|
|
75
|
+
* - Silently refreshed after 24 hours of activity, resetting the 30-day window
|
|
76
|
+
*/
|
|
77
|
+
export const SOULCRAFT_SESSION_CONFIG = {
|
|
78
|
+
expiresIn: 30 * 24 * 60 * 60,
|
|
79
|
+
updateAge: 24 * 60 * 60,
|
|
80
|
+
};
|
|
81
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
// Pure utility functions
|
|
83
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
84
|
+
/**
|
|
85
|
+
* Compute the SHA-256 hex digest of a canonical email address.
|
|
86
|
+
*
|
|
87
|
+
* The email is lower-cased and trimmed before hashing to ensure the same
|
|
88
|
+
* input always yields the same digest regardless of capitalisation or whitespace.
|
|
89
|
+
*
|
|
90
|
+
* Stored as `emailHash` on the user record. Used by Workshop to deterministically
|
|
91
|
+
* locate the user's Brainy data directory without exposing the raw email in paths.
|
|
92
|
+
*
|
|
93
|
+
* @param email - Raw email address (any case, may have leading/trailing whitespace).
|
|
94
|
+
* @returns 64-character lowercase hex SHA-256 digest.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* computeEmailHash('Alice@Example.COM') === computeEmailHash('alice@example.com') // true
|
|
98
|
+
* computeEmailHash('alice@example.com')
|
|
99
|
+
* // → 'b4c9a289522dd28a04617a41d7b14cf18d43d06febe513d9e27b5da67c17e52e'
|
|
100
|
+
*/
|
|
101
|
+
export function computeEmailHash(email) {
|
|
102
|
+
return createHash('sha256')
|
|
103
|
+
.update(email.toLowerCase().trim())
|
|
104
|
+
.digest('hex');
|
|
105
|
+
}
|
|
106
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
107
|
+
// SSO / OIDC environment helpers
|
|
108
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* Determine the authentication mode from environment variables.
|
|
111
|
+
*
|
|
112
|
+
* - `'standalone'` — each product runs its own better-auth instance with a local
|
|
113
|
+
* SQLite database. No cross-product SSO. Default before `auth.soulcraft.com` is live.
|
|
114
|
+
* - `'oidc-client'` — set `SOULCRAFT_IDP_URL` to activate. The product's better-auth
|
|
115
|
+
* instance delegates all authentication to the central IdP.
|
|
116
|
+
*
|
|
117
|
+
* @returns The current auth mode derived from `SOULCRAFT_IDP_URL`.
|
|
118
|
+
*/
|
|
119
|
+
export function getAuthMode() {
|
|
120
|
+
return process.env['SOULCRAFT_IDP_URL'] ? 'oidc-client' : 'standalone';
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Read OIDC client configuration from environment variables.
|
|
124
|
+
*
|
|
125
|
+
* Returns `null` in standalone mode (when `SOULCRAFT_IDP_URL` is unset).
|
|
126
|
+
* Throws with a descriptive message if the URL is set but required variables
|
|
127
|
+
* are missing — prevents silent misconfiguration.
|
|
128
|
+
*
|
|
129
|
+
* | Variable | Required | Description |
|
|
130
|
+
* |------------------------------|----------|-----------------------------------------|
|
|
131
|
+
* | `SOULCRAFT_IDP_URL` | Yes | Central IdP base URL |
|
|
132
|
+
* | `SOULCRAFT_OIDC_CLIENT_ID` | Yes | This product's registered client ID |
|
|
133
|
+
* | `SOULCRAFT_OIDC_CLIENT_SECRET` | Yes | This product's registered client secret |
|
|
134
|
+
* | `SOULCRAFT_OIDC_REDIRECT_URI` | No | Deprecated — auto-derived from BETTER_AUTH_URL |
|
|
135
|
+
*
|
|
136
|
+
* @returns OIDC client config or null in standalone mode.
|
|
137
|
+
* @throws {Error} If `SOULCRAFT_IDP_URL` is set but client ID or secret are missing.
|
|
138
|
+
*/
|
|
139
|
+
export function getOIDCClientConfig() {
|
|
140
|
+
const idpUrl = process.env['SOULCRAFT_IDP_URL'];
|
|
141
|
+
if (!idpUrl)
|
|
142
|
+
return null;
|
|
143
|
+
const clientId = process.env['SOULCRAFT_OIDC_CLIENT_ID'];
|
|
144
|
+
const clientSecret = process.env['SOULCRAFT_OIDC_CLIENT_SECRET'];
|
|
145
|
+
if (!clientId || !clientSecret) {
|
|
146
|
+
const missing = [
|
|
147
|
+
!clientId && 'SOULCRAFT_OIDC_CLIENT_ID',
|
|
148
|
+
!clientSecret && 'SOULCRAFT_OIDC_CLIENT_SECRET',
|
|
149
|
+
].filter(Boolean).join(', ');
|
|
150
|
+
throw new Error(`SOULCRAFT_IDP_URL is set to "${idpUrl}" but the following required OIDC ` +
|
|
151
|
+
`client environment variables are missing: ${missing}. ` +
|
|
152
|
+
`Either set both variables for OIDC client mode, or unset SOULCRAFT_IDP_URL ` +
|
|
153
|
+
`to run in standalone mode.`);
|
|
154
|
+
}
|
|
155
|
+
const redirectUri = process.env['SOULCRAFT_OIDC_REDIRECT_URI'];
|
|
156
|
+
return { idpUrl, clientId, clientSecret, ...(redirectUri ? { redirectUri } : {}) };
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/modules/auth/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,gFAAgF;AAChF,gCAAgC;AAChC,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,YAAY,EAAE;QACZ,IAAI,EAAE,QAAiB;QACvB,QAAQ,EAAE,KAAK;QACf,YAAY,EAAE,SAAS;KACxB;IACD,SAAS,EAAE;QACT,IAAI,EAAE,QAAiB;QACvB,QAAQ,EAAE,KAAK;QACf,YAAY,EAAE,EAAE;KACjB;CACO,CAAA;AAEV,gFAAgF;AAChF,+BAA+B;AAC/B,gFAAgF;AAEhF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;IAC5B,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE;CACf,CAAA;AAEV,gFAAgF;AAChF,yBAAyB;AACzB,gFAAgF;AAEhF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;SAClC,MAAM,CAAC,KAAK,CAAC,CAAA;AAClB,CAAC;AAED,gFAAgF;AAChF,iCAAiC;AACjC,gFAAgF;AAEhF;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAA;AACxE,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;IAC/C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAExB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;IACxD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAA;IAEhE,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG;YACd,CAAC,QAAQ,IAAI,0BAA0B;YACvC,CAAC,YAAY,IAAI,8BAA8B;SAChD,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAE5B,MAAM,IAAI,KAAK,CACb,gCAAgC,MAAM,oCAAoC;YAC1E,6CAA6C,OAAO,IAAI;YACxD,6EAA6E;YAC7E,4BAA4B,CAC7B,CAAA;IACH,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAA;IAC9D,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AACpF,CAAC"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module modules/auth/middleware
|
|
3
|
+
* @description Hono auth middleware factories and remote session verification
|
|
4
|
+
* for Soulcraft product backends.
|
|
5
|
+
*
|
|
6
|
+
* Provides framework-agnostic middleware that products mount in their Hono server
|
|
7
|
+
* to authenticate requests against a better-auth instance (local or remote IdP).
|
|
8
|
+
*
|
|
9
|
+
* The middleware pattern is product-agnostic — Workshop, Venue, and Academy all
|
|
10
|
+
* use the same factories, passing their own `auth` instance. This replaces the
|
|
11
|
+
* per-product copies of `requireAuth` / `optionalAuth` in Workshop's better-auth.ts.
|
|
12
|
+
*
|
|
13
|
+
* ## Remote session verification
|
|
14
|
+
*
|
|
15
|
+
* `createRemoteSessionVerifier` is used by products that delegate authentication
|
|
16
|
+
* to the central IdP (`auth.soulcraft.com`) but need to verify sessions without
|
|
17
|
+
* running a full better-auth instance. It proxies the session lookup via HTTP with
|
|
18
|
+
* an LRU cache to avoid per-request round-trips to the IdP.
|
|
19
|
+
*
|
|
20
|
+
* @example Hono setup (Workshop)
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { createAuthMiddleware } from '@soulcraft/sdk/server'
|
|
23
|
+
* import { auth } from './better-auth.js'
|
|
24
|
+
*
|
|
25
|
+
* const { requireAuth, optionalAuth } = createAuthMiddleware(auth)
|
|
26
|
+
*
|
|
27
|
+
* app.get('/api/workspaces', requireAuth, async (c) => {
|
|
28
|
+
* const user = c.get('user')! // SoulcraftSessionUser, guaranteed non-null
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import type { SoulcraftSessionUser, SoulcraftSession } from './types.js';
|
|
33
|
+
import type { Context, Next } from 'hono';
|
|
34
|
+
/** The Hono context variable key where the resolved user is stored. */
|
|
35
|
+
export declare const AUTH_USER_KEY: "user";
|
|
36
|
+
/** Hono context type with the resolved Soulcraft user variable. */
|
|
37
|
+
export type AuthContext = Context<{
|
|
38
|
+
Variables: {
|
|
39
|
+
[AUTH_USER_KEY]: SoulcraftSessionUser | null;
|
|
40
|
+
};
|
|
41
|
+
}>;
|
|
42
|
+
/** Minimal better-auth API surface the middleware depends on. */
|
|
43
|
+
export interface BetterAuthLike {
|
|
44
|
+
api: {
|
|
45
|
+
getSession(opts: {
|
|
46
|
+
headers: Headers;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
user: Record<string, unknown>;
|
|
49
|
+
session: {
|
|
50
|
+
id: string;
|
|
51
|
+
expiresAt: Date | string | number;
|
|
52
|
+
};
|
|
53
|
+
} | null>;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* @description Options for createAuthMiddleware().
|
|
58
|
+
*/
|
|
59
|
+
export interface AuthMiddlewareOptions {
|
|
60
|
+
/**
|
|
61
|
+
* If true, a synthetic dev user is injected in non-production environments,
|
|
62
|
+
* bypassing the session check entirely. Set to false to disable dev auto-login
|
|
63
|
+
* (e.g. for integration test servers that need real auth even in dev).
|
|
64
|
+
* @default true
|
|
65
|
+
*/
|
|
66
|
+
devAutoLogin?: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* @description The object returned by createAuthMiddleware().
|
|
70
|
+
*/
|
|
71
|
+
export interface AuthMiddleware {
|
|
72
|
+
/**
|
|
73
|
+
* Require authentication. Resolves the better-auth session. If the session is
|
|
74
|
+
* valid, attaches the typed user to the Hono context and calls next(). Returns
|
|
75
|
+
* HTTP 401 if unauthenticated.
|
|
76
|
+
*
|
|
77
|
+
* In development mode (unless devAutoLogin is disabled), a synthetic dev user
|
|
78
|
+
* is injected automatically.
|
|
79
|
+
*/
|
|
80
|
+
requireAuth: (c: AuthContext, next: Next) => Promise<void | Response>;
|
|
81
|
+
/**
|
|
82
|
+
* Optional authentication. Resolves the session if one exists, but does not
|
|
83
|
+
* reject unauthenticated requests. User will be null for anonymous requests.
|
|
84
|
+
*/
|
|
85
|
+
optionalAuth: (c: AuthContext, next: Next) => Promise<void | Response>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* @description Options for createRemoteSessionVerifier().
|
|
89
|
+
*/
|
|
90
|
+
export interface RemoteSessionVerifierOptions {
|
|
91
|
+
/** The central IdP base URL, e.g. "https://auth.soulcraft.com". */
|
|
92
|
+
idpUrl: string;
|
|
93
|
+
/** Session cache TTL in milliseconds. Default: 30 000 (30 seconds). */
|
|
94
|
+
cacheTtlMs?: number;
|
|
95
|
+
/** Maximum cached sessions. Default: 500. */
|
|
96
|
+
cacheMax?: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* @description Creates Hono auth middleware bound to a specific better-auth instance.
|
|
100
|
+
*
|
|
101
|
+
* Returns `requireAuth` and `optionalAuth` middleware functions. Both functions
|
|
102
|
+
* resolve the session from request cookies/headers using better-auth's `getSession`
|
|
103
|
+
* API and attach the user to the Hono context under the `'user'` variable key.
|
|
104
|
+
*
|
|
105
|
+
* In non-production environments (`NODE_ENV !== 'production'`), a synthetic dev
|
|
106
|
+
* user is injected automatically to allow local development without OAuth setup.
|
|
107
|
+
* Disable this with `options.devAutoLogin = false`.
|
|
108
|
+
*
|
|
109
|
+
* @param auth - The product's better-auth instance.
|
|
110
|
+
* @param options - Optional middleware configuration.
|
|
111
|
+
* @returns Middleware pair: `{ requireAuth, optionalAuth }`.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* const { requireAuth, optionalAuth } = createAuthMiddleware(auth)
|
|
116
|
+
* app.get('/api/me', requireAuth, (c) => c.json(c.get('user')))
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare function createAuthMiddleware(auth: BetterAuthLike, options?: AuthMiddlewareOptions): AuthMiddleware;
|
|
120
|
+
/**
|
|
121
|
+
* @description Creates a cached remote session verifier that proxies session lookups
|
|
122
|
+
* to the central IdP at `auth.soulcraft.com`.
|
|
123
|
+
*
|
|
124
|
+
* Used by products that operate as OIDC clients and need to verify sessions without
|
|
125
|
+
* running a full better-auth instance locally. Caches successful lookups to avoid
|
|
126
|
+
* per-request HTTP calls to the IdP.
|
|
127
|
+
*
|
|
128
|
+
* The verifier sends the cookie header to the IdP's `/api/auth/get-session` endpoint
|
|
129
|
+
* and returns the resolved `SoulcraftSession` or null if the session is invalid.
|
|
130
|
+
*
|
|
131
|
+
* @param options - IdP URL, cache TTL, and max cache size.
|
|
132
|
+
* @returns An async function that accepts a cookie header string and returns a session or null.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const verifySession = createRemoteSessionVerifier({
|
|
137
|
+
* idpUrl: 'https://auth.soulcraft.com',
|
|
138
|
+
* cacheTtlMs: 30_000,
|
|
139
|
+
* })
|
|
140
|
+
*
|
|
141
|
+
* const session = await verifySession(c.req.header('cookie') ?? '')
|
|
142
|
+
* if (!session) return c.json({ error: 'Unauthorized' }, 401)
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export declare function createRemoteSessionVerifier(options: RemoteSessionVerifierOptions): (cookieHeader: string) => Promise<SoulcraftSession | null>;
|
|
146
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AACxE,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAMzC,uEAAuE;AACvE,eAAO,MAAM,aAAa,EAAG,MAAe,CAAA;AAE5C,mEAAmE;AACnE,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE;QAAE,CAAC,aAAa,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,CAAC,CAAA;AAElG,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE;QACH,UAAU,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAAC,OAAO,EAAE;gBAAE,EAAE,EAAE,MAAM,CAAC;gBAAC,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAA;aAAE,CAAA;SAAE,GAAG,IAAI,CAAC,CAAA;KACtJ,CAAA;CACF;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;OAOG;IACH,WAAW,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAA;IAErE;;;OAGG;IACH,YAAY,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAA;CACvE;AAED;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAA;IACd,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE,qBAA0B,GAClC,cAAc,CA+DhB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,4BAA4B,GACpC,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA4D5D"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module modules/auth/middleware
|
|
3
|
+
* @description Hono auth middleware factories and remote session verification
|
|
4
|
+
* for Soulcraft product backends.
|
|
5
|
+
*
|
|
6
|
+
* Provides framework-agnostic middleware that products mount in their Hono server
|
|
7
|
+
* to authenticate requests against a better-auth instance (local or remote IdP).
|
|
8
|
+
*
|
|
9
|
+
* The middleware pattern is product-agnostic — Workshop, Venue, and Academy all
|
|
10
|
+
* use the same factories, passing their own `auth` instance. This replaces the
|
|
11
|
+
* per-product copies of `requireAuth` / `optionalAuth` in Workshop's better-auth.ts.
|
|
12
|
+
*
|
|
13
|
+
* ## Remote session verification
|
|
14
|
+
*
|
|
15
|
+
* `createRemoteSessionVerifier` is used by products that delegate authentication
|
|
16
|
+
* to the central IdP (`auth.soulcraft.com`) but need to verify sessions without
|
|
17
|
+
* running a full better-auth instance. It proxies the session lookup via HTTP with
|
|
18
|
+
* an LRU cache to avoid per-request round-trips to the IdP.
|
|
19
|
+
*
|
|
20
|
+
* @example Hono setup (Workshop)
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { createAuthMiddleware } from '@soulcraft/sdk/server'
|
|
23
|
+
* import { auth } from './better-auth.js'
|
|
24
|
+
*
|
|
25
|
+
* const { requireAuth, optionalAuth } = createAuthMiddleware(auth)
|
|
26
|
+
*
|
|
27
|
+
* app.get('/api/workspaces', requireAuth, async (c) => {
|
|
28
|
+
* const user = c.get('user')! // SoulcraftSessionUser, guaranteed non-null
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import { LRUCache } from 'lru-cache';
|
|
33
|
+
import { computeEmailHash } from './config.js';
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
// Types
|
|
36
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
/** The Hono context variable key where the resolved user is stored. */
|
|
38
|
+
export const AUTH_USER_KEY = 'user';
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
// createAuthMiddleware
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* @description Creates Hono auth middleware bound to a specific better-auth instance.
|
|
44
|
+
*
|
|
45
|
+
* Returns `requireAuth` and `optionalAuth` middleware functions. Both functions
|
|
46
|
+
* resolve the session from request cookies/headers using better-auth's `getSession`
|
|
47
|
+
* API and attach the user to the Hono context under the `'user'` variable key.
|
|
48
|
+
*
|
|
49
|
+
* In non-production environments (`NODE_ENV !== 'production'`), a synthetic dev
|
|
50
|
+
* user is injected automatically to allow local development without OAuth setup.
|
|
51
|
+
* Disable this with `options.devAutoLogin = false`.
|
|
52
|
+
*
|
|
53
|
+
* @param auth - The product's better-auth instance.
|
|
54
|
+
* @param options - Optional middleware configuration.
|
|
55
|
+
* @returns Middleware pair: `{ requireAuth, optionalAuth }`.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const { requireAuth, optionalAuth } = createAuthMiddleware(auth)
|
|
60
|
+
* app.get('/api/me', requireAuth, (c) => c.json(c.get('user')))
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export function createAuthMiddleware(auth, options = {}) {
|
|
64
|
+
const devAutoLogin = options.devAutoLogin ?? true;
|
|
65
|
+
const isDev = process.env['NODE_ENV'] !== 'production';
|
|
66
|
+
const DEV_USER = {
|
|
67
|
+
id: 'dev-user-001',
|
|
68
|
+
email: 'dev@localhost',
|
|
69
|
+
name: 'Dev User',
|
|
70
|
+
image: null,
|
|
71
|
+
emailHash: computeEmailHash('dev@localhost'),
|
|
72
|
+
platformRole: 'creator',
|
|
73
|
+
};
|
|
74
|
+
function resolveUser(raw) {
|
|
75
|
+
const email = String(raw['email'] ?? '');
|
|
76
|
+
const emailHash = raw['emailHash']
|
|
77
|
+
? String(raw['emailHash'])
|
|
78
|
+
: computeEmailHash(email);
|
|
79
|
+
return {
|
|
80
|
+
id: String(raw['id'] ?? ''),
|
|
81
|
+
email,
|
|
82
|
+
name: String(raw['name'] ?? ''),
|
|
83
|
+
image: raw['image'] ?? null,
|
|
84
|
+
platformRole: raw['platformRole'] ?? 'creator',
|
|
85
|
+
emailHash,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const requireAuth = async (c, next) => {
|
|
89
|
+
if (isDev && devAutoLogin) {
|
|
90
|
+
if (!c.get(AUTH_USER_KEY))
|
|
91
|
+
c.set(AUTH_USER_KEY, DEV_USER);
|
|
92
|
+
await next();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const session = await auth.api.getSession({ headers: c.req.raw.headers });
|
|
96
|
+
if (!session?.user) {
|
|
97
|
+
return c.json({ error: 'Authentication required' }, 401);
|
|
98
|
+
}
|
|
99
|
+
c.set(AUTH_USER_KEY, resolveUser(session.user));
|
|
100
|
+
await next();
|
|
101
|
+
return;
|
|
102
|
+
};
|
|
103
|
+
const optionalAuth = async (c, next) => {
|
|
104
|
+
if (isDev && devAutoLogin) {
|
|
105
|
+
if (!c.get(AUTH_USER_KEY))
|
|
106
|
+
c.set(AUTH_USER_KEY, DEV_USER);
|
|
107
|
+
await next();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const session = await auth.api.getSession({ headers: c.req.raw.headers });
|
|
111
|
+
if (session?.user) {
|
|
112
|
+
c.set(AUTH_USER_KEY, resolveUser(session.user));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
c.set(AUTH_USER_KEY, null);
|
|
116
|
+
}
|
|
117
|
+
await next();
|
|
118
|
+
};
|
|
119
|
+
return { requireAuth, optionalAuth };
|
|
120
|
+
}
|
|
121
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
122
|
+
// createRemoteSessionVerifier
|
|
123
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
124
|
+
/**
|
|
125
|
+
* @description Creates a cached remote session verifier that proxies session lookups
|
|
126
|
+
* to the central IdP at `auth.soulcraft.com`.
|
|
127
|
+
*
|
|
128
|
+
* Used by products that operate as OIDC clients and need to verify sessions without
|
|
129
|
+
* running a full better-auth instance locally. Caches successful lookups to avoid
|
|
130
|
+
* per-request HTTP calls to the IdP.
|
|
131
|
+
*
|
|
132
|
+
* The verifier sends the cookie header to the IdP's `/api/auth/get-session` endpoint
|
|
133
|
+
* and returns the resolved `SoulcraftSession` or null if the session is invalid.
|
|
134
|
+
*
|
|
135
|
+
* @param options - IdP URL, cache TTL, and max cache size.
|
|
136
|
+
* @returns An async function that accepts a cookie header string and returns a session or null.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* const verifySession = createRemoteSessionVerifier({
|
|
141
|
+
* idpUrl: 'https://auth.soulcraft.com',
|
|
142
|
+
* cacheTtlMs: 30_000,
|
|
143
|
+
* })
|
|
144
|
+
*
|
|
145
|
+
* const session = await verifySession(c.req.header('cookie') ?? '')
|
|
146
|
+
* if (!session) return c.json({ error: 'Unauthorized' }, 401)
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
export function createRemoteSessionVerifier(options) {
|
|
150
|
+
const cacheTtl = options.cacheTtlMs ?? 30_000;
|
|
151
|
+
const cacheMax = options.cacheMax ?? 500;
|
|
152
|
+
const sessionUrl = `${options.idpUrl.replace(/\/$/, '')}/api/auth/get-session`;
|
|
153
|
+
const cache = new LRUCache({
|
|
154
|
+
max: cacheMax,
|
|
155
|
+
ttl: cacheTtl,
|
|
156
|
+
});
|
|
157
|
+
return async function verifyRemoteSession(cookieHeader) {
|
|
158
|
+
if (!cookieHeader)
|
|
159
|
+
return null;
|
|
160
|
+
// Use cookie header as cache key — it contains the session token
|
|
161
|
+
const cached = cache.get(cookieHeader);
|
|
162
|
+
if (cached !== undefined)
|
|
163
|
+
return cached;
|
|
164
|
+
let response;
|
|
165
|
+
try {
|
|
166
|
+
response = await fetch(sessionUrl, {
|
|
167
|
+
headers: { cookie: cookieHeader },
|
|
168
|
+
credentials: 'include',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
if (!response.ok)
|
|
175
|
+
return null;
|
|
176
|
+
let data;
|
|
177
|
+
try {
|
|
178
|
+
data = await response.json();
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const rawUser = data['user'];
|
|
184
|
+
const rawSession = data['session'];
|
|
185
|
+
if (!rawUser || !rawSession)
|
|
186
|
+
return null;
|
|
187
|
+
const email = String(rawUser['email'] ?? '');
|
|
188
|
+
const session = {
|
|
189
|
+
user: {
|
|
190
|
+
id: String(rawUser['id'] ?? ''),
|
|
191
|
+
email,
|
|
192
|
+
name: String(rawUser['name'] ?? ''),
|
|
193
|
+
image: rawUser['image'] ?? null,
|
|
194
|
+
platformRole: rawUser['platformRole'] ?? 'creator',
|
|
195
|
+
emailHash: rawUser['emailHash'] ? String(rawUser['emailHash']) : computeEmailHash(email),
|
|
196
|
+
},
|
|
197
|
+
sessionId: String(rawSession['id'] ?? ''),
|
|
198
|
+
expiresAt: Number(rawSession['expiresAt'] ?? 0),
|
|
199
|
+
};
|
|
200
|
+
cache.set(cookieHeader, session);
|
|
201
|
+
return session;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/modules/auth/middleware.ts"],"names":[],"mappings":"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"}
|