diff --git a/client/app/(authentication)/register/page.tsx b/client/app/(authentication)/register/page.tsx index 55644c6..645595b 100644 --- a/client/app/(authentication)/register/page.tsx +++ b/client/app/(authentication)/register/page.tsx @@ -9,7 +9,7 @@ export default async function RegisterPage() { const cookieStore = cookies(); const flowId = cookieStore.get(REGISTER_FLOW_ID_COOKIE); - let flowData; + let flow; if (flowId) { try { @@ -19,7 +19,7 @@ export default async function RegisterPage() { }); if (data) { console.log('GOT FLOW DATA'); - flowData = data; + flow = data; } } catch (err) { console.log(typeof err); @@ -31,7 +31,7 @@ export default async function RegisterPage() { } return ( - + ); diff --git a/client/components/register/email-verification.tsx b/client/components/register/email-verification.tsx index ef50634..e5aef06 100644 --- a/client/components/register/email-verification.tsx +++ b/client/components/register/email-verification.tsx @@ -27,7 +27,7 @@ const registerVerificationSchema = yup .required(); export default function RegisterEmailVerification() { - const { setCurrentStep, flowData } = useRegisterFlow(); + const { setCurrentStep, flow: flowData } = useRegisterFlow(); const router = useRouter(); diff --git a/client/components/register/flow-provider.tsx b/client/components/register/flow-provider.tsx index 3662c5a..22fe711 100644 --- a/client/components/register/flow-provider.tsx +++ b/client/components/register/flow-provider.tsx @@ -4,27 +4,27 @@ import { createContext, useContext, useState } from 'react'; interface RegisterFlowContextType { setCurrentStep: (step: string | null) => void; currentStep: string | null; - flowData: { email: string; currentStep: string; id: string } | undefined; + flow: { email: string; currentStep: string; id: string } | undefined; } // TODO: pass register flow email here const RegisterFlowContext = createContext({ setCurrentStep(step) {}, currentStep: null, - flowData: undefined, + flow: undefined, }); export const useRegisterFlow = () => useContext(RegisterFlowContext); interface RegisterFlowProviderProps { - flowData: { email: string; currentStep: string; id: string } | undefined; + flow: { email: string; currentStep: string; id: string } | undefined; } export const RegisterFlowProvider: React.FC< React.PropsWithChildren -> = ({ children, flowData }) => { +> = ({ children, flow }) => { const [localCurrentStep, setCurrentStep] = useState( - flowData ? flowData.currentStep : null + flow ? flow.currentStep : null ); return ( @@ -32,7 +32,7 @@ export const RegisterFlowProvider: React.FC< value={{ setCurrentStep, currentStep: localCurrentStep, - flowData, + flow, }} > {children} diff --git a/client/components/register/webauthn-registration.tsx b/client/components/register/webauthn-registration.tsx index 3ce3a28..163cec2 100644 --- a/client/components/register/webauthn-registration.tsx +++ b/client/components/register/webauthn-registration.tsx @@ -19,7 +19,7 @@ export default function RegisterWebAuthnRegistration() { const { handleSubmit, formState, setError } = useForm({}); const router = useRouter(); - const { flowData } = useRegisterFlow(); + const { flow: flowData } = useRegisterFlow(); const searchParams = useSearchParams(); const returnTo = searchParams.get('returnTo') || DEFAULT_REDIRECT_TO; diff --git a/client/lib/client.ts b/client/lib/client.ts index 1e57540..da52b5b 100644 --- a/client/lib/client.ts +++ b/client/lib/client.ts @@ -2,6 +2,13 @@ import createClient, { Middleware } from 'openapi-fetch'; import type { paths } from '../generated/api/v1'; import { env } from './env'; +function getBaseUrl(): string { + if (typeof window !== 'undefined') { + return env.NEXT_PUBLIC_API_BASE_URL; // browser should use public URL + } + return env.API_BASE_URL; // SSR should use server side URL +} + const errorMiddleware: Middleware = { async onResponse(res) { if (!res.ok) { @@ -17,13 +24,6 @@ const errorMiddleware: Middleware = { }, }; -function getBaseUrl(): string { - if (typeof window !== 'undefined') { - return env.NEXT_PUBLIC_API_BASE_URL; // browser should use relative url - } - return env.API_BASE_URL; // SSR should use localhost -} - export const client = createClient({ baseUrl: getBaseUrl(), headers: { 'Content-Type': 'application/json' }, diff --git a/server/app/schemas/auth.py b/server/app/schemas/auth.py index 47422e4..8248b90 100644 --- a/server/app/schemas/auth.py +++ b/server/app/schemas/auth.py @@ -1,7 +1,7 @@ from typing import Annotated from uuid import UUID -from pydantic import EmailStr, Field, Json, RootModel +from pydantic import EmailStr, Field, Json, RootModel, field_serializer from webauthn.helpers.structs import ( PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, @@ -10,6 +10,7 @@ from app.lib.enums import RegisterFlowStep from app.schemas.base import BaseSchema from app.schemas.user import UserSchema +from app.utils.formatting import redact_email class RegisterFlowSchema(BaseSchema): @@ -26,6 +27,12 @@ class RegisterFlowSchema(BaseSchema): ), ] + @field_serializer("email") + @classmethod + def serialize_email(cls, email: str) -> str: + """Redact the given email (for security purposes).""" + return redact_email(email=email) + class RegisterFlowStartInput(BaseSchema): email: Annotated[ diff --git a/server/app/utils/formatting.py b/server/app/utils/formatting.py new file mode 100644 index 0000000..8113726 --- /dev/null +++ b/server/app/utils/formatting.py @@ -0,0 +1,15 @@ +def redact_email(email: str) -> str: + """ + Redact the given email. + + Replaces the characters in the username with asterisks, + except for the first and last character. + """ + # Split the email address into username and domain + username, domain = email.split("@") + + # Redact the username + redacted_username = username[0] + "*" * (len(username) - 2) + username[-1] + + # Reconstruct the redacted email + return redacted_username + "@" + domain