feat: add everyone group (#1322)
* feat: add everyone group through seed * feat: add reserved group name check in group router actions * feat: improve user interface for everyone group * fix: reserved group alert is a server component * feat: add all users to everyone group * chore: update lockfile * fix: format issues * fix: lint issues * fix: lint format issues * test: add unit tests for everyone group * refactor: add codegen for documentation urls by sitemap * refactor: change group query to count * chore: remove migrations temporarily * chore: add migrations again * chore: add lint rule to prevent usage of raw documentation links * fix: format issues
This commit is contained in:
@@ -5,6 +5,7 @@ import type { NextAuthConfig } from "next-auth";
|
||||
import { and, eq, inArray } from "@homarr/db";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { groupMembers, groups, users } from "@homarr/db/schema/sqlite";
|
||||
import { everyoneGroup } from "@homarr/definitions";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { env } from "./env.mjs";
|
||||
@@ -33,6 +34,7 @@ export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["
|
||||
if ("groups" in user && Array.isArray(user.groups)) {
|
||||
await synchronizeGroupsWithExternalForUserAsync(db, user.id, user.groups as string[]);
|
||||
}
|
||||
await addUserToEveryoneGroupIfNotMemberAsync(db, user.id);
|
||||
|
||||
if (dbUser.name !== user.name) {
|
||||
await db.update(users).set({ name: user.name }).where(eq(users.id, user.id));
|
||||
@@ -57,7 +59,27 @@ export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["
|
||||
};
|
||||
};
|
||||
|
||||
const addUserToEveryoneGroupIfNotMemberAsync = async (db: Database, userId: string) => {
|
||||
const dbEveryoneGroup = await db.query.groups.findFirst({
|
||||
where: eq(groups.name, everyoneGroup),
|
||||
with: {
|
||||
members: {
|
||||
where: eq(groupMembers.userId, userId),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (dbEveryoneGroup?.members.length === 0) {
|
||||
await db.insert(groupMembers).values({
|
||||
userId,
|
||||
groupId: dbEveryoneGroup.id,
|
||||
});
|
||||
logger.info(`Added user to everyone group. user=${userId}`);
|
||||
}
|
||||
};
|
||||
|
||||
const synchronizeGroupsWithExternalForUserAsync = async (db: Database, userId: string, externalGroups: string[]) => {
|
||||
const ignoredGroups = [everyoneGroup];
|
||||
const dbGroupMembers = await db.query.groupMembers.findMany({
|
||||
where: eq(groupMembers.userId, userId),
|
||||
with: {
|
||||
@@ -102,11 +124,11 @@ const synchronizeGroupsWithExternalForUserAsync = async (db: Database, userId: s
|
||||
}
|
||||
|
||||
/**
|
||||
* The below groups are those groups the user is part of in Homarr, but not in the external system.
|
||||
* The below groups are those groups the user is part of in Homarr, but not in the external system and not ignored.
|
||||
* So he has to be removed from those groups.
|
||||
*/
|
||||
const groupsUserIsNoLongerMemberOfExternally = dbGroupMembers.filter(
|
||||
({ group }) => !externalGroups.includes(group.name),
|
||||
({ group }) => !externalGroups.concat(ignoredGroups).includes(group.name),
|
||||
);
|
||||
|
||||
if (groupsUserIsNoLongerMemberOfExternally.length > 0) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { eq } from "@homarr/db";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { groupMembers, groups, users } from "@homarr/db/schema/sqlite";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import { everyoneGroup } from "@homarr/definitions";
|
||||
|
||||
import { createSignInEventHandler } from "../events";
|
||||
|
||||
@@ -34,6 +35,29 @@ vi.mock("next/headers", async (importOriginal) => {
|
||||
});
|
||||
|
||||
describe("createSignInEventHandler should create signInEventHandler", () => {
|
||||
describe("signInEventHandler should add users to everyone group", () => {
|
||||
test("should add user to everyone group if he isn't already", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
await createGroupAsync(db, everyoneGroup);
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test" },
|
||||
profile: undefined,
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbGroupMembers = await db.query.groupMembers.findFirst({
|
||||
where: eq(groupMembers.userId, "1"),
|
||||
});
|
||||
expect(dbGroupMembers?.groupId).toBe("1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("signInEventHandler should synchronize ldap groups", () => {
|
||||
test("should add missing group membership", async () => {
|
||||
// Arrange
|
||||
@@ -79,6 +103,30 @@ describe("createSignInEventHandler should create signInEventHandler", () => {
|
||||
});
|
||||
expect(dbGroupMembers).toBeUndefined();
|
||||
});
|
||||
test("should not remove group membership for everyone group", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
await createGroupAsync(db, everyoneGroup);
|
||||
await db.insert(groupMembers).values({
|
||||
userId: "1",
|
||||
groupId: "1",
|
||||
});
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test", groups: [] } as never,
|
||||
profile: undefined,
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbGroupMembers = await db.query.groupMembers.findFirst({
|
||||
where: eq(groupMembers.userId, "1"),
|
||||
});
|
||||
expect(dbGroupMembers?.groupId).toBe("1");
|
||||
});
|
||||
});
|
||||
describe("signInEventHandler should synchronize oidc groups", () => {
|
||||
test("should add missing group membership", async () => {
|
||||
@@ -125,6 +173,30 @@ describe("createSignInEventHandler should create signInEventHandler", () => {
|
||||
});
|
||||
expect(dbGroupMembers).toBeUndefined();
|
||||
});
|
||||
test("should not remove group membership for everyone group", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
await createGroupAsync(db, everyoneGroup);
|
||||
await db.insert(groupMembers).values({
|
||||
userId: "1",
|
||||
groupId: "1",
|
||||
});
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test" },
|
||||
profile: { preferred_username: "test", someRandomGroupsKey: [] },
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbGroupMembers = await db.query.groupMembers.findFirst({
|
||||
where: eq(groupMembers.userId, "1"),
|
||||
});
|
||||
expect(dbGroupMembers?.groupId).toBe("1");
|
||||
});
|
||||
});
|
||||
test.each([
|
||||
["ldap" as const, { name: "test-new" }, undefined],
|
||||
@@ -183,8 +255,8 @@ const createUserAsync = async (db: Database) =>
|
||||
colorScheme: "dark",
|
||||
});
|
||||
|
||||
const createGroupAsync = async (db: Database) =>
|
||||
const createGroupAsync = async (db: Database, name = "test") =>
|
||||
await db.insert(groups).values({
|
||||
id: "1",
|
||||
name: "test",
|
||||
name,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user