diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..d908cee --- /dev/null +++ b/deno.lock @@ -0,0 +1,250 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "npm:drizzle-orm@0.33.0": "npm:drizzle-orm@0.33.0_pg@8.12.0", + "npm:pg@8.12.0": "npm:pg@8.12.0" + }, + "npm": { + "drizzle-orm@0.33.0_pg@8.12.0": { + "integrity": "sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA==", + "dependencies": { + "pg": "pg@8.12.0" + } + }, + "pg-cloudflare@1.1.1": { + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "dependencies": {} + }, + "pg-connection-string@2.6.4": { + "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==", + "dependencies": {} + }, + "pg-int8@1.0.1": { + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "dependencies": {} + }, + "pg-pool@3.6.2_pg@8.12.0": { + "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", + "dependencies": { + "pg": "pg@8.12.0" + } + }, + "pg-protocol@1.6.1": { + "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==", + "dependencies": {} + }, + "pg-types@2.2.0": { + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "pg-int8@1.0.1", + "postgres-array": "postgres-array@2.0.0", + "postgres-bytea": "postgres-bytea@1.0.0", + "postgres-date": "postgres-date@1.0.7", + "postgres-interval": "postgres-interval@1.2.0" + } + }, + "pg@8.12.0": { + "integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==", + "dependencies": { + "pg-cloudflare": "pg-cloudflare@1.1.1", + "pg-connection-string": "pg-connection-string@2.6.4", + "pg-pool": "pg-pool@3.6.2_pg@8.12.0", + "pg-protocol": "pg-protocol@1.6.1", + "pg-types": "pg-types@2.2.0", + "pgpass": "pgpass@1.0.5" + } + }, + "pgpass@1.0.5": { + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "split2@4.2.0" + } + }, + "postgres-array@2.0.0": { + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "dependencies": {} + }, + "postgres-bytea@1.0.0": { + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "dependencies": {} + }, + "postgres-date@1.0.7": { + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "dependencies": {} + }, + "postgres-interval@1.2.0": { + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "xtend@4.0.2" + } + }, + "split2@4.2.0": { + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dependencies": {} + }, + "xtend@4.0.2": { + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dependencies": {} + } + } + }, + "redirects": { + "https://esm.sh/@aws-sdk/client-s3@^3.592.0": "https://esm.sh/@aws-sdk/client-s3@3.645.0", + "https://esm.sh/@aws-sdk/s3-request-presigner@^3.592.0": "https://esm.sh/@aws-sdk/s3-request-presigner@3.645.0" + }, + "remote": { + "https://deno.land/std@0.208.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", + "https://deno.land/std@0.208.0/assert/_diff.ts": "58e1461cc61d8eb1eacbf2a010932bf6a05b79344b02ca38095f9b805795dc48", + "https://deno.land/std@0.208.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", + "https://deno.land/std@0.208.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.208.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", + "https://deno.land/std@0.208.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", + "https://deno.land/std@0.208.0/assert/assert_equals.ts": "d8ec8a22447fbaf2fc9d7c3ed2e66790fdb74beae3e482855d75782218d68227", + "https://deno.land/std@0.208.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", + "https://deno.land/std@0.208.0/assert/assert_false.ts": "0ccbcaae910f52c857192ff16ea08bda40fdc79de80846c206bfc061e8c851c6", + "https://deno.land/std@0.208.0/assert/assert_greater.ts": "ae2158a2d19313bf675bf7251d31c6dc52973edb12ac64ac8fc7064152af3e63", + "https://deno.land/std@0.208.0/assert/assert_greater_or_equal.ts": "1439da5ebbe20855446cac50097ac78b9742abe8e9a43e7de1ce1426d556e89c", + "https://deno.land/std@0.208.0/assert/assert_instance_of.ts": "3aedb3d8186e120812d2b3a5dea66a6e42bf8c57a8bd927645770bd21eea554c", + "https://deno.land/std@0.208.0/assert/assert_is_error.ts": "c21113094a51a296ffaf036767d616a78a2ae5f9f7bbd464cd0197476498b94b", + "https://deno.land/std@0.208.0/assert/assert_less.ts": "aec695db57db42ec3e2b62e97e1e93db0063f5a6ec133326cc290ff4b71b47e4", + "https://deno.land/std@0.208.0/assert/assert_less_or_equal.ts": "5fa8b6a3ffa20fd0a05032fe7257bf985d207b85685fdbcd23651b70f928c848", + "https://deno.land/std@0.208.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", + "https://deno.land/std@0.208.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", + "https://deno.land/std@0.208.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", + "https://deno.land/std@0.208.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", + "https://deno.land/std@0.208.0/assert/assert_not_strict_equals.ts": "4cdef83df17488df555c8aac1f7f5ec2b84ad161b6d0645ccdbcc17654e80c99", + "https://deno.land/std@0.208.0/assert/assert_object_match.ts": "d8fc2867cfd92eeacf9cea621e10336b666de1874a6767b5ec48988838370b54", + "https://deno.land/std@0.208.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", + "https://deno.land/std@0.208.0/assert/assert_strict_equals.ts": "b1f538a7ea5f8348aeca261d4f9ca603127c665e0f2bbfeb91fa272787c87265", + "https://deno.land/std@0.208.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", + "https://deno.land/std@0.208.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", + "https://deno.land/std@0.208.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.208.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", + "https://deno.land/std@0.208.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", + "https://deno.land/std@0.208.0/assert/mod.ts": "37c49a26aae2b254bbe25723434dc28cd7532e444cf0b481a97c045d110ec085", + "https://deno.land/std@0.208.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", + "https://deno.land/std@0.208.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", + "https://deno.land/std@0.208.0/fmt/colors.ts": "34b3f77432925eb72cf0bfb351616949746768620b8e5ead66da532f93d10ba2", + "https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", + "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", + "https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293", + "https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7", + "https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74", + "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", + "https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff", + "https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46", + "https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b", + "https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c", + "https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491", + "https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68", + "https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3", + "https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7", + "https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29", + "https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a", + "https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a", + "https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8", + "https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693", + "https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31", + "https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5", + "https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8", + "https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb", + "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", + "https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47", + "https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68", + "https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3", + "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", + "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", + "https://deno.land/std@0.224.0/collections/_utils.ts": "b2ec8ada31b5a72ebb1d99774b849b4c09fe4b3a38d07794bd010bd218a16e0b", + "https://deno.land/std@0.224.0/collections/deep_merge.ts": "04f8d2a6cfa15c7580e788689bcb5e162512b9ccb18bab1241824b432a78551e", + "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", + "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", + "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", + "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", + "https://deno.land/x/b64@1.1.28/src/base64.js": "c81768c67f6f461b01d10ec24c6c4da71e2f12b3c96e32c62146c98c69685101", + "https://esm.sh/@aws-sdk/client-s3@3.645.0": "2e295b9079e0a711af7e5db24b02d36c8a3670796893253e46292dfaff59b8f5", + "https://esm.sh/@aws-sdk/s3-request-presigner@3.645.0": "03cf57cb951aece8cb946fb31f910b5d96fcb54aadc15973cee8fa079a9783a1", + "https://esm.sh/nanoevents@9.0.0": "29ccd1d8839f2d7dd8d28ad9ec5d18723a7dbf966bf29179063442b1e88b3a4f", + "https://esm.sh/v135/@aws-crypto/crc32@5.2.0/denonext/crc32.mjs": "6a9bc8418c01e2539665b528ccea843f1319a3b32d759fcbb1d4468156c25100", + "https://esm.sh/v135/@aws-crypto/crc32c@5.2.0/denonext/crc32c.mjs": "1e8985997bd2c0807d349acaf192a54147d779e5349faf6507f51aa8becb85ca", + "https://esm.sh/v135/@aws-crypto/sha1-browser@5.2.0/denonext/sha1-browser.mjs": "d80868d5524769e0334b50124d547ce9875fb05f9924acca4c42ed877b41ce7f", + "https://esm.sh/v135/@aws-crypto/sha256-browser@5.2.0/denonext/sha256-browser.mjs": "84e59b20eb4974a23fafdcf5fcd6513757ad195ca809b80d19a389602cff335a", + "https://esm.sh/v135/@aws-crypto/sha256-js@5.2.0/denonext/sha256-js.mjs": "2e1014e03baf7b5eb5d773c8409af836dacbec2c0a522b789774f76d3eb2e5ad", + "https://esm.sh/v135/@aws-crypto/supports-web-crypto@5.2.0/denonext/supports-web-crypto.mjs": "2ae3bd2aa25db0761277ad0feda7aea68cd829c89b714e8e03e07aac06345d81", + "https://esm.sh/v135/@aws-crypto/util@5.2.0/denonext/util.mjs": "376903ba54e09eed466b45e243cef1133f20bf015c0505e70fc794896d1412d5", + "https://esm.sh/v135/@aws-sdk/client-s3@3.645.0/denonext/client-s3.mjs": "9913ffe288034103e23f7b33a39620b53e5964a6954013df6c362e65ed2e8f5f", + "https://esm.sh/v135/@aws-sdk/core@3.635.0/denonext/client.js": "8a39588a5d58924ebc0fbe17dcb8b9a72d16372ea5a19a8ad57087f9bff48683", + "https://esm.sh/v135/@aws-sdk/core@3.635.0/denonext/core.mjs": "7cf3ea701618416bd56c43488b12adf12d564bfe317850baaf54cb6d123e4246", + "https://esm.sh/v135/@aws-sdk/core@3.635.0/denonext/httpAuthSchemes.js": "0618ba15447abfa957d5ec60b3deb280c8c4e806b96e4a66110226e2fbc4eb0d", + "https://esm.sh/v135/@aws-sdk/core@3.635.0/denonext/protocols.js": "c2ea1228ca889c7dfcdbc36a4924a14507eb6d8c40d44f0e060d8e600d94af73", + "https://esm.sh/v135/@aws-sdk/middleware-expect-continue@3.620.0/denonext/middleware-expect-continue.mjs": "7257dc7aa9fd7a34fc44b5f8b2460cadfdd72b2e8d7a54d2027a69d1e94c902e", + "https://esm.sh/v135/@aws-sdk/middleware-flexible-checksums@3.620.0/denonext/middleware-flexible-checksums.mjs": "13e3af9f03eae1deb232c6201bac2eabbf986c2bb6f5cfbd80c06988172e5cd6", + "https://esm.sh/v135/@aws-sdk/middleware-host-header@3.620.0/denonext/middleware-host-header.mjs": "1e2c8804ebfb981b393e843ada215a2f2a5faf82f92ebe8906794bb0d1f09338", + "https://esm.sh/v135/@aws-sdk/middleware-location-constraint@3.609.0/denonext/middleware-location-constraint.mjs": "ba8c934c030e5168ad09260026bae3b5f538eca8c50b528fb3b6e945967b7f36", + "https://esm.sh/v135/@aws-sdk/middleware-logger@3.609.0/denonext/middleware-logger.mjs": "2105c33b2e62ed2567b20a71438f8f1409220f7bd0426910b0bccf5b84316b84", + "https://esm.sh/v135/@aws-sdk/middleware-recursion-detection@3.620.0/denonext/middleware-recursion-detection.mjs": "e4b76653eb33598813018b3d924a4d7ff86243a7bd4d818ac7a194d147e7a267", + "https://esm.sh/v135/@aws-sdk/middleware-sdk-s3@3.635.0/denonext/middleware-sdk-s3.mjs": "19d026384d6c2223ef650a5f6791da38f2cf93612a2f3f2474bca2c78c002a19", + "https://esm.sh/v135/@aws-sdk/middleware-ssec@3.609.0/denonext/middleware-ssec.mjs": "55d27e9c5fcdd0f4bf2cf7b8f0c6b834d4b3cba6c044de9a57cc0419c58d64bf", + "https://esm.sh/v135/@aws-sdk/middleware-user-agent@3.645.0/denonext/middleware-user-agent.mjs": "a506307c80af68bf9618d5eb8810603ec03810aa1ea9086ed57653745517f89c", + "https://esm.sh/v135/@aws-sdk/region-config-resolver@3.614.0/denonext/region-config-resolver.mjs": "580b2f14c0d72423f166859afd2441fdf3883f7a3ab86c36d746a159029d40fd", + "https://esm.sh/v135/@aws-sdk/s3-request-presigner@3.645.0/denonext/s3-request-presigner.mjs": "57125a72c13a69f88078aa6505ef6088efa4c773604463a08b9be275996c38ae", + "https://esm.sh/v135/@aws-sdk/signature-v4-multi-region@3.635.0/denonext/signature-v4-multi-region.mjs": "de9c08397d25f620680522d022422ebb30cc534d44cc91592f31922ec3f9bc88", + "https://esm.sh/v135/@aws-sdk/util-arn-parser@3.568.0/denonext/util-arn-parser.mjs": "e80995eaf790640e591f09d89d9099b022efa6d7954d6e23a1a7f5691b9b5110", + "https://esm.sh/v135/@aws-sdk/util-endpoints@3.645.0/denonext/util-endpoints.mjs": "c72e746a164f107dbe5d43f4e175635cd0bde6f25bf41852134d4622a5e0cd58", + "https://esm.sh/v135/@aws-sdk/util-format-url@3.609.0/denonext/util-format-url.mjs": "097aa6da9b813dfd68e0bdcd25391d7e77ae808911463309604f8022ac38ab0b", + "https://esm.sh/v135/@aws-sdk/util-locate-window@3.568.0/denonext/util-locate-window.mjs": "44c4acffec7669f2d0e0307ebfca7cac1f85260a6f8238dcbeb5e79f769e6f00", + "https://esm.sh/v135/@aws-sdk/util-user-agent-browser@3.609.0/denonext/util-user-agent-browser.mjs": "47329052476de081fa1bd227be1f83dd1ed360162aecae204218295bf9dc5ab5", + "https://esm.sh/v135/@aws-sdk/xml-builder@3.609.0/denonext/xml-builder.mjs": "1822a0c319298642be9cdac624fadf1c77392d02f6b33fb9e36b27738de5fcc6", + "https://esm.sh/v135/@smithy/chunked-blob-reader@3.0.0/denonext/chunked-blob-reader.mjs": "bfd33430ff0d1b7c3dc6e42401a2adfcdeaf2dbb9ac56ca6578782c99e2cb359", + "https://esm.sh/v135/@smithy/config-resolver@3.0.5/denonext/config-resolver.mjs": "0ccf80d6a6427058db95154498485b6a5ae77d12c4fdae48406c9a60b41afe2b", + "https://esm.sh/v135/@smithy/core@2.4.0/denonext/core.mjs": "3ad714d4c1fdb7dcffd91936255289197d6bf0523f13d36bb94e9ce1fd1756d5", + "https://esm.sh/v135/@smithy/eventstream-codec@3.1.2/denonext/eventstream-codec.mjs": "8ea933c44dc8baa334f47b1c2b70a9bf2a14836f9fab720b1125664fb26c4527", + "https://esm.sh/v135/@smithy/eventstream-serde-browser@3.0.6/denonext/eventstream-serde-browser.mjs": "c66e37b7b31c63ff96977e1627d8016b543fccdf95a4f9e388da378d61ce7d0f", + "https://esm.sh/v135/@smithy/eventstream-serde-config-resolver@3.0.3/denonext/eventstream-serde-config-resolver.mjs": "0960eeb9f45540bca3281e9d539b75ed114891b8453c91c2c87dee294387d81d", + "https://esm.sh/v135/@smithy/eventstream-serde-universal@3.0.5/denonext/eventstream-serde-universal.mjs": "ff38bc5052d81372e0cb60749c44354488002d3b0dba292e3626f195bc560ac8", + "https://esm.sh/v135/@smithy/fetch-http-handler@3.2.4/denonext/fetch-http-handler.mjs": "7890ad9cef41a0b0a1a5440153108391d5e5f995a39a028637b3cef271c76075", + "https://esm.sh/v135/@smithy/hash-blob-browser@3.1.2/denonext/hash-blob-browser.mjs": "39b8b23e12aafc146af0af6954ff957752343c9c040d09bda1a0cf4aa5de52fa", + "https://esm.sh/v135/@smithy/invalid-dependency@3.0.3/denonext/invalid-dependency.mjs": "99f4bdd11680348113a0acd593a7f402a33d10654cf4218b5b0f967dbcdae19e", + "https://esm.sh/v135/@smithy/is-array-buffer@3.0.0/denonext/is-array-buffer.mjs": "f8bb7f850b646a10880d4e52c60151913b7d81911b2b1cd1355c9adef56ab3e2", + "https://esm.sh/v135/@smithy/md5-js@3.0.3/denonext/md5-js.mjs": "6f4d21d0d4e09cce9245a4e3bddb899b40da3a1c0ce9a8fd12b8f8ac09375857", + "https://esm.sh/v135/@smithy/middleware-content-length@3.0.5/denonext/middleware-content-length.mjs": "bce550610386d8945899345a97f9aabb00976d7db378a51c463c043008e0f6df", + "https://esm.sh/v135/@smithy/middleware-endpoint@3.1.0/denonext/middleware-endpoint.mjs": "becfe2cb560079a86b0102a3a817c3a6b6f61d7ed1b7f65b6b28ae772871e638", + "https://esm.sh/v135/@smithy/middleware-retry@3.0.15/denonext/middleware-retry.mjs": "2d6b23bdb5ce62336afc02d045d8bb1bf0832fa8eafb022d500372c08b8ea6cb", + "https://esm.sh/v135/@smithy/middleware-serde@3.0.3/denonext/middleware-serde.mjs": "2513b3aaa3f35cf0c33841550aa23b4f4ab4d645d60f86c7a173a11b2b0c9b7a", + "https://esm.sh/v135/@smithy/middleware-stack@3.0.3/denonext/middleware-stack.mjs": "a84a0dda6e1d402ba69cba6747643d6d3f0f3532ac263beb0920f0f5f34ed53c", + "https://esm.sh/v135/@smithy/property-provider@3.1.3/denonext/property-provider.mjs": "8fbecd9b01ba1486726b9f43559926332389b292f276a10708239b1bb666c819", + "https://esm.sh/v135/@smithy/protocol-http@4.1.0/denonext/protocol-http.mjs": "8dc60c296a28eea35bb0c394d3cfdb22bb81385424e0c1099bbda21d38ff132c", + "https://esm.sh/v135/@smithy/querystring-builder@3.0.3/denonext/querystring-builder.mjs": "26803f47afc07fdcfb0506cb95235db97250abfb6e5e31311d4d3e34356ffd45", + "https://esm.sh/v135/@smithy/querystring-parser@3.0.3/denonext/querystring-parser.mjs": "1186ec8e490e5eb9a911945652400304ab9a2128e13734f80717e59f455d0b3b", + "https://esm.sh/v135/@smithy/service-error-classification@3.0.3/denonext/service-error-classification.mjs": "46b409a7d492acacb936ecae2c05e8e11e4910146f6eb2f290067b3cdae8410b", + "https://esm.sh/v135/@smithy/signature-v4@4.1.0/denonext/signature-v4.mjs": "d4adec6b85e442a4dbce5bc391d3856ef202f00f2bedc37f12d5f40fec050e69", + "https://esm.sh/v135/@smithy/smithy-client@3.2.0/denonext/smithy-client.mjs": "2f051fcd8addfba2786c6d712cb8ce443c25b32f2ba258d1d0a46f550eb31451", + "https://esm.sh/v135/@smithy/types@3.3.0/denonext/types.mjs": "0b82ba4c0d421c6476ac68730acdd7a0c9bd014d34c9c556b627fd1c06673eb3", + "https://esm.sh/v135/@smithy/url-parser@3.0.3/denonext/url-parser.mjs": "69067083fcbb733d78ff55e0a1b39852dba3c893e436868fc062829fec623cd8", + "https://esm.sh/v135/@smithy/util-base64@3.0.0/denonext/util-base64.mjs": "d6a01faaa94fdbeb4b92b02e91801dfbe241439e37a0edf7d817c59daf66c0e3", + "https://esm.sh/v135/@smithy/util-body-length-browser@3.0.0/denonext/util-body-length-browser.mjs": "d67382004d61919b97a756a454f9b312cfb0011a9727d3d1ca69ebddf1c7843a", + "https://esm.sh/v135/@smithy/util-config-provider@3.0.0/denonext/util-config-provider.mjs": "832c0ab1d3b06a51351ea23b33628bd36a37ef570e02e469f6ab39f71d88d7b1", + "https://esm.sh/v135/@smithy/util-defaults-mode-browser@3.0.15/denonext/util-defaults-mode-browser.mjs": "9c1088619d3fe879e13cd81f97e26a605c1fe6d28aa0e47022441a6229965a1d", + "https://esm.sh/v135/@smithy/util-endpoints@2.0.5/denonext/util-endpoints.mjs": "3876bd3404b820a5fab88bbe3f8ba2a8e373bb0099c9838617ec88f898dd78d0", + "https://esm.sh/v135/@smithy/util-hex-encoding@3.0.0/denonext/util-hex-encoding.mjs": "cbdd7aabeb3903596980e2903efec3e5501f7e1259fb7b97e327a3b4e635f23c", + "https://esm.sh/v135/@smithy/util-middleware@3.0.3/denonext/util-middleware.mjs": "a885e613b933ce02c7c73507e80ef5b81374a55a647cc4bc397bb1f19284a95b", + "https://esm.sh/v135/@smithy/util-retry@3.0.3/denonext/util-retry.mjs": "2bc452ea87cbe471e2bee783776d528fec4afcd083367c1dafd8936e229c64f3", + "https://esm.sh/v135/@smithy/util-stream@3.1.3/denonext/util-stream.mjs": "13b6b4e3c10e0a0586e6fca8a7e3d2d8fea840aecb413337c2d75c0fceb75f37", + "https://esm.sh/v135/@smithy/util-uri-escape@3.0.0/denonext/util-uri-escape.mjs": "df2c80781ede692323dee6e2da3711e7ccc4f7a1cee949b09aba8d1ce15bbe03", + "https://esm.sh/v135/@smithy/util-utf8@2.0.2/denonext/util-utf8.mjs": "d1869dca8a21b3e6c297cb55f90e1b78bf8f365afd1f173c16d719f28245604b", + "https://esm.sh/v135/@smithy/util-utf8@2.3.0/denonext/util-utf8.mjs": "10a9f2014b2b5b2e387e04c1c7974e8219332fa30a6904923f54a46c974c6c84", + "https://esm.sh/v135/@smithy/util-utf8@3.0.0/denonext/util-utf8.mjs": "abe704ed8c4266b29906116ef723b98e8729078537b252c9a213ad373559488a", + "https://esm.sh/v135/@smithy/util-waiter@3.1.2/denonext/util-waiter.mjs": "8bff673e4c8b620b34f59cbfa0e6c92de95b3c00190861b5b2cb113923bf8288", + "https://esm.sh/v135/bowser@2.11.0/denonext/bowser.mjs": "3fd0c5d68c4bb8b3243c1b0ac76442fa90f5e20ee12773ce2b2f476c2e7a3615", + "https://esm.sh/v135/fast-xml-parser@4.4.1/denonext/fast-xml-parser.mjs": "506f0ae0ce83e4664b4e2a3bf3cde30b3d44c019012938ab12b76fa38353e864", + "https://esm.sh/v135/nanoevents@9.0.0/denonext/nanoevents.mjs": "666c9d584019a64758bd3071e561051747454da64299ac06b79ede7210fe5e85", + "https://esm.sh/v135/strnum@1.0.5/denonext/strnum.mjs": "1ffef4adec2f74139e36a2bfed8381880541396fe1c315779fb22e081b17468b", + "https://esm.sh/v135/tslib@2.6.2/denonext/tslib.mjs": "29782bcd3139f77ec063dc5a9385c0fff4a8d0a23b6765c73d9edeb169a04bf1", + "https://esm.sh/v135/tslib@2.6.3/denonext/tslib.mjs": "0834c22e9fbf95f6a5659cc2017543f7d41aa880f24ab84cb11d24e6bee99303", + "https://esm.sh/v135/uuid@9.0.1/denonext/uuid.mjs": "7d7d3aa57fa136e2540886654c416d9da10d8cfebe408bae47fd47070f0bfb2a", + "https://esm.sh/v135/zod-validation-error@3.3.0/denonext/zod-validation-error.mjs": "4efabd593e1430c31a044f79d299a62120946a3e701159b29922b50f3223c186", + "https://esm.sh/v135/zod@3.23.8/denonext/zod.mjs": "b3707b03ddc01aab11b740436ab23c0fcc8d15fed072be20085c1fd611016b61", + "https://esm.sh/zod-validation-error@3.3.0": "d8825ca67952b6adff6b35026dc465f9638d4923dbd54fe9e8e81fbfddca9630", + "https://esm.sh/zod@3.23.8": "728819c1f651800179a5a80daf24b3e54b2ddea87828bd10e63875a604bcb94e" + } +} diff --git a/modules/captcha/actors/throttle.ts b/modules/captcha/actors/throttle.ts new file mode 100644 index 0000000..0acf905 --- /dev/null +++ b/modules/captcha/actors/throttle.ts @@ -0,0 +1,44 @@ +import { ActorBase, ActorContext, Empty } from "../module.gen.ts"; +import { ThrottleRequest, ThrottleResponse } from "../utils/types.ts"; + +type Input = undefined; + +interface State { + start: number; + count: number; +} + +export class Actor extends ActorBase { + public initialize(_ctx: ActorContext): State { + // Will refill on first call of `throttle` + return { + start: 0, + count: 0, + }; + } + + throttle(_ctx: ActorContext, req: ThrottleRequest): ThrottleResponse { + const now = Date.now(); + + if (now - this.state.start > req.period) { + this.state.start = now; + this.state.count = 1; + return { success: true }; + } + + if (this.state.count >= req.requests) { + return { success: false }; + } + + this.state.count += 1; + + return { success: true }; + } + + reset(_ctx: ActorContext, req: Empty): Empty { + this.state.start = 0; + this.state.count = 0; + + return {}; + } +} diff --git a/modules/captcha/module.json b/modules/captcha/module.json new file mode 100644 index 0000000..5cb0b9c --- /dev/null +++ b/modules/captcha/module.json @@ -0,0 +1,34 @@ +{ + "status": "stable", + "name": "Captcha", + "description": "", + "icon": "", + "tags": [], + "authors": [ + "rivet-gg", + "ABCxFF" + ], + "scripts": { + "verify_captcha_token": { + "name": "Verify Captcha Response", + "public": false + }, + "guard": { + "name": "Ratelimit Guarded with Captcha Challenge", + "public": false + } + }, + "errors": { + "captcha_failed": { + "name": "Captcha Challenge Failed", + "internal": false + }, + "captcha_needed": { + "name": "Captcha Required (Rate Limit Exceeded)", + "internal": false + } + }, + "actors": { + "throttle": {} + } +} \ No newline at end of file diff --git a/modules/captcha/scripts/guard.ts b/modules/captcha/scripts/guard.ts new file mode 100644 index 0000000..5f28047 --- /dev/null +++ b/modules/captcha/scripts/guard.ts @@ -0,0 +1,54 @@ +import { RuntimeError, ScriptContext } from "../module.gen.ts"; +import { getPublicConfig } from "../utils/get_sitekey.ts"; +// import { getPublicConfig } from "../utils/get_sitekey.ts"; +import type { CaptchaProvider, ThrottleRequest, ThrottleResponse } from "../utils/types.ts"; + +export interface Request { + type: string; + key: string; + requests: number; + period: number; + captchaToken?: string | null, + captchaProvider: CaptchaProvider +} + +export type Response = Record; + +export async function run( + ctx: ScriptContext, + req: Request, +): Promise { + const key = `${JSON.stringify(req.type)}.${JSON.stringify(req.key)}`; + + if (req.captchaToken) { + try { + await ctx.modules.captcha.verifyCaptchaToken({ + token: req.captchaToken, + provider: req.captchaProvider + }); + + await ctx.actors.throttle.getOrCreateAndCall(key, undefined, "reset", {}); + + return {}; + } catch { + // If we error, it means the captcha failed, we can continue with our normal ratelimitting + } + } + + const res = await ctx.actors.throttle.getOrCreateAndCall< + undefined, + ThrottleRequest, + ThrottleResponse + >(key, undefined, "throttle", { + requests: req.requests, + period: req.period, + }); + + if (!res.success) { + throw new RuntimeError("captcha_needed", { + meta: getPublicConfig(req.captchaProvider) + }); + } + + return {}; +} \ No newline at end of file diff --git a/modules/captcha/scripts/verify_captcha_token.ts b/modules/captcha/scripts/verify_captcha_token.ts new file mode 100644 index 0000000..47832e2 --- /dev/null +++ b/modules/captcha/scripts/verify_captcha_token.ts @@ -0,0 +1,36 @@ +import { RuntimeError, ScriptContext } from "../module.gen.ts"; +import { validateHCaptchaResponse } from "../utils/providers/hcaptcha.ts"; +import { validateCFTurnstileResponse } from "../utils/providers/turnstile.ts"; +// import { validateHCaptchaResponse } from "../providers/hcaptcha.ts"; +// import { validateCFTurnstileResponse } from "../providers/turnstile.ts"; +import { CaptchaProvider } from "../utils/types.ts"; + +export interface Request { + token: string, + provider: CaptchaProvider +} + +export type Response = Record; + +export async function run( + ctx: ScriptContext, + req: Request, +): Promise { + const captchaToken = req.token; + const captchaProvider = req.provider; + + let success: boolean = false; + if ("hcaptcha" in captchaProvider) { + success = await validateHCaptchaResponse(captchaProvider.hcaptcha.secret, captchaToken); + } else if ("turnstile" in captchaProvider) { + success = await validateCFTurnstileResponse(captchaProvider.turnstile.secret, captchaToken); + } else { + success = true; + } + + if (!success) { + throw new RuntimeError("captcha_failed"); + } + + return {}; +} \ No newline at end of file diff --git a/modules/captcha/tests/e2e_guard.ts b/modules/captcha/tests/e2e_guard.ts new file mode 100644 index 0000000..824fc67 --- /dev/null +++ b/modules/captcha/tests/e2e_guard.ts @@ -0,0 +1,45 @@ +import { test, TestContext } from "../module.gen.ts"; +import { assertEquals } from "https://deno.land/std@0.217.0/assert/mod.ts"; + +const didFail = async (x: () => Promise) => { + try { + await x(); + return false + } catch { + return true; + } +} + +test("e2e success and failure", async (ctx: TestContext) => { + const PERIOD = 5000; + const REQUESTS = 5; + + const captchaProvider = { + turnstile: { + secret: "0x0000000000000000000000000000000000000000", + sitekey: "" // doesn't really matter here + } + } + + assertEquals(false, await didFail(async () => { + for (let i = 0; i < REQUESTS; ++i) { + await ctx.modules.captcha.guard({ + type: "ip", + key: "aaaa", + requests: REQUESTS, + period: PERIOD, + captchaProvider + }); + } + })); + + assertEquals(true, await didFail(async () => { + await ctx.modules.captcha.guard({ + type: "ip", + key: "aaaa", + requests: REQUESTS, + period: PERIOD, + captchaProvider + }); + })); +}); \ No newline at end of file diff --git a/modules/captcha/tests/e2e_verify_token.ts b/modules/captcha/tests/e2e_verify_token.ts new file mode 100644 index 0000000..2e014cf --- /dev/null +++ b/modules/captcha/tests/e2e_verify_token.ts @@ -0,0 +1,75 @@ +import { test, TestContext } from "../module.gen.ts"; +import { assertEquals } from "https://deno.land/std@0.217.0/assert/mod.ts"; + +const didFail = async (x: () => Promise) => { + try { + await x(); + return false + } catch { + return true; + } +} + +test( + "hcaptcha success and failure", + async (ctx: TestContext) => { + const shouldBeFalse = await didFail(async () => { + await ctx.modules.captcha.verifyCaptchaToken({ + provider: { + hcaptcha: { + secret: "0x0000000000000000000000000000000000000000", + sitekey: "" // doesn't really matter here + } + }, + token: "10000000-aaaa-bbbb-cccc-000000000001" + }); + }); + assertEquals(shouldBeFalse, false); + + const shouldBeTrue = await didFail(async () => { + await ctx.modules.captcha.verifyCaptchaToken({ + provider: { + hcaptcha: { + secret: "0x0000000000000000000000000000000000000000", + sitekey: "" // doesn't really matter here + } + }, + token: "lorem" + }); + }); + assertEquals(shouldBeTrue, true); + }, +); + +test( + "turnstile success and failure", + async (ctx: TestContext) => { + // Always passes + const shouldBeTrue = await didFail(async () => { + await ctx.modules.captcha.verifyCaptchaToken({ + provider: { + turnstile: { + secret: "2x0000000000000000000000000000000AA", + sitekey: "" // doesn't really matter here + } + }, + token: "lorem" + }); + }); + assertEquals(shouldBeTrue, true); + + // Always fails + const shouldBeFalse = await didFail(async () => { + await ctx.modules.captcha.verifyCaptchaToken({ + provider: { + turnstile: { + secret: "1x0000000000000000000000000000000AA", + sitekey: "" // doesn't really matter here + } + }, + token: "ipsum" + }); + }); + assertEquals(shouldBeFalse, false); + }, +); diff --git a/modules/captcha/utils/get_sitekey.ts b/modules/captcha/utils/get_sitekey.ts new file mode 100644 index 0000000..8930e45 --- /dev/null +++ b/modules/captcha/utils/get_sitekey.ts @@ -0,0 +1,19 @@ +import { CaptchaProvider, PublicCaptchaProviderConfig } from "./types.ts"; + +export const getPublicConfig = (provider: CaptchaProvider): PublicCaptchaProviderConfig => { + if ("hcaptcha" in provider) { + return { + hcaptcha: { sitekey: provider.hcaptcha.sitekey } + }; + } else if ("turnstile" in provider) { + return { + turnstile: { + sitekey: provider.turnstile.sitekey + } + } + } else { + return { + test: {} + } + } +} \ No newline at end of file diff --git a/modules/captcha/utils/providers/hcaptcha.ts b/modules/captcha/utils/providers/hcaptcha.ts new file mode 100644 index 0000000..9de6903 --- /dev/null +++ b/modules/captcha/utils/providers/hcaptcha.ts @@ -0,0 +1,21 @@ +const API = "https://api.hcaptcha.com/siteverify"; +export const validateHCaptchaResponse = async ( + secret: string, + response: string +): Promise => { + try { + const body = new FormData(); + body.append("secret", secret); + body.append("response", response); + const result = await fetch(API, { + body, + method: "POST", + }); + + const { success } = await result.json(); + + return success; + } catch {} + + return false; +} \ No newline at end of file diff --git a/modules/captcha/utils/providers/turnstile.ts b/modules/captcha/utils/providers/turnstile.ts new file mode 100644 index 0000000..542a4e1 --- /dev/null +++ b/modules/captcha/utils/providers/turnstile.ts @@ -0,0 +1,21 @@ +const API = "https://challenges.cloudflare.com/turnstile/v0/siteverify"; +export const validateCFTurnstileResponse = async ( + secret: string, + response: string +): Promise => { + try { + const result = await fetch(API, { + body: JSON.stringify({ secret, response }), + method: "POST", + headers: { + "Content-Type": "application/json", + } + }); + + const { success } = await result.json(); + + return success; + } catch {} + + return false; +} \ No newline at end of file diff --git a/modules/captcha/utils/types.ts b/modules/captcha/utils/types.ts new file mode 100644 index 0000000..1e17b81 --- /dev/null +++ b/modules/captcha/utils/types.ts @@ -0,0 +1,31 @@ + + +interface ProviderCFTurnstile { + sitekey: string; + secret: string; +} + +interface ProviderHCaptcha { + // TODO: Score threshold + sitekey: string; + secret: string; +} +type PublicCFTurnstileConfig = { sitekey: string; } +type PublicHCaptchaConfig = { sitekey: string; } + +export type CaptchaProvider = { test: Record } + | { turnstile: ProviderCFTurnstile } + | { hcaptcha: ProviderHCaptcha }; + +export type PublicCaptchaProviderConfig = { test: Record } + | { turnstile: PublicCFTurnstileConfig } + | { hcaptcha: PublicHCaptchaConfig }; + +export interface ThrottleRequest { + requests: number; + period: number; +} + +export interface ThrottleResponse { + success: boolean; +} \ No newline at end of file diff --git a/tests/basic/backend.json b/tests/basic/backend.json index 6a6547b..8f40519 100644 --- a/tests/basic/backend.json +++ b/tests/basic/backend.json @@ -10,6 +10,9 @@ "achievements": { "registry": "local" }, + "captcha": { + "registry": "local" + }, "analytics": { "registry": "local" },