Files
homarr/packages/auth/providers/test/ldap-authorization.spec.ts
Meier Lukas dc75ffb9e6 feat: add ldap and oidc sso (#500)
* wip: sso

* feat: add ldap client and provider

* feat: implement login form

* feat: finish sso

* fix: lint and format issue

* chore: address pull request feedback

* fix: build not working

* fix: oidc is redirected to internal docker container hostname

* fix: build not working

* refactor: migrate to ldapts

* fix: format and frozen lock file

* fix: deepsource issues

* fix: unit tests for ldap authorization not working

* refactor: remove unnecessary args from dockerfile

* chore: address pull request feedback

* fix: use console instead of logger in auth env.mjs

* fix: default value for auth provider of wrong type

* fix: broken lock file

* fix: format issue
2024-07-20 22:23:58 +02:00

205 lines
5.6 KiB
TypeScript

import type { Adapter } from "@auth/core/adapters";
import { CredentialsSignin } from "@auth/core/errors";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { describe, expect, test, vi } from "vitest";
import { createId, eq } from "@homarr/db";
import { users } from "@homarr/db/schema/sqlite";
import { createDb } from "@homarr/db/test";
import { createSaltAsync, hashPasswordAsync } from "../../security";
import { authorizeWithLdapCredentialsAsync } from "../credentials/authorization/ldap-authorization";
import * as ldapClient from "../credentials/ldap-client";
vi.mock("../../env.mjs", () => ({
env: {
AUTH_LDAP_BIND_DN: "bind_dn",
AUTH_LDAP_BIND_PASSWORD: "bind_password",
AUTH_LDAP_USER_MAIL_ATTRIBUTE: "mail",
},
}));
describe("authorizeWithLdapCredentials", () => {
test("should fail when wrong ldap base credentials", async () => {
// Arrange
const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation(
() =>
({
bindAsync: vi.fn(() => Promise.reject(new Error("bindAsync"))),
}) as unknown as ldapClient.LdapClient,
);
// Act
const act = () =>
authorizeWithLdapCredentialsAsync(null as unknown as Adapter, {
name: "test",
password: "test",
credentialType: "ldap",
});
// Assert
await expect(act()).rejects.toThrow(CredentialsSignin);
});
test("should fail when user not found", async () => {
// Arrange
const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation(
() =>
({
bindAsync: vi.fn(() => Promise.resolve()),
searchAsync: vi.fn(() => Promise.resolve([])),
}) as unknown as ldapClient.LdapClient,
);
// Act
const act = () =>
authorizeWithLdapCredentialsAsync(null as unknown as Adapter, {
name: "test",
password: "test",
credentialType: "ldap",
});
// Assert
await expect(act()).rejects.toThrow(CredentialsSignin);
});
test("should fail when user has invalid email", async () => {
// Arrange
const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation(
() =>
({
bindAsync: vi.fn(() => Promise.resolve()),
searchAsync: vi.fn(() =>
Promise.resolve([
{
dn: "test",
mail: "test",
},
]),
),
}) as unknown as ldapClient.LdapClient,
);
// Act
const act = () =>
authorizeWithLdapCredentialsAsync(null as unknown as Adapter, {
name: "test",
password: "test",
credentialType: "ldap",
});
// Assert
await expect(act()).rejects.toThrow(CredentialsSignin);
});
test("should fail when user password is incorrect", async () => {
// Arrange
const searchSpy = vi.fn(() =>
Promise.resolve([
{
dn: "test",
mail: "test@gmail.com",
},
]),
);
const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation(
() =>
({
bindAsync: vi.fn((props: ldapClient.BindOptions) =>
props.distinguishedName === "test" ? Promise.reject(new Error("bindAsync")) : Promise.resolve(),
),
searchAsync: searchSpy,
}) as unknown as ldapClient.LdapClient,
);
// Act
const act = () =>
authorizeWithLdapCredentialsAsync(null as unknown as Adapter, {
name: "test",
password: "test",
credentialType: "ldap",
});
// Assert
await expect(act()).rejects.toThrow(CredentialsSignin);
expect(searchSpy).toHaveBeenCalledTimes(1);
});
test("should authorize user with correct credentials and create user", async () => {
// Arrange
const db = createDb();
const adapter = DrizzleAdapter(db);
const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation(
() =>
({
bindAsync: vi.fn(() => Promise.resolve()),
searchAsync: vi.fn(() =>
Promise.resolve([
{
dn: "test",
mail: "test@gmail.com",
},
]),
),
disconnectAsync: vi.fn(),
}) as unknown as ldapClient.LdapClient,
);
// Act
const result = await authorizeWithLdapCredentialsAsync(adapter, {
name: "test",
password: "test",
credentialType: "ldap",
});
// Assert
expect(result.name).toBe("test");
const dbUser = await db.query.users.findFirst({
where: eq(users.name, "test"),
});
expect(dbUser).toBeDefined();
expect(dbUser?.id).toBe(result.id);
expect(dbUser?.email).toBe("test@gmail.com");
expect(dbUser?.emailVerified).not.toBeNull();
});
test("should authorize user with correct credentials and update name", async () => {
// Arrange
const userId = createId();
const db = createDb();
const adapter = DrizzleAdapter(db);
const salt = await createSaltAsync();
await db.insert(users).values({
id: userId,
name: "test-old",
salt,
password: await hashPasswordAsync("test", salt),
email: "test@gmail.com",
});
// Act
const result = await authorizeWithLdapCredentialsAsync(adapter, {
name: "test",
password: "test",
credentialType: "ldap",
});
// Assert
expect(result).toEqual({ id: userId, name: "test" });
const dbUser = await db.query.users.findFirst({
where: eq(users.id, userId),
});
expect(dbUser).toBeDefined();
expect(dbUser?.id).toBe(userId);
expect(dbUser?.name).toBe("test");
expect(dbUser?.email).toBe("test@gmail.com");
});
});