diff --git a/bun.lock b/bun.lock index 473143e..6500d50 100644 --- a/bun.lock +++ b/bun.lock @@ -10,12 +10,14 @@ "@duxcore/eventra": "^1.1.0", "@prisma/adapter-pg": "^7.3.0", "@prisma/client": "^7.3.0", + "@socket.io/bun-engine": "^0.1.0", "cors": "^2.8.6", "cuid": "^3.0.0", "hono": "^4.11.5", "jsonwebtoken": "^9.0.3", "keypair": "^1.0.4", "prisma": "^7.3.0", + "socket.io": "^4.8.3", "zod": "^4.3.6", "zon": "^1.0.3", }, @@ -81,10 +83,16 @@ "@prisma/studio-core": ["@prisma/studio-core@0.13.1", "", { "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg=="], + "@socket.io/bun-engine": ["@socket.io/bun-engine@0.1.0", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-B1z6GuAxZlfvjgaa3BHZBOfqHJNfnpebTw15p+Un1HuBL4YM7wUxO9sJa7K4NDTe7XbUBeqLIwTDA5tOOjffog=="], + + "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], + "@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="], + "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="], "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], @@ -93,8 +101,12 @@ "@types/react": ["@types/react@19.2.9", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA=="], + "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], + "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], @@ -111,6 +123,8 @@ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -119,6 +133,8 @@ "cuid": ["cuid@3.0.0", "", {}, "sha512-WZYYkHdIDnaxdeP8Misq3Lah5vFjJwGuItJuV+tvMafosMzw0nF297T7mrm8IOWiPJkV6gc7sa8pzx27+w25Zg=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], @@ -135,6 +151,10 @@ "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "engine.io": ["engine.io@6.6.5", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3" } }, "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A=="], + + "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="], + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], @@ -195,12 +215,18 @@ "lru.min": ["lru.min@1.1.3", "", {}, "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q=="], + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "mysql2": ["mysql2@3.15.3", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg=="], "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="], + "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], "nypm": ["nypm@0.6.4", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw=="], @@ -279,6 +305,12 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "socket.io": ["socket.io@4.8.3", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A=="], + + "socket.io-adapter": ["socket.io-adapter@2.5.6", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.18.3" } }, "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ=="], + + "socket.io-parser": ["socket.io-parser@4.2.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], @@ -299,6 +331,8 @@ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], "zeptomatch": ["zeptomatch@2.1.0", "", { "dependencies": { "grammex": "^3.1.11", "graphmatch": "^1.1.0" } }, "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA=="], diff --git a/package.json b/package.json index 7e727de..dfb0424 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,14 @@ "@duxcore/eventra": "^1.1.0", "@prisma/adapter-pg": "^7.3.0", "@prisma/client": "^7.3.0", + "@socket.io/bun-engine": "^0.1.0", "cors": "^2.8.6", "cuid": "^3.0.0", "hono": "^4.11.5", "jsonwebtoken": "^9.0.3", "keypair": "^1.0.4", "prisma": "^7.3.0", + "socket.io": "^4.8.3", "zod": "^4.3.6", "zon": "^1.0.3" } diff --git a/src/api/auth/redirect.ts b/src/api/auth/redirect.ts index bebac0b..0dd539b 100644 --- a/src/api/auth/redirect.ts +++ b/src/api/auth/redirect.ts @@ -4,12 +4,10 @@ import * as msal from "@azure/msal-node"; import { msalClient } from "../../constants"; import { users } from "../../managers/users"; -/* /v1/authRedirect */ +/* /v1/auth/redirect */ export default createRoute("get", ["/"], async (c) => { c.status(200); - console.log("Query", c.req.query()); - const tokenRequest: msal.AuthorizationCodeRequest = { code: c.req.query().code as string, scopes: ["user.read"], @@ -20,17 +18,10 @@ export default createRoute("get", ["/"], async (c) => { await users.authenticate(authResult); - // This closes the window because duh - return c.html(` - - `); - - /* return c.json({ - status: 200, - message: "Auth Redirect Endpoint", - data: authResult, - successful: true, - }); */ + return c.json({ + status: 200, + message: "Auth Redirect Endpoint", + data: authResult, + successful: true, + }); }); diff --git a/src/api/auth/refresh.ts b/src/api/auth/refresh.ts new file mode 100644 index 0000000..6a32f1a --- /dev/null +++ b/src/api/auth/refresh.ts @@ -0,0 +1,26 @@ +import { Hono } from "hono/tiny"; +import { createRoute } from "../../modules/api-utils/createRoute"; +import { sessions } from "../../managers/sessions"; + +/* /v1/auth/refresh */ +export default createRoute("post", ["/"], async (c) => { + c.status(201); + + const refreshToken = c.req.header("x-refresh-token") || ""; + + const session = await sessions.fetch({ + refreshToken: refreshToken, + }); + + const newAccessToken = await session.refresh(refreshToken); + + return c.json({ + status: 201, + message: "Token refreshed successfully!", + data: { + accessToken: newAccessToken, + refreshToken, + }, + successful: true, + }); +}); diff --git a/src/api/middleware/authorization.ts b/src/api/middleware/authorization.ts new file mode 100644 index 0000000..22a9153 --- /dev/null +++ b/src/api/middleware/authorization.ts @@ -0,0 +1,80 @@ +import { Context, Env, MiddlewareHandler } from "hono"; +import AuthorizationError from "../../Errors/AuthorizationError"; +import { sessions } from "../../managers/sessions"; +import { Variables } from "../../types/HonoTypes"; +import GenericError from "../../Errors/GenericError"; +import { events } from "../../modules/globalEvents"; + +/** + * Authorization Middleware + * + * This middleware will do all of the authorization for all the routes that may need authorization. + * It will check which auth type is being used and pull the correct credentials from said auth type and + * supply them as a variable to the route. If there is an error thrown at any point in this middleware, it + * will hault and will not proceed to the route handler. + * + * Eventually this method will analyze roles and permissions and supply those as objects to the route handler. + * + * ## Auth Types + * - Bearer: Access Token for user authentication + * - Key: API Key + * + * @param ctx - Hono Context Object + * @param next - Move onto the handler + */ +export const authMiddleware = (permParams?: { + permissions?: string[]; + scopes?: string[]; + forbiddenAuthTypes?: string[]; +}): MiddlewareHandler<{ + Variables: Variables; +}> => { + return async (ctx, next) => { + const authorization = ctx.req.header()["authorization"]; + if (!authorization) + throw new AuthorizationError("Missing 'authorization' header."); + + const components = authorization.match( + /^(Bearer|Key)\s([a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+)$/, + ); + if (!components) + throw new AuthorizationError( + "Invalid or malformed authorization header...", + ); + + const authType: string = components[1] ?? ""; + const authValue: string = components[2] ?? ""; + + if (permParams?.forbiddenAuthTypes?.includes(authType)) + throw new GenericError({ + name: "NonpermittedAuthType", + message: + "The authorization method you are using is not permitted for this API request.", + cause: `Type '${authType}' is not permitted.`, + status: 403, + }); + + if (authType) { + const session = await sessions.fetch({ accessToken: authValue }); + const user = await session.fetchUser(); + ctx.set("user", user); + + if ( + permParams?.permissions && + permParams?.permissions.length > 0 && + ( + await Promise.all( + permParams?.permissions.map((p) => user.hasPermission(p)), + ) + ).includes(false) + ) + throw new GenericError({ + name: "InsufficentPermission", + message: "You do not have sufficent permissions to do this.", + status: 403, + }); + } + + await next(); + }; +}; diff --git a/src/constants.ts b/src/constants.ts index 73149c4..cd8a3f0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,6 +2,8 @@ import { readFileSync } from "fs"; import { PrismaPg } from "@prisma/adapter-pg"; import { Prisma, PrismaClient } from "../generated/prisma/client"; import * as msal from "@azure/msal-node"; +import { Server } from "socket.io"; +import { Server as Engine } from "@socket.io/bun-engine"; const connectionString = `${process.env.DATABASE_URL}`; const adapter = new PrismaPg({ connectionString }); @@ -44,3 +46,11 @@ const msalConfig: msal.Configuration = { // MSAL Client Instance export const msalClient = new msal.ConfidentialClientApplication(msalConfig); + +// Socket.io + +const io = new Server(); +const engine = new Engine(); + +io.bind(engine); +export { io, engine }; diff --git a/src/index.ts b/src/index.ts index d40f9ae..e05c0f5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,15 @@ import app from "./api/server"; -import { PORT } from "./constants"; +import { engine, PORT } from "./constants"; Bun.serve({ - port: PORT, - fetch: app.fetch -}); \ No newline at end of file + port: PORT, + fetch: (req, server) => { + const url = new URL(req.url); + + if (url.pathname.startsWith("/socket.io/")) { + return engine.handleRequest(req, server as any); + } + + return app.fetch(req, server); + }, +}); diff --git a/src/types/HonoTypes.ts b/src/types/HonoTypes.ts index 8cc1ec4..57c865c 100644 --- a/src/types/HonoTypes.ts +++ b/src/types/HonoTypes.ts @@ -1,3 +1,5 @@ +import UserController from "../controllers/UserController"; + export type Variables = { - foo: "bar" + user: UserController; };