Add Super Viewer read-only administrator role
CI / checks (push) Successful in 5m40s

This commit is contained in:
2026-10-11 23:30:40 -04:00
parent 253d47300c
commit 4ea291d305
48 changed files with 1684 additions and 933 deletions
+13 -6
View File
@@ -256,7 +256,7 @@ describe('password storage and verification', () => {
describe('administrator user management', () => {
it('rejects the reserved system username for every role while allowing distinct usernames', async () => {
const admin = seedUser();
for (const role of ['admin', 'operator', 'viewer'] as const) {
for (const role of ['admin', 'super_viewer', 'operator', 'viewer'] as const) {
for (const username of ['Maxis', 'maxis', 'MAXIS', 'mAxIs']) {
await expect(
auth.createUser(admin.id, { username, role, password })
@@ -275,7 +275,7 @@ describe('administrator user management', () => {
it('creates each supported role with a forced initial password change and rejects case-insensitive duplicate names', async () => {
const admin = seedUser();
for (const role of ['admin', 'operator', 'viewer'] as const) {
for (const role of ['admin', 'super_viewer', 'operator', 'viewer'] as const) {
const user = await auth.createUser(admin.id, {
username: role + 'User',
displayName: ' ' + role + ' user ',
@@ -295,13 +295,14 @@ describe('administrator user management', () => {
await expect(
auth.createUser(admin.id, { username: 'OPERATORUSER', role: 'viewer', password })
).rejects.toMatchObject({ status: 409 });
expect(auth.listUsers()).toHaveLength(4);
expect(auth.listUsers()).toHaveLength(5);
expect(JSON.stringify(auth.listAuthAudit())).not.toContain(password);
});
it('rejects administrator mutations by operators, viewers, disabled administrators and forced-change administrators', async () => {
it('rejects administrator mutations by read-only roles, operators, disabled administrators and forced-change administrators', async () => {
const target = seedUser('target', 'viewer');
const actors = [
seedUser('superviewer', 'super_viewer'),
seedUser('operator', 'operator'),
seedUser('viewer', 'viewer'),
seedUser('disabledadmin', 'admin', { disabled: true }),
@@ -324,7 +325,11 @@ describe('administrator user management', () => {
it('preserves at least one enabled administrator and permits demotion only when another remains', async () => {
const first = seedUser();
seedUser('superviewer', 'super_viewer');
const session = auth.createSession(first.id);
await expect(
auth.updateUser(first.id, first.id, { role: 'super_viewer' })
).rejects.toMatchObject({ status: 409 });
await expect(
auth.updateUser(first.id, first.id, { role: 'operator' })
).rejects.toMatchObject({ status: 409 });
@@ -333,7 +338,8 @@ describe('administrator user management', () => {
);
expect(auth.readSession(session.token)?.user.role).toBe('admin');
const second = seedUser('secondadmin');
await auth.updateUser(first.id, first.id, { role: 'viewer' });
await auth.updateUser(first.id, first.id, { role: 'super_viewer' });
expect(auth.getUser(first.id)?.role).toBe('super_viewer');
expect(auth.readSession(session.token)).toBeNull();
await expect(
auth.updateUser(second.id, second.id, { disabled: true })
@@ -347,7 +353,8 @@ describe('administrator user management', () => {
const administratorSession = auth.createSession(admin.id);
const first = auth.createSession(target.id),
second = auth.createSession(target.id);
await auth.updateUser(admin.id, target.id, { role: 'operator' });
await auth.updateUser(admin.id, target.id, { role: 'super_viewer' });
expect(auth.getUser(target.id)?.role).toBe('super_viewer');
expect(auth.readSession(first.token)).toBeNull();
expect(auth.readSession(second.token)).toBeNull();
const third = auth.createSession(target.id);