Search…
Migrating Data in the IDS
You can define clients and scopes for production using seed migrations in the seeders/data folder.
These are generally "island.is" clients and scopes that need to be deployed to our dev, staging and prod environments. Other organisations will eventually get access to a self-service interface and should not need to manage their data in all IDS environments.

Defining seed migrations

First, create a TypeScript module with a file name describing your new client or scope, prefixed with client- or scope- respectively. E.g. scooe-finance.ts.
The filename is important in that Sequelize uses the filename to deduplicate seed migrations. Each seed migration runs only once in each environment. This means you should avoid renaming migrations after they've run in some environments.
Seed migrations run in alphabetized order based on file names, which means that client-*.ts migrations are run before scope-*.ts migrations.
If you're creating data that is connected and migrated in the same release, make sure that one (e.g. the client) is created first and then connect them when creating the other one (e.g. the addToClients option on the scope).
It is not possible to import any modules from outside the seeders/data folder since everything in this folder is transpiled manually into the dist folder during build.

Creating clients

We provide a helper called createClient to seed a client in all environments:
1
// client-test.ts
2
import { createClient } from './helpers'
3
4
export const up = createClient({
5
// Required:
6
clientId: '@island.is/test',
7
clientType: 'machine',
8
displayName: 'Test client',
9
description: 'Talar við test API',
10
11
// Optional:
12
contactNationalId: '1111111111',
13
contactEmail: '[email protected]',
14
grantTypes: ['client_credentials']
15
allowedScopes: ['openid', '@island.is/applications:read'],
16
supportDelegations: true,
17
redirectUris: {
18
dev: ['http://localhost:4444/some/path', 'https://beta.dev01.devland.is/some/path'],
19
staging: ['https://beta.staging01.devland.is/some/path'],
20
prod: ['https://island.is/some/path'],
21
},
22
postLogoutRedirectUri: {
23
dev: 'https://beta.dev01.devland.is',
24
staging: 'https://beta.staging01.devland.is',
25
prod: 'https://island.is',
26
},
27
})
Copied!
Notes about the arguments:
  • clientId should be prefixed with the organisation domain, eg @island.is.
  • clientType should be spa for front-end web apps, web for websites that have a cookie based authentication through a backend, native for mobile apps and machine for backend clients.
  • displayName will be shown to the user when they are authenticating to this client.
  • description should describe the purpose of the client. Not shown to the user.
  • contactNationalId and contactEmail default to Digital Iceland and it's technical contact.
  • grantTypes specifies which token grant flows the client can use. Defaults to ['client_credentials'] for machine and web clients, ['authorization_code'] for spa and native clients.
  • allowedScopes lists the scopes this client can request. Should include openid and any identity and api scopes that the client needs access to.
  • supportDelegations toggles whether users can authenticate to this client with delegations (legal guardians, procuring holders and custom delegations).
  • redirectUri should specify the list of allowed redirect uris for each environment.
  • postLogoutRedirectUri is where the user should be redirected to after logging out.
The module export must be named up so Sequelize picks it up. You don't need to define down exports. All migrations should be one directional and backwards compatible and never need a rollback.

Creating scopes

There's another helper called createScope to seed a scope in all environments:
1
// scope-test.ts
2
import { createScope } from './helpers'
3
4
export const up = createScope({
5
// Required:
6
name: '@island.is/test',
7
displayName: 'Test scope',
8
description: 'Good description for the scope',
9
10
// Optional:
11
delegation: {
12
custom: true,
13
legalGuardians: true,
14
procuringHolders: true,
15
},
16
accessControlled: false,
17
addToResource: '@island.is',
18
addToClients: ['@island.is/test'],
19
})
Copied!
Notes about the arguments:
  • name should be prefixed with the organisation domain, eg @island.is.
  • displayName is the name of the scope, shown to users.
  • description is a description of what the scope gives access to, also shown to users.
  • delegation allows you to configure if the scope should be automatically granted to legal guardians and procuring holders, or if it should support custom delegations. Defaults to no delegation support.
  • accessControlled: true makes this a special scope that normal users don't have access to. It is possible to give users access to this scope in the IDS admin. This is a simple tool to manage access to admin clients and resources.
  • addToResource specifies which resource this scope belongs to. Defaults to @island.is.
  • addToClients adds this scope as allowedScopes for the specified clients.

Using compose.

Each seed migration can define multiple clients and scopes using compose:
1
import { compose, createClient, createScope } from './helpers'
2
3
export const up = compose(
4
createClient({
5
clientId: '@island.is/test',
6
clientType: 'machine',
7
displayName: 'Test Client',
8
description: 'Bla',
9
allowedScopes: ['openid'],
10
}),
11
createScope({
12
name: '@island.is/test',
13
displayName: 'Test',
14
description: 'Bla',
15
addToClients: ['@island.is/test'],
16
}),
17
createScope({
18
name: '@island.is/test2',
19
displayName: 'Test 2',
20
description: 'Bla',
21
addToClients: ['@island.is/test'],
22
}),
23
)
Copied!
Please keep in mind the order of filenames if there are dependencies between seed migrations in the same release.

Error handling and create/update logic

These helpers will generally catch and log database errors that correspond to unique index conflicts.
This means that it's generally safe to define the same data in a seed migration as well as in the IDS admin. The migration will ignore any data that already exists.
It works on a row by row basis. The migration will not update any existing data, but it will add new rows/relations when they don't exist.
Let's say you specify a client X in a seed migration that is allowed to use scopes A and B. If client X is already defined in dev with scope A when the migration runs, it will only add scope B as an allowed scope for client X. The seed migration may still create client X on staging and prod if it doesn't exist already.
Last modified 4mo ago