From 54fcc23a6c82abe465ab0d0f04a1a52760691781 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 19 Apr 2024 16:16:16 -0700 Subject: [PATCH] Begin groups phase 2b --- .../20240419200953_email-confirmation.ts | 15 ++ backend/src/db/schemas/users.ts | 3 +- .../services/license/__mocks__/licence-fns.ts | 4 +- .../src/ee/services/license/licence-fns.ts | 4 +- .../src/ee/services/license/license-types.ts | 4 +- backend/src/ee/services/scim/scim-service.ts | 24 ++- backend/src/server/routes/index.ts | 2 +- backend/src/server/routes/v2/user-router.ts | 63 +++++++- .../services/auth-token/auth-token-service.ts | 6 + .../services/auth-token/auth-token-types.ts | 1 + .../src/services/auth/auth-signup-service.ts | 2 +- backend/src/services/smtp/smtp-service.ts | 1 + .../templates/emailVerification.handlebars | 16 +- .../signupEmailVerification.handlebars | 17 ++ backend/src/services/user/user-service.ts | 73 ++++++++- frontend/src/hooks/api/auth/index.tsx | 5 +- frontend/src/hooks/api/auth/queries.tsx | 2 +- frontend/src/hooks/api/users/index.tsx | 7 +- frontend/src/hooks/api/users/mutation.tsx | 20 +++ frontend/src/pages/signup/index.tsx | 4 +- frontend/src/views/Signup/SignupSSO.tsx | 4 +- .../EmailConfirmationStep.tsx | 149 ++++++++++++++++++ .../EmailConfirmationStep/index.tsx | 1 + .../src/views/Signup/components/index.tsx | 1 + 24 files changed, 394 insertions(+), 34 deletions(-) create mode 100644 backend/src/db/migrations/20240419200953_email-confirmation.ts create mode 100644 backend/src/services/smtp/templates/signupEmailVerification.handlebars create mode 100644 frontend/src/views/Signup/components/EmailConfirmationStep/EmailConfirmationStep.tsx create mode 100644 frontend/src/views/Signup/components/EmailConfirmationStep/index.tsx diff --git a/backend/src/db/migrations/20240419200953_email-confirmation.ts b/backend/src/db/migrations/20240419200953_email-confirmation.ts new file mode 100644 index 000000000..59d7b3d41 --- /dev/null +++ b/backend/src/db/migrations/20240419200953_email-confirmation.ts @@ -0,0 +1,15 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable(TableName.Users, (t) => { + t.boolean("isEmailVerified"); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable(TableName.Users, (t) => { + t.dropColumn("isEmailVerified"); + }); +} diff --git a/backend/src/db/schemas/users.ts b/backend/src/db/schemas/users.ts index 86ee2fb74..3eee2683f 100644 --- a/backend/src/db/schemas/users.ts +++ b/backend/src/db/schemas/users.ts @@ -21,7 +21,8 @@ export const UsersSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), isGhost: z.boolean().default(false), - username: z.string() + username: z.string(), + isEmailVerified: z.boolean().nullable().optional() }); export type TUsers = z.infer; diff --git a/backend/src/ee/services/license/__mocks__/licence-fns.ts b/backend/src/ee/services/license/__mocks__/licence-fns.ts index b5cbf103e..20186718d 100644 --- a/backend/src/ee/services/license/__mocks__/licence-fns.ts +++ b/backend/src/ee/services/license/__mocks__/licence-fns.ts @@ -17,8 +17,8 @@ export const getDefaultOnPremFeatures = () => { customAlerts: false, auditLogs: false, auditLogsRetentionDays: 0, - samlSSO: false, - scim: false, + samlSSO: true, + scim: true, ldap: false, groups: false, status: null, diff --git a/backend/src/ee/services/license/licence-fns.ts b/backend/src/ee/services/license/licence-fns.ts index 8a4de57f1..9179fde32 100644 --- a/backend/src/ee/services/license/licence-fns.ts +++ b/backend/src/ee/services/license/licence-fns.ts @@ -24,8 +24,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ customAlerts: false, auditLogs: false, auditLogsRetentionDays: 0, - samlSSO: false, - scim: false, + samlSSO: true, + scim: true, ldap: false, groups: false, status: null, diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index 1cea39a83..2cc321373 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -40,8 +40,8 @@ export type TFeatureSet = { customAlerts: false; auditLogs: false; auditLogsRetentionDays: 0; - samlSSO: false; - scim: false; + samlSSO: true; + scim: true; ldap: false; groups: false; status: null; diff --git a/backend/src/ee/services/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index 15ca67a10..120b41bd1 100644 --- a/backend/src/ee/services/scim/scim-service.ts +++ b/backend/src/ee/services/scim/scim-service.ts @@ -57,6 +57,8 @@ type TScimServiceFactoryDep = { export type TScimServiceFactory = ReturnType; +// TODO: finish updating all userId refs to orgMembershipId + export const scimServiceFactory = ({ licenseService, scimDAL, @@ -145,6 +147,7 @@ export const scimServiceFactory = ({ // SCIM server endpoints const listScimUsers = async ({ offset, limit, filter, orgId }: TListScimUsersDTO): Promise => { + console.log("listScimUsers"); // done const org = await orgDAL.findById(orgId); if (!org.scimEnabled) @@ -178,9 +181,11 @@ export const scimServiceFactory = ({ findOpts ); - const scimUsers = users.map(({ userId, username, firstName, lastName, email }) => + console.log("orgDAL.findMembership users: ", users); + + const scimUsers = users.map(({ id, username, firstName, lastName, email }) => buildScimUser({ - userId: userId ?? "", + userId: id ?? "", username, firstName: firstName ?? "", lastName: lastName ?? "", @@ -197,6 +202,7 @@ export const scimServiceFactory = ({ }; const getScimUser = async ({ userId, orgId }: TGetScimUserDTO) => { + console.log("getScimUser"); // done const [membership] = await orgDAL .findMembership({ userId, @@ -221,8 +227,10 @@ export const scimServiceFactory = ({ status: 403 }); + console.log("getScimUser membership: ", membership); + return buildScimUser({ - userId: membership.userId as string, + userId: membership.id, username: membership.username, email: membership.email ?? "", firstName: membership.firstName as string, @@ -232,6 +240,7 @@ export const scimServiceFactory = ({ }; const createScimUser = async ({ username, email, firstName, lastName, orgId }: TCreateScimUserDTO) => { + console.log("createScimUser"); // TODO: update implementation to always create a new user and be based on orgMembershipId const org = await orgDAL.findById(orgId); if (!org) @@ -331,6 +340,7 @@ export const scimServiceFactory = ({ }; const updateScimUser = async ({ userId, orgId, operations }: TUpdateScimUserDTO) => { + console.log("updateScimUser"); // done const [membership] = await orgDAL .findMembership({ userId, @@ -380,7 +390,7 @@ export const scimServiceFactory = ({ } return buildScimUser({ - userId: membership.userId as string, + userId: membership.id, username: membership.username, email: membership.email, firstName: membership.firstName as string, @@ -390,6 +400,7 @@ export const scimServiceFactory = ({ }; const replaceScimUser = async ({ userId, active, orgId }: TReplaceScimUserDTO) => { + console.log("replaceScimUser"); // done const [membership] = await orgDAL .findMembership({ userId, @@ -426,7 +437,7 @@ export const scimServiceFactory = ({ } return buildScimUser({ - userId: membership.userId as string, + userId: membership.id, username: membership.username, email: membership.email, firstName: membership.firstName as string, @@ -436,6 +447,7 @@ export const scimServiceFactory = ({ }; const deleteScimUser = async ({ userId, orgId }: TDeleteScimUserDTO) => { + console.log("deleteScimUser"); // done const [membership] = await orgDAL .findMembership({ userId, @@ -489,7 +501,7 @@ export const scimServiceFactory = ({ buildScimGroup({ groupId: group.id, name: group.name, - members: [] + members: [] // does this need to be populated? }) ); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 5d77c340b..893f36770 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -315,7 +315,7 @@ export const registerRoutes = async ( }); const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); - const userService = userServiceFactory({ userDAL }); + const userService = userServiceFactory({ userDAL, tokenService, smtpService }); const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL, tokenDAL: authTokenDAL }); const passwordService = authPaswordServiceFactory({ tokenService, diff --git a/backend/src/server/routes/v2/user-router.ts b/backend/src/server/routes/v2/user-router.ts index d1e80702f..dd49f6d5e 100644 --- a/backend/src/server/routes/v2/user-router.ts +++ b/backend/src/server/routes/v2/user-router.ts @@ -2,11 +2,72 @@ import { z } from "zod"; import { AuthTokenSessionsSchema, OrganizationsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas"; import { ApiKeysSchema } from "@app/db/schemas/api-keys"; -import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMethod, AuthMode } from "@app/services/auth/auth-type"; export const registerUserRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/me/emails/code", + config: { + rateLimit: authRateLimit + }, + schema: { + response: { + 200: z.object({}) + } + }, + preHandler: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + await server.services.user.sendEmailVerificationCode(req.permission.id); + return {}; + } + }); + + server.route({ + method: "POST", + url: "/me/emails/verify", + config: { + rateLimit: authRateLimit + }, + schema: { + body: z.object({ + code: z.string().trim() + }), + response: { + 200: z.object({}) + } + }, + preHandler: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + await server.services.user.verifyEmailVerificationCode(req.permission.id, req.body.code); + return {}; + } + }); + + server.route({ + method: "GET", + url: "/me/users/same-email", + config: { + rateLimit: readLimit + }, + schema: { + response: { + 200: z.object({ + users: UsersSchema.array() + }) + } + }, + preHandler: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const users = await server.services.user.listUsersWithSameEmail(req.permission.id); + return { + users + }; + } + }); + server.route({ method: "PATCH", url: "/me/mfa", diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 59f336e5a..bd35f3ae1 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -27,6 +27,12 @@ export const getTokenConfig = (tokenType: TokenType) => { const expiresAt = new Date(new Date().getTime() + 86400000); return { token, expiresAt }; } + case TokenType.TOKEN_EMAIL_VERIFICATION: { + // generate random 6-digit code + const token = String(crypto.randomInt(10 ** 5, 10 ** 6 - 1)); + const expiresAt = new Date(new Date().getTime() + 86400000); + return { token, expiresAt }; + } case TokenType.TOKEN_EMAIL_MFA: { // generate random 6-digit code const token = String(crypto.randomInt(10 ** 5, 10 ** 6 - 1)); diff --git a/backend/src/services/auth-token/auth-token-types.ts b/backend/src/services/auth-token/auth-token-types.ts index 74787f4ac..630e36310 100644 --- a/backend/src/services/auth-token/auth-token-types.ts +++ b/backend/src/services/auth-token/auth-token-types.ts @@ -1,5 +1,6 @@ export enum TokenType { TOKEN_EMAIL_CONFIRMATION = "emailConfirmation", + TOKEN_EMAIL_VERIFICATION = "emailVerification", // unverified -> verified TOKEN_EMAIL_MFA = "emailMfa", TOKEN_EMAIL_ORG_INVITATION = "organizationInvitation", TOKEN_EMAIL_PASSWORD_RESET = "passwordReset" diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 3db935769..b997e2293 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -60,7 +60,7 @@ export const authSignupServiceFactory = ({ }); await smtpService.sendMail({ - template: SmtpTemplates.EmailVerification, + template: SmtpTemplates.SignupEmailVerification, subjectLine: "Infisical confirmation code", recipients: [email], substitutions: { diff --git a/backend/src/services/smtp/smtp-service.ts b/backend/src/services/smtp/smtp-service.ts index 7ebeaa227..0b43ffb90 100644 --- a/backend/src/services/smtp/smtp-service.ts +++ b/backend/src/services/smtp/smtp-service.ts @@ -17,6 +17,7 @@ export type TSmtpSendMail = { export type TSmtpService = ReturnType; export enum SmtpTemplates { + SignupEmailVerification = "signupEmailVerification.handlebars", EmailVerification = "emailVerification.handlebars", SecretReminder = "secretReminder.handlebars", EmailMfa = "emailMfa.handlebars", diff --git a/backend/src/services/smtp/templates/emailVerification.handlebars b/backend/src/services/smtp/templates/emailVerification.handlebars index fc738d202..ad9694d5c 100644 --- a/backend/src/services/smtp/templates/emailVerification.handlebars +++ b/backend/src/services/smtp/templates/emailVerification.handlebars @@ -1,17 +1,15 @@ - - - - + + + Code - + - +

Confirm your email address

-

Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.

+

Your confirmation code is below — enter it in the browser window where you've started confirming your email.

{{code}}

-

Questions about setting up Infisical? Email us at support@infisical.com

- + \ No newline at end of file diff --git a/backend/src/services/smtp/templates/signupEmailVerification.handlebars b/backend/src/services/smtp/templates/signupEmailVerification.handlebars new file mode 100644 index 000000000..fc738d202 --- /dev/null +++ b/backend/src/services/smtp/templates/signupEmailVerification.handlebars @@ -0,0 +1,17 @@ + + + + + + + Code + + + +

Confirm your email address

+

Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.

+

{{code}}

+

Questions about setting up Infisical? Email us at support@infisical.com

+ + + \ No newline at end of file diff --git a/backend/src/services/user/user-service.ts b/backend/src/services/user/user-service.ts index c85e40eb3..453848bf7 100644 --- a/backend/src/services/user/user-service.ts +++ b/backend/src/services/user/user-service.ts @@ -1,15 +1,83 @@ import { BadRequestError } from "@app/lib/errors"; +import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; +import { TokenType } from "@app/services/auth-token/auth-token-types"; +import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { AuthMethod } from "../auth/auth-type"; import { TUserDALFactory } from "./user-dal"; type TUserServiceFactoryDep = { userDAL: TUserDALFactory; + tokenService: TAuthTokenServiceFactory; + smtpService: TSmtpService; }; export type TUserServiceFactory = ReturnType; -export const userServiceFactory = ({ userDAL }: TUserServiceFactoryDep) => { +export const userServiceFactory = ({ userDAL, tokenService, smtpService }: TUserServiceFactoryDep) => { + const sendEmailVerificationCode = async (userId: string) => { + console.log("sendEmailVerificationCode userId: ", userId); + const user = await userDAL.findById(userId); + if (!user) throw new BadRequestError({ name: "Failed to find user" }); + if (!user.email) + throw new BadRequestError({ name: "Failed to send email verification code due to no email on user" }); + if (user.isEmailVerified) + throw new BadRequestError({ name: "Failed to send email verification code due to email already verified" }); + + console.log("sendEmailVerificationCode user: ", user); + const token = await tokenService.createTokenForUser({ + type: TokenType.TOKEN_EMAIL_VERIFICATION, + userId: user.id + }); + + console.log("sendEmailVerificationCode 2"); + await smtpService.sendMail({ + template: SmtpTemplates.EmailVerification, + subjectLine: "Infisical confirmation code", + recipients: [user.email], + substitutions: { + code: token + } + }); + }; + + const verifyEmailVerificationCode = async (userId: string, code: string) => { + console.log("verifyEmailVerificationCode args: ", { + userId, + code + }); + + const user = await userDAL.findById(userId); + if (!user) throw new BadRequestError({ name: "Failed to find user" }); + if (user.isEmailVerified) + throw new BadRequestError({ name: "Failed to verify email verification code due to email already verified" }); + + await tokenService.validateTokenForUser({ + type: TokenType.TOKEN_EMAIL_VERIFICATION, + userId: user.id, + code + }); + + await userDAL.updateById(userId, { isEmailVerified: true }); + }; + + // lists users with same verified email only + const listUsersWithSameEmail = async (userId: string) => { + const user = await userDAL.findById(userId); + if (!user) throw new BadRequestError({ name: "Failed to find user" }); + if (!user.email) + throw new BadRequestError({ name: "Failed to list users with same email due to no email on user" }); + if (!user.isEmailVerified) + throw new BadRequestError({ name: "Failed to list users with same email due to email not verified" }); + + const users = await userDAL.find({ + email: user.email, + isEmailVerified: true + }); + + return users; + }; + const toggleUserMfa = async (userId: string, isMfaEnabled: boolean) => { const user = await userDAL.findById(userId); @@ -72,6 +140,9 @@ export const userServiceFactory = ({ userDAL }: TUserServiceFactoryDep) => { }; return { + sendEmailVerificationCode, + verifyEmailVerificationCode, + listUsersWithSameEmail, toggleUserMfa, updateUserName, updateAuthMethods, diff --git a/frontend/src/hooks/api/auth/index.tsx b/frontend/src/hooks/api/auth/index.tsx index 8b918c7ab..505f7b05f 100644 --- a/frontend/src/hooks/api/auth/index.tsx +++ b/frontend/src/hooks/api/auth/index.tsx @@ -5,7 +5,6 @@ export { useSendMfaToken, useSendPasswordResetEmail, useSendVerificationEmail, - useVerifyEmailVerificationCode, useVerifyMfaToken, - useVerifyPasswordResetCode -} from "./queries"; + useVerifyPasswordResetCode, + useVerifySignupEmailVerificationCode} from "./queries"; diff --git a/frontend/src/hooks/api/auth/queries.tsx b/frontend/src/hooks/api/auth/queries.tsx index 4d05fb963..20209df71 100644 --- a/frontend/src/hooks/api/auth/queries.tsx +++ b/frontend/src/hooks/api/auth/queries.tsx @@ -164,7 +164,7 @@ export const useSendVerificationEmail = () => { }); }; -export const useVerifyEmailVerificationCode = () => { +export const useVerifySignupEmailVerificationCode = () => { return useMutation({ mutationFn: async ({ email, code }: { email: string; code: string }) => { const { data } = await apiRequest.post("/api/v3/signup/email/verify", { diff --git a/frontend/src/hooks/api/users/index.tsx b/frontend/src/hooks/api/users/index.tsx index 9c51948f2..a8ad89f4c 100644 --- a/frontend/src/hooks/api/users/index.tsx +++ b/frontend/src/hooks/api/users/index.tsx @@ -1,4 +1,9 @@ -export { useAddUserToWsE2EE, useAddUserToWsNonE2EE } from "./mutation"; +export { + useAddUserToWsE2EE, + useAddUserToWsNonE2EE, + useSendEmailVerificationCode, + useVerifyEmailVerificationCode +} from "./mutation"; export { fetchOrgUsers, useAddUserToOrg, diff --git a/frontend/src/hooks/api/users/mutation.tsx b/frontend/src/hooks/api/users/mutation.tsx index a5c77b15f..e1e939105 100644 --- a/frontend/src/hooks/api/users/mutation.tsx +++ b/frontend/src/hooks/api/users/mutation.tsx @@ -61,3 +61,23 @@ export const useAddUserToWsNonE2EE = () => { } }); }; + +export const useSendEmailVerificationCode = () => { + return useMutation({ + mutationFn: async () => { + await apiRequest.post("/api/v2/users/me/emails/code"); + return {}; + } + }); +}; + +export const useVerifyEmailVerificationCode = () => { + return useMutation({ + mutationFn: async ({ code }: { code: string }) => { + await apiRequest.post("/api/v2/users/me/emails/verify", { + code + }); + return {}; + } + }); +}; diff --git a/frontend/src/pages/signup/index.tsx b/frontend/src/pages/signup/index.tsx index 17316e1e6..0719111d6 100644 --- a/frontend/src/pages/signup/index.tsx +++ b/frontend/src/pages/signup/index.tsx @@ -13,7 +13,7 @@ import TeamInviteStep from "@app/components/signup/TeamInviteStep"; import UserInfoStep from "@app/components/signup/UserInfoStep"; import SecurityClient from "@app/components/utilities/SecurityClient"; import { useServerConfig } from "@app/context"; -import { useVerifyEmailVerificationCode } from "@app/hooks/api"; +import { useVerifySignupEmailVerificationCode } from "@app/hooks/api"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; @@ -34,7 +34,7 @@ export default function SignUp() { const [isSignupWithEmail, setIsSignupWithEmail] = useState(false); const [isCodeInputCheckLoading, setIsCodeInputCheckLoading] = useState(false); const { t } = useTranslation(); - const { mutateAsync } = useVerifyEmailVerificationCode(); + const { mutateAsync } = useVerifySignupEmailVerificationCode(); const { config } = useServerConfig(); useEffect(() => { diff --git a/frontend/src/views/Signup/SignupSSO.tsx b/frontend/src/views/Signup/SignupSSO.tsx index 141865026..d74da3639 100644 --- a/frontend/src/views/Signup/SignupSSO.tsx +++ b/frontend/src/views/Signup/SignupSSO.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import jwt_decode from "jwt-decode"; -import { BackupPDFStep, UserInfoSSOStep } from "./components"; +import { BackupPDFStep, EmailConfirmationStep,UserInfoSSOStep } from "./components"; type Props = { providerAuthToken: string; @@ -28,6 +28,8 @@ export const SignupSSO = ({ providerAuthToken }: Props) => { /> ); case 1: + return ; + case 2: return ( ); diff --git a/frontend/src/views/Signup/components/EmailConfirmationStep/EmailConfirmationStep.tsx b/frontend/src/views/Signup/components/EmailConfirmationStep/EmailConfirmationStep.tsx new file mode 100644 index 000000000..e9b0a6e84 --- /dev/null +++ b/frontend/src/views/Signup/components/EmailConfirmationStep/EmailConfirmationStep.tsx @@ -0,0 +1,149 @@ +// confirm email +// if same email exists, then trigger fn to merge automatically +import { useState } from "react"; +import ReactCodeInput from "react-code-input"; + +// import Error from "@app/components/basic/Error"; +import { createNotification } from "@app/components/notifications"; +import { Button } from "@app/components/v2"; +import { useUser } from "@app/context"; +import { useSendEmailVerificationCode, useVerifyEmailVerificationCode } from "@app/hooks/api"; + +// The style for the verification code input +const props = { + inputStyle: { + fontFamily: "monospace", + margin: "4px", + MozAppearance: "textfield", + width: "55px", + borderRadius: "5px", + fontSize: "24px", + height: "55px", + paddingLeft: "7", + backgroundColor: "#0d1117", + color: "white", + border: "1px solid #2d2f33", + textAlign: "center", + outlineColor: "#8ca542", + borderColor: "#2d2f33" + } +} as const; +const propsPhone = { + inputStyle: { + fontFamily: "monospace", + margin: "4px", + MozAppearance: "textfield", + width: "40px", + borderRadius: "5px", + fontSize: "24px", + height: "40px", + paddingLeft: "7", + backgroundColor: "#0d1117", + color: "white", + border: "1px solid #2d2f33", + textAlign: "center", + outlineColor: "#8ca542", + borderColor: "#2d2f33" + } +} as const; + +export const EmailConfirmationStep = () => { + const { user } = useUser(); + const [code, setCode] = useState(""); + // const [codeError, setCodeError] = useState(false); + const [isResendingVerificationEmail] = useState(false); + const [isLoading] = useState(false); + + const { mutateAsync: sendEmailVerificationCode } = useSendEmailVerificationCode(); + const { mutateAsync: verifyEmailVerificationCode } = useVerifyEmailVerificationCode(); + + const checkCode = async () => { + try { + console.log("checkCode code: ", code); + await verifyEmailVerificationCode({ code }); + console.log("checkCode 2"); + } catch (err) { + createNotification({ + text: "Failed to verify code", + type: "error" + }); + } + }; + + const resendCode = async () => { + try { + console.log("resendCode"); + await sendEmailVerificationCode(); + console.log("resendCode"); + } catch (err) { + createNotification({ + text: "Failed to resend code", + type: "error" + }); + } + }; + + return ( +
+

+ We've sent a verification code to +

+

+ {user?.email} +

+
+ +
+
+ +
+ {/* {codeError && } */} +
+
+ +
+
+
+
+ Don't see the code? +
+ +
+
+

Make sure to check your spam inbox.

+
+
+ ); +}; diff --git a/frontend/src/views/Signup/components/EmailConfirmationStep/index.tsx b/frontend/src/views/Signup/components/EmailConfirmationStep/index.tsx new file mode 100644 index 000000000..32f3a636e --- /dev/null +++ b/frontend/src/views/Signup/components/EmailConfirmationStep/index.tsx @@ -0,0 +1 @@ +export { EmailConfirmationStep } from "./EmailConfirmationStep"; diff --git a/frontend/src/views/Signup/components/index.tsx b/frontend/src/views/Signup/components/index.tsx index a4628de35..7ab3d853c 100644 --- a/frontend/src/views/Signup/components/index.tsx +++ b/frontend/src/views/Signup/components/index.tsx @@ -1,2 +1,3 @@ export { BackupPDFStep } from "./BackupPDFStep"; +export { EmailConfirmationStep } from "./EmailConfirmationStep"; export { UserInfoSSOStep } from "./UserInfoSSOStep";