Add all repos

This commit is contained in:
Darren Clarke 2023-02-13 12:41:30 +00:00
parent faa12c60bc
commit 8a91c9b89b
369 changed files with 29047 additions and 28 deletions

View file

@ -0,0 +1,114 @@
import * as Boom from "@hapi/boom";
import * as Hoek from "@hapi/hoek";
import * as Hapi from "@hapi/hapi";
import { promisify } from "util";
import jwt from "jsonwebtoken";
import jwksClient, { hapiJwt2KeyAsync } from "jwks-rsa";
import type { IAppConfig } from "../../config";
const CF_JWT_HEADER_NAME = "cf-access-jwt-assertion";
const CF_JWT_ALGOS = ["RS256"];
const verifyToken = (settings: any) => {
const { audience, issuer } = settings;
const client = jwksClient({
jwksUri: `${issuer}/cdn-cgi/access/certs`,
});
return async (token: any) => {
const getKey = (header: any, callback: any) => {
client.getSigningKey(header.kid, (err, key) => {
if (err)
throw Boom.serverUnavailable(
"failed to fetch cloudflare access jwks"
);
callback(undefined, key?.getPublicKey());
});
};
const opts = {
algorithms: CF_JWT_ALGOS,
audience,
issuer,
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (promisify(jwt.verify) as any)(token, getKey, opts);
};
};
const handleCfJwt = (verify: any) => async (
request: Hapi.Request,
h: Hapi.ResponseToolkit
) => {
const token = request.headers[CF_JWT_HEADER_NAME];
if (token) {
try {
await verify(token);
} catch (error) {
console.error(error);
return Boom.unauthorized("invalid cloudflare access token");
}
}
return h.continue;
};
const defaultOpts = {
issuer: undefined,
audience: undefined,
strategyName: "clouflareaccess",
validate: undefined,
};
const cfJwtRegister = async (server: Hapi.Server, options: any): Promise<void> => {
server.dependency(["hapi-auth-jwt2"]);
const settings = Hoek.applyToDefaults(defaultOpts, options);
const verify = verifyToken(settings);
const { validate, strategyName, audience, issuer } = settings;
server.ext("onPreAuth", handleCfJwt(verify));
server.auth.strategy(strategyName!, "jwt", {
key: hapiJwt2KeyAsync({
jwksUri: `${issuer}/cdn-cgi/access/certs`,
}),
cookieKey: false,
urlKey: false,
headerKey: CF_JWT_HEADER_NAME,
validate,
verifyOptions: {
audience,
issuer,
algorithms: ["RS256"],
},
});
};
export const registerCloudflareAccessJwt = async (
server: Hapi.Server,
config: IAppConfig
): Promise<void> => {
const { audience, domain } = config.cfaccess;
// only enable this plugin if cloudflare access config is configured
if (audience && domain) {
server.log(["auth"], "cloudflare access authorization enabled");
await server.register({
plugin: {
name: "cloudflare-jwt",
version: "0.0.1",
register: cfJwtRegister,
},
options: {
issuer: `https://${domain}`,
audience,
validate: (decoded: any, _request: any) => {
const { email, name } = decoded;
return {
isValid: true,
credentials: { user: { email, name } },
};
},
},
});
}
};

View file

@ -0,0 +1,26 @@
import type * as Hapi from "@hapi/hapi";
import NextAuthPlugin, { AdapterFactory } from "@digiresilience/hapi-nextauth";
import { NextAuthAdapter } from "common";
import type { SavedUser, UnsavedUser, SavedSession } from "common";
import { IAppConfig } from "config";
export const registerNextAuth = async (
server: Hapi.Server,
config: IAppConfig
): Promise<void> => {
// I'm not sure why I need to be so explicit with the generic types here
// I thought ts could figure out the generics based on the concrete params, but apparently not
const nextAuthAdapterFactory: AdapterFactory<
SavedUser,
UnsavedUser,
SavedSession
> = (request: Hapi.Request) => new NextAuthAdapter(request.db());
await server.register({
plugin: NextAuthPlugin,
options: {
nextAuthAdapterFactory,
sharedSecret: config.nextAuth.secret,
},
});
};

View file

@ -0,0 +1,32 @@
import type * as Hapi from "@hapi/hapi";
import Schmervice from "@hapipal/schmervice";
import PgPromisePlugin from "@digiresilience/hapi-pg-promise";
import type { IAppConfig } from "../../config";
import { dbInitOptions } from "db";
import { registerNextAuth } from "./hapi-nextauth";
import { registerSwagger } from "./swagger";
import { registerNextAuthJwt } from "./nextauth-jwt";
import { registerCloudflareAccessJwt } from "./cloudflare-jwt";
export const register = async (
server: Hapi.Server,
config: IAppConfig
): Promise<void> => {
await server.register(Schmervice);
await server.register({
plugin: PgPromisePlugin,
options: {
// the only required parameter is the connection string
connection: config.db.connection,
// ... and the pg-promise initialization options
pgpInit: dbInitOptions(config),
},
});
await registerNextAuth(server, config);
await registerSwagger(server);
await registerNextAuthJwt(server, config);
await registerCloudflareAccessJwt(server, config);
};

View file

@ -0,0 +1,100 @@
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<void> => {
server.dependency(["hapi-auth-jwt2"]);
const settings: any = Hoek.applyToDefaults(jwtDefaults, options);
const key = settings.jwkeysB64.map((k) => jwkToHapiAuthJwt2(k));
server.auth.strategy(settings.strategyName!, "jwt", {
key,
cookieKey: false,
urlKey: false,
validate: settings.validate,
});
};
export const registerNextAuthJwt = async (
server: Hapi.Server,
config: IAppConfig
): Promise<void> => {
if (config.nextAuth.signingKey) {
await server.register({
plugin: {
name: "nextauth-jwt",
version: "0.0.2",
register: jwtRegister,
},
options: {
jwkeysB64: [config.nextAuth.signingKey],
validate: async (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."
);
}
};
*/

View file

@ -0,0 +1,32 @@
import * as Inert from "@hapi/inert";
import * as Vision from "@hapi/vision";
import type * as Hapi from "@hapi/hapi";
import * as HapiSwagger from "hapi-swagger";
export const registerSwagger = async (server: Hapi.Server): Promise<void> => {
const swaggerOptions: HapiSwagger.RegisterOptions = {
info: {
title: "Metamigo API Docs",
description: "part of CDR Link",
version: "0.1",
},
// group sets of endpoints by tag
tags: [
{
name: "users",
description: "API for Users",
},
],
documentationRouteTags: ["swagger"],
documentationPath: "/api-docs",
};
await server.register([
{ plugin: Inert },
{ plugin: Vision },
{
plugin: HapiSwagger,
options: swaggerOptions,
},
]);
};