setup_realm.js 19 KB

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