mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:47:32 +00:00
Merge branch 'main' into pnpm-turbo-local-gen
This commit is contained in:
2
.github/workflows/ready_for_review.yml
vendored
2
.github/workflows/ready_for_review.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
|
||||
Please make sure you tick the following checkboxes before marking this Pull Request (PR) as ready for review:
|
||||
|
||||
- [ ] I am happy with the code
|
||||
- [ ] I have reviewed my changes and would approve it
|
||||
- [ ] Documentations and examples are up-to-date
|
||||
- [ ] Logical behavior changes are tested automatically
|
||||
- [ ] No debug or dead code
|
||||
|
@@ -9,7 +9,7 @@ plugins:
|
||||
- allow_delete_body
|
||||
- remove_internal_comments=true
|
||||
- preserve_rpc_order=true
|
||||
- local: ./protoc-gen-connect-openapi
|
||||
- local: ./protoc-gen-connect-openapi/protoc-gen-connect-openapi
|
||||
out: .artifacts/openapi3
|
||||
strategy: all
|
||||
opt:
|
||||
|
@@ -218,7 +218,6 @@ module.exports = {
|
||||
showLastUpdateTime: true,
|
||||
editUrl: "https://github.com/zitadel/zitadel/edit/main/docs/",
|
||||
remarkPlugins: [require("mdx-mermaid")],
|
||||
|
||||
docItemComponent: "@theme/ApiItem",
|
||||
},
|
||||
theme: {
|
||||
@@ -243,6 +242,17 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"@signalwire/docusaurus-plugin-llms-txt",
|
||||
{
|
||||
depth: 3,
|
||||
logLevel: 1,
|
||||
content: {
|
||||
excludeRoutes: ["/search"],
|
||||
enableMarkdownFiles: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"docusaurus-plugin-openapi-docs",
|
||||
{
|
||||
|
@@ -30,6 +30,7 @@
|
||||
"@docusaurus/theme-search-algolia": "^3.8.1",
|
||||
"@headlessui/react": "^1.7.4",
|
||||
"@heroicons/react": "^2.0.13",
|
||||
"@signalwire/docusaurus-plugin-llms-txt": "^1.2.0",
|
||||
"@inkeep/cxkit-docusaurus": "^0.5.89",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"clsx": "^1.2.1",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
echo $(uname -m)
|
||||
|
||||
mkdir protoc-gen-connect-openapi
|
||||
cd ./protoc-gen-connect-openapi/
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
curl -L -o protoc-gen-connect-openapi.tar.gz https://github.com/sudorandom/protoc-gen-connect-openapi/releases/download/v0.18.0/protoc-gen-connect-openapi_0.18.0_darwin_all.tar.gz
|
||||
else
|
||||
|
@@ -30,6 +30,7 @@ func (s *Server) ListInstanceDomains(ctx context.Context, req *admin_pb.ListInst
|
||||
}
|
||||
return &admin_pb.ListInstanceDomainsResponse{
|
||||
Result: instance_grpc.DomainsToPb(domains.Domains),
|
||||
SortingColumn: req.SortingColumn,
|
||||
Details: object.ToListDetails(
|
||||
domains.Count,
|
||||
domains.Sequence,
|
||||
@@ -49,6 +50,7 @@ func (s *Server) ListInstanceTrustedDomains(ctx context.Context, req *admin_pb.L
|
||||
}
|
||||
return &admin_pb.ListInstanceTrustedDomainsResponse{
|
||||
Result: instance_grpc.TrustedDomainsToPb(domains.Domains),
|
||||
SortingColumn: req.SortingColumn,
|
||||
Details: object.ToListDetails(
|
||||
domains.Count,
|
||||
domains.Sequence,
|
||||
|
@@ -51,8 +51,23 @@ func ListInstanceTrustedDomainsRequestToModel(req *admin_pb.ListInstanceTrustedD
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: fieldNameToInstanceDomainColumn(req.SortingColumn),
|
||||
SortingColumn: fieldNameToInstanceTrustedDomainColumn(req.SortingColumn),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func fieldNameToInstanceTrustedDomainColumn(fieldName instance.DomainFieldName) query.Column {
|
||||
switch fieldName {
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN:
|
||||
return query.InstanceTrustedDomainDomainCol
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE:
|
||||
return query.InstanceTrustedDomainCreationDateCol
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED,
|
||||
instance.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY,
|
||||
instance.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED:
|
||||
return query.InstanceTrustedDomainCreationDateCol
|
||||
default:
|
||||
return query.Column{}
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,11 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
user, err := l.query.GetUserByLoginName(setContext(r.Context(), authReq.UserOrgID), true, authReq.LoginName)
|
||||
// We check if the user really exists or if it is just a placeholder or an unknown user.
|
||||
// In theory, we could also check for the unknownUserID constant. However, that could disclose
|
||||
// information about the existence of a user to an attacker if they check response times,
|
||||
// since those requests would take shorter than the ones for real users.
|
||||
user, err := l.query.GetUserByID(setContext(r.Context(), authReq.UserOrgID), true, authReq.UserID)
|
||||
if err != nil {
|
||||
if authReq.LoginPolicy.IgnoreUnknownUsernames && zerrors.IsNotFound(err) {
|
||||
err = nil
|
||||
|
@@ -1055,6 +1055,10 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// in case the user was set automatically, we might not have the org set
|
||||
if request.UserOrgID == "" {
|
||||
request.UserOrgID = user.ResourceOwner
|
||||
}
|
||||
userSession, err := userSessionByIDs(ctx, repo.UserSessionViewProvider, repo.UserEventProvider, request.AgentID, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -1,22 +1,41 @@
|
||||
import { getSAMLFormCookie } from "@/lib/saml";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const url = searchParams.get("url");
|
||||
const relayState = searchParams.get("RelayState");
|
||||
const samlResponse = searchParams.get("SAMLResponse");
|
||||
const id = searchParams.get("id");
|
||||
|
||||
if (!url || !relayState || !samlResponse) {
|
||||
return new NextResponse("Missing required parameters", { status: 400 });
|
||||
if (!url) {
|
||||
return new NextResponse("Missing url parameter", { status: 400 });
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
return new NextResponse("Missing id parameter", { status: 400 });
|
||||
}
|
||||
|
||||
const formData = await getSAMLFormCookie(id);
|
||||
|
||||
const formDataParsed = formData ? JSON.parse(formData) : null;
|
||||
|
||||
if (!formDataParsed) {
|
||||
return new NextResponse("SAML form data not found", { status: 404 });
|
||||
}
|
||||
|
||||
// Generate hidden input fields for all key-value pairs in formDataParsed
|
||||
const hiddenInputs = Object.entries(formDataParsed)
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`<input type="hidden" name="${key}" value="${value}" />`,
|
||||
)
|
||||
.join("\n ");
|
||||
|
||||
// Respond with an HTML form that auto-submits via POST
|
||||
const html = `
|
||||
<html>
|
||||
<body onload="document.forms[0].submit()">
|
||||
<form action="${url}" method="post">
|
||||
<input type="hidden" name="RelayState" value="${relayState}" />
|
||||
<input type="hidden" name="SAMLResponse" value="${samlResponse}" />
|
||||
${hiddenInputs}
|
||||
<noscript>
|
||||
<button type="submit">Continue</button>
|
||||
</noscript>
|
||||
|
@@ -520,16 +520,24 @@ export async function GET(request: NextRequest) {
|
||||
if (url && binding.case === "redirect") {
|
||||
return NextResponse.redirect(url);
|
||||
} else if (url && binding.case === "post") {
|
||||
const redirectUrl = constructUrl(request, "/saml-post");
|
||||
// Create HTML form that auto-submits via POST and escape the SAML cookie
|
||||
const html = `
|
||||
<html>
|
||||
<body onload="document.forms[0].submit()">
|
||||
<form action="${url}" method="post">
|
||||
<input type="hidden" name="RelayState" value="${binding.value.relayState}" />
|
||||
<input type="hidden" name="SAMLResponse" value="${binding.value.samlResponse}" />
|
||||
<noscript>
|
||||
<button type="submit">Continue</button>
|
||||
</noscript>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
redirectUrl.searchParams.set("url", url);
|
||||
redirectUrl.searchParams.set("RelayState", binding.value.relayState);
|
||||
redirectUrl.searchParams.set(
|
||||
"SAMLResponse",
|
||||
binding.value.samlResponse,
|
||||
);
|
||||
|
||||
return NextResponse.redirect(redirectUrl.toString());
|
||||
return new NextResponse(html, {
|
||||
headers: { "Content-Type": "text/html" },
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
"could not create response, redirect user to choose other account",
|
||||
|
@@ -4,7 +4,9 @@ import { createResponse, getLoginSettings } from "@/lib/zitadel";
|
||||
import { create } from "@zitadel/client";
|
||||
import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { constructUrl } from "./service-url";
|
||||
import { isSessionValid } from "./session";
|
||||
|
||||
@@ -17,6 +19,37 @@ type LoginWithSAMLAndSession = {
|
||||
request: NextRequest;
|
||||
};
|
||||
|
||||
export async function getSAMLFormUID() {
|
||||
return uuidv4();
|
||||
}
|
||||
|
||||
export async function setSAMLFormCookie(value: string): Promise<string> {
|
||||
const cookiesList = await cookies();
|
||||
|
||||
const uid = await getSAMLFormUID();
|
||||
|
||||
await cookiesList.set({
|
||||
name: uid,
|
||||
value: value,
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
maxAge: 5 * 60, // 5 minutes
|
||||
});
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
export async function getSAMLFormCookie(uid: string): Promise<string | null> {
|
||||
const cookiesList = await cookies();
|
||||
|
||||
const cookie = cookiesList.get(uid);
|
||||
if (!cookie || !cookie.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cookie.value;
|
||||
}
|
||||
|
||||
export async function loginWithSAMLAndSession({
|
||||
serviceUrl,
|
||||
samlRequest,
|
||||
|
@@ -52,6 +52,7 @@ import {
|
||||
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { unstable_cacheLife as cacheLife } from "next/cache";
|
||||
import { getUserAgent } from "./fingerprint";
|
||||
import { setSAMLFormCookie } from "./saml";
|
||||
import { createServiceForHost } from "./service";
|
||||
|
||||
const useCache = process.env.DEBUG !== "true";
|
||||
@@ -981,18 +982,15 @@ export async function startIdentityProviderFlow({
|
||||
value: urls,
|
||||
},
|
||||
})
|
||||
.then((resp) => {
|
||||
.then(async (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);
|
||||
});
|
||||
const dataId = await setSAMLFormCookie(JSON.stringify(formData.fields));
|
||||
const params = new URLSearchParams({ url: formData.url, id: dataId });
|
||||
|
||||
return `${redirectUrl}?${params.toString()}`;
|
||||
} else {
|
||||
|
Reference in New Issue
Block a user