setup_realm.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. import dotenv from 'dotenv';
  2. import axios from 'axios';
  3. // Lade Umgebungsvariablen
  4. dotenv.config();
  5. // Konfigurationskonstanten
  6. const KEYCLOAK_URL = process.env.KEYCLOAK_URL || 'https://auth.mrx8086.com';
  7. const ADMIN_USERNAME = process.env.KEYCLOAK_ADMIN_USER;
  8. const ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD;
  9. const REALM_NAME = 'office-automation';
  10. // Client IDs und deren Konfiguration
  11. const CLIENTS = {
  12. [process.env.NEXTCLOUD_CLIENT_ID || 'nextcloud']: {
  13. redirectUris: ["https://cloud.mrx8086.com/*"]
  14. },
  15. [process.env.PAPERLESS_CLIENT_ID || 'paperless']: {
  16. redirectUris: ["https://docs.mrx8086.com/*"]
  17. },
  18. [process.env.NODERED_CLIENT_ID || 'nodered']: {
  19. redirectUris: ["https://automate.mrx8086.com/*"]
  20. }
  21. };
  22. // Hilfsfunktion für API-Fehlerbehandlung
  23. const handleAxiosError = (error, operation, config, response) => {
  24. console.error(`Error during ${operation}:`);
  25. if (config) {
  26. console.error('Request:', {
  27. method: config.method,
  28. url: config.url,
  29. headers: config.headers,
  30. data: config.data,
  31. });
  32. }
  33. if (error.response) {
  34. console.error('Response:', {
  35. status: error.response.status,
  36. data: error.response.data
  37. });
  38. } else {
  39. console.error('Error Message:', error.message);
  40. }
  41. throw error;
  42. };
  43. // Admin Token abrufen
  44. async function getAdminToken() {
  45. try {
  46. const response = await axios.post(
  47. `${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`,
  48. new URLSearchParams({
  49. 'client_id': 'admin-cli',
  50. 'username': ADMIN_USERNAME,
  51. 'password': ADMIN_PASSWORD,
  52. 'grant_type': 'password'
  53. }),
  54. {
  55. headers: {
  56. 'Content-Type': 'application/x-www-form-urlencoded'
  57. }
  58. }
  59. );
  60. return response.data.access_token;
  61. } catch (error) {
  62. handleAxiosError(error, 'getting admin token');
  63. }
  64. }
  65. // Prüfen ob Realm existiert
  66. async function checkRealmExists(token) {
  67. try {
  68. await axios.get(
  69. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`,
  70. {
  71. headers: {
  72. 'Authorization': `Bearer ${token}`
  73. }
  74. }
  75. );
  76. return true;
  77. } catch (error) {
  78. if (error.response?.status === 404) {
  79. return false;
  80. }
  81. handleAxiosError(error, 'checking realm existence');
  82. }
  83. }
  84. // Funktion um Client Infos abzufragen
  85. async function getClient(token, clientId) {
  86. try {
  87. const response = await axios.get(
  88. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
  89. {
  90. headers: {
  91. 'Authorization': `Bearer ${token}`
  92. },
  93. params: {
  94. clientId: clientId
  95. }
  96. }
  97. );
  98. if (response.data.length === 0) {
  99. console.error(`Client ${clientId} not found`);
  100. return null;
  101. }
  102. return response.data[0];
  103. } catch (error) {
  104. handleAxiosError(error, `getting client ${clientId}`);
  105. }
  106. }
  107. // Prüfen ob Client existiert
  108. async function checkClientExists(token, clientId) {
  109. const client = await getClient(token, clientId);
  110. return !!client;
  111. }
  112. async function getClientMappers(token, clientId) {
  113. try {
  114. const client = await getClient(token, clientId);
  115. if (!client) {
  116. return [];
  117. }
  118. const response = await axios.get(
  119. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
  120. {
  121. headers: {
  122. 'Authorization': `Bearer ${token}`
  123. }
  124. }
  125. );
  126. return response.data;
  127. } catch (error) {
  128. handleAxiosError(error, `getting client mappers for ${clientId}`, error.config, error.response);
  129. return [];
  130. }
  131. }
  132. // Realm erstellen
  133. async function createRealm(token) {
  134. const realmConfig = {
  135. realm: REALM_NAME,
  136. enabled: true,
  137. displayName: "Office Automation",
  138. displayNameHtml: "<div class=\"kc-logo-text\">Office Automation</div>",
  139. sslRequired: "external",
  140. registrationAllowed: false,
  141. loginWithEmailAllowed: true,
  142. duplicateEmailsAllowed: false,
  143. resetPasswordAllowed: true,
  144. editUsernameAllowed: false,
  145. bruteForceProtected: true,
  146. permanentLockout: false,
  147. maxFailureWaitSeconds: 900,
  148. minimumQuickLoginWaitSeconds: 60,
  149. waitIncrementSeconds: 60,
  150. quickLoginCheckMilliSeconds: 1000,
  151. maxDeltaTimeSeconds: 43200,
  152. failureFactor: 3,
  153. defaultSignatureAlgorithm: "RS256",
  154. offlineSessionMaxLifespan: 5184000,
  155. offlineSessionMaxLifespanEnabled: true,
  156. webAuthnPolicySignatureAlgorithms: ["ES256"],
  157. webAuthnPolicyAttestationConveyancePreference: "none",
  158. webAuthnPolicyAuthenticatorAttachment: "cross-platform",
  159. webAuthnPolicyRequireResidentKey: "not specified",
  160. webAuthnPolicyUserVerificationRequirement: "preferred",
  161. webAuthnPolicyCreateTimeout: 0,
  162. webAuthnPolicyAvoidSameAuthenticatorRegister: false,
  163. defaultDefaultClientScopes: [
  164. "email",
  165. "profile",
  166. "roles",
  167. "web-origins"
  168. ],
  169. defaultOptionalClientScopes: [
  170. "address",
  171. "phone",
  172. "offline_access",
  173. "microprofile-jwt"
  174. ]
  175. };
  176. try {
  177. await axios.post(
  178. `${KEYCLOAK_URL}/admin/realms`,
  179. realmConfig,
  180. {
  181. headers: {
  182. 'Authorization': `Bearer ${token}`,
  183. 'Content-Type': 'application/json'
  184. }
  185. }
  186. );
  187. console.log('Realm created successfully');
  188. } catch (error) {
  189. handleAxiosError(error, 'creating realm');
  190. }
  191. }
  192. // Client erstellen
  193. async function createClient(token, clientId, clientName, redirectUris) {
  194. let client;
  195. const clientExists = await checkClientExists(token, clientId);
  196. if (!clientExists) {
  197. const clientConfig = {
  198. clientId: clientId,
  199. name: clientName,
  200. enabled: true,
  201. protocol: "openid-connect",
  202. publicClient: false,
  203. authorizationServicesEnabled: true,
  204. serviceAccountsEnabled: true,
  205. standardFlowEnabled: true,
  206. implicitFlowEnabled: false,
  207. directAccessGrantsEnabled: true,
  208. redirectUris: redirectUris,
  209. webOrigins: ["+"],
  210. defaultClientScopes: [
  211. "roles"
  212. ],
  213. optionalClientScopes: [
  214. "address",
  215. "phone",
  216. "offline_access",
  217. "microprofile-jwt"
  218. ]
  219. };
  220. try {
  221. const response = await axios.post(
  222. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
  223. clientConfig,
  224. {
  225. headers: {
  226. 'Authorization': `Bearer ${token}`,
  227. 'Content-Type': 'application/json'
  228. }
  229. }
  230. );
  231. console.log(`Client ${clientId} created successfully`);
  232. client = response.data;
  233. } catch (error) {
  234. handleAxiosError(error, `creating client: ${clientId}`);
  235. return;
  236. }
  237. } else {
  238. client = await getClient(token, clientId);
  239. console.log(`Client ${clientId} already exists, checking mappers`);
  240. }
  241. if (client) {
  242. const existingMappers = await getClientMappers(token, clientId)
  243. const requiredMappers = [
  244. {
  245. name: "groups",
  246. protocol: "openid-connect",
  247. protocolMapper: "oidc-group-membership-mapper",
  248. config: {
  249. "full.path": "true",
  250. "id.token.claim": "true",
  251. "access.token.claim": "true",
  252. "userinfo.token.claim": "true",
  253. "claim.name": "groups"
  254. }
  255. },
  256. {
  257. name: "realm roles",
  258. protocol: "openid-connect",
  259. protocolMapper: "oidc-usermodel-realm-role-mapper",
  260. config: {
  261. "id.token.claim": "true",
  262. "access.token.claim": "true",
  263. "userinfo.token.claim": "true",
  264. "claim.name": "roles",
  265. "jsonType.label": "String",
  266. "multivalued": "true"
  267. }
  268. }
  269. ];
  270. for (const mapper of requiredMappers) {
  271. const existingMapper = existingMappers.find(m => m.name === mapper.name);
  272. try {
  273. if (existingMapper) {
  274. // Update existierenden Mapper
  275. await axios.put(
  276. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models/${existingMapper.id}`,
  277. { ...existingMapper, ...mapper },
  278. {
  279. headers: {
  280. 'Authorization': `Bearer ${token}`,
  281. 'Content-Type': 'application/json'
  282. }
  283. }
  284. );
  285. console.log(`Mapper ${mapper.name} updated for client ${clientId}`);
  286. } else {
  287. // Erstelle neuen Mapper
  288. await axios.post(
  289. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
  290. mapper,
  291. {
  292. headers: {
  293. 'Authorization': `Bearer ${token}`,
  294. 'Content-Type': 'application/json'
  295. }
  296. }
  297. );
  298. console.log(`Mapper ${mapper.name} created for client ${clientId}`);
  299. }
  300. } catch (error) {
  301. handleAxiosError(error, `managing mapper ${mapper.name} for client ${clientId}`, error.config, error.response);
  302. // Wir werfen den Fehler nicht weiter, damit andere Mapper noch verarbeitet werden können
  303. }
  304. }
  305. }
  306. }
  307. // Gruppen erstellen
  308. async function createDefaultGroups(token) {
  309. const groups = [
  310. {
  311. name: "Administrators",
  312. path: "/Administrators",
  313. attributes: {
  314. "description": ["Full system access"]
  315. }
  316. },
  317. {
  318. name: "Users",
  319. path: "/Users",
  320. attributes: {
  321. "description": ["Regular system users"]
  322. }
  323. }
  324. ];
  325. for (const group of groups) {
  326. try {
  327. // Prüfen ob Gruppe existiert
  328. const existingGroup = await axios.get(
  329. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
  330. {
  331. headers: {
  332. 'Authorization': `Bearer ${token}`
  333. },
  334. params: {
  335. search: group.name,
  336. }
  337. }
  338. );
  339. if (existingGroup.data.length > 0) {
  340. console.log(`Group ${group.name} already exists`);
  341. continue;
  342. }
  343. await axios.post(
  344. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
  345. group,
  346. {
  347. headers: {
  348. 'Authorization': `Bearer ${token}`,
  349. 'Content-Type': 'application/json'
  350. }
  351. }
  352. );
  353. console.log(`Group ${group.name} created successfully`);
  354. } catch (error) {
  355. if (error.response?.status === 409) {
  356. console.log(`Group ${group.name} already exists`);
  357. } else {
  358. handleAxiosError(error, `creating group: ${group.name}`);
  359. }
  360. }
  361. }
  362. }
  363. async function createTestToken(token, username) {
  364. try {
  365. const nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
  366. const client = await getClient(token, nextcloudClientId);
  367. if (!client)
  368. return null;
  369. const response = await axios.post(
  370. `${KEYCLOAK_URL}/realms/${REALM_NAME}/protocol/openid-connect/token`,
  371. new URLSearchParams({
  372. 'client_id': nextcloudClientId,
  373. 'client_secret': process.env.KEYCLOAK_NEXTCLOUD_CLIENT_SECRET,
  374. 'username': username,
  375. 'password': process.env.TESTADMIN_PASSWORD || "initial123!",
  376. 'grant_type': 'password'
  377. }),
  378. {
  379. headers: {
  380. 'Content-Type': 'application/x-www-form-urlencoded'
  381. }
  382. }
  383. );
  384. return response.data.access_token;
  385. } catch (error) {
  386. handleAxiosError(error, `getting test token for ${username}`, error.config, error.response);
  387. }
  388. }
  389. // Funktion zum Decodieren eines JWT-Tokens
  390. function decodeToken(token) {
  391. try {
  392. const base64Url = token.split('.')[1];
  393. const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
  394. const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
  395. return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
  396. }).join(''));
  397. return JSON.parse(jsonPayload);
  398. } catch (error) {
  399. console.error("Error decoding token:", error.message);
  400. return null;
  401. }
  402. }
  403. // Test-User erstellen
  404. async function createInitialUsers(token) {
  405. const users = [
  406. {
  407. username: "testadmin",
  408. enabled: true,
  409. emailVerified: true,
  410. firstName: "Test",
  411. lastName: "Admin",
  412. email: "testadmin@mrx8086.com",
  413. credentials: [{
  414. type: "password",
  415. value: process.env.TESTADMIN_PASSWORD || "initial123!",
  416. temporary: true
  417. }],
  418. groups: ["/Administrators"]
  419. },
  420. {
  421. username: "testuser",
  422. enabled: true,
  423. emailVerified: true,
  424. firstName: "Test",
  425. lastName: "User",
  426. email: "testuser@mrx8086.com",
  427. credentials: [{
  428. type: "password",
  429. value: process.env.TESTUSER_PASSWORD || "initial123!",
  430. temporary: true
  431. }],
  432. groups: ["/Users"]
  433. }
  434. ];
  435. for (const user of users) {
  436. try {
  437. // Prüfen ob User existiert
  438. const existingUsers = await axios.get(
  439. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
  440. {
  441. headers: {
  442. 'Authorization': `Bearer ${token}`
  443. },
  444. params: {
  445. username: user.username,
  446. exact: true
  447. }
  448. }
  449. );
  450. if (existingUsers.data.length > 0) {
  451. console.log(`User ${user.username} already exists`);
  452. continue;
  453. }
  454. // User erstellen
  455. await axios.post(
  456. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
  457. user,
  458. {
  459. headers: {
  460. 'Authorization': `Bearer ${token}`,
  461. 'Content-Type': 'application/json'
  462. }
  463. }
  464. );
  465. console.log(`User ${user.username} created successfully`);
  466. } catch (error) {
  467. handleAxiosError(error, `creating user: ${user.username}`, error.config, error.response);
  468. }
  469. }
  470. }
  471. // Hauptfunktion
  472. async function setupRealm() {
  473. try {
  474. console.log('Starting Keycloak setup...');
  475. const token = await getAdminToken();
  476. // Prüfe ob Realm existiert
  477. const realmExists = await checkRealmExists(token);
  478. if (!realmExists) {
  479. console.log('Creating new realm...');
  480. await createRealm(token);
  481. } else {
  482. console.log('Realm already exists, skipping base setup');
  483. }
  484. // Clients erstellen
  485. for (const clientId in CLIENTS) {
  486. await createClient(token, clientId, clientId, CLIENTS[clientId].redirectUris);
  487. }
  488. // Gruppen erstellen
  489. await createDefaultGroups(token);
  490. // Test User erstellen
  491. await createInitialUsers(token);
  492. // Konfiguration des Office-Automation Realms mit Admin Token auslesen
  493. if (token) {
  494. console.log("Master Realm Admin Token:", token);
  495. try {
  496. const realmConfig = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`, {
  497. headers: {
  498. 'Authorization': `Bearer ${token}`
  499. }
  500. });
  501. console.log("Office Automation Realm Configuration:", realmConfig.data)
  502. } catch (error) {
  503. handleAxiosError(error, 'getting office realm configuration', error.config, error.response);
  504. }
  505. } else {
  506. console.error("Error getting Master Realm admin token")
  507. }
  508. // Test Token erstellen
  509. const testToken = await createTestToken(token, "testadmin@mrx8086.com");
  510. if (testToken) {
  511. console.log("Test Token generated successfully!");
  512. const decodedToken = decodeToken(testToken);
  513. if (decodedToken)
  514. console.log("Token:", decodedToken);
  515. } else {
  516. console.error("Error generating Test Token");
  517. }
  518. console.log('Setup completed successfully');
  519. } catch (error) {
  520. console.error('Setup failed:', error);
  521. process.exit(1);
  522. }
  523. }
  524. // Script ausführen
  525. setupRealm();