@lastshotlabs/bunshot 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CLAUDE.md +102 -0
  2. package/README.md +1458 -0
  3. package/bun.lock +170 -0
  4. package/package.json +47 -0
  5. package/src/adapters/memoryAuth.ts +240 -0
  6. package/src/adapters/mongoAuth.ts +91 -0
  7. package/src/adapters/sqliteAuth.ts +320 -0
  8. package/src/app.ts +368 -0
  9. package/src/cli.ts +265 -0
  10. package/src/index.ts +52 -0
  11. package/src/lib/HttpError.ts +5 -0
  12. package/src/lib/appConfig.ts +29 -0
  13. package/src/lib/authAdapter.ts +46 -0
  14. package/src/lib/authRateLimit.ts +104 -0
  15. package/src/lib/constants.ts +2 -0
  16. package/src/lib/context.ts +17 -0
  17. package/src/lib/emailVerification.ts +105 -0
  18. package/src/lib/fingerprint.ts +43 -0
  19. package/src/lib/jwt.ts +17 -0
  20. package/src/lib/logger.ts +9 -0
  21. package/src/lib/mongo.ts +70 -0
  22. package/src/lib/oauth.ts +114 -0
  23. package/src/lib/queue.ts +18 -0
  24. package/src/lib/redis.ts +45 -0
  25. package/src/lib/roles.ts +23 -0
  26. package/src/lib/session.ts +91 -0
  27. package/src/lib/validate.ts +14 -0
  28. package/src/lib/ws.ts +82 -0
  29. package/src/middleware/bearerAuth.ts +15 -0
  30. package/src/middleware/botProtection.ts +73 -0
  31. package/src/middleware/cacheResponse.ts +189 -0
  32. package/src/middleware/cors.ts +19 -0
  33. package/src/middleware/errorHandler.ts +14 -0
  34. package/src/middleware/identify.ts +36 -0
  35. package/src/middleware/index.ts +8 -0
  36. package/src/middleware/logger.ts +9 -0
  37. package/src/middleware/rateLimit.ts +37 -0
  38. package/src/middleware/requireRole.ts +42 -0
  39. package/src/middleware/requireVerifiedEmail.ts +31 -0
  40. package/src/middleware/userAuth.ts +9 -0
  41. package/src/models/AuthUser.ts +17 -0
  42. package/src/routes/auth.ts +245 -0
  43. package/src/routes/health.ts +27 -0
  44. package/src/routes/home.ts +21 -0
  45. package/src/routes/oauth.ts +174 -0
  46. package/src/schemas/auth.ts +14 -0
  47. package/src/server.ts +91 -0
  48. package/src/services/auth.ts +59 -0
  49. package/src/ws/index.ts +42 -0
  50. package/tsconfig.json +43 -0
package/bun.lock ADDED
@@ -0,0 +1,170 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 0,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "project",
7
+ "dependencies": {
8
+ "@hono/zod-openapi": "1.2.2",
9
+ "@scalar/hono-api-reference": "0.10.0",
10
+ "arctic": "^3.7.0",
11
+ "bullmq": "^5.70.4",
12
+ "hono": "4.12.5",
13
+ "ioredis": "5.10.0",
14
+ "jose": "6.2.0",
15
+ "mongoose": "9.2.4",
16
+ "zod": "4.3.6",
17
+ },
18
+ "devDependencies": {
19
+ "@types/bun": "1.3.10",
20
+ },
21
+ },
22
+ },
23
+ "packages": {
24
+ "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@8.4.3", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-lwfMTN7kDbFDwMniYZUebiGGHxVGBw9ZSI4IBYjm6Ey22Kd5z/fsQb2k+Okr8WMbCCC553vi/ZM9utl5/XcvuQ=="],
25
+
26
+ "@hono/zod-openapi": ["@hono/zod-openapi@1.2.2", "", { "dependencies": { "@asteasolutions/zod-to-openapi": "^8.4.1", "@hono/zod-validator": "^0.7.6", "openapi3-ts": "^4.5.0" }, "peerDependencies": { "hono": ">=4.3.6", "zod": "^4.0.0" } }, "sha512-va6vsL23wCJ1d0Vd+vGL1XOt+wPwItxirYafuhlW9iC2MstYr2FvsI7mctb45eBTjZfkqB/3LYDJEppPjOEiHw=="],
27
+
28
+ "@hono/zod-validator": ["@hono/zod-validator@0.7.6", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw=="],
29
+
30
+ "@ioredis/commands": ["@ioredis/commands@1.5.1", "", {}, "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw=="],
31
+
32
+ "@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.4.6", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g=="],
33
+
34
+ "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
35
+
36
+ "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="],
37
+
38
+ "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="],
39
+
40
+ "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="],
41
+
42
+ "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="],
43
+
44
+ "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="],
45
+
46
+ "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
47
+
48
+ "@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="],
49
+
50
+ "@oslojs/crypto": ["@oslojs/crypto@1.0.1", "", { "dependencies": { "@oslojs/asn1": "1.0.0", "@oslojs/binary": "1.0.0" } }, "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ=="],
51
+
52
+ "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
53
+
54
+ "@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="],
55
+
56
+ "@scalar/core": ["@scalar/core@0.4.0", "", { "dependencies": { "@scalar/types": "0.7.0" } }, "sha512-Zcl+V8oBxb0S7vR+Nro8J53GD/w/kSjuyX0UoT3r1sn0bUM3Buf4Ob44n3CSfluQzxmiMuZxfOROHqa9F+ckbg=="],
57
+
58
+ "@scalar/helpers": ["@scalar/helpers@0.3.0", "", {}, "sha512-lhQdehgighJC+PiSTJbbggM/SM3UydcRQil6Cfp/M4l539qklIh35pt4eh1+H+5Esa03gHnJwhTHF3TwglSOJw=="],
59
+
60
+ "@scalar/hono-api-reference": ["@scalar/hono-api-reference@0.10.0", "", { "dependencies": { "@scalar/core": "0.4.0" }, "peerDependencies": { "hono": "^4.11.5" } }, "sha512-5QGNilMAnLRzSufMhh0Ni8DepWzL2UOJm+RQI+e/slrhhfhO+pSV2bcLE8dhBz6k+V70El6Pvl0m2cPaDvP4sw=="],
61
+
62
+ "@scalar/types": ["@scalar/types@0.7.0", "", { "dependencies": { "@scalar/helpers": "0.3.0", "nanoid": "^5.1.6", "type-fest": "^5.3.1", "zod": "^4.3.5" } }, "sha512-IkG62M4ztmqkYNVhLpcswBojlQctbXLdkDa3UFsY8FfT7yfZ2LppjptycW9tWjD09ZQb4QAZ070FAUHmFRIS7w=="],
63
+
64
+ "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
65
+
66
+ "@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="],
67
+
68
+ "@types/webidl-conversions": ["@types/webidl-conversions@7.0.3", "", {}, "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="],
69
+
70
+ "@types/whatwg-url": ["@types/whatwg-url@13.0.0", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q=="],
71
+
72
+ "arctic": ["arctic@3.7.0", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-ZMQ+f6VazDgUJOd+qNV+H7GohNSYal1mVjm5kEaZfE2Ifb7Ss70w+Q7xpJC87qZDkMZIXYf0pTIYZA0OPasSbw=="],
73
+
74
+ "bson": ["bson@7.2.0", "", {}, "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ=="],
75
+
76
+ "bullmq": ["bullmq@5.70.4", "", { "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.9.3", "msgpackr": "1.11.5", "node-abort-controller": "3.1.1", "semver": "7.7.4", "tslib": "2.8.1", "uuid": "11.1.0" } }, "sha512-S58YT/tGdhc4pEPcIahtZRBR1TcTLpss1UKiXimF+Vy4yZwF38pW2IvhHqs4j4dEbZqDt8oi0jGGN/WYQHbPDg=="],
77
+
78
+ "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
79
+
80
+ "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
81
+
82
+ "cron-parser": ["cron-parser@4.9.0", "", { "dependencies": { "luxon": "^3.2.1" } }, "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q=="],
83
+
84
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
85
+
86
+ "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
87
+
88
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
89
+
90
+ "hono": ["hono@4.12.5", "", {}, "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg=="],
91
+
92
+ "ioredis": ["ioredis@5.10.0", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-HVBe9OFuqs+Z6n64q09PQvP1/R4Bm+30PAyyD4wIEqssh3v9L21QjCVk4kRLucMBcDokJTcLjsGeVRlq/nH6DA=="],
93
+
94
+ "jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
95
+
96
+ "kareem": ["kareem@3.2.0", "", {}, "sha512-VS8MWZz/cT+SqBCpVfNN4zoVz5VskR3N4+sTmUXme55e9avQHntpwpNq0yjnosISXqwJ3AQVjlbI4Dyzv//JtA=="],
97
+
98
+ "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
99
+
100
+ "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
101
+
102
+ "luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="],
103
+
104
+ "memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="],
105
+
106
+ "mongodb": ["mongodb@7.0.0", "", { "dependencies": { "@mongodb-js/saslprep": "^1.3.0", "bson": "^7.0.0", "mongodb-connection-string-url": "^7.0.0" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.806.0", "@mongodb-js/zstd": "^7.0.0", "gcp-metadata": "^7.0.1", "kerberos": "^7.0.0", "mongodb-client-encryption": ">=7.0.0 <7.1.0", "snappy": "^7.3.2", "socks": "^2.8.6" }, "optionalPeers": ["@aws-sdk/credential-providers", "@mongodb-js/zstd", "gcp-metadata", "kerberos", "mongodb-client-encryption", "snappy", "socks"] }, "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg=="],
107
+
108
+ "mongodb-connection-string-url": ["mongodb-connection-string-url@7.0.1", "", { "dependencies": { "@types/whatwg-url": "^13.0.0", "whatwg-url": "^14.1.0" } }, "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ=="],
109
+
110
+ "mongoose": ["mongoose@9.2.4", "", { "dependencies": { "kareem": "3.2.0", "mongodb": "~7.0", "mpath": "0.9.0", "mquery": "6.0.0", "ms": "2.1.3", "sift": "17.1.3" } }, "sha512-XNh+jiztVMddDFDCv8TWxVxi/rGx+0FfsK3Ftj6hcYzEmhTcos2uC144OJRmUFPHSu3hJr6Pgip++Ab2+Da35Q=="],
111
+
112
+ "mpath": ["mpath@0.9.0", "", {}, "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="],
113
+
114
+ "mquery": ["mquery@6.0.0", "", {}, "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw=="],
115
+
116
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
117
+
118
+ "msgpackr": ["msgpackr@1.11.5", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA=="],
119
+
120
+ "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="],
121
+
122
+ "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
123
+
124
+ "node-abort-controller": ["node-abort-controller@3.1.1", "", {}, "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="],
125
+
126
+ "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
127
+
128
+ "openapi3-ts": ["openapi3-ts@4.5.0", "", { "dependencies": { "yaml": "^2.8.0" } }, "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ=="],
129
+
130
+ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
131
+
132
+ "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
133
+
134
+ "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],
135
+
136
+ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
137
+
138
+ "sift": ["sift@17.1.3", "", {}, "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="],
139
+
140
+ "sparse-bitfield": ["sparse-bitfield@3.0.3", "", { "dependencies": { "memory-pager": "^1.0.2" } }, "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ=="],
141
+
142
+ "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
143
+
144
+ "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
145
+
146
+ "tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="],
147
+
148
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
149
+
150
+ "type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="],
151
+
152
+ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
153
+
154
+ "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
155
+
156
+ "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
157
+
158
+ "whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="],
159
+
160
+ "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
161
+
162
+ "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
163
+
164
+ "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
165
+
166
+ "bullmq/ioredis": ["ioredis@5.9.3", "", { "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA=="],
167
+
168
+ "bullmq/ioredis/@ioredis/commands": ["@ioredis/commands@1.5.0", "", {}, "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow=="],
169
+ }
170
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@lastshotlabs/bunshot",
3
+ "version": "0.0.1",
4
+ "description": "Batteries-included Bun + Hono API framework — auth, sessions, rate limiting, WebSocket, queues, and OpenAPI docs out of the box",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Last-Shot-Labs/bunshot.git"
8
+ },
9
+ "license": "MIT",
10
+ "keywords": [
11
+ "bun",
12
+ "hono",
13
+ "api",
14
+ "framework",
15
+ "auth",
16
+ "websocket",
17
+ "openapi"
18
+ ],
19
+ "module": "src/index.ts",
20
+ "exports": {
21
+ ".": "./src/index.ts"
22
+ },
23
+ "bin": {
24
+ "bunshot": "./src/cli.ts"
25
+ },
26
+ "scripts": {
27
+ "dev": "bun --watch src/index.ts",
28
+ "start": "bun src/index.ts"
29
+ },
30
+ "dependencies": {
31
+ "@hono/zod-openapi": "1.2.2",
32
+ "@scalar/hono-api-reference": "0.10.0",
33
+ "arctic": "^3.7.0",
34
+ "bullmq": "^5.70.4",
35
+ "hono": "4.12.5",
36
+ "ioredis": "5.10.0",
37
+ "jose": "6.2.0",
38
+ "mongoose": "9.2.4",
39
+ "zod": "4.3.6"
40
+ },
41
+ "devDependencies": {
42
+ "@types/bun": "1.3.10"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }
@@ -0,0 +1,240 @@
1
+ import { HttpError } from "@lib/HttpError";
2
+ import type { AuthAdapter, OAuthProfile } from "@lib/authAdapter";
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // In-memory stores — module-level Maps, always ready, lost on process restart
6
+ // ---------------------------------------------------------------------------
7
+
8
+ interface UserRecord {
9
+ id: string;
10
+ email: string | null;
11
+ passwordHash: string | null;
12
+ providerIds: string[];
13
+ roles: string[];
14
+ emailVerified: boolean;
15
+ }
16
+
17
+ const _users = new Map<string, UserRecord>();
18
+ const _byEmail = new Map<string, string>();
19
+ const _sessions = new Map<string, { token: string; expiresAt: number }>();
20
+ const _oauthStates = new Map<string, { codeVerifier?: string; linkUserId?: string; expiresAt: number }>();
21
+ const _cache = new Map<string, { value: string; expiresAt?: number }>();
22
+ const _verificationTokens = new Map<string, { userId: string; email: string; expiresAt: number }>();
23
+
24
+ /** Reset all in-memory state. Useful for test isolation. */
25
+ export const clearMemoryStore = (): void => {
26
+ _users.clear();
27
+ _byEmail.clear();
28
+ _sessions.clear();
29
+ _oauthStates.clear();
30
+ _cache.clear();
31
+ _verificationTokens.clear();
32
+ };
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Auth adapter
36
+ // ---------------------------------------------------------------------------
37
+
38
+ export const memoryAuthAdapter: AuthAdapter = {
39
+ async findByEmail(email) {
40
+ const id = _byEmail.get(email.toLowerCase());
41
+ if (!id) return null;
42
+ const user = _users.get(id);
43
+ if (!user || !user.passwordHash) return null;
44
+ return { id: user.id, passwordHash: user.passwordHash };
45
+ },
46
+
47
+ async create(email, passwordHash) {
48
+ const normalised = email.toLowerCase();
49
+ if (_byEmail.has(normalised)) throw new HttpError(409, "Email already registered");
50
+ const id = crypto.randomUUID();
51
+ const user: UserRecord = { id, email: normalised, passwordHash, providerIds: [], roles: [], emailVerified: false };
52
+ _users.set(id, user);
53
+ _byEmail.set(normalised, id);
54
+ return { id };
55
+ },
56
+
57
+ async setPassword(userId, passwordHash) {
58
+ const user = _users.get(userId);
59
+ if (!user) return;
60
+ user.passwordHash = passwordHash;
61
+ },
62
+
63
+ async findOrCreateByProvider(provider: string, providerId: string, profile: OAuthProfile) {
64
+ const key = `${provider}:${providerId}`;
65
+
66
+ // Find by provider key
67
+ for (const user of _users.values()) {
68
+ if (user.providerIds.includes(key)) return { id: user.id, created: false };
69
+ }
70
+
71
+ // Reject if email belongs to a credential account
72
+ if (profile.email) {
73
+ const existingId = _byEmail.get(profile.email.toLowerCase());
74
+ if (existingId) throw new HttpError(409, "An account with this email already exists. Sign in with your credentials, then link Google from your account settings.");
75
+ }
76
+
77
+ const id = crypto.randomUUID();
78
+ const email = profile.email ? profile.email.toLowerCase() : null;
79
+ const user: UserRecord = { id, email, passwordHash: null, providerIds: [key], roles: [], emailVerified: false };
80
+ _users.set(id, user);
81
+ if (email) _byEmail.set(email, id);
82
+ return { id, created: true };
83
+ },
84
+
85
+ async linkProvider(userId, provider, providerId) {
86
+ const user = _users.get(userId);
87
+ if (!user) throw new HttpError(404, "User not found");
88
+ const key = `${provider}:${providerId}`;
89
+ if (!user.providerIds.includes(key)) user.providerIds.push(key);
90
+ },
91
+
92
+ async getRoles(userId) {
93
+ return _users.get(userId)?.roles ?? [];
94
+ },
95
+
96
+ async setRoles(userId, roles) {
97
+ const user = _users.get(userId);
98
+ if (!user) return;
99
+ user.roles = [...roles];
100
+ },
101
+
102
+ async addRole(userId, role) {
103
+ const user = _users.get(userId);
104
+ if (!user) return;
105
+ if (!user.roles.includes(role)) user.roles.push(role);
106
+ },
107
+
108
+ async removeRole(userId, role) {
109
+ const user = _users.get(userId);
110
+ if (!user) return;
111
+ user.roles = user.roles.filter((r) => r !== role);
112
+ },
113
+
114
+ async getUser(userId) {
115
+ const user = _users.get(userId);
116
+ if (!user) return null;
117
+ return {
118
+ email: user.email ?? undefined,
119
+ providerIds: [...user.providerIds],
120
+ emailVerified: user.emailVerified,
121
+ };
122
+ },
123
+
124
+ async unlinkProvider(userId, provider) {
125
+ const user = _users.get(userId);
126
+ if (!user) throw new HttpError(404, "User not found");
127
+ user.providerIds = user.providerIds.filter((id) => !id.startsWith(`${provider}:`));
128
+ },
129
+
130
+ async findByIdentifier(value) {
131
+ const id = _byEmail.get(value.toLowerCase());
132
+ if (!id) return null;
133
+ const user = _users.get(id);
134
+ if (!user || !user.passwordHash) return null;
135
+ return { id: user.id, passwordHash: user.passwordHash };
136
+ },
137
+
138
+ async setEmailVerified(userId, verified) {
139
+ const user = _users.get(userId);
140
+ if (user) user.emailVerified = verified;
141
+ },
142
+
143
+ async getEmailVerified(userId) {
144
+ return _users.get(userId)?.emailVerified ?? false;
145
+ },
146
+ };
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Session helpers (used by src/lib/session.ts)
150
+ // ---------------------------------------------------------------------------
151
+
152
+ const SESSION_TTL_MS = 60 * 60 * 24 * 7 * 1000; // 7 days
153
+
154
+ export const memoryCreateSession = (userId: string, token: string): void => {
155
+ _sessions.set(userId, { token, expiresAt: Date.now() + SESSION_TTL_MS });
156
+ };
157
+
158
+ export const memoryGetSession = (userId: string): string | null => {
159
+ const entry = _sessions.get(userId);
160
+ if (!entry || entry.expiresAt <= Date.now()) return null;
161
+ return entry.token;
162
+ };
163
+
164
+ export const memoryDeleteSession = (userId: string): void => {
165
+ _sessions.delete(userId);
166
+ };
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // OAuth state helpers (used by src/lib/oauth.ts)
170
+ // ---------------------------------------------------------------------------
171
+
172
+ const OAUTH_STATE_TTL_MS = 5 * 60 * 1000; // 5 minutes
173
+
174
+ export const memoryStoreOAuthState = (state: string, codeVerifier?: string, linkUserId?: string): void => {
175
+ _oauthStates.set(state, { codeVerifier, linkUserId, expiresAt: Date.now() + OAUTH_STATE_TTL_MS });
176
+ };
177
+
178
+ export const memoryConsumeOAuthState = (state: string): { codeVerifier?: string; linkUserId?: string } | null => {
179
+ const entry = _oauthStates.get(state);
180
+ if (!entry || entry.expiresAt <= Date.now()) {
181
+ _oauthStates.delete(state);
182
+ return null;
183
+ }
184
+ _oauthStates.delete(state);
185
+ return { codeVerifier: entry.codeVerifier, linkUserId: entry.linkUserId };
186
+ };
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // Cache helpers (used by src/middleware/cacheResponse.ts)
190
+ // ---------------------------------------------------------------------------
191
+
192
+ export const memoryGetCache = (key: string): string | null => {
193
+ const entry = _cache.get(key);
194
+ if (!entry) return null;
195
+ if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {
196
+ _cache.delete(key);
197
+ return null;
198
+ }
199
+ return entry.value;
200
+ };
201
+
202
+ export const memorySetCache = (key: string, value: string, ttlSeconds?: number): void => {
203
+ const expiresAt = ttlSeconds ? Date.now() + ttlSeconds * 1000 : undefined;
204
+ _cache.set(key, { value, expiresAt });
205
+ };
206
+
207
+ export const memoryDelCache = (key: string): void => {
208
+ _cache.delete(key);
209
+ };
210
+
211
+ export const memoryDelCachePattern = (pattern: string): void => {
212
+ // Convert glob * to a regex
213
+ const regex = new RegExp("^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$");
214
+ for (const key of _cache.keys()) {
215
+ if (regex.test(key)) _cache.delete(key);
216
+ }
217
+ };
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // Email verification token helpers (used by src/lib/emailVerification.ts)
221
+ // ---------------------------------------------------------------------------
222
+
223
+ const EMAIL_VERIFICATION_TTL_MS = 60 * 60 * 24 * 1000; // 24 hours
224
+
225
+ export const memoryCreateVerificationToken = (token: string, userId: string, email: string): void => {
226
+ _verificationTokens.set(token, { userId, email, expiresAt: Date.now() + EMAIL_VERIFICATION_TTL_MS });
227
+ };
228
+
229
+ export const memoryGetVerificationToken = (token: string): { userId: string; email: string } | null => {
230
+ const entry = _verificationTokens.get(token);
231
+ if (!entry || entry.expiresAt <= Date.now()) {
232
+ _verificationTokens.delete(token);
233
+ return null;
234
+ }
235
+ return { userId: entry.userId, email: entry.email };
236
+ };
237
+
238
+ export const memoryDeleteVerificationToken = (token: string): void => {
239
+ _verificationTokens.delete(token);
240
+ };
@@ -0,0 +1,91 @@
1
+ import { AuthUser } from "@models/AuthUser";
2
+ import { HttpError } from "@lib/HttpError";
3
+ import type { AuthAdapter } from "@lib/authAdapter";
4
+
5
+ export const mongoAuthAdapter: AuthAdapter = {
6
+ async findByEmail(email) {
7
+ const user = await AuthUser.findOne({ email });
8
+ if (!user) return null;
9
+ return { id: String(user._id), passwordHash: user.password as string };
10
+ },
11
+ async create(email, passwordHash) {
12
+ try {
13
+ const user = await AuthUser.create({ email, password: passwordHash });
14
+ return { id: String(user._id) };
15
+ } catch (err: any) {
16
+ if (err?.code === 11000) throw new HttpError(409, "Email already registered");
17
+ throw err;
18
+ }
19
+ },
20
+ async setPassword(userId, passwordHash) {
21
+ await AuthUser.findByIdAndUpdate(userId, { password: passwordHash });
22
+ },
23
+ async findOrCreateByProvider(provider, providerId, profile) {
24
+ const key = `${provider}:${providerId}`;
25
+
26
+ // Find by provider key
27
+ let user = await AuthUser.findOne({ providerIds: key });
28
+ if (user) return { id: String(user._id), created: false };
29
+
30
+ // Reject if the email belongs to a credential account — user must link manually
31
+ if (profile.email) {
32
+ const existing = await AuthUser.findOne({ email: profile.email });
33
+ if (existing) throw new HttpError(409, "An account with this email already exists. Sign in with your credentials, then link Google from your account settings.");
34
+ }
35
+
36
+ // Create new user
37
+ user = await AuthUser.create({ email: profile.email, providerIds: [key] });
38
+ return { id: String(user._id), created: true };
39
+ },
40
+ async linkProvider(userId, provider, providerId) {
41
+ const key = `${provider}:${providerId}`;
42
+ const user = await AuthUser.findById(userId);
43
+ if (!user) throw new HttpError(404, "User not found");
44
+ if (!(user.providerIds as string[]).includes(key)) {
45
+ user.providerIds = [...(user.providerIds as string[]), key];
46
+ await user.save();
47
+ }
48
+ },
49
+ async getRoles(userId) {
50
+ const user = await AuthUser.findById(userId, "roles").lean();
51
+ return (user?.roles as string[]) ?? [];
52
+ },
53
+ async setRoles(userId, roles) {
54
+ await AuthUser.findByIdAndUpdate(userId, { roles });
55
+ },
56
+ async addRole(userId, role) {
57
+ await AuthUser.findByIdAndUpdate(userId, { $addToSet: { roles: role } });
58
+ },
59
+ async removeRole(userId, role) {
60
+ await AuthUser.findByIdAndUpdate(userId, { $pull: { roles: role } });
61
+ },
62
+ async getUser(userId) {
63
+ const user = await AuthUser.findById(userId, "email providerIds emailVerified").lean();
64
+ if (!user) return null;
65
+ return {
66
+ email: user.email as string | undefined,
67
+ providerIds: user.providerIds as string[] | undefined,
68
+ emailVerified: (user.emailVerified as boolean | undefined) ?? false,
69
+ };
70
+ },
71
+ async unlinkProvider(userId, provider) {
72
+ const user = await AuthUser.findById(userId);
73
+ if (!user) throw new HttpError(404, "User not found");
74
+ user.providerIds = (user.providerIds as string[]).filter(
75
+ (id) => !id.startsWith(`${provider}:`)
76
+ );
77
+ await user.save();
78
+ },
79
+ async findByIdentifier(value) {
80
+ const user = await AuthUser.findOne({ email: value });
81
+ if (!user) return null;
82
+ return { id: String(user._id), passwordHash: user.password as string };
83
+ },
84
+ async setEmailVerified(userId, verified) {
85
+ await AuthUser.findByIdAndUpdate(userId, { emailVerified: verified });
86
+ },
87
+ async getEmailVerified(userId) {
88
+ const user = await AuthUser.findById(userId, "emailVerified").lean();
89
+ return (user?.emailVerified as boolean | undefined) ?? false;
90
+ },
91
+ };