setup_realm.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  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. async function getClientScopes(token, clientId){
  133. try {
  134. const client = await getClient(token, clientId);
  135. if(!client)
  136. return [];
  137. const response = await axios.get(
  138. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/client-scopes`,
  139. {
  140. headers: {
  141. 'Authorization': `Bearer ${token}`
  142. }
  143. }
  144. );
  145. return response.data;
  146. } catch(error){
  147. handleAxiosError(error, `getting client scopes for ${clientId}`, error.config, error.response);
  148. return [];
  149. }
  150. }
  151. async function getClientScope(token, scopeName) {
  152. try {
  153. const response = await axios.get(
  154. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes`,
  155. {
  156. headers: {
  157. 'Authorization': `Bearer ${token}`
  158. },
  159. params: {
  160. name: scopeName
  161. }
  162. }
  163. );
  164. if(response.data.length === 0){
  165. console.error(`Client Scope ${scopeName} not found`);
  166. return null;
  167. }
  168. return response.data[0]
  169. } catch (error){
  170. handleAxiosError(error, `getting client scope ${scopeName}`, error.config, error.response);
  171. return null;
  172. }
  173. }
  174. async function addDefaultClientScope(token, clientId, scopeName){
  175. try {
  176. const client = await getClient(token, clientId);
  177. const scope = await getClientScope(token, scopeName);
  178. if(!client || !scope){
  179. return null;
  180. }
  181. await axios.put(
  182. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/default-client-scopes/${scope.id}`,
  183. null,
  184. {
  185. headers: {
  186. 'Authorization': `Bearer ${token}`,
  187. 'Content-Type': 'application/json'
  188. }
  189. }
  190. );
  191. console.log(`Client scope ${scopeName} added as default scope for client ${clientId}`)
  192. } catch(error){
  193. handleAxiosError(error, `adding client scope ${scopeName} as default for client ${clientId}`);
  194. }
  195. }
  196. // Realm erstellen
  197. async function createRealm(token) {
  198. const realmConfig = {
  199. realm: REALM_NAME,
  200. enabled: true,
  201. displayName: "Office Automation",
  202. displayNameHtml: "<div class=\"kc-logo-text\">Office Automation</div>",
  203. sslRequired: "external",
  204. registrationAllowed: false,
  205. loginWithEmailAllowed: true,
  206. duplicateEmailsAllowed: false,
  207. resetPasswordAllowed: true,
  208. editUsernameAllowed: false,
  209. bruteForceProtected: true,
  210. permanentLockout: false,
  211. maxFailureWaitSeconds: 900,
  212. minimumQuickLoginWaitSeconds: 60,
  213. waitIncrementSeconds: 60,
  214. quickLoginCheckMilliSeconds: 1000,
  215. maxDeltaTimeSeconds: 43200,
  216. failureFactor: 3,
  217. defaultSignatureAlgorithm: "RS256",
  218. offlineSessionMaxLifespan: 5184000,
  219. offlineSessionMaxLifespanEnabled: true,
  220. webAuthnPolicySignatureAlgorithms: ["ES256"],
  221. webAuthnPolicyAttestationConveyancePreference: "none",
  222. webAuthnPolicyAuthenticatorAttachment: "cross-platform",
  223. webAuthnPolicyRequireResidentKey: "not specified",
  224. webAuthnPolicyUserVerificationRequirement: "preferred",
  225. webAuthnPolicyCreateTimeout: 0,
  226. webAuthnPolicyAvoidSameAuthenticatorRegister: false,
  227. defaultDefaultClientScopes: [
  228. "email",
  229. "profile",
  230. "roles",
  231. "web-origins"
  232. ],
  233. defaultOptionalClientScopes: [
  234. "address",
  235. "phone",
  236. "offline_access",
  237. "microprofile-jwt"
  238. ]
  239. };
  240. try {
  241. await axios.post(
  242. `${KEYCLOAK_URL}/admin/realms`,
  243. realmConfig,
  244. {
  245. headers: {
  246. 'Authorization': `Bearer ${token}`,
  247. 'Content-Type': 'application/json'
  248. }
  249. }
  250. );
  251. console.log('Realm created successfully');
  252. } catch (error) {
  253. handleAxiosError(error, 'creating realm');
  254. }
  255. }
  256. // Client erstellen
  257. async function createClient(token, clientId, clientName, redirectUris) {
  258. let client;
  259. const clientExists = await checkClientExists(token, clientId);
  260. if (!clientExists) {
  261. const clientConfig = {
  262. clientId: clientId,
  263. name: clientName,
  264. enabled: true,
  265. protocol: "openid-connect",
  266. publicClient: false,
  267. authorizationServicesEnabled: true,
  268. serviceAccountsEnabled: true,
  269. standardFlowEnabled: true,
  270. implicitFlowEnabled: false,
  271. directAccessGrantsEnabled: true,
  272. redirectUris: redirectUris,
  273. webOrigins: ["+"],
  274. defaultClientScopes: [
  275. "roles"
  276. ],
  277. optionalClientScopes: [
  278. "address",
  279. "phone",
  280. "offline_access",
  281. "microprofile-jwt"
  282. ]
  283. };
  284. try {
  285. const response = await axios.post(
  286. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
  287. clientConfig,
  288. {
  289. headers: {
  290. 'Authorization': `Bearer ${token}`,
  291. 'Content-Type': 'application/json'
  292. }
  293. }
  294. );
  295. console.log(`Client ${clientId} created successfully`);
  296. client = response.data;
  297. } catch (error) {
  298. handleAxiosError(error, `creating client: ${clientId}`);
  299. return;
  300. }
  301. } else {
  302. client = await getClient(token, clientId);
  303. console.log(`Client ${clientId} already exists, checking mappers`);
  304. }
  305. if (client) {
  306. const existingMappers = await getClientMappers(token, clientId)
  307. const requiredMappers = [
  308. {
  309. name: "groups",
  310. protocol: "openid-connect",
  311. protocolMapper: "oidc-group-membership-mapper",
  312. config: {
  313. "full.path": "true",
  314. "id.token.claim": "true",
  315. "access.token.claim": "true",
  316. "userinfo.token.claim": "true",
  317. "claim.name": "groups"
  318. }
  319. },
  320. {
  321. name: "realm roles",
  322. protocol: "openid-connect",
  323. protocolMapper: "oidc-usermodel-realm-role-mapper",
  324. config: {
  325. "id.token.claim": "true",
  326. "access.token.claim": "true",
  327. "userinfo.token.claim": "true",
  328. "claim.name": "roles",
  329. "jsonType.label": "String",
  330. "multivalued": "true"
  331. }
  332. }
  333. ];
  334. for (const mapper of requiredMappers) {
  335. const existingMapper = existingMappers.find(m => m.name === mapper.name);
  336. try {
  337. if (existingMapper) {
  338. // Update existierenden Mapper
  339. await axios.put(
  340. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models/${existingMapper.id}`,
  341. { ...existingMapper, ...mapper },
  342. {
  343. headers: {
  344. 'Authorization': `Bearer ${token}`,
  345. 'Content-Type': 'application/json'
  346. }
  347. }
  348. );
  349. console.log(`Mapper ${mapper.name} updated for client ${clientId}`);
  350. } else {
  351. // Erstelle neuen Mapper
  352. await axios.post(
  353. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
  354. mapper,
  355. {
  356. headers: {
  357. 'Authorization': `Bearer ${token}`,
  358. 'Content-Type': 'application/json'
  359. }
  360. }
  361. );
  362. console.log(`Mapper ${mapper.name} created for client ${clientId}`);
  363. }
  364. } catch (error) {
  365. handleAxiosError(error, `managing mapper ${mapper.name} for client ${clientId}`, error.config, error.response);
  366. // Wir werfen den Fehler nicht weiter, damit andere Mapper noch verarbeitet werden können
  367. }
  368. }
  369. }
  370. }
  371. // Gruppen erstellen
  372. async function createDefaultGroups(token) {
  373. const groups = [
  374. {
  375. name: "Administrators",
  376. path: "/Administrators",
  377. attributes: {
  378. "description": ["Full system access"]
  379. }
  380. },
  381. {
  382. name: "Users",
  383. path: "/Users",
  384. attributes: {
  385. "description": ["Regular system users"]
  386. }
  387. }
  388. ];
  389. for (const group of groups) {
  390. try {
  391. // Prüfen ob Gruppe existiert
  392. const existingGroup = await axios.get(
  393. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
  394. {
  395. headers: {
  396. 'Authorization': `Bearer ${token}`
  397. },
  398. params: {
  399. search: group.name,
  400. }
  401. }
  402. );
  403. if (existingGroup.data.length > 0) {
  404. console.log(`Group ${group.name} already exists`);
  405. continue;
  406. }
  407. await axios.post(
  408. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
  409. group,
  410. {
  411. headers: {
  412. 'Authorization': `Bearer ${token}`,
  413. 'Content-Type': 'application/json'
  414. }
  415. }
  416. );
  417. console.log(`Group ${group.name} created successfully`);
  418. } catch (error) {
  419. if (error.response?.status === 409) {
  420. console.log(`Group ${group.name} already exists`);
  421. } else {
  422. handleAxiosError(error, `creating group: ${group.name}`);
  423. }
  424. }
  425. }
  426. }
  427. async function createTestToken(token, username) {
  428. try {
  429. const nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
  430. const client = await getClient(token, nextcloudClientId);
  431. if (!client)
  432. return null;
  433. const response = await axios.post(
  434. `${KEYCLOAK_URL}/realms/${REALM_NAME}/protocol/openid-connect/token`,
  435. new URLSearchParams({
  436. 'client_id': nextcloudClientId,
  437. 'client_secret': process.env.KEYCLOAK_NEXTCLOUD_CLIENT_SECRET,
  438. 'username': username,
  439. 'password': process.env.TESTADMIN_PASSWORD || "initial123!",
  440. 'grant_type': 'password'
  441. }),
  442. {
  443. headers: {
  444. 'Content-Type': 'application/x-www-form-urlencoded'
  445. }
  446. }
  447. );
  448. return response.data.access_token;
  449. } catch (error) {
  450. handleAxiosError(error, `getting test token for ${username}`, error.config, error.response);
  451. }
  452. }
  453. // Funktion zum Decodieren eines JWT-Tokens
  454. function decodeToken(token) {
  455. try {
  456. const base64Url = token.split('.')[1];
  457. const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
  458. const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
  459. return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
  460. }).join(''));
  461. return JSON.parse(jsonPayload);
  462. } catch (error) {
  463. console.error("Error decoding token:", error.message);
  464. return null;
  465. }
  466. }
  467. // Test-User erstellen
  468. async function createInitialUsers(token) {
  469. const users = [
  470. {
  471. username: "testadmin",
  472. enabled: true,
  473. emailVerified: true,
  474. firstName: "Test",
  475. lastName: "Admin",
  476. email: "testadmin@mrx8086.com",
  477. credentials: [{
  478. type: "password",
  479. value: process.env.TESTADMIN_PASSWORD || "initial123!",
  480. temporary: true
  481. }],
  482. groups: ["/Administrators"]
  483. },
  484. {
  485. username: "testuser",
  486. enabled: true,
  487. emailVerified: true,
  488. firstName: "Test",
  489. lastName: "User",
  490. email: "testuser@mrx8086.com",
  491. credentials: [{
  492. type: "password",
  493. value: process.env.TESTUSER_PASSWORD || "initial123!",
  494. temporary: true
  495. }],
  496. groups: ["/Users"]
  497. }
  498. ];
  499. for (const user of users) {
  500. try {
  501. // Prüfen ob User existiert
  502. const existingUsers = await axios.get(
  503. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
  504. {
  505. headers: {
  506. 'Authorization': `Bearer ${token}`
  507. },
  508. params: {
  509. username: user.username,
  510. exact: true
  511. }
  512. }
  513. );
  514. if (existingUsers.data.length > 0) {
  515. console.log(`User ${user.username} already exists`);
  516. continue;
  517. }
  518. // User erstellen
  519. await axios.post(
  520. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
  521. user,
  522. {
  523. headers: {
  524. 'Authorization': `Bearer ${token}`,
  525. 'Content-Type': 'application/json'
  526. }
  527. }
  528. );
  529. console.log(`User ${user.username} created successfully`);
  530. } catch (error) {
  531. handleAxiosError(error, `creating user: ${user.username}`, error.config, error.response);
  532. }
  533. }
  534. }
  535. // Hauptfunktion
  536. async function setupRealm() {
  537. try {
  538. console.log('Starting Keycloak setup...');
  539. const token = await getAdminToken();
  540. // Prüfe ob Realm existiert
  541. const realmExists = await checkRealmExists(token);
  542. if (!realmExists) {
  543. console.log('Creating new realm...');
  544. await createRealm(token);
  545. } else {
  546. console.log('Realm already exists, skipping base setup');
  547. }
  548. // Clients erstellen
  549. for (const clientId in CLIENTS) {
  550. await createClient(token, clientId, clientId, CLIENTS[clientId].redirectUris);
  551. }
  552. const nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
  553. await addDefaultClientScope(token, nextcloudClientId, "openid");
  554. // Gruppen erstellen
  555. await createDefaultGroups(token);
  556. // Test User erstellen
  557. await createInitialUsers(token);
  558. // Konfiguration des Office-Automation Realms mit Admin Token auslesen
  559. if (token) {
  560. console.log("Master Realm Admin Token:", token);
  561. try {
  562. const realmConfig = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`, {
  563. headers: {
  564. 'Authorization': `Bearer ${token}`
  565. }
  566. });
  567. console.log("Office Automation Realm Configuration:", realmConfig.data)
  568. } catch (error) {
  569. handleAxiosError(error, 'getting office realm configuration', error.config, error.response);
  570. }
  571. } else {
  572. console.error("Error getting Master Realm admin token")
  573. }
  574. // Test Token erstellen
  575. const testToken = await createTestToken(token, "testadmin@mrx8086.com");
  576. if (testToken) {
  577. console.log("Test Token generated successfully!");
  578. const decodedToken = decodeToken(testToken);
  579. if (decodedToken)
  580. console.log("Token:", decodedToken);
  581. } else {
  582. console.error("Error generating Test Token");
  583. }
  584. console.log('Setup completed successfully');
  585. } catch (error) {
  586. console.error('Setup failed:', error);
  587. process.exit(1);
  588. }
  589. }
  590. // Script ausführen
  591. setupRealm();