diff --git a/apps/services/auth/admin-api/src/app/v2/clients/me-clients.controller.ts b/apps/services/auth/admin-api/src/app/v2/clients/me-clients.controller.ts index 5bb808ac4fd0..649e2ba61991 100644 --- a/apps/services/auth/admin-api/src/app/v2/clients/me-clients.controller.ts +++ b/apps/services/auth/admin-api/src/app/v2/clients/me-clients.controller.ts @@ -7,6 +7,7 @@ import { Post, UseGuards, Delete, + Query, } from '@nestjs/common' import { ApiSecurity, ApiTags } from '@nestjs/swagger' @@ -74,7 +75,7 @@ export class MeClientsController { @CurrentUser() user: User, @Param('tenantId') tenantId: string, @Param('clientId') clientId: string, - @Param('includeArchived') includeArchived?: boolean, + @Query('includeArchived') includeArchived?: boolean, ): Promise { return this.clientsService.findByTenantIdAndClientId( tenantId, diff --git a/apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.controller.ts b/apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.controller.ts index 1889f4886baf..4ab51563c17a 100644 --- a/apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.controller.ts +++ b/apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.controller.ts @@ -1,9 +1,11 @@ import { + Body, Controller, Delete, Get, Headers, Param, + Post, UseGuards, } from '@nestjs/common' import { ApiTags } from '@nestjs/swagger' @@ -16,8 +18,10 @@ import { User, } from '@island.is/auth-nest-tools' import { + CreatePaperDelegationDto, DelegationAdminCustomDto, DelegationAdminCustomService, + DelegationDTO, } from '@island.is/auth-api-lib' import { Documentation } from '@island.is/nest/swagger' import { Audit, AuditService } from '@island.is/nest/audit' @@ -65,6 +69,28 @@ export class DelegationAdminController { ) } + @Post() + @Scopes(DelegationAdminScopes.admin) + @Documentation({ + response: { status: 201, type: DelegationDTO }, + }) + create( + @CurrentUser() user: User, + @Body() delegation: CreatePaperDelegationDto, + ): Promise { + return this.auditService.auditPromise( + { + auth: user, + namespace, + action: 'create', + resources: (result) => { + return result?.id ?? undefined + }, + }, + this.delegationAdminService.createDelegation(user, delegation), + ) + } + @Delete(':delegationId') @Scopes(DelegationAdminScopes.admin) @Documentation({ diff --git a/apps/services/auth/admin-api/src/app/v2/delegations/test/delegation-admin.auth.spec.ts b/apps/services/auth/admin-api/src/app/v2/delegations/test/delegation-admin.auth.spec.ts new file mode 100644 index 000000000000..f97758aaf310 --- /dev/null +++ b/apps/services/auth/admin-api/src/app/v2/delegations/test/delegation-admin.auth.spec.ts @@ -0,0 +1,135 @@ +import request from 'supertest' + +import { + getRequestMethod, + setupApp, + setupAppWithoutAuth, + TestApp, + TestEndpointOptions, +} from '@island.is/testing/nest' +import { User } from '@island.is/auth-nest-tools' +import { FixtureFactory } from '@island.is/services/auth/testing' +import { createCurrentUser } from '@island.is/testing/fixtures' +import { DelegationAdminScopes } from '@island.is/auth/scopes' +import { SequelizeConfigService } from '@island.is/auth-api-lib' + +import { AppModule } from '../../../app.module' + +describe('withoutAuth and permissions', () => { + async function formatUrl(app: TestApp, endpoint: string, user?: User) { + if (!endpoint.includes(':delegation')) { + return endpoint + } + const factory = new FixtureFactory(app) + const domain = await factory.createDomain({ + name: 'd1', + apiScopes: [{ name: 's1' }], + }) + const delegation = await factory.createCustomDelegation({ + fromNationalId: user?.nationalId, + domainName: domain.name, + scopes: [{ scopeName: 's1' }], + }) + return endpoint.replace(':delegation', encodeURIComponent(delegation.id)) + } + + it.each` + method | endpoint + ${'GET'} | ${'/delegation-admin'} + ${'DELETE'} | ${'/delegation-admin/:delegation'} + `( + '$method $endpoint should return 401 when user is not authenticated', + async ({ method, endpoint }) => { + // Arrange + const app = await setupAppWithoutAuth({ + AppModule, + SequelizeConfigService, + dbType: 'postgres', + }) + const server = request(app.getHttpServer()) + const url = await formatUrl(app, endpoint) + + // Act + const res = await getRequestMethod(server, method)(url) + + // Assert + expect(res.status).toEqual(401) + expect(res.body).toMatchObject({ + status: 401, + type: 'https://httpstatuses.org/401', + title: 'Unauthorized', + }) + }, + ) + + it.each` + method | endpoint + ${'GET'} | ${'/delegation-admin'} + ${'DELETE'} | ${'/delegation-admin/:delegation'} + `( + '$method $endpoint should return 403 Forbidden when user does not have the correct scope', + async ({ method, endpoint }: TestEndpointOptions) => { + // Arrange + const user = createCurrentUser() + const app = await setupApp({ + AppModule, + SequelizeConfigService, + user, + dbType: 'postgres', + }) + const server = request(app.getHttpServer()) + const url = await formatUrl(app, endpoint, user) + + // Act + const res = await getRequestMethod(server, method)(url) + + // Assert + expect(res.status).toEqual(403) + expect(res.body).toMatchObject({ + status: 403, + type: 'https://httpstatuses.org/403', + title: 'Forbidden', + detail: 'Forbidden resource', + }) + + // CleanUp + app.cleanUp() + }, + ) + + it.each` + method | endpoint + ${'DELETE'} | ${'/delegation-admin/:delegation'} + `( + '$method $endpoint should return 403 Forbidden when user does not have the admin scope', + async ({ method, endpoint }: TestEndpointOptions) => { + // Arrange + const user = createCurrentUser({ + scope: [DelegationAdminScopes.read], + }) + const app = await setupApp({ + AppModule, + SequelizeConfigService, + user, + dbType: 'postgres', + }) + const server = request(app.getHttpServer()) + const url = await formatUrl(app, endpoint, user) + + // Act + const res = await getRequestMethod(server, method)(url) + + // Assert + expect(res.status).toEqual(403) + expect(res.body).toMatchObject({ + status: 403, + type: 'https://httpstatuses.org/403', + title: 'Forbidden', + detail: 'Forbidden resource', + }) + + // CleanUp + app.cleanUp() + }, + ) +}) diff --git a/apps/services/auth/admin-api/src/app/v2/delegations/test/delegation-admin.spec.ts b/apps/services/auth/admin-api/src/app/v2/delegations/test/delegation-admin.spec.ts new file mode 100644 index 000000000000..718b6427b800 --- /dev/null +++ b/apps/services/auth/admin-api/src/app/v2/delegations/test/delegation-admin.spec.ts @@ -0,0 +1,332 @@ +import request from 'supertest' + +import { getRequestMethod, setupApp, TestApp } from '@island.is/testing/nest' +import { User } from '@island.is/auth-nest-tools' +import { FixtureFactory } from '@island.is/services/auth/testing' +import { + createCurrentUser, + createNationalRegistryUser, +} from '@island.is/testing/fixtures' +import { DelegationAdminScopes } from '@island.is/auth/scopes' +import addDays from 'date-fns/addDays' +import { + CreatePaperDelegationDto, + Delegation, + DELEGATION_TAG, + DelegationDelegationType, + DelegationsIndexService, + SequelizeConfigService, + ZENDESK_CUSTOM_FIELDS, +} from '@island.is/auth-api-lib' + +import { AppModule } from '../../../app.module' +import { AuthDelegationType } from '@island.is/shared/types' +import { getModelToken } from '@nestjs/sequelize' +import { faker } from '@island.is/shared/mocking' +import { TicketStatus, ZendeskService } from '@island.is/clients/zendesk' +import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2' + +const currentUser = createCurrentUser({ + scope: [DelegationAdminScopes.read, DelegationAdminScopes.admin], +}) + +describe('DelegationAdmin - With authentication', () => { + let app: TestApp + let server: request.SuperTest + let factory: FixtureFactory + let zendeskService: ZendeskService + let nationalRegistryApi: NationalRegistryClientService + let delegationIndexServiceApi: DelegationsIndexService + + beforeEach(async () => { + app = await setupApp({ + AppModule, + SequelizeConfigService, + user: currentUser, + dbType: 'postgres', + }) + + server = request(app.getHttpServer()) + factory = new FixtureFactory(app) + + zendeskService = app.get(ZendeskService) + nationalRegistryApi = app.get(NationalRegistryClientService) + delegationIndexServiceApi = app.get(DelegationsIndexService) + + jest + .spyOn(delegationIndexServiceApi, 'indexCustomDelegations') + .mockImplementation(async () => { + return + }) + + jest + .spyOn(delegationIndexServiceApi, 'indexGeneralMandateDelegations') + .mockImplementation(async () => { + return + }) + }) + + afterEach(async () => { + await app.cleanUp() + }) + + async function createDelegationAdmin(user?: User) { + const domain = await factory.createDomain({ + name: 'd1', + apiScopes: [ + { name: 's1', supportedDelegationTypes: [AuthDelegationType.Custom] }, + ], + }) + + return factory.createCustomDelegation({ + fromNationalId: user?.nationalId ?? '', + domainName: domain.name, + scopes: [{ scopeName: 's1' }], + referenceId: 'ref1', + }) + } + + describe('GET /delegation-admin', () => { + it('GET /delegation-admin should return delegations for nationalId', async () => { + // Arrange + const delegation = await createDelegationAdmin(currentUser) + // Act + const res = await getRequestMethod( + server, + 'GET', + )('/delegation-admin').set('X-Query-National-Id', currentUser.nationalId) + + // Assert + expect(res.status).toEqual(200) + expect(res.body['outgoing'][0].id).toEqual(delegation.id) + }) + }) + + describe('DELETE /delegation-admin/:delegation', () => { + it('DELETE /delegation-admin/:delegation should not delete delegation that has no reference id', async () => { + // Arrange + const delegationModel = await app.get(getModelToken(Delegation)) + const delegation = await createDelegationAdmin(currentUser) + // Remove the referenceId + await delegationModel.update( + { + referenceId: null, + }, + { + where: { + id: delegation.id, + }, + }, + ) + + // Act + const res = await getRequestMethod( + server, + 'DELETE', + )(`/delegation-admin/${delegation.id}`) + + // Assert + expect(res.status).toEqual(204) + + // Assert db + const deletedDelegation = await delegationModel.findByPk(delegation.id) + + expect(deletedDelegation).not.toBeNull() + }) + + it('DELETE /delegation-admin/:delegation should delete delegation', async () => { + // Arrange + const delegation = await createDelegationAdmin(currentUser) + + // Act + const res = await getRequestMethod( + server, + 'DELETE', + )(`/delegation-admin/${delegation.id}`) + + // Assert + expect(res.status).toEqual(204) + + // Assert db + const delegationModel = await app.get(getModelToken(Delegation)) + const deletedDelegation = await delegationModel.findByPk(delegation.id) + + expect(deletedDelegation).toBeNull() + }) + + it('DELETE /delegation-admin/:delegation should throw error since id does not exist', async () => { + // Arrange + await createDelegationAdmin(currentUser) + + const invalidId = faker.datatype.uuid() + // Act + const res = await getRequestMethod( + server, + 'DELETE', + )(`/delegation-admin/${invalidId}`) + + // Assert + expect(res.status).toEqual(204) + + // Assert db + const delegationModel = await app.get(getModelToken(Delegation)) + const deletedDelegation = await delegationModel.findAll() + + expect(deletedDelegation).not.toBeNull() + }) + }) + + describe('POST /delegation-admin', () => { + const toNationalId = '0101302399' + const fromNationalId = '0101307789' + + let zendeskServiceApiSpy: jest.SpyInstance + let nationalRegistryApiSpy: jest.SpyInstance + + let delegationModel: typeof Delegation + let delegationDelegationTypeModel: typeof DelegationDelegationType + + beforeEach(async () => { + delegationModel = await app.get(getModelToken(Delegation)) + delegationDelegationTypeModel = await app.get( + getModelToken(DelegationDelegationType), + ) + + await factory.createDomain({ + name: 'd1', + apiScopes: [ + { + name: 's1', + supportedDelegationTypes: [ + AuthDelegationType.Custom, + AuthDelegationType.GeneralMandate, + ], + }, + ], + }) + + mockZendeskService(toNationalId, fromNationalId) + mockNationalRegistryService() + }) + + const mockNationalRegistryService = () => { + nationalRegistryApiSpy = jest + .spyOn(nationalRegistryApi, 'getIndividual') + .mockImplementation(async (id) => { + const user = createNationalRegistryUser({ + nationalId: id, + }) + + return user ?? null + }) + } + + const mockZendeskService = ( + toNationalId: string, + fromNationalId: string, + ) => { + zendeskServiceApiSpy = jest + .spyOn(zendeskService, 'getTicket') + .mockImplementation((ticketId: string) => { + return new Promise((resolve) => + resolve({ + id: ticketId, + tags: [DELEGATION_TAG], + status: TicketStatus.Solved, + custom_fields: [ + { + id: ZENDESK_CUSTOM_FIELDS.DelegationToReferenceId, + value: toNationalId, + }, + { + id: ZENDESK_CUSTOM_FIELDS.DelegationFromReferenceId, + value: fromNationalId, + }, + ], + }), + ) + }) + } + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('POST /delegation-admin should create delegation', async () => { + // Arrange + const delegation: CreatePaperDelegationDto = { + toNationalId, + fromNationalId, + referenceId: 'ref1', + validTo: addDays(new Date(), 3), + } + + // Act + const res = await getRequestMethod( + server, + 'POST', + )('/delegation-admin').send(delegation) + + // Assert + expect(res.status).toEqual(201) + expect(res.body).toHaveProperty('id') + expect(res.body.fromNationalId).toEqual(fromNationalId) + expect(res.body.toNationalId).toEqual(toNationalId) + expect(res.body.referenceId).toEqual(delegation.referenceId) + expect(res.body.validTo).toEqual(delegation.validTo?.toISOString()) + }) + + it('POST /delegation-admin should create delegation with no expiration date', async () => { + // Arrange + const delegation: CreatePaperDelegationDto = { + toNationalId, + fromNationalId, + referenceId: 'ref1', + } + + // Act + const res = await getRequestMethod( + server, + 'POST', + )('/delegation-admin').send(delegation) + + // Assert + expect(res.status).toEqual(201) + expect(res.body).toHaveProperty('id') + expect(res.body.fromNationalId).toEqual(fromNationalId) + expect(res.body.toNationalId).toEqual(toNationalId) + expect(res.body.referenceId).toEqual(delegation.referenceId) + expect(res.body).not.toHaveProperty('validTo') + + // Assert db + const createdDelegation = await delegationModel.findByPk(res.body.id) + const createdDelegationDelegationType = + await delegationDelegationTypeModel.findOne({ + where: { + delegationId: res.body.id, + }, + }) + + expect(createdDelegation).not.toBeNull() + expect(createdDelegationDelegationType).not.toBeNull() + }) + + it('POST /delegation-admin should not create delegation with company national id', async () => { + // Arrange + const delegation: CreatePaperDelegationDto = { + toNationalId: '5005005001', + fromNationalId, + referenceId: 'ref1', + } + + // Act + const res = await getRequestMethod( + server, + 'POST', + )('/delegation-admin').send(delegation) + + // Assert + expect(res.status).toEqual(400) + }) + }) +}) diff --git a/apps/services/auth/admin-api/src/openApi.ts b/apps/services/auth/admin-api/src/openApi.ts index 7e1b27bf3538..64ae673f7545 100644 --- a/apps/services/auth/admin-api/src/openApi.ts +++ b/apps/services/auth/admin-api/src/openApi.ts @@ -1,8 +1,27 @@ import { DocumentBuilder } from '@nestjs/swagger' +import { environment } from './environments' +import { AuthScope } from '@island.is/auth/scopes' export const openApi = new DocumentBuilder() .setTitle('IdentityServer Admin api') .setDescription('Api for administration.') .setVersion('2.0') .addTag('auth-admin-api') + .addOAuth2( + { + type: 'oauth2', + description: + 'Authentication and authorization using island.is authentication service (IAS).', + flows: { + authorizationCode: { + authorizationUrl: `${environment.auth.issuer}/connect/authorize`, + tokenUrl: `${environment.auth.issuer}/connect/token`, + scopes: { + openid: 'Default openid scope', + }, + }, + }, + }, + 'ias', + ) .build() diff --git a/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.resolver.ts b/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.resolver.ts index 37f89bac5db3..b871d2c40668 100644 --- a/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.resolver.ts +++ b/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.resolver.ts @@ -109,6 +109,16 @@ export class DelegationAdminResolver { @Loader(DomainLoader) domainLoader: DomainDataLoader, @Parent() delegation: DelegationDTO, ): Promise { + if (!delegation.domainName) { + return { + name: '', + displayName: '', + description: '', + nationalId: '', + organisationLogoKey: '', + } + } + const domainName = delegation.domainName ?? ISLAND_DOMAIN const domain = await domainLoader.load({ lang: 'is', diff --git a/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.service.ts b/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.service.ts index e6ce773980a3..00b3ee818f2b 100644 --- a/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.service.ts +++ b/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.service.ts @@ -42,15 +42,8 @@ export class DelegationAdminService { } async createDelegationAdmin(user: User, input: CreateDelegationInput) { - // Mock response - return Promise.resolve({ - id: 'some-id', - fromNationalId: '0101302399', - toNationalId: '0101302399', - scopes: [], - validTo: null, - type: 'Custom', - provider: 'delegationdb', + return this.delegationsWithAuth(user).delegationAdminControllerCreate({ + createPaperDelegationDto: input, }) } } diff --git a/libs/api/domains/auth/src/lib/models/delegation.model.ts b/libs/api/domains/auth/src/lib/models/delegation.model.ts index af3cf9cc5cd4..91b22ada9926 100644 --- a/libs/api/domains/auth/src/lib/models/delegation.model.ts +++ b/libs/api/domains/auth/src/lib/models/delegation.model.ts @@ -56,6 +56,9 @@ export abstract class Delegation { @Field(() => AuthDelegationProvider) provider!: AuthDelegationProvider + + @Field(() => String, { nullable: true }) + referenceId?: string } @ObjectType('AuthLegalGuardianDelegation', { diff --git a/libs/auth-api-lib/sequelize.config.js b/libs/auth-api-lib/sequelize.config.js index 1808cea88c41..de497b35ab6a 100644 --- a/libs/auth-api-lib/sequelize.config.js +++ b/libs/auth-api-lib/sequelize.config.js @@ -1,12 +1,12 @@ /* eslint-env node */ module.exports = { development: { - username: process.env.DB_USER ?? 'dev_db', - password: process.env.DB_PASS ?? 'dev_db', - database: process.env.DB_NAME ?? 'dev_db', + username: process.env.DB_USER_AUTH_DB ?? 'dev_db', + password: process.env.DB_PASS_AUTH_DB ?? 'dev_db', + database: process.env.DB_USER_AUTH_DB ?? 'dev_db', host: 'localhost', dialect: 'postgres', - port: process.env.DB_PORT ?? 5433, + port: process.env.DB_PORT_AUTH_DB ?? 5433, seederStorage: 'sequelize', }, test: { diff --git a/libs/auth-api-lib/src/index.ts b/libs/auth-api-lib/src/index.ts index 68842edcc489..b9305d456101 100644 --- a/libs/auth-api-lib/src/index.ts +++ b/libs/auth-api-lib/src/index.ts @@ -39,6 +39,7 @@ export * from './lib/delegations/types/delegationDirection' export * from './lib/delegations/types/delegationType' export * from './lib/delegations/types/delegationRecord' export * from './lib/delegations/types/delegationValidity' +export * from './lib/delegations/dto/create-paper-delegation.dto' export * from './lib/delegations/dto/delegation-scope.dto' export * from './lib/delegations/dto/delegation-admin-custom.dto' export * from './lib/delegations/dto/delegation.dto' @@ -58,6 +59,7 @@ export * from './lib/delegations/DelegationConfig' export * from './lib/delegations/utils/scopes' export * from './lib/delegations/admin/delegation-admin-custom.service' export * from './lib/delegations/constants/names' +export * from './lib/delegations/constants/zendesk' // Resources module export * from './lib/resources/resources.module' diff --git a/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts b/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts index f5f878a88e15..43ced573b3d8 100644 --- a/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts @@ -1,101 +1,211 @@ -import { Injectable } from '@nestjs/common' +import { BadRequestException, Injectable } from '@nestjs/common' import { InjectModel } from '@nestjs/sequelize' +import { Sequelize } from 'sequelize-typescript' +import kennitala from 'kennitala' +import { uuid } from 'uuidv4' + +import { AuthDelegationType } from '@island.is/shared/types' +import { User } from '@island.is/auth-nest-tools' +import { NoContentException } from '@island.is/nest/problem' +import { + Ticket, + TicketStatus, + ZendeskService, +} from '@island.is/clients/zendesk' import { Delegation } from '../models/delegation.model' import { DelegationAdminCustomDto } from '../dto/delegation-admin-custom.dto' import { DelegationScope } from '../models/delegation-scope.model' import { ApiScope } from '../../resources/models/api-scope.model' import { ApiScopeDelegationType } from '../../resources/models/api-scope-delegation-type.model' -import { AuthDelegationType } from '@island.is/shared/types' -import { User } from '@island.is/auth-nest-tools' import { DelegationResourcesService } from '../../resources/delegation-resources.service' import { DelegationsIndexService } from '../delegations-index.service' import { DelegationScopeService } from '../delegation-scope.service' -import { NoContentException } from '@island.is/nest/problem' -import { Sequelize } from 'sequelize-typescript' +import { CreatePaperDelegationDto } from '../dto/create-paper-delegation.dto' +import { DelegationDTO } from '../dto/delegation.dto' +import { NamesService } from '../names.service' +import { DELEGATION_TAG, ZENDESK_CUSTOM_FIELDS } from '../constants/zendesk' +import { DelegationDelegationType } from '../models/delegation-delegation-type.model' +import { DelegationsIncomingCustomService } from '../delegations-incoming-custom.service' +import { DelegationValidity } from '../types/delegationValidity' @Injectable() export class DelegationAdminCustomService { constructor( @InjectModel(Delegation) private delegationModel: typeof Delegation, + @InjectModel(DelegationDelegationType) + private delegationDelegationTypeModel: typeof DelegationDelegationType, + private readonly zendeskService: ZendeskService, private delegationResourceService: DelegationResourcesService, + private delegationsIncomingCustomService: DelegationsIncomingCustomService, private delegationIndexService: DelegationsIndexService, private delegationScopeService: DelegationScopeService, + private namesService: NamesService, private sequelize: Sequelize, ) {} + private getNationalIdsFromZendeskTicket(ticket: Ticket): { + fromReferenceId: string + toReferenceId: string + } { + const fromReferenceId = ticket.custom_fields.find( + (field) => field.id === ZENDESK_CUSTOM_FIELDS.DelegationFromReferenceId, + ) + const toReferenceId = ticket.custom_fields.find( + (field) => field.id === ZENDESK_CUSTOM_FIELDS.DelegationToReferenceId, + ) + + if (!fromReferenceId || !toReferenceId) { + throw new BadRequestException( + 'Zendesk ticket is missing required custom fields', + ) + } + + return { + fromReferenceId: fromReferenceId.value, + toReferenceId: toReferenceId.value, + } + } + async getAllDelegationsByNationalId( nationalId: string, ): Promise { - const incomingDelegations = await this.delegationModel.findAll({ - where: { - toNationalId: nationalId, - }, - include: [ - { - model: DelegationScope, - required: true, - include: [ - { - model: ApiScope, - as: 'apiScope', - required: true, - where: { - enabled: true, - }, - include: [ - { - model: ApiScopeDelegationType, - required: true, - where: { - delegationType: AuthDelegationType.Custom, - }, - }, - ], - }, - ], + const [ + incomingCustomDelegations, + incomingGeneralDelegations, + outgoingCustomDelegations, + outgoingGeneralDelegations, + ] = await Promise.all([ + this.delegationsIncomingCustomService.findAllValidIncoming({ + nationalId: nationalId, + validity: DelegationValidity.ALL, + }), + this.delegationsIncomingCustomService.findAllValidGeneralMandate({ + nationalId: nationalId, + }), + this.delegationModel.findAll({ + where: { + fromNationalId: nationalId, }, - ], - }) - - const outgoingDelegations = await this.delegationModel.findAll({ - where: { - fromNationalId: nationalId, - }, - include: [ - { - model: DelegationScope, - required: true, - include: [ - { - model: ApiScope, - required: true, - as: 'apiScope', - where: { - enabled: true, - }, - include: [ - { - model: ApiScopeDelegationType, - required: true, - where: { - delegationType: AuthDelegationType.Custom, - }, + include: [ + { + model: DelegationScope, + required: true, + include: [ + { + model: ApiScope, + required: true, + as: 'apiScope', + where: { + enabled: true, }, - ], - }, - ], + include: [ + { + model: ApiScopeDelegationType, + required: true, + where: { + delegationType: AuthDelegationType.Custom, + }, + }, + ], + }, + ], + }, + ], + }), + this.delegationModel.findAll({ + where: { + fromNationalId: nationalId, }, - ], - }) + include: [ + { + model: DelegationDelegationType, + required: true, + where: { + delegationTypeId: AuthDelegationType.GeneralMandate, + }, + }, + ], + }), + ]) return { - incoming: incomingDelegations.map((delegation) => delegation.toDTO()), - outgoing: outgoingDelegations.map((delegation) => delegation.toDTO()), + incoming: [...incomingCustomDelegations, ...incomingGeneralDelegations], + outgoing: [ + ...outgoingGeneralDelegations.map((d) => + d.toDTO(AuthDelegationType.GeneralMandate), + ), + ...outgoingCustomDelegations.map((delegation) => delegation.toDTO()), + ], } } + async createDelegation( + user: User, + delegation: CreatePaperDelegationDto, + ): Promise { + this.validatePersonsNationalIds( + delegation.toNationalId, + delegation.fromNationalId, + ) + + const zendeskCase = await this.zendeskService.getTicket( + delegation.referenceId, + ) + + if (!zendeskCase.tags.includes(DELEGATION_TAG)) { + throw new BadRequestException('Zendesk ticket is missing required tag') + } + + if (zendeskCase.status !== TicketStatus.Solved) { + throw new BadRequestException('Zendesk case is not solved') + } + + const { fromReferenceId, toReferenceId } = + this.getNationalIdsFromZendeskTicket(zendeskCase) + + if ( + fromReferenceId !== delegation.fromNationalId || + toReferenceId !== delegation.toNationalId + ) { + throw new BadRequestException( + 'Zendesk ticket nationalIds does not match delegation nationalIds', + ) + } + + const [fromDisplayName, toName] = await Promise.all([ + this.namesService.getPersonName(delegation.fromNationalId), + this.namesService.getPersonName(delegation.toNationalId), + ]) + + const newDelegation = await this.delegationModel.create( + { + id: uuid(), + toNationalId: delegation.toNationalId, + fromNationalId: delegation.fromNationalId, + createdByNationalId: user.actor?.nationalId ?? user.nationalId, + referenceId: delegation.referenceId, + toName, + fromDisplayName, + delegationDelegationTypes: [ + { + delegationTypeId: AuthDelegationType.GeneralMandate, + validTo: delegation.validTo, + }, + ] as DelegationDelegationType[], + }, + { + include: [this.delegationDelegationTypeModel], + }, + ) + + // Index delegations for the toNationalId + void this.indexDelegations(delegation.toNationalId) + + return newDelegation.toDTO(AuthDelegationType.GeneralMandate) + } + async deleteDelegation(user: User, delegationId: string): Promise { const delegation = await this.delegationModel.findByPk(delegationId) @@ -103,6 +213,10 @@ export class DelegationAdminCustomService { throw new NoContentException() } + if (!delegation.referenceId) { + throw new NoContentException() + } + const userScopes = await this.delegationResourceService.findScopes( user, delegation.domainName ?? null, @@ -119,6 +233,7 @@ export class DelegationAdminCustomService { const remainingScopes = await this.delegationScopeService.findAll( delegationId, ) + if (remainingScopes.length === 0) { await this.delegationModel.destroy({ transaction, @@ -128,10 +243,32 @@ export class DelegationAdminCustomService { }) } - // Index custom delegations for the toNationalId - void this.delegationIndexService.indexCustomDelegations( - delegation.toNationalId, - ) + // Index delegations for the toNationalId + void this.indexDelegations(delegation.toNationalId) }) } + + private validatePersonsNationalIds( + toNationalId: string, + fromNationalId: string, + ) { + if (toNationalId === fromNationalId) { + throw new BadRequestException( + 'Cannot create a delegation between the same nationalId.', + ) + } + + if ( + !(kennitala.isPerson(fromNationalId) && kennitala.isPerson(toNationalId)) + ) { + throw new BadRequestException( + 'National ids needs to be valid person national ids', + ) + } + } + + private indexDelegations(nationalId: string) { + void this.delegationIndexService.indexCustomDelegations(nationalId) + void this.delegationIndexService.indexGeneralMandateDelegations(nationalId) + } } diff --git a/libs/auth-api-lib/src/lib/delegations/constants/zendesk.ts b/libs/auth-api-lib/src/lib/delegations/constants/zendesk.ts new file mode 100644 index 000000000000..b6e4ba2f6cd6 --- /dev/null +++ b/libs/auth-api-lib/src/lib/delegations/constants/zendesk.ts @@ -0,0 +1,5 @@ +export const DELEGATION_TAG = 'umsokn_um_umboð_a_mínum_síðum' +export const ZENDESK_CUSTOM_FIELDS = { + DelegationFromReferenceId: 21401464004498, + DelegationToReferenceId: 21401435545234, +} diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts index b13ce486becc..4e07372ce897 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts @@ -33,6 +33,7 @@ import { DelegationDelegationType } from './models/delegation-delegation-type.mo type FindAllValidIncomingOptions = { nationalId: string domainName?: string + validity?: DelegationValidity } type FromNameInfo = { @@ -59,13 +60,17 @@ export class DelegationsIncomingCustomService { ) {} async findAllValidIncoming( - { nationalId, domainName }: FindAllValidIncomingOptions, + { + nationalId, + domainName, + validity = DelegationValidity.NOW, + }: FindAllValidIncomingOptions, useMaster = false, ): Promise { const { delegations, fromNameInfo } = await this.findAllIncoming( { nationalId, - validity: DelegationValidity.NOW, + validity, domainName, }, useMaster, diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-index.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-index.service.ts index f6af5c4b46a5..46b51cd42f48 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-index.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-index.service.ts @@ -280,6 +280,12 @@ export class DelegationsIndexService { await this.saveToIndex(nationalId, delegations) } + /* Index incoming general mandate delegations */ + async indexGeneralMandateDelegations(nationalId: string) { + const delegations = await this.getGeneralMandateDelegation(nationalId, true) + await this.saveToIndex(nationalId, delegations) + } + /* Index incoming personal representative delegations */ async indexRepresentativeDelegations(nationalId: string) { const delegations = await this.getRepresentativeDelegations( @@ -483,6 +489,19 @@ export class DelegationsIndexService { ) } + private async getGeneralMandateDelegation( + nationalId: string, + useMaster = false, + ) { + const delegation = + await this.delegationsIncomingCustomService.findAllValidGeneralMandate( + { nationalId }, + useMaster, + ) + + return delegation.map(toDelegationIndexInfo) + } + private async getCustomDelegations(nationalId: string, useMaster = false) { const delegations = await this.delegationsIncomingCustomService.findAllValidIncoming( diff --git a/libs/auth-api-lib/src/lib/delegations/delegations.module.ts b/libs/auth-api-lib/src/lib/delegations/delegations.module.ts index e5f79d1aab6c..1fb0530b43d0 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations.module.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations.module.ts @@ -6,6 +6,10 @@ import { NationalRegistryClientModule } from '@island.is/clients/national-regist import { CompanyRegistryClientModule } from '@island.is/clients/rsk/company-registry' import { SyslumennClientModule } from '@island.is/clients/syslumenn' import { FeatureFlagModule } from '@island.is/nest/feature-flags' +import { + ZendeskModule, + ZendeskServiceOptions, +} from '@island.is/clients/zendesk' import { ClientAllowedScope } from '../clients/models/client-allowed-scope.model' import { Client } from '../clients/models/client.model' @@ -28,6 +32,7 @@ import { DelegationsIncomingService } from './delegations-incoming.service' import { DelegationsIndexService } from './delegations-index.service' import { DelegationsOutgoingService } from './delegations-outgoing.service' import { DelegationsService } from './delegations.service' +import { environment } from '../environments' import { DelegationDelegationType } from './models/delegation-delegation-type.model' import { DelegationIndexMeta } from './models/delegation-index-meta.model' import { DelegationIndex } from './models/delegation-index.model' @@ -46,6 +51,7 @@ import { NamesService } from './names.service' CompanyRegistryClientModule, UserIdentitiesModule, FeatureFlagModule, + ZendeskModule.register(environment.zendeskOptions as ZendeskServiceOptions), SequelizeModule.forFeature([ ApiScope, ApiScopeDelegationType, diff --git a/libs/auth-api-lib/src/lib/delegations/dto/create-paper-delegation.dto.ts b/libs/auth-api-lib/src/lib/delegations/dto/create-paper-delegation.dto.ts new file mode 100644 index 000000000000..addcc91b66b5 --- /dev/null +++ b/libs/auth-api-lib/src/lib/delegations/dto/create-paper-delegation.dto.ts @@ -0,0 +1,21 @@ +import { IsDateString, IsOptional, IsString } from 'class-validator' +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' + +export class CreatePaperDelegationDto { + @IsString() + @ApiProperty() + fromNationalId!: string + + @IsString() + @ApiProperty() + toNationalId!: string + + @ApiProperty() + @IsString() + referenceId!: string + + @IsOptional() + @IsDateString() + @ApiPropertyOptional({ nullable: true, type: Date }) + validTo?: Date | null +} diff --git a/libs/auth-api-lib/src/lib/delegations/models/delegation-delegation-type.model.ts b/libs/auth-api-lib/src/lib/delegations/models/delegation-delegation-type.model.ts index ba00c7fc4b3f..f84933c7a80a 100644 --- a/libs/auth-api-lib/src/lib/delegations/models/delegation-delegation-type.model.ts +++ b/libs/auth-api-lib/src/lib/delegations/models/delegation-delegation-type.model.ts @@ -51,7 +51,7 @@ export class DelegationDelegationType extends Model< type: DataType.DATE, allowNull: true, }) - validTo?: Date + validTo?: Date | null @CreatedAt readonly created!: CreationOptional diff --git a/libs/auth-api-lib/src/lib/delegations/models/delegation.model.ts b/libs/auth-api-lib/src/lib/delegations/models/delegation.model.ts index 41ef58402d08..cff697005338 100644 --- a/libs/auth-api-lib/src/lib/delegations/models/delegation.model.ts +++ b/libs/auth-api-lib/src/lib/delegations/models/delegation.model.ts @@ -25,6 +25,7 @@ import { AuthDelegationType, } from '@island.is/shared/types' import { DelegationDelegationType } from './delegation-delegation-type.model' +import { isDefined } from '@island.is/shared/utils' @Table({ tableName: 'delegation', @@ -96,6 +97,16 @@ export class Delegation extends Model< referenceId?: string get validTo(): Date | null | undefined { + if ( + this.delegationDelegationTypes && + this.delegationDelegationTypes.length > 0 + ) { + const dates = this.delegationDelegationTypes + .map((x) => x.validTo) + .filter((x) => isDefined(x)) as Array + return max(dates) + } + // 1. Find a value with null as validTo. Null means that delegation scope set valid not to a specific time period const withNullValue = this.delegationScopes?.find((x) => x.validTo === null) if (withNullValue) { @@ -104,7 +115,7 @@ export class Delegation extends Model< // 2. Find items with value in the array const dates = (this.delegationScopes - ?.filter((x) => x.validTo !== null && x.validTo !== undefined) + ?.filter((x) => isDefined(x.validTo)) .map((x) => x.validTo) || []) as Array // Return the max value @@ -136,6 +147,7 @@ export class Delegation extends Model< : [], provider: AuthDelegationProvider.Custom, type: type, + referenceId: this.referenceId, domainName: this.domainName, } } diff --git a/libs/auth-api-lib/src/lib/environments/environment.ts b/libs/auth-api-lib/src/lib/environments/environment.ts new file mode 100644 index 000000000000..9db41f8ead3b --- /dev/null +++ b/libs/auth-api-lib/src/lib/environments/environment.ts @@ -0,0 +1,9 @@ +const config = { + zendeskOptions: { + email: process.env.ZENDESK_CONTACT_FORM_EMAIL, + token: process.env.ZENDESK_CONTACT_FORM_TOKEN, + subdomain: process.env.ZENDESK_CONTACT_FORM_SUBDOMAIN, + }, +} + +export default config diff --git a/libs/auth-api-lib/src/lib/environments/index.ts b/libs/auth-api-lib/src/lib/environments/index.ts new file mode 100644 index 000000000000..f1c9690a5bd4 --- /dev/null +++ b/libs/auth-api-lib/src/lib/environments/index.ts @@ -0,0 +1 @@ +export { default as environment } from './environment' diff --git a/libs/clients/zendesk/src/lib/zendesk.service.ts b/libs/clients/zendesk/src/lib/zendesk.service.ts index 13739a9c89ce..3927ee2ea8cf 100644 --- a/libs/clients/zendesk/src/lib/zendesk.service.ts +++ b/libs/clients/zendesk/src/lib/zendesk.service.ts @@ -5,7 +5,16 @@ import { LOGGER_PROVIDER } from '@island.is/logging' export const ZENDESK_OPTIONS = 'ZENDESK_OPTIONS' -export type Ticket = { +export enum TicketStatus { + Open = 'open', + Pending = 'pending', + Solved = 'solved', + Closed = 'closed', + New = 'new', + OnHold = 'on-hold', +} + +export type SubmitTicketInput = { subject?: string message: string requesterId?: number @@ -18,6 +27,13 @@ export type User = { id: number } +export type Ticket = { + id: string + status: TicketStatus | string + custom_fields: Array<{ id: number; value: string }> + tags: Array +} + export interface ZendeskServiceOptions { email: string token: string @@ -121,7 +137,7 @@ export class ZendeskService { subject, requesterId, tags = [], - }: Ticket): Promise { + }: SubmitTicketInput): Promise { const newTicket = JSON.stringify({ ticket: { requester_id: requesterId, @@ -146,4 +162,23 @@ export class ZendeskService { return true } + + async getTicket(ticketId: string): Promise { + try { + const response = await axios.get(`${this.api}/tickets/${ticketId}.json`, { + ...this.params, + }) + + return response.data.ticket + } catch (e) { + const errMsg = 'Failed to get Zendesk ticket' + const description = e.response.data.description + + this.logger.error(errMsg, { + message: description, + }) + + throw new Error(`${errMsg}: ${description}`) + } + } } diff --git a/libs/portals/admin/delegation-admin/src/components/DelegationList.tsx b/libs/portals/admin/delegation-admin/src/components/DelegationList.tsx index b248b3e20505..b8e47dc9168d 100644 --- a/libs/portals/admin/delegation-admin/src/components/DelegationList.tsx +++ b/libs/portals/admin/delegation-admin/src/components/DelegationList.tsx @@ -1,6 +1,8 @@ import { AuthCustomDelegation } from '@island.is/api/schema' import { Box, Stack } from '@island.is/island-ui/core' import { AccessCard } from '@island.is/portals/shared-modules/delegations' +import { useDeleteCustomDelegationAdminMutation } from '../screens/DelegationAdminDetails/DelegationAdmin.generated' +import { useRevalidator } from 'react-router-dom' interface DelegationProps { direction: 'incoming' | 'outgoing' @@ -8,20 +10,34 @@ interface DelegationProps { } const DelegationList = ({ delegationsList, direction }: DelegationProps) => { + const [deleteCustomDelegationAdminMutation] = + useDeleteCustomDelegationAdminMutation() + const { revalidate } = useRevalidator() + return ( - {delegationsList.map((delegation) => ( - { - console.warn('Delete delegation') - }} - /> - ))} + {delegationsList.map((delegation) => { + return ( + { + const { data } = await deleteCustomDelegationAdminMutation({ + variables: { + id: delegation.id as string, + }, + }) + if (data) { + revalidate() + } + }} + /> + ) + })} ) diff --git a/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.graphql b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.graphql index ba61ef19ccae..3b5dab5125d8 100644 --- a/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.graphql +++ b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.graphql @@ -5,6 +5,7 @@ query getCustomDelegationsAdmin($nationalId: String!) { incoming { id validTo + referenceId domain { name organisationLogoKey @@ -31,6 +32,7 @@ query getCustomDelegationsAdmin($nationalId: String!) { outgoing { id validTo + referenceId domain { name organisationLogoKey @@ -56,3 +58,7 @@ query getCustomDelegationsAdmin($nationalId: String!) { } } } + +mutation deleteCustomDelegationAdmin($id: String!) { + authDeleteAdminDelegation(id: $id) +} diff --git a/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx index 4dd86fb617e4..daea025576c2 100644 --- a/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx +++ b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx @@ -72,11 +72,11 @@ const DelegationAdminScreen = () => { { label: formatMessage(m.delegationFrom), content: - delegationAdmin.incoming.length > 0 ? ( + delegationAdmin.outgoing.length > 0 ? ( ) : ( @@ -89,11 +89,11 @@ const DelegationAdminScreen = () => { { label: formatMessage(m.delegationTo), content: - delegationAdmin.outgoing.length > 0 ? ( + delegationAdmin.incoming.length > 0 ? ( ) : ( diff --git a/libs/portals/shared-modules/delegations/src/components/access/AccessCard.tsx b/libs/portals/shared-modules/delegations/src/components/access/AccessCard.tsx index ab3273fb365d..1537f63de36e 100644 --- a/libs/portals/shared-modules/delegations/src/components/access/AccessCard.tsx +++ b/libs/portals/shared-modules/delegations/src/components/access/AccessCard.tsx @@ -20,7 +20,6 @@ import { m as coreMessages } from '@island.is/portals/core' import uniqBy from 'lodash/uniqBy' import sortBy from 'lodash/sortBy' import { m } from '../../lib/messages' -import { DelegationPaths } from '../../lib/paths' import { AuthApiScope, AuthDelegationType } from '@island.is/api/schema' import { AuthCustomDelegation, @@ -57,6 +56,9 @@ interface AccessCardProps { direction?: 'incoming' | 'outgoing' canModify?: boolean + href?: string + + isAdminView?: boolean } export const AccessCard = ({ @@ -66,6 +68,8 @@ export const AccessCard = ({ variant = 'outgoing', direction = 'outgoing', canModify = true, + href, + isAdminView = false, }: AccessCardProps) => { const { formatMessage } = useLocale() const navigate = useNavigate() @@ -74,10 +78,12 @@ export const AccessCard = ({ const hasTags = tags.length > 0 const isOutgoing = variant === 'outgoing' - const href = `${DelegationPaths.Delegations}/${delegation.id}` const isExpired = useMemo(() => { - if (delegation.validTo) { + if ( + delegation.validTo || + delegation.type === AuthDelegationType.GeneralMandate + ) { return isDateExpired(delegation.validTo) } @@ -176,10 +182,10 @@ export const AccessCard = ({ - {!isOutgoing && ( + {(isAdminView || !isOutgoing) && ( <> {renderDelegationTypeLabel(delegation.type)} - {delegation.domain && ( + {delegation.domain?.name && ( {'|'} @@ -234,7 +240,7 @@ export const AccessCard = ({ {formatMessage(coreMessages.view)} - ) : !isExpired ? ( + ) : !isExpired && href ? ( - ) : ( + ) : href ? ( - )} + ) : null} )} diff --git a/libs/portals/shared-modules/delegations/src/components/delegations/incoming/DelegationsIncoming.tsx b/libs/portals/shared-modules/delegations/src/components/delegations/incoming/DelegationsIncoming.tsx index c444a298339b..526439fcdbb4 100644 --- a/libs/portals/shared-modules/delegations/src/components/delegations/incoming/DelegationsIncoming.tsx +++ b/libs/portals/shared-modules/delegations/src/components/delegations/incoming/DelegationsIncoming.tsx @@ -14,6 +14,7 @@ import { DelegationsEmptyState } from '../DelegationsEmptyState' import { DelegationIncomingModal } from './DelegationIncomingModal/DelegationIncomingModal' import { useAuthDelegationsIncomingQuery } from './DelegationIncomingModal/DelegationIncomingModal.generated' import { AuthCustomDelegationIncoming } from '../../../types/customDelegation' +import { DelegationPaths } from '../../../lib/paths' export const DelegationsIncoming = () => { const { formatMessage, lang = 'is' } = useLocale() @@ -78,6 +79,7 @@ export const DelegationsIncoming = () => { }} direction="incoming" variant="incoming" + href={`${DelegationPaths.Delegations}/${delegation.id}`} /> ), )} diff --git a/libs/portals/shared-modules/delegations/src/components/delegations/outgoing/DelegationsOutgoing.tsx b/libs/portals/shared-modules/delegations/src/components/delegations/outgoing/DelegationsOutgoing.tsx index dc7f9174dee8..b4b079342a28 100644 --- a/libs/portals/shared-modules/delegations/src/components/delegations/outgoing/DelegationsOutgoing.tsx +++ b/libs/portals/shared-modules/delegations/src/components/delegations/outgoing/DelegationsOutgoing.tsx @@ -17,6 +17,7 @@ import { useAuthDelegationsOutgoingQuery } from './DelegationsOutgoing.generated import { AuthCustomDelegationOutgoing } from '../../../types/customDelegation' import { ALL_DOMAINS } from '../../../constants/domain' import { m } from '../../../lib/messages' +import { DelegationPaths } from '../../../lib/paths' const prepareDomainName = (domainName: string | null) => domainName === ALL_DOMAINS ? null : domainName @@ -114,6 +115,7 @@ export const DelegationsOutgoing = () => { ) }} variant="outgoing" + href={`${DelegationPaths.Delegations}/${delegation.id}`} /> ), )} diff --git a/libs/services/auth/testing/src/fixtures/fixture-factory.ts b/libs/services/auth/testing/src/fixtures/fixture-factory.ts index a8929a9a9f13..b6a37fbf32b5 100644 --- a/libs/services/auth/testing/src/fixtures/fixture-factory.ts +++ b/libs/services/auth/testing/src/fixtures/fixture-factory.ts @@ -376,6 +376,7 @@ export class FixtureFactory { domainName, fromName, scopes = [], + referenceId, }: CreateCustomDelegation): Promise { const delegation = await this.get(Delegation).create({ id: faker.datatype.uuid(), @@ -384,6 +385,7 @@ export class FixtureFactory { domainName, fromDisplayName: fromName ?? faker.name.findName(), toName: faker.name.findName(), + referenceId: referenceId ?? undefined, }) delegation.delegationScopes = await Promise.all( diff --git a/libs/services/auth/testing/src/fixtures/types.ts b/libs/services/auth/testing/src/fixtures/types.ts index c3f77d51825b..8b96406dd1e3 100644 --- a/libs/services/auth/testing/src/fixtures/types.ts +++ b/libs/services/auth/testing/src/fixtures/types.ts @@ -35,8 +35,11 @@ export type CreateCustomDelegationScope = Optional< 'validFrom' | 'validTo' > export type CreateCustomDelegation = Optional< - Pick, - 'toNationalId' | 'fromNationalId' | 'fromName' + Pick< + DelegationDTO, + 'toNationalId' | 'fromNationalId' | 'fromName' | 'referenceId' + >, + 'toNationalId' | 'fromNationalId' | 'fromName' | 'referenceId' > & { domainName: string scopes?: CreateCustomDelegationScope[]