Bring in hapi-nextauth and dev support libs
This commit is contained in:
parent
feab3f90d7
commit
7aa1ec74eb
45 changed files with 14633 additions and 1141 deletions
363
packages/hapi-nextauth/src/index.spec.ts
Normal file
363
packages/hapi-nextauth/src/index.spec.ts
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
import * as Hapi from "@hapi/hapi";
|
||||
import HapiBasic from "@hapi/basic";
|
||||
import NextAuthPlugin from ".";
|
||||
|
||||
describe("plugin option validation", () => {
|
||||
let server;
|
||||
beforeEach(async () => {
|
||||
server = new Hapi.Server();
|
||||
});
|
||||
|
||||
it("should throw when options contain no next auth adapter", async () => {
|
||||
expect(server.register(NextAuthPlugin)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("plugin runtime", () => {
|
||||
let server;
|
||||
const user = { id: "abc", email: "abc@abc.abc" };
|
||||
const session = {
|
||||
id: "zyx",
|
||||
userId: "abc",
|
||||
expires: Date.now(),
|
||||
sessionToken: "foo",
|
||||
accessToken: "bar",
|
||||
};
|
||||
|
||||
const start = async (mock) => {
|
||||
await server.register(HapiBasic);
|
||||
await server.register({
|
||||
plugin: NextAuthPlugin,
|
||||
options: {
|
||||
nextAuthAdapterFactory: () => mock,
|
||||
},
|
||||
});
|
||||
|
||||
await server.start();
|
||||
return server;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
server = new Hapi.Server({ port: 0 });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it("createUser", async () => {
|
||||
const createUser = jest.fn(() => user);
|
||||
const profile = { email: "abc@abc.abc" };
|
||||
|
||||
await start({ createUser });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "post",
|
||||
url: "/api/nextauth/createUser",
|
||||
payload: profile,
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(createUser).toHaveBeenCalledWith(profile);
|
||||
expect(result).toStrictEqual(user);
|
||||
});
|
||||
|
||||
it("createUser fails with invalid payload", async () => {
|
||||
const createUser = jest.fn(() => user);
|
||||
const profile = { name: "name" };
|
||||
|
||||
await start({ createUser });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "post",
|
||||
url: "/api/nextauth/createUser",
|
||||
payload: profile,
|
||||
});
|
||||
expect(statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it("getUser", async () => {
|
||||
const getUser = jest.fn(() => user);
|
||||
|
||||
await start({ getUser });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUser/abc",
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(getUser).toHaveBeenCalledWith("abc");
|
||||
expect(result).toBe(user);
|
||||
});
|
||||
|
||||
it("getUserByEmail", async () => {
|
||||
const getUserByEmail = jest.fn(() => user);
|
||||
|
||||
await start({ getUserByEmail });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUserByEmail/abc@abc.abc",
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(getUserByEmail).toHaveBeenCalledWith("abc@abc.abc");
|
||||
expect(result).toBe(user);
|
||||
});
|
||||
|
||||
it("getUserByEmail fails with invalid email", async () => {
|
||||
const getUserByEmail = jest.fn(() => user);
|
||||
|
||||
await start({ getUserByEmail });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUserByEmail/notanemail@foo",
|
||||
});
|
||||
expect(statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it("getUserByProviderAccountId", async () => {
|
||||
const getUserByProviderAccountId = jest.fn(() => user);
|
||||
|
||||
await start({ getUserByProviderAccountId });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUserByProviderAccountId/foo/bar",
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(getUserByProviderAccountId).toHaveBeenCalledWith("foo", "bar");
|
||||
expect(result).toBe(user);
|
||||
});
|
||||
|
||||
it("updateUser", async () => {
|
||||
const updateUser = jest.fn(() => user);
|
||||
|
||||
await start({ updateUser });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "put",
|
||||
url: "/api/nextauth/updateUser",
|
||||
payload: user,
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(updateUser).toHaveBeenCalledWith(user);
|
||||
expect(result).toStrictEqual(user);
|
||||
});
|
||||
|
||||
it("updateUser fails with invalid payload", async () => {
|
||||
const updateUser = jest.fn(() => user);
|
||||
|
||||
await start({ updateUser });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "put",
|
||||
url: "/api/nextauth/updateUser",
|
||||
payload: {
|
||||
// id not specified
|
||||
email: "abc@abc.abc",
|
||||
},
|
||||
});
|
||||
expect(statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it("linkUser", async () => {
|
||||
const linkAccount = jest.fn(() => undefined);
|
||||
const args = {
|
||||
userId: "abc",
|
||||
providerId: "foo",
|
||||
providerType: "something",
|
||||
providerAccountId: "bar",
|
||||
refreshToken: "refreshToken",
|
||||
accessToken: "accessToken",
|
||||
accessTokenExpires: 10,
|
||||
};
|
||||
|
||||
await start({ linkAccount });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "put",
|
||||
url: "/api/nextauth/linkAccount",
|
||||
payload: args,
|
||||
});
|
||||
expect(statusCode).toBe(204);
|
||||
expect(linkAccount.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it("createSession", async () => {
|
||||
const createSession = jest.fn(() => session);
|
||||
|
||||
await start({ createSession });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "post",
|
||||
url: "/api/nextauth/createSession",
|
||||
payload: user,
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(createSession).toHaveBeenCalledWith(user);
|
||||
expect(result).toStrictEqual(session);
|
||||
});
|
||||
|
||||
it("getSession", async () => {
|
||||
const getSession = jest.fn(() => session);
|
||||
|
||||
await start({ getSession });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getSession/xyz",
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(getSession).toHaveBeenCalledWith("xyz");
|
||||
expect(result).toBe(session);
|
||||
});
|
||||
|
||||
it("updateSession", async () => {
|
||||
const updateSession = jest.fn(() => session);
|
||||
|
||||
await start({ updateSession });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "put",
|
||||
url: "/api/nextauth/updateSession",
|
||||
payload: session,
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(updateSession).toHaveBeenCalledWith(
|
||||
{
|
||||
...session,
|
||||
expires: new Date(session.expires),
|
||||
},
|
||||
false
|
||||
);
|
||||
expect(result).toStrictEqual(session);
|
||||
});
|
||||
|
||||
it("updateSession - force", async () => {
|
||||
const updateSession = jest.fn(() => session);
|
||||
|
||||
await start({ updateSession });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "put",
|
||||
url: "/api/nextauth/updateSession?force=true",
|
||||
payload: session,
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(updateSession).toHaveBeenCalledWith(
|
||||
{
|
||||
...session,
|
||||
expires: new Date(session.expires),
|
||||
},
|
||||
true
|
||||
);
|
||||
expect(result).toStrictEqual(session);
|
||||
});
|
||||
|
||||
it("deleteSession", async () => {
|
||||
const deleteSession = jest.fn(() => undefined);
|
||||
|
||||
await start({ deleteSession });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "delete",
|
||||
url: "/api/nextauth/deleteSession/xyz",
|
||||
});
|
||||
expect(statusCode).toBe(204);
|
||||
expect(deleteSession).toHaveBeenCalledWith("xyz");
|
||||
});
|
||||
});
|
||||
|
||||
describe("plugin authentication", () => {
|
||||
const user = { id: "abc", email: "abc@abc.abc" };
|
||||
const sharedSecret = "secret";
|
||||
let server;
|
||||
|
||||
const start = async (mock) => {
|
||||
await server.register(HapiBasic);
|
||||
await server.register({
|
||||
plugin: NextAuthPlugin,
|
||||
options: {
|
||||
nextAuthAdapterFactory: () => mock,
|
||||
sharedSecret,
|
||||
},
|
||||
});
|
||||
|
||||
await server.start();
|
||||
return server;
|
||||
};
|
||||
|
||||
const basicHeader = (username, password) =>
|
||||
"Basic " +
|
||||
Buffer.from(username + ":" + password, "utf8").toString("base64");
|
||||
|
||||
beforeEach(async () => {
|
||||
server = new Hapi.Server({ port: 0 });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it("getUser - no auth header fails", async () => {
|
||||
const getUser = jest.fn(() => user);
|
||||
|
||||
await start({ getUser });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUser/abc",
|
||||
});
|
||||
expect(statusCode).toBe(401);
|
||||
expect(getUser).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("getUser - with auth header suceeds", async () => {
|
||||
const getUser = jest.fn(() => user);
|
||||
|
||||
await start({ getUser });
|
||||
|
||||
const { statusCode, result } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUser/abc",
|
||||
headers: {
|
||||
authorization: basicHeader(sharedSecret, ""),
|
||||
},
|
||||
});
|
||||
expect(statusCode).toBe(200);
|
||||
expect(getUser).toHaveBeenCalledWith("abc");
|
||||
expect(result).toBe(user);
|
||||
});
|
||||
|
||||
it("getUser - with invalid credentials fails", async () => {
|
||||
const getUser = jest.fn(() => user);
|
||||
|
||||
await start({ getUser });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUser/abc",
|
||||
headers: {
|
||||
authorization: basicHeader("wrong secret", ""),
|
||||
},
|
||||
});
|
||||
expect(statusCode).toBe(401);
|
||||
expect(getUser).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("getUser - with secret in password field fails", async () => {
|
||||
const getUser = jest.fn(() => user);
|
||||
|
||||
await start({ getUser });
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: "get",
|
||||
url: "/api/nextauth/getUser/abc",
|
||||
headers: {
|
||||
authorization: basicHeader("", "sharedSecret"),
|
||||
},
|
||||
});
|
||||
expect(statusCode).toBe(401);
|
||||
expect(getUser).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
105
packages/hapi-nextauth/src/index.ts
Normal file
105
packages/hapi-nextauth/src/index.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import * as Hapi from "@hapi/hapi";
|
||||
import * as Hoek from "@hapi/hoek";
|
||||
import * as Joi from "joi";
|
||||
|
||||
import { NextAuthPluginOptions } from "./types";
|
||||
import * as Routes from "./routes";
|
||||
|
||||
const minimumProfileSchema = Joi.object()
|
||||
.keys({
|
||||
email: Joi.string().email().required(),
|
||||
})
|
||||
.unknown(true);
|
||||
|
||||
const minimumUserSchema = Joi.object()
|
||||
.keys({
|
||||
id: Joi.string().required(),
|
||||
email: Joi.string().email().required(),
|
||||
})
|
||||
.unknown(true);
|
||||
|
||||
const minimumSessionSchema = Joi.object()
|
||||
.keys({
|
||||
id: Joi.string().required(),
|
||||
userId: Joi.string().required(),
|
||||
expires: Joi.number().required(),
|
||||
sessionToken: Joi.string().required(),
|
||||
accessToken: Joi.string().required(),
|
||||
})
|
||||
.unknown(true);
|
||||
|
||||
const defaultOptions = {
|
||||
basePath: "/api/nextauth",
|
||||
validators: {
|
||||
userId: Joi.string().required(),
|
||||
profile: minimumProfileSchema,
|
||||
user: minimumUserSchema,
|
||||
session: minimumSessionSchema,
|
||||
},
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const validateAuth = (sharedSecret) => (request, username, password) => {
|
||||
// we follow stripe's lead here for authenticating with basic auth
|
||||
// the shared secret should be bassed as the basic auth username, the password should be empty
|
||||
if (password !== "") {
|
||||
console.error(
|
||||
"hapi-nextauth: attempted authentication with basic auth password. only the username should be defined."
|
||||
);
|
||||
|
||||
return { isValid: false, credentials: {} };
|
||||
}
|
||||
|
||||
const isValid = username === sharedSecret;
|
||||
const credentials = {
|
||||
id: "nextauth-frontend",
|
||||
};
|
||||
|
||||
return { isValid, credentials };
|
||||
};
|
||||
|
||||
const register = async <TUser, TProfile, TSession>(
|
||||
server: Hapi.Server,
|
||||
pluginOpts?: NextAuthPluginOptions<TUser, TProfile, TSession>
|
||||
): Promise<void> => {
|
||||
const options: NextAuthPluginOptions<
|
||||
TUser,
|
||||
TProfile,
|
||||
TSession
|
||||
> = Hoek.applyToDefaults(
|
||||
// a little type gymnastics here to workaround poor typing
|
||||
defaultOptions as unknown,
|
||||
pluginOpts
|
||||
) as NextAuthPluginOptions<TUser, TProfile, TSession>;
|
||||
|
||||
if (!options.nextAuthAdapterFactory) {
|
||||
throw new Error(
|
||||
"You must pass a NextAuthAdapterFactory instance to hapi-nextauth."
|
||||
);
|
||||
}
|
||||
|
||||
server.validator(Joi);
|
||||
let auth = "hapi-nextauth";
|
||||
if (options.sharedSecret) {
|
||||
server.dependency(["@hapi/basic"]);
|
||||
server.auth.strategy(auth, "basic", {
|
||||
validate: validateAuth(options.sharedSecret),
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
"hapi-nextauth: AUTHENTICATION OF FRONTEND TO NEXTAUTH ENDPOINTS DISABLED!"
|
||||
);
|
||||
auth = undefined;
|
||||
}
|
||||
|
||||
await Routes.register(server, options, auth);
|
||||
};
|
||||
|
||||
const nextAuthPlugin = {
|
||||
register,
|
||||
name: "@digiresilience/hapi-nextauth",
|
||||
version: "0.0.3",
|
||||
};
|
||||
|
||||
export * from "./types";
|
||||
export default nextAuthPlugin;
|
||||
262
packages/hapi-nextauth/src/routes.ts
Normal file
262
packages/hapi-nextauth/src/routes.ts
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
/* eslint-disable unicorn/no-null */
|
||||
import Toys from "@hapipal/toys";
|
||||
import * as Joi from "joi";
|
||||
import * as Hapi from "@hapi/hapi";
|
||||
import { NextAuthPluginOptions } from "./types";
|
||||
|
||||
export const register = async <TUser, TProfile, TSession>(
|
||||
server: Hapi.Server,
|
||||
opts: NextAuthPluginOptions<TUser, TProfile, TSession>,
|
||||
auth?: string
|
||||
): Promise<void> => {
|
||||
const withDefaults = Toys.withRouteDefaults({
|
||||
options: {
|
||||
auth,
|
||||
tags: opts.tags,
|
||||
},
|
||||
});
|
||||
|
||||
server.route([
|
||||
withDefaults({
|
||||
method: "POST",
|
||||
path: `${opts.basePath}/createUser`,
|
||||
options: {
|
||||
validate: {
|
||||
payload: opts.validators.profile,
|
||||
},
|
||||
handler: async (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.Toolkit
|
||||
): Promise<TUser> => {
|
||||
const payload: TProfile = request.payload as TProfile;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.createUser(payload);
|
||||
return h.response(r);
|
||||
},
|
||||
description: "Create a user from a profile",
|
||||
},
|
||||
}),
|
||||
withDefaults({
|
||||
method: "GET",
|
||||
path: `${opts.basePath}/getUser/{userId}`,
|
||||
options: {
|
||||
validate: {
|
||||
params: {
|
||||
userId: opts.validators.userId,
|
||||
},
|
||||
},
|
||||
handler: async (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.Toolkit
|
||||
): Promise<TUser | null> => {
|
||||
const id = request.params.userId;
|
||||
const r = await opts.nextAuthAdapterFactory(request).getUser(id);
|
||||
if (!r) return h.response().code(404);
|
||||
return h.response(r);
|
||||
},
|
||||
description: "Get a user by id",
|
||||
},
|
||||
}),
|
||||
withDefaults({
|
||||
method: "GET",
|
||||
path: `${opts.basePath}/getUserByEmail/{userEmail}`,
|
||||
options: {
|
||||
validate: {
|
||||
params: {
|
||||
userEmail: Joi.string().email(),
|
||||
},
|
||||
},
|
||||
handler: async (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.Toolkit
|
||||
): Promise<TUser | null> => {
|
||||
const email = request.params.userEmail;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.getUserByEmail(email);
|
||||
if (!r) return h.response().code(404);
|
||||
return h.response(r);
|
||||
},
|
||||
description: "Get a user by email",
|
||||
},
|
||||
}),
|
||||
withDefaults({
|
||||
method: "GET",
|
||||
path: `${opts.basePath}/getUserByProviderAccountId/{providerId}/{providerAccountId}`,
|
||||
options: {
|
||||
validate: {
|
||||
params: {
|
||||
providerId: Joi.string(),
|
||||
providerAccountId: Joi.string(),
|
||||
},
|
||||
},
|
||||
handler: async (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.Toolkit
|
||||
): Promise<TUser | null> => {
|
||||
const { providerId, providerAccountId } = request.params;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.getUserByProviderAccountId(providerId, providerAccountId);
|
||||
if (!r) return h.response().code(404);
|
||||
return h.response(r);
|
||||
},
|
||||
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 (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.Toolkit
|
||||
): Promise<TUser> => {
|
||||
const payload: TUser = request.payload as TUser;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.updateUser(payload);
|
||||
if (!r) return h.response().code(404);
|
||||
return h.response(r);
|
||||
},
|
||||
description: "Update a user's data",
|
||||
},
|
||||
}),
|
||||
withDefaults({
|
||||
method: "PUT",
|
||||
path: `${opts.basePath}/linkAccount`,
|
||||
options: {
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
userId: opts.validators.userId,
|
||||
providerId: Joi.string(),
|
||||
providerType: Joi.string(),
|
||||
providerAccountId: Joi.string(),
|
||||
refreshToken: Joi.string().optional().allow(null),
|
||||
accessToken: Joi.string().optional().allow(null),
|
||||
accessTokenExpires: Joi.number().optional().allow(null),
|
||||
}).options({ presence: "required" }),
|
||||
},
|
||||
handler: async (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.Toolkit
|
||||
): Promise<void> => {
|
||||
const {
|
||||
userId,
|
||||
providerId,
|
||||
providerType,
|
||||
providerAccountId,
|
||||
refreshToken,
|
||||
accessToken,
|
||||
accessTokenExpires,
|
||||
} = request.payload;
|
||||
await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.linkAccount(
|
||||
userId,
|
||||
providerId,
|
||||
providerType,
|
||||
providerAccountId,
|
||||
refreshToken,
|
||||
accessToken,
|
||||
accessTokenExpires
|
||||
);
|
||||
return h.response().code(204);
|
||||
},
|
||||
description: "Link a provider account with a user",
|
||||
},
|
||||
}),
|
||||
withDefaults({
|
||||
method: "POST",
|
||||
path: `${opts.basePath}/createSession`,
|
||||
options: {
|
||||
validate: {
|
||||
payload: opts.validators.user,
|
||||
},
|
||||
handler: async (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.Toolkit
|
||||
): Promise<TSession> => {
|
||||
const payload: TUser = request.payload as TUser;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.createSession(payload);
|
||||
return h.response(r);
|
||||
},
|
||||
description: "Create a new session for a user",
|
||||
},
|
||||
}),
|
||||
withDefaults({
|
||||
method: "GET",
|
||||
path: `${opts.basePath}/getSession/{sessionToken}`,
|
||||
options: {
|
||||
validate: {
|
||||
params: {
|
||||
sessionToken: Joi.string(),
|
||||
},
|
||||
},
|
||||
handler: async (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.Toolkit
|
||||
): Promise<TSession | null> => {
|
||||
const token = request.params.sessionToken;
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.getSession(token);
|
||||
if (!r) return h.response().code(404);
|
||||
return h.response(r);
|
||||
},
|
||||
description: "Get a session by its token",
|
||||
},
|
||||
}),
|
||||
withDefaults({
|
||||
method: "PUT",
|
||||
path: `${opts.basePath}/updateSession`,
|
||||
options: {
|
||||
validate: {
|
||||
payload: opts.validators.session,
|
||||
},
|
||||
handler: async (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.Toolkit
|
||||
): Promise<TSession> => {
|
||||
const payload = {
|
||||
...request.payload,
|
||||
expires: new Date(request.payload.expires),
|
||||
};
|
||||
const force = Boolean(request.query.force);
|
||||
const r = await opts
|
||||
.nextAuthAdapterFactory(request)
|
||||
.updateSession(payload, force);
|
||||
if (!r) return h.response().code(204);
|
||||
return h.response(r);
|
||||
},
|
||||
description: "Update a session for a user",
|
||||
},
|
||||
}),
|
||||
withDefaults({
|
||||
method: "DELETE",
|
||||
path: `${opts.basePath}/deleteSession/{sessionToken}`,
|
||||
options: {
|
||||
validate: {
|
||||
params: {
|
||||
sessionToken: Joi.string(),
|
||||
},
|
||||
},
|
||||
handler: async (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.Toolkit
|
||||
): Promise<void> => {
|
||||
const token = request.params.sessionToken;
|
||||
await opts.nextAuthAdapterFactory(request).deleteSession(token);
|
||||
return h.response().code(204);
|
||||
},
|
||||
description: "Delete a user's session",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
};
|
||||
21
packages/hapi-nextauth/src/types.ts
Normal file
21
packages/hapi-nextauth/src/types.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import type { AdapterInstance } from "next-auth/adapters";
|
||||
import type { NumberSchema, StringSchema, ObjectSchema } from "joi";
|
||||
import type { Request } from "@hapi/hapi";
|
||||
|
||||
export type AdapterFactory<TUser, TProfile, TSession> = (
|
||||
request: Request
|
||||
) => AdapterInstance<TUser, TProfile, TSession>;
|
||||
|
||||
export interface NextAuthPluginOptions<TUser, TProfile, TSession> {
|
||||
nextAuthAdapterFactory: AdapterFactory<TUser, TProfile, TSession>;
|
||||
|
||||
validators?: {
|
||||
profile: ObjectSchema;
|
||||
userId: StringSchema | NumberSchema;
|
||||
user: ObjectSchema;
|
||||
session: ObjectSchema;
|
||||
};
|
||||
sharedSecret?: string;
|
||||
basePath?: string;
|
||||
tags?: string[];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue