setup_realm.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. // setup_realm.js
  2. import dotenv from 'dotenv';
  3. import axios from 'axios';
  4. // Lade Umgebungsvariablen
  5. dotenv.config();
  6. // Konfigurationskonstanten
  7. const KEYCLOAK_URL = process.env.KEYCLOAK_URL || 'https://auth.mrx8086.com';
  8. const ADMIN_USERNAME = process.env.KEYCLOAK_ADMIN_USER;
  9. const ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD;
  10. const REALM_NAME = 'office-automation';
  11. // Client IDs aus Umgebungsvariablen
  12. const NEXTCLOUD_CLIENT_ID = process.env.NEXTCLOUD_CLIENT_ID || 'nextcloud';
  13. const PAPERLESS_CLIENT_ID = process.env.PAPERLESS_CLIENT_ID || 'paperless';
  14. const NODERED_CLIENT_ID = process.env.NODERED_CLIENT_ID || 'nodered';
  15. // Hilfsfunktion für API-Fehlerbehandlung
  16. const handleAxiosError = (error, operation) => {
  17. if (error.response) {
  18. console.error(`Error during ${operation}:`, {
  19. status: error.response.status,
  20. data: error.response.data
  21. });
  22. } else {
  23. console.error(`Error during ${operation}:`, error.message);
  24. }
  25. throw error;
  26. };
  27. // Admin Token abrufen
  28. async function getAdminToken() {
  29. try {
  30. const response = await axios.post(
  31. `${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`,
  32. new URLSearchParams({
  33. 'client_id': 'admin-cli',
  34. 'username': ADMIN_USERNAME,
  35. 'password': ADMIN_PASSWORD,
  36. 'grant_type': 'password'
  37. }),
  38. {
  39. headers: {
  40. 'Content-Type': 'application/x-www-form-urlencoded'
  41. }
  42. }
  43. );
  44. return response.data.access_token;
  45. } catch (error) {
  46. handleAxiosError(error, 'getting admin token');
  47. }
  48. }
  49. // Prüfen ob Realm existiert
  50. async function checkRealmExists(token) {
  51. try {
  52. await axios.get(
  53. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`,
  54. {
  55. headers: {
  56. 'Authorization': `Bearer ${token}`
  57. }
  58. }
  59. );
  60. return true;
  61. } catch (error) {
  62. if (error.response?.status === 404) {
  63. return false;
  64. }
  65. handleAxiosError(error, 'checking realm existence');
  66. }
  67. }
  68. // Prüfen ob Client existiert
  69. async function checkClientExists(token, clientId) {
  70. try {
  71. const response = await axios.get(
  72. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
  73. {
  74. headers: {
  75. 'Authorization': `Bearer ${token}`
  76. },
  77. params: {
  78. clientId: clientId
  79. }
  80. }
  81. );
  82. return response.data.length > 0;
  83. } catch (error) {
  84. handleAxiosError(error, `checking client existence: ${clientId}`);
  85. }
  86. }
  87. // Realm erstellen
  88. async function createRealm(token) {
  89. const realmConfig = {
  90. realm: REALM_NAME,
  91. enabled: true,
  92. displayName: "Office Automation",
  93. displayNameHtml: "<div class=\"kc-logo-text\">Office Automation</div>",
  94. sslRequired: "external",
  95. registrationAllowed: false,
  96. loginWithEmailAllowed: true,
  97. duplicateEmailsAllowed: false,
  98. resetPasswordAllowed: true,
  99. editUsernameAllowed: false,
  100. bruteForceProtected: true,
  101. permanentLockout: false,
  102. maxFailureWaitSeconds: 900,
  103. minimumQuickLoginWaitSeconds: 60,
  104. waitIncrementSeconds: 60,
  105. quickLoginCheckMilliSeconds: 1000,
  106. maxDeltaTimeSeconds: 43200,
  107. failureFactor: 3,
  108. defaultSignatureAlgorithm: "RS256",
  109. offlineSessionMaxLifespan: 5184000,
  110. offlineSessionMaxLifespanEnabled: true,
  111. webAuthnPolicySignatureAlgorithms: ["ES256"],
  112. webAuthnPolicyAttestationConveyancePreference: "none",
  113. webAuthnPolicyAuthenticatorAttachment: "cross-platform",
  114. webAuthnPolicyRequireResidentKey: "not specified",
  115. webAuthnPolicyUserVerificationRequirement: "preferred",
  116. webAuthnPolicyCreateTimeout: 0,
  117. webAuthnPolicyAvoidSameAuthenticatorRegister: false,
  118. defaultDefaultClientScopes: [
  119. "email",
  120. "profile",
  121. "roles",
  122. "web-origins"
  123. ],
  124. defaultOptionalClientScopes: [
  125. "address",
  126. "phone",
  127. "offline_access",
  128. "microprofile-jwt"
  129. ]
  130. };
  131. try {
  132. await axios.post(
  133. `${KEYCLOAK_URL}/admin/realms`,
  134. realmConfig,
  135. {
  136. headers: {
  137. 'Authorization': `Bearer ${token}`,
  138. 'Content-Type': 'application/json'
  139. }
  140. }
  141. );
  142. console.log('Realm created successfully');
  143. } catch (error) {
  144. handleAxiosError(error, 'creating realm');
  145. }
  146. }
  147. // Client erstellen
  148. async function createClient(token, clientId, clientName, redirectUris) {
  149. const clientConfig = {
  150. clientId: clientId,
  151. name: clientName,
  152. enabled: true,
  153. protocol: "openid-connect",
  154. publicClient: false,
  155. authorizationServicesEnabled: true,
  156. serviceAccountsEnabled: true,
  157. standardFlowEnabled: true,
  158. implicitFlowEnabled: false,
  159. directAccessGrantsEnabled: true,
  160. redirectUris: redirectUris,
  161. webOrigins: ["+"],
  162. defaultClientScopes: [
  163. "email",
  164. "profile",
  165. "roles",
  166. "web-origins"
  167. ],
  168. optionalClientScopes: [
  169. "address",
  170. "phone",
  171. "offline_access",
  172. "microprofile-jwt"
  173. ]
  174. };
  175. try {
  176. await axios.post(
  177. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
  178. clientConfig,
  179. {
  180. headers: {
  181. 'Authorization': `Bearer ${token}`,
  182. 'Content-Type': 'application/json'
  183. }
  184. }
  185. );
  186. console.log(`Client ${clientId} created successfully`);
  187. } catch (error) {
  188. handleAxiosError(error, `creating client: ${clientId}`);
  189. }
  190. }
  191. // Gruppen erstellen
  192. async function createDefaultGroups(token) {
  193. const groups = [
  194. {
  195. name: "Administrators",
  196. path: "/Administrators",
  197. attributes: {
  198. "description": ["Full system access"]
  199. }
  200. },
  201. {
  202. name: "Users",
  203. path: "/Users",
  204. attributes: {
  205. "description": ["Regular system users"]
  206. }
  207. }
  208. ];
  209. for (const group of groups) {
  210. try {
  211. await axios.post(
  212. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
  213. group,
  214. {
  215. headers: {
  216. 'Authorization': `Bearer ${token}`,
  217. 'Content-Type': 'application/json'
  218. }
  219. }
  220. );
  221. console.log(`Group ${group.name} created successfully`);
  222. } catch (error) {
  223. if (error.response?.status === 409) {
  224. console.log(`Group ${group.name} already exists`);
  225. } else {
  226. handleAxiosError(error, `creating group: ${group.name}`);
  227. }
  228. }
  229. }
  230. }
  231. // Test-User erstellen
  232. async function createInitialUsers(token) {
  233. const users = [
  234. {
  235. username: "testadmin",
  236. enabled: true,
  237. emailVerified: true,
  238. firstName: "Test",
  239. lastName: "Admin",
  240. email: "testadmin@mrx8086.com",
  241. credentials: [{
  242. type: "password",
  243. value: process.env.TESTADMIN_PASSWORD || "initial123!",
  244. temporary: true
  245. }],
  246. groups: ["/Administrators"]
  247. },
  248. {
  249. username: "testuser",
  250. enabled: true,
  251. emailVerified: true,
  252. firstName: "Test",
  253. lastName: "User",
  254. email: "testuser@mrx8086.com",
  255. credentials: [{
  256. type: "password",
  257. value: process.env.TESTUSER_PASSWORD || "initial123!",
  258. temporary: true
  259. }],
  260. groups: ["/Users"]
  261. }
  262. ];
  263. for (const user of users) {
  264. try {
  265. // Prüfen ob User existiert
  266. const existingUsers = await axios.get(
  267. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
  268. {
  269. headers: {
  270. 'Authorization': `Bearer ${token}`
  271. },
  272. params: {
  273. username: user.username,
  274. exact: true
  275. }
  276. }
  277. );
  278. if (existingUsers.data.length > 0) {
  279. console.log(`User ${user.username} already exists`);
  280. continue;
  281. }
  282. // User erstellen
  283. await axios.post(
  284. `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
  285. user,
  286. {
  287. headers: {
  288. 'Authorization': `Bearer ${token}`,
  289. 'Content-Type': 'application/json'
  290. }
  291. }
  292. );
  293. console.log(`User ${user.username} created successfully`);
  294. } catch (error) {
  295. handleAxiosError(error, `creating user: ${user.username}`);
  296. }
  297. }
  298. }
  299. // Hauptfunktion
  300. async function setupRealm() {
  301. try {
  302. console.log('Starting Keycloak setup...');
  303. const token = await getAdminToken();
  304. // Prüfe ob Realm existiert
  305. const realmExists = await checkRealmExists(token);
  306. if (!realmExists) {
  307. console.log('Creating new realm...');
  308. await createRealm(token);
  309. // Clients erstellen
  310. const clients = [
  311. { id: NEXTCLOUD_CLIENT_ID, name: "Nextcloud", redirectUris: ["https://cloud.mrx8086.com/*"] },
  312. { id: PAPERLESS_CLIENT_ID, name: "Paperless", redirectUris: ["https://docs.mrx8086.com/*"] },
  313. { id: NODERED_CLIENT_ID, name: "Node-RED", redirectUris: ["https://automate.mrx8086.com/*"] }
  314. ];
  315. for (const client of clients) {
  316. const clientExists = await checkClientExists(token, client.id);
  317. if (!clientExists) {
  318. await createClient(token, client.id, client.name, client.redirectUris);
  319. } else {
  320. console.log(`Client ${client.id} already exists`);
  321. }
  322. }
  323. // Gruppen erstellen
  324. await createDefaultGroups(token);
  325. } else {
  326. console.log('Realm already exists, skipping base setup');
  327. }
  328. // User erstellen/aktualisieren
  329. await createInitialUsers(token);
  330. console.log('Setup completed successfully');
  331. } catch (error) {
  332. console.error('Setup failed:', error);
  333. process.exit(1);
  334. }
  335. }
  336. // Script ausführen
  337. setupRealm();