@repo-os/repoos 0.5.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +110 -0
- package/README.md +272 -0
- package/dist/.build-info.json +4 -0
- package/dist/.build-stamp.json +3 -0
- package/dist/cli/colors.d.ts +13 -0
- package/dist/cli/colors.js +37 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +221 -0
- package/dist/commands/check.d.ts +170 -0
- package/dist/commands/check.js +1227 -0
- package/dist/commands/docs.d.ts +2 -0
- package/dist/commands/docs.js +70 -0
- package/dist/commands/gc.d.ts +1 -0
- package/dist/commands/gc.js +57 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +699 -0
- package/dist/commands/screenshots.d.ts +7 -0
- package/dist/commands/screenshots.js +77 -0
- package/dist/commands/serve.d.ts +55 -0
- package/dist/commands/serve.js +204 -0
- package/dist/commands/status.d.ts +140 -0
- package/dist/commands/status.js +569 -0
- package/dist/commands/stop.d.ts +1 -0
- package/dist/commands/stop.js +59 -0
- package/dist/commands/tasks.d.ts +47 -0
- package/dist/commands/tasks.js +334 -0
- package/dist/commands/tunnel.d.ts +1 -0
- package/dist/commands/tunnel.js +1271 -0
- package/dist/commands/ui-harness.d.ts +53 -0
- package/dist/commands/ui-harness.js +62 -0
- package/dist/commands/ui-smoke.d.ts +6 -0
- package/dist/commands/ui-smoke.js +220 -0
- package/dist/commands/upgrade.d.ts +15 -0
- package/dist/commands/upgrade.js +188 -0
- package/dist/core/auth-store.d.ts +81 -0
- package/dist/core/auth-store.js +530 -0
- package/dist/core/auth.d.ts +75 -0
- package/dist/core/auth.js +124 -0
- package/dist/core/bootstrap.d.ts +41 -0
- package/dist/core/bootstrap.js +236 -0
- package/dist/core/build.d.ts +92 -0
- package/dist/core/build.js +249 -0
- package/dist/core/canary.d.ts +10 -0
- package/dist/core/canary.js +14 -0
- package/dist/core/check-failure-summary.d.ts +10 -0
- package/dist/core/check-failure-summary.js +34 -0
- package/dist/core/config.d.ts +160 -0
- package/dist/core/config.js +1288 -0
- package/dist/core/context-pack.d.ts +74 -0
- package/dist/core/context-pack.js +778 -0
- package/dist/core/db.d.ts +236 -0
- package/dist/core/db.js +747 -0
- package/dist/core/detect.d.ts +69 -0
- package/dist/core/detect.js +251 -0
- package/dist/core/docs.d.ts +76 -0
- package/dist/core/docs.js +225 -0
- package/dist/core/frontmatter.d.ts +29 -0
- package/dist/core/frontmatter.js +215 -0
- package/dist/core/git.d.ts +505 -0
- package/dist/core/git.js +1453 -0
- package/dist/core/indexer.d.ts +33 -0
- package/dist/core/indexer.js +282 -0
- package/dist/core/input.d.ts +66 -0
- package/dist/core/input.js +222 -0
- package/dist/core/logger.d.ts +70 -0
- package/dist/core/logger.js +138 -0
- package/dist/core/models.d.ts +45 -0
- package/dist/core/models.js +291 -0
- package/dist/core/net-probe.d.ts +4 -0
- package/dist/core/net-probe.js +20 -0
- package/dist/core/providers/deepinfra.d.ts +23 -0
- package/dist/core/providers/deepinfra.js +44 -0
- package/dist/core/providers/index.d.ts +24 -0
- package/dist/core/providers/index.js +59 -0
- package/dist/core/providers/openrouter.d.ts +28 -0
- package/dist/core/providers/openrouter.js +82 -0
- package/dist/core/providers/spend.d.ts +86 -0
- package/dist/core/providers/spend.js +286 -0
- package/dist/core/providers/types.d.ts +56 -0
- package/dist/core/providers/types.js +39 -0
- package/dist/core/repoos.d.ts +59 -0
- package/dist/core/repoos.js +221 -0
- package/dist/core/runtime.d.ts +35 -0
- package/dist/core/runtime.js +167 -0
- package/dist/core/skills-registry.d.ts +39 -0
- package/dist/core/skills-registry.js +148 -0
- package/dist/core/skills-registry.test.d.ts +1 -0
- package/dist/core/skills-registry.test.js +52 -0
- package/dist/core/sysmem.d.ts +7 -0
- package/dist/core/sysmem.js +55 -0
- package/dist/core/tailscale.d.ts +2 -0
- package/dist/core/tailscale.js +19 -0
- package/dist/core/task.d.ts +53 -0
- package/dist/core/task.js +310 -0
- package/dist/core/toml-line.d.ts +20 -0
- package/dist/core/toml-line.js +52 -0
- package/dist/core/toml-validate.d.ts +44 -0
- package/dist/core/toml-validate.js +335 -0
- package/dist/core/tunnel-assistant.d.ts +30 -0
- package/dist/core/tunnel-assistant.js +65 -0
- package/dist/core/tunnel-registry.d.ts +96 -0
- package/dist/core/tunnel-registry.js +267 -0
- package/dist/core/tunnel-registry.test.d.ts +1 -0
- package/dist/core/tunnel-registry.test.js +309 -0
- package/dist/core/tunnel.d.ts +103 -0
- package/dist/core/tunnel.js +320 -0
- package/dist/core/types.d.ts +811 -0
- package/dist/core/types.js +27 -0
- package/dist/core/worktree-gc.d.ts +57 -0
- package/dist/core/worktree-gc.js +194 -0
- package/dist/server/__tests__/integration-job.test.d.ts +1 -0
- package/dist/server/__tests__/integration-job.test.js +129 -0
- package/dist/server/agents.d.ts +1020 -0
- package/dist/server/agents.js +4070 -0
- package/dist/server/attachments.d.ts +51 -0
- package/dist/server/attachments.js +130 -0
- package/dist/server/auto-engineering.d.ts +45 -0
- package/dist/server/auto-engineering.js +275 -0
- package/dist/server/branchless-release.d.ts +22 -0
- package/dist/server/branchless-release.js +67 -0
- package/dist/server/built-in-agents.d.ts +305 -0
- package/dist/server/built-in-agents.js +1838 -0
- package/dist/server/cto-monitor.d.ts +35 -0
- package/dist/server/cto-monitor.js +281 -0
- package/dist/server/cto.d.ts +125 -0
- package/dist/server/cto.js +590 -0
- package/dist/server/deployments.d.ts +119 -0
- package/dist/server/deployments.js +325 -0
- package/dist/server/diff-snapshot.d.ts +20 -0
- package/dist/server/diff-snapshot.js +73 -0
- package/dist/server/done.d.ts +160 -0
- package/dist/server/done.js +488 -0
- package/dist/server/freeform.d.ts +55 -0
- package/dist/server/freeform.js +179 -0
- package/dist/server/handoff.d.ts +99 -0
- package/dist/server/handoff.js +575 -0
- package/dist/server/hetzner.d.ts +45 -0
- package/dist/server/hetzner.js +117 -0
- package/dist/server/icons.d.ts +8 -0
- package/dist/server/icons.js +195 -0
- package/dist/server/integration-job.d.ts +75 -0
- package/dist/server/integration-job.js +144 -0
- package/dist/server/integration-orchestrator.d.ts +211 -0
- package/dist/server/integration-orchestrator.js +1299 -0
- package/dist/server/integration-status.d.ts +48 -0
- package/dist/server/integration-status.js +71 -0
- package/dist/server/live-index.d.ts +295 -0
- package/dist/server/live-index.js +452 -0
- package/dist/server/model-test.d.ts +20 -0
- package/dist/server/model-test.js +100 -0
- package/dist/server/ntfy.d.ts +57 -0
- package/dist/server/ntfy.js +112 -0
- package/dist/server/pm-attachments.d.ts +35 -0
- package/dist/server/pm-attachments.js +181 -0
- package/dist/server/pm-runs.d.ts +58 -0
- package/dist/server/pm-runs.js +80 -0
- package/dist/server/preview.d.ts +174 -0
- package/dist/server/preview.js +641 -0
- package/dist/server/release.d.ts +88 -0
- package/dist/server/release.js +449 -0
- package/dist/server/reload.d.ts +194 -0
- package/dist/server/reload.js +586 -0
- package/dist/server/remote-validation.d.ts +132 -0
- package/dist/server/remote-validation.js +478 -0
- package/dist/server/repo-lock.d.ts +36 -0
- package/dist/server/repo-lock.js +141 -0
- package/dist/server/review-guard.d.ts +14 -0
- package/dist/server/review-guard.js +159 -0
- package/dist/server/review.d.ts +243 -0
- package/dist/server/review.js +1211 -0
- package/dist/server/routes/agents.d.ts +6 -0
- package/dist/server/routes/agents.js +28 -0
- package/dist/server/routes/auth.d.ts +60 -0
- package/dist/server/routes/auth.js +755 -0
- package/dist/server/routes/config.d.ts +20 -0
- package/dist/server/routes/config.js +347 -0
- package/dist/server/routes/debugger.d.ts +33 -0
- package/dist/server/routes/debugger.js +360 -0
- package/dist/server/routes/deployments.d.ts +3 -0
- package/dist/server/routes/deployments.js +16 -0
- package/dist/server/routes/docs.d.ts +8 -0
- package/dist/server/routes/docs.js +135 -0
- package/dist/server/routes/helpers.d.ts +21 -0
- package/dist/server/routes/helpers.js +171 -0
- package/dist/server/routes/index.d.ts +19 -0
- package/dist/server/routes/index.js +18 -0
- package/dist/server/routes/info.d.ts +17 -0
- package/dist/server/routes/info.js +177 -0
- package/dist/server/routes/inputs.d.ts +7 -0
- package/dist/server/routes/inputs.js +125 -0
- package/dist/server/routes/model-providers.d.ts +27 -0
- package/dist/server/routes/model-providers.js +94 -0
- package/dist/server/routes/models.d.ts +3 -0
- package/dist/server/routes/models.js +36 -0
- package/dist/server/routes/notify.d.ts +2 -0
- package/dist/server/routes/notify.js +17 -0
- package/dist/server/routes/playground.d.ts +29 -0
- package/dist/server/routes/playground.js +81 -0
- package/dist/server/routes/release.d.ts +20 -0
- package/dist/server/routes/release.js +86 -0
- package/dist/server/routes/router.d.ts +12 -0
- package/dist/server/routes/router.js +32 -0
- package/dist/server/routes/skill-registry.d.ts +5 -0
- package/dist/server/routes/skill-registry.js +51 -0
- package/dist/server/routes/tasks.d.ts +58 -0
- package/dist/server/routes/tasks.js +1442 -0
- package/dist/server/routes/transcribe.d.ts +2 -0
- package/dist/server/routes/transcribe.js +124 -0
- package/dist/server/routes/types.d.ts +68 -0
- package/dist/server/routes/types.js +1 -0
- package/dist/server/routes/ui.d.ts +5 -0
- package/dist/server/routes/ui.js +81 -0
- package/dist/server/routes/utils.d.ts +3 -0
- package/dist/server/routes/utils.js +23 -0
- package/dist/server/serve-reaper.d.ts +135 -0
- package/dist/server/serve-reaper.js +491 -0
- package/dist/server/server.d.ts +49 -0
- package/dist/server/server.js +2171 -0
- package/dist/server/supervisor.d.ts +89 -0
- package/dist/server/supervisor.js +231 -0
- package/dist/server/system.d.ts +198 -0
- package/dist/server/system.js +480 -0
- package/dist/server/task-check.d.ts +45 -0
- package/dist/server/task-check.js +71 -0
- package/dist/server/task-transitions.d.ts +51 -0
- package/dist/server/task-transitions.js +78 -0
- package/dist/server/task-watchdog.d.ts +158 -0
- package/dist/server/task-watchdog.js +681 -0
- package/dist/server/test-run.d.ts +28 -0
- package/dist/server/test-run.js +81 -0
- package/dist/server/watcher.d.ts +21 -0
- package/dist/server/watcher.js +194 -0
- package/dist/server/write.d.ts +94 -0
- package/dist/server/write.js +345 -0
- package/dist/ui/assets/AgentsView-CF4wRvHh.css +1 -0
- package/dist/ui/assets/AgentsView-D-myL-kA.js +1 -0
- package/dist/ui/assets/ContextView-D3TC-RSp.css +1 -0
- package/dist/ui/assets/ContextView-DsOqAdyE.js +4 -0
- package/dist/ui/assets/DashboardView-BOhsIJqO.js +1 -0
- package/dist/ui/assets/DashboardView-Ck9o0RP2.css +1 -0
- package/dist/ui/assets/DeploymentsView--iIUVfEH.css +1 -0
- package/dist/ui/assets/DeploymentsView-DXu_FYR2.js +1 -0
- package/dist/ui/assets/InputsView-24HVbVPk.js +1 -0
- package/dist/ui/assets/InputsView-BLb7GHtx.css +1 -0
- package/dist/ui/assets/LoginView-6QlGg6cx.css +1 -0
- package/dist/ui/assets/LoginView-BqupI0Lt.js +1 -0
- package/dist/ui/assets/ReleasesView-BEG1A0YH.css +1 -0
- package/dist/ui/assets/ReleasesView-CAgMuxpe.js +3 -0
- package/dist/ui/assets/SettingsView-CA-NYSMK.css +1 -0
- package/dist/ui/assets/SettingsView-O3iZSnjg.js +3 -0
- package/dist/ui/assets/WorkView-D8Tl6qwy.js +1 -0
- package/dist/ui/assets/WorkView-DMYL2s3W.css +1 -0
- package/dist/ui/assets/abnfDiagram-VCTEODGH-J5lkSnEE.js +1 -0
- package/dist/ui/assets/arc-BHZnmhhM.js +1 -0
- package/dist/ui/assets/architecture-7GRP2DOG-pLdFt-xS.js +1 -0
- package/dist/ui/assets/architectureDiagram-5GKGNRK7-C3k89uKX.js +36 -0
- package/dist/ui/assets/array-BifhSqXX.js +1 -0
- package/dist/ui/assets/blockDiagram-I7D4REHJ-AWNoBzop.js +129 -0
- package/dist/ui/assets/button-lbikqSph.js +1 -0
- package/dist/ui/assets/c4Diagram-7LVT6UL2-DKG1D5gd.js +38 -0
- package/dist/ui/assets/channel-gA4qBv3A.js +1 -0
- package/dist/ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/ui/assets/chunk-4HAMMTFA-D1YR8lF_.js +62 -0
- package/dist/ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dist/ui/assets/chunk-75Z2AOVW-5GuBxfsL.js +2 -0
- package/dist/ui/assets/chunk-DU6HZSFF-DfdNNqwa.js +127 -0
- package/dist/ui/assets/chunk-F27PBJKO-BPZzinQ4.js +1 -0
- package/dist/ui/assets/chunk-FOHPRMQF-l2z4K2jD.js +161 -0
- package/dist/ui/assets/chunk-GMAD6QVW-DGWvSKj5.js +72 -0
- package/dist/ui/assets/chunk-GVQU2GXP-D6Aw75wJ.js +1 -0
- package/dist/ui/assets/chunk-IMKFNOWR-B8mMBKCW.js +231 -0
- package/dist/ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dist/ui/assets/chunk-L3NEJ4N5-D5OJuXYE.js +1 -0
- package/dist/ui/assets/chunk-OSK3NFVY-B7r9w2UW.js +10 -0
- package/dist/ui/assets/chunk-P2QGCYS3-C74X4AQX.js +1 -0
- package/dist/ui/assets/chunk-POPQ4Y6H-C5_u1gUS.js +1 -0
- package/dist/ui/assets/chunk-PWAF6VOD-DfoTLtxn.js +1 -0
- package/dist/ui/assets/chunk-SHT3W25Y-D40m2eqz.js +168 -0
- package/dist/ui/assets/chunk-SVP7TREG-euxi0Kap.js +88 -0
- package/dist/ui/assets/chunk-TICWLB2K-fkdxngzN.js +206 -0
- package/dist/ui/assets/chunk-XXDRQBXY-DFBUG-OT.js +1 -0
- package/dist/ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dist/ui/assets/classDiagram-ZZMXUADV-BzW8bq2P.js +1 -0
- package/dist/ui/assets/classDiagram-v2-VYDZK3BY-BzW8bq2P.js +1 -0
- package/dist/ui/assets/cose-bilkent-JH36ORCC-BTcgMon1.js +1 -0
- package/dist/ui/assets/cynefin-OW5HDTMX-B5m_COsS.js +1 -0
- package/dist/ui/assets/cynefinDiagram-5FMLGOSQ-C-AG6CGe.js +62 -0
- package/dist/ui/assets/cytoscape.esm-FBntH-9t.js +321 -0
- package/dist/ui/assets/dagre-GXQ25YYZ-DDfDnp8L.js +4 -0
- package/dist/ui/assets/dagre-PrKaheQc.js +1 -0
- package/dist/ui/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/ui/assets/diagram-S7CK7UJ4-DjQZAlEY.js +30 -0
- package/dist/ui/assets/diagram-UQ7AKVKN-DAZ9yp1B.js +41 -0
- package/dist/ui/assets/diagram-VSXAHHWV-BWepUKJD.js +3 -0
- package/dist/ui/assets/diagram-VX7I27RA-EcV1J81b.js +24 -0
- package/dist/ui/assets/diagram-Z3DM3KII-Dbz7XU_K.js +24 -0
- package/dist/ui/assets/dist-DTg6UBE_.js +1 -0
- package/dist/ui/assets/ebnfDiagram-PWID7BFC-BUyQw8Mm.js +1 -0
- package/dist/ui/assets/erDiagram-RLTQ6QDP-WVf8ukKt.js +99 -0
- package/dist/ui/assets/eventmodeling-NTZA5JFV-DHqyLOAn.js +1 -0
- package/dist/ui/assets/flowDiagram-HODETNUW-CYk_ze5K.js +1 -0
- package/dist/ui/assets/ganttDiagram-EL5Y4UJY-rB4MZuB-.js +292 -0
- package/dist/ui/assets/gitGraph-4MIJSDKK-BOTWM8dY.js +1 -0
- package/dist/ui/assets/gitGraphDiagram-WWUBYQGX-eYscuhtP.js +106 -0
- package/dist/ui/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/ui/assets/index-BACBfnga.css +1 -0
- package/dist/ui/assets/index-BOQ-ust6.js +26 -0
- package/dist/ui/assets/info-A6RAGUB7-_67wOORe.js +1 -0
- package/dist/ui/assets/infoDiagram-27XIBGKW-XVXbddTA.js +2 -0
- package/dist/ui/assets/init-C-OQMol4.js +1 -0
- package/dist/ui/assets/ishikawaDiagram-5VMMS53U-ClLNiEru.js +70 -0
- package/dist/ui/assets/journeyDiagram-3NMN7TZE-NRawjR6i.js +139 -0
- package/dist/ui/assets/kanban-definition-UXKFOSKX-BgSzBfIx.js +89 -0
- package/dist/ui/assets/katex-DolUETbr.js +257 -0
- package/dist/ui/assets/line-KAyfuul4.js +1 -0
- package/dist/ui/assets/linear-cCCPOpu7.js +1 -0
- package/dist/ui/assets/mermaid-parser.core-CgXZk1iN.js +7 -0
- package/dist/ui/assets/mermaid.core-BUUkChHW.js +44 -0
- package/dist/ui/assets/mindmap-definition-YA3MSWOX-Cpn7vW4r.js +96 -0
- package/dist/ui/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/ui/assets/packet-AYTQ26CC-0lZwM-d6.js +1 -0
- package/dist/ui/assets/path-COt_16Va.js +1 -0
- package/dist/ui/assets/pegDiagram-XKGWAZYB-impy0inn.js +1 -0
- package/dist/ui/assets/pie-WAS4IAKB-CqfdGHYg.js +1 -0
- package/dist/ui/assets/pieDiagram-E7YTZNPT-CxjL4I1h.js +39 -0
- package/dist/ui/assets/quadrantDiagram-AXDQQJYC-Cjd1QMIe.js +7 -0
- package/dist/ui/assets/radar-RG4KPBEZ-CRkQIwiv.js +1 -0
- package/dist/ui/assets/railroad-74A4TZTK-i67XSWTB.js +1 -0
- package/dist/ui/assets/railroad-abnf-HS5TGJTU-Cv9m5aS3.js +1 -0
- package/dist/ui/assets/railroad-ebnf-LZEXJU2U-C5ZIn-Jh.js +1 -0
- package/dist/ui/assets/railroad-peg-WCYAUIDC-CGra9Oq-.js +1 -0
- package/dist/ui/assets/railroadDiagram-O6MQD6OU-_pK0qyaY.js +1 -0
- package/dist/ui/assets/releases-B0WNV-SY.js +1 -0
- package/dist/ui/assets/repoos-cto-square.webp +0 -0
- package/dist/ui/assets/repoos-helpful-pm-square.webp +0 -0
- package/dist/ui/assets/repoos-orchestrator-square.webp +0 -0
- package/dist/ui/assets/repoos-performance-agent-square.webp +0 -0
- package/dist/ui/assets/repoos-ross-from-friends-square.webp +0 -0
- package/dist/ui/assets/repoos-tech-debt-agent-square.webp +0 -0
- package/dist/ui/assets/requirementDiagram-BXWQKSXE-CRrFX-yw.js +84 -0
- package/dist/ui/assets/rough.esm-By172zw-.js +1 -0
- package/dist/ui/assets/sankeyDiagram-P5KCCOFB-X_xFRj9E.js +40 -0
- package/dist/ui/assets/sequenceDiagram-WJ2MYXX4-Byb49lKf.js +162 -0
- package/dist/ui/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- package/dist/ui/assets/src-BH-TyZbA.js +1 -0
- package/dist/ui/assets/stateDiagram-D77RDMKH-CzaPrqYF.js +1 -0
- package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-BCXckz7K.js +1 -0
- package/dist/ui/assets/swimlanes-42K2YHIH-Cx_1lnqr.js +1 -0
- package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-54qsKi6A.js +8 -0
- package/dist/ui/assets/timeline-definition-24CTP7MA-B1IbZPQ-.js +120 -0
- package/dist/ui/assets/treeView-Q6P3EWNA-DFWhVE3d.js +1 -0
- package/dist/ui/assets/treemap-WGGIJYW6-BmbYPThn.js +1 -0
- package/dist/ui/assets/vendor-ui-DPm7zLxi.js +3 -0
- package/dist/ui/assets/vendor-vue-CobgyEJp.js +1 -0
- package/dist/ui/assets/vennDiagram-4TSXK5OY-Ae2AtRpt.js +34 -0
- package/dist/ui/assets/wardley-WFR3VGLG-BtD4X4tL.js +1 -0
- package/dist/ui/assets/wardleyDiagram-VM6X3IG4-B3YN2fTd.js +78 -0
- package/dist/ui/assets/xychartDiagram-S5SC5T6Z-CIzJfMKA.js +7 -0
- package/dist/ui/favicon.svg +11 -0
- package/dist/ui/index.html +49 -0
- package/dist/ui/sw.js +48 -0
- package/package.json +91 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth persistence layer. Wraps the SQLite database with typed operations
|
|
3
|
+
* for users, sessions, OTP challenges, and audit log entries.
|
|
4
|
+
*
|
|
5
|
+
* All OTP codes and session tokens are stored hashed — plaintext never
|
|
6
|
+
* touches disk.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { hashSessionToken, randomHex } from "./auth.js";
|
|
12
|
+
let Database;
|
|
13
|
+
let dbAvailable = false;
|
|
14
|
+
let sqliteLoadAttempted = false;
|
|
15
|
+
const runtimeRequire = createRequire(import.meta.url);
|
|
16
|
+
/**
|
|
17
|
+
* Load SQLite on first actual use, not at module import time — see the
|
|
18
|
+
* matching comment in db.ts for why (this module is on the same
|
|
19
|
+
* nearly-every-command import path, so requiring node:sqlite eagerly here
|
|
20
|
+
* would print its ExperimentalWarning before the CLI even dispatches).
|
|
21
|
+
*/
|
|
22
|
+
function loadSqlite() {
|
|
23
|
+
if (sqliteLoadAttempted)
|
|
24
|
+
return;
|
|
25
|
+
sqliteLoadAttempted = true;
|
|
26
|
+
try {
|
|
27
|
+
const g = globalThis;
|
|
28
|
+
if (g.Bun && typeof g.Bun === "object") {
|
|
29
|
+
try {
|
|
30
|
+
const sqlite = runtimeRequire("bun:sqlite");
|
|
31
|
+
Database = sqlite.Database;
|
|
32
|
+
dbAvailable = true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/* fall through */
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!dbAvailable) {
|
|
39
|
+
try {
|
|
40
|
+
const sqlite = runtimeRequire("node:sqlite");
|
|
41
|
+
Database = sqlite.DatabaseSync;
|
|
42
|
+
dbAvailable = true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* unavailable */
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* degrade */
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Schema migration
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Safety-net DDL — identical to db.ts migration v2 but ensures auth tables
|
|
57
|
+
// exist even if the auth-store is opened before the main DB migration runs.
|
|
58
|
+
// All statements use CREATE TABLE IF NOT EXISTS, so this is idempotent.
|
|
59
|
+
const AUTH_MIGRATION = `
|
|
60
|
+
CREATE TABLE IF NOT EXISTS auth_users (
|
|
61
|
+
email TEXT PRIMARY KEY,
|
|
62
|
+
role TEXT NOT NULL DEFAULT 'member',
|
|
63
|
+
display_name TEXT,
|
|
64
|
+
auth_source TEXT NOT NULL DEFAULT 'otp',
|
|
65
|
+
added_by TEXT,
|
|
66
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
67
|
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
CREATE TABLE IF NOT EXISTS auth_sessions (
|
|
71
|
+
session_id TEXT PRIMARY KEY,
|
|
72
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
73
|
+
email TEXT NOT NULL,
|
|
74
|
+
role TEXT NOT NULL,
|
|
75
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
76
|
+
expires_at TEXT NOT NULL,
|
|
77
|
+
revoked_at TEXT
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_auth_sessions_token_hash ON auth_sessions(token_hash);
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_auth_sessions_email ON auth_sessions(email);
|
|
82
|
+
|
|
83
|
+
CREATE TABLE IF NOT EXISTS auth_otp (
|
|
84
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
85
|
+
email TEXT NOT NULL,
|
|
86
|
+
code_hash TEXT NOT NULL,
|
|
87
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
88
|
+
expires_at TEXT NOT NULL,
|
|
89
|
+
used_at TEXT,
|
|
90
|
+
source_ip TEXT
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_auth_otp_email ON auth_otp(email);
|
|
94
|
+
|
|
95
|
+
CREATE TABLE IF NOT EXISTS auth_audit_log (
|
|
96
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
97
|
+
action TEXT NOT NULL,
|
|
98
|
+
target_email TEXT,
|
|
99
|
+
actor_email TEXT,
|
|
100
|
+
details TEXT,
|
|
101
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
102
|
+
);
|
|
103
|
+
`;
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Store
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
export class AuthStore {
|
|
108
|
+
db;
|
|
109
|
+
available;
|
|
110
|
+
constructor(repoRoot) {
|
|
111
|
+
this.available = false;
|
|
112
|
+
loadSqlite();
|
|
113
|
+
if (!dbAvailable || !Database)
|
|
114
|
+
return;
|
|
115
|
+
try {
|
|
116
|
+
const cacheDir = join(repoRoot, ".repoos");
|
|
117
|
+
if (!existsSync(cacheDir))
|
|
118
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
119
|
+
const dbPath = join(cacheDir, "repoos.db");
|
|
120
|
+
this.db = new Database(dbPath);
|
|
121
|
+
this.db.exec("PRAGMA journal_mode=WAL");
|
|
122
|
+
this.db.exec("PRAGMA synchronous=NORMAL");
|
|
123
|
+
this.db.exec(AUTH_MIGRATION);
|
|
124
|
+
this.available = true;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
this.available = false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
isAvailable() {
|
|
131
|
+
return this.available;
|
|
132
|
+
}
|
|
133
|
+
// ---- Users ----
|
|
134
|
+
getUser(email) {
|
|
135
|
+
if (!this.available)
|
|
136
|
+
return null;
|
|
137
|
+
try {
|
|
138
|
+
const rows = this.db.prepare("SELECT * FROM auth_users WHERE email = ?").all(email);
|
|
139
|
+
return rows.length > 0 ? this.toUser(rows[0]) : null;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
listUsers() {
|
|
146
|
+
if (!this.available)
|
|
147
|
+
return [];
|
|
148
|
+
try {
|
|
149
|
+
const rows = this.db.prepare("SELECT * FROM auth_users ORDER BY created_at").all();
|
|
150
|
+
return rows.map((r) => this.toUser(r));
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
upsertUser(email, role, addedBy, displayName) {
|
|
157
|
+
if (!this.available)
|
|
158
|
+
return;
|
|
159
|
+
try {
|
|
160
|
+
this.db
|
|
161
|
+
.prepare(`
|
|
162
|
+
INSERT INTO auth_users (email, role, display_name, added_by, updated_at)
|
|
163
|
+
VALUES (?, ?, ?, ?, datetime('now'))
|
|
164
|
+
ON CONFLICT(email) DO UPDATE SET
|
|
165
|
+
role = excluded.role,
|
|
166
|
+
display_name = COALESCE(excluded.display_name, display_name),
|
|
167
|
+
updated_at = datetime('now')
|
|
168
|
+
`)
|
|
169
|
+
.run(email, role, displayName ?? null, addedBy);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
/* ignore */
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
deleteUser(email) {
|
|
176
|
+
if (!this.available)
|
|
177
|
+
return false;
|
|
178
|
+
try {
|
|
179
|
+
this.db.prepare("DELETE FROM auth_users WHERE email = ?").run(email);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
getAdminCount() {
|
|
187
|
+
if (!this.available)
|
|
188
|
+
return 0;
|
|
189
|
+
try {
|
|
190
|
+
const rows = this.db
|
|
191
|
+
.prepare("SELECT COUNT(*) as cnt FROM auth_users WHERE role = 'admin'")
|
|
192
|
+
.all();
|
|
193
|
+
return rows[0]?.cnt ?? 0;
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// ---- Sessions ----
|
|
200
|
+
createSession(email, role, maxAgeSeconds) {
|
|
201
|
+
if (!this.available)
|
|
202
|
+
return "";
|
|
203
|
+
const token = randomHex(32);
|
|
204
|
+
const tokenHash = hashSessionToken(token);
|
|
205
|
+
const sessionId = randomHex(16);
|
|
206
|
+
const now = new Date();
|
|
207
|
+
const expires = new Date(now.getTime() + maxAgeSeconds * 1000);
|
|
208
|
+
try {
|
|
209
|
+
this.db
|
|
210
|
+
.prepare(`
|
|
211
|
+
INSERT INTO auth_sessions (session_id, token_hash, email, role, created_at, expires_at)
|
|
212
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
213
|
+
`)
|
|
214
|
+
.run(sessionId, tokenHash, email, role, now.toISOString(), expires.toISOString());
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return "";
|
|
218
|
+
}
|
|
219
|
+
return token;
|
|
220
|
+
}
|
|
221
|
+
getSession(token) {
|
|
222
|
+
if (!this.available)
|
|
223
|
+
return null;
|
|
224
|
+
const tokenHash = hashSessionToken(token);
|
|
225
|
+
try {
|
|
226
|
+
const now = new Date().toISOString();
|
|
227
|
+
const rows = this.db
|
|
228
|
+
.prepare(`
|
|
229
|
+
SELECT * FROM auth_sessions
|
|
230
|
+
WHERE token_hash = ? AND revoked_at IS NULL AND expires_at > ?
|
|
231
|
+
`)
|
|
232
|
+
.all(tokenHash, now);
|
|
233
|
+
return rows.length > 0 ? this.toSession(rows[0]) : null;
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
revokeSession(token) {
|
|
240
|
+
if (!this.available)
|
|
241
|
+
return false;
|
|
242
|
+
const tokenHash = hashSessionToken(token);
|
|
243
|
+
try {
|
|
244
|
+
this.db
|
|
245
|
+
.prepare(`
|
|
246
|
+
UPDATE auth_sessions SET revoked_at = datetime('now') WHERE token_hash = ? AND revoked_at IS NULL
|
|
247
|
+
`)
|
|
248
|
+
.run(tokenHash);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
revokeAllSessions(email) {
|
|
256
|
+
if (!this.available)
|
|
257
|
+
return 0;
|
|
258
|
+
try {
|
|
259
|
+
const result = this.db
|
|
260
|
+
.prepare(`
|
|
261
|
+
UPDATE auth_sessions SET revoked_at = datetime('now')
|
|
262
|
+
WHERE email = ? AND revoked_at IS NULL
|
|
263
|
+
`)
|
|
264
|
+
.run(email);
|
|
265
|
+
return result.changes ?? 0;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** Update the role column on all active sessions for a user. */
|
|
272
|
+
updateSessionRoles(email, newRole) {
|
|
273
|
+
if (!this.available)
|
|
274
|
+
return 0;
|
|
275
|
+
try {
|
|
276
|
+
const result = this.db
|
|
277
|
+
.prepare(`
|
|
278
|
+
UPDATE auth_sessions SET role = ?
|
|
279
|
+
WHERE email = ? AND revoked_at IS NULL
|
|
280
|
+
`)
|
|
281
|
+
.run(newRole, email);
|
|
282
|
+
return result.changes ?? 0;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
cleanupExpiredSessions() {
|
|
289
|
+
if (!this.available)
|
|
290
|
+
return 0;
|
|
291
|
+
try {
|
|
292
|
+
const now = new Date().toISOString();
|
|
293
|
+
const result = this.db
|
|
294
|
+
.prepare(`
|
|
295
|
+
DELETE FROM auth_sessions WHERE expires_at < ?
|
|
296
|
+
`)
|
|
297
|
+
.run(now);
|
|
298
|
+
return result.changes ?? 0;
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// ---- OTP ----
|
|
305
|
+
/** Store a hashed OTP challenge. Returns the challenge id. */
|
|
306
|
+
createOtpChallenge(email, codeHash, ttlSeconds, sourceIp) {
|
|
307
|
+
if (!this.available)
|
|
308
|
+
return -1;
|
|
309
|
+
const now = new Date();
|
|
310
|
+
const expires = new Date(now.getTime() + ttlSeconds * 1000);
|
|
311
|
+
try {
|
|
312
|
+
const result = this.db
|
|
313
|
+
.prepare(`
|
|
314
|
+
INSERT INTO auth_otp (email, code_hash, created_at, expires_at, source_ip)
|
|
315
|
+
VALUES (?, ?, ?, ?, ?)
|
|
316
|
+
`)
|
|
317
|
+
.run(email, codeHash, now.toISOString(), expires.toISOString(), sourceIp);
|
|
318
|
+
return result.lastInsertRowid ?? -1;
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return -1;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/** Find a valid, unused OTP for this email. Returns null if none found or expired. */
|
|
325
|
+
findValidOtp(email, codeHash) {
|
|
326
|
+
if (!this.available)
|
|
327
|
+
return null;
|
|
328
|
+
const now = new Date().toISOString();
|
|
329
|
+
try {
|
|
330
|
+
const rows = this.db
|
|
331
|
+
.prepare(`
|
|
332
|
+
SELECT * FROM auth_otp
|
|
333
|
+
WHERE email = ? AND code_hash = ? AND used_at IS NULL AND expires_at > ?
|
|
334
|
+
ORDER BY created_at DESC LIMIT 1
|
|
335
|
+
`)
|
|
336
|
+
.all(email, codeHash, now);
|
|
337
|
+
return rows.length > 0 ? this.toOtp(rows[0]) : null;
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/** Mark an OTP as used. */
|
|
344
|
+
markOtpUsed(otpId) {
|
|
345
|
+
if (!this.available)
|
|
346
|
+
return;
|
|
347
|
+
try {
|
|
348
|
+
this.db
|
|
349
|
+
.prepare(`
|
|
350
|
+
UPDATE auth_otp SET used_at = datetime('now') WHERE id = ?
|
|
351
|
+
`)
|
|
352
|
+
.run(otpId);
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
/* ignore */
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/** Count OTP requests for this email in the given time window. */
|
|
359
|
+
countRecentOtpRequests(email, windowSeconds) {
|
|
360
|
+
if (!this.available)
|
|
361
|
+
return 0;
|
|
362
|
+
try {
|
|
363
|
+
const cutoff = new Date(Date.now() - windowSeconds * 1000).toISOString();
|
|
364
|
+
const rows = this.db
|
|
365
|
+
.prepare(`
|
|
366
|
+
SELECT COUNT(*) as cnt FROM auth_otp
|
|
367
|
+
WHERE email = ? AND created_at > ?
|
|
368
|
+
`)
|
|
369
|
+
.all(email, cutoff);
|
|
370
|
+
return rows[0]?.cnt ?? 0;
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/** Count failed verify attempts for this email in the given time window. */
|
|
377
|
+
countFailedVerifyAttempts(email, windowSeconds) {
|
|
378
|
+
if (!this.available)
|
|
379
|
+
return 0;
|
|
380
|
+
try {
|
|
381
|
+
const cutoff = new Date(Date.now() - windowSeconds * 1000).toISOString();
|
|
382
|
+
const rows = this.db
|
|
383
|
+
.prepare(`
|
|
384
|
+
SELECT COUNT(*) as cnt FROM auth_otp
|
|
385
|
+
WHERE email = ? AND created_at > ? AND used_at IS NOT NULL AND code_hash != ''
|
|
386
|
+
`)
|
|
387
|
+
.all(email, cutoff);
|
|
388
|
+
return rows[0]?.cnt ?? 0;
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
cleanupExpiredOtps() {
|
|
395
|
+
if (!this.available)
|
|
396
|
+
return 0;
|
|
397
|
+
try {
|
|
398
|
+
const now = new Date().toISOString();
|
|
399
|
+
const result = this.db
|
|
400
|
+
.prepare(`
|
|
401
|
+
DELETE FROM auth_otp WHERE expires_at < ?
|
|
402
|
+
`)
|
|
403
|
+
.run(now);
|
|
404
|
+
return result.changes ?? 0;
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return 0;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// ---- Audit Log ----
|
|
411
|
+
logAudit(action, targetEmail, actorEmail, details) {
|
|
412
|
+
if (!this.available)
|
|
413
|
+
return;
|
|
414
|
+
try {
|
|
415
|
+
this.db
|
|
416
|
+
.prepare(`
|
|
417
|
+
INSERT INTO auth_audit_log (action, target_email, actor_email, details, created_at)
|
|
418
|
+
VALUES (?, ?, ?, ?, datetime('now'))
|
|
419
|
+
`)
|
|
420
|
+
.run(action, targetEmail, actorEmail, details ?? null);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
/* ignore */
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
getAuditLog(limit = 50) {
|
|
427
|
+
if (!this.available)
|
|
428
|
+
return [];
|
|
429
|
+
try {
|
|
430
|
+
return this.db
|
|
431
|
+
.prepare(`
|
|
432
|
+
SELECT * FROM auth_audit_log ORDER BY created_at DESC LIMIT ?
|
|
433
|
+
`)
|
|
434
|
+
.all(limit)
|
|
435
|
+
.map((r) => this.toAuditEntry(r));
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
return [];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// ---- Row mappers ----
|
|
442
|
+
toUser(row) {
|
|
443
|
+
return {
|
|
444
|
+
email: row.email,
|
|
445
|
+
role: row.role,
|
|
446
|
+
displayName: row.display_name,
|
|
447
|
+
authSource: row.auth_source,
|
|
448
|
+
addedBy: row.added_by,
|
|
449
|
+
createdAt: row.created_at,
|
|
450
|
+
updatedAt: row.updated_at,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
toSession(row) {
|
|
454
|
+
return {
|
|
455
|
+
sessionId: row.session_id,
|
|
456
|
+
tokenHash: row.token_hash,
|
|
457
|
+
email: row.email,
|
|
458
|
+
role: row.role,
|
|
459
|
+
createdAt: row.created_at,
|
|
460
|
+
expiresAt: row.expires_at,
|
|
461
|
+
revokedAt: row.revoked_at,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
toOtp(row) {
|
|
465
|
+
return {
|
|
466
|
+
id: row.id,
|
|
467
|
+
email: row.email,
|
|
468
|
+
codeHash: row.code_hash,
|
|
469
|
+
createdAt: row.created_at,
|
|
470
|
+
expiresAt: row.expires_at,
|
|
471
|
+
usedAt: row.used_at,
|
|
472
|
+
sourceIp: row.source_ip,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
toAuditEntry(row) {
|
|
476
|
+
return {
|
|
477
|
+
id: row.id,
|
|
478
|
+
action: row.action,
|
|
479
|
+
targetEmail: row.target_email,
|
|
480
|
+
actorEmail: row.actor_email,
|
|
481
|
+
details: row.details,
|
|
482
|
+
createdAt: row.created_at,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
close() {
|
|
486
|
+
if (this.db) {
|
|
487
|
+
try {
|
|
488
|
+
this.db.close();
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
/* ignore */
|
|
492
|
+
}
|
|
493
|
+
this.db = null;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// ---------------------------------------------------------------------------
|
|
498
|
+
// Singleton + periodic cleanup
|
|
499
|
+
// ---------------------------------------------------------------------------
|
|
500
|
+
let authStoreInstance = null;
|
|
501
|
+
let cleanupTimer = null;
|
|
502
|
+
const CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
503
|
+
export function getAuthStore(repoRoot) {
|
|
504
|
+
if (!authStoreInstance) {
|
|
505
|
+
authStoreInstance = new AuthStore(repoRoot);
|
|
506
|
+
}
|
|
507
|
+
// Start periodic cleanup on first access
|
|
508
|
+
if (!cleanupTimer && authStoreInstance.isAvailable()) {
|
|
509
|
+
cleanupTimer = setInterval(() => {
|
|
510
|
+
if (authStoreInstance?.isAvailable()) {
|
|
511
|
+
authStoreInstance.cleanupExpiredSessions();
|
|
512
|
+
authStoreInstance.cleanupExpiredOtps();
|
|
513
|
+
}
|
|
514
|
+
}, CLEANUP_INTERVAL_MS);
|
|
515
|
+
// Allow the process to exit even if the timer is running
|
|
516
|
+
if (cleanupTimer.unref)
|
|
517
|
+
cleanupTimer.unref();
|
|
518
|
+
}
|
|
519
|
+
return authStoreInstance.isAvailable() ? authStoreInstance : null;
|
|
520
|
+
}
|
|
521
|
+
export function resetAuthStoreInstance() {
|
|
522
|
+
if (cleanupTimer) {
|
|
523
|
+
clearInterval(cleanupTimer);
|
|
524
|
+
cleanupTimer = null;
|
|
525
|
+
}
|
|
526
|
+
if (authStoreInstance) {
|
|
527
|
+
authStoreInstance.close();
|
|
528
|
+
}
|
|
529
|
+
authStoreInstance = null;
|
|
530
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication module for RepoOS.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in, disabled by default. When enabled, protects all UI, API, and SSE
|
|
5
|
+
* routes with server-side sessions and HttpOnly cookies. Supports email OTP
|
|
6
|
+
* (via Resend) and Google OAuth as login methods.
|
|
7
|
+
*
|
|
8
|
+
* Zero runtime dependencies — native crypto, fetch, and SQLite.
|
|
9
|
+
*/
|
|
10
|
+
export type AuthRole = "admin" | "member";
|
|
11
|
+
export interface AuthEmailProvider {
|
|
12
|
+
type: "resend";
|
|
13
|
+
apiKey: string;
|
|
14
|
+
fromAddress: string;
|
|
15
|
+
/** Display name shown alongside fromAddress (e.g. "RepoOS"). Optional —
|
|
16
|
+
* without it, mail clients fall back to showing the address's local part. */
|
|
17
|
+
fromName?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface AuthGoogleConfig {
|
|
20
|
+
clientId: string;
|
|
21
|
+
clientSecret: string;
|
|
22
|
+
}
|
|
23
|
+
export interface AuthConfig {
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
/** Server-side session secret (auto-generated if not provided). Never exposed to browser. */
|
|
26
|
+
sessionSecret?: string;
|
|
27
|
+
/** Session lifetime in seconds. Default 7 days. */
|
|
28
|
+
sessionMaxAge?: number;
|
|
29
|
+
/** Email provider config (required when auth enabled). */
|
|
30
|
+
emailProvider?: AuthEmailProvider;
|
|
31
|
+
/** Google OAuth config (optional). */
|
|
32
|
+
google?: AuthGoogleConfig;
|
|
33
|
+
/** Bootstrap admin email (required on first enable). */
|
|
34
|
+
bootstrapAdmin?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Generate a cryptographically random hex string. */
|
|
37
|
+
export declare function randomHex(bytes?: number): string;
|
|
38
|
+
/** Generate a 6-digit OTP code. */
|
|
39
|
+
export declare function generateOtp(): string;
|
|
40
|
+
/** SHA-256 hash a string (for OTP storage). */
|
|
41
|
+
export declare function hashOtp(code: string): string;
|
|
42
|
+
/** Constant-time string comparison. */
|
|
43
|
+
export declare function timingSafeEqualStr(a: string, b: string): boolean;
|
|
44
|
+
/** Generate a random session token. */
|
|
45
|
+
export declare function generateSessionToken(): string;
|
|
46
|
+
/** Hash a session token for storage. */
|
|
47
|
+
export declare function hashSessionToken(token: string): string;
|
|
48
|
+
/** Generate a CSRF-like state parameter for OAuth. */
|
|
49
|
+
export declare function generateOAuthState(): string;
|
|
50
|
+
/** Generate a PKCE code verifier (43-128 chars, unreserved chars only). */
|
|
51
|
+
export declare function generatePkceVerifier(): string;
|
|
52
|
+
/** SHA-256 hash for PKCE code challenge, base64url encoded. */
|
|
53
|
+
export declare function pkceChallenge(verifier: string): string;
|
|
54
|
+
export declare const SESSION_COOKIE_NAME = "repoos_session";
|
|
55
|
+
export declare const DEFAULT_SESSION_MAX_AGE: number;
|
|
56
|
+
export declare function buildSessionCookie(token: string, maxAge: number, secure: boolean): string;
|
|
57
|
+
export declare function parseCookies(header: string | undefined): Record<string, string>;
|
|
58
|
+
export declare function isValidEmail(email: string): boolean;
|
|
59
|
+
export interface RateLimitEntry {
|
|
60
|
+
count: number;
|
|
61
|
+
resetAt: number;
|
|
62
|
+
}
|
|
63
|
+
export declare class RateLimiter {
|
|
64
|
+
private entries;
|
|
65
|
+
private windowMs;
|
|
66
|
+
private maxAttempts;
|
|
67
|
+
constructor(windowMs: number, maxAttempts: number);
|
|
68
|
+
/** Returns true if the request should be allowed. */
|
|
69
|
+
tryAcquire(key: string): boolean;
|
|
70
|
+
/** Reset a key (e.g. after successful OTP verify). */
|
|
71
|
+
reset(key: string): void;
|
|
72
|
+
}
|
|
73
|
+
export declare const otpRequestLimiter: RateLimiter;
|
|
74
|
+
export declare const otpVerifyLimiter: RateLimiter;
|
|
75
|
+
export declare const globalRateLimiter: RateLimiter;
|