diff --git a/ApiCore-Backend/prisma/migrations/20260826071724_drop_legacy_role/migration.sql b/ApiCore-Backend/prisma/migrations/20260826071724_drop_legacy_role/migration.sql new file mode 100644 index 0000000..25fceb4 --- /dev/null +++ b/ApiCore-Backend/prisma/migrations/20260826071724_drop_legacy_role/migration.sql @@ -0,0 +1,10 @@ +-- RBAC Phase 3: drops the transitional LegacyRole enum + User.legacyRole +-- column now that every controller runs on @RequirePermission and no code +-- reads the legacy field anymore (see permission-catalog.ts / roles module). + +BEGIN; + +ALTER TABLE "User" DROP COLUMN "legacyRole"; +DROP TYPE "LegacyRole"; + +COMMIT; diff --git a/ApiCore-Backend/prisma/schema.prisma b/ApiCore-Backend/prisma/schema.prisma index 865b241..61f1136 100644 --- a/ApiCore-Backend/prisma/schema.prisma +++ b/ApiCore-Backend/prisma/schema.prisma @@ -16,17 +16,6 @@ datasource db { // Benutzer / Auth / Audit (Abschnitt 3, 14, 15, 16) // --------------------------------------------------------------------------- -// Superseded by the Role/Permission/RolePermission models below. Kept only -// as a transitional column (User.legacyRole) until the RBAC rollout's -// Phase 3 drops it — see docs/lastenheft.md RBAC migration notes. -enum LegacyRole { - ADMIN - BEEKEEPER - STAFF - READONLY - API_CLIENT -} - enum RoleStatus { ACTIVE INACTIVE @@ -80,7 +69,6 @@ model User { email String @unique passwordHash String name String - legacyRole LegacyRole @default(STAFF) roleId String role Role @relation(fields: [roleId], references: [id]) isActive Boolean @default(true) diff --git a/ApiCore-Backend/prisma/seed.ts b/ApiCore-Backend/prisma/seed.ts index 23ef1b9..d11d5f1 100644 --- a/ApiCore-Backend/prisma/seed.ts +++ b/ApiCore-Backend/prisma/seed.ts @@ -30,7 +30,6 @@ async function main(): Promise { email, passwordHash, name: 'Administrator', - legacyRole: 'ADMIN', roleId: adminRole.id, }, }); diff --git a/ApiCore-Backend/src/app.module.ts b/ApiCore-Backend/src/app.module.ts index 4093a22..c737c6d 100644 --- a/ApiCore-Backend/src/app.module.ts +++ b/ApiCore-Backend/src/app.module.ts @@ -38,7 +38,6 @@ import { PublicModule } from './public/public.module'; import { TaskTemplatesModule } from './task-templates/task-templates.module'; import { SwarmControlModule } from './swarm-control/swarm-control.module'; import { JwtAuthGuard } from './auth/guards/jwt-auth.guard'; -import { RolesGuard } from './auth/guards/roles.guard'; import { PermissionsGuard } from './auth/guards/permissions.guard'; import { PermissionsModule } from './auth/permissions/permissions.module'; @@ -86,7 +85,6 @@ import { PermissionsModule } from './auth/permissions/permissions.module'; controllers: [], providers: [ { provide: APP_GUARD, useClass: JwtAuthGuard }, - { provide: APP_GUARD, useClass: RolesGuard }, { provide: APP_GUARD, useClass: PermissionsGuard }, { provide: APP_GUARD, useClass: ThrottlerGuard }, ], diff --git a/ApiCore-Backend/src/auth/auth.service.spec.ts b/ApiCore-Backend/src/auth/auth.service.spec.ts index 8ea6793..4916adc 100644 --- a/ApiCore-Backend/src/auth/auth.service.spec.ts +++ b/ApiCore-Backend/src/auth/auth.service.spec.ts @@ -23,7 +23,6 @@ describe('AuthService', () => { const activeUser = { id: 'user-1', email: 'user@example.com', - legacyRole: 'STAFF', isActive: true, passwordHash: '', }; diff --git a/ApiCore-Backend/src/auth/auth.service.ts b/ApiCore-Backend/src/auth/auth.service.ts index 704be44..37ae47c 100644 --- a/ApiCore-Backend/src/auth/auth.service.ts +++ b/ApiCore-Backend/src/auth/auth.service.ts @@ -18,7 +18,6 @@ export interface CurrentUserProfile { id: string; email: string; name: string; - role: string; roleKey: string; roleName: string; permissions: string[]; @@ -46,7 +45,6 @@ export class AuthService { id: user.id, email: user.email, name: user.name, - role: user.legacyRole, roleKey: user.role.key, roleName: user.role.name, permissions: Array.from(permissions), @@ -62,7 +60,7 @@ export class AuthService { parseDurationToMs(process.env.JWT_ACCESS_EXPIRES_IN ?? '15m') / 1000, ); return this.jwtService.sign( - { sub: user.id, email: user.email, role: user.role }, + { sub: user.id, email: user.email }, { secret: process.env.JWT_ACCESS_SECRET, expiresIn: expiresInSeconds, @@ -106,7 +104,6 @@ export class AuthService { const authenticatedUser: AuthenticatedUser = { id: user.id, email: user.email, - role: user.legacyRole, }; const accessToken = this.signAccessToken(authenticatedUser); const refreshToken = await this.issueRefreshToken(user.id); @@ -138,7 +135,6 @@ export class AuthService { const authenticatedUser: AuthenticatedUser = { id: user.id, email: user.email, - role: user.legacyRole, }; const accessToken = this.signAccessToken(authenticatedUser); const newRefreshToken = await this.issueRefreshToken(user.id); diff --git a/ApiCore-Backend/src/auth/decorators/roles.decorator.ts b/ApiCore-Backend/src/auth/decorators/roles.decorator.ts deleted file mode 100644 index be18bfb..0000000 --- a/ApiCore-Backend/src/auth/decorators/roles.decorator.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { LegacyRole as Role } from '../../../generated/prisma'; - -export const ROLES_KEY = 'roles'; -export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); diff --git a/ApiCore-Backend/src/auth/guards/roles.guard.spec.ts b/ApiCore-Backend/src/auth/guards/roles.guard.spec.ts deleted file mode 100644 index 4d36d0a..0000000 --- a/ApiCore-Backend/src/auth/guards/roles.guard.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ExecutionContext } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { RolesGuard } from './roles.guard'; -import { LegacyRole as Role } from '../../../generated/prisma'; - -function createContext(user: { role: Role } | undefined): ExecutionContext { - return { - getHandler: () => ({}), - getClass: () => ({}), - switchToHttp: () => ({ - getRequest: () => ({ user }), - }), - } as unknown as ExecutionContext; -} - -describe('RolesGuard', () => { - it('allows access when no roles are required', () => { - const reflector = { - getAllAndOverride: () => undefined, - } as unknown as Reflector; - const guard = new RolesGuard(reflector); - expect(guard.canActivate(createContext(undefined))).toBe(true); - }); - - it('denies access when the user role does not match', () => { - const reflector = { - getAllAndOverride: () => [Role.ADMIN], - } as unknown as Reflector; - const guard = new RolesGuard(reflector); - expect(guard.canActivate(createContext({ role: Role.STAFF }))).toBe(false); - }); - - it('allows access when the user role matches', () => { - const reflector = { - getAllAndOverride: () => [Role.ADMIN], - } as unknown as Reflector; - const guard = new RolesGuard(reflector); - expect(guard.canActivate(createContext({ role: Role.ADMIN }))).toBe(true); - }); - - it('denies access when no user is attached to the request', () => { - const reflector = { - getAllAndOverride: () => [Role.ADMIN], - } as unknown as Reflector; - const guard = new RolesGuard(reflector); - expect(guard.canActivate(createContext(undefined))).toBe(false); - }); -}); diff --git a/ApiCore-Backend/src/auth/guards/roles.guard.ts b/ApiCore-Backend/src/auth/guards/roles.guard.ts deleted file mode 100644 index effd267..0000000 --- a/ApiCore-Backend/src/auth/guards/roles.guard.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { LegacyRole as Role } from '../../../generated/prisma'; -import { ROLES_KEY } from '../decorators/roles.decorator'; -import { AuthenticatedUser } from '../types/authenticated-user'; - -interface RequestWithUser { - user?: AuthenticatedUser; -} - -@Injectable() -export class RolesGuard implements CanActivate { - constructor(private readonly reflector: Reflector) {} - - canActivate(context: ExecutionContext): boolean { - const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ - context.getHandler(), - context.getClass(), - ]); - if (!requiredRoles || requiredRoles.length === 0) { - return true; - } - const request = context.switchToHttp().getRequest(); - const user = request.user; - return !!user && requiredRoles.includes(user.role); - } -} diff --git a/ApiCore-Backend/src/auth/strategies/jwt.strategy.ts b/ApiCore-Backend/src/auth/strategies/jwt.strategy.ts index 9d4e92f..e7aec45 100644 --- a/ApiCore-Backend/src/auth/strategies/jwt.strategy.ts +++ b/ApiCore-Backend/src/auth/strategies/jwt.strategy.ts @@ -6,7 +6,6 @@ import { AuthenticatedUser } from '../types/authenticated-user'; interface AccessTokenPayload { sub: string; email: string; - role: AuthenticatedUser['role']; } @Injectable() @@ -20,6 +19,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) { } validate(payload: AccessTokenPayload): AuthenticatedUser { - return { id: payload.sub, email: payload.email, role: payload.role }; + return { id: payload.sub, email: payload.email }; } } diff --git a/ApiCore-Backend/src/auth/types/authenticated-user.ts b/ApiCore-Backend/src/auth/types/authenticated-user.ts index a8f0b39..fd99764 100644 --- a/ApiCore-Backend/src/auth/types/authenticated-user.ts +++ b/ApiCore-Backend/src/auth/types/authenticated-user.ts @@ -1,7 +1,4 @@ -import { LegacyRole as Role } from '../../../generated/prisma'; - export interface AuthenticatedUser { id: string; email: string; - role: Role; } diff --git a/ApiCore-Backend/src/users/dto/create-user.dto.ts b/ApiCore-Backend/src/users/dto/create-user.dto.ts index 2f9ddc7..81fae08 100644 --- a/ApiCore-Backend/src/users/dto/create-user.dto.ts +++ b/ApiCore-Backend/src/users/dto/create-user.dto.ts @@ -1,6 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsEnum, IsString, MinLength } from 'class-validator'; -import { LegacyRole as Role } from '../../../generated/prisma'; +import { IsEmail, IsString, MinLength } from 'class-validator'; export class CreateUserDto { @ApiProperty() @@ -16,7 +15,8 @@ export class CreateUserDto { @IsString() name!: string; - @ApiProperty({ enum: Role }) - @IsEnum(Role) - role!: Role; + @ApiProperty({ description: 'Id of an active Role' }) + @IsString() + @MinLength(1) + roleId!: string; } diff --git a/ApiCore-Backend/src/users/dto/update-user.dto.ts b/ApiCore-Backend/src/users/dto/update-user.dto.ts index b7f127e..9f236b5 100644 --- a/ApiCore-Backend/src/users/dto/update-user.dto.ts +++ b/ApiCore-Backend/src/users/dto/update-user.dto.ts @@ -1,6 +1,5 @@ import { ApiPropertyOptional } from '@nestjs/swagger'; -import { IsEnum, IsOptional, IsString } from 'class-validator'; -import { LegacyRole as Role } from '../../../generated/prisma'; +import { IsOptional, IsString, MinLength } from 'class-validator'; export class UpdateUserDto { @ApiPropertyOptional() @@ -8,8 +7,9 @@ export class UpdateUserDto { @IsString() name?: string; - @ApiPropertyOptional({ enum: Role }) + @ApiPropertyOptional({ description: 'Id of an active Role' }) @IsOptional() - @IsEnum(Role) - role?: Role; + @IsString() + @MinLength(1) + roleId?: string; } diff --git a/ApiCore-Backend/src/users/users.service.spec.ts b/ApiCore-Backend/src/users/users.service.spec.ts index 9a5bf2b..461d428 100644 --- a/ApiCore-Backend/src/users/users.service.spec.ts +++ b/ApiCore-Backend/src/users/users.service.spec.ts @@ -13,18 +13,25 @@ describe('UsersService', () => { update: jest.Mock; }; role: { - findUniqueOrThrow: jest.Mock; + findUnique: jest.Mock; }; }; let auditLog: { record: jest.Mock }; + const staffRole = { + id: 'role-staff', + key: 'STAFF', + name: 'Mitarbeiter', + status: 'ACTIVE', + }; + const existingUser = { id: 'user-1', email: 'existing@example.com', passwordHash: 'hashed', name: 'Existing User', - legacyRole: 'STAFF', - roleId: 'role-staff', + roleId: staffRole.id, + role: staffRole, isActive: true, createdAt: new Date(), deactivatedAt: null, @@ -39,9 +46,7 @@ describe('UsersService', () => { update: jest.fn(), }, role: { - findUniqueOrThrow: jest - .fn() - .mockResolvedValue({ id: 'role-staff', key: 'STAFF' }), + findUnique: jest.fn().mockResolvedValue(staffRole), }, }; auditLog = { record: jest.fn().mockResolvedValue(undefined) }; @@ -61,7 +66,9 @@ describe('UsersService', () => { id: existingUser.id, email: existingUser.email, name: existingUser.name, - role: existingUser.legacyRole, + roleId: staffRole.id, + roleKey: staffRole.key, + roleName: staffRole.name, isActive: existingUser.isActive, createdAt: existingUser.createdAt, deactivatedAt: existingUser.deactivatedAt, @@ -84,7 +91,36 @@ describe('UsersService', () => { email: existingUser.email, password: 'password123', name: 'New', - role: 'STAFF', + roleId: staffRole.id, + }), + ).rejects.toBeInstanceOf(ConflictException); + }); + + it('create throws NotFoundException when the role does not exist', async () => { + prisma.user.findUnique.mockResolvedValue(null); + prisma.role.findUnique.mockResolvedValue(null); + await expect( + usersService.create({ + email: 'new@example.com', + password: 'password123', + name: 'New User', + roleId: 'missing-role', + }), + ).rejects.toBeInstanceOf(NotFoundException); + }); + + it('create throws ConflictException when the role is not active', async () => { + prisma.user.findUnique.mockResolvedValue(null); + prisma.role.findUnique.mockResolvedValue({ + ...staffRole, + status: 'INACTIVE', + }); + await expect( + usersService.create({ + email: 'new@example.com', + password: 'password123', + name: 'New User', + roleId: staffRole.id, }), ).rejects.toBeInstanceOf(ConflictException); }); @@ -101,7 +137,7 @@ describe('UsersService', () => { email: 'new@example.com', password: 'password123', name: 'New User', - role: 'STAFF', + roleId: staffRole.id, }); expect(result.id).toBe('user-2'); diff --git a/ApiCore-Backend/src/users/users.service.ts b/ApiCore-Backend/src/users/users.service.ts index 80aa0d2..1177ed8 100644 --- a/ApiCore-Backend/src/users/users.service.ts +++ b/ApiCore-Backend/src/users/users.service.ts @@ -6,17 +6,25 @@ import { import * as bcrypt from 'bcryptjs'; import { PrismaService } from '../prisma/prisma.service'; import { AuditLogService } from '../audit/audit-log.service'; -import { User } from '../../generated/prisma'; +import { Prisma } from '../../generated/prisma'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; const BCRYPT_SALT_ROUNDS = 12; +const USER_WITH_ROLE = { + include: { role: true }, +} satisfies { include: Prisma.UserInclude }; + +type UserWithRole = Prisma.UserGetPayload; + export interface UserSummary { id: string; email: string; name: string; - role: string; + roleId: string; + roleKey: string; + roleName: string; isActive: boolean; createdAt: Date; deactivatedAt: Date | null; @@ -29,41 +37,43 @@ export class UsersService { private readonly auditLog: AuditLogService, ) {} - private toSummary(user: User): UserSummary { - const { id, email, name, legacyRole, isActive, createdAt, deactivatedAt } = - user; + private toSummary(user: UserWithRole): UserSummary { return { - id, - email, - name, - role: legacyRole, - isActive, - createdAt, - deactivatedAt, + id: user.id, + email: user.email, + name: user.name, + roleId: user.roleId, + roleKey: user.role.key, + roleName: user.role.name, + isActive: user.isActive, + createdAt: user.createdAt, + deactivatedAt: user.deactivatedAt, }; } - /** - * Resolves the RBAC roleId that corresponds to a legacy role key, so - * User.roleId stays in sync while callers still speak in terms of the - * legacy enum (Phase 1 of the RBAC rollout; see Role/Permission models). - */ - private async resolveRoleId(legacyRole: string): Promise { - const role = await this.prisma.role.findUniqueOrThrow({ - where: { key: legacyRole }, - }); - return role.id; + private async assertActiveRole(roleId: string): Promise { + const role = await this.prisma.role.findUnique({ where: { id: roleId } }); + if (!role) { + throw new NotFoundException('Role not found'); + } + if (role.status !== 'ACTIVE') { + throw new ConflictException('Role is not active'); + } } async findAll(): Promise { const users = await this.prisma.user.findMany({ + ...USER_WITH_ROLE, orderBy: { createdAt: 'asc' }, }); return users.map((user) => this.toSummary(user)); } - async findById(id: string): Promise { - const user = await this.prisma.user.findUnique({ where: { id } }); + async findById(id: string): Promise { + const user = await this.prisma.user.findUnique({ + where: { id }, + ...USER_WITH_ROLE, + }); if (!user) { throw new NotFoundException('User not found'); } @@ -74,8 +84,8 @@ export class UsersService { return this.toSummary(await this.findById(id)); } - async findByEmail(email: string): Promise { - return this.prisma.user.findUnique({ where: { email } }); + async findByEmail(email: string): Promise { + return this.prisma.user.findUnique({ where: { email }, ...USER_WITH_ROLE }); } async create( @@ -86,16 +96,16 @@ export class UsersService { if (existing) { throw new ConflictException('Email already in use'); } + await this.assertActiveRole(dto.roleId); const passwordHash = await bcrypt.hash(dto.password, BCRYPT_SALT_ROUNDS); - const roleId = await this.resolveRoleId(dto.role); const user = await this.prisma.user.create({ data: { email: dto.email, passwordHash, name: dto.name, - legacyRole: dto.role, - roleId, + roleId: dto.roleId, }, + ...USER_WITH_ROLE, }); await this.auditLog.record({ entityType: 'User', @@ -113,14 +123,16 @@ export class UsersService { actingUserId?: string, ): Promise { const before = await this.findById(id); - const roleId = dto.role ? await this.resolveRoleId(dto.role) : undefined; + if (dto.roleId) { + await this.assertActiveRole(dto.roleId); + } const user = await this.prisma.user.update({ where: { id }, data: { name: dto.name, - legacyRole: dto.role, - roleId, + roleId: dto.roleId, }, + ...USER_WITH_ROLE, }); await this.auditLog.record({ entityType: 'User', @@ -138,6 +150,7 @@ export class UsersService { const user = await this.prisma.user.update({ where: { id }, data: { isActive: false, deactivatedAt: new Date() }, + ...USER_WITH_ROLE, }); await this.auditLog.record({ entityType: 'User', @@ -158,6 +171,7 @@ export class UsersService { const user = await this.prisma.user.update({ where: { id }, data: { isActive: true, deactivatedAt: null }, + ...USER_WITH_ROLE, }); await this.auditLog.record({ entityType: 'User', diff --git a/ApiCore-Backend/test/apiaries.e2e-spec.ts b/ApiCore-Backend/test/apiaries.e2e-spec.ts index a288061..3077340 100644 --- a/ApiCore-Backend/test/apiaries.e2e-spec.ts +++ b/ApiCore-Backend/test/apiaries.e2e-spec.ts @@ -34,7 +34,6 @@ describe('Apiaries (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -44,7 +43,6 @@ describe('Apiaries (e2e)', () => { email: readonlyEmail, passwordHash, name: 'Readonly', - legacyRole: 'READONLY', role: { connect: { key: 'READONLY' } }, }, }); diff --git a/ApiCore-Backend/test/audit-log.e2e-spec.ts b/ApiCore-Backend/test/audit-log.e2e-spec.ts index 2dbf223..ababb42 100644 --- a/ApiCore-Backend/test/audit-log.e2e-spec.ts +++ b/ApiCore-Backend/test/audit-log.e2e-spec.ts @@ -34,7 +34,6 @@ describe('Audit log (e2e)', () => { email: adminEmail, passwordHash, name: 'Admin', - legacyRole: 'ADMIN', role: { connect: { key: 'ADMIN' } }, }, }); @@ -44,7 +43,6 @@ describe('Audit log (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); diff --git a/ApiCore-Backend/test/auth-throttle.e2e-spec.ts b/ApiCore-Backend/test/auth-throttle.e2e-spec.ts index 5dd9bfc..7392f78 100644 --- a/ApiCore-Backend/test/auth-throttle.e2e-spec.ts +++ b/ApiCore-Backend/test/auth-throttle.e2e-spec.ts @@ -24,7 +24,6 @@ describe('Auth login throttling (e2e)', () => { email, passwordHash, name: 'Throttle E2E User', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/auth.e2e-spec.ts b/ApiCore-Backend/test/auth.e2e-spec.ts index b7f6f70..213ffbc 100644 --- a/ApiCore-Backend/test/auth.e2e-spec.ts +++ b/ApiCore-Backend/test/auth.e2e-spec.ts @@ -23,7 +23,6 @@ describe('Auth (e2e)', () => { email, passwordHash, name: 'Auth E2E User', - legacyRole: 'ADMIN', role: { connect: { key: 'ADMIN' } }, }, }); diff --git a/ApiCore-Backend/test/authorization-coverage.e2e-spec.ts b/ApiCore-Backend/test/authorization-coverage.e2e-spec.ts index c643292..d12ffe1 100644 --- a/ApiCore-Backend/test/authorization-coverage.e2e-spec.ts +++ b/ApiCore-Backend/test/authorization-coverage.e2e-spec.ts @@ -8,7 +8,6 @@ import { import { PATH_METADATA } from '@nestjs/common/constants'; import { AppModule } from '../src/app.module'; import { IS_PUBLIC_KEY } from '../src/auth/decorators/public.decorator'; -import { ROLES_KEY } from '../src/auth/decorators/roles.decorator'; import { REQUIRE_PERMISSION_KEY } from '../src/auth/decorators/require-permission.decorator'; import { PERMISSION_CATALOG } from '../src/auth/permissions/permission-catalog'; @@ -22,7 +21,7 @@ const ALLOWLIST = new Set([ ]); describe('Authorization coverage (e2e)', () => { - it('every non-public route requires a role or a catalog-valid permission', async () => { + it('every non-public route requires a catalog-valid permission', async () => { const moduleRef = await Test.createTestingModule({ imports: [AppModule, DiscoveryModule], }).compile(); @@ -64,19 +63,14 @@ describe('Authorization coverage (e2e)', () => { continue; } - const roles = reflector.getAllAndOverride(ROLES_KEY, [ - handler, - constructor, - ]); const permissions = reflector.getAllAndOverride( REQUIRE_PERMISSION_KEY, [handler, constructor], ); - const hasRoles = Array.isArray(roles) && roles.length > 0; const hasPermissions = Array.isArray(permissions) && permissions.length > 0; - if (!hasRoles && !hasPermissions) { + if (!hasPermissions) { uncoveredRoutes.push(routeKey); } if (hasPermissions) { diff --git a/ApiCore-Backend/test/automation.e2e-spec.ts b/ApiCore-Backend/test/automation.e2e-spec.ts index 0162502..3486e5a 100644 --- a/ApiCore-Backend/test/automation.e2e-spec.ts +++ b/ApiCore-Backend/test/automation.e2e-spec.ts @@ -38,7 +38,6 @@ describe('Automation rules (e2e)', () => { email: adminEmail, passwordHash, name: 'Automation E2E Admin', - legacyRole: 'ADMIN', role: { connect: { key: 'ADMIN' } }, }, }); @@ -48,7 +47,6 @@ describe('Automation rules (e2e)', () => { email: staffEmail, passwordHash, name: 'Automation E2E Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/bottling.e2e-spec.ts b/ApiCore-Backend/test/bottling.e2e-spec.ts index 5bd6962..1052a69 100644 --- a/ApiCore-Backend/test/bottling.e2e-spec.ts +++ b/ApiCore-Backend/test/bottling.e2e-spec.ts @@ -41,7 +41,6 @@ describe('Bottling (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); diff --git a/ApiCore-Backend/test/calendar.e2e-spec.ts b/ApiCore-Backend/test/calendar.e2e-spec.ts index f508f34..b950fad 100644 --- a/ApiCore-Backend/test/calendar.e2e-spec.ts +++ b/ApiCore-Backend/test/calendar.e2e-spec.ts @@ -32,7 +32,6 @@ describe('Calendar (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); diff --git a/ApiCore-Backend/test/colonies.e2e-spec.ts b/ApiCore-Backend/test/colonies.e2e-spec.ts index 3f66569..9a325c2 100644 --- a/ApiCore-Backend/test/colonies.e2e-spec.ts +++ b/ApiCore-Backend/test/colonies.e2e-spec.ts @@ -46,7 +46,6 @@ describe('Colonies (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -56,7 +55,6 @@ describe('Colonies (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/dashboard.e2e-spec.ts b/ApiCore-Backend/test/dashboard.e2e-spec.ts index 3465fa8..b38effd 100644 --- a/ApiCore-Backend/test/dashboard.e2e-spec.ts +++ b/ApiCore-Backend/test/dashboard.e2e-spec.ts @@ -37,7 +37,6 @@ describe('Dashboard (e2e)', () => { email: readonlyEmail, passwordHash, name: 'Readonly', - legacyRole: 'READONLY', role: { connect: { key: 'READONLY' } }, }, }); diff --git a/ApiCore-Backend/test/documents.e2e-spec.ts b/ApiCore-Backend/test/documents.e2e-spec.ts index c933440..5ee872e 100644 --- a/ApiCore-Backend/test/documents.e2e-spec.ts +++ b/ApiCore-Backend/test/documents.e2e-spec.ts @@ -41,7 +41,6 @@ describe('Documents (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Documents E2E Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -51,7 +50,6 @@ describe('Documents (e2e)', () => { email: readonlyEmail, passwordHash, name: 'Documents E2E Readonly', - legacyRole: 'READONLY', role: { connect: { key: 'READONLY' } }, }, }); diff --git a/ApiCore-Backend/test/equipment.e2e-spec.ts b/ApiCore-Backend/test/equipment.e2e-spec.ts index c14068e..7ac60dd 100644 --- a/ApiCore-Backend/test/equipment.e2e-spec.ts +++ b/ApiCore-Backend/test/equipment.e2e-spec.ts @@ -34,7 +34,6 @@ describe('Equipment (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -44,7 +43,6 @@ describe('Equipment (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/export.e2e-spec.ts b/ApiCore-Backend/test/export.e2e-spec.ts index cae7602..cf91565 100644 --- a/ApiCore-Backend/test/export.e2e-spec.ts +++ b/ApiCore-Backend/test/export.e2e-spec.ts @@ -29,7 +29,6 @@ describe('Export (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); diff --git a/ApiCore-Backend/test/extractions.e2e-spec.ts b/ApiCore-Backend/test/extractions.e2e-spec.ts index c91114d..f91772b 100644 --- a/ApiCore-Backend/test/extractions.e2e-spec.ts +++ b/ApiCore-Backend/test/extractions.e2e-spec.ts @@ -36,7 +36,6 @@ describe('Extractions (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -46,7 +45,6 @@ describe('Extractions (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/feedings.e2e-spec.ts b/ApiCore-Backend/test/feedings.e2e-spec.ts index f56833b..57b4796 100644 --- a/ApiCore-Backend/test/feedings.e2e-spec.ts +++ b/ApiCore-Backend/test/feedings.e2e-spec.ts @@ -40,7 +40,6 @@ describe('Feedings (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); @@ -50,7 +49,6 @@ describe('Feedings (e2e)', () => { email: readonlyEmail, passwordHash, name: 'Readonly', - legacyRole: 'READONLY', role: { connect: { key: 'READONLY' } }, }, }); diff --git a/ApiCore-Backend/test/harvests.e2e-spec.ts b/ApiCore-Backend/test/harvests.e2e-spec.ts index 936b6e6..3bf3b65 100644 --- a/ApiCore-Backend/test/harvests.e2e-spec.ts +++ b/ApiCore-Backend/test/harvests.e2e-spec.ts @@ -37,7 +37,6 @@ describe('Harvests (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); @@ -47,7 +46,6 @@ describe('Harvests (e2e)', () => { email: readonlyEmail, passwordHash, name: 'Readonly', - legacyRole: 'READONLY', role: { connect: { key: 'READONLY' } }, }, }); diff --git a/ApiCore-Backend/test/honey-batches.e2e-spec.ts b/ApiCore-Backend/test/honey-batches.e2e-spec.ts index 401a065..ec2359f 100644 --- a/ApiCore-Backend/test/honey-batches.e2e-spec.ts +++ b/ApiCore-Backend/test/honey-batches.e2e-spec.ts @@ -53,7 +53,6 @@ describe('Honey batches (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -63,7 +62,6 @@ describe('Honey batches (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/import.e2e-spec.ts b/ApiCore-Backend/test/import.e2e-spec.ts index c5f595a..339524b 100644 --- a/ApiCore-Backend/test/import.e2e-spec.ts +++ b/ApiCore-Backend/test/import.e2e-spec.ts @@ -49,7 +49,6 @@ describe('Import (e2e)', () => { email: adminEmail, passwordHash, name: 'Import E2E Admin', - legacyRole: 'ADMIN', role: { connect: { key: 'ADMIN' } }, }, }); @@ -59,7 +58,6 @@ describe('Import (e2e)', () => { email: staffEmail, passwordHash, name: 'Import E2E Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/inspections.e2e-spec.ts b/ApiCore-Backend/test/inspections.e2e-spec.ts index 178eb67..7b02e4a 100644 --- a/ApiCore-Backend/test/inspections.e2e-spec.ts +++ b/ApiCore-Backend/test/inspections.e2e-spec.ts @@ -41,7 +41,6 @@ describe('Inspections (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); @@ -51,7 +50,6 @@ describe('Inspections (e2e)', () => { email: readonlyEmail, passwordHash, name: 'Readonly', - legacyRole: 'READONLY', role: { connect: { key: 'READONLY' } }, }, }); diff --git a/ApiCore-Backend/test/notifications.e2e-spec.ts b/ApiCore-Backend/test/notifications.e2e-spec.ts index d569749..86269d1 100644 --- a/ApiCore-Backend/test/notifications.e2e-spec.ts +++ b/ApiCore-Backend/test/notifications.e2e-spec.ts @@ -34,7 +34,6 @@ describe('Notifications (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -44,7 +43,6 @@ describe('Notifications (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/products.e2e-spec.ts b/ApiCore-Backend/test/products.e2e-spec.ts index 37f1ceb..944f6d3 100644 --- a/ApiCore-Backend/test/products.e2e-spec.ts +++ b/ApiCore-Backend/test/products.e2e-spec.ts @@ -34,7 +34,6 @@ describe('Products (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -44,7 +43,6 @@ describe('Products (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/public.e2e-spec.ts b/ApiCore-Backend/test/public.e2e-spec.ts index dd26fff..e5b5b4c 100644 --- a/ApiCore-Backend/test/public.e2e-spec.ts +++ b/ApiCore-Backend/test/public.e2e-spec.ts @@ -42,7 +42,6 @@ describe('Public batch origin page (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); diff --git a/ApiCore-Backend/test/qr.e2e-spec.ts b/ApiCore-Backend/test/qr.e2e-spec.ts index 78007df..3b735d9 100644 --- a/ApiCore-Backend/test/qr.e2e-spec.ts +++ b/ApiCore-Backend/test/qr.e2e-spec.ts @@ -26,7 +26,6 @@ describe('QR codes (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); diff --git a/ApiCore-Backend/test/queens.e2e-spec.ts b/ApiCore-Backend/test/queens.e2e-spec.ts index 5546173..1cda2b9 100644 --- a/ApiCore-Backend/test/queens.e2e-spec.ts +++ b/ApiCore-Backend/test/queens.e2e-spec.ts @@ -33,7 +33,6 @@ describe('Queens (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); diff --git a/ApiCore-Backend/test/roles.e2e-spec.ts b/ApiCore-Backend/test/roles.e2e-spec.ts index bb2449d..04adfbb 100644 --- a/ApiCore-Backend/test/roles.e2e-spec.ts +++ b/ApiCore-Backend/test/roles.e2e-spec.ts @@ -19,7 +19,6 @@ interface RoleBody { interface MeBody { id: string; email: string; - role: string; roleKey: string; permissions: string[]; } @@ -57,7 +56,6 @@ describe('Roles (e2e)', () => { email: adminEmail, passwordHash, name: 'Roles E2E Admin', - legacyRole: 'ADMIN', role: { connect: { key: 'ADMIN' } }, }, }); @@ -67,7 +65,6 @@ describe('Roles (e2e)', () => { email: staffEmail, passwordHash, name: 'Roles E2E Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/search.e2e-spec.ts b/ApiCore-Backend/test/search.e2e-spec.ts index e657299..cb541cc 100644 --- a/ApiCore-Backend/test/search.e2e-spec.ts +++ b/ApiCore-Backend/test/search.e2e-spec.ts @@ -39,7 +39,6 @@ describe('Search and filters (e2e)', () => { email: readonlyEmail, passwordHash, name: 'Readonly', - legacyRole: 'READONLY', role: { connect: { key: 'READONLY' } }, }, }); diff --git a/ApiCore-Backend/test/statistics.e2e-spec.ts b/ApiCore-Backend/test/statistics.e2e-spec.ts index 122e826..3c3e67b 100644 --- a/ApiCore-Backend/test/statistics.e2e-spec.ts +++ b/ApiCore-Backend/test/statistics.e2e-spec.ts @@ -60,7 +60,6 @@ describe('Statistics (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); diff --git a/ApiCore-Backend/test/storage.e2e-spec.ts b/ApiCore-Backend/test/storage.e2e-spec.ts index a81b85e..813bbd7 100644 --- a/ApiCore-Backend/test/storage.e2e-spec.ts +++ b/ApiCore-Backend/test/storage.e2e-spec.ts @@ -33,7 +33,6 @@ describe('Storage (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -43,7 +42,6 @@ describe('Storage (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/swarm-control.e2e-spec.ts b/ApiCore-Backend/test/swarm-control.e2e-spec.ts index 016aeca..5f3c58e 100644 --- a/ApiCore-Backend/test/swarm-control.e2e-spec.ts +++ b/ApiCore-Backend/test/swarm-control.e2e-spec.ts @@ -36,7 +36,6 @@ describe('Swarm control checks (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -46,7 +45,6 @@ describe('Swarm control checks (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/task-templates.e2e-spec.ts b/ApiCore-Backend/test/task-templates.e2e-spec.ts index fd2adfd..14a76e0 100644 --- a/ApiCore-Backend/test/task-templates.e2e-spec.ts +++ b/ApiCore-Backend/test/task-templates.e2e-spec.ts @@ -31,7 +31,6 @@ describe('TaskTemplates (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); diff --git a/ApiCore-Backend/test/tasks.e2e-spec.ts b/ApiCore-Backend/test/tasks.e2e-spec.ts index 0c618de..dc8161f 100644 --- a/ApiCore-Backend/test/tasks.e2e-spec.ts +++ b/ApiCore-Backend/test/tasks.e2e-spec.ts @@ -34,7 +34,6 @@ describe('Tasks (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); @@ -44,7 +43,6 @@ describe('Tasks (e2e)', () => { email: readonlyEmail, passwordHash, name: 'Readonly', - legacyRole: 'READONLY', role: { connect: { key: 'READONLY' } }, }, }); diff --git a/ApiCore-Backend/test/treatments.e2e-spec.ts b/ApiCore-Backend/test/treatments.e2e-spec.ts index a1be7c3..3c948f3 100644 --- a/ApiCore-Backend/test/treatments.e2e-spec.ts +++ b/ApiCore-Backend/test/treatments.e2e-spec.ts @@ -35,7 +35,6 @@ describe('Treatments (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -45,7 +44,6 @@ describe('Treatments (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/users.e2e-spec.ts b/ApiCore-Backend/test/users.e2e-spec.ts index a5d1fbe..eea030a 100644 --- a/ApiCore-Backend/test/users.e2e-spec.ts +++ b/ApiCore-Backend/test/users.e2e-spec.ts @@ -17,18 +17,23 @@ describe('Users (e2e)', () => { let adminAccessToken: string; let staffAccessToken: string; let createdUserId: string; + let beekeeperRoleId: string; beforeAll(async () => { app = await createTestApp(); prisma = app.get(PrismaService); + const beekeeperRole = await prisma.role.findUniqueOrThrow({ + where: { key: 'BEEKEEPER' }, + }); + beekeeperRoleId = beekeeperRole.id; + const passwordHash = await bcrypt.hash(password, 12); const admin = await prisma.user.create({ data: { email: adminEmail, passwordHash, name: 'Users E2E Admin', - legacyRole: 'ADMIN', role: { connect: { key: 'ADMIN' } }, }, }); @@ -38,7 +43,6 @@ describe('Users (e2e)', () => { email: staffEmail, passwordHash, name: 'Users E2E Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); @@ -76,7 +80,7 @@ describe('Users (e2e)', () => { email: `users-e2e-created-${Date.now()}@example.com`, password: 'AnotherSecret123!', name: 'Created via e2e', - role: 'BEEKEEPER', + roleId: beekeeperRoleId, }) .expect(201); const created = response.body as UserBody & { passwordHash?: string }; diff --git a/ApiCore-Backend/test/utils/api.ts b/ApiCore-Backend/test/utils/api.ts index 392e780..e96d6fd 100644 --- a/ApiCore-Backend/test/utils/api.ts +++ b/ApiCore-Backend/test/utils/api.ts @@ -11,7 +11,9 @@ export interface UserBody { id: string; email: string; name: string; - role: string; + roleId: string; + roleKey: string; + roleName: string; isActive: boolean; createdAt: string; deactivatedAt: string | null; diff --git a/ApiCore-Backend/test/varroa.e2e-spec.ts b/ApiCore-Backend/test/varroa.e2e-spec.ts index a0c4077..45a8947 100644 --- a/ApiCore-Backend/test/varroa.e2e-spec.ts +++ b/ApiCore-Backend/test/varroa.e2e-spec.ts @@ -35,7 +35,6 @@ describe('Varroa measurements (e2e)', () => { email: beekeeperEmail, passwordHash, name: 'Beekeeper', - legacyRole: 'BEEKEEPER', role: { connect: { key: 'BEEKEEPER' } }, }, }); @@ -45,7 +44,6 @@ describe('Varroa measurements (e2e)', () => { email: staffEmail, passwordHash, name: 'Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); diff --git a/ApiCore-Backend/test/webhooks.e2e-spec.ts b/ApiCore-Backend/test/webhooks.e2e-spec.ts index caf5f49..fd1058d 100644 --- a/ApiCore-Backend/test/webhooks.e2e-spec.ts +++ b/ApiCore-Backend/test/webhooks.e2e-spec.ts @@ -48,7 +48,6 @@ describe('Webhooks (e2e)', () => { email: adminEmail, passwordHash, name: 'Webhooks E2E Admin', - legacyRole: 'ADMIN', role: { connect: { key: 'ADMIN' } }, }, }); @@ -58,7 +57,6 @@ describe('Webhooks (e2e)', () => { email: staffEmail, passwordHash, name: 'Webhooks E2E Staff', - legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, });