import KeycloakAdminClient from "@keycloak/keycloak-admin-client"; import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation"; import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation"; import OrganizationRepresentation from "@keycloak/keycloak-admin-client/lib/defs/organizationRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; import type { RoleMappingPayload } from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; import type { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata"; import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation"; import { Credentials } from "@keycloak/keycloak-admin-client/lib/utils/auth"; import { merge } from "lodash-es"; import { SERVER_URL } from "../constants"; class AdminClient { readonly #client = new KeycloakAdminClient({ baseUrl: SERVER_URL, realmName: "master", }); #login() { return this.inRealm("master", () => this.#client.auth({ username: "admin", password: "admin", grantType: "password", clientId: "admin-cli", }), ); } async auth(credentials: Credentials) { return this.#client.auth(credentials); } async loginUser(username: string, password: string, clientId: string) { return this.#client.auth({ username: username, password: password, grantType: "password", clientId: clientId, }); } async createRealm(realm: string, payload?: RealmRepresentation) { await this.#login(); await this.#client.realms.create({ realm, ...payload }); } async updateRealm(realm: string, payload: RealmRepresentation) { await this.#login(); await this.#client.realms.update({ realm }, payload); } async getRealm(realm: string) { await this.#login(); return await this.#client.realms.findOne({ realm }); } async deleteRealm(realm: string) { await this.#login(); await this.#client.realms.del({ realm }); } async createClient( client: ClientRepresentation & { realm?: string; }, ) { await this.#login(); await this.#client.clients.create(client); } async deleteClient(clientName: string) { await this.#login(); const client = ( await this.#client.clients.find({ clientId: clientName }) )[0]; if (client) { await this.#client.clients.del({ id: client.id! }); } } async getClient(clientName: string) { await this.#login(); return (await this.#client.clients.find({ clientId: clientName }))[0]; } async createGroup(groupName: string) { await this.#login(); return await this.#client.groups.create({ name: groupName }); } async createSubGroups(groups: string[]) { await this.#login(); let parentGroup = undefined; const createdGroups = []; for (const group of groups) { if (!parentGroup) { parentGroup = await this.#client.groups.create({ name: group }); } else { parentGroup = await this.#client.groups.createChildGroup( { id: parentGroup.id }, { name: group }, ); } createdGroups.push(parentGroup); } return createdGroups; } async deleteGroups() { await this.#login(); const groups = await this.#client.groups.find(); for (const group of groups) { await this.#client.groups.del({ id: group.id! }); } } async createUser(user: UserRepresentation) { await this.#login(); const { id } = await this.#client.users.create(user); const createdUser = await this.#client.users.findOne({ id }); if (!createdUser) { throw new Error( "Unable to create user, created user could not be found.", ); } return createdUser; } async updateUser(id: string, payload: UserRepresentation) { await this.#login(); return this.#client.users.update({ id }, payload); } async getAdminUser() { await this.#login(); const [user] = await this.#client.users.find({ username: "admin" }); return user; } async addUserToGroup(userId: string, groupId: string) { await this.#login(); await this.#client.users.addToGroup({ id: userId, groupId }); } async createUserInGroup(username: string, groupId: string) { await this.#login(); const user = await this.createUser({ username, enabled: true }); await this.#client.users.addToGroup({ id: user.id!, groupId }); } async addRealmRoleToUser(userId: string, roleName: string) { await this.#login(); const realmRole = await this.#client.roles.findOneByName({ name: roleName, }); await this.#client.users.addRealmRoleMappings({ id: userId, roles: [realmRole as RoleMappingPayload], }); } async addClientRoleToUser( userId: string, clientId: string, roleNames: string[], ) { await this.#login(); const client = await this.#client.clients.find({ clientId }); const clientRoles = await Promise.all( roleNames.map( async (roleName) => (await this.#client.clients.findRole({ id: client[0].id!, roleName: roleName, })) as RoleMappingPayload, ), ); await this.#client.users.addClientRoleMappings({ id: userId, clientUniqueId: client[0].id!, roles: clientRoles, }); } async deleteUser(username: string, ignoreNonExisting: boolean = false) { await this.#login(); const foundUsers = await this.#client.users.find({ username }); if (foundUsers.length == 0) { if (ignoreNonExisting) { return; } else { throw new Error(`User not found: ${username}`); } } await this.#client.users.del({ id: foundUsers[0].id! }); } async createClientScope(scope: ClientScopeRepresentation) { await this.#login(); return await this.#client.clientScopes.create(scope); } async deleteClientScope(clientScopeName: string) { await this.#login(); const clientScope = await this.#client.clientScopes.findOneByName({ name: clientScopeName, }); return await this.#client.clientScopes.del({ id: clientScope?.id! }); } async existsClientScope(clientScopeName: string) { await this.#login(); return (await this.#client.clientScopes.findOneByName({ name: clientScopeName, })) == undefined ? false : true; } async addDefaultClientScopeInClient( clientScopeName: string, clientId: string, ) { await this.#login(); const scope = await this.#client.clientScopes.findOneByName({ name: clientScopeName, }); const client = await this.#client.clients.find({ clientId: clientId }); return await this.#client.clients.addDefaultClientScope({ id: client[0]?.id!, clientScopeId: scope?.id!, }); } async removeDefaultClientScopeInClient( clientScopeName: string, clientId: string, ) { await this.#login(); const scope = await this.#client.clientScopes.findOneByName({ name: clientScopeName, }); const client = await this.#client.clients.find({ clientId: clientId }); return await this.#client.clients.delDefaultClientScope({ id: client[0]?.id!, clientScopeId: scope?.id!, }); } async getUserProfile(realm: string) { await this.#login(); return await this.#client.users.getProfile({ realm }); } async updateUserProfile(realm: string, userProfile: UserProfileConfig) { await this.#login(); await this.#client.users.updateProfile(merge(userProfile, { realm })); } async addGroupToProfile(realm: string, groupName: string) { await this.#login(); const currentProfile = await this.#client.users.getProfile({ realm }); await this.#client.users.updateProfile({ ...currentProfile, realm, ...{ groups: [...currentProfile.groups!, { name: groupName }] }, }); } async createRealmRole(payload: RoleRepresentation) { await this.#login(); return await this.#client.roles.create(payload); } async deleteRealmRole(name: string) { await this.#login(); return await this.#client.roles.delByName({ name }); } async createIdentityProvider(idpDisplayName: string, alias: string) { await this.#login(); const identityProviders = (await this.#client.serverInfo.find()).identityProviders || []; const idp = identityProviders.find(({ name }) => name === idpDisplayName); await this.#client.identityProviders.create({ providerId: idp?.id!, displayName: idpDisplayName, alias: alias, }); } async deleteIdentityProvider(idpAlias: string) { await this.#login(); await this.#client.identityProviders.del({ alias: idpAlias, }); } async unlinkAccountIdentityProvider( username: string, idpDisplayName: string, ) { await this.#login(); const user = await this.#client.users.find({ username }); const identityProviders = (await this.#client.serverInfo.find()).identityProviders || []; const idp = identityProviders.find(({ name }) => name === idpDisplayName); await this.#client.users.delFromFederatedIdentity({ id: user[0].id!, federatedIdentityId: idp?.id!, }); } async linkAccountIdentityProvider(username: string, idpDisplayName: string) { await this.#login(); const user = await this.#client.users.find({ username }); const identityProviders = (await this.#client.serverInfo.find()).identityProviders || []; const idp = identityProviders.find(({ name }) => name === idpDisplayName); const fedIdentity = { identityProvider: idp?.id, userId: "testUserIdApi", userName: "testUserNameApi", }; await this.#client.users.addToFederatedIdentity({ id: user[0].id!, federatedIdentityId: idp?.id!, federatedIdentity: fedIdentity, }); } async addLocalizationText(locale: string, key: string, value: string) { await this.#login(); await this.#client.realms.addLocalization( { realm: this.#client.realmName, selectedLocale: locale, key: key }, value, ); } async removeAllLocalizationTexts() { await this.#login(); const localesWithTexts = await this.#client.realms.getRealmSpecificLocales({ realm: this.#client.realmName, }); await Promise.all( localesWithTexts.map((locale) => this.#client.realms.deleteRealmLocalizationTexts({ realm: this.#client.realmName, selectedLocale: locale, }), ), ); } async inRealm(realm: string, fn: () => Promise) { const prevRealm = this.#client.realmName; this.#client.realmName = realm; try { return await fn(); } finally { this.#client.realmName = prevRealm; } } async createOrganization(org: OrganizationRepresentation) { await this.#login(); await this.#client.organizations.create(org); } async deleteOrganization(name: string) { await this.#login(); const { id } = (await this.#client.organizations.find({ search: name }))[0]; await this.#client.organizations.delById({ id: id! }); } } const adminClient = new AdminClient(); export default adminClient;