Repository navigation
Expand file tree
/
Copy pathauthValidation.ts
More file actions
167 lines (137 loc) · 4.87 KB
/
Copy pathauthValidation.ts
File metadata and controls
167 lines (137 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
export const PASSWORD_MIN_LENGTH = 6;
export const DISPLAY_NAME_MAX_LENGTH = 60;
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export type AuthContext = "default" | "login" | "signup" | "passwordReset";
export type LoginFormValues = {
email: string;
password: string;
};
export type SignupFormValues = LoginFormValues & {
name: string;
};
export type LoginFormErrors = {
email: string;
password: string;
};
export type SignupFormErrors = LoginFormErrors & {
name: string;
};
export type PasswordStrength = {
level: 0 | 1 | 2 | 3 | 4;
label: "" | "Weak" | "Fair" | "Good" | "Strong";
};
type AuthErrorLike = {
code?: string;
message?: string;
} | null | undefined;
function sanitizeFirebaseMessage(message?: string): string {
return (message || "")
.replace("Firebase: ", "")
.replace(/\(auth\/.*?\)\.?/g, "")
.trim();
}
export function validateEmailAddress(email: string): string {
if (!email) {
return "Enter your email address.";
}
if (!EMAIL_PATTERN.test(email)) {
return "Enter a valid email address.";
}
return "";
}
export function normalizeDisplayName(name: string): string {
return (name || "").trim().replace(/\s+/g, " ");
}
export function validateDisplayName(name: string): string {
const normalizedName = normalizeDisplayName(name);
if (!normalizedName) {
return "Enter your full name.";
}
if (normalizedName.length < 2) {
return "Use at least 2 characters for your name.";
}
if (normalizedName.length > DISPLAY_NAME_MAX_LENGTH) {
return `Use ${DISPLAY_NAME_MAX_LENGTH} characters or fewer for your name.`;
}
return "";
}
export function validateLoginForm({ email, password }: LoginFormValues): LoginFormErrors {
return {
email: validateEmailAddress(email),
password: password ? "" : "Enter your password.",
};
}
export function validateSignupForm({
name,
email,
password,
}: SignupFormValues): SignupFormErrors {
let passwordError = "";
if (!password) {
passwordError = "Create a password to continue.";
} else if (password.length < PASSWORD_MIN_LENGTH) {
passwordError = `Use at least ${PASSWORD_MIN_LENGTH} characters for your password.`;
}
return {
name: validateDisplayName(name),
email: validateEmailAddress(email),
password: passwordError,
};
}
export function getPasswordStrength(password: string): PasswordStrength {
if (!password) {
return { level: 0, label: "" };
}
let score = 0;
if (password.length >= PASSWORD_MIN_LENGTH) score += 1;
if (password.length >= 10) score += 1;
if (/[A-Z]/.test(password) || /[0-9]/.test(password)) score += 1;
if (/[^A-Za-z0-9]/.test(password)) score += 1;
if (score <= 1) return { level: 1, label: "Weak" };
if (score === 2) return { level: 2, label: "Fair" };
if (score === 3) return { level: 3, label: "Good" };
return { level: 4, label: "Strong" };
}
export function hasValidationErrors(errors: Record<string, string>): boolean {
return Object.values(errors).some(Boolean);
}
export function formatAuthMessage(error: AuthErrorLike, context: AuthContext = "default"): string {
const code = error?.code || "";
switch (code) {
case "auth/invalid-email":
case "auth/missing-email":
return "Enter a valid email address.";
case "auth/missing-password":
return context === "signup"
? "Create a password to continue."
: "Enter your password to continue.";
case "auth/email-already-in-use":
return "An account with this email already exists. Sign in instead or reset your password.";
case "auth/weak-password":
return `Use at least ${PASSWORD_MIN_LENGTH} characters for your password.`;
case "auth/user-not-found":
case "auth/wrong-password":
case "auth/invalid-credential":
return context === "passwordReset"
? "We could not send a reset link with those details. Check the email address and try again."
: "Incorrect email or password. Please try again.";
case "auth/user-disabled":
return "This account has been disabled. Contact support if you need help.";
case "auth/too-many-requests":
return context === "passwordReset"
? "Too many reset requests were made. Please wait a moment and try again."
: "Too many attempts were made. Please wait a moment and try again.";
case "auth/network-request-failed":
return "We could not reach the server. Check your internet connection and try again.";
case "auth/operation-not-allowed":
return context === "signup"
? "Account creation is currently unavailable. Please contact support."
: "Email sign-in is currently unavailable. Please contact support.";
case "auth/quota-exceeded":
return "We could not complete that request right now. Please try again shortly.";
default: {
const fallback = sanitizeFirebaseMessage(error?.message);
return fallback || "Something went wrong. Please try again.";
}
}
}