generated from bastian/boilerplate
deploy
This commit is contained in:
@@ -3,6 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ApiClientService } from '@boilerplate/api-client';
|
||||
import { AdminRoleDetailPageComponent } from './admin-role-detail.page';
|
||||
import { adminPermissionGroups } from './admin-permissions';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('AdminRoleDetailPageComponent', () => {
|
||||
it('groups permissions for the role editor', async () => {
|
||||
@@ -31,4 +32,41 @@ describe('AdminRoleDetailPageComponent', () => {
|
||||
expect(element.textContent).toContain('Benutzer aktivieren');
|
||||
expect(element.textContent).toContain('notifications.manage');
|
||||
});
|
||||
|
||||
it('shows the environment-managed OIDC mapping for the system admin role', async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AdminRoleDetailPageComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: { snapshot: { paramMap: { get: () => 'admin-role' } } },
|
||||
},
|
||||
{ provide: Router, useValue: { navigate: vi.fn() } },
|
||||
{
|
||||
provide: ApiClientService,
|
||||
useValue: {
|
||||
adminRole: () =>
|
||||
of({
|
||||
id: 'admin-role',
|
||||
name: 'admin',
|
||||
description: 'Administrator',
|
||||
system: true,
|
||||
protected: true,
|
||||
roleKey: 'ADMIN',
|
||||
oidcManaged: true,
|
||||
oidcRoleName: 'hauspilot-admin',
|
||||
permissions: [],
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
const fixture = TestBed.createComponent(AdminRoleDetailPageComponent);
|
||||
fixture.detectChanges();
|
||||
const host = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(host.textContent).toContain('Automatische OIDC-Verwaltung');
|
||||
expect(host.textContent).toContain('hauspilot-admin');
|
||||
expect(host.textContent).toContain('OIDC_ADMIN_ROLE');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,6 +31,19 @@ import { adminPermissionGroups } from './admin-permissions';
|
||||
@if (role()?.system) {
|
||||
<span class="system">Systemrolle</span>
|
||||
}
|
||||
@if (role()?.roleKey === 'ADMIN'; as isAdminRole) {
|
||||
<aside class="oidc-notice">
|
||||
<strong>Automatische OIDC-Verwaltung</strong>
|
||||
@if (role()?.oidcManaged) {
|
||||
<span
|
||||
>OIDC-Rolle: <code>{{ role()?.oidcRoleName }}</code></span
|
||||
>
|
||||
<small>Quelle: Umgebungsvariable OIDC_ADMIN_ROLE</small>
|
||||
} @else {
|
||||
<span>Die automatische Administrator-Synchronisierung ist nicht konfiguriert.</span>
|
||||
}
|
||||
</aside>
|
||||
}
|
||||
<label>
|
||||
Name
|
||||
<input
|
||||
@@ -168,6 +181,14 @@ import { adminPermissionGroups } from './admin-permissions';
|
||||
padding: 4px 8px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.oidc-notice {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
background: var(--color-background);
|
||||
}
|
||||
@media (min-width: 900px) {
|
||||
.groups {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ApiClientService, type AdminUserDetailDto, type RoleDto } from '@boilerplate/api-client';
|
||||
import { of } from 'rxjs';
|
||||
import { AdminUserDetailPageComponent } from './admin-user-detail.page';
|
||||
|
||||
describe('AdminUserDetailPageComponent', () => {
|
||||
it('shows OIDC role provenance and does not offer local removal without a manual source', async () => {
|
||||
const adminRole = {
|
||||
id: 'admin-role',
|
||||
name: 'admin',
|
||||
description: 'Administrator',
|
||||
system: true,
|
||||
protected: true,
|
||||
roleKey: 'ADMIN',
|
||||
oidcManaged: true,
|
||||
oidcRoleName: 'hauspilot-admin',
|
||||
permissions: [],
|
||||
} satisfies RoleDto;
|
||||
const user = {
|
||||
id: 'user-1',
|
||||
name: 'Bastian',
|
||||
email: 'admin@example.test',
|
||||
active: true,
|
||||
roles: [{ id: adminRole.id, name: 'admin', system: true, sources: ['OIDC'] }],
|
||||
lastLoginAt: '2026-07-20',
|
||||
createdAt: '2026-07-20',
|
||||
activeSessionCount: 0,
|
||||
effectivePermissions: [],
|
||||
sessions: [],
|
||||
} satisfies AdminUserDetailDto;
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AdminUserDetailPageComponent],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: { snapshot: { paramMap: { get: () => user.id } } } },
|
||||
{
|
||||
provide: ApiClientService,
|
||||
useValue: { adminRoles: () => of([adminRole]), adminUser: () => of(user) },
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
const fixture = TestBed.createComponent(AdminUserDetailPageComponent);
|
||||
fixture.detectChanges();
|
||||
const host = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(host.textContent).toContain('Herkunft: OIDC');
|
||||
expect(host.textContent).toContain('Entfernen Sie die externe Rolle im Identity Provider');
|
||||
expect(host.textContent).not.toContain('Manuelle Zuweisung entfernen');
|
||||
});
|
||||
});
|
||||
@@ -50,11 +50,20 @@ import {
|
||||
<h3>Rollen</h3>
|
||||
<div class="chips">
|
||||
@for (role of currentUser.roles; track role.id) {
|
||||
<span>
|
||||
{{ role.name }}
|
||||
<button type="button" (click)="removeRole(currentUser.id, role.id, role.name)">
|
||||
Entfernen
|
||||
</button>
|
||||
<span class="role-chip">
|
||||
<span>
|
||||
<strong>{{ role.name }}</strong>
|
||||
<small>Herkunft: {{ roleSourceLabel(role.sources) }}</small>
|
||||
</span>
|
||||
@if (role.sources.includes('MANUAL')) {
|
||||
<button type="button" (click)="removeRole(currentUser.id, role.id, role.name)">
|
||||
Manuelle Zuweisung entfernen
|
||||
</button>
|
||||
} @else if (role.sources.includes('OIDC')) {
|
||||
<small class="managed-hint">
|
||||
Automatisch verwaltet. Entfernen Sie die externe Rolle im Identity Provider.
|
||||
</small>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
@@ -203,6 +212,13 @@ import {
|
||||
border-radius: 999px;
|
||||
padding: 4px 6px 4px 10px;
|
||||
}
|
||||
.role-chip > span {
|
||||
display: grid;
|
||||
}
|
||||
.managed-hint {
|
||||
max-width: 20rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.chips button {
|
||||
min-height: 34px;
|
||||
background: var(--color-danger);
|
||||
@@ -323,8 +339,15 @@ export class AdminUserDetailPageComponent {
|
||||
}
|
||||
|
||||
availableRoles(user: AdminUserDetailDto): RoleDto[] {
|
||||
const assigned = new Set(user.roles.map((role) => role.id));
|
||||
return this.roles().filter((role) => !assigned.has(role.id));
|
||||
const manuallyAssigned = new Set(
|
||||
user.roles.filter((role) => role.sources.includes('MANUAL')).map((role) => role.id),
|
||||
);
|
||||
return this.roles().filter((role) => !manuallyAssigned.has(role.id));
|
||||
}
|
||||
|
||||
roleSourceLabel(sources: AdminUserDetailDto['roles'][number]['sources']): string {
|
||||
const labels = { MANUAL: 'Manuell', OIDC: 'OIDC', SYSTEM: 'System' } as const;
|
||||
return sources.map((source) => labels[source]).join(' + ');
|
||||
}
|
||||
|
||||
messageFor(error: ApiErrorBody): string {
|
||||
|
||||
Reference in New Issue
Block a user