364 lines
9 KiB
TypeScript
364 lines
9 KiB
TypeScript
|
|
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);
|
||
|
|
});
|
||
|
|
});
|