mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 06:42:59 +00:00
Merge pull request #11 from zitadel/username
feat: v2 alpha service, login with username password, email verification
This commit is contained in:
103
apps/login/app/(login)/accounts/page.tsx
Normal file
103
apps/login/app/(login)/accounts/page.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Session } from "#/../../packages/zitadel-server/dist";
|
||||
import { listSessions, server } from "#/lib/zitadel";
|
||||
import Alert from "#/ui/Alert";
|
||||
import { Avatar } from "#/ui/Avatar";
|
||||
import { getAllSessionIds } from "#/utils/cookies";
|
||||
import { UserPlusIcon, XCircleIcon } from "@heroicons/react/24/outline";
|
||||
import moment from "moment";
|
||||
import Link from "next/link";
|
||||
|
||||
async function loadSessions(): Promise<Session[]> {
|
||||
const ids = await getAllSessionIds();
|
||||
|
||||
if (ids && ids.length) {
|
||||
const response = await listSessions(
|
||||
server,
|
||||
ids.filter((id: string | undefined) => !!id)
|
||||
);
|
||||
return response?.sessions ?? [];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const sessions = await loadSessions();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>Accounts</h1>
|
||||
<p className="ztdl-p mb-6 block">Use your ZITADEL Account</p>
|
||||
|
||||
<div className="flex flex-col w-full space-y-2">
|
||||
{sessions ? (
|
||||
sessions
|
||||
.filter((session) => session?.factors?.user?.loginName)
|
||||
.map((session, index) => {
|
||||
const validPassword = session?.factors?.password?.verifiedAt;
|
||||
return (
|
||||
<Link
|
||||
key={"session-" + index}
|
||||
href={
|
||||
validPassword
|
||||
? `/signedin?` +
|
||||
new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName as string,
|
||||
})
|
||||
: `/password?` +
|
||||
new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName as string,
|
||||
})
|
||||
}
|
||||
className="group flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light hover:shadow-lg dark:hover:bg-white/10 py-2 px-4 rounded-md transition-all"
|
||||
>
|
||||
<div className="pr-4">
|
||||
<Avatar
|
||||
size="small"
|
||||
loginName={session.factors?.user?.loginName as string}
|
||||
name={session.factors?.user?.displayName ?? ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<span className="">
|
||||
{session.factors?.user?.displayName}
|
||||
</span>
|
||||
<span className="text-xs opacity-80">
|
||||
{session.factors?.user?.loginName}
|
||||
</span>
|
||||
{validPassword && (
|
||||
<span className="text-xs opacity-80">
|
||||
{moment(new Date(validPassword)).fromNow()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<span className="flex-grow"></span>
|
||||
<div className="relative flex flex-row items-center">
|
||||
{validPassword ? (
|
||||
<div className="absolute h-2 w-2 bg-green-500 rounded-full mx-2 transform right-0 group-hover:right-6 transition-all"></div>
|
||||
) : (
|
||||
<div className="absolute h-2 w-2 bg-red-500 rounded-full mx-2 transform right-0 group-hover:right-6 transition-all"></div>
|
||||
)}
|
||||
|
||||
<XCircleIcon className="hidden group-hover:block h-5 w-5 transition-all opacity-50 hover:opacity-100" />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Alert>No Sessions available!</Alert>
|
||||
)}
|
||||
<Link href="/username">
|
||||
<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" />
|
||||
</div>
|
||||
<span className="text-sm">Add another account</span>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,9 +10,9 @@ export default function Error({ error, reset }: any) {
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<Boundary labels={["Home page Error UI"]} color="pink">
|
||||
<Boundary labels={["Home page Error UI"]} color="red">
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm text-pink-500">
|
||||
<div className="text-sm text-red-500 dark:text-red-500">
|
||||
<strong className="font-bold">Error:</strong> {error?.message}
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
import { ZitadelLogo } from "#/ui/ZitadelLogo";
|
||||
import { BrandingSettings } from "@zitadel/server";
|
||||
import React from "react";
|
||||
import { getBrandingSettings, server } from "#/lib/zitadel";
|
||||
import { Logo } from "#/ui/Logo";
|
||||
|
||||
export default async function Layout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const branding = await getBrandingSettings(server);
|
||||
let partial: Partial<BrandingSettings> | undefined;
|
||||
if (branding) {
|
||||
partial = {
|
||||
lightTheme: branding?.lightTheme,
|
||||
darkTheme: branding?.darkTheme,
|
||||
};
|
||||
}
|
||||
return (
|
||||
<div className="mx-auto flex flex-col items-center space-y-4">
|
||||
<div className="relative">
|
||||
<ZitadelLogo height={70} width={180} />
|
||||
{branding && (
|
||||
<Logo
|
||||
lightSrc={branding.lightTheme?.logoUrl}
|
||||
darkSrc={branding.darkTheme?.logoUrl}
|
||||
height={150}
|
||||
width={150}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="w-full">{children}</div>
|
||||
|
||||
@@ -12,7 +12,11 @@ export default function Page() {
|
||||
<h1>Password</h1>
|
||||
<p className="ztdl-p mb-6 block">Enter your password.</p>
|
||||
|
||||
<UserAvatar name="max@zitadel.com"></UserAvatar>
|
||||
<UserAvatar
|
||||
showDropdown
|
||||
displayName="Max Peintner"
|
||||
loginName="max@zitadel.com"
|
||||
></UserAvatar>
|
||||
|
||||
<div className="w-full">
|
||||
<TextInput type="password" label="Password" />
|
||||
|
||||
@@ -12,8 +12,11 @@ export default function Page() {
|
||||
<h1>Password</h1>
|
||||
<p className="ztdl-p mb-6 block">Enter your password.</p>
|
||||
|
||||
<UserAvatar name="max@zitadel.com"></UserAvatar>
|
||||
|
||||
<UserAvatar
|
||||
showDropdown
|
||||
displayName="Max Peintner"
|
||||
loginName="max@zitadel.com"
|
||||
></UserAvatar>
|
||||
<div className="w-full">
|
||||
<TextInput type="password" label="Password" />
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,11 @@ export default function Page() {
|
||||
<h1>Password</h1>
|
||||
<p className="ztdl-p mb-6 block">Enter your password.</p>
|
||||
|
||||
<UserAvatar name="max@zitadel.com"></UserAvatar>
|
||||
<UserAvatar
|
||||
showDropdown
|
||||
displayName="Max Peintner"
|
||||
loginName="max@zitadel.com"
|
||||
></UserAvatar>
|
||||
|
||||
<div className="w-full">
|
||||
<TextInput type="password" label="Password" />
|
||||
|
||||
@@ -1,32 +1,50 @@
|
||||
"use client";
|
||||
import { Button, ButtonVariants } from "#/ui/Button";
|
||||
import { TextInput } from "#/ui/Input";
|
||||
import { getSession, server } from "#/lib/zitadel";
|
||||
import Alert from "#/ui/Alert";
|
||||
import PasswordForm from "#/ui/PasswordForm";
|
||||
import UserAvatar from "#/ui/UserAvatar";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { getMostRecentCookieWithLoginname } from "#/utils/cookies";
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter();
|
||||
export default async function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Record<string | number | symbol, string | undefined>;
|
||||
}) {
|
||||
const { loginName } = searchParams;
|
||||
const sessionFactors = await loadSession(loginName);
|
||||
|
||||
async function loadSession(loginName?: string) {
|
||||
const recent = await getMostRecentCookieWithLoginname(loginName);
|
||||
|
||||
return getSession(server, recent.id, recent.token).then((response) => {
|
||||
if (response?.session) {
|
||||
return response.session;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>Password</h1>
|
||||
<h1>{sessionFactors?.factors?.user?.displayName ?? "Password"}</h1>
|
||||
<p className="ztdl-p mb-6 block">Enter your password.</p>
|
||||
|
||||
<UserAvatar name="max@zitadel.com"></UserAvatar>
|
||||
{!sessionFactors && (
|
||||
<div className="py-4">
|
||||
<Alert>
|
||||
Could not get the context of the user. Make sure to enter the
|
||||
username first or provide a loginName as searchParam.
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-full">
|
||||
<TextInput type="password" label="Password" />
|
||||
</div>
|
||||
{sessionFactors && (
|
||||
<UserAvatar
|
||||
loginName={loginName ?? sessionFactors.factors?.user?.loginName ?? ""}
|
||||
displayName={sessionFactors.factors?.user?.displayName}
|
||||
showDropdown
|
||||
></UserAvatar>
|
||||
)}
|
||||
|
||||
<div className="flex w-full flex-row items-center justify-between">
|
||||
<Button
|
||||
onClick={() => router.back()}
|
||||
variant={ButtonVariants.Secondary}
|
||||
>
|
||||
back
|
||||
</Button>
|
||||
<Button variant={ButtonVariants.Primary}>continue</Button>
|
||||
</div>
|
||||
<PasswordForm loginName={loginName} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,11 @@ export default function Page() {
|
||||
<h1>Password</h1>
|
||||
<p className="ztdl-p mb-6 block">Enter your password.</p>
|
||||
|
||||
<UserAvatar name="max@zitadel.cloud"></UserAvatar>
|
||||
<UserAvatar
|
||||
showDropdown
|
||||
displayName="Max Peintner"
|
||||
loginName="max@zitadel.com"
|
||||
></UserAvatar>
|
||||
|
||||
<div className="w-full">
|
||||
<TextInput type="password" label="Password" />
|
||||
|
||||
@@ -12,7 +12,11 @@ export default function Page() {
|
||||
<h1>Password</h1>
|
||||
<p className="ztdl-p mb-6 block">Enter your password.</p>
|
||||
|
||||
<UserAvatar name="max@zitadel.com"></UserAvatar>
|
||||
<UserAvatar
|
||||
showDropdown
|
||||
displayName="Max Peintner"
|
||||
loginName="max@zitadel.com"
|
||||
></UserAvatar>
|
||||
|
||||
<div className="w-full">
|
||||
<TextInput type="password" label="Password" />
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import {
|
||||
getPasswordComplexityPolicy,
|
||||
getPrivacyPolicy,
|
||||
getLegalAndSupportSettings,
|
||||
getPasswordComplexitySettings,
|
||||
server,
|
||||
} from "#/lib/zitadel";
|
||||
import RegisterForm from "#/ui/RegisterForm";
|
||||
|
||||
export default async function Page() {
|
||||
const privacyPolicy = await getPrivacyPolicy(server);
|
||||
const passwordComplexityPolicy = await getPasswordComplexityPolicy(server);
|
||||
const legal = await getLegalAndSupportSettings(server);
|
||||
const passwordComplexitySettings = await getPasswordComplexitySettings(
|
||||
server
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>Register</h1>
|
||||
<p className="ztdl-p">Create your ZITADEL account.</p>
|
||||
|
||||
{privacyPolicy && passwordComplexityPolicy && (
|
||||
{legal && passwordComplexitySettings && (
|
||||
<RegisterForm
|
||||
privacyPolicy={privacyPolicy}
|
||||
passwordComplexityPolicy={passwordComplexityPolicy}
|
||||
legal={legal}
|
||||
passwordComplexitySettings={passwordComplexitySettings}
|
||||
></RegisterForm>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Button, ButtonVariants } from "#/ui/Button";
|
||||
import { NextPage, NextPageContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
type Props = {
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
|
||||
31
apps/login/app/(login)/signedin/page.tsx
Normal file
31
apps/login/app/(login)/signedin/page.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { getSession, server } from "#/lib/zitadel";
|
||||
import UserAvatar from "#/ui/UserAvatar";
|
||||
import { getMostRecentCookieWithLoginname } from "#/utils/cookies";
|
||||
|
||||
async function loadSession(loginName: string) {
|
||||
const recent = await getMostRecentCookieWithLoginname(`${loginName}`);
|
||||
|
||||
return getSession(server, recent.id, recent.token).then((response) => {
|
||||
if (response?.session) {
|
||||
return response.session;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default async function Page({ searchParams }: { searchParams: any }) {
|
||||
const { loginName } = searchParams;
|
||||
const sessionFactors = await loadSession(loginName);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>{`Welcome ${sessionFactors?.factors?.user?.displayName}`}</h1>
|
||||
<p className="ztdl-p mb-6 block">You are signed in.</p>
|
||||
|
||||
<UserAvatar
|
||||
loginName={loginName ?? sessionFactors?.factors?.user?.loginName}
|
||||
displayName={sessionFactors?.factors?.user?.displayName}
|
||||
showDropdown
|
||||
></UserAvatar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { Button, ButtonVariants } from "#/ui/Button";
|
||||
import IdentityProviders from "#/ui/IdentityProviders";
|
||||
import { TextInput } from "#/ui/Input";
|
||||
import { useRouter } from "next/navigation";
|
||||
import UsernameForm from "#/ui/UsernameForm";
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter();
|
||||
|
||||
function submit() {
|
||||
router.push("/password");
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>Welcome back!</h1>
|
||||
<p className="ztdl-p">Enter your login data.</p>
|
||||
|
||||
<form className="w-full" onSubmit={() => submit()}>
|
||||
<div className="block">
|
||||
<TextInput label="Loginname" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<IdentityProviders />
|
||||
</div>
|
||||
<div className="mt-8 flex w-full flex-row items-center justify-between">
|
||||
<Button type="button" variant={ButtonVariants.Secondary}>
|
||||
back
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant={ButtonVariants.Primary}
|
||||
onClick={() => submit()}
|
||||
>
|
||||
continue
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<UsernameForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
28
apps/login/app/(login)/verify/page.tsx
Normal file
28
apps/login/app/(login)/verify/page.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import VerifyEmailForm from "#/ui/VerifyEmailForm";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
export default async function Page({ searchParams }: { searchParams: any }) {
|
||||
const { userID, code, submit, orgID, loginname, passwordset } = searchParams;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<h1>Verify user</h1>
|
||||
<p className="ztdl-p mb-6 block">
|
||||
Enter the Code provided in the verification email.
|
||||
</p>
|
||||
|
||||
{userID ? (
|
||||
<VerifyEmailForm
|
||||
userId={userID}
|
||||
code={code}
|
||||
submit={submit === "true"}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full flex flex-row items-center justify-center border border-yellow-600/40 dark:border-yellow-500/20 bg-yellow-200/30 text-yellow-600 dark:bg-yellow-700/20 dark:text-yellow-200 rounded-md py-2 scroll-px-40">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 mr-2" />
|
||||
<span className="text-center text-sm">No userId provided!</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,13 +2,12 @@ import "#/styles/globals.scss";
|
||||
import { AddressBar } from "#/ui/AddressBar";
|
||||
import { GlobalNav } from "#/ui/GlobalNav";
|
||||
import { Lato } from "next/font/google";
|
||||
import Byline from "#/ui/Byline";
|
||||
import { LayoutProviders } from "#/ui/LayoutProviders";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import ThemeWrapper from "#/ui/ThemeWrapper";
|
||||
import { getBranding } from "#/lib/zitadel";
|
||||
import { getBrandingSettings } from "#/lib/zitadel";
|
||||
import { server } from "../lib/zitadel";
|
||||
import { LabelPolicyColors } from "#/utils/colors";
|
||||
import { BrandingSettings } from "@zitadel/server";
|
||||
|
||||
const lato = Lato({
|
||||
weight: ["400", "700", "900"],
|
||||
@@ -25,26 +24,23 @@ export default async function RootLayout({
|
||||
// later only shown with dev mode enabled
|
||||
const showNav = true;
|
||||
|
||||
const branding = await getBranding(server);
|
||||
let partialPolicy: LabelPolicyColors | undefined;
|
||||
console.log(branding);
|
||||
const branding = await getBrandingSettings(server);
|
||||
let partial: Partial<BrandingSettings> | undefined;
|
||||
if (branding) {
|
||||
partialPolicy = {
|
||||
backgroundColor: branding?.backgroundColor,
|
||||
backgroundColorDark: branding?.backgroundColorDark,
|
||||
primaryColor: branding?.primaryColor,
|
||||
primaryColorDark: branding?.primaryColorDark,
|
||||
warnColor: branding?.warnColor,
|
||||
warnColorDark: branding?.warnColorDark,
|
||||
fontColor: branding?.fontColor,
|
||||
fontColorDark: branding?.fontColorDark,
|
||||
partial = {
|
||||
lightTheme: branding?.lightTheme,
|
||||
darkTheme: branding?.darkTheme,
|
||||
};
|
||||
}
|
||||
|
||||
let domain = process.env.ZITADEL_API_URL;
|
||||
domain = domain ? domain.replace("https://", "") : "acme.com";
|
||||
|
||||
return (
|
||||
<html lang="en" className={`${lato.className}`} suppressHydrationWarning>
|
||||
<head />
|
||||
<body>
|
||||
<ThemeWrapper branding={partialPolicy}>
|
||||
<ThemeWrapper branding={partial}>
|
||||
<LayoutProviders>
|
||||
<div className="h-screen overflow-y-scroll bg-background-light-600 dark:bg-background-dark-600 bg-[url('/grid-light.svg')] dark:bg-[url('/grid-dark.svg')]">
|
||||
{showNav && <GlobalNav />}
|
||||
@@ -54,7 +50,7 @@ export default async function RootLayout({
|
||||
{showNav && (
|
||||
<div className="rounded-lg bg-vc-border-gradient dark:bg-dark-vc-border-gradient p-px shadow-lg shadow-black/5 dark:shadow-black/20">
|
||||
<div className="rounded-lg bg-background-light-400 dark:bg-background-dark-500">
|
||||
<AddressBar />
|
||||
<AddressBar domain={domain} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -64,16 +60,6 @@ export default async function RootLayout({
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`rounded-lg bg-vc-border-gradient dark:bg-dark-vc-border-gradient p-px shadow-lg shadow-black/5 dark:shadow-black/20 ${
|
||||
showNav ? "" : "max-w-[440px] w-full fixed bottom-4"
|
||||
}`}
|
||||
>
|
||||
<div className="rounded-lg bg-background-light-400 dark:bg-background-dark-500">
|
||||
<Byline />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,11 +4,9 @@ import Link from "next/link";
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<h1 className="text-xl font-medium text-gray-800 dark:text-gray-300">
|
||||
Pages
|
||||
</h1>
|
||||
<h1 className="text-xl font-medium">Pages</h1>
|
||||
|
||||
<div className="space-y-10 text-white">
|
||||
<div className="space-y-10">
|
||||
{demos.map((section) => {
|
||||
return (
|
||||
<div key={section.name} className="space-y-5">
|
||||
@@ -21,14 +19,12 @@ export default function Page() {
|
||||
<Link
|
||||
href={`/${item.slug}`}
|
||||
key={item.name}
|
||||
className="bg-background-light-400 dark:bg-background-dark-400 group block space-y-1.5 rounded-lg px-5 py-3 hover:bg-background-light-500 hover:dark:bg-background-dark-300 hover:shadow-lg border border-divider-light dark:border-divider-dark transition-all "
|
||||
className="bg-background-light-400 dark:bg-background-dark-400 group block space-y-1.5 rounded-md px-5 py-3 hover:shadow-lg hover:dark:bg-white/10 border border-divider-light dark:border-divider-dark transition-all "
|
||||
>
|
||||
<div className="font-medium text-gray-600 dark:text-gray-200 group-hover:text-gray-900 dark:group-hover:text-gray-300">
|
||||
{item.name}
|
||||
</div>
|
||||
<div className="font-medium">{item.name}</div>
|
||||
|
||||
{item.description ? (
|
||||
<div className="line-clamp-3 text-sm text-gray-500 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-gray-300">
|
||||
<div className="line-clamp-3 text-sm text-text-light-secondary-500 dark:text-text-dark-secondary-500">
|
||||
{item.description}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
20
apps/login/app/resendverifyemail/route.ts
Normal file
20
apps/login/app/resendverifyemail/route.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { setEmail, server } from "#/lib/zitadel";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
if (body) {
|
||||
const { userId, code } = body;
|
||||
|
||||
// replace with resend Mail method once its implemented
|
||||
return setEmail(server, userId)
|
||||
.then((resp) => {
|
||||
return NextResponse.json(resp);
|
||||
})
|
||||
.catch((error) => {
|
||||
return NextResponse.json(error, { status: 500 });
|
||||
});
|
||||
} else {
|
||||
return NextResponse.error();
|
||||
}
|
||||
}
|
||||
124
apps/login/app/session/route.ts
Normal file
124
apps/login/app/session/route.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { createSession, getSession, server, setSession } from "#/lib/zitadel";
|
||||
import {
|
||||
SessionCookie,
|
||||
addSessionToCookie,
|
||||
getMostRecentSessionCookie,
|
||||
updateSessionCookie,
|
||||
} from "#/utils/cookies";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
if (body) {
|
||||
const { loginName } = body;
|
||||
|
||||
const createdSession = await createSession(server, loginName);
|
||||
if (createdSession) {
|
||||
return getSession(
|
||||
server,
|
||||
createdSession.sessionId,
|
||||
createdSession.sessionToken
|
||||
).then((response) => {
|
||||
if (response?.session && response.session?.factors?.user?.loginName) {
|
||||
const sessionCookie: SessionCookie = {
|
||||
id: createdSession.sessionId,
|
||||
token: createdSession.sessionToken,
|
||||
changeDate: response.session.changeDate?.toString() ?? "",
|
||||
loginName: response.session?.factors?.user?.loginName ?? "",
|
||||
};
|
||||
return addSessionToCookie(sessionCookie).then(() => {
|
||||
return NextResponse.json({ factors: response?.session?.factors });
|
||||
});
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{
|
||||
details:
|
||||
"could not get session or session does not have loginName",
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return NextResponse.error();
|
||||
}
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ details: "Session could not be created" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param request password for the most recent session
|
||||
* @returns the updated most recent Session with the added password
|
||||
*/
|
||||
export async function PUT(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
if (body) {
|
||||
const { password } = body;
|
||||
|
||||
const recent = await getMostRecentSessionCookie();
|
||||
|
||||
return setSession(server, recent.id, recent.token, password)
|
||||
.then((session) => {
|
||||
if (session) {
|
||||
const sessionCookie: SessionCookie = {
|
||||
id: recent.id,
|
||||
token: session.sessionToken,
|
||||
changeDate: session.details?.changeDate?.toString() ?? "",
|
||||
loginName: recent.loginName,
|
||||
};
|
||||
|
||||
return getSession(server, sessionCookie.id, sessionCookie.token).then(
|
||||
(response) => {
|
||||
if (
|
||||
response?.session &&
|
||||
response.session.factors?.user?.loginName
|
||||
) {
|
||||
const { session } = response;
|
||||
const newCookie: SessionCookie = {
|
||||
id: sessionCookie.id,
|
||||
token: sessionCookie.token,
|
||||
changeDate: session.changeDate?.toString() ?? "",
|
||||
loginName: session.factors?.user?.loginName ?? "",
|
||||
};
|
||||
|
||||
return updateSessionCookie(sessionCookie.id, newCookie)
|
||||
.then(() => {
|
||||
return NextResponse.json({ factors: session.factors });
|
||||
})
|
||||
.catch((error) => {
|
||||
return NextResponse.json(
|
||||
{ details: "could not set cookie" },
|
||||
{ status: 500 }
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{
|
||||
details:
|
||||
"could not get session or session does not have loginName",
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ details: "Session not be set" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("erasd", error);
|
||||
return NextResponse.json(error, { status: 500 });
|
||||
});
|
||||
} else {
|
||||
return NextResponse.error();
|
||||
}
|
||||
}
|
||||
19
apps/login/app/verifyemail/route.ts
Normal file
19
apps/login/app/verifyemail/route.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { server, verifyEmail } from "#/lib/zitadel";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
if (body) {
|
||||
const { userId, code } = body;
|
||||
|
||||
return verifyEmail(server, userId, code)
|
||||
.then((resp) => {
|
||||
return NextResponse.json(resp);
|
||||
})
|
||||
.catch((error) => {
|
||||
return NextResponse.json(error, { status: 500 });
|
||||
});
|
||||
} else {
|
||||
return NextResponse.error();
|
||||
}
|
||||
}
|
||||
@@ -19,35 +19,40 @@ export const demos: { name: string; items: Item[] }[] = [
|
||||
description: "The page to request a users password",
|
||||
},
|
||||
{
|
||||
name: "Set Password",
|
||||
slug: "password/set",
|
||||
description: "The page to set a users password",
|
||||
},
|
||||
{
|
||||
name: "MFA",
|
||||
slug: "mfa",
|
||||
description: "The page to request a users mfa method",
|
||||
},
|
||||
{
|
||||
name: "MFA Set",
|
||||
slug: "mfa/set",
|
||||
description: "The page to set a users mfa method",
|
||||
},
|
||||
{
|
||||
name: "MFA Create",
|
||||
slug: "mfa/create",
|
||||
description: "The page to create a users mfa method",
|
||||
},
|
||||
{
|
||||
name: "Passwordless",
|
||||
slug: "passwordless",
|
||||
description: "The page to login a user with his passwordless device",
|
||||
},
|
||||
{
|
||||
name: "Passwordless Create",
|
||||
slug: "passwordless/create",
|
||||
description: "The page to add a users passwordless device",
|
||||
name: "Accounts",
|
||||
slug: "accounts",
|
||||
description: "List active and inactive sessions",
|
||||
},
|
||||
// {
|
||||
// name: "Set Password",
|
||||
// slug: "password/set",
|
||||
// description: "The page to set a users password",
|
||||
// },
|
||||
// {
|
||||
// name: "MFA",
|
||||
// slug: "mfa",
|
||||
// description: "The page to request a users mfa method",
|
||||
// },
|
||||
// {
|
||||
// name: "MFA Set",
|
||||
// slug: "mfa/set",
|
||||
// description: "The page to set a users mfa method",
|
||||
// },
|
||||
// {
|
||||
// name: "MFA Create",
|
||||
// slug: "mfa/create",
|
||||
// description: "The page to create a users mfa method",
|
||||
// },
|
||||
// {
|
||||
// name: "Passwordless",
|
||||
// slug: "passwordless",
|
||||
// description: "The page to login a user with his passwordless device",
|
||||
// },
|
||||
// {
|
||||
// name: "Passwordless Create",
|
||||
// slug: "passwordless/create",
|
||||
// description: "The page to add a users passwordless device",
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -58,6 +63,11 @@ export const demos: { name: string; items: Item[] }[] = [
|
||||
slug: "register",
|
||||
description: "Create your ZITADEL account",
|
||||
},
|
||||
{
|
||||
name: "Verify email",
|
||||
slug: "verify",
|
||||
description: "Verify your account with an email code",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import {
|
||||
management,
|
||||
ZitadelServer,
|
||||
ZitadelServerOptions,
|
||||
getManagement,
|
||||
orgMetadata,
|
||||
getServer,
|
||||
user,
|
||||
settings,
|
||||
getServers,
|
||||
LabelPolicy,
|
||||
initializeServer,
|
||||
PrivacyPolicy,
|
||||
PasswordComplexityPolicy,
|
||||
session,
|
||||
GetGeneralSettingsResponse,
|
||||
CreateSessionResponse,
|
||||
GetBrandingSettingsResponse,
|
||||
GetPasswordComplexitySettingsResponse,
|
||||
GetLegalAndSupportSettingsResponse,
|
||||
AddHumanUserResponse,
|
||||
BrandingSettings,
|
||||
ListSessionsResponse,
|
||||
LegalAndSupportSettings,
|
||||
PasswordComplexitySettings,
|
||||
GetSessionResponse,
|
||||
VerifyEmailResponse,
|
||||
SetSessionResponse,
|
||||
} from "@zitadel/server";
|
||||
// import { getAuth } from "@zitadel/server/auth";
|
||||
|
||||
export const zitadelConfig: ZitadelServerOptions = {
|
||||
name: "zitadel login",
|
||||
@@ -26,46 +34,83 @@ if (!getServers().length) {
|
||||
server = initializeServer(zitadelConfig);
|
||||
}
|
||||
|
||||
export function getBranding(
|
||||
export function getBrandingSettings(
|
||||
server: ZitadelServer
|
||||
): Promise<LabelPolicy | undefined> {
|
||||
const mgmt = getManagement(server);
|
||||
return mgmt
|
||||
.getLabelPolicy(
|
||||
{},
|
||||
{
|
||||
// metadata: orgMetadata(process.env.ZITADEL_ORG_ID ?? "")
|
||||
}
|
||||
)
|
||||
.then((resp) => resp.policy);
|
||||
): Promise<BrandingSettings | undefined> {
|
||||
const settingsService = settings.getSettings(server);
|
||||
return settingsService
|
||||
.getBrandingSettings({}, {})
|
||||
.then((resp: GetBrandingSettingsResponse) => resp.settings);
|
||||
}
|
||||
|
||||
export function getPrivacyPolicy(
|
||||
export function getGeneralSettings(
|
||||
server: ZitadelServer
|
||||
): Promise<PrivacyPolicy | undefined> {
|
||||
const mgmt = getManagement(server);
|
||||
return mgmt
|
||||
.getPrivacyPolicy(
|
||||
{},
|
||||
{
|
||||
// metadata: orgMetadata(process.env.ZITADEL_ORG_ID ?? "")
|
||||
}
|
||||
)
|
||||
.then((resp) => resp.policy);
|
||||
): Promise<string[] | undefined> {
|
||||
const settingsService = settings.getSettings(server);
|
||||
return settingsService
|
||||
.getGeneralSettings({}, {})
|
||||
.then((resp: GetGeneralSettingsResponse) => resp.supportedLanguages);
|
||||
}
|
||||
|
||||
export function getPasswordComplexityPolicy(
|
||||
export function getLegalAndSupportSettings(
|
||||
server: ZitadelServer
|
||||
): Promise<PasswordComplexityPolicy | undefined> {
|
||||
const mgmt = getManagement(server);
|
||||
return mgmt
|
||||
.getPasswordComplexityPolicy(
|
||||
{},
|
||||
{
|
||||
// metadata: orgMetadata(process.env.ZITADEL_ORG_ID ?? "")
|
||||
}
|
||||
)
|
||||
.then((resp) => resp.policy);
|
||||
): Promise<LegalAndSupportSettings | undefined> {
|
||||
const settingsService = settings.getSettings(server);
|
||||
return settingsService
|
||||
.getLegalAndSupportSettings({}, {})
|
||||
.then((resp: GetLegalAndSupportSettingsResponse) => {
|
||||
return resp.settings;
|
||||
});
|
||||
}
|
||||
|
||||
export function getPasswordComplexitySettings(
|
||||
server: ZitadelServer
|
||||
): Promise<PasswordComplexitySettings | undefined> {
|
||||
const settingsService = settings.getSettings(server);
|
||||
|
||||
return settingsService
|
||||
.getPasswordComplexitySettings({}, {})
|
||||
.then((resp: GetPasswordComplexitySettingsResponse) => resp.settings);
|
||||
}
|
||||
|
||||
export function createSession(
|
||||
server: ZitadelServer,
|
||||
loginName: string
|
||||
): Promise<CreateSessionResponse | undefined> {
|
||||
const sessionService = session.getSession(server);
|
||||
return sessionService.createSession({ checks: { user: { loginName } } }, {});
|
||||
}
|
||||
|
||||
export function setSession(
|
||||
server: ZitadelServer,
|
||||
sessionId: string,
|
||||
sessionToken: string,
|
||||
password: string
|
||||
): Promise<SetSessionResponse | undefined> {
|
||||
const sessionService = session.getSession(server);
|
||||
return sessionService.setSession(
|
||||
{ sessionId, sessionToken, checks: { password: { password } } },
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
export function getSession(
|
||||
server: ZitadelServer,
|
||||
sessionId: string,
|
||||
sessionToken: string
|
||||
): Promise<GetSessionResponse | undefined> {
|
||||
const sessionService = session.getSession(server);
|
||||
return sessionService.getSession({ sessionId, sessionToken }, {});
|
||||
}
|
||||
|
||||
export function listSessions(
|
||||
server: ZitadelServer,
|
||||
ids: string[]
|
||||
): Promise<ListSessionsResponse | undefined> {
|
||||
const sessionService = session.getSession(server);
|
||||
const query = { offset: 0, limit: 100, asc: true };
|
||||
const queries = [{ idsQuery: { ids } }];
|
||||
return sessionService.listSessions({ queries: queries }, {});
|
||||
}
|
||||
|
||||
export type AddHumanUserData = {
|
||||
@@ -74,27 +119,56 @@ export type AddHumanUserData = {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export function addHumanUser(
|
||||
server: ZitadelServer,
|
||||
{ email, firstName, lastName, password }: AddHumanUserData
|
||||
): Promise<string> {
|
||||
const mgmt = getManagement(server);
|
||||
const mgmt = user.getUser(server);
|
||||
return mgmt
|
||||
.addHumanUser(
|
||||
{
|
||||
email: { email, isEmailVerified: false },
|
||||
userName: email,
|
||||
email: { email },
|
||||
username: email,
|
||||
profile: { firstName, lastName },
|
||||
initialPassword: password,
|
||||
password: { password },
|
||||
},
|
||||
{
|
||||
// metadata: orgMetadata(process.env.ZITADEL_ORG_ID ?? "")
|
||||
}
|
||||
{}
|
||||
)
|
||||
.then((resp) => {
|
||||
console.log("added user", resp.userId);
|
||||
.then((resp: AddHumanUserResponse) => {
|
||||
return resp.userId;
|
||||
});
|
||||
}
|
||||
|
||||
export function verifyEmail(
|
||||
server: ZitadelServer,
|
||||
userId: string,
|
||||
verificationCode: string
|
||||
): Promise<VerifyEmailResponse> {
|
||||
const userservice = user.getUser(server);
|
||||
return userservice.verifyEmail(
|
||||
{
|
||||
userId,
|
||||
verificationCode,
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param server
|
||||
* @param userId the id of the user where the email should be set
|
||||
* @returns the newly set email
|
||||
*/
|
||||
export function setEmail(server: ZitadelServer, userId: string): Promise<any> {
|
||||
const userservice = user.getUser(server);
|
||||
return userservice.setEmail(
|
||||
{
|
||||
userId,
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
export { server };
|
||||
|
||||
@@ -3,20 +3,13 @@ const nextConfig = {
|
||||
reactStrictMode: true, // Recommended for the `pages` directory, default in `app`.
|
||||
swcMinify: true,
|
||||
experimental: {
|
||||
// Required:
|
||||
appDir: true,
|
||||
serverActions: true,
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "zitadel.com",
|
||||
port: "",
|
||||
pathname: "/**",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "zitadel.cloud",
|
||||
hostname: process.env.ZITADEL_API_URL.replace("https://", ""),
|
||||
port: "",
|
||||
pathname: "/**",
|
||||
},
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
"@zitadel/server": "workspace:*",
|
||||
"clsx": "1.2.1",
|
||||
"date-fns": "2.29.3",
|
||||
"next": "13.3.2-canary.2",
|
||||
"moment": "^2.29.4",
|
||||
"next": "13.4.2",
|
||||
"next-themes": "^0.2.1",
|
||||
"nice-grpc": "2.0.1",
|
||||
"react": "18.2.0",
|
||||
@@ -45,7 +46,6 @@
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@vercel/git-hooks": "1.0.0",
|
||||
"@zitadel/tsconfig": "workspace:*",
|
||||
"zitadel-tailwind-config": "workspace:*",
|
||||
"autoprefixer": "10.4.13",
|
||||
"del-cli": "5.0.0",
|
||||
"eslint-config-zitadel": "workspace:*",
|
||||
@@ -56,6 +56,7 @@
|
||||
"prettier-plugin-tailwindcss": "0.1.13",
|
||||
"tailwindcss": "3.2.4",
|
||||
"ts-proto": "^1.139.0",
|
||||
"typescript": "4.8.4"
|
||||
"typescript": "4.8.4",
|
||||
"zitadel-tailwind-config": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,28 @@ module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors,
|
||||
animation: {
|
||||
shake: "shake .8s cubic-bezier(.36,.07,.19,.97) both;",
|
||||
},
|
||||
keyframes: {
|
||||
shake: {
|
||||
"10%, 90%": {
|
||||
transform: "translate3d(-1px, 0, 0)",
|
||||
},
|
||||
|
||||
"20%, 80%": {
|
||||
transform: "translate3d(2px, 0, 0)",
|
||||
},
|
||||
|
||||
"30%, 50%, 70%": {
|
||||
transform: "translate3d(-4px, 0, 0)",
|
||||
},
|
||||
|
||||
"40%, 60%": {
|
||||
transform: "translate3d(4px, 0, 0)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("@tailwindcss/forms")],
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
import React from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export function AddressBar() {
|
||||
type Props = {
|
||||
domain: string;
|
||||
};
|
||||
|
||||
export function AddressBar({ domain }: Props) {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
@@ -24,7 +28,7 @@ export function AddressBar() {
|
||||
</div>
|
||||
<div className="flex space-x-1 text-sm font-medium">
|
||||
<div>
|
||||
<span className="px-2 text-gray-500">acme.com</span>
|
||||
<span className="px-2 text-gray-500">{domain}</span>
|
||||
</div>
|
||||
{pathname ? (
|
||||
<>
|
||||
|
||||
14
apps/login/ui/Alert.tsx
Normal file
14
apps/login/ui/Alert.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function Alert({ children }: Props) {
|
||||
return (
|
||||
<div className="flex flex-row items-center justify-center border border-yellow-600/40 dark:border-yellow-500/20 bg-yellow-200/30 text-yellow-600 dark:bg-yellow-700/20 dark:text-yellow-200 rounded-md py-2 scroll-px-40">
|
||||
<ExclamationTriangleIcon className="flex-shrink-0 h-5 w-5 mr-2 ml-2" />
|
||||
<span className="text-center text-sm">{children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,35 +1,27 @@
|
||||
import { Color, ColorShade, getColorHash } from "#/utils/colors";
|
||||
import { useTheme } from "next-themes";
|
||||
import { FC } from "react";
|
||||
"use client";
|
||||
|
||||
export enum AvatarSize {
|
||||
SMALL = "small",
|
||||
BASE = "base",
|
||||
LARGE = "large",
|
||||
}
|
||||
import { ColorShade, getColorHash } from "#/utils/colors";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
interface AvatarProps {
|
||||
name: string | null | undefined;
|
||||
loginName: string;
|
||||
imageUrl?: string;
|
||||
size?: AvatarSize;
|
||||
size?: "small" | "base" | "large";
|
||||
shadow?: boolean;
|
||||
}
|
||||
|
||||
export const Avatar: FC<AvatarProps> = ({
|
||||
size = AvatarSize.BASE,
|
||||
name,
|
||||
loginName,
|
||||
imageUrl,
|
||||
shadow,
|
||||
}) => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
function getInitials(name: string, loginName: string) {
|
||||
let credentials = "";
|
||||
|
||||
if (name) {
|
||||
const split = name.split(" ");
|
||||
const initials = split[0].charAt(0) + (split[1] ? split[1].charAt(0) : "");
|
||||
credentials = initials;
|
||||
if (split) {
|
||||
const initials =
|
||||
split[0].charAt(0) + (split[1] ? split[1].charAt(0) : "");
|
||||
credentials = initials;
|
||||
} else {
|
||||
credentials = name.charAt(0);
|
||||
}
|
||||
} else {
|
||||
const username = loginName.split("@")[0];
|
||||
let separator = "_";
|
||||
@@ -44,6 +36,19 @@ export const Avatar: FC<AvatarProps> = ({
|
||||
credentials = initials;
|
||||
}
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
export function Avatar({
|
||||
size = "base",
|
||||
name,
|
||||
loginName,
|
||||
imageUrl,
|
||||
shadow,
|
||||
}: AvatarProps) {
|
||||
const { resolvedTheme } = useTheme();
|
||||
const credentials = getInitials(name ?? loginName, loginName);
|
||||
|
||||
const color: ColorShade = getColorHash(loginName);
|
||||
|
||||
const avatarStyleDark = {
|
||||
@@ -61,12 +66,12 @@ export const Avatar: FC<AvatarProps> = ({
|
||||
className={`w-full h-full flex-shrink-0 flex justify-center items-center cursor-default pointer-events-none group-focus:outline-none group-focus:ring-2 transition-colors duration-200 dark:group-focus:ring-offset-blue bg-primary-light-500 text-primary-light-contrast-500 hover:bg-primary-light-400 hover:dark:bg-primary-dark-500 group-focus:ring-primary-light-200 dark:group-focus:ring-primary-dark-400 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 dark:text-blue rounded-full ${
|
||||
shadow ? "shadow" : ""
|
||||
} ${
|
||||
size === AvatarSize.LARGE
|
||||
size === "large"
|
||||
? "h-20 w-20 font-normal"
|
||||
: size === AvatarSize.BASE
|
||||
: size === "base"
|
||||
? "w-[38px] h-[38px] font-bold"
|
||||
: size === AvatarSize.SMALL
|
||||
? "w-[32px] h-[32px] font-bold"
|
||||
: size === "small"
|
||||
? "w-[32px] h-[32px] font-bold text-[13px]"
|
||||
: ""
|
||||
}`}
|
||||
style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark}
|
||||
@@ -78,13 +83,11 @@ export const Avatar: FC<AvatarProps> = ({
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className={`uppercase ${
|
||||
size === AvatarSize.LARGE ? "text-xl" : "text-13px"
|
||||
}`}
|
||||
className={`uppercase ${size === "large" ? "text-xl" : "text-13px"}`}
|
||||
>
|
||||
{credentials}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,15 +8,16 @@ const Label = ({
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
animateRerendering?: boolean;
|
||||
color?: "default" | "pink" | "blue" | "violet" | "cyan" | "orange";
|
||||
color?: "default" | "pink" | "blue" | "violet" | "cyan" | "orange" | "red";
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx("rounded-full px-1.5 shadow-[0_0_1px_3px_black]", {
|
||||
className={clsx("rounded-full px-1.5", {
|
||||
"bg-gray-800 text-gray-500": color === "default",
|
||||
"bg-pink-500 text-pink-100": color === "pink",
|
||||
"bg-blue-500 text-blue-100": color === "blue",
|
||||
"bg-cyan-500 text-cyan-100": color === "cyan",
|
||||
"bg-red-500 text-red-100": color === "red",
|
||||
"bg-violet-500 text-violet-100": color === "violet",
|
||||
"bg-orange-500 text-orange-100": color === "orange",
|
||||
"animate-[highlight_1s_ease-in-out_1]": animateRerendering,
|
||||
@@ -36,7 +37,7 @@ export const Boundary = ({
|
||||
children: React.ReactNode;
|
||||
labels?: string[];
|
||||
size?: "small" | "default";
|
||||
color?: "default" | "pink" | "blue" | "violet" | "cyan" | "orange";
|
||||
color?: "default" | "pink" | "blue" | "violet" | "cyan" | "orange" | "red";
|
||||
animateRerendering?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
@@ -48,6 +49,7 @@ export const Boundary = ({
|
||||
"border-pink-500": color === "pink",
|
||||
"border-blue-500": color === "blue",
|
||||
"border-cyan-500": color === "cyan",
|
||||
"border-red-500": color === "red",
|
||||
"border-violet-500": color === "violet",
|
||||
"border-orange-500": color === "orange",
|
||||
"animate-[rerender_1s_ease-in-out_1] text-pink-500": animateRerendering,
|
||||
@@ -55,7 +57,7 @@ export const Boundary = ({
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute -top-2.5 flex space-x-1 text-[9px] uppercase leading-4 tracking-widest",
|
||||
"absolute -top-2 flex space-x-1 text-[9px] uppercase leading-4 tracking-widest",
|
||||
{
|
||||
"left-3 lg:left-5": size === "small",
|
||||
"left-4 lg:left-9": size === "default",
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import Theme from "./Theme";
|
||||
|
||||
export default function Byline() {
|
||||
return (
|
||||
<div className="flex items-center justify-between w-full p-3.5 lg:px-5 lg:py-3">
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<div className="text-sm text-gray-600">By</div>
|
||||
<div className="text-sm font-semibold">ZITADEL</div>
|
||||
</div>
|
||||
<Theme />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,13 +7,14 @@ import { useSelectedLayoutSegment, usePathname } from "next/navigation";
|
||||
import clsx from "clsx";
|
||||
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/solid";
|
||||
import { useState } from "react";
|
||||
import Theme from "./Theme";
|
||||
|
||||
export function GlobalNav() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const close = () => setIsOpen(false);
|
||||
|
||||
return (
|
||||
<div className="fixed top-0 z-10 flex w-full flex-col border-b border-divider-light dark:border-divider-dark bg-background-light-700 dark:bg-background-dark-700 lg:bottom-0 lg:z-auto lg:w-72 lg:border-r">
|
||||
<div className="fixed top-0 z-10 flex w-full flex-col border-b border-divider-light dark:border-divider-dark bg-white/80 dark:bg-black/80 lg:bottom-0 lg:z-auto lg:w-72 lg:border-r">
|
||||
<div className="flex h-14 items-center py-4 px-4 lg:h-auto">
|
||||
<Link
|
||||
href="/"
|
||||
@@ -29,27 +30,34 @@ export function GlobalNav() {
|
||||
</h2>
|
||||
</Link>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="group absolute right-0 top-0 flex h-14 items-center space-x-2 px-4 lg:hidden"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<div className="font-medium text-text-light-secondary-500 group-hover:text-text-light-500 dark:text-text-dark-secondary-500 dark:group-hover:text-text-dark-500">
|
||||
Menu
|
||||
</div>
|
||||
{isOpen ? (
|
||||
<XMarkIcon className="block w-6 " />
|
||||
) : (
|
||||
<Bars3Icon className="block w-6 " />
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="absolute right-0 top-0 flex flex-row items-center lg:hidden">
|
||||
<Theme />
|
||||
<button
|
||||
type="button"
|
||||
className="group flex h-14 items-center space-x-2 px-4"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<div className="font-medium text-text-light-secondary-500 group-hover:text-text-light-500 dark:text-text-dark-secondary-500 dark:group-hover:text-text-dark-500">
|
||||
Menu
|
||||
</div>
|
||||
{isOpen ? (
|
||||
<XMarkIcon className="block w-6 " />
|
||||
) : (
|
||||
<Bars3Icon className="block w-6 " />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={clsx("overflow-y-auto lg:static lg:block", {
|
||||
"fixed inset-x-0 bottom-0 top-14 mt-px bg-background-light-500 dark:bg-background-dark-500":
|
||||
isOpen,
|
||||
hidden: !isOpen,
|
||||
})}
|
||||
className={clsx(
|
||||
"overflow-y-auto lg:static lg:flex lg:flex-col justify-between h-full",
|
||||
{
|
||||
"fixed inset-x-0 bottom-0 top-14 mt-px bg-white/80 dark:bg-black/80 backdrop-blur-lg":
|
||||
isOpen,
|
||||
hidden: !isOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<nav
|
||||
className={`space-y-6 px-4 py-5 ${
|
||||
@@ -72,6 +80,10 @@ export function GlobalNav() {
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="flex flex-row p-4">
|
||||
<Theme />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
37
apps/login/ui/Logo.tsx
Normal file
37
apps/login/ui/Logo.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import Image from "next/image";
|
||||
|
||||
type Props = {
|
||||
darkSrc?: string;
|
||||
lightSrc?: string;
|
||||
height?: number;
|
||||
width?: number;
|
||||
};
|
||||
|
||||
export function Logo({ lightSrc, darkSrc, height = 40, width = 147.5 }: Props) {
|
||||
return (
|
||||
<>
|
||||
{darkSrc && (
|
||||
<div className="hidden dark:flex">
|
||||
<Image
|
||||
height={height}
|
||||
width={width}
|
||||
src={darkSrc}
|
||||
alt="logo"
|
||||
priority={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{lightSrc && (
|
||||
<div className="flex dark:hidden">
|
||||
<Image
|
||||
height={height}
|
||||
width={width}
|
||||
priority={true}
|
||||
src={lightSrc}
|
||||
alt="logo"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
symbolValidator,
|
||||
upperCaseValidator,
|
||||
} from "#/utils/validators";
|
||||
import { PasswordComplexityPolicy } from "@zitadel/server";
|
||||
import { PasswordComplexitySettings } from "@zitadel/server";
|
||||
|
||||
type Props = {
|
||||
passwordComplexityPolicy: PasswordComplexityPolicy;
|
||||
passwordComplexitySettings: PasswordComplexitySettings;
|
||||
password: string;
|
||||
equals: boolean;
|
||||
};
|
||||
@@ -48,11 +48,11 @@ const desc =
|
||||
"text-14px leading-4 text-input-light-label dark:text-input-dark-label";
|
||||
|
||||
export default function PasswordComplexity({
|
||||
passwordComplexityPolicy,
|
||||
passwordComplexitySettings,
|
||||
password,
|
||||
equals,
|
||||
}: Props) {
|
||||
const hasMinLength = password?.length >= passwordComplexityPolicy.minLength;
|
||||
const hasMinLength = password?.length >= passwordComplexitySettings.minLength;
|
||||
const hasSymbol = symbolValidator(password);
|
||||
const hasNumber = numberValidator(password);
|
||||
const hasUppercase = upperCaseValidator(password);
|
||||
@@ -63,7 +63,7 @@ export default function PasswordComplexity({
|
||||
<div className="flex flex-row items-center">
|
||||
{hasMinLength ? check : cross}
|
||||
<span className={desc}>
|
||||
Password length {passwordComplexityPolicy.minLength}
|
||||
Password length {passwordComplexitySettings.minLength}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
|
||||
101
apps/login/ui/PasswordForm.tsx
Normal file
101
apps/login/ui/PasswordForm.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button, ButtonVariants } from "./Button";
|
||||
import { TextInput } from "./Input";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Spinner } from "./Spinner";
|
||||
import Alert from "./Alert";
|
||||
|
||||
type Inputs = {
|
||||
password: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
loginName?: string;
|
||||
};
|
||||
|
||||
export default function PasswordForm({ loginName }: Props) {
|
||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
});
|
||||
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
async function submitPassword(values: Inputs) {
|
||||
setError("");
|
||||
setLoading(true);
|
||||
const res = await fetch("/session", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
password: values.password,
|
||||
}),
|
||||
});
|
||||
|
||||
const response = await res.json();
|
||||
|
||||
setLoading(false);
|
||||
if (!res.ok) {
|
||||
setError(response.details);
|
||||
return Promise.reject(response.details);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
function submitPasswordAndContinue(value: Inputs): Promise<boolean | void> {
|
||||
return submitPassword(value).then((resp: any) => {
|
||||
return router.push(`/accounts`);
|
||||
});
|
||||
}
|
||||
|
||||
const { errors } = formState;
|
||||
|
||||
return (
|
||||
<form className="w-full">
|
||||
<div className={`${error && "transform-gpu animate-shake"}`}>
|
||||
<TextInput
|
||||
type="password"
|
||||
autoComplete="password"
|
||||
{...register("password", { required: "This field is required" })}
|
||||
label="Password"
|
||||
// error={errors.username?.message as string}
|
||||
/>
|
||||
|
||||
{loginName && (
|
||||
<input type="hidden" name="loginName" value={loginName} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="py-4">
|
||||
<Alert>{error}</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center">
|
||||
{/* <Button type="button" variant={ButtonVariants.Secondary}>
|
||||
back
|
||||
</Button> */}
|
||||
<span className="flex-grow"></span>
|
||||
<Button
|
||||
type="submit"
|
||||
className="self-end"
|
||||
variant={ButtonVariants.Primary}
|
||||
disabled={loading || !formState.isValid}
|
||||
onClick={handleSubmit(submitPasswordAndContinue)}
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
continue
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Checkbox } from "./Checkbox";
|
||||
import { PrivacyPolicy } from "@zitadel/server";
|
||||
import { LegalAndSupportSettings } from "@zitadel/server";
|
||||
|
||||
type Props = {
|
||||
privacyPolicy: PrivacyPolicy;
|
||||
legal: LegalAndSupportSettings;
|
||||
onChange: (allAccepted: boolean) => void;
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ type AcceptanceState = {
|
||||
privacyPolicyAccepted: boolean;
|
||||
};
|
||||
|
||||
export function PrivacyPolicyCheckboxes({ privacyPolicy, onChange }: Props) {
|
||||
export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
|
||||
const [acceptanceState, setAcceptanceState] = useState<AcceptanceState>({
|
||||
tosAccepted: false,
|
||||
privacyPolicyAccepted: false,
|
||||
@@ -24,9 +24,9 @@ export function PrivacyPolicyCheckboxes({ privacyPolicy, onChange }: Props) {
|
||||
<>
|
||||
<p className="flex flex-row items-center text-text-light-secondary-500 dark:text-text-dark-secondary-500 mt-4 text-sm">
|
||||
To register you must agree to the terms and conditions
|
||||
{privacyPolicy?.helpLink && (
|
||||
{legal?.helpLink && (
|
||||
<span>
|
||||
<Link href={privacyPolicy.helpLink} target="_blank">
|
||||
<Link href={legal.helpLink} target="_blank">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
@@ -45,7 +45,7 @@ export function PrivacyPolicyCheckboxes({ privacyPolicy, onChange }: Props) {
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
{privacyPolicy?.tosLink && (
|
||||
{legal?.tosLink && (
|
||||
<div className="mt-4 flex items-center">
|
||||
<Checkbox
|
||||
className="mr-4"
|
||||
@@ -62,18 +62,14 @@ export function PrivacyPolicyCheckboxes({ privacyPolicy, onChange }: Props) {
|
||||
<div className="mr-4 w-[28rem]">
|
||||
<p className="text-sm text-text-light-500 dark:text-text-dark-500">
|
||||
Agree
|
||||
<Link
|
||||
href={privacyPolicy.tosLink}
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
<Link href={legal.tosLink} className="underline" target="_blank">
|
||||
Terms of Service
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{privacyPolicy?.privacyLink && (
|
||||
{legal?.privacyPolicyLink && (
|
||||
<div className="mt-4 flex items-center">
|
||||
<Checkbox
|
||||
className="mr-4"
|
||||
@@ -91,7 +87,7 @@ export function PrivacyPolicyCheckboxes({ privacyPolicy, onChange }: Props) {
|
||||
<p className="text-sm text-text-light-500 dark:text-text-dark-500">
|
||||
Agree
|
||||
<Link
|
||||
href={privacyPolicy.privacyLink}
|
||||
href={legal.privacyPolicyLink}
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { PasswordComplexityPolicy, PrivacyPolicy } from "@zitadel/server";
|
||||
import {
|
||||
LegalAndSupportSettings,
|
||||
PasswordComplexitySettings,
|
||||
} from "@zitadel/server";
|
||||
import PasswordComplexity from "./PasswordComplexity";
|
||||
import { useState } from "react";
|
||||
import { Button, ButtonVariants } from "./Button";
|
||||
@@ -27,13 +30,13 @@ type Inputs =
|
||||
| FieldValues;
|
||||
|
||||
type Props = {
|
||||
privacyPolicy: PrivacyPolicy;
|
||||
passwordComplexityPolicy: PasswordComplexityPolicy;
|
||||
legal: LegalAndSupportSettings;
|
||||
passwordComplexitySettings: PasswordComplexitySettings;
|
||||
};
|
||||
|
||||
export default function RegisterForm({
|
||||
privacyPolicy,
|
||||
passwordComplexityPolicy,
|
||||
legal,
|
||||
passwordComplexitySettings,
|
||||
}: Props) {
|
||||
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
@@ -69,7 +72,7 @@ export default function RegisterForm({
|
||||
|
||||
function submitAndLink(value: Inputs): Promise<boolean | void> {
|
||||
return submitRegister(value).then((resp: any) => {
|
||||
return router.push(`/register/success?userid=${resp.userId}`);
|
||||
return router.push(`/verify?userID=${resp.userId}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,19 +84,19 @@ export default function RegisterForm({
|
||||
const [tosAndPolicyAccepted, setTosAndPolicyAccepted] = useState(false);
|
||||
|
||||
const hasMinLength =
|
||||
passwordComplexityPolicy &&
|
||||
watchPassword?.length >= passwordComplexityPolicy.minLength;
|
||||
passwordComplexitySettings &&
|
||||
watchPassword?.length >= passwordComplexitySettings.minLength;
|
||||
const hasSymbol = symbolValidator(watchPassword);
|
||||
const hasNumber = numberValidator(watchPassword);
|
||||
const hasUppercase = upperCaseValidator(watchPassword);
|
||||
const hasLowercase = lowerCaseValidator(watchPassword);
|
||||
|
||||
const policyIsValid =
|
||||
passwordComplexityPolicy &&
|
||||
(passwordComplexityPolicy.hasLowercase ? hasLowercase : true) &&
|
||||
(passwordComplexityPolicy.hasNumber ? hasNumber : true) &&
|
||||
(passwordComplexityPolicy.hasUppercase ? hasUppercase : true) &&
|
||||
(passwordComplexityPolicy.hasSymbol ? hasSymbol : true) &&
|
||||
passwordComplexitySettings &&
|
||||
(passwordComplexitySettings.requiresLowercase ? hasLowercase : true) &&
|
||||
(passwordComplexitySettings.requiresNumber ? hasNumber : true) &&
|
||||
(passwordComplexitySettings.requiresUppercase ? hasUppercase : true) &&
|
||||
(passwordComplexitySettings.requiresSymbol ? hasSymbol : true) &&
|
||||
hasMinLength;
|
||||
|
||||
return (
|
||||
@@ -155,17 +158,17 @@ export default function RegisterForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{passwordComplexityPolicy && (
|
||||
{passwordComplexitySettings && (
|
||||
<PasswordComplexity
|
||||
passwordComplexityPolicy={passwordComplexityPolicy}
|
||||
passwordComplexitySettings={passwordComplexitySettings}
|
||||
password={watchPassword}
|
||||
equals={!!watchPassword && watchPassword === watchConfirmPassword}
|
||||
/>
|
||||
)}
|
||||
|
||||
{privacyPolicy && (
|
||||
{legal && (
|
||||
<PrivacyPolicyCheckboxes
|
||||
privacyPolicy={privacyPolicy}
|
||||
legal={legal}
|
||||
onChange={setTosAndPolicyAccepted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { Switch } from "@headlessui/react";
|
||||
import { MoonIcon, SunIcon } from "@heroicons/react/24/outline";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MoonIcon, SunIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
export default function Theme() {
|
||||
function Theme() {
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
const [mounted, setMounted] = useState<boolean>(false);
|
||||
|
||||
@@ -21,28 +20,27 @@ export default function Theme() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch
|
||||
checked={isDark}
|
||||
onChange={(checked) => setTheme(checked ? "dark" : "light")}
|
||||
className={`${
|
||||
isDark
|
||||
? "!bg-gray-800 dark:bg-background-dark-400"
|
||||
: "!bg-gray-200 dark:bg-background-dark-400"
|
||||
}
|
||||
relative inline-flex h-4 w-9 items-center rounded-full`}
|
||||
<div
|
||||
className={`relative grid grid-cols-2 rounded-full border border-divider-light dark:border-divider-dark p-1`}
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={`${
|
||||
isDark ? "translate-x-5" : "translate-x-0"
|
||||
} flex flex-row items-center justify-center h-4 w-4 transform rounded-full bg-white transition-all shadow dark:bg-background-dark-500 ring-1 ring-[#00000020] dark:ring-[#ffffff20] ring-offset-1 ring-offset-[#ffffff50] dark:ring-offset-[#00000005]`}
|
||||
<button
|
||||
className={`h-8 w-8 rounded-full flex flex-row items-center justify-center hover:opacity-100 transition-all ${
|
||||
isDark ? "bg-black/10 dark:bg-white/10" : "opacity-60"
|
||||
}`}
|
||||
onClick={() => setTheme("dark")}
|
||||
>
|
||||
{isDark ? (
|
||||
<MoonIcon className="dark:text-amber-500 h-4 w-4" />
|
||||
) : (
|
||||
<SunIcon className="text-amber-500 h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
</Switch>
|
||||
<MoonIcon className="h-4 w-4 flex-shrink-0 text-xl rounded-full" />
|
||||
</button>
|
||||
<button
|
||||
className={`h-8 w-8 rounded-full flex flex-row items-center justify-center hover:opacity-100 transition-all ${
|
||||
!isDark ? "bg-black/10 dark:bg-white/10" : "opacity-60"
|
||||
}`}
|
||||
onClick={() => setTheme("light")}
|
||||
>
|
||||
<SunIcon className="h-6 w-6 flex-shrink-0 text-xl rounded-full" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Theme;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { setTheme, LabelPolicyColors } from "#/utils/colors";
|
||||
import { BrandingSettings } from "@zitadel/server";
|
||||
import { setTheme } from "#/utils/colors";
|
||||
import { useEffect } from "react";
|
||||
|
||||
type Props = {
|
||||
branding: LabelPolicyColors | undefined;
|
||||
branding: Partial<BrandingSettings> | undefined;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,37 @@
|
||||
import { Avatar, AvatarSize } from "#/ui/Avatar";
|
||||
import { Avatar } from "#/ui/Avatar";
|
||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||
import Link from "next/link";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
loginName: string;
|
||||
displayName?: string;
|
||||
showDropdown: boolean;
|
||||
};
|
||||
|
||||
export default function UserAvatar({ name }: Props) {
|
||||
export default function UserAvatar({
|
||||
loginName,
|
||||
displayName,
|
||||
showDropdown,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className="flex h-full w-full flex-row items-center rounded-full border p-[1px] dark:border-white/20">
|
||||
<div>
|
||||
<Avatar size={AvatarSize.SMALL} name={name} loginName={name} />
|
||||
<Avatar
|
||||
size="small"
|
||||
name={displayName ?? loginName}
|
||||
loginName={loginName}
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-4 text-14px">{name}</span>
|
||||
<span className="ml-4 text-14px">{loginName}</span>
|
||||
<span className="flex-grow"></span>
|
||||
{showDropdown && (
|
||||
<Link
|
||||
href="/accounts"
|
||||
className="flex items-center justify-center p-1 hover:bg-black/10 dark:hover:bg-white/10 rounded-full mr-1 transition-all"
|
||||
>
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
83
apps/login/ui/UsernameForm.tsx
Normal file
83
apps/login/ui/UsernameForm.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button, ButtonVariants } from "./Button";
|
||||
import { TextInput } from "./Input";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Spinner } from "./Spinner";
|
||||
|
||||
type Inputs = {
|
||||
loginName: string;
|
||||
};
|
||||
|
||||
export default function UsernameForm() {
|
||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
async function submitUsername(values: Inputs) {
|
||||
setLoading(true);
|
||||
const res = await fetch("/session", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
loginName: values.loginName,
|
||||
}),
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to set user");
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
function submitUsernameAndContinue(value: Inputs): Promise<boolean | void> {
|
||||
return submitUsername(value).then(({ factors }) => {
|
||||
return router.push(
|
||||
`/password?` +
|
||||
new URLSearchParams({ loginName: `${factors.user.loginName}` })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const { errors } = formState;
|
||||
|
||||
return (
|
||||
<form className="w-full">
|
||||
<div className="">
|
||||
<TextInput
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
{...register("loginName", { required: "This field is required" })}
|
||||
label="Loginname"
|
||||
// error={errors.username?.message as string}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center">
|
||||
{/* <Button type="button" variant={ButtonVariants.Secondary}>
|
||||
back
|
||||
</Button> */}
|
||||
<span className="flex-grow"></span>
|
||||
<Button
|
||||
type="submit"
|
||||
className="self-end"
|
||||
variant={ButtonVariants.Primary}
|
||||
disabled={loading || !formState.isValid}
|
||||
onClick={handleSubmit(submitUsernameAndContinue)}
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
continue
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
133
apps/login/ui/VerifyEmailForm.tsx
Normal file
133
apps/login/ui/VerifyEmailForm.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button, ButtonVariants } from "./Button";
|
||||
import { TextInput } from "./Input";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Spinner } from "./Spinner";
|
||||
import Alert from "#/ui/Alert";
|
||||
|
||||
type Inputs = {
|
||||
code: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
userId: string;
|
||||
code: string;
|
||||
submit: boolean;
|
||||
};
|
||||
|
||||
export default function VerifyEmailForm({ userId, code, submit }: Props) {
|
||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
code: code ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (submit && code && userId) {
|
||||
submitCode({ code });
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
async function submitCode(values: Inputs) {
|
||||
setLoading(true);
|
||||
const res = await fetch("/verifyemail", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
code: values.code,
|
||||
userId,
|
||||
}),
|
||||
});
|
||||
|
||||
const response = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
setLoading(false);
|
||||
setError(response.details);
|
||||
return Promise.reject(response);
|
||||
} else {
|
||||
setLoading(false);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
async function resendCode() {
|
||||
setLoading(true);
|
||||
const res = await fetch("/resendverifyemail", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const response = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
setLoading(false);
|
||||
setError(response.details);
|
||||
return Promise.reject(response);
|
||||
} else {
|
||||
setLoading(false);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
function submitCodeAndContinue(value: Inputs): Promise<boolean | void> {
|
||||
return submitCode(value).then((resp: any) => {
|
||||
return router.push(`/username`);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="w-full">
|
||||
<div className="">
|
||||
<TextInput
|
||||
type="text"
|
||||
autoComplete="one-time-code"
|
||||
{...register("code", { required: "This field is required" })}
|
||||
label="Code"
|
||||
// error={errors.username?.message as string}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="py-4">
|
||||
<Alert>{error}</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => resendCode()}
|
||||
variant={ButtonVariants.Secondary}
|
||||
>
|
||||
resend code
|
||||
</Button>
|
||||
<span className="flex-grow"></span>
|
||||
<Button
|
||||
type="submit"
|
||||
className="self-end"
|
||||
variant={ButtonVariants.Primary}
|
||||
disabled={loading || !formState.isValid}
|
||||
onClick={handleSubmit(submitCodeAndContinue)}
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
continue
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import tinycolor from "tinycolor2";
|
||||
|
||||
import { BrandingSettings } from "@zitadel/server";
|
||||
|
||||
export interface Color {
|
||||
name: string;
|
||||
hex: string;
|
||||
@@ -52,32 +54,36 @@ export type LabelPolicyColors = {
|
||||
primaryColorDark: string;
|
||||
};
|
||||
|
||||
export function setTheme(document: any, policy?: LabelPolicyColors) {
|
||||
const lP = {
|
||||
backgroundColor: BACKGROUND,
|
||||
backgroundColorDark: DARK_BACKGROUND,
|
||||
primaryColor: PRIMARY,
|
||||
primaryColorDark: DARK_PRIMARY,
|
||||
warnColor: WARN,
|
||||
warnColorDark: DARK_WARN,
|
||||
fontColor: TEXT,
|
||||
fontColorDark: DARK_TEXT,
|
||||
linkColor: TEXT,
|
||||
linkColorDark: DARK_TEXT,
|
||||
type BrandingColors = {
|
||||
lightTheme: {
|
||||
backgroundColor: string;
|
||||
fontColor: string;
|
||||
primaryColor: string;
|
||||
warnColor: string;
|
||||
};
|
||||
darkTheme: {
|
||||
backgroundColor: string;
|
||||
fontColor: string;
|
||||
primaryColor: string;
|
||||
warnColor: string;
|
||||
};
|
||||
};
|
||||
|
||||
if (policy) {
|
||||
lP.backgroundColor = policy.backgroundColor;
|
||||
lP.backgroundColorDark = policy.backgroundColorDark;
|
||||
lP.primaryColor = policy.primaryColor;
|
||||
lP.primaryColorDark = policy.primaryColorDark;
|
||||
lP.warnColor = policy.warnColor;
|
||||
lP.warnColorDark = policy.warnColorDark;
|
||||
lP.fontColor = policy.fontColor;
|
||||
lP.fontColorDark = policy.fontColorDark;
|
||||
lP.linkColor = policy.fontColor;
|
||||
lP.linkColorDark = policy.fontColorDark;
|
||||
}
|
||||
export function setTheme(document: any, policy?: Partial<BrandingSettings>) {
|
||||
const lP: BrandingColors = {
|
||||
lightTheme: {
|
||||
backgroundColor: policy?.lightTheme?.backgroundColor ?? BACKGROUND,
|
||||
fontColor: policy?.lightTheme?.fontColor ?? TEXT,
|
||||
primaryColor: policy?.lightTheme?.primaryColor ?? PRIMARY,
|
||||
warnColor: policy?.lightTheme?.warnColor ?? WARN,
|
||||
},
|
||||
darkTheme: {
|
||||
backgroundColor: policy?.darkTheme?.backgroundColor ?? DARK_BACKGROUND,
|
||||
fontColor: policy?.darkTheme?.fontColor ?? DARK_TEXT,
|
||||
primaryColor: policy?.darkTheme?.primaryColor ?? DARK_PRIMARY,
|
||||
warnColor: policy?.darkTheme?.warnColor ?? DARK_WARN,
|
||||
},
|
||||
};
|
||||
|
||||
const dark = computeMap(lP, true);
|
||||
const light = computeMap(lP, false);
|
||||
@@ -177,25 +183,24 @@ function getContrast(color: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function computeMap(
|
||||
labelpolicy: LabelPolicyColors,
|
||||
dark: boolean
|
||||
): ColorMap {
|
||||
export function computeMap(branding: BrandingColors, dark: boolean): ColorMap {
|
||||
return {
|
||||
background: computeColors(
|
||||
dark ? labelpolicy.backgroundColorDark : labelpolicy.backgroundColor
|
||||
dark
|
||||
? branding.darkTheme.backgroundColor
|
||||
: branding.lightTheme.backgroundColor
|
||||
),
|
||||
primary: computeColors(
|
||||
dark ? labelpolicy.primaryColorDark : labelpolicy.primaryColor
|
||||
dark ? branding.darkTheme.primaryColor : branding.lightTheme.primaryColor
|
||||
),
|
||||
warn: computeColors(
|
||||
dark ? labelpolicy.warnColorDark : labelpolicy.warnColor
|
||||
dark ? branding.darkTheme.warnColor : branding.lightTheme.warnColor
|
||||
),
|
||||
text: computeColors(
|
||||
dark ? labelpolicy.fontColorDark : labelpolicy.fontColor
|
||||
dark ? branding.darkTheme.fontColor : branding.lightTheme.fontColor
|
||||
),
|
||||
link: computeColors(
|
||||
dark ? labelpolicy.fontColorDark : labelpolicy.fontColor
|
||||
dark ? branding.darkTheme.fontColor : branding.lightTheme.fontColor
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
145
apps/login/utils/cookies.ts
Normal file
145
apps/login/utils/cookies.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
"use server";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export type SessionCookie = {
|
||||
id: string;
|
||||
token: string;
|
||||
loginName: string;
|
||||
changeDate: string;
|
||||
};
|
||||
|
||||
function setSessionHttpOnlyCookie(sessions: SessionCookie[]) {
|
||||
const cookiesList = cookies();
|
||||
// @ts-ignore
|
||||
return cookiesList.set({
|
||||
name: "sessions",
|
||||
value: JSON.stringify(sessions),
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
});
|
||||
}
|
||||
export async function addSessionToCookie(session: SessionCookie): Promise<any> {
|
||||
const cookiesList = cookies();
|
||||
const stringifiedCookie = cookiesList.get("sessions");
|
||||
|
||||
let currentSessions: SessionCookie[] = stringifiedCookie?.value
|
||||
? JSON.parse(stringifiedCookie?.value)
|
||||
: [];
|
||||
|
||||
const index = currentSessions.findIndex(
|
||||
(s) => s.loginName === session.loginName
|
||||
);
|
||||
|
||||
if (index > -1) {
|
||||
currentSessions[index] = session;
|
||||
} else {
|
||||
currentSessions = [...currentSessions, session];
|
||||
}
|
||||
|
||||
setSessionHttpOnlyCookie(currentSessions);
|
||||
}
|
||||
|
||||
export async function updateSessionCookie(
|
||||
id: string,
|
||||
session: SessionCookie
|
||||
): Promise<any> {
|
||||
const cookiesList = cookies();
|
||||
const stringifiedCookie = cookiesList.get("sessions");
|
||||
|
||||
const sessions: SessionCookie[] = stringifiedCookie?.value
|
||||
? JSON.parse(stringifiedCookie?.value)
|
||||
: [session];
|
||||
|
||||
const foundIndex = sessions.findIndex((session) => session.id === id);
|
||||
sessions[foundIndex] = session;
|
||||
|
||||
return setSessionHttpOnlyCookie(sessions);
|
||||
}
|
||||
|
||||
export async function removeSessionFromCookie(
|
||||
session: SessionCookie
|
||||
): Promise<any> {
|
||||
const cookiesList = cookies();
|
||||
const stringifiedCookie = cookiesList.get("sessions");
|
||||
|
||||
const sessions: SessionCookie[] = stringifiedCookie?.value
|
||||
? JSON.parse(stringifiedCookie?.value)
|
||||
: [session];
|
||||
|
||||
const filteredSessions = sessions.filter((s) => s.id !== session.id);
|
||||
|
||||
return setSessionHttpOnlyCookie(filteredSessions);
|
||||
}
|
||||
|
||||
export async function getMostRecentSessionCookie(): Promise<any> {
|
||||
const cookiesList = cookies();
|
||||
const stringifiedCookie = cookiesList.get("sessions");
|
||||
|
||||
if (stringifiedCookie?.value) {
|
||||
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
|
||||
|
||||
const latest = sessions.reduce((prev, current) => {
|
||||
return new Date(prev.changeDate).getTime() >
|
||||
new Date(current.changeDate).getTime()
|
||||
? prev
|
||||
: current;
|
||||
});
|
||||
|
||||
return latest;
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllSessionIds(): Promise<any> {
|
||||
const cookiesList = cookies();
|
||||
const stringifiedCookie = cookiesList.get("sessions");
|
||||
|
||||
if (stringifiedCookie?.value) {
|
||||
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
|
||||
return sessions.map((session) => session.id);
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns most recent session filtered by optinal loginName
|
||||
* @param loginName
|
||||
* @returns most recent session
|
||||
*/
|
||||
export async function getMostRecentCookieWithLoginname(
|
||||
loginName?: string
|
||||
): Promise<any> {
|
||||
const cookiesList = cookies();
|
||||
const stringifiedCookie = cookiesList.get("sessions");
|
||||
|
||||
if (stringifiedCookie?.value) {
|
||||
const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value);
|
||||
|
||||
const filtered = sessions.filter((cookie) => {
|
||||
return !!loginName ? cookie.loginName === loginName : true;
|
||||
});
|
||||
|
||||
const latest =
|
||||
filtered && filtered.length
|
||||
? filtered.reduce((prev, current) => {
|
||||
return new Date(prev.changeDate).getTime() >
|
||||
new Date(current.changeDate).getTime()
|
||||
? prev
|
||||
: current;
|
||||
})
|
||||
: undefined;
|
||||
|
||||
if (latest) {
|
||||
return latest;
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearSessions() {}
|
||||
@@ -17,7 +17,7 @@
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-zitadel": "workspace:*",
|
||||
"prettier": "^2.5.1",
|
||||
"turbo": "latest"
|
||||
"turbo": "^1.9.8"
|
||||
},
|
||||
"packageManager": "pnpm@7.15.0"
|
||||
}
|
||||
|
||||
@@ -22,14 +22,15 @@
|
||||
"@types/react": "^17.0.13",
|
||||
"@types/react-dom": "^17.0.8",
|
||||
"@zitadel/tsconfig": "workspace:*",
|
||||
"zitadel-tailwind-config": "workspace:*",
|
||||
"autoprefixer": "10.4.13",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-zitadel": "workspace:*",
|
||||
"postcss": "8.4.21",
|
||||
"sass": "^1.62.0",
|
||||
"tailwindcss": "3.2.4",
|
||||
"tsup": "^5.10.1",
|
||||
"typescript": "^4.5.3"
|
||||
"typescript": "^4.5.3",
|
||||
"zitadel-tailwind-config": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"type": "commonjs",
|
||||
"sideEffects": false,
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts src/*/index.ts --format esm,cjs --dts",
|
||||
"dev": "tsup src/index.ts src/*/index.ts --format esm,cjs --watch --dts",
|
||||
"build": "tsup --dts",
|
||||
"dev": "tsup --dts --watch",
|
||||
"lint": "eslint \"src/**/*.ts*\"",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
||||
"prebuild": "pnpm run generate",
|
||||
|
||||
@@ -1,12 +1,54 @@
|
||||
export * from "./server";
|
||||
import * as settings from "./v2/settings";
|
||||
import * as session from "./v2/session";
|
||||
import * as user from "./v2/user";
|
||||
|
||||
import * as login from "./proto/server/zitadel/settings/v2alpha/login_settings";
|
||||
import * as password from "./proto/server/zitadel/settings/v2alpha/password_settings";
|
||||
import * as legal from "./proto/server/zitadel/settings/v2alpha/legal_settings";
|
||||
|
||||
export {
|
||||
BrandingSettings,
|
||||
Theme,
|
||||
} from "./proto/server/zitadel/settings/v2alpha/branding_settings";
|
||||
|
||||
export { Session } from "./proto/server/zitadel/session/v2alpha/session";
|
||||
export {
|
||||
ListSessionsResponse,
|
||||
GetSessionResponse,
|
||||
CreateSessionResponse,
|
||||
SetSessionResponse,
|
||||
} from "./proto/server/zitadel/session/v2alpha/session_service";
|
||||
export {
|
||||
GetPasswordComplexitySettingsResponse,
|
||||
GetBrandingSettingsResponse,
|
||||
GetLegalAndSupportSettingsResponse,
|
||||
GetGeneralSettingsResponse,
|
||||
} from "./proto/server/zitadel/settings/v2alpha/settings_service";
|
||||
export {
|
||||
AddHumanUserResponse,
|
||||
VerifyEmailResponse,
|
||||
} from "./proto/server/zitadel/user/v2alpha/user_service";
|
||||
|
||||
export { type LegalAndSupportSettings } from "./proto/server/zitadel/settings/v2alpha/legal_settings";
|
||||
export { type PasswordComplexitySettings } from "./proto/server/zitadel/settings/v2alpha/password_settings";
|
||||
|
||||
import {
|
||||
getServers,
|
||||
initializeServer,
|
||||
ZitadelServer,
|
||||
ZitadelServerOptions,
|
||||
} from "./server";
|
||||
export * from "./middleware";
|
||||
export * from "./management";
|
||||
|
||||
// export * as auth from "./auth";
|
||||
// export * as management from "./management";
|
||||
// export * as admin from "./admin";
|
||||
// export * as system from "./system";
|
||||
|
||||
// export * from "./proto/server/zitadel/management";
|
||||
// export * from "./proto/server/zitadel/system";
|
||||
// export * from "./proto/server/zitadel/admin";
|
||||
export {
|
||||
getServers,
|
||||
ZitadelServer,
|
||||
type ZitadelServerOptions,
|
||||
initializeServer,
|
||||
user,
|
||||
session,
|
||||
settings,
|
||||
login,
|
||||
password,
|
||||
legal,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from "./management";
|
||||
export * as management from "../proto/server/zitadel/management";
|
||||
export * from "../proto/server/zitadel/policy";
|
||||
export * as policy from "../proto/server/zitadel/policy";
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { createChannel, createClientFactory } from "nice-grpc";
|
||||
import {
|
||||
SettingsServiceClient,
|
||||
SettingsServiceDefinition,
|
||||
} from "./proto/server/zitadel/settings/v2alpha/settings_service";
|
||||
import { authMiddleware } from "./middleware";
|
||||
import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions";
|
||||
|
||||
let apps: ZitadelServer[] = [];
|
||||
|
||||
export interface ZitadelServerProps {
|
||||
@@ -49,3 +57,18 @@ export function getServer(name?: string): ZitadelServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createClient = <Client>(
|
||||
definition: CompatServiceDefinition,
|
||||
apiUrl: string,
|
||||
token: string
|
||||
) => {
|
||||
if (!apiUrl) {
|
||||
throw Error("ZITADEL_API_URL not set");
|
||||
}
|
||||
|
||||
const channel = createChannel(process.env.ZITADEL_API_URL ?? "");
|
||||
return createClientFactory()
|
||||
.use(authMiddleware(token))
|
||||
.create(definition, channel) as Client;
|
||||
};
|
||||
|
||||
2
packages/zitadel-server/src/v2/session/index.ts
Normal file
2
packages/zitadel-server/src/v2/session/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./session";
|
||||
export * from "../../proto/server/zitadel/session/v2alpha/session";
|
||||
28
packages/zitadel-server/src/v2/session/session.ts
Normal file
28
packages/zitadel-server/src/v2/session/session.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions";
|
||||
|
||||
import {
|
||||
SessionServiceClient,
|
||||
SessionServiceDefinition,
|
||||
} from "../../proto/server/zitadel/session/v2alpha/session_service";
|
||||
|
||||
import { ZitadelServer, createClient, getServers } from "../../server";
|
||||
|
||||
export const getSession = (server?: string | ZitadelServer) => {
|
||||
let config;
|
||||
if (server && typeof server === "string") {
|
||||
const apps = getServers();
|
||||
config = apps.find((a) => a.name === server)?.config;
|
||||
} else if (server && typeof server === "object") {
|
||||
config = server.config;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
throw Error("No ZITADEL server found");
|
||||
}
|
||||
|
||||
return createClient<SessionServiceClient>(
|
||||
SessionServiceDefinition as CompatServiceDefinition,
|
||||
config.apiUrl,
|
||||
config.token
|
||||
);
|
||||
};
|
||||
2
packages/zitadel-server/src/v2/settings/index.ts
Normal file
2
packages/zitadel-server/src/v2/settings/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./settings";
|
||||
export * from "../../proto/server/zitadel/settings/v2alpha/settings";
|
||||
28
packages/zitadel-server/src/v2/settings/settings.ts
Normal file
28
packages/zitadel-server/src/v2/settings/settings.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions";
|
||||
|
||||
import {
|
||||
SettingsServiceClient,
|
||||
SettingsServiceDefinition,
|
||||
} from "../../proto/server/zitadel/settings/v2alpha/settings_service";
|
||||
|
||||
import { ZitadelServer, createClient, getServers } from "../../server";
|
||||
|
||||
export const getSettings = (server?: string | ZitadelServer) => {
|
||||
let config;
|
||||
if (server && typeof server === "string") {
|
||||
const apps = getServers();
|
||||
config = apps.find((a) => a.name === server)?.config;
|
||||
} else if (server && typeof server === "object") {
|
||||
config = server.config;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
throw Error("No ZITADEL server found");
|
||||
}
|
||||
|
||||
return createClient<SettingsServiceClient>(
|
||||
SettingsServiceDefinition as CompatServiceDefinition,
|
||||
config.apiUrl,
|
||||
config.token
|
||||
);
|
||||
};
|
||||
2
packages/zitadel-server/src/v2/user/index.ts
Normal file
2
packages/zitadel-server/src/v2/user/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./user";
|
||||
export * from "../../proto/server/zitadel/user/v2alpha/user";
|
||||
28
packages/zitadel-server/src/v2/user/user.ts
Normal file
28
packages/zitadel-server/src/v2/user/user.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions";
|
||||
|
||||
import {
|
||||
UserServiceClient,
|
||||
UserServiceDefinition,
|
||||
} from "../../proto/server/zitadel/user/v2alpha/user_service";
|
||||
|
||||
import { ZitadelServer, createClient, getServers } from "../../server";
|
||||
|
||||
export const getUser = (server?: string | ZitadelServer) => {
|
||||
let config;
|
||||
if (server && typeof server === "string") {
|
||||
const apps = getServers();
|
||||
config = apps.find((a) => a.name === server)?.config;
|
||||
} else if (server && typeof server === "object") {
|
||||
config = server.config;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
throw Error("No ZITADEL server found");
|
||||
}
|
||||
|
||||
return createClient<UserServiceClient>(
|
||||
UserServiceDefinition as CompatServiceDefinition,
|
||||
config.apiUrl,
|
||||
config.token
|
||||
);
|
||||
};
|
||||
@@ -3,12 +3,7 @@
|
||||
"include": ["src/**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"paths": {
|
||||
"#": ["."],
|
||||
"*": ["./*"],
|
||||
"#/*": ["./*"]
|
||||
}
|
||||
"rootDir": "src"
|
||||
},
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
|
||||
13
packages/zitadel-server/tsup.config.ts
Normal file
13
packages/zitadel-server/tsup.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineConfig, Options } from "tsup";
|
||||
|
||||
export default defineConfig((options: Options) => ({
|
||||
treeshake: true,
|
||||
splitting: true,
|
||||
publicDir: true,
|
||||
entry: ["src/index.ts", "src/**/index.ts"],
|
||||
format: ["esm", "cjs"],
|
||||
dts: true,
|
||||
minify: true,
|
||||
clean: true,
|
||||
...options,
|
||||
}));
|
||||
142
pnpm-lock.yaml
generated
142
pnpm-lock.yaml
generated
@@ -8,13 +8,13 @@ importers:
|
||||
eslint: ^7.32.0
|
||||
eslint-config-zitadel: workspace:*
|
||||
prettier: ^2.5.1
|
||||
turbo: latest
|
||||
turbo: ^1.9.8
|
||||
devDependencies:
|
||||
'@changesets/cli': 2.25.2
|
||||
eslint: 7.32.0
|
||||
eslint-config-zitadel: link:packages/eslint-config-zitadel
|
||||
prettier: 2.8.0
|
||||
turbo: 1.9.3
|
||||
turbo: 1.9.8
|
||||
|
||||
apps/login:
|
||||
specifiers:
|
||||
@@ -41,7 +41,8 @@ importers:
|
||||
grpc-tools: 1.11.3
|
||||
lint-staged: 13.0.3
|
||||
make-dir-cli: 3.0.0
|
||||
next: 13.3.2-canary.2
|
||||
moment: ^2.29.4
|
||||
next: 13.4.2
|
||||
next-themes: ^0.2.1
|
||||
nice-grpc: 2.0.1
|
||||
postcss: 8.4.21
|
||||
@@ -65,8 +66,9 @@ importers:
|
||||
'@zitadel/server': link:../../packages/zitadel-server
|
||||
clsx: 1.2.1
|
||||
date-fns: 2.29.3
|
||||
next: 13.3.2-canary.2_krg7tz6h6n3fx3eq7tclunioeu
|
||||
next-themes: 0.2.1_fbdiuaz6irj67j3n36ky3t2nbi
|
||||
moment: 2.29.4
|
||||
next: 13.4.2_krg7tz6h6n3fx3eq7tclunioeu
|
||||
next-themes: 0.2.1_cmp7sjki5xcmfyvhcokzzink7a
|
||||
nice-grpc: 2.0.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
@@ -154,6 +156,7 @@ importers:
|
||||
'@types/react': ^17.0.13
|
||||
'@types/react-dom': ^17.0.8
|
||||
'@zitadel/tsconfig': workspace:*
|
||||
autoprefixer: 10.4.13
|
||||
eslint: ^7.32.0
|
||||
eslint-config-zitadel: workspace:*
|
||||
postcss: 8.4.21
|
||||
@@ -169,6 +172,7 @@ importers:
|
||||
'@types/react': 17.0.52
|
||||
'@types/react-dom': 17.0.18
|
||||
'@zitadel/tsconfig': link:../zitadel-tsconfig
|
||||
autoprefixer: 10.4.13_postcss@8.4.21
|
||||
eslint: 7.32.0
|
||||
eslint-config-zitadel: link:../eslint-config-zitadel
|
||||
postcss: 8.4.21
|
||||
@@ -667,8 +671,8 @@ packages:
|
||||
resolution: {integrity: sha512-FN50r/E+b8wuqyRjmGaqvqNDuWBWYWQiigfZ50KnSFH0f+AMQQyaZl+Zm2+CIpKk0fL9QxhLxOpTVA3xFHgFow==}
|
||||
dev: false
|
||||
|
||||
/@next/env/13.3.2-canary.2:
|
||||
resolution: {integrity: sha512-/NqWjXLGlNpGkxPAXR8TDWT6ZYsYGwWNfwhpPhtyMtUOU78wwWiT5p/smGd/+h/PFaIeLjrjtqiA7hHqrw0u0A==}
|
||||
/@next/env/13.4.2:
|
||||
resolution: {integrity: sha512-Wqvo7lDeS0KGwtwg9TT9wKQ8raelmUxt+TQKWvG/xKfcmDXNOtCuaszcfCF8JzlBG1q0VhpI6CKaRMbVPMDWgw==}
|
||||
dev: false
|
||||
|
||||
/@next/eslint-plugin-next/13.3.1:
|
||||
@@ -704,8 +708,8 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-arm64/13.3.2-canary.2:
|
||||
resolution: {integrity: sha512-HdqGogdJAF88hzmVLhSXu/msxlkv2MP395natN1MmGxjqfTNGLSJewWmPf4vdOBIP54lDc6Nap/b2joYWOrCDw==}
|
||||
/@next/swc-darwin-arm64/13.4.2:
|
||||
resolution: {integrity: sha512-6BBlqGu3ewgJflv9iLCwO1v1hqlecaIH2AotpKfVUEzUxuuDNJQZ2a4KLb4MBl8T9/vca1YuWhSqtbF6ZuUJJw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
@@ -722,8 +726,8 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-x64/13.3.2-canary.2:
|
||||
resolution: {integrity: sha512-u9LPNpaRXjKi6WPDqhrXEYW3UJxyf3J2mva8fmb3CGZHR8BrkItRDcn7VDgSZ0jTHRHpCGqYXlPE+z6+bVYdeg==}
|
||||
/@next/swc-darwin-x64/13.4.2:
|
||||
resolution: {integrity: sha512-iZuYr7ZvGLPjPmfhhMl0ISm+z8EiyLBC1bLyFwGBxkWmPXqdJ60mzuTaDSr5WezDwv0fz32HB7JHmRC6JVHSZg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
@@ -758,8 +762,8 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-gnu/13.3.2-canary.2:
|
||||
resolution: {integrity: sha512-e/aUm7RZoDcvLHrK7sTiRMX3cS+1LVlN2gUKV9PYrrXGftuQGkIwJyZPUm4nsJUX7ozNWXPU50YeHPvt9K0c2Q==}
|
||||
/@next/swc-linux-arm64-gnu/13.4.2:
|
||||
resolution: {integrity: sha512-2xVabFtIge6BJTcJrW8YuUnYTuQjh4jEuRuS2mscyNVOj6zUZkom3CQg+egKOoS+zh2rrro66ffSKIS+ztFJTg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -776,8 +780,8 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-musl/13.3.2-canary.2:
|
||||
resolution: {integrity: sha512-wDvtL9LcN0pSao+M/A3qSYVHvPcyH1H9d0v7aIbwd6F/JuTIlTeXgKuxVCYY5OBNC6dXbzOyGSREZ8hLCx9Wjw==}
|
||||
/@next/swc-linux-arm64-musl/13.4.2:
|
||||
resolution: {integrity: sha512-wKRCQ27xCUJx5d6IivfjYGq8oVngqIhlhSAJntgXLt7Uo9sRT/3EppMHqUZRfyuNBTbykEre1s5166z+pvRB5A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -794,8 +798,8 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-gnu/13.3.2-canary.2:
|
||||
resolution: {integrity: sha512-Z/GTeCcD6YK92rBdrAa5GVLC9TzXkXpGKnlDLJLm/2oY1eBRTVpQT5/vp0vrRcPYjdHXubizquk1Q3eyAtlKTg==}
|
||||
/@next/swc-linux-x64-gnu/13.4.2:
|
||||
resolution: {integrity: sha512-NpCa+UVhhuNeaFVUP1Bftm0uqtvLWq2JTm7+Ta48+2Uqj2mNXrDIvyn1DY/ZEfmW/1yvGBRaUAv9zkMkMRixQA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -812,8 +816,8 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-musl/13.3.2-canary.2:
|
||||
resolution: {integrity: sha512-P0KCzP17aoxfq3k+rtgDhOl8BILdgw3pw8w88/qD5WA2xK2R9Rg4lRI6pAQSro0++ToNDgnrXpRuJov7n1OfeQ==}
|
||||
/@next/swc-linux-x64-musl/13.4.2:
|
||||
resolution: {integrity: sha512-ZWVC72x0lW4aj44e3khvBrj2oSYj1bD0jESmyah3zG/3DplEy/FOtYkMzbMjHTdDSheso7zH8GIlW6CDQnKhmQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -830,8 +834,8 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-arm64-msvc/13.3.2-canary.2:
|
||||
resolution: {integrity: sha512-yGpQpU0To4gp/bjhwKHqu3zVJ/Jco+g4Okv95IWnbYUX7sd14kophZGwHiZN4dLErB9Pdd4vvmz8ccJP5h+Ubg==}
|
||||
/@next/swc-win32-arm64-msvc/13.4.2:
|
||||
resolution: {integrity: sha512-pLT+OWYpzJig5K4VKhLttlIfBcVZfr2+Xbjra0Tjs83NQSkFS+y7xx+YhCwvpEmXYLIvaggj2ONPyjbiigOvHQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
@@ -848,8 +852,8 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-ia32-msvc/13.3.2-canary.2:
|
||||
resolution: {integrity: sha512-iHtddC48Xdl7RxCdhBWZ6+1hq/eC0duTR4y3yYPELpXpZnIwGjOT5W5N+3nVRXUVLsj6teRf8fEfWBp3WbJ0RQ==}
|
||||
/@next/swc-win32-ia32-msvc/13.4.2:
|
||||
resolution: {integrity: sha512-dhpiksQCyGca4WY0fJyzK3FxMDFoqMb0Cn+uDB+9GYjpU2K5//UGPQlCwiK4JHxuhg8oLMag5Nf3/IPSJNG8jw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
@@ -866,8 +870,8 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-x64-msvc/13.3.2-canary.2:
|
||||
resolution: {integrity: sha512-Ctw3gL8cBMvREpJM09xvC+pPKsG8TVSWxsQPTLvD33qFED0gtU9HSIacJ09eXd8mqtRGebcXaNjY9fVFfGHZ3A==}
|
||||
/@next/swc-win32-x64-msvc/13.4.2:
|
||||
resolution: {integrity: sha512-O7bort1Vld00cu8g0jHZq3cbSTUNMohOEvYqsqE10+yfohhdPHzvzO+ziJRz4Dyyr/fYKREwS7gR4JC0soSOMw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -948,8 +952,8 @@ packages:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@swc/helpers/0.5.0:
|
||||
resolution: {integrity: sha512-SjY/p4MmECVVEWspzSRpQEM3sjR17sP8PbGxELWrT+YZMBfiUyt1MRUNjMV23zohwlG2HYtCQOsCwsTHguXkyg==}
|
||||
/@swc/helpers/0.5.1:
|
||||
resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==}
|
||||
dependencies:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
@@ -1354,7 +1358,7 @@ packages:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.21.5
|
||||
caniuse-lite: 1.0.30001434
|
||||
caniuse-lite: 1.0.30001473
|
||||
fraction.js: 4.2.0
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.0
|
||||
@@ -1481,6 +1485,7 @@ packages:
|
||||
|
||||
/caniuse-lite/1.0.30001434:
|
||||
resolution: {integrity: sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==}
|
||||
dev: false
|
||||
|
||||
/caniuse-lite/1.0.30001473:
|
||||
resolution: {integrity: sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==}
|
||||
@@ -3581,6 +3586,10 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/moment/2.29.4:
|
||||
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
|
||||
dev: false
|
||||
|
||||
/ms/2.0.0:
|
||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||
dev: false
|
||||
@@ -3608,14 +3617,14 @@ packages:
|
||||
/natural-compare/1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
/next-themes/0.2.1_fbdiuaz6irj67j3n36ky3t2nbi:
|
||||
/next-themes/0.2.1_cmp7sjki5xcmfyvhcokzzink7a:
|
||||
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
|
||||
peerDependencies:
|
||||
next: '*'
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
next: 13.3.2-canary.2_krg7tz6h6n3fx3eq7tclunioeu
|
||||
next: 13.4.2_krg7tz6h6n3fx3eq7tclunioeu
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
@@ -3667,9 +3676,9 @@ packages:
|
||||
- babel-plugin-macros
|
||||
dev: false
|
||||
|
||||
/next/13.3.2-canary.2_krg7tz6h6n3fx3eq7tclunioeu:
|
||||
resolution: {integrity: sha512-tAJBdhzzQxzomn2Ge3lR3zCVPBnPSfXy6+fTQTDtZHDQe/pH9xJgnMpwvA8kBYEr5yrCcJn0U3kxeo32LRJUjw==}
|
||||
engines: {node: '>=14.18.0'}
|
||||
/next/13.4.2_krg7tz6h6n3fx3eq7tclunioeu:
|
||||
resolution: {integrity: sha512-aNFqLs3a3nTGvLWlO9SUhCuMUHVPSFQC0+tDNGAsDXqx+WJDFSbvc233gOJ5H19SBc7nw36A9LwQepOJ2u/8Kg==}
|
||||
engines: {node: '>=16.8.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.1.0
|
||||
@@ -3688,8 +3697,8 @@ packages:
|
||||
sass:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@next/env': 13.3.2-canary.2
|
||||
'@swc/helpers': 0.5.0
|
||||
'@next/env': 13.4.2
|
||||
'@swc/helpers': 0.5.1
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: 1.0.30001473
|
||||
postcss: 8.4.14
|
||||
@@ -3697,16 +3706,17 @@ packages:
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
sass: 1.62.0
|
||||
styled-jsx: 5.1.1_react@18.2.0
|
||||
zod: 3.21.4
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 13.3.2-canary.2
|
||||
'@next/swc-darwin-x64': 13.3.2-canary.2
|
||||
'@next/swc-linux-arm64-gnu': 13.3.2-canary.2
|
||||
'@next/swc-linux-arm64-musl': 13.3.2-canary.2
|
||||
'@next/swc-linux-x64-gnu': 13.3.2-canary.2
|
||||
'@next/swc-linux-x64-musl': 13.3.2-canary.2
|
||||
'@next/swc-win32-arm64-msvc': 13.3.2-canary.2
|
||||
'@next/swc-win32-ia32-msvc': 13.3.2-canary.2
|
||||
'@next/swc-win32-x64-msvc': 13.3.2-canary.2
|
||||
'@next/swc-darwin-arm64': 13.4.2
|
||||
'@next/swc-darwin-x64': 13.4.2
|
||||
'@next/swc-linux-arm64-gnu': 13.4.2
|
||||
'@next/swc-linux-arm64-musl': 13.4.2
|
||||
'@next/swc-linux-x64-gnu': 13.4.2
|
||||
'@next/swc-linux-x64-musl': 13.4.2
|
||||
'@next/swc-win32-arm64-msvc': 13.4.2
|
||||
'@next/swc-win32-ia32-msvc': 13.4.2
|
||||
'@next/swc-win32-x64-msvc': 13.4.2
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
@@ -5072,65 +5082,65 @@ packages:
|
||||
yargs: 17.6.2
|
||||
dev: true
|
||||
|
||||
/turbo-darwin-64/1.9.3:
|
||||
resolution: {integrity: sha512-0dFc2cWXl82kRE4Z+QqPHhbEFEpUZho1msHXHWbz5+PqLxn8FY0lEVOHkq5tgKNNEd5KnGyj33gC/bHhpZOk5g==}
|
||||
/turbo-darwin-64/1.9.8:
|
||||
resolution: {integrity: sha512-PkTdBjPfgpj/Dob/6SjkzP0BBP80/KmFjLEocXVEECCLJE6tHKbWLRdvc79B0N6SufdYdZ1uvvoU3KPtBokSPw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-darwin-arm64/1.9.3:
|
||||
resolution: {integrity: sha512-1cYbjqLBA2zYE1nbf/qVnEkrHa4PkJJbLo7hnuMuGM0bPzh4+AnTNe98gELhqI1mkTWBu/XAEeF5u6dgz0jLNA==}
|
||||
/turbo-darwin-arm64/1.9.8:
|
||||
resolution: {integrity: sha512-sLwqOx3XV57QCEoJM9GnDDnnqidG8wf29ytxssBaWHBdeJTjupyrmzTUrX+tyKo3Q+CjWvbPLyqVqxT4g5NuXQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-linux-64/1.9.3:
|
||||
resolution: {integrity: sha512-UuBPFefawEwpuxh5pM9Jqq3q4C8M0vYxVYlB3qea/nHQ80pxYq7ZcaLGEpb10SGnr3oMUUs1zZvkXWDNKCJb8Q==}
|
||||
/turbo-linux-64/1.9.8:
|
||||
resolution: {integrity: sha512-AMg6VT6sW7aOD1uOs5suxglXfTYz9T0uVyKGKokDweGOYTWmuTMGU5afUT1tYRUwQ+kVPJI+83Atl5Ob0oBsgw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-linux-arm64/1.9.3:
|
||||
resolution: {integrity: sha512-vUrNGa3hyDtRh9W0MkO+l1dzP8Co2gKnOVmlJQW0hdpOlWlIh22nHNGGlICg+xFa2f9j4PbQlWTsc22c019s8Q==}
|
||||
/turbo-linux-arm64/1.9.8:
|
||||
resolution: {integrity: sha512-tLnxFv+OIklwTjiOZ8XMeEeRDAf150Ry4BCivNwgTVFAqQGEqkFP6KGBy56hb5RRF1frPQpoPGipJNVm7c8m1w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-windows-64/1.9.3:
|
||||
resolution: {integrity: sha512-0BZ7YaHs6r+K4ksqWus1GKK3W45DuDqlmfjm/yuUbTEVc8szmMCs12vugU2Zi5GdrdJSYfoKfEJ/PeegSLIQGQ==}
|
||||
/turbo-windows-64/1.9.8:
|
||||
resolution: {integrity: sha512-r3pCjvXTMR7kq2E3iqwFlN1R7pFO/TOsuUjMhOSPP7HwuuUIinAckU4I9foM3q7ZCQd1XXScBUt3niDyHijAqQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-windows-arm64/1.9.3:
|
||||
resolution: {integrity: sha512-QJUYLSsxdXOsR1TquiOmLdAgtYcQ/RuSRpScGvnZb1hY0oLc7JWU0llkYB81wVtWs469y8H9O0cxbKwCZGR4RQ==}
|
||||
/turbo-windows-arm64/1.9.8:
|
||||
resolution: {integrity: sha512-CWzRbX2TM5IfHBC6uWM659qUOEDC4h0nn16ocG8yIq1IF3uZMzKRBHgGOT5m1BHom+R08V0NcjTmPRoqpiI0dg==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo/1.9.3:
|
||||
resolution: {integrity: sha512-ID7mxmaLUPKG/hVkp+h0VuucB1U99RPCJD9cEuSEOdIPoSIuomcIClEJtKamUsdPLhLCud+BvapBNnhgh58Nzw==}
|
||||
/turbo/1.9.8:
|
||||
resolution: {integrity: sha512-dTouGZBm4a2fE0OPafcTQERCp4i3ZOow0Pr0JlOyxKmzJy0JRwXypH013kbZoK6k1ET5tS/g9rwUXIM/AmWXXQ==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
optionalDependencies:
|
||||
turbo-darwin-64: 1.9.3
|
||||
turbo-darwin-arm64: 1.9.3
|
||||
turbo-linux-64: 1.9.3
|
||||
turbo-linux-arm64: 1.9.3
|
||||
turbo-windows-64: 1.9.3
|
||||
turbo-windows-arm64: 1.9.3
|
||||
turbo-darwin-64: 1.9.8
|
||||
turbo-darwin-arm64: 1.9.8
|
||||
turbo-linux-64: 1.9.8
|
||||
turbo-linux-arm64: 1.9.8
|
||||
turbo-windows-64: 1.9.8
|
||||
turbo-windows-arm64: 1.9.8
|
||||
dev: true
|
||||
|
||||
/type-check/0.4.0:
|
||||
@@ -5420,3 +5430,7 @@ packages:
|
||||
/yocto-queue/0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/zod/3.21.4:
|
||||
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
|
||||
dev: false
|
||||
|
||||
@@ -23,11 +23,5 @@
|
||||
}
|
||||
},
|
||||
"globalDependencies": ["**/.env.*local"],
|
||||
"globalEnv": [
|
||||
"ZITADEL_API_URL",
|
||||
"ZITADEL_PROJECT_ID",
|
||||
"ZITADEL_APP_ID",
|
||||
"ZITADEL_SERVICE_USER_TOKEN",
|
||||
"ZITADEL_ORG_ID"
|
||||
]
|
||||
"globalEnv": ["ZITADEL_API_URL", "ZITADEL_SERVICE_USER_TOKEN"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user