@nuraly/lumenjs 0.1.3 → 0.2.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 (306) 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 +82 -0
  11. package/dist/auth/native-auth.js +340 -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 +121 -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/totp.d.ts +22 -0
  31. package/dist/auth/routes/totp.js +232 -0
  32. package/dist/auth/routes/utils.d.ts +7 -0
  33. package/dist/auth/routes/utils.js +35 -0
  34. package/dist/auth/routes/verify.d.ts +3 -0
  35. package/dist/auth/routes/verify.js +26 -0
  36. package/dist/auth/routes.d.ts +8 -0
  37. package/dist/auth/routes.js +124 -0
  38. package/dist/auth/session.d.ts +8 -0
  39. package/dist/auth/session.js +54 -0
  40. package/dist/auth/token.d.ts +33 -0
  41. package/dist/auth/token.js +90 -0
  42. package/dist/auth/types.d.ts +156 -0
  43. package/dist/auth/types.js +2 -0
  44. package/dist/build/build-client.d.ts +15 -0
  45. package/dist/build/build-client.js +45 -0
  46. package/dist/build/build-prerender.d.ts +11 -0
  47. package/dist/build/build-prerender.js +159 -0
  48. package/dist/build/build-server.d.ts +18 -0
  49. package/dist/build/build-server.js +107 -0
  50. package/dist/build/build.js +60 -123
  51. package/dist/build/scan.d.ts +18 -0
  52. package/dist/build/scan.js +77 -6
  53. package/dist/build/serve-api.js +8 -2
  54. package/dist/build/serve-loaders.d.ts +4 -4
  55. package/dist/build/serve-loaders.js +26 -18
  56. package/dist/build/serve-ssr.js +38 -11
  57. package/dist/build/serve-static.js +3 -3
  58. package/dist/build/serve.js +341 -18
  59. package/dist/cli.js +37 -6
  60. package/dist/communication/encryption.d.ts +35 -0
  61. package/dist/communication/encryption.js +90 -0
  62. package/dist/communication/handlers/context.d.ts +27 -0
  63. package/dist/communication/handlers/context.js +1 -0
  64. package/dist/communication/handlers/conversation.d.ts +24 -0
  65. package/dist/communication/handlers/conversation.js +113 -0
  66. package/dist/communication/handlers/file-upload.d.ts +17 -0
  67. package/dist/communication/handlers/file-upload.js +62 -0
  68. package/dist/communication/handlers/messaging.d.ts +30 -0
  69. package/dist/communication/handlers/messaging.js +237 -0
  70. package/dist/communication/handlers/presence.d.ts +15 -0
  71. package/dist/communication/handlers/presence.js +76 -0
  72. package/dist/communication/handlers.d.ts +5 -0
  73. package/dist/communication/handlers.js +5 -0
  74. package/dist/communication/index.d.ts +9 -0
  75. package/dist/communication/index.js +7 -0
  76. package/dist/communication/link-preview.d.ts +18 -0
  77. package/dist/communication/link-preview.js +115 -0
  78. package/dist/communication/schema.d.ts +10 -0
  79. package/dist/communication/schema.js +101 -0
  80. package/dist/communication/server.d.ts +86 -0
  81. package/dist/communication/server.js +212 -0
  82. package/dist/communication/signaling.d.ts +43 -0
  83. package/dist/communication/signaling.js +271 -0
  84. package/dist/communication/store.d.ts +71 -0
  85. package/dist/communication/store.js +289 -0
  86. package/dist/communication/types.d.ts +454 -0
  87. package/dist/communication/types.js +1 -0
  88. package/dist/create.d.ts +1 -0
  89. package/dist/create.js +55 -0
  90. package/dist/db/auto-migrate.d.ts +3 -0
  91. package/dist/db/auto-migrate.js +100 -0
  92. package/dist/db/client.d.ts +3 -0
  93. package/dist/db/client.js +18 -0
  94. package/dist/db/index.d.ts +17 -13
  95. package/dist/db/index.js +205 -26
  96. package/dist/db/seed.d.ts +12 -0
  97. package/dist/db/seed.js +88 -0
  98. package/dist/db/table.d.ts +10 -0
  99. package/dist/db/table.js +12 -0
  100. package/dist/dev-server/config.d.ts +11 -0
  101. package/dist/dev-server/config.js +40 -20
  102. package/dist/dev-server/index-html.d.ts +4 -0
  103. package/dist/dev-server/index-html.js +21 -6
  104. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  105. package/dist/dev-server/nuralyui-aliases.js +115 -94
  106. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  107. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  108. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  109. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  110. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  111. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  113. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  114. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  115. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  116. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  117. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  118. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  119. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  120. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  121. package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
  122. package/dist/dev-server/plugins/vite-plugin-routes.js +16 -5
  123. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  125. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  126. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  127. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  128. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  129. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +140 -3
  130. package/dist/dev-server/server.js +242 -70
  131. package/dist/dev-server/ssr-render.d.ts +2 -1
  132. package/dist/dev-server/ssr-render.js +117 -50
  133. package/dist/editor/ai/backend.d.ts +20 -0
  134. package/dist/editor/ai/backend.js +113 -0
  135. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  136. package/dist/editor/ai/claude-code-client.js +145 -0
  137. package/dist/editor/ai/deepseek-client.d.ts +7 -0
  138. package/dist/editor/ai/deepseek-client.js +113 -0
  139. package/dist/editor/ai/opencode-client.d.ts +14 -0
  140. package/dist/editor/ai/opencode-client.js +99 -0
  141. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  142. package/dist/editor/ai/snapshot-store.js +35 -0
  143. package/dist/editor/ai/types.d.ts +30 -0
  144. package/dist/editor/ai/types.js +136 -0
  145. package/dist/editor/ai-chat-panel.d.ts +13 -0
  146. package/dist/editor/ai-chat-panel.js +613 -0
  147. package/dist/editor/ai-markdown.d.ts +10 -0
  148. package/dist/editor/ai-markdown.js +70 -0
  149. package/dist/editor/ai-project-panel.d.ts +11 -0
  150. package/dist/editor/ai-project-panel.js +332 -0
  151. package/dist/editor/ast-modification.d.ts +11 -0
  152. package/dist/editor/ast-modification.js +1 -0
  153. package/dist/editor/ast-service.d.ts +30 -0
  154. package/dist/editor/ast-service.js +180 -0
  155. package/dist/editor/css-rules.d.ts +54 -0
  156. package/dist/editor/css-rules.js +423 -0
  157. package/dist/editor/editor-api-client.d.ts +51 -0
  158. package/dist/editor/editor-api-client.js +162 -0
  159. package/dist/editor/editor-bridge.d.ts +1 -0
  160. package/dist/editor/editor-bridge.js +18 -8
  161. package/dist/editor/editor-toolbar.d.ts +14 -0
  162. package/dist/editor/editor-toolbar.js +115 -0
  163. package/dist/editor/file-editor.d.ts +9 -0
  164. package/dist/editor/file-editor.js +236 -0
  165. package/dist/editor/file-service.d.ts +16 -0
  166. package/dist/editor/file-service.js +52 -0
  167. package/dist/editor/i18n-key-gen.d.ts +1 -0
  168. package/dist/editor/i18n-key-gen.js +7 -0
  169. package/dist/editor/inline-text-edit.d.ts +5 -0
  170. package/dist/editor/inline-text-edit.js +173 -92
  171. package/dist/editor/overlay-events.d.ts +5 -0
  172. package/dist/editor/overlay-events.js +364 -0
  173. package/dist/editor/overlay-hmr.d.ts +2 -0
  174. package/dist/editor/overlay-hmr.js +76 -0
  175. package/dist/editor/overlay-selection.d.ts +29 -0
  176. package/dist/editor/overlay-selection.js +148 -0
  177. package/dist/editor/overlay-utils.d.ts +12 -0
  178. package/dist/editor/overlay-utils.js +59 -0
  179. package/dist/editor/properties-panel-persist.d.ts +14 -0
  180. package/dist/editor/properties-panel-persist.js +70 -0
  181. package/dist/editor/properties-panel-rows.d.ts +10 -0
  182. package/dist/editor/properties-panel-rows.js +349 -0
  183. package/dist/editor/properties-panel-styles.d.ts +4 -0
  184. package/dist/editor/properties-panel-styles.js +174 -0
  185. package/dist/editor/properties-panel.d.ts +4 -0
  186. package/dist/editor/properties-panel.js +148 -0
  187. package/dist/editor/property-registry.d.ts +16 -0
  188. package/dist/editor/property-registry.js +303 -0
  189. package/dist/editor/standalone-file-panel.d.ts +0 -0
  190. package/dist/editor/standalone-file-panel.js +1 -0
  191. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  192. package/dist/editor/standalone-overlay-dom.js +1 -0
  193. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  194. package/dist/editor/standalone-overlay-styles.js +1 -0
  195. package/dist/editor/standalone-overlay.d.ts +1 -0
  196. package/dist/editor/standalone-overlay.js +76 -0
  197. package/dist/editor/syntax-highlighter.d.ts +4 -0
  198. package/dist/editor/syntax-highlighter.js +81 -0
  199. package/dist/editor/text-toolbar.d.ts +11 -0
  200. package/dist/editor/text-toolbar.js +327 -0
  201. package/dist/editor/toolbar-styles.d.ts +4 -0
  202. package/dist/editor/toolbar-styles.js +198 -0
  203. package/dist/email/index.d.ts +32 -0
  204. package/dist/email/index.js +154 -0
  205. package/dist/email/providers/resend.d.ts +2 -0
  206. package/dist/email/providers/resend.js +24 -0
  207. package/dist/email/providers/sendgrid.d.ts +2 -0
  208. package/dist/email/providers/sendgrid.js +31 -0
  209. package/dist/email/providers/smtp.d.ts +13 -0
  210. package/dist/email/providers/smtp.js +125 -0
  211. package/dist/email/template-engine.d.ts +18 -0
  212. package/dist/email/template-engine.js +116 -0
  213. package/dist/email/templates/base.d.ts +9 -0
  214. package/dist/email/templates/base.js +65 -0
  215. package/dist/email/templates/password-reset.d.ts +5 -0
  216. package/dist/email/templates/password-reset.js +15 -0
  217. package/dist/email/templates/verify-email.d.ts +5 -0
  218. package/dist/email/templates/verify-email.js +15 -0
  219. package/dist/email/templates/welcome.d.ts +5 -0
  220. package/dist/email/templates/welcome.js +13 -0
  221. package/dist/email/types.d.ts +49 -0
  222. package/dist/email/types.js +1 -0
  223. package/dist/llms/generate.d.ts +46 -0
  224. package/dist/llms/generate.js +185 -0
  225. package/dist/permissions/guard.d.ts +28 -0
  226. package/dist/permissions/guard.js +30 -0
  227. package/dist/permissions/index.d.ts +6 -0
  228. package/dist/permissions/index.js +3 -0
  229. package/dist/permissions/service.d.ts +80 -0
  230. package/dist/permissions/service.js +210 -0
  231. package/dist/permissions/tables.d.ts +5 -0
  232. package/dist/permissions/tables.js +68 -0
  233. package/dist/permissions/types.d.ts +33 -0
  234. package/dist/permissions/types.js +1 -0
  235. package/dist/runtime/app-shell.d.ts +1 -1
  236. package/dist/runtime/app-shell.js +164 -0
  237. package/dist/runtime/auth.d.ts +10 -0
  238. package/dist/runtime/auth.js +30 -0
  239. package/dist/runtime/communication.d.ts +137 -0
  240. package/dist/runtime/communication.js +228 -0
  241. package/dist/runtime/error-boundary.d.ts +23 -0
  242. package/dist/runtime/error-boundary.js +120 -0
  243. package/dist/runtime/i18n.d.ts +6 -1
  244. package/dist/runtime/i18n.js +42 -21
  245. package/dist/runtime/island.d.ts +16 -0
  246. package/dist/runtime/island.js +80 -0
  247. package/dist/runtime/router-data.d.ts +3 -0
  248. package/dist/runtime/router-data.js +102 -17
  249. package/dist/runtime/router-hydration.js +34 -2
  250. package/dist/runtime/router.d.ts +19 -2
  251. package/dist/runtime/router.js +237 -43
  252. package/dist/runtime/socket-client.d.ts +2 -0
  253. package/dist/runtime/socket-client.js +30 -0
  254. package/dist/runtime/webrtc.d.ts +91 -0
  255. package/dist/runtime/webrtc.js +428 -0
  256. package/dist/shared/dom-shims.js +4 -2
  257. package/dist/shared/graceful-shutdown.d.ts +8 -0
  258. package/dist/shared/graceful-shutdown.js +36 -0
  259. package/dist/shared/health.d.ts +8 -0
  260. package/dist/shared/health.js +25 -0
  261. package/dist/shared/llms-txt.d.ts +31 -0
  262. package/dist/shared/llms-txt.js +85 -0
  263. package/dist/shared/logger.d.ts +32 -0
  264. package/dist/shared/logger.js +93 -0
  265. package/dist/shared/meta.d.ts +27 -0
  266. package/dist/shared/meta.js +71 -0
  267. package/dist/shared/middleware-runner.d.ts +9 -0
  268. package/dist/shared/middleware-runner.js +29 -0
  269. package/dist/shared/rate-limit.d.ts +18 -0
  270. package/dist/shared/rate-limit.js +71 -0
  271. package/dist/shared/request-id.d.ts +5 -0
  272. package/dist/shared/request-id.js +18 -0
  273. package/dist/shared/route-matching.js +16 -1
  274. package/dist/shared/security-headers.d.ts +18 -0
  275. package/dist/shared/security-headers.js +38 -0
  276. package/dist/shared/socket-io-setup.d.ts +11 -0
  277. package/dist/shared/socket-io-setup.js +51 -0
  278. package/dist/shared/types.d.ts +15 -0
  279. package/dist/shared/utils.d.ts +33 -7
  280. package/dist/shared/utils.js +164 -27
  281. package/dist/storage/adapters/local.d.ts +44 -0
  282. package/dist/storage/adapters/local.js +85 -0
  283. package/dist/storage/adapters/s3.d.ts +32 -0
  284. package/dist/storage/adapters/s3.js +119 -0
  285. package/dist/storage/adapters/types.d.ts +53 -0
  286. package/dist/storage/adapters/types.js +1 -0
  287. package/dist/storage/index.d.ts +76 -0
  288. package/dist/storage/index.js +83 -0
  289. package/package.json +45 -7
  290. package/templates/blog/api/posts.ts +4 -18
  291. package/templates/blog/data/migrations/001_init.sql +6 -5
  292. package/templates/blog/lumenjs.config.ts +3 -0
  293. package/templates/blog/package.json +14 -0
  294. package/templates/blog/pages/_layout.ts +25 -0
  295. package/templates/blog/pages/index.ts +48 -22
  296. package/templates/blog/pages/posts/[slug].ts +45 -20
  297. package/templates/blog/pages/tag/[tag].ts +44 -0
  298. package/templates/dashboard/api/stats.ts +8 -5
  299. package/templates/dashboard/lumenjs.config.ts +3 -0
  300. package/templates/dashboard/package.json +14 -0
  301. package/templates/dashboard/pages/_layout.ts +25 -0
  302. package/templates/dashboard/pages/index.ts +54 -23
  303. package/templates/dashboard/pages/settings/index.ts +29 -0
  304. package/templates/default/lumenjs.config.ts +3 -0
  305. package/templates/default/package.json +14 -0
  306. package/templates/default/pages/index.ts +24 -0
@@ -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>;
@@ -0,0 +1,121 @@
1
+ import crypto from 'node:crypto';
2
+ import { getOidcProvider, hasNativeAuth, getNativeProvider } from '../config.js';
3
+ import { discoverProvider, buildAuthorizationUrl, generateCodeVerifier, } from '../oidc-client.js';
4
+ import { encryptSession, createSessionCookie, } from '../session.js';
5
+ import { sendJson, readBody, isTokenMode, safeReturnTo } from './utils.js';
6
+ /**
7
+ * Handle OIDC login — redirect to provider's authorization endpoint.
8
+ * Returns true if handled, false if no OIDC provider found (caller should fall through).
9
+ */
10
+ export async function handleOidcLogin(config, res, url, providerName) {
11
+ const oidc = providerName
12
+ ? config.providers.find(p => p.type === 'oidc' && p.name === providerName)
13
+ : getOidcProvider(config);
14
+ if (!oidc) {
15
+ // No OIDC provider — return available methods as JSON
16
+ sendJson(res, 200, {
17
+ providers: config.providers.map(p => ({ type: p.type, name: p.name })),
18
+ nativeLogin: `${config.routes.login} (POST)`,
19
+ signup: hasNativeAuth(config) ? config.routes.signup : undefined,
20
+ });
21
+ return true;
22
+ }
23
+ const metadata = await discoverProvider(oidc.issuer);
24
+ const state = crypto.randomBytes(16).toString('hex');
25
+ const codeVerifier = generateCodeVerifier();
26
+ const returnTo = safeReturnTo(url.searchParams.get('returnTo'), config.routes.postLogin);
27
+ const redirectUri = `${url.origin}${config.routes.callback}`;
28
+ // Store PKCE state in short-lived encrypted cookie
29
+ const stateData = JSON.stringify({ state, codeVerifier, returnTo, provider: oidc.name });
30
+ const encrypted = await encryptSession({ accessToken: stateData, expiresAt: Math.floor(Date.now() / 1000) + 600, user: { sub: '', roles: [] } }, config.session.secret);
31
+ const stateCookie = createSessionCookie('nk-auth-state', encrypted, 600, config.session.secure);
32
+ const authUrl = buildAuthorizationUrl(metadata, oidc.clientId, redirectUri, oidc.scopes || ['openid', 'profile', 'email'], state, codeVerifier);
33
+ res.writeHead(302, { Location: authUrl, 'Set-Cookie': stateCookie });
34
+ res.end();
35
+ return true;
36
+ }
37
+ export async function handleNativeLogin(config, req, res, url, db) {
38
+ const nativeProvider = getNativeProvider(config);
39
+ if (!nativeProvider || !db) {
40
+ sendJson(res, 400, { error: 'Native auth not configured' });
41
+ return true;
42
+ }
43
+ let body;
44
+ try {
45
+ body = JSON.parse(await readBody(req));
46
+ }
47
+ catch {
48
+ sendJson(res, 400, { error: 'Invalid JSON body' });
49
+ return true;
50
+ }
51
+ const { email, password } = body;
52
+ if (!email || !password) {
53
+ sendJson(res, 400, { error: 'Email and password required' });
54
+ return true;
55
+ }
56
+ const { authenticateUser, isEmailVerified, getTotpState } = await import('../native-auth.js');
57
+ const user = await authenticateUser(db, email, password);
58
+ if (!user) {
59
+ sendJson(res, 401, { error: 'Invalid credentials' });
60
+ return true;
61
+ }
62
+ // Check email verification if required
63
+ if (nativeProvider.requireEmailVerification && !await isEmailVerified(db, user.sub)) {
64
+ sendJson(res, 403, { error: 'Please verify your email before signing in', code: 'EMAIL_NOT_VERIFIED' });
65
+ return true;
66
+ }
67
+ // Check TOTP — if enabled, issue a short-lived pending cookie instead of a full session
68
+ const totpState = await getTotpState(db, user.sub);
69
+ if (totpState.totpEnabled) {
70
+ const pendingData = {
71
+ accessToken: `totp-pending:${user.sub}`,
72
+ expiresAt: Math.floor(Date.now() / 1000) + 300,
73
+ user: { sub: user.sub, roles: [] },
74
+ provider: 'native',
75
+ createdAt: Math.floor(Date.now() / 1000),
76
+ };
77
+ const pendingEncrypted = await encryptSession(pendingData, config.session.secret);
78
+ const pendingCookie = createSessionCookie('nk-totp-pending', pendingEncrypted, 300, config.session.secure);
79
+ const returnTo = safeReturnTo(url.searchParams.get('returnTo'), config.routes.postLogin);
80
+ if (req.headers.accept?.includes('application/json')) {
81
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': pendingCookie });
82
+ res.end(JSON.stringify({ requires2fa: true, returnTo }));
83
+ }
84
+ else {
85
+ res.writeHead(302, { Location: `/auth/totp-challenge?returnTo=${encodeURIComponent(returnTo)}`, 'Set-Cookie': pendingCookie });
86
+ res.end();
87
+ }
88
+ return true;
89
+ }
90
+ // Create session — same as OIDC callback
91
+ const sessionData = {
92
+ accessToken: `native:${user.sub}`,
93
+ expiresAt: Math.floor(Date.now() / 1000) + config.session.maxAge,
94
+ user,
95
+ provider: 'native',
96
+ createdAt: Math.floor(Date.now() / 1000),
97
+ };
98
+ const encrypted = await encryptSession(sessionData, config.session.secret);
99
+ const cookie = createSessionCookie(config.session.cookieName, encrypted, config.session.maxAge, config.session.secure);
100
+ const returnTo = safeReturnTo(url.searchParams.get('returnTo'), config.routes.postLogin);
101
+ // Token mode: return bearer tokens instead of cookie
102
+ if (isTokenMode(url, req) && config.token.enabled) {
103
+ const { issueAccessToken, generateRefreshToken, storeRefreshToken, ensureRefreshTokenTable } = await import('../token.js');
104
+ await ensureRefreshTokenTable(db);
105
+ const accessToken = issueAccessToken(user, config.session.secret, config.token.accessTokenTTL);
106
+ const refreshToken = generateRefreshToken();
107
+ await storeRefreshToken(db, refreshToken, user.sub, config.token.refreshTokenTTL);
108
+ sendJson(res, 200, { accessToken, refreshToken, expiresIn: config.token.accessTokenTTL, tokenType: 'Bearer', user });
109
+ return true;
110
+ }
111
+ // Cookie mode: set session cookie
112
+ if (req.headers.accept?.includes('application/json')) {
113
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': cookie });
114
+ res.end(JSON.stringify({ user, returnTo }));
115
+ }
116
+ else {
117
+ res.writeHead(302, { Location: returnTo, 'Set-Cookie': cookie });
118
+ res.end();
119
+ }
120
+ return true;
121
+ }
@@ -0,0 +1,4 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import type { ResolvedAuthConfig } from '../types.js';
3
+ export declare function handleLogout(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, url: URL, db?: any): Promise<boolean>;
4
+ export declare function handleLogoutAll(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;
@@ -0,0 +1,79 @@
1
+ import { discoverProvider } from '../oidc-client.js';
2
+ import { clearSessionCookie, parseSessionCookie, decryptSession, } from '../session.js';
3
+ import { sendJson } from './utils.js';
4
+ export async function handleLogout(config, req, res, url, db) {
5
+ const clearCookie = clearSessionCookie(config.session.cookieName);
6
+ // Check if this was an OIDC session — redirect to provider's end_session_endpoint
7
+ let idToken;
8
+ let providerName;
9
+ const cookieHeader = req.headers.cookie || '';
10
+ const sessionCookie = parseSessionCookie(cookieHeader, config.session.cookieName);
11
+ if (sessionCookie) {
12
+ const session = await decryptSession(sessionCookie, config.session.secret);
13
+ idToken = session?.idToken || undefined;
14
+ providerName = session?.provider;
15
+ // Invalidate refresh tokens for this user if DB is available
16
+ if (db && session?.user?.sub) {
17
+ try {
18
+ const { deleteAllRefreshTokens, ensureRefreshTokenTable } = await import('../token.js');
19
+ ensureRefreshTokenTable(db);
20
+ deleteAllRefreshTokens(db, session.user.sub);
21
+ }
22
+ catch { }
23
+ }
24
+ }
25
+ let redirectUrl = config.routes.postLogout;
26
+ // If OIDC session, try provider logout
27
+ if (providerName && idToken) {
28
+ const oidc = config.providers.find(p => p.type === 'oidc' && p.name === providerName);
29
+ if (oidc) {
30
+ try {
31
+ const metadata = await discoverProvider(oidc.issuer);
32
+ if (metadata.end_session_endpoint) {
33
+ const params = new URLSearchParams({
34
+ id_token_hint: idToken,
35
+ post_logout_redirect_uri: `${url.origin}${config.routes.postLogout}`,
36
+ });
37
+ redirectUrl = `${metadata.end_session_endpoint}?${params}`;
38
+ }
39
+ }
40
+ catch { }
41
+ }
42
+ }
43
+ res.writeHead(302, { Location: redirectUrl, 'Set-Cookie': clearCookie });
44
+ res.end();
45
+ return true;
46
+ }
47
+ export async function handleLogoutAll(config, req, res, db) {
48
+ const user = req.nkAuth?.user;
49
+ if (!user) {
50
+ sendJson(res, 401, { error: 'Authentication required' });
51
+ return true;
52
+ }
53
+ if (!db) {
54
+ sendJson(res, 400, { error: 'Native auth not configured' });
55
+ return true;
56
+ }
57
+ // Set sessions_revoked_at — any session created before this timestamp is invalid
58
+ const { revokeAllSessions, ensureUsersTable } = await import('../native-auth.js');
59
+ ensureUsersTable(db);
60
+ revokeAllSessions(db, user.sub);
61
+ // Delete all refresh tokens (mobile/API sessions)
62
+ try {
63
+ const { deleteAllRefreshTokens, ensureRefreshTokenTable } = await import('../token.js');
64
+ ensureRefreshTokenTable(db);
65
+ deleteAllRefreshTokens(db, user.sub);
66
+ }
67
+ catch { }
68
+ // Clear current session cookie
69
+ const clearCookie = clearSessionCookie(config.session.cookieName);
70
+ if (req.headers.accept?.includes('application/json')) {
71
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': clearCookie });
72
+ res.end(JSON.stringify({ ok: true, message: 'All sessions have been signed out' }));
73
+ }
74
+ else {
75
+ res.writeHead(302, { Location: config.routes.postLogout, 'Set-Cookie': clearCookie });
76
+ res.end();
77
+ }
78
+ return true;
79
+ }
@@ -0,0 +1,3 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import type { ResolvedAuthConfig } from '../types.js';
3
+ export declare function handleOidcCallback(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, url: URL, db?: any): Promise<boolean>;
@@ -0,0 +1,70 @@
1
+ import { getOidcProvider, hasNativeAuth } from '../config.js';
2
+ import { discoverProvider, exchangeCode, extractUser, validateIdTokenClaims, decodeJwtPayload, } from '../oidc-client.js';
3
+ import { encryptSession, createSessionCookie, clearSessionCookie, parseSessionCookie, decryptSession, } from '../session.js';
4
+ import { sendJson, safeReturnTo } from './utils.js';
5
+ export async function handleOidcCallback(config, req, res, url, db) {
6
+ const code = url.searchParams.get('code');
7
+ const stateParam = url.searchParams.get('state');
8
+ if (!code || !stateParam) {
9
+ sendJson(res, 400, { error: 'Missing code or state' });
10
+ return true;
11
+ }
12
+ const cookieHeader = req.headers.cookie || '';
13
+ const stateCookie = parseSessionCookie(cookieHeader, 'nk-auth-state');
14
+ if (!stateCookie) {
15
+ sendJson(res, 400, { error: 'Missing state cookie' });
16
+ return true;
17
+ }
18
+ const stateSession = await decryptSession(stateCookie, config.session.secret);
19
+ if (!stateSession) {
20
+ sendJson(res, 400, { error: 'Invalid state cookie' });
21
+ return true;
22
+ }
23
+ const { state, codeVerifier, returnTo, provider: providerName } = JSON.parse(stateSession.accessToken);
24
+ if (state !== stateParam) {
25
+ sendJson(res, 400, { error: 'State mismatch' });
26
+ return true;
27
+ }
28
+ // Find the OIDC provider that initiated this flow
29
+ const oidc = (providerName
30
+ ? config.providers.find(p => p.type === 'oidc' && p.name === providerName)
31
+ : getOidcProvider(config));
32
+ if (!oidc) {
33
+ sendJson(res, 400, { error: 'Unknown OIDC provider' });
34
+ return true;
35
+ }
36
+ const metadata = await discoverProvider(oidc.issuer);
37
+ const redirectUri = `${url.origin}${config.routes.callback}`;
38
+ const tokens = await exchangeCode(metadata, oidc.clientId, oidc.clientSecret, code, redirectUri, codeVerifier);
39
+ // Validate ID token claims (iss, aud, exp) before trusting
40
+ if (tokens.id_token) {
41
+ const claims = decodeJwtPayload(tokens.id_token);
42
+ validateIdTokenClaims(claims, oidc.issuer, oidc.clientId);
43
+ }
44
+ let user = extractUser(tokens.id_token || tokens.access_token);
45
+ user.provider = oidc.name;
46
+ // Account linking: if native auth is also configured, link by email
47
+ if (db && hasNativeAuth(config) && user.email) {
48
+ try {
49
+ const { linkOidcUser, ensureUsersTable } = await import('../native-auth.js');
50
+ await ensureUsersTable(db);
51
+ user = await linkOidcUser(db, user);
52
+ }
53
+ catch { }
54
+ }
55
+ const sessionData = {
56
+ accessToken: tokens.access_token,
57
+ refreshToken: tokens.refresh_token,
58
+ idToken: tokens.id_token,
59
+ expiresAt: Math.floor(Date.now() / 1000) + tokens.expires_in,
60
+ user,
61
+ provider: oidc.name,
62
+ createdAt: Math.floor(Date.now() / 1000),
63
+ };
64
+ const encrypted = await encryptSession(sessionData, config.session.secret);
65
+ const sessionCookie = createSessionCookie(config.session.cookieName, encrypted, config.session.maxAge, config.session.secure);
66
+ const clearState = clearSessionCookie('nk-auth-state');
67
+ res.writeHead(302, { Location: safeReturnTo(returnTo, '/'), 'Set-Cookie': [sessionCookie, clearState] });
68
+ res.end();
69
+ return true;
70
+ }
@@ -0,0 +1,5 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import type { ResolvedAuthConfig } from '../types.js';
3
+ export declare function handleForgotPassword(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;
4
+ export declare function handleResetPassword(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;
5
+ export declare function handleChangePassword(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;
@@ -0,0 +1,149 @@
1
+ import { getNativeProvider } from '../config.js';
2
+ import { sendJson, readBody } from './utils.js';
3
+ export async function handleForgotPassword(config, req, res, db) {
4
+ if (!db) {
5
+ sendJson(res, 400, { error: 'Native auth not configured' });
6
+ return true;
7
+ }
8
+ let body;
9
+ try {
10
+ body = JSON.parse(await readBody(req));
11
+ }
12
+ catch {
13
+ sendJson(res, 400, { error: 'Invalid JSON body' });
14
+ return true;
15
+ }
16
+ const { email } = body;
17
+ if (!email) {
18
+ sendJson(res, 400, { error: 'Email required' });
19
+ return true;
20
+ }
21
+ const { findUserIdByEmail, generateResetToken } = await import('../native-auth.js');
22
+ const userId = await findUserIdByEmail(db, email);
23
+ // Always return success (don't reveal if email exists)
24
+ if (userId && config.onEvent) {
25
+ const userRow = await db.get('SELECT password_hash FROM _nk_auth_users WHERE id = ?', userId);
26
+ const token = generateResetToken(userId, config.session.secret, 3600, userRow?.password_hash);
27
+ const origin = `${req.headers['x-forwarded-proto'] || 'http'}://${req.headers.host}`;
28
+ const resetUrl = `${origin}/__nk_auth/reset-password?token=${encodeURIComponent(token)}`;
29
+ try {
30
+ await config.onEvent({ type: 'password-reset', email, token, url: resetUrl });
31
+ }
32
+ catch { }
33
+ }
34
+ sendJson(res, 200, { message: 'If an account with that email exists, a password reset link has been sent.' });
35
+ return true;
36
+ }
37
+ export async function handleResetPassword(config, req, res, db) {
38
+ if (!db) {
39
+ sendJson(res, 400, { error: 'Native auth not configured' });
40
+ return true;
41
+ }
42
+ let body;
43
+ try {
44
+ body = JSON.parse(await readBody(req));
45
+ }
46
+ catch {
47
+ sendJson(res, 400, { error: 'Invalid JSON body' });
48
+ return true;
49
+ }
50
+ const { token, password } = body;
51
+ if (!token || !password) {
52
+ sendJson(res, 400, { error: 'Token and password required' });
53
+ return true;
54
+ }
55
+ const { verifyResetToken, updatePassword, decodeResetTokenUserId } = await import('../native-auth.js');
56
+ // First, decode the userId from the token payload (without full verification)
57
+ // so we can look up the current password hash for single-use validation
58
+ const candidateUserId = decodeResetTokenUserId(token);
59
+ let currentHash;
60
+ if (candidateUserId) {
61
+ const userRow = await db.get('SELECT password_hash FROM _nk_auth_users WHERE id = ?', candidateUserId);
62
+ currentHash = userRow?.password_hash;
63
+ }
64
+ const userId = verifyResetToken(token, config.session.secret, currentHash);
65
+ if (!userId) {
66
+ sendJson(res, 400, { error: 'Invalid or expired reset link' });
67
+ return true;
68
+ }
69
+ const nativeProvider = getNativeProvider(config);
70
+ const minLength = nativeProvider?.minPasswordLength ?? 8;
71
+ try {
72
+ await updatePassword(db, userId, password, minLength);
73
+ }
74
+ catch (err) {
75
+ sendJson(res, 400, { error: err.message });
76
+ return true;
77
+ }
78
+ // Notify via event hook
79
+ const row = await db.get('SELECT email FROM _nk_auth_users WHERE id = ?', userId);
80
+ if (row && config.onEvent) {
81
+ try {
82
+ await config.onEvent({ type: 'password-changed', email: row.email, userId });
83
+ }
84
+ catch { }
85
+ }
86
+ sendJson(res, 200, { message: 'Password has been reset. You can now sign in.' });
87
+ return true;
88
+ }
89
+ export async function handleChangePassword(config, req, res, db) {
90
+ const user = req.nkAuth?.user;
91
+ if (!user) {
92
+ sendJson(res, 401, { error: 'Authentication required' });
93
+ return true;
94
+ }
95
+ if (!db) {
96
+ sendJson(res, 400, { error: 'Native auth not configured' });
97
+ return true;
98
+ }
99
+ let body;
100
+ try {
101
+ body = JSON.parse(await readBody(req));
102
+ }
103
+ catch {
104
+ sendJson(res, 400, { error: 'Invalid JSON body' });
105
+ return true;
106
+ }
107
+ const { currentPassword, newPassword } = body;
108
+ if (!currentPassword || !newPassword) {
109
+ sendJson(res, 400, { error: 'Current password and new password required' });
110
+ return true;
111
+ }
112
+ const { verifyPassword, updatePassword } = await import('../native-auth.js');
113
+ const row = await db.get('SELECT password_hash FROM _nk_auth_users WHERE id = ?', user.sub);
114
+ if (!row) {
115
+ sendJson(res, 404, { error: 'User not found' });
116
+ return true;
117
+ }
118
+ const valid = await verifyPassword(currentPassword, row.password_hash);
119
+ if (!valid) {
120
+ sendJson(res, 401, { error: 'Current password is incorrect' });
121
+ return true;
122
+ }
123
+ const nativeProvider = getNativeProvider(config);
124
+ const minLength = nativeProvider?.minPasswordLength ?? 8;
125
+ try {
126
+ await updatePassword(db, user.sub, newPassword, minLength);
127
+ }
128
+ catch (err) {
129
+ sendJson(res, 400, { error: err.message });
130
+ return true;
131
+ }
132
+ // Invalidate all other sessions and refresh tokens after password change
133
+ try {
134
+ const { revokeAllSessions } = await import('../native-auth.js');
135
+ await revokeAllSessions(db, user.sub);
136
+ const { deleteAllRefreshTokens, ensureRefreshTokenTable } = await import('../token.js');
137
+ await ensureRefreshTokenTable(db);
138
+ await deleteAllRefreshTokens(db, user.sub);
139
+ }
140
+ catch { }
141
+ if (config.onEvent) {
142
+ try {
143
+ await config.onEvent({ type: 'password-changed', email: user.email, userId: user.sub });
144
+ }
145
+ catch { }
146
+ }
147
+ sendJson(res, 200, { message: 'Password updated successfully' });
148
+ return true;
149
+ }
@@ -0,0 +1,3 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import type { ResolvedAuthConfig } from '../types.js';
3
+ export declare function handleNativeSignup(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;
@@ -0,0 +1,81 @@
1
+ import { getNativeProvider } from '../config.js';
2
+ import { encryptSession, createSessionCookie, } from '../session.js';
3
+ import { sendJson, readBody, isTokenMode } from './utils.js';
4
+ export async function handleNativeSignup(config, req, res, db) {
5
+ const nativeProvider = getNativeProvider(config);
6
+ if (!nativeProvider || !db) {
7
+ sendJson(res, 400, { error: 'Native auth not configured' });
8
+ return true;
9
+ }
10
+ if (nativeProvider.allowRegistration === false) {
11
+ sendJson(res, 403, { error: 'Registration is disabled' });
12
+ return true;
13
+ }
14
+ let body;
15
+ try {
16
+ body = JSON.parse(await readBody(req));
17
+ }
18
+ catch {
19
+ sendJson(res, 400, { error: 'Invalid JSON body' });
20
+ return true;
21
+ }
22
+ const { email, password, name } = body;
23
+ if (!email || !password) {
24
+ sendJson(res, 400, { error: 'Email and password required' });
25
+ return true;
26
+ }
27
+ try {
28
+ const { registerUser, ensureUsersTable, generateVerificationToken } = await import('../native-auth.js');
29
+ await ensureUsersTable(db);
30
+ const user = await registerUser(db, email, password, name, nativeProvider);
31
+ // Send verification email if required
32
+ if (nativeProvider.requireEmailVerification && config.onEvent) {
33
+ const token = generateVerificationToken(user.sub, config.session.secret);
34
+ const origin = `${req.headers['x-forwarded-proto'] || 'http'}://${req.headers.host}`;
35
+ const verifyUrl = `${origin}/__nk_auth/verify-email?token=${encodeURIComponent(token)}`;
36
+ try {
37
+ await config.onEvent({ type: 'verification-email', email, token, url: verifyUrl });
38
+ }
39
+ catch { }
40
+ }
41
+ // If email verification required, don't auto-login
42
+ if (nativeProvider.requireEmailVerification) {
43
+ sendJson(res, 201, { user, message: 'Account created. Please check your email to verify.' });
44
+ return true;
45
+ }
46
+ // Auto-login after signup
47
+ const sessionData = {
48
+ accessToken: `native:${user.sub}`,
49
+ expiresAt: Math.floor(Date.now() / 1000) + config.session.maxAge,
50
+ user,
51
+ provider: 'native',
52
+ createdAt: Math.floor(Date.now() / 1000),
53
+ };
54
+ const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
55
+ // Token mode: return bearer tokens
56
+ if (isTokenMode(url, req) && config.token.enabled) {
57
+ const { issueAccessToken, generateRefreshToken, storeRefreshToken, ensureRefreshTokenTable } = await import('../token.js');
58
+ await ensureRefreshTokenTable(db);
59
+ const accessToken = issueAccessToken(user, config.session.secret, config.token.accessTokenTTL);
60
+ const refreshToken = generateRefreshToken();
61
+ await storeRefreshToken(db, refreshToken, user.sub, config.token.refreshTokenTTL);
62
+ sendJson(res, 201, { accessToken, refreshToken, expiresIn: config.token.accessTokenTTL, tokenType: 'Bearer', user });
63
+ return true;
64
+ }
65
+ // Cookie mode
66
+ const encrypted = await encryptSession(sessionData, config.session.secret);
67
+ const cookie = createSessionCookie(config.session.cookieName, encrypted, config.session.maxAge, config.session.secure);
68
+ if (req.headers.accept?.includes('application/json')) {
69
+ res.writeHead(201, { 'Content-Type': 'application/json', 'Set-Cookie': cookie });
70
+ res.end(JSON.stringify({ user }));
71
+ }
72
+ else {
73
+ res.writeHead(302, { Location: config.routes.postLogin, 'Set-Cookie': cookie });
74
+ res.end();
75
+ }
76
+ }
77
+ catch (err) {
78
+ sendJson(res, 400, { error: err.message });
79
+ }
80
+ return true;
81
+ }
@@ -0,0 +1,4 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import type { ResolvedAuthConfig } from '../types.js';
3
+ export declare function handleTokenRefresh(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;
4
+ export declare function handleTokenRevoke(req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;
@@ -0,0 +1,70 @@
1
+ import { sendJson, readBody } from './utils.js';
2
+ export async function handleTokenRefresh(config, req, res, db) {
3
+ if (!config.token.enabled || !db) {
4
+ sendJson(res, 400, { error: 'Token auth not configured' });
5
+ return true;
6
+ }
7
+ let body;
8
+ try {
9
+ body = JSON.parse(await readBody(req));
10
+ }
11
+ catch {
12
+ sendJson(res, 400, { error: 'Invalid JSON body' });
13
+ return true;
14
+ }
15
+ const { refreshToken } = body;
16
+ if (!refreshToken) {
17
+ sendJson(res, 400, { error: 'refreshToken required' });
18
+ return true;
19
+ }
20
+ const { validateRefreshToken, deleteRefreshToken, storeRefreshToken, generateRefreshToken, issueAccessToken, ensureRefreshTokenTable } = await import('../token.js');
21
+ await ensureRefreshTokenTable(db);
22
+ const userId = await validateRefreshToken(db, refreshToken);
23
+ if (!userId) {
24
+ sendJson(res, 401, { error: 'Invalid or expired refresh token' });
25
+ return true;
26
+ }
27
+ // Rotate: delete old, issue new
28
+ await deleteRefreshToken(db, refreshToken);
29
+ // Look up user from DB
30
+ const { findUserByEmail } = await import('../native-auth.js');
31
+ const row = await db.get('SELECT * FROM _nk_auth_users WHERE id = ?', userId);
32
+ if (!row) {
33
+ sendJson(res, 401, { error: 'User not found' });
34
+ return true;
35
+ }
36
+ let roles = [];
37
+ try {
38
+ roles = JSON.parse(row.roles);
39
+ }
40
+ catch { }
41
+ const user = { sub: row.id, email: row.email, name: row.name, roles, provider: 'native' };
42
+ const newAccessToken = issueAccessToken(user, config.session.secret, config.token.accessTokenTTL);
43
+ const newRefreshToken = generateRefreshToken();
44
+ await storeRefreshToken(db, newRefreshToken, userId, config.token.refreshTokenTTL);
45
+ sendJson(res, 200, { accessToken: newAccessToken, refreshToken: newRefreshToken, expiresIn: config.token.accessTokenTTL });
46
+ return true;
47
+ }
48
+ export async function handleTokenRevoke(req, res, db) {
49
+ if (!db) {
50
+ sendJson(res, 400, { error: 'Token auth not configured' });
51
+ return true;
52
+ }
53
+ let body;
54
+ try {
55
+ body = JSON.parse(await readBody(req));
56
+ }
57
+ catch {
58
+ sendJson(res, 400, { error: 'Invalid JSON body' });
59
+ return true;
60
+ }
61
+ const { refreshToken } = body;
62
+ if (!refreshToken) {
63
+ sendJson(res, 400, { error: 'refreshToken required' });
64
+ return true;
65
+ }
66
+ const { deleteRefreshToken } = await import('../token.js');
67
+ await deleteRefreshToken(db, refreshToken);
68
+ sendJson(res, 200, { ok: true });
69
+ return true;
70
+ }
@@ -0,0 +1,22 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import type { ResolvedAuthConfig } from '../types.js';
3
+ /**
4
+ * POST /__nk_auth/totp/setup
5
+ * Generates a new TOTP secret for the authenticated user and returns a QR code.
6
+ */
7
+ export declare function handleTotpSetup(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;
8
+ /**
9
+ * POST /__nk_auth/totp/verify-setup
10
+ * Confirms setup by verifying the first 6-digit code and enables TOTP.
11
+ */
12
+ export declare function handleTotpVerifySetup(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;
13
+ /**
14
+ * POST /__nk_auth/totp/disable
15
+ * Disables TOTP after verifying a valid code.
16
+ */
17
+ export declare function handleTotpDisable(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;
18
+ /**
19
+ * POST /__nk_auth/totp/challenge
20
+ * Exchanges a pending-2FA cookie + valid TOTP code for a full session cookie.
21
+ */
22
+ export declare function handleTotpChallenge(config: ResolvedAuthConfig, req: IncomingMessage, res: ServerResponse, db?: any): Promise<boolean>;