@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.
Files changed (149) hide show
  1. package/dist/client/index.d.ts +62 -0
  2. package/dist/client/index.d.ts.map +1 -0
  3. package/dist/client/index.js +60 -0
  4. package/dist/client/index.js.map +1 -0
  5. package/dist/index.d.ts +33 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +17 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/modules/ai/index.d.ts +55 -0
  10. package/dist/modules/ai/index.d.ts.map +1 -0
  11. package/dist/modules/ai/index.js +263 -0
  12. package/dist/modules/ai/index.js.map +1 -0
  13. package/dist/modules/ai/types.d.ts +216 -0
  14. package/dist/modules/ai/types.d.ts.map +1 -0
  15. package/dist/modules/ai/types.js +30 -0
  16. package/dist/modules/ai/types.js.map +1 -0
  17. package/dist/modules/auth/backchannel.d.ts +85 -0
  18. package/dist/modules/auth/backchannel.d.ts.map +1 -0
  19. package/dist/modules/auth/backchannel.js +168 -0
  20. package/dist/modules/auth/backchannel.js.map +1 -0
  21. package/dist/modules/auth/config.d.ts +122 -0
  22. package/dist/modules/auth/config.d.ts.map +1 -0
  23. package/dist/modules/auth/config.js +158 -0
  24. package/dist/modules/auth/config.js.map +1 -0
  25. package/dist/modules/auth/middleware.d.ts +146 -0
  26. package/dist/modules/auth/middleware.d.ts.map +1 -0
  27. package/dist/modules/auth/middleware.js +204 -0
  28. package/dist/modules/auth/middleware.js.map +1 -0
  29. package/dist/modules/auth/types.d.ts +162 -0
  30. package/dist/modules/auth/types.d.ts.map +1 -0
  31. package/dist/modules/auth/types.js +14 -0
  32. package/dist/modules/auth/types.js.map +1 -0
  33. package/dist/modules/billing/types.d.ts +7 -0
  34. package/dist/modules/billing/types.d.ts.map +1 -0
  35. package/dist/modules/billing/types.js +7 -0
  36. package/dist/modules/billing/types.js.map +1 -0
  37. package/dist/modules/brainy/auth.d.ts +104 -0
  38. package/dist/modules/brainy/auth.d.ts.map +1 -0
  39. package/dist/modules/brainy/auth.js +144 -0
  40. package/dist/modules/brainy/auth.js.map +1 -0
  41. package/dist/modules/brainy/errors.d.ts +118 -0
  42. package/dist/modules/brainy/errors.d.ts.map +1 -0
  43. package/dist/modules/brainy/errors.js +142 -0
  44. package/dist/modules/brainy/errors.js.map +1 -0
  45. package/dist/modules/brainy/events.d.ts +63 -0
  46. package/dist/modules/brainy/events.d.ts.map +1 -0
  47. package/dist/modules/brainy/events.js +14 -0
  48. package/dist/modules/brainy/events.js.map +1 -0
  49. package/dist/modules/brainy/proxy.d.ts +48 -0
  50. package/dist/modules/brainy/proxy.d.ts.map +1 -0
  51. package/dist/modules/brainy/proxy.js +95 -0
  52. package/dist/modules/brainy/proxy.js.map +1 -0
  53. package/dist/modules/brainy/types.d.ts +83 -0
  54. package/dist/modules/brainy/types.d.ts.map +1 -0
  55. package/dist/modules/brainy/types.js +21 -0
  56. package/dist/modules/brainy/types.js.map +1 -0
  57. package/dist/modules/events/index.d.ts +41 -0
  58. package/dist/modules/events/index.d.ts.map +1 -0
  59. package/dist/modules/events/index.js +53 -0
  60. package/dist/modules/events/index.js.map +1 -0
  61. package/dist/modules/events/types.d.ts +129 -0
  62. package/dist/modules/events/types.d.ts.map +1 -0
  63. package/dist/modules/events/types.js +32 -0
  64. package/dist/modules/events/types.js.map +1 -0
  65. package/dist/modules/formats/types.d.ts +7 -0
  66. package/dist/modules/formats/types.d.ts.map +1 -0
  67. package/dist/modules/formats/types.js +7 -0
  68. package/dist/modules/formats/types.js.map +1 -0
  69. package/dist/modules/hall/types.d.ts +56 -0
  70. package/dist/modules/hall/types.d.ts.map +1 -0
  71. package/dist/modules/hall/types.js +16 -0
  72. package/dist/modules/hall/types.js.map +1 -0
  73. package/dist/modules/kits/types.d.ts +7 -0
  74. package/dist/modules/kits/types.d.ts.map +1 -0
  75. package/dist/modules/kits/types.js +7 -0
  76. package/dist/modules/kits/types.js.map +1 -0
  77. package/dist/modules/license/types.d.ts +7 -0
  78. package/dist/modules/license/types.d.ts.map +1 -0
  79. package/dist/modules/license/types.js +7 -0
  80. package/dist/modules/license/types.js.map +1 -0
  81. package/dist/modules/notifications/types.d.ts +7 -0
  82. package/dist/modules/notifications/types.d.ts.map +1 -0
  83. package/dist/modules/notifications/types.js +7 -0
  84. package/dist/modules/notifications/types.js.map +1 -0
  85. package/dist/modules/skills/index.d.ts +60 -0
  86. package/dist/modules/skills/index.d.ts.map +1 -0
  87. package/dist/modules/skills/index.js +253 -0
  88. package/dist/modules/skills/index.js.map +1 -0
  89. package/dist/modules/skills/types.d.ts +127 -0
  90. package/dist/modules/skills/types.d.ts.map +1 -0
  91. package/dist/modules/skills/types.js +23 -0
  92. package/dist/modules/skills/types.js.map +1 -0
  93. package/dist/modules/versions/types.d.ts +31 -0
  94. package/dist/modules/versions/types.d.ts.map +1 -0
  95. package/dist/modules/versions/types.js +9 -0
  96. package/dist/modules/versions/types.js.map +1 -0
  97. package/dist/modules/vfs/types.d.ts +26 -0
  98. package/dist/modules/vfs/types.d.ts.map +1 -0
  99. package/dist/modules/vfs/types.js +11 -0
  100. package/dist/modules/vfs/types.js.map +1 -0
  101. package/dist/server/create-sdk.d.ts +70 -0
  102. package/dist/server/create-sdk.d.ts.map +1 -0
  103. package/dist/server/create-sdk.js +125 -0
  104. package/dist/server/create-sdk.js.map +1 -0
  105. package/dist/server/hall-handlers.d.ts +195 -0
  106. package/dist/server/hall-handlers.d.ts.map +1 -0
  107. package/dist/server/hall-handlers.js +239 -0
  108. package/dist/server/hall-handlers.js.map +1 -0
  109. package/dist/server/handlers.d.ts +216 -0
  110. package/dist/server/handlers.d.ts.map +1 -0
  111. package/dist/server/handlers.js +214 -0
  112. package/dist/server/handlers.js.map +1 -0
  113. package/dist/server/index.d.ts +52 -0
  114. package/dist/server/index.d.ts.map +1 -0
  115. package/dist/server/index.js +50 -0
  116. package/dist/server/index.js.map +1 -0
  117. package/dist/server/instance-pool.d.ts +299 -0
  118. package/dist/server/instance-pool.d.ts.map +1 -0
  119. package/dist/server/instance-pool.js +359 -0
  120. package/dist/server/instance-pool.js.map +1 -0
  121. package/dist/transports/http.d.ts +86 -0
  122. package/dist/transports/http.d.ts.map +1 -0
  123. package/dist/transports/http.js +134 -0
  124. package/dist/transports/http.js.map +1 -0
  125. package/dist/transports/local.d.ts +76 -0
  126. package/dist/transports/local.d.ts.map +1 -0
  127. package/dist/transports/local.js +101 -0
  128. package/dist/transports/local.js.map +1 -0
  129. package/dist/transports/sse.d.ts +99 -0
  130. package/dist/transports/sse.d.ts.map +1 -0
  131. package/dist/transports/sse.js +192 -0
  132. package/dist/transports/sse.js.map +1 -0
  133. package/dist/transports/transport.d.ts +68 -0
  134. package/dist/transports/transport.d.ts.map +1 -0
  135. package/dist/transports/transport.js +14 -0
  136. package/dist/transports/transport.js.map +1 -0
  137. package/dist/transports/ws.d.ts +135 -0
  138. package/dist/transports/ws.d.ts.map +1 -0
  139. package/dist/transports/ws.js +331 -0
  140. package/dist/transports/ws.js.map +1 -0
  141. package/dist/types.d.ts +152 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +8 -0
  144. package/dist/types.js.map +1 -0
  145. package/docs/ADR-001-sdk-design.md +282 -0
  146. package/docs/IMPLEMENTATION-PLAN.md +708 -0
  147. package/docs/USAGE.md +646 -0
  148. package/docs/kit-sdk-guide.md +474 -0
  149. 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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"}