mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:27:32 +00:00
simplify
This commit is contained in:
@@ -46,17 +46,23 @@ The application is now available at `http://localhost:3000`
|
||||
Configure apps/login/.env.local to target the Zitadel instance of your choice.
|
||||
The login app live-reloads on changes, so you can start developing right away.
|
||||
|
||||
<!-- Console doesn't load
|
||||
|
||||
### Developing Against Your Local ZITADEL Instance
|
||||
|
||||
The following command uses Docker to run a local ZITADEL instance and the login application in live-reloading dev mode.
|
||||
Additionally, it runs a Traefik reverse proxy that exposes the login at https://localhost with a self-signed certificate.
|
||||
Additionally, it runs a Traefik reverse proxy that exposes the login with a self-signed certificate at https://127.0.0.1.sslip.io
|
||||
127.0.0.1.sslip.io is a special domain that resolves to your localhost, so it's safe to allow your browser to proceed with loading the page.
|
||||
|
||||
```sh
|
||||
# This command runs all dependencies and overwrites the file ./apps/login/.env.test.local.
|
||||
pnpm test:acceptance:setup
|
||||
|
||||
# As soon as the setup container completed successfully, you are ready to run the login application in live-reloading dev mode
|
||||
pnpm dev
|
||||
```
|
||||
-->
|
||||
|
||||
Log in at https://127.0.0.1.sslip.io/ui/v2/login/loginname and use the following credentials:
|
||||
**Loginname**: *zitadel-admin@zitadel.127.0.0.1.sslip.io*
|
||||
**Password**: *Password1!*.
|
||||
|
||||
### Quality Assurance
|
||||
|
||||
|
13
Makefile
13
Makefile
@@ -26,7 +26,7 @@ export LOGIN_TEST_ACCEPTANCE_SAMLSP_TAG := login-test-acceptance-samlsp:${DOCKER
|
||||
export LOGIN_TEST_ACCEPTANCE_SAMLIDP_TAG := login-test-acceptance-samlidp:${DOCKER_METADATA_OUTPUT_VERSION}
|
||||
export POSTGRES_TAG := postgres:17.0-alpine3.19
|
||||
export GOLANG_TAG := golang:1.24-alpine
|
||||
export ZITADEL_TAG ?= ghcr.io/zitadel/zitadel:v3.3.0
|
||||
export ZITADEL_TAG ?= ghcr.io/zitadel/zitadel:02617cf17fdde849378c1a6b5254bbfb2745b164
|
||||
export CORE_MOCK_TAG := login-core-mock:${DOCKER_METADATA_OUTPUT_VERSION}
|
||||
|
||||
.PHONY: login-help
|
||||
@@ -52,10 +52,10 @@ login-test-unit:
|
||||
$(LOGIN_BAKE_CLI_WITH_COMMON_ARGS) login-test-unit
|
||||
|
||||
login-test-integration-build:
|
||||
$(LOGIN_BAKE_CLI_WITH_COMMON_ARGS) login-core-mock login-test-integration login-standalone
|
||||
$(LOGIN_BAKE_CLI_WITH_COMMON_ARGS) core-mock login-test-integration login-standalone
|
||||
|
||||
login-test-integration-dev: login-test-integration-cleanup
|
||||
$(LOGIN_BAKE_CLI_WITH_COMMON_ARGS) login-core-mock && docker compose --file $(LOGIN_DIR)apps/login-test-integration/docker-compose.yaml run --service-ports --rm login-core-mock
|
||||
$(LOGIN_BAKE_CLI_WITH_COMMON_ARGS) core-mock && docker compose --file $(LOGIN_DIR)apps/login-test-integration/docker-compose.yaml run --service-ports --rm core-mock
|
||||
|
||||
login-test-integration-run: login-test-integration-cleanup
|
||||
docker compose --file $(LOGIN_DIR)apps/login-test-integration/docker-compose.yaml run --rm integration
|
||||
@@ -79,8 +79,11 @@ login-test-acceptance-build-compose:
|
||||
login-test-acceptance-build: login-test-acceptance-build-compose login-test-acceptance-build-bake
|
||||
@:
|
||||
|
||||
login-test-acceptance-dev: login-test-acceptance-build-compose login-test-acceptance-cleanup
|
||||
docker compose --file $(LOGIN_DIR)apps/login-test-acceptance/docker-compose.yaml up zitadel setup traefik setup sink
|
||||
login-test-acceptance-env: login-test-acceptance-build-compose login-test-acceptance-cleanup
|
||||
docker compose --file $(LOGIN_DIR)apps/login-test-acceptance/docker-compose.yaml run setup
|
||||
|
||||
login-test-acceptance-dev:
|
||||
docker compose --file $(LOGIN_DIR)apps/login-test-acceptance/docker-compose.yaml up zitadel traefik sink
|
||||
|
||||
login-test-acceptance-run: login-test-acceptance-cleanup
|
||||
docker compose --file $(LOGIN_DIR)apps/login-test-acceptance/docker-compose.yaml --file $(LOGIN_DIR)apps/login-test-acceptance/docker-compose-ci.yaml run --rm --service-ports acceptance
|
||||
|
@@ -9,7 +9,6 @@ services:
|
||||
|
||||
setup:
|
||||
environment:
|
||||
WRITE_ENVIRONMENT_FILE: /login-env/.env
|
||||
ZITADEL_API_DOMAIN: traefik
|
||||
ZITADEL_API_URL: https://traefik
|
||||
LOGIN_BASE_URL: https://traefik/ui/v2/login/
|
||||
@@ -38,6 +37,8 @@ services:
|
||||
- CI
|
||||
- LOGIN_BASE_URL=https://traefik/ui/v2/login/
|
||||
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
volumes:
|
||||
- ../login/.env.test.local:/builder/apps/login/.env.test.local
|
||||
ports:
|
||||
- 9323:9323
|
||||
ipc: "host"
|
||||
|
@@ -5,7 +5,7 @@ services:
|
||||
image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:v3.3.0}"
|
||||
container_name: acceptance-zitadel
|
||||
pull_policy: always
|
||||
command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml'
|
||||
command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --config /zitadel.yaml --steps /zitadel.yaml'
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.zitadel.rule=!PathPrefix(`/ui/v2/login`)"
|
||||
@@ -55,7 +55,7 @@ services:
|
||||
- "traefik.http.routers.login.rule=PathPrefix(`/ui/v2/login`)"
|
||||
- "traefik.http.services.login-service.loadbalancer.server.url=http://host.docker.internal:3000"
|
||||
command:
|
||||
- "--log.level=DEBUG"
|
||||
# - "--log.level=DEBUG"
|
||||
- "--ping"
|
||||
- "--api.insecure=true"
|
||||
- "--providers.docker=true"
|
||||
@@ -70,6 +70,7 @@ services:
|
||||
start_period: "20s"
|
||||
ports:
|
||||
- "443:443"
|
||||
- "8090:8080"
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
extra_hosts:
|
||||
@@ -87,18 +88,16 @@ services:
|
||||
environment:
|
||||
PAT_FILE: /pat/zitadel-admin-sa.pat
|
||||
ZITADEL_API_INTERNAL_URL: http://zitadel:8080
|
||||
WRITE_ENVIRONMENT_FILE: /login-env/.env.local
|
||||
WRITE_TEST_ENVIRONMENT_FILE: /acceptance-env/.env
|
||||
WRITE_ENVIRONMENT_FILE: /login-env/.env.test.local
|
||||
SINK_EMAIL_INTERNAL_URL: http://sink:3333/email
|
||||
SINK_SMS_INTERNAL_URL: http://sink:3333/sms
|
||||
SINK_NOTIFICATION_URL: http://localhost:3333/notification
|
||||
LOGIN_BASE_URL: https://localhost/ui/v2/login/
|
||||
ZITADEL_API_URL: https://localhost
|
||||
ZITADEL_API_DOMAIN: localhost
|
||||
ZITADEL_ADMIN_USER: zitadel-admin@zitadel.localhost
|
||||
LOGIN_BASE_URL: https://127.0.0.1.sslip.io/ui/v2/login/
|
||||
ZITADEL_API_URL: https://127.0.0.1.sslip.io
|
||||
ZITADEL_API_DOMAIN: 127.0.0.1.sslip.io
|
||||
ZITADEL_ADMIN_USER: zitadel-admin@zitadel.127.0.0.1.sslip.io
|
||||
volumes:
|
||||
- ./pat:/pat # Read the PAT file from zitadels setup
|
||||
- ./env:/acceptance-env # Write the environment variables file for the tests
|
||||
- ../login:/login-env # Write the environment variables file for the login
|
||||
depends_on:
|
||||
traefik:
|
||||
|
2
apps/login-test-acceptance/env/.gitignore
vendored
2
apps/login-test-acceptance/env/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitkeep
|
0
apps/login-test-acceptance/env/.gitkeep
vendored
0
apps/login-test-acceptance/env/.gitkeep
vendored
@@ -3,6 +3,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test:acceptance": "pnpm exec playwright",
|
||||
"test:acceptance:env": "cd ../.. && make login-test-acceptance-env",
|
||||
"test:acceptance:setup": "cd ../.. && make login-test-acceptance-dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@@ -2,7 +2,7 @@ import { defineConfig, devices } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, "./env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../login/.env.test.local") });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -ex pipefail
|
||||
set -e pipefail
|
||||
|
||||
PAT_FILE=${PAT_FILE:-./pat/zitadel-admin-sa.pat}
|
||||
LOGIN_BASE_URL=${LOGIN_BASE_URL:-"http://localhost:3000"}
|
||||
@@ -12,8 +12,7 @@ ZITADEL_API_INTERNAL_URL="${ZITADEL_API_INTERNAL_URL:-${ZITADEL_API_URL}}"
|
||||
SINK_EMAIL_INTERNAL_URL="${SINK_EMAIL_INTERNAL_URL:-"http://sink:3333/email"}"
|
||||
SINK_SMS_INTERNAL_URL="${SINK_SMS_INTERNAL_URL:-"http://sink:3333/sms"}"
|
||||
SINK_NOTIFICATION_URL="${SINK_NOTIFICATION_URL:-"http://localhost:3333/notification"}"
|
||||
WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local}
|
||||
WRITE_TEST_ENVIRONMENT_FILE=${WRITE_TEST_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login-test-acceptance/tests/.env.local}
|
||||
WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.test.local}
|
||||
|
||||
if [ -z "${PAT}" ]; then
|
||||
echo "Reading PAT from file ${PAT_FILE}"
|
||||
@@ -59,7 +58,6 @@ echo "Received ServiceAccount Token: ${SA_PAT}"
|
||||
#################################################################
|
||||
|
||||
echo "Writing environment file ${WRITE_ENVIRONMENT_FILE}."
|
||||
echo "Writing environment file ${WRITE_TEST_ENVIRONMENT_FILE}."
|
||||
|
||||
echo "ZITADEL_API_URL=${ZITADEL_API_URL}
|
||||
ZITADEL_SERVICE_USER_TOKEN=${SA_PAT}
|
||||
@@ -71,14 +69,11 @@ LOGIN_BASE_URL=${LOGIN_BASE_URL}
|
||||
NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
ZITADEL_ADMIN_USER=${ZITADEL_ADMIN_USER:-"zitadel-admin@zitadel.localhost"}
|
||||
NEXT_PUBLIC_BASE_PATH=/ui/v2/login
|
||||
" | tee "${WRITE_ENVIRONMENT_FILE}" "${WRITE_TEST_ENVIRONMENT_FILE}" > /dev/null
|
||||
" > ${WRITE_ENVIRONMENT_FILE}
|
||||
|
||||
echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}"
|
||||
cat ${WRITE_ENVIRONMENT_FILE}
|
||||
|
||||
echo "Wrote environment file ${WRITE_TEST_ENVIRONMENT_FILE}"
|
||||
cat ${WRITE_TEST_ENVIRONMENT_FILE}
|
||||
|
||||
#################################################################
|
||||
# SMS provider with HTTP
|
||||
#################################################################
|
||||
|
@@ -9,7 +9,7 @@ import { getCodeFromSink } from "./sink";
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
|
@@ -7,7 +7,7 @@ import { registerWithPasskey, registerWithPassword } from "./register";
|
||||
import { removeUserByUsername } from "./zitadel";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
test("register with password", async ({ page }) => {
|
||||
const username = faker.internet.email();
|
||||
|
@@ -6,7 +6,7 @@ import { loginScreenExpect, loginWithPasskey } from "./login";
|
||||
import { PasskeyUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasskeyUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
|
@@ -7,7 +7,7 @@ import { changePassword } from "./password";
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
|
@@ -8,7 +8,7 @@ import { changePasswordScreen, changePasswordScreenExpect } from "./password-scr
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
|
@@ -8,7 +8,7 @@ import { loginScreenExpect, loginWithPassword, loginWithPasswordAndEmailOTP } fr
|
||||
import { OtpType, PasswordUserWithOTP } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({
|
||||
user: async ({ page }, use) => {
|
||||
|
@@ -8,7 +8,7 @@ import { loginScreenExpect, loginWithPassword, loginWithPasswordAndPhoneOTP } fr
|
||||
import { OtpType, PasswordUserWithOTP } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({
|
||||
user: async ({ page }, use) => {
|
||||
|
@@ -9,7 +9,7 @@ import { resetPasswordScreen, resetPasswordScreenExpect } from "./password-scree
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
|
@@ -8,7 +8,7 @@ import { loginScreenExpect, loginWithPassword, loginWithPasswordAndTOTP } from "
|
||||
import { PasswordUserWithTOTP } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUserWithTOTP; sink: any }>({
|
||||
user: async ({ page }, use) => {
|
||||
|
@@ -10,7 +10,7 @@ import { passwordScreenExpect } from "./password-screen";
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
|
@@ -7,7 +7,7 @@ import { request } from "gaxios";
|
||||
import path from "path";
|
||||
import { OtpType, userProps } from "./user";
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, "../env/.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
export async function addUser(props: userProps) {
|
||||
const body = {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
ExternalDomain: localhost
|
||||
ExternalDomain: 127.0.0.1.sslip.io
|
||||
ExternalSecure: true
|
||||
ExternalPort: 443
|
||||
TLS.Enabled: false
|
||||
|
||||
FirstInstance:
|
||||
PatPath: /pat/zitadel-admin-sa.pat
|
||||
|
@@ -1,5 +1,5 @@
|
||||
NEXT_PUBLIC_BASE_PATH=""
|
||||
ZITADEL_API_URL=http://localhost:22222
|
||||
ZITADEL_SERVICE_USER_TOKEN="yolo"
|
||||
EMAIL_VERIFICATION=true
|
||||
DEBUG=true
|
||||
NEXT_PUBLIC_BASE_PATH=""
|
3
apps/login/.gitignore
vendored
3
apps/login/.gitignore
vendored
@@ -1,3 +1,2 @@
|
||||
custom-config.js
|
||||
.env.local
|
||||
.env.acceptance
|
||||
.env*.local
|
||||
|
@@ -22,7 +22,8 @@
|
||||
"loginname": {
|
||||
"title": "Willkommen zurück!",
|
||||
"description": "Geben Sie Ihre Anmeldedaten ein.",
|
||||
"register": "Neuen Benutzer registrieren"
|
||||
"register": "Neuen Benutzer registrieren",
|
||||
"submit": "Weiter"
|
||||
},
|
||||
"password": {
|
||||
"verify": {
|
||||
@@ -72,6 +73,10 @@
|
||||
"linkingError": {
|
||||
"title": "Konto-Verknüpfung fehlgeschlagen",
|
||||
"description": "Beim Verknüpfen Ihres Kontos ist ein Fehler aufgetreten."
|
||||
},
|
||||
"completeRegister": {
|
||||
"title": "Registrierung abschließen",
|
||||
"description": "Bitte vervollständige die Registrierung, um dein Konto zu erstellen."
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
@@ -149,11 +154,13 @@
|
||||
},
|
||||
"title": "Registrieren",
|
||||
"description": "Erstellen Sie Ihr ZITADEL-Konto.",
|
||||
"noMethodAvailableWarning": "Keine Authentifizierungsmethode verfügbar. Bitte wenden Sie sich an den Administrator.",
|
||||
"selectMethod": "Wählen Sie die Methode, mit der Sie sich authentifizieren möchten",
|
||||
"agreeTo": "Um sich zu registrieren, müssen Sie den Nutzungsbedingungen zustimmen",
|
||||
"termsOfService": "Nutzungsbedingungen",
|
||||
"privacyPolicy": "Datenschutzrichtlinie",
|
||||
"submit": "Weiter",
|
||||
"orUseIDP": "oder verwenden Sie einen Identitätsanbieter",
|
||||
"password": {
|
||||
"title": "Passwort festlegen",
|
||||
"description": "Legen Sie das Passwort für Ihr Konto fest",
|
||||
|
@@ -22,7 +22,8 @@
|
||||
"loginname": {
|
||||
"title": "Welcome back!",
|
||||
"description": "Enter your login data.",
|
||||
"register": "Register new user"
|
||||
"register": "Register new user",
|
||||
"submit": "Continue"
|
||||
},
|
||||
"password": {
|
||||
"verify": {
|
||||
@@ -72,6 +73,10 @@
|
||||
"linkingError": {
|
||||
"title": "Account linking failed",
|
||||
"description": "An error occurred while trying to link your account."
|
||||
},
|
||||
"completeRegister": {
|
||||
"title": "Complete your data",
|
||||
"description": "You need to complete your registration by providing your email address and name."
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
@@ -149,11 +154,13 @@
|
||||
},
|
||||
"title": "Register",
|
||||
"description": "Create your ZITADEL account.",
|
||||
"noMethodAvailableWarning": "No authentication method available. Please contact your administrator.",
|
||||
"selectMethod": "Select the method you would like to authenticate",
|
||||
"agreeTo": "To register you must agree to the terms and conditions",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"submit": "Continue",
|
||||
"orUseIDP": "or use an Identity Provider",
|
||||
"password": {
|
||||
"title": "Set Password",
|
||||
"description": "Set the password for your account",
|
||||
|
@@ -22,7 +22,8 @@
|
||||
"loginname": {
|
||||
"title": "¡Bienvenido de nuevo!",
|
||||
"description": "Introduce tus datos de acceso.",
|
||||
"register": "Registrar nuevo usuario"
|
||||
"register": "Registrar nuevo usuario",
|
||||
"submit": "Continuar"
|
||||
},
|
||||
"password": {
|
||||
"verify": {
|
||||
@@ -72,6 +73,10 @@
|
||||
"linkingError": {
|
||||
"title": "Error al vincular la cuenta",
|
||||
"description": "Ocurrió un error al intentar vincular tu cuenta."
|
||||
},
|
||||
"completeRegister": {
|
||||
"title": "Completar registro",
|
||||
"description": "Para completar el registro, debes establecer una contraseña."
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
@@ -149,11 +154,13 @@
|
||||
},
|
||||
"title": "Registrarse",
|
||||
"description": "Crea tu cuenta ZITADEL.",
|
||||
"noMethodAvailableWarning": "No hay métodos de autenticación disponibles. Por favor, contacta a tu administrador.",
|
||||
"selectMethod": "Selecciona el método con el que deseas autenticarte",
|
||||
"agreeTo": "Para registrarte debes aceptar los términos y condiciones",
|
||||
"termsOfService": "Términos de Servicio",
|
||||
"privacyPolicy": "Política de Privacidad",
|
||||
"submit": "Continuar",
|
||||
"orUseIDP": "o usa un Proveedor de Identidad",
|
||||
"password": {
|
||||
"title": "Establecer Contraseña",
|
||||
"description": "Establece la contraseña para tu cuenta",
|
||||
|
@@ -22,7 +22,8 @@
|
||||
"loginname": {
|
||||
"title": "Bentornato!",
|
||||
"description": "Inserisci i tuoi dati di accesso.",
|
||||
"register": "Registrati come nuovo utente"
|
||||
"register": "Registrati come nuovo utente",
|
||||
"submit": "Continua"
|
||||
},
|
||||
"password": {
|
||||
"verify": {
|
||||
@@ -72,6 +73,10 @@
|
||||
"linkingError": {
|
||||
"title": "Collegamento account fallito",
|
||||
"description": "Si è verificato un errore durante il tentativo di collegare il tuo account."
|
||||
},
|
||||
"completeRegister": {
|
||||
"title": "Completa la registrazione",
|
||||
"description": "Completa la registrazione del tuo account."
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
@@ -149,11 +154,13 @@
|
||||
},
|
||||
"title": "Registrati",
|
||||
"description": "Crea il tuo account ZITADEL.",
|
||||
"noMethodAvailableWarning": "Nessun metodo di autenticazione disponibile. Contatta l'amministratore di sistema per assistenza.",
|
||||
"selectMethod": "Seleziona il metodo con cui desideri autenticarti",
|
||||
"agreeTo": "Per registrarti devi accettare i termini e le condizioni",
|
||||
"termsOfService": "Termini di Servizio",
|
||||
"privacyPolicy": "Informativa sulla Privacy",
|
||||
"submit": "Continua",
|
||||
"orUseIDP": "o usa un Identity Provider",
|
||||
"password": {
|
||||
"title": "Imposta Password",
|
||||
"description": "Imposta la password per il tuo account",
|
||||
|
@@ -22,7 +22,8 @@
|
||||
"loginname": {
|
||||
"title": "Witamy ponownie!",
|
||||
"description": "Wprowadź dane logowania.",
|
||||
"register": "Zarejestruj nowego użytkownika"
|
||||
"register": "Zarejestruj nowego użytkownika",
|
||||
"submit": "Kontynuuj"
|
||||
},
|
||||
"password": {
|
||||
"verify": {
|
||||
@@ -72,6 +73,10 @@
|
||||
"linkingError": {
|
||||
"title": "Powiązanie konta nie powiodło się",
|
||||
"description": "Wystąpił błąd podczas próby powiązania konta."
|
||||
},
|
||||
"completeRegister": {
|
||||
"title": "Ukończ rejestrację",
|
||||
"description": "Ukończ rejestrację swojego konta."
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
@@ -149,11 +154,13 @@
|
||||
},
|
||||
"title": "Rejestracja",
|
||||
"description": "Utwórz konto ZITADEL.",
|
||||
"noMethodAvailableWarning": "Brak dostępnych metod uwierzytelniania. Skontaktuj się z administratorem.",
|
||||
"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",
|
||||
"orUseIDP": "lub użyj dostawcy tożsamości",
|
||||
"password": {
|
||||
"title": "Ustaw hasło",
|
||||
"description": "Ustaw hasło dla swojego konta",
|
||||
|
@@ -22,7 +22,8 @@
|
||||
"loginname": {
|
||||
"title": "С возвращением!",
|
||||
"description": "Введите свои данные для входа.",
|
||||
"register": "Зарегистрировать нового пользователя"
|
||||
"register": "Зарегистрировать нового пользователя",
|
||||
"submit": "Продолжить"
|
||||
},
|
||||
"password": {
|
||||
"verify": {
|
||||
@@ -72,6 +73,10 @@
|
||||
"linkingError": {
|
||||
"title": "Ошибка привязки аккаунта",
|
||||
"description": "Произошла ошибка при попытке привязать аккаунт."
|
||||
},
|
||||
"completeRegister": {
|
||||
"title": "Завершите регистрацию",
|
||||
"description": "Завершите регистрацию вашего аккаунта."
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
@@ -149,11 +154,13 @@
|
||||
},
|
||||
"title": "Регистрация",
|
||||
"description": "Создайте свой аккаунт ZITADEL.",
|
||||
"noMethodAvailableWarning": "Нет доступных методов аутентификации. Обратитесь к администратору.",
|
||||
"selectMethod": "Выберите метод аутентификации",
|
||||
"agreeTo": "Для регистрации необходимо принять условия:",
|
||||
"termsOfService": "Условия использования",
|
||||
"privacyPolicy": "Политика конфиденциальности",
|
||||
"submit": "Продолжить",
|
||||
"orUseIDP": "или используйте Identity Provider",
|
||||
"password": {
|
||||
"title": "Установить пароль",
|
||||
"description": "Установите пароль для вашего аккаунта",
|
||||
|
@@ -22,7 +22,8 @@
|
||||
"loginname": {
|
||||
"title": "欢迎回来!",
|
||||
"description": "请输入您的登录信息。",
|
||||
"register": "注册新用户"
|
||||
"register": "注册新用户",
|
||||
"submit": "继续"
|
||||
},
|
||||
"password": {
|
||||
"verify": {
|
||||
@@ -72,6 +73,10 @@
|
||||
"linkingError": {
|
||||
"title": "账户链接失败",
|
||||
"description": "链接账户时发生错误。"
|
||||
},
|
||||
"completeRegister": {
|
||||
"title": "完成注册",
|
||||
"description": "完成您的账户注册。"
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
@@ -149,11 +154,13 @@
|
||||
},
|
||||
"title": "注册",
|
||||
"description": "创建您的 ZITADEL 账户。",
|
||||
"noMethodAvailableWarning": "没有可用的认证方法。请联系您的系统管理员。",
|
||||
"selectMethod": "选择您想使用的认证方法",
|
||||
"agreeTo": "注册即表示您同意条款和条件",
|
||||
"termsOfService": "服务条款",
|
||||
"privacyPolicy": "隐私政策",
|
||||
"submit": "继续",
|
||||
"orUseIDP": "或使用身份提供者",
|
||||
"password": {
|
||||
"title": "设置密码",
|
||||
"description": "为您的账户设置密码",
|
||||
|
@@ -26,8 +26,6 @@ const secureHeaders = [
|
||||
key: "X-XSS-Protection",
|
||||
value: "1; mode=block",
|
||||
},
|
||||
// img-src vercel.com needed for deploy button,
|
||||
// script-src va.vercel-scripts.com for analytics/vercel scripts
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: `${DEFAULT_CSP} frame-ancestors 'none'`,
|
||||
|
@@ -3,8 +3,8 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec next dev --turbopack",
|
||||
"test:unit": "pnpm exec vitest",
|
||||
"dev": "pnpm next dev --turbopack",
|
||||
"test:unit": "pnpm vitest",
|
||||
"test:unit:watch": "pnpm test:unit --watch",
|
||||
"lint": "pnpm exec next lint && pnpm exec prettier --check .",
|
||||
"lint:fix": "pnpm exec prettier --write .",
|
||||
@@ -31,10 +31,9 @@
|
||||
"clsx": "1.2.1",
|
||||
"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.4.0-canary.3",
|
||||
"next": "15.4.0-canary.86",
|
||||
"next-intl": "^3.25.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"nice-grpc": "2.0.1",
|
||||
@@ -42,7 +41,6 @@
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-hook-form": "7.39.5",
|
||||
"swr": "^2.2.0",
|
||||
"tinycolor2": "1.4.2",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SessionsList } from "@/components/sessions-list";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { getAllSessionCookieIds } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import {
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { UserPlusIcon } from "@heroicons/react/24/outline";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
import Link from "next/link";
|
||||
|
||||
@@ -33,7 +34,6 @@ export default async function Page(props: {
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "accounts" });
|
||||
|
||||
const requestId = searchParams?.requestId;
|
||||
const organization = searchParams?.organization;
|
||||
@@ -71,8 +71,12 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("title")}</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="title" namespace="accounts" />
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="description" namespace="accounts" />
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col w-full space-y-2">
|
||||
<SessionsList sessions={sessions} requestId={requestId} />
|
||||
@@ -81,7 +85,9 @@ export default async function Page(props: {
|
||||
<div className="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
|
||||
<UserPlusIcon className="h-5 w-5" />
|
||||
</div>
|
||||
<span className="text-sm">{t("addAnother")}</span>
|
||||
<span className="text-sm">
|
||||
<Translated i18nKey="addAnother" namespace="accounts" />
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
@@ -3,6 +3,7 @@ import { BackButton } from "@/components/back-button";
|
||||
import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
@@ -17,7 +18,7 @@ import {
|
||||
listAuthenticationMethodTypes,
|
||||
} from "@/lib/zitadel";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
@@ -26,8 +27,6 @@ export default async function Page(props: {
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "authenticator" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { loginName, requestId, organization, sessionId } = searchParams;
|
||||
|
||||
@@ -99,7 +98,11 @@ export default async function Page(props: {
|
||||
!sessionWithData.factors ||
|
||||
!sessionWithData.factors.user
|
||||
) {
|
||||
return <Alert>{tError("unknownContext")}</Alert>;
|
||||
return (
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings({
|
||||
@@ -165,9 +168,13 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("title")}</h1>
|
||||
<h1>
|
||||
<Translated i18nKey="title" namespace="authenticator" />
|
||||
</h1>
|
||||
|
||||
<p className="ztdl-p">{t("description")}</p>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="description" namespace="authenticator" />
|
||||
</p>
|
||||
|
||||
<UserAvatar
|
||||
loginName={sessionWithData.factors?.user?.loginName}
|
||||
@@ -187,7 +194,9 @@ export default async function Page(props: {
|
||||
{loginSettings?.allowExternalIdp && !!identityProviders.length && (
|
||||
<>
|
||||
<div className="py-3 flex flex-col">
|
||||
<p className="ztdl-p text-center">{t("linkWithIDP")}</p>
|
||||
<p className="ztdl-p text-center">
|
||||
<Translated i18nKey="linkWithIDP" namespace="authenticator" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<SignInWithIdp
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { ConsentScreen } from "@/components/consent";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
@@ -7,22 +8,23 @@ import {
|
||||
getDeviceAuthorizationRequest,
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale });
|
||||
|
||||
const userCode = searchParams?.user_code;
|
||||
const requestId = searchParams?.requestId;
|
||||
const organization = searchParams?.organization;
|
||||
|
||||
if (!userCode || !requestId) {
|
||||
return <div>{t("error.noUserCode")}</div>;
|
||||
return (
|
||||
<div>
|
||||
<Translated i18nKey="noUserCode" namespace="error" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const _headers = await headers();
|
||||
@@ -34,7 +36,11 @@ export default async function Page(props: {
|
||||
});
|
||||
|
||||
if (!deviceAuthorizationRequest) {
|
||||
return <div>{t("error.noDeviceRequest")}</div>;
|
||||
return (
|
||||
<div>
|
||||
<Translated i18nKey="noDeviceRequest" namespace="error" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let defaultOrganization;
|
||||
@@ -66,15 +72,19 @@ export default async function Page(props: {
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>
|
||||
{t("device.request.title", {
|
||||
appName: deviceAuthorizationRequest?.appName,
|
||||
})}
|
||||
<Translated
|
||||
i18nKey="request.title"
|
||||
namespace="device"
|
||||
data={{ appName: deviceAuthorizationRequest?.appName }}
|
||||
/>
|
||||
</h1>
|
||||
|
||||
<p className="ztdl-p">
|
||||
{t("device.request.description", {
|
||||
appName: deviceAuthorizationRequest?.appName,
|
||||
})}
|
||||
<Translated
|
||||
i18nKey="request.description"
|
||||
namespace="device"
|
||||
data={{ appName: deviceAuthorizationRequest?.appName }}
|
||||
/>
|
||||
</p>
|
||||
|
||||
<ConsentScreen
|
||||
|
@@ -1,17 +1,15 @@
|
||||
import { DeviceCodeForm } from "@/components/device-code-form";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "device" });
|
||||
|
||||
const userCode = searchParams?.user_code;
|
||||
const organization = searchParams?.organization;
|
||||
@@ -37,8 +35,12 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("usercode.title")}</h1>
|
||||
<p className="ztdl-p">{t("usercode.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="usercode.title" namespace="device" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="usercode.description" namespace="device" />
|
||||
</p>
|
||||
<DeviceCodeForm userCode={userCode}></DeviceCodeForm>
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Boundary } from "@/components/boundary";
|
||||
import { Button } from "@/components/button";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function Error({ error, reset }: any) {
|
||||
@@ -10,8 +10,6 @@ export default function Error({ error, reset }: any) {
|
||||
console.log("logging error:", error);
|
||||
}, [error]);
|
||||
|
||||
const t = useTranslations("error");
|
||||
|
||||
return (
|
||||
<Boundary labels={["Login Error"]} color="red">
|
||||
<div className="space-y-4">
|
||||
@@ -19,7 +17,9 @@ export default function Error({ error, reset }: any) {
|
||||
<strong className="font-bold">Error:</strong> {error?.message}
|
||||
</div>
|
||||
<div>
|
||||
<Button onClick={() => reset()}>{t("tryagain")}</Button>
|
||||
<Button data-i18n-key="error.tryagain" onClick={() => reset()}>
|
||||
<Translated i18nKey="tryagain" namespace="error" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Boundary>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Alert, AlertType } from "@/components/alert";
|
||||
import { ChooseAuthenticatorToLogin } from "@/components/choose-authenticator-to-login";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import {
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
@@ -19,8 +19,6 @@ export default async function Page(props: {
|
||||
params: Promise<{ provider: string }>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "idp" });
|
||||
|
||||
const { organization, userId } = searchParams;
|
||||
|
||||
@@ -77,8 +75,12 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("loginError.title")}</h1>
|
||||
<Alert type={AlertType.ALERT}>{t("loginError.description")}</Alert>
|
||||
<h1>
|
||||
<Translated i18nKey="loginError.title" namespace="idp" />
|
||||
</h1>
|
||||
<Alert type={AlertType.ALERT}>
|
||||
<Translated i18nKey="loginError.description" namespace="idp" />
|
||||
</Alert>
|
||||
|
||||
{userId && authMethods.length && (
|
||||
<>
|
||||
|
@@ -1,51 +1,98 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { IdpSignin } from "@/components/idp-signin";
|
||||
import { completeIDP } from "@/components/idps/pages/complete-idp";
|
||||
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 { Translated } from "@/components/translated";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import {
|
||||
addHuman,
|
||||
addIDPLink,
|
||||
getBrandingSettings,
|
||||
getDefaultOrg,
|
||||
getIDPByID,
|
||||
getLoginSettings,
|
||||
getOrgsByDomain,
|
||||
listUsers,
|
||||
retrieveIDPIntent,
|
||||
updateHuman,
|
||||
} from "@/lib/zitadel";
|
||||
import { ConnectError, 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 { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import {
|
||||
AddHumanUserRequest,
|
||||
AddHumanUserRequestSchema,
|
||||
UpdateHumanUserRequestSchema,
|
||||
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
const ORG_SUFFIX_REGEX = /(?<=@)(.+)/;
|
||||
|
||||
async function resolveOrganizationForUser({
|
||||
organization,
|
||||
addHumanUser,
|
||||
serviceUrl,
|
||||
}: {
|
||||
organization?: string;
|
||||
addHumanUser?: { username?: string };
|
||||
serviceUrl: string;
|
||||
}): Promise<string | undefined> {
|
||||
if (organization) return organization;
|
||||
|
||||
if (addHumanUser?.username && ORG_SUFFIX_REGEX.test(addHumanUser.username)) {
|
||||
const matched = ORG_SUFFIX_REGEX.exec(addHumanUser.username);
|
||||
const suffix = matched?.[1] ?? "";
|
||||
|
||||
const orgs = await getOrgsByDomain({
|
||||
serviceUrl,
|
||||
domain: suffix,
|
||||
});
|
||||
const orgToCheckForDiscovery =
|
||||
orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined;
|
||||
|
||||
if (orgToCheckForDiscovery) {
|
||||
const orgLoginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
organization: orgToCheckForDiscovery,
|
||||
});
|
||||
if (orgLoginSettings?.allowDomainDiscovery) {
|
||||
return orgToCheckForDiscovery;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
params: Promise<{ provider: string }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "idp" });
|
||||
const { id, token, requestId, organization, link } = searchParams;
|
||||
let { id, token, requestId, organization, link } = searchParams;
|
||||
const { provider } = params;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const branding = await getBrandingSettings({
|
||||
let branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
organization,
|
||||
});
|
||||
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg({
|
||||
serviceUrl,
|
||||
});
|
||||
if (org) {
|
||||
organization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!provider || !id || !token) {
|
||||
return loginFailed(branding, "IDP context missing");
|
||||
}
|
||||
@@ -59,18 +106,6 @@ export default async function Page(props: {
|
||||
const { idpInformation, userId } = intent;
|
||||
let { addHumanUser } = intent;
|
||||
|
||||
// sign in user. If user should be linked continue
|
||||
if (userId && !link) {
|
||||
// TODO: update user if idp.options.isAutoUpdate is true
|
||||
|
||||
return loginSuccess(
|
||||
userId,
|
||||
{ idpIntentId: id, idpIntentToken: token },
|
||||
requestId,
|
||||
branding,
|
||||
);
|
||||
}
|
||||
|
||||
if (!idpInformation) {
|
||||
return loginFailed(branding, "IDP information missing");
|
||||
}
|
||||
@@ -79,12 +114,41 @@ export default async function Page(props: {
|
||||
serviceUrl,
|
||||
id: idpInformation.idpId,
|
||||
});
|
||||
|
||||
const options = idp?.config?.options;
|
||||
|
||||
if (!idp) {
|
||||
throw new Error("IDP not found");
|
||||
}
|
||||
|
||||
// sign in user. If user should be linked continue
|
||||
if (userId && !link) {
|
||||
// if auto update is enabled, we will update the user with the new information
|
||||
if (options?.isAutoUpdate && addHumanUser) {
|
||||
try {
|
||||
await updateHuman({
|
||||
serviceUrl,
|
||||
request: create(UpdateHumanUserRequestSchema, {
|
||||
userId: userId,
|
||||
profile: addHumanUser.profile,
|
||||
email: addHumanUser.email,
|
||||
phone: addHumanUser.phone,
|
||||
}),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
// Log the error and continue with the login process
|
||||
console.warn("An error occurred while updating the user:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return loginSuccess(
|
||||
userId,
|
||||
{ idpIntentId: id, idpIntentToken: token },
|
||||
requestId,
|
||||
branding,
|
||||
);
|
||||
}
|
||||
|
||||
if (link) {
|
||||
if (!options?.isLinkingAllowed) {
|
||||
// linking was probably disallowed since the invitation was created
|
||||
@@ -176,36 +240,15 @@ export default async function Page(props: {
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.isAutoCreation) {
|
||||
let orgToRegisterOn: string | undefined = organization;
|
||||
let newUser;
|
||||
|
||||
if (
|
||||
!orgToRegisterOn &&
|
||||
addHumanUser?.username && // username or email?
|
||||
ORG_SUFFIX_REGEX.test(addHumanUser.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
|
||||
const orgs = await getOrgsByDomain({
|
||||
// automatic creation of a user is allowed and data is complete
|
||||
if (options?.isAutoCreation && addHumanUser) {
|
||||
const orgToRegisterOn = await resolveOrganizationForUser({
|
||||
organization,
|
||||
addHumanUser,
|
||||
serviceUrl,
|
||||
domain: suffix,
|
||||
});
|
||||
const orgToCheckForDiscovery =
|
||||
orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined;
|
||||
|
||||
const orgLoginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
organization: orgToCheckForDiscovery,
|
||||
});
|
||||
if (orgLoginSettings?.allowDomainDiscovery) {
|
||||
orgToRegisterOn = orgToCheckForDiscovery;
|
||||
}
|
||||
}
|
||||
|
||||
if (addHumanUser) {
|
||||
let addHumanUserWithOrganization: AddHumanUserRequest;
|
||||
if (orgToRegisterOn) {
|
||||
const organizationSchema = create(OrganizationSchema, {
|
||||
@@ -241,14 +284,47 @@ export default async function Page(props: {
|
||||
: "Could not create user",
|
||||
);
|
||||
}
|
||||
} else if (options?.isCreationAllowed) {
|
||||
// if no user was found, we will create a new user manually / redirect to the registration page
|
||||
const orgToRegisterOn = await resolveOrganizationForUser({
|
||||
organization,
|
||||
addHumanUser,
|
||||
serviceUrl,
|
||||
});
|
||||
|
||||
if (orgToRegisterOn) {
|
||||
branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
organization: orgToRegisterOn,
|
||||
});
|
||||
}
|
||||
|
||||
if (!orgToRegisterOn) {
|
||||
return loginFailed(branding, "No organization found for registration");
|
||||
}
|
||||
|
||||
return completeIDP({
|
||||
branding,
|
||||
idpIntent: { idpIntentId: id, idpIntentToken: token },
|
||||
addHumanUser,
|
||||
organization: orgToRegisterOn,
|
||||
requestId,
|
||||
idpUserId: idpInformation?.userId,
|
||||
idpId: idpInformation?.idpId,
|
||||
idpUserName: idpInformation?.userName,
|
||||
});
|
||||
}
|
||||
|
||||
if (newUser) {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("registerSuccess.title")}</h1>
|
||||
<p className="ztdl-p">{t("registerSuccess.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="registerSuccess.title" namespace="idp" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="registerSuccess.description" namespace="idp" />
|
||||
</p>
|
||||
<IdpSignin
|
||||
userId={newUser.userId}
|
||||
idpIntent={{ idpIntentId: id, idpIntentToken: token }}
|
||||
@@ -258,7 +334,6 @@ export default async function Page(props: {
|
||||
</DynamicTheme>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// return login failed if no linking or creation is allowed and no user was found
|
||||
return loginFailed(branding, "No user found");
|
||||
|
@@ -1,16 +1,14 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "idp" });
|
||||
|
||||
const requestId = searchParams?.requestId;
|
||||
const organization = searchParams?.organization;
|
||||
@@ -33,8 +31,12 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("title")}</h1>
|
||||
<p className="ztdl-p">{t("description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="title" namespace="idp" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="description" namespace="idp" />
|
||||
</p>
|
||||
|
||||
{identityProviders && (
|
||||
<SignInWithIdp
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UsernameForm } from "@/components/username-form";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import {
|
||||
@@ -9,15 +10,12 @@ import {
|
||||
getLoginSettings,
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "loginname" });
|
||||
|
||||
const loginName = searchParams?.loginName;
|
||||
const requestId = searchParams?.requestId;
|
||||
@@ -63,8 +61,12 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("title")}</h1>
|
||||
<p className="ztdl-p">{t("description")}</p>
|
||||
<h1 data-i18n-key="error.tryagain">
|
||||
<Translated i18nKey="title" namespace="loginname" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="description" namespace="loginname" />
|
||||
</p>
|
||||
|
||||
<UsernameForm
|
||||
loginName={loginName}
|
||||
@@ -75,7 +77,7 @@ export default async function Page(props: {
|
||||
submit={submit}
|
||||
allowRegister={!!loginSettings?.allowRegister}
|
||||
>
|
||||
{identityProviders && (
|
||||
{identityProviders && loginSettings?.allowExternalIdp && (
|
||||
<SignInWithIdp
|
||||
identityProviders={identityProviders}
|
||||
requestId={requestId}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SessionsClearList } from "@/components/sessions-clear-list";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { getAllSessionCookieIds } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import {
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
listSessions,
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
|
||||
@@ -30,8 +30,6 @@ export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "logout" });
|
||||
|
||||
const organization = searchParams?.organization;
|
||||
const postLogoutRedirectUri = searchParams?.post_logout_redirect_uri;
|
||||
@@ -67,8 +65,12 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("title")}</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="title" namespace="logout" />
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="description" namespace="logout" />
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col w-full space-y-2">
|
||||
<SessionsClearList
|
||||
|
@@ -1,14 +1,12 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "logout" });
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||
@@ -33,8 +31,12 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("success.title")}</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("success.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="success.title" namespace="logout" />
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="success.description" namespace="logout" />
|
||||
</p>
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
);
|
||||
|
@@ -2,6 +2,7 @@ import { Alert } from "@/components/alert";
|
||||
import { BackButton } from "@/components/back-button";
|
||||
import { ChooseSecondFactor } from "@/components/choose-second-factor";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
@@ -11,16 +12,12 @@ import {
|
||||
getSession,
|
||||
listAuthenticationMethodTypes,
|
||||
} from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "mfa" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { loginName, requestId, organization, sessionId } = searchParams;
|
||||
|
||||
@@ -90,9 +87,13 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("verify.title")}</h1>
|
||||
<h1>
|
||||
<Translated i18nKey="verify.title" namespace="mfa" />
|
||||
</h1>
|
||||
|
||||
<p className="ztdl-p">{t("verify.description")}</p>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="verify.description" namespace="mfa" />
|
||||
</p>
|
||||
|
||||
{sessionFactors && (
|
||||
<UserAvatar
|
||||
@@ -103,7 +104,11 @@ export default async function Page(props: {
|
||||
></UserAvatar>
|
||||
)}
|
||||
|
||||
{!(loginName || sessionId) && <Alert>{tError("unknownContext")}</Alert>}
|
||||
{!(loginName || sessionId) && (
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{sessionFactors ? (
|
||||
<ChooseSecondFactor
|
||||
@@ -114,7 +119,9 @@ export default async function Page(props: {
|
||||
userMethods={sessionFactors.authMethods ?? []}
|
||||
></ChooseSecondFactor>
|
||||
) : (
|
||||
<Alert>{t("verify.noResults")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="verify.noResults" namespace="mfa" />
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center">
|
||||
|
@@ -2,6 +2,7 @@ import { Alert } from "@/components/alert";
|
||||
import { BackButton } from "@/components/back-button";
|
||||
import { ChooseSecondFactorToSetup } from "@/components/choose-second-factor-to-setup";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { Timestamp, timestampDate } from "@zitadel/client";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
function isSessionValid(session: Partial<Session>): {
|
||||
@@ -38,9 +38,6 @@ export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "mfa" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { loginName, checkAfter, force, requestId, organization, sessionId } =
|
||||
searchParams;
|
||||
@@ -119,9 +116,13 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("set.title")}</h1>
|
||||
<h1>
|
||||
<Translated i18nKey="set.title" namespace="mfa" />
|
||||
</h1>
|
||||
|
||||
<p className="ztdl-p">{t("set.description")}</p>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="set.description" namespace="mfa" />
|
||||
</p>
|
||||
|
||||
{sessionWithData && (
|
||||
<UserAvatar
|
||||
@@ -132,9 +133,17 @@ export default async function Page(props: {
|
||||
></UserAvatar>
|
||||
)}
|
||||
|
||||
{!(loginName || sessionId) && <Alert>{tError("unknownContext")}</Alert>}
|
||||
{!(loginName || sessionId) && (
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!valid && <Alert>{tError("sessionExpired")}</Alert>}
|
||||
{!valid && (
|
||||
<Alert>
|
||||
<Translated i18nKey="sessionExpired" namespace="error" />
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{isSessionValid(sessionWithData).valid &&
|
||||
loginSettings &&
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Alert } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { LoginOTP } from "@/components/login-otp";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
getLoginSettings,
|
||||
getSession,
|
||||
} from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
@@ -20,8 +21,6 @@ export default async function Page(props: {
|
||||
const params = await props.params;
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "otp" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||
@@ -81,20 +80,30 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("verify.title")}</h1>
|
||||
<h1>
|
||||
<Translated i18nKey="verify.title" namespace="otp" />
|
||||
</h1>
|
||||
{method === "time-based" && (
|
||||
<p className="ztdl-p">{t("verify.totpDescription")}</p>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="verify.totpDescription" namespace="otp" />
|
||||
</p>
|
||||
)}
|
||||
{method === "sms" && (
|
||||
<p className="ztdl-p">{t("verify.smsDescription")}</p>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="verify.smsDescription" namespace="otp" />
|
||||
</p>
|
||||
)}
|
||||
{method === "email" && (
|
||||
<p className="ztdl-p">{t("verify.emailDescription")}</p>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="verify.emailDescription" namespace="otp" />
|
||||
</p>
|
||||
)}
|
||||
|
||||
{!session && (
|
||||
<div className="py-4">
|
||||
<Alert>{tError("unknownContext")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { BackButton } from "@/components/back-button";
|
||||
import { Button, ButtonVariants } from "@/components/button";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { TotpRegister } from "@/components/totp-register";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
@@ -14,7 +15,6 @@ import {
|
||||
registerTOTP,
|
||||
} from "@/lib/zitadel";
|
||||
import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -25,9 +25,6 @@ export default async function Page(props: {
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "otp" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { loginName, organization, sessionId, requestId, checkAfter } =
|
||||
searchParams;
|
||||
@@ -128,10 +125,14 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("set.title")}</h1>
|
||||
<h1>
|
||||
<Translated i18nKey="set.title" namespace="otp" />
|
||||
</h1>
|
||||
{!session && (
|
||||
<div className="py-4">
|
||||
<Alert>{tError("unknownContext")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -152,7 +153,12 @@ export default async function Page(props: {
|
||||
|
||||
{totpResponse && "uri" in totpResponse && "secret" in totpResponse ? (
|
||||
<>
|
||||
<p className="ztdl-p">{t("set.totpRegisterDescription")}</p>
|
||||
<p className="ztdl-p">
|
||||
<Translated
|
||||
i18nKey="set.totpRegisterDescription"
|
||||
namespace="otp"
|
||||
/>
|
||||
</p>
|
||||
<div>
|
||||
<TotpRegister
|
||||
uri={totpResponse.uri as string}
|
||||
@@ -186,7 +192,7 @@ export default async function Page(props: {
|
||||
className="self-end"
|
||||
variant={ButtonVariants.Primary}
|
||||
>
|
||||
{t("set.submit")}
|
||||
<Translated i18nKey="set.submit" namespace="otp" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
@@ -1,21 +1,18 @@
|
||||
import { Alert } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { LoginPasskey } from "@/components/login-passkey";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import { getBrandingSettings, getSession } from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "passkey" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { loginName, altPassword, requestId, organization, sessionId } =
|
||||
searchParams;
|
||||
@@ -55,7 +52,9 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("verify.title")}</h1>
|
||||
<h1>
|
||||
<Translated i18nKey="verify.title" namespace="passkey" />
|
||||
</h1>
|
||||
|
||||
{sessionFactors && (
|
||||
<UserAvatar
|
||||
@@ -65,9 +64,15 @@ export default async function Page(props: {
|
||||
searchParams={searchParams}
|
||||
></UserAvatar>
|
||||
)}
|
||||
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="verify.description" namespace="passkey" />
|
||||
</p>
|
||||
|
||||
{!(loginName || sessionId) && <Alert>{tError("unknownContext")}</Alert>}
|
||||
{!(loginName || sessionId) && (
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{(loginName || sessionId) && (
|
||||
<LoginPasskey
|
||||
|
@@ -1,20 +1,17 @@
|
||||
import { Alert, AlertType } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { RegisterPasskey } from "@/components/register-passkey";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import { getBrandingSettings } from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "passkey" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { loginName, prompt, organization, requestId, userId } = searchParams;
|
||||
|
||||
@@ -37,7 +34,9 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("set.title")}</h1>
|
||||
<h1>
|
||||
<Translated i18nKey="set.title" namespace="passkey" />
|
||||
</h1>
|
||||
|
||||
{session && (
|
||||
<UserAvatar
|
||||
@@ -47,24 +46,28 @@ export default async function Page(props: {
|
||||
searchParams={searchParams}
|
||||
></UserAvatar>
|
||||
)}
|
||||
<p className="ztdl-p mb-6 block">{t("set.description")}</p>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="set.description" namespace="passkey" />
|
||||
</p>
|
||||
|
||||
<Alert type={AlertType.INFO}>
|
||||
<span>
|
||||
{t("set.info.description")}
|
||||
<Translated i18nKey="set.info.description" namespace="passkey" />
|
||||
<a
|
||||
className="text-primary-light-500 dark:text-primary-dark-500 hover:text-primary-light-300 hover:dark:text-primary-dark-300"
|
||||
target="_blank"
|
||||
href="https://zitadel.com/docs/guides/manage/user/reg-create-user#with-passwordless"
|
||||
>
|
||||
{t("set.info.link")}
|
||||
<Translated i18nKey="set.info.link" namespace="passkey" />
|
||||
</a>
|
||||
</span>
|
||||
</Alert>
|
||||
|
||||
{!session && (
|
||||
<div className="py-4">
|
||||
<Alert>{tError("unknownContext")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Alert } from "@/components/alert";
|
||||
import { ChangePasswordForm } from "@/components/change-password-form";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
getLoginSettings,
|
||||
getPasswordComplexitySettings,
|
||||
} from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
@@ -19,9 +19,6 @@ export default async function Page(props: {
|
||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "password" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { loginName, organization, requestId } = searchParams;
|
||||
|
||||
@@ -53,15 +50,21 @@ export default async function Page(props: {
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>
|
||||
{sessionFactors?.factors?.user?.displayName ?? t("change.title")}
|
||||
{sessionFactors?.factors?.user?.displayName ?? (
|
||||
<Translated i18nKey="change.title" namespace="password" />
|
||||
)}
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("change.description")}</p>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="change.description" namespace="u2f" />
|
||||
</p>
|
||||
|
||||
{/* show error only if usernames should be shown to be unknown */}
|
||||
{(!sessionFactors || !loginName) &&
|
||||
!loginSettings?.ignoreUnknownUsernames && (
|
||||
<div className="py-4">
|
||||
<Alert>{tError("unknownContext")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -86,7 +89,9 @@ export default async function Page(props: {
|
||||
/>
|
||||
) : (
|
||||
<div className="py-4">
|
||||
<Alert>{tError("failedLoading")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="failedLoading" namespace="error" />
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Alert } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { PasswordForm } from "@/components/password-form";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
@@ -11,17 +12,12 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "password" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
let { loginName, organization, requestId, alt } = searchParams;
|
||||
|
||||
const _headers = await headers();
|
||||
@@ -66,15 +62,21 @@ export default async function Page(props: {
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>
|
||||
{sessionFactors?.factors?.user?.displayName ?? t("verify.title")}
|
||||
{sessionFactors?.factors?.user?.displayName ?? (
|
||||
<Translated i18nKey="verify.title" namespace="password" />
|
||||
)}
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="verify.description" namespace="password" />
|
||||
</p>
|
||||
|
||||
{/* show error only if usernames should be shown to be unknown */}
|
||||
{(!sessionFactors || !loginName) &&
|
||||
!loginSettings?.ignoreUnknownUsernames && (
|
||||
<div className="py-4">
|
||||
<Alert>{tError("unknownContext")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Alert, AlertType } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SetPasswordForm } from "@/components/set-password-form";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
@@ -12,7 +13,7 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
@@ -20,8 +21,6 @@ export default async function Page(props: {
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "password" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { userId, loginName, organization, requestId, code, initial } =
|
||||
searchParams;
|
||||
@@ -73,13 +72,21 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{session?.factors?.user?.displayName ?? t("set.title")}</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("set.description")}</p>
|
||||
<h1>
|
||||
{session?.factors?.user?.displayName ?? (
|
||||
<Translated i18nKey="set.title" namespace="password" />
|
||||
)}
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="set.description" namespace="password" />
|
||||
</p>
|
||||
|
||||
{/* show error only if usernames should be shown to be unknown */}
|
||||
{loginName && !session && !loginSettings?.ignoreUnknownUsernames && (
|
||||
<div className="py-4">
|
||||
<Alert>{tError("unknownContext")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -99,7 +106,11 @@ export default async function Page(props: {
|
||||
></UserAvatar>
|
||||
) : null}
|
||||
|
||||
{!initial && <Alert type={AlertType.INFO}>{t("set.codeSent")}</Alert>}
|
||||
{!initial && (
|
||||
<Alert type={AlertType.INFO}>
|
||||
<Translated i18nKey="set.codeSent" namespace="password" />
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{passwordComplexity &&
|
||||
(loginName ?? user?.preferredLoginName) &&
|
||||
@@ -115,7 +126,9 @@ export default async function Page(props: {
|
||||
/>
|
||||
) : (
|
||||
<div className="py-4">
|
||||
<Alert>{tError("failedLoading")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="failedLoading" namespace="error" />
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -1,7 +1,11 @@
|
||||
import { Alert } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { RegisterForm } from "@/components/register-form";
|
||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import {
|
||||
getActiveIdentityProviders,
|
||||
getBrandingSettings,
|
||||
getDefaultOrg,
|
||||
getLegalAndSupportSettings,
|
||||
@@ -9,7 +13,8 @@ import {
|
||||
getPasswordComplexitySettings,
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
@@ -17,7 +22,6 @@ export default async function Page(props: {
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "register" });
|
||||
|
||||
let { firstname, lastname, email, organization, requestId } = searchParams;
|
||||
|
||||
@@ -52,12 +56,25 @@ export default async function Page(props: {
|
||||
organization,
|
||||
});
|
||||
|
||||
const identityProviders = await getActiveIdentityProviders({
|
||||
serviceUrl,
|
||||
orgId: organization,
|
||||
}).then((resp) => {
|
||||
return resp.identityProviders.filter((idp) => {
|
||||
return idp.options?.isAutoCreation || idp.options?.isCreationAllowed; // check if IDP allows to create account automatically or manual creation is allowed
|
||||
});
|
||||
});
|
||||
|
||||
if (!loginSettings?.allowRegister) {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("disabled.title")}</h1>
|
||||
<p className="ztdl-p">{t("disabled.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="disabled.title" namespace="register" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="disabled.description" namespace="register" />
|
||||
</p>
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
);
|
||||
@@ -66,11 +83,28 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("title")}</h1>
|
||||
<p className="ztdl-p">{t("description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="title" namespace="register" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="description" namespace="register" />
|
||||
</p>
|
||||
|
||||
{legal && passwordComplexitySettings && (
|
||||
{!organization && (
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{legal &&
|
||||
passwordComplexitySettings &&
|
||||
organization &&
|
||||
(loginSettings.allowUsernamePassword ||
|
||||
loginSettings.passkeysType == PasskeysType.ALLOWED) && (
|
||||
<RegisterForm
|
||||
idpCount={
|
||||
!loginSettings?.allowExternalIdp ? 0 : identityProviders.length
|
||||
}
|
||||
legal={legal}
|
||||
organization={organization}
|
||||
firstname={firstname}
|
||||
@@ -80,6 +114,22 @@ export default async function Page(props: {
|
||||
loginSettings={loginSettings}
|
||||
></RegisterForm>
|
||||
)}
|
||||
|
||||
{loginSettings?.allowExternalIdp && !!identityProviders.length && (
|
||||
<>
|
||||
<div className="py-3 flex flex-col items-center">
|
||||
<p className="ztdl-p text-center">
|
||||
<Translated i18nKey="orUseIDP" namespace="register" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<SignInWithIdp
|
||||
identityProviders={identityProviders}
|
||||
requestId={requestId}
|
||||
organization={organization}
|
||||
></SignInWithIdp>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SetRegisterPasswordForm } from "@/components/set-register-password-form";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
@@ -9,15 +10,12 @@ import {
|
||||
getPasswordComplexitySettings,
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "register" });
|
||||
|
||||
let { firstname, lastname, email, organization, requestId } = searchParams;
|
||||
|
||||
@@ -33,7 +31,7 @@ export default async function Page(props: {
|
||||
}
|
||||
}
|
||||
|
||||
const missingData = !firstname || !lastname || !email;
|
||||
const missingData = !firstname || !lastname || !email || !organization;
|
||||
|
||||
const legal = await getLegalAndSupportSettings({
|
||||
serviceUrl,
|
||||
@@ -57,15 +55,23 @@ export default async function Page(props: {
|
||||
return missingData ? (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("missingdata.title")}</h1>
|
||||
<p className="ztdl-p">{t("missingdata.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="missingdata.title" namespace="register" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="missingdata.description" namespace="register" />
|
||||
</p>
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
) : loginSettings?.allowRegister && loginSettings.allowUsernamePassword ? (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("password.title")}</h1>
|
||||
<p className="ztdl-p">{t("description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="password.title" namespace="register" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="description" namespace="register" />
|
||||
</p>
|
||||
|
||||
{legal && passwordComplexitySettings && (
|
||||
<SetRegisterPasswordForm
|
||||
@@ -73,7 +79,7 @@ export default async function Page(props: {
|
||||
email={email}
|
||||
firstname={firstname}
|
||||
lastname={lastname}
|
||||
organization={organization}
|
||||
organization={organization as string} // organization is guaranteed to be a string here otherwise we would have returned earlier
|
||||
requestId={requestId}
|
||||
></SetRegisterPasswordForm>
|
||||
)}
|
||||
@@ -82,8 +88,12 @@ export default async function Page(props: {
|
||||
) : (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("disabled.title")}</h1>
|
||||
<p className="ztdl-p">{t("disabled.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="disabled.title" namespace="register" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="disabled.description" namespace="register" />
|
||||
</p>
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Alert, AlertType } from "@/components/alert";
|
||||
import { Button, ButtonVariants } from "@/components/button";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import {
|
||||
getMostRecentCookieWithLoginname,
|
||||
@@ -14,7 +15,6 @@ import {
|
||||
getLoginSettings,
|
||||
getSession,
|
||||
} from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
import Link from "next/link";
|
||||
|
||||
@@ -37,8 +37,6 @@ async function loadSessionById(
|
||||
|
||||
export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "signedin" });
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||
@@ -66,8 +64,12 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("error.title")}</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("error.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="error.title" namespace="signedin" />
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="error.description" namespace="signedin" />
|
||||
</p>
|
||||
<Alert>{err.message}</Alert>
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
@@ -94,9 +96,15 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>
|
||||
{t("title", { user: sessionFactors?.factors?.user?.displayName })}
|
||||
<Translated
|
||||
i18nKey="title"
|
||||
namespace="signedin"
|
||||
data={{ user: sessionFactors?.factors?.user?.displayName }}
|
||||
/>
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("description")}</p>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="description" namespace="signedin" />
|
||||
</p>
|
||||
|
||||
<UserAvatar
|
||||
loginName={loginName ?? sessionFactors?.factors?.user?.loginName}
|
||||
@@ -122,7 +130,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
className="self-end"
|
||||
variant={ButtonVariants.Primary}
|
||||
>
|
||||
{t("continue")}
|
||||
<Translated i18nKey="continue" namespace="signedin" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { Alert } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { LoginPasskey } from "@/components/login-passkey";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import { getBrandingSettings, getSession } from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
@@ -14,8 +15,6 @@ export default async function Page(props: {
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "u2f" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { loginName, requestId, sessionId, organization } = searchParams;
|
||||
|
||||
@@ -59,7 +58,9 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("verify.title")}</h1>
|
||||
<h1>
|
||||
<Translated i18nKey="verify.title" namespace="u2f" />
|
||||
</h1>
|
||||
|
||||
{sessionFactors && (
|
||||
<UserAvatar
|
||||
@@ -69,9 +70,15 @@ export default async function Page(props: {
|
||||
searchParams={searchParams}
|
||||
></UserAvatar>
|
||||
)}
|
||||
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="verify.description" namespace="u2f" />
|
||||
</p>
|
||||
|
||||
{!(loginName || sessionId) && <Alert>{tError("unknownContext")}</Alert>}
|
||||
{!(loginName || sessionId) && (
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{(loginName || sessionId) && (
|
||||
<LoginPasskey
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { Alert } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { RegisterU2f } from "@/components/register-u2f";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import { getBrandingSettings } from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
@@ -13,8 +14,6 @@ export default async function Page(props: {
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "u2f" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { loginName, organization, requestId, checkAfter } = searchParams;
|
||||
|
||||
@@ -37,7 +36,9 @@ export default async function Page(props: {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("set.title")}</h1>
|
||||
<h1>
|
||||
<Translated i18nKey="set.title" namespace="u2f" />
|
||||
</h1>
|
||||
|
||||
{sessionFactors && (
|
||||
<UserAvatar
|
||||
@@ -47,11 +48,16 @@ export default async function Page(props: {
|
||||
searchParams={searchParams}
|
||||
></UserAvatar>
|
||||
)}
|
||||
<p className="ztdl-p mb-6 block">{t("set.description")}</p>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
{" "}
|
||||
<Translated i18nKey="set.description" namespace="u2f" />
|
||||
</p>
|
||||
|
||||
{!sessionFactors && (
|
||||
<div className="py-4">
|
||||
<Alert>{tError("unknownContext")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Alert, AlertType } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { VerifyForm } from "@/components/verify-form";
|
||||
import { sendEmailCode, sendInviteEmailCode } from "@/lib/server/verify";
|
||||
@@ -7,14 +8,12 @@ import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import { getBrandingSettings, getUserByID } from "@/lib/zitadel";
|
||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "verify" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { userId, loginName, code, organization, requestId, invite, send } =
|
||||
searchParams;
|
||||
@@ -121,23 +120,26 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("verify.title")}</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="verify.title" namespace="verify" />
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="verify.description" namespace="verify" />
|
||||
</p>
|
||||
|
||||
{!id && (
|
||||
<>
|
||||
<h1>{t("verify.title")}</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
|
||||
|
||||
<div className="py-4">
|
||||
<Alert>{tError("unknownContext")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="unknownContext" namespace="error" />
|
||||
</Alert>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{id && send && (
|
||||
<div className="py-4 w-full">
|
||||
<Alert type={AlertType.INFO}>{t("verify.codeSent")}</Alert>
|
||||
<Alert type={AlertType.INFO}>
|
||||
<Translated i18nKey="verify.codeSent" namespace="verify" />
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
@@ -8,13 +9,10 @@ import {
|
||||
getUserByID,
|
||||
} from "@/lib/zitadel";
|
||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "verify" });
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||
@@ -65,8 +63,12 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("successTitle")}</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("successDescription")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="successTitle" namespace="verify" />
|
||||
</h1>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
<Translated i18nKey="successDescription" namespace="verify" />
|
||||
</p>
|
||||
|
||||
{sessionFactors ? (
|
||||
<UserAvatar
|
||||
|
@@ -3,7 +3,7 @@
|
||||
import { Boundary } from "@/components/boundary";
|
||||
import { Button } from "@/components/button";
|
||||
import { ThemeWrapper } from "@/components/theme-wrapper";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Translated } from "@/components/translated";
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
@@ -12,8 +12,6 @@ export default function GlobalError({
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
const t = useTranslations("error");
|
||||
|
||||
return (
|
||||
// global-error must include html and body tags
|
||||
<html>
|
||||
@@ -25,7 +23,9 @@ export default function GlobalError({
|
||||
<span className="font-bold">Error:</span> {error?.message}
|
||||
</div>
|
||||
<div>
|
||||
<Button onClick={() => reset()}>{t("tryagain")}</Button>
|
||||
<Button data-i18n-key="error.tryagain" onClick={() => reset()}>
|
||||
<Translated i18nKey="tryagain" namespace="error" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Boundary>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({}, { status: 200 });
|
||||
return NextResponse.json({token: process.env.ZITADEL_SERVICE_USER_TOKEN}, { status: 200 });
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroup } from "@headlessui/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
export enum AuthenticationMethod {
|
||||
Passkey = "passkey",
|
||||
@@ -20,8 +20,6 @@ export function AuthenticationMethodRadio({
|
||||
selected: any;
|
||||
selectionChanged: (value: any) => void;
|
||||
}) {
|
||||
const t = useTranslations("register");
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
@@ -80,7 +78,18 @@ export function AuthenticationMethodRadio({
|
||||
as="p"
|
||||
className={`font-medium ${checked ? "" : ""}`}
|
||||
>
|
||||
{t(`methods.${method}`)}
|
||||
{method === AuthenticationMethod.Passkey && (
|
||||
<Translated
|
||||
i18nKey="methods.passkey"
|
||||
namespace="register"
|
||||
/>
|
||||
)}
|
||||
{method === AuthenticationMethod.Password && (
|
||||
<Translated
|
||||
i18nKey="methods.password"
|
||||
namespace="register"
|
||||
/>
|
||||
)}
|
||||
</RadioGroup.Label>
|
||||
</div>
|
||||
</>
|
||||
|
@@ -1,11 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
export function BackButton() {
|
||||
const t = useTranslations("common");
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Button
|
||||
@@ -13,7 +12,7 @@ export function BackButton() {
|
||||
type="button"
|
||||
variant={ButtonVariants.Secondary}
|
||||
>
|
||||
{t("back")}
|
||||
<Translated i18nKey="back" namespace="common" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
@@ -13,7 +13,6 @@ import {
|
||||
import { create } from "@zitadel/client";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
@@ -23,6 +22,7 @@ import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { PasswordComplexity } from "./password-complexity";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs =
|
||||
| {
|
||||
@@ -46,7 +46,6 @@ export function ChangePasswordForm({
|
||||
requestId,
|
||||
organization,
|
||||
}: Props) {
|
||||
const t = useTranslations("password");
|
||||
const router = useRouter();
|
||||
|
||||
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
|
||||
@@ -203,8 +202,8 @@ export function ChangePasswordForm({
|
||||
onClick={handleSubmit(submitChange)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("change.submit")}
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
||||
<Translated i18nKey="change.submit" namespace="password" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -3,8 +3,8 @@ import {
|
||||
PasskeysType,
|
||||
} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { PASSKEYS, PASSWORD } from "./auth-methods";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Props = {
|
||||
authMethods: AuthenticationMethodType[];
|
||||
@@ -17,13 +17,13 @@ export function ChooseAuthenticatorToLogin({
|
||||
params,
|
||||
loginSettings,
|
||||
}: Props) {
|
||||
const t = useTranslations("idp");
|
||||
|
||||
return (
|
||||
<>
|
||||
{authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
||||
loginSettings?.allowUsernamePassword && (
|
||||
<div className="ztdl-p">Choose an alternative method to login </div>
|
||||
<div className="ztdl-p">
|
||||
<Translated i18nKey="chooseAlternativeMethod" namespace="idp" />
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-1 gap-5 w-full pt-4">
|
||||
{authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
PasskeysType,
|
||||
} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Alert, AlertType } from "./alert";
|
||||
import { PASSKEYS, PASSWORD } from "./auth-methods";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Props = {
|
||||
authMethods: AuthenticationMethodType[];
|
||||
@@ -18,16 +18,23 @@ export function ChooseAuthenticatorToSetup({
|
||||
params,
|
||||
loginSettings,
|
||||
}: Props) {
|
||||
const t = useTranslations("authenticator");
|
||||
|
||||
if (authMethods.length !== 0) {
|
||||
return <Alert type={AlertType.ALERT}>{t("allSetup")}</Alert>;
|
||||
return (
|
||||
<Alert type={AlertType.ALERT}>
|
||||
<Translated i18nKey="allSetup" namespace="authenticator" />
|
||||
</Alert>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{loginSettings.passkeysType == PasskeysType.NOT_ALLOWED &&
|
||||
!loginSettings.allowUsernamePassword && (
|
||||
<Alert type={AlertType.ALERT}>{t("noMethodsAvailable")}</Alert>
|
||||
<Alert type={AlertType.ALERT}>
|
||||
<Translated
|
||||
i18nKey="noMethodsAvailable"
|
||||
namespace="authenticator"
|
||||
/>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 gap-5 w-full pt-4">
|
||||
|
@@ -6,9 +6,9 @@ import {
|
||||
SecondFactorType,
|
||||
} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { EMAIL, SMS, TOTP, U2F } from "./auth-methods";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Props = {
|
||||
userId: string;
|
||||
@@ -37,7 +37,6 @@ export function ChooseSecondFactorToSetup({
|
||||
emailVerified,
|
||||
force,
|
||||
}: Props) {
|
||||
const t = useTranslations("mfa");
|
||||
const router = useRouter();
|
||||
const params = new URLSearchParams({});
|
||||
|
||||
@@ -112,7 +111,7 @@ export function ChooseSecondFactorToSetup({
|
||||
type="button"
|
||||
data-testid="reset-button"
|
||||
>
|
||||
{t("set.skip")}
|
||||
<Translated i18nKey="set.skip" namespace="mfa" />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
|
@@ -8,6 +8,7 @@ import { useState } from "react";
|
||||
import { Alert } from "./alert";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
export function ConsentScreen({
|
||||
scope,
|
||||
@@ -50,7 +51,7 @@ export function ConsentScreen({
|
||||
<ul className="list-disc space-y-2 w-full">
|
||||
{scopes?.length === 0 && (
|
||||
<span className="w-full text-sm flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light py-2 px-4 rounded-md transition-all">
|
||||
{t("device.scope.openid")}
|
||||
<Translated i18nKey="device.scope.openid" namespace="device" />
|
||||
</span>
|
||||
)}
|
||||
{scopes?.map((s) => {
|
||||
@@ -73,7 +74,11 @@ export function ConsentScreen({
|
||||
</ul>
|
||||
|
||||
<p className="ztdl-p text-xs text-left">
|
||||
{t("device.request.disclaimer", { appName: appName })}
|
||||
<Translated
|
||||
i18nKey="request.disclaimer"
|
||||
namespace="device"
|
||||
data={{ appName: appName }}
|
||||
/>
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
@@ -91,7 +96,7 @@ export function ConsentScreen({
|
||||
data-testid="deny-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("device.request.deny")}
|
||||
<Translated i18nKey="device.request.deny" namespace="device" />
|
||||
</Button>
|
||||
<span className="flex-grow"></span>
|
||||
|
||||
@@ -102,7 +107,7 @@ export function ConsentScreen({
|
||||
className="self-end"
|
||||
variant={ButtonVariants.Primary}
|
||||
>
|
||||
{t("device.request.submit")}
|
||||
<Translated i18nKey="device.request.submit" namespace="device" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
import { Alert } from "@/components/alert";
|
||||
import { getDeviceAuthorizationRequest } from "@/lib/server/oidc";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -10,14 +9,13 @@ import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs = {
|
||||
userCode: string;
|
||||
};
|
||||
|
||||
export function DeviceCodeForm({ userCode }: { userCode?: string }) {
|
||||
const t = useTranslations("verify");
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||
@@ -87,8 +85,8 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) {
|
||||
onClick={handleSubmit(submitCodeAndContinue)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("verify.submit")}
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
||||
<Translated i18nKey="verify.submit" namespace="verify" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
55
apps/login/src/components/idps/pages/complete-idp.tsx
Normal file
55
apps/login/src/components/idps/pages/complete-idp.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { RegisterFormIDPIncomplete } from "@/components/register-form-idp-incomplete";
|
||||
import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb";
|
||||
import { AddHumanUserRequest } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { DynamicTheme } from "../../dynamic-theme";
|
||||
import { Translated } from "../../translated";
|
||||
|
||||
export async function completeIDP({
|
||||
idpUserId,
|
||||
idpId,
|
||||
idpUserName,
|
||||
addHumanUser,
|
||||
requestId,
|
||||
organization,
|
||||
branding,
|
||||
idpIntent,
|
||||
}: {
|
||||
idpUserId: string;
|
||||
idpId: string;
|
||||
idpUserName: string;
|
||||
addHumanUser?: AddHumanUserRequest;
|
||||
requestId?: string;
|
||||
organization: string;
|
||||
branding?: BrandingSettings;
|
||||
idpIntent: {
|
||||
idpIntentId: string;
|
||||
idpIntentToken: string;
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>
|
||||
<Translated i18nKey="completeRegister.title" namespace="idp" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="completeRegister.description" namespace="idp" />
|
||||
</p>
|
||||
|
||||
<RegisterFormIDPIncomplete
|
||||
idpUserId={idpUserId}
|
||||
idpId={idpId}
|
||||
idpUserName={idpUserName}
|
||||
defaultValues={{
|
||||
email: addHumanUser?.email?.email || "",
|
||||
firstname: addHumanUser?.profile?.givenName || "",
|
||||
lastname: addHumanUser?.profile?.familyName || "",
|
||||
}}
|
||||
requestId={requestId}
|
||||
organization={organization}
|
||||
idpIntent={idpIntent}
|
||||
/>
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
);
|
||||
}
|
@@ -1,20 +1,21 @@
|
||||
import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { Alert, AlertType } from "../../alert";
|
||||
import { DynamicTheme } from "../../dynamic-theme";
|
||||
import { Translated } from "../../translated";
|
||||
|
||||
export async function linkingFailed(
|
||||
branding?: BrandingSettings,
|
||||
error?: string,
|
||||
) {
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "idp" });
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("linkingError.title")}</h1>
|
||||
<p className="ztdl-p">{t("linkingError.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="linkingError.title" namespace="idp" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="linkingError.description" namespace="idp" />
|
||||
</p>
|
||||
{error && (
|
||||
<div className="w-full">
|
||||
{<Alert type={AlertType.ALERT}>{error}</Alert>}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { DynamicTheme } from "../../dynamic-theme";
|
||||
import { IdpSignin } from "../../idp-signin";
|
||||
import { Translated } from "../../translated";
|
||||
|
||||
export async function linkingSuccess(
|
||||
userId: string,
|
||||
@@ -9,14 +9,15 @@ export async function linkingSuccess(
|
||||
requestId?: string,
|
||||
branding?: BrandingSettings,
|
||||
) {
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "idp" });
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("linkingSuccess.title")}</h1>
|
||||
<p className="ztdl-p">{t("linkingSuccess.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="linkingSuccess.title" namespace="idp" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="linkingSuccess.description" namespace="idp" />
|
||||
</p>
|
||||
|
||||
<IdpSignin
|
||||
userId={userId}
|
||||
|
@@ -1,17 +1,18 @@
|
||||
import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { Alert, AlertType } from "../../alert";
|
||||
import { DynamicTheme } from "../../dynamic-theme";
|
||||
import { Translated } from "../../translated";
|
||||
|
||||
export async function loginFailed(branding?: BrandingSettings, error?: string) {
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "idp" });
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("loginError.title")}</h1>
|
||||
<p className="ztdl-p">{t("loginError.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="loginError.title" namespace="idp" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="loginError.description" namespace="idp" />
|
||||
</p>
|
||||
{error && (
|
||||
<div className="w-full">
|
||||
{<Alert type={AlertType.ALERT}>{error}</Alert>}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { DynamicTheme } from "../../dynamic-theme";
|
||||
import { IdpSignin } from "../../idp-signin";
|
||||
import { Translated } from "../../translated";
|
||||
|
||||
export async function loginSuccess(
|
||||
userId: string,
|
||||
@@ -9,14 +9,15 @@ export async function loginSuccess(
|
||||
requestId?: string,
|
||||
branding?: BrandingSettings,
|
||||
) {
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "idp" });
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{t("loginSuccess.title")}</h1>
|
||||
<p className="ztdl-p">{t("loginSuccess.description")}</p>
|
||||
<h1>
|
||||
<Translated i18nKey="loginSuccess.title" namespace="idp" />
|
||||
</h1>
|
||||
<p className="ztdl-p">
|
||||
<Translated i18nKey="loginSuccess.description" namespace="idp" />
|
||||
</p>
|
||||
|
||||
<IdpSignin
|
||||
userId={userId}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { forwardRef } from "react";
|
||||
import { Translated } from "../translated";
|
||||
import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
|
||||
export const SignInWithApple = forwardRef<
|
||||
@@ -9,7 +9,6 @@ export const SignInWithApple = forwardRef<
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithApple(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
const t = useTranslations("idp");
|
||||
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
@@ -24,7 +23,13 @@ export const SignInWithApple = forwardRef<
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">{name ? name : t("signInWithApple")}</span>
|
||||
<span className="ml-4">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Translated i18nKey="signInWithApple" namespace="idp" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { forwardRef } from "react";
|
||||
import { Translated } from "../translated";
|
||||
import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
|
||||
export const SignInWithAzureAd = forwardRef<
|
||||
@@ -9,7 +9,6 @@ export const SignInWithAzureAd = forwardRef<
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithAzureAd(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
const t = useTranslations("idp");
|
||||
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
@@ -30,7 +29,13 @@ export const SignInWithAzureAd = forwardRef<
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">{name ? name : t("signInWithAzureAD")}</span>
|
||||
<span className="ml-4">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Translated i18nKey="signInWithAzureAD" namespace="idp" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { forwardRef } from "react";
|
||||
import { Translated } from "../translated";
|
||||
import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
|
||||
function GitHubLogo() {
|
||||
@@ -42,7 +42,6 @@ export const SignInWithGithub = forwardRef<
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithGithub(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
const t = useTranslations("idp");
|
||||
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
@@ -52,7 +51,13 @@ export const SignInWithGithub = forwardRef<
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">{name ? name : t("signInWithGithub")}</span>
|
||||
<span className="ml-4">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Translated i18nKey="signInWithGithub" namespace="idp" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { forwardRef } from "react";
|
||||
import { Translated } from "../translated";
|
||||
import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
|
||||
export const SignInWithGitlab = forwardRef<
|
||||
@@ -9,7 +9,6 @@ export const SignInWithGitlab = forwardRef<
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithGitlab(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
const t = useTranslations("idp");
|
||||
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
@@ -41,7 +40,13 @@ export const SignInWithGitlab = forwardRef<
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">{name ? name : t("signInWithGitlab")}</span>
|
||||
<span className="ml-4">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Translated i18nKey="signInWithGitlab" namespace="idp" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { forwardRef } from "react";
|
||||
import { Translated } from "../translated";
|
||||
import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
|
||||
export const SignInWithGoogle = forwardRef<
|
||||
@@ -9,7 +9,6 @@ export const SignInWithGoogle = forwardRef<
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithGoogle(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
const t = useTranslations("idp");
|
||||
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
@@ -54,7 +53,13 @@ export const SignInWithGoogle = forwardRef<
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">{name ? name : t("signInWithGoogle")}</span>
|
||||
<span className="ml-4">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Translated i18nKey="signInWithGoogle" namespace="idp" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
|
@@ -6,7 +6,6 @@ import { create } from "@zitadel/client";
|
||||
import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -15,6 +14,7 @@ import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
// either loginName or sessionId must be provided
|
||||
type Props = {
|
||||
@@ -42,8 +42,6 @@ export function LoginOTP({
|
||||
code,
|
||||
loginSettings,
|
||||
}: Props) {
|
||||
const t = useTranslations("otp");
|
||||
|
||||
const [error, setError] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
@@ -223,7 +221,7 @@ export function LoginOTP({
|
||||
<Alert type={AlertType.INFO}>
|
||||
<div className="flex flex-row">
|
||||
<span className="flex-1 mr-auto text-left">
|
||||
{t("verify.noCodeReceived")}
|
||||
<Translated i18nKey="verify.noCodeReceived" namespace="otp" />
|
||||
</span>
|
||||
<button
|
||||
aria-label="Resend OTP Code"
|
||||
@@ -243,7 +241,7 @@ export function LoginOTP({
|
||||
}}
|
||||
data-testid="resend-button"
|
||||
>
|
||||
{t("verify.resendCode")}
|
||||
<Translated i18nKey="verify.resendCode" namespace="otp" />
|
||||
</button>
|
||||
</div>
|
||||
</Alert>
|
||||
@@ -277,8 +275,8 @@ export function LoginOTP({
|
||||
})}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("verify.submit")}
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
||||
<Translated i18nKey="verify.submit" namespace="otp" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -9,13 +9,13 @@ import {
|
||||
UserVerificationRequirement,
|
||||
} from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Alert } from "./alert";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
// either loginName or sessionId must be provided
|
||||
type Props = {
|
||||
@@ -35,8 +35,6 @@ export function LoginPasskey({
|
||||
organization,
|
||||
login = true,
|
||||
}: Props) {
|
||||
const t = useTranslations("passkey");
|
||||
|
||||
const [error, setError] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
@@ -234,7 +232,7 @@ export function LoginPasskey({
|
||||
}}
|
||||
data-testid="password-button"
|
||||
>
|
||||
{t("verify.usePassword")}
|
||||
<Translated i18nKey="verify.usePassword" namespace="passkey" />
|
||||
</Button>
|
||||
) : (
|
||||
<BackButton />
|
||||
@@ -273,8 +271,8 @@ export function LoginPasskey({
|
||||
}}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("verify.submit")}
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
||||
<Translated i18nKey="verify.submit" namespace="passkey" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -4,7 +4,6 @@ import { resetPassword, sendPassword } from "@/lib/server/password";
|
||||
import { create } from "@zitadel/client";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -13,6 +12,7 @@ import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs = {
|
||||
password: string;
|
||||
@@ -35,8 +35,6 @@ export function PasswordForm({
|
||||
promptPasswordless,
|
||||
isAlternative,
|
||||
}: Props) {
|
||||
const t = useTranslations("password");
|
||||
|
||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
});
|
||||
@@ -136,7 +134,7 @@ export function PasswordForm({
|
||||
disabled={loading}
|
||||
data-testid="reset-button"
|
||||
>
|
||||
{t("verify.resetPassword")}
|
||||
<Translated i18nKey="verify.resetPassword" namespace="password" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -173,8 +171,8 @@ export function PasswordForm({
|
||||
onClick={handleSubmit(submitPassword)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("verify.submit")}
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
||||
<Translated i18nKey="verify.submit" namespace="password" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { Checkbox } from "./checkbox";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Props = {
|
||||
legal: LegalAndSupportSettings;
|
||||
@@ -16,7 +16,6 @@ type AcceptanceState = {
|
||||
};
|
||||
|
||||
export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
|
||||
const t = useTranslations("register");
|
||||
const [acceptanceState, setAcceptanceState] = useState<AcceptanceState>({
|
||||
tosAccepted: false,
|
||||
privacyPolicyAccepted: false,
|
||||
@@ -25,7 +24,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
|
||||
return (
|
||||
<>
|
||||
<p className="flex flex-row items-center text-text-light-secondary-500 dark:text-text-dark-secondary-500 mt-4 text-sm">
|
||||
{t("agreeTo")}
|
||||
<Translated i18nKey="agreeTo" namespace="register" />
|
||||
{legal?.helpLink && (
|
||||
<span>
|
||||
<Link href={legal.helpLink} target="_blank">
|
||||
@@ -66,7 +65,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
|
||||
<div className="mr-4 w-[28rem]">
|
||||
<p className="text-sm text-text-light-500 dark:text-text-dark-500">
|
||||
<Link href={legal.tosLink} className="underline" target="_blank">
|
||||
{t("termsOfService")}
|
||||
<Translated i18nKey="termsOfService" namespace="register" />
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
@@ -95,7 +94,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
{t("privacyPolicy")}
|
||||
<Translated i18nKey="privacyPolicy" namespace="register" />
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { inviteUser } from "@/lib/server/invite";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { registerUserAndLinkToIDP } from "@/lib/server/register";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
@@ -10,6 +9,7 @@ import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs =
|
||||
| {
|
||||
@@ -20,26 +20,37 @@ type Inputs =
|
||||
| FieldValues;
|
||||
|
||||
type Props = {
|
||||
organization: string;
|
||||
requestId?: string;
|
||||
idpIntent: {
|
||||
idpIntentId: string;
|
||||
idpIntentToken: string;
|
||||
};
|
||||
defaultValues?: {
|
||||
firstname?: string;
|
||||
lastname?: string;
|
||||
email?: string;
|
||||
organization?: string;
|
||||
};
|
||||
idpUserId: string;
|
||||
idpId: string;
|
||||
idpUserName: string;
|
||||
};
|
||||
|
||||
export function InviteForm({
|
||||
email,
|
||||
firstname,
|
||||
lastname,
|
||||
export function RegisterFormIDPIncomplete({
|
||||
organization,
|
||||
requestId,
|
||||
idpIntent,
|
||||
defaultValues,
|
||||
idpUserId,
|
||||
idpId,
|
||||
idpUserName,
|
||||
}: Props) {
|
||||
const t = useTranslations("register");
|
||||
|
||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
email: email ?? "",
|
||||
firstName: firstname ?? "",
|
||||
lastname: lastname ?? "",
|
||||
email: defaultValues?.email ?? "",
|
||||
firstname: defaultValues?.firstname ?? "",
|
||||
lastname: defaultValues?.lastname ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,39 +59,37 @@ export function InviteForm({
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
async function submitAndContinue(values: Inputs) {
|
||||
async function submitAndRegister(values: Inputs) {
|
||||
setLoading(true);
|
||||
const response = await inviteUser({
|
||||
const response = await registerUserAndLinkToIDP({
|
||||
idpId: idpId,
|
||||
idpUserName: idpUserName,
|
||||
idpUserId: idpUserId,
|
||||
email: values.email,
|
||||
firstName: values.firstname,
|
||||
lastName: values.lastname,
|
||||
organization: organization,
|
||||
requestId: requestId,
|
||||
idpIntent: idpIntent,
|
||||
})
|
||||
.catch(() => {
|
||||
setError("Could not create invitation Code");
|
||||
setError("Could not register user");
|
||||
return;
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
if (response && typeof response === "object" && "error" in response) {
|
||||
if (response && "error" in response && response.error) {
|
||||
setError(response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
setError("Could not create invitation Code");
|
||||
return;
|
||||
if (response && "redirect" in response && response.redirect) {
|
||||
return router.push(response.redirect);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({});
|
||||
|
||||
if (response) {
|
||||
params.append("userId", response);
|
||||
}
|
||||
|
||||
return router.push(`/invite/success?` + params);
|
||||
return response;
|
||||
}
|
||||
|
||||
const { errors } = formState;
|
||||
@@ -88,16 +97,6 @@ export function InviteForm({
|
||||
return (
|
||||
<form className="w-full">
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<div className="col-span-2">
|
||||
<TextInput
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
{...register("email", { required: "This field is required" })}
|
||||
label="E-mail"
|
||||
error={errors.email?.message as string}
|
||||
/>
|
||||
</div>
|
||||
<div className="">
|
||||
<TextInput
|
||||
type="firstname"
|
||||
@@ -106,6 +105,7 @@ export function InviteForm({
|
||||
{...register("firstname", { required: "This field is required" })}
|
||||
label="First name"
|
||||
error={errors.firstname?.message as string}
|
||||
data-testid="firstname-text-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="">
|
||||
@@ -116,6 +116,18 @@ export function InviteForm({
|
||||
{...register("lastname", { required: "This field is required" })}
|
||||
label="Last name"
|
||||
error={errors.lastname?.message as string}
|
||||
data-testid="lastname-text-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<TextInput
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
{...register("email", { required: "This field is required" })}
|
||||
label="E-mail"
|
||||
error={errors.email?.message as string}
|
||||
data-testid="email-text-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -127,15 +139,16 @@ export function InviteForm({
|
||||
)}
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center justify-between">
|
||||
<BackButton />
|
||||
<BackButton data-testid="back-button" />
|
||||
<Button
|
||||
type="submit"
|
||||
variant={ButtonVariants.Primary}
|
||||
disabled={loading || !formState.isValid}
|
||||
onClick={handleSubmit(submitAndContinue)}
|
||||
onClick={handleSubmit(submitAndRegister)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("submit")}
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
||||
<Translated i18nKey="submit" namespace="register" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
@@ -6,11 +6,10 @@ import {
|
||||
LoginSettings,
|
||||
PasskeysType,
|
||||
} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
import { Alert } from "./alert";
|
||||
import { Alert, AlertType } from "./alert";
|
||||
import {
|
||||
AuthenticationMethod,
|
||||
AuthenticationMethodRadio,
|
||||
@@ -21,6 +20,7 @@ import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { PrivacyPolicyCheckboxes } from "./privacy-policy-checkboxes";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs =
|
||||
| {
|
||||
@@ -35,9 +35,10 @@ type Props = {
|
||||
firstname?: string;
|
||||
lastname?: string;
|
||||
email?: string;
|
||||
organization?: string;
|
||||
organization: string;
|
||||
requestId?: string;
|
||||
loginSettings?: LoginSettings;
|
||||
idpCount: number;
|
||||
};
|
||||
|
||||
export function RegisterForm({
|
||||
@@ -48,9 +49,8 @@ export function RegisterForm({
|
||||
organization,
|
||||
requestId,
|
||||
loginSettings,
|
||||
idpCount = 0,
|
||||
}: Props) {
|
||||
const t = useTranslations("register");
|
||||
|
||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
@@ -165,17 +165,34 @@ export function RegisterForm({
|
||||
onChange={setTosAndPolicyAccepted}
|
||||
/>
|
||||
)}
|
||||
<p className="mt-4 ztdl-p mb-6 block text-left">{t("selectMethod")}</p>
|
||||
{/* show chooser if both methods are allowed */}
|
||||
{loginSettings &&
|
||||
loginSettings.allowUsernamePassword &&
|
||||
loginSettings.passkeysType == PasskeysType.ALLOWED && (
|
||||
<>
|
||||
<p className="mt-4 ztdl-p mb-6 block text-left">
|
||||
<Translated i18nKey="selectMethod" namespace="register" />
|
||||
</p>
|
||||
|
||||
<div className="pb-4">
|
||||
<AuthenticationMethodRadio
|
||||
selected={selected}
|
||||
selectionChanged={setSelected}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!loginSettings?.allowUsernamePassword &&
|
||||
loginSettings?.passkeysType !== PasskeysType.ALLOWED &&
|
||||
(!loginSettings?.allowExternalIdp || !idpCount) && (
|
||||
<div className="py-4">
|
||||
<Alert type={AlertType.INFO}>
|
||||
<Translated
|
||||
i18nKey="noMethodAvailableWarning"
|
||||
namespace="register"
|
||||
/>
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
@@ -183,6 +200,7 @@ export function RegisterForm({
|
||||
<Alert>{error}</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center justify-between">
|
||||
<BackButton data-testid="back-button" />
|
||||
<Button
|
||||
@@ -201,7 +219,7 @@ export function RegisterForm({
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("submit")}
|
||||
<Translated i18nKey="submit" namespace="register" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -5,7 +5,6 @@ import {
|
||||
registerPasskeyLink,
|
||||
verifyPasskeyRegistration,
|
||||
} from "@/lib/server/passkeys";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -13,6 +12,7 @@ import { Alert } from "./alert";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs = {};
|
||||
|
||||
@@ -29,8 +29,6 @@ export function RegisterPasskey({
|
||||
organization,
|
||||
requestId,
|
||||
}: Props) {
|
||||
const t = useTranslations("passkey");
|
||||
|
||||
const { handleSubmit, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
});
|
||||
@@ -198,7 +196,7 @@ export function RegisterPasskey({
|
||||
continueAndLogin();
|
||||
}}
|
||||
>
|
||||
{t("set.skip")}
|
||||
<Translated i18nKey="set.skip" namespace="passkey" />
|
||||
</Button>
|
||||
) : (
|
||||
<BackButton />
|
||||
@@ -213,8 +211,8 @@ export function RegisterPasskey({
|
||||
onClick={handleSubmit(submitRegisterAndContinue)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("set.submit")}
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
||||
<Translated i18nKey="set.submit" namespace="passkey" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -5,13 +5,13 @@ import { getNextUrl } from "@/lib/client";
|
||||
import { addU2F, verifyU2F } from "@/lib/server/u2f";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { Alert } from "./alert";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Props = {
|
||||
loginName?: string;
|
||||
@@ -30,8 +30,6 @@ export function RegisterU2f({
|
||||
checkAfter,
|
||||
loginSettings,
|
||||
}: Props) {
|
||||
const t = useTranslations("u2f");
|
||||
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
@@ -218,8 +216,8 @@ export function RegisterU2f({
|
||||
onClick={submitRegisterAndContinue}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("set.submit")}
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
||||
<Translated i18nKey="set.submit" namespace="u2f" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -4,11 +4,12 @@ import { clearSession } from "@/lib/server/session";
|
||||
import { timestampDate } from "@zitadel/client";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import moment from "moment";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import { useLocale } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { Avatar } from "./avatar";
|
||||
import { isSessionValid } from "./session-item";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
export function SessionClearItem({
|
||||
session,
|
||||
@@ -17,8 +18,6 @@ export function SessionClearItem({
|
||||
session: Session;
|
||||
reload: () => void;
|
||||
}) {
|
||||
const t = useTranslations("logout");
|
||||
|
||||
const currentLocale = useLocale();
|
||||
moment.locale(currentLocale === "zh" ? "zh-cn" : currentLocale);
|
||||
|
||||
@@ -70,10 +69,13 @@ export function SessionClearItem({
|
||||
</span>
|
||||
{valid ? (
|
||||
<span className="text-xs opacity-80 text-ellipsis">
|
||||
{verifiedAt &&
|
||||
t("verfiedAt", {
|
||||
time: moment(timestampDate(verifiedAt)).fromNow(),
|
||||
})}
|
||||
{verifiedAt && (
|
||||
<Translated
|
||||
i18nKey="verfiedAt"
|
||||
namespace="logout"
|
||||
data={{ time: moment(timestampDate(verifiedAt)).fromNow() }}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
verifiedAt && (
|
||||
@@ -89,7 +91,7 @@ export function SessionClearItem({
|
||||
<span className="flex-grow"></span>
|
||||
<div className="relative flex flex-row items-center">
|
||||
<div className="mr-6 px-2 py-[2px] text-xs hidden group-hover:block transition-all text-warn-light-500 dark:text-warn-dark-500 bg-[#ff0000]/10 dark:bg-[#ff0000]/10 rounded-full flex items-center justify-center">
|
||||
{t("clear")}
|
||||
<Translated i18nKey="clear" namespace="logout" />
|
||||
</div>
|
||||
|
||||
{valid ? (
|
||||
|
@@ -3,11 +3,11 @@
|
||||
import { clearSession } from "@/lib/server/session";
|
||||
import { timestampDate } from "@zitadel/client";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { redirect, useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Alert, AlertType } from "./alert";
|
||||
import { SessionClearItem } from "./session-clear-item";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Props = {
|
||||
sessions: Session[];
|
||||
@@ -22,7 +22,6 @@ export function SessionsClearList({
|
||||
postLogoutRedirectUri,
|
||||
organization,
|
||||
}: Props) {
|
||||
const t = useTranslations("logout");
|
||||
const [list, setList] = useState<Session[]>(sessions);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -97,10 +96,14 @@ export function SessionsClearList({
|
||||
);
|
||||
})}
|
||||
{list.length === 0 && (
|
||||
<Alert type={AlertType.INFO}>{t("noResults")}</Alert>
|
||||
<Alert type={AlertType.INFO}>
|
||||
<Translated i18nKey="noResults" namespace="logout" />
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Alert>{t("noResults")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="noResults" namespace="logout" />
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
@@ -2,10 +2,10 @@
|
||||
|
||||
import { timestampDate } from "@zitadel/client";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { Alert } from "./alert";
|
||||
import { SessionItem } from "./session-item";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Props = {
|
||||
sessions: Session[];
|
||||
@@ -13,7 +13,6 @@ type Props = {
|
||||
};
|
||||
|
||||
export function SessionsList({ sessions, requestId }: Props) {
|
||||
const t = useTranslations("accounts");
|
||||
const [list, setList] = useState<Session[]>(sessions);
|
||||
return sessions ? (
|
||||
<div className="flex flex-col space-y-2">
|
||||
@@ -44,6 +43,8 @@ export function SessionsList({ sessions, requestId }: Props) {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<Alert>{t("noResults")}</Alert>
|
||||
<Alert>
|
||||
<Translated i18nKey="noResults" namespace="accounts" />
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
@@ -14,7 +14,6 @@ import {
|
||||
import { create } from "@zitadel/client";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
@@ -24,6 +23,7 @@ import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { PasswordComplexity } from "./password-complexity";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs =
|
||||
| {
|
||||
@@ -52,8 +52,6 @@ export function SetPasswordForm({
|
||||
code,
|
||||
codeRequired,
|
||||
}: Props) {
|
||||
const t = useTranslations("password");
|
||||
|
||||
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
@@ -195,7 +193,7 @@ export function SetPasswordForm({
|
||||
<Alert type={AlertType.INFO}>
|
||||
<div className="flex flex-row">
|
||||
<span className="flex-1 mr-auto text-left">
|
||||
{t("set.noCodeReceived")}
|
||||
<Translated i18nKey="set.noCodeReceived" namespace="password" />
|
||||
</span>
|
||||
<button
|
||||
aria-label="Resend OTP Code"
|
||||
@@ -207,7 +205,7 @@ export function SetPasswordForm({
|
||||
}}
|
||||
data-testid="resend-button"
|
||||
>
|
||||
{t("set.resend")}
|
||||
<Translated i18nKey="set.resend" namespace="password" />
|
||||
</button>
|
||||
</div>
|
||||
</Alert>
|
||||
@@ -279,8 +277,8 @@ export function SetPasswordForm({
|
||||
onClick={handleSubmit(submitPassword)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("set.submit")}
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
||||
<Translated i18nKey="set.submit" namespace="password" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -8,7 +8,6 @@ import {
|
||||
} from "@/helpers/validators";
|
||||
import { registerUser } from "@/lib/server/register";
|
||||
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
@@ -18,6 +17,7 @@ import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { PasswordComplexity } from "./password-complexity";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs =
|
||||
| {
|
||||
@@ -31,7 +31,7 @@ type Props = {
|
||||
email: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
organization?: string;
|
||||
organization: string;
|
||||
requestId?: string;
|
||||
};
|
||||
|
||||
@@ -43,8 +43,6 @@ export function SetRegisterPasswordForm({
|
||||
organization,
|
||||
requestId,
|
||||
}: Props) {
|
||||
const t = useTranslations("register");
|
||||
|
||||
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
@@ -163,8 +161,8 @@ export function SetRegisterPasswordForm({
|
||||
onClick={handleSubmit(submitRegister)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("password.submit")}
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
||||
<Translated i18nKey="password.submit" namespace="register" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -3,7 +3,6 @@
|
||||
import { getNextUrl } from "@/lib/client";
|
||||
import { verifyTOTP } from "@/lib/server/verify";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
@@ -14,6 +13,7 @@ import { Button, ButtonVariants } from "./button";
|
||||
import { CopyToClipboard } from "./copy-to-clipboard";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs = {
|
||||
code: string;
|
||||
@@ -39,8 +39,6 @@ export function TotpRegister({
|
||||
checkAfter,
|
||||
loginSettings,
|
||||
}: Props) {
|
||||
const t = useTranslations("otp");
|
||||
|
||||
const [error, setError] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const router = useRouter();
|
||||
@@ -148,7 +146,7 @@ export function TotpRegister({
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("set.submit")}
|
||||
<Translated i18nKey="set.submit" namespace="otp" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
23
apps/login/src/components/translated.tsx
Normal file
23
apps/login/src/components/translated.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export function Translated({
|
||||
i18nKey,
|
||||
children,
|
||||
namespace,
|
||||
data,
|
||||
...props
|
||||
}: {
|
||||
i18nKey: string;
|
||||
children?: React.ReactNode;
|
||||
namespace?: string;
|
||||
data?: any;
|
||||
} & React.HTMLAttributes<HTMLSpanElement>) {
|
||||
const t = useTranslations(namespace);
|
||||
const helperKey = `${namespace ? `${namespace}.` : ""}${i18nKey}`;
|
||||
|
||||
return (
|
||||
<span data-i18n-key={helperKey} {...props}>
|
||||
{t(i18nKey, data)}
|
||||
</span>
|
||||
);
|
||||
}
|
@@ -2,7 +2,6 @@
|
||||
|
||||
import { sendLoginname } from "@/lib/server/loginname";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -11,6 +10,7 @@ import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs = {
|
||||
loginName: string;
|
||||
@@ -37,7 +37,6 @@ export function UsernameForm({
|
||||
allowRegister,
|
||||
children,
|
||||
}: Props) {
|
||||
const t = useTranslations("loginname");
|
||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
@@ -127,7 +126,7 @@ export function UsernameForm({
|
||||
disabled={loading}
|
||||
data-testid="register-button"
|
||||
>
|
||||
{t("register")}
|
||||
<Translated i18nKey="register" namespace="loginname" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -152,7 +151,7 @@ export function UsernameForm({
|
||||
onClick={handleSubmit((e) => submitLoginName(e, organization))}
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
continue
|
||||
<Translated i18nKey="submit" namespace="loginname" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
import { Alert, AlertType } from "@/components/alert";
|
||||
import { resendVerification, sendVerification } from "@/lib/server/verify";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -10,6 +9,7 @@ import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Inputs = {
|
||||
code: string;
|
||||
@@ -32,8 +32,6 @@ export function VerifyForm({
|
||||
code,
|
||||
isInvite,
|
||||
}: Props) {
|
||||
const t = useTranslations("verify");
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||
@@ -117,7 +115,7 @@ export function VerifyForm({
|
||||
<Alert type={AlertType.INFO}>
|
||||
<div className="flex flex-row">
|
||||
<span className="flex-1 mr-auto text-left">
|
||||
{t("verify.noCodeReceived")}
|
||||
<Translated i18nKey="verify.noCodeReceived" namespace="verify" />
|
||||
</span>
|
||||
<button
|
||||
aria-label="Resend Code"
|
||||
@@ -129,7 +127,7 @@ export function VerifyForm({
|
||||
}}
|
||||
data-testid="resend-button"
|
||||
>
|
||||
{t("verify.resendCode")}
|
||||
<Translated i18nKey="verify.resendCode" namespace="verify" />
|
||||
</button>
|
||||
</div>
|
||||
</Alert>
|
||||
@@ -161,7 +159,7 @@ export function VerifyForm({
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
{t("verify.submit")}
|
||||
<Translated i18nKey="verify.submit" namespace="verify" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -1,4 +1,7 @@
|
||||
import { LANGS, LANGUAGE_COOKIE_NAME, LANGUAGE_HEADER_NAME } from "@/lib/i18n";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||
import { getHostedLoginTranslation } from "@/lib/zitadel";
|
||||
import { JsonObject } from "@zitadel/client";
|
||||
import deepmerge from "deepmerge";
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
import { cookies, headers } from "next/headers";
|
||||
@@ -9,6 +12,26 @@ export default getRequestConfig(async () => {
|
||||
|
||||
let locale: string = fallback;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const i18nOrganization = _headers.get("x-zitadel-i18n-organization") || ""; // You may need to set this header in middleware
|
||||
console.log("i18nOrganization:", i18nOrganization);
|
||||
let translations: JsonObject | {} = {};
|
||||
try {
|
||||
const i18nJSON = await getHostedLoginTranslation({
|
||||
serviceUrl,
|
||||
locale,
|
||||
organization: i18nOrganization,
|
||||
});
|
||||
|
||||
if (i18nJSON) {
|
||||
translations = i18nJSON;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Error fetching custom translations:", error);
|
||||
}
|
||||
|
||||
const languageHeader = await (await headers()).get(LANGUAGE_HEADER_NAME);
|
||||
if (languageHeader) {
|
||||
const headerLocale = languageHeader.split(",")[0].split("-")[0]; // Extract the language code
|
||||
@@ -24,12 +47,13 @@ export default getRequestConfig(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const userMessages = (await import(`../../locales/${locale}.json`)).default;
|
||||
const customMessages = translations;
|
||||
const localeMessages = (await import(`../../locales/${locale}.json`)).default;
|
||||
const fallbackMessages = (await import(`../../locales/${fallback}.json`))
|
||||
.default;
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: deepmerge(fallbackMessages, userMessages),
|
||||
messages: deepmerge.all([fallbackMessages, localeMessages, customMessages]),
|
||||
};
|
||||
});
|
||||
|
@@ -109,15 +109,20 @@ export async function createSessionAndUpdateCookie(command: {
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSessionForIdpAndUpdateCookie(
|
||||
userId: string,
|
||||
export async function createSessionForIdpAndUpdateCookie({
|
||||
userId,
|
||||
idpIntent,
|
||||
requestId,
|
||||
lifetime,
|
||||
}: {
|
||||
userId: string;
|
||||
idpIntent: {
|
||||
idpIntentId?: string | undefined;
|
||||
idpIntentToken?: string | undefined;
|
||||
},
|
||||
requestId: string | undefined,
|
||||
lifetime?: Duration,
|
||||
): Promise<Session> {
|
||||
};
|
||||
requestId: string | undefined;
|
||||
lifetime?: Duration;
|
||||
}): Promise<Session> {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
|
@@ -122,12 +122,12 @@ export async function createNewSessionFromIdpIntent(
|
||||
organization: userResponse.user.details?.resourceOwner,
|
||||
});
|
||||
|
||||
const session = await createSessionForIdpAndUpdateCookie(
|
||||
command.userId,
|
||||
command.idpIntent,
|
||||
command.requestId,
|
||||
loginSettings?.externalLoginCheckLifetime,
|
||||
);
|
||||
const session = await createSessionForIdpAndUpdateCookie({
|
||||
userId: command.userId,
|
||||
idpIntent: command.idpIntent,
|
||||
requestId: command.requestId,
|
||||
lifetime: loginSettings?.externalLoginCheckLifetime,
|
||||
});
|
||||
|
||||
if (!session || !session.factors?.user) {
|
||||
return { error: "Could not create session" };
|
||||
|
@@ -1,58 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { addHumanUser, createInviteCode } from "@/lib/zitadel";
|
||||
import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { headers } from "next/headers";
|
||||
import { getServiceUrlFromHeaders } from "../service-url";
|
||||
|
||||
type InviteUserCommand = {
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
password?: string;
|
||||
organization?: string;
|
||||
requestId?: string;
|
||||
};
|
||||
|
||||
export type RegisterUserResponse = {
|
||||
userId: string;
|
||||
sessionId: string;
|
||||
factors: Factors | undefined;
|
||||
};
|
||||
|
||||
export async function inviteUser(command: InviteUserCommand) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host) {
|
||||
return { error: "Could not get domain" };
|
||||
}
|
||||
|
||||
const human = await addHumanUser({
|
||||
serviceUrl,
|
||||
email: command.email,
|
||||
firstName: command.firstName,
|
||||
lastName: command.lastName,
|
||||
password: command.password ? command.password : undefined,
|
||||
organization: command.organization,
|
||||
});
|
||||
|
||||
if (!human) {
|
||||
return { error: "Could not create user" };
|
||||
}
|
||||
|
||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
|
||||
|
||||
const codeResponse = await createInviteCode({
|
||||
serviceUrl,
|
||||
urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`,
|
||||
userId: human.userId,
|
||||
});
|
||||
|
||||
if (!codeResponse || !human) {
|
||||
return { error: "Could not create invite code" };
|
||||
}
|
||||
|
||||
return human.userId;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user