import dotenv from 'dotenv'; import axios from 'axios'; // Load environment variables dotenv.config(); // Configuration constants 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 ID for Paperless const PAPERLESS_CLIENT_ID = process.env.PAPERLESS_CLIENT_ID || 'paperless'; // Helper function for API error handling 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; }; // Get Admin Token async function getAdminToken() { console.log('Getting admin token...'); 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' } } ); console.log('Admin token received.'); return response.data.access_token; } catch (error) { handleAxiosError(error, 'getting admin token'); } } // Function to get client information by clientId async function getClient(token, clientId) { console.log(`Getting client information for ${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.log(`Client ${clientId} not found`); return null; } console.log(`Client ${clientId} found.`); return response.data[0]; } catch (error) { handleAxiosError(error, `getting client ${clientId}`); return null; } } // Function to get client secret async function getClientSecret(token, clientId) { console.log(`Getting client secret for ${clientId}...`); const client = await getClient(token, clientId); if (!client) { console.log(`Client ${clientId} not found, no secret to get.`); return null; } try { const response = await axios.get( `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}`, { headers: { 'Authorization': `Bearer ${token}` } } ); console.log(`Client secret for ${clientId} retrieved.`); return response.data.secret; } catch (error) { handleAxiosError(error, `getting client secret for ${clientId}`); return null; } } // Function to get redirect URIs async function getClientRedirectUris(token, clientId) { console.log(`Getting redirect URIs for ${clientId}...`); const client = await getClient(token, clientId); if (!client) { console.log(`Client ${clientId} not found, no redirect URIs to get.`); return null; } try { const response = await axios.get( `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}`, { headers: { 'Authorization': `Bearer ${token}` } } ); console.log(`Redirect URIs for ${clientId} retrieved.`); return response.data.redirectUris; } catch (error) { handleAxiosError(error, `getting redirect URIs for ${clientId}`); return null; } } // Function to get default client scopes async function getDefaultClientScopes(token, clientId) { console.log(`Getting default client scopes for ${clientId}...`); const client = await getClient(token, clientId); if (!client) { console.log(`Client ${clientId} not found, no default client scopes to get.`); return null; } try { const response = await axios.get( `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}`, { headers: { 'Authorization': `Bearer ${token}` } } ); console.log(`Default client scopes for ${clientId} retrieved.`); return response.data.defaultClientScopes; } catch (error) { handleAxiosError(error, `getting default client scopes for ${clientId}`); return null; } } // Function to get client mappers async function getClientMappers(token, clientId) { console.log(`Getting client mappers for ${clientId}...`); const client = await getClient(token, clientId); if (!client) { console.log(`Client ${clientId} not found, no mappers to get.`); return []; } try { const response = await axios.get( `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`, { headers: { 'Authorization': `Bearer ${token}` } } ); console.log(`Client mappers for ${clientId} retrieved.`); return response.data; } catch (error) { handleAxiosError(error, `getting client mappers for ${clientId}`); return []; } } // Function to update redirect URIs async function updateClientRedirectUris(token, clientId, redirectUris) { console.log(`Updating redirect URIs for client ${clientId} to: ${JSON.stringify(redirectUris)}...`); const client = await getClient(token, clientId); if (!client) { console.log(`Client ${clientId} not found, cannot update redirect URIs.`); return; } try { await axios.put( `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}`, { ...client, redirectUris: redirectUris }, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } } ); console.log(`Redirect URIs for client ${clientId} updated successfully.`); } catch (error) { handleAxiosError(error, `updating redirect URIs for client ${clientId}`); } } // Function to update default client scopes async function updateDefaultClientScopes(token, clientId, defaultClientScopes) { console.log(`Updating default client scopes for client ${clientId} to: ${JSON.stringify(defaultClientScopes)}...`); const client = await getClient(token, clientId); if (!client) { console.log(`Client ${clientId} not found, cannot update default client scopes.`); return; } try { await axios.put( `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}`, { ...client, defaultClientScopes: defaultClientScopes }, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } } ); console.log(`Default client scopes for client ${clientId} updated successfully.`); } catch (error) { handleAxiosError(error, `updating default client scopes for client ${clientId}`); } } // Main function to check client configuration async function checkClientConfig(token, clientId) { console.log(`Checking configuration for client ${clientId}...`); const clientSecret = await getClientSecret(token, clientId); const redirectUris = await getClientRedirectUris(token, clientId); const defaultClientScopes = await getDefaultClientScopes(token, clientId); const mappers = await getClientMappers(token, clientId); console.log(`Client Secret: ${clientSecret}`); console.log(`Redirect URIs: ${JSON.stringify(redirectUris)}`); console.log(`Default Client Scopes: ${JSON.stringify(defaultClientScopes)}`); console.log("Mappers:"); mappers.forEach(mapper => console.log(` - name: ${mapper.name}, type: ${mapper.protocolMapper}`)); // Check and update redirect URIs const expectedRedirectUris = ["https://docs.mrx8086.com/*"]; if (JSON.stringify(redirectUris) !== JSON.stringify(expectedRedirectUris)) { console.log(`Redirect URIs do not match expected values. Updating...`); await updateClientRedirectUris(token, clientId, expectedRedirectUris); } // Check and update default client scopes const expectedDefaultClientScopes = ["profile", "groups-paperless"]; if (JSON.stringify(defaultClientScopes) !== JSON.stringify(expectedDefaultClientScopes)) { console.log(`Default client scopes do not match expected values. Updating...`); await updateDefaultClientScopes(token, clientId, expectedDefaultClientScopes); } console.log(`Finished checking configuration for client ${clientId}.`); } // Main function async function main() { try { console.log('Starting Keycloak client configuration check...'); const token = await getAdminToken(); await checkClientConfig(token, PAPERLESS_CLIENT_ID); console.log('Keycloak client configuration check completed successfully.'); } catch (error) { console.error('Keycloak client configuration check failed:', error); process.exit(1); } } // Execute the script main();