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(auth-api): Add column to delegation type indicating if actor discretion is required #16226

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ export const indexingTestCases: Record<string, TestCase> = {
createClient({
clientId: clientId,
supportsPersonalRepresentatives: true,
supportedDelegationTypes: [AuthDelegationType.PersonalRepresentative],
supportedDelegationTypes: [
`${AuthDelegationType.PersonalRepresentative}:${prRight1}`,
],
}),
{
fromRepresentative: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import request from 'supertest'
import { getModelToken } from '@nestjs/sequelize'
import request from 'supertest'

import { TestApp } from '@island.is/testing/nest'
import {
createCurrentUser,
createNationalId,
} from '@island.is/testing/fixtures'
import { AuthScope } from '@island.is/auth/scopes'
import { DelegationIndex } from '@island.is/auth-api-lib'
import { AuthScope } from '@island.is/auth/scopes'
import { FixtureFactory } from '@island.is/services/auth/testing'
import {
AuthDelegationProvider,
AuthDelegationType,
} from '@island.is/shared/types'
import {
createCurrentUser,
createNationalId,
} from '@island.is/testing/fixtures'
import { TestApp } from '@island.is/testing/nest'

import { setupWithAuth } from '../../../../../test/setup'

Expand Down Expand Up @@ -249,6 +250,7 @@ describe('DelegationIndexController', () => {
describe('With valid delegation provider', () => {
let app: TestApp
let server: request.SuperTest<request.Test>
let factory: FixtureFactory

let delegationIndexModel: typeof DelegationIndex
const delegationProvider = AuthDelegationProvider.CompanyRegistry
Expand All @@ -262,6 +264,8 @@ describe('DelegationIndexController', () => {
app = await setupWithAuth({
user,
})
factory = new FixtureFactory(app)
await factory.createAllDelegationTypes()
server = request(app.getHttpServer())

delegationIndexModel = app.get(getModelToken(DelegationIndex))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('DelegationsController', () => {

await factory.createClient(testcase.client)
await factory.createDomain(testcase.domain)
await factory.createAllDelegationTypes()

// create api scopes in db
await Promise.all(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {
DelegationDirection,
PersonalRepresentativeDelegationType,
} from '@island.is/auth-api-lib'
import { AuthDelegationType } from '@island.is/shared/types'
import { createNationalId } from '@island.is/testing/fixtures'

import {
personalRepresentativeRightTypeCodeFinance,
scopes,
TestCase,
} from './delegations.controller-test-types'
import {
DelegationDirection,
PersonalRepresentativeDelegationType,
} from '@island.is/auth-api-lib'

const person1 = createNationalId('person')
const person2 = createNationalId('person')
Expand Down Expand Up @@ -185,7 +187,7 @@ export const validTestCases: Record<
},
{
toNationalId: person1,
type: personalRepresentativeRightTypeCodeFinance as PersonalRepresentativeDelegationType, // type that scope has no permission for
type: `${AuthDelegationType.PersonalRepresentative}:${personalRepresentativeRightTypeCodeFinance}` as PersonalRepresentativeDelegationType, // type that scope has no permission for
},
],
scopes: [scopes.representative], // has permission for postholf right type not finance
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getModelToken } from '@nestjs/sequelize'
import addDays from 'date-fns/addDays'
import request from 'supertest'
import { uuid } from 'uuidv4'
import addDays from 'date-fns/addDays'

import {
ApiScope,
Expand Down Expand Up @@ -245,6 +245,7 @@ describe('DelegationsController', () => {
name: 'custom',
description: 'custom',
providerId: AuthDelegationProvider.Custom,
actorDiscretionRequired: false,
})

await delegationScopesModel.create({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ const delegations: Record<string, DelegationRecordDTO[]> = {
toNationalId: userWithNoDelegations.nationalId,
subjectId: null, // test that 3rd party login is not used if subjectId is null
type: AuthDelegationType.ProcurationHolder,
actorDiscretionRequired: false,
},
],
[userWithDelegations2.nationalId]: [
Expand All @@ -173,6 +174,7 @@ const delegations: Record<string, DelegationRecordDTO[]> = {
toNationalId: userWithDelegations.nationalId,
subjectId: delegationSubjectId,
type: AuthDelegationType.ProcurationHolder,
actorDiscretionRequired: false,
},
],
[userWithSendToDelegationsFeatureFlagDisabled.nationalId]: [
Expand All @@ -181,6 +183,7 @@ const delegations: Record<string, DelegationRecordDTO[]> = {
toNationalId: userWithNoDelegations.nationalId,
subjectId: faker.datatype.uuid(),
type: AuthDelegationType.ProcurationHolder,
actorDiscretionRequired: false,
},
],
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict'

module.exports = {
async up(queryInterface, Sequelize) {
return Promise.all([
queryInterface.addColumn('delegation_type', 'actor_discretion_required', {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false,
}),
])
},

async down(queryInterface) {
return Promise.all([
queryInterface.removeColumn(
'delegation_type',
'actor_discretion_required',
),
])
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict'

module.exports = {
up: (queryInterface) => {
return Promise.all([
queryInterface.addConstraint('delegation_index', {
fields: ['type'],
type: 'foreign key',
name: 'FK_delegation_index_delegation_type',
references: {
table: 'delegation_type',
field: 'id',
},
}),
queryInterface.addConstraint('delegation_index', {
fields: ['provider'],
type: 'foreign key',
name: 'FK_delegation_index_delegation_provider',
references: {
table: 'delegation_provider',
field: 'id',
},
}),
])
},

down: (queryInterface) => {
return Promise.all([
queryInterface.removeConstraint(
'delegation_index',
'FK_delegation_index_delegation_type',
),
queryInterface.removeConstraint(
'delegation_index',
'FK_delegation_index_delegation_provider',
),
])
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
up(queryInterface) {
return queryInterface.sequelize.query(`
BEGIN;
UPDATE delegation_type
SET actor_discretion_required = true
WHERE id = 'LegalRepresentative';

COMMIT;
`)
},

down(queryInterface) {
return queryInterface.sequelize.query(`
BEGIN;
UPDATE delegation_type
SET actor_discretion_required = false
WHERE id = 'LegalRepresentative';

COMMIT;
`)
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import union from 'lodash/union'
import { Op } from 'sequelize'

import { Auth, User } from '@island.is/auth-nest-tools'
import { AuditService } from '@island.is/nest/audit'
import { type Logger, LOGGER_PROVIDER } from '@island.is/logging'
import { AuditService } from '@island.is/nest/audit'
import {
AuthDelegationProvider,
AuthDelegationType,
Expand All @@ -30,6 +30,7 @@ import {
import { DelegationDTO } from './dto/delegation.dto'
import { DelegationIndexMeta } from './models/delegation-index-meta.model'
import { DelegationIndex } from './models/delegation-index.model'
import { DelegationTypeModel } from './models/delegation-type.model'
import { DelegationDirection } from './types/delegationDirection'
import {
DelegationRecordType,
Expand Down Expand Up @@ -378,6 +379,7 @@ export class DelegationsIndexService {
type: types,
validTo: { [Op.or]: [{ [Op.gte]: new Date() }, { [Op.is]: null }] },
},
include: [{ model: DelegationTypeModel }],
})
.then((d) => d.map((d) => d.toDTO()))
}
Expand Down Expand Up @@ -647,6 +649,7 @@ export class DelegationsIndexService {
customDelegationScopes: { [Op.contains]: [scope.name] },
validTo: { [Op.or]: [{ [Op.gte]: new Date() }, { [Op.is]: null }] },
},
include: [{ model: DelegationTypeModel }],
})
.then((d) => d.map((d) => d.toDTO()))
}
Expand Down Expand Up @@ -682,6 +685,7 @@ export class DelegationsIndexService {
provider: AuthDelegationProvider.PersonalRepresentativeRegistry,
validTo: { [Op.or]: [{ [Op.gte]: new Date() }, { [Op.is]: null }] },
},
include: [{ model: DelegationTypeModel }],
})
.then((d) => d.map((d) => d.toDTO()))
}
Expand All @@ -708,6 +712,7 @@ export class DelegationsIndexService {
provider: AuthDelegationProvider.CompanyRegistry,
validTo: { [Op.or]: [{ [Op.gte]: new Date() }, { [Op.is]: null }] },
},
include: [{ model: DelegationTypeModel }],
})
.then((d) => d.map((d) => d.toDTO()))
}
Expand Down Expand Up @@ -736,6 +741,7 @@ export class DelegationsIndexService {
provider: AuthDelegationProvider.NationalRegistry,
validTo: { [Op.or]: [{ [Op.gte]: new Date() }, { [Op.is]: null }] },
},
include: [{ model: DelegationTypeModel }],
})
.then((d) => d.map((d) => d.toDTO()))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import { IsDateString, IsNumber, IsOptional, IsString } from 'class-validator'
import {
IsBoolean,
IsDateString,
IsNumber,
IsOptional,
IsString,
} from 'class-validator'

import { PageInfoDto } from '@island.is/nest/pagination'
import {
Expand All @@ -24,6 +30,11 @@ export class DelegationRecordDTO {
@IsString()
@ApiProperty({ type: String })
type!: AuthDelegationType

@IsOptional()
@IsBoolean()
@ApiProperty({ type: Boolean, nullable: true })
actorDiscretionRequired?: boolean | null
}

export class PaginatedDelegationRecordDTO {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import {
type CreationOptional,
InferAttributes,
InferCreationAttributes,
type NonAttribute,
} from 'sequelize'
import {
BelongsTo,
Column,
CreatedAt,
DataType,
ForeignKey,
Model,
Table,
UpdatedAt,
} from 'sequelize-typescript'
import {
type CreationOptional,
InferAttributes,
InferCreationAttributes,
} from 'sequelize'

import { AuthDelegationType } from '@island.is/shared/types'

import { DelegationRecordDTO } from '../dto/delegation-index.dto'
import { DelegationProviderModel } from './delegation-provider.model'
import { DelegationTypeModel } from './delegation-type.model'

@Table({
tableName: 'delegation_index',
Expand Down Expand Up @@ -43,13 +48,15 @@ export class DelegationIndex extends Model<
allowNull: false,
primaryKey: true,
})
@ForeignKey(() => DelegationProviderModel)
provider!: string

@Column({
type: DataType.STRING,
allowNull: false,
primaryKey: true,
})
@ForeignKey(() => DelegationTypeModel)
type!: string

@Column({
Expand All @@ -76,12 +83,16 @@ export class DelegationIndex extends Model<
@UpdatedAt
readonly modified?: Date

@BelongsTo(() => DelegationTypeModel)
delegationType?: NonAttribute<DelegationTypeModel>

toDTO(): DelegationRecordDTO {
return {
fromNationalId: this.fromNationalId,
toNationalId: this.toNationalId,
subjectId: this.subjectId,
type: this.type as AuthDelegationType,
actorDiscretionRequired: this.delegationType?.actorDiscretionRequired,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle potential undefined 'actorDiscretionRequired' in DTO.

Since delegationType might not be loaded, actorDiscretionRequired could be undefined. Consider providing a default value or handling this case to ensure consistent behavior for consumers of the DTO.

}
}
}
Loading
Loading