| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657 |
- 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, config, response) => {
- console.error(`Error during ${operation}:`);
- if (config) {
- console.error('Request:', {
- method: config.method,
- url: config.url,
- headers: config.headers,
- data: config.data,
- });
- }
- if (error.response) {
- console.error('Response:', {
- status: error.response.status,
- data: error.response.data
- });
- } else {
- console.error('Error Message:', 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;
- }
- 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}/protocol-mappers/models`,
- {
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- }
- );
- return response.data;
- } catch (error) {
- handleAxiosError(error, `getting client mappers for ${clientId}`, error.config, error.response);
- return [];
- }
- }
- async function getClientScopes(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}/client-scopes`,
- {
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- }
- );
- return response.data;
- } catch(error){
- handleAxiosError(error, `getting client scopes for ${clientId}`, error.config, error.response);
- return [];
- }
- }
- async function getClientScope(token, scopeName) {
- try {
- const response = await axios.get(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes`,
- {
- headers: {
- 'Authorization': `Bearer ${token}`
- },
- params: {
- name: scopeName
- }
- }
- );
- if(response.data.length === 0){
- console.error(`Client Scope ${scopeName} not found`);
- return null;
- }
- return response.data[0]
- } catch (error){
- handleAxiosError(error, `getting client scope ${scopeName}`, error.config, error.response);
- return null;
- }
- }
- async function addDefaultClientScope(token, clientId, scopeName){
- try {
- const client = await getClient(token, clientId);
- const scope = await getClientScope(token, scopeName);
- if(!client || !scope){
- return null;
- }
- await axios.put(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/default-client-scopes/${scope.id}`,
- null,
- {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- }
- );
- console.log(`Client scope ${scopeName} added as default scope for client ${clientId}`)
- } catch(error){
- handleAxiosError(error, `adding client scope ${scopeName} as default for client ${clientId}`);
- }
- }
- // Realm erstellen
- async function createRealm(token) {
- const realmConfig = {
- realm: REALM_NAME,
- enabled: true,
- displayName: "Office Automation",
- displayNameHtml: "<div class=\"kc-logo-text\">Office Automation</div>",
- 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) {
-
- const existingMappers = await getClientMappers(token, clientId)
- 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) {
- handleAxiosError(error, `managing mapper ${mapper.name} for client ${clientId}`, error.config, error.response);
- // 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 nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
- const client = await getClient(token, nextcloudClientId);
- if (!client)
- return null;
- const response = await axios.post(
- `${KEYCLOAK_URL}/realms/${REALM_NAME}/protocol/openid-connect/token`,
- new URLSearchParams({
- 'client_id': nextcloudClientId,
- '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}`, error.config, error.response);
- }
- }
- // 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}`, error.config, error.response);
- }
- }
- }
- // 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
- for (const clientId in CLIENTS) {
- await createClient(token, clientId, clientId, CLIENTS[clientId].redirectUris);
- }
- const nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
- await addDefaultClientScope(token, nextcloudClientId, "openid");
- // Gruppen erstellen
- await createDefaultGroups(token);
- // Test User erstellen
- await createInitialUsers(token);
- // 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', error.config, error.response);
- }
- } 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();
|