src/providers/keycloak/keycloak.provider.ts
Properties |
|
Methods |
|
constructor()
|
| Async createUser | ||||||||||||
createUser(username: string, email: string)
|
||||||||||||
|
Creates new user in keycloak
Parameters :
Returns :
unknown
|
| Async getCredentials | ||||||||||||
getCredentials(client: string, username: string)
|
||||||||||||
|
Gets impersonated access token for user from keycloak if user exists
Parameters :
Returns :
unknown
|
| Async getImpersonationToken | ||||||||||||
getImpersonationToken(audience: string, requestedSubject: string)
|
||||||||||||
|
Gets impersonated access token from keycloak
Parameters :
Returns :
unknown
|
| Async getOrCreateUser | ||||||||||||
getOrCreateUser(username: string, email: string)
|
||||||||||||
|
Gets user or creates a new user if user not found
Parameters :
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 :
Returns :
unknown
|
| initialize | ||||||||||||||||||||||||
initialize(tokenUrl: string, clientId: string, clientSecret: string, adminApiUrl: string, turoServicesClientId: string)
|
||||||||||||||||||||||||
|
Parameters :
Returns :
void
|
| Async searchUser |
searchUser(email: string, idpAlias: string)
|
|
Returns :
unknown
|
| 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();
}
}