mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 07:24:51 +00:00
verify email
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { listSessions, server } from "#/lib/zitadel";
|
||||
import Alert from "#/ui/Alert";
|
||||
import { Avatar } from "#/ui/Avatar";
|
||||
import { getAllSessionIds } from "#/utils/cookies";
|
||||
import {
|
||||
@@ -35,11 +36,12 @@ export default async function Page() {
|
||||
|
||||
<div className="flex flex-col w-full space-y-1">
|
||||
{sessions ? (
|
||||
sessions.map((session: any) => {
|
||||
sessions.map((session: any, index: number) => {
|
||||
const validPassword = session.factors.password?.verifiedAt;
|
||||
console.log(session);
|
||||
return (
|
||||
<Link
|
||||
key={"session-" + index}
|
||||
href={
|
||||
validPassword
|
||||
? `/signedin?` +
|
||||
@@ -87,10 +89,7 @@ export default async function Page() {
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<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="h-5 w-5 mr-2" />
|
||||
<span className="text-center text-sm">No Sessions available!</span>
|
||||
</div>
|
||||
<Alert>No Sessions available!</Alert>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import VerifyEmailForm from "#/ui/VerifyEmailForm";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
export default async function Page({ searchParams }: { searchParams: any }) {
|
||||
const { userID, code, orgID, loginname, passwordset } = searchParams;
|
||||
const { userID, code, submit, orgID, loginname, passwordset } = searchParams;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
@@ -12,7 +12,11 @@ export default async function Page({ searchParams }: { searchParams: any }) {
|
||||
</p>
|
||||
|
||||
{userID ? (
|
||||
<VerifyEmailForm userId={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" />
|
||||
|
||||
@@ -34,6 +34,10 @@ export default async function RootLayout({
|
||||
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 />
|
||||
@@ -48,7 +52,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>
|
||||
)}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,13 @@ export async function POST(request: NextRequest) {
|
||||
if (body) {
|
||||
const { userId, code } = body;
|
||||
|
||||
return verifyEmail(server, userId, code).then((resp) => {
|
||||
return NextResponse.json(resp);
|
||||
});
|
||||
return verifyEmail(server, userId, code)
|
||||
.then((resp) => {
|
||||
return NextResponse.json(resp);
|
||||
})
|
||||
.catch((error) => {
|
||||
return NextResponse.json(error, { status: 500 });
|
||||
});
|
||||
} else {
|
||||
return NextResponse.error();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ import {
|
||||
getServers,
|
||||
initializeServer,
|
||||
session,
|
||||
GetGeneralSettingsResponse,
|
||||
GetBrandingSettingsResponse,
|
||||
GetPasswordComplexitySettingsResponse,
|
||||
GetLegalAndSupportSettingsResponse,
|
||||
AddHumanUserResponse,
|
||||
} from "@zitadel/server";
|
||||
|
||||
export const zitadelConfig: ZitadelServerOptions = {
|
||||
@@ -33,7 +38,7 @@ export function getBrandingSettings(
|
||||
// metadata: orgMetadata(process.env.ZITADEL_ORG_ID ?? "")
|
||||
}
|
||||
)
|
||||
.then((resp) => resp.settings);
|
||||
.then((resp: GetBrandingSettingsResponse) => resp.settings);
|
||||
}
|
||||
|
||||
export function getGeneralSettings(
|
||||
@@ -48,7 +53,7 @@ export function getGeneralSettings(
|
||||
// metadata: orgMetadata(process.env.ZITADEL_ORG_ID ?? "")
|
||||
}
|
||||
)
|
||||
.then((resp) => resp.supportedLanguages);
|
||||
.then((resp: GetGeneralSettingsResponse) => resp.supportedLanguages);
|
||||
}
|
||||
|
||||
export function getLegalAndSupportSettings(
|
||||
@@ -62,7 +67,7 @@ export function getLegalAndSupportSettings(
|
||||
// metadata: orgMetadata(process.env.ZITADEL_ORG_ID ?? "")
|
||||
}
|
||||
)
|
||||
.then((resp) => resp.settings);
|
||||
.then((resp: GetLegalAndSupportSettingsResponse) => resp.settings);
|
||||
}
|
||||
|
||||
export function getPasswordComplexitySettings(
|
||||
@@ -77,7 +82,7 @@ export function getPasswordComplexitySettings(
|
||||
// metadata: orgMetadata(process.env.ZITADEL_ORG_ID ?? "")
|
||||
}
|
||||
)
|
||||
.then((resp) => resp.settings);
|
||||
.then((resp: GetPasswordComplexitySettingsResponse) => resp.settings);
|
||||
}
|
||||
|
||||
export function createSession(
|
||||
@@ -145,7 +150,7 @@ export function addHumanUser(
|
||||
// metadata: orgMetadata(process.env.ZITADEL_ORG_ID ?? "")
|
||||
}
|
||||
)
|
||||
.then((resp) => {
|
||||
.then((resp: AddHumanUserResponse) => {
|
||||
console.log("added user", resp.userId);
|
||||
return resp.userId;
|
||||
});
|
||||
@@ -156,8 +161,8 @@ export function verifyEmail(
|
||||
userId: string,
|
||||
verificationCode: string
|
||||
): Promise<any> {
|
||||
const mgmt = user.getUser(server);
|
||||
return mgmt.verifyEmail(
|
||||
const userservice = user.getUser(server);
|
||||
return userservice.verifyEmail(
|
||||
{
|
||||
userId,
|
||||
verificationCode,
|
||||
@@ -166,4 +171,20 @@ export function verifyEmail(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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,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="h-5 w-5 mr-2" />
|
||||
<span className="text-center text-sm">{children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
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;
|
||||
@@ -13,13 +14,24 @@ type Inputs = {
|
||||
|
||||
type Props = {
|
||||
userId: string;
|
||||
code: string;
|
||||
submit: boolean;
|
||||
};
|
||||
|
||||
export default function VerifyEmailForm({ userId }: Props) {
|
||||
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);
|
||||
@@ -39,13 +51,37 @@ export default function VerifyEmailForm({ userId }: Props) {
|
||||
}),
|
||||
});
|
||||
|
||||
const response = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
setLoading(false);
|
||||
throw new Error("Failed to verify email");
|
||||
setError(response.details);
|
||||
return Promise.reject(response);
|
||||
} else {
|
||||
setLoading(false);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
return res.json();
|
||||
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> {
|
||||
@@ -55,8 +91,6 @@ export default function VerifyEmailForm({ userId }: Props) {
|
||||
});
|
||||
}
|
||||
|
||||
const { errors } = formState;
|
||||
|
||||
return (
|
||||
<form className="w-full">
|
||||
<div className="">
|
||||
@@ -69,10 +103,20 @@ export default function VerifyEmailForm({ userId }: Props) {
|
||||
/>
|
||||
</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> */}
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => resendCode()}
|
||||
variant={ButtonVariants.Secondary}
|
||||
>
|
||||
resend code
|
||||
</Button>
|
||||
<span className="flex-grow"></span>
|
||||
<Button
|
||||
type="submit"
|
||||
|
||||
Reference in New Issue
Block a user