mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:27:32 +00:00
merge main
This commit is contained in:
71
acceptance/docker-compose.yaml
Normal file
71
acceptance/docker-compose.yaml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
services:
|
||||||
|
zitadel:
|
||||||
|
user: "${ZITADEL_DEV_UID}"
|
||||||
|
image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:02617cf17fdde849378c1a6b5254bbfb2745b164}"
|
||||||
|
command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml'
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- ./pat:/pat
|
||||||
|
- ./zitadel.yaml:/zitadel.yaml
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: "service_healthy"
|
||||||
|
extra_hosts:
|
||||||
|
- "localhost:host-gateway"
|
||||||
|
|
||||||
|
db:
|
||||||
|
restart: "always"
|
||||||
|
image: postgres:17.0-alpine3.19
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=zitadel
|
||||||
|
- PGUSER=zitadel
|
||||||
|
- POSTGRES_DB=zitadel
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
|
command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c shared_buffers=1GB -c work_mem=16MB -c effective_io_concurrency=100 -c wal_level=minimal -c archive_mode=off -c max_wal_senders=0
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready"]
|
||||||
|
interval: "10s"
|
||||||
|
timeout: "30s"
|
||||||
|
retries: 5
|
||||||
|
start_period: "20s"
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
wait_for_zitadel:
|
||||||
|
image: curlimages/curl:8.00.1
|
||||||
|
command: /bin/sh -c "until curl -s -o /dev/null -i -f http://zitadel:8080/debug/ready; do echo 'waiting' && sleep 1; done; echo 'ready' && sleep 5;" || false
|
||||||
|
depends_on:
|
||||||
|
- zitadel
|
||||||
|
|
||||||
|
setup:
|
||||||
|
user: "${ZITADEL_DEV_UID}"
|
||||||
|
container_name: setup
|
||||||
|
image: acceptance-setup:latest
|
||||||
|
environment:
|
||||||
|
PAT_FILE: /pat/zitadel-admin-sa.pat
|
||||||
|
ZITADEL_API_INTERNAL_URL: http://zitadel:8080
|
||||||
|
WRITE_ENVIRONMENT_FILE: /apps/login/.env.local
|
||||||
|
WRITE_TEST_ENVIRONMENT_FILE: /acceptance/tests/.env.local
|
||||||
|
SINK_EMAIL_INTERNAL_URL: http://sink:3333/email
|
||||||
|
SINK_SMS_INTERNAL_URL: http://sink:3333/sms
|
||||||
|
SINK_NOTIFICATION_URL: http://localhost:3333/notification
|
||||||
|
volumes:
|
||||||
|
- "./pat:/pat"
|
||||||
|
- "../apps/login:/apps/login"
|
||||||
|
- "../acceptance/tests:/acceptance/tests"
|
||||||
|
depends_on:
|
||||||
|
wait_for_zitadel:
|
||||||
|
condition: "service_completed_successfully"
|
||||||
|
|
||||||
|
sink:
|
||||||
|
image: golang:1.24-alpine
|
||||||
|
container_name: sink
|
||||||
|
command: go run /sink/main.go -port '3333' -email '/email' -sms '/sms' -notification '/notification'
|
||||||
|
ports:
|
||||||
|
- 3333:3333
|
||||||
|
volumes:
|
||||||
|
- "./sink:/sink"
|
||||||
|
depends_on:
|
||||||
|
setup:
|
||||||
|
condition: "service_completed_successfully"
|
2
apps/login/next-env-vars.d.ts
vendored
2
apps/login/next-env-vars.d.ts
vendored
@@ -28,6 +28,6 @@ declare namespace NodeJS {
|
|||||||
* Optional: custom request headers to be added to every request
|
* Optional: custom request headers to be added to every request
|
||||||
* Split by comma, key value pairs separated by colon
|
* Split by comma, key value pairs separated by colon
|
||||||
*/
|
*/
|
||||||
CUSTOM_REQUEST_HEADERS: string;
|
CUSTOM_REQUEST_HEADERS?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -217,7 +217,7 @@ export async function GET(request: NextRequest) {
|
|||||||
params.set("organization", organization);
|
params.set("organization", organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
return startIdentityProviderFlow({
|
let url: string | null = await startIdentityProviderFlow({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
idpId,
|
idpId,
|
||||||
urls: {
|
urls: {
|
||||||
@@ -228,14 +228,21 @@ export async function GET(request: NextRequest) {
|
|||||||
`${origin}/idp/${provider}/failure?` +
|
`${origin}/idp/${provider}/failure?` +
|
||||||
new URLSearchParams(params),
|
new URLSearchParams(params),
|
||||||
},
|
},
|
||||||
}).then((resp) => {
|
|
||||||
if (
|
|
||||||
resp.nextStep.value &&
|
|
||||||
typeof resp.nextStep.value === "string"
|
|
||||||
) {
|
|
||||||
return NextResponse.redirect(resp.nextStep.value);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Could not start IDP flow" },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.startsWith("/")) {
|
||||||
|
// if the url is a relative path, construct the absolute url
|
||||||
|
url = constructUrl(request, url).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.redirect(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,7 @@ export default getRequestConfig(async () => {
|
|||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
const i18nOrganization = _headers.get("x-zitadel-i18n-organization") || ""; // You may need to set this header in middleware
|
const i18nOrganization = _headers.get("x-zitadel-i18n-organization") || ""; // You may need to set this header in middleware
|
||||||
console.log("i18nOrganization:", i18nOrganization);
|
|
||||||
let translations: JsonObject | {} = {};
|
let translations: JsonObject | {} = {};
|
||||||
try {
|
try {
|
||||||
const i18nJSON = await getHostedLoginTranslation({
|
const i18nJSON = await getHostedLoginTranslation({
|
||||||
|
@@ -1,20 +1,16 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { createServerTransport } from "@zitadel/client/node";
|
|
||||||
import { createUserServiceClient } from "@zitadel/client/v2";
|
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { getSessionCookieById } from "./cookies";
|
import { getSessionCookieById } from "./cookies";
|
||||||
import { getServiceUrlFromHeaders } from "./service-url";
|
import { getServiceUrlFromHeaders } from "./service-url";
|
||||||
import { getSession } from "./zitadel";
|
import { createServerTransport, getSession } from "./zitadel";
|
||||||
|
|
||||||
const transport = async (serviceUrl: string, token: string) => {
|
|
||||||
return createServerTransport(token, {
|
|
||||||
baseUrl: serviceUrl,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const myUserService = async (serviceUrl: string, sessionToken: string) => {
|
const myUserService = async (serviceUrl: string, sessionToken: string) => {
|
||||||
const transportPromise = await transport(serviceUrl, sessionToken);
|
const transportPromise = await createServerTransport(
|
||||||
|
sessionToken,
|
||||||
|
serviceUrl,
|
||||||
|
);
|
||||||
return createUserServiceClient(transportPromise);
|
return createUserServiceClient(transportPromise);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -74,22 +74,20 @@ export type StartIDPFlowCommand = {
|
|||||||
async function startIDPFlow(command: StartIDPFlowCommand) {
|
async function startIDPFlow(command: StartIDPFlowCommand) {
|
||||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
|
||||||
|
|
||||||
return startIdentityProviderFlow({
|
const url = await startIdentityProviderFlow({
|
||||||
serviceUrl: command.serviceUrl,
|
serviceUrl: command.serviceUrl,
|
||||||
idpId: command.idpId,
|
idpId: command.idpId,
|
||||||
urls: {
|
urls: {
|
||||||
successUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.successUrl}`,
|
successUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.successUrl}`,
|
||||||
failureUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.failureUrl}`,
|
failureUrl: `${command.host.includes("localhost") ? "http://" : "https://"}${command.host}${basePath}${command.failureUrl}`,
|
||||||
},
|
},
|
||||||
}).then((response) => {
|
|
||||||
if (
|
|
||||||
response &&
|
|
||||||
response.nextStep.case === "authUrl" &&
|
|
||||||
response?.nextStep.value
|
|
||||||
) {
|
|
||||||
return { redirect: response.nextStep.value };
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return { error: "Could not start IDP flow" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { redirect: url };
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateNewSessionCommand = {
|
type CreateNewSessionCommand = {
|
||||||
|
@@ -102,7 +102,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
|
|
||||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
|
||||||
|
|
||||||
const resp = await startIdentityProviderFlow({
|
const url = await startIdentityProviderFlow({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
idpId: identityProviders[0].id,
|
idpId: identityProviders[0].id,
|
||||||
urls: {
|
urls: {
|
||||||
@@ -115,9 +115,11 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resp?.nextStep.case === "authUrl") {
|
if (!url) {
|
||||||
return { redirect: resp.nextStep.value };
|
return { error: "Could not start IDP flow" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { redirect: url };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,7 +168,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
|
|
||||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
|
||||||
|
|
||||||
const resp = await startIdentityProviderFlow({
|
const url = await startIdentityProviderFlow({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
idpId: idp.id,
|
idpId: idp.id,
|
||||||
urls: {
|
urls: {
|
||||||
@@ -179,9 +181,11 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resp?.nextStep.case === "authUrl") {
|
if (!url) {
|
||||||
return { redirect: resp.nextStep.value };
|
return { error: "Could not start IDP flow" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { redirect: url };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -17,7 +17,6 @@ import {
|
|||||||
setUserPassword,
|
setUserPassword,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { ConnectError, create } from "@zitadel/client";
|
import { ConnectError, create } from "@zitadel/client";
|
||||||
import { createServerTransport } from "@zitadel/client/node";
|
|
||||||
import { createUserServiceClient } from "@zitadel/client/v2";
|
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||||
import {
|
import {
|
||||||
Checks,
|
Checks,
|
||||||
@@ -39,6 +38,7 @@ import {
|
|||||||
checkPasswordChangeRequired,
|
checkPasswordChangeRequired,
|
||||||
checkUserVerification,
|
checkUserVerification,
|
||||||
} from "../verify-helper";
|
} from "../verify-helper";
|
||||||
|
import { createServerTransport } from "../zitadel";
|
||||||
|
|
||||||
type ResetPasswordCommand = {
|
type ResetPasswordCommand = {
|
||||||
loginName: string;
|
loginName: string;
|
||||||
@@ -428,9 +428,7 @@ export async function checkSessionAndSetPassword({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const transport = async (serviceUrl: string, token: string) => {
|
const transport = async (serviceUrl: string, token: string) => {
|
||||||
return createServerTransport(token, {
|
return createServerTransport(token, serviceUrl);
|
||||||
baseUrl: serviceUrl,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const myUserService = async (serviceUrl: string, sessionToken: string) => {
|
const myUserService = async (serviceUrl: string, sessionToken: string) => {
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { createClientFor } from "@zitadel/client";
|
import { createClientFor } from "@zitadel/client";
|
||||||
import { createServerTransport } from "@zitadel/client/node";
|
|
||||||
import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb";
|
import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb";
|
||||||
import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||||
import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb";
|
import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb";
|
||||||
@@ -8,6 +7,7 @@ import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_servic
|
|||||||
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
||||||
import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
import { systemAPIToken } from "./api";
|
import { systemAPIToken } from "./api";
|
||||||
|
import { createServerTransport } from "./zitadel";
|
||||||
|
|
||||||
type ServiceClass =
|
type ServiceClass =
|
||||||
| typeof IdentityProviderService
|
| typeof IdentityProviderService
|
||||||
@@ -43,24 +43,7 @@ export async function createServiceForHost<T extends ServiceClass>(
|
|||||||
throw new Error("No token found");
|
throw new Error("No token found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const transport = createServerTransport(token, {
|
const transport = createServerTransport(token, serviceUrl);
|
||||||
baseUrl: serviceUrl,
|
|
||||||
interceptors: !process.env.CUSTOM_REQUEST_HEADERS
|
|
||||||
? undefined
|
|
||||||
: [
|
|
||||||
(next) => {
|
|
||||||
return (req) => {
|
|
||||||
process.env.CUSTOM_REQUEST_HEADERS.split(",").forEach(
|
|
||||||
(header) => {
|
|
||||||
const kv = header.split(":");
|
|
||||||
req.header.set(kv[0], kv[1]);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return next(req);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
return createClientFor<T>(service)(transport);
|
return createClientFor<T>(service)(transport);
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Client, create, Duration } from "@zitadel/client";
|
import { Client, create, Duration } from "@zitadel/client";
|
||||||
|
import { createServerTransport as libCreateServerTransport } from "@zitadel/client/node";
|
||||||
import { makeReqCtx } from "@zitadel/client/v2";
|
import { makeReqCtx } from "@zitadel/client/v2";
|
||||||
import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb";
|
import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb";
|
||||||
import {
|
import {
|
||||||
@@ -23,7 +24,10 @@ import {
|
|||||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||||
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
||||||
import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb";
|
import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb";
|
||||||
import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb";
|
import type {
|
||||||
|
FormData,
|
||||||
|
RedirectURLsJson,
|
||||||
|
} from "@zitadel/proto/zitadel/user/v2/idp_pb";
|
||||||
import {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
SendPasswordResetLinkSchema,
|
SendPasswordResetLinkSchema,
|
||||||
@@ -88,7 +92,6 @@ export async function getHostedLoginTranslation({
|
|||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
console.log(resp);
|
|
||||||
return resp.translations ? resp.translations : undefined;
|
return resp.translations ? resp.translations : undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -964,18 +967,37 @@ export async function startIdentityProviderFlow({
|
|||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
idpId: string;
|
idpId: string;
|
||||||
urls: RedirectURLsJson;
|
urls: RedirectURLsJson;
|
||||||
}) {
|
}): Promise<string | null> {
|
||||||
const userService: Client<typeof UserService> = await createServiceForHost(
|
const userService: Client<typeof UserService> = await createServiceForHost(
|
||||||
UserService,
|
UserService,
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
return userService.startIdentityProviderIntent({
|
return userService
|
||||||
|
.startIdentityProviderIntent({
|
||||||
idpId,
|
idpId,
|
||||||
content: {
|
content: {
|
||||||
case: "urls",
|
case: "urls",
|
||||||
value: urls,
|
value: urls,
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
if (resp.nextStep.case === "authUrl" && resp.nextStep.value) {
|
||||||
|
return resp.nextStep.value;
|
||||||
|
} else if (resp.nextStep.case === "formData" && resp.nextStep.value) {
|
||||||
|
const formData: FormData = resp.nextStep.value;
|
||||||
|
const redirectUrl = "/saml-post";
|
||||||
|
|
||||||
|
const params = new URLSearchParams({ url: formData.url });
|
||||||
|
|
||||||
|
Object.entries(formData.fields).forEach(([k, v]) => {
|
||||||
|
params.append(k, v);
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${redirectUrl}?${params.toString()}`;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1476,3 +1498,28 @@ export async function listAuthenticationMethodTypes({
|
|||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createServerTransport(token: string, baseUrl: string) {
|
||||||
|
return libCreateServerTransport(token, {
|
||||||
|
baseUrl,
|
||||||
|
interceptors: !process.env.CUSTOM_REQUEST_HEADERS
|
||||||
|
? undefined
|
||||||
|
: [
|
||||||
|
(next) => {
|
||||||
|
return (req) => {
|
||||||
|
process.env
|
||||||
|
.CUSTOM_REQUEST_HEADERS!.split(",")
|
||||||
|
.forEach((header) => {
|
||||||
|
const kv = header.split(":");
|
||||||
|
if (kv.length === 2) {
|
||||||
|
req.header.set(kv[0].trim(), kv[1].trim());
|
||||||
|
} else {
|
||||||
|
console.warn(`Skipping malformed header: ${header}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return next(req);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user