@nuraly/lumenjs 0.1.3 → 0.1.4

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 (333) hide show
  1. package/README.md +62 -282
  2. package/dist/auth/config.d.ts +23 -0
  3. package/dist/auth/config.js +115 -0
  4. package/dist/auth/guard.d.ts +12 -0
  5. package/dist/auth/guard.js +28 -0
  6. package/dist/auth/index.d.ts +3 -0
  7. package/dist/auth/index.js +1 -0
  8. package/dist/auth/middleware.d.ts +23 -0
  9. package/dist/auth/middleware.js +89 -0
  10. package/dist/auth/native-auth.d.ts +73 -0
  11. package/dist/auth/native-auth.js +293 -0
  12. package/dist/auth/oidc-client.d.ts +17 -0
  13. package/dist/auth/oidc-client.js +123 -0
  14. package/dist/auth/providers/google.d.ts +23 -0
  15. package/dist/auth/providers/google.js +25 -0
  16. package/dist/auth/providers/index.d.ts +2 -0
  17. package/dist/auth/providers/index.js +1 -0
  18. package/dist/auth/routes/login.d.ts +8 -0
  19. package/dist/auth/routes/login.js +98 -0
  20. package/dist/auth/routes/logout.d.ts +4 -0
  21. package/dist/auth/routes/logout.js +79 -0
  22. package/dist/auth/routes/oidc-callback.d.ts +3 -0
  23. package/dist/auth/routes/oidc-callback.js +70 -0
  24. package/dist/auth/routes/password.d.ts +5 -0
  25. package/dist/auth/routes/password.js +149 -0
  26. package/dist/auth/routes/signup.d.ts +3 -0
  27. package/dist/auth/routes/signup.js +81 -0
  28. package/dist/auth/routes/token.d.ts +4 -0
  29. package/dist/auth/routes/token.js +70 -0
  30. package/dist/auth/routes/utils.d.ts +7 -0
  31. package/dist/auth/routes/utils.js +35 -0
  32. package/dist/auth/routes/verify.d.ts +3 -0
  33. package/dist/auth/routes/verify.js +26 -0
  34. package/dist/auth/routes.d.ts +8 -0
  35. package/dist/auth/routes.js +110 -0
  36. package/dist/auth/session.d.ts +8 -0
  37. package/dist/auth/session.js +54 -0
  38. package/dist/auth/token.d.ts +33 -0
  39. package/dist/auth/token.js +90 -0
  40. package/dist/auth/types.d.ts +156 -0
  41. package/dist/auth/types.js +2 -0
  42. package/dist/build/build-client.d.ts +15 -0
  43. package/dist/build/build-client.js +45 -0
  44. package/dist/build/build-prerender.d.ts +11 -0
  45. package/dist/build/build-prerender.js +159 -0
  46. package/dist/build/build-server.d.ts +17 -0
  47. package/dist/build/build-server.js +98 -0
  48. package/dist/build/build.js +48 -120
  49. package/dist/build/scan.d.ts +17 -0
  50. package/dist/build/scan.js +76 -6
  51. package/dist/build/serve-api.js +8 -2
  52. package/dist/build/serve-loaders.d.ts +4 -4
  53. package/dist/build/serve-loaders.js +26 -18
  54. package/dist/build/serve-ssr.js +38 -11
  55. package/dist/build/serve-static.js +3 -3
  56. package/dist/build/serve.js +218 -15
  57. package/dist/cli.js +37 -6
  58. package/dist/communication/encryption.d.ts +35 -0
  59. package/dist/communication/encryption.js +90 -0
  60. package/dist/communication/handlers/context.d.ts +27 -0
  61. package/dist/communication/handlers/context.js +1 -0
  62. package/dist/communication/handlers/conversation.d.ts +24 -0
  63. package/dist/communication/handlers/conversation.js +113 -0
  64. package/dist/communication/handlers/file-upload.d.ts +17 -0
  65. package/dist/communication/handlers/file-upload.js +62 -0
  66. package/dist/communication/handlers/messaging.d.ts +30 -0
  67. package/dist/communication/handlers/messaging.js +237 -0
  68. package/dist/communication/handlers/presence.d.ts +15 -0
  69. package/dist/communication/handlers/presence.js +76 -0
  70. package/dist/communication/handlers.d.ts +5 -0
  71. package/dist/communication/handlers.js +5 -0
  72. package/dist/communication/index.d.ts +9 -0
  73. package/dist/communication/index.js +7 -0
  74. package/dist/communication/link-preview.d.ts +18 -0
  75. package/dist/communication/link-preview.js +115 -0
  76. package/dist/communication/schema.d.ts +10 -0
  77. package/dist/communication/schema.js +101 -0
  78. package/dist/communication/server.d.ts +86 -0
  79. package/dist/communication/server.js +212 -0
  80. package/dist/communication/signaling.d.ts +43 -0
  81. package/dist/communication/signaling.js +271 -0
  82. package/dist/communication/store.d.ts +71 -0
  83. package/dist/communication/store.js +289 -0
  84. package/dist/communication/types.d.ts +454 -0
  85. package/dist/communication/types.js +1 -0
  86. package/dist/create.d.ts +1 -0
  87. package/dist/create.js +55 -0
  88. package/dist/db/auto-migrate.d.ts +3 -0
  89. package/dist/db/auto-migrate.js +100 -0
  90. package/dist/db/client.d.ts +3 -0
  91. package/dist/db/client.js +18 -0
  92. package/dist/db/index.d.ts +17 -13
  93. package/dist/db/index.js +205 -26
  94. package/dist/db/seed.d.ts +12 -0
  95. package/dist/db/seed.js +88 -0
  96. package/dist/db/table.d.ts +10 -0
  97. package/dist/db/table.js +12 -0
  98. package/dist/dev-server/config.d.ts +11 -0
  99. package/dist/dev-server/config.js +23 -20
  100. package/dist/dev-server/index-html.d.ts +3 -0
  101. package/dist/dev-server/index-html.js +18 -6
  102. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  103. package/dist/dev-server/nuralyui-aliases.js +115 -94
  104. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  105. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  106. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  107. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  108. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  109. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  110. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  111. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  113. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  114. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  115. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  116. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  117. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  118. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  119. package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
  120. package/dist/dev-server/plugins/vite-plugin-routes.js +15 -5
  121. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  122. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  123. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  125. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  126. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  127. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +111 -2
  128. package/dist/dev-server/server.js +127 -13
  129. package/dist/dev-server/ssr-render.d.ts +2 -1
  130. package/dist/dev-server/ssr-render.js +107 -48
  131. package/dist/editor/ai/backend.d.ts +20 -0
  132. package/dist/editor/ai/backend.js +104 -0
  133. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  134. package/dist/editor/ai/claude-code-client.js +145 -0
  135. package/dist/editor/ai/opencode-client.d.ts +14 -0
  136. package/dist/editor/ai/opencode-client.js +125 -0
  137. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  138. package/dist/editor/ai/snapshot-store.js +35 -0
  139. package/dist/editor/ai/types.d.ts +30 -0
  140. package/dist/editor/ai/types.js +136 -0
  141. package/dist/editor/ai-chat-panel.d.ts +13 -0
  142. package/dist/editor/ai-chat-panel.js +587 -0
  143. package/dist/editor/ai-markdown.d.ts +10 -0
  144. package/dist/editor/ai-markdown.js +70 -0
  145. package/dist/editor/ai-project-panel.d.ts +11 -0
  146. package/dist/editor/ai-project-panel.js +332 -0
  147. package/dist/editor/ast-modification.d.ts +11 -0
  148. package/dist/editor/ast-modification.js +1 -0
  149. package/dist/editor/ast-service.d.ts +30 -0
  150. package/dist/editor/ast-service.js +180 -0
  151. package/dist/editor/css-rules.d.ts +54 -0
  152. package/dist/editor/css-rules.js +423 -0
  153. package/dist/editor/editor-api-client.d.ts +51 -0
  154. package/dist/editor/editor-api-client.js +162 -0
  155. package/dist/editor/editor-bridge.d.ts +1 -0
  156. package/dist/editor/editor-bridge.js +17 -8
  157. package/dist/editor/editor-toolbar.d.ts +14 -0
  158. package/dist/editor/editor-toolbar.js +115 -0
  159. package/dist/editor/file-editor.d.ts +9 -0
  160. package/dist/editor/file-editor.js +236 -0
  161. package/dist/editor/file-service.d.ts +16 -0
  162. package/dist/editor/file-service.js +52 -0
  163. package/dist/editor/i18n-key-gen.d.ts +1 -0
  164. package/dist/editor/i18n-key-gen.js +7 -0
  165. package/dist/editor/inline-text-edit.d.ts +5 -0
  166. package/dist/editor/inline-text-edit.js +173 -92
  167. package/dist/editor/overlay-events.d.ts +5 -0
  168. package/dist/editor/overlay-events.js +364 -0
  169. package/dist/editor/overlay-hmr.d.ts +2 -0
  170. package/dist/editor/overlay-hmr.js +75 -0
  171. package/dist/editor/overlay-selection.d.ts +29 -0
  172. package/dist/editor/overlay-selection.js +148 -0
  173. package/dist/editor/overlay-utils.d.ts +12 -0
  174. package/dist/editor/overlay-utils.js +59 -0
  175. package/dist/editor/properties-panel-persist.d.ts +14 -0
  176. package/dist/editor/properties-panel-persist.js +70 -0
  177. package/dist/editor/properties-panel-rows.d.ts +10 -0
  178. package/dist/editor/properties-panel-rows.js +349 -0
  179. package/dist/editor/properties-panel-styles.d.ts +4 -0
  180. package/dist/editor/properties-panel-styles.js +174 -0
  181. package/dist/editor/properties-panel.d.ts +4 -0
  182. package/dist/editor/properties-panel.js +148 -0
  183. package/dist/editor/property-registry.d.ts +16 -0
  184. package/dist/editor/property-registry.js +303 -0
  185. package/dist/editor/standalone-file-panel.d.ts +0 -0
  186. package/dist/editor/standalone-file-panel.js +1 -0
  187. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  188. package/dist/editor/standalone-overlay-dom.js +1 -0
  189. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  190. package/dist/editor/standalone-overlay-styles.js +1 -0
  191. package/dist/editor/standalone-overlay.d.ts +1 -0
  192. package/dist/editor/standalone-overlay.js +76 -0
  193. package/dist/editor/syntax-highlighter.d.ts +4 -0
  194. package/dist/editor/syntax-highlighter.js +81 -0
  195. package/dist/editor/text-toolbar.d.ts +11 -0
  196. package/dist/editor/text-toolbar.js +327 -0
  197. package/dist/editor/toolbar-styles.d.ts +4 -0
  198. package/dist/editor/toolbar-styles.js +198 -0
  199. package/dist/email/index.d.ts +32 -0
  200. package/dist/email/index.js +154 -0
  201. package/dist/email/providers/resend.d.ts +2 -0
  202. package/dist/email/providers/resend.js +24 -0
  203. package/dist/email/providers/sendgrid.d.ts +2 -0
  204. package/dist/email/providers/sendgrid.js +31 -0
  205. package/dist/email/providers/smtp.d.ts +13 -0
  206. package/dist/email/providers/smtp.js +125 -0
  207. package/dist/email/template-engine.d.ts +18 -0
  208. package/dist/email/template-engine.js +116 -0
  209. package/dist/email/templates/base.d.ts +9 -0
  210. package/dist/email/templates/base.js +65 -0
  211. package/dist/email/templates/password-reset.d.ts +5 -0
  212. package/dist/email/templates/password-reset.js +15 -0
  213. package/dist/email/templates/verify-email.d.ts +5 -0
  214. package/dist/email/templates/verify-email.js +15 -0
  215. package/dist/email/templates/welcome.d.ts +5 -0
  216. package/dist/email/templates/welcome.js +13 -0
  217. package/dist/email/types.d.ts +49 -0
  218. package/dist/email/types.js +1 -0
  219. package/dist/llms/generate.d.ts +46 -0
  220. package/dist/llms/generate.js +185 -0
  221. package/dist/permissions/guard.d.ts +28 -0
  222. package/dist/permissions/guard.js +30 -0
  223. package/dist/permissions/index.d.ts +6 -0
  224. package/dist/permissions/index.js +3 -0
  225. package/dist/permissions/service.d.ts +80 -0
  226. package/dist/permissions/service.js +210 -0
  227. package/dist/permissions/tables.d.ts +5 -0
  228. package/dist/permissions/tables.js +68 -0
  229. package/dist/permissions/types.d.ts +33 -0
  230. package/dist/permissions/types.js +1 -0
  231. package/dist/runtime/app-shell.js +163 -0
  232. package/dist/runtime/auth.d.ts +10 -0
  233. package/dist/runtime/auth.js +30 -0
  234. package/dist/runtime/communication.d.ts +137 -0
  235. package/dist/runtime/communication.js +228 -0
  236. package/dist/runtime/error-boundary.d.ts +23 -0
  237. package/dist/runtime/error-boundary.js +120 -0
  238. package/dist/runtime/i18n.d.ts +6 -1
  239. package/dist/runtime/i18n.js +42 -21
  240. package/dist/runtime/router-data.d.ts +3 -0
  241. package/dist/runtime/router-data.js +102 -17
  242. package/dist/runtime/router-hydration.js +25 -0
  243. package/dist/runtime/router.d.ts +16 -1
  244. package/dist/runtime/router.js +188 -42
  245. package/dist/runtime/socket-client.d.ts +2 -0
  246. package/dist/runtime/socket-client.js +30 -0
  247. package/dist/runtime/webrtc.d.ts +47 -0
  248. package/dist/runtime/webrtc.js +178 -0
  249. package/dist/shared/graceful-shutdown.d.ts +8 -0
  250. package/dist/shared/graceful-shutdown.js +36 -0
  251. package/dist/shared/health.d.ts +8 -0
  252. package/dist/shared/health.js +25 -0
  253. package/dist/shared/llms-txt.d.ts +31 -0
  254. package/dist/shared/llms-txt.js +85 -0
  255. package/dist/shared/logger.d.ts +32 -0
  256. package/dist/shared/logger.js +93 -0
  257. package/dist/shared/meta.d.ts +27 -0
  258. package/dist/shared/meta.js +71 -0
  259. package/dist/shared/middleware-runner.d.ts +9 -0
  260. package/dist/shared/middleware-runner.js +29 -0
  261. package/dist/shared/rate-limit.d.ts +18 -0
  262. package/dist/shared/rate-limit.js +71 -0
  263. package/dist/shared/request-id.d.ts +5 -0
  264. package/dist/shared/request-id.js +18 -0
  265. package/dist/shared/route-matching.js +16 -1
  266. package/dist/shared/security-headers.d.ts +18 -0
  267. package/dist/shared/security-headers.js +38 -0
  268. package/dist/shared/socket-io-setup.d.ts +11 -0
  269. package/dist/shared/socket-io-setup.js +51 -0
  270. package/dist/shared/types.d.ts +14 -0
  271. package/dist/shared/utils.d.ts +33 -7
  272. package/dist/shared/utils.js +164 -27
  273. package/dist/storage/adapters/local.d.ts +44 -0
  274. package/dist/storage/adapters/local.js +85 -0
  275. package/dist/storage/adapters/s3.d.ts +32 -0
  276. package/dist/storage/adapters/s3.js +116 -0
  277. package/dist/storage/adapters/types.d.ts +53 -0
  278. package/dist/storage/adapters/types.js +1 -0
  279. package/dist/storage/index.d.ts +76 -0
  280. package/dist/storage/index.js +83 -0
  281. package/package.json +19 -7
  282. package/templates/blog/api/posts.ts +4 -18
  283. package/templates/blog/data/migrations/001_init.sql +6 -5
  284. package/templates/blog/lumenjs.config.ts +3 -0
  285. package/templates/blog/package.json +14 -0
  286. package/templates/blog/pages/_layout.ts +25 -0
  287. package/templates/blog/pages/index.ts +48 -22
  288. package/templates/blog/pages/posts/[slug].ts +45 -20
  289. package/templates/blog/pages/tag/[tag].ts +44 -0
  290. package/templates/dashboard/api/stats.ts +8 -5
  291. package/templates/dashboard/lumenjs.config.ts +3 -0
  292. package/templates/dashboard/package.json +14 -0
  293. package/templates/dashboard/pages/_layout.ts +25 -0
  294. package/templates/dashboard/pages/index.ts +54 -23
  295. package/templates/dashboard/pages/settings/index.ts +29 -0
  296. package/templates/default/lumenjs.config.ts +3 -0
  297. package/templates/default/package.json +14 -0
  298. package/templates/default/pages/index.ts +24 -0
  299. package/templates/social/api/posts/[id].ts +14 -0
  300. package/templates/social/api/posts.ts +11 -0
  301. package/templates/social/api/profile/[username].ts +10 -0
  302. package/templates/social/api/upload.ts +19 -0
  303. package/templates/social/data/migrations/001_init.sql +78 -0
  304. package/templates/social/data/migrations/002_add_image_url.sql +1 -0
  305. package/templates/social/data/migrations/003_auth.sql +7 -0
  306. package/templates/social/docs/architecture.md +76 -0
  307. package/templates/social/docs/components.md +100 -0
  308. package/templates/social/docs/data.md +89 -0
  309. package/templates/social/docs/pages.md +96 -0
  310. package/templates/social/docs/theming.md +52 -0
  311. package/templates/social/lib/media.ts +130 -0
  312. package/templates/social/lumenjs.auth.ts +21 -0
  313. package/templates/social/lumenjs.config.ts +3 -0
  314. package/templates/social/package.json +5 -0
  315. package/templates/social/pages/_layout.ts +239 -0
  316. package/templates/social/pages/apps/[id].ts +173 -0
  317. package/templates/social/pages/apps/index.ts +116 -0
  318. package/templates/social/pages/auth/login.ts +92 -0
  319. package/templates/social/pages/bookmarks.ts +57 -0
  320. package/templates/social/pages/explore.ts +73 -0
  321. package/templates/social/pages/index.ts +351 -0
  322. package/templates/social/pages/messages.ts +298 -0
  323. package/templates/social/pages/new.ts +77 -0
  324. package/templates/social/pages/notifications.ts +73 -0
  325. package/templates/social/pages/post/[id].ts +124 -0
  326. package/templates/social/pages/profile/[username].ts +100 -0
  327. package/templates/social/pages/settings/accessibility.ts +153 -0
  328. package/templates/social/pages/settings/account.ts +260 -0
  329. package/templates/social/pages/settings/help.ts +141 -0
  330. package/templates/social/pages/settings/language.ts +103 -0
  331. package/templates/social/pages/settings/privacy.ts +183 -0
  332. package/templates/social/pages/settings/security.ts +133 -0
  333. package/templates/social/pages/settings.ts +185 -0
@@ -0,0 +1,89 @@
1
+ import { parseSessionCookie, decryptSession, encryptSession, createSessionCookie, shouldRefreshSession } from './session.js';
2
+ /**
3
+ * Create Connect middleware that parses the session cookie and attaches req.nkAuth.
4
+ * Works with both OIDC and native auth sessions — the cookie format is identical.
5
+ *
6
+ * Behavior:
7
+ * 1. Skip non-page requests (/@, /node_modules, static files with `.`)
8
+ * 2. Check Bearer token (mobile/API clients)
9
+ * 3. Parse + decrypt session cookie → attach req.nkAuth
10
+ * 4. Refresh OIDC tokens if expiring within 5 minutes
11
+ * 5. Always call next() — never blocks the middleware chain
12
+ */
13
+ export function createAuthMiddleware(config, db) {
14
+ return async (req, res, next) => {
15
+ const url = req.url || '';
16
+ // Skip non-page requests
17
+ if (url.startsWith('/@') || url.startsWith('/node_modules') || url.includes('.')) {
18
+ return next();
19
+ }
20
+ req.nkAuth = null;
21
+ // 1. Check bearer token first (mobile apps)
22
+ const authHeader = req.headers.authorization;
23
+ if (authHeader?.startsWith('Bearer ')) {
24
+ try {
25
+ const { verifyAccessToken } = await import('./token.js');
26
+ const user = verifyAccessToken(authHeader.slice(7), config.session.secret);
27
+ if (user) {
28
+ req.nkAuth = { user, session: { accessToken: authHeader.slice(7), expiresAt: 0, user } };
29
+ return next();
30
+ }
31
+ }
32
+ catch { /* Invalid token — fall through to cookie auth */ }
33
+ }
34
+ // 2. Fall back to cookie-based session (browsers)
35
+ const cookieHeader = req.headers.cookie;
36
+ if (!cookieHeader)
37
+ return next();
38
+ const sessionCookie = parseSessionCookie(cookieHeader, config.session.cookieName);
39
+ if (!sessionCookie)
40
+ return next();
41
+ const session = await decryptSession(sessionCookie, config.session.secret);
42
+ if (!session)
43
+ return next();
44
+ req.nkAuth = { user: session.user, session };
45
+ // 3. Check if session was revoked via logout-all
46
+ if (db && session.createdAt && session.user?.sub) {
47
+ try {
48
+ const { getSessionsRevokedAt } = await import('./native-auth.js');
49
+ const revokedAt = await getSessionsRevokedAt(db, session.user.sub);
50
+ if (revokedAt && session.createdAt <= revokedAt) {
51
+ req.nkAuth = null;
52
+ return next();
53
+ }
54
+ }
55
+ catch { /* Revocation check failed — deny access (fail closed) */
56
+ req.nkAuth = null;
57
+ return next();
58
+ }
59
+ }
60
+ // 4. Refresh OIDC tokens if about to expire (native sessions don't expire the same way)
61
+ if (shouldRefreshSession(session) && session.refreshToken && session.provider !== 'native') {
62
+ const oidc = config.providers.find(p => p.type === 'oidc' && p.name === session.provider);
63
+ if (oidc) {
64
+ try {
65
+ const { discoverProvider, refreshTokens, extractUser } = await import('./oidc-client.js');
66
+ const metadata = await discoverProvider(oidc.issuer);
67
+ const tokens = await refreshTokens(metadata, oidc.clientId, oidc.clientSecret, session.refreshToken);
68
+ const user = extractUser(tokens.id_token || session.idToken || '', undefined);
69
+ user.provider = oidc.name;
70
+ const newSession = {
71
+ accessToken: tokens.access_token,
72
+ refreshToken: tokens.refresh_token || session.refreshToken,
73
+ idToken: tokens.id_token || session.idToken,
74
+ expiresAt: Math.floor(Date.now() / 1000) + tokens.expires_in,
75
+ user,
76
+ provider: oidc.name,
77
+ createdAt: session.createdAt ?? Math.floor(Date.now() / 1000),
78
+ };
79
+ const encrypted = await encryptSession(newSession, config.session.secret);
80
+ const cookie = createSessionCookie(config.session.cookieName, encrypted, config.session.maxAge, config.session.secure);
81
+ res.setHeader('Set-Cookie', cookie);
82
+ req.nkAuth = { user: newSession.user, session: newSession };
83
+ }
84
+ catch { /* OIDC token refresh failed — keep existing session */ }
85
+ }
86
+ }
87
+ next();
88
+ };
89
+ }
@@ -0,0 +1,73 @@
1
+ import type { AuthUser, NativeProvider } from './types.js';
2
+ /**
3
+ * Hash a password using scrypt (Node.js built-in, no dependencies).
4
+ */
5
+ export declare function hashPassword(password: string): Promise<string>;
6
+ /**
7
+ * Verify a password against a stored hash.
8
+ */
9
+ export declare function verifyPassword(password: string, hash: string): Promise<boolean>;
10
+ /** DB interface — subset of LumenDb */
11
+ interface Db {
12
+ all<T = any>(sql: string, ...params: any[]): Promise<T[]>;
13
+ get<T = any>(sql: string, ...params: any[]): Promise<T | undefined>;
14
+ run(sql: string, ...params: any[]): Promise<{
15
+ changes: number;
16
+ lastInsertRowid: number | bigint;
17
+ }>;
18
+ exec(sql: string): Promise<void>;
19
+ }
20
+ /**
21
+ * Ensure the native auth users table exists.
22
+ */
23
+ export declare function ensureUsersTable(db: Db): Promise<void>;
24
+ /**
25
+ * Register a new user with email/password.
26
+ */
27
+ export declare function registerUser(db: Db, email: string, password: string, name?: string, provider?: NativeProvider): Promise<AuthUser>;
28
+ /**
29
+ * Authenticate a user with email/password.
30
+ */
31
+ export declare function authenticateUser(db: Db, email: string, password: string): Promise<AuthUser | null>;
32
+ /**
33
+ * Find a user by email (for account linking with OIDC).
34
+ */
35
+ export declare function findUserByEmail(db: Db, email: string): Promise<AuthUser | null>;
36
+ /**
37
+ * Create or link a native user from OIDC claims (account linking by email).
38
+ * Only links to existing native accounts if the OIDC provider has verified the email.
39
+ */
40
+ export declare function linkOidcUser(db: Db, oidcUser: AuthUser): Promise<AuthUser>;
41
+ /**
42
+ * Generate an HMAC-signed email verification token.
43
+ * Format: userId.expiry.signature (base64url)
44
+ */
45
+ export declare function generateVerificationToken(userId: string, secret: string, ttlSeconds?: number): string;
46
+ /**
47
+ * Verify and decode an email verification token.
48
+ * Returns userId or null if invalid/expired.
49
+ */
50
+ export declare function verifyVerificationToken(token: string, secret: string): string | null;
51
+ /** Mark a user's email as verified. */
52
+ export declare function verifyUserEmail(db: Db, userId: string): Promise<boolean>;
53
+ /** Check if a user's email is verified. */
54
+ export declare function isEmailVerified(db: Db, userId: string): Promise<boolean>;
55
+ /**
56
+ * Generate an HMAC-signed password reset token.
57
+ * Includes a hash of the current password hash so the token auto-invalidates
58
+ * after the password is changed (single-use without server state).
59
+ */
60
+ export declare function generateResetToken(userId: string, secret: string, ttlSeconds?: number, currentPasswordHash?: string): string;
61
+ /** Verify a password reset token. Returns userId or null. */
62
+ export declare function verifyResetToken(token: string, secret: string, currentPasswordHash?: string): string | null;
63
+ /** Decode the userId from a reset token payload without verifying. */
64
+ export declare function decodeResetTokenUserId(token: string): string | null;
65
+ /** Update a user's password. */
66
+ export declare function updatePassword(db: Db, userId: string, newPassword: string, minLength?: number): Promise<void>;
67
+ /** Find a user by email. Returns { id, email, name } or null. */
68
+ export declare function findUserIdByEmail(db: Db, email: string): Promise<string | null>;
69
+ /** Set sessions_revoked_at to now, invalidating all sessions created before this moment. */
70
+ export declare function revokeAllSessions(db: Db, userId: string): Promise<void>;
71
+ /** Get the epoch-seconds timestamp of the last logout-all, or null if never revoked. */
72
+ export declare function getSessionsRevokedAt(db: Db, userId: string): Promise<number | null>;
73
+ export {};
@@ -0,0 +1,293 @@
1
+ import crypto from 'node:crypto';
2
+ const SALT_LENGTH = 16;
3
+ const KEY_LENGTH = 64;
4
+ const SCRYPT_COST = 16384;
5
+ /**
6
+ * Hash a password using scrypt (Node.js built-in, no dependencies).
7
+ */
8
+ export async function hashPassword(password) {
9
+ const salt = crypto.randomBytes(SALT_LENGTH);
10
+ return new Promise((resolve, reject) => {
11
+ crypto.scrypt(password, salt, KEY_LENGTH, { N: SCRYPT_COST }, (err, derivedKey) => {
12
+ if (err)
13
+ reject(err);
14
+ else
15
+ resolve(`${salt.toString('hex')}:${derivedKey.toString('hex')}`);
16
+ });
17
+ });
18
+ }
19
+ /**
20
+ * Verify a password against a stored hash.
21
+ */
22
+ export async function verifyPassword(password, hash) {
23
+ const [saltHex, keyHex] = hash.split(':');
24
+ if (!saltHex || !keyHex)
25
+ return false;
26
+ const salt = Buffer.from(saltHex, 'hex');
27
+ return new Promise((resolve, reject) => {
28
+ crypto.scrypt(password, salt, KEY_LENGTH, { N: SCRYPT_COST }, (err, derivedKey) => {
29
+ if (err)
30
+ reject(err);
31
+ else
32
+ resolve(crypto.timingSafeEqual(derivedKey, Buffer.from(keyHex, 'hex')));
33
+ });
34
+ });
35
+ }
36
+ /**
37
+ * Ensure the native auth users table exists.
38
+ */
39
+ export async function ensureUsersTable(db) {
40
+ await db.exec(`CREATE TABLE IF NOT EXISTS _nk_auth_users (
41
+ id TEXT PRIMARY KEY,
42
+ email TEXT NOT NULL UNIQUE,
43
+ name TEXT,
44
+ password_hash TEXT NOT NULL,
45
+ email_verified INTEGER NOT NULL DEFAULT 0,
46
+ roles TEXT NOT NULL DEFAULT '[]',
47
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
48
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
49
+ )`);
50
+ // Add email_verified column if table already exists without it
51
+ try {
52
+ await db.exec('ALTER TABLE _nk_auth_users ADD COLUMN email_verified INTEGER NOT NULL DEFAULT 0');
53
+ }
54
+ catch { }
55
+ ;
56
+ // Add sessions_revoked_at column for logout-all support
57
+ try {
58
+ await db.exec('ALTER TABLE _nk_auth_users ADD COLUMN sessions_revoked_at TEXT');
59
+ }
60
+ catch { }
61
+ ;
62
+ }
63
+ /**
64
+ * Register a new user with email/password.
65
+ */
66
+ export async function registerUser(db, email, password, name, provider) {
67
+ // Basic email format validation
68
+ if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
69
+ throw new Error('Invalid email address');
70
+ }
71
+ const MAX_PASSWORD_LENGTH = 128;
72
+ const minLength = provider?.minPasswordLength ?? 8;
73
+ if (password.length < minLength) {
74
+ throw new Error(`Password must be at least ${minLength} characters`);
75
+ }
76
+ if (password.length > MAX_PASSWORD_LENGTH) {
77
+ throw new Error(`Password must be at most ${MAX_PASSWORD_LENGTH} characters`);
78
+ }
79
+ const existing = await db.get('SELECT id FROM _nk_auth_users WHERE email = ?', email);
80
+ if (existing) {
81
+ throw new Error('Email already registered');
82
+ }
83
+ const id = crypto.randomUUID();
84
+ const hash = await hashPassword(password);
85
+ await db.run('INSERT INTO _nk_auth_users (id, email, name, password_hash) VALUES (?, ?, ?, ?)', id, email, name || null, hash);
86
+ return {
87
+ sub: id,
88
+ email,
89
+ name,
90
+ roles: [],
91
+ provider: 'native',
92
+ };
93
+ }
94
+ /**
95
+ * Authenticate a user with email/password.
96
+ */
97
+ export async function authenticateUser(db, email, password) {
98
+ const row = await db.get('SELECT * FROM _nk_auth_users WHERE email = ?', email);
99
+ if (!row)
100
+ return null;
101
+ const valid = await verifyPassword(password, row.password_hash);
102
+ if (!valid)
103
+ return null;
104
+ let roles = [];
105
+ try {
106
+ roles = JSON.parse(row.roles);
107
+ }
108
+ catch { }
109
+ return {
110
+ sub: row.id,
111
+ email: row.email,
112
+ name: row.name,
113
+ roles,
114
+ provider: 'native',
115
+ };
116
+ }
117
+ /**
118
+ * Find a user by email (for account linking with OIDC).
119
+ */
120
+ export async function findUserByEmail(db, email) {
121
+ const row = await db.get('SELECT * FROM _nk_auth_users WHERE email = ?', email);
122
+ if (!row)
123
+ return null;
124
+ let roles = [];
125
+ try {
126
+ roles = JSON.parse(row.roles);
127
+ }
128
+ catch { }
129
+ return {
130
+ sub: row.id,
131
+ email: row.email,
132
+ name: row.name,
133
+ roles,
134
+ provider: 'native',
135
+ };
136
+ }
137
+ /**
138
+ * Create or link a native user from OIDC claims (account linking by email).
139
+ * Only links to existing native accounts if the OIDC provider has verified the email.
140
+ */
141
+ export async function linkOidcUser(db, oidcUser) {
142
+ if (!oidcUser.email)
143
+ return oidcUser;
144
+ const existing = await db.get('SELECT * FROM _nk_auth_users WHERE email = ?', oidcUser.email);
145
+ if (existing) {
146
+ // Only link if the OIDC provider has verified the email — prevents account takeover
147
+ if (!oidcUser.email_verified) {
148
+ return oidcUser;
149
+ }
150
+ // Merge roles from both sources
151
+ let nativeRoles = [];
152
+ try {
153
+ nativeRoles = JSON.parse(existing.roles);
154
+ }
155
+ catch { }
156
+ const mergedRoles = [...new Set([...nativeRoles, ...(oidcUser.roles || [])])];
157
+ return {
158
+ ...oidcUser,
159
+ sub: existing.id,
160
+ roles: mergedRoles,
161
+ };
162
+ }
163
+ // Auto-create native user record from OIDC
164
+ const id = crypto.randomUUID();
165
+ const roles = JSON.stringify(oidcUser.roles || []);
166
+ await db.run(`INSERT INTO _nk_auth_users (id, email, name, password_hash, roles) VALUES (?, ?, ?, '', ?)`, id, oidcUser.email, oidcUser.name || null, roles);
167
+ return { ...oidcUser, sub: id };
168
+ }
169
+ // ── Email Verification ──────────────────────────────────────────
170
+ /**
171
+ * Generate an HMAC-signed email verification token.
172
+ * Format: userId.expiry.signature (base64url)
173
+ */
174
+ export function generateVerificationToken(userId, secret, ttlSeconds = 86400) {
175
+ const expiry = Math.floor(Date.now() / 1000) + ttlSeconds;
176
+ const payload = `${userId}.${expiry}`;
177
+ const sig = crypto.createHmac('sha256', secret).update(payload).digest('base64url');
178
+ return `${Buffer.from(payload).toString('base64url')}.${sig}`;
179
+ }
180
+ /**
181
+ * Verify and decode an email verification token.
182
+ * Returns userId or null if invalid/expired.
183
+ */
184
+ export function verifyVerificationToken(token, secret) {
185
+ try {
186
+ const parts = token.split('.');
187
+ if (parts.length !== 2)
188
+ return null;
189
+ const payload = Buffer.from(parts[0], 'base64url').toString('utf8');
190
+ const expectedSig = crypto.createHmac('sha256', secret).update(payload).digest('base64url');
191
+ if (!crypto.timingSafeEqual(Buffer.from(parts[1]), Buffer.from(expectedSig)))
192
+ return null;
193
+ const [userId, expiryStr] = payload.split('.');
194
+ if (parseInt(expiryStr) < Math.floor(Date.now() / 1000))
195
+ return null;
196
+ return userId;
197
+ }
198
+ catch {
199
+ return null;
200
+ }
201
+ }
202
+ /** Mark a user's email as verified. */
203
+ export async function verifyUserEmail(db, userId) {
204
+ const result = await db.run('UPDATE _nk_auth_users SET email_verified = 1, updated_at = datetime("now") WHERE id = ?', userId);
205
+ return result.changes > 0;
206
+ }
207
+ /** Check if a user's email is verified. */
208
+ export async function isEmailVerified(db, userId) {
209
+ const row = await db.get('SELECT email_verified FROM _nk_auth_users WHERE id = ?', userId);
210
+ return row?.email_verified === 1;
211
+ }
212
+ // ── Password Reset ──────────────────────────────────────────────
213
+ /**
214
+ * Generate an HMAC-signed password reset token.
215
+ * Includes a hash of the current password hash so the token auto-invalidates
216
+ * after the password is changed (single-use without server state).
217
+ */
218
+ export function generateResetToken(userId, secret, ttlSeconds = 3600, currentPasswordHash) {
219
+ const expiry = Math.floor(Date.now() / 1000) + ttlSeconds;
220
+ // Include a fingerprint of the current password hash to invalidate on change
221
+ const pwFingerprint = currentPasswordHash
222
+ ? crypto.createHash('sha256').update(currentPasswordHash).digest('hex').slice(0, 8)
223
+ : '';
224
+ const payload = `${userId}.${expiry}.${pwFingerprint}`;
225
+ const sig = crypto.createHmac('sha256', secret).update(payload).digest('base64url');
226
+ return `${Buffer.from(payload).toString('base64url')}.${sig}`;
227
+ }
228
+ /** Verify a password reset token. Returns userId or null. */
229
+ export function verifyResetToken(token, secret, currentPasswordHash) {
230
+ try {
231
+ const parts = token.split('.');
232
+ if (parts.length !== 2)
233
+ return null;
234
+ const payload = Buffer.from(parts[0], 'base64url').toString('utf8');
235
+ const expectedSig = crypto.createHmac('sha256', secret).update(payload).digest('base64url');
236
+ if (!crypto.timingSafeEqual(Buffer.from(parts[1]), Buffer.from(expectedSig)))
237
+ return null;
238
+ const segments = payload.split('.');
239
+ const [userId, expiryStr] = segments;
240
+ const pwFingerprint = segments[2] || '';
241
+ if (parseInt(expiryStr) < Math.floor(Date.now() / 1000))
242
+ return null;
243
+ // Verify password hasn't changed since token was issued
244
+ if (pwFingerprint && currentPasswordHash) {
245
+ const currentFingerprint = crypto.createHash('sha256').update(currentPasswordHash).digest('hex').slice(0, 8);
246
+ if (pwFingerprint !== currentFingerprint)
247
+ return null;
248
+ }
249
+ return userId;
250
+ }
251
+ catch {
252
+ return null;
253
+ }
254
+ }
255
+ /** Decode the userId from a reset token payload without verifying. */
256
+ export function decodeResetTokenUserId(token) {
257
+ try {
258
+ const parts = token.split('.');
259
+ if (parts.length !== 2)
260
+ return null;
261
+ const payload = Buffer.from(parts[0], 'base64url').toString('utf8');
262
+ return payload.split('.')[0] || null;
263
+ }
264
+ catch {
265
+ return null;
266
+ }
267
+ }
268
+ /** Update a user's password. */
269
+ export async function updatePassword(db, userId, newPassword, minLength = 8) {
270
+ if (newPassword.length < minLength)
271
+ throw new Error(`Password must be at least ${minLength} characters`);
272
+ if (newPassword.length > 128)
273
+ throw new Error('Password must be at most 128 characters');
274
+ const hash = await hashPassword(newPassword);
275
+ await db.run('UPDATE _nk_auth_users SET password_hash = ?, updated_at = datetime("now") WHERE id = ?', hash, userId);
276
+ }
277
+ /** Find a user by email. Returns { id, email, name } or null. */
278
+ export async function findUserIdByEmail(db, email) {
279
+ const row = await db.get('SELECT id FROM _nk_auth_users WHERE email = ?', email);
280
+ return row?.id || null;
281
+ }
282
+ // ── Session Revocation (Logout All) ─────────────────────────────
283
+ /** Set sessions_revoked_at to now, invalidating all sessions created before this moment. */
284
+ export async function revokeAllSessions(db, userId) {
285
+ await db.run('UPDATE _nk_auth_users SET sessions_revoked_at = datetime("now") WHERE id = ?', userId);
286
+ }
287
+ /** Get the epoch-seconds timestamp of the last logout-all, or null if never revoked. */
288
+ export async function getSessionsRevokedAt(db, userId) {
289
+ const row = await db.get('SELECT sessions_revoked_at FROM _nk_auth_users WHERE id = ?', userId);
290
+ if (!row?.sessions_revoked_at)
291
+ return null;
292
+ return Math.floor(new Date(row.sessions_revoked_at + 'Z').getTime() / 1000);
293
+ }
@@ -0,0 +1,17 @@
1
+ import type { OIDCMetadata, TokenSet, AuthUser } from './types.js';
2
+ export declare function discoverProvider(issuer: string): Promise<OIDCMetadata>;
3
+ export declare function generateCodeVerifier(): string;
4
+ export declare function generateCodeChallenge(verifier: string): string;
5
+ export declare function buildAuthorizationUrl(metadata: OIDCMetadata, clientId: string, redirectUri: string, scopes: string[], state: string, codeVerifier: string): string;
6
+ export declare function exchangeCode(metadata: OIDCMetadata, clientId: string, clientSecret: string | undefined, code: string, redirectUri: string, codeVerifier: string): Promise<TokenSet>;
7
+ export declare function refreshTokens(metadata: OIDCMetadata, clientId: string, clientSecret: string | undefined, refreshToken: string): Promise<TokenSet>;
8
+ export declare function fetchUserInfo(metadata: OIDCMetadata, accessToken: string): Promise<Record<string, any>>;
9
+ export declare function decodeJwtPayload(token: string): Record<string, any>;
10
+ /**
11
+ * Validate basic ID token claims (iss, aud, exp) after decoding.
12
+ * Note: Full JWKS signature verification is not yet implemented.
13
+ * The token is received directly from the provider's token endpoint over TLS,
14
+ * which provides transport-level trust, but does not satisfy OIDC §3.1.3.7.
15
+ */
16
+ export declare function validateIdTokenClaims(claims: Record<string, any>, issuer: string, clientId: string): void;
17
+ export declare function extractUser(idToken: string, userInfo?: Record<string, any>): AuthUser;
@@ -0,0 +1,123 @@
1
+ import crypto from 'node:crypto';
2
+ const METADATA_TTL_MS = 60 * 60 * 1000; // 1 hour
3
+ const metadataCache = new Map();
4
+ export async function discoverProvider(issuer) {
5
+ const cached = metadataCache.get(issuer);
6
+ if (cached && Date.now() - cached.fetchedAt < METADATA_TTL_MS)
7
+ return cached.metadata;
8
+ const url = issuer.replace(/\/+$/, '') + '/.well-known/openid-configuration';
9
+ const res = await fetch(url);
10
+ if (!res.ok)
11
+ throw new Error(`OIDC discovery failed: ${res.status} ${res.statusText}`);
12
+ const metadata = await res.json();
13
+ metadataCache.set(issuer, { metadata, fetchedAt: Date.now() });
14
+ return metadata;
15
+ }
16
+ export function generateCodeVerifier() {
17
+ return crypto.randomBytes(32).toString('base64url');
18
+ }
19
+ export function generateCodeChallenge(verifier) {
20
+ return crypto.createHash('sha256').update(verifier).digest('base64url');
21
+ }
22
+ export function buildAuthorizationUrl(metadata, clientId, redirectUri, scopes, state, codeVerifier) {
23
+ const params = new URLSearchParams({
24
+ response_type: 'code',
25
+ client_id: clientId,
26
+ redirect_uri: redirectUri,
27
+ scope: scopes.join(' '),
28
+ state,
29
+ code_challenge: generateCodeChallenge(codeVerifier),
30
+ code_challenge_method: 'S256',
31
+ });
32
+ return `${metadata.authorization_endpoint}?${params}`;
33
+ }
34
+ export async function exchangeCode(metadata, clientId, clientSecret, code, redirectUri, codeVerifier) {
35
+ const body = new URLSearchParams({
36
+ grant_type: 'authorization_code',
37
+ client_id: clientId,
38
+ code,
39
+ redirect_uri: redirectUri,
40
+ code_verifier: codeVerifier,
41
+ });
42
+ if (clientSecret)
43
+ body.set('client_secret', clientSecret);
44
+ const res = await fetch(metadata.token_endpoint, {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
47
+ body: body.toString(),
48
+ });
49
+ if (!res.ok) {
50
+ const err = await res.text();
51
+ throw new Error(`Token exchange failed: ${res.status} ${err}`);
52
+ }
53
+ return res.json();
54
+ }
55
+ export async function refreshTokens(metadata, clientId, clientSecret, refreshToken) {
56
+ const body = new URLSearchParams({
57
+ grant_type: 'refresh_token',
58
+ client_id: clientId,
59
+ refresh_token: refreshToken,
60
+ });
61
+ if (clientSecret)
62
+ body.set('client_secret', clientSecret);
63
+ const res = await fetch(metadata.token_endpoint, {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
66
+ body: body.toString(),
67
+ });
68
+ if (!res.ok)
69
+ throw new Error(`Token refresh failed: ${res.status}`);
70
+ return res.json();
71
+ }
72
+ export async function fetchUserInfo(metadata, accessToken) {
73
+ const res = await fetch(metadata.userinfo_endpoint, {
74
+ headers: { Authorization: `Bearer ${accessToken}` },
75
+ });
76
+ if (!res.ok)
77
+ throw new Error(`UserInfo fetch failed: ${res.status}`);
78
+ return res.json();
79
+ }
80
+ export function decodeJwtPayload(token) {
81
+ const parts = token.split('.');
82
+ if (parts.length !== 3)
83
+ throw new Error('Invalid JWT');
84
+ const payload = Buffer.from(parts[1], 'base64url').toString('utf8');
85
+ return JSON.parse(payload);
86
+ }
87
+ /**
88
+ * Validate basic ID token claims (iss, aud, exp) after decoding.
89
+ * Note: Full JWKS signature verification is not yet implemented.
90
+ * The token is received directly from the provider's token endpoint over TLS,
91
+ * which provides transport-level trust, but does not satisfy OIDC §3.1.3.7.
92
+ */
93
+ export function validateIdTokenClaims(claims, issuer, clientId) {
94
+ if (!claims.iss)
95
+ throw new Error('ID token missing iss claim');
96
+ if (claims.iss !== issuer) {
97
+ throw new Error(`ID token issuer mismatch: expected ${issuer}, got ${claims.iss}`);
98
+ }
99
+ if (!claims.aud)
100
+ throw new Error('ID token missing aud claim');
101
+ const audiences = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
102
+ if (!audiences.includes(clientId)) {
103
+ throw new Error(`ID token audience mismatch: expected ${clientId}`);
104
+ }
105
+ if (!claims.exp || typeof claims.exp !== 'number')
106
+ throw new Error('ID token missing exp claim');
107
+ if (claims.exp < Math.floor(Date.now() / 1000)) {
108
+ throw new Error('ID token has expired');
109
+ }
110
+ }
111
+ export function extractUser(idToken, userInfo) {
112
+ const claims = decodeJwtPayload(idToken);
113
+ const merged = { ...claims, ...userInfo };
114
+ return {
115
+ sub: merged.sub,
116
+ email: merged.email,
117
+ name: merged.name || merged.preferred_username,
118
+ preferred_username: merged.preferred_username,
119
+ roles: merged.realm_access?.roles || merged.roles || [],
120
+ email_verified: merged.email_verified,
121
+ provider: merged.provider,
122
+ };
123
+ }
@@ -0,0 +1,23 @@
1
+ import type { OIDCProvider } from '../types.js';
2
+ export interface GoogleProviderOptions {
3
+ clientId: string;
4
+ clientSecret?: string;
5
+ /** Extra scopes beyond openid, profile, email. */
6
+ scopes?: string[];
7
+ }
8
+ /**
9
+ * Pre-configured Google OIDC provider.
10
+ *
11
+ * Google publishes a standard OIDC discovery document at
12
+ * https://accounts.google.com/.well-known/openid-configuration
13
+ * so no manual endpoint config is needed.
14
+ *
15
+ * @example
16
+ * // lumenjs.auth.ts
17
+ * import { googleProvider } from '@nuraly/lumenjs/dist/auth/providers/google.js';
18
+ * export default {
19
+ * providers: [googleProvider({ clientId: process.env.GOOGLE_CLIENT_ID! })],
20
+ * session: { secret: process.env.SESSION_SECRET! },
21
+ * };
22
+ */
23
+ export declare function googleProvider(opts: GoogleProviderOptions): OIDCProvider;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Pre-configured Google OIDC provider.
3
+ *
4
+ * Google publishes a standard OIDC discovery document at
5
+ * https://accounts.google.com/.well-known/openid-configuration
6
+ * so no manual endpoint config is needed.
7
+ *
8
+ * @example
9
+ * // lumenjs.auth.ts
10
+ * import { googleProvider } from '@nuraly/lumenjs/dist/auth/providers/google.js';
11
+ * export default {
12
+ * providers: [googleProvider({ clientId: process.env.GOOGLE_CLIENT_ID! })],
13
+ * session: { secret: process.env.SESSION_SECRET! },
14
+ * };
15
+ */
16
+ export function googleProvider(opts) {
17
+ return {
18
+ type: 'oidc',
19
+ name: 'google',
20
+ issuer: 'https://accounts.google.com',
21
+ clientId: opts.clientId,
22
+ clientSecret: opts.clientSecret,
23
+ scopes: ['openid', 'profile', 'email', ...(opts.scopes ?? [])],
24
+ };
25
+ }
@@ -0,0 +1,2 @@
1
+ export { googleProvider } from './google.js';
2
+ export type { GoogleProviderOptions } from './google.js';
@@ -0,0 +1 @@
1
+ export { googleProvider } from './google.js';
@@ -0,0 +1,8 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import type { ResolvedAuthConfig } from '../types.js';
3
+ /**
4
+ * Handle OIDC login — redirect to provider's authorization endpoint.
5
+ * Returns true if handled, false if no OIDC provider found (caller should fall through).
6
+ */
7
+ export declare function handleOidcLogin(config: ResolvedAuthConfig, res: ServerResponse, url: URL, providerName: string | undefined): Promise<boolean>;
8
+ export declare function handleNativeLogin(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, url: URL, db?: any): Promise<boolean>;