theokit 0.2.4 → 0.4.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.
- package/dist/{actions-virtual-module-HWNTIEPI.js → actions-virtual-module-IY46EEEX.js} +2 -2
- package/dist/{actions-virtual-module-SEIPACG5.js → actions-virtual-module-XNHDNCZ6.js} +2 -2
- package/dist/add-2ZFFGGE4.js +0 -0
- package/dist/{app-typed-client-6GC6VWXS.js → app-typed-client-OUJO3V5J.js} +10 -5
- package/dist/{app-typed-client-6GC6VWXS.js.map → app-typed-client-OUJO3V5J.js.map} +1 -1
- package/dist/{app-typed-client-US2ZXBEC.js → app-typed-client-YOMWSA3F.js} +11 -6
- package/dist/{app-typed-client-US2ZXBEC.js.map → app-typed-client-YOMWSA3F.js.map} +1 -1
- package/dist/audit-log-BQWM5YLG.d.ts +60 -0
- package/dist/{aws-lambda-XYCGZH3I.js → aws-lambda-GKSTX6HD.js} +3 -3
- package/dist/body-parser-web-MUWBKZ3F.js +70 -0
- package/dist/body-parser-web-MUWBKZ3F.js.map +1 -0
- package/dist/broadcast-QOQGUNNK.js +0 -0
- package/dist/{build-MREW6MVS.js → build-D4J7XECU.js} +33 -24
- package/dist/build-D4J7XECU.js.map +1 -0
- package/dist/build-request-body-preview-II2235E6.js +0 -0
- package/dist/{bun-K73TQEWC.js → bun-ISHSNFIO.js} +3 -3
- package/dist/{check-PZR67OY5.js → check-NXYPLM6M.js} +2 -2
- package/dist/{chunk-WZ7CPVQB.js → chunk-27LWMYNK.js} +28 -24
- package/dist/chunk-27LWMYNK.js.map +1 -0
- package/dist/{chunk-5OKZY2VL.js → chunk-2F4FROEQ.js} +1695 -1522
- package/dist/chunk-2F4FROEQ.js.map +1 -0
- package/dist/chunk-4HBI7DHP.js +20 -0
- package/dist/chunk-4HBI7DHP.js.map +1 -0
- package/dist/{chunk-X4JAX2U3.js → chunk-4S2UCILN.js} +180 -125
- package/dist/chunk-4S2UCILN.js.map +1 -0
- package/dist/{chunk-C52GSJPF.js → chunk-4S6YHNG2.js} +11 -8
- package/dist/chunk-4S6YHNG2.js.map +1 -0
- package/dist/chunk-5ODOE6EF.js +0 -0
- package/dist/{chunk-KJVEJILQ.js → chunk-5PU65T5W.js} +9 -3
- package/dist/chunk-5PU65T5W.js.map +1 -0
- package/dist/chunk-5QW7IQQU.js +36 -0
- package/dist/chunk-5QW7IQQU.js.map +1 -0
- package/dist/chunk-5Z2727GV.js +1324 -0
- package/dist/chunk-5Z2727GV.js.map +1 -0
- package/dist/{chunk-YLBSSEHX.js → chunk-6NEXVBPY.js} +5 -15
- package/dist/chunk-6NEXVBPY.js.map +1 -0
- package/dist/chunk-7MQOHNHE.js +36 -0
- package/dist/chunk-7MQOHNHE.js.map +1 -0
- package/dist/{chunk-TPCT3MXV.js → chunk-ABVITXFH.js} +405 -272
- package/dist/chunk-ABVITXFH.js.map +1 -0
- package/dist/chunk-ASDXVRJD.js +185 -0
- package/dist/chunk-ASDXVRJD.js.map +1 -0
- package/dist/chunk-B3L3TJVF.js +406 -0
- package/dist/chunk-B3L3TJVF.js.map +1 -0
- package/dist/{chunk-PB4GR7EP.js → chunk-C3ZZ56YZ.js} +1 -18
- package/dist/chunk-C3ZZ56YZ.js.map +1 -0
- package/dist/{chunk-O7335ZGH.js → chunk-CG563V5M.js} +5 -3
- package/dist/chunk-CG563V5M.js.map +1 -0
- package/dist/{chunk-5OFPQK6N.js → chunk-COXLEZCN.js} +2 -1
- package/dist/chunk-COXLEZCN.js.map +1 -0
- package/dist/{chunk-O4BLVPML.js → chunk-DNMSV3R3.js} +22 -28
- package/dist/chunk-DNMSV3R3.js.map +1 -0
- package/dist/chunk-E3JH6YUM.js +1 -0
- package/dist/chunk-ESC54TAK.js +220 -0
- package/dist/chunk-ESC54TAK.js.map +1 -0
- package/dist/chunk-FI7MPTJW.js +77 -0
- package/dist/chunk-FI7MPTJW.js.map +1 -0
- package/dist/chunk-H4J2LECY.js +201 -0
- package/dist/chunk-H4J2LECY.js.map +1 -0
- package/dist/chunk-IAJ2JVEH.js +256 -0
- package/dist/chunk-IAJ2JVEH.js.map +1 -0
- package/dist/{chunk-F3TG5FJV.js → chunk-IEZCAT2I.js} +7 -1
- package/dist/{chunk-F3TG5FJV.js.map → chunk-IEZCAT2I.js.map} +1 -1
- package/dist/chunk-JHEAWR3L.js +1 -0
- package/dist/chunk-JQSKBMXP.js +17 -0
- package/dist/chunk-JQSKBMXP.js.map +1 -0
- package/dist/{chunk-4QTKSLTO.js → chunk-K4M64WD6.js} +9 -5
- package/dist/{chunk-4QTKSLTO.js.map → chunk-K4M64WD6.js.map} +1 -1
- package/dist/chunk-KI7OWQUX.js +293 -0
- package/dist/chunk-KI7OWQUX.js.map +1 -0
- package/dist/chunk-KT3MWNZG.js +0 -0
- package/dist/{chunk-ZJW5FFYJ.js → chunk-LC5PYNJP.js} +2 -2
- package/dist/{chunk-X2OLD264.js → chunk-M2EY7KDR.js} +1228 -1124
- package/dist/chunk-M2EY7KDR.js.map +1 -0
- package/dist/{chunk-YWWHK7R6.js → chunk-MR7OLIC6.js} +3 -3
- package/dist/chunk-NM4WF3XD.js +63 -0
- package/dist/chunk-NM4WF3XD.js.map +1 -0
- package/dist/chunk-NPMCKOX4.js +1 -0
- package/dist/{chunk-VRHXOKRP.js → chunk-NZXH2LQQ.js} +86 -83
- package/dist/chunk-NZXH2LQQ.js.map +1 -0
- package/dist/{chunk-2BQYEBCK.js → chunk-OLPB36O2.js} +87 -5
- package/dist/chunk-OLPB36O2.js.map +1 -0
- package/dist/chunk-PIVX3DYW.js +142 -0
- package/dist/chunk-PIVX3DYW.js.map +1 -0
- package/dist/{chunk-7TOMTMHT.js → chunk-R7OC6UDK.js} +2 -1
- package/dist/chunk-R7OC6UDK.js.map +1 -0
- package/dist/chunk-RESN62GB.js +269 -0
- package/dist/chunk-RESN62GB.js.map +1 -0
- package/dist/{chunk-64JI5LTO.js → chunk-RMU7EBRO.js} +2 -2
- package/dist/chunk-RMU7EBRO.js.map +1 -0
- package/dist/chunk-TQYSPYKU.js +131 -0
- package/dist/chunk-TQYSPYKU.js.map +1 -0
- package/dist/chunk-TSLSIRBR.js +107 -0
- package/dist/chunk-TSLSIRBR.js.map +1 -0
- package/dist/{chunk-XP7YXRHO.js → chunk-U6TPSW4L.js} +37 -5
- package/dist/chunk-U6TPSW4L.js.map +1 -0
- package/dist/chunk-UD3LFGDL.js +45 -0
- package/dist/chunk-UD3LFGDL.js.map +1 -0
- package/dist/chunk-UUFYP2UM.js +161 -0
- package/dist/chunk-UUFYP2UM.js.map +1 -0
- package/dist/{chunk-SNDZQ64K.js → chunk-VBZCVFSA.js} +85 -3
- package/dist/chunk-VBZCVFSA.js.map +1 -0
- package/dist/chunk-VMEWD57H.js +137 -0
- package/dist/chunk-VMEWD57H.js.map +1 -0
- package/dist/{chunk-BE2LKDI7.js → chunk-WEGALZEU.js} +3 -3
- package/dist/{chunk-ZOXNJV2O.js → chunk-WGHJD6I7.js} +35 -138
- package/dist/chunk-WGHJD6I7.js.map +1 -0
- package/dist/chunk-XMS5VRIK.js +0 -0
- package/dist/chunk-Y4H3JNVK.js +18 -0
- package/dist/chunk-Y4H3JNVK.js.map +1 -0
- package/dist/chunk-Z5IGLBWE.js +329 -0
- package/dist/chunk-Z5IGLBWE.js.map +1 -0
- package/dist/chunk-ZEGYW52B.js +26 -0
- package/dist/chunk-ZEGYW52B.js.map +1 -0
- package/dist/chunk-ZJQ4O76G.js +87 -0
- package/dist/chunk-ZJQ4O76G.js.map +1 -0
- package/dist/cli/index.js +44 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +107 -1
- package/dist/client/index.js +152 -6
- package/dist/client/index.js.map +1 -1
- package/dist/{cloudflare-LCAOTRYZ.js → cloudflare-OJGJ7R2D.js} +3 -3
- package/dist/configure-agent-registry-6YGTJYBC.js +0 -0
- package/dist/cookies-MJKMGJ7N.js +22 -0
- package/dist/csrf-BBrEZSBW.d.ts +107 -0
- package/dist/csrf-readiness-store-CjIoub3U.d.ts +43 -0
- package/dist/define-websocket-CdK94O-D.d.ts +64 -0
- package/dist/{deno-deploy-ZN4RE5HF.js → deno-deploy-BHWKFTOW.js} +3 -3
- package/dist/{dev-6P2DM33Q.js → dev-MJVSRZGF.js} +13 -13
- package/dist/{dev-emit-MLDFOLUU.js → dev-emit-5VPRCHOD.js} +39 -142
- package/dist/dev-emit-5VPRCHOD.js.map +1 -0
- package/dist/{dev-emit-PRSRPDHA.js → dev-emit-HHP2XJXE.js} +5 -5
- package/dist/devtools/entry.js +185 -131
- package/dist/devtools/entry.js.map +1 -1
- package/dist/{dispatcher-AQJGI3HU.js → dispatcher-63LBXYLE.js} +2 -2
- package/dist/{dispatcher-E6QV32BO.js → dispatcher-EJME6C6M.js} +5 -2
- package/dist/dispatcher-EJME6C6M.js.map +1 -0
- package/dist/{dist-E6BL4ZVY.js → dist-KRW6OVD4.js} +7 -7
- package/dist/dist-KRW6OVD4.js.map +1 -0
- package/dist/docker-EZDA5FT7.js +0 -0
- package/dist/error-envelope-BsNzzAV5.d.ts +62 -0
- package/dist/{generate-DT4IIAHF.js → generate-AZ2K6Z2Z.js} +75 -2
- package/dist/generate-AZ2K6Z2Z.js.map +1 -0
- package/dist/index-DWWEdFh5.d.ts +399 -0
- package/dist/index.d.ts +161 -1391
- package/dist/index.js +20 -8
- package/dist/index.js.map +1 -1
- package/dist/{info-FJ4NXKTB.js → info-47VB62MV.js} +5 -5
- package/dist/job-backend-CgC8Xf33.d.ts +68 -0
- package/dist/{lib-NL5JXGEB.js → lib-NST3PNZX.js} +31 -31
- package/dist/lib-NST3PNZX.js.map +1 -0
- package/dist/module-loader-Cfz7dgCP.d.ts +26 -0
- package/dist/{netlify-5VQ22Z2P.js → netlify-GSNSJGMN.js} +3 -3
- package/dist/{node-3APTLGWB.js → node-6I5B6RL3.js} +3 -3
- package/dist/node-6I5B6RL3.js.map +1 -0
- package/dist/{openapi-SOKZTFE3.js → openapi-NFLSNNVQ.js} +10 -10
- package/dist/plugin-runner-BGBkzgi0.d.ts +95 -0
- package/dist/plugin-types-DNJGxr4Z.d.ts +79 -0
- package/dist/{proper-lockfile-4Y3DP3RP.js → proper-lockfile-MVKMQGFK.js} +27 -27
- package/dist/proper-lockfile-MVKMQGFK.js.map +1 -0
- package/dist/rate-limit-BdNDZ3vt.d.ts +58 -0
- package/dist/registry-QHEZ3BWB.js +25 -0
- package/dist/router-RGZFX75G.js +274 -0
- package/dist/router-RGZFX75G.js.map +1 -0
- package/dist/{routes-XVRAUH6H.js → routes-3A4XGIEJ.js} +6 -6
- package/dist/{scan-C2BOKLSY.js → scan-NQFI5S7P.js} +2 -2
- package/dist/scan-NQFI5S7P.js.map +1 -0
- package/dist/schema-D3v8VB7o.d.ts +76 -0
- package/dist/{schema-VR6YNVLQ.js → schema-KZVOV333.js} +5 -3
- package/dist/schema-KZVOV333.js.map +1 -0
- package/dist/server/agent/index.d.ts +229 -0
- package/dist/server/agent/index.js +16 -0
- package/dist/server/agent/index.js.map +1 -0
- package/dist/server/auth/index.d.ts +46 -1
- package/dist/server/auth/index.js +10 -3
- package/dist/server/cost/index.d.ts +1 -3
- package/dist/server/cost/index.js +1 -1
- package/dist/server/cron/index.js +4 -2
- package/dist/server/define/index.d.ts +281 -0
- package/dist/server/define/index.js +26 -0
- package/dist/server/define/index.js.map +1 -0
- package/dist/server/http/index.d.ts +10 -0
- package/dist/server/http/index.js +74 -0
- package/dist/server/http/index.js.map +1 -0
- package/dist/server/index.d.ts +151 -1677
- package/dist/server/index.js +558 -1102
- package/dist/server/index.js.map +1 -1
- package/dist/server/jobs/index.d.ts +348 -2
- package/dist/server/jobs/index.js +5 -3
- package/dist/server/observability/index.d.ts +324 -0
- package/dist/server/observability/index.js +48 -0
- package/dist/server/observability/index.js.map +1 -0
- package/dist/server/plugins/index.d.ts +17 -0
- package/dist/server/plugins/index.js +17 -0
- package/dist/server/plugins/index.js.map +1 -0
- package/dist/server/rate-limit/index.d.ts +105 -0
- package/dist/server/rate-limit/index.js +25 -0
- package/dist/server/rate-limit/index.js.map +1 -0
- package/dist/server/realtime/index.d.ts +15 -0
- package/dist/server/realtime/index.js +8 -0
- package/dist/server/realtime/index.js.map +1 -0
- package/dist/server/scan/index.d.ts +106 -0
- package/dist/server/scan/index.js +43 -0
- package/dist/server/scan/index.js.map +1 -0
- package/dist/server/security/index.d.ts +193 -0
- package/dist/server/security/index.js +50 -0
- package/dist/server/security/index.js.map +1 -0
- package/dist/server/storage/index.d.ts +22 -0
- package/dist/server/storage/index.js +14 -0
- package/dist/server/storage/index.js.map +1 -0
- package/dist/server/webhook/index.d.ts +148 -0
- package/dist/server/webhook/index.js +19 -0
- package/dist/server/webhook/index.js.map +1 -0
- package/dist/server-error-to-envelope-NO4CCAFQ.js +8 -0
- package/dist/server-error-to-envelope-NO4CCAFQ.js.map +1 -0
- package/dist/server-error-to-envelope-UJK6OWDJ.js +134 -0
- package/dist/server-error-to-envelope-UJK6OWDJ.js.map +1 -0
- package/dist/server-routes-hmr-GZJGPWMS.js +44 -0
- package/dist/server-routes-hmr-GZJGPWMS.js.map +1 -0
- package/dist/server-routes-hmr-PBHSKCQJ.js +42 -0
- package/dist/server-routes-hmr-PBHSKCQJ.js.map +1 -0
- package/dist/services-json-Z2QNGVPW.js +142 -0
- package/dist/services-json-Z2QNGVPW.js.map +1 -0
- package/dist/{services-typed-client-XWYYBWGW.js → services-typed-client-KK6RHRDG.js} +2 -2
- package/dist/{services-typed-client-ZX5JUHH3.js → services-typed-client-T7M5VPBP.js} +2 -2
- package/dist/{sqlite-vec-ARPNUBWT.js → sqlite-vec-54KB4RD3.js} +2 -2
- package/dist/sqlite-vec-54KB4RD3.js.map +1 -0
- package/dist/{start-R7XSQL5L.js → start-CGSZPBAG.js} +23 -23
- package/dist/start-CGSZPBAG.js.map +1 -0
- package/dist/{static-TZBVBDTB.js → static-QI5NQT2X.js} +6 -6
- package/dist/storage-manager-C4jsO0Tp.d.ts +89 -0
- package/dist/storage-manager-D5KBXXKB.js +0 -0
- package/dist/{storage-types-DtIVejC1.d.ts → storage-types-DsDTCPbp.d.ts} +1 -1
- package/dist/{theo-cloud-3K4DGB3S.js → theo-cloud-RSQCBNXL.js} +4 -4
- package/dist/theo-cloud-RSQCBNXL.js.map +1 -0
- package/dist/upgrade-readiness-GUJBCJIS.js +0 -0
- package/dist/{vercel-MWW24ABC.js → vercel-TKPZK62A.js} +3 -3
- package/dist/vite-plugin/index.d.ts +3 -1
- package/dist/vite-plugin/index.js +20 -8
- package/dist/{vite-plugin-6MPHTKIW.js → vite-plugin-CR4JDFUQ.js} +9 -9
- package/dist/vite-plugin-CR4JDFUQ.js.map +1 -0
- package/package.json +59 -10
- package/LICENSE +0 -201
- package/dist/build-MREW6MVS.js.map +0 -1
- package/dist/chunk-2BQYEBCK.js.map +0 -1
- package/dist/chunk-5OFPQK6N.js.map +0 -1
- package/dist/chunk-5OKZY2VL.js.map +0 -1
- package/dist/chunk-64JI5LTO.js.map +0 -1
- package/dist/chunk-7TOMTMHT.js.map +0 -1
- package/dist/chunk-C52GSJPF.js.map +0 -1
- package/dist/chunk-CZM6WWWS.js +0 -2450
- package/dist/chunk-CZM6WWWS.js.map +0 -1
- package/dist/chunk-KJVEJILQ.js.map +0 -1
- package/dist/chunk-O4BLVPML.js.map +0 -1
- package/dist/chunk-O7335ZGH.js.map +0 -1
- package/dist/chunk-PB4GR7EP.js.map +0 -1
- package/dist/chunk-SNDZQ64K.js.map +0 -1
- package/dist/chunk-TPCT3MXV.js.map +0 -1
- package/dist/chunk-VRHXOKRP.js.map +0 -1
- package/dist/chunk-WZ7CPVQB.js.map +0 -1
- package/dist/chunk-X2OLD264.js.map +0 -1
- package/dist/chunk-X4JAX2U3.js.map +0 -1
- package/dist/chunk-XP7YXRHO.js.map +0 -1
- package/dist/chunk-YLBSSEHX.js.map +0 -1
- package/dist/chunk-ZOXNJV2O.js.map +0 -1
- package/dist/dev-emit-MLDFOLUU.js.map +0 -1
- package/dist/dispatcher-E6QV32BO.js.map +0 -1
- package/dist/dist-E6BL4ZVY.js.map +0 -1
- package/dist/generate-DT4IIAHF.js.map +0 -1
- package/dist/index-li83Swxa.d.ts +0 -506
- package/dist/lib-NL5JXGEB.js.map +0 -1
- package/dist/proper-lockfile-4Y3DP3RP.js.map +0 -1
- package/dist/registry-5QFTLOL2.js +0 -25
- package/dist/schema-CjVly9c_.d.ts +0 -274
- package/dist/sqlite-vec-ARPNUBWT.js.map +0 -1
- package/dist/start-R7XSQL5L.js.map +0 -1
- package/dist/theo-cloud-3K4DGB3S.js.map +0 -1
- /package/dist/{actions-virtual-module-HWNTIEPI.js.map → actions-virtual-module-IY46EEEX.js.map} +0 -0
- /package/dist/{actions-virtual-module-SEIPACG5.js.map → actions-virtual-module-XNHDNCZ6.js.map} +0 -0
- /package/dist/{aws-lambda-XYCGZH3I.js.map → aws-lambda-GKSTX6HD.js.map} +0 -0
- /package/dist/{bun-K73TQEWC.js.map → bun-ISHSNFIO.js.map} +0 -0
- /package/dist/{check-PZR67OY5.js.map → check-NXYPLM6M.js.map} +0 -0
- /package/dist/{dispatcher-AQJGI3HU.js.map → chunk-E3JH6YUM.js.map} +0 -0
- /package/dist/{node-3APTLGWB.js.map → chunk-JHEAWR3L.js.map} +0 -0
- /package/dist/{chunk-ZJW5FFYJ.js.map → chunk-LC5PYNJP.js.map} +0 -0
- /package/dist/{chunk-YWWHK7R6.js.map → chunk-MR7OLIC6.js.map} +0 -0
- /package/dist/{scan-C2BOKLSY.js.map → chunk-NPMCKOX4.js.map} +0 -0
- /package/dist/{chunk-BE2LKDI7.js.map → chunk-WEGALZEU.js.map} +0 -0
- /package/dist/{cloudflare-LCAOTRYZ.js.map → cloudflare-OJGJ7R2D.js.map} +0 -0
- /package/dist/{schema-VR6YNVLQ.js.map → cookies-MJKMGJ7N.js.map} +0 -0
- /package/dist/{deno-deploy-ZN4RE5HF.js.map → deno-deploy-BHWKFTOW.js.map} +0 -0
- /package/dist/{dev-6P2DM33Q.js.map → dev-MJVSRZGF.js.map} +0 -0
- /package/dist/{dev-emit-PRSRPDHA.js.map → dev-emit-HHP2XJXE.js.map} +0 -0
- /package/dist/{vite-plugin-6MPHTKIW.js.map → dispatcher-63LBXYLE.js.map} +0 -0
- /package/dist/{info-FJ4NXKTB.js.map → info-47VB62MV.js.map} +0 -0
- /package/dist/{netlify-5VQ22Z2P.js.map → netlify-GSNSJGMN.js.map} +0 -0
- /package/dist/{openapi-SOKZTFE3.js.map → openapi-NFLSNNVQ.js.map} +0 -0
- /package/dist/{registry-5QFTLOL2.js.map → registry-QHEZ3BWB.js.map} +0 -0
- /package/dist/{routes-XVRAUH6H.js.map → routes-3A4XGIEJ.js.map} +0 -0
- /package/dist/{services-typed-client-XWYYBWGW.js.map → services-typed-client-KK6RHRDG.js.map} +0 -0
- /package/dist/{services-typed-client-ZX5JUHH3.js.map → services-typed-client-T7M5VPBP.js.map} +0 -0
- /package/dist/{static-TZBVBDTB.js.map → static-QI5NQT2X.js.map} +0 -0
- /package/dist/{vercel-MWW24ABC.js.map → vercel-TKPZK62A.js.map} +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/server/realtime/channel-manager.ts
|
|
2
|
+
var ChannelManager = class {
|
|
3
|
+
rooms = /* @__PURE__ */ new Map();
|
|
4
|
+
wsRooms = /* @__PURE__ */ new Map();
|
|
5
|
+
subscribe(ws, room) {
|
|
6
|
+
let roomSet = this.rooms.get(room);
|
|
7
|
+
if (!roomSet) {
|
|
8
|
+
roomSet = /* @__PURE__ */ new Set();
|
|
9
|
+
this.rooms.set(room, roomSet);
|
|
10
|
+
}
|
|
11
|
+
roomSet.add(ws);
|
|
12
|
+
let wsSet = this.wsRooms.get(ws);
|
|
13
|
+
if (!wsSet) {
|
|
14
|
+
wsSet = /* @__PURE__ */ new Set();
|
|
15
|
+
this.wsRooms.set(ws, wsSet);
|
|
16
|
+
}
|
|
17
|
+
wsSet.add(room);
|
|
18
|
+
}
|
|
19
|
+
unsubscribe(ws, room) {
|
|
20
|
+
this.rooms.get(room)?.delete(ws);
|
|
21
|
+
if (this.rooms.get(room)?.size === 0) this.rooms.delete(room);
|
|
22
|
+
this.wsRooms.get(ws)?.delete(room);
|
|
23
|
+
if (this.wsRooms.get(ws)?.size === 0) this.wsRooms.delete(ws);
|
|
24
|
+
}
|
|
25
|
+
broadcast(room, data, exclude) {
|
|
26
|
+
const clients = this.rooms.get(room);
|
|
27
|
+
if (!clients) return;
|
|
28
|
+
const msg = JSON.stringify(data);
|
|
29
|
+
for (const ws of clients) {
|
|
30
|
+
if (ws !== exclude) ws.send(msg);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
broadcastAll(data) {
|
|
34
|
+
const msg = JSON.stringify(data);
|
|
35
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36
|
+
for (const clients of this.rooms.values()) {
|
|
37
|
+
for (const ws of clients) {
|
|
38
|
+
if (!seen.has(ws)) {
|
|
39
|
+
ws.send(msg);
|
|
40
|
+
seen.add(ws);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
getRoomSize(room) {
|
|
46
|
+
return this.rooms.get(room)?.size ?? 0;
|
|
47
|
+
}
|
|
48
|
+
cleanup(ws) {
|
|
49
|
+
const rooms = this.wsRooms.get(ws);
|
|
50
|
+
if (rooms) {
|
|
51
|
+
for (const room of rooms) {
|
|
52
|
+
this.rooms.get(room)?.delete(ws);
|
|
53
|
+
if (this.rooms.get(room)?.size === 0) this.rooms.delete(room);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
this.wsRooms.delete(ws);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
ChannelManager
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=chunk-NM4WF3XD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/realtime/channel-manager.ts"],"sourcesContent":["import type { WebSocketLike } from '../define/define-websocket.js'\n\nexport class ChannelManager {\n private rooms = new Map<string, Set<WebSocketLike>>()\n private wsRooms = new Map<WebSocketLike, Set<string>>()\n\n subscribe(ws: WebSocketLike, room: string): void {\n let roomSet = this.rooms.get(room)\n if (!roomSet) {\n roomSet = new Set()\n this.rooms.set(room, roomSet)\n }\n roomSet.add(ws)\n\n let wsSet = this.wsRooms.get(ws)\n if (!wsSet) {\n wsSet = new Set()\n this.wsRooms.set(ws, wsSet)\n }\n wsSet.add(room)\n }\n\n unsubscribe(ws: WebSocketLike, room: string): void {\n this.rooms.get(room)?.delete(ws)\n if (this.rooms.get(room)?.size === 0) this.rooms.delete(room)\n\n this.wsRooms.get(ws)?.delete(room)\n if (this.wsRooms.get(ws)?.size === 0) this.wsRooms.delete(ws)\n }\n\n broadcast(room: string, data: unknown, exclude?: WebSocketLike): void {\n const clients = this.rooms.get(room)\n if (!clients) return\n\n const msg = JSON.stringify(data)\n for (const ws of clients) {\n if (ws !== exclude) ws.send(msg)\n }\n }\n\n broadcastAll(data: unknown): void {\n const msg = JSON.stringify(data)\n const seen = new Set<WebSocketLike>()\n for (const clients of this.rooms.values()) {\n for (const ws of clients) {\n if (!seen.has(ws)) {\n ws.send(msg)\n seen.add(ws)\n }\n }\n }\n }\n\n getRoomSize(room: string): number {\n return this.rooms.get(room)?.size ?? 0\n }\n\n cleanup(ws: WebSocketLike): void {\n const rooms = this.wsRooms.get(ws)\n if (rooms) {\n for (const room of rooms) {\n this.rooms.get(room)?.delete(ws)\n if (this.rooms.get(room)?.size === 0) this.rooms.delete(room)\n }\n }\n this.wsRooms.delete(ws)\n }\n}\n"],"mappings":";AAEO,IAAM,iBAAN,MAAqB;AAAA,EAClB,QAAQ,oBAAI,IAAgC;AAAA,EAC5C,UAAU,oBAAI,IAAgC;AAAA,EAEtD,UAAU,IAAmB,MAAoB;AAC/C,QAAI,UAAU,KAAK,MAAM,IAAI,IAAI;AACjC,QAAI,CAAC,SAAS;AACZ,gBAAU,oBAAI,IAAI;AAClB,WAAK,MAAM,IAAI,MAAM,OAAO;AAAA,IAC9B;AACA,YAAQ,IAAI,EAAE;AAEd,QAAI,QAAQ,KAAK,QAAQ,IAAI,EAAE;AAC/B,QAAI,CAAC,OAAO;AACV,cAAQ,oBAAI,IAAI;AAChB,WAAK,QAAQ,IAAI,IAAI,KAAK;AAAA,IAC5B;AACA,UAAM,IAAI,IAAI;AAAA,EAChB;AAAA,EAEA,YAAY,IAAmB,MAAoB;AACjD,SAAK,MAAM,IAAI,IAAI,GAAG,OAAO,EAAE;AAC/B,QAAI,KAAK,MAAM,IAAI,IAAI,GAAG,SAAS,EAAG,MAAK,MAAM,OAAO,IAAI;AAE5D,SAAK,QAAQ,IAAI,EAAE,GAAG,OAAO,IAAI;AACjC,QAAI,KAAK,QAAQ,IAAI,EAAE,GAAG,SAAS,EAAG,MAAK,QAAQ,OAAO,EAAE;AAAA,EAC9D;AAAA,EAEA,UAAU,MAAc,MAAe,SAA+B;AACpE,UAAM,UAAU,KAAK,MAAM,IAAI,IAAI;AACnC,QAAI,CAAC,QAAS;AAEd,UAAM,MAAM,KAAK,UAAU,IAAI;AAC/B,eAAW,MAAM,SAAS;AACxB,UAAI,OAAO,QAAS,IAAG,KAAK,GAAG;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,aAAa,MAAqB;AAChC,UAAM,MAAM,KAAK,UAAU,IAAI;AAC/B,UAAM,OAAO,oBAAI,IAAmB;AACpC,eAAW,WAAW,KAAK,MAAM,OAAO,GAAG;AACzC,iBAAW,MAAM,SAAS;AACxB,YAAI,CAAC,KAAK,IAAI,EAAE,GAAG;AACjB,aAAG,KAAK,GAAG;AACX,eAAK,IAAI,EAAE;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAY,MAAsB;AAChC,WAAO,KAAK,MAAM,IAAI,IAAI,GAAG,QAAQ;AAAA,EACvC;AAAA,EAEA,QAAQ,IAAyB;AAC/B,UAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE;AACjC,QAAI,OAAO;AACT,iBAAW,QAAQ,OAAO;AACxB,aAAK,MAAM,IAAI,IAAI,GAAG,OAAO,EAAE;AAC/B,YAAI,KAAK,MAAM,IAAI,IAAI,GAAG,SAAS,EAAG,MAAK,MAAM,OAAO,IAAI;AAAA,MAC9D;AAAA,IACF;AACA,SAAK,QAAQ,OAAO,EAAE;AAAA,EACxB;AACF;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-NPMCKOX4.js.map
|
|
@@ -1,59 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const key = trimmed.slice(0, eqIdx);
|
|
10
|
-
const rawValue = trimmed.slice(eqIdx + 1);
|
|
11
|
-
let value;
|
|
12
|
-
try {
|
|
13
|
-
value = decodeURIComponent(rawValue);
|
|
14
|
-
} catch {
|
|
15
|
-
value = rawValue;
|
|
16
|
-
}
|
|
17
|
-
map.set(key, value);
|
|
18
|
-
}
|
|
19
|
-
return map;
|
|
20
|
-
}
|
|
21
|
-
function getCookie(req, name) {
|
|
22
|
-
const cookies = parseCookieHeader(req.headers.cookie);
|
|
23
|
-
const value = cookies.get(name);
|
|
24
|
-
if (value === void 0) return void 0;
|
|
25
|
-
if (/(?:%[^0-9A-Fa-f])|(?:%[0-9A-Fa-f]$)/.test(value)) return void 0;
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
function setCookie(res, name, value, options) {
|
|
29
|
-
const opts = {
|
|
30
|
-
httpOnly: true,
|
|
31
|
-
secure: process.env.NODE_ENV === "production",
|
|
32
|
-
sameSite: "lax",
|
|
33
|
-
path: "/",
|
|
34
|
-
...options
|
|
35
|
-
};
|
|
36
|
-
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
37
|
-
if (opts.httpOnly) parts.push("HttpOnly");
|
|
38
|
-
if (opts.secure) parts.push("Secure");
|
|
39
|
-
const sameSiteCanonical = opts.sameSite.charAt(0).toUpperCase() + opts.sameSite.slice(1);
|
|
40
|
-
parts.push(`SameSite=${sameSiteCanonical}`);
|
|
41
|
-
if (opts.maxAge !== void 0) parts.push(`Max-Age=${opts.maxAge}`);
|
|
42
|
-
if (opts.path) parts.push(`Path=${opts.path}`);
|
|
43
|
-
if (opts.domain) parts.push(`Domain=${opts.domain}`);
|
|
44
|
-
const existing = res.getHeader("Set-Cookie");
|
|
45
|
-
let cookies = [];
|
|
46
|
-
if (Array.isArray(existing)) {
|
|
47
|
-
cookies = existing.map(String);
|
|
48
|
-
} else if (existing !== void 0) {
|
|
49
|
-
cookies = [String(existing)];
|
|
50
|
-
}
|
|
51
|
-
cookies.push(parts.join("; "));
|
|
52
|
-
res.setHeader("Set-Cookie", cookies);
|
|
53
|
-
}
|
|
54
|
-
function deleteCookie(res, name, options) {
|
|
55
|
-
setCookie(res, name, "", { maxAge: 0, path: options?.path ?? "/" });
|
|
56
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
appendCookieToHeaders,
|
|
3
|
+
appendDeleteCookieToHeaders,
|
|
4
|
+
deleteCookie,
|
|
5
|
+
getCookie,
|
|
6
|
+
getCookieFromRequest,
|
|
7
|
+
setCookie
|
|
8
|
+
} from "./chunk-ZJQ4O76G.js";
|
|
57
9
|
|
|
58
10
|
// src/server/auth/crypto.ts
|
|
59
11
|
var encoder = new TextEncoder();
|
|
@@ -143,17 +95,29 @@ function normalizeSecrets(input) {
|
|
|
143
95
|
}
|
|
144
96
|
return arr;
|
|
145
97
|
}
|
|
98
|
+
async function encryptSessionEnvelope(data, secrets, maxAge) {
|
|
99
|
+
const envelope = {
|
|
100
|
+
data,
|
|
101
|
+
exp: Date.now() + maxAge * 1e3
|
|
102
|
+
};
|
|
103
|
+
return encrypt(envelope, secrets[0]);
|
|
104
|
+
}
|
|
105
|
+
async function decryptSessionWithFallback(raw, secrets) {
|
|
106
|
+
const results = await Promise.all(
|
|
107
|
+
secrets.map(async (secret, i) => {
|
|
108
|
+
const env = await decrypt(raw, secret);
|
|
109
|
+
return env ? { envelope: env, index: i } : null;
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
for (const result of results) {
|
|
113
|
+
if (result) return result;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
146
117
|
function createSessionManager(config) {
|
|
147
118
|
const secrets = normalizeSecrets(config.secret);
|
|
148
119
|
const cookieName = config.cookieName ?? "theo_session";
|
|
149
120
|
const maxAge = config.maxAge ?? 604800;
|
|
150
|
-
async function encryptEnvelope(data) {
|
|
151
|
-
const envelope = {
|
|
152
|
-
data,
|
|
153
|
-
exp: Date.now() + maxAge * 1e3
|
|
154
|
-
};
|
|
155
|
-
return encrypt(envelope, secrets[0]);
|
|
156
|
-
}
|
|
157
121
|
function writeCookie(res, token) {
|
|
158
122
|
setCookie(res, cookieName, token, {
|
|
159
123
|
httpOnly: true,
|
|
@@ -162,18 +126,6 @@ function createSessionManager(config) {
|
|
|
162
126
|
path: "/"
|
|
163
127
|
});
|
|
164
128
|
}
|
|
165
|
-
async function decryptWithFallback(raw) {
|
|
166
|
-
const results = await Promise.all(
|
|
167
|
-
secrets.map(async (secret, i) => {
|
|
168
|
-
const env = await decrypt(raw, secret);
|
|
169
|
-
return env ? { envelope: env, index: i } : null;
|
|
170
|
-
})
|
|
171
|
-
);
|
|
172
|
-
for (const result of results) {
|
|
173
|
-
if (result) return result;
|
|
174
|
-
}
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
129
|
return {
|
|
178
130
|
async getSession(req) {
|
|
179
131
|
const { data } = await this.getSessionWithMeta(req);
|
|
@@ -182,7 +134,7 @@ function createSessionManager(config) {
|
|
|
182
134
|
async getSessionWithMeta(req) {
|
|
183
135
|
const raw = getCookie(req, cookieName);
|
|
184
136
|
if (!raw) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } };
|
|
185
|
-
const decoded = await
|
|
137
|
+
const decoded = await decryptSessionWithFallback(raw, secrets);
|
|
186
138
|
if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } };
|
|
187
139
|
if (decoded.envelope.exp < Date.now()) {
|
|
188
140
|
return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } };
|
|
@@ -193,7 +145,7 @@ function createSessionManager(config) {
|
|
|
193
145
|
};
|
|
194
146
|
},
|
|
195
147
|
async createSession(res, data) {
|
|
196
|
-
const token = await
|
|
148
|
+
const token = await encryptSessionEnvelope(data, secrets, maxAge);
|
|
197
149
|
writeCookie(res, token);
|
|
198
150
|
},
|
|
199
151
|
destroySession(res) {
|
|
@@ -202,12 +154,65 @@ function createSessionManager(config) {
|
|
|
202
154
|
async rotateSession(req, res) {
|
|
203
155
|
const { data } = await this.getSessionWithMeta(req);
|
|
204
156
|
if (data === null) return null;
|
|
205
|
-
const token = await
|
|
157
|
+
const token = await encryptSessionEnvelope(data, secrets, maxAge);
|
|
206
158
|
writeCookie(res, token);
|
|
207
159
|
return data;
|
|
208
160
|
}
|
|
209
161
|
};
|
|
210
162
|
}
|
|
163
|
+
function createSessionManagerWeb(config) {
|
|
164
|
+
const secrets = normalizeSecrets(config.secret);
|
|
165
|
+
const cookieName = config.cookieName ?? "theo_session";
|
|
166
|
+
const maxAge = config.maxAge ?? 604800;
|
|
167
|
+
function writeCookieWeb(target, token) {
|
|
168
|
+
appendCookieToHeaders(target, cookieName, token, {
|
|
169
|
+
httpOnly: true,
|
|
170
|
+
sameSite: "lax",
|
|
171
|
+
maxAge,
|
|
172
|
+
path: "/"
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
async getSession(request) {
|
|
177
|
+
const { data } = await this.getSessionWithMeta(request);
|
|
178
|
+
return data;
|
|
179
|
+
},
|
|
180
|
+
async getSessionWithMeta(request) {
|
|
181
|
+
const raw = getCookieFromRequest(request, cookieName);
|
|
182
|
+
if (raw === void 0) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } };
|
|
183
|
+
const decoded = await decryptSessionWithFallback(raw, secrets);
|
|
184
|
+
if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } };
|
|
185
|
+
if (decoded.envelope.exp < Date.now()) {
|
|
186
|
+
return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } };
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
data: decoded.envelope.data,
|
|
190
|
+
meta: { secretIndex: decoded.index, needsReencrypt: decoded.index > 0 }
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
async createSession(target, data) {
|
|
194
|
+
const token = await encryptSessionEnvelope(data, secrets, maxAge);
|
|
195
|
+
writeCookieWeb(target, token);
|
|
196
|
+
},
|
|
197
|
+
destroySession(target) {
|
|
198
|
+
appendDeleteCookieToHeaders(target, cookieName);
|
|
199
|
+
},
|
|
200
|
+
async rotateSession(request, target) {
|
|
201
|
+
const { data } = await this.getSessionWithMeta(request);
|
|
202
|
+
if (data === null) return null;
|
|
203
|
+
const token = await encryptSessionEnvelope(data, secrets, maxAge);
|
|
204
|
+
writeCookieWeb(target, token);
|
|
205
|
+
return data;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function rotateIfNeededWeb(sm, request, target) {
|
|
210
|
+
const { data, meta } = await sm.getSessionWithMeta(request);
|
|
211
|
+
if (data !== null && meta.needsReencrypt) {
|
|
212
|
+
await sm.createSession(target, data);
|
|
213
|
+
}
|
|
214
|
+
return data;
|
|
215
|
+
}
|
|
211
216
|
async function rotateIfNeeded(sm, req, res) {
|
|
212
217
|
const { data, meta } = await sm.getSessionWithMeta(req);
|
|
213
218
|
if (data !== null && meta.needsReencrypt) {
|
|
@@ -573,14 +578,12 @@ async function discoverOidcProvider(issuer) {
|
|
|
573
578
|
}
|
|
574
579
|
|
|
575
580
|
export {
|
|
576
|
-
parseCookieHeader,
|
|
577
|
-
getCookie,
|
|
578
|
-
setCookie,
|
|
579
|
-
deleteCookie,
|
|
580
581
|
encrypt,
|
|
581
582
|
decrypt,
|
|
582
583
|
_resetKeyCacheForTests,
|
|
583
584
|
createSessionManager,
|
|
585
|
+
createSessionManagerWeb,
|
|
586
|
+
rotateIfNeededWeb,
|
|
584
587
|
rotateIfNeeded,
|
|
585
588
|
assertProductionSecret,
|
|
586
589
|
generateTotp,
|
|
@@ -598,4 +601,4 @@ export {
|
|
|
598
601
|
clearOidcCache,
|
|
599
602
|
discoverOidcProvider
|
|
600
603
|
};
|
|
601
|
-
//# sourceMappingURL=chunk-
|
|
604
|
+
//# sourceMappingURL=chunk-NZXH2LQQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/auth/crypto.ts","../src/server/auth/session.ts","../src/server/_internal/encoding.ts","../src/server/auth/auth-totp.ts","../src/server/auth/auth-backup-codes.ts","../src/server/auth/auth-throttle.ts","../src/server/auth/oauth-pkce.ts","../src/server/auth/oauth-state.ts","../src/server/auth/oidc-discovery.ts"],"sourcesContent":["// AES-GCM-256 token encryption with cached key derivation.\n//\n// CR-002 fixes:\n// - KDF: HKDF (RFC 5869) over the raw secret, not raw SHA-256.\n// HKDF binds the application context (\"theo-session-v1\") into the\n// derived key so the same secret used elsewhere cannot decrypt these\n// tokens. SHA-256 of the raw secret has neither domain separation nor\n// proper key-stretching guarantees.\n// - Cache: derived `CryptoKey` is memoized per (secret, info) tuple.\n// Pre-fix, every encrypt/decrypt re-ran SHA-256 + importKey, which (a)\n// burned CPU on every authenticated request and (b) made\n// `decryptWithFallback` leak rotation-array position via timing.\n// - Buffer typing: `Uint8Array` flows directly into Web Crypto\n// (`BufferSource`-compatible). The previous `as unknown as ArrayBuffer`\n// double-cast (CR-016) is removed.\n\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder()\n\nconst HKDF_INFO_DEFAULT = encoder.encode('theo-session-v1')\nconst HKDF_SALT_EMPTY = new Uint8Array(0)\n\n// Memoize derived keys. Bounded by the number of distinct secrets the app\n// ever uses (typically 1 in steady state, 2-5 during rotation). Map-based\n// so distinct secrets do not evict each other.\nconst keyCache = new Map<string, Promise<CryptoKey>>()\n\nasync function deriveKey(secret: string): Promise<CryptoKey> {\n const cached = keyCache.get(secret)\n if (cached) return cached\n\n const derive = (async () => {\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HKDF' },\n false,\n ['deriveKey'],\n )\n return crypto.subtle.deriveKey(\n { name: 'HKDF', hash: 'SHA-256', salt: HKDF_SALT_EMPTY, info: HKDF_INFO_DEFAULT },\n keyMaterial,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n })()\n\n keyCache.set(secret, derive)\n // If derivation fails (invalid secret), evict so a retry can try fresh.\n derive.catch(() => {\n keyCache.delete(secret)\n })\n return derive\n}\n\nfunction toBase64Url(data: Uint8Array): string {\n return Buffer.from(data).toString('base64url')\n}\n\n/**\n * Decode a base64url string into a `Uint8Array<ArrayBuffer>`. The explicit\n * generic matters: Web Crypto APIs expect `BufferSource`, and TS 5.7+\n * tightened `Uint8Array` to `Uint8Array<ArrayBufferLike>` which includes\n * `SharedArrayBuffer`. Returning the narrower generic lets the value pass\n * `crypto.subtle.decrypt` without `as` casts (CR-016 follow-through).\n */\nfunction fromBase64Url(str: string): Uint8Array<ArrayBuffer> {\n const buf = Buffer.from(str, 'base64url')\n const copy = new Uint8Array(buf.length)\n copy.set(buf)\n return copy\n}\n\n// The `T` generic exists so call sites get type-safe round-tripping\n// (`encrypt<Session>(s, ...)` pairs with `decrypt<Session>(...)`); the\n// runtime body just stringifies `data`, hence T does not appear in the\n// signature beyond the argument.\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T documents the round-trip contract with decrypt<T>\nexport async function encrypt<T>(data: T, secret: string): Promise<string> {\n const key = await deriveKey(secret)\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const plaintext = encoder.encode(JSON.stringify(data))\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext),\n )\n return `${toBase64Url(iv)}:${toBase64Url(ciphertext)}`\n}\n\nexport async function decrypt<T>(token: string, secret: string): Promise<T | null> {\n try {\n const parts = token.split(':')\n if (parts.length !== 2) return null\n\n const iv = fromBase64Url(parts[0])\n const ciphertext = fromBase64Url(parts[1])\n const key = await deriveKey(secret)\n\n const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext)\n return JSON.parse(decoder.decode(plaintext)) as T\n } catch {\n return null\n }\n}\n\n/**\n * Reset the derived-key cache. Test helper — never call from production code.\n * Used by Vitest to guarantee isolation between tests that use distinct\n * secrets and want to confirm the derivation path runs.\n */\nexport function _resetKeyCacheForTests(): void {\n keyCache.clear()\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport {\n getCookie,\n setCookie,\n deleteCookie,\n getCookieFromRequest,\n appendCookieToHeaders,\n appendDeleteCookieToHeaders,\n} from '../http/cookies.js'\n\nimport { encrypt, decrypt } from './crypto.js'\n\nexport interface SessionConfig {\n /**\n * Session secret. Either a single string (legacy) or an array of strings\n * where index 0 is the newest. The array form enables dual-key rotation\n * (T3.1 / ADR D5): encrypt always uses `secrets[0]`, decrypt walks the\n * array, transparent re-encrypt on legacy hits (T3.2).\n *\n * EC-1: array length capped at 5 — enforced via throw at construction.\n */\n secret: string | string[]\n cookieName?: string\n maxAge?: number\n}\n\ninterface SessionEnvelope<T> {\n data: T\n exp: number\n}\n\nexport interface SessionMeta {\n /** Index of the secret that decrypted the session. 0 = newest; > 0 = legacy. */\n secretIndex: number\n /** True when the decrypt used a legacy secret and the cookie should be re-encrypted. */\n needsReencrypt: boolean\n}\n\nexport interface SessionManager<TSession> {\n getSession(req: IncomingMessage): Promise<TSession | null>\n /**\n * T3.2 — Variant of `getSession` that surfaces decrypt metadata.\n * Used by `api-middleware.ts` to wire transparent re-encrypt BEFORE\n * the handler runs (so streaming SSR routes don't miss the Set-Cookie\n * window — EC-4).\n */\n getSessionWithMeta(req: IncomingMessage): Promise<{ data: TSession | null; meta: SessionMeta }>\n createSession(res: ServerResponse, data: TSession): Promise<void>\n destroySession(res: ServerResponse): void\n /**\n * T3.3 — OWASP A07:2021 session fixation mitigation. Re-encrypts the\n * current session with a fresh IV and refreshed expiry. No-op when no\n * session is present.\n */\n rotateSession(req: IncomingMessage, res: ServerResponse): Promise<TSession | null>\n}\n\n/**\n * EC-1 — array length cap. Enforced via throw at construction. Silent\n * truncation would create false sense of rotation; silent acceptance\n * would create unbounded CPU on legacy decrypt walks.\n */\nconst MAX_SECRETS = 5\n\n/**\n * Validate + normalize the secret config into a non-empty array of\n * strings ≥ 32 chars each.\n */\nfunction normalizeSecrets(input: string | string[]): string[] {\n const arr = Array.isArray(input) ? input.slice() : [input]\n if (arr.length === 0) {\n throw new Error('Session secret must be non-empty (received an empty array)')\n }\n if (arr.length > MAX_SECRETS) {\n throw new Error(\n `Session secret array exceeds maximum of ${MAX_SECRETS} entries — drop the oldest before adding a new one (received ${arr.length})`,\n )\n }\n const wasArray = Array.isArray(input)\n for (let i = 0; i < arr.length; i++) {\n const s = arr[i]\n if (typeof s !== 'string' || s.length < 32) {\n const where = wasArray ? ` at index ${i}` : ''\n throw new Error(`Session secret${where} must be at least 32 characters for secure encryption`)\n }\n }\n return arr\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — module-level pure helpers extracted so\n * `createSessionManager` (IncomingMessage path) and\n * `createSessionManagerWeb` (Web path) share the same encryption +\n * dual-key rotation logic without code duplication (sonarjs\n * no-identical-functions). Both wrappers pass `secrets` + `maxAge`\n * captured from their respective factory scope.\n */\nasync function encryptSessionEnvelope(\n data: unknown,\n secrets: string[],\n maxAge: number,\n): Promise<string> {\n const envelope: SessionEnvelope<unknown> = {\n data,\n exp: Date.now() + maxAge * 1000,\n }\n return encrypt(envelope, secrets[0]) // newest\n}\n\n/**\n * CR-002 constant-time: try EVERY rotation entry in parallel, then pick\n * the newest match. Sequential early-exit leaked rotation position via\n * timing (success on entry 0 returned in ~3 ms; success on entry 4 in\n * ~15 ms). Running in parallel binds wall-clock to the slowest single\n * attempt, independent of which entry actually matched. With derived-key\n * cache (CR-002 in crypto.ts), the CPU multiplier per request is ~0\n * after the first request per secret.\n */\nasync function decryptSessionWithFallback<TSession>(\n raw: string,\n secrets: string[],\n): Promise<{ envelope: SessionEnvelope<TSession>; index: number } | null> {\n const results = await Promise.all(\n secrets.map(async (secret, i) => {\n const env = await decrypt<SessionEnvelope<TSession>>(raw, secret)\n return env ? { envelope: env, index: i } : null\n }),\n )\n for (const result of results) {\n if (result) return result\n }\n return null\n}\n\nexport function createSessionManager<TSession>(config: SessionConfig): SessionManager<TSession> {\n const secrets = normalizeSecrets(config.secret)\n const cookieName = config.cookieName ?? 'theo_session'\n const maxAge = config.maxAge ?? 604800 // 7 days\n\n function writeCookie(res: ServerResponse, token: string): void {\n setCookie(res, cookieName, token, {\n httpOnly: true,\n sameSite: 'lax',\n maxAge,\n path: '/',\n })\n }\n\n return {\n async getSession(req) {\n const { data } = await this.getSessionWithMeta(req)\n return data\n },\n\n async getSessionWithMeta(req) {\n const raw = getCookie(req, cookieName)\n if (!raw) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n const decoded = await decryptSessionWithFallback<TSession>(raw, secrets)\n if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n if (decoded.envelope.exp < Date.now()) {\n return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } }\n }\n return {\n data: decoded.envelope.data,\n meta: { secretIndex: decoded.index, needsReencrypt: decoded.index > 0 },\n }\n },\n\n async createSession(res, data) {\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookie(res, token)\n },\n\n destroySession(res) {\n deleteCookie(res, cookieName)\n },\n\n async rotateSession(req, res) {\n const { data } = await this.getSessionWithMeta(req)\n if (data === null) return null\n // Fresh IV per encrypt call (Web Crypto guarantee — see crypto.ts)\n // + refreshed expiry. Last write wins for the cookie value.\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookie(res, token)\n return data\n },\n }\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — Web-Standards session manager.\n *\n * Mirror of `SessionManager<TSession>` for the Web `Request` + `Headers`\n * shape. Same `SessionConfig`, same encryption (`encrypt`/`decrypt` from\n * `./crypto.js`), same dual-key rotation logic (CR-002 constant-time\n * decrypt fallback walk + transparent re-encrypt), same OWASP A07\n * `rotateSession` invariant.\n *\n * **Signature differences vs `SessionManager`:**\n * - Read methods take `Request` (instead of `IncomingMessage`).\n * - Write methods take a `Headers` instance to mutate (instead of\n * `ServerResponse`) — caller passes the headers they're building\n * for their `Response` constructor.\n *\n * Uses `getCookieFromRequest` / `appendCookieToHeaders` /\n * `appendDeleteCookieToHeaders` from Phase B slice 6/6.\n */\nexport interface SessionManagerWeb<TSession> {\n getSession(request: Request): Promise<TSession | null>\n getSessionWithMeta(request: Request): Promise<{ data: TSession | null; meta: SessionMeta }>\n createSession(target: Headers, data: TSession): Promise<void>\n destroySession(target: Headers): void\n rotateSession(request: Request, target: Headers): Promise<TSession | null>\n}\n\nexport function createSessionManagerWeb<TSession>(\n config: SessionConfig,\n): SessionManagerWeb<TSession> {\n const secrets = normalizeSecrets(config.secret)\n const cookieName = config.cookieName ?? 'theo_session'\n const maxAge = config.maxAge ?? 604800 // 7 days\n\n function writeCookieWeb(target: Headers, token: string): void {\n appendCookieToHeaders(target, cookieName, token, {\n httpOnly: true,\n sameSite: 'lax',\n maxAge,\n path: '/',\n })\n }\n\n return {\n async getSession(request) {\n const { data } = await this.getSessionWithMeta(request)\n return data\n },\n\n async getSessionWithMeta(request) {\n const raw = getCookieFromRequest(request, cookieName)\n if (raw === undefined) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n const decoded = await decryptSessionWithFallback<TSession>(raw, secrets)\n if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n if (decoded.envelope.exp < Date.now()) {\n return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } }\n }\n return {\n data: decoded.envelope.data,\n meta: { secretIndex: decoded.index, needsReencrypt: decoded.index > 0 },\n }\n },\n\n async createSession(target, data) {\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookieWeb(target, token)\n },\n\n destroySession(target) {\n appendDeleteCookieToHeaders(target, cookieName)\n },\n\n async rotateSession(request, target) {\n const { data } = await this.getSessionWithMeta(request)\n if (data === null) return null\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookieWeb(target, token)\n return data\n },\n }\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — Web-Standards transparent re-encrypt helper.\n *\n * Mirror of `rotateIfNeeded(sm, req, res)` for the Web Request + Headers\n * shape. Reads session via `getSessionWithMeta`; if the cookie was\n * decrypted with a legacy secret (index > 0), re-issues the cookie with\n * the newest secret via `createSession(target, data)`.\n *\n * **EC-4 honest framing:** unlike the IncomingMessage path where the\n * timing constraint is \"before `res.writeHead` fires\", the Web Request\n * path's constraint is \"before the `Response` object is constructed and\n * returned to the runtime\". Caller MUST invoke `rotateIfNeededWeb`\n * BEFORE building the final `Response(body, { headers: target })` so the\n * re-encrypted Set-Cookie lands on the wire.\n */\nexport async function rotateIfNeededWeb<TSession>(\n sm: SessionManagerWeb<TSession>,\n request: Request,\n target: Headers,\n): Promise<TSession | null> {\n const { data, meta } = await sm.getSessionWithMeta(request)\n if (data !== null && meta.needsReencrypt) {\n await sm.createSession(target, data)\n }\n return data\n}\n\n/**\n * T3.2 — Transparent re-encrypt helper (EC-4 timing-safe wrapper).\n *\n * Call this in your `createContext` (before any rendering / streaming\n * starts). It:\n * 1. Reads the session via `getSessionWithMeta`.\n * 2. If the cookie was decrypted with a legacy secret (index > 0), it\n * immediately re-issues the cookie with `secrets[0]` so the next\n * request lands on the newest key.\n * 3. Returns the session data (or null) for use downstream.\n *\n * EC-4: this MUST run before `renderToPipeableStream` / `res.writeHead`\n * fires. Calling it inside an SSR component or handler body that has\n * already streamed bytes makes the re-encrypt a silent no-op (Set-Cookie\n * is locked once headers commit), trapping users on the legacy secret\n * forever once it's removed from the array.\n */\nexport async function rotateIfNeeded<TSession>(\n sm: SessionManager<TSession>,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<TSession | null> {\n const { data, meta } = await sm.getSessionWithMeta(req)\n if (data !== null && meta.needsReencrypt) {\n await sm.createSession(res, data)\n }\n return data\n}\n\n/**\n * EC-2 — Production secret guard. Refuses to boot when `NODE_ENV === 'production'`\n * AND the secret is too short (< 32 chars) OR matches a known placeholder.\n *\n * Accepts a single secret OR an array. In array form, each entry is validated\n * independently — a single bad entry refuses boot with an index-qualified message.\n */\nconst PLACEHOLDER_PATTERN = /CHANGE_ME|demo[-_]|placeholder/i\n\nexport function assertProductionSecret(secret: string | string[]): void {\n const arr = Array.isArray(secret) ? secret : [secret]\n const isProd = process.env.NODE_ENV === 'production'\n\n for (let i = 0; i < arr.length; i++) {\n const s = arr[i]\n const isPlaceholder = PLACEHOLDER_PATTERN.test(s)\n const isTooShort = s.length < 32\n const prefix = arr.length > 1 ? `Session secret at index ${i}` : 'Session secret'\n\n if (isProd) {\n if (isTooShort) {\n throw new Error(\n `${prefix} too short for production (${s.length} chars; minimum 32). ` +\n `Set a 32+ random char secret in your env (e.g., \\`openssl rand -hex 32\\`).`,\n )\n }\n if (isPlaceholder) {\n throw new Error(\n `${prefix} looks like a placeholder (\"${s.slice(0, 16)}…\") and NODE_ENV is \"production\". ` +\n `Replace it with a 32+ random char secret (e.g., \\`openssl rand -hex 32\\`) before deploying.`,\n )\n }\n continue\n }\n if (isPlaceholder || isTooShort) {\n console.warn(\n `[theokit] WARNING: ${prefix.toLowerCase()} is a placeholder or too short. ` +\n `This is OK for dev, but the production server will REFUSE to boot until you replace it.`,\n )\n }\n }\n}\n","/**\n * Shared encoding utilities for crypto-adjacent code.\n *\n * CR-020 DRY: `base64urlEncode` was duplicated across `oauth-pkce.ts` and\n * `oauth-state.ts`. Duplication in cryptographic helpers is a bug class —\n * a future RFC-driven tweak (e.g., padding semantics) would diverge across\n * files. Centralizing the single canonical implementation eliminates that\n * vector.\n *\n * Module is intentionally under `_internal/` so it is **not** part of the\n * public API surface. Consumers outside `packages/theo/src/server` must\n * not import from here.\n */\n\n/**\n * RFC 4648 §5 — base64url-encode bytes without padding. Used wherever an\n * RFC requires URL-safe base64 (PKCE per RFC 7636 §4.1, JWT per RFC 7515,\n * etc.). Pure function — does not mutate `bytes`.\n */\nexport function base64urlEncode(bytes: Uint8Array): string {\n let s = ''\n for (const b of bytes) s += String.fromCharCode(b)\n // The `=+$` trailing-padding regex is bounded (at most 2 chars for\n // base64). sonarjs flags the `+` quantifier conservatively, but the\n // input is the output of `btoa()` — a string with at most 2 trailing\n // `=` characters by spec.\n // eslint-disable-next-line sonarjs/slow-regex -- bounded trailing-padding pattern (max 2 chars)\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Constant-time string equality.\n *\n * CR-012: prior implementations early-exited on length mismatch\n * (`if (a.length !== b.length) return false`). That early exit leaks\n * the comparison-target length via wall-clock timing — observable when\n * the secret length is itself sensitive (length-based bucket assignment,\n * variable-length nonces). This implementation always iterates\n * `max(a.length, b.length)` characters, OR-folding mismatches into a\n * single accumulator that is checked once at the end.\n *\n * Returns `false` for empty inputs as a defensive default — never accept\n * \"both empty\" as a match.\n */\nexport function constantTimeEquals(a: string, b: string): boolean {\n if (typeof a !== 'string' || typeof b !== 'string') return false\n if (a.length === 0 || b.length === 0) return false\n\n const maxLen = Math.max(a.length, b.length)\n let diff = a.length ^ b.length\n for (let i = 0; i < maxLen; i++) {\n // charCodeAt returns NaN for out-of-range indices; (NaN ^ x) is x, so we\n // explicitly substitute 0 to keep the OR-fold meaningful.\n const ca = i < a.length ? a.charCodeAt(i) : 0\n const cb = i < b.length ? b.charCodeAt(i) : 0\n diff |= ca ^ cb\n }\n return diff === 0\n}\n","/**\n * T6.2 — RFC 6238 TOTP primitive.\n *\n * Reference: https://datatracker.ietf.org/doc/html/rfc6238\n * HMAC-based one-time password (HOTP) base: https://datatracker.ietf.org/doc/html/rfc4226\n *\n * Pure-function, dependency-free implementation via Web Crypto. Secrets\n * may be passed as Uint8Array (preferred) or base32 string.\n *\n * SECURITY NOTES (documented per EC-13):\n * - TOTP secrets are equivalent to passwords. Encrypt at rest using a\n * separate KMS key from the session secret. If your DB leaks, ALL\n * 2FA codes are compromised — rotate by forcing all users to re-enroll.\n * - Use `verifyTotp` constant-time (we use crypto.timingSafeEqual internally).\n */\n\n// CR-012 fix: use the shared constant-time helper that does not early-exit\n// on length mismatch (the previous local version did, leaking the digit\n// count via timing).\nimport { constantTimeEquals } from '../_internal/encoding.js'\n\n/** Supported HMAC algorithms per RFC 6238 §1.2. Default SHA-1. */\nexport type TotpAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-512'\n\nexport interface TotpOptions {\n /** Shared secret — bytes (preferred) OR base32 string. */\n secret: Uint8Array | string\n /** Time step in seconds. Default 30 (RFC 6238 §5.2 recommended). */\n step?: number\n /** Code length: 6, 7, or 8 digits. Default 6. */\n digits?: 6 | 7 | 8\n /** HMAC algorithm. Default SHA-1 (RFC default). */\n algorithm?: TotpAlgorithm\n /** Time in ms since epoch. Default Date.now(). */\n time?: number\n}\n\nexport interface VerifyTotpOptions extends TotpOptions {\n /**\n * Drift tolerance in number of steps on each side. Default 1\n * (RFC 6238 §5.2 recommends ±1 step = 90s total window).\n */\n window?: number\n}\n\nconst DEFAULT_STEP = 30\nconst DEFAULT_DIGITS = 6\nconst DEFAULT_ALGORITHM: TotpAlgorithm = 'SHA-1'\n\n/** Decode base32 (RFC 4648, no padding) to bytes. Throws on invalid input. */\nfunction base32Decode(s: string): Uint8Array {\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\n // Trailing `=` is bounded by spec to 0..6 chars; `\\s+` collapses runs\n // of whitespace. Input length is the user's TOTP secret (10..50 chars\n // typical), so super-linear backtracking cannot escalate.\n // eslint-disable-next-line sonarjs/slow-regex -- bounded inputs (TOTP secrets are short)\n const clean = s.toUpperCase().replace(/=+$/, '').replace(/\\s+/g, '')\n if (!/^[A-Z2-7]+$/.test(clean)) {\n throw new Error('Invalid base32 secret')\n }\n const bits: number[] = []\n for (const c of clean) {\n const idx = alphabet.indexOf(c)\n for (let i = 4; i >= 0; i--) bits.push((idx >> i) & 1)\n }\n // Trim trailing partial byte\n const fullBytes = Math.floor(bits.length / 8)\n const out = new Uint8Array(fullBytes)\n for (let i = 0; i < fullBytes; i++) {\n let v = 0\n for (let j = 0; j < 8; j++) v = (v << 1) | bits[i * 8 + j]\n out[i] = v\n }\n return out\n}\n\n/** Encode bytes to base32 (RFC 4648, no padding). */\nfunction base32Encode(bytes: Uint8Array): string {\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\n let out = ''\n let buffer = 0\n let bits = 0\n for (const b of bytes) {\n buffer = (buffer << 8) | b\n bits += 8\n while (bits >= 5) {\n out += alphabet[(buffer >> (bits - 5)) & 0x1f]\n bits -= 5\n }\n }\n if (bits > 0) out += alphabet[(buffer << (5 - bits)) & 0x1f]\n return out\n}\n\nfunction normalizeSecret(secret: Uint8Array | string): Uint8Array {\n if (typeof secret === 'string') return base32Decode(secret)\n return secret\n}\n\n/** Counter (8-byte big-endian) from time in ms / step in seconds. */\nfunction counterBytes(timeMs: number, stepSec: number): Uint8Array {\n const counter = Math.floor(timeMs / 1000 / stepSec)\n const out = new Uint8Array(8)\n // Two 32-bit halves (Number can safely represent up to 2^53; OK until year 285,000+)\n const hi = Math.floor(counter / 0x100000000)\n const lo = counter >>> 0\n out[0] = (hi >>> 24) & 0xff\n out[1] = (hi >>> 16) & 0xff\n out[2] = (hi >>> 8) & 0xff\n out[3] = hi & 0xff\n out[4] = (lo >>> 24) & 0xff\n out[5] = (lo >>> 16) & 0xff\n out[6] = (lo >>> 8) & 0xff\n out[7] = lo & 0xff\n return out\n}\n\nasync function hmac(\n secret: Uint8Array,\n message: Uint8Array,\n algorithm: TotpAlgorithm,\n): Promise<Uint8Array> {\n const key = await crypto.subtle.importKey(\n 'raw',\n secret as BufferSource,\n { name: 'HMAC', hash: algorithm },\n false,\n ['sign'],\n )\n const sig = await crypto.subtle.sign('HMAC', key, message as BufferSource)\n return new Uint8Array(sig)\n}\n\n/** RFC 4226 §5.3 dynamic truncation. */\nfunction truncate(mac: Uint8Array, digits: number): string {\n const offset = mac[mac.length - 1] & 0x0f\n const code =\n ((mac[offset] & 0x7f) << 24) |\n ((mac[offset + 1] & 0xff) << 16) |\n ((mac[offset + 2] & 0xff) << 8) |\n (mac[offset + 3] & 0xff)\n const mod = 10 ** digits\n return String(code % mod).padStart(digits, '0')\n}\n\n/** Generate a TOTP code for a given time. */\nexport async function generateTotp(opts: TotpOptions): Promise<string> {\n const secret = normalizeSecret(opts.secret)\n const step = opts.step ?? DEFAULT_STEP\n const digits = opts.digits ?? DEFAULT_DIGITS\n const algorithm = opts.algorithm ?? DEFAULT_ALGORITHM\n const time = opts.time ?? Date.now()\n const mac = await hmac(secret, counterBytes(time, step), algorithm)\n return truncate(mac, digits)\n}\n\n/**\n * Verify a TOTP code against the current window ± `window` steps.\n *\n * Returns `false` for malformed tokens (non-digit, wrong length) without\n * throwing — defensive against accidental user-input passthrough.\n */\nexport async function verifyTotp(token: string, opts: VerifyTotpOptions): Promise<boolean> {\n if (typeof token !== 'string') return false\n const digits = opts.digits ?? DEFAULT_DIGITS\n if (!/^\\d+$/.test(token) || token.length !== digits) return false\n\n const step = opts.step ?? DEFAULT_STEP\n const algorithm = opts.algorithm ?? DEFAULT_ALGORITHM\n const time = opts.time ?? Date.now()\n const drift = opts.window ?? 1\n const secret = normalizeSecret(opts.secret)\n\n // Walk the whole drift range constant-time-ish (no early exit).\n let matched = false\n for (let delta = -drift; delta <= drift; delta++) {\n const t = time + delta * step * 1000\n const code = truncate(await hmac(secret, counterBytes(t, step), algorithm), digits)\n if (constantTimeEquals(code, token)) matched = true\n }\n return matched\n}\n\n/** Generate a cryptographically random TOTP secret. Default 20 bytes (RFC minimum). */\nexport function generateTotpSecret(bytes = 20): Uint8Array {\n if (bytes < 16) {\n throw new Error('TOTP secret must be at least 16 bytes (RFC 6238 §3 minimum)')\n }\n const out = new Uint8Array(bytes)\n crypto.getRandomValues(out)\n return out\n}\n\n/**\n * Build an `otpauth://` URI for QR code enrollment. Format follows the\n * de facto Google Authenticator spec adopted by every major TOTP app.\n * otpauth://totp/<issuer>:<account>?secret=<base32>&issuer=<issuer>&algorithm=<alg>&digits=<n>&period=<sec>\n */\nexport interface TotpUriOptions {\n secret: Uint8Array\n issuer: string\n account: string\n algorithm?: TotpAlgorithm\n digits?: 6 | 7 | 8\n step?: number\n}\n\nexport function totpUri(opts: TotpUriOptions): string {\n const label = `${opts.issuer}:${opts.account}`\n const params = new URLSearchParams({\n secret: base32Encode(opts.secret),\n issuer: opts.issuer,\n algorithm: opts.algorithm ?? DEFAULT_ALGORITHM,\n digits: String(opts.digits ?? DEFAULT_DIGITS),\n period: String(opts.step ?? DEFAULT_STEP),\n })\n return `otpauth://totp/${encodeURIComponent(label)}?${params.toString()}`\n}\n","/**\n * T6.3 — Backup codes for 2FA recovery.\n *\n * generateBackupCodes returns N plaintexts (display once to user) + hashes\n * (store in DB). verifyBackupCode constant-time-walks all hashes and\n * returns the matchedHash so caller can delete the used code from storage\n * — replay protection lives at the caller (we can't see their database).\n *\n * SECURITY NOTES:\n * - Codes have ~40 bits entropy (8 chars × 5 bits per char from a\n * 32-char alphabet excluding I/L/O/0/1). Single-use → argon2id\n * overhead is unnecessary; SHA-256 of the normalized code suffices.\n * - The caller MUST atomically delete the matched hash on successful\n * verify. Without that, the code is replayable. We surface\n * matchedHash explicitly so this step is conspicuous.\n * - Pair this with throttleLoginAttempts (T6.1) on the verify endpoint\n * to prevent online brute-force against the 40-bit space.\n */\n\nexport interface BackupCodeOptions {\n /** How many codes to generate. Default 10. */\n count?: number\n /** Length in chars (excluding separator). Default 8. */\n length?: number\n /** Separator inserted at the midpoint. Default '-'; pass null to omit. */\n separator?: '-' | null\n /** Custom alphabet. Default excludes ambiguous chars (I, L, O, 0, 1). */\n alphabet?: string\n}\n\nexport interface BackupCode {\n plaintext: string\n hash: string\n}\n\nconst DEFAULT_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // no I, O, 0, 1\nconst DEFAULT_COUNT = 10\nconst DEFAULT_LENGTH = 8\n\n/**\n * Generate cryptographically random backup codes + their SHA-256 hashes.\n *\n * Plaintext is uppercase alphanumeric (default alphabet excludes ambiguous\n * chars). Separator inserted at the midpoint (default `-`).\n */\nexport async function generateBackupCodes(opts: BackupCodeOptions = {}): Promise<BackupCode[]> {\n const count = opts.count ?? DEFAULT_COUNT\n const length = opts.length ?? DEFAULT_LENGTH\n const separator = opts.separator === undefined ? '-' : opts.separator\n const alphabet = opts.alphabet ?? DEFAULT_ALPHABET\n\n // CR-022 fix: bound count and length explicitly. Pathological inputs\n // (count=Infinity, length=0) would cause an infinite loop or wasteful\n // resource usage. Bounds are generous (count ≤ 1000, length ∈ [4, 64])\n // — well above any realistic 2FA backup-codes use case.\n if (!Number.isInteger(count) || count <= 0 || count > 1000) {\n throw new RangeError(\n `generateBackupCodes: count must be an integer in [1, 1000] (got ${String(count)})`,\n )\n }\n if (!Number.isInteger(length) || length < 4 || length > 64) {\n throw new RangeError(\n `generateBackupCodes: length must be an integer in [4, 64] (got ${String(length)})`,\n )\n }\n if (alphabet.length < 8) {\n throw new RangeError(\n `generateBackupCodes: alphabet must contain at least 8 distinct chars (got ${alphabet.length})`,\n )\n }\n\n const codes: BackupCode[] = []\n const seen = new Set<string>()\n while (codes.length < count) {\n const raw = randomFromAlphabet(length, alphabet)\n if (seen.has(raw)) continue\n seen.add(raw)\n const plaintext = formatWithSeparator(raw, separator)\n const hash = await sha256Hex(normalizeCode(plaintext))\n codes.push({ plaintext, hash })\n }\n return codes\n}\n\n/**\n * Verify a code against a list of stored hashes. Walks all hashes\n * constant-time (no early exit). Returns `valid:true` + matchedHash on\n * hit; `valid:false` otherwise.\n *\n * `matchedHash` is the EXACT string the caller stored, ready to be passed\n * to a `DELETE FROM backup_codes WHERE hash = ?` query.\n */\nexport async function verifyBackupCode(\n code: string,\n hashes: readonly string[],\n): Promise<{ valid: boolean; matchedHash?: string }> {\n const candidate = await sha256Hex(normalizeCode(code))\n let matchedHash: string | undefined\n // Constant-time iteration over hashes: never short-circuit.\n for (const h of hashes) {\n if (constantTimeEquals(h, candidate)) {\n matchedHash = h\n }\n }\n return matchedHash ? { valid: true, matchedHash } : { valid: false }\n}\n\n/**\n * Normalize a code for hashing: uppercase + strip separator. This way\n * `abcd-efgh` and `ABCDEFGH` produce the same hash. Whitespace is also\n * stripped defensively.\n */\nfunction normalizeCode(input: string): string {\n return input.toUpperCase().replace(/[-\\s]+/g, '')\n}\n\nfunction formatWithSeparator(raw: string, separator: '-' | null): string {\n if (!separator || raw.length < 4) return raw\n const mid = Math.floor(raw.length / 2)\n return raw.slice(0, mid) + separator + raw.slice(mid)\n}\n\nfunction randomFromAlphabet(length: number, alphabet: string): string {\n const random = new Uint8Array(length)\n crypto.getRandomValues(random)\n let out = ''\n for (let i = 0; i < length; i++) {\n out += alphabet[random[i] % alphabet.length]\n }\n return out\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const bytes = new TextEncoder().encode(input)\n const hash = await crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\nfunction constantTimeEquals(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n","import type { RateLimitStore } from '../rate-limit/rate-limit-store.js'\n\n/**\n * T6.1 — Login throttling primitive.\n *\n * Per-credential failure counter backed by any `RateLimitStore`. After\n * `maxAttempts` failures within `windowMs`, the identifier is locked\n * for `lockoutMs`. Successful login resets the counter.\n *\n * Usage in a login handler:\n *\n * const state = await checkThrottle({ store, identifier: hash(email) })\n * if (!state.allowed) return 429\n * const valid = await verifyPassword(creds)\n * await recordAttempt({ store, identifier: hash(email) }, valid)\n * if (!valid) return 401\n *\n * SECURITY:\n * - NEVER pass raw email/username as `identifier`. Hash or normalize\n * first (e.g. SHA-256 of the lowercased email). This prevents PII\n * from landing in the rate-limit store or audit logs.\n * - Combine with the per-route IP rate limit (T2.2) for layered defense:\n * IP-level limits stop scripted floods; credential-level limits stop\n * distributed brute force.\n */\n\nexport interface ThrottleOptions {\n store: RateLimitStore\n /**\n * Stable identifier per credential. Hash raw emails/usernames before\n * passing in — never leak PII to the rate-limit key space.\n */\n identifier: string\n /** Failures before lockout. Default 5. */\n maxAttempts?: number\n /** Sliding window for counting failures. Default 15 minutes. */\n windowMs?: number\n /** Lockout duration after maxAttempts hit. Default 1 hour. */\n lockoutMs?: number\n}\n\nexport interface ThrottleState {\n allowed: boolean\n remainingAttempts: number\n /** Set when `allowed === false`. Absolute timestamp when the lockout expires. */\n lockedUntil?: Date\n}\n\nconst DEFAULT_MAX_ATTEMPTS = 5\nconst DEFAULT_LOCKOUT_MS = 60 * 60_000\n\n/**\n * Read the current throttle state for an identifier WITHOUT modifying\n * the counter. Call before validating credentials so locked attempts\n * are rejected fast.\n */\nexport async function checkThrottle(opts: ThrottleOptions): Promise<ThrottleState> {\n const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS\n const state = await opts.store.get(opts.identifier)\n if (!state) {\n return { allowed: true, remainingAttempts: maxAttempts }\n }\n if (state.count >= maxAttempts) {\n return {\n allowed: false,\n remainingAttempts: 0,\n // Lockout expires when the store TTL fires.\n lockedUntil: new Date(state.resetAt),\n }\n }\n return {\n allowed: true,\n remainingAttempts: Math.max(0, maxAttempts - state.count),\n }\n}\n\n/**\n * Record an attempt outcome. On success → reset the counter. On failure\n * → increment within the sliding window. Returns the new throttle state.\n *\n * Storage model: the store entry's TTL is `lockoutMs` (which doubles as\n * the failure-accumulation window). When `lockoutMs` is short — common\n * in tests or for soft lockouts — the entry auto-expires and the next\n * attempt starts fresh.\n */\nexport async function recordAttempt(\n opts: ThrottleOptions,\n success: boolean,\n): Promise<ThrottleState> {\n const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS\n if (success) {\n await opts.store.reset(opts.identifier)\n return {\n allowed: true,\n remainingAttempts: maxAttempts,\n }\n }\n const lockoutMs = opts.lockoutMs ?? DEFAULT_LOCKOUT_MS\n const state = await opts.store.incr(opts.identifier, lockoutMs)\n\n if (state.count >= maxAttempts) {\n return {\n allowed: false,\n remainingAttempts: 0,\n lockedUntil: new Date(state.resetAt),\n }\n }\n return {\n allowed: true,\n remainingAttempts: Math.max(0, maxAttempts - state.count),\n }\n}\n","/**\n * T7.3 — RFC 7636 PKCE primitive.\n *\n * Reference: https://datatracker.ietf.org/doc/html/rfc7636\n *\n * Pure-function, dependency-free implementation using Web Crypto.\n *\n * Usage in an OAuth authorization-code flow:\n *\n * const { codeVerifier, codeChallenge, codeChallengeMethod } = await generatePkceChallenge()\n * // Store codeVerifier in the user's session\n * // Send codeChallenge + codeChallengeMethod in the /authorize redirect\n * // ... user authenticates with provider ...\n * // On /callback, send codeVerifier with the code → token exchange\n */\n\nexport interface PkceChallenge {\n codeVerifier: string\n codeChallenge: string\n codeChallengeMethod: 'S256'\n}\n\n// CR-020 DRY: single canonical base64url encoder lives in _internal/encoding.\nimport { base64urlEncode } from '../_internal/encoding.js'\n\n/**\n * Compute the code_challenge from a code_verifier per RFC 7636 §4.2.\n * Exported separately to enable RFC 7636 Appendix B vector testing.\n */\nexport async function pkceChallengeFromVerifier(verifier: string): Promise<string> {\n const bytes = new TextEncoder().encode(verifier)\n const hash = await crypto.subtle.digest('SHA-256', bytes)\n return base64urlEncode(new Uint8Array(hash))\n}\n\n/**\n * Generate a fresh PKCE challenge pair.\n *\n * Verifier: 32 cryptographically random bytes → 43-char base64url string.\n * RFC 7636 §4.1 allows 43–128 chars; we ship the minimum-safe default.\n *\n * Method: 'S256' only — no 'plain' fallback. RFC 7636 §7.2 strongly\n * discourages plain and modern providers reject it.\n */\nexport async function generatePkceChallenge(): Promise<PkceChallenge> {\n const random = new Uint8Array(32)\n crypto.getRandomValues(random)\n const codeVerifier = base64urlEncode(random)\n const codeChallenge = await pkceChallengeFromVerifier(codeVerifier)\n return { codeVerifier, codeChallenge, codeChallengeMethod: 'S256' }\n}\n","/**\n * T7.4 — OAuth `state` parameter helpers (RFC 6749 §10.12).\n *\n * `state` is a cryptographically random anti-CSRF token. The client\n * stores it (in session/cookie/db) before redirecting to the\n * authorization endpoint and verifies it on the callback. Without it,\n * an attacker can authorize on the user's account using a captured code.\n */\n\n// CR-020 DRY + CR-012 constant-time: shared helpers in _internal/encoding.\nimport { base64urlEncode, constantTimeEquals } from '../_internal/encoding.js'\n\n/**\n * Generate a cryptographically random state token. Default 32 bytes\n * (~43 base64url chars). Increase for higher entropy if your provider\n * supports it.\n */\nexport function generateOAuthState(opts: { bytes?: number } = {}): string {\n const len = opts.bytes ?? 32\n const buf = new Uint8Array(len)\n crypto.getRandomValues(buf)\n return base64urlEncode(buf)\n}\n\n/**\n * Verify a state token. Constant-time compare. Empty inputs always fail\n * (defensive — never accept empty as \"they match\").\n *\n * CR-012 fix: `constantTimeEquals` does not early-exit on length mismatch,\n * so the comparison time is independent of how the input was forged.\n */\nexport function verifyOAuthState(provided: string, stored: string): boolean {\n return constantTimeEquals(provided, stored)\n}\n","/**\n * T7.4 — OpenID Connect Discovery 1.0 metadata fetcher.\n *\n * Reference: https://openid.net/specs/openid-connect-discovery-1_0.html\n *\n * Caches metadata in module scope. Cache key is the exact issuer string\n * (trailing-slash sensitive per RFC 8414 §3). Failures are NOT cached —\n * subsequent calls retry the fetch.\n *\n * EC-7 — HTTPS enforced. RFC 8414 §3 requires HTTPS for OIDC issuers.\n * Localhost (loopback) is allowed for development.\n */\n\nexport interface OidcMetadata {\n issuer: string\n authorization_endpoint: string\n token_endpoint: string\n jwks_uri?: string\n userinfo_endpoint?: string\n end_session_endpoint?: string\n scopes_supported?: string[]\n response_types_supported?: string[]\n grant_types_supported?: string[]\n id_token_signing_alg_values_supported?: string[]\n [key: string]: unknown // OIDC providers ship many optional fields\n}\n\nconst cache = new Map<string, Promise<OidcMetadata>>()\n\n/** Clear the discovery cache. Used by tests. */\nexport function clearOidcCache(): void {\n cache.clear()\n}\n\nfunction isLoopback(host: string): boolean {\n return host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host === '::1'\n}\n\n/**\n * Fetch (or return cached) OIDC provider metadata.\n *\n * Throws when:\n * - Issuer URL is malformed\n * - Issuer is HTTP and not a loopback host (EC-7)\n * - HTTP response is non-OK\n * - Metadata lacks `authorization_endpoint` (sanity check)\n */\nexport async function discoverOidcProvider(issuer: string | URL): Promise<OidcMetadata> {\n const issuerStr = typeof issuer === 'string' ? issuer : issuer.toString()\n // Validate protocol BEFORE checking cache — invalid issuers should never poison.\n const url = new URL(issuerStr)\n if (url.protocol !== 'https:' && !isLoopback(url.hostname)) {\n throw new Error(\n `OIDC issuer must use HTTPS (received \"${url.protocol}//${url.hostname}…\"). ` +\n `RFC 8414 §3 forbids plain HTTP except for loopback hosts.`,\n )\n }\n\n const cached = cache.get(issuerStr)\n if (cached) return cached\n\n const promise = (async () => {\n const wellKnown = new URL(\n '.well-known/openid-configuration',\n issuerStr.endsWith('/') ? issuerStr : issuerStr + '/',\n )\n const res = await fetch(wellKnown)\n if (!res.ok) {\n throw new Error(`OIDC discovery failed: ${res.status} ${res.statusText} at ${wellKnown}`)\n }\n const metadata = (await res.json()) as OidcMetadata\n if (!metadata.authorization_endpoint) {\n throw new Error('OIDC metadata missing authorization_endpoint — provider response is invalid')\n }\n return metadata\n })()\n // Cache the promise; on rejection, remove so the next call retries.\n cache.set(issuerStr, promise)\n promise.catch(() => {\n if (cache.get(issuerStr) === promise) cache.delete(issuerStr)\n })\n return promise\n}\n"],"mappings":"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","names":["constantTimeEquals"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
walkSourceFiles
|
|
3
|
-
} from "./chunk-X2VVCJ4V.js";
|
|
4
1
|
import {
|
|
5
2
|
HTTP_METHODS
|
|
6
3
|
} from "./chunk-WSJKACWB.js";
|
|
4
|
+
import {
|
|
5
|
+
walkSourceFiles
|
|
6
|
+
} from "./chunk-X2VVCJ4V.js";
|
|
7
7
|
|
|
8
8
|
// src/server/scan/match.ts
|
|
9
9
|
function compilePattern(routePath) {
|
|
@@ -34,7 +34,7 @@ function matchRoute(url, routes) {
|
|
|
34
34
|
|
|
35
35
|
// src/server/scan/scan.ts
|
|
36
36
|
import { existsSync, statSync } from "fs";
|
|
37
|
-
import { extname, join, relative } from "path";
|
|
37
|
+
import { basename, extname, join, relative } from "path";
|
|
38
38
|
|
|
39
39
|
// src/server/scan/detect-http-methods.ts
|
|
40
40
|
import { readFileSync } from "fs";
|
|
@@ -89,8 +89,88 @@ function detectExportedHttpMethods(filePath, content) {
|
|
|
89
89
|
return [...found].sort();
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// src/server/scan/errors.ts
|
|
93
|
+
var ROUTER_MIGRATION_GUIDE_URL = "https://theokit.dev/migration/0.3-to-0.4-router";
|
|
94
|
+
var RouterConventionError = class extends Error {
|
|
95
|
+
name = "RouterConventionError";
|
|
96
|
+
file;
|
|
97
|
+
suggestion;
|
|
98
|
+
migrationUrl;
|
|
99
|
+
constructor(opts) {
|
|
100
|
+
const migrationUrl = opts.migrationUrl ?? ROUTER_MIGRATION_GUIDE_URL;
|
|
101
|
+
const message = [
|
|
102
|
+
`Router convention violation: dotted route basename is not supported in theokit 0.4+.`,
|
|
103
|
+
``,
|
|
104
|
+
` File: ${opts.file}`,
|
|
105
|
+
` Use directory-nested form: ${opts.suggestion}`,
|
|
106
|
+
``,
|
|
107
|
+
`Migration guide: ${migrationUrl}`,
|
|
108
|
+
`Run \`theokit migrate router\` to convert all dotted basenames automatically.`
|
|
109
|
+
].join("\n");
|
|
110
|
+
super(message);
|
|
111
|
+
this.file = opts.file;
|
|
112
|
+
this.suggestion = opts.suggestion;
|
|
113
|
+
this.migrationUrl = migrationUrl;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
92
117
|
// src/server/scan/scan.ts
|
|
93
118
|
var ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
119
|
+
var TEST_OR_SPEC_RE = /\.(test|spec)\.[jt]sx?$/;
|
|
120
|
+
function isTestOrSpecFile(filePath) {
|
|
121
|
+
return TEST_OR_SPEC_RE.test(basename(filePath));
|
|
122
|
+
}
|
|
123
|
+
function hasDotOutsideBrackets(segment) {
|
|
124
|
+
let depth = 0;
|
|
125
|
+
for (const ch of segment) {
|
|
126
|
+
if (ch === "[") depth++;
|
|
127
|
+
else if (ch === "]") depth--;
|
|
128
|
+
else if (ch === "." && depth === 0) return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
function splitDottedSegmentOutsideBrackets(segment) {
|
|
133
|
+
const parts = [];
|
|
134
|
+
let current = "";
|
|
135
|
+
let depth = 0;
|
|
136
|
+
for (const ch of segment) {
|
|
137
|
+
if (ch === "[") {
|
|
138
|
+
depth++;
|
|
139
|
+
current += ch;
|
|
140
|
+
} else if (ch === "]") {
|
|
141
|
+
depth--;
|
|
142
|
+
current += ch;
|
|
143
|
+
} else if (ch === "." && depth === 0) {
|
|
144
|
+
if (current) parts.push(current);
|
|
145
|
+
current = "";
|
|
146
|
+
} else {
|
|
147
|
+
current += ch;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (current) parts.push(current);
|
|
151
|
+
return parts;
|
|
152
|
+
}
|
|
153
|
+
function buildDirectoryNestedSuggestion(filePath, routesDir) {
|
|
154
|
+
const rel = relative(routesDir, filePath).replace(/\\/g, "/");
|
|
155
|
+
const ext = extname(rel);
|
|
156
|
+
const withoutExt = rel.slice(0, -ext.length);
|
|
157
|
+
const segments = withoutExt.split("/").flatMap(splitDottedSegmentOutsideBrackets);
|
|
158
|
+
return `routes/${segments.join("/")}${ext}`;
|
|
159
|
+
}
|
|
160
|
+
function assertNoDottedSegment(filePath, routesDir) {
|
|
161
|
+
const rel = relative(routesDir, filePath).replace(/\\/g, "/");
|
|
162
|
+
const ext = extname(rel);
|
|
163
|
+
const withoutExt = rel.slice(0, -ext.length);
|
|
164
|
+
const segments = withoutExt.split("/");
|
|
165
|
+
for (const seg of segments) {
|
|
166
|
+
if (hasDotOutsideBrackets(seg)) {
|
|
167
|
+
throw new RouterConventionError({
|
|
168
|
+
file: filePath,
|
|
169
|
+
suggestion: buildDirectoryNestedSuggestion(filePath, routesDir)
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
94
174
|
function fileToRoutePath(filePath, routesDir) {
|
|
95
175
|
let rel = relative(routesDir, filePath);
|
|
96
176
|
const ext = extname(rel);
|
|
@@ -112,6 +192,8 @@ function scanServerRoutes(serverDir) {
|
|
|
112
192
|
}
|
|
113
193
|
const results = [];
|
|
114
194
|
walkSourceFiles(routesDir, { extensions: ROUTE_EXTENSIONS }, (absPath) => {
|
|
195
|
+
if (isTestOrSpecFile(absPath)) return;
|
|
196
|
+
assertNoDottedSegment(absPath, routesDir);
|
|
115
197
|
const routePath = fileToRoutePath(absPath, routesDir);
|
|
116
198
|
const { pattern, paramNames } = compilePattern(routePath);
|
|
117
199
|
const methods = detectExportedHttpMethods(absPath);
|
|
@@ -174,4 +256,4 @@ export {
|
|
|
174
256
|
scanServerRoutes,
|
|
175
257
|
scanWebSocketRoutes
|
|
176
258
|
};
|
|
177
|
-
//# sourceMappingURL=chunk-
|
|
259
|
+
//# sourceMappingURL=chunk-OLPB36O2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/scan/match.ts","../src/server/scan/scan.ts","../src/server/scan/detect-http-methods.ts","../src/server/scan/errors.ts","../src/server/scan/ws-scan.ts"],"sourcesContent":["export interface ServerRouteNode {\n filePath: string\n routePath: string\n paramNames: string[]\n pattern: RegExp\n /** HTTP methods (uppercase) the route file exports. Optional for backward\n * compatibility with manifests generated before G1. Empty array means the\n * file has no HTTP exports (util-only); undefined means \"not detected\". */\n methods?: string[]\n}\n\nexport function compilePattern(routePath: string): {\n pattern: RegExp\n paramNames: string[]\n} {\n const paramNames: string[] = []\n // Single pass: handle both catch-all (:...name) and regular (:name) params\n const regexStr = routePath.replace(/:(?:\\.\\.\\.)?([^/]+)/g, (match: string, name: string) => {\n paramNames.push(name)\n // Catch-all matches across slashes, regular matches single segment\n return match.startsWith(':...') ? '(.+)' : '([^/]+)'\n })\n // `regexStr` is derived from a developer-authored route path (build-time\n // input, not HTTP-controlled). The `security/detect-non-literal-regexp`\n // rule cannot see this constraint — disable narrowly.\n // eslint-disable-next-line security/detect-non-literal-regexp -- route pattern from build-time scan, never HTTP input\n return { pattern: new RegExp(`^${regexStr}$`), paramNames }\n}\n\nexport function matchRoute(\n url: string,\n routes: ServerRouteNode[],\n): { route: ServerRouteNode; params: Record<string, string> } | null {\n // Strip query string and trailing slash\n let path = url.split('?')[0]\n if (path.length > 1 && path.endsWith('/')) {\n path = path.slice(0, -1)\n }\n\n for (const route of routes) {\n const match = route.pattern.exec(path)\n if (match) {\n const params: Record<string, string> = {}\n route.paramNames.forEach((name, i) => {\n params[name] = match[i + 1]\n })\n return { route, params }\n }\n }\n return null\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/routes/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { basename, extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport { detectExportedHttpMethods } from './detect-http-methods.js'\nimport { RouterConventionError } from './errors.js'\nimport { compilePattern, type ServerRouteNode } from './match.js'\n\nconst ROUTE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\n// EC-4: Co-located unit/spec tests must be silently skipped by the scanner,\n// BEFORE the dotted-basename check fires. Matches `*.test.ts|tsx|js|jsx`\n// and `*.spec.ts|tsx|js|jsx`.\nconst TEST_OR_SPEC_RE = /\\.(test|spec)\\.[jt]sx?$/\n\nfunction isTestOrSpecFile(filePath: string): boolean {\n return TEST_OR_SPEC_RE.test(basename(filePath))\n}\n\nfunction hasDotOutsideBrackets(segment: string): boolean {\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') depth++\n else if (ch === ']') depth--\n else if (ch === '.' && depth === 0) return true\n }\n return false\n}\n\nfunction splitDottedSegmentOutsideBrackets(segment: string): string[] {\n const parts: string[] = []\n let current = ''\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') {\n depth++\n current += ch\n } else if (ch === ']') {\n depth--\n current += ch\n } else if (ch === '.' && depth === 0) {\n if (current) parts.push(current)\n current = ''\n } else {\n current += ch\n }\n }\n if (current) parts.push(current)\n return parts\n}\n\nfunction buildDirectoryNestedSuggestion(filePath: string, routesDir: string): string {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/').flatMap(splitDottedSegmentOutsideBrackets)\n return `routes/${segments.join('/')}${ext}`\n}\n\nfunction assertNoDottedSegment(filePath: string, routesDir: string): void {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/')\n for (const seg of segments) {\n if (hasDotOutsideBrackets(seg)) {\n throw new RouterConventionError({\n file: filePath,\n suggestion: buildDirectoryNestedSuggestion(filePath, routesDir),\n })\n }\n }\n}\n\nfunction fileToRoutePath(filePath: string, routesDir: string): string {\n let rel = relative(routesDir, filePath)\n // Strip extension\n const ext = extname(rel)\n rel = rel.slice(0, -ext.length)\n // Normalize separators\n rel = rel.replace(/\\\\/g, '/')\n // Strip index suffix\n if (rel.endsWith('/index')) {\n rel = rel.slice(0, -6)\n } else if (rel === 'index') {\n rel = ''\n }\n // Replace [...param] with :...param (catch-all, before regular params).\n // Replace [param] with :param. Inputs are file paths bounded by the\n // OS filename limit; the bracket capture is bounded by `]`.\n rel = rel.replace(/\\[\\.\\.\\.([^\\]]+)\\]/g, ':...$1')\n // eslint-disable-next-line sonarjs/slow-regex -- bounded by `]`; input is a single filename\n rel = rel.replace(/\\[([^\\]]+)\\]/g, ':$1')\n return `/api/${rel}`\n}\n\nexport function scanServerRoutes(serverDir: string): ServerRouteNode[] {\n const routesDir = join(serverDir, 'routes')\n if (!existsSync(routesDir) || !statSync(routesDir).isDirectory()) {\n return []\n }\n\n const results: ServerRouteNode[] = []\n walkSourceFiles(routesDir, { extensions: ROUTE_EXTENSIONS }, (absPath) => {\n // EC-4: skip co-located test/spec files BEFORE the dotted-basename check\n if (isTestOrSpecFile(absPath)) return\n\n // G6 T1.1: reject dotted basenames (legacy convention that produced wrong\n // paramNames due to greedy `:(?:\\.\\.\\.)?([^/]+)` regex in compilePattern).\n assertNoDottedSegment(absPath, routesDir)\n\n const routePath = fileToRoutePath(absPath, routesDir)\n const { pattern, paramNames } = compilePattern(routePath)\n const methods = detectExportedHttpMethods(absPath)\n results.push({\n filePath: absPath,\n routePath,\n paramNames,\n pattern,\n methods,\n })\n })\n\n // T1.4 / EC-2 — refuse to scan if a user route collides with the reserved\n // batch endpoint path. User must rename or disable batching.\n const conflicting = results.find((r) => r.routePath === '/api/__theo_batch__')\n if (conflicting) {\n throw new Error(\n `Server route ${conflicting.filePath} resolves to '/api/__theo_batch__' which is reserved for the batch endpoint. Rename the route or disable batching in theo.config.ts.`,\n )\n }\n\n // Sort: static first, then dynamic, then catch-all last\n const isCatchAll = (route: ServerRouteNode) => route.routePath.includes(':...')\n results.sort((a, b) => {\n const aStatic = a.paramNames.length === 0\n const bStatic = b.paramNames.length === 0\n const aCatchAll = isCatchAll(a)\n const bCatchAll = isCatchAll(b)\n\n // Static routes first\n if (aStatic && !bStatic) return -1\n if (!aStatic && bStatic) return 1\n // Catch-all routes last\n if (aCatchAll && !bCatchAll) return 1\n if (!aCatchAll && bCatchAll) return -1\n return a.routePath.localeCompare(b.routePath)\n })\n\n return results\n}\n","/**\n * Detect which HTTP-method named exports a route file declares.\n *\n * Uses the TypeScript compiler API (not regex) per G1 edge-case review EC-4:\n * regex over file content emits false positives for `// export const GET = ...`\n * in comments and `` `export const GET = ...` `` in template literals. AST\n * walking avoids both classes of bug.\n *\n * `typescript` ships as CommonJS with internal dynamic `require('fs')`.\n * When loaded via ESM `import`, the dynamic requires fail at module-bootstrap.\n * We use `createRequire(import.meta.url)` to keep the package on its native\n * CJS path. The type-only namespace import gives us the AST helpers shape.\n *\n * Returns the set of HTTP methods (uppercase) the file exports. Empty array\n * means the file has no HTTP exports (the route file is util-only).\n */\n\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\n\nimport type * as TS from 'typescript'\n\nimport { HTTP_METHODS, type HttpMethod } from '../../core/contracts/http-methods.js'\n\nconst require_ = createRequire(import.meta.url)\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst ts = require_('typescript') as typeof TS\n\nconst HTTP_METHOD_NAMES = new Set<string>(HTTP_METHODS)\n\nfunction hasExportModifier(modifiers: readonly TS.Modifier[] | undefined): boolean {\n if (!modifiers) return false\n for (const m of modifiers) {\n if (m.kind === ts.SyntaxKind.ExportKeyword) return true\n }\n return false\n}\n\nfunction collectFromStatement(stmt: TS.Statement, found: Set<HttpMethod>): void {\n // `export const GET = ...` / `export function GET ...` / `export async function GET ...`\n if (ts.isVariableStatement(stmt) && hasExportModifier(ts.getModifiers(stmt))) {\n for (const decl of stmt.declarationList.declarations) {\n if (ts.isIdentifier(decl.name) && HTTP_METHOD_NAMES.has(decl.name.text)) {\n found.add(decl.name.text as HttpMethod)\n }\n }\n return\n }\n\n if (\n (ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) &&\n hasExportModifier(ts.getModifiers(stmt))\n ) {\n if (stmt.name && HTTP_METHOD_NAMES.has(stmt.name.text)) {\n found.add(stmt.name.text as HttpMethod)\n }\n return\n }\n\n // `export { GET }` / `export { handler as GET } from './shared'` (EC-5)\n if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {\n for (const spec of stmt.exportClause.elements) {\n // spec.name is the exported (re-)name; spec.propertyName is the original (when renamed)\n if (HTTP_METHOD_NAMES.has(spec.name.text)) {\n found.add(spec.name.text as HttpMethod)\n }\n }\n }\n}\n\nexport function detectExportedHttpMethods(filePath: string, content?: string): HttpMethod[] {\n const src = content ?? readFileSync(filePath, 'utf-8')\n const sourceFile = ts.createSourceFile(\n filePath,\n src,\n ts.ScriptTarget.Latest,\n /* setParentNodes */ false,\n ts.ScriptKind.TS,\n )\n const found = new Set<HttpMethod>()\n for (const stmt of sourceFile.statements) {\n collectFromStatement(stmt, found)\n }\n return [...found].sort()\n}\n","/**\n * Router-convention errors thrown by the server-route scanner.\n *\n * Plan: .claude/knowledge-base/plans/g6-router-convention-plan.md v1.1\n *\n * theokit 0.4.0+ enforces directory-nested file-system routing\n * (`auth/[provider]/login.ts`) and REJECTS dotted-basename routes\n * (`auth.[provider].login.ts`) because the legacy regex extracted the\n * dotted basename incorrectly — `params.provider` was undefined at request\n * time. See ADR-XXX (router-convention-decision) in CHANGELOG 0.4.0.\n */\n\n/**\n * Canonical migration guide URL. T4.2 establishes this as the authoritative\n * landing page. EC-3: error message uses this constant so the URL never\n * drifts from the doc location.\n */\nexport const ROUTER_MIGRATION_GUIDE_URL = 'https://theokit.dev/migration/0.3-to-0.4-router'\n\nexport interface RouterConventionErrorOptions {\n /** Absolute path of the offending route file. */\n file: string\n /** Suggested directory-nested replacement path (relative, e.g. `routes/auth/[provider]/login.ts`). */\n suggestion: string\n /** Migration guide URL (defaults to `ROUTER_MIGRATION_GUIDE_URL`). */\n migrationUrl?: string\n}\n\n/**\n * Thrown by `scanServerRoutes` when a route file uses the legacy\n * dotted-basename convention (`auth.[provider].login.ts`).\n *\n * The error is FAIL-FAST by design — running with a route that has wrong\n * `paramNames` produces silent 404s at request time, which is strictly\n * worse than a build-time error.\n */\nexport class RouterConventionError extends Error {\n override readonly name = 'RouterConventionError'\n readonly file: string\n readonly suggestion: string\n readonly migrationUrl: string\n\n constructor(opts: RouterConventionErrorOptions) {\n const migrationUrl = opts.migrationUrl ?? ROUTER_MIGRATION_GUIDE_URL\n const message = [\n `Router convention violation: dotted route basename is not supported in theokit 0.4+.`,\n ``,\n ` File: ${opts.file}`,\n ` Use directory-nested form: ${opts.suggestion}`,\n ``,\n `Migration guide: ${migrationUrl}`,\n `Run \\`theokit migrate router\\` to convert all dotted basenames automatically.`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.suggestion = opts.suggestion\n this.migrationUrl = migrationUrl\n }\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/ws/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nconst WS_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\nexport interface WebSocketRouteNode {\n filePath: string\n wsPath: string\n}\n\nexport function scanWebSocketRoutes(serverDir: string): WebSocketRouteNode[] {\n const wsDir = join(serverDir, 'ws')\n if (!existsSync(wsDir) || !statSync(wsDir).isDirectory()) {\n return []\n }\n\n const results: WebSocketRouteNode[] = []\n walkSourceFiles(wsDir, { extensions: WS_EXTENSIONS }, (absPath) => {\n let rel = relative(wsDir, absPath)\n rel = rel.replace(/\\\\/g, '/')\n rel = rel.slice(0, -extname(rel).length)\n if (rel.endsWith('/index')) rel = rel.slice(0, -6)\n else if (rel === 'index') rel = ''\n results.push({\n filePath: absPath,\n wsPath: `/ws/${rel}`,\n })\n })\n return results\n}\n"],"mappings":"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","names":["existsSync","statSync","extname","join","relative","join","existsSync","statSync","relative","extname"]}
|