src/app/core/user/user-admin-service/user-admin.service.ts

Description

Admin functionalities to manage users in an authentication server like Keycloak.

Relationships

Depends on

No results matching.

Index

Properties
Methods

Properties

Static Readonly ACCOUNT_MANAGER_ROLE
Type : string
Default value : "account_manager"

Users with this role can create and update other accounts.

Methods

canManageAccounts
canManageAccounts()

Whether the current user has the role required to manage accounts (e.g. list, create, update or delete accounts) on the authentication server.

Returns : boolean
Abstract createRole
createRole(role: Pick<Role, "name" | "description">)

Create a new realm role in the authentication server

Parameters :
Name Type Optional
role Pick<Role, "name" | "description"> No
Returns : Observable<void>
Throws :
Abstract createUser
createUser(userEntityId: string, email: string, roles: Role[])

Create a user account with the given name and email

Parameters :
Name Type Optional Description
userEntityId string No

The entity id of the "profile" linked with the account

email string No
roles Role[] No

The user account created

Throws :
Abstract deleteRole
deleteRole(roleName: string)

Delete a realm role from the authentication server. Users currently having the role lose it.

Parameters :
Name Type Optional
roleName string No
Returns : Observable<void>
Abstract deleteUser
deleteUser(userEntityId: string)

Delete a user with the given id

Parameters :
Name Type Optional Description
userEntityId string No

The entity id of the "profile" linked with the account

Returns : Observable<{ userDeleted: boolean }>
Abstract getAllRoles
getAllRoles()

Get all available roles of the server

Returns : Observable<Role[]>
Abstract getAllUsers
getAllUsers()

Get all users registered in the authentication server (Keycloak) for this realm

An array of all user accounts with their details, including only non-technical roles

Abstract getUser
getUser(userEntityId: string)

Get the user account details of the user linked to the given entity.

Parameters :
Name Type Optional Description
userEntityId string No

The entity id of the "profile" linked with the account

The user account details or null of no user is found

Throws :
Abstract resendInvitation
resendInvitation(userAccountId: string)

Re-send the account invitation email (e.g. if the original link expired)

Parameters :
Name Type Optional Description
userAccountId string No

The user account id of the authentication server (not the profile entity id)

Returns : Observable<void>
Abstract updateRole
updateRole(roleName: string, update: Pick<Role, "description">)

Update the description of an existing realm role

Parameters :
Name Type Optional
roleName string No
update Pick<Role, "description"> No
Returns : Observable<void>
Abstract updateUser
updateUser(userAccountId: string, updatedUser: Partial<UserAccount>)

Update the user with the given id

Parameters :
Name Type Optional Description
userAccountId string No

The user account id of the authentication server (not the profile entity id)

updatedUser Partial<UserAccount> No

see ://www.keycloak.org/docs-api/19.0.2/rest-api/index.html#_userrepresentation{@link https}

Returns : Observable<{ userUpdated: boolean }>
import { inject } from "@angular/core";
import { Observable } from "rxjs";
import { Role, UserAccount } from "./user-account";
import { SessionSubject } from "../../session/auth/session-info";

/**
 * Admin functionalities to manage users in an authentication server like Keycloak.
 */
export abstract class UserAdminService {
  /**
   * Users with this role can create and update other accounts.
   */
  static readonly ACCOUNT_MANAGER_ROLE = "account_manager";

  private readonly sessionInfo = inject(SessionSubject, { optional: true });

  /**
   * Whether the current user has the role required to manage accounts
   * (e.g. list, create, update or delete accounts) on the authentication server.
   */
  canManageAccounts(): boolean {
    return (
      this.sessionInfo?.value?.roles?.includes(
        UserAdminService.ACCOUNT_MANAGER_ROLE,
      ) ?? false
    );
  }

  /**
   * Get the user account details of the user linked to the given entity.
   * @param userEntityId The entity id of the "profile" linked with the account
   * @returns The user account details or `null` of no user is found
   * @throws {@link UserAdminApiError} if an error occurs (but not if no user is found)
   */
  abstract getUser(userEntityId: string): Observable<UserAccount>;

  /**
   * Create a user account with the given name and email
   * @param userEntityId The entity id of the "profile" linked with the account
   * @param email
   * @param roles
   * @returns The user account created
   * @throws {@link UserAdminApiError} status=409 if email already exists
   */
  abstract createUser(
    userEntityId: string,
    email: string,
    roles: Role[],
  ): Observable<UserAccount>;

  /**
   * Update the user with the given id
   * @param userAccountId The user account id of the authentication server (not the profile entity id)
   * @param updatedUser see {@link https://www.keycloak.org/docs-api/19.0.2/rest-api/index.html#_userrepresentation}
   */
  abstract updateUser(
    userAccountId: string,
    updatedUser: Partial<UserAccount>,
  ): Observable<{ userUpdated: boolean }>;

  /**
   * Delete a user with the given id
   * @param userEntityId The entity id of the "profile" linked with the account
   */
  abstract deleteUser(
    userEntityId: string,
  ): Observable<{ userDeleted: boolean }>;

  /**
   * Re-send the account invitation email (e.g. if the original link expired)
   * @param userAccountId The user account id of the authentication server (not the profile entity id)
   */
  abstract resendInvitation(userAccountId: string): Observable<void>;

  /**
   * Get all available roles of the server
   */
  abstract getAllRoles(): Observable<Role[]>;

  /**
   * Create a new realm role in the authentication server
   * @throws {@link UserAdminApiError} status=409 if the role already exists
   */
  abstract createRole(
    role: Pick<Role, "name" | "description">,
  ): Observable<void>;

  /**
   * Update the description of an existing realm role
   */
  abstract updateRole(
    roleName: string,
    update: Pick<Role, "description">,
  ): Observable<void>;

  /**
   * Delete a realm role from the authentication server.
   * Users currently having the role lose it.
   */
  abstract deleteRole(roleName: string): Observable<void>;

  /**
   * Get all users registered in the authentication server (Keycloak) for this realm
   * @returns An array of all user accounts with their details, including only non-technical roles
   */
  abstract getAllUsers(): Observable<UserAccount[]>;
}

export class UserAdminApiError extends Error {
  /**
   * The HTTP status code of the error.
   * - 404: Not Found (e.g. user not found)
   * - 409: Conflict (e.g. email already exists)
   * - 500: Internal Server Error
   */
  status: number;

  constructor(status: number, message?: string) {
    super(message);
    this.name = "UserAdminApiError";
    // Set the prototype explicitly to maintain the correct prototype chain (see https://medium.com/@Nelsonalfonso/understanding-custom-errors-in-typescript-a-complete-guide-f47a1df9354c)
    Object.setPrototypeOf(this, UserAdminApiError.prototype);

    this.status = status;
    if (!message) {
      this.message = this.generateDefaultMessage(status);
    }
  }

  private generateDefaultMessage(status: number) {
    switch (status) {
      case 403:
        return $localize`:User API error:You do not have permission to manage user accounts.`;
      case 404:
        return $localize`:User API error:The entry does not exist.`;
      case 409:
        return $localize`:User API error:The email address is already in use. Only one account is allowed per email address.`;
      case 500:
        return $localize`:User API error:There was an internal error at the user management server. Please try again later or reach out to your technical support team.`;
      default:
        return `Unexpected error with status code ${status}`;
    }
  }
}

results matching ""

    No results matching ""