Add login-required user management (admin/user roles, Config->Benutzer)

No self-signup: admins create every account in Config -> Benutzer with a
typed start password; a forced password change gates first use. Session-
cookie auth protects all /api/* routes; /api/users additionally requires
the admin role. See docs/authentication.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Grovy311 2026-09-28 19:51:47 +02:00
parent b50c00c0ac
commit c56cfb30ad
36 changed files with 4928 additions and 38 deletions

View file

@ -0,0 +1,87 @@
import crypto from "node:crypto";
import { asc, eq } from "drizzle-orm";
import type { AppDatabase } from "../database-context.js";
import { users } from "../schema/users.js";
import type { UserRole } from "../../shared/constants/user-roles.js";
export interface CreateUserRow {
username: string;
name: string;
role: UserRole;
passwordHash: string;
}
export class UserRepository {
constructor(private readonly database: AppDatabase) {}
async list() {
const db = this.database;
return db.select().from(users).orderBy(asc(users.name));
}
async count() {
const db = this.database;
const rows = await db.select({ id: users.id }).from(users).limit(1);
return rows.length;
}
async findById(userId: string) {
const db = this.database;
const [row] = await db.select().from(users).where(eq(users.id, userId)).limit(1);
return row ?? null;
}
async findByUsername(username: string) {
const db = this.database;
const [row] = await db
.select()
.from(users)
.where(eq(users.username, username.trim().toLowerCase()))
.limit(1);
return row ?? null;
}
async create(input: CreateUserRow) {
const db = this.database;
const id = crypto.randomUUID();
await db.insert(users).values({
id,
username: input.username.trim().toLowerCase(),
name: input.name,
role: input.role,
passwordHash: input.passwordHash,
active: true,
mustChangePassword: true,
});
return this.findById(id);
}
async updateRoleAndActive(
userId: string,
patch: { role?: UserRole; active?: boolean }
) {
const db = this.database;
await db
.update(users)
.set({
...(patch.role !== undefined ? { role: patch.role } : {}),
...(patch.active !== undefined ? { active: patch.active } : {}),
updatedAt: new Date(),
})
.where(eq(users.id, userId));
return this.findById(userId);
}
async setPassword(
userId: string,
passwordHash: string,
mustChangePassword: boolean
) {
const db = this.database;
await db
.update(users)
.set({ passwordHash, mustChangePassword, updatedAt: new Date() })
.where(eq(users.id, userId));
return this.findById(userId);
}
}