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: "
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) { 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();