Some checks failed
CD / Build and push images (push) Successful in 3m12s
CI / Lint, typecheck, test (push) Failing after 2m29s
CI / Auth e2e pack (push) Successful in 3m32s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m11s
CD / Promote to Int (push) Successful in 11s
Instance operators get basic user administration for support, abuse handling, and GDPR groundwork (security.md §Privacy). - api `admin/`: Site-Admin-gated `/admin/users` — a searchable, paginated list (username, e-mail, status, role, pond count, last login) plus lifecycle actions: disable/enable (a disabled user is logged out everywhere and login is refused with the distinct `account_disabled`), resend verification, delete, and grant/revoke Site Admin. Guards: you cannot act on your own account (`cannot_modify_self`) and the last Site Admin cannot be dropped (`last_site_admin`). Every action is audit-logged with the actor. - `PseudonymizationService`: account deletion scrubs the PII, removes all login identities + sessions, and trashes the personal pond — the kept row is what authorship references, so shared content the user authored shows as "Deleted user" (no orphaned/cascaded content). - web: the Admin area gains a 'Users' surface — search, pagination, and the actions (destructive ones behind an inline two-step confirm; self-actions hidden). New `users` i18n namespace (de+en). - tests: `user-admin.e2e.db.test.ts` (disable → logout + login blocked; delete → pseudonymized authorship + personal pond trashed + credentials gone; last Site Admin and self protected; Site-Admin gating); a non-destructive browser `admin-users` pack proving disable-in-UI blocks login and enable restores it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
71 lines
1.9 KiB
TypeScript
71 lines
1.9 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
HttpCode,
|
|
Param,
|
|
Patch,
|
|
Post,
|
|
Query,
|
|
Req,
|
|
UseGuards,
|
|
} from '@nestjs/common';
|
|
import {
|
|
AdminUserListQuery,
|
|
AdminUserListView,
|
|
AdminUserView,
|
|
adminUserListQuerySchema,
|
|
setSiteAdminSchema,
|
|
setUserDisabledSchema,
|
|
} from '@dorfteich/shared';
|
|
|
|
import { AuthedRequest } from '../auth/auth.guard';
|
|
import { ZodValidationPipe } from '../common/zod-validation.pipe';
|
|
import { SiteAdminGuard } from './site-admin.guard';
|
|
import { UserAdminService } from './user-admin.service';
|
|
|
|
/** Site-Admin user management (issue #59). */
|
|
@Controller('admin/users')
|
|
@UseGuards(SiteAdminGuard)
|
|
export class UserAdminController {
|
|
constructor(private readonly users: UserAdminService) {}
|
|
|
|
@Get()
|
|
async list(
|
|
@Query(new ZodValidationPipe(adminUserListQuerySchema)) query: AdminUserListQuery,
|
|
): Promise<AdminUserListView> {
|
|
return this.users.list(query);
|
|
}
|
|
|
|
@Patch(':id/disabled')
|
|
async setDisabled(
|
|
@Param('id') id: string,
|
|
@Body(new ZodValidationPipe(setUserDisabledSchema)) input: { disabled: boolean },
|
|
@Req() request: AuthedRequest,
|
|
): Promise<AdminUserView> {
|
|
return this.users.setDisabled(request.user!, id, input.disabled);
|
|
}
|
|
|
|
@Post(':id/resend-verification')
|
|
@HttpCode(204)
|
|
async resendVerification(@Param('id') id: string, @Req() request: AuthedRequest): Promise<void> {
|
|
await this.users.resendVerification(request.user!, id);
|
|
}
|
|
|
|
@Delete(':id')
|
|
@HttpCode(204)
|
|
async remove(@Param('id') id: string, @Req() request: AuthedRequest): Promise<void> {
|
|
await this.users.deleteUser(request.user!, id);
|
|
}
|
|
|
|
@Patch(':id/site-admin')
|
|
async setSiteAdmin(
|
|
@Param('id') id: string,
|
|
@Body(new ZodValidationPipe(setSiteAdminSchema)) input: { isSiteAdmin: boolean },
|
|
@Req() request: AuthedRequest,
|
|
): Promise<AdminUserView> {
|
|
return this.users.setSiteAdmin(request.user!, id, input.isSiteAdmin);
|
|
}
|
|
}
|