diff --git a/package-lock.json b/package-lock.json index a08efeaf..624ec71b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "requires": true, "packages": { "": { - "version": "0.2.7", + "version": "0.2.9", "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.6", diff --git a/src/tasks/keycloak/keycloak.ts b/src/tasks/keycloak/keycloak.ts index 75896895..cbfd59e4 100644 --- a/src/tasks/keycloak/keycloak.ts +++ b/src/tasks/keycloak/keycloak.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Issuer } from 'openid-client' +import { Issuer, TokenSet } from 'openid-client' import { ClientsApi, IdentityProvidersApi, @@ -8,8 +8,11 @@ import { ProtocolMappersApi, RealmsAdminApi, IdentityProviderMapperRepresentation, + IdentityProviderRepresentation, + ClientScopeRepresentation, + ClientRepresentation, + ProtocolMapperRepresentation, } from '@redkubes/keycloak-client-node' -import { find } from 'lodash' import * as realmConfig from './realm-factory' import { cleanEnv, @@ -36,7 +39,7 @@ async function main(): Promise { await faultTolerantFetch(env.KEYCLOAK_ADDRESS) let basePath - let token + let token: TokenSet try { const keycloakAddress = env.KEYCLOAK_ADDRESS const keycloakRealm = env.KEYCLOAK_REALM @@ -73,106 +76,116 @@ async function main(): Promise { // Create Client Scopes const scope = realmConfig.createClientScopes() - const clientScopes = !(await doApiCall(errors, 'Creating openid client scope', () => - clientScope.realmClientScopesPost(env.KEYCLOAK_REALM, scope), - )) - if (clientScopes) { - // @NOTE this PUT operation is almost pointless as it is not updating deep nested properties because of various db constraints + + const clientScopes = (await doApiCall(errors, 'Getting openid client scope', () => + clientScope.realmClientScopesGet(env.KEYCLOAK_REALM), + )) as Array + const existingScope = clientScopes.find((el) => el.name === scope.name) + if (existingScope) { await doApiCall(errors, 'Updating openid client scope', async () => { - const currentClientScopes = await clientScope.realmClientScopesGet(env.KEYCLOAK_REALM) - const { id } = ensure(find(currentClientScopes.body, { name: scope.name })) - return clientScope.realmClientScopesIdPut(env.KEYCLOAK_REALM, ensure(id), scope) + // @NOTE this PUT operation is almost pointless as it is not updating deep nested properties because of various db constraints + return clientScope.realmClientScopesIdPut(env.KEYCLOAK_REALM, existingScope.id!, scope) }) + } else { + await doApiCall(errors, 'Creating openid client scope', () => + clientScope.realmClientScopesPost(env.KEYCLOAK_REALM, scope), + ) } // Create Roles const teamRoles = realmConfig.mapTeamsToRoles() + interface RealmRole { + name: string + } + const existingRoles = (await doApiCall(errors, `Getting all roles from realm ${env.KEYCLOAK_REALM}`, async () => + roles.realmRolesGet(env.KEYCLOAK_REALM), + )) as Array + await Promise.all( - teamRoles.map( - async (role): Promise => { - const exists = await doApiCall(errors, `Creating role ${role.name}`, async () => - roles.realmRolesPost(env.KEYCLOAK_REALM, role), + teamRoles.map((role) => { + const exists = existingRoles.some((el) => el.name === role.name!) + if (exists) { + return doApiCall(errors, `Updating role ${role.name!}`, async () => + roles.realmRolesRoleNamePut(env.KEYCLOAK_REALM, role.name ?? '', role), ) - if (exists) - await doApiCall(errors, `Updating role ${role.name}`, async () => - roles.realmRolesRoleNamePut(env.KEYCLOAK_REALM, role.name ?? '', role), - ) - }, - ), + } + return doApiCall(errors, `Creating role ${role.name!}`, async () => + roles.realmRolesPost(env.KEYCLOAK_REALM, role), + ) + }), ) // Create Identity Provider const idp = await realmConfig.createIdProvider() - const idpExists = await doApiCall(errors, 'Creating identity provider', async () => { - return providers.realmIdentityProviderInstancesPost(env.KEYCLOAK_REALM, idp) - }) - if (idpExists) { + + const existingProviders = (await doApiCall(errors, 'Geting identity provider', async () => { + return providers.realmIdentityProviderInstancesGet(env.KEYCLOAK_REALM) + })) as Array + + if (existingProviders.some((el) => el.alias === idp.alias)) { await doApiCall(errors, 'Updating identity provider', async () => { return providers.realmIdentityProviderInstancesAliasPut(env.KEYCLOAK_REALM, ensure(idp.alias), idp) }) + } else { + await doApiCall(errors, 'Creating identity provider', async () => { + return providers.realmIdentityProviderInstancesPost(env.KEYCLOAK_REALM, idp) + }) } // Create Identity Provider Mappers // @NOTE - PUT involves adding strict required properties not in the factory const idpMappers = realmConfig.createIdpMappers() - const existingMappers: IdentityProviderMapperRepresentation[] = await doApiCall( - errors, - `Getting mappers`, - () => providers.realmIdentityProviderInstancesAliasMappersGet(env.KEYCLOAK_REALM, env.IDP_ALIAS), - 400, - ) + + const existingMappers = (await doApiCall(errors, `Getting role mappers`, () => + providers.realmIdentityProviderInstancesAliasMappersGet(env.KEYCLOAK_REALM, env.IDP_ALIAS), + )) as IdentityProviderMapperRepresentation[] + await Promise.all( - idpMappers.map(async (idpMapper) => { + idpMappers.map((idpMapper) => { const existingMapper: IdentityProviderMapperRepresentation | undefined = (existingMappers || []).find( (m) => m.name === idpMapper.name, ) if (existingMapper) { - await doApiCall( - errors, - `Updating mapper ${idpMapper.name}`, - () => - providers.realmIdentityProviderInstancesAliasMappersIdPut( - env.KEYCLOAK_REALM, - env.IDP_ALIAS, - existingMapper.id!, - { ...existingMapper, ...idpMapper }, - ), - 400, - ) - } else { - await doApiCall( - errors, - `Creating mapper ${idpMapper.name}`, - () => providers.realmIdentityProviderInstancesAliasMappersPost(env.KEYCLOAK_REALM, env.IDP_ALIAS, idpMapper), - 400, + return doApiCall(errors, `Updating mapper ${idpMapper.name!}`, () => + providers.realmIdentityProviderInstancesAliasMappersIdPut( + env.KEYCLOAK_REALM, + env.IDP_ALIAS, + existingMapper.id!, + { ...existingMapper, ...idpMapper }, + ), ) } + return doApiCall(errors, `Creating mapper ${idpMapper.name!}`, () => + providers.realmIdentityProviderInstancesAliasMappersPost(env.KEYCLOAK_REALM, env.IDP_ALIAS, idpMapper), + ) }), ) - // Create Otomi Client const client = realmConfig.createClient() - const clientExists = await doApiCall(errors, 'Creating otomi client', () => - clients.realmClientsPost(env.KEYCLOAK_REALM, client), - ) - if (clientExists) { + const allClients = (await doApiCall(errors, 'Getting otomi client', () => + clients.realmClientsGet(env.KEYCLOAK_REALM), + )) as Array + + if (allClients.some((el) => el.name === client.name)) { await doApiCall(errors, 'Updating otomi client', () => clients.realmClientsIdPut(env.KEYCLOAK_REALM, ensure(client.id), client), ) + } else { + await doApiCall(errors, 'Creating otomi client', () => clients.realmClientsPost(env.KEYCLOAK_REALM, client)) } - // add email claim for client protocolMappers - // @NOTE - PUT involves adding strict required properties not in the factory - await doApiCall(errors, 'creating client email claim', () => - protocols.realmClientsIdProtocolMappersModelsPost( - env.KEYCLOAK_REALM, - ensure(client.id), - realmConfig.createClientEmailClaimMapper(), - ), - ) + const allClaims = (await doApiCall(errors, 'Getting client email claim', () => + protocols.realmClientsIdProtocolMappersModelsGet(env.KEYCLOAK_REALM, ensure(client.id)), + )) as Array + const mapper = realmConfig.createClientEmailClaimMapper() + if (!allClaims.some((el) => el.name === mapper.name)) { + await doApiCall(errors, 'Creating client email claim mapper', () => + protocols.realmClientsIdProtocolMappersModelsPost(env.KEYCLOAK_REALM, ensure(client.id), mapper), + ) + } // set login theme - await doApiCall(errors, 'adding login theme', () => + await doApiCall(errors, 'adding theme for login page', () => realms.realmPut(env.KEYCLOAK_REALM, realmConfig.createLoginThemeConfig('otomi')), ) @@ -181,5 +194,6 @@ async function main(): Promise { // Run main only on execution, not on import (like tests) if (typeof require !== 'undefined' && require.main === module) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises main() }