diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 47aa4adef0..81f3104065 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,7 +86,7 @@ jobs: actions: write id-token: write with: - ignore-run-cache: ${{ github.event_name == 'workflow_dispatch' }} + ignore-run-cache: ${{ github.event_name == 'workflow_dispatch' || fromJSON(github.run_attempt) > 1 }} node_version: "20" container: @@ -106,7 +106,7 @@ jobs: packages: write id-token: write with: - login_build_image_name: "ghcr.io/zitadel/login-build" + login_build_image_name: "ghcr.io/zitadel/zitadel-login-build" node_version: "20" e2e: @@ -133,5 +133,5 @@ jobs: image_name: "ghcr.io/zitadel/zitadel" google_image_name: "europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel" build_image_name_login: ${{ needs.login-container.outputs.login_build_image }} - image_name_login: "ghcr.io/zitadel/login" - google_image_name_login: europe-docker.pkg.dev/zitadel-common/zitadel-repo/login + image_name_login: "ghcr.io/zitadel/zitadel-login" + google_image_name_login: "europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel-login" diff --git a/.github/workflows/login-container.yml b/.github/workflows/login-container.yml index bce15512af..5cc841bff4 100644 --- a/.github/workflows/login-container.yml +++ b/.github/workflows/login-container.yml @@ -22,6 +22,7 @@ env: default_labels: | org.opencontainers.image.documentation=https://zitadel.com/docs org.opencontainers.image.vendor=CAOS AG + org.opencontainers.image.licenses=MIT jobs: login-container: @@ -29,6 +30,7 @@ jobs: runs-on: depot-ubuntu-22.04-8 permissions: id-token: write + packages: write steps: - uses: actions/checkout@v4 - uses: depot/setup-action@v1 @@ -40,6 +42,8 @@ jobs: with: images: ${{ inputs.login_build_image_name }} labels: ${{ env.default_labels}} + annotations: | + manifest:org.opencontainers.image.licenses=MIT tags: | type=sha,prefix=,suffix=,format=long - name: Login to Docker registry @@ -53,11 +57,14 @@ jobs: env: NODE_VERSION: ${{ inputs.node_version }} with: - workdir: login push: true + provenance: true + sbom: true targets: login-standalone - set: login-standalone.platforms=[linux/amd64,linux/arm64] + set: login-*.context=./login/ project: w47wkxzdtw files: | + ./login/docker-bake.hcl + ./login/docker-bake-release.hcl ./docker-bake.hcl cwd://${{ steps.login-meta.outputs.bake-file }} diff --git a/login/Makefile b/login/Makefile index a6e781374b..05cf704c3f 100644 --- a/login/Makefile +++ b/login/Makefile @@ -14,7 +14,7 @@ export GID := $(id -g) export LOGIN_TEST_ACCEPTANCE_BUILD_CONTEXT := $(LOGIN_DIR)apps/login-test-acceptance export DOCKER_METADATA_OUTPUT_VERSION ?= local -export LOGIN_TAG ?= login:${DOCKER_METADATA_OUTPUT_VERSION} +export LOGIN_TAG ?= zitadel-login:${DOCKER_METADATA_OUTPUT_VERSION} export LOGIN_TEST_UNIT_TAG := login-test-unit:${DOCKER_METADATA_OUTPUT_VERSION} export LOGIN_TEST_INTEGRATION_TAG := login-test-integration:${DOCKER_METADATA_OUTPUT_VERSION} export LOGIN_TEST_ACCEPTANCE_TAG := login-test-acceptance:${DOCKER_METADATA_OUTPUT_VERSION} diff --git a/login/apps/login-test-acceptance/docker-compose-ci.yaml b/login/apps/login-test-acceptance/docker-compose-ci.yaml index 7a531fcf42..6f5963df43 100644 --- a/login/apps/login-test-acceptance/docker-compose-ci.yaml +++ b/login/apps/login-test-acceptance/docker-compose-ci.yaml @@ -16,7 +16,7 @@ services: ZITADEL_ADMIN_USER: zitadel-admin@zitadel.traefik login: - image: "${LOGIN_TAG:-login:local}" + image: "${LOGIN_TAG:-zitadel-login:local}" container_name: acceptance-login labels: - "traefik.enable=true" diff --git a/login/apps/login/src/app/(login)/loginname/page.tsx b/login/apps/login/src/app/(login)/loginname/page.tsx index 6d8f209572..f15f440930 100644 --- a/login/apps/login/src/app/(login)/loginname/page.tsx +++ b/login/apps/login/src/app/(login)/loginname/page.tsx @@ -61,7 +61,7 @@ export default async function Page(props: { return (
-

+

diff --git a/login/apps/login/src/lib/server/loginname.ts b/login/apps/login/src/lib/server/loginname.ts index 68cb345c06..dee740bf4f 100644 --- a/login/apps/login/src/lib/server/loginname.ts +++ b/login/apps/login/src/lib/server/loginname.ts @@ -291,23 +291,25 @@ export async function sendLoginname(command: SendLoginnameCommand) { }; } - const paramsPassword: any = { + const paramsPassword = new URLSearchParams({ loginName: session.factors?.user?.loginName, - }; + }); // TODO: does this have to be checked in loginSettings.allowDomainDiscovery if (command.organization || session.factors?.user?.organizationId) { - paramsPassword.organization = - command.organization ?? session.factors?.user?.organizationId; + paramsPassword.append( + "organization", + command.organization ?? session.factors?.user?.organizationId, + ); } if (command.requestId) { - paramsPassword.requestId = command.requestId; + paramsPassword.append("requestId", command.requestId); } return { - redirect: "/password?" + new URLSearchParams(paramsPassword), + redirect: "/password?" + paramsPassword, }; case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY @@ -318,36 +320,42 @@ export async function sendLoginname(command: SendLoginnameCommand) { }; } - const paramsPasskey: any = { loginName: command.loginName }; + const paramsPasskey = new URLSearchParams({ + loginName: session.factors?.user?.loginName, + }); if (command.requestId) { - paramsPasskey.requestId = command.requestId; + paramsPasskey.append("requestId", command.requestId); } if (command.organization || session.factors?.user?.organizationId) { - paramsPasskey.organization = - command.organization ?? session.factors?.user?.organizationId; + paramsPasskey.append( + "organization", + command.organization ?? session.factors?.user?.organizationId, + ); } - return { redirect: "/passkey?" + new URLSearchParams(paramsPasskey) }; + return { redirect: "/passkey?" + paramsPasskey }; } } else { // prefer passkey in favor of other methods if (methods.authMethodTypes.includes(AuthenticationMethodType.PASSKEY)) { - const passkeyParams: any = { - loginName: command.loginName, + const passkeyParams = new URLSearchParams({ + loginName: session.factors?.user?.loginName, altPassword: `${methods.authMethodTypes.includes(1)}`, // show alternative password option - }; + }); if (command.requestId) { - passkeyParams.requestId = command.requestId; + passkeyParams.append("requestId", command.requestId); } if (command.organization || session.factors?.user?.organizationId) { - passkeyParams.organization = - command.organization ?? session.factors?.user?.organizationId; + passkeyParams.append( + "organization", + command.organization ?? session.factors?.user?.organizationId, + ); } - return { redirect: "/passkey?" + new URLSearchParams(passkeyParams) }; + return { redirect: "/passkey?" + passkeyParams }; } else if ( methods.authMethodTypes.includes(AuthenticationMethodType.IDP) ) { @@ -356,19 +364,23 @@ export async function sendLoginname(command: SendLoginnameCommand) { methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD) ) { // user has no passkey setup and login settings allow passkeys - const paramsPasswordDefault: any = { loginName: command.loginName }; + const paramsPasswordDefault = new URLSearchParams({ + loginName: session.factors?.user?.loginName, + }); if (command.requestId) { - paramsPasswordDefault.requestId = command.requestId; + paramsPasswordDefault.append("requestId", command.requestId); } if (command.organization || session.factors?.user?.organizationId) { - paramsPasswordDefault.organization = - command.organization ?? session.factors?.user?.organizationId; + paramsPasswordDefault.append( + "organization", + command.organization ?? session.factors?.user?.organizationId, + ); } return { - redirect: "/password?" + new URLSearchParams(paramsPasswordDefault), + redirect: "/password?" + paramsPasswordDefault, }; } } diff --git a/login/apps/login/src/lib/session.ts b/login/apps/login/src/lib/session.ts index 9698c4c4ba..8c2548b8fb 100644 --- a/login/apps/login/src/lib/session.ts +++ b/login/apps/login/src/lib/session.ts @@ -13,7 +13,6 @@ import { type LoadMostRecentSessionParams = { serviceUrl: string; - sessionParams: { loginName?: string; organization?: string; diff --git a/login/apps/login/src/lib/zitadel.ts b/login/apps/login/src/lib/zitadel.ts index 483d4e4ac9..442c2be85c 100644 --- a/login/apps/login/src/lib/zitadel.ts +++ b/login/apps/login/src/lib/zitadel.ts @@ -854,15 +854,15 @@ export async function searchUsers({ const emailQuery = EmailQuery(searchValue); emailAndPhoneQueries.push(emailQuery); } else { - const emailAndPhoneOrQueries: SearchQuery[] = []; + const orQuery: SearchQuery[] = []; const emailQuery = EmailQuery(searchValue); - emailAndPhoneOrQueries.push(emailQuery); + orQuery.push(emailQuery); let phoneQuery; if (searchValue.length <= 20) { phoneQuery = PhoneQuery(searchValue); - emailAndPhoneOrQueries.push(phoneQuery); + orQuery.push(phoneQuery); } emailAndPhoneQueries.push( @@ -870,7 +870,7 @@ export async function searchUsers({ query: { case: "orQuery", value: { - queries: emailAndPhoneOrQueries, + queries: orQuery, }, }, }), @@ -903,7 +903,7 @@ export async function searchUsers({ } if (emailOrPhoneResult.result.length == 1) { - return loginNameResult; + return emailOrPhoneResult; } return { error: "User not found in the system" }; diff --git a/login/docker-bake-release.hcl b/login/docker-bake-release.hcl new file mode 100644 index 0000000000..51e1c194f6 --- /dev/null +++ b/login/docker-bake-release.hcl @@ -0,0 +1,3 @@ +target "release" { + platforms = ["linux/amd64", "linux/arm64"] +} diff --git a/login/docker-bake.hcl b/login/docker-bake.hcl index 9520b752fa..b60fd7270a 100644 --- a/login/docker-bake.hcl +++ b/login/docker-bake.hcl @@ -6,12 +6,18 @@ variable "DOCKERFILES_DIR" { default = "dockerfiles/" } +# The release target is overwritten in docker-bake-release.hcl +# It makes sure the image is built for multiple platforms. +# By default the platforms property is empty, so images are only built for the current bake runtime platform. +target "release" {} + # typescript-proto-client is used to generate the client code for the login service. # It is not login-prefixed, so it is easily extendable. # To extend this bake-file.hcl, set the context of all login-prefixed targets to a different directory. # For example docker bake --file login/docker-bake.hcl --file docker-bake.hcl --set login-*.context=./login/ # The zitadel repository uses this to generate the client and the mock server from local proto files. target "typescript-proto-client" { + inherits = ["release"] dockerfile = "${DOCKERFILES_DIR}typescript-proto-client.Dockerfile" contexts = { # We directly generate and download the client server-side with buf, so we don't need the proto files @@ -37,6 +43,7 @@ target "login-typescript-proto-client-out" { # For example docker bake --file login/docker-bake.hcl --file docker-bake.hcl --set login-*.context=./login/ # The zitadel repository uses this to generate the client and the mock server from local proto files. target "proto-files" { + inherits = ["release"] dockerfile = "${DOCKERFILES_DIR}proto-files.Dockerfile" contexts = { login-pnpm = "target:login-pnpm" @@ -48,6 +55,7 @@ variable "NODE_VERSION" { } target "login-pnpm" { + inherits = ["release"] dockerfile = "${DOCKERFILES_DIR}login-pnpm.Dockerfile" args = { NODE_VERSION = "${NODE_VERSION}" @@ -76,6 +84,7 @@ target "login-test-unit" { } target "login-client" { + inherits = ["release"] dockerfile = "${DOCKERFILES_DIR}login-client.Dockerfile" contexts = { login-pnpm = "target:login-pnpm" @@ -93,7 +102,7 @@ target "core-mock" { contexts = { protos = "target:proto-files" } - tags = ["${LOGIN_CORE_MOCK_TAG}"] + tags = ["${LOGIN_CORE_MOCK_TAG}"] } variable "LOGIN_TEST_INTEGRATION_TAG" { @@ -105,7 +114,7 @@ target "login-test-integration" { contexts = { login-pnpm = "target:login-pnpm" } - tags = ["${LOGIN_TEST_INTEGRATION_TAG}"] + tags = ["${LOGIN_TEST_INTEGRATION_TAG}"] } variable "LOGIN_TEST_ACCEPTANCE_TAG" { @@ -117,28 +126,33 @@ target "login-test-acceptance" { contexts = { login-pnpm = "target:login-pnpm" } - tags = ["${LOGIN_TEST_ACCEPTANCE_TAG}"] + tags = ["${LOGIN_TEST_ACCEPTANCE_TAG}"] } variable "LOGIN_TAG" { default = "zitadel-login:local" } -target "docker-metadata-action" {} +target "docker-metadata-action" { + # In the pipeline, this target is overwritten by the docker metadata action. + tags = ["${LOGIN_TAG}"] +} # We run integration and acceptance tests against the next standalone server for docker. target "login-standalone" { - inherits = ["docker-metadata-action"] + inherits = [ + "docker-metadata-action", + "release", + ] dockerfile = "${DOCKERFILES_DIR}login-standalone.Dockerfile" contexts = { login-client = "target:login-client" } - tags = ["${LOGIN_TAG}"] } target "login-standalone-out" { - inherits = ["login-standalone"] - target = "login-standalone-out" + inherits = ["login-standalone"] + target = "login-standalone-out" output = [ "type=local,dest=${LOGIN_DIR}apps/login/standalone" ]