@digitalpaws/starr 0.0.1

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 (290) hide show
  1. package/README.rst +75 -0
  2. package/data/.nfs.811bbb0d.43c34.4 +0 -0
  3. package/data/.nfs.81ba4cd0.ffff87804.4 +0 -0
  4. package/data/.nfs.82d127f7.fffff5704.4 +0 -0
  5. package/data/.nfs.82d127fe.fffff5704.4 +0 -0
  6. package/data/.nfs.82d159a5.fffff5aa4.4 +0 -0
  7. package/data/.nfs.82d159aa.fffff5aa4.4 +0 -0
  8. package/data/.nfs.82d15a9c.fffff5bb4.4 +0 -0
  9. package/data/.nfs.82d15aa1.fffff5bb4.4 +0 -0
  10. package/data/.nfs.82d15c65.fffff5c24.4 +0 -0
  11. package/data/.nfs.82d1852c.fffff5e24.4 +0 -0
  12. package/data/.nfs.82d18536.fffff5e24.4 +0 -0
  13. package/data/.nfs.82d18dbe.fffff5e44.4 +0 -0
  14. package/data/.nfs.82d18dc6.fffff5e44.4 +0 -0
  15. package/data/.nfs.8309a3f5.fffff82f4.4 +0 -0
  16. package/data/.nfs.8309a3fe.fffff82f4.4 +0 -0
  17. package/data/.nfs.8314122e.fffffb044.4 +0 -0
  18. package/data/.nfs.83141236.fffffb044.4 +0 -0
  19. package/data/.nfs.833c4fe9.fffffbc34.4 +0 -0
  20. package/data/.nfs.833c4ff3.fffffbc34.4 +0 -0
  21. package/data/logs/starr.log +66244 -0
  22. package/data/starr.db +0 -0
  23. package/data/starr.db-shm +0 -0
  24. package/data/starr.db-wal +0 -0
  25. package/data/starr.jwt +1 -0
  26. package/data/starr.key +1 -0
  27. package/dist/auth/auth-service.d.ts +42 -0
  28. package/dist/auth/auth-service.d.ts.map +1 -0
  29. package/dist/auth/auth-service.js +226 -0
  30. package/dist/auth/auth-service.js.map +1 -0
  31. package/dist/auth/http-server.d.ts +16 -0
  32. package/dist/auth/http-server.d.ts.map +1 -0
  33. package/dist/auth/http-server.js +207 -0
  34. package/dist/auth/http-server.js.map +1 -0
  35. package/dist/auth/jwt-secret.d.ts +6 -0
  36. package/dist/auth/jwt-secret.d.ts.map +1 -0
  37. package/dist/auth/jwt-secret.js +51 -0
  38. package/dist/auth/jwt-secret.js.map +1 -0
  39. package/dist/auth/oidc/oidc-flow.d.ts +60 -0
  40. package/dist/auth/oidc/oidc-flow.d.ts.map +1 -0
  41. package/dist/auth/oidc/oidc-flow.js +201 -0
  42. package/dist/auth/oidc/oidc-flow.js.map +1 -0
  43. package/dist/auth/oidc/oidc-provider-service.d.ts +59 -0
  44. package/dist/auth/oidc/oidc-provider-service.d.ts.map +1 -0
  45. package/dist/auth/oidc/oidc-provider-service.js +124 -0
  46. package/dist/auth/oidc/oidc-provider-service.js.map +1 -0
  47. package/dist/auth/oidc/user-service.d.ts +67 -0
  48. package/dist/auth/oidc/user-service.d.ts.map +1 -0
  49. package/dist/auth/oidc/user-service.js +177 -0
  50. package/dist/auth/oidc/user-service.js.map +1 -0
  51. package/dist/auth/rate-limiter.d.ts +31 -0
  52. package/dist/auth/rate-limiter.d.ts.map +1 -0
  53. package/dist/auth/rate-limiter.js +70 -0
  54. package/dist/auth/rate-limiter.js.map +1 -0
  55. package/dist/config/config-service.d.ts +83 -0
  56. package/dist/config/config-service.d.ts.map +1 -0
  57. package/dist/config/config-service.js +309 -0
  58. package/dist/config/config-service.js.map +1 -0
  59. package/dist/config/validate.d.ts +12 -0
  60. package/dist/config/validate.d.ts.map +1 -0
  61. package/dist/config/validate.js +140 -0
  62. package/dist/config/validate.js.map +1 -0
  63. package/dist/content-types/registry.d.ts +75 -0
  64. package/dist/content-types/registry.d.ts.map +1 -0
  65. package/dist/content-types/registry.js +171 -0
  66. package/dist/content-types/registry.js.map +1 -0
  67. package/dist/datastore/db.d.ts +9 -0
  68. package/dist/datastore/db.d.ts.map +1 -0
  69. package/dist/datastore/db.js +27 -0
  70. package/dist/datastore/db.js.map +1 -0
  71. package/dist/datastore/encrypt.d.ts +9 -0
  72. package/dist/datastore/encrypt.d.ts.map +1 -0
  73. package/dist/datastore/encrypt.js +84 -0
  74. package/dist/datastore/encrypt.js.map +1 -0
  75. package/dist/datastore/migrations.d.ts +10 -0
  76. package/dist/datastore/migrations.d.ts.map +1 -0
  77. package/dist/datastore/migrations.js +340 -0
  78. package/dist/datastore/migrations.js.map +1 -0
  79. package/dist/datastore/storage.d.ts +4 -0
  80. package/dist/datastore/storage.d.ts.map +1 -0
  81. package/dist/datastore/storage.js +60 -0
  82. package/dist/datastore/storage.js.map +1 -0
  83. package/dist/events/event-bus.d.ts +15 -0
  84. package/dist/events/event-bus.d.ts.map +1 -0
  85. package/dist/events/event-bus.js +140 -0
  86. package/dist/events/event-bus.js.map +1 -0
  87. package/dist/events/spine.d.ts +31 -0
  88. package/dist/events/spine.d.ts.map +1 -0
  89. package/dist/events/spine.js +68 -0
  90. package/dist/events/spine.js.map +1 -0
  91. package/dist/events/trace.d.ts +24 -0
  92. package/dist/events/trace.d.ts.map +1 -0
  93. package/dist/events/trace.js +33 -0
  94. package/dist/events/trace.js.map +1 -0
  95. package/dist/host.d.ts +65 -0
  96. package/dist/host.d.ts.map +1 -0
  97. package/dist/host.js +94 -0
  98. package/dist/host.js.map +1 -0
  99. package/dist/library/importer.d.ts +29 -0
  100. package/dist/library/importer.d.ts.map +1 -0
  101. package/dist/library/importer.js +145 -0
  102. package/dist/library/importer.js.map +1 -0
  103. package/dist/library/library-cache.d.ts +46 -0
  104. package/dist/library/library-cache.d.ts.map +1 -0
  105. package/dist/library/library-cache.js +152 -0
  106. package/dist/library/library-cache.js.map +1 -0
  107. package/dist/library/library-config.d.ts +12 -0
  108. package/dist/library/library-config.d.ts.map +1 -0
  109. package/dist/library/library-config.js +10 -0
  110. package/dist/library/library-config.js.map +1 -0
  111. package/dist/library/library-scanner.d.ts +62 -0
  112. package/dist/library/library-scanner.d.ts.map +1 -0
  113. package/dist/library/library-scanner.js +336 -0
  114. package/dist/library/library-scanner.js.map +1 -0
  115. package/dist/library/library-service.d.ts +65 -0
  116. package/dist/library/library-service.d.ts.map +1 -0
  117. package/dist/library/library-service.js +197 -0
  118. package/dist/library/library-service.js.map +1 -0
  119. package/dist/library/metadata-matcher.d.ts +56 -0
  120. package/dist/library/metadata-matcher.d.ts.map +1 -0
  121. package/dist/library/metadata-matcher.js +207 -0
  122. package/dist/library/metadata-matcher.js.map +1 -0
  123. package/dist/library/naming.d.ts +8 -0
  124. package/dist/library/naming.d.ts.map +1 -0
  125. package/dist/library/naming.js +47 -0
  126. package/dist/library/naming.js.map +1 -0
  127. package/dist/library/scan-controller.d.ts +95 -0
  128. package/dist/library/scan-controller.d.ts.map +1 -0
  129. package/dist/library/scan-controller.js +379 -0
  130. package/dist/library/scan-controller.js.map +1 -0
  131. package/dist/library/source-resolver.d.ts +17 -0
  132. package/dist/library/source-resolver.d.ts.map +1 -0
  133. package/dist/library/source-resolver.js +75 -0
  134. package/dist/library/source-resolver.js.map +1 -0
  135. package/dist/logging/logger.d.ts +3 -0
  136. package/dist/logging/logger.d.ts.map +1 -0
  137. package/dist/logging/logger.js +107 -0
  138. package/dist/logging/logger.js.map +1 -0
  139. package/dist/main.d.ts +36 -0
  140. package/dist/main.d.ts.map +1 -0
  141. package/dist/main.js +229 -0
  142. package/dist/main.js.map +1 -0
  143. package/dist/plugins/context.d.ts +10 -0
  144. package/dist/plugins/context.d.ts.map +1 -0
  145. package/dist/plugins/context.js +110 -0
  146. package/dist/plugins/context.js.map +1 -0
  147. package/dist/plugins/discovery.d.ts +2 -0
  148. package/dist/plugins/discovery.d.ts.map +1 -0
  149. package/dist/plugins/discovery.js +32 -0
  150. package/dist/plugins/discovery.js.map +1 -0
  151. package/dist/plugins/loader.d.ts +9 -0
  152. package/dist/plugins/loader.d.ts.map +1 -0
  153. package/dist/plugins/loader.js +245 -0
  154. package/dist/plugins/loader.js.map +1 -0
  155. package/dist/state/request-intake.d.ts +26 -0
  156. package/dist/state/request-intake.d.ts.map +1 -0
  157. package/dist/state/request-intake.js +74 -0
  158. package/dist/state/request-intake.js.map +1 -0
  159. package/dist/state/state-store.d.ts +146 -0
  160. package/dist/state/state-store.d.ts.map +1 -0
  161. package/dist/state/state-store.js +465 -0
  162. package/dist/state/state-store.js.map +1 -0
  163. package/dist/state/trace-store.d.ts +12 -0
  164. package/dist/state/trace-store.d.ts.map +1 -0
  165. package/dist/state/trace-store.js +24 -0
  166. package/dist/state/trace-store.js.map +1 -0
  167. package/dist/types/plugin.d.ts +93 -0
  168. package/dist/types/plugin.d.ts.map +1 -0
  169. package/dist/types/plugin.js +4 -0
  170. package/dist/types/plugin.js.map +1 -0
  171. package/dist/web/index.d.ts +10 -0
  172. package/dist/web/index.d.ts.map +1 -0
  173. package/dist/web/index.js +153 -0
  174. package/dist/web/index.js.map +1 -0
  175. package/dist/web/routes/activity.d.ts +6 -0
  176. package/dist/web/routes/activity.d.ts.map +1 -0
  177. package/dist/web/routes/activity.js +31 -0
  178. package/dist/web/routes/activity.js.map +1 -0
  179. package/dist/web/routes/auth.d.ts +6 -0
  180. package/dist/web/routes/auth.d.ts.map +1 -0
  181. package/dist/web/routes/auth.js +142 -0
  182. package/dist/web/routes/auth.js.map +1 -0
  183. package/dist/web/routes/config.d.ts +7 -0
  184. package/dist/web/routes/config.d.ts.map +1 -0
  185. package/dist/web/routes/config.js +204 -0
  186. package/dist/web/routes/config.js.map +1 -0
  187. package/dist/web/routes/content-types.d.ts +6 -0
  188. package/dist/web/routes/content-types.d.ts.map +1 -0
  189. package/dist/web/routes/content-types.js +29 -0
  190. package/dist/web/routes/content-types.js.map +1 -0
  191. package/dist/web/routes/discovery.d.ts +6 -0
  192. package/dist/web/routes/discovery.d.ts.map +1 -0
  193. package/dist/web/routes/discovery.js +111 -0
  194. package/dist/web/routes/discovery.js.map +1 -0
  195. package/dist/web/routes/library-import.d.ts +6 -0
  196. package/dist/web/routes/library-import.d.ts.map +1 -0
  197. package/dist/web/routes/library-import.js +120 -0
  198. package/dist/web/routes/library-import.js.map +1 -0
  199. package/dist/web/routes/library-management.d.ts +6 -0
  200. package/dist/web/routes/library-management.d.ts.map +1 -0
  201. package/dist/web/routes/library-management.js +120 -0
  202. package/dist/web/routes/library-management.js.map +1 -0
  203. package/dist/web/routes/library.d.ts +6 -0
  204. package/dist/web/routes/library.d.ts.map +1 -0
  205. package/dist/web/routes/library.js +28 -0
  206. package/dist/web/routes/library.js.map +1 -0
  207. package/dist/web/routes/oidc.d.ts +9 -0
  208. package/dist/web/routes/oidc.d.ts.map +1 -0
  209. package/dist/web/routes/oidc.js +203 -0
  210. package/dist/web/routes/oidc.js.map +1 -0
  211. package/dist/web/routes/requests.d.ts +6 -0
  212. package/dist/web/routes/requests.d.ts.map +1 -0
  213. package/dist/web/routes/requests.js +183 -0
  214. package/dist/web/routes/requests.js.map +1 -0
  215. package/dist/web/routes/sse.d.ts +6 -0
  216. package/dist/web/routes/sse.d.ts.map +1 -0
  217. package/dist/web/routes/sse.js +54 -0
  218. package/dist/web/routes/sse.js.map +1 -0
  219. package/package.json +33 -0
  220. package/package.json.license +2 -0
  221. package/src/auth/auth-service.ts +333 -0
  222. package/src/auth/http-server.ts +249 -0
  223. package/src/auth/jwt-secret.ts +65 -0
  224. package/src/auth/oidc/oidc-flow.ts +266 -0
  225. package/src/auth/oidc/oidc-provider-service.ts +174 -0
  226. package/src/auth/oidc/user-service.ts +227 -0
  227. package/src/auth/rate-limiter.ts +85 -0
  228. package/src/config/config-service.ts +434 -0
  229. package/src/config/validate.ts +153 -0
  230. package/src/content-types/registry.ts +194 -0
  231. package/src/datastore/db.ts +38 -0
  232. package/src/datastore/encrypt.ts +107 -0
  233. package/src/datastore/migrations.ts +367 -0
  234. package/src/datastore/storage.ts +83 -0
  235. package/src/events/event-bus.ts +180 -0
  236. package/src/events/spine.ts +87 -0
  237. package/src/events/trace.ts +38 -0
  238. package/src/host.ts +130 -0
  239. package/src/library/importer.ts +174 -0
  240. package/src/library/library-cache.ts +212 -0
  241. package/src/library/library-config.ts +22 -0
  242. package/src/library/library-scanner.ts +404 -0
  243. package/src/library/library-service.ts +282 -0
  244. package/src/library/metadata-matcher.ts +285 -0
  245. package/src/library/naming.ts +59 -0
  246. package/src/library/scan-controller.ts +490 -0
  247. package/src/library/source-resolver.ts +92 -0
  248. package/src/logging/logger.ts +115 -0
  249. package/src/main.ts +364 -0
  250. package/src/plugins/context.ts +138 -0
  251. package/src/plugins/discovery.ts +29 -0
  252. package/src/plugins/loader.ts +309 -0
  253. package/src/state/request-intake.ts +101 -0
  254. package/src/state/state-store.ts +621 -0
  255. package/src/state/trace-store.ts +27 -0
  256. package/src/types/plugin.ts +110 -0
  257. package/src/web/index.ts +182 -0
  258. package/src/web/routes/activity.ts +41 -0
  259. package/src/web/routes/auth.ts +170 -0
  260. package/src/web/routes/config.ts +282 -0
  261. package/src/web/routes/content-types.ts +42 -0
  262. package/src/web/routes/discovery.ts +156 -0
  263. package/src/web/routes/library-import.ts +196 -0
  264. package/src/web/routes/library-management.ts +140 -0
  265. package/src/web/routes/library.ts +46 -0
  266. package/src/web/routes/oidc.ts +260 -0
  267. package/src/web/routes/requests.ts +250 -0
  268. package/src/web/routes/sse.ts +92 -0
  269. package/tests/auth.test.ts +510 -0
  270. package/tests/config-route.test.ts +345 -0
  271. package/tests/config.test.ts +612 -0
  272. package/tests/content-types.test.ts +376 -0
  273. package/tests/datastore.test.ts +345 -0
  274. package/tests/e2e.test.ts +430 -0
  275. package/tests/event-bus.test.ts +925 -0
  276. package/tests/host.test.ts +449 -0
  277. package/tests/library-cache.test.ts +171 -0
  278. package/tests/library-scanner.test.ts +177 -0
  279. package/tests/library.test.ts +210 -0
  280. package/tests/loader-context.test.ts +644 -0
  281. package/tests/logger-jsonl.test.ts +154 -0
  282. package/tests/metadata-matcher.test.ts +198 -0
  283. package/tests/oidc.test.ts +218 -0
  284. package/tests/scan-controller.test.ts +328 -0
  285. package/tests/source-resolver.test.ts +100 -0
  286. package/tests/starr-video-integration.test.ts +153 -0
  287. package/tests/state-store.test.ts +700 -0
  288. package/tests/trace-history.test.ts +356 -0
  289. package/tsconfig.json +8 -0
  290. package/tsconfig.json.license +2 -0
package/data/starr.db ADDED
Binary file
Binary file
Binary file
package/data/starr.jwt ADDED
@@ -0,0 +1 @@
1
+ 2ycV61gcvBbFPUFSyDwik7EjLTB5iEqpiONLIthUFr4=
package/data/starr.key ADDED
@@ -0,0 +1 @@
1
+ dNLIYmYMSfBQNU7M/vsS8LZR88nH8/LhxHAL4jjeB3o=
@@ -0,0 +1,42 @@
1
+ import type Database from "better-sqlite3";
2
+ import type { Logger } from "../types/plugin.js";
3
+ export declare class AuthService {
4
+ private db;
5
+ private logger;
6
+ constructor(db: Database.Database, logger: Logger);
7
+ needsSetup(): boolean;
8
+ setup(username: string, password: string): Promise<void>;
9
+ login(username: string, password: string): Promise<string | null>;
10
+ /**
11
+ * Create a session for an OIDC-authenticated user.
12
+ * Called after a successful OIDC callback.
13
+ * @param userId The user's UUID from the users table.
14
+ * @param role The user's role ("admin" | "user").
15
+ * @returns The JWT string.
16
+ */
17
+ loginOidc(userId: string, role: string): string;
18
+ validateToken(token: string): {
19
+ userId: string;
20
+ sessionId: string;
21
+ role: string;
22
+ } | null;
23
+ logout(sessionId: string): void;
24
+ changePassword(username: string, currentPassword: string, newPassword: string): Promise<void>;
25
+ /**
26
+ * Get the role for a user. Looks up by id or username in the users table.
27
+ * Falls back to "admin" for backward compatibility (pre-migration rows).
28
+ */
29
+ private getUserRole;
30
+ /**
31
+ * Get user details for the status endpoint.
32
+ * Returns the user's id, username, display name, avatar URL, and role.
33
+ */
34
+ getUserInfo(userId: string): {
35
+ id: string;
36
+ username: string;
37
+ displayName: string | null;
38
+ avatarUrl: string | null;
39
+ role: string;
40
+ } | null;
41
+ }
42
+ //# sourceMappingURL=auth-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-service.d.ts","sourceRoot":"","sources":["../../src/auth/auth-service.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AA2OjD,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,MAAM,CAAS;gBAGrB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE,MAAM;IAMhB,UAAU,IAAI,OAAO;IAIf,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxD,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASvE;;;;;;OAMG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAK/C,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAIxF,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIzB,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAIhB;;;OAGG;IACH,OAAO,CAAC,WAAW;IAOnB;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;QAC3B,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC;KACd,GAAG,IAAI;CAaT"}
@@ -0,0 +1,226 @@
1
+ // SPDX-License-Identifier: 0BSD
2
+ // SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
3
+ import * as argon2 from "argon2";
4
+ import { createHmac, randomUUID } from "node:crypto";
5
+ import { getJwtSecret } from "./jwt-secret.js";
6
+ // ── Password hashing (Argon2id) ─────────────────────────────
7
+ const ARGON2_OPTIONS = {
8
+ type: argon2.argon2id, // explicit Argon2id
9
+ memoryCost: 19456, // ~19 MiB (OWASP minimum for Argon2id)
10
+ timeCost: 2,
11
+ parallelism: 1,
12
+ };
13
+ async function hashPassword(password) {
14
+ return argon2.hash(password, ARGON2_OPTIONS);
15
+ }
16
+ async function verifyPassword(hash, password) {
17
+ return argon2.verify(hash, password);
18
+ }
19
+ function base64url(buf) {
20
+ return buf
21
+ .toString("base64")
22
+ .replace(/\+/g, "-")
23
+ .replace(/\//g, "_")
24
+ .replace(/=+$/, "");
25
+ }
26
+ function base64urlDecode(str) {
27
+ str = str.replace(/-/g, "+").replace(/_/g, "/");
28
+ while (str.length % 4)
29
+ str += "=";
30
+ return Buffer.from(str, "base64");
31
+ }
32
+ function signJwt(payload) {
33
+ const header = base64url(Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })));
34
+ const body = base64url(Buffer.from(JSON.stringify(payload)));
35
+ const secret = getJwtSecret();
36
+ const sig = base64url(createHmac("sha256", secret).update(`${header}.${body}`).digest());
37
+ return `${header}.${body}.${sig}`;
38
+ }
39
+ function verifyJwt(token) {
40
+ const parts = token.split(".");
41
+ if (parts.length !== 3)
42
+ return null;
43
+ const [headerB64, bodyB64, sigB64] = parts;
44
+ // Verify signature
45
+ const secret = getJwtSecret();
46
+ const expectedSig = base64url(createHmac("sha256", secret).update(`${headerB64}.${bodyB64}`).digest());
47
+ if (sigB64 !== expectedSig)
48
+ return null;
49
+ // Decode payload
50
+ let payload;
51
+ try {
52
+ payload = JSON.parse(base64urlDecode(bodyB64).toString("utf8"));
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ // Validate expiry
58
+ if (typeof payload.exp !== "number" || payload.exp < Math.floor(Date.now() / 1000)) {
59
+ return null;
60
+ }
61
+ return payload;
62
+ }
63
+ // ── Session TTL ─────────────────────────────────────────────
64
+ const SESSION_TTL_HOURS = parseInt(process.env.STARR_SESSION_TTL_HOURS ?? "168", 10);
65
+ // ── Session management (DB) ────────────────────────────────
66
+ function createSession(db, userId, role, // NEW parameter
67
+ logger) {
68
+ const sessionId = randomUUID();
69
+ const now = Math.floor(Date.now() / 1000);
70
+ const exp = now + SESSION_TTL_HOURS * 3600;
71
+ const payload = { sub: userId, sid: sessionId, role, iat: now, exp };
72
+ const jwt = signJwt(payload);
73
+ db.prepare(`INSERT INTO sessions (session_id, user_id, created_at, expires_at)
74
+ VALUES (@sid, @uid, datetime('now'), datetime(@exp, 'unixepoch'))`).run({ sid: sessionId, uid: userId, exp });
75
+ logger.info("session created", { userId, sessionId });
76
+ return { sessionId, jwt };
77
+ }
78
+ function validateSession(db, token, logger) {
79
+ const payload = verifyJwt(token);
80
+ if (!payload)
81
+ return null;
82
+ // Check session exists and is not expired in the DB
83
+ const row = db.prepare(`SELECT user_id, expires_at FROM sessions WHERE session_id = @sid`).get({ sid: payload.sid });
84
+ if (!row) {
85
+ logger.debug("session not found in DB", { sessionId: payload.sid });
86
+ return null;
87
+ }
88
+ // DB expiry check (defense in depth; JWT exp is primary)
89
+ if (new Date(row.expires_at + "Z").getTime() < Date.now()) {
90
+ logger.debug("session expired", { sessionId: payload.sid });
91
+ // Clean up expired session
92
+ db.prepare("DELETE FROM sessions WHERE session_id = @sid").run({ sid: payload.sid });
93
+ return null;
94
+ }
95
+ return {
96
+ userId: payload.sub,
97
+ sessionId: payload.sid,
98
+ role: payload.role ?? "user", // NEW: extract role from JWT
99
+ };
100
+ }
101
+ function destroySession(db, sessionId) {
102
+ db.prepare("DELETE FROM sessions WHERE session_id = @sid").run({ sid: sessionId });
103
+ }
104
+ function destroyAllSessionsForUser(db, userId) {
105
+ db.prepare("DELETE FROM sessions WHERE user_id = @uid").run({ uid: userId });
106
+ }
107
+ // ── First-run detection & Account CRUD ─────────────────────
108
+ function hasAdminAccount(db) {
109
+ const row = db.prepare("SELECT 1 FROM admin_accounts LIMIT 1").get();
110
+ return row !== undefined;
111
+ }
112
+ async function createAdminAccount(db, username, password, logger) {
113
+ if (hasAdminAccount(db)) {
114
+ throw new Error("Admin account already exists");
115
+ }
116
+ if (!username || username.length < 1) {
117
+ throw new Error("Username must not be empty");
118
+ }
119
+ if (!password || password.length < 8) {
120
+ throw new Error("Password must be at least 8 characters");
121
+ }
122
+ const passwordHash = await hashPassword(password);
123
+ // Use a transaction to keep both inserts atomic
124
+ db.transaction(() => {
125
+ db.prepare(`INSERT INTO admin_accounts (username, password_hash) VALUES (@u, @h)`).run({ u: username, h: passwordHash });
126
+ // Also create the corresponding users row
127
+ db.prepare(`
128
+ INSERT OR IGNORE INTO users (id, username, display_name, role, provider)
129
+ VALUES (@id, @username, @displayName, 'admin', 'local')
130
+ `).run({
131
+ id: username, // use the username as the id for backward compat
132
+ username,
133
+ displayName: username,
134
+ });
135
+ })();
136
+ logger.info("admin account created", { username });
137
+ }
138
+ async function verifyAdminPassword(db, username, password) {
139
+ const row = db.prepare("SELECT password_hash FROM admin_accounts WHERE username = @u").get({ u: username });
140
+ if (!row)
141
+ return false;
142
+ return verifyPassword(row.password_hash, password);
143
+ }
144
+ async function changePassword(db, username, currentPassword, newPassword, logger) {
145
+ if (!newPassword || newPassword.length < 8) {
146
+ throw new Error("New password must be at least 8 characters");
147
+ }
148
+ const valid = await verifyAdminPassword(db, username, currentPassword);
149
+ if (!valid) {
150
+ throw new Error("Current password is incorrect");
151
+ }
152
+ const newHash = await hashPassword(newPassword);
153
+ db.prepare(`UPDATE admin_accounts SET password_hash = @h, updated_at = datetime('now') WHERE username = @u`).run({ u: username, h: newHash });
154
+ // Invalidate all existing sessions for this user
155
+ destroyAllSessionsForUser(db, username);
156
+ logger.info("password changed", { username });
157
+ }
158
+ // ── AuthService class ──────────────────────────────────────
159
+ export class AuthService {
160
+ db;
161
+ logger;
162
+ constructor(db, logger) {
163
+ this.db = db;
164
+ this.logger = logger;
165
+ }
166
+ needsSetup() {
167
+ return !hasAdminAccount(this.db);
168
+ }
169
+ async setup(username, password) {
170
+ return createAdminAccount(this.db, username, password, this.logger);
171
+ }
172
+ async login(username, password) {
173
+ const valid = await verifyAdminPassword(this.db, username, password);
174
+ if (!valid)
175
+ return null;
176
+ // Look up the user's role from the users table
177
+ const role = this.getUserRole(username);
178
+ const { jwt } = createSession(this.db, username, role, this.logger);
179
+ return jwt;
180
+ }
181
+ /**
182
+ * Create a session for an OIDC-authenticated user.
183
+ * Called after a successful OIDC callback.
184
+ * @param userId The user's UUID from the users table.
185
+ * @param role The user's role ("admin" | "user").
186
+ * @returns The JWT string.
187
+ */
188
+ loginOidc(userId, role) {
189
+ const { jwt } = createSession(this.db, userId, role, this.logger);
190
+ return jwt;
191
+ }
192
+ validateToken(token) {
193
+ return validateSession(this.db, token, this.logger);
194
+ }
195
+ logout(sessionId) {
196
+ destroySession(this.db, sessionId);
197
+ }
198
+ async changePassword(username, currentPassword, newPassword) {
199
+ return changePassword(this.db, username, currentPassword, newPassword, this.logger);
200
+ }
201
+ /**
202
+ * Get the role for a user. Looks up by id or username in the users table.
203
+ * Falls back to "admin" for backward compatibility (pre-migration rows).
204
+ */
205
+ getUserRole(userId) {
206
+ const row = this.db.prepare("SELECT role FROM users WHERE id = @id OR username = @id").get({ id: userId });
207
+ return row?.role ?? "admin";
208
+ }
209
+ /**
210
+ * Get user details for the status endpoint.
211
+ * Returns the user's id, username, display name, avatar URL, and role.
212
+ */
213
+ getUserInfo(userId) {
214
+ const row = this.db.prepare("SELECT id, username, display_name, avatar_url, role FROM users WHERE id = @id OR username = @id").get({ id: userId });
215
+ if (!row)
216
+ return null;
217
+ return {
218
+ id: row.id,
219
+ username: row.username,
220
+ displayName: row.display_name,
221
+ avatarUrl: row.avatar_url,
222
+ role: row.role,
223
+ };
224
+ }
225
+ }
226
+ //# sourceMappingURL=auth-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-service.js","sourceRoot":"","sources":["../../src/auth/auth-service.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,4DAA4D;AAE5D,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGrD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,+DAA+D;AAE/D,MAAM,cAAc,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAG,oBAAoB;IAC5C,UAAU,EAAE,KAAK,EAAO,uCAAuC;IAC/D,QAAQ,EAAE,CAAC;IACX,WAAW,EAAE,CAAC;CACf,CAAC;AAEF,KAAK,UAAU,YAAY,CAAC,QAAgB;IAC1C,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AAC/C,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,QAAgB;IAC1D,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC;AAYD,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,GAAG;SACP,QAAQ,CAAC,QAAQ,CAAC;SAClB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,eAAe,CAAC,GAAW;IAClC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAChD,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,GAAG,IAAI,GAAG,CAAC;IAClC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,OAAO,CAAC,OAAmB;IAClC,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACpF,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,SAAS,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACzF,OAAO,GAAG,MAAM,IAAI,IAAI,IAAI,GAAG,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,SAAS,CAAC,KAAa;IAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IAE3C,mBAAmB;IACnB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,WAAW,GAAG,SAAS,CAC3B,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CACxE,CAAC;IACF,IAAI,MAAM,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAExC,iBAAiB;IACjB,IAAI,OAAmB,CAAC;IACxB,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kBAAkB;IAClB,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,+DAA+D;AAE/D,MAAM,iBAAiB,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,KAAK,EAAE,EAAE,CAAC,CAAC;AAErF,8DAA8D;AAE9D,SAAS,aAAa,CACpB,EAAqB,EACrB,MAAc,EACd,IAAY,EAAO,gBAAgB;AACnC,MAAc;IAEd,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,GAAG,GAAG,iBAAiB,GAAG,IAAI,CAAC;IAE3C,MAAM,OAAO,GAAe,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACjF,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE7B,EAAE,CAAC,OAAO,CACR;uEACmE,CACpE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAE5C,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACtD,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;AAC5B,CAAC;AAED,SAAS,eAAe,CACtB,EAAqB,EACrB,KAAa,EACb,MAAc;IAEd,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,oDAAoD;IACpD,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CACpB,kEAAkE,CACnE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAwD,CAAC;IAEnF,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,yDAAyD;IACzD,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5D,2BAA2B;QAC3B,EAAE,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACrF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,GAAG;QACnB,SAAS,EAAE,OAAO,CAAC,GAAG;QACtB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,MAAM,EAAG,6BAA6B;KAC7D,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,EAAqB,EAAE,SAAiB;IAC9D,EAAE,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;AACrF,CAAC;AAED,SAAS,yBAAyB,CAAC,EAAqB,EAAE,MAAc;IACtE,EAAE,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,8DAA8D;AAE9D,SAAS,eAAe,CAAC,EAAqB;IAC5C,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,EAAE,CAAC;IACrE,OAAO,GAAG,KAAK,SAAS,CAAC;AAC3B,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,EAAqB,EACrB,QAAgB,EAChB,QAAgB,EAChB,MAAc;IAEd,IAAI,eAAe,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;IAElD,gDAAgD;IAChD,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QAClB,EAAE,CAAC,OAAO,CACR,sEAAsE,CACvE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;QAExC,0CAA0C;QAC1C,EAAE,CAAC,OAAO,CAAC;;;KAGV,CAAC,CAAC,GAAG,CAAC;YACL,EAAE,EAAE,QAAQ,EAAG,iDAAiD;YAChE,QAAQ;YACR,WAAW,EAAE,QAAQ;SACtB,CAAC,CAAC;IACL,CAAC,CAAC,EAAE,CAAC;IAEL,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,KAAK,UAAU,mBAAmB,CAChC,EAAqB,EACrB,QAAgB,EAChB,QAAgB;IAEhB,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CACpB,8DAA8D,CAC/D,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,CAA0C,CAAC;IAChE,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,OAAO,cAAc,CAAC,GAAG,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;AACrD,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,EAAqB,EACrB,QAAgB,EAChB,eAAuB,EACvB,WAAmB,EACnB,MAAc;IAEd,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,EAAE,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;IACvE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,WAAW,CAAC,CAAC;IAChD,EAAE,CAAC,OAAO,CACR,gGAAgG,CACjG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IAEnC,iDAAiD;IACjD,yBAAyB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAExC,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,8DAA8D;AAE9D,MAAM,OAAO,WAAW;IACd,EAAE,CAAoB;IACtB,MAAM,CAAS;IAEvB,YACE,EAAqB,EACrB,MAAc;QAEd,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,UAAU;QACR,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,QAAgB;QAC5C,OAAO,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACtE,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,QAAgB;QAC5C,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACrE,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,+CAA+C;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,EAAE,GAAG,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACpE,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;;;;OAMG;IACH,SAAS,CAAC,MAAc,EAAE,IAAY;QACpC,MAAM,EAAE,GAAG,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAClE,OAAO,GAAG,CAAC;IACb,CAAC;IAED,aAAa,CAAC,KAAa;QACzB,OAAO,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,CAAC,SAAiB;QACtB,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,QAAgB,EAChB,eAAuB,EACvB,WAAmB;QAEnB,OAAO,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACtF,CAAC;IAED;;;OAGG;IACK,WAAW,CAAC,MAAc;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CACzB,yDAAyD,CAC1D,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAiC,CAAC;QACtD,OAAO,GAAG,EAAE,IAAI,IAAI,OAAO,CAAC;IAC9B,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,MAAc;QAOxB,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CACzB,iGAAiG,CAClG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAQ,CAAC;QAC7B,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,OAAO;YACL,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,WAAW,EAAE,GAAG,CAAC,YAAY;YAC7B,SAAS,EAAE,GAAG,CAAC,UAAU;YACzB,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ import type { Server } from "node:http";
3
+ import type { Logger } from "../types/plugin.js";
4
+ import { AuthService } from "./auth-service.js";
5
+ declare const COOKIE_NAME = "starr_sid";
6
+ declare function parseCookies(req: IncomingMessage): Record<string, string>;
7
+ /**
8
+ * Extract and validate the JWT from the request cookie.
9
+ * Returns the authenticated userId, or null if not authenticated.
10
+ */
11
+ declare function authenticate(req: IncomingMessage, auth: AuthService): string | null;
12
+ type AuthHandler = (req: IncomingMessage, res: ServerResponse, userId: string) => Promise<void>;
13
+ declare function withAuth(auth: AuthService, handler: AuthHandler): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
14
+ export declare function createAuthServer(auth: AuthService, logger: Logger, port?: number): Server;
15
+ export { authenticate, withAuth, parseCookies, COOKIE_NAME };
16
+ //# sourceMappingURL=http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../../src/auth/http-server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA+BhD,QAAA,MAAM,WAAW,cAAc,CAAC;AAEhC,iBAAS,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUlE;AAmBD;;;GAGG;AACH,iBAAS,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAM5E;AAED,KAAK,WAAW,GAAG,CACjB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,iBAAS,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,IACzC,KAAK,eAAe,EAAE,KAAK,cAAc,mBAQxD;AAcD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,SAAO,GACV,MAAM,CAqIR;AAED,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,207 @@
1
+ // SPDX-License-Identifier: 0BSD
2
+ // SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
3
+ import { createServer } from "node:http";
4
+ import { RateLimiter } from "./rate-limiter.js";
5
+ // ── JSON Helpers ───────────────────────────────────────────
6
+ function readBody(req) {
7
+ return new Promise((resolve, reject) => {
8
+ let body = "";
9
+ req.on("data", (chunk) => (body += chunk));
10
+ req.on("end", () => resolve(body));
11
+ req.on("error", reject);
12
+ });
13
+ }
14
+ async function readJson(req) {
15
+ const raw = await readBody(req);
16
+ if (!raw)
17
+ throw new Error("Empty request body");
18
+ return JSON.parse(raw);
19
+ }
20
+ function sendJson(res, status, data) {
21
+ const body = JSON.stringify(data);
22
+ res.writeHead(status, {
23
+ "Content-Type": "application/json",
24
+ "Content-Length": String(Buffer.byteLength(body)),
25
+ });
26
+ res.end(body);
27
+ }
28
+ // ── Cookie Helpers ─────────────────────────────────────────
29
+ const COOKIE_NAME = "starr_sid";
30
+ function parseCookies(req) {
31
+ const header = req.headers.cookie;
32
+ if (!header)
33
+ return {};
34
+ const result = {};
35
+ for (const part of header.split(";")) {
36
+ const eq = part.indexOf("=");
37
+ if (eq === -1)
38
+ continue;
39
+ result[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
40
+ }
41
+ return result;
42
+ }
43
+ function setAuthCookie(res, jwt) {
44
+ const maxAge = parseInt(process.env.STARR_SESSION_TTL_HOURS ?? "168", 10) * 3600;
45
+ res.setHeader("Set-Cookie", `${COOKIE_NAME}=${jwt}; HttpOnly; SameSite=Strict; Path=/; Max-Age=${maxAge}`);
46
+ }
47
+ function clearAuthCookie(res) {
48
+ res.setHeader("Set-Cookie", `${COOKIE_NAME}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`);
49
+ }
50
+ // ── Auth Middleware ─────────────────────────────────────────
51
+ /**
52
+ * Extract and validate the JWT from the request cookie.
53
+ * Returns the authenticated userId, or null if not authenticated.
54
+ */
55
+ function authenticate(req, auth) {
56
+ const cookies = parseCookies(req);
57
+ const token = cookies[COOKIE_NAME];
58
+ if (!token)
59
+ return null;
60
+ const result = auth.validateToken(token);
61
+ return result?.userId ?? null;
62
+ }
63
+ function withAuth(auth, handler) {
64
+ return async (req, res) => {
65
+ const userId = authenticate(req, auth);
66
+ if (!userId) {
67
+ sendJson(res, 401, { error: "Unauthorized" });
68
+ return;
69
+ }
70
+ await handler(req, res, userId);
71
+ };
72
+ }
73
+ function getClientIp(req) {
74
+ const forwarded = req.headers["x-forwarded-for"];
75
+ if (typeof forwarded === "string")
76
+ return forwarded.split(",")[0].trim();
77
+ return req.socket.remoteAddress ?? "unknown";
78
+ }
79
+ // ── HTTP Server Factory ────────────────────────────────────
80
+ export function createAuthServer(auth, logger, port = 3457) {
81
+ const limiter = new RateLimiter(4, 15);
82
+ const routes = new Map();
83
+ // ── Public routes ──
84
+ routes.set("GET /api/auth/needs-setup", async (_req, res) => {
85
+ sendJson(res, 200, { needsSetup: auth.needsSetup() });
86
+ });
87
+ routes.set("POST /api/auth/setup", async (req, res) => {
88
+ const ip = getClientIp(req);
89
+ if (!limiter.check(ip)) {
90
+ sendJson(res, 429, { error: "Too many attempts", retryAfter: limiter.retryAfterSeconds(ip) });
91
+ return;
92
+ }
93
+ try {
94
+ const body = await readJson(req);
95
+ if (!body.username || !body.password) {
96
+ sendJson(res, 400, { error: "username and password are required" });
97
+ return;
98
+ }
99
+ await auth.setup(body.username, body.password);
100
+ // Log in immediately after setup
101
+ const jwt = await auth.login(body.username, body.password);
102
+ if (jwt) {
103
+ limiter.reset(ip);
104
+ setAuthCookie(res, jwt);
105
+ sendJson(res, 201, { ok: true });
106
+ }
107
+ else {
108
+ sendJson(res, 500, { error: "Account created but login failed" });
109
+ }
110
+ }
111
+ catch (err) {
112
+ const msg = err instanceof Error ? err.message : "Unknown error";
113
+ if (msg.includes("already exists")) {
114
+ sendJson(res, 403, { error: msg });
115
+ }
116
+ else {
117
+ sendJson(res, 400, { error: msg });
118
+ }
119
+ }
120
+ });
121
+ routes.set("POST /api/auth/login", async (req, res) => {
122
+ const ip = getClientIp(req);
123
+ if (!limiter.check(ip)) {
124
+ res.setHeader("Retry-After", String(limiter.retryAfterSeconds(ip)));
125
+ sendJson(res, 429, { error: "Too many login attempts", retryAfter: limiter.retryAfterSeconds(ip) });
126
+ return;
127
+ }
128
+ try {
129
+ const body = await readJson(req);
130
+ if (!body.username || !body.password) {
131
+ sendJson(res, 400, { error: "username and password are required" });
132
+ return;
133
+ }
134
+ const jwt = await auth.login(body.username, body.password);
135
+ if (!jwt) {
136
+ sendJson(res, 401, { error: "Invalid credentials" });
137
+ return;
138
+ }
139
+ limiter.reset(ip);
140
+ setAuthCookie(res, jwt);
141
+ sendJson(res, 200, { ok: true });
142
+ }
143
+ catch (err) {
144
+ sendJson(res, 500, { error: err instanceof Error ? err.message : "Login failed" });
145
+ }
146
+ });
147
+ // ── Auth-required routes ──
148
+ routes.set("POST /api/auth/logout", withAuth(auth, async (req, res, _userId) => {
149
+ const cookies = parseCookies(req);
150
+ const token = cookies[COOKIE_NAME];
151
+ if (token) {
152
+ const result = auth.validateToken(token);
153
+ if (result)
154
+ auth.logout(result.sessionId);
155
+ }
156
+ clearAuthCookie(res);
157
+ sendJson(res, 200, { ok: true });
158
+ }));
159
+ routes.set("POST /api/auth/change-password", withAuth(auth, async (req, res, userId) => {
160
+ try {
161
+ const body = await readJson(req);
162
+ if (!body.currentPassword || !body.newPassword) {
163
+ sendJson(res, 400, { error: "currentPassword and newPassword are required" });
164
+ return;
165
+ }
166
+ await auth.changePassword(userId, body.currentPassword, body.newPassword);
167
+ // Create a new session with the new password context
168
+ const newJwt = await auth.login(userId, body.newPassword);
169
+ if (newJwt) {
170
+ setAuthCookie(res, newJwt);
171
+ }
172
+ sendJson(res, 200, { ok: true });
173
+ }
174
+ catch (err) {
175
+ sendJson(res, 400, { error: err instanceof Error ? err.message : "Password change failed" });
176
+ }
177
+ }));
178
+ routes.set("GET /api/auth/status", withAuth(auth, async (_req, res, userId) => {
179
+ sendJson(res, 200, { user: userId });
180
+ }));
181
+ // ── Server ──
182
+ const server = createServer(async (req, res) => {
183
+ const method = req.method ?? "GET";
184
+ const url = new URL(req.url ?? "/", "http://localhost");
185
+ const key = `${method} ${url.pathname}`;
186
+ const handler = routes.get(key);
187
+ if (!handler) {
188
+ sendJson(res, 404, { error: "Not found" });
189
+ return;
190
+ }
191
+ try {
192
+ await handler(req, res);
193
+ }
194
+ catch (err) {
195
+ logger.error("unhandled request error", { key, error: err instanceof Error ? err.message : String(err) });
196
+ if (!res.headersSent) {
197
+ sendJson(res, 500, { error: "Internal server error" });
198
+ }
199
+ }
200
+ });
201
+ server.listen(port, () => {
202
+ logger.info("auth HTTP server listening", { port });
203
+ });
204
+ return server;
205
+ }
206
+ export { authenticate, withAuth, parseCookies, COOKIE_NAME };
207
+ //# sourceMappingURL=http-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../src/auth/http-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { Logger } from "../types/plugin.js";
2
+ export declare function loadJwtSecret(dataDir: string, logger: Logger): Buffer;
3
+ /** For testing only. */
4
+ export declare function resetJwtSecret(): void;
5
+ export declare function getJwtSecret(): Buffer;
6
+ //# sourceMappingURL=jwt-secret.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-secret.d.ts","sourceRoot":"","sources":["../../src/auth/jwt-secret.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAMjD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA0CrE;AAED,wBAAwB;AACxB,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAGrC"}
@@ -0,0 +1,51 @@
1
+ // SPDX-License-Identifier: 0BSD
2
+ // SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
3
+ import { randomBytes } from "node:crypto";
4
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
5
+ import path from "node:path";
6
+ const KEY_LENGTH = 32; // 256 bits, bytes
7
+ let _secret = null;
8
+ export function loadJwtSecret(dataDir, logger) {
9
+ if (_secret)
10
+ return _secret;
11
+ // 1. Env var
12
+ const envSecret = process.env.STARR_JWT_SECRET;
13
+ if (envSecret) {
14
+ const buf = Buffer.from(envSecret, "base64");
15
+ if (buf.length !== KEY_LENGTH) {
16
+ throw new Error(`STARR_JWT_SECRET must decode to ${KEY_LENGTH} bytes, got ${buf.length}`);
17
+ }
18
+ _secret = buf;
19
+ logger.info("JWT secret loaded from STARR_JWT_SECRET");
20
+ return _secret;
21
+ }
22
+ // 2. Key file
23
+ const keyPath = path.join(dataDir, "starr.jwt");
24
+ if (existsSync(keyPath)) {
25
+ const fileContent = readFileSync(keyPath, "utf8").trim();
26
+ const buf = Buffer.from(fileContent, "base64");
27
+ if (buf.length !== KEY_LENGTH) {
28
+ throw new Error(`Key file ${keyPath} must decode to ${KEY_LENGTH} bytes, got ${buf.length}`);
29
+ }
30
+ _secret = buf;
31
+ logger.info("JWT secret loaded from file", { keyPath });
32
+ return _secret;
33
+ }
34
+ // 3. Auto-generate
35
+ const newKey = randomBytes(KEY_LENGTH);
36
+ const encoded = newKey.toString("base64");
37
+ writeFileSync(keyPath, encoded + "\n", { mode: 0o600 });
38
+ _secret = newKey;
39
+ logger.info("auto-generated JWT secret (no STARR_JWT_SECRET set)", { keyPath });
40
+ return _secret;
41
+ }
42
+ /** For testing only. */
43
+ export function resetJwtSecret() {
44
+ _secret = null;
45
+ }
46
+ export function getJwtSecret() {
47
+ if (!_secret)
48
+ throw new Error("JWT secret not loaded");
49
+ return _secret;
50
+ }
51
+ //# sourceMappingURL=jwt-secret.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-secret.js","sourceRoot":"","sources":["../../src/auth/jwt-secret.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,4DAA4D;AAE5D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,MAAM,UAAU,GAAG,EAAE,CAAC,CAAC,kBAAkB;AAEzC,IAAI,OAAO,GAAkB,IAAI,CAAC;AAElC,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,MAAc;IAC3D,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAE5B,aAAa;IACb,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC/C,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC7C,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,mCAAmC,UAAU,eAAe,GAAG,CAAC,MAAM,EAAE,CACzE,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,GAAG,CAAC;QACd,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;QACvD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,cAAc;IACd,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAChD,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACzD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QAC/C,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,YAAY,OAAO,mBAAmB,UAAU,eAAe,GAAG,CAAC,MAAM,EAAE,CAC5E,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,GAAG,CAAC;QACd,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACxD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,mBAAmB;IACnB,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC1C,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,OAAO,GAAG,MAAM,CAAC;IACjB,MAAM,CAAC,IAAI,CACT,qDAAqD,EACrD,EAAE,OAAO,EAAE,CACZ,CAAC;IACF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,wBAAwB;AACxB,MAAM,UAAU,cAAc;IAC5B,OAAO,GAAG,IAAI,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACvD,OAAO,OAAO,CAAC;AACjB,CAAC"}