Skip to content

Commit

Permalink
refactor: zitadel server
Browse files Browse the repository at this point in the history
Signed-off-by: Yordis Prieto <[email protected]>
  • Loading branch information
yordis committed Apr 13, 2024
1 parent 862df4b commit fbf7f55
Show file tree
Hide file tree
Showing 113 changed files with 5,476 additions and 9,133 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ jobs:
with:
version: 8

- name: Setup Node.js 18.x
- name: Setup Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 18.x
node-version: 20.x

- uses: pnpm/action-setup@v2
name: Install pnpm
Expand Down
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.next/
dist/
**/src/proto/
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export $(cat ./apps/login/.env.acceptance | xargs)
### Developing Against Your ZITADEL Cloud Instance

Configure your shell by exporting the following environment variables:

```sh
export ZITADEL_API_URL=<your cloud instance URL here>
export ZITADEL_ORG_ID=<your service accounts organization id here>
Expand Down Expand Up @@ -93,5 +94,4 @@ In apps/login, these commands also spin up the application and a ZITADEL gRPC AP
If you want to run the integration tests standalone against an environment of your choice, navigate to ./apps/login, [configure your shell as you like](# Developing Against Your ZITADEL Cloud Instance) and run `pnpm test:integration:run` or `pnpm test:integration:open`.
Then you need to lifecycle the mock process using the command `pnpm mock` or the more fine grained commands `pnpm mock:build`, `pnpm mock:build:nocache`, `pnpm mock:run` and `pnpm mock:destroy`.


That's it! 🎉
67 changes: 39 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# ZITADEL typescript with Turborepo and Changesets

This repository contains all TypeScript and JavaScript packages and applications you need to create your own ZITADEL Login UI.
The repo makes use of the [build system Turbo](https://turbo.build/repo) and the [Changesets CLI for versioning the packages](https://github.com/changesets/changesets).
This repository contains all TypeScript and JavaScript packages and applications you need to create your own ZITADEL
Login UI.
The repo makes use of the [build system Turbo](https://turbo.build/repo) and
the [Changesets CLI for versioning the packages](https://github.com/changesets/changesets).

**⚠️ This repo and packages are in alpha state and subject to change ⚠️**

The scope of functionality of this repo and packages is limited and under active development.
Once the package structure is set and all APIs are fully implemented we'll move this repo to beta state.
You can read the [contribution guide](/CONTRIBUTING.md) on how to contribute.
Questions can be raised in our [Discord channel](https://discord.gg/erh5Brh7jE) or as a [GitHub issue](https://github.com/zitadel/typescript/issues).
Questions can be raised in our [Discord channel](https://discord.gg/erh5Brh7jE) or as
a [GitHub issue](https://github.com/zitadel/typescript/issues).

## Developing Your Own ZITADEL Login UI

Expand Down Expand Up @@ -36,16 +39,18 @@ Each package and app is 100% [TypeScript](https://www.typescriptlang.org/).

### Login

The login is currently in a work in progress state.
The goal is to implement a login UI, using the session API of ZITADEL, which also implements the OIDC Standard and is ready to use for everyone.
The login is currently in a work in progress state.
The goal is to implement a login UI, using the session API of ZITADEL, which also implements the OIDC Standard and is
ready to use for everyone.

In the first phase we want to have a MVP login ready with the OIDC Standard and a basic feature set. In a second step the features will be extended.
In the first phase we want to have a MVP login ready with the OIDC Standard and a basic feature set. In a second step
the features will be extended.

This list should show the current implementation state, and also what is missing.
You can already use the current state, and extend it with your needs.

- [x] Local User Registration (with Password)
- [ ] User Registration and Login with external Provider
- [ ] User Registration and Login with external Provider
- [ ] Google
- [ ] GitHub
- [ ] GitHub Enterprise
Expand All @@ -67,21 +72,21 @@ You can already use the current state, and extend it with your needs.
- [ ] Domain Discovery
- [ ] Branding
- OIDC Standard
- [ ] Authorization Code Flow with PKCE
- [ ] AuthRequest `hintUserId`
- [ ] AuthRequest `loginHint`
- [ ] AuthRequest `prompt`
- [x] Login
- [x] Select Account
- [ ] Consent
- [ ] Create
- Scopes
- [ ] `openid email profile address``
- [ ] `offline access`
- [ ] `urn:zitadel:iam:org:idp:id:{idp_id}`
- [ ] `urn:zitadel:iam:org:project:id:zitadel:aud`
- [ ] `urn:zitadel:iam:org:id:{orgid}`
- [ ] AuthRequest UI locales
- [ ] Authorization Code Flow with PKCE
- [ ] AuthRequest `hintUserId`
- [ ] AuthRequest `loginHint`
- [ ] AuthRequest `prompt`
- [x] Login
- [x] Select Account
- [ ] Consent
- [ ] Create
- Scopes
- [ ] `openid email profile address``
- [ ] `offline access`
- [ ] `urn:zitadel:iam:org:idp:id:{idp_id}`
- [ ] `urn:zitadel:iam:org:project:id:zitadel:aud`
- [ ] `urn:zitadel:iam:org:id:{orgid}`
- [ ] AuthRequest UI locales

## Tooling

Expand All @@ -103,11 +108,14 @@ You can already use the current state, and extend it with your needs.
## Versioning And Publishing Packages

Package publishing has been configured using [Changesets](https://github.com/changesets/changesets).
Here is their [documentation](https://github.com/changesets/changesets#documentation) for more information about the workflow.
Here is their [documentation](https://github.com/changesets/changesets#documentation) for more information about the
workflow.

The [GitHub Action](https://github.com/changesets/action) needs an `NPM_TOKEN` and `GITHUB_TOKEN` in the repository settings. The [Changesets bot](https://github.com/apps/changeset-bot) should also be installed on the GitHub repository.
The [GitHub Action](https://github.com/changesets/action) needs an `NPM_TOKEN` and `GITHUB_TOKEN` in the repository
settings. The [Changesets bot](https://github.com/apps/changeset-bot) should also be installed on the GitHub repository.

Read the [changesets documentation](https://github.com/changesets/changesets/blob/main/docs/automating-changesets.md) for more information about this automation
Read the [changesets documentation](https://github.com/changesets/changesets/blob/main/docs/automating-changesets.md)
for more information about this automation

### NPM

Expand All @@ -134,8 +142,10 @@ pnpm install
```

then setup the environment for the login application which needs a `.env.local` in `/apps/login`.
Go to your instance and create a service user for the application having the IAM_OWNER manager role.
This user is required to have access to create users on your primary organization and reading policy data so it can be restricted to your personal use case but we'll stick with IAM_OWNER for convenience. Create a PAT and copy the value to paste it under the `ZITADEL_SERVICE_USER_TOKEN` key.
Go to your instance and create a service user for the application having the `IAM_OWNER` manager role.
This user is required to have access to create users on your primary organization and reading policy data so it can be
restricted to your personal use case but we'll stick with `IAM_OWNER` for convenience. Create a PAT and copy the value to
paste it under the `ZITADEL_SERVICE_USER_TOKEN` key.
The file should look as follows:

```
Expand All @@ -162,7 +172,8 @@ Open the login application with your favorite browser at `localhost:3000`.

To deploy your own version on Vercel, navigate to your instance and create a service user.
Copy its id from the overview and set it as ZITADEL_SERVICE_USER_ID.
Then create a personal access token (PAT), copy and set it as ZITADEL_SERVICE_USER_TOKEN, then navigate to your instance settings and make sure it gets IAM_OWNER permissions.
Then create a personal access token (PAT), copy and set it as ZITADEL_SERVICE_USER_TOKEN, then navigate to your instance
settings and make sure it gets IAM_OWNER permissions.
Finally set your instance url as ZITADEL_API_URL. Make sure to set it without trailing slash.

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fzitadel%2Ftypescript&env=ZITADEL_API_URL,ZITADEL_SERVICE_USER_ID,ZITADEL_SERVICE_USER_TOKEN&root-directory=apps/login&envDescription=Setup%20a%20service%20account%20with%20IAM_OWNER%20membership%20on%20your%20instance%20and%20provide%20its%20id%20and%20personal%20access%20token.&project-name=zitadel-login&repository-name=zitadel-login)
3 changes: 3 additions & 0 deletions apps/login/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module.exports = {
extends: ["next/core-web-vitals"],
ignorePatterns: ["external/**/*.ts"],
rules: {
"@next/next/no-html-link-for-pages": "off",
},
};
8 changes: 4 additions & 4 deletions apps/login/__test__/PasswordComplexity.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ describe("<PasswordComplexity/>", () => {
requiresSymbol: false,
resourceOwnerType: 0, // ResourceOwnerType.RESOURCE_OWNER_TYPE_UNSPECIFIED,
}}
/>
/>,
);
});
if (expectSVGTitle === false) {
it(`should not render the feedback element`, async () => {
await waitFor(() => {
expect(
screen.queryByText(feedbackElementLabel)
screen.queryByText(feedbackElementLabel),
).not.toBeInTheDocument();
});
});
Expand All @@ -46,12 +46,12 @@ describe("<PasswordComplexity/>", () => {
await waitFor(async () => {
const svg = within(
screen.getByText(feedbackElementLabel)
.parentElement as HTMLElement
.parentElement as HTMLElement,
).findByRole("img");
expect(await svg).toHaveTextContent(expectSVGTitle);
});
});
}
}
},
);
});
15 changes: 7 additions & 8 deletions apps/login/app/(login)/accounts/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { Session } from "@zitadel/server";
import { getBrandingSettings, listSessions, server } from "#/lib/zitadel";
import { getAllSessionCookieIds } from "#/utils/cookies";
import { Session } from "@zitadel/server/v2beta";
import { getBrandingSettings, listSessions } from "@/lib/zitadel";
import { getAllSessionCookieIds } from "@/utils/cookies";
import { UserPlusIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import SessionsList from "#/ui/SessionsList";
import DynamicTheme from "#/ui/DynamicTheme";
import SessionsList from "@/ui/SessionsList";
import DynamicTheme from "@/ui/DynamicTheme";

async function loadSessions(): Promise<Session[]> {
const ids = await getAllSessionCookieIds();

if (ids && ids.length) {
const response = await listSessions(
server,
ids.filter((id: string | undefined) => !!id)
ids.filter((id: string | undefined) => !!id),
);
return response?.sessions ?? [];
} else {
Expand All @@ -31,7 +30,7 @@ export default async function Page({

let sessions = await loadSessions();

const branding = await getBrandingSettings(server, organization);
const branding = await getBrandingSettings(organization);

return (
<DynamicTheme branding={branding}>
Expand Down
4 changes: 2 additions & 2 deletions apps/login/app/(login)/error.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { Boundary } from "#/ui/Boundary";
import { Button } from "#/ui/Button";
import { Boundary } from "@/ui/Boundary";
import { Button } from "@/ui/Button";
import React from "react";

export default function Error({ error, reset }: any) {
Expand Down
18 changes: 4 additions & 14 deletions apps/login/app/(login)/idp/[provider]/failure/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import { ProviderSlug } from "#/lib/demos";
import { getBrandingSettings, server } from "#/lib/zitadel";
import Alert, { AlertType } from "#/ui/Alert";
import DynamicTheme from "#/ui/DynamicTheme";
import IdpSignin from "#/ui/IdpSignin";
import {
AddHumanUserRequest,
IDPInformation,
RetrieveIdentityProviderIntentResponse,
user,
IDPLink,
} from "@zitadel/server";
import { ClientError } from "nice-grpc";
import { ProviderSlug } from "@/lib/demos";
import { getBrandingSettings } from "@/lib/zitadel";
import DynamicTheme from "@/ui/DynamicTheme";

const PROVIDER_NAME_MAPPING: {
[provider: string]: string;
Expand All @@ -29,7 +19,7 @@ export default async function Page({
const { id, token, authRequestId, organization } = searchParams;
const { provider } = params;

const branding = await getBrandingSettings(server, organization);
const branding = await getBrandingSettings(organization);

if (provider) {
return (
Expand Down
23 changes: 10 additions & 13 deletions apps/login/app/(login)/idp/[provider]/success/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { ProviderSlug } from "#/lib/demos";
import { getBrandingSettings, server } from "#/lib/zitadel";
import Alert, { AlertType } from "#/ui/Alert";
import DynamicTheme from "#/ui/DynamicTheme";
import IdpSignin from "#/ui/IdpSignin";
import { ProviderSlug } from "@/lib/demos";
import { getBrandingSettings, userService } from "@/lib/zitadel";
import Alert, { AlertType } from "@/ui/Alert";
import DynamicTheme from "@/ui/DynamicTheme";
import IdpSignin from "@/ui/IdpSignin";
import {
AddHumanUserRequest,
IDPInformation,
RetrieveIdentityProviderIntentResponse,
user,
IDPLink,
} from "@zitadel/server";
} from "@zitadel/server/v2beta";
import { ClientError } from "nice-grpc";

const PROVIDER_MAPPING: {
Expand Down Expand Up @@ -63,21 +62,19 @@ const PROVIDER_MAPPING: {

function retrieveIDPIntent(
id: string,
token: string
token: string,
): Promise<RetrieveIdentityProviderIntentResponse> {
const userService = user.getUser(server);
return userService.retrieveIdentityProviderIntent(
{ idpIntentId: id, idpIntentToken: token },
{}
{},
);
}

function createUser(
provider: ProviderSlug,
info: IDPInformation
info: IDPInformation,
): Promise<string> {
const userData = PROVIDER_MAPPING[provider](info);
const userService = user.getUser(server);
return userService.addHumanUser(userData, {}).then((resp) => resp.userId);
}

Expand All @@ -91,7 +88,7 @@ export default async function Page({
const { id, token, authRequestId, organization } = searchParams;
const { provider } = params;

const branding = await getBrandingSettings(server, organization);
const branding = await getBrandingSettings(organization);

if (provider && id && token) {
return retrieveIDPIntent(id, token)
Expand Down
22 changes: 9 additions & 13 deletions apps/login/app/(login)/idp/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import {
getBrandingSettings,
getLegalAndSupportSettings,
server,
} from "#/lib/zitadel";
import DynamicTheme from "#/ui/DynamicTheme";
import { SignInWithIDP } from "#/ui/SignInWithIDP";
settingsService,
} from "@/lib/zitadel";
import DynamicTheme from "@/ui/DynamicTheme";
import { SignInWithIDP } from "@/ui/SignInWithIDP";
import {
GetActiveIdentityProvidersResponse,
IdentityProvider,
ZitadelServer,
settings,
} from "@zitadel/server";

function getIdentityProviders(
server: ZitadelServer,
orgId?: string
orgId?: string,
): Promise<IdentityProvider[] | undefined> {
const settingsService = settings.getSettings(server);
return settingsService
.getActiveIdentityProviders(
orgId ? { ctx: { orgId } } : { ctx: { instance: true } },
{}
{},
)
.then((resp: GetActiveIdentityProvidersResponse) => {
return resp.identityProviders;
Expand All @@ -35,15 +31,15 @@ export default async function Page({
const authRequestId = searchParams?.authRequestId;
const organization = searchParams?.organization;

const legal = await getLegalAndSupportSettings(server, organization);
const legal = await getLegalAndSupportSettings(organization);

const identityProviders = await getIdentityProviders(server, organization);
const identityProviders = await getIdentityProviders(organization);

const host = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "http://localhost:3000";

const branding = await getBrandingSettings(server, organization);
const branding = await getBrandingSettings(organization);

return (
<DynamicTheme branding={branding}>
Expand Down
Loading

0 comments on commit fbf7f55

Please sign in to comment.