Skip to content

Instantly share code, notes, and snippets.

@slutske22
Created March 20, 2024 21:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slutske22/4bcd9920ccfa4c4fef60cb22d5bfb745 to your computer and use it in GitHub Desktop.
Save slutske22/4bcd9920ccfa4c4fef60cb22d5bfb745 to your computer and use it in GitHub Desktop.
Setup script using the keycloak admin REST api
/* eslint no-console: 0 */
const axios = require("axios");
const chalk = require("chalk");
const KEYCLOAK_BASE_URL = process.env.DEMO
? "http://keycloak_iam:9090"
: "http://localhost:9090";
const CLIENT_NAME = "sva";
const CLIENT_ROLES = ["ROLE_DEP_ADMIN", "ROLE_DEP_GENERAL"];
const MAPPERS = [
{
attribute: "firstName",
jsonLabel: "String",
multivalued: false,
},
{
attribute: "lastName",
jsonLabel: "String",
multivalued: false,
},
{
attribute: "email",
jsonLabel: "String",
multivalued: false,
},
{
attribute: "externalUser",
jsonLabel: "boolean",
multivalued: false,
},
{
attribute: "securityContext.application",
jsonLabel: "String",
multivalued: false,
},
{
attribute: "securityContext.securityRoles",
jsonLabel: "JSON",
multivalued: true,
},
];
const USERS = [
{
username: "dep_admin",
firstName: "Deppy",
lastName: "Admin",
email: "dep_admin@sva-fake.iam",
// Other custom attributes
externalUser: false,
securityRoles: [
{
name: "Employee",
securityLevel: -1,
},
{
name: "FDEP-Admin",
securityLevel: 150,
},
],
},
{
username: "dep_org_admin",
firstName: "Dorgy",
lastName: "Admin",
email: "dep_org_admin@sva-fake.iam",
externalUser: false,
securityRoles: [
{
name: "FDEP-Org-Admin",
securityLevel: 100,
},
],
},
{
username: "general",
firstName: "Jen",
lastName: "Eral",
email: "general@sva-fake.iam",
externalUser: true,
securityRoles: [],
},
{
username: "eligible_entity",
firstName: "Elle",
lastName: "Enty",
email: "eligible_entity@sva-fake.iam",
externalUser: true,
securityRoles: [],
},
{
username: "eligible_entity_admin",
firstName: "Elle",
lastName: "Bossy",
email: "eligible_entity_admin@sva-fake.iam",
externalUser: true,
securityRoles: [],
},
{
username: "contractor",
firstName: "Con",
lastName: "Tractor",
email: "contractor@sva-fake.iam",
externalUser: true,
securityRoles: [],
},
{
username: "other_state_staff",
firstName: "Otter",
lastName: "Statestf",
email: "other_state_staff@sva-fake.iam",
externalUser: true,
securityRoles: [],
},
{
username: "other_state_staff_admin",
firstName: "Otter",
lastName: "Stateboss",
email: "other_state_staff_admin@sva-fake.iam",
externalUser: true,
securityRoles: [],
},
{
username: "sysadmin",
firstName: "God",
lastName: "Mode",
email: "sysadmin@sva-fake.iam",
externalUser: true,
securityRoles: [],
},
];
async function ping() {
return new Promise((res) =>
setTimeout(async () => {
console.log(chalk.blue(`Checking if keycloak is available ...`));
try {
await axios.get(`${KEYCLOAK_BASE_URL}/health/live`);
res("ok");
} catch (e) {
res(undefined);
}
}, 3000),
);
}
async function main() {
let access_token = "";
let keycloakOk = false;
console.log(`Using keycloak base URL: ${KEYCLOAK_BASE_URL}`);
/**
* ---------------------------------------------- Health check keycloak online
*/
for (let i = 0; i < 120; i++) {
keycloakOk = await ping();
if (keycloakOk) break;
}
if (!keycloakOk) {
console.log(chalk.red("Could not find keycloak, aborting"));
return;
}
await new Promise((res) => {
setTimeout(() => {
res();
}, 3000);
});
/**
* ---------------------------------------------- Log in to REST API
*/
console.log(chalk.blue("Logging in . . . \n"));
try {
const token = await axios.post(
`${KEYCLOAK_BASE_URL}/realms/master/protocol/openid-connect/token`,
{
username: "admin",
password: "admin",
grant_type: "password",
client_id: "admin-cli",
},
{
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
},
);
access_token = token.data.access_token;
axios.defaults.headers.common["Authorization"] = `Bearer ${access_token}`;
console.log(chalk.green("Logged in!\n"));
} catch (e) {
console.log(chalk.red("!!! Could not log in! Aborting script."));
throw e;
}
/**
* ---------------------------------------------- Create client
*/
console.log(chalk.blue("Creating Client . . . \n"));
try {
await axios.post(
`${KEYCLOAK_BASE_URL}/admin/realms/master/clients`,
{
clientId: CLIENT_NAME,
id: CLIENT_NAME,
redirectUris: ["*"],
webOrigins: ["*"],
publicClient: true,
},
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${access_token}`,
},
},
);
console.log(chalk.green("Created Client! \n"));
} catch (e) {
console.error(chalk.red("!!! Couldn't create client \n"));
console.error(e.response?.data ?? e.response ?? e.res ?? e);
console.log("");
}
/**
* ---------------------------------------------- Create client roles
*/
for (const role of CLIENT_ROLES) {
console.log(
chalk.blue(`Creating role "${role}" for client: "${CLIENT_NAME}"`),
);
try {
await axios.post(
`${KEYCLOAK_BASE_URL}/admin/realms/master/clients/${CLIENT_NAME}/roles`,
{ name: role },
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${access_token}`,
},
},
);
console.log(
chalk.green(`Created role "${role}" for client: "${CLIENT_NAME}"`),
);
} catch (e) {
console.log(chalk.red("!!! Could not create role! \n"));
console.error(e.response?.data ?? e.response ?? e.res ?? e);
console.log("");
}
}
/**
* ---------------------------------------------- Create client protocol mappers
*/
try {
for (const mapper of MAPPERS) {
console.log(chalk.blue(`Creating mapper: ${mapper.attribute}`));
await axios.post(
`${KEYCLOAK_BASE_URL}/admin/realms/master/clients/${CLIENT_NAME}/protocol-mappers/models`,
{
protocol: "openid-connect",
config: {
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true",
multivalued: mapper.multivalued,
"aggregate.attrs": "",
"user.attribute": mapper.attribute,
"claim.name": mapper.attribute,
"jsonType.label": mapper.jsonLabel,
},
name: mapper.attribute,
protocolMapper: "oidc-usermodel-attribute-mapper",
},
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${access_token}`,
},
},
);
console.log(chalk.green(`Created mapper: ${mapper.attribute}`));
}
} catch (e) {
console.log(chalk.red("!!! Could not create protocol mapper! \n"));
console.error(e.response?.data ?? e.response ?? e.res ?? e);
console.log("");
}
/**
* ---------------------------------------------- Create users
*/
for (const user of USERS) {
console.log(chalk.blue(`Creating user: ${user.username}`));
try {
await axios.post(
`${KEYCLOAK_BASE_URL}/admin/realms/master/users`,
{
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
emailVerified: true,
enabled: true,
credentials: [
{
temporary: false,
type: "password",
value: user.username,
},
],
attributes: {
externalUser: user.externalUser,
"securityContext.application": "sva",
"securityContext.securityRoles": user.securityRoles.map((r) =>
JSON.stringify(r),
),
},
},
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${access_token}`,
},
},
);
console.log(chalk.green(`Created user: ${user.username}`));
} catch (e) {
console.log(chalk.red(`!!! Could not create user: ${user.username} \n`));
console.error(e.response?.data ?? e.response ?? e.res ?? e);
console.log("");
}
}
/**
* ---------------------------------------------- Updating realm token lifetime
*/
try {
console.log(chalk.blue(`Updating realm token lifetime`));
await axios.put(
`${KEYCLOAK_BASE_URL}/admin/realms/master`,
{
accessTokenLifespan: 86400,
sslRequired: "NONE",
},
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${access_token}`,
},
},
);
console.log(chalk.green(`Updated realm token lifetime`));
} catch (e) {
console.log(chalk.red(`!!! Could not update realm token life \n`));
console.error(e.response?.data ?? e.response ?? e.res ?? e);
console.log("");
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment