From 95563fa3a723c8a90b7878260036fbf4c5436f53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:42:37 +0000 Subject: [PATCH 01/45] Bump axios from 1.7.7 to 1.8.2 Bumps [axios](https://github.com/axios/axios) from 1.7.7 to 1.8.2. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.7.7...v1.8.2) --- updated-dependencies: - dependency-name: axios dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 148 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 129 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index a824e47571..e93dde7f50 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@types/node": "^20.17.17", "@vitejs/plugin-react": "^4.3.3", "@zitadel/prettier-config": "workspace:*", - "axios": "^1.7.7", + "axios": "^1.8.2", "dotenv": "^16.4.5", "eslint": "8.57.1", "@zitadel/eslint-config": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b28520f9e2..d47fcbbc4a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,8 +42,8 @@ importers: specifier: workspace:* version: link:packages/zitadel-prettier-config axios: - specifier: ^1.7.7 - version: 1.7.7(debug@4.3.7) + specifier: ^1.8.2 + version: 1.8.3(debug@4.3.7) dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -405,6 +405,10 @@ packages: resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.26.10': + resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.25.9': resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} @@ -916,6 +920,7 @@ packages: '@faker-js/faker@9.2.0': resolution: {integrity: sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} + deprecated: Please update to a newer version '@floating-ui/core@1.6.8': resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} @@ -1899,8 +1904,8 @@ packages: resolution: {integrity: sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==} engines: {node: '>=4'} - axios@1.7.7: - resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + axios@1.8.3: + resolution: {integrity: sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==} axobject-query@3.1.1: resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} @@ -1972,6 +1977,10 @@ packages: resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} engines: {node: '>=6'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} @@ -2316,6 +2325,10 @@ packages: dprint-node@1.0.8: resolution: {integrity: sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -2372,6 +2385,10 @@ packages: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -2387,10 +2404,18 @@ packages: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.3: resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} @@ -2673,8 +2698,8 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -2696,6 +2721,10 @@ packages: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -2762,6 +2791,14 @@ packages: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -2838,6 +2875,10 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2870,6 +2911,10 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} @@ -3362,6 +3407,10 @@ packages: map-stream@0.1.0: resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} @@ -4980,6 +5029,10 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.26.10': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.25.9': dependencies: '@babel/code-frame': 7.26.2 @@ -5959,7 +6012,7 @@ snapshots: '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.26.2 - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.10 '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 @@ -6407,10 +6460,10 @@ snapshots: axe-core@4.10.0: {} - axios@1.7.7(debug@4.3.7): + axios@1.8.3(debug@4.3.7): dependencies: - follow-redirects: 1.15.6(debug@4.3.7) - form-data: 4.0.0 + follow-redirects: 1.15.9(debug@4.3.7) + form-data: 4.0.2 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -6484,12 +6537,17 @@ snapshots: cachedir@2.4.0: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 set-function-length: 1.2.2 callsites@3.1.0: {} @@ -6866,6 +6924,12 @@ snapshots: dependencies: detect-libc: 1.0.3 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + duplexer@0.1.2: {} eastasianwidth@0.2.0: {} @@ -6961,13 +7025,15 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + es-define-property@1.0.1: {} + es-errors@1.3.0: {} es-get-iterator@1.1.3: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 is-arguments: 1.1.1 is-map: 2.0.3 is-set: 2.0.3 @@ -6996,12 +7062,23 @@ snapshots: dependencies: es-errors: 1.3.0 + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.0.3: dependencies: get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 hasown: 2.0.2 + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + es-shim-unscopables@1.0.2: dependencies: hasown: 2.0.2 @@ -7114,7 +7191,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 @@ -7127,7 +7204,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -7148,7 +7225,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -7437,7 +7514,7 @@ snapshots: flatted@3.3.1: {} - follow-redirects@1.15.6(debug@4.3.7): + follow-redirects@1.15.9(debug@4.3.7): optionalDependencies: debug: 4.3.7(supports-color@5.5.0) @@ -7458,6 +7535,13 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + fraction.js@4.3.7: {} from@0.1.7: {} @@ -7530,6 +7614,24 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-stream@5.2.0: dependencies: pump: 3.0.0 @@ -7629,6 +7731,8 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + gopd@1.2.0: {} + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -7654,9 +7758,11 @@ snapshots: has-symbols@1.0.3: {} + has-symbols@1.1.0: {} + has-tostringtag@1.0.2: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 has-unicode@2.0.1: {} @@ -7887,7 +7993,7 @@ snapshots: is-weakset@2.0.3: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 is-windows@1.0.2: {} @@ -8154,6 +8260,8 @@ snapshots: map-stream@0.1.0: {} + math-intrinsics@1.1.0: {} + meow@13.2.0: {} merge-stream@2.0.0: {} @@ -9480,7 +9588,7 @@ snapshots: wait-on@8.0.1(debug@4.3.7): dependencies: - axios: 1.7.7(debug@4.3.7) + axios: 1.8.3(debug@4.3.7) joi: 17.13.3 lodash: 4.17.21 minimist: 1.2.8 From 8ae6bc1686853f5acb042692863a6d0e4150742e Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 13 Mar 2025 16:31:35 +0100 Subject: [PATCH 02/45] fix: enable saml sp --- .../(login)/idp/[provider]/success/page.tsx | 21 +- apps/login/src/lib/idp.ts | 186 ------------------ apps/login/src/lib/zitadel.ts | 21 -- 3 files changed, 9 insertions(+), 219 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 4b6b873c30..6008257e36 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -4,7 +4,7 @@ import { linkingFailed } from "@/components/idps/pages/linking-failed"; import { linkingSuccess } from "@/components/idps/pages/linking-success"; import { loginFailed } from "@/components/idps/pages/login-failed"; import { loginSuccess } from "@/components/idps/pages/login-success"; -import { idpTypeToIdentityProviderType, PROVIDER_MAPPING } from "@/lib/idp"; +import { idpTypeToIdentityProviderType } from "@/lib/idp"; import { getServiceUrlFromHeaders } from "@/lib/service"; import { addHuman, @@ -57,7 +57,7 @@ export default async function Page(props: { token, }); - const { idpInformation, userId } = intent; + const { idpInformation, userId, addHumanUser } = intent; // sign in user. If user should be linked continue if (userId && !link) { @@ -124,7 +124,7 @@ export default async function Page(props: { // search for potential user via username, then link if (options?.isLinkingAllowed) { let foundUser; - const email = PROVIDER_MAPPING[providerType](idpInformation).email?.email; + const email = addHumanUser.email?.email; if (options.autoLinking === AutoLinkingOption.EMAIL && email) { foundUser = await listUsers({ serviceUrl, email }).then((response) => { @@ -181,15 +181,12 @@ export default async function Page(props: { if (options?.isCreationAllowed && options.isAutoCreation) { let orgToRegisterOn: string | undefined = organization; - let userData: AddHumanUserRequest = - PROVIDER_MAPPING[providerType](idpInformation); - if ( !orgToRegisterOn && - userData.username && // username or email? - ORG_SUFFIX_REGEX.test(userData.username) + addHumanUser.username && // username or email? + ORG_SUFFIX_REGEX.test(addHumanUser.username) ) { - const matched = ORG_SUFFIX_REGEX.exec(userData.username); + const matched = ORG_SUFFIX_REGEX.exec(addHumanUser.username); const suffix = matched?.[1] ?? ""; // this just returns orgs where the suffix is set as primary domain @@ -214,15 +211,15 @@ export default async function Page(props: { org: { case: "orgId", value: orgToRegisterOn }, }); - userData = create(AddHumanUserRequestSchema, { - ...userData, + addHumanUser = create(AddHumanUserRequestSchema, { + ...addHumanUser, organization: organizationSchema, }); } const newUser = await addHuman({ serviceUrl, - request: userData, + request: addHumanUser, }); if (newUser) { diff --git a/apps/login/src/lib/idp.ts b/apps/login/src/lib/idp.ts index 1c021cbfe0..825cf25a24 100644 --- a/apps/login/src/lib/idp.ts +++ b/apps/login/src/lib/idp.ts @@ -74,189 +74,3 @@ export function idpTypeToIdentityProviderType( throw new Error("Unknown identity provider type"); } } -// this maps the IDPInformation to the AddHumanUserRequest which is used when creating a user or linking a user (email) -// TODO: extend this object from a other file which can be overwritten by customers like map = { ...PROVIDER_MAPPING, ...customerMap } -export type OIDC_USER = { - User: { - email: string; - name?: string; - given_name?: string; - family_name?: string; - }; -}; - -const GITLAB_MAPPING = (idp: IDPInformation) => { - const rawInfo = idp.rawInformation as { - name: string; - email: string; - email_verified: boolean; - }; - - return create(AddHumanUserRequestSchema, { - username: idp.userName, - email: { - email: rawInfo.email, - verification: { case: "isVerified", value: rawInfo.email_verified }, - }, - profile: { - displayName: rawInfo.name || idp.userName || "", - givenName: "", - familyName: "", - }, - idpLinks: [ - { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }, - ], - }); -}; - -const OIDC_MAPPING = (idp: IDPInformation) => { - const rawInfo = idp.rawInformation as OIDC_USER; - - return create(AddHumanUserRequestSchema, { - username: idp.userName, - email: { - email: rawInfo.User?.email, - verification: { case: "isVerified", value: true }, - }, - profile: { - displayName: rawInfo.User?.name ?? "", - givenName: rawInfo.User?.given_name ?? "", - familyName: rawInfo.User?.family_name ?? "", - }, - idpLinks: [ - { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }, - ], - }); -}; - -const GITHUB_MAPPING = (idp: IDPInformation) => { - const rawInfo = idp.rawInformation as { - email: string; - name: string; - }; - - return create(AddHumanUserRequestSchema, { - username: idp.userName, - email: { - email: rawInfo.email, - verification: { case: "isVerified", value: true }, - }, - profile: { - displayName: rawInfo.name ?? "", - givenName: rawInfo.name ?? "", - familyName: rawInfo.name ?? "", - }, - idpLinks: [ - { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }, - ], - }); -}; - -export const PROVIDER_MAPPING: { - [provider: number]: (rI: IDPInformation) => AddHumanUserRequest; -} = { - [IdentityProviderType.GOOGLE]: (idp: IDPInformation) => { - const rawInfo = idp.rawInformation as OIDC_USER; - - return create(AddHumanUserRequestSchema, { - username: idp.userName, - email: { - email: rawInfo.User?.email, - verification: { case: "isVerified", value: true }, - }, - profile: { - displayName: rawInfo.User?.name ?? "", - givenName: rawInfo.User?.given_name ?? "", - familyName: rawInfo.User?.family_name ?? "", - }, - idpLinks: [ - { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }, - ], - }); - }, - [IdentityProviderType.GITLAB]: GITLAB_MAPPING, - [IdentityProviderType.GITLAB_SELF_HOSTED]: GITLAB_MAPPING, - [IdentityProviderType.OIDC]: OIDC_MAPPING, - // check - [IdentityProviderType.OAUTH]: OIDC_MAPPING, - [IdentityProviderType.AZURE_AD]: (idp: IDPInformation) => { - const rawInfo = idp.rawInformation as { - jobTitle: string; - mail: string; - mobilePhone: string; - preferredLanguage: string; - id: string; - displayName?: string; - givenName?: string; - surname?: string; - officeLocation?: string; - userPrincipalName: string; - }; - - return create(AddHumanUserRequestSchema, { - username: idp.userName, - email: { - email: rawInfo.mail || rawInfo.userPrincipalName || "", - verification: { case: "isVerified", value: true }, - }, - profile: { - displayName: rawInfo.displayName ?? "", - givenName: rawInfo.givenName ?? "", - familyName: rawInfo.surname ?? "", - }, - idpLinks: [ - { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }, - ], - }); - }, - [IdentityProviderType.GITHUB]: GITHUB_MAPPING, - [IdentityProviderType.GITHUB_ES]: GITHUB_MAPPING, - [IdentityProviderType.APPLE]: (idp: IDPInformation) => { - const rawInfo = idp.rawInformation as { - name?: string; - firstName?: string; - lastName?: string; - email?: string; - }; - - return create(AddHumanUserRequestSchema, { - username: idp.userName, - email: { - email: rawInfo.email ?? "", - verification: { case: "isVerified", value: true }, - }, - profile: { - displayName: rawInfo.name ?? "", - givenName: rawInfo.firstName ?? "", - familyName: rawInfo.lastName ?? "", - }, - idpLinks: [ - { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }, - ], - }); - }, -}; diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 89f587d800..0511eaaf0d 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -915,27 +915,6 @@ export async function startIdentityProviderFlow({ }); } -export async function retrieveIdentityProviderInformation({ - serviceUrl, - idpIntentId, - idpIntentToken, -}: { - serviceUrl: string; - - idpIntentId: string; - idpIntentToken: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); - - return userService.retrieveIdentityProviderIntent({ - idpIntentId, - idpIntentToken, - }); -} - export async function getAuthRequest({ serviceUrl, authRequestId, From 6163710282e5ebeec2853a35074d7ed5f7e600c0 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 14 Mar 2025 09:51:56 +0100 Subject: [PATCH 03/45] fix: enable saml sp --- apps/login/src/components/sign-in-with-idp.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/login/src/components/sign-in-with-idp.tsx b/apps/login/src/components/sign-in-with-idp.tsx index 5af5878759..d10d1390f2 100644 --- a/apps/login/src/components/sign-in-with-idp.tsx +++ b/apps/login/src/components/sign-in-with-idp.tsx @@ -88,6 +88,7 @@ export function SignInWithIdp({ ), [IdentityProviderType.GITLAB]: SignInWithGitlab, [IdentityProviderType.GITLAB_SELF_HOSTED]: SignInWithGitlab, + [IdentityProviderType.SAML]: SignInWithGeneric, }; const Component = components[type]; From e6c57068b8e677b0050474c9f7aee4386146fc54 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 27 Dec 2024 11:53:46 -0500 Subject: [PATCH 04/45] chore: improve idp integration using server action --- apps/login/package.json | 1 + .../login/src/components/idps/base-button.tsx | 18 +++++- .../components/idps/sign-in-with-github.tsx | 60 ++++++++++-------- .../login/src/components/sign-in-with-idp.tsx | 61 ++++++------------- apps/login/src/lib/server/idp.ts | 34 +++++++++++ pnpm-lock.yaml | 31 ++++++---- 6 files changed, 120 insertions(+), 85 deletions(-) diff --git a/apps/login/package.json b/apps/login/package.json index c200079530..1017c69310 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -46,6 +46,7 @@ "copy-to-clipboard": "^3.3.3", "deepmerge": "^4.3.1", "jose": "^5.3.0", + "lucide-react": "0.469.0", "moment": "^2.29.4", "next": "15.2.0-canary.33", "next-intl": "^3.25.1", diff --git a/apps/login/src/components/idps/base-button.tsx b/apps/login/src/components/idps/base-button.tsx index 4f24dd17bc..0185c57996 100644 --- a/apps/login/src/components/idps/base-button.tsx +++ b/apps/login/src/components/idps/base-button.tsx @@ -1,7 +1,9 @@ "use client"; import { clsx } from "clsx"; +import { Loader2Icon } from "lucide-react"; import { ButtonHTMLAttributes, DetailedHTMLProps, forwardRef } from "react"; +import { useFormStatus } from "react-dom"; export type SignInWithIdentityProviderProps = DetailedHTMLProps< ButtonHTMLAttributes, @@ -15,15 +17,25 @@ export const BaseButton = forwardRef< HTMLButtonElement, SignInWithIdentityProviderProps >(function BaseButton(props, ref) { + const formStatus = useFormStatus(); + return ( ); }); diff --git a/apps/login/src/components/idps/sign-in-with-github.tsx b/apps/login/src/components/idps/sign-in-with-github.tsx index 49b4ce7b26..45108d17f7 100644 --- a/apps/login/src/components/idps/sign-in-with-github.tsx +++ b/apps/login/src/components/idps/sign-in-with-github.tsx @@ -4,6 +4,39 @@ import { useTranslations } from "next-intl"; import { forwardRef } from "react"; import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; +function GitHubLogo() { + return ( + <> + + + + + + + + ); +} + export const SignInWithGithub = forwardRef< HTMLButtonElement, SignInWithIdentityProviderProps @@ -14,32 +47,7 @@ export const SignInWithGithub = forwardRef< return (
- - - - - - +
{children ? ( children diff --git a/apps/login/src/components/sign-in-with-idp.tsx b/apps/login/src/components/sign-in-with-idp.tsx index 5af5878759..c2fca07cc6 100644 --- a/apps/login/src/components/sign-in-with-idp.tsx +++ b/apps/login/src/components/sign-in-with-idp.tsx @@ -1,13 +1,12 @@ "use client"; import { idpTypeToSlug } from "@/lib/idp"; -import { startIDPFlow } from "@/lib/server/idp"; +import { redirectToIdp } from "@/lib/server/idp"; import { IdentityProvider, IdentityProviderType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useRouter } from "next/navigation"; -import { ReactNode, useCallback, useState } from "react"; +import { ReactNode, useActionState } from "react"; import { Alert } from "./alert"; import { SignInWithIdentityProviderProps } from "./idps/base-button"; import { SignInWithApple } from "./idps/sign-in-with-apple"; @@ -31,45 +30,10 @@ export function SignInWithIdp({ organization, linkOnly, }: Readonly) { - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const router = useRouter(); + const [state, action, _isPending] = useActionState(redirectToIdp, {}); - const startFlow = useCallback( - async (idpId: string, provider: string) => { - setLoading(true); - const params = new URLSearchParams(); - if (linkOnly) params.set("link", "true"); - if (requestId) params.set("requestId", requestId); - if (organization) params.set("organization", organization); - - try { - const response = await startIDPFlow({ - idpId, - successUrl: `/idp/${provider}/success?` + params.toString(), - failureUrl: `/idp/${provider}/failure?` + params.toString(), - }); - - if (response && "error" in response && response?.error) { - setError(response.error); - return; - } - - if (response && "redirect" in response && response?.redirect) { - return router.push(response.redirect); - } - } catch { - setError("Could not start IDP flow"); - } finally { - setLoading(false); - } - }, - [requestId, organization, linkOnly, router], - ); - - const renderIDPButton = (idp: IdentityProvider) => { + const renderIDPButton = (idp: IdentityProvider, index: number) => { const { id, name, type } = idp; - const onClick = () => startFlow(id, idpTypeToSlug(type)); const components: Partial< Record< @@ -92,16 +56,27 @@ export function SignInWithIdp({ const Component = components[type]; return Component ? ( - +
+ + + + + + + ) : null; }; return (
{identityProviders?.map(renderIDPButton)} - {error && ( + {state?.error && (
- {error} + {state?.error}
)}
diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index aa38a63f27..e6861a60c4 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -6,11 +6,45 @@ import { startIdentityProviderFlow, } from "@/lib/zitadel"; import { headers } from "next/headers"; +import { redirect } from "next/navigation"; import { getNextUrl } from "../client"; import { getServiceUrlFromHeaders } from "../service"; import { checkEmailVerification } from "../verify-helper"; import { createSessionForIdpAndUpdateCookie } from "./cookie"; +export type RedirectToIdpState = { error?: string | null } | undefined; + +export async function redirectToIdp( + prevState: RedirectToIdpState, + formData: FormData, +): Promise { + const params = new URLSearchParams(); + + const linkOnly = formData.get("linkOnly") === "true"; + const requestId = formData.get("requestId") as string; + const organization = formData.get("organization") as string; + const idpId = formData.get("id") as string; + const provider = formData.get("provider") as string; + + if (linkOnly) params.set("link", "true"); + if (requestId) params.set("requestId", requestId); + if (organization) params.set("organization", organization); + + const response = await startIDPFlow({ + idpId, + successUrl: `/idp/${provider}/success?` + params.toString(), + failureUrl: `/idp/${provider}/failure?` + params.toString(), + }); + + if (response && "error" in response && response?.error) { + return { error: response.error }; + } + + if (response && "redirect" in response && response?.redirect) { + redirect(response.redirect); + } +} + export type StartIDPFlowCommand = { idpId: string; successUrl: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b28520f9e2..c3bcfab38f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: jose: specifier: ^5.3.0 version: 5.8.0 + lucide-react: + specifier: 0.469.0 + version: 0.469.0(react@19.0.0) moment: specifier: ^2.29.4 version: 2.30.1 @@ -1479,9 +1482,6 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/helpers@0.5.13': - resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} - '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -3339,6 +3339,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.469.0: + resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -5836,7 +5841,7 @@ snapshots: '@react-aria/ssr@3.9.6(react@19.0.0)': dependencies: - '@swc/helpers': 0.5.5 + '@swc/helpers': 0.5.15 react: 19.0.0 '@react-aria/utils@3.25.3(react@19.0.0)': @@ -5844,13 +5849,13 @@ snapshots: '@react-aria/ssr': 3.9.6(react@19.0.0) '@react-stately/utils': 3.10.4(react@19.0.0) '@react-types/shared': 3.25.0(react@19.0.0) - '@swc/helpers': 0.5.5 + '@swc/helpers': 0.5.15 clsx: 2.1.1 react: 19.0.0 '@react-stately/utils@3.10.4(react@19.0.0)': dependencies: - '@swc/helpers': 0.5.13 + '@swc/helpers': 0.5.15 react: 19.0.0 '@react-types/shared@3.25.0(react@19.0.0)': @@ -5925,10 +5930,6 @@ snapshots: '@swc/counter@0.1.3': {} - '@swc/helpers@0.5.13': - dependencies: - tslib: 2.8.1 - '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -7114,7 +7115,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 @@ -7127,7 +7128,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -7148,7 +7149,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -8135,6 +8136,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.469.0(react@19.0.0): + dependencies: + react: 19.0.0 + lz-string@1.5.0: {} magic-string@0.30.12: From 92c54e0f3902aa4bb46d57eea91bc5d26159c64a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 18 Mar 2025 08:22:08 +0100 Subject: [PATCH 05/45] lint --- apps/login/src/app/(login)/idp/[provider]/success/page.tsx | 5 +---- apps/login/src/lib/idp.ts | 6 ------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 6008257e36..844a11e84a 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -19,10 +19,7 @@ import { import { create } from "@zitadel/client"; import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb"; import { OrganizationSchema } from "@zitadel/proto/zitadel/object/v2/object_pb"; -import { - AddHumanUserRequest, - AddHumanUserRequestSchema, -} from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { AddHumanUserRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; diff --git a/apps/login/src/lib/idp.ts b/apps/login/src/lib/idp.ts index 825cf25a24..1d4b82951a 100644 --- a/apps/login/src/lib/idp.ts +++ b/apps/login/src/lib/idp.ts @@ -1,11 +1,5 @@ -import { create } from "@zitadel/client"; import { IDPType } from "@zitadel/proto/zitadel/idp/v2/idp_pb"; import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; -import { - AddHumanUserRequest, - AddHumanUserRequestSchema, -} from "@zitadel/proto/zitadel/user/v2/user_service_pb"; // This maps the IdentityProviderType to a slug which is used in the /success and /failure routes export function idpTypeToSlug(idpType: IdentityProviderType) { From 2d0a0d567b53dbc98ab4beee3c84d6f2189ed914 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 18 Mar 2025 08:34:27 +0100 Subject: [PATCH 06/45] fix build --- .../(login)/idp/[provider]/success/page.tsx | 22 ++++++++++--------- packages/zitadel-proto/package.json | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 844a11e84a..193622fa20 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -54,7 +54,8 @@ export default async function Page(props: { token, }); - const { idpInformation, userId, addHumanUser } = intent; + const { idpInformation, userId } = intent; + let { addHumanUser } = intent; // sign in user. If user should be linked continue if (userId && !link) { @@ -121,7 +122,7 @@ export default async function Page(props: { // search for potential user via username, then link if (options?.isLinkingAllowed) { let foundUser; - const email = addHumanUser.email?.email; + const email = addHumanUser?.email?.email; if (options.autoLinking === AutoLinkingOption.EMAIL && email) { foundUser = await listUsers({ serviceUrl, email }).then((response) => { @@ -177,10 +178,11 @@ export default async function Page(props: { if (options?.isCreationAllowed && options.isAutoCreation) { let orgToRegisterOn: string | undefined = organization; + let newUser; if ( !orgToRegisterOn && - addHumanUser.username && // username or email? + addHumanUser?.username && // username or email? ORG_SUFFIX_REGEX.test(addHumanUser.username) ) { const matched = ORG_SUFFIX_REGEX.exec(addHumanUser.username); @@ -203,21 +205,21 @@ export default async function Page(props: { } } - if (orgToRegisterOn) { + if (addHumanUser && orgToRegisterOn) { const organizationSchema = create(OrganizationSchema, { org: { case: "orgId", value: orgToRegisterOn }, }); - addHumanUser = create(AddHumanUserRequestSchema, { + const addHumanUserWithOrganization = create(AddHumanUserRequestSchema, { ...addHumanUser, organization: organizationSchema, }); - } - const newUser = await addHuman({ - serviceUrl, - request: addHumanUser, - }); + newUser = await addHuman({ + serviceUrl, + request: addHumanUserWithOrganization, + }); + } if (newUser) { return ( diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index dcfa9b3f22..50c8342287 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -14,7 +14,7 @@ ], "sideEffects": false, "scripts": { - "generate": "buf generate https://github.com/zitadel/zitadel.git#tag=v2.71.1 --path ./proto/zitadel", + "generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel", "clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate" }, "dependencies": { From 2458d22fecb5de4213879e07016b97b2ad0762c1 Mon Sep 17 00:00:00 2001 From: Adam Kida <122802098+jmblab-adam@users.noreply.github.com> Date: Thu, 20 Mar 2025 11:09:17 +0100 Subject: [PATCH 07/45] add Polish translations --- apps/login/locales/pl.json | 196 +++++++++++++++++++++++++++++++++++++ apps/login/src/lib/i18n.ts | 4 + 2 files changed, 200 insertions(+) create mode 100644 apps/login/locales/pl.json diff --git a/apps/login/locales/pl.json b/apps/login/locales/pl.json new file mode 100644 index 0000000000..391196c49b --- /dev/null +++ b/apps/login/locales/pl.json @@ -0,0 +1,196 @@ +{ + "common": { + "back": "Powrót" + }, + "accounts": { + "title": "Konta", + "description": "Wybierz konto, którego chcesz użyć.", + "addAnother": "Dodaj kolejne konto", + "noResults": "Nie znaleziono kont" + }, + "loginname": { + "title": "Witamy ponownie!", + "description": "Wprowadź dane logowania.", + "register": "Zarejestruj nowego użytkownika" + }, + "password": { + "verify": { + "title": "Hasło", + "description": "Wprowadź swoje hasło.", + "resetPassword": "Zresetuj hasło", + "submit": "Kontynuuj" + }, + "set": { + "title": "Ustaw hasło", + "description": "Ustaw hasło dla swojego konta", + "codeSent": "Kod został wysłany na twój adres e-mail.", + "noCodeReceived": "Nie otrzymałeś kodu?", + "resend": "Wyślij kod ponownie", + "submit": "Kontynuuj" + }, + "change": { + "title": "Zmień hasło", + "description": "Ustaw nowe hasło dla swojego konta", + "submit": "Kontynuuj" + } + }, + "idp": { + "title": "Zaloguj się za pomocą SSO", + "description": "Wybierz jednego z poniższych dostawców, aby się zalogować", + "signInWithApple": "Zaloguj się przez Apple", + "signInWithGoogle": "Zaloguj się przez Google", + "signInWithAzureAD": "Zaloguj się przez AzureAD", + "signInWithGithub": "Zaloguj się przez GitHub", + "signInWithGitlab": "Zaloguj się przez GitLab", + "loginSuccess": { + "title": "Logowanie udane", + "description": "Zostałeś pomyślnie zalogowany!" + }, + "linkingSuccess": { + "title": "Konto powiązane", + "description": "Pomyślnie powiązałeś swoje konto!" + }, + "registerSuccess": { + "title": "Rejestracja udana", + "description": "Pomyślnie się zarejestrowałeś!" + }, + "loginError": { + "title": "Logowanie nieudane", + "description": "Wystąpił błąd podczas próby logowania." + }, + "linkingError": { + "title": "Powiązanie konta nie powiodło się", + "description": "Wystąpił błąd podczas próby powiązania konta." + } + }, + "mfa": { + "verify": { + "title": "Zweryfikuj swoją tożsamość", + "description": "Wybierz jeden z poniższych sposobów weryfikacji.", + "noResults": "Nie znaleziono dostępnych metod uwierzytelniania dwuskładnikowego." + }, + "set": { + "title": "Skonfiguruj uwierzytelnianie dwuskładnikowe", + "description": "Wybierz jedną z poniższych metod drugiego czynnika.", + "skip": "Pomiń" + } + }, + "otp": { + "verify": { + "title": "Zweryfikuj uwierzytelnianie dwuskładnikowe", + "totpDescription": "Wprowadź kod z aplikacji uwierzytelniającej.", + "smsDescription": "Wprowadź kod otrzymany SMS-em.", + "emailDescription": "Wprowadź kod otrzymany e-mailem.", + "noCodeReceived": "Nie otrzymałeś kodu?", + "resendCode": "Wyślij kod ponownie", + "submit": "Kontynuuj" + }, + "set": { + "title": "Skonfiguruj uwierzytelnianie dwuskładnikowe", + "totpDescription": "Zeskanuj kod QR za pomocą aplikacji uwierzytelniającej.", + "smsDescription": "Wprowadź swój numer telefonu, aby otrzymać kod SMS-em.", + "emailDescription": "Wprowadź swój adres e-mail, aby otrzymać kod e-mailem.", + "totpRegisterDescription": "Zeskanuj kod QR lub otwórz adres URL ręcznie.", + "submit": "Kontynuuj" + } + }, + "passkey": { + "verify": { + "title": "Uwierzytelnij się za pomocą klucza dostępu", + "description": "Twoje urządzenie poprosi o użycie odcisku palca, rozpoznawania twarzy lub blokady ekranu.", + "usePassword": "Użyj hasła", + "submit": "Kontynuuj" + }, + "set": { + "title": "Skonfiguruj klucz dostępu", + "description": "Twoje urządzenie poprosi o użycie odcisku palca, rozpoznawania twarzy lub blokady ekranu.", + "info": { + "description": "Klucz dostępu to metoda uwierzytelniania na urządzeniu, wykorzystująca np. odcisk palca, Apple FaceID lub podobne rozwiązania.", + "link": "Uwierzytelnianie bez hasła" + }, + "skip": "Pomiń", + "submit": "Kontynuuj" + } + }, + "u2f": { + "verify": { + "title": "Zweryfikuj uwierzytelnianie dwuskładnikowe", + "description": "Zweryfikuj swoje konto za pomocą urządzenia." + }, + "set": { + "title": "Skonfiguruj uwierzytelnianie dwuskładnikowe", + "description": "Skonfiguruj urządzenie jako dodatkowy czynnik uwierzytelniania.", + "submit": "Kontynuuj" + } + }, + "register": { + "methods": { + "passkey": "Klucz dostępu", + "password": "Hasło" + }, + "disabled": { + "title": "Rejestracja wyłączona", + "description": "Rejestracja jest wyłączona. Skontaktuj się z administratorem." + }, + "missingdata": { + "title": "Brak danych", + "description": "Podaj e-mail, imię i nazwisko, aby się zarejestrować." + }, + "title": "Rejestracja", + "description": "Utwórz konto ZITADEL.", + "selectMethod": "Wybierz metodę uwierzytelniania, której chcesz użyć", + "agreeTo": "Aby się zarejestrować, musisz zaakceptować warunki korzystania", + "termsOfService": "Regulamin", + "privacyPolicy": "Polityka prywatności", + "submit": "Kontynuuj", + "password": { + "title": "Ustaw hasło", + "description": "Ustaw hasło dla swojego konta", + "submit": "Kontynuuj" + } + }, + "invite": { + "title": "Zaproś użytkownika", + "description": "Podaj adres e-mail oraz imię i nazwisko użytkownika, którego chcesz zaprosić.", + "info": "Użytkownik otrzyma e-mail z dalszymi instrukcjami.", + "notAllowed": "Twoje ustawienia nie pozwalają na zapraszanie użytkowników.", + "submit": "Kontynuuj", + "success": { + "title": "Użytkownik zaproszony", + "description": "E-mail został pomyślnie wysłany.", + "verified": "Użytkownik został zaproszony i już zweryfikował swój e-mail.", + "notVerifiedYet": "Użytkownik został zaproszony. Otrzyma e-mail z dalszymi instrukcjami.", + "submit": "Zaproś kolejnego użytkownika" + } + }, + "signedin": { + "title": "Witaj {user}!", + "description": "Jesteś zalogowany.", + "continue": "Kontynuuj" + }, + "verify": { + "userIdMissing": "Nie podano identyfikatora użytkownika!", + "success": "Użytkownik został pomyślnie zweryfikowany.", + "setupAuthenticator": "Skonfiguruj uwierzytelnianie", + "verify": { + "title": "Zweryfikuj użytkownika", + "description": "Wprowadź kod z wiadomości weryfikacyjnej.", + "noCodeReceived": "Nie otrzymałeś kodu?", + "resendCode": "Wyślij kod ponownie", + "submit": "Kontynuuj" + } + }, + "authenticator": { + "title": "Wybierz metodę uwierzytelniania", + "description": "Wybierz metodę, której chcesz użyć do uwierzytelnienia.", + "noMethodsAvailable": "Brak dostępnych metod uwierzytelniania", + "allSetup": "Już skonfigurowałeś metodę uwierzytelniania!", + "linkWithIDP": "lub połącz z dostawcą tożsamości" + }, + "error": { + "unknownContext": "Nie udało się pobrać kontekstu użytkownika. Upewnij się, że najpierw wprowadziłeś nazwę użytkownika lub podałeś login jako parametr wyszukiwania.", + "sessionExpired": "Twoja sesja wygasła. Zaloguj się ponownie.", + "failedLoading": "Nie udało się załadować danych. Spróbuj ponownie.", + "tryagain": "Spróbuj ponownie" + } +} diff --git a/apps/login/src/lib/i18n.ts b/apps/login/src/lib/i18n.ts index bb088a35b5..b97770e953 100644 --- a/apps/login/src/lib/i18n.ts +++ b/apps/login/src/lib/i18n.ts @@ -20,6 +20,10 @@ export const LANGS: Lang[] = [ name: "Español", code: "es", }, + { + name: "Polski", + code: "pl", + }, { name: "简体中文", code: "zh", From 72eb6be276976dbb6a9a521bf20151098a6ed467 Mon Sep 17 00:00:00 2001 From: Adam Kida <122802098+jmblab-adam@users.noreply.github.com> Date: Thu, 20 Mar 2025 11:44:53 +0100 Subject: [PATCH 08/45] use current locale for Moment.js --- apps/login/src/components/session-item.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index 1274469f01..0521ec6547 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -9,6 +9,7 @@ import moment from "moment"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { Avatar } from "./avatar"; +import { useLocale } from "next-intl"; export function isSessionValid(session: Partial): { valid: boolean; @@ -37,6 +38,9 @@ export function SessionItem({ reload: () => void; requestId?: string; }) { + const currentLocale = useLocale(); + moment.locale(currentLocale === "zh" ? "zh-cn" : currentLocale); + const [loading, setLoading] = useState(false); async function clearSession(id: string) { From f46566abd008122b1821e0419127579e3ade09c8 Mon Sep 17 00:00:00 2001 From: Adam Kida <122802098+jmblab-adam@users.noreply.github.com> Date: Thu, 20 Mar 2025 13:34:22 +0100 Subject: [PATCH 09/45] fix file formating --- apps/login/src/components/session-item.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/login/src/components/session-item.tsx b/apps/login/src/components/session-item.tsx index 0521ec6547..c99f7fceca 100644 --- a/apps/login/src/components/session-item.tsx +++ b/apps/login/src/components/session-item.tsx @@ -6,10 +6,10 @@ import { XCircleIcon } from "@heroicons/react/24/outline"; import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import moment from "moment"; +import { useLocale } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { Avatar } from "./avatar"; -import { useLocale } from "next-intl"; export function isSessionValid(session: Partial): { valid: boolean; From 6efa35f19d683838212d39a1f8603ed67b176f84 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 20 Mar 2025 14:45:22 +0100 Subject: [PATCH 10/45] fix: return html --- apps/login/src/app/login/route.ts | 51 ++++++++++++++++--------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 61daffd185..6f97f4d638 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -479,34 +479,37 @@ export async function GET(request: NextRequest) { SAMLResponse: binding.value.samlResponse, }; - // Convert form data to URL-encoded string - const formBody = Object.entries(formData) - .map( - ([key, value]) => - encodeURIComponent(key) + "=" + encodeURIComponent(value), - ) - .join("&"); + const formHtml = ` + + + + + + Redirecting... + + +
+ ${Object.entries(formData) + .map( + ([key, value]) => + ``, + ) + .join("\n")} +
+ + + + `; - // Make a POST request to the external URL with the form data - const response = await fetch(url, { - method: "POST", + // Return the HTML response + return new NextResponse(formHtml, { headers: { - "Content-Type": "application/x-www-form-urlencoded", + "Content-Type": "text/html", }, - body: formBody, }); - - // Handle the response from the external URL - if (response.ok) { - return NextResponse.json({ - message: "SAML request completed successfully", - }); - } else { - return NextResponse.json( - { error: "Failed to complete SAML request" }, - { status: response.status }, - ); - } } else { console.log( "could not create response, redirect user to choose other account", From 7d5f8eac87c6a55f3a73ea1e3766d5e7cebca9ed Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 20 Mar 2025 14:54:02 +0100 Subject: [PATCH 11/45] fix: seperate route --- apps/login/src/app/(login)/saml-post/page.tsx | 45 +++++++++++++++++++ apps/login/src/app/login/route.ts | 36 +++------------ 2 files changed, 50 insertions(+), 31 deletions(-) create mode 100644 apps/login/src/app/(login)/saml-post/page.tsx diff --git a/apps/login/src/app/(login)/saml-post/page.tsx b/apps/login/src/app/(login)/saml-post/page.tsx new file mode 100644 index 0000000000..bc62efa3b4 --- /dev/null +++ b/apps/login/src/app/(login)/saml-post/page.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { useSearchParams } from "next/navigation"; +import { useEffect } from "react"; + +export default function SamlPost() { + const searchParams = useSearchParams(); + + const url = searchParams.get("url"); + const relayState = searchParams.get("RelayState"); + const samlResponse = searchParams.get("SAMLResponse"); + + console.log(relayState, samlResponse); + + useEffect(() => { + // Automatically submit the form after rendering + const form = document.getElementById("samlForm") as HTMLFormElement; + if (form) { + form.submit(); + } + }, []); + + if (!url || !relayState || !samlResponse) { + return ( +

Missing required parameters for SAML POST.

+ ); + } + + return ( + + + + + Redirecting... + + +
+ + +
+

Redirecting...

+ + + ); +} diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 6f97f4d638..a3f1108778 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -473,43 +473,17 @@ export async function GET(request: NextRequest) { if (url && binding.case === "redirect") { return NextResponse.redirect(url); } else if (url && binding.case === "post") { - // Create form data after SAML standard const formData = { RelayState: binding.value.relayState, SAMLResponse: binding.value.samlResponse, }; - const formHtml = ` - - - - - - Redirecting... - - -
- ${Object.entries(formData) - .map( - ([key, value]) => - ``, - ) - .join("\n")} -
- - - - `; + const redirectUrl = new URL(request.nextUrl.origin + "/saml-post"); + redirectUrl.searchParams.set("url", url); + redirectUrl.searchParams.set("RelayState", formData.RelayState); + redirectUrl.searchParams.set("SAMLResponse", formData.SAMLResponse); - // Return the HTML response - return new NextResponse(formHtml, { - headers: { - "Content-Type": "text/html", - }, - }); + return NextResponse.redirect(redirectUrl.toString()); } else { console.log( "could not create response, redirect user to choose other account", From 0bc9b005e3237f1466826e0389eafcbd4aed08ed Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 20 Mar 2025 14:54:16 +0100 Subject: [PATCH 12/45] rm log --- apps/login/src/app/(login)/saml-post/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/login/src/app/(login)/saml-post/page.tsx b/apps/login/src/app/(login)/saml-post/page.tsx index bc62efa3b4..d5765c8d56 100644 --- a/apps/login/src/app/(login)/saml-post/page.tsx +++ b/apps/login/src/app/(login)/saml-post/page.tsx @@ -10,8 +10,6 @@ export default function SamlPost() { const relayState = searchParams.get("RelayState"); const samlResponse = searchParams.get("SAMLResponse"); - console.log(relayState, samlResponse); - useEffect(() => { // Automatically submit the form after rendering const form = document.getElementById("samlForm") as HTMLFormElement; From f1ab6a236020e432e5179c92395cf5ec2fe8d4b3 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 20 Mar 2025 14:57:20 +0100 Subject: [PATCH 13/45] fix host --- apps/login/src/app/login/route.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index a3f1108778..184e1919bf 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -478,7 +478,8 @@ export async function GET(request: NextRequest) { SAMLResponse: binding.value.samlResponse, }; - const redirectUrl = new URL(request.nextUrl.origin + "/saml-post"); + const redirectUrl = constructUrl(request, "/saml-post"); + redirectUrl.searchParams.set("url", url); redirectUrl.searchParams.set("RelayState", formData.RelayState); redirectUrl.searchParams.set("SAMLResponse", formData.SAMLResponse); From 5af009d58134bb1217d821397e7674f2f22786a2 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 21 Mar 2025 17:33:42 +0100 Subject: [PATCH 14/45] chore: add acceptance tests with saml sp --- .github/workflows/test.yml | 4 + acceptance/Dockerfile | 2 +- acceptance/docker-compose.yaml | 6 +- acceptance/saml/Dockerfile | 5 + acceptance/saml/docker-compose.yaml | 35 ++++++ acceptance/saml/go.mod | 16 +++ acceptance/saml/go.sum | 63 ++++++++++ acceptance/saml/main.go | 118 ++++++++++++++++++ acceptance/saml/setup.sh | 36 ++++++ acceptance/setup.sh | 37 +++++- acceptance/sink/go.mod | 2 +- .../tests/saml-username-password.spec.ts | 39 ++++++ acceptance/tests/saml.ts | 5 + acceptance/tests/select-account.ts | 5 + acceptance/tests/zitadel.ts | 6 +- apps/login/src/app/login/route.ts | 9 +- apps/login/src/middleware.ts | 2 +- package.json | 1 + 18 files changed, 375 insertions(+), 16 deletions(-) create mode 100644 acceptance/saml/Dockerfile create mode 100644 acceptance/saml/docker-compose.yaml create mode 100644 acceptance/saml/go.mod create mode 100644 acceptance/saml/go.sum create mode 100644 acceptance/saml/main.go create mode 100755 acceptance/saml/setup.sh create mode 100644 acceptance/tests/saml-username-password.spec.ts create mode 100644 acceptance/tests/saml.ts create mode 100644 acceptance/tests/select-account.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 79a455b016..05c8052139 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -97,6 +97,10 @@ jobs: run: ZITADEL_DEV_UID=root pnpm run-sink if: ${{ matrix.command == 'test:acceptance' }} + - name: Run SAML SP + run: ZITADEL_DEV_UID=root pnpm run-samlsp + if: ${{ matrix.command == 'test:acceptance' }} + - name: Create Cloud Env File run: | if [ "${{ matrix.command }}" == "test:acceptance:prod" ]; then diff --git a/acceptance/Dockerfile b/acceptance/Dockerfile index 36f6ba8f19..dd29721bc3 100644 --- a/acceptance/Dockerfile +++ b/acceptance/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.19-alpine +FROM golang:1.24-alpine RUN apk add curl jq COPY setup.sh /setup.sh RUN chmod +x /setup.sh diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index 240d91553a..61bcec04db 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,7 +1,7 @@ services: zitadel: user: "${ZITADEL_DEV_UID}" - image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:v2.67.2}" + image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:dc64e35128108d70471c7a5b9ad1dfc2c7c4c654}" command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' ports: - "8080:8080" @@ -11,6 +11,8 @@ services: depends_on: db: condition: "service_healthy" + extra_hosts: + - "host.docker.internal:host-gateway" db: restart: "always" @@ -57,7 +59,7 @@ services: condition: "service_completed_successfully" sink: - image: golang:1.19-alpine + image: golang:1.24-alpine container_name: sink command: go run /sink/main.go -port '3333' -email '/email' -sms '/sms' -notification '/notification' ports: diff --git a/acceptance/saml/Dockerfile b/acceptance/saml/Dockerfile new file mode 100644 index 0000000000..dd29721bc3 --- /dev/null +++ b/acceptance/saml/Dockerfile @@ -0,0 +1,5 @@ +FROM golang:1.24-alpine +RUN apk add curl jq +COPY setup.sh /setup.sh +RUN chmod +x /setup.sh +ENTRYPOINT [ "/setup.sh" ] diff --git a/acceptance/saml/docker-compose.yaml b/acceptance/saml/docker-compose.yaml new file mode 100644 index 0000000000..19b3473e28 --- /dev/null +++ b/acceptance/saml/docker-compose.yaml @@ -0,0 +1,35 @@ +services: + samlsp: + image: golang:1.24-alpine + container_name: samlsp + command: go run main.go -host 'http://localhost' -port '8001' -idp 'http://host.docker.internal:3000/saml/v2/metadata' + working_dir: /saml + ports: + - 8001:8001 + volumes: + - "./:/saml" + extra_hosts: + - "host.docker.internal:host-gateway" + + wait_for_samlsp: + image: curlimages/curl:8.00.1 + command: /bin/sh -c "until curl -s -o /dev/null -i -f http://samlsp:8001/saml/metadata; do echo 'waiting' && sleep 1; done; echo 'ready' && sleep 5;" || false + depends_on: + - samlsp + + setup-samlsp: + user: "${ZITADEL_DEV_UID}" + container_name: setup-samlsp + build: . + environment: + PAT_FILE: /pat/zitadel-admin-sa.pat + ZITADEL_API_URL: http://host.docker.internal:8080 + LOGIN_URI: http://localhost:3000 + SAML_SP_METADATA: http://host.docker.internal:8001/saml/metadata + volumes: + - "../pat:/pat" + extra_hosts: + - "host.docker.internal:host-gateway" + depends_on: + wait_for_samlsp: + condition: "service_completed_successfully" \ No newline at end of file diff --git a/acceptance/saml/go.mod b/acceptance/saml/go.mod new file mode 100644 index 0000000000..d3378ea0b3 --- /dev/null +++ b/acceptance/saml/go.mod @@ -0,0 +1,16 @@ +module github.com/zitadel/typescript/acceptance/saml + +go 1.24.0 + +require github.com/crewjam/saml v0.4.14 + +require ( + github.com/beevik/etree v1.5.0 // indirect + github.com/crewjam/httperr v0.2.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect + github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/russellhaering/goxmldsig v1.5.0 // indirect + golang.org/x/crypto v0.36.0 // indirect +) diff --git a/acceptance/saml/go.sum b/acceptance/saml/go.sum new file mode 100644 index 0000000000..482277f9de --- /dev/null +++ b/acceptance/saml/go.sum @@ -0,0 +1,63 @@ +github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/beevik/etree v1.5.0 h1:iaQZFSDS+3kYZiGoc9uKeOkUY3nYMXOKLl6KIJxiJWs= +github.com/beevik/etree v1.5.0/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= +github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= +github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= +github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3cigIwLonTPM= +github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw= +github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/acceptance/saml/main.go b/acceptance/saml/main.go new file mode 100644 index 0000000000..273e93c115 --- /dev/null +++ b/acceptance/saml/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "context" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "flag" + "fmt" + "net/http" + "net/url" + + "github.com/crewjam/saml/samlsp" +) + +var keyPair = func() tls.Certificate { + cert := []byte(`-----BEGIN CERTIFICATE----- +MIIDITCCAgmgAwIBAgIUKjAUmxsHO44X+/TKBNciPgNl1GEwDQYJKoZIhvcNAQEL +BQAwIDEeMBwGA1UEAwwVbXlzZXJ2aWNlLmV4YW1wbGUuY29tMB4XDTI0MTIxOTEz +Mzc1MVoXDTI1MTIxOTEzMzc1MVowIDEeMBwGA1UEAwwVbXlzZXJ2aWNlLmV4YW1w +bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0QYuJsayILRI +hVT7G1DlitVSXnt1iw3gEXJZfe81Egz06fUbvXF6Yo1LJmwYpqe/rm+hf4FNUb8e +2O+LH2FieA9FkVe4P2gKOzw87A/KxvpV8stgNgl4LlqRCokbc1AzeE/NiLr5TcTD +RXm3DUcYxXxinprtDu2jftFysaOZmNAukvE/iL6qS3X6ggVEDDM7tY9n5FV2eJ4E +p0ImKfypi2aZYROxOK+v5x9ryFRMl4y07lMDvmtcV45uXYmfGNCgG9PNf91Kk/mh +JxEQbxycJwFoSi9XWljR8ahPdO11LXG7Dsj/RVbY8k2LdKNstl6Ae3aCpbe9u2Pj +vxYs1bVJuQIDAQABo1MwUTAdBgNVHQ4EFgQU+mRVN5HYJWgnpopReaLhf2cMcoYw +HwYDVR0jBBgwFoAU+mRVN5HYJWgnpopReaLhf2cMcoYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAQEABJpHVuc9tGhD04infRVlofvqXIUizTlOrjZX +vozW9pIhSWEHX8o+sJP8AMZLnrsdq+bm0HE0HvgYrw7Lb8pd4FpR46TkFHjeukoj +izqfgckjIBl2nwPGlynbKA0/U/rTCSxVt7XiAn+lgYUGIpOzNdk06/hRMitrMNB7 +t2C97NseVC4b1ZgyFrozsefCfUmD8IJF0+XJ4Wzmsh0jRrI8koCtVmPYnKn6vw1b +cZprg/97CWHYrsavd406wOB60CMtYl83Q16ucOF1dretDFqJC5kY+aFLvuqfag2+ +kIaoPV1MnGsxveQyyHdOsEatS5XOv/1OWcmnvePDPxcvb9jCcw== +-----END CERTIFICATE----- +`) + key := []byte(`-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRBi4mxrIgtEiF +VPsbUOWK1VJee3WLDeARcll97zUSDPTp9Ru9cXpijUsmbBimp7+ub6F/gU1Rvx7Y +74sfYWJ4D0WRV7g/aAo7PDzsD8rG+lXyy2A2CXguWpEKiRtzUDN4T82IuvlNxMNF +ebcNRxjFfGKemu0O7aN+0XKxo5mY0C6S8T+IvqpLdfqCBUQMMzu1j2fkVXZ4ngSn +QiYp/KmLZplhE7E4r6/nH2vIVEyXjLTuUwO+a1xXjm5diZ8Y0KAb081/3UqT+aEn +ERBvHJwnAWhKL1daWNHxqE907XUtcbsOyP9FVtjyTYt0o2y2XoB7doKlt727Y+O/ +FizVtUm5AgMBAAECggEACak+l5f6Onj+u5vrjc4JyAaXW6ra6loSM9g8Uu3sHukW +plwoA7Pzp0u20CAxrP1Gpqw984/hSCCcb0Q2ItWMWLaC/YZni5W2WFnOyo3pzlPa +hmH4UNMT+ReCSfF/oW8w69QLcNEMjhfEu0i2iWBygIlA4SoRwC2Db6yEX7nLMwUB +6AICid9hfeACNRz/nq5ytdcHdmcB7Ptgb9jLiXr6RZw26g5AsRPHU3LdcyZAOXjP +aUHriHuHQFKAVkoEUxslvCB6ePCTCpB0bSAuzQbeGoY8fmvmNSCvJ1vrH5hiSUYp +Axtl5iNgFl5o9obb0eBYlY9x3pMSz0twdbCwfR7HAQKBgQDtWhmFm0NaJALoY+tq +lIIC0EOMSrcRIlgeXr6+g8womuDOMi5m/Nr5Mqt4mPOdP4HytrQb+a/ZmEm17KHh +mQb1vwH8ffirCBHbPNC1vwSNoxDKv9E6OysWlKiOzxPFSVZr3dKl2EMX6qi17n0l +LBrGXXaNPgYiHSmwBA5CZvvouQKBgQDhclGJfZfuoubQkUuz8yOA2uxalh/iUmQ/ +G8ac6/w7dmnL9pXehqCWh06SeC3ZvW7yrf7IIGx4sTJji2FzQ+8Ta6pPELMyBEXr +1VirIFrlNVMlMQEbZcbzdzEhchM1RUpZJtl3b4amvH21UcRB69d9klcDRisKoFRm +k0P9QLHpAQKBgQDh5J9nphZa4u0ViYtTW1XFIbs3+R/0IbCl7tww67TRbF3KQL4i +7EHna88ALumkXf3qJvKRsXgoaqS0jSqgUAjst8ZHLQkOldaQxneIkezedDSWEisp +9YgTrJYjnHefiyXB8VL63jE0wPOiewEF8Mzmv6sFz+L8cq7rQ2Di16qmmQKBgQDH +bvCwVxkrMpJK2O2GH8U9fOzu6bUE6eviY/jb4mp8U7EdjGJhuuieoM2iBoxQ/SID +rmYftYcfcWlo4+juJZ99p5W+YcCTs3IDQPUyVOnzr6uA0Avxp6RKxhsBQj+5tTUj +Dpn77P3JzB7MYqvhwPcdD3LH46+5s8FWCFpx02RPAQKBgARbngtggfifatcsMC7n +lSv/FVLH7LYQAHdoW/EH5Be7FeeP+eQvGXwh1dgl+u0VZO8FvI8RwFganpBRR2Nc +ZSBRIb0fSUlTvIsckSWjpEvUJUomJXyi4PIZAfNvd9/u1uLInQiCDtObwb6hnLTU +FHHEZ+dR4eMaJp6PhNm8hu2O +-----END PRIVATE KEY----- +`) + + kp, err := tls.X509KeyPair(cert, key) + if err != nil { + panic(err) + } + kp.Leaf, err = x509.ParseCertificate(kp.Certificate[0]) + if err != nil { + panic(err) + } + return kp +}() + +func hello(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %s!", samlsp.AttributeFromContext(r.Context(), "displayName")) +} + +func main() { + idpURL := flag.String("idp", "http://localhost:3000/saml/v2/metadata", "url to idp metadata, proxied through typescript") + host := flag.String("host", "http://localhost", "url for sp") + port := flag.String("port", "8001", "port for sp") + + flag.Parse() + + idpMetadataURL, err := url.Parse(*idpURL) + if err != nil { + panic(err) + } + idpMetadata, err := samlsp.FetchMetadata(context.Background(), http.DefaultClient, + *idpMetadataURL) + if err != nil { + panic(err) + } + fmt.Printf("idpMetadata: %+v\n", idpMetadata) + rootURL, err := url.Parse(*host + ":" + *port) + if err != nil { + panic(err) + } + + samlSP, err := samlsp.New(samlsp.Options{ + URL: *rootURL, + Key: keyPair.PrivateKey.(*rsa.PrivateKey), + Certificate: keyPair.Leaf, + IDPMetadata: idpMetadata, + }) + if err != nil { + panic(err) + } + + app := http.HandlerFunc(hello) + http.Handle("/hello", samlSP.RequireAccount(app)) + http.Handle("/saml/", samlSP) + http.ListenAndServe(":"+*port, nil) +} diff --git a/acceptance/saml/setup.sh b/acceptance/saml/setup.sh new file mode 100755 index 0000000000..9d8f68ccac --- /dev/null +++ b/acceptance/saml/setup.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +set -ex + +PAT_FILE=${PAT_FILE:-../pat/zitadel-admin-sa.pat} +ZITADEL_API_URL="${ZITADEL_API_URL:-"http://localhost:8080"}" +LOGIN_URI="${LOGIN_URI:-"http://localhost:3000"}" +SAML_SP_METADATA="${SAML_SP_METADATA:-"http://samlsp:8081/saml/metadata"}" + +if [ -z "${PAT}" ]; then + echo "Reading PAT from file ${PAT_FILE}" + PAT=$(cat ${PAT_FILE}) +fi + +################################################################# +# SAML Application +################################################################# + +SAML_PROJECT_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_URL}/management/v1/projects" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}" \ + --header "Content-Type: application/json" \ + -d "{ \"name\": \"SAML\", \"projectRoleAssertion\": true, \"projectRoleCheck\": true, \"hasProjectCheck\": true, \"privateLabelingSetting\": \"PRIVATE_LABELING_SETTING_UNSPECIFIED\"}") +echo "Received SAML Project response: ${SAML_PROJECT_RESPONSE}" + +SAML_PROJECT_ID=$(echo ${SAML_PROJECT_RESPONSE} | jq -r '. | .id') +echo "Received Project ID: ${SAML_PROJECT_ID}" + +SAML_APP_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_URL}/management/v1/projects/${SAML_PROJECT_ID}/apps/saml" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}" \ + --header "Content-Type: application/json" \ + -d "{ \"name\": \"SAML\", \"metadataUrl\": \"${SAML_SP_METADATA}\", \"loginVersion\": { \"loginV2\": { \"baseUri\": \"${LOGIN_URI}\" }}}") +echo "Received SAML App response: ${SAML_APP_RESPONSE}" diff --git a/acceptance/setup.sh b/acceptance/setup.sh index 8438685dde..cdb04043e0 100755 --- a/acceptance/setup.sh +++ b/acceptance/setup.sh @@ -17,6 +17,40 @@ if [ -z "${PAT}" ]; then PAT=$(cat ${PAT_FILE}) fi +################################################################# +# ServiceAccount as Login Client +################################################################# + +SERVICEACCOUNT_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_INTERNAL_URL}/management/v1/users/machine" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}" \ + --header "Content-Type: application/json" \ + -d "{\"userName\": \"login\", \"name\": \"Login v2\", \"description\": \"Serviceaccount for Login v2\", \"accessTokenType\": \"ACCESS_TOKEN_TYPE_BEARER\"}") +echo "Received ServiceAccount response: ${SERVICEACCOUNT_RESPONSE}" + +SERVICEACCOUNT_ID=$(echo ${SERVICEACCOUNT_RESPONSE} | jq -r '. | .userId') +echo "Received ServiceAccount ID: ${SERVICEACCOUNT_ID}" + +MEMBER_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_INTERNAL_URL}/admin/v1/members" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}" \ + --header "Content-Type: application/json" \ + -d "{\"userId\": \"${SERVICEACCOUNT_ID}\", \"roles\": [\"IAM_LOGIN_CLIENT\"]}") +echo "Received Member response: ${MEMBER_RESPONSE}" + +SA_PAT_RESPONSE=$(curl -s --request POST \ + --url "${ZITADEL_API_INTERNAL_URL}/management/v1/users/${SERVICEACCOUNT_ID}/pats" \ + --header "Authorization: Bearer ${PAT}" \ + --header "Host: ${ZITADEL_API_DOMAIN}" \ + --header "Content-Type: application/json" \ + -d "{\"expirationDate\": \"2519-04-01T08:45:00.000000Z\"}") +echo "Received Member response: ${MEMBER_RESPONSE}" + +SA_PAT=$(echo ${SA_PAT_RESPONSE} | jq -r '. | .token') +echo "Received ServiceAccount Token: ${SA_PAT}" + ################################################################# # Environment files ################################################################# @@ -27,7 +61,8 @@ WRITE_TEST_ENVIRONMENT_FILE=${WRITE_TEST_ENVIRONMENT_FILE:-$(dirname "$0")/../ac echo "Writing environment file to ${WRITE_TEST_ENVIRONMENT_FILE} when done." echo "ZITADEL_API_URL=${ZITADEL_API_URL} -ZITADEL_SERVICE_USER_TOKEN=${PAT} +ZITADEL_SERVICE_USER_TOKEN=${SA_PAT} +ZITADEL_ADMIN_TOKEN=${PAT} SINK_NOTIFICATION_URL=${SINK_NOTIFICATION_URL} EMAIL_VERIFICATION=true DEBUG=true"| tee "${WRITE_ENVIRONMENT_FILE}" "${WRITE_TEST_ENVIRONMENT_FILE}" > /dev/null diff --git a/acceptance/sink/go.mod b/acceptance/sink/go.mod index a33d6ae8bd..1da7622b58 100644 --- a/acceptance/sink/go.mod +++ b/acceptance/sink/go.mod @@ -1,3 +1,3 @@ module github.com/zitadel/typescript/acceptance/sink -go 1.22.6 +go 1.24.0 diff --git a/acceptance/tests/saml-username-password.spec.ts b/acceptance/tests/saml-username-password.spec.ts new file mode 100644 index 0000000000..a7f1864317 --- /dev/null +++ b/acceptance/tests/saml-username-password.spec.ts @@ -0,0 +1,39 @@ +import { faker } from "@faker-js/faker"; +import { test as base } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { loginname } from "./loginname"; +import { password } from "./password"; +import { PasswordUser } from "./user"; +import {startSAML} from "./saml"; +import {selectNewAccount} from "./select-account"; + +// Read from ".env" file. +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); + +const test = base.extend<{ user: PasswordUser }>({ + user: async ({ page }, use) => { + const user = new PasswordUser({ + email: faker.internet.email(), + isEmailVerified: true, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + organization: "", + phone: faker.phone.number(), + isPhoneVerified: false, + password: "Password1!", + passwordChangeRequired: false, + }); + await user.ensure(page); + await use(user); + await user.cleanup(); + }, +}); + +test("saml username and password login", async ({ user, page }) => { + await startSAML(page) + await selectNewAccount(page) + await loginname(page, user.getUsername()); + await password(page, user.getPassword()); + // currently fails because of issuer problems +}); diff --git a/acceptance/tests/saml.ts b/acceptance/tests/saml.ts new file mode 100644 index 0000000000..ee9b8ced32 --- /dev/null +++ b/acceptance/tests/saml.ts @@ -0,0 +1,5 @@ +import { expect, Page } from "@playwright/test"; + +export async function startSAML(page: Page) { + await page.goto("http://localhost:8001/hello"); +} \ No newline at end of file diff --git a/acceptance/tests/select-account.ts b/acceptance/tests/select-account.ts new file mode 100644 index 0000000000..ce036ab39b --- /dev/null +++ b/acceptance/tests/select-account.ts @@ -0,0 +1,5 @@ +import {Page} from "@playwright/test"; + +export async function selectNewAccount(page: Page) { + await page.getByRole('link', {name: 'Add another account'}).click(); +} diff --git a/acceptance/tests/zitadel.ts b/acceptance/tests/zitadel.ts index 1923b63dce..ae29bf84e5 100644 --- a/acceptance/tests/zitadel.ts +++ b/acceptance/tests/zitadel.ts @@ -53,7 +53,7 @@ async function deleteCall(url: string) { try { const response = await axios.delete(url, { headers: { - Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`, }, }); @@ -87,7 +87,7 @@ async function listCall(url: string, data: any): Promise { const response = await axios.post(url, data, { headers: { "Content-Type": "application/json", - Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`, }, }); @@ -123,7 +123,7 @@ async function pushCall(url: string, data: any) { const response = await axios.post(url, data, { headers: { "Content-Type": "application/json", - Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`, + Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`, }, }); diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 184e1919bf..65b1b2eae7 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -473,16 +473,11 @@ export async function GET(request: NextRequest) { if (url && binding.case === "redirect") { return NextResponse.redirect(url); } else if (url && binding.case === "post") { - const formData = { - RelayState: binding.value.relayState, - SAMLResponse: binding.value.samlResponse, - }; - const redirectUrl = constructUrl(request, "/saml-post"); redirectUrl.searchParams.set("url", url); - redirectUrl.searchParams.set("RelayState", formData.RelayState); - redirectUrl.searchParams.set("SAMLResponse", formData.SAMLResponse); + redirectUrl.searchParams.set("RelayState", binding.value.relayState); + redirectUrl.searchParams.set("SAMLResponse", binding.value.samlResponse); return NextResponse.redirect(redirectUrl.toString()); } else { diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index e5cbf7ad3f..a1fd47504a 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -22,7 +22,7 @@ export async function middleware(request: NextRequest) { const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const instanceHost = `${serviceUrl}`.replace("https://", ""); + const instanceHost = `${serviceUrl}`.replace("https://", "").replace("http://", ""); const requestHeaders = new Headers(request.headers); diff --git a/package.json b/package.json index a824e47571..2a4868e720 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "release": "turbo run build --filter=login^... && changeset publish", "run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup", "run-sink": "docker compose -f ./acceptance/docker-compose.yaml up -d sink", + "run-samlsp": "docker compose -f ./acceptance/saml/docker-compose.yaml up -d", "stop": "docker compose -f ./acceptance/docker-compose.yaml stop" }, "pnpm": { From 5b714f0136e0e350b1e731e6e43623f652d18791 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Mon, 24 Mar 2025 19:45:29 +0100 Subject: [PATCH 15/45] chore: add acceptance tests with oidc rp --- .github/workflows/test.yml | 4 + acceptance/oidc/docker-compose.yaml | 22 ++ acceptance/oidc/go.mod | 27 ++ acceptance/oidc/go.sum | 69 ++++ acceptance/oidc/main.go | 322 ++++++++++++++++++ acceptance/saml/Dockerfile | 5 - acceptance/saml/docker-compose.yaml | 35 +- acceptance/saml/go.mod | 2 + acceptance/saml/go.sum | 33 +- acceptance/saml/main.go | 167 ++++++++- acceptance/saml/setup.sh | 36 -- .../tests/oidc-username-password.spec.ts | 37 ++ acceptance/tests/oidc.ts | 5 + package.json | 1 + 14 files changed, 663 insertions(+), 102 deletions(-) create mode 100644 acceptance/oidc/docker-compose.yaml create mode 100644 acceptance/oidc/go.mod create mode 100644 acceptance/oidc/go.sum create mode 100644 acceptance/oidc/main.go delete mode 100644 acceptance/saml/Dockerfile delete mode 100755 acceptance/saml/setup.sh create mode 100644 acceptance/tests/oidc-username-password.spec.ts create mode 100644 acceptance/tests/oidc.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 05c8052139..ca1025f6bd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -101,6 +101,10 @@ jobs: run: ZITADEL_DEV_UID=root pnpm run-samlsp if: ${{ matrix.command == 'test:acceptance' }} + - name: Run OIDC RP + run: ZITADEL_DEV_UID=root pnpm run-oidcrp + if: ${{ matrix.command == 'test:acceptance' }} + - name: Create Cloud Env File run: | if [ "${{ matrix.command }}" == "test:acceptance:prod" ]; then diff --git a/acceptance/oidc/docker-compose.yaml b/acceptance/oidc/docker-compose.yaml new file mode 100644 index 0000000000..88f023503c --- /dev/null +++ b/acceptance/oidc/docker-compose.yaml @@ -0,0 +1,22 @@ +services: + oidcrp: + image: golang:1.24-alpine + container_name: oidcrp + command: go run main.go + environment: + API_URL: 'http://localhost:8080' + API_DOMAIN: 'localhost:8080' + PAT_FILE: '/pat/zitadel-admin-sa.pat' + LOGIN_URL: 'http://localhost:3000' + ISSUER: 'http://localhost:3000' + HOST: 'http://localhost' + PORT: '8000' + SCOPES: 'openid profile email' + working_dir: /oidc + ports: + - 8000:8000 + volumes: + - "../pat:/pat" + - "./:/oidc" + extra_hosts: + - "localhost:host-gateway" diff --git a/acceptance/oidc/go.mod b/acceptance/oidc/go.mod new file mode 100644 index 0000000000..7bb49c983f --- /dev/null +++ b/acceptance/oidc/go.mod @@ -0,0 +1,27 @@ +module github.com/zitadel/typescript/acceptance/oidc + +go 1.24.1 + +require ( + github.com/google/uuid v1.6.0 + github.com/joho/godotenv v1.5.1 + github.com/sirupsen/logrus v1.9.3 + github.com/zitadel/logging v0.6.1 + github.com/zitadel/oidc/v3 v3.36.1 +) + +require ( + github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/muhlemmer/gu v0.3.1 // indirect + github.com/zitadel/schema v1.3.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect +) diff --git a/acceptance/oidc/go.sum b/acceptance/oidc/go.sum new file mode 100644 index 0000000000..7bcd270709 --- /dev/null +++ b/acceptance/oidc/go.sum @@ -0,0 +1,69 @@ +github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= +github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= +github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA= +github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= +github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= +github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= +github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/zitadel/logging v0.6.1 h1:Vyzk1rl9Kq9RCevcpX6ujUaTYFX43aa4LkvV1TvUk+Y= +github.com/zitadel/logging v0.6.1/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow= +github.com/zitadel/oidc/v3 v3.36.1 h1:1AT1NqKKEqAwx4GmKJZ9fYkWH2WIn/VKMfQ46nBtRf0= +github.com/zitadel/oidc/v3 v3.36.1/go.mod h1:dApGZLvWZTHRuxmcbQlW5d2XVjVYR3vGOdq536igmTs= +github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0= +github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/acceptance/oidc/main.go b/acceptance/oidc/main.go new file mode 100644 index 0000000000..b76caaeee6 --- /dev/null +++ b/acceptance/oidc/main.go @@ -0,0 +1,322 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "log/slog" + "net/http" + "os" + "os/signal" + "strings" + "sync/atomic" + "syscall" + "time" + + "github.com/google/uuid" + "github.com/sirupsen/logrus" + + "github.com/joho/godotenv" + "github.com/zitadel/logging" + "github.com/zitadel/oidc/v3/pkg/client/rp" + httphelper "github.com/zitadel/oidc/v3/pkg/http" + "github.com/zitadel/oidc/v3/pkg/oidc" +) + +var ( + callbackPath = "/auth/callback" + key = []byte("test1234test1234") +) + +func main() { + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + + apiURL := os.Getenv("API_URL") + patFile := os.Getenv("PAT_FILE") + domain := os.Getenv("API_DOMAIN") + loginURL := os.Getenv("LOGIN_URL") + issuer := os.Getenv("ISSUER") + host := os.Getenv("HOST") + port := os.Getenv("PORT") + scopeList := strings.Split(os.Getenv("SCOPES"), " ") + + f, err := os.Open(patFile) + if err != nil { + panic(err) + } + pat, err := io.ReadAll(f) + if err != nil { + panic(err) + } + patStr := strings.Trim(string(pat), "\n") + + redirectURI := fmt.Sprintf("%v:%v%v", host, port, callbackPath) + cookieHandler := httphelper.NewCookieHandler(key, key, httphelper.WithUnsecure()) + + clientID, clientSecret, err := createZitadelResources(apiURL, patStr, domain, redirectURI, loginURL) + if err != nil { + panic(err) + } + + logger := slog.New( + slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + AddSource: true, + Level: slog.LevelDebug, + }), + ) + client := &http.Client{ + Timeout: time.Minute, + } + // enable outgoing request logging + logging.EnableHTTPClient(client, + logging.WithClientGroup("client"), + ) + + options := []rp.Option{ + rp.WithCookieHandler(cookieHandler), + rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)), + rp.WithHTTPClient(client), + rp.WithLogger(logger), + rp.WithSigningAlgsFromDiscovery(), + } + if clientSecret == "" { + options = append(options, rp.WithPKCE(cookieHandler)) + } + + // One can add a logger to the context, + // pre-defining log attributes as required. + ctx := logging.ToContext(context.TODO(), logger) + provider, err := rp.NewRelyingPartyOIDC(ctx, issuer, clientID, clientSecret, redirectURI, scopeList, options...) + if err != nil { + logrus.Fatalf("error creating provider %s", err.Error()) + } + + // generate some state (representing the state of the user in your application, + // e.g. the page where he was before sending him to login + state := func() string { + return uuid.New().String() + } + + urlOptions := []rp.URLParamOpt{ + rp.WithPromptURLParam("Welcome back!"), + } + + // register the AuthURLHandler at your preferred path. + // the AuthURLHandler creates the auth request and redirects the user to the auth server. + // including state handling with secure cookie and the possibility to use PKCE. + // Prompts can optionally be set to inform the server of + // any messages that need to be prompted back to the user. + http.Handle("/login", rp.AuthURLHandler( + state, + provider, + urlOptions..., + )) + + // for demonstration purposes the returned userinfo response is written as JSON object onto response + marshalUserinfo := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty, info *oidc.UserInfo) { + fmt.Println("access token", tokens.AccessToken) + fmt.Println("refresh token", tokens.RefreshToken) + fmt.Println("id token", tokens.IDToken) + + data, err := json.Marshal(info) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("content-type", "application/json") + w.Write(data) + } + + // register the CodeExchangeHandler at the callbackPath + // the CodeExchangeHandler handles the auth response, creates the token request and calls the callback function + // with the returned tokens from the token endpoint + // in this example the callback function itself is wrapped by the UserinfoCallback which + // will call the Userinfo endpoint, check the sub and pass the info into the callback function + http.Handle(callbackPath, rp.CodeExchangeHandler(rp.UserinfoCallback(marshalUserinfo), provider)) + + // if you would use the callback without calling the userinfo endpoint, simply switch the callback handler for: + // + // http.Handle(callbackPath, rp.CodeExchangeHandler(marshalToken, provider)) + + // simple counter for request IDs + var counter atomic.Int64 + // enable incomming request logging + mw := logging.Middleware( + logging.WithLogger(logger), + logging.WithGroup("server"), + logging.WithIDFunc(func() slog.Attr { + return slog.Int64("id", counter.Add(1)) + }), + ) + + server := &http.Server{ + Addr: ":" + port, + Handler: mw(http.DefaultServeMux), + } + go func() { + if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + log.Fatalf("HTTP server error: %v", err) + } + log.Println("Stopped serving new connections.") + }() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + <-sigChan + + shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) + defer shutdownRelease() + + if err := server.Shutdown(shutdownCtx); err != nil { + log.Fatalf("HTTP shutdown error: %v", err) + } +} + +func createZitadelResources(apiURL, pat, domain, redirectURI, loginURL string) (string, string, error) { + projectID, err := CreateProject(apiURL, pat, domain) + if err != nil { + return "", "", err + } + return CreateApp(apiURL, pat, domain, projectID, redirectURI, loginURL) +} + +type project struct { + ID string `json:"id"` +} +type createProject struct { + Name string `json:"name"` + ProjectRoleAssertion bool `json:"projectRoleAssertion"` + ProjectRoleCheck bool `json:"projectRoleCheck"` + HasProjectCheck bool `json:"hasProjectCheck"` + PrivateLabelingSetting string `json:"privateLabelingSetting"` +} + +func CreateProject(apiURL, pat, domain string) (string, error) { + createProject := &createProject{ + Name: "OIDC", + ProjectRoleAssertion: false, + ProjectRoleCheck: false, + HasProjectCheck: false, + PrivateLabelingSetting: "PRIVATE_LABELING_SETTING_UNSPECIFIED", + } + body, err := json.Marshal(createProject) + if err != nil { + return "", err + } + + resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, body) + if err != nil { + return "", err + } + data, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + defer resp.Body.Close() + + p := new(project) + if err := json.Unmarshal(data, p); err != nil { + return "", err + } + fmt.Printf("projectID: %+v\n", p.ID) + return p.ID, nil +} + +type createApp struct { + Name string `json:"name"` + RedirectUris []string `json:"redirectUris"` + ResponseTypes []string `json:"responseTypes"` + GrantTypes []string `json:"grantTypes"` + AppType string `json:"appType"` + AuthMethodType string `json:"authMethodType"` + PostLogoutRedirectUris []string `json:"postLogoutRedirectUris"` + Version string `json:"version"` + DevMode bool `json:"devMode"` + AccessTokenType string `json:"accessTokenType"` + AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion"` + IdTokenRoleAssertion bool `json:"idTokenRoleAssertion"` + IdTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion"` + ClockSkew string `json:"clockSkew"` + AdditionalOrigins []string `json:"additionalOrigins"` + SkipNativeAppSuccessPage bool `json:"skipNativeAppSuccessPage"` + BackChannelLogoutUri []string `json:"backChannelLogoutUri"` + LoginVersion version `json:"loginVersion"` +} + +type version struct { + LoginV2 loginV2 `json:"loginV2"` +} +type loginV2 struct { + BaseUri string `json:"baseUri"` +} + +type app struct { + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` +} + +func CreateApp(apiURL, pat, domain, projectID string, redirectURI, loginURL string) (string, string, error) { + createApp := &createApp{ + Name: "OIDC", + RedirectUris: []string{redirectURI}, + ResponseTypes: []string{"OIDC_RESPONSE_TYPE_CODE"}, + GrantTypes: []string{"OIDC_GRANT_TYPE_AUTHORIZATION_CODE"}, + AppType: "OIDC_APP_TYPE_WEB", + AuthMethodType: "OIDC_AUTH_METHOD_TYPE_BASIC", + Version: "OIDC_VERSION_1_0", + DevMode: true, + AccessTokenType: "OIDC_TOKEN_TYPE_BEARER", + AccessTokenRoleAssertion: true, + IdTokenRoleAssertion: true, + IdTokenUserinfoAssertion: true, + ClockSkew: "1s", + SkipNativeAppSuccessPage: true, + LoginVersion: version{ + LoginV2: loginV2{ + BaseUri: loginURL, + }, + }, + } + body, err := json.Marshal(createApp) + if err != nil { + return "", "", err + } + + resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/oidc", pat, domain, body) + data, err := io.ReadAll(resp.Body) + if err != nil { + return "", "", err + } + defer resp.Body.Close() + + a := new(app) + if err := json.Unmarshal(data, a); err != nil { + return "", "", err + } + return a.ClientID, a.ClientSecret, err +} + +func doRequestWithHeaders(apiURL, pat, domain string, body []byte) (*http.Response, error) { + req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(body))) + if err != nil { + return nil, err + } + values := http.Header{} + values.Add("Authorization", "Bearer "+pat) + values.Add("x-forwarded-host", domain) + values.Add("Content-Type", "application/json") + req.Header = values + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/acceptance/saml/Dockerfile b/acceptance/saml/Dockerfile deleted file mode 100644 index dd29721bc3..0000000000 --- a/acceptance/saml/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM golang:1.24-alpine -RUN apk add curl jq -COPY setup.sh /setup.sh -RUN chmod +x /setup.sh -ENTRYPOINT [ "/setup.sh" ] diff --git a/acceptance/saml/docker-compose.yaml b/acceptance/saml/docker-compose.yaml index 19b3473e28..c2301bac69 100644 --- a/acceptance/saml/docker-compose.yaml +++ b/acceptance/saml/docker-compose.yaml @@ -2,34 +2,21 @@ services: samlsp: image: golang:1.24-alpine container_name: samlsp - command: go run main.go -host 'http://localhost' -port '8001' -idp 'http://host.docker.internal:3000/saml/v2/metadata' + command: go run main.go + environment: + API_URL: 'http://localhost:8080' + API_DOMAIN: 'localhost:8080' + PAT_FILE: '/pat/zitadel-admin-sa.pat' + LOGIN_URL: 'http://localhost:3000' + IDP_URL: 'http://localhost:3000/saml/v2/metadata' + HOST: 'http://localhost' + PORT: '8001' working_dir: /saml ports: - 8001:8001 volumes: + - "../pat:/pat" - "./:/saml" extra_hosts: - - "host.docker.internal:host-gateway" + - "localhost:host-gateway" - wait_for_samlsp: - image: curlimages/curl:8.00.1 - command: /bin/sh -c "until curl -s -o /dev/null -i -f http://samlsp:8001/saml/metadata; do echo 'waiting' && sleep 1; done; echo 'ready' && sleep 5;" || false - depends_on: - - samlsp - - setup-samlsp: - user: "${ZITADEL_DEV_UID}" - container_name: setup-samlsp - build: . - environment: - PAT_FILE: /pat/zitadel-admin-sa.pat - ZITADEL_API_URL: http://host.docker.internal:8080 - LOGIN_URI: http://localhost:3000 - SAML_SP_METADATA: http://host.docker.internal:8001/saml/metadata - volumes: - - "../pat:/pat" - extra_hosts: - - "host.docker.internal:host-gateway" - depends_on: - wait_for_samlsp: - condition: "service_completed_successfully" \ No newline at end of file diff --git a/acceptance/saml/go.mod b/acceptance/saml/go.mod index d3378ea0b3..d3716924ab 100644 --- a/acceptance/saml/go.mod +++ b/acceptance/saml/go.mod @@ -8,9 +8,11 @@ require ( github.com/beevik/etree v1.5.0 // indirect github.com/crewjam/httperr v0.2.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/russellhaering/goxmldsig v1.5.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect golang.org/x/crypto v0.36.0 // indirect ) diff --git a/acceptance/saml/go.sum b/acceptance/saml/go.sum index 482277f9de..2461101e17 100644 --- a/acceptance/saml/go.sum +++ b/acceptance/saml/go.sum @@ -1,8 +1,5 @@ -github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= -github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/etree v1.5.0 h1:iaQZFSDS+3kYZiGoc9uKeOkUY3nYMXOKLl6KIJxiJWs= github.com/beevik/etree v1.5.0/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= @@ -10,53 +7,31 @@ github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1n github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= -github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3cigIwLonTPM= -github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw= github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= diff --git a/acceptance/saml/main.go b/acceptance/saml/main.go index 273e93c115..898e993a0d 100644 --- a/acceptance/saml/main.go +++ b/acceptance/saml/main.go @@ -1,14 +1,25 @@ package main import ( + "bytes" "context" "crypto/rsa" "crypto/tls" "crypto/x509" - "flag" + "encoding/base64" + "encoding/json" + "encoding/xml" + "errors" "fmt" + "io" + "log" "net/http" "net/url" + "os" + "os/signal" + "strings" + "syscall" + "time" "github.com/crewjam/saml/samlsp" ) @@ -80,13 +91,25 @@ func hello(w http.ResponseWriter, r *http.Request) { } func main() { - idpURL := flag.String("idp", "http://localhost:3000/saml/v2/metadata", "url to idp metadata, proxied through typescript") - host := flag.String("host", "http://localhost", "url for sp") - port := flag.String("port", "8001", "port for sp") + apiURL := os.Getenv("API_URL") + patFile := os.Getenv("PAT_FILE") + domain := os.Getenv("API_DOMAIN") + loginURL := os.Getenv("LOGIN_URL") + idpURL := os.Getenv("IDP_URL") + host := os.Getenv("HOST") + port := os.Getenv("PORT") - flag.Parse() + f, err := os.Open(patFile) + if err != nil { + panic(err) + } + pat, err := io.ReadAll(f) + if err != nil { + panic(err) + } + patStr := strings.Trim(string(pat), "\n") - idpMetadataURL, err := url.Parse(*idpURL) + idpMetadataURL, err := url.Parse(idpURL) if err != nil { panic(err) } @@ -96,7 +119,7 @@ func main() { panic(err) } fmt.Printf("idpMetadata: %+v\n", idpMetadata) - rootURL, err := url.Parse(*host + ":" + *port) + rootURL, err := url.Parse(host + ":" + port) if err != nil { panic(err) } @@ -111,8 +134,136 @@ func main() { panic(err) } + server := &http.Server{ + Addr: ":" + port, + } app := http.HandlerFunc(hello) http.Handle("/hello", samlSP.RequireAccount(app)) http.Handle("/saml/", samlSP) - http.ListenAndServe(":"+*port, nil) + go func() { + if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + log.Fatalf("HTTP server error: %v", err) + } + log.Println("Stopped serving new connections.") + }() + + metadata, err := xml.MarshalIndent(samlSP.ServiceProvider.Metadata(), "", " ") + if err != nil { + panic(err) + } + if err := createZitadelResources(apiURL, patStr, domain, metadata, loginURL); err != nil { + panic(err) + } + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + <-sigChan + + shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) + defer shutdownRelease() + + if err := server.Shutdown(shutdownCtx); err != nil { + log.Fatalf("HTTP shutdown error: %v", err) + } +} + +func createZitadelResources(apiURL, pat, domain string, metadata []byte, loginURL string) error { + projectID, err := CreateProject(apiURL, pat, domain) + if err != nil { + return err + } + return CreateApp(apiURL, pat, domain, projectID, metadata, loginURL) +} + +type project struct { + ID string `json:"id"` +} +type createProject struct { + Name string `json:"name"` + ProjectRoleAssertion bool `json:"projectRoleAssertion"` + ProjectRoleCheck bool `json:"projectRoleCheck"` + HasProjectCheck bool `json:"hasProjectCheck"` + PrivateLabelingSetting string `json:"privateLabelingSetting"` +} + +func CreateProject(apiURL, pat, domain string) (string, error) { + createProject := &createProject{ + Name: "SAML", + ProjectRoleAssertion: false, + ProjectRoleCheck: false, + HasProjectCheck: false, + PrivateLabelingSetting: "PRIVATE_LABELING_SETTING_UNSPECIFIED", + } + body, err := json.Marshal(createProject) + if err != nil { + return "", err + } + + resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, body) + if err != nil { + return "", err + } + data, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + defer resp.Body.Close() + + p := new(project) + if err := json.Unmarshal(data, p); err != nil { + return "", err + } + return p.ID, nil +} + +type createApp struct { + Name string `json:"name"` + MetadataXml string `json:"metadataXml"` + LoginVersion version `json:"loginVersion"` +} +type version struct { + LoginV2 loginV2 `json:"loginV2"` +} +type loginV2 struct { + BaseUri string `json:"baseUri"` +} + +func CreateApp(apiURL, pat, domain, projectID string, spMetadata []byte, loginURL string) error { + encoded := make([]byte, base64.URLEncoding.EncodedLen(len(spMetadata))) + base64.URLEncoding.Encode(encoded, spMetadata) + + createApp := &createApp{ + Name: "SAML", + MetadataXml: string(encoded), + LoginVersion: version{ + LoginV2: loginV2{ + BaseUri: loginURL, + }, + }, + } + body, err := json.Marshal(createApp) + if err != nil { + return err + } + + _, err = doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/saml", pat, domain, body) + return err +} + +func doRequestWithHeaders(apiURL, pat, domain string, body []byte) (*http.Response, error) { + req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(body))) + if err != nil { + return nil, err + } + values := http.Header{} + values.Add("Authorization", "Bearer "+pat) + values.Add("x-forwarded-host", domain) + values.Add("Content-Type", "application/json") + req.Header = values + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + return resp, nil } diff --git a/acceptance/saml/setup.sh b/acceptance/saml/setup.sh deleted file mode 100755 index 9d8f68ccac..0000000000 --- a/acceptance/saml/setup.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh - -set -ex - -PAT_FILE=${PAT_FILE:-../pat/zitadel-admin-sa.pat} -ZITADEL_API_URL="${ZITADEL_API_URL:-"http://localhost:8080"}" -LOGIN_URI="${LOGIN_URI:-"http://localhost:3000"}" -SAML_SP_METADATA="${SAML_SP_METADATA:-"http://samlsp:8081/saml/metadata"}" - -if [ -z "${PAT}" ]; then - echo "Reading PAT from file ${PAT_FILE}" - PAT=$(cat ${PAT_FILE}) -fi - -################################################################# -# SAML Application -################################################################# - -SAML_PROJECT_RESPONSE=$(curl -s --request POST \ - --url "${ZITADEL_API_URL}/management/v1/projects" \ - --header "Authorization: Bearer ${PAT}" \ - --header "Host: ${ZITADEL_API_DOMAIN}" \ - --header "Content-Type: application/json" \ - -d "{ \"name\": \"SAML\", \"projectRoleAssertion\": true, \"projectRoleCheck\": true, \"hasProjectCheck\": true, \"privateLabelingSetting\": \"PRIVATE_LABELING_SETTING_UNSPECIFIED\"}") -echo "Received SAML Project response: ${SAML_PROJECT_RESPONSE}" - -SAML_PROJECT_ID=$(echo ${SAML_PROJECT_RESPONSE} | jq -r '. | .id') -echo "Received Project ID: ${SAML_PROJECT_ID}" - -SAML_APP_RESPONSE=$(curl -s --request POST \ - --url "${ZITADEL_API_URL}/management/v1/projects/${SAML_PROJECT_ID}/apps/saml" \ - --header "Authorization: Bearer ${PAT}" \ - --header "Host: ${ZITADEL_API_DOMAIN}" \ - --header "Content-Type: application/json" \ - -d "{ \"name\": \"SAML\", \"metadataUrl\": \"${SAML_SP_METADATA}\", \"loginVersion\": { \"loginV2\": { \"baseUri\": \"${LOGIN_URI}\" }}}") -echo "Received SAML App response: ${SAML_APP_RESPONSE}" diff --git a/acceptance/tests/oidc-username-password.spec.ts b/acceptance/tests/oidc-username-password.spec.ts new file mode 100644 index 0000000000..09b415655b --- /dev/null +++ b/acceptance/tests/oidc-username-password.spec.ts @@ -0,0 +1,37 @@ +import { faker } from "@faker-js/faker"; +import { test as base, expect } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; +import { loginname } from "./loginname"; +import { password } from "./password"; +import { PasswordUser } from "./user"; +import {startOIDC} from "./oidc"; + +// Read from ".env" file. +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); + +const test = base.extend<{ user: PasswordUser }>({ + user: async ({ page }, use) => { + const user = new PasswordUser({ + email: faker.internet.email(), + isEmailVerified: true, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + organization: "", + phone: faker.phone.number(), + isPhoneVerified: false, + password: "Password1!", + passwordChangeRequired: false, + }); + await user.ensure(page); + await use(user); + await user.cleanup(); + }, +}); + +test("oidc username and password login", async ({ user, page }) => { + await startOIDC(page) + await loginname(page, user.getUsername()); + await password(page, user.getPassword()); + await expect(page.locator('pre')).toContainText(user.getUsername()); +}); diff --git a/acceptance/tests/oidc.ts b/acceptance/tests/oidc.ts new file mode 100644 index 0000000000..d85a68fccb --- /dev/null +++ b/acceptance/tests/oidc.ts @@ -0,0 +1,5 @@ +import { expect, Page } from "@playwright/test"; + +export async function startOIDC(page: Page) { + await page.goto("http://localhost:8000/login"); +} \ No newline at end of file diff --git a/package.json b/package.json index 2a4868e720..a917730ed7 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup", "run-sink": "docker compose -f ./acceptance/docker-compose.yaml up -d sink", "run-samlsp": "docker compose -f ./acceptance/saml/docker-compose.yaml up -d", + "run-oidcrp": "docker compose -f ./acceptance/oidc/docker-compose.yaml up -d", "stop": "docker compose -f ./acceptance/docker-compose.yaml stop" }, "pnpm": { From 56a231697387b9f8c5695c0a5612dc27695bf9ff Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 25 Mar 2025 14:10:12 +0100 Subject: [PATCH 16/45] chore: add saml idp for acceptance tests --- acceptance/idp/saml/docker-compose.yaml | 20 ++ acceptance/idp/saml/go.mod | 16 ++ acceptance/idp/saml/go.sum | 49 ++++ acceptance/idp/saml/main.go | 330 ++++++++++++++++++++++++ acceptance/oidc/go.mod | 1 - acceptance/oidc/go.sum | 2 - acceptance/oidc/main.go | 27 +- acceptance/saml/main.go | 21 +- package.json | 1 + 9 files changed, 432 insertions(+), 35 deletions(-) create mode 100644 acceptance/idp/saml/docker-compose.yaml create mode 100644 acceptance/idp/saml/go.mod create mode 100644 acceptance/idp/saml/go.sum create mode 100644 acceptance/idp/saml/main.go diff --git a/acceptance/idp/saml/docker-compose.yaml b/acceptance/idp/saml/docker-compose.yaml new file mode 100644 index 0000000000..30e5a26fc3 --- /dev/null +++ b/acceptance/idp/saml/docker-compose.yaml @@ -0,0 +1,20 @@ +services: + samlidp: + image: golang:1.24-alpine + container_name: samlidp + command: go run main.go + environment: + API_URL: 'http://localhost:8080' + API_DOMAIN: 'localhost:8080' + PAT_FILE: '/pat/zitadel-admin-sa.pat' + SCHEMA: 'http' + HOST: 'localhost' + PORT: "8003" + working_dir: /saml + ports: + - 8003:8003 + volumes: + - "../../pat:/pat" + - "./:/saml" + extra_hosts: + - "localhost:host-gateway" diff --git a/acceptance/idp/saml/go.mod b/acceptance/idp/saml/go.mod new file mode 100644 index 0000000000..e73b4feb3b --- /dev/null +++ b/acceptance/idp/saml/go.mod @@ -0,0 +1,16 @@ +module github.com/zitadel/typescript/acceptance/idp/saml + +go 1.24.1 + +require ( + github.com/crewjam/saml v0.4.14 + github.com/mattermost/xml-roundtrip-validator v0.1.0 + github.com/zenazn/goji v1.0.1 + golang.org/x/crypto v0.36.0 +) + +require ( + github.com/beevik/etree v1.1.0 // indirect + github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/russellhaering/goxmldsig v1.3.0 // indirect +) diff --git a/acceptance/idp/saml/go.sum b/acceptance/idp/saml/go.sum new file mode 100644 index 0000000000..1208550f6e --- /dev/null +++ b/acceptance/idp/saml/go.sum @@ -0,0 +1,49 @@ +github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= +github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3cigIwLonTPM= +github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= +github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/acceptance/idp/saml/main.go b/acceptance/idp/saml/main.go new file mode 100644 index 0000000000..b084a9398b --- /dev/null +++ b/acceptance/idp/saml/main.go @@ -0,0 +1,330 @@ +package main + +import ( + "bytes" + "crypto" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "encoding/xml" + "errors" + "io" + "log" + "net/http" + "net/url" + "os" + "os/signal" + "strings" + "syscall" + + "github.com/crewjam/saml" + "github.com/crewjam/saml/logger" + "github.com/crewjam/saml/samlidp" + xrv "github.com/mattermost/xml-roundtrip-validator" + "github.com/zenazn/goji" + "github.com/zenazn/goji/bind" + "golang.org/x/crypto/bcrypt" +) + +var key = func() crypto.PrivateKey { + b, _ := pem.Decode([]byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA0OhbMuizgtbFOfwbK7aURuXhZx6VRuAs3nNibiuifwCGz6u9 +yy7bOR0P+zqN0YkjxaokqFgra7rXKCdeABmoLqCC0U+cGmLNwPOOA0PaD5q5xKhQ +4Me3rt/R9C4Ca6k3/OnkxnKwnogcsmdgs2l8liT3qVHP04Oc7Uymq2v09bGb6nPu +fOrkXS9F6mSClxHG/q59AGOWsXK1xzIRV1eu8W2SNdyeFVU1JHiQe444xLoPul5t +InWasKayFsPlJfWNc8EoU8COjNhfo/GovFTHVjh9oUR/gwEFVwifIHihRE0Hazn2 +EQSLaOr2LM0TsRsQroFjmwSGgI+X2bfbMTqWOQIDAQABAoIBAFWZwDTeESBdrLcT +zHZe++cJLxE4AObn2LrWANEv5AeySYsyzjRBYObIN9IzrgTb8uJ900N/zVr5VkxH +xUa5PKbOcowd2NMfBTw5EEnaNbILLm+coHdanrNzVu59I9TFpAFoPavrNt/e2hNo +NMGPSdOkFi81LLl4xoadz/WR6O/7N2famM+0u7C2uBe+TrVwHyuqboYoidJDhO8M +w4WlY9QgAUhkPyzZqrl+VfF1aDTGVf4LJgaVevfFCas8Ws6DQX5q4QdIoV6/0vXi +B1M+aTnWjHuiIzjBMWhcYW2+I5zfwNWRXaxdlrYXRukGSdnyO+DH/FhHePJgmlkj +NInADDkCgYEA6MEQFOFSCc/ELXYWgStsrtIlJUcsLdLBsy1ocyQa2lkVUw58TouW +RciE6TjW9rp31pfQUnO2l6zOUC6LT9Jvlb9PSsyW+rvjtKB5PjJI6W0hjX41wEO6 +fshFELMJd9W+Ezao2AsP2hZJ8McCF8no9e00+G4xTAyxHsNI2AFTCQcCgYEA5cWZ +JwNb4t7YeEajPt9xuYNUOQpjvQn1aGOV7KcwTx5ELP/Hzi723BxHs7GSdrLkkDmi +Gpb+mfL4wxCt0fK0i8GFQsRn5eusyq9hLqP/bmjpHoXe/1uajFbE1fZQR+2LX05N +3ATlKaH2hdfCJedFa4wf43+cl6Yhp6ZA0Yet1r8CgYEAwiu1j8W9G+RRA5/8/DtO +yrUTOfsbFws4fpLGDTA0mq0whf6Soy/96C90+d9qLaC3srUpnG9eB0CpSOjbXXbv +kdxseLkexwOR3bD2FHX8r4dUM2bzznZyEaxfOaQypN8SV5ME3l60Fbr8ajqLO288 +wlTmGM5Mn+YCqOg/T7wjGmcCgYBpzNfdl/VafOROVbBbhgXWtzsz3K3aYNiIjbp+ +MunStIwN8GUvcn6nEbqOaoiXcX4/TtpuxfJMLw4OvAJdtxUdeSmEee2heCijV6g3 +ErrOOy6EqH3rNWHvlxChuP50cFQJuYOueO6QggyCyruSOnDDuc0BM0SGq6+5g5s7 +H++S/wKBgQDIkqBtFr9UEf8d6JpkxS0RXDlhSMjkXmkQeKGFzdoJcYVFIwq8jTNB +nJrVIGs3GcBkqGic+i7rTO1YPkquv4dUuiIn+vKZVoO6b54f+oPBXd4S0BnuEqFE +rdKNuCZhiaE2XD9L/O9KP1fh5bfEcKwazQ23EvpJHBMm8BGC+/YZNw== +-----END RSA PRIVATE KEY-----`)) + k, _ := x509.ParsePKCS1PrivateKey(b.Bytes) + return k +}() + +var cert = func() *x509.Certificate { + b, _ := pem.Decode([]byte(`-----BEGIN CERTIFICATE----- +MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV +BAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5 +NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8A +hs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+a +ucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWx +m+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6 +D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURN +B2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0O +BBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56 +zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5 +pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uv +NONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEf +y/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL +/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsb +GFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTL +UzreO96WzlBBMtY= +-----END CERTIFICATE-----`)) + c, _ := x509.ParseCertificate(b.Bytes) + return c +}() + +// Example from https://github.com/crewjam/saml/blob/main/example/idp/idp.go +func main() { + apiURL := os.Getenv("API_URL") + patFile := os.Getenv("PAT_FILE") + domain := os.Getenv("API_DOMAIN") + schema := os.Getenv("SCHEMA") + host := os.Getenv("HOST") + port := os.Getenv("PORT") + + f, err := os.Open(patFile) + if err != nil { + panic(err) + } + pat, err := io.ReadAll(f) + if err != nil { + panic(err) + } + patStr := strings.Trim(string(pat), "\n") + + baseURL, err := url.Parse(schema + "://" + host + ":" + port) + if err != nil { + + panic(err) + } + + idpServer, err := samlidp.New(samlidp.Options{ + URL: *baseURL, + Logger: logger.DefaultLogger, + Key: key, + Certificate: cert, + Store: &samlidp.MemoryStore{}, + }) + if err != nil { + + panic(err) + } + + metadata, err := xml.MarshalIndent(idpServer.IDP.Metadata(), "", " ") + if err != nil { + panic(err) + } + idpID, err := createZitadelResources(apiURL, patStr, domain, metadata) + if err != nil { + panic(err) + } + + lis := bind.Socket(":" + baseURL.Port()) + goji.Handle("/*", idpServer) + + go func() { + goji.ServeListener(lis) + }() + + addService(idpServer, apiURL+"/idps/"+idpID+"/saml/metadata") + addUsers(idpServer) + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + <-sigChan + + if err := lis.Close(); err != nil { + log.Fatalf("HTTP shutdown error: %v", err) + } +} + +func addService(idpServer *samlidp.Server, spURLStr string) { + metadataResp, err := http.Get(spURLStr) + if err != nil { + panic(err) + } + defer metadataResp.Body.Close() + spMetadata, err := getSPMetadata(metadataResp.Body) + if err != nil { + panic(err) + } + + err = idpServer.Store.Put("/services/sp", samlidp.Service{ + Name: spURLStr, + Metadata: *spMetadata, + }) + if err != nil { + panic(err) + } +} + +func getSPMetadata(r io.Reader) (spMetadata *saml.EntityDescriptor, err error) { + var data []byte + if data, err = io.ReadAll(r); err != nil { + return nil, err + } + + spMetadata = &saml.EntityDescriptor{} + if err := xrv.Validate(bytes.NewBuffer(data)); err != nil { + return nil, err + } + + if err := xml.Unmarshal(data, &spMetadata); err != nil { + if err.Error() == "expected element type but have " { + entities := &saml.EntitiesDescriptor{} + if err := xml.Unmarshal(data, &entities); err != nil { + return nil, err + } + + for _, e := range entities.EntityDescriptors { + if len(e.SPSSODescriptors) > 0 { + return &e, nil + } + } + + // there were no SPSSODescriptors in the response + return nil, errors.New("metadata contained no service provider metadata") + } + + return nil, err + } + + return spMetadata, nil +} + +func addUsers(idpServer *samlidp.Server) { + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("hunter2"), bcrypt.DefaultCost) + err := idpServer.Store.Put("/users/alice", samlidp.User{Name: "alice", + HashedPassword: hashedPassword, + Groups: []string{"Administrators", "Users"}, + Email: "alice@example.com", + CommonName: "Alice Smith", + Surname: "Smith", + GivenName: "Alice", + }) + if err != nil { + panic(err) + } + + err = idpServer.Store.Put("/users/bob", samlidp.User{ + Name: "bob", + HashedPassword: hashedPassword, + Groups: []string{"Users"}, + Email: "bob@example.com", + CommonName: "Bob Smith", + Surname: "Smith", + GivenName: "Bob", + }) + if err != nil { + panic(err) + } +} + +func createZitadelResources(apiURL, pat, domain string, metadata []byte) (string, error) { + idpID, err := CreateIDP(apiURL, pat, domain, metadata) + if err != nil { + return "", err + } + return idpID, ActivateIDP(apiURL, pat, domain, idpID) +} + +type createIDP struct { + Name string `json:"name"` + MetadataXml string `json:"metadataXml"` + Binding string `json:"binding"` + WithSignedRequest bool `json:"withSignedRequest"` + ProviderOptions providerOptions `json:"providerOptions"` + NameIdFormat string `json:"nameIdFormat"` +} +type providerOptions struct { + IsLinkingAllowed bool `json:"isLinkingAllowed"` + IsCreationAllowed bool `json:"isCreationAllowed"` + IsAutoCreation bool `json:"isAutoCreation"` + IsAutoUpdate bool `json:"isAutoUpdate"` + AutoLinking string `json:"autoLinking"` +} + +type idp struct { + ID string `json:"id"` +} + +func CreateIDP(apiURL, pat, domain string, idpMetadata []byte) (string, error) { + encoded := make([]byte, base64.URLEncoding.EncodedLen(len(idpMetadata))) + base64.URLEncoding.Encode(encoded, idpMetadata) + + createIDP := &createIDP{ + Name: "CREWJAM", + MetadataXml: string(encoded), + Binding: "SAML_BINDING_POST", + WithSignedRequest: true, + ProviderOptions: providerOptions{ + IsLinkingAllowed: true, + IsCreationAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + AutoLinking: "AUTO_LINKING_OPTION_USERNAME", + }, + NameIdFormat: "SAML_NAME_ID_FORMAT_PERSISTENT", + } + + resp, err := doRequestWithHeaders(apiURL+"/admin/v1/idps/saml", pat, domain, createIDP) + if err != nil { + return "", err + } + data, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + defer resp.Body.Close() + + idp := new(idp) + if err := json.Unmarshal(data, idp); err != nil { + return "", err + } + return idp.ID, nil +} + +type activateIDP struct { + IdpId string `json:"idpId"` +} + +func ActivateIDP(apiURL, pat, domain string, idpID string) error { + activateIDP := &activateIDP{ + IdpId: idpID, + } + _, err := doRequestWithHeaders(apiURL+"/admin/v1/policies/login/idps", pat, domain, activateIDP) + return err +} + +func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) { + data, err := json.Marshal(body) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data))) + if err != nil { + return nil, err + } + values := http.Header{} + values.Add("Authorization", "Bearer "+pat) + values.Add("x-forwarded-host", domain) + values.Add("Content-Type", "application/json") + req.Header = values + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/acceptance/oidc/go.mod b/acceptance/oidc/go.mod index 7bb49c983f..b251c2485c 100644 --- a/acceptance/oidc/go.mod +++ b/acceptance/oidc/go.mod @@ -4,7 +4,6 @@ go 1.24.1 require ( github.com/google/uuid v1.6.0 - github.com/joho/godotenv v1.5.1 github.com/sirupsen/logrus v1.9.3 github.com/zitadel/logging v0.6.1 github.com/zitadel/oidc/v3 v3.36.1 diff --git a/acceptance/oidc/go.sum b/acceptance/oidc/go.sum index 7bcd270709..5037a5b30a 100644 --- a/acceptance/oidc/go.sum +++ b/acceptance/oidc/go.sum @@ -22,8 +22,6 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA= github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= diff --git a/acceptance/oidc/main.go b/acceptance/oidc/main.go index b76caaeee6..69b3b5c96a 100644 --- a/acceptance/oidc/main.go +++ b/acceptance/oidc/main.go @@ -20,7 +20,6 @@ import ( "github.com/google/uuid" "github.com/sirupsen/logrus" - "github.com/joho/godotenv" "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/client/rp" httphelper "github.com/zitadel/oidc/v3/pkg/http" @@ -33,11 +32,6 @@ var ( ) func main() { - err := godotenv.Load() - if err != nil { - log.Fatal("Error loading .env file") - } - apiURL := os.Getenv("API_URL") patFile := os.Getenv("PAT_FILE") domain := os.Getenv("API_DOMAIN") @@ -206,12 +200,7 @@ func CreateProject(apiURL, pat, domain string) (string, error) { HasProjectCheck: false, PrivateLabelingSetting: "PRIVATE_LABELING_SETTING_UNSPECIFIED", } - body, err := json.Marshal(createProject) - if err != nil { - return "", err - } - - resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, body) + resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, createProject) if err != nil { return "", err } @@ -284,12 +273,8 @@ func CreateApp(apiURL, pat, domain, projectID string, redirectURI, loginURL stri }, }, } - body, err := json.Marshal(createApp) - if err != nil { - return "", "", err - } - resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/oidc", pat, domain, body) + resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/oidc", pat, domain, createApp) data, err := io.ReadAll(resp.Body) if err != nil { return "", "", err @@ -303,8 +288,12 @@ func CreateApp(apiURL, pat, domain, projectID string, redirectURI, loginURL stri return a.ClientID, a.ClientSecret, err } -func doRequestWithHeaders(apiURL, pat, domain string, body []byte) (*http.Response, error) { - req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(body))) +func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) { + data, err := json.Marshal(body) + if err != nil { + return nil, err + } + req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data))) if err != nil { return nil, err } diff --git a/acceptance/saml/main.go b/acceptance/saml/main.go index 898e993a0d..053a7222bd 100644 --- a/acceptance/saml/main.go +++ b/acceptance/saml/main.go @@ -194,12 +194,7 @@ func CreateProject(apiURL, pat, domain string) (string, error) { HasProjectCheck: false, PrivateLabelingSetting: "PRIVATE_LABELING_SETTING_UNSPECIFIED", } - body, err := json.Marshal(createProject) - if err != nil { - return "", err - } - - resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, body) + resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, createProject) if err != nil { return "", err } @@ -241,17 +236,17 @@ func CreateApp(apiURL, pat, domain, projectID string, spMetadata []byte, loginUR }, }, } - body, err := json.Marshal(createApp) - if err != nil { - return err - } - _, err = doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/saml", pat, domain, body) + _, err := doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/saml", pat, domain, createApp) return err } -func doRequestWithHeaders(apiURL, pat, domain string, body []byte) (*http.Response, error) { - req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(body))) +func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) { + data, err := json.Marshal(body) + if err != nil { + return nil, err + } + req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data))) if err != nil { return nil, err } diff --git a/package.json b/package.json index a917730ed7..2a1c50f613 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup", "run-sink": "docker compose -f ./acceptance/docker-compose.yaml up -d sink", "run-samlsp": "docker compose -f ./acceptance/saml/docker-compose.yaml up -d", + "run-samlidp": "docker compose -f ./acceptance/idp/saml/docker-compose.yaml up -d", "run-oidcrp": "docker compose -f ./acceptance/oidc/docker-compose.yaml up -d", "stop": "docker compose -f ./acceptance/docker-compose.yaml stop" }, From e5604edff843704f5a90454618127c60092bbd5d Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 27 Mar 2025 09:50:06 +0100 Subject: [PATCH 17/45] chore: add oidc idp for acceptance tests --- acceptance/idp/oidc/docker-compose.yaml | 20 +++ acceptance/idp/oidc/go.mod | 28 ++++ acceptance/idp/oidc/go.sum | 71 +++++++++ acceptance/idp/oidc/main.go | 186 ++++++++++++++++++++++++ acceptance/idp/saml/main.go | 26 ++-- acceptance/oidc/main.go | 26 ++-- acceptance/saml/main.go | 28 ++-- 7 files changed, 348 insertions(+), 37 deletions(-) create mode 100644 acceptance/idp/oidc/docker-compose.yaml create mode 100644 acceptance/idp/oidc/go.mod create mode 100644 acceptance/idp/oidc/go.sum create mode 100644 acceptance/idp/oidc/main.go diff --git a/acceptance/idp/oidc/docker-compose.yaml b/acceptance/idp/oidc/docker-compose.yaml new file mode 100644 index 0000000000..3aeced18a8 --- /dev/null +++ b/acceptance/idp/oidc/docker-compose.yaml @@ -0,0 +1,20 @@ +services: + oidcop: + image: golang:1.24-alpine + container_name: oidcop + command: go run main.go + environment: + API_URL: 'http://localhost:8080' + API_DOMAIN: 'localhost:8080' + PAT_FILE: '/pat/zitadel-admin-sa.pat' + SCHEMA: 'http' + HOST: 'localhost' + PORT: "8004" + working_dir: /oidc + ports: + - 8004:8004 + volumes: + - "../../pat:/pat" + - "./:/oidc" + extra_hosts: + - "localhost:host-gateway" diff --git a/acceptance/idp/oidc/go.mod b/acceptance/idp/oidc/go.mod new file mode 100644 index 0000000000..84dae766c8 --- /dev/null +++ b/acceptance/idp/oidc/go.mod @@ -0,0 +1,28 @@ +module github.com/zitadel/typescript/acceptance/idp/oidc + +go 1.24.1 + +require github.com/zitadel/oidc/v3 v3.37.0 + +require ( + github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect + github.com/go-chi/chi/v5 v5.2.1 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/muhlemmer/gu v0.3.1 // indirect + github.com/muhlemmer/httpforwarded v0.1.0 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/zitadel/logging v0.6.2 // indirect + github.com/zitadel/schema v1.3.1 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect +) diff --git a/acceptance/idp/oidc/go.sum b/acceptance/idp/oidc/go.sum new file mode 100644 index 0000000000..42d80d8683 --- /dev/null +++ b/acceptance/idp/oidc/go.sum @@ -0,0 +1,71 @@ +github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= +github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= +github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= +github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= +github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= +github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU= +github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4= +github.com/zitadel/oidc/v3 v3.37.0 h1:nYATWlnP7f18XiAbw6upUruBaqfB1kUrXrSTf1EYGO8= +github.com/zitadel/oidc/v3 v3.37.0/go.mod h1:/xDan4OUQhguJ4Ur73OOJrtugvR164OMnidXP9xfVNw= +github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU= +github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/acceptance/idp/oidc/main.go b/acceptance/idp/oidc/main.go new file mode 100644 index 0000000000..c33f95c263 --- /dev/null +++ b/acceptance/idp/oidc/main.go @@ -0,0 +1,186 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "log/slog" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "github.com/zitadel/oidc/v3/example/server/exampleop" + "github.com/zitadel/oidc/v3/example/server/storage" +) + +func main() { + apiURL := os.Getenv("API_URL") + pat := readPAT(os.Getenv("PAT_FILE")) + domain := os.Getenv("API_DOMAIN") + schema := os.Getenv("SCHEMA") + host := os.Getenv("HOST") + port := os.Getenv("PORT") + + logger := slog.New( + slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + AddSource: true, + Level: slog.LevelDebug, + }), + ) + + issuer := fmt.Sprintf("%s://%s:%s/", schema, host, port) + redirectURI := fmt.Sprintf("%s/idps/callback", apiURL) + + clientID := "web" + clientSecret := "secret" + storage.RegisterClients( + storage.WebClient(clientID, clientSecret, redirectURI), + ) + + storage := storage.NewStorage(storage.NewUserStore(issuer)) + router := exampleop.SetupServer(issuer, storage, logger, false) + + server := &http.Server{ + Addr: ":" + port, + Handler: router, + } + go func() { + if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + log.Fatalf("HTTP server error: %v", err) + } + log.Println("Stopped serving new connections.") + }() + + createZitadelResources(apiURL, pat, domain, issuer, clientID, clientSecret) + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + <-sigChan + + shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) + defer shutdownRelease() + + if err := server.Shutdown(shutdownCtx); err != nil { + log.Fatalf("HTTP shutdown error: %v", err) + } +} + +func readPAT(path string) string { + f, err := os.Open(path) + if err != nil { + panic(err) + } + pat, err := io.ReadAll(f) + if err != nil { + panic(err) + } + return strings.Trim(string(pat), "\n") +} + +func createZitadelResources(apiURL, pat, domain, issuer, clientID, clientSecret string) error { + idpID, err := CreateIDP(apiURL, pat, domain, issuer, clientID, clientSecret) + if err != nil { + return err + } + return ActivateIDP(apiURL, pat, domain, idpID) +} + +type createIDP struct { + Name string `json:"name"` + Issuer string `json:"issuer"` + ClientId string `json:"clientId"` + ClientSecret string `json:"clientSecret"` + Scopes []string `json:"scopes"` + ProviderOptions providerOptions `json:"providerOptions"` + IsIdTokenMapping bool `json:"isIdTokenMapping"` + UsePkce bool `json:"usePkce"` +} + +type providerOptions struct { + IsLinkingAllowed bool `json:"isLinkingAllowed"` + IsCreationAllowed bool `json:"isCreationAllowed"` + IsAutoCreation bool `json:"isAutoCreation"` + IsAutoUpdate bool `json:"isAutoUpdate"` + AutoLinking string `json:"autoLinking"` +} + +type idp struct { + ID string `json:"id"` +} + +func CreateIDP(apiURL, pat, domain string, issuer, clientID, clientSecret string) (string, error) { + createIDP := &createIDP{ + Name: "OIDC", + Issuer: issuer, + ClientId: clientID, + ClientSecret: clientSecret, + Scopes: []string{"openid", "profile", "email"}, + ProviderOptions: providerOptions{ + IsLinkingAllowed: true, + IsCreationAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + AutoLinking: "AUTO_LINKING_OPTION_UNSPECIFIED", + }, + IsIdTokenMapping: false, + UsePkce: false, + } + + resp, err := doRequestWithHeaders(apiURL+"/admin/v1/idps/generic_oidc", pat, domain, createIDP) + if err != nil { + return "", err + } + data, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + defer resp.Body.Close() + + idp := new(idp) + if err := json.Unmarshal(data, idp); err != nil { + return "", err + } + return idp.ID, nil +} + +type activateIDP struct { + IdpId string `json:"idpId"` +} + +func ActivateIDP(apiURL, pat, domain string, idpID string) error { + activateIDP := &activateIDP{ + IdpId: idpID, + } + _, err := doRequestWithHeaders(apiURL+"/admin/v1/policies/login/idps", pat, domain, activateIDP) + return err +} + +func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) { + data, err := json.Marshal(body) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data))) + if err != nil { + return nil, err + } + values := http.Header{} + values.Add("Authorization", "Bearer "+pat) + values.Add("x-forwarded-host", domain) + values.Add("Content-Type", "application/json") + req.Header = values + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/acceptance/idp/saml/main.go b/acceptance/idp/saml/main.go index b084a9398b..04d33410a5 100644 --- a/acceptance/idp/saml/main.go +++ b/acceptance/idp/saml/main.go @@ -86,22 +86,12 @@ UzreO96WzlBBMtY= // Example from https://github.com/crewjam/saml/blob/main/example/idp/idp.go func main() { apiURL := os.Getenv("API_URL") - patFile := os.Getenv("PAT_FILE") + pat := readPAT(os.Getenv("PAT_FILE")) domain := os.Getenv("API_DOMAIN") schema := os.Getenv("SCHEMA") host := os.Getenv("HOST") port := os.Getenv("PORT") - f, err := os.Open(patFile) - if err != nil { - panic(err) - } - pat, err := io.ReadAll(f) - if err != nil { - panic(err) - } - patStr := strings.Trim(string(pat), "\n") - baseURL, err := url.Parse(schema + "://" + host + ":" + port) if err != nil { @@ -124,7 +114,7 @@ func main() { if err != nil { panic(err) } - idpID, err := createZitadelResources(apiURL, patStr, domain, metadata) + idpID, err := createZitadelResources(apiURL, pat, domain, metadata) if err != nil { panic(err) } @@ -148,6 +138,18 @@ func main() { } } +func readPAT(path string) string { + f, err := os.Open(path) + if err != nil { + panic(err) + } + pat, err := io.ReadAll(f) + if err != nil { + panic(err) + } + return strings.Trim(string(pat), "\n") +} + func addService(idpServer *samlidp.Server, spURLStr string) { metadataResp, err := http.Get(spURLStr) if err != nil { diff --git a/acceptance/oidc/main.go b/acceptance/oidc/main.go index 69b3b5c96a..ac3242c132 100644 --- a/acceptance/oidc/main.go +++ b/acceptance/oidc/main.go @@ -33,7 +33,7 @@ var ( func main() { apiURL := os.Getenv("API_URL") - patFile := os.Getenv("PAT_FILE") + pat := readPAT(os.Getenv("PAT_FILE")) domain := os.Getenv("API_DOMAIN") loginURL := os.Getenv("LOGIN_URL") issuer := os.Getenv("ISSUER") @@ -41,20 +41,10 @@ func main() { port := os.Getenv("PORT") scopeList := strings.Split(os.Getenv("SCOPES"), " ") - f, err := os.Open(patFile) - if err != nil { - panic(err) - } - pat, err := io.ReadAll(f) - if err != nil { - panic(err) - } - patStr := strings.Trim(string(pat), "\n") - redirectURI := fmt.Sprintf("%v:%v%v", host, port, callbackPath) cookieHandler := httphelper.NewCookieHandler(key, key, httphelper.WithUnsecure()) - clientID, clientSecret, err := createZitadelResources(apiURL, patStr, domain, redirectURI, loginURL) + clientID, clientSecret, err := createZitadelResources(apiURL, pat, domain, redirectURI, loginURL) if err != nil { panic(err) } @@ -173,6 +163,18 @@ func main() { } } +func readPAT(path string) string { + f, err := os.Open(path) + if err != nil { + panic(err) + } + pat, err := io.ReadAll(f) + if err != nil { + panic(err) + } + return strings.Trim(string(pat), "\n") +} + func createZitadelResources(apiURL, pat, domain, redirectURI, loginURL string) (string, string, error) { projectID, err := CreateProject(apiURL, pat, domain) if err != nil { diff --git a/acceptance/saml/main.go b/acceptance/saml/main.go index 053a7222bd..0886fa5613 100644 --- a/acceptance/saml/main.go +++ b/acceptance/saml/main.go @@ -87,28 +87,18 @@ FHHEZ+dR4eMaJp6PhNm8hu2O }() func hello(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, %s!", samlsp.AttributeFromContext(r.Context(), "displayName")) + fmt.Fprintf(w, "Hello, %s!", samlsp.AttributeFromContext(r.Context(), "UserName")) } func main() { apiURL := os.Getenv("API_URL") - patFile := os.Getenv("PAT_FILE") + pat := readPAT(os.Getenv("PAT_FILE")) domain := os.Getenv("API_DOMAIN") loginURL := os.Getenv("LOGIN_URL") idpURL := os.Getenv("IDP_URL") host := os.Getenv("HOST") port := os.Getenv("PORT") - f, err := os.Open(patFile) - if err != nil { - panic(err) - } - pat, err := io.ReadAll(f) - if err != nil { - panic(err) - } - patStr := strings.Trim(string(pat), "\n") - idpMetadataURL, err := url.Parse(idpURL) if err != nil { panic(err) @@ -151,7 +141,7 @@ func main() { if err != nil { panic(err) } - if err := createZitadelResources(apiURL, patStr, domain, metadata, loginURL); err != nil { + if err := createZitadelResources(apiURL, pat, domain, metadata, loginURL); err != nil { panic(err) } @@ -167,6 +157,18 @@ func main() { } } +func readPAT(path string) string { + f, err := os.Open(path) + if err != nil { + panic(err) + } + pat, err := io.ReadAll(f) + if err != nil { + panic(err) + } + return strings.Trim(string(pat), "\n") +} + func createZitadelResources(apiURL, pat, domain string, metadata []byte, loginURL string) error { projectID, err := CreateProject(apiURL, pat, domain) if err != nil { From f55d64b51fe1569001d283b8f738e1545a4ad4ff Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Fri, 28 Mar 2025 18:25:30 +0100 Subject: [PATCH 18/45] chore: add oidc idp for acceptance tests --- acceptance/docker-compose.yaml | 4 ++-- acceptance/idp/oidc/main.go | 2 +- acceptance/idp/saml/main.go | 22 ++++++++----------- .../tests/saml-username-password.spec.ts | 4 ++-- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index 61bcec04db..d033b1c39c 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,7 +1,7 @@ services: zitadel: user: "${ZITADEL_DEV_UID}" - image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:dc64e35128108d70471c7a5b9ad1dfc2c7c4c654}" + image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:02617cf17fdde849378c1a6b5254bbfb2745b164}" command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' ports: - "8080:8080" @@ -12,7 +12,7 @@ services: db: condition: "service_healthy" extra_hosts: - - "host.docker.internal:host-gateway" + - "localhost:host-gateway" db: restart: "always" diff --git a/acceptance/idp/oidc/main.go b/acceptance/idp/oidc/main.go index c33f95c263..b04ac94234 100644 --- a/acceptance/idp/oidc/main.go +++ b/acceptance/idp/oidc/main.go @@ -127,7 +127,7 @@ func CreateIDP(apiURL, pat, domain string, issuer, clientID, clientSecret string IsCreationAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, - AutoLinking: "AUTO_LINKING_OPTION_UNSPECIFIED", + AutoLinking: "AUTO_LINKING_OPTION_USERNAME", }, IsIdTokenMapping: false, UsePkce: false, diff --git a/acceptance/idp/saml/main.go b/acceptance/idp/saml/main.go index 04d33410a5..059eab79e2 100644 --- a/acceptance/idp/saml/main.go +++ b/acceptance/idp/saml/main.go @@ -12,6 +12,7 @@ import ( "io" "log" "net/http" + "net/http/httptest" "net/url" "os" "os/signal" @@ -24,6 +25,7 @@ import ( xrv "github.com/mattermost/xml-roundtrip-validator" "github.com/zenazn/goji" "github.com/zenazn/goji/bind" + "github.com/zenazn/goji/web" "golang.org/x/crypto/bcrypt" ) @@ -156,18 +158,12 @@ func addService(idpServer *samlidp.Server, spURLStr string) { panic(err) } defer metadataResp.Body.Close() - spMetadata, err := getSPMetadata(metadataResp.Body) - if err != nil { - panic(err) - } - err = idpServer.Store.Put("/services/sp", samlidp.Service{ - Name: spURLStr, - Metadata: *spMetadata, - }) - if err != nil { - panic(err) - } + idpServer.HandlePutService( + web.C{URLParams: map[string]string{"id": spURLStr}}, + httptest.NewRecorder(), + httptest.NewRequest(http.MethodPost, spURLStr, metadataResp.Body), + ) } func getSPMetadata(r io.Reader) (spMetadata *saml.EntityDescriptor, err error) { @@ -267,8 +263,8 @@ func CreateIDP(apiURL, pat, domain string, idpMetadata []byte) (string, error) { createIDP := &createIDP{ Name: "CREWJAM", MetadataXml: string(encoded), - Binding: "SAML_BINDING_POST", - WithSignedRequest: true, + Binding: "SAML_BINDING_REDIRECT", + WithSignedRequest: false, ProviderOptions: providerOptions{ IsLinkingAllowed: true, IsCreationAllowed: true, diff --git a/acceptance/tests/saml-username-password.spec.ts b/acceptance/tests/saml-username-password.spec.ts index a7f1864317..7dae2b7710 100644 --- a/acceptance/tests/saml-username-password.spec.ts +++ b/acceptance/tests/saml-username-password.spec.ts @@ -1,5 +1,5 @@ import { faker } from "@faker-js/faker"; -import { test as base } from "@playwright/test"; +import {expect, test as base} from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; import { loginname } from "./loginname"; @@ -35,5 +35,5 @@ test("saml username and password login", async ({ user, page }) => { await selectNewAccount(page) await loginname(page, user.getUsername()); await password(page, user.getPassword()); - // currently fails because of issuer problems + await expect(page.locator('html')).toContainText(user.getUsername()); }); From 395e3fc93a7599fae119ce645983a8d9ba52c64f Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:03:47 +0200 Subject: [PATCH 19/45] chore: add oidc idp for acceptance tests --- acceptance/tests/saml-username-password.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/acceptance/tests/saml-username-password.spec.ts b/acceptance/tests/saml-username-password.spec.ts index 7dae2b7710..503fef8326 100644 --- a/acceptance/tests/saml-username-password.spec.ts +++ b/acceptance/tests/saml-username-password.spec.ts @@ -31,9 +31,12 @@ const test = base.extend<{ user: PasswordUser }>({ }); test("saml username and password login", async ({ user, page }) => { + //TODO commented out because of prefetching while calling ACS of SAML SP + /* await startSAML(page) await selectNewAccount(page) await loginname(page, user.getUsername()); await password(page, user.getPassword()); await expect(page.locator('html')).toContainText(user.getUsername()); + */ }); From 472bfb007698843f6af487d2fea3d04244d40b5e Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:06:03 +0200 Subject: [PATCH 20/45] chore: add oidc idp for acceptance tests --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2a1c50f613..8e706d3b43 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "run-samlsp": "docker compose -f ./acceptance/saml/docker-compose.yaml up -d", "run-samlidp": "docker compose -f ./acceptance/idp/saml/docker-compose.yaml up -d", "run-oidcrp": "docker compose -f ./acceptance/oidc/docker-compose.yaml up -d", + "run-oidcop": "docker compose -f ./acceptance/idp/oidc/docker-compose.yaml up -d", "stop": "docker compose -f ./acceptance/docker-compose.yaml stop" }, "pnpm": { From e91be075b70db37449e781aaa1bce73ad87f399c Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:07:59 +0200 Subject: [PATCH 21/45] chore: add oidc idp for acceptance tests --- apps/login/src/app/login/route.ts | 5 ++++- apps/login/src/middleware.ts | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index 65b1b2eae7..e3834e5a27 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -477,7 +477,10 @@ export async function GET(request: NextRequest) { redirectUrl.searchParams.set("url", url); redirectUrl.searchParams.set("RelayState", binding.value.relayState); - redirectUrl.searchParams.set("SAMLResponse", binding.value.samlResponse); + redirectUrl.searchParams.set( + "SAMLResponse", + binding.value.samlResponse, + ); return NextResponse.redirect(redirectUrl.toString()); } else { diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index a1fd47504a..8d4080cddf 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -22,7 +22,9 @@ export async function middleware(request: NextRequest) { const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const instanceHost = `${serviceUrl}`.replace("https://", "").replace("http://", ""); + const instanceHost = `${serviceUrl}` + .replace("https://", "") + .replace("http://", ""); const requestHeaders = new Headers(request.headers); From 2b6eabe16ed64f8b609450b8eb242eaf6b278fac Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 31 Mar 2025 17:25:33 +0200 Subject: [PATCH 22/45] fix: format --- acceptance/tests/oidc-username-password.spec.ts | 6 +++--- acceptance/tests/oidc.ts | 6 +++--- acceptance/tests/saml-username-password.spec.ts | 6 +----- acceptance/tests/saml.ts | 6 +++--- acceptance/tests/select-account.ts | 4 ++-- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/acceptance/tests/oidc-username-password.spec.ts b/acceptance/tests/oidc-username-password.spec.ts index 09b415655b..eba368f8ef 100644 --- a/acceptance/tests/oidc-username-password.spec.ts +++ b/acceptance/tests/oidc-username-password.spec.ts @@ -3,9 +3,9 @@ import { test as base, expect } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; import { loginname } from "./loginname"; +import { startOIDC } from "./oidc"; import { password } from "./password"; import { PasswordUser } from "./user"; -import {startOIDC} from "./oidc"; // Read from ".env" file. dotenv.config({ path: path.resolve(__dirname, ".env.local") }); @@ -30,8 +30,8 @@ const test = base.extend<{ user: PasswordUser }>({ }); test("oidc username and password login", async ({ user, page }) => { - await startOIDC(page) + await startOIDC(page); await loginname(page, user.getUsername()); await password(page, user.getPassword()); - await expect(page.locator('pre')).toContainText(user.getUsername()); + await expect(page.locator("pre")).toContainText(user.getUsername()); }); diff --git a/acceptance/tests/oidc.ts b/acceptance/tests/oidc.ts index d85a68fccb..b4981575cc 100644 --- a/acceptance/tests/oidc.ts +++ b/acceptance/tests/oidc.ts @@ -1,5 +1,5 @@ -import { expect, Page } from "@playwright/test"; +import { Page } from "@playwright/test"; export async function startOIDC(page: Page) { - await page.goto("http://localhost:8000/login"); -} \ No newline at end of file + await page.goto("http://localhost:8000/login"); +} diff --git a/acceptance/tests/saml-username-password.spec.ts b/acceptance/tests/saml-username-password.spec.ts index 503fef8326..1fae55c067 100644 --- a/acceptance/tests/saml-username-password.spec.ts +++ b/acceptance/tests/saml-username-password.spec.ts @@ -1,12 +1,8 @@ import { faker } from "@faker-js/faker"; -import {expect, test as base} from "@playwright/test"; +import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; -import { loginname } from "./loginname"; -import { password } from "./password"; import { PasswordUser } from "./user"; -import {startSAML} from "./saml"; -import {selectNewAccount} from "./select-account"; // Read from ".env" file. dotenv.config({ path: path.resolve(__dirname, ".env.local") }); diff --git a/acceptance/tests/saml.ts b/acceptance/tests/saml.ts index ee9b8ced32..5ca3e22a82 100644 --- a/acceptance/tests/saml.ts +++ b/acceptance/tests/saml.ts @@ -1,5 +1,5 @@ -import { expect, Page } from "@playwright/test"; +import { Page } from "@playwright/test"; export async function startSAML(page: Page) { - await page.goto("http://localhost:8001/hello"); -} \ No newline at end of file + await page.goto("http://localhost:8001/hello"); +} diff --git a/acceptance/tests/select-account.ts b/acceptance/tests/select-account.ts index ce036ab39b..64bd7cd145 100644 --- a/acceptance/tests/select-account.ts +++ b/acceptance/tests/select-account.ts @@ -1,5 +1,5 @@ -import {Page} from "@playwright/test"; +import { Page } from "@playwright/test"; export async function selectNewAccount(page: Page) { - await page.getByRole('link', {name: 'Add another account'}).click(); + await page.getByRole("link", { name: "Add another account" }).click(); } From dd12b7971e1c9c9603d485639f3c3f324efe4119 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:58:24 +0200 Subject: [PATCH 23/45] chore: correct error message lockout policy --- acceptance/tests/password-screen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/tests/password-screen.ts b/acceptance/tests/password-screen.ts index f207b17035..6dff9a3a8f 100644 --- a/acceptance/tests/password-screen.ts +++ b/acceptance/tests/password-screen.ts @@ -29,7 +29,7 @@ export async function passwordScreen(page: Page, password: string) { export async function passwordScreenExpect(page: Page, password: string) { await expect(page.getByTestId(passwordField)).toHaveValue(password); - await expect(page.getByTestId("error").locator("div")).toContainText("Could not verify password"); + await expect(page.getByTestId("error").locator("div")).toContainText("Failed to authenticate."); } export async function changePasswordScreenExpect( From 8543368dd177945578500910c0606910845ac4bc Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 1 Apr 2025 10:36:22 +0200 Subject: [PATCH 24/45] chore: correct order on pipeline --- .github/workflows/test.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ca1025f6bd..256215acaa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -97,14 +97,6 @@ jobs: run: ZITADEL_DEV_UID=root pnpm run-sink if: ${{ matrix.command == 'test:acceptance' }} - - name: Run SAML SP - run: ZITADEL_DEV_UID=root pnpm run-samlsp - if: ${{ matrix.command == 'test:acceptance' }} - - - name: Run OIDC RP - run: ZITADEL_DEV_UID=root pnpm run-oidcrp - if: ${{ matrix.command == 'test:acceptance' }} - - name: Create Cloud Env File run: | if [ "${{ matrix.command }}" == "test:acceptance:prod" ]; then @@ -118,6 +110,14 @@ jobs: run: pnpm build if: ${{ startsWith(matrix.command, 'test:acceptance') }} + - name: Run SAML SP + run: ZITADEL_DEV_UID=root pnpm run-samlsp + if: ${{ matrix.command == 'test:acceptance' }} + + - name: Run OIDC RP + run: ZITADEL_DEV_UID=root pnpm run-oidcrp + if: ${{ matrix.command == 'test:acceptance' }} + - name: Check id: check run: pnpm ${{ contains(matrix.command, 'test:acceptance') && 'test:acceptance' || matrix.command }} From e43e8502c9c9e914b5090ba0817c1d86c3c4588d Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:54:20 +0200 Subject: [PATCH 25/45] chore: zitadel version --- .github/workflows/test.yml | 4 ++-- packages/zitadel-proto/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 256215acaa..dac5fcee85 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,9 @@ name: Quality on: pull_request: - schedule: + # schedule: # Every morning at 6:00 AM CET - - cron: '0 4 * * *' + # - cron: '0 4 * * *' workflow_dispatch: inputs: target-env: diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index 50c8342287..20f8cd6c57 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -14,7 +14,7 @@ ], "sideEffects": false, "scripts": { - "generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel", + "generate": "buf generate https://github.com/zitadel/zitadel.git#tag=v2.71.6 --path ./proto/zitadel", "clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate" }, "dependencies": { From b89987dd25febbe55cffb2dae4efa8db53b262b4 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:38:18 +0200 Subject: [PATCH 26/45] chore: zitadel version --- CONTRIBUTING.md | 24 ++++++-- .../tests/oidc-username-password.spec.ts | 57 ++++++++++--------- packages/zitadel-proto/package.json | 2 +- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 560afffbe4..c9b10e11e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,11 +47,8 @@ export ZITADEL_DEV_UID="$(id -u)" # Pull images docker compose --file ./acceptance/docker-compose.yaml pull -# Run ZITADEL and configure ./apps/login/.env.local -docker compose --file ./acceptance/docker-compose.yaml run setup - -# Configure your shell to use the environment variables written to ./apps/login/.env.acceptance -export $(cat ./apps/login/.env.acceptance | xargs) +# Run ZITADEL with local notification sink and configure ./apps/login/.env.local +pnpm run-sink ``` ### Developing Against Your ZITADEL Cloud Instance @@ -79,6 +76,23 @@ pnpm dev The application is now available at `http://localhost:3000` +### Adding applications and IDPs + +```sh +# OPTIONAL Run SAML SP +pnpm run-samlsp + +# OPTIONAL Run OIDC RP +pnpm run-oidcrp + +# OPTIONAL Run SAML IDP +pnpm run-samlidp + +# OPTIONAL Run OIDC OP +pnpm run-oidcop +``` + + ### Testing You can execute the following commands `pnpm test` for a single test run or `pnpm test:watch` in the following directories: diff --git a/acceptance/tests/oidc-username-password.spec.ts b/acceptance/tests/oidc-username-password.spec.ts index eba368f8ef..a33e2f9f6c 100644 --- a/acceptance/tests/oidc-username-password.spec.ts +++ b/acceptance/tests/oidc-username-password.spec.ts @@ -1,37 +1,38 @@ -import { faker } from "@faker-js/faker"; -import { test as base, expect } from "@playwright/test"; +import {faker} from "@faker-js/faker"; +import {test as base} from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; -import { loginname } from "./loginname"; -import { startOIDC } from "./oidc"; -import { password } from "./password"; -import { PasswordUser } from "./user"; +import {PasswordUser} from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, ".env.local") }); +dotenv.config({path: path.resolve(__dirname, ".env.local")}); const test = base.extend<{ user: PasswordUser }>({ - user: async ({ page }, use) => { - const user = new PasswordUser({ - email: faker.internet.email(), - isEmailVerified: true, - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - organization: "", - phone: faker.phone.number(), - isPhoneVerified: false, - password: "Password1!", - passwordChangeRequired: false, - }); - await user.ensure(page); - await use(user); - await user.cleanup(); - }, + user: async ({page}, use) => { + const user = new PasswordUser({ + email: faker.internet.email(), + isEmailVerified: true, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + organization: "", + phone: faker.phone.number(), + isPhoneVerified: false, + password: "Password1!", + passwordChangeRequired: false, + }); + await user.ensure(page); + await use(user); + await user.cleanup(); + }, }); -test("oidc username and password login", async ({ user, page }) => { - await startOIDC(page); - await loginname(page, user.getUsername()); - await password(page, user.getPassword()); - await expect(page.locator("pre")).toContainText(user.getUsername()); +test("oidc username and password login", async ({user, page}) => { +//TODO commented out because of relocating into E2E tests + /* + await startOIDC(page); + await loginname(page, user.getUsername()); + await password(page, user.getPassword()); + await expect(page.locator("pre")).toContainText(user.getUsername()); + + */ }); diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index 20f8cd6c57..50c8342287 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -14,7 +14,7 @@ ], "sideEffects": false, "scripts": { - "generate": "buf generate https://github.com/zitadel/zitadel.git#tag=v2.71.6 --path ./proto/zitadel", + "generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel", "clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate" }, "dependencies": { From c74fe93ed94fc55ac533235a7cdc107e6c879e7e Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:58:47 +0200 Subject: [PATCH 27/45] chore: remove oidc and saml tests --- .../tests/oidc-username-password.spec.ts | 38 ------------------- acceptance/tests/oidc.ts | 5 --- .../tests/saml-username-password.spec.ts | 38 ------------------- acceptance/tests/saml.ts | 5 --- 4 files changed, 86 deletions(-) delete mode 100644 acceptance/tests/oidc-username-password.spec.ts delete mode 100644 acceptance/tests/oidc.ts delete mode 100644 acceptance/tests/saml-username-password.spec.ts delete mode 100644 acceptance/tests/saml.ts diff --git a/acceptance/tests/oidc-username-password.spec.ts b/acceptance/tests/oidc-username-password.spec.ts deleted file mode 100644 index a33e2f9f6c..0000000000 --- a/acceptance/tests/oidc-username-password.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {faker} from "@faker-js/faker"; -import {test as base} from "@playwright/test"; -import dotenv from "dotenv"; -import path from "path"; -import {PasswordUser} from "./user"; - -// Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, ".env.local")}); - -const test = base.extend<{ user: PasswordUser }>({ - user: async ({page}, use) => { - const user = new PasswordUser({ - email: faker.internet.email(), - isEmailVerified: true, - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - organization: "", - phone: faker.phone.number(), - isPhoneVerified: false, - password: "Password1!", - passwordChangeRequired: false, - }); - await user.ensure(page); - await use(user); - await user.cleanup(); - }, -}); - -test("oidc username and password login", async ({user, page}) => { -//TODO commented out because of relocating into E2E tests - /* - await startOIDC(page); - await loginname(page, user.getUsername()); - await password(page, user.getPassword()); - await expect(page.locator("pre")).toContainText(user.getUsername()); - - */ -}); diff --git a/acceptance/tests/oidc.ts b/acceptance/tests/oidc.ts deleted file mode 100644 index b4981575cc..0000000000 --- a/acceptance/tests/oidc.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Page } from "@playwright/test"; - -export async function startOIDC(page: Page) { - await page.goto("http://localhost:8000/login"); -} diff --git a/acceptance/tests/saml-username-password.spec.ts b/acceptance/tests/saml-username-password.spec.ts deleted file mode 100644 index 1fae55c067..0000000000 --- a/acceptance/tests/saml-username-password.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { faker } from "@faker-js/faker"; -import { test as base } from "@playwright/test"; -import dotenv from "dotenv"; -import path from "path"; -import { PasswordUser } from "./user"; - -// Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, ".env.local") }); - -const test = base.extend<{ user: PasswordUser }>({ - user: async ({ page }, use) => { - const user = new PasswordUser({ - email: faker.internet.email(), - isEmailVerified: true, - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - organization: "", - phone: faker.phone.number(), - isPhoneVerified: false, - password: "Password1!", - passwordChangeRequired: false, - }); - await user.ensure(page); - await use(user); - await user.cleanup(); - }, -}); - -test("saml username and password login", async ({ user, page }) => { - //TODO commented out because of prefetching while calling ACS of SAML SP - /* - await startSAML(page) - await selectNewAccount(page) - await loginname(page, user.getUsername()); - await password(page, user.getPassword()); - await expect(page.locator('html')).toContainText(user.getUsername()); - */ -}); diff --git a/acceptance/tests/saml.ts b/acceptance/tests/saml.ts deleted file mode 100644 index 5ca3e22a82..0000000000 --- a/acceptance/tests/saml.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Page } from "@playwright/test"; - -export async function startSAML(page: Page) { - await page.goto("http://localhost:8001/hello"); -} From d45e169cc6f8fba14bafbb0f50872edb6e37be9d Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 07:27:59 +0200 Subject: [PATCH 28/45] fmt --- CONTRIBUTING.md | 3 +- .../tests/oidc-username-password.spec.ts | 46 +++++++++---------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9b10e11e6..f69f76f9bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,7 +79,7 @@ The application is now available at `http://localhost:3000` ### Adding applications and IDPs ```sh -# OPTIONAL Run SAML SP +# OPTIONAL Run SAML SP pnpm run-samlsp # OPTIONAL Run OIDC RP @@ -92,7 +92,6 @@ pnpm run-samlidp pnpm run-oidcop ``` - ### Testing You can execute the following commands `pnpm test` for a single test run or `pnpm test:watch` in the following directories: diff --git a/acceptance/tests/oidc-username-password.spec.ts b/acceptance/tests/oidc-username-password.spec.ts index a33e2f9f6c..8702f926ea 100644 --- a/acceptance/tests/oidc-username-password.spec.ts +++ b/acceptance/tests/oidc-username-password.spec.ts @@ -1,34 +1,34 @@ -import {faker} from "@faker-js/faker"; -import {test as base} from "@playwright/test"; +import { faker } from "@faker-js/faker"; +import { test as base } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; -import {PasswordUser} from "./user"; +import { PasswordUser } from "./user"; // Read from ".env" file. -dotenv.config({path: path.resolve(__dirname, ".env.local")}); +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); const test = base.extend<{ user: PasswordUser }>({ - user: async ({page}, use) => { - const user = new PasswordUser({ - email: faker.internet.email(), - isEmailVerified: true, - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - organization: "", - phone: faker.phone.number(), - isPhoneVerified: false, - password: "Password1!", - passwordChangeRequired: false, - }); - await user.ensure(page); - await use(user); - await user.cleanup(); - }, + user: async ({ page }, use) => { + const user = new PasswordUser({ + email: faker.internet.email(), + isEmailVerified: true, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + organization: "", + phone: faker.phone.number(), + isPhoneVerified: false, + password: "Password1!", + passwordChangeRequired: false, + }); + await user.ensure(page); + await use(user); + await user.cleanup(); + }, }); -test("oidc username and password login", async ({user, page}) => { -//TODO commented out because of relocating into E2E tests - /* +test("oidc username and password login", async ({ user, page }) => { + //TODO commented out because of relocating into E2E tests + /* await startOIDC(page); await loginname(page, user.getUsername()); await password(page, user.getPassword()); From 2738d6547cb195d1e36cfce31e4a80433af489f8 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 07:28:13 +0200 Subject: [PATCH 29/45] proto ref --- packages/zitadel-proto/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index 50c8342287..7322266c18 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -14,7 +14,7 @@ ], "sideEffects": false, "scripts": { - "generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel", + "generate": "buf generate https://github.com/zitadel/zitadel.git#ref=02617cf17fdde849378c1a6b5254bbfb2745b164 --path ./proto/zitadel", "clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate" }, "dependencies": { From d6b488d3f4b1e1b8f2400b65e8510a66180e95bf Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 09:02:20 +0200 Subject: [PATCH 30/45] feat: publish standalone docker image --- .github/workflows/docker.yml | 42 +++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 773a82a846..19e9c975cf 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,10 +4,15 @@ on: push: branches: - main + workflow_dispatch: jobs: build: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + arch: [amd64,arm64] steps: - name: Check out code uses: actions/checkout@v4 @@ -39,7 +44,14 @@ jobs: with: driver-opts: 'image=moby/buildkit:v0.11.6' - - name: Login + - name: Login Public + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login Private uses: docker/login-action@v3 with: registry: ${{ secrets.DOCKER_REGISTRY }} @@ -50,9 +62,13 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ secrets.DOCKER_IMAGE }} - # generate Docker tags based on the following events/attributes - tags: type=sha + images: | + ghcr.io/zitadel/login + ${{ secrets.DOCKER_IMAGE }} + tags: | + type=edge + type=ref + type=sha - name: Install dependencies run: pnpm install @@ -69,8 +85,24 @@ jobs: timeout-minutes: 10 with: context: . + push: true cache-from: type=gha cache-to: type=gha,mode=max + platforms: linux/${{ matrix.arch }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - push: true + outputs: type=image,name=${{ inputs.build_image_name }},push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p /tmp/digests/app + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/app/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.arch }} + path: /tmp/digests + if-no-files-found: error + retention-days: 1 From 54da5db318d4d35c2370646a19d0ada210785a21 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 09:23:40 +0200 Subject: [PATCH 31/45] fix outputs --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 19e9c975cf..18185e7454 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -91,7 +91,7 @@ jobs: platforms: linux/${{ matrix.arch }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ inputs.build_image_name }},push-by-digest=true,name-canonical=true,push=true + outputs: type=image,push-by-digest=true,name-canonical=true,push=true - name: Export digest run: | From 6e1fb3ed666b620264921300365c1d28776a6ab5 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 09:25:41 +0200 Subject: [PATCH 32/45] copilot review --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 18185e7454..3e28b34e9a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -91,7 +91,7 @@ jobs: platforms: linux/${{ matrix.arch }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,push-by-digest=true,name-canonical=true,push=true + outputs: type=image,push-by-digest=true,name-canonical=true - name: Export digest run: | From 15cb84daaab4a77368339a7089e83e7aeec915dd Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 09:31:03 +0200 Subject: [PATCH 33/45] remove arch matrix --- .github/workflows/docker.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3e28b34e9a..7d59a1c3ff 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,10 +9,6 @@ on: jobs: build: runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - arch: [amd64,arm64] steps: - name: Check out code uses: actions/checkout@v4 @@ -88,7 +84,6 @@ jobs: push: true cache-from: type=gha cache-to: type=gha,mode=max - platforms: linux/${{ matrix.arch }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} outputs: type=image,push-by-digest=true,name-canonical=true @@ -102,7 +97,7 @@ jobs: - name: Upload digest uses: actions/upload-artifact@v4 with: - name: digests-${{ matrix.arch }} + name: digests path: /tmp/digests if-no-files-found: error retention-days: 1 From 08235328508b0e0101f3c9febad5f48ae5b8aee9 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 10:56:39 +0200 Subject: [PATCH 34/45] test workflow --- .github/workflows/docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7d59a1c3ff..f28853f2ab 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - publish-image workflow_dispatch: jobs: From ba3acceb5ae7c78916026c2bd360ab3691ae0311 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 11:21:51 +0200 Subject: [PATCH 35/45] fix ref tag --- .github/workflows/docker.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f28853f2ab..786a1152db 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -64,7 +64,9 @@ jobs: ${{ secrets.DOCKER_IMAGE }} tags: | type=edge - type=ref + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr type=sha - name: Install dependencies From c24654c17a1bfbff5e560b3b911091c73e991a42 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 12:32:17 +0200 Subject: [PATCH 36/45] remove proto ref --- packages/zitadel-proto/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index 7322266c18..50c8342287 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -14,7 +14,7 @@ ], "sideEffects": false, "scripts": { - "generate": "buf generate https://github.com/zitadel/zitadel.git#ref=02617cf17fdde849378c1a6b5254bbfb2745b164 --path ./proto/zitadel", + "generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel", "clean": "rm -rf zitadel .turbo node_modules google protoc-gen-openapiv2 validate" }, "dependencies": { From d7c433e989c03ec73cfc110bef8ed811fa036f54 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 12:36:08 +0200 Subject: [PATCH 37/45] write packages permission --- .github/workflows/docker.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 786a1152db..670131d94c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -7,6 +7,9 @@ on: - publish-image workflow_dispatch: +permissions: + packages: write + jobs: build: runs-on: ubuntu-latest From 58b91a01f1099bf0d6dbae9d66cf127a870c6ffd Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 12:40:26 +0200 Subject: [PATCH 38/45] no output --- .github/workflows/docker.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 670131d94c..f9fd53f024 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -92,7 +92,6 @@ jobs: cache-to: type=gha,mode=max tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,push-by-digest=true,name-canonical=true - name: Export digest run: | From 2aa578dac2e26921ab789bda402ecd6863a30aec Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Wed, 2 Apr 2025 12:45:34 +0200 Subject: [PATCH 39/45] cleanup --- .github/workflows/docker.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f9fd53f024..36c80b399d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - publish-image workflow_dispatch: permissions: From 9ebb07bc004ec8dcb699139487d944e9973bb748 Mon Sep 17 00:00:00 2001 From: Fuchsoria Date: Mon, 7 Apr 2025 23:27:23 +0200 Subject: [PATCH 40/45] Add russian language --- apps/login/locales/ru.json | 196 +++++++++++++++++++++++++++++++++++++ apps/login/src/lib/i18n.ts | 4 + 2 files changed, 200 insertions(+) create mode 100644 apps/login/locales/ru.json diff --git a/apps/login/locales/ru.json b/apps/login/locales/ru.json new file mode 100644 index 0000000000..c52047897a --- /dev/null +++ b/apps/login/locales/ru.json @@ -0,0 +1,196 @@ +{ + "common": { + "back": "Назад" + }, + "accounts": { + "title": "Аккаунты", + "description": "Выберите аккаунт, который хотите использовать.", + "addAnother": "Добавить другой аккаунт", + "noResults": "Аккаунты не найдены" + }, + "loginname": { + "title": "С возвращением!", + "description": "Введите свои данные для входа.", + "register": "Зарегистрировать нового пользователя" + }, + "password": { + "verify": { + "title": "Пароль", + "description": "Введите ваш пароль.", + "resetPassword": "Сбросить пароль", + "submit": "Продолжить" + }, + "set": { + "title": "Установить пароль", + "description": "Установите пароль для вашего аккаунта", + "codeSent": "Код отправлен на ваш адрес электронной почты.", + "noCodeReceived": "Не получили код?", + "resend": "Отправить код повторно", + "submit": "Продолжить" + }, + "change": { + "title": "Изменить пароль", + "description": "Установите пароль для вашего аккаунта", + "submit": "Продолжить" + } + }, + "idp": { + "title": "Войти через SSO", + "description": "Выберите одного из провайдеров для входа", + "signInWithApple": "Войти через Apple", + "signInWithGoogle": "Войти через Google", + "signInWithAzureAD": "Войти через AzureAD", + "signInWithGithub": "Войти через GitHub", + "signInWithGitlab": "Войти через GitLab", + "loginSuccess": { + "title": "Вход выполнен успешно", + "description": "Вы успешно вошли в систему!" + }, + "linkingSuccess": { + "title": "Аккаунт привязан", + "description": "Аккаунт успешно привязан!" + }, + "registerSuccess": { + "title": "Регистрация завершена", + "description": "Вы успешно зарегистрировались!" + }, + "loginError": { + "title": "Ошибка входа", + "description": "Произошла ошибка при попытке входа." + }, + "linkingError": { + "title": "Ошибка привязки аккаунта", + "description": "Произошла ошибка при попытке привязать аккаунт." + } + }, + "mfa": { + "verify": { + "title": "Подтвердите вашу личность", + "description": "Выберите один из следующих факторов.", + "noResults": "Нет доступных методов двухфакторной аутентификации" + }, + "set": { + "title": "Настройка двухфакторной аутентификации", + "description": "Выберите один из следующих методов.", + "skip": "Пропустить" + } + }, + "otp": { + "verify": { + "title": "Подтверждение 2FA", + "totpDescription": "Введите код из приложения-аутентификатора.", + "smsDescription": "Введите код, полученный по SMS.", + "emailDescription": "Введите код, полученный по email.", + "noCodeReceived": "Не получили код?", + "resendCode": "Отправить код повторно", + "submit": "Продолжить" + }, + "set": { + "title": "Настройка двухфакторной аутентификации", + "totpDescription": "Отсканируйте QR-код в приложении-аутентификаторе.", + "smsDescription": "Введите номер телефона для получения кода по SMS.", + "emailDescription": "Введите email для получения кода.", + "totpRegisterDescription": "Отсканируйте QR-код или перейдите по ссылке вручную.", + "submit": "Продолжить" + } + }, + "passkey": { + "verify": { + "title": "Аутентификация с помощью пасскей", + "description": "Устройство запросит отпечаток пальца, лицо или экранный замок", + "usePassword": "Использовать пароль", + "submit": "Продолжить" + }, + "set": { + "title": "Настройка пасскей", + "description": "Устройство запросит отпечаток пальца, лицо или экранный замок", + "info": { + "description": "Пасскей — метод аутентификации через устройство (отпечаток пальца, Apple FaceID и аналоги).", + "link": "Аутентификация без пароля" + }, + "skip": "Пропустить", + "submit": "Продолжить" + } + }, + "u2f": { + "verify": { + "title": "Подтверждение 2FA", + "description": "Подтвердите аккаунт с помощью устройства." + }, + "set": { + "title": "Настройка двухфакторной аутентификации", + "description": "Настройте устройство как второй фактор.", + "submit": "Продолжить" + } + }, + "register": { + "methods": { + "passkey": "Пасскей", + "password": "Пароль" + }, + "disabled": { + "title": "Регистрация отключена", + "description": "Регистрация недоступна. Обратитесь к администратору." + }, + "missingdata": { + "title": "Недостаточно данных", + "description": "Укажите email, имя и фамилию для регистрации." + }, + "title": "Регистрация", + "description": "Создайте свой аккаунт ZITADEL.", + "selectMethod": "Выберите метод аутентификации", + "agreeTo": "Для регистрации необходимо принять условия:", + "termsOfService": "Условия использования", + "privacyPolicy": "Политика конфиденциальности", + "submit": "Продолжить", + "password": { + "title": "Установить пароль", + "description": "Установите пароль для вашего аккаунта", + "submit": "Продолжить" + } + }, + "invite": { + "title": "Пригласить пользователя", + "description": "Укажите email и имя пользователя для приглашения.", + "info": "Пользователь получит email с инструкциями.", + "notAllowed": "Ваши настройки не позволяют приглашать пользователей.", + "submit": "Продолжить", + "success": { + "title": "Пользователь приглашён", + "description": "Письмо успешно отправлено.", + "verified": "Пользователь приглашён и уже подтвердил email.", + "notVerifiedYet": "Пользователь приглашён. Он получит email с инструкциями.", + "submit": "Пригласить другого пользователя" + } + }, + "signedin": { + "title": "Добро пожаловать, {user}!", + "description": "Вы вошли в систему.", + "continue": "Продолжить" + }, + "verify": { + "userIdMissing": "Не указан userId!", + "success": "Пользователь успешно подтверждён.", + "setupAuthenticator": "Настроить аутентификатор", + "verify": { + "title": "Подтверждение пользователя", + "description": "Введите код из письма подтверждения.", + "noCodeReceived": "Не получили код?", + "resendCode": "Отправить код повторно", + "submit": "Продолжить" + } + }, + "authenticator": { + "title": "Выбор метода аутентификации", + "description": "Выберите предпочитаемый метод аутентификации", + "noMethodsAvailable": "Нет доступных методов аутентификации", + "allSetup": "Аутентификатор уже настроен!", + "linkWithIDP": "или привязать через Identity Provider" + }, + "error": { + "unknownContext": "Не удалось получить контекст пользователя. Укажите имя пользователя или loginName в параметрах поиска.", + "sessionExpired": "Ваша сессия истекла. Войдите снова.", + "failedLoading": "Ошибка загрузки данных. Попробуйте ещё раз.", + "tryagain": "Попробовать снова" + } +} diff --git a/apps/login/src/lib/i18n.ts b/apps/login/src/lib/i18n.ts index b97770e953..5a101dcc8f 100644 --- a/apps/login/src/lib/i18n.ts +++ b/apps/login/src/lib/i18n.ts @@ -28,6 +28,10 @@ export const LANGS: Lang[] = [ name: "简体中文", code: "zh", }, + { + name: "Русский", + code: "ru", + }, ]; export const LANGUAGE_COOKIE_NAME = "NEXT_LOCALE"; From 9e956bbc425c23c3cc348b0099a69f348ce2ed13 Mon Sep 17 00:00:00 2001 From: Fuchsoria Date: Sat, 12 Apr 2025 21:17:17 +0200 Subject: [PATCH 41/45] Add arm64 platform --- .github/workflows/docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 36c80b399d..9485e82977 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -87,6 +87,7 @@ jobs: with: context: . push: true + platforms: linux/amd64,linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max tags: ${{ steps.meta.outputs.tags }} From 8f6c1578d29a9381b0e0a441daea358abfd92593 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 00:19:04 +0000 Subject: [PATCH 42/45] chore(deps): bump golang.org/x/crypto in /acceptance/oidc Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.32.0 to 0.35.0. - [Commits](https://github.com/golang/crypto/compare/v0.32.0...v0.35.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.35.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- acceptance/oidc/go.mod | 4 ++-- acceptance/oidc/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/acceptance/oidc/go.mod b/acceptance/oidc/go.mod index b251c2485c..f2cda3058e 100644 --- a/acceptance/oidc/go.mod +++ b/acceptance/oidc/go.mod @@ -19,8 +19,8 @@ require ( go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel/metric v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.29.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sys v0.29.0 // indirect + golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect ) diff --git a/acceptance/oidc/go.sum b/acceptance/oidc/go.sum index 5037a5b30a..33244ea6eb 100644 --- a/acceptance/oidc/go.sum +++ b/acceptance/oidc/go.sum @@ -48,15 +48,15 @@ go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2 go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From d50dc746c7232c9d15887354d309ae201aef5e31 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 15 Apr 2025 09:39:42 +0200 Subject: [PATCH 43/45] fix: 405 --- .../app/(login)/authenticator/set/page.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 793cb6714d..8240023c2d 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -157,17 +157,20 @@ export default async function Page(props: { > )} -
-

{t("linkWithIDP")}

-
- {loginSettings?.allowExternalIdp && identityProviders && ( - + <> + {identityProviders.length && ( +
+

{t("linkWithIDP")}

+
+ )} + + )}
From e5c4dde48f855e977e41593ae09be566da88db4a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 16 Apr 2025 11:28:17 +0200 Subject: [PATCH 44/45] fix: next canary --- apps/login/package.json | 2 +- pnpm-lock.yaml | 309 +++++++++++++++++++++------------------- 2 files changed, 164 insertions(+), 147 deletions(-) diff --git a/apps/login/package.json b/apps/login/package.json index c200079530..fe2eedb679 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -47,7 +47,7 @@ "deepmerge": "^4.3.1", "jose": "^5.3.0", "moment": "^2.29.4", - "next": "15.2.0-canary.33", + "next": "15.3.1-canary.9", "next-intl": "^3.25.1", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d47fcbbc4a..ba2eed0adb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,7 +85,7 @@ importers: version: 0.5.7(tailwindcss@3.4.14) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0) + version: 1.3.1(next@15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -108,14 +108,14 @@ importers: specifier: ^2.29.4 version: 2.30.1 next: - specifier: 15.2.0-canary.33 - version: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + specifier: 15.3.1-canary.9 + version: 15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) next-intl: specifier: ^3.25.1 - version: 3.25.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0) + version: 3.25.1(next@15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 0.2.1(next@15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -604,8 +604,8 @@ packages: '@cypress/xvfb@1.2.4': resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} - '@emnapi/runtime@1.3.1': - resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} @@ -1001,107 +1001,112 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + '@img/sharp-darwin-arm64@0.34.1': + resolution: {integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + '@img/sharp-darwin-x64@0.34.1': + resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + '@img/sharp-libvips-darwin-arm64@1.1.0': + resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + '@img/sharp-libvips-darwin-x64@1.1.0': + resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + '@img/sharp-libvips-linux-arm64@1.1.0': + resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + '@img/sharp-libvips-linux-arm@1.1.0': + resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + '@img/sharp-libvips-linux-ppc64@1.1.0': + resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.1.0': + resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + '@img/sharp-libvips-linux-x64@1.1.0': + resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + '@img/sharp-linux-arm64@0.34.1': + resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + '@img/sharp-linux-arm@0.34.1': + resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + '@img/sharp-linux-s390x@0.34.1': + resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + '@img/sharp-linux-x64@0.34.1': + resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + '@img/sharp-linuxmusl-arm64@0.34.1': + resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + '@img/sharp-linuxmusl-x64@0.34.1': + resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + '@img/sharp-wasm32@0.34.1': + resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + '@img/sharp-win32-ia32@0.34.1': + resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + '@img/sharp-win32-x64@0.34.1': + resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -1141,56 +1146,56 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@next/env@15.2.0-canary.33': - resolution: {integrity: sha512-y3EPM+JYKU8t2K+i6bc0QrotEZVGpqu9eVjprj4cfS8QZyZcL54s+W9aGB0TBuGavU9tQdZ50W186+toeMV+hw==} + '@next/env@15.3.1-canary.9': + resolution: {integrity: sha512-ahnXk9D1SECEeq6KhZBAkhuijmvONJqXoAmcmyVcVekq+u5I7LAQe8A7AFTSU0d5jItwJ+bfnpA6ZDGaXVG2CQ==} '@next/eslint-plugin-next@14.2.18': resolution: {integrity: sha512-KyYTbZ3GQwWOjX3Vi1YcQbekyGP0gdammb7pbmmi25HBUCINzDReyrzCMOJIeZisK1Q3U6DT5Rlc4nm2/pQeXA==} - '@next/swc-darwin-arm64@15.2.0-canary.33': - resolution: {integrity: sha512-+fCdK2KmR6lWoCTk1fSd5pvbiLZHfZF+D/Xdz3xrXw+pbnBtXWLKQrPT0bCtDseMxD31qcOywq5mAApvI3EGpA==} + '@next/swc-darwin-arm64@15.3.1-canary.9': + resolution: {integrity: sha512-/44Wi2KxNg1pz/Q+jNs+m0ze0Lzp7Ian1lFO22B2UcAdgPaThBp/ItBro5G39Oge6n0O0xpp2+HS3YbydJ5lOg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.2.0-canary.33': - resolution: {integrity: sha512-GrrU+tSmeBRow+7bnn7i5M96g3tc28hPH5t5Y65qUXGmmrZwGZN1e1d+8QbXPdAGkvjEPcOkUNQuQVpp1qpYPA==} + '@next/swc-darwin-x64@15.3.1-canary.9': + resolution: {integrity: sha512-jl7BJS/lysYlUa7rYPYKM6udKuXEUoI3e31g+8wMFbLmevivnnowzPc7yCJ3MAQmW0J6jv9U9obo+a2n5wafsQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.2.0-canary.33': - resolution: {integrity: sha512-8RnGxnUpASHoUf6aHUifmZom5b4Ow5nTdCib/CNYXZ6VLuL5ocvmr+DXs/SKzi9h8OHR7JkLwKXHCcF8WyscSg==} + '@next/swc-linux-arm64-gnu@15.3.1-canary.9': + resolution: {integrity: sha512-7Lj6VFzrkO82ojfTBlxIyCMKylgpCpCflekU5sJgD0ooRGcWlWSEVCiUcOSvMPeHW7TiJqTTB2NVzuHhY+yLhA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.2.0-canary.33': - resolution: {integrity: sha512-COyE0LzMuLBZSR+Z/TOGilyJPdwSU588Vt0+o8GoECkoDEnjyuO2s2nHa2kDAcEfUEPkhlo0tErU3mF+8AVOTQ==} + '@next/swc-linux-arm64-musl@15.3.1-canary.9': + resolution: {integrity: sha512-iqZSNVVD+PDASb64m/WD0nCRNXXd3eLIwC/U1YjfYNWwW2I0OJL1bLX0kTp57wQadYMqyv52a96yEaeen+47AQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.2.0-canary.33': - resolution: {integrity: sha512-3Y9lqJs+ftU9jgbLdCtvAvF8MNJsJYGMH7icb8QMs1+yOyHHbmwkZoElKdjwfUWzQ2sX28ywp73GWq4HbrsoUg==} + '@next/swc-linux-x64-gnu@15.3.1-canary.9': + resolution: {integrity: sha512-CLlCDftzkvzx+kqrp6J/lY+K7x9cNFlAwpCUJZxrzX+m5TDwgMLD045z4+Jwj+3gLF3Q+6ubPSZly0q5EwpfRQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.2.0-canary.33': - resolution: {integrity: sha512-FS9iA+RkZlhdWGQEKtsplVBXIYZJUn5nsRB+1UY46b3uaL6dDypu13ODaSwYuAwXGgkrZBVF9AFO3y4biBnPlA==} + '@next/swc-linux-x64-musl@15.3.1-canary.9': + resolution: {integrity: sha512-9CL3IMmfabYNHjr3Z9hw0jKVe/HBd/WhOmWvF4xtbtVHZuQcz74O2O77BPNHe8NEuhG11WiFhJsnbkSbkFJ+sQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.2.0-canary.33': - resolution: {integrity: sha512-Ji9CtBbUx06qvvN/rPohJN2FEFGsUv26F50f2nMRYRwrq3POXDjloGOiRocrjU0ty/cUzCz71qTUfKdmv/ajmg==} + '@next/swc-win32-arm64-msvc@15.3.1-canary.9': + resolution: {integrity: sha512-sITL9Fru9NB00sXC/+gDKpxojAPlVYIwzI1bKzugSgLwnDyydbSnqMgmea1NOr/7nFbeBnWDXpYHgq8T4VX2Bg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.2.0-canary.33': - resolution: {integrity: sha512-hjdbGnkwIZ8zN2vlS6lNsEJO37HRtcEGimzfkruBMsi/DwJBqkJvZbNC/XCJy3HFcU58igncqV52p1IPjmAJAw==} + '@next/swc-win32-x64-msvc@15.3.1-canary.9': + resolution: {integrity: sha512-ickRm1FKSMHaELoN//K1CMEMSTJpclg4KgaAqmv0fDnOvBLsCw/reAoZrT9N+Bt2ToDP4jbwEEhSOmH8uQ1lQg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -3523,8 +3528,8 @@ packages: react: '*' react-dom: '*' - next@15.2.0-canary.33: - resolution: {integrity: sha512-WF8QLeYkakuYwksdWY/F+Bi8tNJfIbiSYk9hCmldn9sNp1lU3lqI1hrW1ynbcMSaXC+qQEr7yol2OdvVZ4nZYQ==} + next@15.3.1-canary.9: + resolution: {integrity: sha512-aCGJOPF7+e3uElbeOLc5BMhlhQ0Q0J9EAOZv47lkm/VsRgjVWh98yYy4KZg7H11YONQX7zEUa2nuD4IVbG/1KQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -4159,6 +4164,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} @@ -4173,8 +4183,8 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + sharp@0.34.1: + resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@1.2.0: @@ -5311,7 +5321,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@emnapi/runtime@1.3.1': + '@emnapi/runtime@1.4.3': dependencies: tslib: 2.8.1 optional: true @@ -5586,79 +5596,82 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} - '@img/sharp-darwin-arm64@0.33.5': + '@img/sharp-darwin-arm64@0.34.1': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-arm64': 1.1.0 optional: true - '@img/sharp-darwin-x64@0.33.5': + '@img/sharp-darwin-x64@0.34.1': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.1.0 optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': + '@img/sharp-libvips-darwin-arm64@1.1.0': optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': + '@img/sharp-libvips-darwin-x64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': + '@img/sharp-libvips-linux-arm64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm@1.0.5': + '@img/sharp-libvips-linux-arm@1.1.0': optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': + '@img/sharp-libvips-linux-ppc64@1.1.0': optional: true - '@img/sharp-libvips-linux-x64@1.0.4': + '@img/sharp-libvips-linux-s390x@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + '@img/sharp-libvips-linux-x64@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': optional: true - '@img/sharp-linux-arm64@0.33.5': + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + optional: true + + '@img/sharp-linux-arm64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-arm64': 1.1.0 optional: true - '@img/sharp-linux-arm@0.33.5': + '@img/sharp-linux-arm@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm': 1.1.0 optional: true - '@img/sharp-linux-s390x@0.33.5': + '@img/sharp-linux-s390x@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.1.0 optional: true - '@img/sharp-linux-x64@0.33.5': + '@img/sharp-linux-x64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.1.0 optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': + '@img/sharp-linuxmusl-arm64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 optional: true - '@img/sharp-linuxmusl-x64@0.33.5': + '@img/sharp-linuxmusl-x64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 optional: true - '@img/sharp-wasm32@0.33.5': + '@img/sharp-wasm32@0.34.1': dependencies: - '@emnapi/runtime': 1.3.1 + '@emnapi/runtime': 1.4.3 optional: true - '@img/sharp-win32-ia32@0.33.5': + '@img/sharp-win32-ia32@0.34.1': optional: true - '@img/sharp-win32-x64@0.33.5': + '@img/sharp-win32-x64@0.34.1': optional: true '@isaacs/cliui@8.0.2': @@ -5720,34 +5733,34 @@ snapshots: - encoding - supports-color - '@next/env@15.2.0-canary.33': {} + '@next/env@15.3.1-canary.9': {} '@next/eslint-plugin-next@14.2.18': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@15.2.0-canary.33': + '@next/swc-darwin-arm64@15.3.1-canary.9': optional: true - '@next/swc-darwin-x64@15.2.0-canary.33': + '@next/swc-darwin-x64@15.3.1-canary.9': optional: true - '@next/swc-linux-arm64-gnu@15.2.0-canary.33': + '@next/swc-linux-arm64-gnu@15.3.1-canary.9': optional: true - '@next/swc-linux-arm64-musl@15.2.0-canary.33': + '@next/swc-linux-arm64-musl@15.3.1-canary.9': optional: true - '@next/swc-linux-x64-gnu@15.2.0-canary.33': + '@next/swc-linux-x64-gnu@15.3.1-canary.9': optional: true - '@next/swc-linux-x64-musl@15.2.0-canary.33': + '@next/swc-linux-x64-musl@15.3.1-canary.9': optional: true - '@next/swc-win32-arm64-msvc@15.2.0-canary.33': + '@next/swc-win32-arm64-msvc@15.3.1-canary.9': optional: true - '@next/swc-win32-x64-msvc@15.2.0-canary.33': + '@next/swc-win32-x64-msvc@15.3.1-canary.9': optional: true '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': @@ -6207,11 +6220,11 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)': + '@vercel/analytics@1.3.1(next@15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + next: 15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) react: 19.0.0 '@vercel/git-hooks@1.0.0': {} @@ -7191,7 +7204,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 @@ -7204,7 +7217,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -7225,7 +7238,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -8332,23 +8345,23 @@ snapshots: negotiator@1.0.0: {} - next-intl@3.25.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0): + next-intl@3.25.1(next@15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 1.0.0 - next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + next: 15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) react: 19.0.0 use-intl: 3.25.1(react@19.0.0) - next-themes@0.2.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next-themes@0.2.1(next@15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) + next: 15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7): + next@15.3.1-canary.9(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7): dependencies: - '@next/env': 15.2.0-canary.33 + '@next/env': 15.3.1-canary.9 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 @@ -8358,17 +8371,17 @@ snapshots: react-dom: 19.0.0(react@19.0.0) styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.2.0-canary.33 - '@next/swc-darwin-x64': 15.2.0-canary.33 - '@next/swc-linux-arm64-gnu': 15.2.0-canary.33 - '@next/swc-linux-arm64-musl': 15.2.0-canary.33 - '@next/swc-linux-x64-gnu': 15.2.0-canary.33 - '@next/swc-linux-x64-musl': 15.2.0-canary.33 - '@next/swc-win32-arm64-msvc': 15.2.0-canary.33 - '@next/swc-win32-x64-msvc': 15.2.0-canary.33 + '@next/swc-darwin-arm64': 15.3.1-canary.9 + '@next/swc-darwin-x64': 15.3.1-canary.9 + '@next/swc-linux-arm64-gnu': 15.3.1-canary.9 + '@next/swc-linux-arm64-musl': 15.3.1-canary.9 + '@next/swc-linux-x64-gnu': 15.3.1-canary.9 + '@next/swc-linux-x64-musl': 15.3.1-canary.9 + '@next/swc-win32-arm64-msvc': 15.3.1-canary.9 + '@next/swc-win32-x64-msvc': 15.3.1-canary.9 '@playwright/test': 1.48.2 sass: 1.80.7 - sharp: 0.33.5 + sharp: 0.34.1 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -8908,6 +8921,9 @@ snapshots: semver@7.6.3: {} + semver@7.7.1: + optional: true + server-only@0.0.1: {} set-blocking@2.0.0: {} @@ -8928,31 +8944,32 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - sharp@0.33.5: + sharp@0.34.1: dependencies: color: 4.2.3 detect-libc: 2.0.3 - semver: 7.6.3 + semver: 7.7.1 optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 + '@img/sharp-darwin-arm64': 0.34.1 + '@img/sharp-darwin-x64': 0.34.1 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.1 + '@img/sharp-linux-arm64': 0.34.1 + '@img/sharp-linux-s390x': 0.34.1 + '@img/sharp-linux-x64': 0.34.1 + '@img/sharp-linuxmusl-arm64': 0.34.1 + '@img/sharp-linuxmusl-x64': 0.34.1 + '@img/sharp-wasm32': 0.34.1 + '@img/sharp-win32-ia32': 0.34.1 + '@img/sharp-win32-x64': 0.34.1 optional: true shebang-command@1.2.0: From 6a62c63df550fe9184c978e74dd600df5ce3a8bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:25:59 +0000 Subject: [PATCH 45/45] chore(deps): bump github.com/golang-jwt/jwt/v4 in /acceptance/saml Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.1 to 4.5.2. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.1...v4.5.2) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- acceptance/saml/go.mod | 2 +- acceptance/saml/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acceptance/saml/go.mod b/acceptance/saml/go.mod index d3716924ab..9986149bfb 100644 --- a/acceptance/saml/go.mod +++ b/acceptance/saml/go.mod @@ -7,7 +7,7 @@ require github.com/crewjam/saml v0.4.14 require ( github.com/beevik/etree v1.5.0 // indirect github.com/crewjam/httperr v0.2.0 // indirect - github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect diff --git a/acceptance/saml/go.sum b/acceptance/saml/go.sum index 2461101e17..3394a39410 100644 --- a/acceptance/saml/go.sum +++ b/acceptance/saml/go.sum @@ -7,8 +7,8 @@ github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1n github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=