hapi-nextauth: update packages, fix linter and typescript

This commit is contained in:
Abel Luck 2023-03-13 09:34:36 +00:00
parent 7aa1ec74eb
commit fbe33c5c7b
6 changed files with 595 additions and 3870 deletions

4209
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
.PHONY: fmt test yarn
.npmrc:
echo '@guardianproject-ops:registry=https://gitlab.com/api/v4/packages/npm/' > .npmrc
echo '//gitlab.com/api/v4/packages/npm/:_authToken=${CI_JOB_TOKEN}' >> .npmrc
echo '//gitlab.com/api/v4/projects/:_authToken=${CI_JOB_TOKEN}' >> .npmrc
echo '//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}' >> .npmrc
yarn:
yarn
test: yarn
mkdir -p coverage
yarn lint
yarn test
publish: test .npmrc
npm publish

View file

@ -8,28 +8,26 @@
"private": false, "private": false,
"devDependencies": { "devDependencies": {
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
"@hapi/basic": "^6.0.0", "@hapi/basic": "^7.0.1",
"tsconfig-link": "*", "tsconfig-link": "*",
"eslint-config-link": "*", "eslint-config-link": "*",
"jest-config-link": "*" "jest-config-link": "*",
"babel-preset-link": "*"
}, },
"dependencies": { "dependencies": {
"@hapi/hapi": "^20.2.0", "@hapi/hapi": "^21.3.0",
"@hapi/hoek": "^9.2.1", "@hapi/hoek": "^11.0.1",
"joi": "^17.4.2", "joi": "^17.6.1",
"@hapipal/toys": "^3.1.0",
"next-auth": "3.29.0" "next-auth": "3.29.0"
}, },
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json", "build": "tsc -p tsconfig.json",
"fix:lint": "eslint src --ext .ts --fix", "fix:lint": "eslint src --ext .ts --fix",
"fix:prettier": "prettier \"src/**/*.ts\" --write", "fmt": "prettier \"src/**/*.ts\" --write",
"test": "yarn test:jest && yarn test:lint && yarn test:prettier", "test": "jest --coverage --forceExit --detectOpenHandles --reporters=default --reporters=jest-junit",
"test:lint": "eslint src --ext .ts", "lint": "eslint src --ext .ts",
"test:prettier": "prettier \"src/**/*.ts\" --list-different", "lint-fmt": "prettier \"src/**/*.ts\" --list-different",
"test:jest": "jest --coverage --forceExit --detectOpenHandles --reporters=default --reporters=jest-junit", "doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
"doc": "yarn run doc:html",
"doc:html": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
"watch:build": "tsc -p tsconfig.json -w" "watch:build": "tsc -p tsconfig.json -w"
} }
} }

View file

@ -62,11 +62,8 @@ const register = async <TUser, TProfile, TSession>(
server: Hapi.Server, server: Hapi.Server,
pluginOpts?: NextAuthPluginOptions<TUser, TProfile, TSession> pluginOpts?: NextAuthPluginOptions<TUser, TProfile, TSession>
): Promise<void> => { ): Promise<void> => {
const options: NextAuthPluginOptions< const options: NextAuthPluginOptions<TUser, TProfile, TSession> =
TUser, Hoek.applyToDefaults(
TProfile,
TSession
> = Hoek.applyToDefaults(
// a little type gymnastics here to workaround poor typing // a little type gymnastics here to workaround poor typing
defaultOptions as unknown, defaultOptions as unknown,
pluginOpts pluginOpts

View file

@ -1,138 +1,155 @@
/* eslint-disable unicorn/no-null */ /* eslint-disable unicorn/no-null */
import Toys from "@hapipal/toys";
import * as Joi from "joi"; import * as Joi from "joi";
import * as Hapi from "@hapi/hapi"; import * as Hapi from "@hapi/hapi";
import { NextAuthPluginOptions } from "./types"; import { NextAuthPluginOptions } from "./types";
import { ResponseToolkit, ResponseObject } from "@hapi/hapi";
export interface LinkAccountPayload {
userId: string;
providerType: string;
providerId: string;
providerAccountId: string;
refreshToken: string;
accessToken: string;
accessTokenExpires?: null;
}
export const register = async <TUser, TProfile, TSession>( export const register = async <TUser, TProfile, TSession>(
server: Hapi.Server, server: Hapi.Server,
opts: NextAuthPluginOptions<TUser, TProfile, TSession>, opts: NextAuthPluginOptions<TUser, TProfile, TSession>,
auth?: string auth?: string
): Promise<void> => { ): Promise<void> => {
const withDefaults = Toys.withRouteDefaults({ const { tags, basePath, validators } = opts;
const { session, user, userId, profile } = validators;
server.route([
{
method: "POST",
path: `${basePath}/createUser`,
options: { options: {
auth, auth,
tags: opts.tags, tags,
},
});
server.route([
withDefaults({
method: "POST",
path: `${opts.basePath}/createUser`,
options: {
validate: { validate: {
payload: opts.validators.profile, payload: profile,
}, },
handler: async ( async handler(
request: Hapi.Request, request: Hapi.Request,
h: Hapi.Toolkit h: ResponseToolkit
): Promise<TUser> => { ): Promise<Hapi.ResponseObject> {
const payload: TProfile = request.payload as TProfile; const payload: TProfile = request.payload as TProfile;
const r = await opts const r = await opts
.nextAuthAdapterFactory(request) .nextAuthAdapterFactory(request)
.createUser(payload); .createUser(payload);
return h.response(r); return h.response(r as object);
}, },
description: "Create a user from a profile", description: "Create a user from a profile",
}, },
}), },
withDefaults({ {
method: "GET", method: "GET",
path: `${opts.basePath}/getUser/{userId}`, path: `${basePath}/getUser/{userId}`,
options: { options: {
auth,
tags,
validate: { validate: {
params: { params: {
userId: opts.validators.userId, userId,
}, },
}, },
handler: async ( async handler(
request: Hapi.Request, request: Hapi.Request,
h: Hapi.Toolkit h: ResponseToolkit
): Promise<TUser | null> => { ): Promise<ResponseObject> {
const id = request.params.userId; const id = request.params.userId;
const r = await opts.nextAuthAdapterFactory(request).getUser(id); const r = await opts.nextAuthAdapterFactory(request).getUser(id);
if (!r) return h.response().code(404); if (!r) return h.response().code(404);
return h.response(r); return h.response(r as object);
}, },
description: "Get a user by id", description: "Get a user by id",
}, },
}), },
withDefaults({ {
method: "GET", method: "GET",
path: `${opts.basePath}/getUserByEmail/{userEmail}`, path: `${basePath}/getUserByEmail/{userEmail}`,
options: { options: {
auth,
tags,
validate: { validate: {
params: { params: {
userEmail: Joi.string().email(), userEmail: Joi.string().email(),
}, },
}, },
handler: async ( async handler(
request: Hapi.Request, request: Hapi.Request,
h: Hapi.Toolkit h: ResponseToolkit
): Promise<TUser | null> => { ): Promise<ResponseObject> {
const email = request.params.userEmail; const email = request.params.userEmail;
const r = await opts const r = await opts
.nextAuthAdapterFactory(request) .nextAuthAdapterFactory(request)
.getUserByEmail(email); .getUserByEmail(email);
if (!r) return h.response().code(404); if (!r) return h.response().code(404);
return h.response(r); return h.response(r as object);
}, },
description: "Get a user by email", description: "Get a user by email",
}, },
}), },
withDefaults({ {
method: "GET", method: "GET",
path: `${opts.basePath}/getUserByProviderAccountId/{providerId}/{providerAccountId}`, path: `${basePath}/getUserByProviderAccountId/{providerId}/{providerAccountId}`,
options: { options: {
auth,
tags,
validate: { validate: {
params: { params: {
providerId: Joi.string(), providerId: Joi.string(),
providerAccountId: Joi.string(), providerAccountId: Joi.string(),
}, },
}, },
handler: async ( async handler(
request: Hapi.Request, request: Hapi.Request,
h: Hapi.Toolkit h: ResponseToolkit
): Promise<TUser | null> => { ): Promise<ResponseObject> {
const { providerId, providerAccountId } = request.params; const { providerId, providerAccountId } = request.params;
const r = await opts const r = await opts
.nextAuthAdapterFactory(request) .nextAuthAdapterFactory(request)
.getUserByProviderAccountId(providerId, providerAccountId); .getUserByProviderAccountId(providerId, providerAccountId);
if (!r) return h.response().code(404); if (!r) return h.response().code(404);
return h.response(r); return h.response(r as object);
}, },
description: "Get a user by provider id and provider account id", description: "Get a user by provider id and provider account id",
}, },
}),
withDefaults({
method: "PUT",
path: `${opts.basePath}/updateUser`,
options: {
validate: {
payload: opts.validators.user,
}, },
handler: async ( {
method: "PUT",
path: `${basePath}/updateUser`,
options: {
auth,
tags,
validate: {
payload: user,
},
async handler(
request: Hapi.Request, request: Hapi.Request,
h: Hapi.Toolkit h: ResponseToolkit
): Promise<TUser> => { ): Promise<ResponseObject> {
const payload: TUser = request.payload as TUser; const payload: TUser = request.payload as TUser;
const r = await opts const r = await opts
.nextAuthAdapterFactory(request) .nextAuthAdapterFactory(request)
.updateUser(payload); .updateUser(payload);
if (!r) return h.response().code(404); if (!r) return h.response().code(404);
return h.response(r); return h.response(r as object);
}, },
description: "Update a user's data", description: "Update a user's data",
}, },
}), },
withDefaults({ {
method: "PUT", method: "PUT",
path: `${opts.basePath}/linkAccount`, path: `${basePath}/linkAccount`,
options: { options: {
auth,
tags,
validate: { validate: {
payload: Joi.object({ payload: Joi.object({
userId: opts.validators.userId, userId,
providerId: Joi.string(), providerId: Joi.string(),
providerType: Joi.string(), providerType: Joi.string(),
providerAccountId: Joi.string(), providerAccountId: Joi.string(),
@ -141,10 +158,10 @@ export const register = async <TUser, TProfile, TSession>(
accessTokenExpires: Joi.number().optional().allow(null), accessTokenExpires: Joi.number().optional().allow(null),
}).options({ presence: "required" }), }).options({ presence: "required" }),
}, },
handler: async ( async handler(
request: Hapi.Request, request: Hapi.Request,
h: Hapi.Toolkit h: ResponseToolkit
): Promise<void> => { ): Promise<ResponseObject> {
const { const {
userId, userId,
providerId, providerId,
@ -153,7 +170,7 @@ export const register = async <TUser, TProfile, TSession>(
refreshToken, refreshToken,
accessToken, accessToken,
accessTokenExpires, accessTokenExpires,
} = request.payload; } = request.payload as LinkAccountPayload;
await opts await opts
.nextAuthAdapterFactory(request) .nextAuthAdapterFactory(request)
.linkAccount( .linkAccount(
@ -169,94 +186,104 @@ export const register = async <TUser, TProfile, TSession>(
}, },
description: "Link a provider account with a user", description: "Link a provider account with a user",
}, },
}),
withDefaults({
method: "POST",
path: `${opts.basePath}/createSession`,
options: {
validate: {
payload: opts.validators.user,
}, },
handler: async ( {
method: "POST",
path: `${basePath}/createSession`,
options: {
auth,
tags,
validate: {
payload: user,
},
async handler(
request: Hapi.Request, request: Hapi.Request,
h: Hapi.Toolkit h: ResponseToolkit
): Promise<TSession> => { ): Promise<ResponseObject> {
const payload: TUser = request.payload as TUser; const payload: TUser = request.payload as TUser;
const r = await opts const r = await opts
.nextAuthAdapterFactory(request) .nextAuthAdapterFactory(request)
.createSession(payload); .createSession(payload);
return h.response(r); return h.response(r as object);
}, },
description: "Create a new session for a user", description: "Create a new session for a user",
}, },
}), },
withDefaults({ {
method: "GET", method: "GET",
path: `${opts.basePath}/getSession/{sessionToken}`, path: `${basePath}/getSession/{sessionToken}`,
options: { options: {
auth,
tags,
validate: { validate: {
params: { params: {
sessionToken: Joi.string(), sessionToken: Joi.string(),
}, },
}, },
handler: async ( async handler(
request: Hapi.Request, request: Hapi.Request,
h: Hapi.Toolkit h: ResponseToolkit
): Promise<TSession | null> => { ): Promise<ResponseObject> {
const token = request.params.sessionToken; const token = request.params.sessionToken;
const r = await opts const r = await opts
.nextAuthAdapterFactory(request) .nextAuthAdapterFactory(request)
.getSession(token); .getSession(token);
if (!r) return h.response().code(404); if (!r) return h.response().code(404);
return h.response(r); return h.response(r as object);
}, },
description: "Get a session by its token", description: "Get a session by its token",
}, },
}),
withDefaults({
method: "PUT",
path: `${opts.basePath}/updateSession`,
options: {
validate: {
payload: opts.validators.session,
}, },
handler: async ( {
method: "PUT",
path: `${basePath}/updateSession`,
options: {
auth,
tags,
validate: {
payload: session,
},
async handler(
request: Hapi.Request, request: Hapi.Request,
h: Hapi.Toolkit h: ResponseToolkit
): Promise<TSession> => { ): Promise<ResponseObject> {
const inputPayload = request.payload as any;
const { expires } = inputPayload;
const payload = { const payload = {
...request.payload, ...inputPayload,
expires: new Date(request.payload.expires), expires: new Date(expires),
}; };
const force = Boolean(request.query.force); const force = Boolean(request.query.force);
const r = await opts const r = await opts
.nextAuthAdapterFactory(request) .nextAuthAdapterFactory(request)
.updateSession(payload, force); .updateSession(payload, force);
if (!r) return h.response().code(204); if (!r) return h.response().code(204);
return h.response(r); return h.response(r as object);
}, },
description: "Update a session for a user", description: "Update a session for a user",
}, },
}), },
withDefaults({ {
method: "DELETE", method: "DELETE",
path: `${opts.basePath}/deleteSession/{sessionToken}`, path: `${basePath}/deleteSession/{sessionToken}`,
options: { options: {
auth,
tags,
validate: { validate: {
params: { params: {
sessionToken: Joi.string(), sessionToken: Joi.string(),
}, },
}, },
handler: async ( async handler(
request: Hapi.Request, request: Hapi.Request,
h: Hapi.Toolkit h: ResponseToolkit
): Promise<void> => { ): Promise<ResponseObject> {
const token = request.params.sessionToken; const token = request.params.sessionToken;
await opts.nextAuthAdapterFactory(request).deleteSession(token); await opts.nextAuthAdapterFactory(request).deleteSession(token);
return h.response().code(204); return h.response().code(204);
}, },
description: "Delete a user's session", description: "Delete a user's session",
}, },
}), },
]); ]);
}; };

View file

@ -1,5 +1,5 @@
{ {
"extends": "tsconfig", "extends": "tsconfig-link",
"compilerOptions": { "compilerOptions": {
"incremental": true, "incremental": true,
"outDir": "build/main", "outDir": "build/main",