Added missing verify-email route
This commit is contained in:
parent
ff3e4605a1
commit
98092e12d6
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
.prettied-it
|
||||
build
|
||||
deployed
|
||||
|
||||
|
0
frontend/.prettied-it
Normal file
0
frontend/.prettied-it
Normal file
7
frontend/pretty-it
Executable file
7
frontend/pretty-it
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
if [[ ! -e .prettied-it ]]; then
|
||||
find src -name '*tsx' | while read file; do npx prettier --write $file; done
|
||||
else
|
||||
find src -name '*tsx' -newer .prettied-it | while read file; do npx prettier --write $file; done
|
||||
fi
|
||||
touch .prettied-it
|
@ -612,14 +612,7 @@ const LoginForm = (): JSX.Element => {
|
||||
|
||||
// Device Management Component
|
||||
const TrustedDevicesManager = (): JSX.Element => {
|
||||
const [devices, _setDevices] = useState<MFAData[]>([]);
|
||||
const [_loading, setLoading] = useState(true);
|
||||
|
||||
// This would need API endpoints to manage trusted devices
|
||||
useEffect(() => {
|
||||
// Load trusted devices
|
||||
setLoading(false);
|
||||
}, []);
|
||||
const devices: MFAData[] = [];
|
||||
|
||||
return (
|
||||
<Card>
|
||||
|
@ -18,7 +18,6 @@ const GenerateImage = (props: GenerateImageProps): JSX.Element => {
|
||||
const { setSnack } = useAppState();
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
const [status, setStatus] = useState<string>('');
|
||||
const [image, _setImage] = useState<string>('');
|
||||
const controllerRef = useRef<string>(null);
|
||||
|
||||
// Effect to trigger profile generation when user data is ready
|
||||
@ -97,7 +96,6 @@ const GenerateImage = (props: GenerateImageProps): JSX.Element => {
|
||||
minHeight: 'max-content',
|
||||
}}
|
||||
>
|
||||
{image !== '' && <img alt={prompt} src={`${image}/${chatSession.id}`} />}
|
||||
{prompt && (
|
||||
<Quote
|
||||
size={processing ? 'normal' : 'small'}
|
||||
|
@ -20,7 +20,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
import PendingIcon from '@mui/icons-material/Pending';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Candidate, ChatMessage, SkillAssessment, SkillStatus } from 'types/types';
|
||||
import { Candidate, SkillAssessment, SkillStatus } from 'types/types';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { BackstoryPageProps } from './BackstoryTab';
|
||||
import { Job } from 'types/types';
|
||||
@ -49,11 +49,9 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
const [loadingRequirements, setLoadingRequirements] = useState<boolean>(false);
|
||||
const [expanded, setExpanded] = useState<string | false>(false);
|
||||
const [overallScore, setOverallScore] = useState<number>(0);
|
||||
const [_statusMessage, setStatusMessage] = useState<ChatMessage | null>(null);
|
||||
const [startAnalysis, setStartAnalysis] = useState<boolean>(false);
|
||||
const [analyzing, setAnalyzing] = useState<boolean>(false);
|
||||
const [matchStatus, setMatchStatus] = useState<string>('');
|
||||
const [_matchStatusType, setMatchStatusType] = useState<Types.ApiActivityType | null>(null);
|
||||
const [percentage, setPercentage] = useState<number>(0);
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
@ -137,7 +135,6 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
|
||||
setRequirements(requirements);
|
||||
setSkillMatches(initialSkillMatches);
|
||||
setStatusMessage(null);
|
||||
setLoadingRequirements(false);
|
||||
setOverallScore(0);
|
||||
},
|
||||
@ -151,11 +148,10 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
const skillMatchHandlers = useMemo(() => {
|
||||
return {
|
||||
onStatus: (status: Types.ChatMessageStatus): void => {
|
||||
setMatchStatusType(status.activity);
|
||||
setMatchStatus(status.content.toLowerCase());
|
||||
},
|
||||
};
|
||||
}, [setMatchStatus, setMatchStatusType]);
|
||||
}, [setMatchStatus]);
|
||||
|
||||
// Fetch match data for each requirement
|
||||
useEffect(() => {
|
||||
@ -200,7 +196,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
break;
|
||||
}
|
||||
if (
|
||||
skillMatch.evidenceStrength == 'none' &&
|
||||
skillMatch.evidenceStrength === 'none' &&
|
||||
skillMatch.evidenceDetails &&
|
||||
skillMatch.evidenceDetails.length > 3
|
||||
) {
|
||||
|
@ -113,7 +113,7 @@ interface HeaderProps {
|
||||
sessionId?: string | null;
|
||||
}
|
||||
|
||||
type MenuItem = {
|
||||
type NavigationMenuItem = {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: React.ReactElement | null;
|
||||
@ -151,8 +151,8 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
const userMenuGroups = getUserMenuItemsByGroup(user?.userType || null, isAdmin);
|
||||
|
||||
// Create user menu items array with proper actions
|
||||
const createUserMenuItems = (): MenuItem[] => {
|
||||
const items: Array<MenuItem> = [];
|
||||
const createUserMenuItems = (): NavigationMenuItem[] => {
|
||||
const items: Array<NavigationMenuItem> = [];
|
||||
|
||||
// Add profile group items
|
||||
userMenuGroups.profile.forEach(item => {
|
||||
|
@ -66,7 +66,7 @@ interface AnalysisState {
|
||||
resume: string | null;
|
||||
}
|
||||
|
||||
interface Step {
|
||||
interface StepData {
|
||||
index: number;
|
||||
label: string;
|
||||
requiredState: string[];
|
||||
@ -82,7 +82,7 @@ const initialState: AnalysisState = {
|
||||
};
|
||||
|
||||
// Steps in our process
|
||||
const steps: Step[] = [
|
||||
const steps: StepData[] = [
|
||||
{ requiredState: [], title: 'Job Selection', icon: <WorkIcon /> },
|
||||
{ requiredState: ['job'], title: 'Select Candidate', icon: <PersonIcon /> },
|
||||
{
|
||||
@ -110,7 +110,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
|
||||
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
|
||||
const { selectedJob, setSelectedJob } = useSelectedJob();
|
||||
|
||||
const [activeStep, setActiveStep] = useState<Step>(steps[0]);
|
||||
const [activeStep, setActiveStep] = useState<StepData>(steps[0]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [jobTab, setJobTab] = useState<string>('select');
|
||||
const [analysisState, setAnalysisState] = useState<AnalysisState | null>(null);
|
||||
@ -119,7 +119,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const canAccessStep = useCallback(
|
||||
(step: Step) => {
|
||||
(step: StepData) => {
|
||||
if (!analysisState) {
|
||||
return;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ from device_manager import DeviceManager
|
||||
from email_service import VerificationEmailRateLimiter, email_service
|
||||
from logger import logger
|
||||
from models import (
|
||||
EmailVerificationRequest,
|
||||
LoginRequest,
|
||||
CreateCandidateRequest,
|
||||
Candidate,
|
||||
@ -1061,3 +1062,114 @@ async def confirm_password_reset(request: PasswordResetConfirm, database: RedisD
|
||||
return JSONResponse(
|
||||
status_code=500, content=create_error_response("RESET_ERROR", "An error occurred resetting the password")
|
||||
)
|
||||
|
||||
@router.post("/verify-email")
|
||||
async def verify_email(
|
||||
request: EmailVerificationRequest,
|
||||
database: RedisDatabase = Depends(get_database)
|
||||
):
|
||||
"""Verify email address and activate account"""
|
||||
try:
|
||||
# Get verification data
|
||||
verification_data = await database.get_email_verification_token(request.token)
|
||||
|
||||
if not verification_data:
|
||||
logger.warning(f"⚠️ Invalid verification token: {request.token}")
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content=create_error_response("INVALID_TOKEN", "Invalid or expired verification token")
|
||||
)
|
||||
|
||||
if verification_data.get("verified"):
|
||||
logger.warning(f"⚠️ Attempt to verify already verified email: {verification_data['email']}")
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content=create_error_response("ALREADY_VERIFIED", "Email already verified")
|
||||
)
|
||||
|
||||
# Check expiration
|
||||
expires_at = datetime.fromisoformat(verification_data["expires_at"])
|
||||
if datetime.now(timezone.utc) > expires_at:
|
||||
logger.warning(f"⚠️ Verification token expired for: {verification_data['email']}")
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content=create_error_response("TOKEN_EXPIRED", "Verification token has expired")
|
||||
)
|
||||
|
||||
# Extract user data
|
||||
user_type = verification_data["user_type"]
|
||||
user_data_container = verification_data["user_data"]
|
||||
|
||||
if user_type == "candidate":
|
||||
candidate_data = user_data_container["candidate_data"]
|
||||
password = user_data_container["password"]
|
||||
username = user_data_container["username"]
|
||||
|
||||
# Activate candidate
|
||||
candidate_data["status"] = "active"
|
||||
candidate = Candidate.model_validate(candidate_data)
|
||||
|
||||
# Create authentication record
|
||||
auth_manager = AuthenticationManager(database)
|
||||
await auth_manager.create_user_authentication(candidate.id, password)
|
||||
|
||||
# Store in database
|
||||
await database.set_candidate(candidate.id, candidate.model_dump())
|
||||
|
||||
# Add user lookup records
|
||||
user_auth_data = {
|
||||
"id": candidate.id,
|
||||
"type": "candidate",
|
||||
"email": candidate.email,
|
||||
"username": username
|
||||
}
|
||||
|
||||
await database.set_user(candidate.email, user_auth_data)
|
||||
await database.set_user(username, user_auth_data)
|
||||
await database.set_user_by_id(candidate.id, user_auth_data)
|
||||
|
||||
elif user_type == "employer":
|
||||
employer_data = user_data_container["employer_data"]
|
||||
password = user_data_container["password"]
|
||||
username = user_data_container["username"]
|
||||
|
||||
# Activate employer
|
||||
employer_data["status"] = "active"
|
||||
employer = Employer.model_validate(employer_data)
|
||||
|
||||
# Create authentication record
|
||||
auth_manager = AuthenticationManager(database)
|
||||
await auth_manager.create_user_authentication(employer.id, password)
|
||||
|
||||
# Store in database
|
||||
await database.set_employer(employer.id, employer.model_dump())
|
||||
|
||||
# Add user lookup records
|
||||
user_auth_data = {
|
||||
"id": employer.id,
|
||||
"type": "employer",
|
||||
"email": employer.email,
|
||||
"username": username
|
||||
}
|
||||
|
||||
await database.set_user(employer.email, user_auth_data)
|
||||
await database.set_user(username, user_auth_data)
|
||||
await database.set_user_by_id(employer.id, user_auth_data)
|
||||
|
||||
# Mark as verified
|
||||
await database.mark_email_verified(request.token)
|
||||
|
||||
logger.info(f"✅ Email verified and account activated for: {verification_data['email']}")
|
||||
|
||||
return create_success_response({
|
||||
"message": "Email verified successfully! Your account is now active.",
|
||||
"accountActivated": True,
|
||||
"userType": user_type
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Email verification error: {e}")
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content=create_error_response("VERIFICATION_FAILED", "Failed to verify email")
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user