|
|
@ -1,7 +1,85 @@ |
|
|
import { Rate } from "k6/metrics"; |
|
|
import { Rate } from "k6/metrics"; |
|
|
import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.0.0/index.js"; |
|
|
import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.0.0/index.js"; |
|
|
import http from 'k6/http'; |
|
|
import http from 'k6/http'; |
|
|
|
|
|
import encoding from 'k6/encoding'; |
|
|
|
|
|
import { randomSeed } from 'k6'; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Maps environment variables to configuration items. |
|
|
|
|
|
*/ |
|
|
|
|
|
const configEnvVars = { |
|
|
|
|
|
"KEYCLOAK_URL": { |
|
|
|
|
|
"name": "keycloakURL", |
|
|
|
|
|
"type": "string", |
|
|
|
|
|
"mandatory": true |
|
|
|
|
|
}, |
|
|
|
|
|
"KEYCLOAK_OFFLINE_TOKENS": { |
|
|
|
|
|
"name": "offlineTokens", |
|
|
|
|
|
"type": "boolean", |
|
|
|
|
|
"mandatory": false, |
|
|
|
|
|
"default": false |
|
|
|
|
|
}, |
|
|
|
|
|
"REALM_COUNT": { |
|
|
|
|
|
"name": "realmCount", |
|
|
|
|
|
"type": "int", |
|
|
|
|
|
"mandatory": true |
|
|
|
|
|
}, |
|
|
|
|
|
"SESSION_COUNT": { |
|
|
|
|
|
"name": "sessionCount", |
|
|
|
|
|
"type": "int", |
|
|
|
|
|
"mandatory": false, |
|
|
|
|
|
"default": 100 |
|
|
|
|
|
}, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Returns a config object filled with configuration from environment variables. |
|
|
|
|
|
* |
|
|
|
|
|
* @throws {Error} Mandatory environment variable is not set |
|
|
|
|
|
* @returns {Object} Found configuration |
|
|
|
|
|
*/ |
|
|
|
|
|
export function getTestConfig() { |
|
|
|
|
|
let config = {}; |
|
|
|
|
|
for (const [env, params] of Object.entries(configEnvVars)) { |
|
|
|
|
|
let value; |
|
|
|
|
|
if (__ENV[env] != null && __ENV[env] != "") { |
|
|
|
|
|
if (params.type == "boolean") { |
|
|
|
|
|
value = __ENV[env] == "true" || __ENV[env] == "yes" || __ENV[env] == "1" |
|
|
|
|
|
} else if (params.type == "int") { |
|
|
|
|
|
value = parseInt(__ENV[env], 10); |
|
|
|
|
|
} else { |
|
|
|
|
|
value = __ENV[env]; |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
if (params.mandatory) { |
|
|
|
|
|
throw new Error(`Please set the ${env} environment variable`); |
|
|
|
|
|
} |
|
|
|
|
|
if (params.default) { |
|
|
|
|
|
value = params.default; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
config[params.name] = value; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (__VU == 1) { |
|
|
|
|
|
console.log("Using the following config:") |
|
|
|
|
|
for (const [k,v] of Object.entries(config)) { |
|
|
|
|
|
console.log(`- ${k}: ${v}`); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return config; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Loads the realm matching the calling Virtual User. |
|
|
|
|
|
* The realm file must be in the "data" folder and realm files must follow |
|
|
|
|
|
* this naming: realm-XYZ.json (where XYZ is a zero padded integer). |
|
|
|
|
|
* |
|
|
|
|
|
* Note that there can be more Virtual Users than realms but the opposite is not true. |
|
|
|
|
|
* @param {Number} realmCount The number of realm files in the "data" folder. |
|
|
|
|
|
* @returns {Oject} the Keycloak realm |
|
|
|
|
|
*/ |
|
|
export function pickRealm(realmCount) { |
|
|
export function pickRealm(realmCount) { |
|
|
var realmId = __VU % realmCount; |
|
|
var realmId = __VU % realmCount; |
|
|
realmId = `${realmId}`.padStart(3, "0"); |
|
|
realmId = `${realmId}`.padStart(3, "0"); |
|
|
@ -9,28 +87,57 @@ export function pickRealm(realmCount) { |
|
|
return JSON.parse(open(fileName)); |
|
|
return JSON.parse(open(fileName)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export function pickClient(realm) { |
|
|
/** |
|
|
var clients = realm.clients; |
|
|
* Picks a random item in the supplied array. |
|
|
if (clients == null || clients.length == 0) { |
|
|
* |
|
|
|
|
|
* @param {Array} list an array in which to choose from |
|
|
|
|
|
* @returns {Object} the random item |
|
|
|
|
|
*/ |
|
|
|
|
|
export function pickRandom(list) { |
|
|
|
|
|
if (list == null ||list.length == 0) { |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var i = Math.floor(Math.random() * Math.floor(clients.length)); |
|
|
var i = Math.floor(Math.random() * Math.floor(list.length)); |
|
|
return clients[i]; |
|
|
return list[i]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export function pickUser(realm) { |
|
|
/** |
|
|
var users = realm.users; |
|
|
* Picks a random client in the supplied Keycloak realm. |
|
|
if (users == null || users.length == 0) { |
|
|
* |
|
|
return null; |
|
|
* @param {Object} realm the Keycloak realm |
|
|
} |
|
|
* @returns {Object} the random client |
|
|
|
|
|
*/ |
|
|
|
|
|
export function pickClient(realm) { |
|
|
|
|
|
return pickRandom(realm.clients); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var i = Math.floor(Math.random() * Math.floor(users.length)); |
|
|
/** |
|
|
return users[i]; |
|
|
* Picks a random user in the supplied Keycloak realm. |
|
|
|
|
|
* |
|
|
|
|
|
* @param {Object} realm the Keycloak realm |
|
|
|
|
|
* @returns {Object} the random user |
|
|
|
|
|
*/ |
|
|
|
|
|
export function pickUser(realm) { |
|
|
|
|
|
return pickRandom(realm.users); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* K6 "Rate" metric for counting Javascript errors during a test run. |
|
|
|
|
|
* |
|
|
|
|
|
* @see {@link wrapWithErrorCounting} |
|
|
|
|
|
*/ |
|
|
export var script_errors = Rate("script_errors"); |
|
|
export var script_errors = Rate("script_errors"); |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Wraps a K6 test function with error counting. |
|
|
|
|
|
* @see {@link script_errors} |
|
|
|
|
|
* |
|
|
|
|
|
* @param {Function} fn The K6 test function to wrap |
|
|
|
|
|
* @returns {Function} The wrapped test function |
|
|
|
|
|
*/ |
|
|
export function wrapWithErrorCounting(fn) { |
|
|
export function wrapWithErrorCounting(fn) { |
|
|
|
|
|
// result from the "setup" function is passed to the test function in "data"
|
|
|
return (data) => { |
|
|
return (data) => { |
|
|
try { |
|
|
try { |
|
|
fn(data); |
|
|
fn(data); |
|
|
@ -42,7 +149,13 @@ export function wrapWithErrorCounting(fn) { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export function buildQueryString(data) { |
|
|
/** |
|
|
|
|
|
* Builds a query string from an object containing keys & values. |
|
|
|
|
|
* |
|
|
|
|
|
* @param {Object} data a key/value object |
|
|
|
|
|
* @returns {String} the encoded query string |
|
|
|
|
|
*/ |
|
|
|
|
|
function buildQueryString(data) { |
|
|
const result = []; |
|
|
const result = []; |
|
|
|
|
|
|
|
|
Object.keys(data) |
|
|
Object.keys(data) |
|
|
@ -54,17 +167,51 @@ export function buildQueryString(data) { |
|
|
return result.join("&"); |
|
|
return result.join("&"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export function keycloakEndpoints(keycloakUrl, realmId) { |
|
|
/** |
|
|
const BASE_URL = `${keycloakUrl}/realms/${realmId}`; |
|
|
* Represents a Keycloak client. |
|
|
|
|
|
*/ |
|
|
|
|
|
export const Keycloak = class { |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Creates a keycloak client from the server URL and params. |
|
|
|
|
|
* |
|
|
|
|
|
* Currently accepted params: |
|
|
|
|
|
* - offlineTokens (boolean): request offline tokens instead of regular refresh tokens |
|
|
|
|
|
* @param {String} keycloakURL the keycloak server URL |
|
|
|
|
|
* @param {Object} params a key/value object |
|
|
|
|
|
*/ |
|
|
|
|
|
constructor(keycloakURL, params) { |
|
|
|
|
|
this.keycloakURL = keycloakURL; |
|
|
|
|
|
this.params = Object.assign({ offlineTokens: false }, params); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Returns OpenID Connect endpoints for a realm. |
|
|
|
|
|
* |
|
|
|
|
|
* @param {Object} realm the Keycloak realm name |
|
|
|
|
|
* @returns {Object} the OIDC endpoints |
|
|
|
|
|
*/ |
|
|
|
|
|
endpoints(realm) { |
|
|
|
|
|
const BASE_URL = `${this.keycloakURL}/realms/${realm}`; |
|
|
return { |
|
|
return { |
|
|
"login": `${BASE_URL}/protocol/openid-connect/auth`, |
|
|
"login": `${BASE_URL}/protocol/openid-connect/auth`, |
|
|
"token": `${BASE_URL}/protocol/openid-connect/token`, |
|
|
"token": `${BASE_URL}/protocol/openid-connect/token`, |
|
|
"userinfo": `${BASE_URL}/protocol/openid-connect/userinfo`, |
|
|
"userinfo": `${BASE_URL}/protocol/openid-connect/userinfo`, |
|
|
"tokeninfo": `${BASE_URL}/protocol/openid-connect/token/introspect`, |
|
|
"tokeninfo": `${BASE_URL}/protocol/openid-connect/token/introspect`, |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export function keycloakLogin(endpoints, client, user, check) { |
|
|
/** |
|
|
|
|
|
* Simulates a user performing a login through a browser (OIDC Authorization Code flow). |
|
|
|
|
|
* |
|
|
|
|
|
* @param {String} realm realm name |
|
|
|
|
|
* @param {Object} client the client to use for login |
|
|
|
|
|
* @param {Object} user the user to use for login |
|
|
|
|
|
* @param {Function} check the K6 check function (pass an empty function to disable checks) |
|
|
|
|
|
* @returns {Object} access and refresh tokens (as returned from the token endpoint) |
|
|
|
|
|
*/ |
|
|
|
|
|
login(realm, client, user, check) { |
|
|
|
|
|
let endpoints = this.endpoints(realm); |
|
|
const UI_HEADERS = { |
|
|
const UI_HEADERS = { |
|
|
"Accept": "text/html,application/xhtml+xml,application/xml", |
|
|
"Accept": "text/html,application/xhtml+xml,application/xml", |
|
|
"Accept-Encoding": "gzip, deflate", |
|
|
"Accept-Encoding": "gzip, deflate", |
|
|
@ -72,10 +219,15 @@ export function keycloakLogin(endpoints, client, user, check) { |
|
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0", |
|
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0", |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let scopes = [ "openid" ]; |
|
|
|
|
|
if (this.params.offlineTokens == "true") { |
|
|
|
|
|
scopes.push("offline_access"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const LOGIN_PARAMS = { |
|
|
const LOGIN_PARAMS = { |
|
|
"login": "true", |
|
|
"login": "true", |
|
|
"response_type": "code", |
|
|
"response_type": "code", |
|
|
"scope": "openid", |
|
|
"scope": scopes.join(" "), |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
let login_params = Object.assign(LOGIN_PARAMS, { "client_id": client.clientId, "state": uuidv4(), "redirect_uri": client.redirectUris[0] }); |
|
|
let login_params = Object.assign(LOGIN_PARAMS, { "client_id": client.clientId, "state": uuidv4(), "redirect_uri": client.redirectUris[0] }); |
|
|
@ -115,7 +267,7 @@ export function keycloakLogin(endpoints, client, user, check) { |
|
|
"client_id": client.clientId, |
|
|
"client_id": client.clientId, |
|
|
"client_secret": client.secret |
|
|
"client_secret": client.secret |
|
|
}; |
|
|
}; |
|
|
let access_token_response = http.post(`${endpoints.token}`, access_token_request, { "tags": { name: "access-token-request" } }); |
|
|
let access_token_response = http.post(endpoints.token, access_token_request, { "tags": { name: "access-token-request" } }); |
|
|
|
|
|
|
|
|
check(access_token_response, { |
|
|
check(access_token_response, { |
|
|
'access_token_response.status == 200': (http) => http.status === 200, |
|
|
'access_token_response.status == 200': (http) => http.status === 200, |
|
|
@ -126,17 +278,63 @@ export function keycloakLogin(endpoints, client, user, check) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return access_token_response.json(); |
|
|
return access_token_response.json(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Simulates a user performing a login through API (OIDC Resource Owner Password Credentials flow). |
|
|
|
|
|
* |
|
|
|
|
|
* @param {String} realm realm name |
|
|
|
|
|
* @param {Object} client the client to use for login |
|
|
|
|
|
* @param {Object} user the user to use for login |
|
|
|
|
|
* @param {Function} check the K6 check function (pass an empty function to disable checks) |
|
|
|
|
|
* @returns {Object} access and refresh tokens (as returned from the token endpoint) |
|
|
|
|
|
*/ |
|
|
|
|
|
headlessLogin(realm, client, user, check) { |
|
|
|
|
|
let endpoints = this.endpoints(realm); |
|
|
|
|
|
let scopes = [ "openid" ]; |
|
|
|
|
|
if (this.params.offlineTokens == "true") { |
|
|
|
|
|
scopes.push("offline_access"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let access_token_request = { |
|
|
|
|
|
"grant_type": "password", |
|
|
|
|
|
"redirect_uri": client.redirectUris[0], |
|
|
|
|
|
"client_id": client.clientId, |
|
|
|
|
|
"client_secret": client.secret, |
|
|
|
|
|
"scope": scopes.join(" "), |
|
|
|
|
|
"username": user.username, |
|
|
|
|
|
"password": user.credentials[0].value, |
|
|
|
|
|
}; |
|
|
|
|
|
let access_token_response = http.post(endpoints.token, access_token_request, { "tags": { name: "access-token-request" } }); |
|
|
|
|
|
check(access_token_response, { |
|
|
|
|
|
'access_token_response.status == 200': (http) => http.status === 200, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (access_token_response.status !== 200) { |
|
|
|
|
|
throw new Error(`access_token_response.status is ${access_token_response.status}, expected 200`); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return access_token_response.json(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
export function keycloakRefreshTokens(endpoints, tokens, client, check) { |
|
|
/** |
|
|
|
|
|
* Refreshes the provided access and refresh tokens. |
|
|
|
|
|
* |
|
|
|
|
|
* @param {String} realm realm name |
|
|
|
|
|
* @param {Object} tokens the result from the last call to the token endpoint |
|
|
|
|
|
* @param {Object} client the client to use for login |
|
|
|
|
|
* @param {Function} check the K6 check function (pass an empty function to disable checks) |
|
|
|
|
|
* @returns {Object} access and refresh tokens (as returned from the token endpoint) |
|
|
|
|
|
*/ |
|
|
|
|
|
refreshTokens(realm, tokens, client, check) { |
|
|
|
|
|
let endpoints = this.endpoints(realm); |
|
|
let access_token_request = { |
|
|
let access_token_request = { |
|
|
"grant_type": "refresh_token", |
|
|
"grant_type": "refresh_token", |
|
|
"refresh_token": tokens.refresh_token, |
|
|
"refresh_token": tokens.refresh_token, |
|
|
"client_id": client.clientId, |
|
|
"client_id": client.clientId, |
|
|
"client_secret": client.secret |
|
|
"client_secret": client.secret |
|
|
}; |
|
|
}; |
|
|
let access_token_response = http.post(`${endpoints.token}`, access_token_request, { "tags": { name: "refresh-tokens" } }); |
|
|
let access_token_response = http.post(endpoints.token, access_token_request, { "tags": { name: "refresh-tokens" } }); |
|
|
|
|
|
|
|
|
check(access_token_response, { |
|
|
check(access_token_response, { |
|
|
'access_token_response.status == 200': (http) => http.status === 200, |
|
|
'access_token_response.status == 200': (http) => http.status === 200, |
|
|
}); |
|
|
}); |
|
|
@ -146,4 +344,111 @@ export function keycloakRefreshTokens(endpoints, tokens, client, check) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return access_token_response.json(); |
|
|
return access_token_response.json(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Calls the tokeninfo endpoint. |
|
|
|
|
|
* |
|
|
|
|
|
* @param {String} realm realm name |
|
|
|
|
|
* @param {Object} tokens the result from the last call to the token endpoint |
|
|
|
|
|
* @param {Object} client the client to use for login |
|
|
|
|
|
* @param {Function} check the K6 check function (pass an empty function to disable checks) |
|
|
|
|
|
* @returns {Object} the tokeninfo response (as-is) |
|
|
|
|
|
*/ |
|
|
|
|
|
tokeninfo(realm, tokens, client, check) { |
|
|
|
|
|
let endpoints = this.endpoints(realm); |
|
|
|
|
|
let body = { |
|
|
|
|
|
"token": tokens.access_token |
|
|
|
|
|
}; |
|
|
|
|
|
let credentials = encoding.b64encode(`${client.clientId}:${client.secret}`); |
|
|
|
|
|
let tokeninfo = http.post(endpoints.tokeninfo, body, { "headers": { "Authorization": `Basic ${credentials}`}, "tags": { name: "tokeninfo" } }); |
|
|
|
|
|
check(tokeninfo, { |
|
|
|
|
|
'tokeninfo.status == 200': (http) => http.status === 200, |
|
|
|
|
|
}); |
|
|
|
|
|
return tokeninfo.json(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Calls the tokeninfo endpoint. |
|
|
|
|
|
* |
|
|
|
|
|
* @param {String} realm realm name |
|
|
|
|
|
* @param {Object} tokens the result from the last call to the token endpoint |
|
|
|
|
|
* @param {Object} client the client to use for login |
|
|
|
|
|
* @param {Function} check the K6 check function (pass an empty function to disable checks) |
|
|
|
|
|
* @returns {Object} the tokeninfo response (as-is) |
|
|
|
|
|
*/ |
|
|
|
|
|
userinfo(realm, tokens, check) { |
|
|
|
|
|
let endpoints = this.endpoints(realm); |
|
|
|
|
|
let userinfo = http.get(endpoints.userinfo, { "headers": { "Authorization": `Bearer ${tokens.access_token}`}, "tags": { name: "userinfo" } }); |
|
|
|
|
|
check(userinfo, { |
|
|
|
|
|
'userinfo.status == 200': (http) => http.status === 200, |
|
|
|
|
|
}); |
|
|
|
|
|
return userinfo.json(); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Shuffles an array in-place. |
|
|
|
|
|
* |
|
|
|
|
|
* @param {Array} array the array to shuffle. |
|
|
|
|
|
*/ |
|
|
|
|
|
export function shuffleArray(array) { |
|
|
|
|
|
// https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
|
|
|
|
|
|
for (var i = array.length - 1; i > 0; i--) { |
|
|
|
|
|
var j = Math.floor(Math.random() * (i + 1)); |
|
|
|
|
|
var temp = array[i]; |
|
|
|
|
|
array[i] = array[j]; |
|
|
|
|
|
array[j] = temp; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Returns a K6 "setup" function that opens Keycloak sessions. |
|
|
|
|
|
* |
|
|
|
|
|
* @param {Keycloak} keycloak the Keycloak client |
|
|
|
|
|
* @param {Number} realmCount the number of realm files in the "data" folder |
|
|
|
|
|
* @param {Number} sessionCount the number of Keycloak sessions to open |
|
|
|
|
|
* @returns {Function} the K6 setup function |
|
|
|
|
|
*/ |
|
|
|
|
|
export function setupOpenSessions(keycloak, realmCount, sessionCount) { |
|
|
|
|
|
var realms = []; |
|
|
|
|
|
for (var i = 0; i < realmCount; i++) { |
|
|
|
|
|
let realmId = `${i}`.padStart(3, "0"); |
|
|
|
|
|
let fileName = `data/realm-${realmId}.json`; |
|
|
|
|
|
realms.push(JSON.parse(open(fileName))); |
|
|
|
|
|
} |
|
|
|
|
|
return () => { |
|
|
|
|
|
let sessions = []; |
|
|
|
|
|
randomSeed(__VU); |
|
|
|
|
|
for (let i = 0; i < sessionCount; i++) { |
|
|
|
|
|
let session = {}; |
|
|
|
|
|
const realm = pickRandom(realms); |
|
|
|
|
|
session.realm = { |
|
|
|
|
|
id: realm.id |
|
|
|
|
|
}; |
|
|
|
|
|
let user = pickUser(realm); |
|
|
|
|
|
session.user = { |
|
|
|
|
|
username: user.username, |
|
|
|
|
|
credentials: [ { value: user.credentials[0].value } ], |
|
|
|
|
|
}; |
|
|
|
|
|
let client = pickClient(realm); |
|
|
|
|
|
session.client = { |
|
|
|
|
|
clientId: client.clientId, |
|
|
|
|
|
secret: client.secret, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let tokens = keycloak.headlessLogin(realm.id, client, user, () => {}); |
|
|
|
|
|
session.tokens = { |
|
|
|
|
|
access_token: tokens.access_token, |
|
|
|
|
|
refresh_token: tokens.refresh_token, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if ((i+1) % 100 == 0) { |
|
|
|
|
|
console.log(`Opened ${i+1} Keycloak sessions so far...`); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
sessions.push(session); |
|
|
|
|
|
} |
|
|
|
|
|
return sessions; |
|
|
|
|
|
}; |
|
|
} |
|
|
} |