This commit is contained in:
Andras Schmelczer 2026-07-12 20:30:19 +01:00
parent 6df2812a4e
commit 9e4e65fa2a
35 changed files with 1172 additions and 70 deletions

View file

@ -32,6 +32,16 @@ function authRefreshInvalidated(error: unknown): boolean {
return status === 401 || status === 403;
}
/**
* Normalize an email so login and registration are case- and
* whitespace-insensitive: ` John@Example.com` and `john@example.com`
* resolve to the same account. Applied at the PocketBase boundary rather
* than on the input field, so the user still sees exactly what they typed.
*/
function normalizeEmail(email: string): string {
return email.trim().toLowerCase();
}
export function useAuth() {
const { t } = useTranslation();
const [user, setUser] = useState<AuthUser | null>(() => {
@ -62,7 +72,9 @@ export function useAuth() {
setError(null);
setErrorAction(null);
try {
const result = await pb.collection('users').authWithPassword(email, password);
const result = await pb
.collection('users')
.authWithPassword(normalizeEmail(email), password);
setUser(recordToUser(result.record));
trackEvent('Login', { method: 'email' });
} catch (err) {
@ -82,12 +94,13 @@ export function useAuth() {
setLoading(true);
setError(null);
setErrorAction(null);
const normalizedEmail = normalizeEmail(email);
try {
// Step 1: create the account. A failure here means nothing was created,
// so surface a friendly, field-aware message (e.g. "email already exists").
try {
await pb.collection('users').create({
email,
email: normalizedEmail,
password,
passwordConfirm: password,
newsletter: true,
@ -103,7 +116,7 @@ export function useAuth() {
// orphaned: tell the user it was created and let them log in with the
// same credentials, instead of a misleading "registration failed".
try {
const result = await pb.collection('users').authWithPassword(email, password);
const result = await pb.collection('users').authWithPassword(normalizedEmail, password);
setUser(recordToUser(result.record));
trackEvent('Register');
} catch (err) {
@ -159,7 +172,7 @@ export function useAuth() {
setError(null);
setErrorAction(null);
try {
await pb.collection('users').requestPasswordReset(email);
await pb.collection('users').requestPasswordReset(normalizeEmail(email));
} catch (err) {
const { message, action } = resolveAuthError(err, 'reset', t);
setError(message);
@ -172,6 +185,27 @@ export function useAuth() {
[t]
);
const confirmPasswordReset = useCallback(
async (token: string, password: string) => {
setLoading(true);
setError(null);
setErrorAction(null);
try {
// PocketBase requires the password twice; the reset form uses a single
// (show/hide) field, so both arguments are the same value.
await pb.collection('users').confirmPasswordReset(token, password, password);
} catch (err) {
const { message, action } = resolveAuthError(err, 'confirmReset', t);
setError(message);
setErrorAction(action);
throw err;
} finally {
setLoading(false);
}
},
[t]
);
const refreshAuth = useCallback(async () => {
setLoading(true);
setError(null);
@ -208,6 +242,7 @@ export function useAuth() {
loginWithOAuth,
logout,
requestPasswordReset,
confirmPasswordReset,
refreshAuth,
clearError,
};