verify email

This commit is contained in:
Max Peintner
2023-05-22 11:48:18 +02:00
parent db7ba251ed
commit b169faad0e
12 changed files with 159 additions and 33 deletions

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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>
)}

View 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();
}
}

View File

@@ -6,8 +6,12 @@ export async function POST(request: NextRequest) {
if (body) {
const { userId, code } = body;
return verifyEmail(server, userId, code).then((resp) => {
return verifyEmail(server, userId, code)
.then((resp) => {
return NextResponse.json(resp);
})
.catch((error) => {
return NextResponse.json(error, { status: 500 });
});
} else {
return NextResponse.error();

View File

@@ -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 };

View File

@@ -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
View 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>
);
}

View File

@@ -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;
}
}
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);
return res.json();
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"

View File

@@ -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"

View File

@@ -11,6 +11,14 @@ export {
Theme,
} from "./proto/server/zitadel/settings/v2alpha/branding_settings";
export {
GetPasswordComplexitySettingsResponse,
GetBrandingSettingsResponse,
GetLegalAndSupportSettingsResponse,
GetGeneralSettingsResponse,
} from "./proto/server/zitadel/settings/v2alpha/settings_service";
export { AddHumanUserResponse } 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";

5
pnpm-lock.yaml generated
View File

@@ -156,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
@@ -171,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
@@ -1356,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
@@ -1483,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==}