File

src/providers/keycloak/keycloak.provider.ts

Index

Properties
Methods

Constructor

constructor()

Methods

Async createUser
createUser(username: string, email: string)

Creates new user in keycloak

Parameters :
Name Type Optional Description
username string No

username of the new user

email string No
Returns : unknown
Async getCredentials
getCredentials(client: string, username: string)

Gets impersonated access token for user from keycloak if user exists

Parameters :
Name Type Optional Description
client string No

Turo client (Service Account) in keycloak the token needs to be issued for

username string No

username of the user the token needs to be issued for

Returns : unknown
Async getImpersonationToken
getImpersonationToken(audience: string, requestedSubject: string)

Gets impersonated access token from keycloak

Parameters :
Name Type Optional Description
audience string No

client in keycloak the token needs to be issued for

requestedSubject string No

username of the user the token needs to be issued for

Returns : unknown
Async getOrCreateUser
getOrCreateUser(username: string, email: string)

Gets user or creates a new user if user not found

Parameters :
Name Type Optional Description
username string No

username of the user that needs to be fetched or created

email string No
Returns : unknown
Async getServiceAccountAccessToken
getServiceAccountAccessToken()

Gets access token from keycloak for the TURO service account

Returns : unknown
Async getUser
getUser(username: string)

Gets user from keycloak

Parameters :
Name Type Optional Description
username string No

username of the user that needs to be fetched

Returns : unknown
initialize
initialize(tokenUrl: string, clientId: string, clientSecret: string, adminApiUrl: string, turoServicesClientId: string)
Parameters :
Name Type Optional Default value
tokenUrl string No null
clientId string No null
clientSecret string No null
adminApiUrl string No null
turoServicesClientId string No null
Returns : void
Async searchUser
searchUser(email: string, idpAlias: string)
Parameters :
Name Type Optional
email string No
idpAlias string No
Returns : unknown

Properties

Private adminApiUrl
Type : null
Default value : null
Private clientId
Type : null
Default value : null
Private clientSecret
Type : null
Default value : null
Private tokenUrl
Type : null
Default value : null
Static turoServicesClientId
Type : null
Default value : null
import { Injectable } from "@nestjs/common";

@Injectable()
export class KeycloakProvider {
  private tokenUrl = null;
  private clientId = null;
  private clientSecret = null;
  private adminApiUrl = null;
  static turoServicesClientId = null;

  constructor() {
    this.initialize();
  }

  initialize(
    tokenUrl: string = null,
    clientId: string = null,
    clientSecret: string = null,
    adminApiUrl: string = null,
    turoServicesClientId: string = null,
  ) {
    this.tokenUrl = tokenUrl || process.env.OAUTH2_TOKEN_URL;
    this.clientId = clientId || process.env.KEYCLOAK_CLIENT_ID;
    this.clientSecret = clientSecret || process.env.KEYCLOAK_CLIENT_SECRET;
    this.adminApiUrl = adminApiUrl || process.env.KEYCLOAK_ADMIN_API_URL;
    KeycloakProvider.turoServicesClientId =
      turoServicesClientId || process.env.KEYCLOAK_TURO_SERVICES_CLIENT_ID;

    const config = {
      tokenUrl: this.tokenUrl,
      clientId: this.clientId,
      clientSecret: this.clientSecret,
      adminApiUrl: this.adminApiUrl,
      turoServicesClientId: KeycloakProvider.turoServicesClientId,
    };

    const config_values = Object.values(config);
    if (
      config_values.includes(null) ||
      config_values.includes(undefined) ||
      config_values.includes(false)
    ) {
      const keepUndefined = (k, v) => {
        return v === undefined ? null : v;
      };
      throw new Error(
        `Can't initialise KeycloakProvider - config: ${JSON.stringify(config, keepUndefined, 2)}`,
      );
    }
  }

  /**
   * Gets access token from keycloak for the TURO service account
   */
  async getServiceAccountAccessToken() {
    const response = await fetch(this.tokenUrl, {
      method: "POST",
      headers: {
        Authorization: "Basic " + btoa(`${this.clientId}:${this.clientSecret}`),
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
      },
      body: new URLSearchParams({
        grant_type: "client_credentials",
      }),
    });
    const tokenInfo = await response.json();
    return tokenInfo["access_token"];
  }

  /**
   * Gets impersonated access token for user from keycloak if user exists
   * @param {string} client Turo client (Service Account) in keycloak the token needs to be issued for
   * @param {string} username username of the user the token needs to be issued for
   */
  async getCredentials(client: string, username: string) {
    const user = await this.getUser(username);
    if (!user) {
      throw new Error(`Can't find user - ${username}`);
    }

    const userToken = await this.getImpersonationToken(
      client,
      user["username"],
    );
    return userToken;
  }

  /**
   * Creates new user in keycloak
   * @param {string} username username of the new user
   */
  async createUser(username: string, email: string) {
    const serviceToken = await this.getServiceAccountAccessToken();
    const endpoint = `${this.adminApiUrl}/users`;
    const response = await fetch(endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer " + serviceToken,
      },
      body: JSON.stringify({ username: username, email: email, enabled: true }),
    });
    return response;
  }

  /**
   * Gets user from keycloak
   * @param {string} username username of the user that needs to be fetched
   */
  async getUser(username: string) {
    const serviceToken = await this.getServiceAccountAccessToken();
    const endpoint = `${this.adminApiUrl}/users`;
    const response = await fetch(
      endpoint + "?" + new URLSearchParams({ username: username }),
      {
        method: "GET",
        headers: {
          Authorization: "Bearer " + serviceToken,
        },
      },
    );
    if (response.status == 200) {
      const matchedUsers = await response.json();
      const user = matchedUsers?.find((user) => user.username === username);
      return user;
    } else {
      console.error(`Can't getUser - ${username}`, response);
      throw new Error(`Can't getUser - ${username}`);
    }
  }

  async searchUser(email: string, idpAlias: string) {
    const serviceToken = await this.getServiceAccountAccessToken();
    const endpoint = `${this.adminApiUrl}/users`;
    const response = await fetch(
      endpoint +
        "?" +
        new URLSearchParams({ email: email, idpAlias: idpAlias }),
      {
        method: "GET",
        headers: {
          Authorization: "Bearer " + serviceToken,
        },
      },
    );
    return response.json();
  }

  /**
   * Gets user or creates a new user if user not found
   * @param {string} username username of the user that needs to be fetched or created
   */
  async getOrCreateUser(username: string, email: string) {
    let user = await this.getUser(username);
    if (user) {
      return user;
    }

    // user not found - create
    const response = await this.createUser(username, email);
    if (response.status != 201) {
      throw new Error(
        `Can't create user - ${response.status} - ${JSON.stringify(await response.json())}`,
      );
    }

    // fetch and return created user
    user = await this.getUser(username);
    if (!user) {
      console.error(`Can't getOrCreateUser - ${username}`, user);
      throw new Error(`Can't getOrCreateUser - ${username}`);
    }
    return user;
  }

  /**
   * Gets impersonated access token from keycloak
   * @param {string} audience client in keycloak the token needs to be issued for
   * @param {string} requestedSubject username of the user the token needs to be issued for
   */
  async getImpersonationToken(audience: string, requestedSubject: string) {
    const serviceToken = await this.getServiceAccountAccessToken();
    const body = {
      client_id: this.clientId,
      client_secret: this.clientSecret,
      subject_token: serviceToken,
      grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
      requested_token_type: "urn:ietf:params:oauth:token-type:access_token",
      audience: audience,
      requested_subject: requestedSubject,
    };

    const response = await fetch(this.tokenUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
      },
      body: new URLSearchParams(body),
    });
    return await response.json();
  }
}

results matching ""

    No results matching ""