Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ids-api): Add alive check to legal representative delegation type #16284

Merged
merged 18 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions libs/auth-api-lib/src/lib/delegations/alive-status.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Inject, Logger } from '@nestjs/common'
import * as kennitala from 'kennitala'

import {
IndividualDto,
NationalRegistryClientService,
} from '@island.is/clients/national-registry-v2'
import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry'
import { LOGGER_PROVIDER } from '@island.is/logging'
import { isDefined } from '@island.is/shared/utils'

import { UNKNOWN_NAME } from './constants/names'
import { partitionWithIndex } from './utils/partitionWithIndex'

export type NameInfo = {
nationalId: string
name: string
}

export class AliveStatusService {
valurefugl marked this conversation as resolved.
Show resolved Hide resolved
constructor(
private nationalRegistryClient: NationalRegistryClientService,
private companyRegistryClient: CompanyRegistryClientService,
@Inject(LOGGER_PROVIDER)
private logger: Logger,
) {}

/**
* Divides nationalIds into alive and deceased
* - Makes calls for every nationalId to NationalRegistry to check if the person exists.
* - Divides the nationalIds into alive and deceased, based on
* 1. All companies will be divided into alive.
valurefugl marked this conversation as resolved.
Show resolved Hide resolved
* 2. If the person exists in NationalRegistry, then the person is alive.
*/
public async getStatus(nationalIds: string[]): Promise<{
aliveNationalIds: string[]
deceasedNationalIds: string[]
aliveNameInfo: NameInfo[]
}> {
if (nationalIds.length === 0) {
return {
aliveNationalIds: [],
deceasedNationalIds: [],
aliveNameInfo: [],
}
}

const promises = nationalIds.map((nationalId) =>
kennitala.isCompany(nationalId)
? this.companyRegistryClient
.getCompany(nationalId)
.catch(this.handlerGetError)
valurefugl marked this conversation as resolved.
Show resolved Hide resolved
: this.nationalRegistryClient
.getIndividual(nationalId)
.catch(this.handlerGetError),
)

try {
// Check if nationalId is linked to a person, i.e. not deceased
const identities = await Promise.all(promises)
const identitiesValuesNoError = identities
.filter(this.isNotError)
.filter(isDefined)
.map((identity) => ({
nationalId: identity.nationalId,
name: identity.name ?? UNKNOWN_NAME,
}))

// Divide nationalIds into alive or deceased.
const [aliveNationalIds, deceasedNationalIds] = partitionWithIndex(
nationalIds,
(nationalId, index) =>
// All companies will be divided into alive
kennitala.isCompany(nationalId) ||
// Pass through although Þjóðskrá API throws an error
identities[index] instanceof Error ||
// Make sure we can match the person to the nationalId, i.e. not deceased
(identities[index] as IndividualDto)?.nationalId === nationalId,
)

return {
aliveNationalIds,
deceasedNationalIds,
aliveNameInfo: identitiesValuesNoError,
saevarma marked this conversation as resolved.
Show resolved Hide resolved
}
} catch (error) {
this.logger.error(`Error getting live status.}`, error)
valurefugl marked this conversation as resolved.
Show resolved Hide resolved

// We do not want to fail the whole request if we cannot get the live status.
// Therefore, we return all nationalIds as alive.
return {
aliveNationalIds: nationalIds,
deceasedNationalIds: [],
aliveNameInfo: [],
}
}
}

private handlerGetError(error: null | Error) {
return error
}

/**
* Checks if item is not an instance of Error
*/
private isNotError<T>(item: T | Error): item is T {
return item instanceof Error === false
}
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,37 @@
import { Inject, Injectable, Logger } from '@nestjs/common'
import { InjectModel } from '@nestjs/sequelize'
import { Inject, Injectable } from '@nestjs/common'
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 startOfDay from 'date-fns/startOfDay'

import { User } from '@island.is/auth-nest-tools'
import {
IndividualDto,
NationalRegistryClientService,
} from '@island.is/clients/national-registry-v2'
import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry'
import { LOGGER_PROVIDER } from '@island.is/logging'
import { AuditService } from '@island.is/nest/audit'
import { AuthDelegationType } from '@island.is/shared/types'
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 { AliveStatusService, NameInfo } from './alive-status.service'
import { UNKNOWN_NAME } from './constants/names'
import { DelegationConfig } from './DelegationConfig'
import { ApiScopeInfo } from './delegations-incoming.service'
import { DelegationDTO } from './dto/delegation.dto'
import { MergedDelegationDTO } from './dto/merged-delegation.dto'
import { DelegationDelegationType } from './models/delegation-delegation-type.model'
import { DelegationScope } from './models/delegation-scope.model'
import { Delegation } from './models/delegation.model'
import { DelegationValidity } from './types/delegationValidity'
import { partitionWithIndex } from './utils/partitionWithIndex'
import { getScopeValidityWhereClause } from './utils/scopes'
import { DelegationDelegationType } from './models/delegation-delegation-type.model'
import { DelegationConfig } from './DelegationConfig'

type FindAllValidIncomingOptions = {
nationalId: string
domainName?: string
validity?: DelegationValidity
}

type FromNameInfo = {
nationalId: string
name: string
}

/**
* Service class for incoming delegations.
* This class supports domain based delegations.
Expand All @@ -54,10 +43,7 @@ export class DelegationsIncomingCustomService {
private delegationModel: typeof Delegation,
@InjectModel(ApiScopeUserAccess)
private apiScopeUserAccessModel: typeof ApiScopeUserAccess,
private nationalRegistryClient: NationalRegistryClientService,
private companyRegistryClient: CompanyRegistryClientService,
@Inject(LOGGER_PROVIDER)
private logger: Logger,
private aliveStatusService: AliveStatusService,
@Inject(DelegationConfig.KEY)
private delegationConfig: ConfigType<typeof DelegationConfig>,
private auditService: AuditService,
Expand Down Expand Up @@ -243,7 +229,7 @@ export class DelegationsIncomingCustomService {
private async findAllIncomingGeneralMandates(
{ nationalId }: FindAllValidIncomingOptions,
useMaster = false,
): Promise<{ delegations: Delegation[]; fromNameInfo: FromNameInfo[] }> {
): Promise<{ delegations: Delegation[]; fromNameInfo: NameInfo[] }> {
const startOfToday = startOfDay(new Date())

const delegations = await this.delegationModel.findAll({
Expand All @@ -268,10 +254,15 @@ export class DelegationsIncomingCustomService {
})

// Check live status, i.e. dead or alive for delegations
const { aliveDelegations, deceasedDelegations, fromNameInfo } =
await this.getLiveStatusFromDelegations(delegations)
const { aliveNationalIds, deceasedNationalIds, aliveNameInfo } =
await this.aliveStatusService.getStatus(
delegations.map((d) => d.fromNationalId),
)

if (deceasedDelegations.length > 0) {
if (deceasedNationalIds.length > 0) {
const deceasedDelegations = delegations.filter((d) =>
deceasedNationalIds.includes(d.fromNationalId),
)
// Delete all deceased delegations by deleting them and their scopes.
const deletePromises = deceasedDelegations.map((delegation) =>
delegation.destroy(),
Expand All @@ -286,7 +277,12 @@ export class DelegationsIncomingCustomService {
})
}

return { delegations: aliveDelegations, fromNameInfo }
return {
delegations: delegations.filter((d) =>
aliveNationalIds.includes(d.fromNationalId),
),
fromNameInfo: aliveNameInfo,
}
}

private async findAllIncoming(
Expand All @@ -298,7 +294,7 @@ export class DelegationsIncomingCustomService {
validity: DelegationValidity
},
useMaster = false,
): Promise<{ delegations: Delegation[]; fromNameInfo: FromNameInfo[] }> {
): Promise<{ delegations: Delegation[]; fromNameInfo: NameInfo[] }> {
let whereOptions = getScopeValidityWhereClause(validity)
if (domainName) whereOptions = { ...whereOptions, domainName: domainName }

Expand Down Expand Up @@ -336,10 +332,15 @@ export class DelegationsIncomingCustomService {
})

// Check live status, i.e. dead or alive for delegations
const { aliveDelegations, deceasedDelegations, fromNameInfo } =
await this.getLiveStatusFromDelegations(delegations)
const { aliveNationalIds, deceasedNationalIds, aliveNameInfo } =
await this.aliveStatusService.getStatus(
delegations.map((d) => d.fromNationalId),
)

if (deceasedDelegations.length > 0) {
if (deceasedNationalIds.length > 0) {
const deceasedDelegations = delegations.filter((d) =>
deceasedNationalIds.includes(d.fromNationalId),
)
// Delete all deceased delegations by deleting them and their scopes.
const deletePromises = deceasedDelegations.map((delegation) =>
delegation.destroy(),
Expand All @@ -354,7 +355,12 @@ export class DelegationsIncomingCustomService {
})
}

return { delegations: aliveDelegations, fromNameInfo }
return {
delegations: delegations.filter((d) =>
aliveNationalIds.includes(d.fromNationalId),
),
fromNameInfo: aliveNameInfo,
}
}

private checkIfScopeIsValid(
Expand Down Expand Up @@ -389,100 +395,11 @@ export class DelegationsIncomingCustomService {
return false
}

/**
* Divides delegations into alive and deceased delegations
* - Makes calls for every delegation to NationalRegistry to check if the person exists.
* - Divides the delegations into alive and deceased delegations, based on
* 1. All companies will be divided into alive delegations.
* 2. If the person exists in NationalRegistry, then the delegation is alive.
*/
private async getLiveStatusFromDelegations(
delegations: Delegation[],
): Promise<{
aliveDelegations: Delegation[]
deceasedDelegations: Delegation[]
fromNameInfo: FromNameInfo[]
}> {
if (delegations.length === 0) {
return {
aliveDelegations: [],
deceasedDelegations: [],
fromNameInfo: [],
}
}

const delegationsPromises = delegations.map(({ fromNationalId }) =>
kennitala.isCompany(fromNationalId)
? this.companyRegistryClient
.getCompany(fromNationalId)
.catch(this.handlerGetError)
: this.nationalRegistryClient
.getIndividual(fromNationalId)
.catch(this.handlerGetError),
)

try {
// Check if delegations is linked to a person, i.e. not deceased
const identities = await Promise.all(delegationsPromises)
const identitiesValuesNoError = identities
.filter(this.isNotError)
.filter(isDefined)
.map((identity) => ({
nationalId: identity.nationalId,
name: identity.name ?? UNKNOWN_NAME,
}))

// Divide delegations into alive or deceased delegations.
const [aliveDelegations, deceasedDelegations] = partitionWithIndex(
delegations,
({ fromNationalId }, index) =>
// All companies will be divided into aliveDelegations
kennitala.isCompany(fromNationalId) ||
// Pass through although Þjóðskrá API throws an error since it is not required to view the delegation.
identities[index] instanceof Error ||
// Make sure we can match the person to the delegation, i.e. not deceased
(identities[index] as IndividualDto)?.nationalId === fromNationalId,
)

return {
aliveDelegations,
deceasedDelegations,
fromNameInfo: identitiesValuesNoError,
}
} catch (error) {
this.logger.error(
`Error getting live status from delegations. Delegations: ${delegations.map(
(d) => d.id,
)}`,
error,
)

// We do not want to fail the whole request if we cannot get the live status from delegations.
// Therefore, we return all delegations as alive delegations.
return {
aliveDelegations: delegations,
deceasedDelegations: [],
fromNameInfo: [],
}
}
}

private handlerGetError(error: null | Error) {
return error
}

/**
* Checks if item is not an instance of Error
*/
private isNotError<T>(item: T | Error): item is T {
return item instanceof Error === false
}

/**
* Finds person by nationalId.
*/
private getPersonByNationalId(
identities: Array<FromNameInfo | null>,
identities: Array<NameInfo | null>,
nationalId: string,
) {
return identities.find((identity) => identity?.nationalId === nationalId)
valurefugl marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Loading
Loading