@access-dlsu/leapify 0.260507.4 → 0.260524.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts.map +1 -1
- package/dist/auth/auth.d.ts.map +1 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/chunk-64MMUYMK.cjs +167 -0
- package/dist/chunk-64MMUYMK.cjs.map +1 -0
- package/dist/chunk-AKCERDGP.js +161 -0
- package/dist/chunk-AKCERDGP.js.map +1 -0
- package/dist/client/auth.d.ts +1 -13
- package/dist/client/auth.d.ts.map +1 -1
- package/dist/client/index.cjs +55 -841
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.ts +3 -8
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +53 -839
- package/dist/client/index.js.map +1 -1
- package/dist/client/turnstile.d.ts +25 -0
- package/dist/client/turnstile.d.ts.map +1 -0
- package/dist/client/types.d.ts +2 -15
- package/dist/client/types.d.ts.map +1 -1
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/schema/classes.d.ts +0 -19
- package/dist/db/schema/classes.d.ts.map +1 -1
- package/dist/db/schema/site-config.d.ts +0 -83
- package/dist/db/schema/site-config.d.ts.map +1 -1
- package/dist/index.cjs +687 -38309
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +541 -38156
- package/dist/index.js.map +1 -1
- package/dist/lib/middleware/cors.d.ts.map +1 -1
- package/dist/lib/middleware/rate-limit.d.ts.map +1 -1
- package/dist/lib/middleware/referer-guard.d.ts.map +1 -1
- package/dist/lib/middleware/turnstile-challenge.cjs +29 -0
- package/dist/lib/middleware/turnstile-challenge.cjs.map +1 -0
- package/dist/lib/middleware/turnstile-challenge.d.ts +45 -0
- package/dist/lib/middleware/turnstile-challenge.d.ts.map +1 -0
- package/dist/lib/middleware/turnstile-challenge.js +4 -0
- package/dist/lib/middleware/turnstile-challenge.js.map +1 -0
- package/dist/queues/jobs.d.ts +0 -5
- package/dist/queues/jobs.d.ts.map +1 -1
- package/dist/routes/classes.d.ts.map +1 -1
- package/dist/routes/faqs.d.ts.map +1 -1
- package/dist/routes/site-config.d.ts.map +1 -1
- package/dist/routes/themes.d.ts.map +1 -1
- package/dist/routes/uploads.d.ts.map +1 -1
- package/dist/types.d.ts +5 -26
- package/dist/types.d.ts.map +1 -1
- package/dist/worker-handler.d.ts +1 -0
- package/dist/worker-handler.d.ts.map +1 -1
- package/dist/worker.js +704 -64338
- package/dist/worker.js.map +1 -1
- package/package.json +155 -153
- package/dist/bun-sqlite-dialect-na--YwnN-NIYANHVJ.cjs +0 -162
- package/dist/bun-sqlite-dialect-na--YwnN-NIYANHVJ.cjs.map +0 -1
- package/dist/bun-sqlite-dialect-na--YwnN-XVQNOKSL.js +0 -160
- package/dist/bun-sqlite-dialect-na--YwnN-XVQNOKSL.js.map +0 -1
- package/dist/chunk-4DPT2KQR.cjs +0 -467
- package/dist/chunk-4DPT2KQR.cjs.map +0 -1
- package/dist/chunk-5JKLV7IE.cjs +0 -2962
- package/dist/chunk-5JKLV7IE.cjs.map +0 -1
- package/dist/chunk-5OQD5ALM.cjs +0 -76
- package/dist/chunk-5OQD5ALM.cjs.map +0 -1
- package/dist/chunk-6MMWL46O.cjs +0 -7170
- package/dist/chunk-6MMWL46O.cjs.map +0 -1
- package/dist/chunk-BFMJDSDI.cjs +0 -2297
- package/dist/chunk-BFMJDSDI.cjs.map +0 -1
- package/dist/chunk-EGRHWZRV.js +0 -3
- package/dist/chunk-EGRHWZRV.js.map +0 -1
- package/dist/chunk-EMMSS5I5.cjs +0 -37
- package/dist/chunk-EMMSS5I5.cjs.map +0 -1
- package/dist/chunk-FUCJEA2S.js +0 -6196
- package/dist/chunk-FUCJEA2S.js.map +0 -1
- package/dist/chunk-G3PMV62Z.js +0 -33
- package/dist/chunk-G3PMV62Z.js.map +0 -1
- package/dist/chunk-GNRL67OU.js +0 -2949
- package/dist/chunk-GNRL67OU.js.map +0 -1
- package/dist/chunk-HHNEB7YR.js +0 -8
- package/dist/chunk-HHNEB7YR.js.map +0 -1
- package/dist/chunk-IQEWVHLM.js +0 -889
- package/dist/chunk-IQEWVHLM.js.map +0 -1
- package/dist/chunk-JIZPYG6H.js +0 -72
- package/dist/chunk-JIZPYG6H.js.map +0 -1
- package/dist/chunk-JPVIXCF5.cjs +0 -10
- package/dist/chunk-JPVIXCF5.cjs.map +0 -1
- package/dist/chunk-JQSZJWBN.cjs +0 -3075
- package/dist/chunk-JQSZJWBN.cjs.map +0 -1
- package/dist/chunk-LJ5BSSYE.js +0 -2286
- package/dist/chunk-LJ5BSSYE.js.map +0 -1
- package/dist/chunk-MCOLCTFX.js +0 -213
- package/dist/chunk-MCOLCTFX.js.map +0 -1
- package/dist/chunk-MKWVLWVJ.cjs +0 -219
- package/dist/chunk-MKWVLWVJ.cjs.map +0 -1
- package/dist/chunk-MNEW2V4T.js +0 -447
- package/dist/chunk-MNEW2V4T.js.map +0 -1
- package/dist/chunk-MY37YE52.js +0 -3034
- package/dist/chunk-MY37YE52.js.map +0 -1
- package/dist/chunk-NKIQRCOM.cjs +0 -4
- package/dist/chunk-NKIQRCOM.cjs.map +0 -1
- package/dist/chunk-OK6RVPEH.cjs +0 -6200
- package/dist/chunk-OK6RVPEH.cjs.map +0 -1
- package/dist/chunk-RFP2X2FA.cjs +0 -903
- package/dist/chunk-RFP2X2FA.cjs.map +0 -1
- package/dist/chunk-XJSWMHDL.js +0 -7142
- package/dist/chunk-XJSWMHDL.js.map +0 -1
- package/dist/client/pow.d.ts +0 -28
- package/dist/client/pow.d.ts.map +0 -1
- package/dist/cron/lifecycle-check.d.ts +0 -10
- package/dist/cron/lifecycle-check.d.ts.map +0 -1
- package/dist/d1-sqlite-dialect-C2B7YsIT-6TVV7EJ5.js +0 -122
- package/dist/d1-sqlite-dialect-C2B7YsIT-6TVV7EJ5.js.map +0 -1
- package/dist/d1-sqlite-dialect-C2B7YsIT-PE74FLHQ.cjs +0 -124
- package/dist/d1-sqlite-dialect-C2B7YsIT-PE74FLHQ.cjs.map +0 -1
- package/dist/dist-DZHA5VYX.cjs +0 -260
- package/dist/dist-DZHA5VYX.cjs.map +0 -1
- package/dist/dist-RRQUBLLO.js +0 -258
- package/dist/dist-RRQUBLLO.js.map +0 -1
- package/dist/kysely-adapter-C76KJVG7.js +0 -10
- package/dist/kysely-adapter-C76KJVG7.js.map +0 -1
- package/dist/kysely-adapter-TGY4UUP5.cjs +0 -27
- package/dist/kysely-adapter-TGY4UUP5.cjs.map +0 -1
- package/dist/lib/middleware/pow-challenge.cjs +0 -29
- package/dist/lib/middleware/pow-challenge.cjs.map +0 -1
- package/dist/lib/middleware/pow-challenge.d.ts +0 -58
- package/dist/lib/middleware/pow-challenge.d.ts.map +0 -1
- package/dist/lib/middleware/pow-challenge.js +0 -4
- package/dist/lib/middleware/pow-challenge.js.map +0 -1
- package/dist/node-sqlite-dialect-B3H37T3R.cjs +0 -162
- package/dist/node-sqlite-dialect-B3H37T3R.cjs.map +0 -1
- package/dist/node-sqlite-dialect-GDP7ZE54.js +0 -160
- package/dist/node-sqlite-dialect-GDP7ZE54.js.map +0 -1
- package/dist/routes/contentful-sync.d.ts +0 -4
- package/dist/routes/contentful-sync.d.ts.map +0 -1
- package/dist/services/contentful-management.d.ts +0 -38
- package/dist/services/contentful-management.d.ts.map +0 -1
- package/dist/services/contentful.d.ts +0 -97
- package/dist/services/contentful.d.ts.map +0 -1
- package/dist/services/snapshot.d.ts +0 -95
- package/dist/services/snapshot.d.ts.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/middleware/pow-challenge.ts"],"names":[],"mappings":";;;AAyBO,IAAM,QAAA,GAAW;AAGjB,IAAM,eAAA,GAAkB,GAAG,QAAQ,CAAA,OAAA;AAGnC,IAAM,eAAA,GAAkB;AAG/B,IAAM,mBAAA,GAAsB,gBAAA;AAG5B,IAAM,sBAAA,GAAyB,CAAA;AAG/B,IAAM,iBAAA,GAAoB,GAAA;AAG1B,IAAM,kBAAA,GAAqB,IAAA;AAG3B,IAAM,YAAA,GAAe,CAAC,SAAA,EAAW,WAAA,EAAa,WAAW,CAAA;AAIzD,SAAS,gBAAgB,KAAA,EAA2B;AAClD,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAA,IAAU,MAAA,CAAO,aAAa,IAAI,CAAA;AAAA,EACpC;AACA,EAAA,OAAO,IAAA,CAAK,MAAM,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC/E;AAEA,SAAS,gBAAgB,GAAA,EAAsC;AAC7D,EAAA,MAAM,MAAA,GAAS,IAAI,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA,CAAE,OAAA,CAAQ,MAAM,GAAG,CAAA;AACvD,EAAA,MAAM,MAAA,GAAS,KAAK,MAAM,CAAA;AAC1B,EAAA,MAAM,QAAQ,IAAI,UAAA,CAAW,IAAI,WAAA,CAAY,MAAA,CAAO,MAAM,CAAC,CAAA;AAC3D,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,CAAO,QAAQ,CAAA,EAAA,EAAK;AACtC,IAAA,KAAA,CAAM,CAAC,CAAA,GAAI,MAAA,CAAO,UAAA,CAAW,CAAC,CAAA;AAAA,EAChC;AACA,EAAA,OAAO,KAAA;AACT;AAIA,eAAe,mBAAA,GAAuC;AACpD,EAAA,MAAM,QAAQ,MAAA,CAAO,eAAA,CAAgB,IAAI,UAAA,CAAW,EAAE,CAAC,CAAA;AACvD,EAAA,OAAO,gBAAgB,KAAK,CAAA;AAC9B;AAEA,eAAe,cAAc,MAAA,EAAoC;AAC/D,EAAA,OAAO,OAAO,MAAA,CAAO,SAAA;AAAA,IACnB,KAAA;AAAA,IACA,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAM,CAAA;AAAA,IAC/B,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAU;AAAA,IAChC,KAAA;AAAA,IACA,CAAC,QAAQ,QAAQ;AAAA,GACnB;AACF;AAEA,eAAe,UAAA,CAAW,QAAgB,EAAA,EAA6B;AACrE,EAAA,MAAM,EAAA,GAAK,KAAK,GAAA,EAAI;AACpB,EAAA,MAAM,KAAA,GAAQ,gBAAgB,MAAA,CAAO,eAAA,CAAgB,IAAI,UAAA,CAAW,CAAC,CAAC,CAAC,CAAA;AACvE,EAAA,MAAM,UAAU,CAAA,EAAG,EAAE,CAAA,CAAA,EAAI,EAAE,IAAI,KAAK,CAAA,CAAA;AACpC,EAAA,MAAM,GAAA,GAAM,MAAM,aAAA,CAAc,MAAM,CAAA;AACtC,EAAA,MAAM,GAAA,GAAM,MAAM,MAAA,CAAO,MAAA,CAAO,IAAA;AAAA,IAC9B,MAAA;AAAA,IACA,GAAA;AAAA,IACA,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,OAAO;AAAA,GAClC;AACA,EAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,IAAI,UAAA,CAAW,GAAG,CAAC,CAAA;AAClD,EAAA,OAAO,CAAA,EAAG,eAAA,CAAgB,IAAI,WAAA,EAAY,CAAE,OAAO,OAAO,CAAC,CAAC,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AACxE;AAEA,eAAe,cAAA,CACb,MAAA,EACA,MAAA,EACA,EAAA,EACkB;AAClB,EAAA,IAAI;AACF,IAAA,MAAM,CAAC,UAAA,EAAY,MAAM,CAAA,GAAI,MAAA,CAAO,MAAM,GAAG,CAAA;AAC7C,IAAA,IAAI,CAAC,UAAA,IAAc,CAAC,MAAA,EAAQ,OAAO,KAAA;AAEnC,IAAA,MAAM,YAAA,GAAe,gBAAgB,UAAU,CAAA;AAC/C,IAAA,MAAM,QAAA,GAAW,gBAAgB,MAAM,CAAA;AAGvC,IAAA,MAAM,GAAA,GAAM,MAAM,aAAA,CAAc,MAAM,CAAA;AACtC,IAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,MAAA,CAAO,MAAA;AAAA,MAChC,MAAA;AAAA,MACA,GAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AAGnB,IAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY,CAAE,OAAO,YAAY,CAAA;AACrD,IAAA,MAAM,CAAC,QAAA,EAAU,KAAK,CAAA,GAAI,OAAA,CAAQ,MAAM,GAAG,CAAA;AAG3C,IAAA,IAAI,QAAA,KAAa,IAAI,OAAO,KAAA;AAG5B,IAAA,MAAM,EAAA,GAAK,QAAA,CAAS,KAAA,EAAO,EAAE,CAAA;AAC7B,IAAA,IAAI,KAAA,CAAM,EAAE,CAAA,IAAK,IAAA,CAAK,KAAI,GAAI,EAAA,GAAK,kBAAA,GAAqB,GAAA,EAAM,OAAO,KAAA;AAErE,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAEA,SAAS,YAAY,CAAA,EAAmD;AACtE,EAAA,OACE,CAAA,CAAE,IAAI,MAAA,CAAO,kBAAkB,KAC/B,CAAA,CAAE,GAAA,CAAI,OAAO,WAAW,CAAA,IACxB,EAAE,GAAA,CAAI,MAAA,CAAO,iBAAiB,CAAA,EAAG,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,EAAG,IAAA,EAAK,IACrD,SAAA;AAEJ;AAEA,SAAS,cAAc,GAAA,EAA8B;AACnD,EAAA,MAAM,MAAM,GAAA,CAAI,cAAA;AAChB,EAAA,IAAI,CAAC,KAAK,OAAO,sBAAA;AACjB,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,GAAA,EAAK,EAAE,CAAA;AAC/B,EAAA,OAAO,KAAA,CAAM,MAAM,CAAA,GACf,sBAAA,GACA,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,MAAA,EAAQ,CAAC,CAAC,CAAA;AACrC;AAIA,SAAS,iBAAA,CACP,WAAA,EACA,UAAA,EACA,WAAA,EACQ;AACR,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAA,EAwBmB,IAAA,CAAK,SAAA,CAAU,WAAW,CAAC,CAAA;AAAA,yBAAA,EAC5B,UAAU,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kCAAA,EAUD,IAAA,CAAK,SAAA,CAAU,eAAe,CAAC,CAAA;AAAA;AAAA;AAAA,2EAAA,EAGU,IAAA,CAAK,SAAA,CAAU,WAAW,CAAC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAAA,CAAA;AAaxG;AAUA,eAAsB,gBACpB,CAAA,EACA;AACA,EAAA,MAAM,IAAA,GAAO,MAAM,CAAA,CAAE,GAAA,CAAI,IAAA,EAKtB;AAEH,EAAA,MAAM,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAM,GAAI,IAAA;AAE7B,EAAA,IAAI,CAAC,EAAA,IAAM,OAAO,KAAA,KAAU,QAAA,EAAU;AACpC,IAAA,OAAO,CAAA,CAAE,IAAA;AAAA,MACP;AAAA,QACE,KAAA,EAAO;AAAA,UACL,IAAA,EAAM,kBAAA;AAAA,UACN,OAAA,EAAS;AAAA;AACX,OACF;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAGA,EAAA,MAAM,SAAA,GAAY,MAAM,CAAA,CAAE,GAAA,CAAI,EAAA,CAAG,IAAI,CAAA,EAAG,mBAAmB,CAAA,EAAG,EAAE,CAAA,CAAE,CAAA;AAClE,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAO,CAAA,CAAE,IAAA;AAAA,MACP,EAAE,KAAA,EAAO,EAAE,MAAM,WAAA,EAAa,OAAA,EAAS,gCAA+B,EAAE;AAAA,MACxE;AAAA,KACF;AAAA,EACF;AAEA,EAAA,MAAM,EAAE,UAAA,EAAW,GAAI,IAAA,CAAK,MAAM,SAAS,CAAA;AAG3C,EAAA,MAAM,KAAA,GAAQ,IAAI,WAAA,EAAY,CAAE,OAAO,CAAA,EAAG,EAAE,CAAA,CAAA,EAAI,KAAK,CAAA,CAAE,CAAA;AACvD,EAAA,MAAM,OAAO,MAAM,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,WAAW,KAAK,CAAA;AACxD,EAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,IAAI,WAAW,IAAI,CAAC,EACxC,GAAA,CAAI,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAC,CAAA,CAC1C,IAAA,CAAK,EAAE,CAAA;AAEV,EAAA,MAAM,iBAAiB,GAAA,CAAI,MAAA,CAAO,KAAK,IAAA,CAAK,UAAA,GAAa,CAAC,CAAC,CAAA;AAC3D,EAAA,IAAI,CAAC,GAAA,CAAI,UAAA,CAAW,cAAc,CAAA,EAAG;AACnC,IAAA,OAAO,CAAA,CAAE,IAAA;AAAA,MACP,EAAE,KAAA,EAAO,EAAE,MAAM,kBAAA,EAAoB,OAAA,EAAS,yBAAwB,EAAE;AAAA,MACxE;AAAA,KACF;AAAA,EACF;AAGA,EAAA,MAAM,CAAA,CAAE,IAAI,EAAA,CAAG,MAAA,CAAO,GAAG,mBAAmB,CAAA,EAAG,EAAE,CAAA,CAAE,CAAA;AAGnD,EAAA,MAAM,MAAA,GAAS,EAAE,GAAA,CAAI,mBAAA;AACrB,EAAA,MAAM,EAAA,GAAK,YAAY,CAAC,CAAA;AACxB,EAAA,MAAM,KAAA,GAAQ,MAAM,UAAA,CAAW,MAAA,EAAQ,EAAE,CAAA;AAEzC,EAAA,CAAA,CAAE,MAAA;AAAA,IACA,YAAA;AAAA,IACA,CAAA,EAAG,eAAe,CAAA,CAAA,EAAI,KAAK,qBAAqB,kBAAkB,CAAA,gCAAA;AAAA,GACpE;AAEA,EAAA,OAAO,EAAE,IAAA,CAAK,EAAE,QAAA,EAAU,KAAA,IAAS,KAAK,CAAA;AAC1C;AAYO,SAAS,4BAAA,GAA+B;AAC7C,EAAA,OAAO,gBAAA,CAAgD,OAAO,CAAA,EAAG,IAAA,KAAS;AAExE,IAAA,IAAI,CAAA,CAAE,GAAA,CAAI,IAAA,KAAS,eAAA,SAAwB,IAAA,EAAK;AAGhD,IAAA,IAAI,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,GAAA,CAAI,IAAA,CAAK,UAAA,CAAW,CAAC,CAAC,CAAA,EAAG,OAAO,IAAA,EAAK;AAGpE,IAAA,IAAI,CAAA,CAAE,GAAA,CAAI,MAAA,KAAW,SAAA,SAAkB,IAAA,EAAK;AAG5C,IAAA,IAAI,EAAE,GAAA,CAAI,MAAA,CAAO,eAAe,CAAA,SAAU,IAAA,EAAK;AAG/C,IAAA,MAAM,YAAA,GAAe,CAAA,CAAE,GAAA,CAAI,MAAA,CAAO,QAAQ,CAAA,IAAK,EAAA;AAC/C,IAAA,MAAM,cAAc,YAAA,CAAa,KAAA;AAAA,MAC/B,IAAI,MAAA,CAAO,CAAA,EAAG,eAAe,CAAA,QAAA,CAAU;AAAA,KACzC;AACA,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,MAAM,MAAA,GAAS,EAAE,GAAA,CAAI,mBAAA;AACrB,MAAA,MAAM,EAAA,GAAK,YAAY,CAAC,CAAA;AACxB,MAAA,MAAM,QAAQ,MAAM,cAAA,CAAe,QAAQ,WAAA,CAAY,CAAC,GAAG,EAAE,CAAA;AAC7D,MAAA,IAAI,KAAA,SAAc,IAAA,EAAK;AAAA,IACzB;AAIA,IAAA,MAAM,UAAA,GAAa,aAAA,CAAc,CAAA,CAAE,GAAG,CAAA;AACtC,IAAA,MAAM,WAAA,GAAc,MAAM,mBAAA,EAAoB;AAG9C,IAAA,MAAM,CAAA,CAAE,IAAI,EAAA,CAAG,GAAA;AAAA,MACb,CAAA,EAAG,mBAAmB,CAAA,EAAG,WAAW,CAAA,CAAA;AAAA,MACpC,IAAA,CAAK,UAAU,EAAE,UAAA,EAAY,WAAW,IAAA,CAAK,GAAA,IAAO,CAAA;AAAA,MACpD,EAAE,eAAe,iBAAA;AAAkB,KACrC;AAGA,IAAA,MAAM,WAAA,GAAc,CAAA,CAAE,GAAA,CAAI,IAAA,IAAQ,CAAA,CAAE,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,GAAI,CAAA,CAAE,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,GAAI,EAAA,CAAA;AACxE,IAAA,MAAM,IAAA,GAAO,iBAAA,CAAkB,WAAA,EAAa,UAAA,EAAY,WAAW,CAAA;AAEnE,IAAA,OAAO,CAAA,CAAE,IAAA,CAAK,IAAA,EAAM,GAAG,CAAA;AAAA,EACzB,CAAC,CAAA;AACH","file":"chunk-MCOLCTFX.js","sourcesContent":["/**\r\n * Proof-of-Work Challenge Middleware (ADR-006, Layer 7).\r\n *\r\n * Anubis-inspired PoW challenge, implemented natively as Hono middleware.\r\n * Requires browsers to solve a SHA-256 PoW puzzle before accessing API endpoints.\r\n * After solving, a signed cookie is issued so subsequent requests bypass the challenge.\r\n *\r\n * Flow:\r\n * 1. Request arrives → no valid PoW cookie → serve challenge HTML page\r\n * 2. Browser runs JS PoW (find nonce where SHA-256(challengeId:nonce) meets difficulty)\r\n * 3. Client POSTs solution to /.well-known/leapify/pow/verify\r\n * 4. Server validates → sets signed cookie → redirects back to original URL\r\n * 5. Subsequent requests include cookie → pass through immediately\r\n *\r\n * Signing key: INTERNAL_API_SECRET (reused — same HMAC purpose as internal route auth)\r\n * Difficulty: POW_DIFFICULTY env var or DEFAULT_POW_DIFFICULTY (leading zero bits)\r\n */\r\n\r\nimport { createMiddleware } from 'hono/factory'\r\nimport type { Context } from 'hono'\r\nimport type { LeapifyBindings } from '../../types'\r\n\r\n// ─── Constants ──────────────────────────────────────────────────────────────────\r\n\r\n/** Base path for PoW challenge routes */\r\nexport const POW_PATH = '/.well-known/leapify/pow'\r\n\r\n/** Challenge verification endpoint */\r\nexport const POW_VERIFY_PATH = `${POW_PATH}/verify`\r\n\r\n/** Cookie name for PoW auth token */\r\nexport const POW_COOKIE_NAME = 'leapify-pow'\r\n\r\n/** KV key prefix for stored challenges */\r\nconst CHALLENGE_KV_PREFIX = 'pow:challenge:'\r\n\r\n/** Default difficulty (leading zero bits required in SHA-256 hash) */\r\nconst DEFAULT_POW_DIFFICULTY = 4\r\n\r\n/** Challenge expiration time in seconds */\r\nconst CHALLENGE_TTL_SEC = 120\r\n\r\n/** Cookie expiration time in seconds (1 hour) */\r\nconst COOKIE_MAX_AGE_SEC = 3600\r\n\r\n/** Paths exempt from PoW challenge */\r\nconst EXEMPT_PATHS = ['/health', '/internal', '/api/auth']\r\n\r\n// ─── Base64url Utilities ────────────────────────────────────────────────────────\r\n\r\nfunction base64urlEncode(bytes: Uint8Array): string {\r\n let binary = ''\r\n for (const byte of bytes) {\r\n binary += String.fromCharCode(byte)\r\n }\r\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\r\n}\r\n\r\nfunction base64urlDecode(str: string): Uint8Array<ArrayBuffer> {\r\n const padded = str.replace(/-/g, '+').replace(/_/g, '/')\r\n const binary = atob(padded)\r\n const bytes = new Uint8Array(new ArrayBuffer(binary.length))\r\n for (let i = 0; i < binary.length; i++) {\r\n bytes[i] = binary.charCodeAt(i)\r\n }\r\n return bytes\r\n}\r\n\r\n// ─── Crypto Helpers ─────────────────────────────────────────────────────────────\r\n\r\nasync function generateChallengeId(): Promise<string> {\r\n const bytes = crypto.getRandomValues(new Uint8Array(16))\r\n return base64urlEncode(bytes)\r\n}\r\n\r\nasync function importHmacKey(secret: string): Promise<CryptoKey> {\r\n return crypto.subtle.importKey(\r\n 'raw',\r\n new TextEncoder().encode(secret),\r\n { name: 'HMAC', hash: 'SHA-256' },\r\n false,\r\n ['sign', 'verify']\r\n )\r\n}\r\n\r\nasync function signCookie(secret: string, ip: string): Promise<string> {\r\n const ts = Date.now()\r\n const nonce = base64urlEncode(crypto.getRandomValues(new Uint8Array(8)))\r\n const payload = `${ip}:${ts}:${nonce}`\r\n const key = await importHmacKey(secret)\r\n const sig = await crypto.subtle.sign(\r\n 'HMAC',\r\n key,\r\n new TextEncoder().encode(payload)\r\n )\r\n const sigB64 = base64urlEncode(new Uint8Array(sig))\r\n return `${base64urlEncode(new TextEncoder().encode(payload))}.${sigB64}`\r\n}\r\n\r\nasync function validateCookie(\r\n secret: string,\r\n cookie: string,\r\n ip: string\r\n): Promise<boolean> {\r\n try {\r\n const [payloadB64, sigB64] = cookie.split('.')\r\n if (!payloadB64 || !sigB64) return false\r\n\r\n const payloadBytes = base64urlDecode(payloadB64)\r\n const sigBytes = base64urlDecode(sigB64)\r\n\r\n // Verify HMAC signature\r\n const key = await importHmacKey(secret)\r\n const valid = await crypto.subtle.verify(\r\n 'HMAC',\r\n key,\r\n sigBytes,\r\n payloadBytes\r\n )\r\n if (!valid) return false\r\n\r\n // Parse payload: ip:ts:nonce\r\n const payload = new TextDecoder().decode(payloadBytes)\r\n const [cookieIp, tsStr] = payload.split(':')\r\n\r\n // Verify IP matches (prevent cookie sharing)\r\n if (cookieIp !== ip) return false\r\n\r\n // Verify not expired\r\n const ts = parseInt(tsStr, 10)\r\n if (isNaN(ts) || Date.now() - ts > COOKIE_MAX_AGE_SEC * 1000) return false\r\n\r\n return true\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\nfunction getClientIp(c: Context<{ Bindings: LeapifyBindings }>): string {\r\n return (\r\n c.req.header('CF-Connecting-IP') ??\r\n c.req.header('X-Real-IP') ??\r\n c.req.header('X-Forwarded-For')?.split(',')[0]?.trim() ??\r\n 'unknown'\r\n )\r\n}\r\n\r\nfunction getDifficulty(env: LeapifyBindings): number {\r\n const raw = env.POW_DIFFICULTY\r\n if (!raw) return DEFAULT_POW_DIFFICULTY\r\n const parsed = parseInt(raw, 10)\r\n return isNaN(parsed)\r\n ? DEFAULT_POW_DIFFICULTY\r\n : Math.max(1, Math.min(parsed, 8))\r\n}\r\n\r\n// ─── Challenge Page HTML ────────────────────────────────────────────────────────\r\n\r\nfunction challengePageHtml(\r\n challengeId: string,\r\n difficulty: number,\r\n originalUrl: string\r\n): string {\r\n return `<!DOCTYPE html>\r\n<html>\r\n<head>\r\n <meta charset=\"utf-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n <title>Verifying your browser</title>\r\n <style>\r\n * { margin: 0; padding: 0; box-sizing: border-box; }\r\n body { font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #f5f5f5; color: #333; }\r\n .card { background: #fff; border-radius: 12px; padding: 2rem; box-shadow: 0 2px 12px rgba(0,0,0,0.08); text-align: center; max-width: 400px; }\r\n .spinner { width: 40px; height: 40px; border: 3px solid #e0e0e0; border-top-color: #333; border-radius: 50%; animation: spin 0.8s linear infinite; margin: 0 auto 1rem; }\r\n @keyframes spin { to { transform: rotate(360deg); } }\r\n h1 { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\r\n p { font-size: 0.9rem; color: #666; }\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"card\">\r\n <div class=\"spinner\"></div>\r\n <h1>Verifying your browser</h1>\r\n <p>This should only take a moment…</p>\r\n </div>\r\n <script>\r\n (async () => {\r\n const challengeId = ${JSON.stringify(challengeId)};\r\n const difficulty = ${difficulty};\r\n const prefix = '0'.repeat(Math.ceil(difficulty / 4));\r\n let nonce = 0;\r\n const t0 = performance.now();\r\n while (true) {\r\n const input = challengeId + ':' + nonce;\r\n const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));\r\n const hex = Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');\r\n if (hex.startsWith(prefix)) {\r\n const elapsed = performance.now() - t0;\r\n const res = await fetch(${JSON.stringify(POW_VERIFY_PATH)}, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ id: challengeId, nonce, elapsed, redir: ${JSON.stringify(originalUrl)} }),\r\n });\r\n const data = await res.json();\r\n if (data.redirect) window.location.href = data.redirect;\r\n else window.location.reload();\r\n break;\r\n }\r\n nonce++;\r\n }\r\n })();\r\n </script>\r\n</body>\r\n</html>`\r\n}\r\n\r\n// ─── Verify Handler ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * POST /.well-known/leapify/pow/verify\r\n *\r\n * Validates a completed PoW challenge and issues a signed cookie.\r\n * Exported for mounting in app.ts.\r\n */\r\nexport async function handlePowVerify(\r\n c: Context<{ Bindings: LeapifyBindings }>\r\n) {\r\n const body = await c.req.json<{\r\n id?: string\r\n nonce?: number\r\n elapsed?: number\r\n redir?: string\r\n }>()\r\n\r\n const { id, nonce, redir } = body\r\n\r\n if (!id || typeof nonce !== 'number') {\r\n return c.json(\r\n {\r\n error: {\r\n code: 'VALIDATION_ERROR',\r\n message: 'Missing challenge id or nonce'\r\n }\r\n },\r\n 422\r\n )\r\n }\r\n\r\n // Retrieve challenge from KV\r\n const challenge = await c.env.KV.get(`${CHALLENGE_KV_PREFIX}${id}`)\r\n if (!challenge) {\r\n return c.json(\r\n { error: { code: 'NOT_FOUND', message: 'Challenge expired or invalid' } },\r\n 404\r\n )\r\n }\r\n\r\n const { difficulty } = JSON.parse(challenge)\r\n\r\n // Verify PoW: SHA-256(challengeId:nonce) must have required leading zeros\r\n const input = new TextEncoder().encode(`${id}:${nonce}`)\r\n const hash = await crypto.subtle.digest('SHA-256', input)\r\n const hex = Array.from(new Uint8Array(hash))\r\n .map((b) => b.toString(16).padStart(2, '0'))\r\n .join('')\r\n\r\n const requiredPrefix = '0'.repeat(Math.ceil(difficulty / 4))\r\n if (!hex.startsWith(requiredPrefix)) {\r\n return c.json(\r\n { error: { code: 'VALIDATION_ERROR', message: 'Invalid proof of work' } },\r\n 422\r\n )\r\n }\r\n\r\n // Invalidate challenge (single-use)\r\n await c.env.KV.delete(`${CHALLENGE_KV_PREFIX}${id}`)\r\n\r\n // Issue signed cookie\r\n const secret = c.env.INTERNAL_API_SECRET\r\n const ip = getClientIp(c)\r\n const token = await signCookie(secret, ip)\r\n\r\n c.header(\r\n 'Set-Cookie',\r\n `${POW_COOKIE_NAME}=${token}; Path=/; Max-Age=${COOKIE_MAX_AGE_SEC}; Secure; HttpOnly; SameSite=Lax`\r\n )\r\n\r\n return c.json({ redirect: redir || '/' })\r\n}\r\n\r\n// ─── Main Middleware ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * PoW challenge middleware.\r\n *\r\n * Mount AFTER cors, BEFORE everything else:\r\n * app.use('*', createCorsMiddleware(...))\r\n * app.use('*', createPowChallengeMiddleware()) ← here\r\n * app.use('*', createRefererGuard(...))\r\n */\r\nexport function createPowChallengeMiddleware() {\r\n return createMiddleware<{ Bindings: LeapifyBindings }>(async (c, next) => {\r\n // Always pass through the verify endpoint itself\r\n if (c.req.path === POW_VERIFY_PATH) return next()\r\n\r\n // Skip exempt paths (health, internal webhooks)\r\n if (EXEMPT_PATHS.some((p) => c.req.path.startsWith(p))) return next()\r\n\r\n // Skip for OPTIONS requests (preflights should never be challenged)\r\n if (c.req.method === 'OPTIONS') return next()\r\n\r\n // Skip if client has a valid Authorization header (Firebase JWT — auth middleware will handle)\r\n if (c.req.header('Authorization')) return next()\r\n\r\n // Check for valid PoW cookie\r\n const cookieHeader = c.req.header('Cookie') ?? ''\r\n const cookieMatch = cookieHeader.match(\r\n new RegExp(`${POW_COOKIE_NAME}=([^;]+)`)\r\n )\r\n if (cookieMatch) {\r\n const secret = c.env.INTERNAL_API_SECRET\r\n const ip = getClientIp(c)\r\n const valid = await validateCookie(secret, cookieMatch[1], ip)\r\n if (valid) return next()\r\n }\r\n\r\n // ── Issue challenge ──────────────────────────────────────────────────────\r\n\r\n const difficulty = getDifficulty(c.env)\r\n const challengeId = await generateChallengeId()\r\n\r\n // Store challenge in KV with TTL\r\n await c.env.KV.put(\r\n `${CHALLENGE_KV_PREFIX}${challengeId}`,\r\n JSON.stringify({ difficulty, createdAt: Date.now() }),\r\n { expirationTtl: CHALLENGE_TTL_SEC }\r\n )\r\n\r\n // Serve challenge page\r\n const originalUrl = c.req.path + (c.req.query('?') ? c.req.query('?') : '')\r\n const html = challengePageHtml(challengeId, difficulty, originalUrl)\r\n\r\n return c.html(html, 200)\r\n })\r\n}\r\n"]}
|
package/dist/chunk-MKWVLWVJ.cjs
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var factory = require('hono/factory');
|
|
4
|
-
|
|
5
|
-
// src/lib/middleware/pow-challenge.ts
|
|
6
|
-
var POW_PATH = "/.well-known/leapify/pow";
|
|
7
|
-
var POW_VERIFY_PATH = `${POW_PATH}/verify`;
|
|
8
|
-
var POW_COOKIE_NAME = "leapify-pow";
|
|
9
|
-
var CHALLENGE_KV_PREFIX = "pow:challenge:";
|
|
10
|
-
var DEFAULT_POW_DIFFICULTY = 4;
|
|
11
|
-
var CHALLENGE_TTL_SEC = 120;
|
|
12
|
-
var COOKIE_MAX_AGE_SEC = 3600;
|
|
13
|
-
var EXEMPT_PATHS = ["/health", "/internal", "/api/auth"];
|
|
14
|
-
function base64urlEncode(bytes) {
|
|
15
|
-
let binary = "";
|
|
16
|
-
for (const byte of bytes) {
|
|
17
|
-
binary += String.fromCharCode(byte);
|
|
18
|
-
}
|
|
19
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
20
|
-
}
|
|
21
|
-
function base64urlDecode(str) {
|
|
22
|
-
const padded = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
23
|
-
const binary = atob(padded);
|
|
24
|
-
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
25
|
-
for (let i = 0; i < binary.length; i++) {
|
|
26
|
-
bytes[i] = binary.charCodeAt(i);
|
|
27
|
-
}
|
|
28
|
-
return bytes;
|
|
29
|
-
}
|
|
30
|
-
async function generateChallengeId() {
|
|
31
|
-
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
32
|
-
return base64urlEncode(bytes);
|
|
33
|
-
}
|
|
34
|
-
async function importHmacKey(secret) {
|
|
35
|
-
return crypto.subtle.importKey(
|
|
36
|
-
"raw",
|
|
37
|
-
new TextEncoder().encode(secret),
|
|
38
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
39
|
-
false,
|
|
40
|
-
["sign", "verify"]
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
async function signCookie(secret, ip) {
|
|
44
|
-
const ts = Date.now();
|
|
45
|
-
const nonce = base64urlEncode(crypto.getRandomValues(new Uint8Array(8)));
|
|
46
|
-
const payload = `${ip}:${ts}:${nonce}`;
|
|
47
|
-
const key = await importHmacKey(secret);
|
|
48
|
-
const sig = await crypto.subtle.sign(
|
|
49
|
-
"HMAC",
|
|
50
|
-
key,
|
|
51
|
-
new TextEncoder().encode(payload)
|
|
52
|
-
);
|
|
53
|
-
const sigB64 = base64urlEncode(new Uint8Array(sig));
|
|
54
|
-
return `${base64urlEncode(new TextEncoder().encode(payload))}.${sigB64}`;
|
|
55
|
-
}
|
|
56
|
-
async function validateCookie(secret, cookie, ip) {
|
|
57
|
-
try {
|
|
58
|
-
const [payloadB64, sigB64] = cookie.split(".");
|
|
59
|
-
if (!payloadB64 || !sigB64) return false;
|
|
60
|
-
const payloadBytes = base64urlDecode(payloadB64);
|
|
61
|
-
const sigBytes = base64urlDecode(sigB64);
|
|
62
|
-
const key = await importHmacKey(secret);
|
|
63
|
-
const valid = await crypto.subtle.verify(
|
|
64
|
-
"HMAC",
|
|
65
|
-
key,
|
|
66
|
-
sigBytes,
|
|
67
|
-
payloadBytes
|
|
68
|
-
);
|
|
69
|
-
if (!valid) return false;
|
|
70
|
-
const payload = new TextDecoder().decode(payloadBytes);
|
|
71
|
-
const [cookieIp, tsStr] = payload.split(":");
|
|
72
|
-
if (cookieIp !== ip) return false;
|
|
73
|
-
const ts = parseInt(tsStr, 10);
|
|
74
|
-
if (isNaN(ts) || Date.now() - ts > COOKIE_MAX_AGE_SEC * 1e3) return false;
|
|
75
|
-
return true;
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function getClientIp(c) {
|
|
81
|
-
return c.req.header("CF-Connecting-IP") ?? c.req.header("X-Real-IP") ?? c.req.header("X-Forwarded-For")?.split(",")[0]?.trim() ?? "unknown";
|
|
82
|
-
}
|
|
83
|
-
function getDifficulty(env) {
|
|
84
|
-
const raw = env.POW_DIFFICULTY;
|
|
85
|
-
if (!raw) return DEFAULT_POW_DIFFICULTY;
|
|
86
|
-
const parsed = parseInt(raw, 10);
|
|
87
|
-
return isNaN(parsed) ? DEFAULT_POW_DIFFICULTY : Math.max(1, Math.min(parsed, 8));
|
|
88
|
-
}
|
|
89
|
-
function challengePageHtml(challengeId, difficulty, originalUrl) {
|
|
90
|
-
return `<!DOCTYPE html>
|
|
91
|
-
<html>
|
|
92
|
-
<head>
|
|
93
|
-
<meta charset="utf-8">
|
|
94
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
95
|
-
<title>Verifying your browser</title>
|
|
96
|
-
<style>
|
|
97
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
98
|
-
body { font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #f5f5f5; color: #333; }
|
|
99
|
-
.card { background: #fff; border-radius: 12px; padding: 2rem; box-shadow: 0 2px 12px rgba(0,0,0,0.08); text-align: center; max-width: 400px; }
|
|
100
|
-
.spinner { width: 40px; height: 40px; border: 3px solid #e0e0e0; border-top-color: #333; border-radius: 50%; animation: spin 0.8s linear infinite; margin: 0 auto 1rem; }
|
|
101
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
102
|
-
h1 { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }
|
|
103
|
-
p { font-size: 0.9rem; color: #666; }
|
|
104
|
-
</style>
|
|
105
|
-
</head>
|
|
106
|
-
<body>
|
|
107
|
-
<div class="card">
|
|
108
|
-
<div class="spinner"></div>
|
|
109
|
-
<h1>Verifying your browser</h1>
|
|
110
|
-
<p>This should only take a moment…</p>
|
|
111
|
-
</div>
|
|
112
|
-
<script>
|
|
113
|
-
(async () => {
|
|
114
|
-
const challengeId = ${JSON.stringify(challengeId)};
|
|
115
|
-
const difficulty = ${difficulty};
|
|
116
|
-
const prefix = '0'.repeat(Math.ceil(difficulty / 4));
|
|
117
|
-
let nonce = 0;
|
|
118
|
-
const t0 = performance.now();
|
|
119
|
-
while (true) {
|
|
120
|
-
const input = challengeId + ':' + nonce;
|
|
121
|
-
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
|
|
122
|
-
const hex = Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
123
|
-
if (hex.startsWith(prefix)) {
|
|
124
|
-
const elapsed = performance.now() - t0;
|
|
125
|
-
const res = await fetch(${JSON.stringify(POW_VERIFY_PATH)}, {
|
|
126
|
-
method: 'POST',
|
|
127
|
-
headers: { 'Content-Type': 'application/json' },
|
|
128
|
-
body: JSON.stringify({ id: challengeId, nonce, elapsed, redir: ${JSON.stringify(originalUrl)} }),
|
|
129
|
-
});
|
|
130
|
-
const data = await res.json();
|
|
131
|
-
if (data.redirect) window.location.href = data.redirect;
|
|
132
|
-
else window.location.reload();
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
nonce++;
|
|
136
|
-
}
|
|
137
|
-
})();
|
|
138
|
-
</script>
|
|
139
|
-
</body>
|
|
140
|
-
</html>`;
|
|
141
|
-
}
|
|
142
|
-
async function handlePowVerify(c) {
|
|
143
|
-
const body = await c.req.json();
|
|
144
|
-
const { id, nonce, redir } = body;
|
|
145
|
-
if (!id || typeof nonce !== "number") {
|
|
146
|
-
return c.json(
|
|
147
|
-
{
|
|
148
|
-
error: {
|
|
149
|
-
code: "VALIDATION_ERROR",
|
|
150
|
-
message: "Missing challenge id or nonce"
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
422
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
const challenge = await c.env.KV.get(`${CHALLENGE_KV_PREFIX}${id}`);
|
|
157
|
-
if (!challenge) {
|
|
158
|
-
return c.json(
|
|
159
|
-
{ error: { code: "NOT_FOUND", message: "Challenge expired or invalid" } },
|
|
160
|
-
404
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
const { difficulty } = JSON.parse(challenge);
|
|
164
|
-
const input = new TextEncoder().encode(`${id}:${nonce}`);
|
|
165
|
-
const hash = await crypto.subtle.digest("SHA-256", input);
|
|
166
|
-
const hex = Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
167
|
-
const requiredPrefix = "0".repeat(Math.ceil(difficulty / 4));
|
|
168
|
-
if (!hex.startsWith(requiredPrefix)) {
|
|
169
|
-
return c.json(
|
|
170
|
-
{ error: { code: "VALIDATION_ERROR", message: "Invalid proof of work" } },
|
|
171
|
-
422
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
await c.env.KV.delete(`${CHALLENGE_KV_PREFIX}${id}`);
|
|
175
|
-
const secret = c.env.INTERNAL_API_SECRET;
|
|
176
|
-
const ip = getClientIp(c);
|
|
177
|
-
const token = await signCookie(secret, ip);
|
|
178
|
-
c.header(
|
|
179
|
-
"Set-Cookie",
|
|
180
|
-
`${POW_COOKIE_NAME}=${token}; Path=/; Max-Age=${COOKIE_MAX_AGE_SEC}; Secure; HttpOnly; SameSite=Lax`
|
|
181
|
-
);
|
|
182
|
-
return c.json({ redirect: redir || "/" });
|
|
183
|
-
}
|
|
184
|
-
function createPowChallengeMiddleware() {
|
|
185
|
-
return factory.createMiddleware(async (c, next) => {
|
|
186
|
-
if (c.req.path === POW_VERIFY_PATH) return next();
|
|
187
|
-
if (EXEMPT_PATHS.some((p) => c.req.path.startsWith(p))) return next();
|
|
188
|
-
if (c.req.method === "OPTIONS") return next();
|
|
189
|
-
if (c.req.header("Authorization")) return next();
|
|
190
|
-
const cookieHeader = c.req.header("Cookie") ?? "";
|
|
191
|
-
const cookieMatch = cookieHeader.match(
|
|
192
|
-
new RegExp(`${POW_COOKIE_NAME}=([^;]+)`)
|
|
193
|
-
);
|
|
194
|
-
if (cookieMatch) {
|
|
195
|
-
const secret = c.env.INTERNAL_API_SECRET;
|
|
196
|
-
const ip = getClientIp(c);
|
|
197
|
-
const valid = await validateCookie(secret, cookieMatch[1], ip);
|
|
198
|
-
if (valid) return next();
|
|
199
|
-
}
|
|
200
|
-
const difficulty = getDifficulty(c.env);
|
|
201
|
-
const challengeId = await generateChallengeId();
|
|
202
|
-
await c.env.KV.put(
|
|
203
|
-
`${CHALLENGE_KV_PREFIX}${challengeId}`,
|
|
204
|
-
JSON.stringify({ difficulty, createdAt: Date.now() }),
|
|
205
|
-
{ expirationTtl: CHALLENGE_TTL_SEC }
|
|
206
|
-
);
|
|
207
|
-
const originalUrl = c.req.path + (c.req.query("?") ? c.req.query("?") : "");
|
|
208
|
-
const html = challengePageHtml(challengeId, difficulty, originalUrl);
|
|
209
|
-
return c.html(html, 200);
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
exports.POW_COOKIE_NAME = POW_COOKIE_NAME;
|
|
214
|
-
exports.POW_PATH = POW_PATH;
|
|
215
|
-
exports.POW_VERIFY_PATH = POW_VERIFY_PATH;
|
|
216
|
-
exports.createPowChallengeMiddleware = createPowChallengeMiddleware;
|
|
217
|
-
exports.handlePowVerify = handlePowVerify;
|
|
218
|
-
//# sourceMappingURL=chunk-MKWVLWVJ.cjs.map
|
|
219
|
-
//# sourceMappingURL=chunk-MKWVLWVJ.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/middleware/pow-challenge.ts"],"names":["createMiddleware"],"mappings":"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","file":"chunk-MKWVLWVJ.cjs","sourcesContent":["/**\r\n * Proof-of-Work Challenge Middleware (ADR-006, Layer 7).\r\n *\r\n * Anubis-inspired PoW challenge, implemented natively as Hono middleware.\r\n * Requires browsers to solve a SHA-256 PoW puzzle before accessing API endpoints.\r\n * After solving, a signed cookie is issued so subsequent requests bypass the challenge.\r\n *\r\n * Flow:\r\n * 1. Request arrives → no valid PoW cookie → serve challenge HTML page\r\n * 2. Browser runs JS PoW (find nonce where SHA-256(challengeId:nonce) meets difficulty)\r\n * 3. Client POSTs solution to /.well-known/leapify/pow/verify\r\n * 4. Server validates → sets signed cookie → redirects back to original URL\r\n * 5. Subsequent requests include cookie → pass through immediately\r\n *\r\n * Signing key: INTERNAL_API_SECRET (reused — same HMAC purpose as internal route auth)\r\n * Difficulty: POW_DIFFICULTY env var or DEFAULT_POW_DIFFICULTY (leading zero bits)\r\n */\r\n\r\nimport { createMiddleware } from 'hono/factory'\r\nimport type { Context } from 'hono'\r\nimport type { LeapifyBindings } from '../../types'\r\n\r\n// ─── Constants ──────────────────────────────────────────────────────────────────\r\n\r\n/** Base path for PoW challenge routes */\r\nexport const POW_PATH = '/.well-known/leapify/pow'\r\n\r\n/** Challenge verification endpoint */\r\nexport const POW_VERIFY_PATH = `${POW_PATH}/verify`\r\n\r\n/** Cookie name for PoW auth token */\r\nexport const POW_COOKIE_NAME = 'leapify-pow'\r\n\r\n/** KV key prefix for stored challenges */\r\nconst CHALLENGE_KV_PREFIX = 'pow:challenge:'\r\n\r\n/** Default difficulty (leading zero bits required in SHA-256 hash) */\r\nconst DEFAULT_POW_DIFFICULTY = 4\r\n\r\n/** Challenge expiration time in seconds */\r\nconst CHALLENGE_TTL_SEC = 120\r\n\r\n/** Cookie expiration time in seconds (1 hour) */\r\nconst COOKIE_MAX_AGE_SEC = 3600\r\n\r\n/** Paths exempt from PoW challenge */\r\nconst EXEMPT_PATHS = ['/health', '/internal', '/api/auth']\r\n\r\n// ─── Base64url Utilities ────────────────────────────────────────────────────────\r\n\r\nfunction base64urlEncode(bytes: Uint8Array): string {\r\n let binary = ''\r\n for (const byte of bytes) {\r\n binary += String.fromCharCode(byte)\r\n }\r\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\r\n}\r\n\r\nfunction base64urlDecode(str: string): Uint8Array<ArrayBuffer> {\r\n const padded = str.replace(/-/g, '+').replace(/_/g, '/')\r\n const binary = atob(padded)\r\n const bytes = new Uint8Array(new ArrayBuffer(binary.length))\r\n for (let i = 0; i < binary.length; i++) {\r\n bytes[i] = binary.charCodeAt(i)\r\n }\r\n return bytes\r\n}\r\n\r\n// ─── Crypto Helpers ─────────────────────────────────────────────────────────────\r\n\r\nasync function generateChallengeId(): Promise<string> {\r\n const bytes = crypto.getRandomValues(new Uint8Array(16))\r\n return base64urlEncode(bytes)\r\n}\r\n\r\nasync function importHmacKey(secret: string): Promise<CryptoKey> {\r\n return crypto.subtle.importKey(\r\n 'raw',\r\n new TextEncoder().encode(secret),\r\n { name: 'HMAC', hash: 'SHA-256' },\r\n false,\r\n ['sign', 'verify']\r\n )\r\n}\r\n\r\nasync function signCookie(secret: string, ip: string): Promise<string> {\r\n const ts = Date.now()\r\n const nonce = base64urlEncode(crypto.getRandomValues(new Uint8Array(8)))\r\n const payload = `${ip}:${ts}:${nonce}`\r\n const key = await importHmacKey(secret)\r\n const sig = await crypto.subtle.sign(\r\n 'HMAC',\r\n key,\r\n new TextEncoder().encode(payload)\r\n )\r\n const sigB64 = base64urlEncode(new Uint8Array(sig))\r\n return `${base64urlEncode(new TextEncoder().encode(payload))}.${sigB64}`\r\n}\r\n\r\nasync function validateCookie(\r\n secret: string,\r\n cookie: string,\r\n ip: string\r\n): Promise<boolean> {\r\n try {\r\n const [payloadB64, sigB64] = cookie.split('.')\r\n if (!payloadB64 || !sigB64) return false\r\n\r\n const payloadBytes = base64urlDecode(payloadB64)\r\n const sigBytes = base64urlDecode(sigB64)\r\n\r\n // Verify HMAC signature\r\n const key = await importHmacKey(secret)\r\n const valid = await crypto.subtle.verify(\r\n 'HMAC',\r\n key,\r\n sigBytes,\r\n payloadBytes\r\n )\r\n if (!valid) return false\r\n\r\n // Parse payload: ip:ts:nonce\r\n const payload = new TextDecoder().decode(payloadBytes)\r\n const [cookieIp, tsStr] = payload.split(':')\r\n\r\n // Verify IP matches (prevent cookie sharing)\r\n if (cookieIp !== ip) return false\r\n\r\n // Verify not expired\r\n const ts = parseInt(tsStr, 10)\r\n if (isNaN(ts) || Date.now() - ts > COOKIE_MAX_AGE_SEC * 1000) return false\r\n\r\n return true\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\nfunction getClientIp(c: Context<{ Bindings: LeapifyBindings }>): string {\r\n return (\r\n c.req.header('CF-Connecting-IP') ??\r\n c.req.header('X-Real-IP') ??\r\n c.req.header('X-Forwarded-For')?.split(',')[0]?.trim() ??\r\n 'unknown'\r\n )\r\n}\r\n\r\nfunction getDifficulty(env: LeapifyBindings): number {\r\n const raw = env.POW_DIFFICULTY\r\n if (!raw) return DEFAULT_POW_DIFFICULTY\r\n const parsed = parseInt(raw, 10)\r\n return isNaN(parsed)\r\n ? DEFAULT_POW_DIFFICULTY\r\n : Math.max(1, Math.min(parsed, 8))\r\n}\r\n\r\n// ─── Challenge Page HTML ────────────────────────────────────────────────────────\r\n\r\nfunction challengePageHtml(\r\n challengeId: string,\r\n difficulty: number,\r\n originalUrl: string\r\n): string {\r\n return `<!DOCTYPE html>\r\n<html>\r\n<head>\r\n <meta charset=\"utf-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n <title>Verifying your browser</title>\r\n <style>\r\n * { margin: 0; padding: 0; box-sizing: border-box; }\r\n body { font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #f5f5f5; color: #333; }\r\n .card { background: #fff; border-radius: 12px; padding: 2rem; box-shadow: 0 2px 12px rgba(0,0,0,0.08); text-align: center; max-width: 400px; }\r\n .spinner { width: 40px; height: 40px; border: 3px solid #e0e0e0; border-top-color: #333; border-radius: 50%; animation: spin 0.8s linear infinite; margin: 0 auto 1rem; }\r\n @keyframes spin { to { transform: rotate(360deg); } }\r\n h1 { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\r\n p { font-size: 0.9rem; color: #666; }\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"card\">\r\n <div class=\"spinner\"></div>\r\n <h1>Verifying your browser</h1>\r\n <p>This should only take a moment…</p>\r\n </div>\r\n <script>\r\n (async () => {\r\n const challengeId = ${JSON.stringify(challengeId)};\r\n const difficulty = ${difficulty};\r\n const prefix = '0'.repeat(Math.ceil(difficulty / 4));\r\n let nonce = 0;\r\n const t0 = performance.now();\r\n while (true) {\r\n const input = challengeId + ':' + nonce;\r\n const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));\r\n const hex = Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');\r\n if (hex.startsWith(prefix)) {\r\n const elapsed = performance.now() - t0;\r\n const res = await fetch(${JSON.stringify(POW_VERIFY_PATH)}, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ id: challengeId, nonce, elapsed, redir: ${JSON.stringify(originalUrl)} }),\r\n });\r\n const data = await res.json();\r\n if (data.redirect) window.location.href = data.redirect;\r\n else window.location.reload();\r\n break;\r\n }\r\n nonce++;\r\n }\r\n })();\r\n </script>\r\n</body>\r\n</html>`\r\n}\r\n\r\n// ─── Verify Handler ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * POST /.well-known/leapify/pow/verify\r\n *\r\n * Validates a completed PoW challenge and issues a signed cookie.\r\n * Exported for mounting in app.ts.\r\n */\r\nexport async function handlePowVerify(\r\n c: Context<{ Bindings: LeapifyBindings }>\r\n) {\r\n const body = await c.req.json<{\r\n id?: string\r\n nonce?: number\r\n elapsed?: number\r\n redir?: string\r\n }>()\r\n\r\n const { id, nonce, redir } = body\r\n\r\n if (!id || typeof nonce !== 'number') {\r\n return c.json(\r\n {\r\n error: {\r\n code: 'VALIDATION_ERROR',\r\n message: 'Missing challenge id or nonce'\r\n }\r\n },\r\n 422\r\n )\r\n }\r\n\r\n // Retrieve challenge from KV\r\n const challenge = await c.env.KV.get(`${CHALLENGE_KV_PREFIX}${id}`)\r\n if (!challenge) {\r\n return c.json(\r\n { error: { code: 'NOT_FOUND', message: 'Challenge expired or invalid' } },\r\n 404\r\n )\r\n }\r\n\r\n const { difficulty } = JSON.parse(challenge)\r\n\r\n // Verify PoW: SHA-256(challengeId:nonce) must have required leading zeros\r\n const input = new TextEncoder().encode(`${id}:${nonce}`)\r\n const hash = await crypto.subtle.digest('SHA-256', input)\r\n const hex = Array.from(new Uint8Array(hash))\r\n .map((b) => b.toString(16).padStart(2, '0'))\r\n .join('')\r\n\r\n const requiredPrefix = '0'.repeat(Math.ceil(difficulty / 4))\r\n if (!hex.startsWith(requiredPrefix)) {\r\n return c.json(\r\n { error: { code: 'VALIDATION_ERROR', message: 'Invalid proof of work' } },\r\n 422\r\n )\r\n }\r\n\r\n // Invalidate challenge (single-use)\r\n await c.env.KV.delete(`${CHALLENGE_KV_PREFIX}${id}`)\r\n\r\n // Issue signed cookie\r\n const secret = c.env.INTERNAL_API_SECRET\r\n const ip = getClientIp(c)\r\n const token = await signCookie(secret, ip)\r\n\r\n c.header(\r\n 'Set-Cookie',\r\n `${POW_COOKIE_NAME}=${token}; Path=/; Max-Age=${COOKIE_MAX_AGE_SEC}; Secure; HttpOnly; SameSite=Lax`\r\n )\r\n\r\n return c.json({ redirect: redir || '/' })\r\n}\r\n\r\n// ─── Main Middleware ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * PoW challenge middleware.\r\n *\r\n * Mount AFTER cors, BEFORE everything else:\r\n * app.use('*', createCorsMiddleware(...))\r\n * app.use('*', createPowChallengeMiddleware()) ← here\r\n * app.use('*', createRefererGuard(...))\r\n */\r\nexport function createPowChallengeMiddleware() {\r\n return createMiddleware<{ Bindings: LeapifyBindings }>(async (c, next) => {\r\n // Always pass through the verify endpoint itself\r\n if (c.req.path === POW_VERIFY_PATH) return next()\r\n\r\n // Skip exempt paths (health, internal webhooks)\r\n if (EXEMPT_PATHS.some((p) => c.req.path.startsWith(p))) return next()\r\n\r\n // Skip for OPTIONS requests (preflights should never be challenged)\r\n if (c.req.method === 'OPTIONS') return next()\r\n\r\n // Skip if client has a valid Authorization header (Firebase JWT — auth middleware will handle)\r\n if (c.req.header('Authorization')) return next()\r\n\r\n // Check for valid PoW cookie\r\n const cookieHeader = c.req.header('Cookie') ?? ''\r\n const cookieMatch = cookieHeader.match(\r\n new RegExp(`${POW_COOKIE_NAME}=([^;]+)`)\r\n )\r\n if (cookieMatch) {\r\n const secret = c.env.INTERNAL_API_SECRET\r\n const ip = getClientIp(c)\r\n const valid = await validateCookie(secret, cookieMatch[1], ip)\r\n if (valid) return next()\r\n }\r\n\r\n // ── Issue challenge ──────────────────────────────────────────────────────\r\n\r\n const difficulty = getDifficulty(c.env)\r\n const challengeId = await generateChallengeId()\r\n\r\n // Store challenge in KV with TTL\r\n await c.env.KV.put(\r\n `${CHALLENGE_KV_PREFIX}${challengeId}`,\r\n JSON.stringify({ difficulty, createdAt: Date.now() }),\r\n { expirationTtl: CHALLENGE_TTL_SEC }\r\n )\r\n\r\n // Serve challenge page\r\n const originalUrl = c.req.path + (c.req.query('?') ? c.req.query('?') : '')\r\n const html = challengePageHtml(challengeId, difficulty, originalUrl)\r\n\r\n return c.html(html, 200)\r\n })\r\n}\r\n"]}
|