import dotenv from 'dotenv';
import axios from 'axios';
// Lade Umgebungsvariablen
dotenv.config();
// Konfigurationskonstanten
const KEYCLOAK_URL = process.env.KEYCLOAK_URL || 'https://auth.mrx8086.com';
const ADMIN_USERNAME = process.env.KEYCLOAK_ADMIN_USER;
const ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD;
const REALM_NAME = 'office-automation';
// Client IDs und deren Konfiguration
const CLIENTS = {
[process.env.NEXTCLOUD_CLIENT_ID || 'nextcloud']: {
redirectUris: ["https://cloud.mrx8086.com/*"]
},
[process.env.PAPERLESS_CLIENT_ID || 'paperless']: {
redirectUris: ["https://docs.mrx8086.com/*"]
},
[process.env.NODERED_CLIENT_ID || 'nodered']: {
redirectUris: ["https://automate.mrx8086.com/*"]
}
};
// Hilfsfunktion für API-Fehlerbehandlung
const handleAxiosError = (error, operation) => {
if (error.response) {
console.error(`Error during ${operation}:`, {
status: error.response.status,
data: error.response.data
});
} else {
console.error(`Error during ${operation}:`, error.message);
}
throw error;
};
// Admin Token abrufen
async function getAdminToken() {
try {
const response = await axios.post(
`${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`,
new URLSearchParams({
'client_id': 'admin-cli',
'username': ADMIN_USERNAME,
'password': ADMIN_PASSWORD,
'grant_type': 'password'
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
return response.data.access_token;
} catch (error) {
handleAxiosError(error, 'getting admin token');
}
}
// Prüfen ob Realm existiert
async function checkRealmExists(token) {
try {
await axios.get(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
return true;
} catch (error) {
if (error.response?.status === 404) {
return false;
}
handleAxiosError(error, 'checking realm existence');
}
}
// Funktion um Client Infos abzufragen
async function getClient(token, clientId) {
try {
const response = await axios.get(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
{
headers: {
'Authorization': `Bearer ${token}`
},
params: {
clientId: clientId
}
}
);
if (response.data.length === 0) {
console.error(`Client ${clientId} not found`);
return null;
}
return response.data[0];
} catch (error) {
handleAxiosError(error, `getting client ${clientId}`);
}
}
// Prüfen ob Client existiert
async function checkClientExists(token, clientId) {
const client = await getClient(token, clientId);
return !!client;
}
// Funktion um Client Mapper abzufragen
async function getClientMappers(token, clientId) {
try {
const client = await getClient(token, clientId);
if (!client) {
return [];
}
const response = await axios.get(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/mappers`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
return response.data;
} catch (error) {
// handleAxiosError(error, `getting client mappers for ${clientId}`);
// wir ignorieren den Fehler hier, da wir diese nun immer setzen
return [];
}
}
// Realm erstellen
async function createRealm(token) {
const realmConfig = {
realm: REALM_NAME,
enabled: true,
displayName: "Office Automation",
displayNameHtml: "
Office Automation
",
sslRequired: "external",
registrationAllowed: false,
loginWithEmailAllowed: true,
duplicateEmailsAllowed: false,
resetPasswordAllowed: true,
editUsernameAllowed: false,
bruteForceProtected: true,
permanentLockout: false,
maxFailureWaitSeconds: 900,
minimumQuickLoginWaitSeconds: 60,
waitIncrementSeconds: 60,
quickLoginCheckMilliSeconds: 1000,
maxDeltaTimeSeconds: 43200,
failureFactor: 3,
defaultSignatureAlgorithm: "RS256",
offlineSessionMaxLifespan: 5184000,
offlineSessionMaxLifespanEnabled: true,
webAuthnPolicySignatureAlgorithms: ["ES256"],
webAuthnPolicyAttestationConveyancePreference: "none",
webAuthnPolicyAuthenticatorAttachment: "cross-platform",
webAuthnPolicyRequireResidentKey: "not specified",
webAuthnPolicyUserVerificationRequirement: "preferred",
webAuthnPolicyCreateTimeout: 0,
webAuthnPolicyAvoidSameAuthenticatorRegister: false,
defaultDefaultClientScopes: [
"email",
"profile",
"roles",
"web-origins"
],
defaultOptionalClientScopes: [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
};
try {
await axios.post(
`${KEYCLOAK_URL}/admin/realms`,
realmConfig,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
console.log('Realm created successfully');
} catch (error) {
handleAxiosError(error, 'creating realm');
}
}
// Client erstellen
async function createClient(token, clientId, clientName, redirectUris) {
let client;
const clientExists = await checkClientExists(token, clientId);
if (!clientExists) {
const clientConfig = {
clientId: clientId,
name: clientName,
enabled: true,
protocol: "openid-connect",
publicClient: false,
authorizationServicesEnabled: true,
serviceAccountsEnabled: true,
standardFlowEnabled: true,
implicitFlowEnabled: false,
directAccessGrantsEnabled: true,
redirectUris: redirectUris,
webOrigins: ["+"],
defaultClientScopes: [
"roles"
],
optionalClientScopes: [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
};
try {
const response = await axios.post(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
clientConfig,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
console.log(`Client ${clientId} created successfully`);
client = response.data;
} catch (error) {
handleAxiosError(error, `creating client: ${clientId}`);
return;
}
} else {
client = await getClient(token, clientId);
console.log(`Client ${clientId} already exists, checking mappers`);
}
if (client) {
// Hole existierende Mapper
const existingMappers = await axios.get(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
).then(response => response.data)
.catch(() => []);
const requiredMappers = [
{
name: "groups",
protocol: "openid-connect",
protocolMapper: "oidc-group-membership-mapper",
config: {
"full.path": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true",
"claim.name": "groups"
}
},
{
name: "realm roles",
protocol: "openid-connect",
protocolMapper: "oidc-usermodel-realm-role-mapper",
config: {
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true",
"claim.name": "roles",
"jsonType.label": "String",
"multivalued": "true"
}
}
];
for (const mapper of requiredMappers) {
const existingMapper = existingMappers.find(m => m.name === mapper.name);
try {
if (existingMapper) {
// Update existierenden Mapper
await axios.put(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models/${existingMapper.id}`,
{ ...existingMapper, ...mapper },
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
console.log(`Mapper ${mapper.name} updated for client ${clientId}`);
} else {
// Erstelle neuen Mapper
await axios.post(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
mapper,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
console.log(`Mapper ${mapper.name} created for client ${clientId}`);
}
} catch (error) {
console.error(`Error managing mapper ${mapper.name} for client ${clientId}:`, error.message);
// Wir werfen den Fehler nicht weiter, damit andere Mapper noch verarbeitet werden können
}
}
}
}
// Gruppen erstellen
async function createDefaultGroups(token) {
const groups = [
{
name: "Administrators",
path: "/Administrators",
attributes: {
"description": ["Full system access"]
}
},
{
name: "Users",
path: "/Users",
attributes: {
"description": ["Regular system users"]
}
}
];
for (const group of groups) {
try {
// Prüfen ob Gruppe existiert
const existingGroup = await axios.get(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
{
headers: {
'Authorization': `Bearer ${token}`
},
params: {
search: group.name,
}
}
);
if (existingGroup.data.length > 0) {
console.log(`Group ${group.name} already exists`);
continue;
}
await axios.post(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
group,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
console.log(`Group ${group.name} created successfully`);
} catch (error) {
if (error.response?.status === 409) {
console.log(`Group ${group.name} already exists`);
} else {
handleAxiosError(error, `creating group: ${group.name}`);
}
}
}
}
async function createTestToken(token, username) {
try {
const client = await getClient(token, NEXTCLOUD_CLIENT_ID);
if (!client)
return null;
const response = await axios.post(
`${KEYCLOAK_URL}/realms/${REALM_NAME}/protocol/openid-connect/token`,
new URLSearchParams({
'client_id': NEXTCLOUD_CLIENT_ID,
'client_secret': process.env.KEYCLOAK_NEXTCLOUD_CLIENT_SECRET,
'username': username,
'password': process.env.TESTADMIN_PASSWORD || "initial123!",
'grant_type': 'password'
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
return response.data.access_token;
} catch (error) {
handleAxiosError(error, `getting test token for ${username}`);
}
}
// Funktion zum Decodieren eines JWT-Tokens
function decodeToken(token) {
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
} catch (error) {
console.error("Error decoding token:", error.message);
return null;
}
}
// Test-User erstellen
async function createInitialUsers(token) {
const users = [
{
username: "testadmin",
enabled: true,
emailVerified: true,
firstName: "Test",
lastName: "Admin",
email: "testadmin@mrx8086.com",
credentials: [{
type: "password",
value: process.env.TESTADMIN_PASSWORD || "initial123!",
temporary: true
}],
groups: ["/Administrators"]
},
{
username: "testuser",
enabled: true,
emailVerified: true,
firstName: "Test",
lastName: "User",
email: "testuser@mrx8086.com",
credentials: [{
type: "password",
value: process.env.TESTUSER_PASSWORD || "initial123!",
temporary: true
}],
groups: ["/Users"]
}
];
for (const user of users) {
try {
// Prüfen ob User existiert
const existingUsers = await axios.get(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
{
headers: {
'Authorization': `Bearer ${token}`
},
params: {
username: user.username,
exact: true
}
}
);
if (existingUsers.data.length > 0) {
console.log(`User ${user.username} already exists`);
continue;
}
// User erstellen
await axios.post(
`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
user,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
console.log(`User ${user.username} created successfully`);
} catch (error) {
handleAxiosError(error, `creating user: ${user.username}`);
}
}
}
// Hauptfunktion
async function setupRealm() {
try {
console.log('Starting Keycloak setup...');
const token = await getAdminToken();
// Prüfe ob Realm existiert
const realmExists = await checkRealmExists(token);
if (!realmExists) {
console.log('Creating new realm...');
await createRealm(token);
} else {
console.log('Realm already exists, skipping base setup');
}
// Clients erstellen
const clients = [
{ id: NEXTCLOUD_CLIENT_ID, name: "nextcloud", redirectUris: ["https://cloud.mrx8086.com/*"] },
{ id: PAPERLESS_CLIENT_ID, name: "paperless", redirectUris: ["https://docs.mrx8086.com/*"] },
{ id: NODERED_CLIENT_ID, name: "nodered", redirectUris: ["https://automate.mrx8086.com/*"] }
];
for (const client of clients) {
await createClient(token, client.id, client.name, client.redirectUris);
}
// Gruppen erstellen
await createDefaultGroups(token);
// Test User erstellen
await createInitialUsers(token);
// Client Mapper überprüfen
const clientMappers = await getClientMappers(token, NEXTCLOUD_CLIENT_ID);
if (clientMappers) {
console.log("Client Mappers:", clientMappers);
const groupMapper = clientMappers.find(mapper => mapper.name === 'groups');
if (!groupMapper) {
console.error("Error: 'groups' mapper not found for Nextcloud client");
} else {
console.log("'groups' mapper found")
if (groupMapper.config && groupMapper.config.fullGroupPath != "true") {
console.warn("Warning: 'Full group path' in mapper 'groups' is not enabled.");
} else {
console.log("'Full group path' in mapper 'groups' is enabled");
}
}
} else {
console.error("Error getting Client Mappers");
}
// Konfiguration des Office-Automation Realms mit Admin Token auslesen
if (token) {
console.log("Master Realm Admin Token:", token);
try {
const realmConfig = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
console.log("Office Automation Realm Configuration:", realmConfig.data)
} catch (error) {
handleAxiosError(error, 'getting office realm configuration');
}
} else {
console.error("Error getting Master Realm admin token")
}
// Test Token erstellen
const testToken = await createTestToken(token, "testadmin@mrx8086.com");
if (testToken) {
console.log("Test Token generated successfully!");
const decodedToken = decodeToken(testToken);
if (decodedToken)
console.log("Token:", decodedToken);
} else {
console.error("Error generating Test Token");
}
console.log('Setup completed successfully');
} catch (error) {
console.error('Setup failed:', error);
process.exit(1);
}
}
// Script ausführen
setupRealm();