156 lines
5.5 KiB
TypeScript
156 lines
5.5 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
|
|
import { eq } from "@homarr/db";
|
|
import { accounts, apiKeys, groupMembers, groups, users } from "@homarr/db/schema";
|
|
import { createDb } from "@homarr/db/test";
|
|
|
|
import { createAdapter } from "../adapter";
|
|
|
|
describe("createAdapter should create drizzle adapter", () => {
|
|
test.each([["credentials" as const], ["ldap" as const], ["oidc" as const]])(
|
|
"createAdapter getUserByEmail should return user for provider %s when this provider provided",
|
|
async (provider) => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const adapter = createAdapter(db, provider);
|
|
const email = "test@example.com";
|
|
await db.insert(users).values({ id: "1", name: "test", email, provider });
|
|
|
|
// Act
|
|
const user = await adapter.getUserByEmail?.(email);
|
|
|
|
// Assert
|
|
expect(user).toEqual({
|
|
id: "1",
|
|
name: "test",
|
|
email,
|
|
emailVerified: null,
|
|
image: null,
|
|
});
|
|
},
|
|
);
|
|
|
|
test.each([
|
|
["credentials", ["ldap", "oidc"]],
|
|
["ldap", ["credentials", "oidc"]],
|
|
["oidc", ["credentials", "ldap"]],
|
|
] as const)(
|
|
"createAdapter getUserByEmail should return null if only for other providers than %s exist",
|
|
async (requestedProvider, existingProviders) => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const adapter = createAdapter(db, requestedProvider);
|
|
const email = "test@example.com";
|
|
for (const provider of existingProviders) {
|
|
await db.insert(users).values({ id: provider, name: `test-${provider}`, email, provider });
|
|
}
|
|
|
|
// Act
|
|
const user = await adapter.getUserByEmail?.(email);
|
|
|
|
// Assert
|
|
expect(user).toBeNull();
|
|
},
|
|
);
|
|
|
|
test("createAdapter getUserByEmail should throw error if provider is unknown", async () => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const adapter = createAdapter(db, "unknown");
|
|
const email = "test@example.com";
|
|
|
|
// Act
|
|
const actAsync = async () => await adapter.getUserByEmail?.(email);
|
|
|
|
// Assert
|
|
await expect(actAsync()).rejects.toThrow("Unable to get user by email for unknown provider");
|
|
});
|
|
|
|
test("getUserByEmail should return a credentials user for oidc when credentials linking is enabled", async () => {
|
|
const db = createDb();
|
|
const adapter = createAdapter(db, "oidc", true);
|
|
const email = "test@example.com";
|
|
await db.insert(users).values({ id: "credentials-user", name: "test", email, provider: "credentials" });
|
|
|
|
const user = await adapter.getUserByEmail?.(email);
|
|
|
|
expect(user?.id).toBe("credentials-user");
|
|
});
|
|
|
|
test("getUserByEmail should not return credentials users for non-oidc providers", async () => {
|
|
const db = createDb();
|
|
const adapter = createAdapter(db, "ldap", true);
|
|
const email = "test@example.com";
|
|
await db.insert(users).values({ id: "credentials-user", name: "test", email, provider: "credentials" });
|
|
|
|
const user = await adapter.getUserByEmail?.(email);
|
|
|
|
expect(user).toBeNull();
|
|
});
|
|
|
|
test("getUserByEmail should prefer an existing oidc user over a credentials user", async () => {
|
|
const db = createDb();
|
|
const adapter = createAdapter(db, "oidc", true);
|
|
const email = "test@example.com";
|
|
await db.insert(users).values([
|
|
{ id: "credentials-user", name: "credentials", email, provider: "credentials" },
|
|
{ id: "oidc-user", name: "oidc", email, provider: "oidc" },
|
|
]);
|
|
|
|
const user = await adapter.getUserByEmail?.(email);
|
|
|
|
expect(user?.id).toBe("oidc-user");
|
|
});
|
|
|
|
test("linkAccount should preserve the credentials user and their data", async () => {
|
|
const db = createDb();
|
|
const adapter = createAdapter(db, "oidc", true);
|
|
const email = "test@example.com";
|
|
await db.insert(users).values({
|
|
id: "credentials-user",
|
|
name: "test",
|
|
email,
|
|
password: "hashed-password",
|
|
provider: "credentials",
|
|
colorScheme: "light",
|
|
completedBoardTour: true,
|
|
});
|
|
await db.insert(groups).values({ id: "group", name: "Test group", position: 1 });
|
|
await db.insert(groupMembers).values({ groupId: "group", userId: "credentials-user" });
|
|
await db.insert(apiKeys).values({ id: "api-key", apiKey: "secret", userId: "credentials-user" });
|
|
|
|
const user = await adapter.getUserByEmail?.(email);
|
|
if (!user) throw new Error("Expected credentials user to be found for OIDC linking");
|
|
await adapter.linkAccount?.({
|
|
userId: user.id,
|
|
type: "oidc",
|
|
provider: "oidc",
|
|
providerAccountId: "oidc-subject",
|
|
});
|
|
|
|
const linkedUser = await db.query.users.findFirst({ where: eq(users.id, "credentials-user") });
|
|
const account = await db.query.accounts.findFirst({ where: eq(accounts.providerAccountId, "oidc-subject") });
|
|
const membership = await db.query.groupMembers.findFirst({
|
|
where: eq(groupMembers.userId, "credentials-user"),
|
|
});
|
|
const apiKey = await db.query.apiKeys.findFirst({ where: eq(apiKeys.userId, "credentials-user") });
|
|
const userByAccount = await adapter.getUserByAccount?.({
|
|
provider: "oidc",
|
|
providerAccountId: "oidc-subject",
|
|
});
|
|
const allUsers = await db.query.users.findMany();
|
|
expect(linkedUser).toMatchObject({
|
|
id: "credentials-user",
|
|
password: "hashed-password",
|
|
provider: "credentials",
|
|
colorScheme: "light",
|
|
completedBoardTour: true,
|
|
});
|
|
expect(account?.userId).toBe("credentials-user");
|
|
expect(membership?.groupId).toBe("group");
|
|
expect(apiKey?.id).toBe("api-key");
|
|
expect(userByAccount?.id).toBe("credentials-user");
|
|
expect(allUsers).toHaveLength(1);
|
|
});
|
|
});
|