You can use a ZITADEL action if you want to prefill the fields {props.fields} with {props.provider} data.
diff --git a/docs/docs/guides/integrate/identity-providers/additional-information.mdx b/docs/docs/guides/integrate/identity-providers/additional-information.mdx
new file mode 100644
index 0000000000..6231a3b891
--- /dev/null
+++ b/docs/docs/guides/integrate/identity-providers/additional-information.mdx
@@ -0,0 +1,15 @@
+---
+title: Additional Information
+sidebar_label: Additional Information
+---
+
+## Automatically prefill user data
+
+import PrefillAction from './_prefill_action.mdx';
+
+' \
+--data-raw '{
+ "org": {
+ "name": "Organisation C",
+ "domain": "org-c.com"
+ },
+ "human": {
+ "userName": "gigi-giraffe",
+ "profile": {
+ "firstName": "Gigi",
+ "lastName": "Giraffe",
+ "nickName": "gigi-giraffe",
+ "displayName": "Gigi Giraffe",
+ "preferredLanguage": "en",
+ "gender": "GENDER_UNSPECIFIED"
+ },
+ "email": {
+ "email": "gigi@zitadel.com",
+ "isEmailVerified": true
+ },
+ "phone": {
+ "phone": "+41 71 000 00 00",
+ "isPhoneVerified": true
+ },
+ "password": "my_53cr3t-P4$$w0rd"
+ },
+ "roles": [
+ "string"
+ ]
+}'
+```
+
+Detailed description of [Setup Organization](/docs/apis/resources/admin/admin-service-set-up-org#setup-organization)
+
+If you need to add custom data to either the organization or the user you can use the metadata.
+Metadata is a key value construct that allows you to store any additional information to the ressources.
+The set organization metadata request allows you to add one key value pair to an organization:
+[Set Organization Metadata](/docs/apis/resources/mgmt/management-service-set-org-metadata)
+If you have more than one field, you can use the bulk add request:
+[Bulk Set Organization Metadata](/docs/apis/resources/mgmt/management-service-bulk-set-org-metadata)
+
+The same requests also exist on the user ressource:
+[Set User Metadata](/docs/apis/resources/mgmt/management-service-set-user-metadata)
+[Bulk Set User Metadata](/docs/apis/resources/mgmt/management-service-bulk-set-user-metadata)
diff --git a/docs/docs/guides/solution-scenarios/onboarding/end-users.mdx b/docs/docs/guides/solution-scenarios/onboarding/end-users.mdx
new file mode 100644
index 0000000000..ec67b7e2ef
--- /dev/null
+++ b/docs/docs/guides/solution-scenarios/onboarding/end-users.mdx
@@ -0,0 +1,92 @@
+---
+title: Onboard Users
+sidebar_label: Onboard Users
+---
+
+End Users have three different possibilities on how to login with ZITADEL.
+1. Local Account with Username, Password, MFA, Passkey, etc
+2. Social Login like Google, Apple, Github, etc
+3. External Identity Provider hosted/managed by Organization like Azure AD, LDAP, Okta etc
+
+You can either use the hosted login of ZITADEL to let users register themselves, or you can build your own UI and use the existing APIs.
+
+## Manually add/invite users
+
+import CreateUser from '/docs/guides/manage/console/_create-user.mdx';
+
+
+
+## Automated / Self-registration possibilities
+
+If you want to start automating the process of onboarding your users and let them do self-registration the following sections give you some guidance.
+
+### Built-in register form
+
+#### Local User Registration
+
+To allow users to register themselves, you have to enable the "register allowed" in the login behavior settings.
+You will now see the register button on the login screen.
+
+
+
+If nothing else is specified, a user will be registered to the default organization.
+
+
+You can specify another organization, by sending the organization scope in the authorization requests.
+By sending the scope below the settings of the specified organization will be triggered and only users of the said organization will be able to authenticate.
+The users will be registered to the given organization.
+```
+urn:zitadel:iam:org:id:{id}
+```
+
+If the user chooses to register a local account, the register form will be shown.
+All the mandatory fields like given name, family name, e-mail and password have to be filled.
+You can only setup authentication with the built-in form.
+
+
+
+#### Registration with Social Login
+
+To allow your users to register with social logins you have to configure the external identity providers.
+If you only need the social logins for your end users and you want to have them all in the organization, we recommend using the default organization for those users.
+In that case you can configure the identity providers on the default organization.
+If you want to have the social logins on different organizations you can configure the default on the instance, and enable it on the needed organizations.
+
+Please follow the configuration guides for the needed providers: [Let Users Login with Preferred Identity Provider in ZITADEL](/docs/guides/integrate/identity-providers)
+
+The configured providers will be shown on the first login screen or when the users click on the registration button, they will be able to choose between local account or the social login.
+
+
+
+#### Registration with Organization External Identity Provider
+
+If your business customer already have an identity provider, and you want to allow SSO for them, you can configure their providers directly for their organization.
+Configure the needed provider such as Azure AD or OKTA.
+
+Please follow the configuration guides for the needed providers: [Let Users Login with Preferred Identity Provider in ZITADEL](/docs/guides/integrate/identity-providers)
+
+import OrgLoginDescription from "./_org_login_description.mdx";
+
+
+
+### Build your own registration form
+
+ZITADEL allows you to build your own registration form and login UI.
+The registration process highly depends on your needs.
+
+We do have a guide series on how to build your own login ui, which also includes the registration of different authentication methods, such as:
+- Password authentication
+- Multi-Factor
+- Passkeys
+- External Login Providers
+
+You can find all the guides here: [Build your own login UI](https://zitadel.com/docs/guides/integrate/login-ui)
+
+The create user request also allows you to add metadata (key, value) to the user.
+This gives you the possibility to collect additional data from your users during the registration process and store it directly to the user in ZITADEL.
+We recommend storing business relevant data in the database of your application, and only authentication and authorization relevant data in ZITADEL to follow the separation of concern pattern.
+
+#### Registration with Organization External Identity Provider
+
+If you want to know more about the multi-tenancy possibilities of ZITADEL, read the following blog post:
+[Multi-Tenancy and Delegated Access Management with Organizations](https://zitadel.com/blog/multi-tenancy-with-organizations)
\ No newline at end of file
diff --git a/docs/docs/guides/start/quickstart.mdx b/docs/docs/guides/start/quickstart.mdx
index 2634cbbd40..36ca32f8f1 100644
--- a/docs/docs/guides/start/quickstart.mdx
+++ b/docs/docs/guides/start/quickstart.mdx
@@ -3,8 +3,6 @@ title: The ZITADEL Quick Start Guide
sidebar_label: Quick Start Guide
---
-import VSCodeFolderView from "../../../static/img/guides/quickstart/vscode1.png";
-
## Introduction
In this quick start guide, we will be learning some fundamentals on how to set up ZITADEL for user management and application security. Thereafter, we will secure a React-based Single Page Application (SPA) using ZITADEL.
@@ -71,103 +69,69 @@ The order of creation for the above components may vary depending on the specifi

-
2. You can sign up by entering your email address or via social login, e.g., Google. Let's sign up using an email address. Enter your email address and click "Sign in with Email".

-
3. Next, complete your details and click "Continue".

-
4. Add a password as shown below. Adhere to the password requirements, agree to the terms of service and privacy policy by selecting the checkbox, and click the "Get started" button.

-
-5. Enter your login data. Provide the password you just created. Click "next".
+5. Enter your login data. Provide the password you just created. Click "next".

-
6. You will now have to verify your email address.

-
6. Go to your inbox and open the email from ZITADEL and click the "Verify email" button.

-
7. The user is now activated. Click the "login" button.

-
-8. Now you will have to log in with the username and password that you provided. Click “Sign in with Email”.
+8. Now you will have to log in with the username and password that you provided. Click “Sign in with Email”, then provide a name for your team or organization and continue.

-
-9. You will now be asked to create an instance.
-
-
-
-
### 2. Create your first instance
-As a user of the ZITADEL Cloud Customer Portal, you now can create multiple instances to suit your specific needs. This includes instances for development, production, or user acceptance testing, as well as instances for different clients or applications. For example, you might create an instance for each product in a B2C scenario, or an instance for each tenant or customer in a B2B scenario. The possibilities are endless. You can create a pay-as-you-go instance for production purposes.
+As a user of the ZITADEL Cloud Customer Portal, you now can create multiple instances to suit your specific needs. This includes instances for development, production, or user acceptance testing, as well as instances for different clients or applications. For example, you might create an instance for each product in a B2C scenario, or an instance for each tenant or customer in a B2B scenario. The possibilities are endless. You can create your first instance for free.
1. Let’s create an instance. Click on “Create new instance”.

-
-
-2. Provide a name for your instance, select the “Free” plan and click on the “Continue” button at the bottom of this screen.
+2. Provide a name for your instance and click on the “Continue” button at the bottom of this screen.

-
3. Next, you should see the following screen. Add a username and password for the instance manager and click "Create".
-

-
-4. The instance creation process will take a few seconds.
+3. Now you will see the details of your first instance. You can click on "Visit" at the top right to go to your instance.

-
-5. Now you will see the details of your first instance. You can click on "Visit" at the top right to go to your instance.
-
-
-
-
-6. To log in to your instance, provide the username and password, and click “next”.
-
-
-
+6. To log in to your instance, provide the username and password you set in the instance creation, and click “next”.
7. Skip the 2-factor authentication for now by clicking “skip”.
-
-
8. And there you go! You now have access to your instance.

-
### 3. Create your first project
-1. To create a project in the instance you just created, click on “Create a project”.
-
-
-
+1. To create a project in the instance you just created, click on "Projects" in the navigation and then “Create a project”.
2. Insert “Project1” (or any name of your choice) as the project’s name and click the “Continue” button.
@@ -343,315 +307,84 @@ To install Visual Studio Code, go to their [website](https://code.visualstudio.c
2. Navigate to the folder where you want to create the React app.
3. Run the following command to create a new React app named "react-oidc-zitadel":
- `npx create-react-app react-oidc-zitadel`
+ `yarn create react-app react-oidc-zitadel --template typescript`
4. Navigate to the "react-oidc-zitadel" folder:
`cd react-oidc-zitadel`
-This will create the following files in your project:
+5. The dependencies for this project include @zitadel/react and react-router-dom. To include them in your React application, you will need to run the following command in your terminal:
-
-
-5. The dependencies for this project include react-router-dom and oidc-client-ts. To include them in your React application, you will need to run the following command in your terminal:
-
- `npm install react-router-dom oidc-client-ts`
+ `yarn add @zitadel/react react-router-dom`
#### 2. Add source files
-The code needed to run this project can be found [here](https://github.com/zitadel/react-user-authentication).
+The code needed to run this project can be found [here](https://github.com/zitadel/zitadel-react).
1. Replace the content in your App.js file with the one provided below:
-[src/App.js](https://github.com/zitadel/react-user-authentication/blob/main/src/App.js):
+[src/App.tsx](https://github.com/zitadel/zitadel-react/blob/main/src/App.tsx):
```
-import React, { useState, useEffect } from "react";
-import { BrowserRouter, Routes, Route } from "react-router-dom";
-import Login from "./components/Login";
-import Callback from "./components/Callback";
-import authConfig from "./authConfig";
-import { UserManager, WebStorageStateStore } from "oidc-client-ts";
+ const config: ZitadelConfig = {
+ authority: "[YOUR-INSTANCE-URL]",
+ client_id: "[YOUR-CLIENT-ID]",
+ };
-function App() {
- const userManager = new UserManager({
- userStore: new WebStorageStateStore({ store: window.localStorage }),
- ...authConfig,
- });
-
- function authorize() {
- userManager.signinRedirect({ state: "a2123a67ff11413fa19217a9ea0fbad5" });
- }
-
- function clearAuth() {
- userManager.signoutRedirect();
- }
-
- const [authenticated, setAuthenticated] = useState(null);
- const [userInfo, setUserInfo] = useState(null);
-
- useEffect(() => {
- userManager.getUser().then((user) => {
- if (user) {
- setAuthenticated(true);
- } else {
- setAuthenticated(false);
- }
- });
- }, [userManager]);
-
- return (
-
-
- }
- />
-
- }
- />
-
-
- );
-}
-
-export default App;
+ const zitadel = createZitadelAuth(config);
+ ...
```
-The App.js file is the root component of the React app that initializes the OIDC flow and manages the user's session. It does this by:
+The App.tsx file is the root component of the React app that initializes the OIDC flow and manages the user's session. It does this by:
-- Importing the necessary libraries and components from the dependencies, including the `oidc-client-ts` library, the `authConfig` file with the OIDC configuration values, and the Login and Callback components.
-- Initializing a new `UserManager` instance with the OIDC configuration values from the authConfig file. The ` UserManager` instance manages the OIDC flow and stores the user's session information.
-- Defining two functions: `authorize` and `clearAuth`. The `authorize` function initiates the OIDC flow when the user clicks the `login` button, while the `clearAuth` function ends the user's session when the user clicks the `logout` button.
-- Defining two state variables: `authenticated` and `userInfo`. The authenticated variable is a boolean that indicates whether the user is authenticated or not, while the `userInfo` variable stores the user's information when they are authenticated.
-- Using the `useEffect` hook to retrieve the user's session information from the `UserManager` instance and updating the `authenticated` and `userInfo` state variables accordingly.
+- Importing the necessary libraries and components from the dependencies, including the `@zitadel/react` library and the Login and Callback components.
+- Initializing a new `zitadel` instance with the OIDC configuration values. The `zitadel` instance manages the OIDC flow and stores the user's session information.
+- Defining two functions: `authorize` and `signout`. The `authorize` function initiates the OIDC flow when the user clicks the `login` button, while the `signout` function ends the user's session when the user clicks the `logout` button.
+- Defining state variable: `authenticated`. The authenticated variable is a boolean that indicates whether the user is authenticated or not.
+- Using the `useEffect` hook to retrieve the user's session information from the `UserManager` instance and updating the `authenticated` state variable accordingly.
- Defining the routes for the app using the `react-router-dom` library, including the `/` and `/callback` routes for the login and callback pages, respectively. The `Login` and `Callback` components handle the login and callback processes, respectively.
+The provided config extends the `UserManagerSettings` of the `oidc-client-ts` library. Take a look at some of the available configuration options:
+
+- authority (the URL of your instance ending with /).
+- client\*id (the unique identifier for the client application).
+- redirect_uri (the URL to redirect to after the authorization flow is complete)
+- post_logout_redirect_uri (the URL to redirect to after the user logs out)
+- scope (the permissions requested from the user)
+- project_resource_id (To add a ZITADEL project scope. `urn:zitadel:iam:org:project:id:[projectId]:aud` and `urn:zitadel:iam:org:projects:roles` [scopes](https://zitadel.com/docs/apis/openidoauth/scopes#reserved-scopes).)
+- prompt ([the OIDC prompt parameter](http://localhost:3000/docs/apis/openidoauth/endpoints#additional-parameters))
+
2. Create a folder named components in the src directory. Create two files named Login.js and Callback.js.
-3. Paste the following code to Login.js.
+3. Paste the following code to Login.tsx.
-[src/components/Login.js](https://github.com/zitadel/react-user-authentication/blob/main/src/components/Login.js)
-
-```
-import { Navigate } from "react-router-dom";
-
-const Login = ({ auth, handleLogin, userManager }) => {
- return (
-
- {auth === null &&
Loading...
}
- {auth === false && (
-
-
Welcome!
-
-
- )}
- {auth &&
}
-
- );
-};
-
-export default Login;
+```ts reference
+https://github.com/zitadel/zitadel-react/blob/main/src/components/Login.tsx
```
-The `/` route corresponds to the login page, which is rendered by the Login component. The Login(Login.js) component is a functional component that displays the login button and calls the `handleLogin` function (which corresponds to the `authorize` function defined in the App component) when the button is clicked. This initiates the OIDC flow by redirecting the user to the authorization endpoint.
+The `/` route corresponds to the login page, which is rendered by the Login component. The Login(Login.tsx) component is a functional component that displays the login button and calls the `handleLogin` function (which corresponds to the `authorize` function defined in the App component) when the button is clicked. This initiates the OIDC flow by redirecting the user to the authorization endpoint.
4. Paste the following code to Callback.js.
-[src/components/Callback.js](https://github.com/zitadel/react-user-authentication/blob/main/src/components/Callback.js)
-
-```
-import React, { useEffect } from 'react';
-import authConfig from '../authConfig';
-
-const Callback = ({ auth, setAuth, userManager, userInfo, setUserInfo, handleLogout }) => {
-
- useEffect(() => {
- if (auth === null) {
- userManager.signinRedirectCallback().then((user) => {
- if (user) {
- setAuth(true);
- const access_token = user.access_token;
- // Make a request to the user info endpoint using the access token
- fetch(authConfig.userinfo_endpoint, {
- headers: {
- 'Authorization': `Bearer ${access_token}`
- }
- })
- .then(response => response.json())
- .then(userInfo => {
- setUserInfo(userInfo);
- });
- } else {
- setAuth(false);
- }
- }).catch((error) => {
- setAuth(false);
- });
- } else if (auth === true && !userInfo) {
- userManager.getUser().then((user) => {
- const access_token = user.access_token;
-
- fetch(authConfig.userinfo_endpoint, {
- headers: {
- Authorization: `Bearer ${access_token}`,
- },
- })
- .then((response) => response.json())
- .then((userInfo) => {
- setUserInfo(userInfo);
- });
- });
- }
- }, [auth, userManager, setAuth]);
-
-
- if (auth === true && userInfo) {
- return (
-
-
Welcome, {userInfo.name}!
- Your ZITADEL Profile Information
- Name: {userInfo.name}
- Email: {userInfo.email}
- Email Verified: {userInfo.email_verified? "Yes": "No"}
- Locale: {userInfo.locale}
-
-
-
- );
- }
- else {
- return Loading...
;
- }
-
-};
-
-export default Callback;
+```ts reference
+https://github.com/zitadel/zitadel-react/blob/main/src/components/Callback.tsx
```
-The `/callback` route corresponds to the callback page, which is rendered by the Callback(Callback.js) component. The Callback component is also a functional component that handles the callback from the authorization server after the user logs in. It retrieves the authorization code from the URL, exchanges it for an access token and id token, and retrieves the user's information from the userinfo endpoint. It also sets the `authenticated` and `userInfo` state variables in the App(App.js) component and displays the `logout` button. When the `logout` button is clicked, the `clearAuth` function is called and the user's session is ended. The `clearAuth` function is defined in the App component and is called with no arguments. It initiates the end session flow by redirecting the user to the end session endpoint.
+The `/callback` route corresponds to the callback page, which is rendered by the Callback (Callback.tsx) component.
+The Callback component is also a functional component that handles the callback from the authorization server after the user logs in.
+It retrieves the authorization code from the URL, exchanges it for an access token and id token, and retrieves the user's information from the userinfo endpoint.
+It also sets the `authenticated` state variable in the App (App.tsx) component and displays the `logout` button.
+When the `logout` button is clicked, the `clearAuth` function is called and the user's session is ended. The `clearAuth` function is defined in the App component and is called with no arguments. It initiates the end session flow by redirecting the user to the end session endpoint.
-5. Create a new file in the src folder named authConfig.js and paste the following code to it.
-
-[src/authConfig.js](https://github.com/zitadel/react-user-authentication/blob/main/src/authConfig.js)
-
-```
-const authConfig = {
- authority: 'https://some_text.zitadel.cloud/', //Replace with your issuer URL
- client_id: 'ABC123@Project', //Replace with your client id
- redirect_uri: 'http://localhost:3000/callback',
- response_type: 'code',
- scope: 'openid profile email',
- post_logout_redirect_uri: 'http://localhost:3000/',
- userinfo_endpoint: 'https://instance-some_text.zitadel.cloud/oidc/v1/userinfo', //Replace with your user-info endpoint
- response_mode: 'query',
- code_challenge_method: 'S256',
- };
-
- export default authConfig;
-```
-
-The authConfig.js file exports an object with configuration values for the OIDC flow. These values are used to initialize the `UserManager` instance in the App component. The configuration values include the:
-
-- authority (the URL of the authorization server). **_Don’t forget to replace the `authority` value with the “issuer URL” that you obtained from this [step](#referred1)._**
-- client_id (the unique identifier for the client application). **_Don’t forget to replace the `client_id` with your “ClientId” that you obtained from this [step](#referred1)._**
-- redirect_uri (the URL to redirect to after the authorization flow is complete)
-- response_type (the type of response expected from the authorization server)
-- scope (the permissions requested from the user)
-- post_logout_redirect_uri (the URL to redirect to after the user logs out)
-- userinfo_endpoint (the URL of the endpoint to retrieve the user's information). **_Don’t forget to replace the `userinfo_endoint` with your “userinfo_endpoint” that you obtained from this [step](#referred1)._**
-- response_mode (the method to use to send the authorization response)
-- code_challenge_method (the method to use to generate the code challenge).
-
-Note that the oidc-client-ts library automatically handles the generation of the code verifier and code challenge when the user clicks on the login button, eliminating the need for the application to manually generate and include these values in the requests.
+Note that the @zitadel/react library automatically handles the generation of the code verifier and code challenge when the user clicks on the login button, eliminating the need for the application to manually generate and include these values in the requests.
In the PKCE flow, the code verifier is a random string generated by the client application, and the code challenge is a transformed version of the code verifier using the SHA-256 hashing algorithm.
-6. Add a file called style.css to the src folder to apply CSS styling to the pages.
+5. Now you can think about styling your application. Edit the `index.css` and `App.css` to apply CSS styling to the pages.
-[src/style.css](https://github.com/zitadel/react-user-authentication/blob/main/src/style.css)
+### 3. Running the application
-```
-/* The body element covers the entire page, so setting the background color here
- will set the background color for the whole page */
- body {
- font-family: 'Open Sans', sans-serif;
- /* The hex code for the light blue color from the image is #9fd3e0 */
- background-color: #9fd3e0;
- /* Center the text in the body element */
- text-align: center;
- }
-
- /* The h1, h2, and h3 elements are used for headings */
- h1, h2, h3 {
- /* The hex code for the dark blue color from the image is #2c3e50 */
- color: #2c3e50;
- text-align: center;
- }
-
- /* The button element represents a clickable button */
- button {
- /* The hex code for the light purple color from the image is #9b59b6 */
- background-color: #9b59b6;
- /* The hex code for the white color is #ffffff */
- color: #ffffff;
- /* Add some padding and a border to the button */
- padding: 10px 20px;
- border: none;
- /* Add some hover effect to the button */
- cursor: pointer;
- }
-
- button:hover {
- /* The hex code for the dark purple color from the image is #8e44ad */
- background-color: #8e44ad;
- }
-```
-
-7. Go to index.js and replace `import './index.css';` with `import './style.css'`. Your index.js file should look like this:
-
-[src/index.js](https://github.com/zitadel/react-user-authentication/blob/main/src/index.js)
-
-```
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import './style.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
-
-const root = ReactDOM.createRoot(document.getElementById('root'));
-root.render(
-
-
-
-);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
-```
-
-### 4. Running the application
-
-1. Run `npm start` to start the development server.
+1. Run `yarn start` to start the development server.
2. Open your browser and navigate to `http://localhost:3000/` to view the app.
3. You will see the login page, which is the landing page of the app, when you run the application. Click on the “Please log in” button.
@@ -673,14 +406,6 @@ reportWebVitals();

-8. To see if the user has been successfully logged out and the user session has been terminated, click on “Please log in”, which brings you to the following page. Select the same user you logged in with earlier.
-
-
-
-9. You will see that since the login session was terminated after logging out, the user has to enter his password again.
-
-
-
And this brings us to the end of this quick start guide!
This tutorial covered how to configure ZITADEL and how to use React to build an app that communicates with ZITADEL to access secured resources.
diff --git a/docs/docs/sdk-examples/angular.mdx b/docs/docs/sdk-examples/angular.mdx
new file mode 100644
index 0000000000..5211a8494c
--- /dev/null
+++ b/docs/docs/sdk-examples/angular.mdx
@@ -0,0 +1,51 @@
+---
+title: Angular
+sidebar_label: Angular
+---
+
+
+
+
+
+ |
+
+ Angular is a popular JavaScript framework for building single-page applications (SPAs) that is known for its two-way data binding, dependency injection, and modular architecture.
+ Integrate authentication to your Angular application easily by using the zitadel-angular Example.
+ |
+
+
+
+### Resources
+- [Angular Example Application with ZITADEL Login](https://github.com/zitadel/zitadel-angular)
+- [Step-By-Step Guide](/docs/examples/login/angular)
+- [ZITADEL Console](https://github.com/zitadel/zitadel/tree/main/console) is built with Angular and can also be used as a reference
+
+
+### Angular SDK
+
+ZITADEL does not provide an Angular specific SDK.
+But you can integrate ZITADEL to your application by using any OIDC Library such as [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc).
+
+Check out our [Example Application](/docs/sdk-examples/angular#example-application)
+
+### Example Application
+
+The [zitadel-angular](https://github.com/zitadel/zitadel-angular) repository includes an Example Application ready to start and show how an Angular application looks like with integrated ZITADEL Login.
+
+What does the Example include:
+- Home Page with Login Button
+- Authenticating user with OIDC PKCE Flow
+- Private Page: Shows user information of authenticated user, only accessible after login
+- Logout
+
+### Step-By-Step Guide
+
+The [Step-By-Step Guide](/docs/examples/login/angular) leads you through the whole process from configuring the right application in ZITADEL to a ready application with integrated Login.
+
+After completing the Step-By-Step Guide you will have:
+1. Example Web Application with integrated ZITADEL Login
+2. Example page accessible by authenticated user showing retrieved user information
+3. Logout
+4. Correct setup for your application in ZITADEL
+
+
diff --git a/docs/docs/sdk-examples/flutter.mdx b/docs/docs/sdk-examples/flutter.mdx
new file mode 100644
index 0000000000..c0b19429ac
--- /dev/null
+++ b/docs/docs/sdk-examples/flutter.mdx
@@ -0,0 +1,67 @@
+---
+title: Flutter
+sidebar_label: Flutter
+---
+
+
+
+
+
+ |
+
+ Flutter is a cross-platform mobile app development framework that allows developers to build native iOS and Android apps using a single codebase.
+ Integrate authentication to your Flutter App easily by using the zitadel-flutter Example.
+ |
+
+
+
+### Resources
+- [Flutter Example Application Repository](https://github.com/zitadel/zitadel_flutter)
+- [Step-By-Step Guide](/docs/examples/login/flutter) to create your Flutter App with ZITADEL Login
+- [Dart Client Library for ZITADEL](https://github.com/smartive/zitadel-dart)
+
+### Flutter SDK
+
+ZITADEL doesn't provide a specific Flutter SDK for authentication in your Web/Mobile App.
+You can use any OIDC Library such as [package:oidc](https://pub.dev/packages/oidc).
+For Mobile Apps we recommend [Flutter AppAuth](https://pub.dev/packages/flutter_appauth).
+
+Check out our [Example Application](/docs/sdk-examples/flutter#example-application).
+
+Additionally, you can use [smartive/zitadel-dart](https://github.com/smartive/zitadel-dart) for user and resource management.
+- Manage Resources through ZITADEL APIs
+ - Authenticate Service User
+ - Generated gRPC Clients for integrating ZITADEL API
+ - User, Organization, Project, etc. Management
+
+:::note
+This library is built by our community.
+:::
+
+### Example Application
+
+The [zitadel-flutter](https://github.com/zitadel/zitadel_flutter) repository includes an Example Application ready to start and show how an Flutter application looks like with integrated ZITADEL Login.
+
+What does the Example include:
+- Home Page with Login Button
+- Authenticating user with OIDC PKCE Flow
+- Private Page: Only accessible after login
+
+### Step-By-Step Guide
+
+The [Step-By-Step Guide](/docs/examples/login/flutter) leads you through the whole process from configuring the right application in ZITADEL to a ready application with integrated Login.
+
+After completing the Step-By-Step Guide you will have:
+1. Example Mobile App with integrated ZITADEL Login
+2. Example page accessible by authenticated user
+3. Correct setup for your application in ZITADEL
+
+
+

+

+
+
+
+

+

+
diff --git a/docs/docs/sdk-examples/go.mdx b/docs/docs/sdk-examples/go.mdx
new file mode 100644
index 0000000000..77e11f37d9
--- /dev/null
+++ b/docs/docs/sdk-examples/go.mdx
@@ -0,0 +1,105 @@
+---
+title: Go
+sidebar_label: Go
+---
+
+
+
+
+
+ |
+
+ Go is an open-source, compiled programming language that is known for its simplicity, efficiency, and concurrency capabilities.
+ Get started integrating authentication to your Go Application by checking out our zitadel-go SDK.
+ |
+
+
+
+
+### Resources
+- [Example App Repository](https://github.com/zitadel/zitadel-go)
+- [Go SDK](https://github.com/zitadel/zitadel-go)
+- [Web APP Step-By-Step Guide](/docs/examples/login/go)
+- [API APP Step-By-Step Guide](/docs/examples/secure-api/go)
+- [Go OIDC Library](https://github.com/zitadel/oidc)
+
+### Go SDK
+
+The [zitadel-go](https://github.com/zitadel/zitadel-go) SDK is a wrapper around the [zitadel/oidc](https://github.com/zitadel/oidc) to integrate Login into your Web App and abstracts the handling of specific configurations for ZITADEL.
+Additionally secure your business APIs and handle permission checks for your users.
+Last part is the integration of the ZITADEL APIs to handle user and resource management.
+
+The following features are covered by the SDK:
+- Authentication in your Web App
+ - Authenticate your user with ZITADEL using OIDC
+ - Requesting ZITADEL userinfo endpoint to get user data
+ - Refresh Token
+ - Requesting User Roles from userinfo
+ - Check if user has specified role
+ - Logout
+- Secure your APIs
+ - Authorization Check using OAuth2 Introspection
+ - Check User Roles on Endpoint
+- Manage Resources through ZITADEL APIs
+ - Authenticate Service User
+ - Generated gRPC Clients for integrating ZITADEL API
+ - User, Organization, Project, etc. Management
+
+The goal is to extend the SDK over the time with the following features:
+- Build your own login UI using our Session API
+
+### Go Examples
+
+You can find different examples for building your Go application in the following package of the repository:
+[zitadel-go/example](https://github.com/zitadel/zitadel-go/tree/next/example)
+
+#### Web Application Example
+
+What does the Web Application Example include:
+- Home Page with Login Button
+- Authenticating user with OIDC PKCE Flow
+- Public Page: Accessible without authentication
+- Private Page: Shows user information of authenticated user, only accessible after login
+- Logout
+
+[Example Web App](https://github.com/zitadel/zitadel-go/tree/next/example/app)
+
+#### API Application Example
+
+What does the API Application Example include:
+- REST API Application secured with Spring Security and OAuth2
+- Public Endpoint: Accessible without authentication
+- Private Endpoint: Accessible with a token
+- Administrator Endpoint: Accessible with a token of a user with admin role
+
+[Example API App](https://github.com/zitadel/zitadel-go/tree/next/example/api/http)
+
+### Step-By-Step Guide
+
+For Go we do have two different Step-By-Step Guides.
+One to create your web application with integrated login and one to create your API with permission checks for calling users.
+The guides lead you through the whole process from configuring the right application in ZITADEL to a ready application with integrated login or authentication checks.
+
+#### Web Application Guide
+
+After completing the Step-By-Step Guide you will have:
+1. Example Web Application with integrated ZITADEL Login
+2. Example page accessible by authenticated user showing retrieved user information
+4. Logout
+5. Correct setup for your application in ZITADEL
+
+[Web APP Step-By-Step Guide](/docs/examples/login/go)
+
+
+
+
+#### API Application Guide
+
+After completing the Step-By-Step Guide you will have:
+1. Example REST API checking tokens against ZITADEL with OAuth2
+2. Public Endpoint accessible by any user
+3. Private Endpoint accessible by authenticated user
+4. Private Endpoint accessible by user with role 'admin'
+5. Correct setup for your application in ZITADEL
+
+[API APP Step-By-Step Guide](/docs/examples/secure-api/go)
diff --git a/docs/docs/sdk-examples/introduction.mdx b/docs/docs/sdk-examples/introduction.mdx
new file mode 100644
index 0000000000..5ee006c6c5
--- /dev/null
+++ b/docs/docs/sdk-examples/introduction.mdx
@@ -0,0 +1,187 @@
+---
+title: Introduction
+sidebar_label: Introduction
+---
+
+You can integrate ZITADEL quickly into your application and be up and running within minutes.
+To achieve your goals as fast as possible, we provide you with SDKs, Example Repositories and Guides.
+
+The SDKs and Integration depend on the framework and language you are using.
+
+import { Tile } from "../../src/components/tile";
+
+### Resources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### OIDC Libraries
+
+OIDC is a standard for authentication and most languages and frameworks do provide a OIDC library which can be easily integrated to your application.
+If we do not provide an specific example, SDK or guide, we strongly recommend using existing authentication libraries for your
+language or framework instead of building your own.
+Certified libraries have undergone rigorous testing and validation to ensure high security and reliability.
+There are many recommended libraries available, this saves time and ensures that users' data is well-protected.
+
+You might want to check out the following links to find a good library:
+
+- [awesome-auth](https://github.com/casbin/awesome-auth)
+- [OpenID General References](https://openid.net/developers/libraries/)
+- [OpenID certified developer tools](https://openid.net/certified-open-id-developer-tools/)
+
+### Other example applications
+
+- [B2B customer portal](https://github.com/zitadel/zitadel-nextjs-b2b): Showcase the use of personal access tokens in a B2B environment. Uses NextJS Framework.
+- [Frontend with backend API](https://github.com/zitadel/example-quote-generator-app): A simple web application using a React front-end and a Python back-end API, both secured using ZITADEL
+- [Introspection](https://github.com/zitadel/examples-api-access-and-token-introspection): Python examples for securing an API and invoking it as a service user
+- [Fine-grained authorization](https://github.com/zitadel/example-fine-grained-authorization): Leverage actions, custom metadata, and claims for attribute-based access control
+
+Search for the "example" tag in our repository to [explore all examples](https://github.com/search?q=topic%3Aexamples+org%3Azitadel&type=repositories).
+
+### Missing SDK
+
+Is your language/framework missing? Fear not, you can generate your gRPC API Client with ease.
+
+1. Make sure to install [buf](https://buf.build/docs/installation/)
+2. Create a `buf.gen.yaml` and configure the [plugins](https://buf.build/plugins) you need
+3. Run `buf generate https://github.com/zitadel/zitadel#format=git,tag=v2.23.1` (change the versions to your needs)
+
+Let us make an example with Ruby. Any other supported language by buf will work as well. Consult
+the [buf plugin registry](https://buf.build/plugins) for more ideas.
+
+#### Example with Ruby
+
+With gRPC we usually need to generate the client stub and the messages/types. This is why we need two plugins.
+The plugin `grpc/ruby` generates the client stub and the plugin `protocolbuffers/ruby` takes care of the messages/types.
+
+```yaml
+version: v1
+plugins:
+ - plugin: buf.build/grpc/ruby
+ out: gen
+ - plugin: buf.build/protocolbuffers/ruby
+ out: gen
+```
+
+If you now run `buf generate https://github.com/zitadel/zitadel#format=git,tag=v2.23.1` in the folder where
+your `buf.gen.yaml` is located you should see the folder `gen` appear.
+
+If you run `ls -la gen/zitadel/` you should see something like this:
+
+```bash
+ffo@ffo-pc:~/git/zitadel/ruby$ ls -la gen/zitadel/
+total 704
+drwxr-xr-x 2 ffo ffo 4096 Apr 11 16:49 .
+drwxr-xr-x 3 ffo ffo 4096 Apr 11 16:49 ..
+-rw-r--r-- 1 ffo ffo 4397 Apr 11 16:49 action_pb.rb
+-rw-r--r-- 1 ffo ffo 141097 Apr 11 16:49 admin_pb.rb
+-rw-r--r-- 1 ffo ffo 25151 Apr 11 16:49 admin_services_pb.rb
+-rw-r--r-- 1 ffo ffo 6537 Apr 11 16:49 app_pb.rb
+-rw-r--r-- 1 ffo ffo 1134 Apr 11 16:49 auth_n_key_pb.rb
+-rw-r--r-- 1 ffo ffo 32881 Apr 11 16:49 auth_pb.rb
+-rw-r--r-- 1 ffo ffo 6896 Apr 11 16:49 auth_services_pb.rb
+-rw-r--r-- 1 ffo ffo 1571 Apr 11 16:49 change_pb.rb
+-rw-r--r-- 1 ffo ffo 2488 Apr 11 16:49 event_pb.rb
+-rw-r--r-- 1 ffo ffo 14782 Apr 11 16:49 idp_pb.rb
+-rw-r--r-- 1 ffo ffo 5031 Apr 11 16:49 instance_pb.rb
+-rw-r--r-- 1 ffo ffo 223348 Apr 11 16:49 management_pb.rb
+-rw-r--r-- 1 ffo ffo 44402 Apr 11 16:49 management_services_pb.rb
+-rw-r--r-- 1 ffo ffo 3020 Apr 11 16:49 member_pb.rb
+-rw-r--r-- 1 ffo ffo 855 Apr 11 16:49 message_pb.rb
+-rw-r--r-- 1 ffo ffo 1445 Apr 11 16:49 metadata_pb.rb
+-rw-r--r-- 1 ffo ffo 2370 Apr 11 16:49 object_pb.rb
+-rw-r--r-- 1 ffo ffo 621 Apr 11 16:49 options_pb.rb
+-rw-r--r-- 1 ffo ffo 4425 Apr 11 16:49 org_pb.rb
+-rw-r--r-- 1 ffo ffo 8538 Apr 11 16:49 policy_pb.rb
+-rw-r--r-- 1 ffo ffo 8223 Apr 11 16:49 project_pb.rb
+-rw-r--r-- 1 ffo ffo 1022 Apr 11 16:49 quota_pb.rb
+-rw-r--r-- 1 ffo ffo 5872 Apr 11 16:49 settings_pb.rb
+-rw-r--r-- 1 ffo ffo 20985 Apr 11 16:49 system_pb.rb
+-rw-r--r-- 1 ffo ffo 4784 Apr 11 16:49 system_services_pb.rb
+-rw-r--r-- 1 ffo ffo 28759 Apr 11 16:49 text_pb.rb
+-rw-r--r-- 1 ffo ffo 24170 Apr 11 16:49 user_pb.rb
+-rw-r--r-- 1 ffo ffo 13568 Apr 11 16:49 v1_pb.rb
+```
+
+Import these files into your project to start interacting with ZITADEL's APIs.
diff --git a/docs/docs/sdk-examples/java.mdx b/docs/docs/sdk-examples/java.mdx
new file mode 100644
index 0000000000..e7afb5188b
--- /dev/null
+++ b/docs/docs/sdk-examples/java.mdx
@@ -0,0 +1,101 @@
+---
+title: Java Spring Boot
+sidebar_label: Java Spring Boot
+---
+
+
+
+
+
+ |
+
+ Java is a general-purpose programming language designed for object-oriented programming.
+ Spring Security is used to protect your applications from unauthorized access, protect sensitive data, and enforce access control policies.
+ Get started integrating authentication to your Java Web App or API by checking out our zitadel-java Example
+ |
+
+
+
+
+### Resources
+- [Example App Repository with Spring Security](https://github.com/zitadel/zitadel-java)
+- [Example Web App with Spring Security](https://github.com/zitadel/zitadel-java/tree/main/web)
+- [Example API App with Spring Security](https://github.com/zitadel/zitadel-java/tree/main/api)
+- [Web APP Step-By-Step Guide](/docs/examples/login/java-spring)
+- [API APP Step-By-Step Guide](/docs/examples/secure-api/java-spring)
+
+### Java SDK
+
+Java Spring Security is a widely used and common framework to integrate Authentication and Authorization into your Applications.
+As of this at the moment there is no specific ZITADEL SDK, but we do show you how to integrate ZITADEL with Java Spring Security.
+You can use this for both your Web as for your API Applications.
+
+The following features are covered by Java Spring Security:
+- Authenticate your user using OIDC
+- Requesting ZITADEL userinfo endpoint to get user data
+- Refresh Token
+- Requesting User Roles from userinfo
+- Check if user has specified role
+- Logout
+
+The goal is to have a ZITADEL Java SDK in the future which will cover the following:
+- Wrapper around Java Spring Security
+- Authentication with OIDC
+- Authorization and checking Rolls
+- Integrate ZITADEL APIs to read and manage resources
+- Integrate ZITADEL Session API to create your own login UI
+
+### Java Examples
+
+#### Web Application Example
+
+What does the Web Application Example include:
+- Home Page with Login Button
+- Authenticating user with OIDC PKCE Flow
+- Public Page: Accessible without authentication
+- Private Page: Shows user information of authenticated user, only accessible after login
+- Task Page: Only accessible after login and uses the API example. Requires the admin role for the application for some interaction.
+- Logout
+
+[Example Web App with Spring Security](https://github.com/zitadel/zitadel-java/web)
+
+#### API Application Example
+
+What does the API Application Example include:
+- REST API Application secured with Spring Security and OAuth2
+- Public Endpoint: Accessible without authentication
+- Private Endpoint: Accessible with a token
+- Administrator Endpoint: Accessible with a token of a user with admin role
+
+[Example API App with Spring Security](https://github.com/zitadel/zitadel-java/api)
+
+### Step-By-Step Guide
+
+For Java Spring we do have two different Step-By-Step Guides.
+One to create your web application with integrated login and one to create your API with permission checks for calling users.
+The guides lead you through the whole process from configuring the right application in ZITADEL to a ready application with integrated login or authentication checks.
+
+#### Web Application Guide
+
+After completing the Step-By-Step Guide you will have:
+1. Example Web Application with integrated ZITADEL Login
+2. Example page accessible by authenticated user showing retrieved user information
+3. Example page accessible by authenticated user showing task list
+ - Task list can be read by authenticated user
+ - New tasks can be created by user with admin role
+4. Logout
+5. Correct setup for your application in ZITADEL
+
+[Web APP Step-By-Step Guide](/docs/examples/login/java-spring)
+
+
+#### API Application Guide
+
+After completing the Step-By-Step Guide you will have:
+1. Example REST API checking tokens against ZITADEL with OAuth2
+2. Public Endpoint accessible by any user
+3. Private Endpoint accessible by authenticated user
+4. Private Endpoint accessible by user with role 'admin'
+5. Correct setup for your application in ZITADEL
+
+[API APP Step-By-Step Guide](/docs/examples/secure-api/java-spring)
diff --git a/docs/docs/sdk-examples/nestjs.mdx b/docs/docs/sdk-examples/nestjs.mdx
new file mode 100644
index 0000000000..9238d823f7
--- /dev/null
+++ b/docs/docs/sdk-examples/nestjs.mdx
@@ -0,0 +1,56 @@
+---
+title: NestJS
+sidebar_label: NestJS
+---
+
+
+
+
+
+ |
+
+ NestJS is a comprehensive and well-maintained TypeScript-based framework for building large-scale, scalable, and maintainable Node.js applications.
+ Get started integrating ZITADEL to your NestJS API by checking out the zitadel-nodejs-nestjs Example.
+ |
+
+
+
+
+### Resources
+- [Example App Repository](https://github.com/ehwplus/zitadel-nodejs-nestjs)
+- [Step-By-Step Guide](/docs/examples/secure-api/nodejs-nestjs)
+- [Passport.js](https://github.com/buehler/node-passport-zitadel) for integrating authentication
+- [Node.js gRPC Client Library](https://www.npmjs.com/package/@zitadel/node) for user and resource management
+
+### SDK
+
+ZITADEL doesn't provide a specific Nestjs SDK. You can use [passport-zitadel](https://github.com/buehler/node-passport-zitadel) for the authentication part.
+
+Check out the [Example Application](/docs/sdk-examples/nestjs#example-application).
+
+Additionally, you can use [@zitadel/node](https://www.npmjs.com/package/@zitadel/node) for user and resource management.
+- Manage Resources through ZITADEL APIs
+- Authenticate Service User
+- Generated gRPC Clients for integrating ZITADEL API
+- User, Organization, Project, etc. Management
+
+:::note
+This library is built by our community.
+:::
+
+### Example Application
+
+What does the API Application Example include:
+- REST API Application secured with Spring Security and OAuth2
+- Private Endpoint: Accessible with a token
+
+[Example API Application](https://github.com/ehwplus/zitadel-nodejs-nestjs)
+
+### Step-By-Step Guide
+
+After completing the Step-By-Step Guide you will have:
+1. Example REST API checking tokens against ZITADEL with OAuth2
+2. Private Endpoint accessible by authenticated user
+3. Correct setup for your application in ZITADEL
+
+[API APP Step-By-Step Guide](/docs/examples/secure-api/nodejs-nestjs)
\ No newline at end of file
diff --git a/docs/docs/sdk-examples/nextjs.mdx b/docs/docs/sdk-examples/nextjs.mdx
new file mode 100644
index 0000000000..8aebbc33f7
--- /dev/null
+++ b/docs/docs/sdk-examples/nextjs.mdx
@@ -0,0 +1,54 @@
+---
+title: Next.js
+sidebar_label: Next.js
+---
+
+
+
+
+
+
+ |
+
+ Next.js is a React-based framework that provides a powerful and flexible set of tools for building high-performance, SEO-friendly web applications.
+ Get started integrating authentication to your Next.js Application by checking out our zitadel-nextjs Example.
+ |
+
+
+
+
+### Resources
+- [Example App Repository](https://github.com/zitadel/zitadel-nextjs)
+- [Step-By-Step Guide](/docs/examples/login/nextjs)
+- [B2B customer portal](https://github.com/zitadel/zitadel-nextjs-b2b): Showcase the use of personal access tokens in a B2B environment built with Next.js
+
+### SDK
+
+ZITADEL does not provide a Next.js specific SDK.
+However, for integrating authentication into your Next.js Application you can use [NextAuth ZITADEL Provider](https://next-auth.js.org/providers/zitadel)
+
+Check out our [Example Application](/docs/sdk-examples/nextjs#example-application)
+
+
+### Example Application
+
+The [zitadel-nextjs](https://github.com/zitadel/zitadel-nextjs) repository includes an Example Application ready to start and show how a Next.js application looks like with integrated ZITADEL Login.
+
+What does the Example include:
+TODO: ?? Update with correct infos
+- Home Page with Login Button
+- Authenticating user with OIDC PKCE Flow
+- Private Page: Shows user information of authenticated user, only accessible after login
+- Logout
+
+### Step-By-Step Guide
+
+The [Step-By-Step Guide](/docs/examples/login/nextjs) leads you through the whole process from configuring the right application in ZITADEL to a ready application with integrated Login.
+
+After completing the Step-By-Step Guide you will have:
+TODO: ?? Updated with correct infos
+1. Example Web Application with integrated ZITADEL Login
+2. Example page accessible by authenticated user showing retrieved user information
+3. Logout
+4. Correct setup for your application in ZITADEL
+
diff --git a/docs/docs/sdk-examples/python-django.mdx b/docs/docs/sdk-examples/python-django.mdx
new file mode 100644
index 0000000000..8c7b3a8898
--- /dev/null
+++ b/docs/docs/sdk-examples/python-django.mdx
@@ -0,0 +1,71 @@
+---
+title: Python Django
+sidebar_label: Python Django
+---
+
+
+
+
+
+ |
+
+ Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design.
+ Get started integrating authentication to your Django API Application by checking out our example-python-django-oauth or your Django Web application by checking out our example-python-django-oidc Example.
+ |
+
+
+
+
+### Resources
+- [Example API App Repository](https://github.com/zitadel/example-python-django-oauth)
+- [API App Step-By-Step Guide](/docs/examples/secure-api/python-django)
+- [Example Web App Repository](https://github.com/zitadel/example-python-django-oidc)
+- [Web App Step-By-Step Guide](/docs/examples/login/python-django)
+
+### Django SDK
+
+ZITADEL does not provide a Python Django specific SDK.
+But you can integrate ZITADEL to your application by using any OIDC Library such as [mozilla-django-oidc](https://github.com/mozilla/mozilla-django-oidc) or [authlib](https://github.com/lepture/authlib).
+
+### Examples Application
+
+What does the API Application Example include:
+- REST API Application secured with OAuth2
+- Public Endpoint: Accessible without authentication
+- Private Endpoint: Accessible with a token
+- Administrator Endpoint: Accessible with a token of a user with admin role
+
+[Example API App](https://github.com/zitadel/example-python-django-oauth)
+
+What does the Web Application Example include:
+1. Example Web Application with integrated ZITADEL Login
+2. Example page accessible by authenticated user showing retrieved user information
+3. Example page accessible by authenticated user showing polls
+ - Votes for polls can only be done if authenticated
+ - New polls can be created by user with admin role
+4. Logout
+5. Correct setup for your application in ZITADEL
+
+[Example Web App](https://github.com/zitadel/example-python-django-oidc)
+
+### Step-By-Step Guide
+
+After completing the Step-By-Step Guide for an API you will have:
+1. Example REST API checking tokens against ZITADEL with OAuth2
+2. Public Endpoint accessible by any user
+3. Private Endpoint accessible by authenticated user
+4. Private Endpoint accessible by user with role 'admin'
+5. Correct setup for your application in ZITADEL
+
+[API App Step-By-Step Guide](/docs/examples/secure-api/python-django)
+
+After completing the Step-By-Step Guide for an Web application you will have:
+1. Example Web Application with integrated ZITADEL Login
+2. Example page accessible by authenticated user showing retrieved user information
+3. Example page accessible by authenticated user showing polls
+ - Votes for polls can only be done if authenticated
+ - New polls can be created by user with admin role
+4. Logout
+5. Correct setup for your application in ZITADEL
+
+[Web App Step-By-Step Guide](/docs/examples/login/python-django)
\ No newline at end of file
diff --git a/docs/docs/sdk-examples/python-flask.mdx b/docs/docs/sdk-examples/python-flask.mdx
new file mode 100644
index 0000000000..d443d0b260
--- /dev/null
+++ b/docs/docs/sdk-examples/python-flask.mdx
@@ -0,0 +1,53 @@
+---
+title: Python Flask
+sidebar_label: Python Flask
+---
+
+
+
+
+
+
+ |
+
+ Flask is a lightweight and easy-to-use microframework for Python web development.
+ Get started integrating authentication to your Flask API Application by checking out our example-api-python3-flask Example.
+ |
+
+
+
+
+### Resources
+- [Example App Repository](https://github.com/zitadel/example-api-python3-flask)
+- [API APP Step-By-Step Guide](/docs/examples/secure-api/python-flask)
+- [Frontend with backend API](https://github.com/zitadel/example-quote-generator-app): A simple web application using a React front-end and a Python back-end API, both secured using ZITADEL
+- [Fine-grained authorization](https://github.com/zitadel/example-fine-grained-authorization): Leverage actions, custom metadata, and claims for attribute-based access control
+
+### Flask SDK
+
+ZITADEL does not provide a Python Flask specific SDK.
+But you can integrate ZITADEL to your application by using any OIDC Library such as [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc).
+
+Check out our [Example Application](/docs/sdk-examples/angular#example-application)
+
+
+### Examples Application
+
+What does the API Application Example include:
+- REST API Application secured with OAuth2
+- Public Endpoint: Accessible without authentication
+- Private Endpoint: Accessible with a token
+- Administrator Endpoint: Accessible with a token of a user with admin role
+
+[Example API App](https://github.com/zitadel/example-api-python3-flask)
+
+### Step-By-Step Guide
+
+After completing the Step-By-Step Guide you will have:
+1. Example REST API checking tokens against ZITADEL with OAuth2
+2. Public Endpoint accessible by any user
+3. Private Endpoint accessible by authenticated user
+4. Private Endpoint accessible by user with role 'admin'
+5. Correct setup for your application in ZITADEL
+
+[API APP Step-By-Step Guide](/docs/examples/secure-api/python-flask)
\ No newline at end of file
diff --git a/docs/docs/sdk-examples/react.mdx b/docs/docs/sdk-examples/react.mdx
new file mode 100644
index 0000000000..edbd20bc9f
--- /dev/null
+++ b/docs/docs/sdk-examples/react.mdx
@@ -0,0 +1,72 @@
+---
+title: React
+sidebar_label: React
+---
+
+
+
+
+
+ |
+
+
+ React
+ {" "}
+ is a declarative, component-based JavaScript library for building user
+ interfaces. Integrate authentication to your React application easily by
+ using the{" "}
+
+ zitadel-react
+ {" "}
+ SDK.
+ |
+
+
+
+### Resources
+
+- [SDK with Example App](https://github.com/zitadel/zitadel-react)
+- [Step-By-Step Guide](/docs/examples/login/react)
+
+### React SDK
+
+The [zitadel-react](https://github.com/zitadel/zitadel-react) SDK is a wrapper around the [oidc-client-ts](https://www.npmjs.com/package/oidc-client-ts) and abstracts the handling of specific configurations for ZITADEL.
+
+The following features are covered by the SDK:
+
+- Authenticate your user with ZITADEL using OIDC
+- Requesting ZITADEL userinfo endpoint to get user data
+- Refresh Token
+- Requesting User Roles from userinfo
+- Check if user has specified role
+- Logout
+
+The goal is to extend the SDK over the time with the following features:
+
+- Integrate ZITADEL APIs to read and manage resources
+- Build your own login UI using our Session API
+
+### Example Application
+
+The [zitadel-react](https://github.com/zitadel/zitadel-react) repository also includes an Example Application ready to start and show how a React application looks like with integrated ZITADEL Login.
+
+What does the Example include:
+
+- Home Page with Login Button
+- Authenticating user with OIDC PKCE Flow
+- Public Page: Accessible without authentication
+- Private Page: Shows user information of authenticated user, only accessible after login
+- Administrator Page: Only accessible after login and with specific administrator role for the application
+- Logout
+
+### Step-By-Step Guide
+
+The [Step-By-Step Guide](/docs/examples/login/react) leads you through the whole process from configuring the right application in ZITADEL to a ready application with integrated Login.
+
+After completing the Step-By-Step Guide you will have:
+
+1. Example Web Application with integrated ZITADEL Login
+2. Example page accessible by authenticated user showing retrieved user information
+3. Example administrator page accessible by user with administrator role
+4. Logout
+5. Correct setup for your application in ZITADEL
diff --git a/docs/docs/sdk-examples/symfony.mdx b/docs/docs/sdk-examples/symfony.mdx
new file mode 100644
index 0000000000..297f757486
--- /dev/null
+++ b/docs/docs/sdk-examples/symfony.mdx
@@ -0,0 +1,53 @@
+---
+title: Symfony PHP Framework
+sidebar_label: Symfony PHP
+---
+
+
+
+
+
+ |
+
+ Symfony is a high-performance PHP framework that provides a solid foundation for building scalable and maintainable web applications.
+ Integrate authentication to your Symfony application easily by using the example-symfony-oidc Example.
+ |
+
+
+
+### Resources
+- [Example Web App with ZITADEL Login](https://github.com/zitadel/example-symfony-oidc)
+- [Step-By-Step Guide](/docs/examples/login/symfony)
+
+### PHP SDK
+
+ZITADEL does not provide a Symfony specific SDK.
+But you can integrate ZITADEL to your application by using any OIDC Library such as [symfony-oidc](https://github.com/Drenso/symfony-oidc).
+
+Check out our [Example Application](/docs/sdk-examples/symfony#example-application)
+
+### Example Application
+
+The [example-symfony-oidc](https://github.com/zitadel/example-symfony-oidc) repository includes an Example Application ready to start and show how a Symfony application looks like with integrated ZITADEL Login.
+
+What does the Example include:
+- OIDC Code flow with User Info call after authentication.
+- Fully integrated with Symfony security and firewall.
+- User Role mapping
+- Persistent user data using local sqlite file. See DATABASE_URL in .env.
+- Public page at /
+- Authenticated /profile page for all users.
+- Authenticated /admin page for admin role users.
+- Logout
+
+### Step-By-Step Guide
+
+The [Step-By-Step Guide](/docs/examples/login/symfony) leads you through the whole process from configuring the right application in ZITADEL to a ready application with integrated Login.
+
+After completing the Step-By-Step Guide you will have:
+1. Example Web Application with integrated ZITADEL Login
+2. Example page accessible without authentication
+3. Example page accessible by authenticated user showing retrieved user information
+4. Example administrator page accessible by user with administrator role
+5. Logout
+6. Correct setup for your application in ZITADEL
diff --git a/docs/docs/sdk-examples/vue.mdx b/docs/docs/sdk-examples/vue.mdx
new file mode 100644
index 0000000000..237b4c64a6
--- /dev/null
+++ b/docs/docs/sdk-examples/vue.mdx
@@ -0,0 +1,62 @@
+---
+title: Vue.js
+sidebar_label: Vue.js
+---
+
+
+
+
+
+ |
+
+ Vue.js is a JavaScript framework for building user interfaces that is simple, flexible, and versatile.
+ Integrate authentication to your Vue.js application easily by using the zitadel-vue SDK
+ |
+
+
+
+### Resources
+- [SDK with Example App](https://github.com/zitadel/zitadel-vue)
+- [Step-By-Step Guide](/docs/examples/login/vue)
+
+### Vue SDK
+
+The [zitadel-vue](https://github.com/zitadel/zitadel-vue) SDK is a wrapper around the [vue-oidc-client](https://github.com/soukoku/vue-oidc-client) and abstracts the handling of specific configurations for ZITADEL.
+
+The following features are covered by the SDK:
+- Authenticate your user with ZITADEL using OIDC
+- Requesting ZITADEL userinfo endpoint to get user data
+- Refresh Token
+- Requesting User Roles from userinfo
+- Check if user has specified role
+- Logout
+
+The goal is to extend the SDK over the time with the following features:
+- Integrate ZITADEL APIs to read and manage resources
+- Build your own login UI using our Session API
+
+### Example Application
+
+The [zitadel-vue](https://github.com/zitadel/zitadel-vue) repository also includes an Example Application ready to start and show how a Vue application looks like with integrated ZITADEL Login.
+
+What does the Example include:
+- Home Page with Login Button
+- Authenticating user with OIDC PKCE Flow
+- Public Page: Accessible without authentication
+- Private Page: Shows user information of authenticated user, only accessible after login
+- Administrator Page: Only accessible after login and with specific administrator role for the application
+- Logout
+
+### Step-By-Step Guide
+
+The [Step-By-Step Guide](/docs/examples/login/vue) leads you through the whole process from configuring the right application in ZITADEL to a ready application with integrated Login.
+
+After completing the Step-By-Step Guide you will have:
+1. Example Web Application with integrated ZITADEL Login
+2. Example page accessible by authenticated user showing retrieved user information
+3. Example administrator page accessible by user with administrator role
+4. Logout
+5. Correct setup for your application in ZITADEL
+
+
+
diff --git a/docs/docs/self-hosting/manage/custom-domain.md b/docs/docs/self-hosting/manage/custom-domain.md
index 57da596985..65186d558b 100644
--- a/docs/docs/self-hosting/manage/custom-domain.md
+++ b/docs/docs/self-hosting/manage/custom-domain.md
@@ -48,7 +48,7 @@ Check out the [reverse proxy configuration examples](/self-hosting/manage/revers
## Organization Domains
Note that by default, you cannot access ZITADEL at an organizations domain.
-Organization level domains [are intended for routing users by their login methods to their correct organization](http://localhost:3000/docs/guides/solution-scenarios/domain-discovery).
+Organization level domains [are intended for routing users by their login methods to their correct organization](/guides/solution-scenarios/domain-discovery).
However, if you want to access ZITADEL at an organization domain, [you can add additional domains using the System API](/apis/resources/system/system-service-add-domain#adds-a-domain-to-an-instance).
Be aware that you won't automatically have the organizations context when you access ZITADEL like this.
diff --git a/docs/docs/self-hosting/manage/production.md b/docs/docs/self-hosting/manage/production.md
index b4ea7f3405..ac15a0a402 100644
--- a/docs/docs/self-hosting/manage/production.md
+++ b/docs/docs/self-hosting/manage/production.md
@@ -121,7 +121,7 @@ Database:
You also might want to configure how [projections](/concepts/eventstore/implementation#projections) are computed. These are the default values:
```yaml
-# The Projections section defines the behaviour for the scheduled and synchronous events projections.
+# The Projections section defines the behavior for the scheduled and synchronous events projections.
Projections:
# Time interval between scheduled projections
RequeueEvery: 60s
diff --git a/docs/docs/self-hosting/manage/updating_scaling.md b/docs/docs/self-hosting/manage/updating_scaling.md
index a716c63a03..7b8c72bd32 100644
--- a/docs/docs/self-hosting/manage/updating_scaling.md
+++ b/docs/docs/self-hosting/manage/updating_scaling.md
@@ -65,7 +65,7 @@ The init phase is idempotent if executed with the same binary version.
### The Setup Phase
-During `zitadel setup`, ZITADEL creates projection tables and migrates existing data, if `--init-projections` is set.
+During `zitadel setup`, ZITADEL creates projection tables and migrates existing data, if `--init-projections=true` is set.
Depending on the ZITADEL version and the runtime resources,
this step can take several minutes.
When deploying a new ZITADEL version,
@@ -85,4 +85,4 @@ Beware, in the background, out-of-date projections
[recompute their state by replaying all missed events](/docs/concepts/eventstore/implementation#projections).
If a new ZITADEL version is deployed, this can take quite a long time,
depending on the amount of events to catch up.
-You probably should consider providing `--init-projections`-flag to the [Setup Phase](#the-setup-phase) to shift the synchronization time to previous steps and delay the startup phase until events are caught up.
\ No newline at end of file
+You probably should consider providing `--init-projections=true`-flag to the [Setup Phase](#the-setup-phase) to shift the synchronization time to previous steps and delay the startup phase until events are caught up.
\ No newline at end of file
diff --git a/docs/docs/support/advisory/a10000.md b/docs/docs/support/advisory/a10000.md
index 5b7a8e20ff..8bd7d2b957 100644
--- a/docs/docs/support/advisory/a10000.md
+++ b/docs/docs/support/advisory/a10000.md
@@ -19,7 +19,7 @@ To address this, we are going to change this behavior so that users will be auto
## Statement
-This behaviour change was tracked in the following issue: [Reuse current session if no prompt is selected](https://github.com/zitadel/zitadel/issues/4841)
+This behavior change was tracked in the following issue: [Reuse current session if no prompt is selected](https://github.com/zitadel/zitadel/issues/4841)
and released in version [v2.32.0](https://github.com/zitadel/zitadel/releases/tag/v2.32.0)
## Mitigation
diff --git a/docs/docs/support/advisory/a10001.md b/docs/docs/support/advisory/a10001.md
index e082107c09..013a9b2d2e 100644
--- a/docs/docs/support/advisory/a10001.md
+++ b/docs/docs/support/advisory/a10001.md
@@ -20,7 +20,7 @@ To address this, we are going to change the behavior of the setting mentioned ab
## Statement
-This behaviour change was tracked in the following PR: [Restrict AllowRegistration check to local registration](https://github.com/zitadel/zitadel/pull/5939).
+This behavior change was tracked in the following PR: [Restrict AllowRegistration check to local registration](https://github.com/zitadel/zitadel/pull/5939).
The change was part of version [v2.35.0](https://github.com/zitadel/zitadel/releases/tag/v2.35.0)
## Mitigation
diff --git a/docs/docs/support/advisory/a10003.md b/docs/docs/support/advisory/a10003.md
index d3a5d868d2..b03265add3 100644
--- a/docs/docs/support/advisory/a10003.md
+++ b/docs/docs/support/advisory/a10003.md
@@ -14,7 +14,7 @@ When users are redirected to the ZITADEL Login-UI without any organizational con
based on the instance settings, e.g. available IDPs and possible login mechanisms. If the user will then register himself,
by the registration form or through an IDP, the user will always be created on the default organization.
-This behaviour led to confusion, e.g. when activating IDPs on default org would not show up in the Login-UI, because they would still be loaded from the instance settings.
+This behavior led to confusion, e.g. when activating IDPs on default org would not show up in the Login-UI, because they would still be loaded from the instance settings.
To improve this, we're introducing the following change:
If users are redirected to the Login-UI without any organizational context, they will be presented a login screen based on the settings of the default organization (incl. IDPs).
diff --git a/docs/docs/support/advisory/a10004.md b/docs/docs/support/advisory/a10004.md
index 787c65aeba..ed5e8fabe1 100644
--- a/docs/docs/support/advisory/a10004.md
+++ b/docs/docs/support/advisory/a10004.md
@@ -10,7 +10,7 @@ Date: 2023-10-14
## Description
-Due to storage optimisations ZITADEL changes the behaviour of sequences.
+Due to storage optimisations ZITADEL changes the behavior of sequences.
This change improves command (create, update, delete) performance of ZITADEL.
Sequences are no longer unique inside an instance.
diff --git a/docs/docs/support/advisory/a10008.md b/docs/docs/support/advisory/a10008.md
index 3bf36b7723..811c64d773 100644
--- a/docs/docs/support/advisory/a10008.md
+++ b/docs/docs/support/advisory/a10008.md
@@ -4,13 +4,13 @@ title: Technical Advisory 10008
## Date and Version
-Version: 2.44.0
+Versions: 2.44.0, 2.43 >= 2.43.6, 2.42 > 2.42.12
Date: 2024-01-25
## Description
-Version 2.44.0 introduces a new flag `--init-projections` to `zitadel setup` commands (`setup`, `start-from-setup`, `start-from-init`)
+The versions mentioned above introduce a new flag `--init-projections` to `zitadel setup` commands (`setup`, `start-from-setup`, `start-from-init`)
This flag enables prefilling of newly added or changed projections (database tables) during setup phase instead of start phase which are used to query data, for example users.
@@ -27,13 +27,13 @@ If you use different configurations on `setup` and `start` and have overwritten
## Statement
-Filling of projections can get time consuming as your system grows and this can cause downtime of self hosted installations of ZITADEL because queries first need to ensure data consistency.
+Filling of projections can get time-consuming as your system grows and this can cause downtime of self-hosted installations of ZITADEL because queries first need to ensure data consistency.
Before this release, this step was executed after the start of ZITADEL and therefore lead to inconsistent retrieval of data until the projections were up-to-date.
## Mitigation
-Enable the `--init-projections`-flag in setup phase and make sure the previous deployment of ZITADEL remains active until the new revision started properly.
+Enable the flag (`--init-projections=true`) in setup phase and make sure the previous deployment of ZITADEL remains active until the new revision started properly.
## Impact
diff --git a/docs/docs/support/technical_advisory.mdx b/docs/docs/support/technical_advisory.mdx
index e90508c984..74c3dfa081 100644
--- a/docs/docs/support/technical_advisory.mdx
+++ b/docs/docs/support/technical_advisory.mdx
@@ -23,7 +23,7 @@ We understand that these advisories may include breaking changes, and we aim to
A-10000
Reusing user session |
- Breaking Behaviour Change |
+ Breaking Behavior Change |
The default behavior for users logging in is to be directed to the Select
Account Page on the Login. With the upcoming changes, users will be
@@ -39,13 +39,13 @@ We understand that these advisories may include breaking changes, and we aim to
A-10001
|
Login Policy - Allow Register |
- Breaking Behaviour Change |
+ Breaking Behavior Change |
When disabling the option, users are currently not able to register
locally and also not through an external IDP. With the upcoming change,
the setting will only prevent local registration. Restriction to Identity
Providers can be managed through the corresponding IDP Template. No action
- is required on your side if this is the intended behaviour or if you
+ is required on your side if this is the intended behavior or if you
already disabled registration on your IDP.
|
2.35.0 |
@@ -75,7 +75,7 @@ We understand that these advisories may include breaking changes, and we aim to
A-10003
Login-UI - Default Context |
- Breaking Behaviour Change |
+ Breaking Behavior Change |
When users are redirected to the ZITADEL Login-UI without any organizational context,
they're currently presented a login screen, based on the instance settings,
@@ -91,9 +91,9 @@ We understand that these advisories may include breaking changes, and we aim to
A-10004
|
Sequence uniquenes |
- Breaking Behaviour Change |
+ Breaking Behavior Change |
- Due to storage optimisations ZITADEL changes the behaviour of sequences.
+ Due to storage optimisations ZITADEL changes the behavior of sequences.
This change improves command (create, update, delete) performance of ZITADEL.
Sequences are no longer unique inside an instance.
From now on sequences are upcounting per aggregate id.
@@ -123,7 +123,7 @@ We understand that these advisories may include breaking changes, and we aim to
A-10006
|
Additional grant to cockroach database user |
- Breaking Behaviour Change |
+ Breaking Behavior Change |
Versions >= 2.39.0 require the cockroach database user of ZITADEL to be granted to the `VIEWACTIVITY` grant. This can either be reached by grant the role manually or execute the `zitadel init` command.
|
@@ -135,7 +135,7 @@ We understand that these advisories may include breaking changes, and we aim to
A-10007
Additional grant to cockroach database user |
- Breaking Behaviour Change |
+ Breaking Behavior Change |
Upcoming Versions require the SYSTEM_OWNER role to be available in the permission role mappings. Self-hosting ZITADEL users who define custom permission role mappings need to make sure their system users don't lose access to the system API.
|
@@ -151,7 +151,7 @@ We understand that these advisories may include breaking changes, and we aim to
new flag `--init-projections` introduced to `zitadel setup` commands (`setup`, `start-from-setup`, `start-from-init`)
|
- 2.44.0 |
+ 2.44.0, 2.43.6, 2.42.12 |
2024-01-25 |
@@ -165,7 +165,7 @@ As ZITADEL Cloud customer, you can also login to the
+ {title}
+
+ {imageSourceLight && (
+
+ )}
+ {external && (
+
+
+
+ )}
+
+ );
+}
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css
index 5cb0c80ec9..38ac4bba88 100644
--- a/docs/src/css/custom.css
+++ b/docs/src/css/custom.css
@@ -127,12 +127,20 @@
--gigibannerforeground: black;
--footer-border: rgba(0, 0, 0, 0.12);
--card-border: rgba(135, 149, 161, 0.2);
+ --card-border-hover: #6c8eef;
--ifm-pagination-nav-color-hover: #000000;
--input-border: #1a191954;
--input-hover-border: #000000;
--input-background: #00000004;
}
+.tile-wrapper {
+ margin: 0 -8px;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+}
+
pre code {
font-size: 14px !important;
}
@@ -232,11 +240,22 @@ pre code {
}
h1 {
- font-size: 2.2rem;
+ font-size: 3rem;
}
h2 {
- font-size: 1.8rem;
+ font-size: 2.5rem;
+ margin-top: 2.5rem;
+}
+
+h3 {
+ font-size: 2rem;
+ margin-top: 2.5rem;
+}
+
+h4 {
+ font-size: 1.5rem;
+ margin-top: 2.5rem;
}
.navbar__brand {
@@ -305,6 +324,7 @@ h2 {
--gigibannerforeground: white;
--footer-border: rgba(255, 255, 255, 0.12);
--card-border: rgba(135, 149, 161, 0.2);
+ --card-border-hover: #ffffff;
--ifm-pagination-nav-color-hover: #ffffff;
--input-border: #f9f7f775;
--input-hover-border: #ffffff;
@@ -563,4 +583,20 @@ p strong {
background-color: var(--ifm-color-secondary-contrast-background);
color: var(--ifm-color-secondary-contrast-foreground);
border-color: var(--ifm-color-secondary-dark);
-}
\ No newline at end of file
+}
+
+.hideondark {
+ display: block !important;
+}
+
+:root[data-theme="dark"] .hideondark {
+ display: none !important;
+}
+
+.hideonlight {
+ display: none !important;
+}
+
+:root[data-theme="dark"] .hideonlight {
+ display: block !important;
+}
diff --git a/docs/src/css/tile.module.css b/docs/src/css/tile.module.css
new file mode 100644
index 0000000000..c2f2ff2eb1
--- /dev/null
+++ b/docs/src/css/tile.module.css
@@ -0,0 +1,69 @@
+.tile {
+ position: relative;
+ margin: 0.5rem;
+ border-radius: 0.5rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-width: 230px;
+ background: var(--card-background);
+ padding: 0.1rem;
+ text-align: center;
+ text-decoration: none;
+ transition: all 0.2s ease-in-out;
+ border: 1px solid var(--card-border);
+ overflow: hidden;
+ text-decoration: none;
+}
+
+.tile h4 {
+ margin-top: 0.5rem;
+ margin-bottom: 0;
+ text-decoration: none;
+}
+
+.tile:hover {
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ cursor: pointer;
+ text-decoration: none !important;
+ border-color: var(--card-border-hover);
+}
+
+.tile p {
+ font-size: 14px;
+ margin: 0;
+ color: var(--ifm-font-color-base);
+}
+
+.tile img {
+ display: block;
+ width: auto;
+ height: 60px;
+ background-size: cover;
+ object-fit: contain;
+ background-position: center;
+ pointer-events: none;
+ box-shadow: none !important;
+ margin: 0 auto;
+}
+
+.external {
+ position: absolute;
+ top: -1rem;
+ right: -1rem;
+ border-radius: 50vw;
+ padding: 1rem;
+ height: 4rem;
+ width: 4rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid var(--card-border);
+ background: #ffffff20;
+}
+
+.external i {
+ margin-bottom: -0.5rem;
+ margin-left: -0.5rem;
+}
diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js
index 0e88b075ac..630a7d4fbd 100644
--- a/docs/src/pages/index.js
+++ b/docs/src/pages/index.js
@@ -38,13 +38,13 @@ const features = [
description=""
/>
Learn how to integrate your applications and build secure workflows and
@@ -133,7 +133,7 @@ const features = [
description=""
/>
\ No newline at end of file
diff --git a/docs/static/img/tech/flask.svg b/docs/static/img/tech/flask.svg
new file mode 100644
index 0000000000..b8f3980529
--- /dev/null
+++ b/docs/static/img/tech/flask.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/static/img/tech/flasklight.svg b/docs/static/img/tech/flasklight.svg
new file mode 100644
index 0000000000..d26a6e44e5
--- /dev/null
+++ b/docs/static/img/tech/flasklight.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/static/img/tech/nestjs.svg b/docs/static/img/tech/nestjs.svg
new file mode 100644
index 0000000000..69830240c2
--- /dev/null
+++ b/docs/static/img/tech/nestjs.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/static/img/tech/nextjs.svg b/docs/static/img/tech/nextjs.svg
index 312dc61b92..803afe1d4b 100644
--- a/docs/static/img/tech/nextjs.svg
+++ b/docs/static/img/tech/nextjs.svg
@@ -5,7 +5,7 @@
Created with Sketch.
-
+
diff --git a/docs/static/img/tech/nextjslight.svg b/docs/static/img/tech/nextjslight.svg
index 803afe1d4b..312dc61b92 100644
--- a/docs/static/img/tech/nextjslight.svg
+++ b/docs/static/img/tech/nextjslight.svg
@@ -5,7 +5,7 @@
Created with Sketch.
-
+
diff --git a/docs/static/img/tech/passportjs.svg b/docs/static/img/tech/passportjs.svg
new file mode 100644
index 0000000000..daf12a04ff
--- /dev/null
+++ b/docs/static/img/tech/passportjs.svg
@@ -0,0 +1,9 @@
+
+
diff --git a/docs/static/img/tech/rust.svg b/docs/static/img/tech/rust.svg
new file mode 100644
index 0000000000..62424d8ffd
--- /dev/null
+++ b/docs/static/img/tech/rust.svg
@@ -0,0 +1,61 @@
+
diff --git a/docs/static/img/tech/rustlight.svg b/docs/static/img/tech/rustlight.svg
new file mode 100644
index 0000000000..e53b4e738f
--- /dev/null
+++ b/docs/static/img/tech/rustlight.svg
@@ -0,0 +1,61 @@
+
diff --git a/docs/static/img/usermanual.png b/docs/static/img/usermanual.png
deleted file mode 100644
index 5ae41a37fb..0000000000
Binary files a/docs/static/img/usermanual.png and /dev/null differ
diff --git a/e2e/cypress/e2e/events/events.cy.ts b/e2e/cypress/e2e/events/events.cy.ts
index facd298b12..f7800000e0 100644
--- a/e2e/cypress/e2e/events/events.cy.ts
+++ b/e2e/cypress/e2e/events/events.cy.ts
@@ -5,7 +5,7 @@ describe('events', () => {
it('events can be filtered', () => {
const eventTypeEnglish = 'Instance added';
- cy.visit('/events');
+ cy.visit('/instance?id=events');
cy.get('[data-e2e="event-type-cell"]').should('have.length', 20);
cy.get('[data-e2e="open-filter-button"]').click();
cy.get('[data-e2e="event-type-filter-checkbox"]').click();
diff --git a/e2e/cypress/e2e/instance/settings/notifications.cy.ts b/e2e/cypress/e2e/instance/settings/notifications.cy.ts
index 06207ce524..4e6856dcac 100644
--- a/e2e/cypress/e2e/instance/settings/notifications.cy.ts
+++ b/e2e/cypress/e2e/instance/settings/notifications.cy.ts
@@ -1,6 +1,6 @@
-const notificationPath = `/settings?id=notifications`;
-const smtpPath = `/settings?id=smtpprovider`;
-const smsPath = `/settings?id=smsprovider`;
+const notificationPath = `/instance?id=notifications`;
+const smtpPath = `/instance?id=smtpprovider`;
+const smsPath = `/instance?id=smsprovider`;
beforeEach(() => {
cy.context().as('ctx');
diff --git a/e2e/cypress/e2e/settings/oidc-settings.cy.ts b/e2e/cypress/e2e/settings/oidc-settings.cy.ts
index 8997c1e7d8..dc6a8e8a03 100644
--- a/e2e/cypress/e2e/settings/oidc-settings.cy.ts
+++ b/e2e/cypress/e2e/settings/oidc-settings.cy.ts
@@ -2,7 +2,7 @@ import { apiAuth } from '../../support/api/apiauth';
import { ensureOIDCSettingsSet } from '../../support/api/oidc-settings';
describe('oidc settings', () => {
- const oidcSettingsPath = `/settings?id=oidc`;
+ const oidcSettingsPath = `/instance?id=oidc`;
const accessTokenPrecondition = 1;
const idTokenPrecondition = 2;
const refreshTokenExpirationPrecondition = 7;
diff --git a/go.mod b/go.mod
index 7a4a364e25..bd0a9c082e 100644
--- a/go.mod
+++ b/go.mod
@@ -61,19 +61,19 @@ require (
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203
github.com/ttacon/libphonenumber v1.2.1
github.com/zitadel/logging v0.5.0
- github.com/zitadel/oidc/v3 v3.10.2
+ github.com/zitadel/oidc/v3 v3.11.0
github.com/zitadel/passwap v0.5.0
github.com/zitadel/saml v0.1.3
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1
- go.opentelemetry.io/otel v1.21.0
+ go.opentelemetry.io/otel v1.22.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
go.opentelemetry.io/otel/exporters/prometheus v0.44.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0
- go.opentelemetry.io/otel/metric v1.21.0
+ go.opentelemetry.io/otel/metric v1.22.0
go.opentelemetry.io/otel/sdk v1.21.0
go.opentelemetry.io/otel/sdk/metric v1.21.0
- go.opentelemetry.io/otel/trace v1.21.0
+ go.opentelemetry.io/otel/trace v1.22.0
go.uber.org/mock v0.4.0
golang.org/x/crypto v0.18.0
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3
@@ -155,7 +155,7 @@ require (
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
- github.com/google/uuid v1.5.0
+ github.com/google/uuid v1.6.0
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
diff --git a/go.sum b/go.sum
index 925f9cd7df..fb8980ea87 100644
--- a/go.sum
+++ b/go.sum
@@ -334,8 +334,8 @@ github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
-github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
@@ -782,8 +782,8 @@ github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8=
github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/zitadel/logging v0.5.0 h1:Kunouvqse/efXy4UDvFw5s3vP+Z4AlHo3y8wF7stXHA=
github.com/zitadel/logging v0.5.0/go.mod h1:IzP5fzwFhzzyxHkSmfF8dsyqFsQRJLLcQmwhIBzlGsE=
-github.com/zitadel/oidc/v3 v3.10.2 h1:nowZrpOBR4tdIlYXE8/l5Nl84QDYwyHpccIE1l2OAd4=
-github.com/zitadel/oidc/v3 v3.10.2/go.mod h1:nfjWH8ps4B7T0JGJyLLOIUlhr0Z4becyGKui/sXYpA8=
+github.com/zitadel/oidc/v3 v3.11.0 h1:g3sOT1ith+Yc8ExDrywe0WJLKg7Fvhs7txiYX3fEcWY=
+github.com/zitadel/oidc/v3 v3.11.0/go.mod h1:UehVNuuqOYrBSFqNeHLzCpt+/Wd+LI0c9Ok87UEO73g=
github.com/zitadel/passwap v0.5.0 h1:kFMoRyo0GnxtOz7j9+r/CsRwSCjHGRaAKoUe69NwPvs=
github.com/zitadel/passwap v0.5.0/go.mod h1:uqY7D3jqdTFcKsW0Q3Pcv5qDMmSHpVTzUZewUKC1KZA=
github.com/zitadel/saml v0.1.3 h1:LI4DOCVyyU1qKPkzs3vrGcA5J3H4pH3+CL9zr9ShkpM=
@@ -801,8 +801,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
-go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
-go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
+go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
+go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
@@ -811,14 +811,14 @@ go.opentelemetry.io/otel/exporters/prometheus v0.44.0 h1:08qeJgaPC0YEBu2PQMbqU3r
go.opentelemetry.io/otel/exporters/prometheus v0.44.0/go.mod h1:ERL2uIeBtg4TxZdojHUwzZfIFlUIjZtxubT5p4h1Gjg=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E=
-go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
-go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
+go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
+go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0=
go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q=
-go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
-go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
+go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
+go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
diff --git a/internal/actions/object/metadata.go b/internal/actions/object/metadata.go
index 55bf239094..87ad69737a 100644
--- a/internal/actions/object/metadata.go
+++ b/internal/actions/object/metadata.go
@@ -12,6 +12,28 @@ import (
"github.com/zitadel/zitadel/internal/query"
)
+func OrgMetadataListFromQuery(c *actions.FieldConfig, orgMetadata *query.OrgMetadataList) goja.Value {
+ result := &metadataList{
+ Count: orgMetadata.Count,
+ Sequence: orgMetadata.Sequence,
+ Timestamp: orgMetadata.LastRun,
+ Metadata: make([]*metadata, len(orgMetadata.Metadata)),
+ }
+
+ for i, md := range orgMetadata.Metadata {
+ result.Metadata[i] = &metadata{
+ CreationDate: md.CreationDate,
+ ChangeDate: md.ChangeDate,
+ ResourceOwner: md.ResourceOwner,
+ Sequence: md.Sequence,
+ Key: md.Key,
+ Value: metadataByteArrayToValue(md.Value, c.Runtime),
+ }
+ }
+
+ return c.Runtime.ToValue(result)
+}
+
func UserMetadataListFromQuery(c *actions.FieldConfig, metadata *query.UserMetadataList) goja.Value {
result := &userMetadataList{
Count: metadata.Count,
@@ -73,6 +95,22 @@ func metadataByteArrayToValue(val []byte, runtime *goja.Runtime) goja.Value {
return runtime.ToValue(value)
}
+type metadataList struct {
+ Count uint64
+ Sequence uint64
+ Timestamp time.Time
+ Metadata []*metadata
+}
+
+type metadata struct {
+ CreationDate time.Time
+ ChangeDate time.Time
+ ResourceOwner string
+ Sequence uint64
+ Key string
+ Value goja.Value
+}
+
type userMetadataList struct {
Count uint64
Sequence uint64
diff --git a/internal/api/grpc/user/user_grant.go b/internal/api/grpc/user/user_grant.go
index 52d75326ea..f4dd099409 100644
--- a/internal/api/grpc/user/user_grant.go
+++ b/internal/api/grpc/user/user_grant.go
@@ -39,6 +39,9 @@ func UserGrantToPb(assetPrefix string, grant *query.UserGrant) *user_pb.UserGran
AvatarUrl: domain.AvatarURL(assetPrefix, grant.UserResourceOwner, grant.AvatarURL),
PreferredLoginName: grant.PreferredLoginName,
UserType: TypeToPb(grant.UserType),
+ GrantedOrgId: grant.GrantedOrgID,
+ GrantedOrgName: grant.GrantedOrgName,
+ GrantedOrgDomain: grant.GrantedOrgDomain,
Details: object.ToViewDetailsPb(
grant.Sequence,
grant.CreationDate,
diff --git a/internal/api/oidc/access_token.go b/internal/api/oidc/access_token.go
index 0c957ade7a..4845c1ea61 100644
--- a/internal/api/oidc/access_token.go
+++ b/internal/api/oidc/access_token.go
@@ -39,7 +39,7 @@ func (s *Server) verifyAccessToken(ctx context.Context, tkn string) (*accessToke
}
tokenID, subject = split[0], split[1]
} else {
- verifier := op.NewAccessTokenVerifier(op.IssuerFromContext(ctx), s.keySet)
+ verifier := op.NewAccessTokenVerifier(op.IssuerFromContext(ctx), s.accessTokenKeySet)
claims, err := op.VerifyAccessToken[*oidc.AccessTokenClaims](ctx, tkn, verifier)
if err != nil {
return nil, zerrors.ThrowPermissionDenied(err, "OIDC-Eib8e", "token is not valid or has expired")
diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go
index 394ecd834b..9af51bff61 100644
--- a/internal/api/oidc/client.go
+++ b/internal/api/oidc/client.go
@@ -491,6 +491,24 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, user *query.User, userGra
return object.UserGrantsFromQuery(c, userGrants)
}),
),
+ actions.SetFields("org",
+ actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
+ return func(goja.FunctionCall) goja.Value {
+ metadata, err := o.query.SearchOrgMetadata(
+ ctx,
+ true,
+ user.ResourceOwner,
+ &query.OrgMetadataSearchQueries{},
+ false,
+ )
+ if err != nil {
+ logging.WithError(err).Info("unable to get org metadata in action")
+ panic(err)
+ }
+ return object.OrgMetadataListFromQuery(c, metadata)
+ }
+ }),
+ ),
),
)
@@ -690,6 +708,24 @@ func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, userG
return object.UserGrantsFromQuery(c, userGrants)
}),
),
+ actions.SetFields("org",
+ actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
+ return func(goja.FunctionCall) goja.Value {
+ metadata, err := o.query.SearchOrgMetadata(
+ ctx,
+ true,
+ user.ResourceOwner,
+ &query.OrgMetadataSearchQueries{},
+ false,
+ )
+ if err != nil {
+ logging.WithError(err).Info("unable to get org metadata in action")
+ panic(err)
+ }
+ return object.OrgMetadataListFromQuery(c, metadata)
+ }
+ }),
+ ),
),
)
diff --git a/internal/api/oidc/client_integration_test.go b/internal/api/oidc/client_integration_test.go
index 44219c6107..2605c812ce 100644
--- a/internal/api/oidc/client_integration_test.go
+++ b/internal/api/oidc/client_integration_test.go
@@ -4,7 +4,6 @@ package oidc_test
import (
"context"
- "fmt"
"testing"
"time"
@@ -330,8 +329,6 @@ func TestServer_VerifyClient(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- fmt.Printf("\n\n%s\n\n", tt.client.keyData)
-
authRequestID, err := Tester.CreateOIDCAuthRequest(CTX, tt.client.authReqClientID, Tester.Users[integration.FirstInstanceUsersKey][integration.Login].ID, redirectURI, oidc.ScopeOpenID)
require.NoError(t, err)
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
diff --git a/internal/api/oidc/key.go b/internal/api/oidc/key.go
index 8f8ca10c34..19b444feda 100644
--- a/internal/api/oidc/key.go
+++ b/internal/api/oidc/key.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"sync"
+ "sync/atomic"
"time"
"github.com/go-jose/go-jose/v3"
@@ -22,31 +23,55 @@ import (
"github.com/zitadel/zitadel/internal/zerrors"
)
-// keySetCache implements oidc.KeySet for Access Token verification.
-// Public Keys are cached in a 2-dimensional map of Instance ID and Key ID.
+type cachedPublicKey struct {
+ lastUse atomic.Int64 // unix micro time.
+ query.PublicKey
+}
+
+func newCachedPublicKey(key query.PublicKey, now time.Time) *cachedPublicKey {
+ cachedKey := &cachedPublicKey{
+ PublicKey: key,
+ }
+ cachedKey.setLastUse(now)
+ return cachedKey
+}
+
+func (c *cachedPublicKey) setLastUse(now time.Time) {
+ c.lastUse.Store(now.UnixMicro())
+}
+
+func (c *cachedPublicKey) getLastUse() time.Time {
+ return time.UnixMicro(c.lastUse.Load())
+}
+
+func (c *cachedPublicKey) expired(now time.Time, validity time.Duration) bool {
+ return c.getLastUse().Add(validity).Before(now)
+}
+
+// publicKeyCache caches public keys in a 2-dimensional map of Instance ID and Key ID.
// When a key is not present the queryKey function is called to obtain the key
// from the database.
-type keySetCache struct {
+type publicKeyCache struct {
mtx sync.RWMutex
- instanceKeys map[string]map[string]query.PublicKey
- queryKey func(ctx context.Context, keyID string, current time.Time) (query.PublicKey, error)
+ instanceKeys map[string]map[string]*cachedPublicKey
+ queryKey func(ctx context.Context, keyID string) (query.PublicKey, error)
clock clockwork.Clock
}
-// newKeySet initializes a keySetCache and starts a purging Go routine,
-// which runs once every purgeInterval.
+// newPublicKeyCache initializes a keySetCache starts a purging Go routine.
+// The purge routine deletes all public keys that are older than maxAge.
// When the passed context is done, the purge routine will terminate.
-func newKeySet(background context.Context, purgeInterval time.Duration, queryKey func(ctx context.Context, keyID string, current time.Time) (query.PublicKey, error)) *keySetCache {
- k := &keySetCache{
- instanceKeys: make(map[string]map[string]query.PublicKey),
+func newPublicKeyCache(background context.Context, maxAge time.Duration, queryKey func(ctx context.Context, keyID string) (query.PublicKey, error)) *publicKeyCache {
+ k := &publicKeyCache{
+ instanceKeys: make(map[string]map[string]*cachedPublicKey),
queryKey: queryKey,
clock: clockwork.FromContext(background), // defaults to real clock
}
- go k.purgeOnInterval(background, k.clock.NewTicker(purgeInterval))
+ go k.purgeOnInterval(background, k.clock.NewTicker(maxAge/5), maxAge)
return k
}
-func (k *keySetCache) purgeOnInterval(background context.Context, ticker clockwork.Ticker) {
+func (k *publicKeyCache) purgeOnInterval(background context.Context, ticker clockwork.Ticker, maxAge time.Duration) {
defer ticker.Stop()
for {
select {
@@ -59,7 +84,7 @@ func (k *keySetCache) purgeOnInterval(background context.Context, ticker clockwo
k.mtx.Lock()
for instanceID, keys := range k.instanceKeys {
for keyID, key := range keys {
- if key.Expiry().Before(k.clock.Now()) {
+ if key.expired(k.clock.Now(), maxAge) {
delete(keys, keyID)
}
}
@@ -71,19 +96,18 @@ func (k *keySetCache) purgeOnInterval(background context.Context, ticker clockwo
}
}
-func (k *keySetCache) setKey(instanceID, keyID string, key query.PublicKey) {
+func (k *publicKeyCache) setKey(instanceID, keyID string, cachedKey *cachedPublicKey) {
k.mtx.Lock()
defer k.mtx.Unlock()
if keys, ok := k.instanceKeys[instanceID]; ok {
- keys[keyID] = key
+ keys[keyID] = cachedKey
return
}
-
- k.instanceKeys[instanceID] = map[string]query.PublicKey{keyID: key}
+ k.instanceKeys[instanceID] = map[string]*cachedPublicKey{keyID: cachedKey}
}
-func (k *keySetCache) getKey(ctx context.Context, keyID string) (_ *jose.JSONWebKey, err error) {
+func (k *publicKeyCache) getKey(ctx context.Context, keyID string) (_ *cachedPublicKey, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -94,22 +118,20 @@ func (k *keySetCache) getKey(ctx context.Context, keyID string) (_ *jose.JSONWeb
k.mtx.RUnlock()
if ok {
- if key.Expiry().After(k.clock.Now()) {
- return jsonWebkey(key), nil
+ key.setLastUse(k.clock.Now())
+ } else {
+ newKey, err := k.queryKey(ctx, keyID)
+ if err != nil {
+ return nil, err
}
- return nil, zerrors.ThrowInvalidArgument(nil, "OIDC-Zoh9E", "Errors.Key.ExpireBeforeNow")
+ key = newCachedPublicKey(newKey, k.clock.Now())
+ k.setKey(instanceID, keyID, key)
}
- key, err = k.queryKey(ctx, keyID, k.clock.Now())
- if err != nil {
- return nil, err
- }
- k.setKey(instanceID, keyID, key)
- return jsonWebkey(key), nil
+ return key, nil
}
-// VerifySignature implements the oidc.KeySet interface.
-func (k *keySetCache) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) (_ []byte, err error) {
+func (k *publicKeyCache) verifySignature(ctx context.Context, jws *jose.JSONWebSignature, checkKeyExpiry bool) (_ []byte, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() {
err = oidcError(err)
@@ -123,7 +145,45 @@ func (k *keySetCache) VerifySignature(ctx context.Context, jws *jose.JSONWebSign
if err != nil {
return nil, err
}
- return jws.Verify(key)
+ if checkKeyExpiry && key.Expiry().Before(k.clock.Now()) {
+ return nil, zerrors.ThrowInvalidArgument(err, "QUERY-ciF4k", "Errors.Key.ExpireBeforeNow")
+ }
+ return jws.Verify(jsonWebkey(key))
+}
+
+type oidcKeySet struct {
+ *publicKeyCache
+
+ keyExpiryCheck bool
+}
+
+// newOidcKeySet returns an oidc.KeySet implementation around the passed cache.
+// It is advised to reuse the same cache if different key set configurations are required.
+func newOidcKeySet(cache *publicKeyCache, opts ...keySetOption) *oidcKeySet {
+ k := &oidcKeySet{
+ publicKeyCache: cache,
+ }
+ for _, opt := range opts {
+ opt(k)
+ }
+ return k
+}
+
+// VerifySignature implements the oidc.KeySet interface.
+func (k *oidcKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) (_ []byte, err error) {
+ return k.verifySignature(ctx, jws, k.keyExpiryCheck)
+}
+
+type keySetOption func(*oidcKeySet)
+
+// withKeyExpiryCheck forces VerifySignature to check the expiry of the public key.
+// Note that public key expiry is not part of the standard,
+// but is currently established behavior of zitadel.
+// We might want to remove this check in the future.
+func withKeyExpiryCheck(check bool) keySetOption {
+ return func(k *oidcKeySet) {
+ k.keyExpiryCheck = check
+ }
}
func jsonWebkey(key query.PublicKey) *jose.JSONWebKey {
diff --git a/internal/api/oidc/key_test.go b/internal/api/oidc/key_test.go
index d1c191e800..266372fbc9 100644
--- a/internal/api/oidc/key_test.go
+++ b/internal/api/oidc/key_test.go
@@ -66,100 +66,100 @@ var (
seq: 3,
expiry: clock.Now().Add(10 * time.Hour),
},
+ "exp1": {
+ id: "key2",
+ alg: "alg",
+ use: domain.KeyUsageSigning,
+ seq: 4,
+ expiry: clock.Now().Add(-time.Hour),
+ },
}
)
-func queryKeyDB(_ context.Context, keyID string, current time.Time) (query.PublicKey, error) {
+func queryKeyDB(_ context.Context, keyID string) (query.PublicKey, error) {
if key, ok := keyDB[keyID]; ok {
return key, nil
}
return nil, errors.New("not found")
}
-func Test_keySetCache(t *testing.T) {
+func Test_publicKeyCache(t *testing.T) {
background, cancel := context.WithCancel(
clockwork.AddToContext(context.Background(), clock),
)
defer cancel()
- // create an empty keySet with a purge go routine, runs every Hour
- keySet := newKeySet(background, time.Hour, queryKeyDB)
+ // create an empty cache with a purge go routine, runs every minute.
+ // keys are cached for at least 1 Hour after last use.
+ cache := newPublicKeyCache(background, time.Hour, queryKeyDB)
ctx := authz.NewMockContext("instanceID", "orgID", "userID")
// query error
- _, err := keySet.getKey(ctx, "key9")
+ _, err := cache.getKey(ctx, "key9")
require.Error(t, err)
- want := &jose.JSONWebKey{
- KeyID: "key1",
- Algorithm: "alg",
- Use: domain.KeyUsageSigning.String(),
- }
-
// get key first time, populate the cache
- got, err := keySet.getKey(ctx, "key1")
+ got, err := cache.getKey(ctx, "key1")
require.NoError(t, err)
- assert.Equal(t, want, got)
+ require.NotNil(t, got)
+ assert.Equal(t, keyDB["key1"], got.PublicKey)
// move time forward
- clock.Advance(5 * time.Minute)
+ clock.Advance(15 * time.Minute)
time.Sleep(time.Millisecond)
// key should still be in cache
- keySet.mtx.RLock()
- _, ok := keySet.instanceKeys["instanceID"]["key1"]
+ cache.mtx.RLock()
+ _, ok := cache.instanceKeys["instanceID"]["key1"]
require.True(t, ok)
- keySet.mtx.RUnlock()
-
- // the key is expired, should error
- _, err = keySet.getKey(ctx, "key1")
- require.Error(t, err)
-
- want = &jose.JSONWebKey{
- KeyID: "key2",
- Algorithm: "alg",
- Use: domain.KeyUsageSigning.String(),
- }
-
- // get the second key from DB
- got, err = keySet.getKey(ctx, "key2")
- require.NoError(t, err)
- assert.Equal(t, want, got)
+ cache.mtx.RUnlock()
// move time forward
- clock.Advance(time.Hour)
+ clock.Advance(50 * time.Minute)
time.Sleep(time.Millisecond)
- // first key shoud be purged, second still present
- keySet.mtx.RLock()
- _, ok = keySet.instanceKeys["instanceID"]["key1"]
- require.False(t, ok)
- _, ok = keySet.instanceKeys["instanceID"]["key2"]
- require.True(t, ok)
- keySet.mtx.RUnlock()
-
- // get the second key from cache
- got, err = keySet.getKey(ctx, "key2")
+ // get the second key from DB
+ got, err = cache.getKey(ctx, "key2")
require.NoError(t, err)
- assert.Equal(t, want, got)
+ require.NotNil(t, got)
+ assert.Equal(t, keyDB["key2"], got.PublicKey)
// move time forward
- clock.Advance(10 * time.Hour)
+ clock.Advance(15 * time.Minute)
+ time.Sleep(time.Millisecond)
+
+ // first key should be purged, second still present
+ cache.mtx.RLock()
+ _, ok = cache.instanceKeys["instanceID"]["key1"]
+ require.False(t, ok)
+ _, ok = cache.instanceKeys["instanceID"]["key2"]
+ require.True(t, ok)
+ cache.mtx.RUnlock()
+
+ // get the second key from cache
+ got, err = cache.getKey(ctx, "key2")
+ require.NoError(t, err)
+ require.NotNil(t, got)
+ assert.Equal(t, keyDB["key2"], got.PublicKey)
+
+ // move time forward
+ clock.Advance(2 * time.Hour)
time.Sleep(time.Millisecond)
// now the cache should be empty
- keySet.mtx.RLock()
- assert.Empty(t, keySet.instanceKeys)
- keySet.mtx.RUnlock()
+ cache.mtx.RLock()
+ assert.Empty(t, cache.instanceKeys)
+ cache.mtx.RUnlock()
}
-func Test_keySetCache_VerifySignature(t *testing.T) {
+func Test_oidcKeySet_VerifySignature(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- k := newKeySet(ctx, time.Second, queryKeyDB)
+ cache := newPublicKeyCache(ctx, time.Second, queryKeyDB)
tests := []struct {
name string
+ opts []keySetOption
jws *jose.JSONWebSignature
}{
{
@@ -186,9 +186,33 @@ func Test_keySetCache_VerifySignature(t *testing.T) {
}},
},
},
+ {
+ name: "expired, no check",
+ jws: &jose.JSONWebSignature{
+ Signatures: []jose.Signature{{
+ Header: jose.Header{
+ KeyID: "exp1",
+ },
+ }},
+ },
+ },
+ {
+ name: "expired, with check",
+ jws: &jose.JSONWebSignature{
+ Signatures: []jose.Signature{{
+ Header: jose.Header{
+ KeyID: "exp1",
+ },
+ }},
+ },
+ opts: []keySetOption{
+ withKeyExpiryCheck(true),
+ },
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ k := newOidcKeySet(cache, tt.opts...)
_, err := k.VerifySignature(ctx, tt.jws)
require.Error(t, err)
})
diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go
index 59aa2f5381..11db1c0409 100644
--- a/internal/api/oidc/op.go
+++ b/internal/api/oidc/op.go
@@ -43,6 +43,7 @@ type Config struct {
DefaultLoginURLV2 string
DefaultLogoutURLV2 string
Features Features
+ PublicKeyCacheMaxAge time.Duration
}
type EndpointConfig struct {
@@ -104,13 +105,17 @@ func NewServer(
return nil, zerrors.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
}
storage := newStorage(config, command, query, repo, encryptionAlg, es, projections, externalSecure)
- var options []op.Option
+ keyCache := newPublicKeyCache(context.TODO(), config.PublicKeyCacheMaxAge, query.GetPublicKeyByID)
+ accessTokenKeySet := newOidcKeySet(keyCache, withKeyExpiryCheck(true))
+ idTokenHintKeySet := newOidcKeySet(keyCache)
+
+ options := []op.Option{
+ op.WithAccessTokenKeySet(accessTokenKeySet),
+ op.WithIDTokenHintKeySet(idTokenHintKeySet),
+ }
if !externalSecure {
options = append(options, op.WithAllowInsecure())
}
- if err != nil {
- return nil, zerrors.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w")
- }
provider, err := op.NewProvider(
opConfig,
storage,
@@ -127,7 +132,8 @@ func NewServer(
repo: repo,
query: query,
command: command,
- keySet: newKeySet(context.TODO(), time.Hour, query.GetActivePublicKeyByID),
+ accessTokenKeySet: accessTokenKeySet,
+ idTokenHintKeySet: idTokenHintKeySet,
defaultLoginURL: fmt.Sprintf("%s%s?%s=", login.HandlerPrefix, login.EndpointLogin, login.QueryAuthRequestID),
defaultLoginURLV2: config.DefaultLoginURLV2,
defaultLogoutURLV2: config.DefaultLogoutURLV2,
diff --git a/internal/api/oidc/server.go b/internal/api/oidc/server.go
index f4d604aedb..ad83239ba1 100644
--- a/internal/api/oidc/server.go
+++ b/internal/api/oidc/server.go
@@ -23,10 +23,11 @@ type Server struct {
*op.LegacyServer
features Features
- repo repository.Repository
- query *query.Queries
- command *command.Commands
- keySet *keySetCache
+ repo repository.Repository
+ query *query.Queries
+ command *command.Commands
+ accessTokenKeySet *oidcKeySet
+ idTokenHintKeySet *oidcKeySet
defaultLoginURL string
defaultLoginURLV2 string
diff --git a/internal/api/oidc/userinfo.go b/internal/api/oidc/userinfo.go
index 3b6a2053bc..b1ccac395c 100644
--- a/internal/api/oidc/userinfo.go
+++ b/internal/api/oidc/userinfo.go
@@ -194,6 +194,24 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user
return object.UserGrantsFromSlice(c, qu.UserGrants)
}),
),
+ actions.SetFields("org",
+ actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
+ return func(goja.FunctionCall) goja.Value {
+ metadata, err := s.query.SearchOrgMetadata(
+ ctx,
+ true,
+ qu.User.ResourceOwner,
+ &query.OrgMetadataSearchQueries{},
+ false,
+ )
+ if err != nil {
+ logging.WithError(err).Info("unable to get org metadata in action")
+ panic(err)
+ }
+ return object.OrgMetadataListFromQuery(c, metadata)
+ }
+ }),
+ ),
),
)
diff --git a/internal/api/saml/storage.go b/internal/api/saml/storage.go
index fb051dec51..49c95b5d05 100644
--- a/internal/api/saml/storage.go
+++ b/internal/api/saml/storage.go
@@ -249,6 +249,24 @@ func (p *Storage) getCustomAttributes(ctx context.Context, user *query.User, use
return object.UserGrantsFromQuery(c, userGrants)
}),
),
+ actions.SetFields("org",
+ actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
+ return func(goja.FunctionCall) goja.Value {
+ metadata, err := p.query.SearchOrgMetadata(
+ ctx,
+ true,
+ user.ResourceOwner,
+ &query.OrgMetadataSearchQueries{},
+ false,
+ )
+ if err != nil {
+ logging.WithError(err).Info("unable to get org metadata in action")
+ panic(err)
+ }
+ return object.OrgMetadataListFromQuery(c, metadata)
+ }
+ }),
+ ),
),
)
diff --git a/internal/api/ui/login/static/resources/scripts/form_submit.js b/internal/api/ui/login/static/resources/scripts/form_submit.js
index b822c4c35b..e51d5dad45 100644
--- a/internal/api/ui/login/static/resources/scripts/form_submit.js
+++ b/internal/api/ui/login/static/resources/scripts/form_submit.js
@@ -1,58 +1,87 @@
-function disableSubmit(checks, button) {
- let form = document.getElementsByTagName('form')[0];
- let inputs = form.getElementsByTagName('input');
- if (button) {
- button.disabled = true;
- }
- addRequiredEventListener(inputs, checks, form, button);
- disableDoubleSubmit(form, button);
+// if an autofilled input is deleted we remove the attribute
+function detectDelete(event) {
+ const key = event.key;
+ if (key === "Backspace" || key === "Delete") {
+ event.target.isAutofilled = false;
+ }
+}
+// if the autofill associated animation is detected we add a property
+// and check if submit button should be disabled or not
+function autofill(target, checks, form, inputs, button) {
+ if (!target.isAutofilled) {
+ target.isAutofilled = true;
+ target.dispatchEvent(new CustomEvent("autofill", { bubbles: true }));
toggleButton(checks, form, inputs, button);
+ }
+}
+
+function disableSubmit(checks, button) {
+ let form = document.getElementsByTagName("form")[0];
+ let inputs = form.getElementsByTagName("input");
+ if (button) {
+ button.disabled = true;
+ }
+ addRequiredEventListener(inputs, checks, form, button);
+ disableDoubleSubmit(form, button);
+
+ toggleButton(checks, form, inputs, button);
}
function addRequiredEventListener(inputs, checks, form, button) {
- let eventType = 'input';
- for (i = 0; i < inputs.length; i++) {
- if (inputs[i].required) {
- eventType = 'input';
- if (inputs[i].type === 'checkbox') {
- eventType = 'click';
- }
- inputs[i].addEventListener(eventType, function () {
- toggleButton(checks, form, inputs, button);
- });
- }
+ let eventType = "input";
+ for (i = 0; i < inputs.length; i++) {
+ if (inputs[i].required) {
+ eventType = "input";
+ if (inputs[i].type === "checkbox") {
+ eventType = "click";
+ }
+
+ inputs[i].addEventListener(eventType, function () {
+ toggleButton(checks, form, inputs, button);
+ });
+
+ if (inputs[i].type !== "checkbox") {
+ // hack for Chrome, add an animationstart event listener
+ // if input is autofilled: https://gist.github.com/jonathantneal/d462fc2bf761a10c9fca60eb634f6977?permalink_comment_id=2901919
+ inputs[i].addEventListener("animationstart", (event) =>
+ autofill(event.target, checks, form, inputs, button)
+ );
+
+ inputs[i].addEventListener("keydown", detectDelete);
+ }
}
+ }
}
function disableDoubleSubmit(form, button) {
- form.addEventListener('submit', function () {
- document.body.classList.add('waiting');
- button.disabled = true;
- });
+ form.addEventListener("submit", function () {
+ document.body.classList.add("waiting");
+ button.disabled = true;
+ });
}
function toggleButton(checks, form, inputs, button) {
- if (checks !== undefined) {
- if (checks() === false) {
- button.disabled = true;
- return;
- }
+ if (checks !== undefined) {
+ if (checks() === false) {
+ button.disabled = true;
+ return;
}
- const targetValue = !allRequiredDone(form, inputs);
- button.disabled = targetValue;
+ }
+ const targetValue = !allRequiredDone(form, inputs);
+ button.disabled = targetValue;
}
function allRequiredDone(form, inputs) {
- for (i = 0; i < inputs.length; i++) {
- if (inputs[i].required) {
- if (inputs[i].type === 'checkbox' && !inputs[i].checked) {
- return false;
- }
- if (inputs[i].value === '') {
- return false;
- }
- }
+ for (i = 0; i < inputs.length; i++) {
+ if (inputs[i].required) {
+ if (inputs[i].type === "checkbox" && !inputs[i].checked) {
+ return false;
+ }
+ if (inputs[i].value === "" && !inputs[i].isAutofilled) {
+ return false;
+ }
}
- return true;
-}
\ No newline at end of file
+ }
+ return true;
+}
diff --git a/internal/api/ui/login/static/resources/themes/scss/styles/input/input_base.scss b/internal/api/ui/login/static/resources/themes/scss/styles/input/input_base.scss
index 02cd12e8ed..69204a05c0 100644
--- a/internal/api/ui/login/static/resources/themes/scss/styles/input/input_base.scss
+++ b/internal/api/ui/login/static/resources/themes/scss/styles/input/input_base.scss
@@ -7,26 +7,41 @@ $lgn-input-border-width: 1px !default;
$lgn-input-placeholder-font-size: 14px !default;
@mixin lgn-input-base {
- display: block;
- box-sizing: border-box;
- padding-inline-start: $lgn-input-padding-start;
- outline: none;
- display: inline-block;
- text-align: start;
- cursor: text;
- border-radius: $lgn-input-border-radius;
- transform: all .2 linear;
- font-size: 1rem;
- border-style: solid;
- border-width: $lgn-input-border-width;
- height: $lgn-input-line-height;
- padding: $lgn-input-padding;
- transition: border-color .2s ease-in-out;
- width: 100%;
- margin: $lgn-input-margin;
+ display: block;
+ box-sizing: border-box;
+ padding-inline-start: $lgn-input-padding-start;
+ outline: none;
+ display: inline-block;
+ text-align: start;
+ cursor: text;
+ border-radius: $lgn-input-border-radius;
+ transform: all 0.2 linear;
+ font-size: 1rem;
+ border-style: solid;
+ border-width: $lgn-input-border-width;
+ height: $lgn-input-line-height;
+ padding: $lgn-input-padding;
+ transition: border-color 0.2s ease-in-out;
+ width: 100%;
+ margin: $lgn-input-margin;
- &::placeholder {
- font-size: $lgn-input-placeholder-font-size;
- font-style: italic;
- }
+ &::placeholder {
+ font-size: $lgn-input-placeholder-font-size;
+ font-style: italic;
+ }
+
+ &:autofill {
+ animation-duration: 50000s;
+ animation-name: onautofillstart;
+ }
+
+ &:-webkit-autofill {
+ animation-duration: 50000s;
+ animation-name: onautofillstart;
+ }
+}
+
+@keyframes onautofillstart {
+ from {
+ }
}
diff --git a/internal/database/cockroach/crdb.go b/internal/database/cockroach/crdb.go
index 487db73e47..47fd7b3fce 100644
--- a/internal/database/cockroach/crdb.go
+++ b/internal/database/cockroach/crdb.go
@@ -79,7 +79,7 @@ func (c *Config) Connect(useAdmin bool, pusherRatio, spoolerRatio float64, purpo
return nil, err
}
- client.SetMaxOpenConns(int(connConfig.MaxIdleConns))
+ client.SetMaxOpenConns(int(connConfig.MaxOpenConns))
client.SetMaxIdleConns(int(connConfig.MaxIdleConns))
client.SetConnMaxLifetime(c.MaxConnLifetime)
client.SetConnMaxIdleTime(c.MaxConnIdleTime)
diff --git a/internal/database/postgres/pg.go b/internal/database/postgres/pg.go
index 008f399ea1..6bf1bbd203 100644
--- a/internal/database/postgres/pg.go
+++ b/internal/database/postgres/pg.go
@@ -80,7 +80,7 @@ func (c *Config) Connect(useAdmin bool, pusherRatio, spoolerRatio float64, purpo
return nil, err
}
- client.SetMaxOpenConns(int(connConfig.MaxIdleConns))
+ client.SetMaxOpenConns(int(connConfig.MaxOpenConns))
client.SetMaxIdleConns(int(connConfig.MaxIdleConns))
client.SetConnMaxLifetime(c.MaxConnLifetime)
client.SetConnMaxIdleTime(c.MaxConnIdleTime)
diff --git a/internal/query/key.go b/internal/query/key.go
index 5e1f4fca5f..ae733e8dd3 100644
--- a/internal/query/key.go
+++ b/internal/query/key.go
@@ -399,7 +399,7 @@ func (wm *PublicKeyReadModel) Query() *eventstore.SearchQueryBuilder {
Builder()
}
-func (q *Queries) GetActivePublicKeyByID(ctx context.Context, keyID string, current time.Time) (_ PublicKey, err error) {
+func (q *Queries) GetPublicKeyByID(ctx context.Context, keyID string) (_ PublicKey, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -410,9 +410,6 @@ func (q *Queries) GetActivePublicKeyByID(ctx context.Context, keyID string, curr
if model.Algorithm == "" || model.Key == nil {
return nil, zerrors.ThrowNotFound(err, "QUERY-Ahf7x", "Errors.Key.NotFound")
}
- if model.Expiry.Before(current) {
- return nil, zerrors.ThrowInvalidArgument(err, "QUERY-ciF4k", "Errors.Key.ExpireBeforeNow")
- }
keyValue, err := crypto.Decrypt(model.Key, q.keyEncryptionAlgorithm)
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-Ie4oh", "Errors.Internal")
diff --git a/internal/query/key_test.go b/internal/query/key_test.go
index af3702c0e7..70e5860eb0 100644
--- a/internal/query/key_test.go
+++ b/internal/query/key_test.go
@@ -269,7 +269,7 @@ MZbmlCoBru+rC8ITlTX/0V1ZcsSbL8tYWhthyu9x6yjo1bH85wiVI4gs0MhU8f2a
-----END PUBLIC KEY-----
`
-func TestQueries_GetActivePublicKeyByID(t *testing.T) {
+func TestQueries_GetPublicKeyByID(t *testing.T) {
now := time.Now()
future := now.Add(time.Hour)
@@ -294,38 +294,6 @@ func TestQueries_GetActivePublicKeyByID(t *testing.T) {
),
wantErr: zerrors.ThrowNotFound(nil, "QUERY-Ahf7x", "Errors.Key.NotFound"),
},
- {
- name: "expired error",
- eventstore: expectEventstore(
- expectFilter(
- eventFromEventPusher(key_repo.NewAddedEvent(context.Background(),
- &eventstore.Aggregate{
- ID: "keyID",
- Type: key_repo.AggregateType,
- ResourceOwner: "instanceID",
- InstanceID: "instanceID",
- Version: key_repo.AggregateVersion,
- },
- domain.KeyUsageSigning, "alg",
- &crypto.CryptoValue{
- CryptoType: crypto.TypeEncryption,
- Algorithm: "alg",
- KeyID: "keyID",
- Crypted: []byte("private"),
- },
- &crypto.CryptoValue{
- CryptoType: crypto.TypeEncryption,
- Algorithm: "alg",
- KeyID: "keyID",
- Crypted: []byte("public"),
- },
- now.Add(-time.Hour),
- now.Add(-time.Hour),
- )),
- ),
- ),
- wantErr: zerrors.ThrowInvalidArgument(nil, "QUERY-ciF4k", "Errors.Key.ExpireBeforeNow"),
- },
{
name: "decrypt error",
eventstore: expectEventstore(
@@ -470,7 +438,7 @@ func TestQueries_GetActivePublicKeyByID(t *testing.T) {
q.keyEncryptionAlgorithm = tt.encryption(t)
}
ctx := authz.NewMockContext("instanceID", "orgID", "loginClient")
- key, err := q.GetActivePublicKeyByID(ctx, "keyID", now)
+ key, err := q.GetPublicKeyByID(ctx, "keyID")
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
return
diff --git a/internal/query/user_grant.go b/internal/query/user_grant.go
index d33fb6ba46..a6d92c3eaf 100644
--- a/internal/query/user_grant.go
+++ b/internal/query/user_grant.go
@@ -48,6 +48,10 @@ type UserGrant struct {
ProjectID string `json:"project_id,omitempty"`
ProjectName string `json:"project_name,omitempty"`
+
+ GrantedOrgID string `json:"granted_org_id,omitempty"`
+ GrantedOrgName string `json:"granted_org_name,omitempty"`
+ GrantedOrgDomain string `json:"granted_org_domain,omitempty"`
}
type UserGrants struct {
@@ -209,6 +213,23 @@ var (
name: projection.UserGrantState,
table: userGrantTable,
}
+ GrantedOrgsTable = table{
+ name: projection.OrgProjectionTable,
+ alias: "granted_orgs",
+ instanceIDCol: projection.OrgColumnInstanceID,
+ }
+ GrantedOrgColumnId = Column{
+ name: projection.OrgColumnID,
+ table: GrantedOrgsTable,
+ }
+ GrantedOrgColumnName = Column{
+ name: projection.OrgColumnName,
+ table: GrantedOrgsTable,
+ }
+ GrantedOrgColumnDomain = Column{
+ name: projection.OrgColumnDomain,
+ table: GrantedOrgsTable,
+ }
)
func (q *Queries) UserGrant(ctx context.Context, shouldTriggerBulk bool, queries ...SearchQuery) (grant *UserGrant, err error) {
@@ -301,12 +322,17 @@ func prepareUserGrantQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
UserGrantProjectID.identifier(),
ProjectColumnName.identifier(),
+
+ GrantedOrgColumnId.identifier(),
+ GrantedOrgColumnName.identifier(),
+ GrantedOrgColumnDomain.identifier(),
).
From(userGrantTable.identifier()).
LeftJoin(join(UserIDCol, UserGrantUserID)).
LeftJoin(join(HumanUserIDCol, UserGrantUserID)).
LeftJoin(join(OrgColumnID, UserGrantResourceOwner)).
LeftJoin(join(ProjectColumnID, UserGrantProjectID)).
+ LeftJoin(join(GrantedOrgColumnId, UserResourceOwnerCol)).
LeftJoin(join(LoginNameUserIDCol, UserGrantUserID) + db.Timetravel(call.Took(ctx))).
Where(
sq.Eq{LoginNameIsPrimaryCol.identifier(): true},
@@ -329,6 +355,10 @@ func prepareUserGrantQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
orgDomain sql.NullString
projectName sql.NullString
+
+ grantedOrgID sql.NullString
+ grantedOrgName sql.NullString
+ grantedOrgDomain sql.NullString
)
err := row.Scan(
@@ -357,6 +387,10 @@ func prepareUserGrantQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
&g.ProjectID,
&projectName,
+
+ &grantedOrgID,
+ &grantedOrgName,
+ &grantedOrgDomain,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
@@ -377,7 +411,9 @@ func prepareUserGrantQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
g.OrgName = orgName.String
g.OrgPrimaryDomain = orgDomain.String
g.ProjectName = projectName.String
-
+ g.GrantedOrgID = grantedOrgID.String
+ g.GrantedOrgName = grantedOrgName.String
+ g.GrantedOrgDomain = grantedOrgDomain.String
return g, nil
}
}
@@ -410,6 +446,10 @@ func prepareUserGrantsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
UserGrantProjectID.identifier(),
ProjectColumnName.identifier(),
+ GrantedOrgColumnId.identifier(),
+ GrantedOrgColumnName.identifier(),
+ GrantedOrgColumnDomain.identifier(),
+
countColumn.identifier(),
).
From(userGrantTable.identifier()).
@@ -417,6 +457,7 @@ func prepareUserGrantsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
LeftJoin(join(HumanUserIDCol, UserGrantUserID)).
LeftJoin(join(OrgColumnID, UserGrantResourceOwner)).
LeftJoin(join(ProjectColumnID, UserGrantProjectID)).
+ LeftJoin(join(GrantedOrgColumnId, UserResourceOwnerCol)).
LeftJoin(join(LoginNameUserIDCol, UserGrantUserID) + db.Timetravel(call.Took(ctx))).
Where(
sq.Eq{LoginNameIsPrimaryCol.identifier(): true},
@@ -441,6 +482,10 @@ func prepareUserGrantsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
orgName sql.NullString
orgDomain sql.NullString
+ grantedOrgID sql.NullString
+ grantedOrgName sql.NullString
+ grantedOrgDomain sql.NullString
+
projectName sql.NullString
)
@@ -471,6 +516,10 @@ func prepareUserGrantsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
&g.ProjectID,
&projectName,
+ &grantedOrgID,
+ &grantedOrgName,
+ &grantedOrgDomain,
+
&count,
)
if err != nil {
@@ -489,6 +538,9 @@ func prepareUserGrantsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
g.OrgName = orgName.String
g.OrgPrimaryDomain = orgDomain.String
g.ProjectName = projectName.String
+ g.GrantedOrgID = grantedOrgID.String
+ g.GrantedOrgName = grantedOrgName.String
+ g.GrantedOrgDomain = grantedOrgDomain.String
userGrants = append(userGrants, g)
}
diff --git a/internal/query/user_grant_test.go b/internal/query/user_grant_test.go
index fc09ca3196..5e6ac79875 100644
--- a/internal/query/user_grant_test.go
+++ b/internal/query/user_grant_test.go
@@ -37,11 +37,15 @@ var (
", projections.orgs1.primary_domain" +
", projections.user_grants4.project_id" +
", projections.projects4.name" +
+ ", granted_orgs.id" +
+ ", granted_orgs.name" +
+ ", granted_orgs.primary_domain" +
" FROM projections.user_grants4" +
" LEFT JOIN projections.users10 ON projections.user_grants4.user_id = projections.users10.id AND projections.user_grants4.instance_id = projections.users10.instance_id" +
" LEFT JOIN projections.users10_humans ON projections.user_grants4.user_id = projections.users10_humans.user_id AND projections.user_grants4.instance_id = projections.users10_humans.instance_id" +
" LEFT JOIN projections.orgs1 ON projections.user_grants4.resource_owner = projections.orgs1.id AND projections.user_grants4.instance_id = projections.orgs1.instance_id" +
" LEFT JOIN projections.projects4 ON projections.user_grants4.project_id = projections.projects4.id AND projections.user_grants4.instance_id = projections.projects4.instance_id" +
+ " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users10.resource_owner = granted_orgs.id AND projections.users10.instance_id = granted_orgs.instance_id" +
" LEFT JOIN projections.login_names3 ON projections.user_grants4.user_id = projections.login_names3.user_id AND projections.user_grants4.instance_id = projections.login_names3.instance_id" +
` AS OF SYSTEM TIME '-1 ms' ` +
" WHERE projections.login_names3.is_primary = $1")
@@ -67,7 +71,10 @@ var (
"name", //org name
"primary_domain",
"project_id",
- "name", //project name
+ "name", // project name
+ "id", // granted org id
+ "name", // granted org name
+ "primary_domain", // granted org domain
}
userGrantsStmt = regexp.QuoteMeta(
"SELECT projections.user_grants4.id" +
@@ -92,12 +99,16 @@ var (
", projections.orgs1.primary_domain" +
", projections.user_grants4.project_id" +
", projections.projects4.name" +
+ ", granted_orgs.id" +
+ ", granted_orgs.name" +
+ ", granted_orgs.primary_domain" +
", COUNT(*) OVER ()" +
" FROM projections.user_grants4" +
" LEFT JOIN projections.users10 ON projections.user_grants4.user_id = projections.users10.id AND projections.user_grants4.instance_id = projections.users10.instance_id" +
" LEFT JOIN projections.users10_humans ON projections.user_grants4.user_id = projections.users10_humans.user_id AND projections.user_grants4.instance_id = projections.users10_humans.instance_id" +
" LEFT JOIN projections.orgs1 ON projections.user_grants4.resource_owner = projections.orgs1.id AND projections.user_grants4.instance_id = projections.orgs1.instance_id" +
" LEFT JOIN projections.projects4 ON projections.user_grants4.project_id = projections.projects4.id AND projections.user_grants4.instance_id = projections.projects4.instance_id" +
+ " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users10.resource_owner = granted_orgs.id AND projections.users10.instance_id = granted_orgs.instance_id" +
" LEFT JOIN projections.login_names3 ON projections.user_grants4.user_id = projections.login_names3.user_id AND projections.user_grants4.instance_id = projections.login_names3.instance_id" +
` AS OF SYSTEM TIME '-1 ms' ` +
" WHERE projections.login_names3.is_primary = $1")
@@ -166,6 +177,9 @@ func Test_UserGrantPrepares(t *testing.T) {
"primary-domain",
"project-id",
"project-name",
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
),
},
@@ -192,6 +206,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
{
@@ -224,6 +241,9 @@ func Test_UserGrantPrepares(t *testing.T) {
"primary-domain",
"project-id",
"project-name",
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
),
},
@@ -250,6 +270,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
{
@@ -282,6 +305,9 @@ func Test_UserGrantPrepares(t *testing.T) {
nil,
"project-id",
"project-name",
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
),
},
@@ -308,6 +334,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "",
ProjectID: "project-id",
ProjectName: "project-name",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
{
@@ -340,6 +369,9 @@ func Test_UserGrantPrepares(t *testing.T) {
"primary-domain",
"project-id",
nil,
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
),
},
@@ -366,6 +398,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
{
@@ -398,6 +433,9 @@ func Test_UserGrantPrepares(t *testing.T) {
"primary-domain",
"project-id",
"project-name",
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
),
},
@@ -424,6 +462,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
{
@@ -486,6 +527,9 @@ func Test_UserGrantPrepares(t *testing.T) {
"primary-domain",
"project-id",
"project-name",
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
},
),
@@ -518,6 +562,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
},
@@ -553,6 +600,9 @@ func Test_UserGrantPrepares(t *testing.T) {
"primary-domain",
"project-id",
"project-name",
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
},
),
@@ -585,6 +635,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
},
@@ -620,6 +673,9 @@ func Test_UserGrantPrepares(t *testing.T) {
nil,
"project-id",
"project-name",
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
},
),
@@ -652,6 +708,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "",
ProjectID: "project-id",
ProjectName: "project-name",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
},
@@ -687,6 +746,9 @@ func Test_UserGrantPrepares(t *testing.T) {
"primary-domain",
"project-id",
nil,
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
},
),
@@ -719,6 +781,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
},
@@ -754,6 +819,9 @@ func Test_UserGrantPrepares(t *testing.T) {
"primary-domain",
"project-id",
"project-name",
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
},
),
@@ -786,6 +854,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
},
@@ -821,6 +892,9 @@ func Test_UserGrantPrepares(t *testing.T) {
"primary-domain",
"project-id",
"project-name",
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
{
"id",
@@ -845,6 +919,9 @@ func Test_UserGrantPrepares(t *testing.T) {
"primary-domain",
"project-id",
"project-name",
+ "granted-org-id",
+ "granted-org-name",
+ "granted-org-domain",
},
},
),
@@ -877,6 +954,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
{
ID: "id",
@@ -901,6 +981,9 @@ func Test_UserGrantPrepares(t *testing.T) {
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
+ GrantedOrgID: "granted-org-id",
+ GrantedOrgName: "granted-org-name",
+ GrantedOrgDomain: "granted-org-domain",
},
},
},
diff --git a/proto/zitadel/user.proto b/proto/zitadel/user.proto
index 40d89cb2f0..09dc74ab5f 100644
--- a/proto/zitadel/user.proto
+++ b/proto/zitadel/user.proto
@@ -786,6 +786,21 @@ message UserGrant {
description: "type of the user (human / machine)"
}
];
+ string granted_org_id = 20 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"69629023906488334\""
+ }
+ ];
+ string granted_org_name = 21 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"ZITADEL\"";
+ }
+ ];
+ string granted_org_domain = 22 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"zitadel.cloud\"";
+ }
+ ];
}
enum UserGrantState {