From df40024c21dd3509eff1e94827a2b276c9758f2c Mon Sep 17 00:00:00 2001 From: valurefugl <65780958+valurefugl@users.noreply.github.com> Date: Wed, 11 Sep 2024 09:18:27 +0100 Subject: [PATCH] feat(ids-api): Add legal representative delegation type and return in delegation list. (#15837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add legal representative delegation type and return in delegation list. * Remove directive. * Add type to domain model. * Fix mocks. * Fix import. * Fix index service mocking. * Add missing mock for company registry. * Testing. * Revert test change. Add try/catch for index. * Fix name and import. * Make types required on delegation dto. * Type fixes. * Reuse constant. * Callback for rejected promise. * Combine imports. * Remove mock. * Refactor name lookup. * Fix export. * Remove unused import. * Move function to class member. * Move index mocking to the shared setup function. --------- Co-authored-by: Sævar Már Atlason Co-authored-by: Valur Einarsson Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../test/delegations-filters-test-cases.ts | 13 +++ .../test/delegations-filters-types.ts | 13 +++ .../test/delegations-filters.spec.ts | 15 ++++ apps/services/auth/ids-api/test/setup.ts | 14 ++-- .../notificationsWorker/mocks.ts | 15 +++- .../auth/src/lib/models/delegation.model.ts | 9 +- ...dd-delegation-type-legal-representative.js | 47 +++++++++++ libs/auth-api-lib/src/index.ts | 1 + .../src/lib/delegations/constants/names.ts | 1 + .../lib/delegations/delegation-dto.mapper.ts | 11 +++ .../delegations-incoming-custom.service.ts | 3 +- ...gations-incoming-representative.service.ts | 3 +- .../delegations-incoming.service.ts | 82 +++++++++++++++++-- .../delegations/delegations-index.service.ts | 31 +++++++ .../lib/delegations/delegations.service.ts | 28 +++---- .../delegations/dto/delegation-index.dto.ts | 9 +- .../models/delegation-index.model.ts | 4 + .../src/lib/delegations/utils/delegations.ts | 9 +- libs/feature-flags/src/lib/features.ts | 3 + .../testing/src/fixtures/fixture-factory.ts | 2 + libs/shared/types/src/lib/delegation.ts | 2 + 21 files changed, 274 insertions(+), 41 deletions(-) create mode 100644 libs/auth-api-lib/seeders/20240829140032-add-delegation-type-legal-representative.js create mode 100644 libs/auth-api-lib/src/lib/delegations/constants/names.ts diff --git a/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters-test-cases.ts b/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters-test-cases.ts index 314f953fb345..f18686a29cc6 100644 --- a/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters-test-cases.ts +++ b/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters-test-cases.ts @@ -287,4 +287,17 @@ export const testCases: Record = { ], }, ), + // Returns available delegations for legal representatives + legalRepresentative1: new TestCase( + createClient({ + clientId: clientId, + supportedDelegationTypes: [AuthDelegationType.LegalRepresentative], + }), + { + fromLegalRepresentative: [person1, person2], + protectedScopes: [], + expectedFrom: [person1, person2], + expectedTypes: [AuthDelegationType.LegalRepresentative], + }, + ), } diff --git a/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters-types.ts b/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters-types.ts index 977e14fb8e86..99bebf40c432 100644 --- a/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters-types.ts +++ b/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters-types.ts @@ -29,6 +29,8 @@ const customScope2 = 'cu2' const customScopeOtherDomain = 'cu-od1' const representativeScope1 = 'pr1' const representativeScope2 = 'pr2' +const legalRepresentativeScope1 = 'lr1' +const legalRepresentativeScope2 = 'lr2' export const legalGuardianScopes = [legalGuardianScope1, legalGuardianScope2] export const procurationHolderScopes = [ @@ -37,12 +39,17 @@ export const procurationHolderScopes = [ ] export const customScopes = [customScope1, customScope2, customScopeOtherDomain] export const representativeScopes = [representativeScope1, representativeScope2] +export const legalRepresentativeScopes = [ + legalRepresentativeScope1, + legalRepresentativeScope2, +] export interface ITestCaseOptions { fromChildren?: string[] fromCompanies?: string[] fromCustom?: string[] fromRepresentative?: string[] + fromLegalRepresentative?: string[] scopes?: string[] protectedScopes?: string[] scopeAccess?: [string, string][] @@ -59,6 +66,7 @@ export class TestCase { fromCompanies: string[] fromCustom: string[] fromRepresentative: string[] + fromLegalRepresentative: string[] scopes: string[] protectedScopes: string[] scopeAccess: [string, string][] @@ -71,11 +79,13 @@ export class TestCase { this.fromCompanies = options.fromCompanies ?? [] this.fromCustom = options.fromCustom ?? [] this.fromRepresentative = options.fromRepresentative ?? [] + this.fromLegalRepresentative = options.fromLegalRepresentative ?? [] this.scopes = options.scopes ?? [ ...legalGuardianScopes, ...procurationHolderScopes, ...customScopes, ...representativeScopes, + ...legalRepresentativeScopes, ] this.protectedScopes = options.protectedScopes ?? [] this.scopeAccess = options.scopeAccess ?? [] @@ -160,6 +170,9 @@ export class TestCase { if (representativeScopes.includes(scopeName)) { result.push(AuthDelegationType.PersonalRepresentative) } + if (legalRepresentativeScopes.includes(scopeName)) { + result.push(AuthDelegationType.LegalRepresentative) + } return result } } diff --git a/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters.spec.ts b/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters.spec.ts index 6538841fba40..c6032e1def00 100644 --- a/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters.spec.ts +++ b/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters.spec.ts @@ -8,6 +8,10 @@ import { MergedDelegationDTO } from '@island.is/auth-api-lib' import { RskRelationshipsClient } from '@island.is/clients-rsk-relationships' import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2' import { FixtureFactory } from '@island.is/services/auth/testing' +import { + AuthDelegationProvider, + AuthDelegationType, +} from '@island.is/shared/types' import { createNationalRegistryUser } from '@island.is/testing/fixtures' import { TestApp, truncate } from '@island.is/testing/nest' @@ -86,6 +90,17 @@ describe('DelegationsController', () => { ), ) + await Promise.all( + testCase.fromLegalRepresentative.map((nationalId) => + factory.createDelegationIndexRecord({ + fromNationalId: nationalId, + toNationalId: testCase.user.nationalId, + type: AuthDelegationType.LegalRepresentative, + provider: AuthDelegationProvider.DistrictCommissionersRegistry, + }), + ), + ) + jest .spyOn(nationalRegistryApi, 'getCustodyChildren') .mockImplementation(async () => testCase.fromChildren) diff --git a/apps/services/auth/ids-api/test/setup.ts b/apps/services/auth/ids-api/test/setup.ts index 7212ed9a56a2..c6b7ae7f9e7b 100644 --- a/apps/services/auth/ids-api/test/setup.ts +++ b/apps/services/auth/ids-api/test/setup.ts @@ -85,10 +85,6 @@ class MockUserProfile { meUserProfileControllerFindUserProfile = jest.fn().mockResolvedValue({}) } -class MockDelegationsIndexService { - indexDelegations = jest.fn().mockImplementation(() => Promise.resolve()) -} - interface SetupOptions { user: User scopes?: Scopes @@ -133,9 +129,7 @@ export const setupWithAuth = async ({ .useValue({ getValue: (feature: Features) => !features || features.includes(feature), - }) - .overrideProvider(DelegationsIndexService) - .useClass(MockDelegationsIndexService), + }), hooks: [ useAuth({ auth: user }), useDatabase({ type: 'postgres', provider: SequelizeConfigService }), @@ -149,6 +143,12 @@ export const setupWithAuth = async ({ const apiScopeModel = app.get(getModelToken(ApiScope)) await apiScopeModel.bulkCreate(Object.values(scopes).map(createApiScope)) + // Mock delegation indexing + const delegationIndexService = app.get(DelegationsIndexService) + delegationIndexService.indexDelegations = jest + .fn() + .mockImplementation(() => Promise.resolve()) + return app } diff --git a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts index 2c34e7ea7311..d204df4df6d3 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts @@ -1,15 +1,19 @@ import faker from 'faker' +import { + AuthDelegationType, + DelegationRecordDTO, +} from '@island.is/clients/auth/delegation-api' import { UserProfileDto } from '@island.is/clients/user-profile' -import { createNationalId } from '@island.is/testing/fixtures' -import { DelegationRecordDTO } from '@island.is/clients/auth/delegation-api' import { Features } from '@island.is/feature-flags' -import type { User } from '@island.is/auth-nest-tools' -import type { ConfigType } from '@island.is/nest/config' +import { createNationalId } from '@island.is/testing/fixtures' import { UserNotificationsConfig } from '../../../../config' import { HnippTemplate } from '../dto/hnippTemplate.response' +import type { User } from '@island.is/auth-nest-tools' +import type { ConfigType } from '@island.is/nest/config' + export const mockFullName = 'mockFullName' export const delegationSubjectId = 'delegation-subject-id' @@ -160,6 +164,7 @@ const delegations: Record = { fromNationalId: userWithDelegations.nationalId, toNationalId: userWithNoDelegations.nationalId, subjectId: null, // test that 3rd party login is not used if subjectId is null + type: AuthDelegationType.ProcurationHolder, }, ], [userWithDelegations2.nationalId]: [ @@ -167,6 +172,7 @@ const delegations: Record = { fromNationalId: userWithDelegations2.nationalId, toNationalId: userWithDelegations.nationalId, subjectId: delegationSubjectId, + type: AuthDelegationType.ProcurationHolder, }, ], [userWithSendToDelegationsFeatureFlagDisabled.nationalId]: [ @@ -174,6 +180,7 @@ const delegations: Record = { fromNationalId: userWithSendToDelegationsFeatureFlagDisabled.nationalId, toNationalId: userWithNoDelegations.nationalId, subjectId: faker.datatype.uuid(), + type: AuthDelegationType.ProcurationHolder, }, ], } 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 6f29dbf20117..63a9830a8676 100644 --- a/libs/api/domains/auth/src/lib/models/delegation.model.ts +++ b/libs/api/domains/auth/src/lib/models/delegation.model.ts @@ -6,11 +6,11 @@ import { registerEnumType, } from '@nestjs/graphql' +import { Identity } from '@island.is/api/domains/identity' import { AuthDelegationProvider, AuthDelegationType, } from '@island.is/clients/auth/delegation-api' -import { Identity } from '@island.is/api/domains/identity' import { DelegationScope } from './delegationScope.model' @@ -32,6 +32,8 @@ const exhaustiveCheck = (param: never) => { return PersonalRepresentativeDelegation case AuthDelegationType.Custom: return CustomDelegation + case AuthDelegationType.LegalRepresentative: + return LegalRepresentativeDelegation default: exhaustiveCheck(delegation.type) } @@ -83,6 +85,11 @@ export class CustomDelegation extends Delegation { domainName?: string } +@ObjectType('AuthLegalRepresentativeDelegation', { + implements: Delegation, +}) +export class LegalRepresentativeDelegation extends Delegation {} + @ObjectType('AuthMergedDelegation') export class MergedDelegation { @Field(() => [AuthDelegationType]) diff --git a/libs/auth-api-lib/seeders/20240829140032-add-delegation-type-legal-representative.js b/libs/auth-api-lib/seeders/20240829140032-add-delegation-type-legal-representative.js new file mode 100644 index 000000000000..1082b3c2ba66 --- /dev/null +++ b/libs/auth-api-lib/seeders/20240829140032-add-delegation-type-legal-representative.js @@ -0,0 +1,47 @@ +module.exports = { + up(queryInterface) { + return queryInterface.sequelize.query(` + BEGIN; + INSERT INTO delegation_provider + (id, name, description) + VALUES + ('syslumenn', 'Sýslumenn', 'Provider for district commissioners registry'); + + INSERT INTO delegation_type + (id, provider, name, description) + VALUES + ('LegalRepresentative', 'syslumenn', 'Legal Representative', 'Legal Representative delegation type'); + + INSERT INTO api_scope_delegation_types + (api_scope_name, delegation_type) + VALUES + ('@island.is/documents', 'LegalRepresentative'); + + INSERT INTO client_delegation_types + (client_id, delegation_type) + VALUES + ('@island.is/web', 'LegalRepresentative'); + + COMMIT; + `) + }, + + down(queryInterface) { + return queryInterface.sequelize.query(` + BEGIN; + DELETE FROM client_delegation_types + WHERE client_id = '@island.is/web' AND delegation_type = 'LegalRepresentative'; + + DELETE FROM api_scope_delegation_types + WHERE api_scope_name = '@island.is/documents' AND delegation_type = 'LegalRepresentative'; + + DELETE FROM delegation_type + WHERE id = 'LegalRepresentative'; + + DELETE FROM delegation_provider + WHERE id = 'syslumenn'; + + COMMIT; + `) + }, +} diff --git a/libs/auth-api-lib/src/index.ts b/libs/auth-api-lib/src/index.ts index 34305b1979e7..de58b144a146 100644 --- a/libs/auth-api-lib/src/index.ts +++ b/libs/auth-api-lib/src/index.ts @@ -54,6 +54,7 @@ export * from './lib/delegations/models/delegation-type.model' export * from './lib/delegations/models/delegation-provider.model' export * from './lib/delegations/DelegationConfig' export * from './lib/delegations/utils/scopes' +export * from './lib/delegations/constants/names' // Resources module export * from './lib/resources/resources.module' diff --git a/libs/auth-api-lib/src/lib/delegations/constants/names.ts b/libs/auth-api-lib/src/lib/delegations/constants/names.ts new file mode 100644 index 000000000000..d73d7d251061 --- /dev/null +++ b/libs/auth-api-lib/src/lib/delegations/constants/names.ts @@ -0,0 +1 @@ +export const UNKNOWN_NAME = 'Óþekkt nafn' diff --git a/libs/auth-api-lib/src/lib/delegations/delegation-dto.mapper.ts b/libs/auth-api-lib/src/lib/delegations/delegation-dto.mapper.ts index e44e2dbea28c..807fb8efde13 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegation-dto.mapper.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegation-dto.mapper.ts @@ -1,3 +1,4 @@ +import { DelegationRecordDTO } from './dto/delegation-index.dto' import { DelegationDTO } from './dto/delegation.dto' import { MergedDelegationDTO } from './dto/merged-delegation.dto' @@ -13,4 +14,14 @@ export class DelegationDTOMapper { scopes: dto.scopes, } } + + public static recordToMergedDelegationDTO( + dto: DelegationRecordDTO, + ): MergedDelegationDTO { + return { + fromNationalId: dto.fromNationalId, + toNationalId: dto.toNationalId, + types: [dto.type], + } + } } 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 d6b3a53d7df1..40022c8082fc 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 @@ -21,6 +21,7 @@ import { isDefined } from '@island.is/shared/utils' import { ApiScopeDelegationType } from '../resources/models/api-scope-delegation-type.model' import { ApiScopeUserAccess } from '../resources/models/api-scope-user-access.model' import { ApiScope } from '../resources/models/api-scope.model' +import { UNKNOWN_NAME } from './constants/names' import { ApiScopeInfo } from './delegations-incoming.service' import { DelegationDTO } from './dto/delegation.dto' import { MergedDelegationDTO } from './dto/merged-delegation.dto' @@ -30,8 +31,6 @@ import { DelegationValidity } from './types/delegationValidity' import { partitionWithIndex } from './utils/partitionWithIndex' import { getScopeValidityWhereClause } from './utils/scopes' -export const UNKNOWN_NAME = 'Óþekkt nafn' - type FindAllValidIncomingOptions = { nationalId: string domainName?: string diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-incoming-representative.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-incoming-representative.service.ts index 6767c7544684..b4380ad4aef1 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-incoming-representative.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-incoming-representative.service.ts @@ -14,12 +14,11 @@ import { isDefined } from '@island.is/shared/utils' import { PersonalRepresentativeDTO } from '../personal-representative/dto/personal-representative.dto' import { PersonalRepresentativeService } from '../personal-representative/services/personalRepresentative.service' +import { UNKNOWN_NAME } from './constants/names' import { ApiScopeInfo } from './delegations-incoming.service' import { DelegationDTO } from './dto/delegation.dto' import { partitionWithIndex } from './utils/partitionWithIndex' -export const UNKNOWN_NAME = 'Óþekkt nafn' - type FindAllIncomingOptions = { nationalId: string clientAllowedApiScopes?: ApiScopeInfo[] diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts index 014468c3ba9e..3a162241164b 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts @@ -1,7 +1,13 @@ -import { BadRequestException, Injectable } from '@nestjs/common' +import { BadRequestException, Inject, Injectable } from '@nestjs/common' import { InjectModel } from '@nestjs/sequelize' import { User } from '@island.is/auth-nest-tools' +import { + IndividualDto, + NationalRegistryClientService, +} from '@island.is/clients/national-registry-v2' +import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' +import { FeatureFlagService, Features } from '@island.is/nest/feature-flags' import { AuthDelegationProvider, AuthDelegationType, @@ -12,6 +18,7 @@ import { ClientDelegationType } from '../clients/models/client-delegation-type.m import { Client } from '../clients/models/client.model' import { ApiScopeDelegationType } from '../resources/models/api-scope-delegation-type.model' import { ApiScope } from '../resources/models/api-scope.model' +import { UNKNOWN_NAME } from './constants/names' import { DelegationDTOMapper } from './delegation-dto.mapper' import { DelegationProviderService } from './delegation-provider.service' import { IncomingDelegationsCompanyService } from './delegations-incoming-company.service' @@ -46,6 +53,8 @@ interface FindAvailableInput { @Injectable() export class DelegationsIncomingService { constructor( + @Inject(LOGGER_PROVIDER) + protected readonly logger: Logger, @InjectModel(Client) private clientModel: typeof Client, @InjectModel(ClientAllowedScope) @@ -58,6 +67,8 @@ export class DelegationsIncomingService { private delegationsIncomingWardService: DelegationsIncomingWardService, private delegationsIndexService: DelegationsIndexService, private delegationProviderService: DelegationProviderService, + private nationalRegistryClient: NationalRegistryClientService, + private readonly featureFlagService: FeatureFlagService, ) {} async findAllValid( @@ -116,20 +127,20 @@ export class DelegationsIncomingService { const client = await this.getClientDelegationInfo(user) if (!client?.supportedDelegationTypes) return [] - const types: ClientDelegationType[] = - client.supportedDelegationTypes.filter( + const types: AuthDelegationType[] = client.supportedDelegationTypes + .filter( (dt) => !delegationTypes || delegationTypes.includes(dt.delegationType as AuthDelegationType), ) + .map((t) => t.delegationType as AuthDelegationType) if (types.length == 0) return [] - const providers = await this.delegationProviderService.findProviders( - types.map((t) => t.delegationType), - ) + const providers = await this.delegationProviderService.findProviders(types) - const clientAllowedApiScopes = await this.getClientAllowedApiScopes(user) + const clientAllowedApiScopes: ApiScopeInfo[] = + await this.getClientAllowedApiScopes(user) const delegationPromises = [] @@ -187,6 +198,27 @@ export class DelegationsIncomingService { ) } + if ( + providers.includes(AuthDelegationProvider.DistrictCommissionersRegistry) + ) { + const isLegalRepresentativeDelegationEnabled = + await this.featureFlagService.getValue( + Features.isLegalRepresentativeDelegationEnabled, + true, + user, + ) + if (isLegalRepresentativeDelegationEnabled) { + delegationPromises.push( + this.getAvailableDistrictCommissionersRegistryDelegations( + user, + types, + clientAllowedApiScopes, + client.requireApiScopes, + ), + ) + } + } + const delegationSets = await Promise.all(delegationPromises) let delegations = ([] as MergedDelegationDTO[]) @@ -224,6 +256,28 @@ export class DelegationsIncomingService { return [...mergedDelegationMap.values()] } + private async getAvailableDistrictCommissionersRegistryDelegations( + user: User, + types: AuthDelegationType[], + clientAllowedApiScopes: ApiScopeInfo[], + requireApiScopes?: boolean, + ): Promise { + const records = + await this.delegationsIndexService.getAvailableDistrictCommissionersRegistryRecords( + user, + types, + clientAllowedApiScopes, + requireApiScopes, + ) + const merged = records.map((d) => + DelegationDTOMapper.recordToMergedDelegationDTO(d), + ) + + await Promise.all(merged.map((d) => this.updateName(d))) + + return merged + } + private getClientDelegationInfo( user: User, ): Promise { @@ -260,4 +314,18 @@ export class DelegationsIncomingService { attributes: ['name', 'isAccessControlled'], }) } + + private async updateName( + mergedDelegation: MergedDelegationDTO, + ): Promise { + try { + const fromIndividual: IndividualDto | null = + await this.nationalRegistryClient.getIndividual( + mergedDelegation.fromNationalId, + ) + mergedDelegation.fromName = fromIndividual?.name ?? UNKNOWN_NAME + } catch (error) { + mergedDelegation.fromName = UNKNOWN_NAME + } + } } 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 5fac1d7c9def..fb382c183b49 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 @@ -19,6 +19,7 @@ import { IncomingDelegationsCompanyService } from './delegations-incoming-compan import { DelegationsIncomingCustomService } from './delegations-incoming-custom.service' import { DelegationsIncomingRepresentativeService } from './delegations-incoming-representative.service' import { DelegationsIncomingWardService } from './delegations-incoming-ward.service' +import { ApiScopeInfo } from './delegations-incoming.service' import { DelegationRecordDTO, DelegationRecordInputDTO, @@ -302,6 +303,36 @@ export class DelegationsIndexService { }) } + async getAvailableDistrictCommissionersRegistryRecords( + user: User, + types: AuthDelegationType[], + clientAllowedApiScopes: ApiScopeInfo[], + requireApiScopes?: boolean, + ): Promise { + if (requireApiScopes) { + const noSupportedScope = !clientAllowedApiScopes.some( + (s) => + s.supportedDelegationTypes?.some( + (dt) => dt.delegationType == AuthDelegationType.LegalRepresentative, + ) && !s.isAccessControlled, + ) + if (noSupportedScope) { + return [] + } + } + + return await this.delegationIndexModel + .findAll({ + where: { + toNationalId: user.nationalId, + provider: AuthDelegationProvider.DistrictCommissionersRegistry, + type: types, + validTo: { [Op.or]: [{ [Op.gte]: new Date() }, { [Op.is]: null }] }, + }, + }) + .then((d) => d.map((d) => d.toDTO())) + } + /* * Private methods * */ diff --git a/libs/auth-api-lib/src/lib/delegations/delegations.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations.service.ts index dc7328824215..e19a0cfb370a 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations.service.ts @@ -7,47 +7,47 @@ import { import { ConfigType } from '@nestjs/config' import { InjectModel } from '@nestjs/sequelize' import startOfDay from 'date-fns/startOfDay' +import * as kennitala from 'kennitala' import uniqBy from 'lodash/uniqBy' import { Op } from 'sequelize' import { isUuid } from 'uuidv4' -import * as kennitala from 'kennitala' -import { AuditService } from '@island.is/nest/audit' -import type { User } from '@island.is/auth-nest-tools' +import { RskRelationshipsClient } from '@island.is/clients-rsk-relationships' import { IndividualDto, NationalRegistryClientService, } from '@island.is/clients/national-registry-v2' -import { RskRelationshipsClient } from '@island.is/clients-rsk-relationships' import { LOGGER_PROVIDER } from '@island.is/logging' -import type { Logger } from '@island.is/logging' +import { AuditService } from '@island.is/nest/audit' import { NoContentException } from '@island.is/nest/problem' +import { + AuthDelegationProvider, + AuthDelegationType, +} from '@island.is/shared/types' import { isDefined } from '@island.is/shared/utils' import { ClientAllowedScope } from '../clients/models/client-allowed-scope.model' import { Client } from '../clients/models/client.model' -import type { PersonalRepresentativeDTO } from '../personal-representative/dto/personal-representative.dto' import { PersonalRepresentativeService } from '../personal-representative/services/personalRepresentative.service' +import { DelegationResourcesService } from '../resources/delegation-resources.service' import { ApiScope } from '../resources/models/api-scope.model' import { ResourcesService } from '../resources/resources.service' import { DEFAULT_DOMAIN } from '../types' -import { DelegationConfig } from './DelegationConfig' +import { UNKNOWN_NAME } from './constants/names' import { DelegationScopeService } from './delegation-scope.service' +import { DelegationConfig } from './DelegationConfig' import { UpdateDelegationScopeDTO } from './dto/delegation-scope.dto' import { DelegationDTO } from './dto/delegation.dto' import { DelegationScope } from './models/delegation-scope.model' import { Delegation } from './models/delegation.model' -import { DelegationValidity } from './types/delegationValidity' import { DelegationDirection } from './types/delegationDirection' +import { DelegationValidity } from './types/delegationValidity' import { partitionWithIndex } from './utils/partitionWithIndex' import { getScopeValidityWhereClause } from './utils/scopes' -import { DelegationResourcesService } from '../resources/delegation-resources.service' -import { - AuthDelegationProvider, - AuthDelegationType, -} from '@island.is/shared/types' -export const UNKNOWN_NAME = 'Óþekkt nafn' +import type { User } from '@island.is/auth-nest-tools' +import type { Logger } from '@island.is/logging' +import type { PersonalRepresentativeDTO } from '../personal-representative/dto/personal-representative.dto' type ClientDelegationInfo = Pick< Client, diff --git a/libs/auth-api-lib/src/lib/delegations/dto/delegation-index.dto.ts b/libs/auth-api-lib/src/lib/delegations/dto/delegation-index.dto.ts index c0da33c52460..dca97f8a3a31 100644 --- a/libs/auth-api-lib/src/lib/delegations/dto/delegation-index.dto.ts +++ b/libs/auth-api-lib/src/lib/delegations/dto/delegation-index.dto.ts @@ -1,10 +1,11 @@ -import { IsDateString, IsNumber, IsOptional, IsString } from 'class-validator' import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' +import { IsDateString, IsNumber, IsOptional, IsString } from 'class-validator' + +import { PageInfoDto } from '@island.is/nest/pagination' import { AuthDelegationProvider, AuthDelegationType, } from '@island.is/shared/types' -import { PageInfoDto } from '@island.is/nest/pagination' export class DelegationRecordDTO { @IsString() @@ -19,6 +20,10 @@ export class DelegationRecordDTO { @IsString() @ApiProperty({ type: String, nullable: true }) subjectId?: string | null + + @IsString() + @ApiProperty({ type: String }) + type!: AuthDelegationType } export class PaginatedDelegationRecordDTO { diff --git a/libs/auth-api-lib/src/lib/delegations/models/delegation-index.model.ts b/libs/auth-api-lib/src/lib/delegations/models/delegation-index.model.ts index a5d8a878043b..97f9fbac9e63 100644 --- a/libs/auth-api-lib/src/lib/delegations/models/delegation-index.model.ts +++ b/libs/auth-api-lib/src/lib/delegations/models/delegation-index.model.ts @@ -11,6 +11,9 @@ import { InferAttributes, InferCreationAttributes, } from 'sequelize' + +import { AuthDelegationType } from '@island.is/shared/types' + import { DelegationRecordDTO } from '../dto/delegation-index.dto' @Table({ @@ -78,6 +81,7 @@ export class DelegationIndex extends Model< fromNationalId: this.fromNationalId, toNationalId: this.toNationalId, subjectId: this.subjectId, + type: this.type as AuthDelegationType, } } } diff --git a/libs/auth-api-lib/src/lib/delegations/utils/delegations.ts b/libs/auth-api-lib/src/lib/delegations/utils/delegations.ts index ec0ede8e35c4..f4f36b46df77 100644 --- a/libs/auth-api-lib/src/lib/delegations/utils/delegations.ts +++ b/libs/auth-api-lib/src/lib/delegations/utils/delegations.ts @@ -1,14 +1,16 @@ -import { User } from '@island.is/auth-nest-tools' +import kennitala from 'kennitala' import { Op, WhereOptions } from 'sequelize' + +import { User } from '@island.is/auth-nest-tools' import { AuthDelegationProvider, AuthDelegationType, } from '@island.is/shared/types' + import { DelegationRecordType, PersonalRepresentativeDelegationType, } from '../types/delegationRecord' -import kennitala from 'kennitala' export const delegationProviderTypeMap: Record< AuthDelegationProvider, @@ -23,6 +25,9 @@ export const delegationProviderTypeMap: Record< PersonalRepresentativeDelegationType.PersonalRepresentativePostholf, ], [AuthDelegationProvider.Custom]: [AuthDelegationType.Custom], + [AuthDelegationProvider.DistrictCommissionersRegistry]: [ + AuthDelegationType.LegalRepresentative, + ], } export const getDelegationNoActorWhereClause = (user: User): WhereOptions => { diff --git a/libs/feature-flags/src/lib/features.ts b/libs/feature-flags/src/lib/features.ts index 00d708e0408b..5abd7e28aad8 100644 --- a/libs/feature-flags/src/lib/features.ts +++ b/libs/feature-flags/src/lib/features.ts @@ -105,6 +105,9 @@ export enum Features { // Single sign on passkeys isPasskeyRegistrationEnabled = 'isPasskeyRegistrationEnabled', isPasskeyAuthEnabled = 'isPasskeyAuthEnabled', + + // Legal represantative delegation type + isLegalRepresentativeDelegationEnabled = 'isLegalRepresentativeDelegationEnabled', } export enum ServerSideFeature { diff --git a/libs/services/auth/testing/src/fixtures/fixture-factory.ts b/libs/services/auth/testing/src/fixtures/fixture-factory.ts index 41db8c762f87..9e6f3acaf428 100644 --- a/libs/services/auth/testing/src/fixtures/fixture-factory.ts +++ b/libs/services/auth/testing/src/fixtures/fixture-factory.ts @@ -321,6 +321,8 @@ export class FixtureFactory { return AuthDelegationProvider.CompanyRegistry case AuthDelegationType.PersonalRepresentative: return AuthDelegationProvider.PersonalRepresentativeRegistry + case AuthDelegationType.LegalRepresentative: + return AuthDelegationProvider.DistrictCommissionersRegistry default: return '' } diff --git a/libs/shared/types/src/lib/delegation.ts b/libs/shared/types/src/lib/delegation.ts index d33e6a311f2e..ee100d6b4b5c 100644 --- a/libs/shared/types/src/lib/delegation.ts +++ b/libs/shared/types/src/lib/delegation.ts @@ -3,6 +3,7 @@ export enum AuthDelegationType { LegalGuardian = 'LegalGuardian', Custom = 'Custom', PersonalRepresentative = 'PersonalRepresentative', + LegalRepresentative = 'LegalRepresentative', } export enum AuthDelegationProvider { @@ -10,4 +11,5 @@ export enum AuthDelegationProvider { CompanyRegistry = 'fyrirtaekjaskra', PersonalRepresentativeRegistry = 'talsmannagrunnur', Custom = 'delegationdb', + DistrictCommissionersRegistry = 'syslumenn', }