mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 01:02:17 +00:00
Merge branch 'main' into user-discovery
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json",
|
||||
"baseBranch": "main",
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["@zitadel/login"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
// This tells ESLint to load the config from the package `eslint-config-zitadel`
|
||||
extends: ["zitadel"],
|
||||
// This tells ESLint to load the config from the package `@zitadel/eslint-config`
|
||||
extends: ["@zitadel/eslint-config"],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ["apps/*/"],
|
||||
|
||||
28
.github/workflows/release.yml
vendored
Normal file
28
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Create Release Pull Request
|
||||
uses: changesets/action@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
12
README.md
12
README.md
@@ -5,11 +5,14 @@ Login UI.
|
||||
|
||||
<img src="./apps/login/screenshots/collage.png" alt="collage of login screens" width="1600px" />
|
||||
|
||||
[](https://www.npmjs.com/package/@zitadel/proto)
|
||||
[](https://www.npmjs.com/package/@zitadel/client)
|
||||
|
||||
**⚠️ This repo and packages are in alpha state and subject to change ⚠️**
|
||||
|
||||
The scope of functionality of this repo and packages is under active development.
|
||||
|
||||
The `@zitadel/client` and `@zitadel/node` packages are using [@connectrpc/connect](https://github.com/connectrpc/connect-es#readme).
|
||||
The `@zitadel/client` package is using [@connectrpc/connect](https://github.com/connectrpc/connect-es#readme).
|
||||
|
||||
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
|
||||
@@ -30,11 +33,10 @@ We think the easiest path of getting up and running, is the following:
|
||||
## Included Apps And Packages
|
||||
|
||||
- `login`: The login UI used by ZITADEL Cloud, powered by Next.js
|
||||
- `@zitadel/node`: core components for establishing node client connection
|
||||
- `@zitadel/client`: shared client utilities
|
||||
- `@zitadel/proto`: shared protobuf types
|
||||
- `@zitadel/client`: shared client utilities for node and browser environments
|
||||
- `@zitadel/proto`: Protocol Buffers (proto) definitions used by ZITADEL projects
|
||||
- `@zitadel/tsconfig`: shared `tsconfig.json`s used throughout the monorepo
|
||||
- `eslint-config-zitadel`: ESLint preset
|
||||
- `@zitadel/eslint-config`: ESLint preset
|
||||
|
||||
Each package and app is 100% [TypeScript](https://www.typescriptlang.org/).
|
||||
|
||||
|
||||
@@ -8,5 +8,5 @@ export async function loginnameScreen(page: Page, username: string) {
|
||||
|
||||
export async function loginnameScreenExpect(page: Page, username: string) {
|
||||
await expect(page.getByTestId(usernameTextInput)).toHaveValue(username);
|
||||
await expect(page.getByTestId("error").locator("div")).toContainText("Could not find user");
|
||||
await expect(page.getByTestId("error").locator("div")).toContainText("User not found in the system");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
ZITADEL_API_URL=http://localhost:22222
|
||||
CACHE_REVALIDATION_INTERVAL_IN_SECONDS=3600
|
||||
EMAIL_VERIFICATION=true
|
||||
DEBUG=true
|
||||
114
apps/login/cypress/integration/invite.cy.ts
Normal file
114
apps/login/cypress/integration/invite.cy.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { stub } from "../support/mock";
|
||||
|
||||
describe("verify invite", () => {
|
||||
beforeEach(() => {
|
||||
stub("zitadel.org.v2.OrganizationService", "ListOrganizations", {
|
||||
data: {
|
||||
details: {
|
||||
totalResult: 1,
|
||||
},
|
||||
result: [{ id: "256088834543534543" }],
|
||||
},
|
||||
});
|
||||
|
||||
stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", {
|
||||
data: {
|
||||
authMethodTypes: [], // user with no auth methods was invited
|
||||
},
|
||||
});
|
||||
|
||||
stub("zitadel.user.v2.UserService", "GetUserByID", {
|
||||
data: {
|
||||
user: {
|
||||
userId: "221394658884845598",
|
||||
state: 1,
|
||||
username: "john@zitadel.com",
|
||||
loginNames: ["john@zitadel.com"],
|
||||
preferredLoginName: "john@zitadel.com",
|
||||
human: {
|
||||
userId: "221394658884845598",
|
||||
state: 1,
|
||||
username: "john@zitadel.com",
|
||||
loginNames: ["john@zitadel.com"],
|
||||
preferredLoginName: "john@zitadel.com",
|
||||
profile: {
|
||||
givenName: "John",
|
||||
familyName: "Doe",
|
||||
avatarUrl: "https://zitadel.com/avatar.jpg",
|
||||
},
|
||||
email: {
|
||||
email: "john@zitadel.com",
|
||||
isVerified: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
stub("zitadel.session.v2.SessionService", "CreateSession", {
|
||||
data: {
|
||||
details: {
|
||||
sequence: 859,
|
||||
changeDate: new Date("2024-04-04T09:40:55.577Z"),
|
||||
resourceOwner: "220516472055706145",
|
||||
},
|
||||
sessionId: "221394658884845598",
|
||||
sessionToken:
|
||||
"SDMc7DlYXPgwRJ-Tb5NlLqynysHjEae3csWsKzoZWLplRji0AYY3HgAkrUEBqtLCvOayLJPMd0ax4Q",
|
||||
challenges: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
stub("zitadel.session.v2.SessionService", "GetSession", {
|
||||
data: {
|
||||
session: {
|
||||
id: "221394658884845598",
|
||||
creationDate: new Date("2024-04-04T09:40:55.577Z"),
|
||||
changeDate: new Date("2024-04-04T09:40:55.577Z"),
|
||||
sequence: 859,
|
||||
factors: {
|
||||
user: {
|
||||
id: "221394658884845598",
|
||||
loginName: "john@zitadel.com",
|
||||
},
|
||||
password: undefined,
|
||||
webAuthN: undefined,
|
||||
intent: undefined,
|
||||
},
|
||||
metadata: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
stub("zitadel.settings.v2.SettingsService", "GetLoginSettings", {
|
||||
data: {
|
||||
settings: {
|
||||
passkeysType: 1,
|
||||
allowUsernamePassword: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it.only("shows authenticators after successful invite verification", () => {
|
||||
stub("zitadel.user.v2.UserService", "VerifyInviteCode");
|
||||
|
||||
cy.visit("/verify?userId=221394658884845598&code=abc&invite=true");
|
||||
cy.location("pathname", { timeout: 10_000 }).should(
|
||||
"eq",
|
||||
"/authenticator/set",
|
||||
);
|
||||
});
|
||||
|
||||
it("shows an error if invite code validation failed", () => {
|
||||
stub("zitadel.user.v2.UserService", "VerifyInviteCode", {
|
||||
code: 3,
|
||||
error: "error validating code",
|
||||
});
|
||||
|
||||
// TODO: Avoid uncaught exception in application
|
||||
cy.once("uncaught:exception", () => false);
|
||||
cy.visit("/verify?userId=221394658884845598&code=abc&invite=true");
|
||||
cy.contains("Could not verify invite", { timeout: 10_000 });
|
||||
});
|
||||
});
|
||||
@@ -165,6 +165,7 @@ describe("login", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should redirect a user with passwordless authentication to /passkey", () => {
|
||||
cy.visit("/loginname?loginName=john%40zitadel.com&submit=true");
|
||||
cy.location("pathname", { timeout: 10_000 }).should("eq", "/passkey");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { stub } from "../support/mock";
|
||||
|
||||
describe("verify invite", () => {
|
||||
describe("verify email", () => {
|
||||
beforeEach(() => {
|
||||
stub("zitadel.org.v2.OrganizationService", "ListOrganizations", {
|
||||
data: {
|
||||
@@ -13,10 +13,12 @@ describe("verify invite", () => {
|
||||
|
||||
stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", {
|
||||
data: {
|
||||
authMethodTypes: [],
|
||||
authMethodTypes: [1], // set one method such that we know that the user was not invited
|
||||
},
|
||||
});
|
||||
|
||||
stub("zitadel.user.v2.UserService", "SendEmailCode");
|
||||
|
||||
stub("zitadel.user.v2.UserService", "GetUserByID", {
|
||||
data: {
|
||||
user: {
|
||||
@@ -81,62 +83,14 @@ describe("verify invite", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.only("shows authenticators after successful invite verification", () => {
|
||||
stub("zitadel.user.v2.UserService", "VerifyInviteCode");
|
||||
cy.visit("/verify?userId=221394658884845598&code=abc&invite=true");
|
||||
cy.location("pathname", { timeout: 10_000 }).should(
|
||||
"eq",
|
||||
"/authenticator/set",
|
||||
);
|
||||
});
|
||||
|
||||
it("shows an error if invite code validation failed", () => {
|
||||
stub("zitadel.user.v2.UserService", "VerifyInviteCode", {
|
||||
code: 3,
|
||||
error: "error validating code",
|
||||
});
|
||||
// TODO: Avoid uncaught exception in application
|
||||
cy.once("uncaught:exception", () => false);
|
||||
cy.visit("/verify?userId=221394658884845598&code=abc&invite=true");
|
||||
cy.contains("Could not verify invite", { timeout: 10_000 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("verify email", () => {
|
||||
beforeEach(() => {
|
||||
stub("zitadel.org.v2.OrganizationService", "ListOrganizations", {
|
||||
data: {
|
||||
details: {
|
||||
totalResult: 1,
|
||||
},
|
||||
result: [{ id: "256088834543534543" }],
|
||||
},
|
||||
});
|
||||
|
||||
stub("zitadel.user.v2.UserService", "ListAuthenticationMethodTypes", {
|
||||
data: {
|
||||
authMethodTypes: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("shows password and passkey method after successful invite verification", () => {
|
||||
stub("zitadel.user.v2.UserService", "VerifyEmail");
|
||||
cy.visit("/verify?userId=221394658884845598&code=abc");
|
||||
cy.location("pathname", { timeout: 10_000 }).should(
|
||||
"eq",
|
||||
"/authenticator/set",
|
||||
);
|
||||
});
|
||||
|
||||
it("shows an error if invite code validation failed", () => {
|
||||
it("shows an error if email code validation failed", () => {
|
||||
stub("zitadel.user.v2.UserService", "VerifyEmail", {
|
||||
code: 3,
|
||||
error: "error validating code",
|
||||
});
|
||||
// TODO: Avoid uncaught exception in application
|
||||
cy.once("uncaught:exception", () => false);
|
||||
cy.visit("/verify?userId=221394658884845598&code=abc&submit=true");
|
||||
cy.visit("/verify?userId=221394658884845598&code=abc");
|
||||
cy.contains("Could not verify email", { timeout: 10_000 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,13 +3,7 @@
|
||||
"service": "zitadel.settings.v2.SettingsService",
|
||||
"method": "GetBrandingSettings",
|
||||
"out": {
|
||||
"data": {
|
||||
"settings": {
|
||||
"darkTheme": {
|
||||
"backgroundColor": "#ff0000"
|
||||
}
|
||||
}
|
||||
}
|
||||
"data": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"test:unit": "vitest",
|
||||
"test:unit:watch": "pnpm test:unit --watch",
|
||||
"test:integration": "pnpm mock:build && concurrently --names 'mock,test' --success command-test --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test start http://localhost:3000 \"test:integration:run\"'",
|
||||
"test:integration:watch": "concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:run\\\"\"'",
|
||||
"test:integration:watch:run": "concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:run\\\"\"'",
|
||||
"test:integration:watch:open": "concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:open\\\"\"'",
|
||||
"test:integration:run": "cypress run --config-file ./cypress/cypress.config.ts --quiet",
|
||||
"test:integration:open": "cypress open --config-file ./cypress/cypress.config.ts",
|
||||
"mock": "pnpm mock:build && pnpm mock:run",
|
||||
@@ -39,7 +40,6 @@
|
||||
"@tailwindcss/forms": "0.5.7",
|
||||
"@vercel/analytics": "^1.2.2",
|
||||
"@zitadel/client": "workspace:*",
|
||||
"@zitadel/node": "workspace:*",
|
||||
"@zitadel/proto": "workspace:*",
|
||||
"clsx": "1.2.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
@@ -73,8 +73,8 @@
|
||||
"concurrently": "^9.1.0",
|
||||
"cypress": "^13.15.2",
|
||||
"del-cli": "6.0.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint-config-zitadel": "workspace:*",
|
||||
"env-cmd": "^10.0.0",
|
||||
"@zitadel/eslint-config": "workspace:*",
|
||||
"grpc-tools": "1.12.4",
|
||||
"jsdom": "^25.0.1",
|
||||
"lint-staged": "15.2.10",
|
||||
@@ -87,6 +87,6 @@
|
||||
"tailwindcss": "3.4.14",
|
||||
"ts-proto": "^2.2.7",
|
||||
"typescript": "^5.6.3",
|
||||
"zitadel-tailwind-config": "workspace:*"
|
||||
"@zitadel/tailwind-config": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,16 @@ export default async function Page(props: {
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (authRequestId) {
|
||||
params.append("authRequestId", authRequestId);
|
||||
}
|
||||
|
||||
if (organization) {
|
||||
params.append("organization", organization);
|
||||
}
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
@@ -57,16 +67,7 @@ export default async function Page(props: {
|
||||
|
||||
<div className="flex flex-col w-full space-y-2">
|
||||
<SessionsList sessions={sessions} authRequestId={authRequestId} />
|
||||
<Link
|
||||
href={
|
||||
authRequestId
|
||||
? `/loginname?` +
|
||||
new URLSearchParams({
|
||||
authRequestId,
|
||||
})
|
||||
: "/loginname"
|
||||
}
|
||||
>
|
||||
<Link href={`/loginname?` + params}>
|
||||
<div className="flex flex-row items-center py-3 px-4 hover:bg-black/10 dark:hover:bg-white/10 rounded-md transition-all">
|
||||
<div className="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
|
||||
<UserPlusIcon className="h-5 w-5" />
|
||||
|
||||
@@ -3,6 +3,8 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { VerifyForm } from "@/components/verify-form";
|
||||
import { VerifyRedirectButton } from "@/components/verify-redirect-button";
|
||||
import { sendEmailCode } from "@/lib/server/verify";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getUserByID,
|
||||
@@ -23,9 +25,39 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
|
||||
let sessionFactors;
|
||||
let user: User | undefined;
|
||||
let human: HumanUser | undefined;
|
||||
if (userId) {
|
||||
let id: string | undefined;
|
||||
|
||||
const doSend = invite !== "true";
|
||||
|
||||
if ("loginName" in searchParams) {
|
||||
sessionFactors = await loadMostRecentSession({
|
||||
loginName,
|
||||
organization,
|
||||
});
|
||||
|
||||
if (doSend && sessionFactors?.factors?.user?.id) {
|
||||
await sendEmailCode({
|
||||
userId: sessionFactors?.factors?.user?.id,
|
||||
authRequestId,
|
||||
}).catch((error) => {
|
||||
console.error("Could not resend verification email", error);
|
||||
throw Error("Failed to send verification email");
|
||||
});
|
||||
}
|
||||
} else if ("userId" in searchParams && userId) {
|
||||
if (doSend) {
|
||||
await sendEmailCode({
|
||||
userId,
|
||||
authRequestId,
|
||||
}).catch((error) => {
|
||||
console.error("Could not resend verification email", error);
|
||||
throw Error("Failed to send verification email");
|
||||
});
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(userId);
|
||||
if (userResponse) {
|
||||
user = userResponse.user;
|
||||
@@ -35,6 +67,8 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
}
|
||||
}
|
||||
|
||||
id = userId ?? sessionFactors?.factors?.user?.id;
|
||||
|
||||
let authMethods: AuthenticationMethodType[] | null = null;
|
||||
if (human?.email?.isVerified) {
|
||||
const authMethodsResponse = await listAuthenticationMethodTypes(userId);
|
||||
@@ -66,7 +100,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
<h1>{t("verify.title")}</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
|
||||
|
||||
{!userId && (
|
||||
{!id && (
|
||||
<>
|
||||
<h1>{t("verify.title")}</h1>
|
||||
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
|
||||
@@ -77,29 +111,44 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
</>
|
||||
)}
|
||||
|
||||
{user && (
|
||||
{sessionFactors ? (
|
||||
<UserAvatar
|
||||
loginName={user.preferredLoginName}
|
||||
displayName={human?.profile?.displayName}
|
||||
showDropdown={false}
|
||||
/>
|
||||
loginName={loginName ?? sessionFactors.factors?.user?.loginName}
|
||||
displayName={sessionFactors.factors?.user?.displayName}
|
||||
showDropdown
|
||||
searchParams={searchParams}
|
||||
></UserAvatar>
|
||||
) : (
|
||||
user && (
|
||||
<UserAvatar
|
||||
loginName={user.preferredLoginName}
|
||||
displayName={human?.profile?.displayName}
|
||||
showDropdown={false}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
{human?.email?.isVerified ? (
|
||||
<VerifyRedirectButton
|
||||
userId={userId}
|
||||
authRequestId={authRequestId}
|
||||
authMethods={authMethods}
|
||||
/>
|
||||
) : (
|
||||
// check if auth methods are set
|
||||
<VerifyForm
|
||||
userId={userId}
|
||||
code={code}
|
||||
isInvite={invite === "true"}
|
||||
params={params}
|
||||
/>
|
||||
)}
|
||||
{id &&
|
||||
(human?.email?.isVerified ? (
|
||||
// show page for already verified users
|
||||
<VerifyRedirectButton
|
||||
userId={id}
|
||||
loginName={loginName}
|
||||
organization={organization}
|
||||
authRequestId={authRequestId}
|
||||
authMethods={authMethods}
|
||||
/>
|
||||
) : (
|
||||
// check if auth methods are set
|
||||
<VerifyForm
|
||||
loginName={loginName}
|
||||
organization={organization}
|
||||
userId={id}
|
||||
code={code}
|
||||
isInvite={invite === "true"}
|
||||
authRequestId={authRequestId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</DynamicTheme>
|
||||
);
|
||||
|
||||
@@ -221,7 +221,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const res = await sendLoginname(command);
|
||||
|
||||
if (res?.redirect) {
|
||||
if (res && "redirect" in res && res?.redirect) {
|
||||
const absoluteUrl = new URL(res.redirect, request.url);
|
||||
return NextResponse.redirect(absoluteUrl.toString());
|
||||
}
|
||||
@@ -429,7 +429,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const res = await sendLoginname(command);
|
||||
|
||||
if (res?.redirect) {
|
||||
if (res && "redirect" in res && res?.redirect) {
|
||||
const absoluteUrl = new URL(res.redirect, request.url);
|
||||
return NextResponse.redirect(absoluteUrl.toString());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { createNewSessionForIdp } from "@/lib/server/session";
|
||||
import { createNewSessionFromIdpIntent } from "@/lib/server/idp";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Alert } from "./alert";
|
||||
@@ -27,7 +27,7 @@ export function IdpSignin({
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
createNewSessionForIdp({
|
||||
createNewSessionFromIdpIntent({
|
||||
userId,
|
||||
idpIntent: {
|
||||
idpIntentId,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64";
|
||||
import { getNextUrl } from "@/lib/client";
|
||||
import { sendPasskey } from "@/lib/server/passkeys";
|
||||
import { updateSession } from "@/lib/server/session";
|
||||
import { create } from "@zitadel/client";
|
||||
import { create, JsonObject } from "@zitadel/client";
|
||||
import {
|
||||
RequestChallengesSchema,
|
||||
UserVerificationRequirement,
|
||||
@@ -118,9 +118,9 @@ export function LoginPasskey({
|
||||
return session;
|
||||
}
|
||||
|
||||
async function submitLogin(data: any) {
|
||||
async function submitLogin(data: JsonObject) {
|
||||
setLoading(true);
|
||||
const response = await updateSession({
|
||||
const response = await sendPasskey({
|
||||
loginName,
|
||||
sessionId,
|
||||
organization,
|
||||
@@ -142,7 +142,9 @@ export function LoginPasskey({
|
||||
return;
|
||||
}
|
||||
|
||||
return response;
|
||||
if (response && "redirect" in response && response.redirect) {
|
||||
return router.push(response.redirect);
|
||||
}
|
||||
}
|
||||
|
||||
async function submitLoginAndContinue(
|
||||
@@ -192,31 +194,7 @@ export function LoginPasskey({
|
||||
},
|
||||
};
|
||||
|
||||
return submitLogin(data).then(async (resp) => {
|
||||
const url =
|
||||
authRequestId && resp?.sessionId
|
||||
? await getNextUrl(
|
||||
{
|
||||
sessionId: resp.sessionId,
|
||||
authRequestId: authRequestId,
|
||||
organization: organization,
|
||||
},
|
||||
loginSettings?.defaultRedirectUri,
|
||||
)
|
||||
: resp?.factors?.user?.loginName
|
||||
? await getNextUrl(
|
||||
{
|
||||
loginName: resp.factors.user.loginName,
|
||||
organization: organization,
|
||||
},
|
||||
loginSettings?.defaultRedirectUri,
|
||||
)
|
||||
: null;
|
||||
|
||||
if (url) {
|
||||
router.push(url);
|
||||
}
|
||||
});
|
||||
return submitLogin(data);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64";
|
||||
import { registerPasskeyLink, verifyPasskey } from "@/lib/server/passkeys";
|
||||
import {
|
||||
registerPasskeyLink,
|
||||
verifyPasskeyRegistration,
|
||||
} from "@/lib/server/passkeys";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
@@ -45,7 +48,7 @@ export function RegisterPasskey({
|
||||
sessionId: string,
|
||||
) {
|
||||
setLoading(true);
|
||||
const response = await verifyPasskey({
|
||||
const response = await verifyPasskeyRegistration({
|
||||
passkeyId,
|
||||
passkeyName,
|
||||
publicKeyCredential,
|
||||
|
||||
@@ -88,11 +88,11 @@ export function SessionItem({
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
if (res?.redirect) {
|
||||
if (res && "redirect" in res && res.redirect) {
|
||||
return router.push(res.redirect);
|
||||
}
|
||||
|
||||
if (res?.error) {
|
||||
if (res && "error" in res && res.error) {
|
||||
setError(res.error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -61,11 +61,11 @@ export function UsernameForm({
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
if (res?.redirect) {
|
||||
if (res && "redirect" in res && res.redirect) {
|
||||
return router.push(res.redirect);
|
||||
}
|
||||
|
||||
if (res?.error) {
|
||||
if (res && "error" in res && res.error) {
|
||||
setError(res.error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Alert, AlertType } from "@/components/alert";
|
||||
import { resendVerification, sendVerification } from "@/lib/server/email";
|
||||
import { resendVerification, sendVerification } from "@/lib/server/verify";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
@@ -17,12 +17,21 @@ type Inputs = {
|
||||
|
||||
type Props = {
|
||||
userId: string;
|
||||
loginName?: string;
|
||||
organization?: string;
|
||||
code?: string;
|
||||
isInvite: boolean;
|
||||
params: URLSearchParams;
|
||||
authRequestId?: string;
|
||||
};
|
||||
|
||||
export function VerifyForm({ userId, code, isInvite, params }: Props) {
|
||||
export function VerifyForm({
|
||||
userId,
|
||||
loginName,
|
||||
organization,
|
||||
authRequestId,
|
||||
code,
|
||||
isInvite,
|
||||
}: Props) {
|
||||
const t = useTranslations("verify");
|
||||
|
||||
const router = useRouter();
|
||||
@@ -67,6 +76,9 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) {
|
||||
code: value.code,
|
||||
userId,
|
||||
isInvite: isInvite,
|
||||
loginName: loginName,
|
||||
organization: organization,
|
||||
authRequestId: authRequestId,
|
||||
})
|
||||
.catch(() => {
|
||||
setError("Could not verify user");
|
||||
@@ -76,12 +88,12 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
if (response?.error) {
|
||||
if (response && "error" in response && response?.error) {
|
||||
setError(response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response?.redirect) {
|
||||
if (response && "redirect" in response && response?.redirect) {
|
||||
return router.push(response?.redirect);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { sendVerificationRedirectWithoutCheck } from "@/lib/server/email";
|
||||
import {
|
||||
sendVerificationRedirectWithoutCheck,
|
||||
SendVerificationRedirectWithoutCheckCommand,
|
||||
} from "@/lib/server/verify";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
@@ -11,12 +14,16 @@ import { Spinner } from "./spinner";
|
||||
|
||||
export function VerifyRedirectButton({
|
||||
userId,
|
||||
loginName,
|
||||
authRequestId,
|
||||
authMethods,
|
||||
organization,
|
||||
}: {
|
||||
userId: string;
|
||||
userId?: string;
|
||||
loginName?: string;
|
||||
authRequestId: string;
|
||||
authMethods: AuthenticationMethodType[] | null;
|
||||
organization?: string;
|
||||
}) {
|
||||
const t = useTranslations("verify");
|
||||
const [error, setError] = useState<string>("");
|
||||
@@ -26,12 +33,26 @@ export function VerifyRedirectButton({
|
||||
async function submitAndContinue(): Promise<boolean | void> {
|
||||
setLoading(true);
|
||||
|
||||
await sendVerificationRedirectWithoutCheck({
|
||||
userId,
|
||||
let command = {
|
||||
organization,
|
||||
authRequestId,
|
||||
})
|
||||
.catch((error) => {
|
||||
setError("Could not verify user");
|
||||
} as SendVerificationRedirectWithoutCheckCommand;
|
||||
|
||||
if (userId) {
|
||||
command = {
|
||||
...command,
|
||||
userId,
|
||||
} as SendVerificationRedirectWithoutCheckCommand;
|
||||
} else if (loginName) {
|
||||
command = {
|
||||
...command,
|
||||
loginName,
|
||||
} as SendVerificationRedirectWithoutCheckCommand;
|
||||
}
|
||||
|
||||
await sendVerificationRedirectWithoutCheck(command)
|
||||
.catch(() => {
|
||||
setError("Could not verify");
|
||||
return;
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { createServerTransport } from "@zitadel/client/node";
|
||||
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||
import { createServerTransport } from "@zitadel/node";
|
||||
import { getSessionCookieById } from "./cookies";
|
||||
import { getSession } from "./zitadel";
|
||||
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import {
|
||||
getUserByID,
|
||||
listAuthenticationMethodTypes,
|
||||
resendEmailCode,
|
||||
resendInviteCode,
|
||||
verifyEmail,
|
||||
verifyInviteCode,
|
||||
} from "@/lib/zitadel";
|
||||
import { create } from "@zitadel/client";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { createSessionAndUpdateCookie } from "./cookie";
|
||||
|
||||
type VerifyUserByEmailCommand = {
|
||||
userId: string;
|
||||
code: string;
|
||||
isInvite: boolean;
|
||||
authRequestId?: string;
|
||||
};
|
||||
|
||||
export async function sendVerification(command: VerifyUserByEmailCommand) {
|
||||
const verifyResponse = command.isInvite
|
||||
? await verifyInviteCode(command.userId, command.code).catch(() => {
|
||||
return { error: "Could not verify invite" };
|
||||
})
|
||||
: await verifyEmail(command.userId, command.code).catch(() => {
|
||||
return { error: "Could not verify email" };
|
||||
});
|
||||
|
||||
if (!verifyResponse) {
|
||||
return { error: "Could not verify user" };
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(command.userId);
|
||||
|
||||
if (!userResponse || !userResponse.user) {
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
const checks = create(ChecksSchema, {
|
||||
user: {
|
||||
search: {
|
||||
case: "loginName",
|
||||
value: userResponse.user.preferredLoginName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const session = await createSessionAndUpdateCookie(
|
||||
checks,
|
||||
undefined,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
const authMethodResponse = await listAuthenticationMethodTypes(
|
||||
command.userId,
|
||||
);
|
||||
|
||||
if (!authMethodResponse || !authMethodResponse.authMethodTypes) {
|
||||
return { error: "Could not load possible authenticators" };
|
||||
}
|
||||
// if no authmethods are found on the user, redirect to set one up
|
||||
if (
|
||||
authMethodResponse &&
|
||||
authMethodResponse.authMethodTypes &&
|
||||
authMethodResponse.authMethodTypes.length == 0
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
sessionId: session.id,
|
||||
});
|
||||
|
||||
if (session.factors?.user?.loginName) {
|
||||
params.set("loginName", session.factors?.user?.loginName);
|
||||
}
|
||||
return { redirect: `/authenticator/set?${params}` };
|
||||
}
|
||||
}
|
||||
|
||||
type resendVerifyEmailCommand = {
|
||||
userId: string;
|
||||
isInvite: boolean;
|
||||
};
|
||||
|
||||
export async function resendVerification(command: resendVerifyEmailCommand) {
|
||||
return command.isInvite
|
||||
? resendInviteCode(command.userId)
|
||||
: resendEmailCode(command.userId);
|
||||
}
|
||||
|
||||
export async function sendVerificationRedirectWithoutCheck(command: {
|
||||
userId: string;
|
||||
authRequestId?: string;
|
||||
}) {
|
||||
const userResponse = await getUserByID(command.userId);
|
||||
|
||||
if (!userResponse || !userResponse.user) {
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
const checks = create(ChecksSchema, {
|
||||
user: {
|
||||
search: {
|
||||
case: "loginName",
|
||||
value: userResponse.user.preferredLoginName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const session = await createSessionAndUpdateCookie(
|
||||
checks,
|
||||
undefined,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
const authMethodResponse = await listAuthenticationMethodTypes(
|
||||
command.userId,
|
||||
);
|
||||
|
||||
if (!authMethodResponse || !authMethodResponse.authMethodTypes) {
|
||||
return { error: "Could not load possible authenticators" };
|
||||
}
|
||||
|
||||
// if no authmethods are found on the user, redirect to set one up
|
||||
if (
|
||||
authMethodResponse &&
|
||||
authMethodResponse.authMethodTypes &&
|
||||
authMethodResponse.authMethodTypes.length == 0
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
sessionId: session.id,
|
||||
});
|
||||
|
||||
if (session.factors?.user?.loginName) {
|
||||
params.set("loginName", session.factors?.user?.loginName);
|
||||
}
|
||||
return { redirect: `/authenticator/set?${params}` };
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
"use server";
|
||||
|
||||
import { startIdentityProviderFlow } from "@/lib/zitadel";
|
||||
import {
|
||||
getLoginSettings,
|
||||
getUserByID,
|
||||
startIdentityProviderFlow,
|
||||
} from "@/lib/zitadel";
|
||||
import { headers } from "next/headers";
|
||||
import { getNextUrl } from "../client";
|
||||
import { checkEmailVerification } from "../verify-helper";
|
||||
import { createSessionForIdpAndUpdateCookie } from "./cookie";
|
||||
|
||||
export type StartIDPFlowCommand = {
|
||||
idpId: string;
|
||||
@@ -32,3 +39,85 @@ export async function startIDPFlow(command: StartIDPFlowCommand) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
type CreateNewSessionCommand = {
|
||||
userId: string;
|
||||
idpIntent: {
|
||||
idpIntentId: string;
|
||||
idpIntentToken: string;
|
||||
};
|
||||
loginName?: string;
|
||||
password?: string;
|
||||
organization?: string;
|
||||
authRequestId?: string;
|
||||
};
|
||||
|
||||
export async function createNewSessionFromIdpIntent(
|
||||
command: CreateNewSessionCommand,
|
||||
) {
|
||||
if (!command.userId || !command.idpIntent) {
|
||||
throw new Error("No userId or loginName provided");
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(command.userId);
|
||||
|
||||
if (!userResponse || !userResponse.user) {
|
||||
return { error: "User not found in the system" };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(
|
||||
userResponse.user.details?.resourceOwner,
|
||||
);
|
||||
|
||||
const session = await createSessionForIdpAndUpdateCookie(
|
||||
command.userId,
|
||||
command.idpIntent,
|
||||
command.authRequestId,
|
||||
loginSettings?.externalLoginCheckLifetime,
|
||||
);
|
||||
|
||||
if (!session || !session.factors?.user) {
|
||||
return { error: "Could not create session" };
|
||||
}
|
||||
|
||||
const humanUser =
|
||||
userResponse.user.type.case === "human"
|
||||
? userResponse.user.type.value
|
||||
: undefined;
|
||||
|
||||
// check to see if user was verified
|
||||
const emailVerificationCheck = checkEmailVerification(
|
||||
session,
|
||||
humanUser,
|
||||
command.organization,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
if (emailVerificationCheck?.redirect) {
|
||||
return emailVerificationCheck;
|
||||
}
|
||||
|
||||
// TODO: check if user has MFA methods
|
||||
// const mfaFactorCheck = checkMFAFactors(session, loginSettings, authMethods, organization, authRequestId);
|
||||
// if (mfaFactorCheck?.redirect) {
|
||||
// return mfaFactorCheck;
|
||||
// }
|
||||
|
||||
const url = await getNextUrl(
|
||||
command.authRequestId && session.id
|
||||
? {
|
||||
sessionId: session.id,
|
||||
authRequestId: command.authRequestId,
|
||||
organization: session.factors.user.organizationId,
|
||||
}
|
||||
: {
|
||||
loginName: session.factors.user.loginName,
|
||||
organization: session.factors.user.organizationId,
|
||||
},
|
||||
loginSettings?.defaultRedirectUri,
|
||||
);
|
||||
|
||||
if (url) {
|
||||
return { redirect: url };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp";
|
||||
|
||||
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { checkInvite } from "../verify-helper";
|
||||
import {
|
||||
getActiveIdentityProviders,
|
||||
getIDPByID,
|
||||
@@ -179,30 +180,23 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
session.factors?.user?.id,
|
||||
);
|
||||
|
||||
// this can be expected to be an invite as users created in console have a password set.
|
||||
if (!methods.authMethodTypes || !methods.authMethodTypes.length) {
|
||||
if (
|
||||
user.type.case === "human" &&
|
||||
user.type.value.email &&
|
||||
!user.type.value.email.isVerified
|
||||
) {
|
||||
const paramsVerify = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName,
|
||||
userId: session.factors?.user?.id, // verify needs user id
|
||||
invite: "true", // TODO: check - set this to true as we dont expect old email verification method here
|
||||
});
|
||||
const humanUser =
|
||||
potentialUsers[0].type.case === "human"
|
||||
? potentialUsers[0].type.value
|
||||
: undefined;
|
||||
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
paramsVerify.append(
|
||||
"organization",
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
}
|
||||
// redirect to /verify invite if no auth method is set and email is not verified
|
||||
const inviteCheck = checkInvite(
|
||||
session,
|
||||
humanUser,
|
||||
session.factors.user.organizationId,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
if (command.authRequestId) {
|
||||
paramsVerify.append("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
return { redirect: "/verify?" + paramsVerify };
|
||||
if (inviteCheck?.redirect) {
|
||||
return inviteCheck;
|
||||
}
|
||||
|
||||
const paramsAuthenticatorSetup = new URLSearchParams({
|
||||
@@ -327,7 +321,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
if (resp) {
|
||||
return resp;
|
||||
}
|
||||
return { error: "Could not find user" };
|
||||
return { error: "User not found in the system" };
|
||||
} else if (
|
||||
loginSettingsByContext?.allowRegister &&
|
||||
loginSettingsByContext?.allowUsernamePassword
|
||||
@@ -361,8 +355,9 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
if (command.authRequestId) {
|
||||
params.set("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
if (command.loginName) {
|
||||
params.set("loginName", command.loginName);
|
||||
params.set("email", command.loginName);
|
||||
}
|
||||
|
||||
return { redirect: "/register?" + params };
|
||||
@@ -387,5 +382,5 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
|
||||
// fallbackToPassword
|
||||
|
||||
return { error: "Could not find user" };
|
||||
return { error: "User not found in the system" };
|
||||
}
|
||||
|
||||
@@ -2,18 +2,28 @@
|
||||
|
||||
import {
|
||||
createPasskeyRegistrationLink,
|
||||
getLoginSettings,
|
||||
getSession,
|
||||
getUserByID,
|
||||
registerPasskey,
|
||||
verifyPasskeyRegistration,
|
||||
verifyPasskeyRegistration as zitadelVerifyPasskeyRegistration,
|
||||
} from "@/lib/zitadel";
|
||||
import { create } from "@zitadel/client";
|
||||
import { create, Duration } from "@zitadel/client";
|
||||
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import {
|
||||
RegisterPasskeyResponse,
|
||||
VerifyPasskeyRegistrationRequestSchema,
|
||||
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { headers } from "next/headers";
|
||||
import { userAgent } from "next/server";
|
||||
import { getSessionCookieById } from "../cookies";
|
||||
import { getNextUrl } from "../client";
|
||||
import {
|
||||
getMostRecentSessionCookie,
|
||||
getSessionCookieById,
|
||||
getSessionCookieByLoginName,
|
||||
} from "../cookies";
|
||||
import { checkEmailVerification } from "../verify-helper";
|
||||
import { setSessionAndUpdateCookie } from "./cookie";
|
||||
|
||||
type VerifyPasskeyCommand = {
|
||||
passkeyId: string;
|
||||
@@ -69,7 +79,7 @@ export async function registerPasskeyLink(
|
||||
return registerPasskey(userId, registerLink.code, hostname);
|
||||
}
|
||||
|
||||
export async function verifyPasskey(command: VerifyPasskeyCommand) {
|
||||
export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
|
||||
// if no name is provided, try to generate one from the user agent
|
||||
let passkeyName = command.passkeyName;
|
||||
if (!!!passkeyName) {
|
||||
@@ -95,7 +105,7 @@ export async function verifyPasskey(command: VerifyPasskeyCommand) {
|
||||
throw new Error("Could not get session");
|
||||
}
|
||||
|
||||
return verifyPasskeyRegistration(
|
||||
return zitadelVerifyPasskeyRegistration(
|
||||
create(VerifyPasskeyRegistrationRequestSchema, {
|
||||
passkeyId: command.passkeyId,
|
||||
publicKeyCredential: command.publicKeyCredential,
|
||||
@@ -104,3 +114,97 @@ export async function verifyPasskey(command: VerifyPasskeyCommand) {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
type SendPasskeyCommand = {
|
||||
loginName?: string;
|
||||
sessionId?: string;
|
||||
organization?: string;
|
||||
checks?: Checks;
|
||||
authRequestId?: string;
|
||||
lifetime?: Duration;
|
||||
};
|
||||
|
||||
export async function sendPasskey(command: SendPasskeyCommand) {
|
||||
let { loginName, sessionId, organization, checks, authRequestId } = command;
|
||||
const recentSession = sessionId
|
||||
? await getSessionCookieById({ sessionId })
|
||||
: loginName
|
||||
? await getSessionCookieByLoginName({ loginName, organization })
|
||||
: await getMostRecentSessionCookie();
|
||||
|
||||
if (!recentSession) {
|
||||
return {
|
||||
error: "Could not find session",
|
||||
};
|
||||
}
|
||||
|
||||
const host = (await headers()).get("host");
|
||||
|
||||
if (!host) {
|
||||
return { error: "Could not get host" };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
|
||||
const lifetime = checks?.webAuthN
|
||||
? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
|
||||
: checks?.otpEmail || checks?.otpSms
|
||||
? loginSettings?.secondFactorCheckLifetime
|
||||
: undefined;
|
||||
|
||||
const session = await setSessionAndUpdateCookie(
|
||||
recentSession,
|
||||
checks,
|
||||
undefined,
|
||||
authRequestId,
|
||||
lifetime,
|
||||
);
|
||||
|
||||
if (!session || !session?.factors?.user?.id) {
|
||||
return { error: "Could not update session" };
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(session?.factors?.user?.id);
|
||||
|
||||
if (!userResponse.user) {
|
||||
return { error: "User not found in the system" };
|
||||
}
|
||||
|
||||
const humanUser =
|
||||
userResponse.user.type.case === "human"
|
||||
? userResponse.user.type.value
|
||||
: undefined;
|
||||
|
||||
const emailVerificationCheck = checkEmailVerification(
|
||||
session,
|
||||
humanUser,
|
||||
organization,
|
||||
authRequestId,
|
||||
);
|
||||
|
||||
if (emailVerificationCheck?.redirect) {
|
||||
return emailVerificationCheck;
|
||||
}
|
||||
|
||||
const url =
|
||||
authRequestId && session.id
|
||||
? await getNextUrl(
|
||||
{
|
||||
sessionId: session.id,
|
||||
authRequestId: authRequestId,
|
||||
organization: organization,
|
||||
},
|
||||
loginSettings?.defaultRedirectUri,
|
||||
)
|
||||
: session?.factors?.user?.loginName
|
||||
? await getNextUrl(
|
||||
{
|
||||
loginName: session.factors.user.loginName,
|
||||
organization: organization,
|
||||
},
|
||||
loginSettings?.defaultRedirectUri,
|
||||
)
|
||||
: null;
|
||||
|
||||
return { redirect: url };
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
setUserPassword,
|
||||
} from "@/lib/zitadel";
|
||||
import { create } from "@zitadel/client";
|
||||
import { createServerTransport } from "@zitadel/client/node";
|
||||
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||
import { createServerTransport } from "@zitadel/node";
|
||||
import {
|
||||
Checks,
|
||||
ChecksSchema,
|
||||
@@ -30,6 +30,11 @@ import {
|
||||
import { headers } from "next/headers";
|
||||
import { getNextUrl } from "../client";
|
||||
import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies";
|
||||
import {
|
||||
checkEmailVerification,
|
||||
checkMFAFactors,
|
||||
checkPasswordChangeRequired,
|
||||
} from "../verify-helper";
|
||||
|
||||
type ResetPasswordCommand = {
|
||||
loginName: string;
|
||||
@@ -118,7 +123,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
const userResponse = await getUserByID(session?.factors?.user?.id);
|
||||
|
||||
if (!userResponse.user) {
|
||||
return { error: "Could not find user" };
|
||||
return { error: "User not found in the system" };
|
||||
}
|
||||
|
||||
user = userResponse.user;
|
||||
@@ -134,6 +139,37 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
|
||||
const humanUser = user.type.case === "human" ? user.type.value : undefined;
|
||||
|
||||
// check if the user has to change password first
|
||||
const passwordChangedCheck = checkPasswordChangeRequired(
|
||||
session,
|
||||
humanUser,
|
||||
command.organization,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
if (passwordChangedCheck?.redirect) {
|
||||
return passwordChangedCheck;
|
||||
}
|
||||
|
||||
// throw error if user is in initial state here and do not continue
|
||||
if (user.state === UserState.INITIAL) {
|
||||
return { error: "Initial User not supported" };
|
||||
}
|
||||
|
||||
// check to see if user was verified
|
||||
const emailVerificationCheck = checkEmailVerification(
|
||||
session,
|
||||
humanUser,
|
||||
command.organization,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
if (emailVerificationCheck?.redirect) {
|
||||
return emailVerificationCheck;
|
||||
}
|
||||
|
||||
// if password, check if user has MFA methods
|
||||
let authMethods;
|
||||
if (command.checks && command.checks.password && session.factors?.user?.id) {
|
||||
@@ -145,131 +181,23 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!authMethods || !session.factors?.user?.loginName) {
|
||||
if (!authMethods) {
|
||||
return { error: "Could not verify password!" };
|
||||
}
|
||||
|
||||
const humanUser = user.type.case === "human" ? user.type.value : undefined;
|
||||
|
||||
// check if the user has to change password first
|
||||
if (humanUser?.passwordChangeRequired) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName,
|
||||
});
|
||||
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
params.append("organization", session.factors?.user?.organizationId);
|
||||
}
|
||||
|
||||
if (command.authRequestId) {
|
||||
params.append("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
return { redirect: "/password/change?" + params };
|
||||
}
|
||||
|
||||
const availableMultiFactors = authMethods?.filter(
|
||||
(m: AuthenticationMethodType) =>
|
||||
m !== AuthenticationMethodType.PASSWORD &&
|
||||
m !== AuthenticationMethodType.PASSKEY,
|
||||
const mfaFactorCheck = checkMFAFactors(
|
||||
session,
|
||||
loginSettings,
|
||||
authMethods,
|
||||
command.organization,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
if (availableMultiFactors?.length == 1) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: session.factors?.user.loginName,
|
||||
});
|
||||
|
||||
if (command.authRequestId) {
|
||||
params.append("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
}
|
||||
|
||||
const factor = availableMultiFactors[0];
|
||||
// if passwordless is other method, but user selected password as alternative, perform a login
|
||||
if (factor === AuthenticationMethodType.TOTP) {
|
||||
return { redirect: `/otp/time-based?` + params };
|
||||
} else if (factor === AuthenticationMethodType.OTP_SMS) {
|
||||
return { redirect: `/otp/sms?` + params };
|
||||
} else if (factor === AuthenticationMethodType.OTP_EMAIL) {
|
||||
return { redirect: `/otp/email?` + params };
|
||||
} else if (factor === AuthenticationMethodType.U2F) {
|
||||
return { redirect: `/u2f?` + params };
|
||||
}
|
||||
} else if (availableMultiFactors?.length >= 1) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: session.factors.user.loginName,
|
||||
});
|
||||
|
||||
if (command.authRequestId) {
|
||||
params.append("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
}
|
||||
|
||||
return { redirect: `/mfa?` + params };
|
||||
if (mfaFactorCheck?.redirect) {
|
||||
return mfaFactorCheck;
|
||||
}
|
||||
// TODO: check if handling of userstate INITIAL is needed
|
||||
else if (user.state === UserState.INITIAL) {
|
||||
return { error: "Initial User not supported" };
|
||||
} else if (
|
||||
(loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) &&
|
||||
!availableMultiFactors.length
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: session.factors.user.loginName,
|
||||
force: "true", // this defines if the mfa is forced in the settings
|
||||
checkAfter: "true", // this defines if the check is directly made after the setup
|
||||
});
|
||||
|
||||
if (command.authRequestId) {
|
||||
params.append("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: provide a way to setup passkeys on mfa page?
|
||||
return { redirect: `/mfa/set?` + params };
|
||||
}
|
||||
// TODO: implement passkey setup
|
||||
|
||||
// else if (
|
||||
// submitted.factors &&
|
||||
// !submitted.factors.webAuthN && // if session was not verified with a passkey
|
||||
// promptPasswordless && // if explicitly prompted due policy
|
||||
// !isAlternative // escaped if password was used as an alternative method
|
||||
// ) {
|
||||
// const params = new URLSearchParams({
|
||||
// loginName: submitted.factors.user.loginName,
|
||||
// prompt: "true",
|
||||
// });
|
||||
|
||||
// if (authRequestId) {
|
||||
// params.append("authRequestId", authRequestId);
|
||||
// }
|
||||
|
||||
// if (organization) {
|
||||
// params.append("organization", organization);
|
||||
// }
|
||||
|
||||
// return router.push(`/passkey/set?` + params);
|
||||
// }
|
||||
else if (command.authRequestId && session.id) {
|
||||
if (command.authRequestId && session.id) {
|
||||
const nextUrl = await getNextUrl(
|
||||
{
|
||||
sessionId: session.id,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { createSessionAndUpdateCookie } from "@/lib/server/cookie";
|
||||
import { addHumanUser, getLoginSettings } from "@/lib/zitadel";
|
||||
import { addHumanUser, getLoginSettings, getUserByID } from "@/lib/zitadel";
|
||||
import { create } from "@zitadel/client";
|
||||
import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import {
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ChecksSchema,
|
||||
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { getNextUrl } from "../client";
|
||||
import { checkEmailVerification } from "../verify-helper";
|
||||
|
||||
type RegisterUserCommand = {
|
||||
email: string;
|
||||
@@ -25,7 +26,7 @@ export type RegisterUserResponse = {
|
||||
factors: Factors | undefined;
|
||||
};
|
||||
export async function registerUser(command: RegisterUserCommand) {
|
||||
const human = await addHumanUser({
|
||||
const addResponse = await addHumanUser({
|
||||
email: command.email,
|
||||
firstName: command.firstName,
|
||||
lastName: command.lastName,
|
||||
@@ -33,14 +34,14 @@ export async function registerUser(command: RegisterUserCommand) {
|
||||
organization: command.organization,
|
||||
});
|
||||
|
||||
if (!human) {
|
||||
if (!addResponse) {
|
||||
return { error: "Could not create user" };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(command.organization);
|
||||
|
||||
let checkPayload: any = {
|
||||
user: { search: { case: "userId", value: human.userId } },
|
||||
user: { search: { case: "userId", value: addResponse.userId } },
|
||||
};
|
||||
|
||||
if (command.password) {
|
||||
@@ -75,6 +76,28 @@ export async function registerUser(command: RegisterUserCommand) {
|
||||
|
||||
return { redirect: "/passkey/set?" + params };
|
||||
} else {
|
||||
const userResponse = await getUserByID(session?.factors?.user?.id);
|
||||
|
||||
if (!userResponse.user) {
|
||||
return { error: "User not found in the system" };
|
||||
}
|
||||
|
||||
const humanUser =
|
||||
userResponse.user.type.case === "human"
|
||||
? userResponse.user.type.value
|
||||
: undefined;
|
||||
|
||||
const emailVerificationCheck = checkEmailVerification(
|
||||
session,
|
||||
humanUser,
|
||||
session.factors.user.organizationId,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
if (emailVerificationCheck?.redirect) {
|
||||
return emailVerificationCheck;
|
||||
}
|
||||
|
||||
const url = await getNextUrl(
|
||||
command.authRequestId && session.id
|
||||
? {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
"use server";
|
||||
|
||||
import {
|
||||
createSessionForIdpAndUpdateCookie,
|
||||
setSessionAndUpdateCookie,
|
||||
} from "@/lib/server/cookie";
|
||||
import { setSessionAndUpdateCookie } from "@/lib/server/cookie";
|
||||
import {
|
||||
deleteSession,
|
||||
getLoginSettings,
|
||||
getUserByID,
|
||||
listAuthenticationMethodTypes,
|
||||
} from "@/lib/zitadel";
|
||||
import { Duration } from "@zitadel/client";
|
||||
@@ -23,62 +19,6 @@ import {
|
||||
removeSessionFromCookie,
|
||||
} from "../cookies";
|
||||
|
||||
type CreateNewSessionCommand = {
|
||||
userId: string;
|
||||
idpIntent: {
|
||||
idpIntentId: string;
|
||||
idpIntentToken: string;
|
||||
};
|
||||
loginName?: string;
|
||||
password?: string;
|
||||
authRequestId?: string;
|
||||
};
|
||||
|
||||
export async function createNewSessionForIdp(options: CreateNewSessionCommand) {
|
||||
const { userId, idpIntent, authRequestId } = options;
|
||||
|
||||
if (!userId || !idpIntent) {
|
||||
throw new Error("No userId or loginName provided");
|
||||
}
|
||||
|
||||
const user = await getUserByID(userId);
|
||||
|
||||
if (!user) {
|
||||
return { error: "Could not find user" };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(user.details?.resourceOwner);
|
||||
|
||||
const session = await createSessionForIdpAndUpdateCookie(
|
||||
userId,
|
||||
idpIntent,
|
||||
authRequestId,
|
||||
loginSettings?.externalLoginCheckLifetime,
|
||||
);
|
||||
|
||||
if (!session || !session.factors?.user) {
|
||||
return { error: "Could not create session" };
|
||||
}
|
||||
|
||||
const url = await getNextUrl(
|
||||
authRequestId && session.id
|
||||
? {
|
||||
sessionId: session.id,
|
||||
authRequestId: authRequestId,
|
||||
organization: session.factors.user.organizationId,
|
||||
}
|
||||
: {
|
||||
loginName: session.factors.user.loginName,
|
||||
organization: session.factors.user.organizationId,
|
||||
},
|
||||
loginSettings?.defaultRedirectUri,
|
||||
);
|
||||
|
||||
if (url) {
|
||||
return { redirect: url };
|
||||
}
|
||||
}
|
||||
|
||||
export async function continueWithSession({
|
||||
authRequestId,
|
||||
...session
|
||||
|
||||
357
apps/login/src/lib/server/verify.ts
Normal file
357
apps/login/src/lib/server/verify.ts
Normal file
@@ -0,0 +1,357 @@
|
||||
"use server";
|
||||
|
||||
import {
|
||||
getLoginSettings,
|
||||
getSession,
|
||||
getUserByID,
|
||||
listAuthenticationMethodTypes,
|
||||
resendEmailCode,
|
||||
resendInviteCode,
|
||||
verifyEmail,
|
||||
verifyInviteCode,
|
||||
sendEmailCode as zitadelSendEmailCode,
|
||||
} from "@/lib/zitadel";
|
||||
import { create } from "@zitadel/client";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { headers } from "next/headers";
|
||||
import { getNextUrl } from "../client";
|
||||
import { getSessionCookieByLoginName } from "../cookies";
|
||||
import { checkMFAFactors } from "../verify-helper";
|
||||
import { createSessionAndUpdateCookie } from "./cookie";
|
||||
|
||||
type VerifyUserByEmailCommand = {
|
||||
userId: string;
|
||||
loginName?: string; // to determine already existing session
|
||||
organization?: string;
|
||||
code: string;
|
||||
isInvite: boolean;
|
||||
authRequestId?: string;
|
||||
};
|
||||
|
||||
export async function sendVerification(command: VerifyUserByEmailCommand) {
|
||||
const verifyResponse = command.isInvite
|
||||
? await verifyInviteCode(command.userId, command.code).catch(() => {
|
||||
return { error: "Could not verify invite" };
|
||||
})
|
||||
: await verifyEmail(command.userId, command.code).catch(() => {
|
||||
return { error: "Could not verify email" };
|
||||
});
|
||||
|
||||
if ("error" in verifyResponse) {
|
||||
return verifyResponse;
|
||||
}
|
||||
|
||||
if (!verifyResponse) {
|
||||
return { error: "Could not verify" };
|
||||
}
|
||||
|
||||
let session: Session | undefined;
|
||||
let user: User | undefined;
|
||||
|
||||
if ("loginName" in command) {
|
||||
const sessionCookie = await getSessionCookieByLoginName({
|
||||
loginName: command.loginName,
|
||||
organization: command.organization,
|
||||
}).catch((error) => {
|
||||
console.warn("Ignored error:", error);
|
||||
});
|
||||
|
||||
if (!sessionCookie) {
|
||||
return { error: "Could not load session cookie" };
|
||||
}
|
||||
|
||||
session = await getSession({
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
}).then((response) => {
|
||||
if (response?.session) {
|
||||
return response.session;
|
||||
}
|
||||
});
|
||||
|
||||
if (!session?.factors?.user?.id) {
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(session?.factors?.user?.id);
|
||||
|
||||
if (!userResponse?.user) {
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
user = userResponse.user;
|
||||
} else {
|
||||
const userResponse = await getUserByID(command.userId);
|
||||
|
||||
if (!userResponse || !userResponse.user) {
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
user = userResponse.user;
|
||||
|
||||
const checks = create(ChecksSchema, {
|
||||
user: {
|
||||
search: {
|
||||
case: "loginName",
|
||||
value: userResponse.user.preferredLoginName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
session = await createSessionAndUpdateCookie(
|
||||
checks,
|
||||
undefined,
|
||||
command.authRequestId,
|
||||
);
|
||||
}
|
||||
|
||||
if (!session?.factors?.user?.id) {
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
|
||||
if (!session?.factors?.user?.id) {
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(user.details?.resourceOwner);
|
||||
|
||||
const authMethodResponse = await listAuthenticationMethodTypes(user.userId);
|
||||
|
||||
if (!authMethodResponse || !authMethodResponse.authMethodTypes) {
|
||||
return { error: "Could not load possible authenticators" };
|
||||
}
|
||||
|
||||
// if no authmethods are found on the user, redirect to set one up
|
||||
if (
|
||||
authMethodResponse &&
|
||||
authMethodResponse.authMethodTypes &&
|
||||
authMethodResponse.authMethodTypes.length == 0
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
sessionId: session.id,
|
||||
});
|
||||
|
||||
if (session.factors?.user?.loginName) {
|
||||
params.set("loginName", session.factors?.user?.loginName);
|
||||
}
|
||||
return { redirect: `/authenticator/set?${params}` };
|
||||
}
|
||||
|
||||
// redirect to mfa factor if user has one, or redirect to set one up
|
||||
const mfaFactorCheck = checkMFAFactors(
|
||||
session,
|
||||
loginSettings,
|
||||
authMethodResponse.authMethodTypes,
|
||||
command.organization,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
if (mfaFactorCheck?.redirect) {
|
||||
return mfaFactorCheck;
|
||||
}
|
||||
|
||||
// login user if no additional steps are required
|
||||
if (command.authRequestId && session.id) {
|
||||
const nextUrl = await getNextUrl(
|
||||
{
|
||||
sessionId: session.id,
|
||||
authRequestId: command.authRequestId,
|
||||
organization:
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
},
|
||||
loginSettings?.defaultRedirectUri,
|
||||
);
|
||||
|
||||
return { redirect: nextUrl };
|
||||
}
|
||||
|
||||
const url = await getNextUrl(
|
||||
{
|
||||
loginName: session.factors.user.loginName,
|
||||
organization: session.factors?.user?.organizationId,
|
||||
},
|
||||
loginSettings?.defaultRedirectUri,
|
||||
);
|
||||
|
||||
return { redirect: url };
|
||||
}
|
||||
|
||||
type resendVerifyEmailCommand = {
|
||||
userId: string;
|
||||
isInvite: boolean;
|
||||
authRequestId?: string;
|
||||
};
|
||||
|
||||
export async function resendVerification(command: resendVerifyEmailCommand) {
|
||||
const host = (await headers()).get("host");
|
||||
|
||||
return command.isInvite
|
||||
? resendInviteCode(command.userId)
|
||||
: resendEmailCode(command.userId, host, command.authRequestId);
|
||||
}
|
||||
|
||||
type sendEmailCommand = {
|
||||
userId: string;
|
||||
authRequestId?: string;
|
||||
};
|
||||
|
||||
export async function sendEmailCode(command: sendEmailCommand) {
|
||||
const host = (await headers()).get("host");
|
||||
return zitadelSendEmailCode(command.userId, host, command.authRequestId);
|
||||
}
|
||||
|
||||
export type SendVerificationRedirectWithoutCheckCommand = {
|
||||
organization?: string;
|
||||
authRequestId?: string;
|
||||
} & (
|
||||
| { userId: string; loginName?: never }
|
||||
| { userId?: never; loginName: string }
|
||||
);
|
||||
|
||||
export async function sendVerificationRedirectWithoutCheck(
|
||||
command: SendVerificationRedirectWithoutCheckCommand,
|
||||
) {
|
||||
if (!("loginName" in command || "userId" in command)) {
|
||||
return { error: "No userId, nor loginname provided" };
|
||||
}
|
||||
|
||||
let session: Session | undefined;
|
||||
let user: User | undefined;
|
||||
|
||||
if ("loginName" in command) {
|
||||
const sessionCookie = await getSessionCookieByLoginName({
|
||||
loginName: command.loginName,
|
||||
organization: command.organization,
|
||||
}).catch((error) => {
|
||||
console.warn("Ignored error:", error);
|
||||
});
|
||||
|
||||
if (!sessionCookie) {
|
||||
return { error: "Could not load session cookie" };
|
||||
}
|
||||
|
||||
session = await getSession({
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
}).then((response) => {
|
||||
if (response?.session) {
|
||||
return response.session;
|
||||
}
|
||||
});
|
||||
|
||||
if (!session?.factors?.user?.id) {
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(session?.factors?.user?.id);
|
||||
|
||||
if (!userResponse?.user) {
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
user = userResponse.user;
|
||||
} else if ("userId" in command) {
|
||||
const userResponse = await getUserByID(command.userId);
|
||||
|
||||
if (!userResponse?.user) {
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
user = userResponse.user;
|
||||
|
||||
const checks = create(ChecksSchema, {
|
||||
user: {
|
||||
search: {
|
||||
case: "loginName",
|
||||
value: userResponse.user.preferredLoginName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
session = await createSessionAndUpdateCookie(
|
||||
checks,
|
||||
undefined,
|
||||
command.authRequestId,
|
||||
);
|
||||
}
|
||||
|
||||
if (!session?.factors?.user?.id) {
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
|
||||
if (!session?.factors?.user?.id) {
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
const authMethodResponse = await listAuthenticationMethodTypes(user.userId);
|
||||
|
||||
if (!authMethodResponse || !authMethodResponse.authMethodTypes) {
|
||||
return { error: "Could not load possible authenticators" };
|
||||
}
|
||||
|
||||
// if no authmethods are found on the user, redirect to set one up
|
||||
if (
|
||||
authMethodResponse &&
|
||||
authMethodResponse.authMethodTypes &&
|
||||
authMethodResponse.authMethodTypes.length == 0
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
sessionId: session.id,
|
||||
});
|
||||
|
||||
if (session.factors?.user?.loginName) {
|
||||
params.set("loginName", session.factors?.user?.loginName);
|
||||
}
|
||||
return { redirect: `/authenticator/set?${params}` };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(user.details?.resourceOwner);
|
||||
|
||||
// redirect to mfa factor if user has one, or redirect to set one up
|
||||
const mfaFactorCheck = checkMFAFactors(
|
||||
session,
|
||||
loginSettings,
|
||||
authMethodResponse.authMethodTypes,
|
||||
command.organization,
|
||||
command.authRequestId,
|
||||
);
|
||||
|
||||
if (mfaFactorCheck?.redirect) {
|
||||
return mfaFactorCheck;
|
||||
}
|
||||
|
||||
// login user if no additional steps are required
|
||||
if (command.authRequestId && session.id) {
|
||||
const nextUrl = await getNextUrl(
|
||||
{
|
||||
sessionId: session.id,
|
||||
authRequestId: command.authRequestId,
|
||||
organization:
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
},
|
||||
loginSettings?.defaultRedirectUri,
|
||||
);
|
||||
|
||||
return { redirect: nextUrl };
|
||||
}
|
||||
|
||||
const url = await getNextUrl(
|
||||
{
|
||||
loginName: session.factors.user.loginName,
|
||||
organization: session.factors?.user?.organizationId,
|
||||
},
|
||||
loginSettings?.defaultRedirectUri,
|
||||
);
|
||||
|
||||
return { redirect: url };
|
||||
}
|
||||
204
apps/login/src/lib/verify-helper.ts
Normal file
204
apps/login/src/lib/verify-helper.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { HumanUser } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
|
||||
export function checkPasswordChangeRequired(
|
||||
session: Session,
|
||||
humanUser: HumanUser | undefined,
|
||||
organization?: string,
|
||||
authRequestId?: string,
|
||||
) {
|
||||
if (humanUser?.passwordChangeRequired) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName as string,
|
||||
});
|
||||
|
||||
if (organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
session.factors?.user?.organizationId as string,
|
||||
);
|
||||
}
|
||||
|
||||
if (authRequestId) {
|
||||
params.append("authRequestId", authRequestId);
|
||||
}
|
||||
|
||||
return { redirect: "/password/change?" + params };
|
||||
}
|
||||
}
|
||||
|
||||
export function checkInvite(
|
||||
session: Session,
|
||||
humanUser?: HumanUser,
|
||||
organization?: string,
|
||||
authRequestId?: string,
|
||||
) {
|
||||
if (!humanUser?.email?.isVerified) {
|
||||
const paramsVerify = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName as string,
|
||||
userId: session.factors?.user?.id as string, // verify needs user id
|
||||
invite: "true", // TODO: check - set this to true as we dont expect old email verification method here
|
||||
});
|
||||
|
||||
if (organization || session.factors?.user?.organizationId) {
|
||||
paramsVerify.append(
|
||||
"organization",
|
||||
organization ?? (session.factors?.user?.organizationId as string),
|
||||
);
|
||||
}
|
||||
|
||||
if (authRequestId) {
|
||||
paramsVerify.append("authRequestId", authRequestId);
|
||||
}
|
||||
|
||||
return { redirect: "/verify?" + paramsVerify };
|
||||
}
|
||||
}
|
||||
|
||||
export function checkEmailVerification(
|
||||
session: Session,
|
||||
humanUser?: HumanUser,
|
||||
organization?: string,
|
||||
authRequestId?: string,
|
||||
) {
|
||||
if (
|
||||
!humanUser?.email?.isVerified &&
|
||||
process.env.EMAIL_VERIFICATION === "true"
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName as string,
|
||||
});
|
||||
|
||||
if (authRequestId) {
|
||||
params.append("authRequestId", authRequestId);
|
||||
}
|
||||
|
||||
if (organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
organization ?? (session.factors?.user?.organizationId as string),
|
||||
);
|
||||
}
|
||||
|
||||
return { redirect: `/verify?` + params };
|
||||
}
|
||||
}
|
||||
|
||||
export function checkMFAFactors(
|
||||
session: Session,
|
||||
loginSettings: LoginSettings | undefined,
|
||||
authMethods: AuthenticationMethodType[],
|
||||
organization?: string,
|
||||
authRequestId?: string,
|
||||
) {
|
||||
const availableMultiFactors = authMethods?.filter(
|
||||
(m: AuthenticationMethodType) =>
|
||||
m !== AuthenticationMethodType.PASSWORD &&
|
||||
m !== AuthenticationMethodType.PASSKEY,
|
||||
);
|
||||
|
||||
const hasAuthenticatedWithPasskey =
|
||||
session.factors?.webAuthN?.verifiedAt &&
|
||||
session.factors?.webAuthN?.userVerified;
|
||||
|
||||
// escape further checks if user has authenticated with passkey
|
||||
if (hasAuthenticatedWithPasskey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if user has not authenticated with passkey and has only one additional mfa factor, redirect to that
|
||||
if (availableMultiFactors?.length == 1) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName as string,
|
||||
});
|
||||
|
||||
if (authRequestId) {
|
||||
params.append("authRequestId", authRequestId);
|
||||
}
|
||||
|
||||
if (organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
organization ?? (session.factors?.user?.organizationId as string),
|
||||
);
|
||||
}
|
||||
|
||||
const factor = availableMultiFactors[0];
|
||||
// if passwordless is other method, but user selected password as alternative, perform a login
|
||||
if (factor === AuthenticationMethodType.TOTP) {
|
||||
return { redirect: `/otp/time-based?` + params };
|
||||
} else if (factor === AuthenticationMethodType.OTP_SMS) {
|
||||
return { redirect: `/otp/sms?` + params };
|
||||
} else if (factor === AuthenticationMethodType.OTP_EMAIL) {
|
||||
return { redirect: `/otp/email?` + params };
|
||||
} else if (factor === AuthenticationMethodType.U2F) {
|
||||
return { redirect: `/u2f?` + params };
|
||||
}
|
||||
} else if (availableMultiFactors?.length > 1) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName as string,
|
||||
});
|
||||
|
||||
if (authRequestId) {
|
||||
params.append("authRequestId", authRequestId);
|
||||
}
|
||||
|
||||
if (organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
organization ?? (session.factors?.user?.organizationId as string),
|
||||
);
|
||||
}
|
||||
|
||||
return { redirect: `/mfa?` + params };
|
||||
} else if (
|
||||
(loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) &&
|
||||
!availableMultiFactors.length
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName as string,
|
||||
force: "true", // this defines if the mfa is forced in the settings
|
||||
checkAfter: "true", // this defines if the check is directly made after the setup
|
||||
});
|
||||
|
||||
if (authRequestId) {
|
||||
params.append("authRequestId", authRequestId);
|
||||
}
|
||||
|
||||
if (organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
organization ?? (session.factors?.user?.organizationId as string),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: provide a way to setup passkeys on mfa page?
|
||||
return { redirect: `/mfa/set?` + params };
|
||||
}
|
||||
|
||||
// TODO: implement passkey setup
|
||||
|
||||
// else if (
|
||||
// submitted.factors &&
|
||||
// !submitted.factors.webAuthN && // if session was not verified with a passkey
|
||||
// promptPasswordless && // if explicitly prompted due policy
|
||||
// !isAlternative // escaped if password was used as an alternative method
|
||||
// ) {
|
||||
// const params = new URLSearchParams({
|
||||
// loginName: submitted.factors.user.loginName,
|
||||
// prompt: "true",
|
||||
// });
|
||||
|
||||
// if (authRequestId) {
|
||||
// params.append("authRequestId", authRequestId);
|
||||
// }
|
||||
|
||||
// if (organization) {
|
||||
// params.append("organization", organization);
|
||||
// }
|
||||
|
||||
// return router.push(`/passkey/set?` + params);
|
||||
// }
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createServerTransport } from "@zitadel/client/node";
|
||||
import {
|
||||
createIdpServiceClient,
|
||||
createOIDCServiceClient,
|
||||
@@ -7,12 +8,14 @@ import {
|
||||
createUserServiceClient,
|
||||
makeReqCtx,
|
||||
} from "@zitadel/client/v2";
|
||||
import { createServerTransport } from "@zitadel/node";
|
||||
import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import {
|
||||
AddHumanUserRequest,
|
||||
ResendEmailCodeRequest,
|
||||
ResendEmailCodeRequestSchema,
|
||||
RetrieveIdentityProviderIntentRequest,
|
||||
SendEmailCodeRequestSchema,
|
||||
SetPasswordRequest,
|
||||
SetPasswordRequestSchema,
|
||||
VerifyPasskeyRegistrationRequest,
|
||||
@@ -23,6 +26,7 @@ import { create, Duration } from "@zitadel/client";
|
||||
import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb";
|
||||
import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb";
|
||||
import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb";
|
||||
import {
|
||||
NotificationType,
|
||||
@@ -270,6 +274,32 @@ export async function resendInviteCode(userId: string) {
|
||||
return userService.resendInviteCode({ userId }, {});
|
||||
}
|
||||
|
||||
export async function sendEmailCode(
|
||||
userId: string,
|
||||
host: string | null,
|
||||
authRequestId?: string,
|
||||
) {
|
||||
let medium = create(SendEmailCodeRequestSchema, {
|
||||
userId,
|
||||
});
|
||||
|
||||
if (host) {
|
||||
medium = create(SendEmailCodeRequestSchema, {
|
||||
...medium,
|
||||
verification: {
|
||||
case: "sendCode",
|
||||
value: create(SendEmailVerificationCodeSchema, {
|
||||
urlTemplate:
|
||||
`${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` +
|
||||
(authRequestId ? `&authRequestId=${authRequestId}` : ""),
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return userService.sendEmailCode(medium, {});
|
||||
}
|
||||
|
||||
export async function createInviteCode(userId: string, host: string | null) {
|
||||
let medium = create(SendInviteCodeSchema, {
|
||||
applicationName: "Typescript Login",
|
||||
@@ -478,13 +508,26 @@ export async function verifyEmail(userId: string, verificationCode: string) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function resendEmailCode(userId: string) {
|
||||
return userService.resendEmailCode(
|
||||
{
|
||||
userId,
|
||||
},
|
||||
{},
|
||||
);
|
||||
export async function resendEmailCode(
|
||||
userId: string,
|
||||
host: string | null,
|
||||
authRequestId?: string,
|
||||
) {
|
||||
let request: ResendEmailCodeRequest = create(ResendEmailCodeRequestSchema, {
|
||||
userId,
|
||||
});
|
||||
|
||||
if (host) {
|
||||
const medium = create(SendEmailVerificationCodeSchema, {
|
||||
urlTemplate:
|
||||
`${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` +
|
||||
(authRequestId ? `&authRequestId=${authRequestId}` : ""),
|
||||
});
|
||||
|
||||
request = { ...request, verification: { case: "sendCode", value: medium } };
|
||||
}
|
||||
|
||||
return userService.resendEmailCode(request, {});
|
||||
}
|
||||
|
||||
export function retrieveIDPIntent(id: string, token: string) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import sharedConfig from "zitadel-tailwind-config/tailwind.config.mjs";
|
||||
import sharedConfig from "@zitadel/tailwind-config/tailwind.config.mjs";
|
||||
|
||||
let colors = {
|
||||
background: { light: { contrast: {} }, dark: { contrast: {} } },
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": ["@zitadel/node#build", "@zitadel/client#build"]
|
||||
"dependsOn": ["@zitadel/client#build"]
|
||||
},
|
||||
"test:integration": {
|
||||
"dependsOn": ["@zitadel/node#build", "@zitadel/client#build"]
|
||||
"dependsOn": ["@zitadel/client#build"]
|
||||
},
|
||||
"test:unit": {
|
||||
"dependsOn": ["@zitadel/node#build", "@zitadel/client#build"]
|
||||
"dependsOn": ["@zitadel/client#build"]
|
||||
},
|
||||
"test:watch": {
|
||||
"dependsOn": ["@zitadel/node#build", "@zitadel/client#build"]
|
||||
"dependsOn": ["@zitadel/client#build"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"axios": "^1.7.7",
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-zitadel": "workspace:*",
|
||||
"@zitadel/eslint-config": "workspace:*",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"tsup": "^8.3.5",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["zitadel"],
|
||||
extends: ["@zitadel/eslint-config"],
|
||||
};
|
||||
|
||||
25
packages/zitadel-client/CHANGELOG.md
Normal file
25
packages/zitadel-client/CHANGELOG.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# @zitadel/client
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- README updates
|
||||
- Updated dependencies
|
||||
- @zitadel/proto@1.0.1
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- 32e1199: Initial Release
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- f32ab7f: Initial release
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [f32ab7f]
|
||||
- Updated dependencies [32e1199]
|
||||
- @zitadel/proto@1.0.0
|
||||
53
packages/zitadel-client/README.md
Normal file
53
packages/zitadel-client/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# ZITADEL Client
|
||||
|
||||
This package exports services and utilities to interact with ZITADEL
|
||||
|
||||
## Installation
|
||||
|
||||
To install the package, use npm or yarn:
|
||||
|
||||
```sh
|
||||
npm install @zitadel/client
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
yarn add @zitadel/client
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Importing Services
|
||||
|
||||
You can import and use the services provided by this package to interact with ZITADEL.
|
||||
|
||||
```ts
|
||||
import { createSettingsServiceClient, makeReqCtx } from "@zitadel/client/v2";
|
||||
|
||||
// Example usage
|
||||
const transport = createServerTransport(process.env.ZITADEL_SERVICE_USER_TOKEN!, { baseUrl: process.env.ZITADEL_API_URL! });
|
||||
|
||||
const settingsService = createSettingsServiceClient(transport);
|
||||
|
||||
settingsService.getBrandingSettings({ ctx: makeReqCtx("orgId") }, {});
|
||||
```
|
||||
|
||||
### Utilities
|
||||
|
||||
This package also provides various utilities to work with ZITADEL
|
||||
|
||||
```ts
|
||||
import { timestampMs } from "@zitadel/client";
|
||||
|
||||
// Example usage
|
||||
console.log(`${timestampMs(session.creationDate)}`);
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed documentation and API references, please visit the [ZITADEL documentation](https://zitadel.com/docs).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read the contributing guidelines before getting started.
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "@zitadel/client",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -27,6 +26,11 @@
|
||||
"types": "./dist/v3alpha.d.ts",
|
||||
"import": "./dist/v3alpha.js",
|
||||
"require": "./dist/v3alpha.cjs"
|
||||
},
|
||||
"./node": {
|
||||
"types": "./dist/node.d.ts",
|
||||
"import": "./dist/node.js",
|
||||
"require": "./dist/node.cjs"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
@@ -46,11 +50,14 @@
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.2.2",
|
||||
"@connectrpc/connect": "^2.0.0",
|
||||
"@connectrpc/connect-node": "^2.0.0",
|
||||
"@connectrpc/connect-web": "^2.0.0",
|
||||
"jose": "^5.3.0",
|
||||
"@zitadel/proto": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@bufbuild/protocompile": "^0.0.1",
|
||||
"@zitadel/tsconfig": "workspace:*",
|
||||
"eslint-config-zitadel": "workspace:*"
|
||||
"@zitadel/eslint-config": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@ export { NewAuthorizationBearerInterceptor } from "./interceptors";
|
||||
|
||||
// TODO: Move this to `./protobuf.ts` and export it from there
|
||||
export { create, fromJson, toJson } from "@bufbuild/protobuf";
|
||||
export type { JsonObject } from "@bufbuild/protobuf";
|
||||
export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt";
|
||||
export type { Duration, Timestamp } from "@bufbuild/protobuf/wkt";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createGrpcTransport, GrpcTransportOptions } from "@connectrpc/connect-node";
|
||||
import { NewAuthorizationBearerInterceptor } from "@zitadel/client";
|
||||
import { importPKCS8, SignJWT } from "jose";
|
||||
import { NewAuthorizationBearerInterceptor } from "./interceptors";
|
||||
|
||||
/**
|
||||
* Create a server transport with the given token and configuration options.
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineConfig, Options } from "tsup";
|
||||
|
||||
export default defineConfig((options: Options) => ({
|
||||
entry: ["src/index.ts", "src/v1.ts", "src/v2.ts", "src/v3alpha.ts"],
|
||||
entry: ["src/index.ts", "src/v1.ts", "src/v2.ts", "src/v3alpha.ts", "src/node.ts"],
|
||||
format: ["esm", "cjs"],
|
||||
treeshake: false,
|
||||
splitting: true,
|
||||
|
||||
13
packages/zitadel-eslint-config/CHANGELOG.md
Normal file
13
packages/zitadel-eslint-config/CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# @zitadel/eslint-config
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- README updates
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- f32ab7f: Initial release
|
||||
35
packages/zitadel-eslint-config/README.md
Normal file
35
packages/zitadel-eslint-config/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# ZITADEL ESLint Config
|
||||
|
||||
This package provides the ESLint configuration used by ZITADEL projects. It includes a set of rules and plugins to ensure consistent code quality and style across all ZITADEL codebases.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the package, use npm or yarn:
|
||||
|
||||
```sh
|
||||
npm install @zitadel/eslint-config
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
yarn add @zitadel/eslint-config
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To use the ESLint configuration in your project, extend it in your `.eslintrc` file:
|
||||
|
||||
```js
|
||||
{
|
||||
"extends": "@zitadel/eslint-config"
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed documentation and configuration options, please refer to the [ESLint documentation](https://eslint.org/docs/user-guide/configuring).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read the contributing guidelines before getting started.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eslint-config-zitadel",
|
||||
"version": "0.0.0",
|
||||
"name": "@zitadel/eslint-config",
|
||||
"version": "0.1.1",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
// TODO: React is not used in the server package
|
||||
extends: ["zitadel"],
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"name": "@zitadel/node",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"test": "pnpm test:unit",
|
||||
"test:watch": "pnpm test:unit:watch",
|
||||
"test:unit": "vitest",
|
||||
"test:unit:watch": "vitest --watch",
|
||||
"dev": "tsup --watch",
|
||||
"lint": "eslint \"src/**/*.ts*\"",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist && rm -rf src/proto"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@zitadel/client": "workspace:*",
|
||||
"@connectrpc/connect": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@connectrpc/connect-node": "^2.0.0",
|
||||
"@connectrpc/connect-web": "^2.0.0",
|
||||
"jose": "^5.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@connectrpc/connect": "^2.0.0",
|
||||
"@types/node": "^22.9.0",
|
||||
"@zitadel/client": "workspace:*",
|
||||
"@zitadel/tsconfig": "workspace:*",
|
||||
"eslint-config-zitadel": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"extends": "@zitadel/tsconfig/tsup.json",
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { defineConfig, Options } from "tsup";
|
||||
|
||||
export default defineConfig((options: Options) => ({
|
||||
treeshake: false,
|
||||
splitting: true,
|
||||
entry: ["src/index.ts"],
|
||||
format: ["esm", "cjs"],
|
||||
dts: true,
|
||||
minify: false,
|
||||
clean: true,
|
||||
sourcemap: true,
|
||||
...options,
|
||||
}));
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"//"
|
||||
],
|
||||
"tasks": {
|
||||
"build": {
|
||||
"outputs": [
|
||||
"dist/**"
|
||||
],
|
||||
"dependsOn": [
|
||||
"@zitadel/client#build"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
13
packages/zitadel-prettier-config/CHANGELOG.md
Normal file
13
packages/zitadel-prettier-config/CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# @zitadel/prettier-config
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- README updates
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- f32ab7f: Initial release
|
||||
36
packages/zitadel-prettier-config/README.md
Normal file
36
packages/zitadel-prettier-config/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# ZITADEL Prettier Config
|
||||
|
||||
This package provides the Prettier configuration used by ZITADEL projects. It includes a set of formatting rules to ensure consistent code style across all ZITADEL codebases.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the package, use npm or yarn:
|
||||
|
||||
```sh
|
||||
npm install @zitadel/prettier-config
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
yarn add @zitadel/prettier-config
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To use the Prettier configuration in your project, extend it in your `prettier.config.js` file:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
...require("@zitadel/prettier-config"),
|
||||
// Add your custom configurations here
|
||||
};
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed documentation and configuration options, please refer to the [Prettier documentation](https://prettier.io/docs/en/configuration.html).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read the contributing guidelines before getting started.
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"name": "@zitadel/prettier-config",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"description": "Prettier configuration",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"exports": {
|
||||
".": "./index.js"
|
||||
}
|
||||
|
||||
17
packages/zitadel-proto/CHANGELOG.md
Normal file
17
packages/zitadel-proto/CHANGELOG.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# @zitadel/proto
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- README updates
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- 32e1199: Initial Release
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- f32ab7f: Initial release
|
||||
35
packages/zitadel-proto/README.md
Normal file
35
packages/zitadel-proto/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# ZITADEL Proto
|
||||
|
||||
This package provides the Protocol Buffers (proto) definitions used by ZITADEL projects. It includes the proto files and generated code for interacting with ZITADEL's gRPC APIs.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the package, use npm or yarn:
|
||||
|
||||
```sh
|
||||
npm install @zitadel/proto
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
yarn add @zitadel/proto
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To use the proto definitions in your project, import the generated code:
|
||||
|
||||
```ts
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
|
||||
const org: Organization | null = await getDefaultOrg();
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed documentation and API references, please visit the [ZITADEL documentation](https://zitadel.com/docs).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read the contributing guidelines before getting started.
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "@zitadel/proto",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"type": "module",
|
||||
"files": [
|
||||
"zitadel/**"
|
||||
"zitadel/**",
|
||||
"validate/**",
|
||||
"google/**",
|
||||
"protoc-gen-openapiv2/**"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
|
||||
13
packages/zitadel-tailwind-config/CHANGELOG.md
Normal file
13
packages/zitadel-tailwind-config/CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# @zitadel/tailwind-config
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- README updates
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- f32ab7f: Initial release
|
||||
36
packages/zitadel-tailwind-config/README.md
Normal file
36
packages/zitadel-tailwind-config/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# ZITADEL Tailwind Config
|
||||
|
||||
This package provides the Tailwind CSS configuration used by ZITADEL projects. It includes a set of default styles, themes, and utility classes to ensure consistent design and styling across all ZITADEL codebases.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the package, use npm or yarn:
|
||||
|
||||
```sh
|
||||
npm install @zitadel/tailwind-config
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
yarn add @zitadel/tailwind-config
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To use the Tailwind CSS configuration in your project, extend it in your `tailwind.config.js` file:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
presets: [require("@zitadel/tailwind-config")],
|
||||
// Add your custom configurations here
|
||||
};
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed documentation and configuration options, please refer to the [Tailwind CSS documentation](https://tailwindcss.com/docs)
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read the contributing guidelines before getting started.
|
||||
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"name": "zitadel-tailwind-config",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"name": "@zitadel/tailwind-config",
|
||||
"version": "0.1.1",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "index.js",
|
||||
"devDependencies": {
|
||||
"tailwindcss": "^3.4.14",
|
||||
|
||||
13
packages/zitadel-tsconfig/CHANGELOG.md
Normal file
13
packages/zitadel-tsconfig/CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# @zitadel/tsconfig
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- README updates
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- f32ab7f: Initial release
|
||||
35
packages/zitadel-tsconfig/README.md
Normal file
35
packages/zitadel-tsconfig/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# ZITADEL TypeScript Config
|
||||
|
||||
This package provides the TypeScript configuration used by ZITADEL projects. It includes a set of rules and settings to ensure consistent TypeScript configuration across all ZITADEL codebases.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the package, use npm or yarn:
|
||||
|
||||
```sh
|
||||
npm install @zitadel/tsconfig
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
yarn add @zitadel/tsconfig
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To use the TypeScript configuration in your project, extend it in your `tsconfig.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "@zitadel/tsconfig/tsup.json"
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed documentation and configuration options, please refer to the [TypeScript documentation](https://www.typescriptlang.org/docs/).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read the contributing guidelines before getting started.
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"name": "@zitadel/tsconfig",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.1.1",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
140
pnpm-lock.yaml
generated
140
pnpm-lock.yaml
generated
@@ -37,6 +37,9 @@ importers:
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(vite@5.4.11(@types/node@22.9.0)(sass@1.80.7))
|
||||
'@zitadel/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:packages/zitadel-eslint-config
|
||||
'@zitadel/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:packages/zitadel-prettier-config
|
||||
@@ -49,9 +52,6 @@ importers:
|
||||
eslint:
|
||||
specifier: 8.57.1
|
||||
version: 8.57.1
|
||||
eslint-config-zitadel:
|
||||
specifier: workspace:*
|
||||
version: link:packages/eslint-config-zitadel
|
||||
prettier:
|
||||
specifier: ^3.2.5
|
||||
version: 3.3.3
|
||||
@@ -91,9 +91,6 @@ importers:
|
||||
'@zitadel/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/zitadel-client
|
||||
'@zitadel/node':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/zitadel-node
|
||||
'@zitadel/proto':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/zitadel-proto
|
||||
@@ -170,9 +167,15 @@ importers:
|
||||
'@vercel/git-hooks':
|
||||
specifier: 1.0.0
|
||||
version: 1.0.0
|
||||
'@zitadel/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/zitadel-eslint-config
|
||||
'@zitadel/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/zitadel-prettier-config
|
||||
'@zitadel/tailwind-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/zitadel-tailwind-config
|
||||
'@zitadel/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/zitadel-tsconfig
|
||||
@@ -189,11 +192,8 @@ importers:
|
||||
specifier: 6.0.0
|
||||
version: 6.0.0
|
||||
env-cmd:
|
||||
specifier: ^10.1.0
|
||||
specifier: ^10.0.0
|
||||
version: 10.1.0
|
||||
eslint-config-zitadel:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/eslint-config-zitadel
|
||||
grpc-tools:
|
||||
specifier: 1.12.4
|
||||
version: 1.12.4
|
||||
@@ -230,11 +230,39 @@ importers:
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.6.3
|
||||
zitadel-tailwind-config:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/zitadel-tailwind-config
|
||||
|
||||
packages/eslint-config-zitadel:
|
||||
packages/zitadel-client:
|
||||
dependencies:
|
||||
'@bufbuild/protobuf':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
'@connectrpc/connect':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@bufbuild/protobuf@2.2.2)
|
||||
'@connectrpc/connect-node':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@bufbuild/protobuf@2.2.2)(@connectrpc/connect@2.0.0(@bufbuild/protobuf@2.2.2))
|
||||
'@connectrpc/connect-web':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@bufbuild/protobuf@2.2.2)(@connectrpc/connect@2.0.0(@bufbuild/protobuf@2.2.2))
|
||||
'@zitadel/proto':
|
||||
specifier: workspace:*
|
||||
version: link:../zitadel-proto
|
||||
jose:
|
||||
specifier: ^5.3.0
|
||||
version: 5.8.0
|
||||
devDependencies:
|
||||
'@bufbuild/protocompile':
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1(@bufbuild/buf@1.47.2)
|
||||
'@zitadel/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../zitadel-eslint-config
|
||||
'@zitadel/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../zitadel-tsconfig
|
||||
|
||||
packages/zitadel-eslint-config:
|
||||
dependencies:
|
||||
'@babel/eslint-parser':
|
||||
specifier: ^7.25.9
|
||||
@@ -255,56 +283,6 @@ importers:
|
||||
specifier: ^7.34.1
|
||||
version: 7.35.0(eslint@8.57.1)
|
||||
|
||||
packages/zitadel-client:
|
||||
dependencies:
|
||||
'@bufbuild/protobuf':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
'@connectrpc/connect':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@bufbuild/protobuf@2.2.2)
|
||||
'@zitadel/proto':
|
||||
specifier: workspace:*
|
||||
version: link:../zitadel-proto
|
||||
devDependencies:
|
||||
'@bufbuild/protocompile':
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1(@bufbuild/buf@1.47.2)
|
||||
'@zitadel/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../zitadel-tsconfig
|
||||
eslint-config-zitadel:
|
||||
specifier: workspace:*
|
||||
version: link:../eslint-config-zitadel
|
||||
|
||||
packages/zitadel-node:
|
||||
dependencies:
|
||||
'@connectrpc/connect-node':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@bufbuild/protobuf@2.2.2)(@connectrpc/connect@2.0.0(@bufbuild/protobuf@2.2.2))
|
||||
'@connectrpc/connect-web':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@bufbuild/protobuf@2.2.2)(@connectrpc/connect@2.0.0(@bufbuild/protobuf@2.2.2))
|
||||
jose:
|
||||
specifier: ^5.3.0
|
||||
version: 5.8.0
|
||||
devDependencies:
|
||||
'@connectrpc/connect':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@bufbuild/protobuf@2.2.2)
|
||||
'@types/node':
|
||||
specifier: ^22.9.0
|
||||
version: 22.9.0
|
||||
'@zitadel/client':
|
||||
specifier: workspace:*
|
||||
version: link:../zitadel-client
|
||||
'@zitadel/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../zitadel-tsconfig
|
||||
eslint-config-zitadel:
|
||||
specifier: workspace:*
|
||||
version: link:../eslint-config-zitadel
|
||||
|
||||
packages/zitadel-prettier-config: {}
|
||||
|
||||
packages/zitadel-proto:
|
||||
@@ -2226,15 +2204,6 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.3.6:
|
||||
resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.3.7:
|
||||
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -3465,9 +3434,6 @@ packages:
|
||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
ms@2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -4480,12 +4446,6 @@ packages:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
ts-api-utils@1.3.0:
|
||||
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
|
||||
engines: {node: '>=16'}
|
||||
peerDependencies:
|
||||
typescript: '>=4.2.0'
|
||||
|
||||
ts-api-utils@1.4.1:
|
||||
resolution: {integrity: sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -6095,7 +6055,7 @@ snapshots:
|
||||
'@typescript-eslint/types': 7.18.0
|
||||
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3)
|
||||
'@typescript-eslint/visitor-keys': 7.18.0
|
||||
debug: 4.3.6
|
||||
debug: 4.3.7(supports-color@5.5.0)
|
||||
eslint: 8.57.1
|
||||
optionalDependencies:
|
||||
typescript: 5.6.3
|
||||
@@ -6137,7 +6097,7 @@ snapshots:
|
||||
is-glob: 4.0.3
|
||||
minimatch: 9.0.5
|
||||
semver: 7.6.3
|
||||
ts-api-utils: 1.3.0(typescript@5.6.3)
|
||||
ts-api-utils: 1.4.1(typescript@5.6.3)
|
||||
optionalDependencies:
|
||||
typescript: 5.6.3
|
||||
transitivePeerDependencies:
|
||||
@@ -6787,10 +6747,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
supports-color: 8.1.1
|
||||
|
||||
debug@4.3.6:
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
debug@4.3.7(supports-color@5.5.0):
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -6936,7 +6892,7 @@ snapshots:
|
||||
env-cmd@10.1.0:
|
||||
dependencies:
|
||||
commander: 4.1.1
|
||||
cross-spawn: 7.0.3
|
||||
cross-spawn: 7.0.5
|
||||
|
||||
environment@1.1.0: {}
|
||||
|
||||
@@ -8242,8 +8198,6 @@ snapshots:
|
||||
|
||||
mri@1.2.0: {}
|
||||
|
||||
ms@2.1.2: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
mz@2.7.0:
|
||||
@@ -9237,10 +9191,6 @@ snapshots:
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
ts-api-utils@1.3.0(typescript@5.6.3):
|
||||
dependencies:
|
||||
typescript: 5.6.3
|
||||
|
||||
ts-api-utils@1.4.1(typescript@5.6.3):
|
||||
dependencies:
|
||||
typescript: 5.6.3
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"ZITADEL_SYSTEM_API_KEY",
|
||||
"ZITADEL_ISSUER",
|
||||
"ZITADEL_ADMIN_TOKEN",
|
||||
"CACHE_REVALIDATION_INTERVAL_IN_SECONDS",
|
||||
"EMAIL_VERIFICATION",
|
||||
"VERCEL_URL"
|
||||
],
|
||||
"tasks": {
|
||||
|
||||
Reference in New Issue
Block a user