diff --git a/apps/metamigo-api/package.json b/apps/metamigo-api/package.json index ab98717..4b7efc8 100644 --- a/apps/metamigo-api/package.json +++ b/apps/metamigo-api/package.json @@ -27,6 +27,7 @@ "fluent-ffmpeg": "^2.1.2", "graphile-migrate": "^1.4.1", "graphile-worker": "^0.13.0", + "hapi-auth-bearer-token": "^8.0.0", "hapi-auth-jwt2": "^10.4.0", "hapi-postgraphile": "^0.11.0", "hapi-swagger": "^16.0.1", diff --git a/apps/metamigo-api/src/app/plugins/auth-bearer.ts b/apps/metamigo-api/src/app/plugins/auth-bearer.ts new file mode 100644 index 0000000..82ae8d6 --- /dev/null +++ b/apps/metamigo-api/src/app/plugins/auth-bearer.ts @@ -0,0 +1,22 @@ +import type * as Hapi from "@hapi/hapi"; +import AuthBearer from "hapi-auth-bearer-token"; +import { IAppConfig } from "@digiresilience/metamigo-config"; +import { IMetamigoRepositories } from "@digiresilience/metamigo-common"; + +export const registerAuthBearer = async ( + server: Hapi.Server, + config: IAppConfig +): Promise => { + await server.register(AuthBearer); + + server.auth.strategy("session-id-bearer-token", "bearer-access-token", { + allowQueryToken: false, + validate: async (request: Hapi.Request, token: string, h: Hapi.ResponseToolkit) => { + const repos = request.db() as IMetamigoRepositories; + const session = await repos.sessions.findBy({ sessionToken: token }); + const isValid = !!session; + const credentials = { token }; + return { isValid, credentials }; + }, + }); +}; diff --git a/apps/metamigo-api/src/app/plugins/index.ts b/apps/metamigo-api/src/app/plugins/index.ts index 9d18587..ee53098 100644 --- a/apps/metamigo-api/src/app/plugins/index.ts +++ b/apps/metamigo-api/src/app/plugins/index.ts @@ -7,8 +7,8 @@ import type { IAppConfig } from "../../config"; import { dbInitOptions, IRepositories } from "@digiresilience/metamigo-db"; import { registerNextAuth } from "./hapi-nextauth.js"; import { registerSwagger } from "./swagger.js"; -import { registerNextAuthJwt } from "./nextauth-jwt.js"; import { registerCloudflareAccessJwt } from "./cloudflare-jwt.js"; +import { registerAuthBearer } from "./auth-bearer.js"; import pg from "pg-promise/typescript/pg-subset"; export const register = async ( @@ -34,6 +34,6 @@ export const register = async ( await registerNextAuth(server, config); await registerSwagger(server); - await registerNextAuthJwt(server, config); await registerCloudflareAccessJwt(server, config); + await registerAuthBearer(server, config); }; diff --git a/apps/metamigo-api/src/app/plugins/nextauth-jwt.ts b/apps/metamigo-api/src/app/plugins/nextauth-jwt.ts deleted file mode 100644 index b843b6d..0000000 --- a/apps/metamigo-api/src/app/plugins/nextauth-jwt.ts +++ /dev/null @@ -1,104 +0,0 @@ -import * as Hoek from "@hapi/hoek"; -import * as Hapi from "@hapi/hapi"; -import type { IAppConfig } from "../../config"; - -// hapi-auth-jwt2 expects the key to be a raw key -const jwkToHapiAuthJwt2 = (jwkString) => { - try { - const jwk = JSON.parse(jwkString); - return Buffer.from(jwk.k, "base64"); - } catch { - throw new Error( - "Failed to parse key for JWT verification. This is probably an application configuration error." - ); - } -}; - -const jwtDefaults = { - jwkeysB64: undefined, - validate: undefined, - strategyName: "nextauth-jwt", -}; - -const jwtRegister = async (server: Hapi.Server, options): Promise => { - server.dependency(["hapi-auth-jwt2"]); - const settings = Hoek.applyToDefaults(jwtDefaults, options); - const key = settings.jwkeysB64.map((k) => jwkToHapiAuthJwt2(k)); - - if (!settings.strategyName) { - throw new Error("Missing strategy name in nextauth-jwt pluginsettings!"); - } - - server.auth.strategy(settings.strategyName, "jwt", { - key, - cookieKey: false, - urlKey: false, - validate: settings.validate, - }); -}; - -export const registerNextAuthJwt = async ( - server: Hapi.Server, - config: IAppConfig -): Promise => { - if (config.nextAuth.signingKey) { - await server.register({ - plugin: { - name: "nextauth-jwt", - version: "0.0.2", - register: jwtRegister, - }, - options: { - jwkeysB64: [config.nextAuth.signingKey], - async validate(decoded, request: Hapi.Request) { - const { email, name, role } = decoded; - const user = await request.db().users.findBy({ email }); - if (!config.isProd) { - server.logger.info( - { - email, - name, - role, - }, - "nextauth-jwt authorizing request" - ); - // server.logger.info({ user }, "nextauth-jwt user result"); - } - - return { - isValid: Boolean(user && user.isActive), - // this credentials object is made available in every request - // at `request.auth.credentials` - credentials: { email, name, role }, - }; - }, - }, - }); - } else if (config.isProd) { - throw new Error("Missing nextauth.signingKey configuration value."); - } else { - server.log( - ["warn"], - "Missing nextauth.signingKey configuration value. Authentication of nextauth endpoints disabled!" - ); - } -}; - -// @hapi/jwt expects the key in its own format -/* UNUSED -const _jwkToHapiJwt = (jwkString) => { - try { - const jwk = JSON.parse(jwkString); - const rawKey = Buffer.from(jwk.k, "base64"); - return { - key: rawKey, - algorithms: [jwk.alg], - kid: jwk.kid, - }; - } catch { - throw new Error( - "Failed to parse key for JWT verification. This is probably an application configuration error." - ); - } -}; -*/ diff --git a/apps/metamigo-api/src/app/routes/helpers/index.ts b/apps/metamigo-api/src/app/routes/helpers/index.ts index 6e1f395..c3c3210 100644 --- a/apps/metamigo-api/src/app/routes/helpers/index.ts +++ b/apps/metamigo-api/src/app/routes/helpers/index.ts @@ -4,7 +4,7 @@ import Toys from "@hapipal/toys"; export const withDefaults = Toys.withRouteDefaults({ options: { cors: true, - auth: "nextauth-jwt", + auth: "session-id-bearer-token", validate: { failAction: Metamigo.validatingFailAction, }, diff --git a/apps/metamigo-api/src/server/manifest.ts b/apps/metamigo-api/src/server/manifest.ts index 66c9859..f8c55ea 100644 --- a/apps/metamigo-api/src/server/manifest.ts +++ b/apps/metamigo-api/src/server/manifest.ts @@ -50,11 +50,10 @@ const build = async (config: IAppConfig): Promise => { route: { path: "/graphql", options: { - auth: false, - // auth: { - // strategies: ["nextauth-jwt"], - // mode: "optional", - // }, + auth: { + strategies: ["session-id-bearer-token"], + mode: "optional", + }, }, }, pgConfig: config.postgraphile.authConnection, diff --git a/package-lock.json b/package-lock.json index fde9d86..3028231 100644 --- a/package-lock.json +++ b/package-lock.json @@ -137,6 +137,7 @@ "fluent-ffmpeg": "^2.1.2", "graphile-migrate": "^1.4.1", "graphile-worker": "^0.13.0", + "hapi-auth-bearer-token": "^8.0.0", "hapi-auth-jwt2": "^10.4.0", "hapi-postgraphile": "^0.11.0", "hapi-swagger": "^16.0.1", @@ -11597,6 +11598,27 @@ "topo": "3.x.x" } }, + "node_modules/hapi-auth-bearer-token": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hapi-auth-bearer-token/-/hapi-auth-bearer-token-8.0.0.tgz", + "integrity": "sha512-1YeUlwhhky8tnNx9bOQPB/TvsEwbgcYwAZ6DAvHlK+tHRiMbXU+2HNE8qpRia+oj21W2K/omaxyZIB5dOzTPoA==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@hapi/boom": ">=7.x.x", + "@hapi/hapi": ">=19.x.x", + "joi": ">=17.x.x" + } + }, + "node_modules/hapi-auth-bearer-token/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, "node_modules/hapi-auth-jwt2": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/hapi-auth-jwt2/-/hapi-auth-jwt2-10.4.0.tgz",