From 7191bc6791efcdc763baed975bf6cc20d2c54404 Mon Sep 17 00:00:00 2001 From: Estifo77 <139631617+Estifo77@users.noreply.github.com> Date: Wed, 22 Jul 2026 14:46:38 +0300 Subject: [PATCH] refactor: standardize import quotes and improve cookie management in auth storage --- libs/auth/src/lib/pages/LoginPage.tsx | 77 +++++++++++++---------- libs/auth/src/lib/store/auth.slice.ts | 24 ++++++-- libs/auth/src/lib/utils/auth-storage.ts | 81 ++++++++++++++----------- 3 files changed, 109 insertions(+), 73 deletions(-) diff --git a/libs/auth/src/lib/pages/LoginPage.tsx b/libs/auth/src/lib/pages/LoginPage.tsx index 142c7cdac..9be1b978b 100644 --- a/libs/auth/src/lib/pages/LoginPage.tsx +++ b/libs/auth/src/lib/pages/LoginPage.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState } from "react"; import { Alert, Anchor, @@ -11,29 +11,35 @@ import { Text, TextInput, Title, -} from '@mantine/core'; +} from "@mantine/core"; import { IconArrowRight, IconDeviceMobile, IconLock, IconMail, -} from '@tabler/icons-react'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { z } from 'zod'; -import { useNavigate, Link } from 'react-router-dom'; -import { useDispatch } from 'react-redux'; -import { useApiMutation } from '@ema-platform/api'; -import { notify } from '@ema-platform/ui'; -import { AuthShell } from '../components/AuthShell'; -import { loginSuccess, setUser, setCurrentProfile } from '../store/auth.slice'; -import type { LoginPayload, AuthUser, CurrentProfile } from '../types/auth.types'; -import { useAuthConfig } from '../AuthConfig'; -import { authStorage } from '../utils/auth-storage'; +} from "@tabler/icons-react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { useNavigate, Link } from "react-router-dom"; +import { useDispatch } from "react-redux"; +import { useApiMutation } from "@ema-platform/api"; +import { notify } from "@ema-platform/ui"; +import { AuthShell } from "../components/AuthShell"; +import { loginSuccess, setUser, setCurrentProfile } from "../store/auth.slice"; +import type { + LoginPayload, + AuthUser, + CurrentProfile, +} from "../types/auth.types"; +import { useAuthConfig } from "../AuthConfig"; +import { authStorage } from "../utils/auth-storage"; const schema = z.object({ - email: z.string().email({ message: 'Enter a valid email' }), - password: z.string().min(5, { message: 'Password must be at least 6 characters' }), + email: z.string().email({ message: "Enter a valid email" }), + password: z + .string() + .min(5, { message: "Password must be at least 6 characters" }), }); type FormValues = z.infer; @@ -48,7 +54,10 @@ export function LoginPage() { const [serverError, setServerError] = useState(null); const [loginTrigger] = useApiMutation(); const [meTrigger] = useApiMutation(); - const [profileCheckTrigger] = useApiMutation<{ total: number; items: CurrentProfile[] }>(); + const [profileCheckTrigger] = useApiMutation<{ + total: number; + items: CurrentProfile[]; + }>(); const { register, @@ -62,15 +71,15 @@ export function LoginPage() { setIsLoading(true); try { const data = await loginTrigger({ - url: '/auth/login', - method: 'POST', + url: "/auth/login", + method: "POST", body: values, }).unwrap(); dispatch(loginSuccess(data)); const me = await meTrigger({ - url: '/auth/me', - method: 'GET', + url: "/auth/me", + method: "GET", }).unwrap(); dispatch(setUser(me)); @@ -79,7 +88,7 @@ export function LoginPage() { const q = `w=user_id:=:${me.id}&i=user,address,profession`; const result = await profileCheckTrigger({ url: `/profiles?q=${encodeURIComponent(q)}`, - method: 'GET', + method: "GET", }).unwrap(); if (result.total > 0 && result.items.length > 0) { const profile = result.items[0]; @@ -92,7 +101,7 @@ export function LoginPage() { } if (!me.isPhoneNumberVerified) { - navigate('/otp-verify', { + navigate("/otp-verify", { state: { email: me.email, phoneNumber: me.phoneNumber, @@ -103,7 +112,7 @@ export function LoginPage() { } if (!hasProfile) { - navigate('/profile-setup'); + navigate("/profile-setup"); return; } @@ -111,10 +120,11 @@ export function LoginPage() { } catch (err: unknown) { const msg = (err as { data?: { message?: string } })?.data?.message ?? - (err instanceof Error ? err.message : 'Something went wrong'); + (err instanceof Error ? err.message : "Something went wrong"); setServerError(msg); notify.error(msg); } finally { + localStorage.setItem("rememberMe", String(rememberMe)); setIsLoading(false); } }; @@ -132,7 +142,12 @@ export function LoginPage() { {serverError && ( - setServerError(null)}> + setServerError(null)} + > {serverError} )} @@ -145,7 +160,7 @@ export function LoginPage() { size="md" leftSection={} error={errors.email?.message} - {...register('email')} + {...register("email")} /> } error={errors.password?.message} - {...register('password')} + {...register("password")} /> @@ -194,14 +209,14 @@ export function LoginPage() { fullWidth size="md" leftSection={} - onClick={() => notify.info('Phone sign-in is coming soon.')} + onClick={() => notify.info("Phone sign-in is coming soon.")} > Sign in with phone number {enableSignup && ( - Don't have an account?{' '} + Don't have an account?{" "} Create one diff --git a/libs/auth/src/lib/store/auth.slice.ts b/libs/auth/src/lib/store/auth.slice.ts index 9b64d9475..5ae8afb6c 100644 --- a/libs/auth/src/lib/store/auth.slice.ts +++ b/libs/auth/src/lib/store/auth.slice.ts @@ -1,6 +1,11 @@ -import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; -import type { AuthState, AuthUser, CurrentProfile, LoginPayload } from '../types/auth.types'; -import { authStorage, clearAllCookies } from '../utils/auth-storage'; +import { createSlice, type PayloadAction } from "@reduxjs/toolkit"; +import type { + AuthState, + AuthUser, + CurrentProfile, + LoginPayload, +} from "../types/auth.types"; +import { authStorage } from "../utils/auth-storage"; const initialState: AuthState = { user: null, @@ -10,7 +15,7 @@ const initialState: AuthState = { }; const authSlice = createSlice({ - name: 'auth', + name: "auth", initialState, reducers: { loginSuccess(state, action: PayloadAction) { @@ -37,7 +42,7 @@ const authSlice = createSlice({ state.isAuthenticated = false; state.currentProfile = null; authStorage.clear(); - clearAllCookies() + authStorage.clear(); }, hydrateAuth(state) { const token = authStorage.getToken(); @@ -55,5 +60,12 @@ const authSlice = createSlice({ }, }); -export const { loginSuccess, setUser, setCurrentProfile, clearCurrentProfile, logout, hydrateAuth } = authSlice.actions; +export const { + loginSuccess, + setUser, + setCurrentProfile, + clearCurrentProfile, + logout, + hydrateAuth, +} = authSlice.actions; export const authReducer = authSlice.reducer; diff --git a/libs/auth/src/lib/utils/auth-storage.ts b/libs/auth/src/lib/utils/auth-storage.ts index 9354d5474..27e546b4f 100644 --- a/libs/auth/src/lib/utils/auth-storage.ts +++ b/libs/auth/src/lib/utils/auth-storage.ts @@ -1,35 +1,45 @@ -import Cookies from 'js-cookie'; - -const COOKIE_EXPIRES_DAYS = 7; // ponytail: cookie lifetime knob; JWT expiry + refresh govern real auth - +import Cookies from "js-cookie"; +const rememberMe = localStorage.getItem("rememberMe") === "true"; +const COOKIE_EXPIRES_DAYS = rememberMe ? 15 : 1; interface Backend { get(k: string): string | null; set(k: string, v: string): void; remove(k: string): void; } -const localBackend: Backend = { +const localStore: Backend = { get: (k) => localStorage.getItem(k), set: (k, v) => localStorage.setItem(k, v), remove: (k) => localStorage.removeItem(k), }; -const cookieBackend: Backend = { +const cookieStore: Backend = { get: (k) => Cookies.get(k) ?? null, - // ponytail: 4KB/cookie cap — auth-user/current-profile JSON must stay under it + set: (k, v) => - Cookies.set(k, v, { path: '/', secure: true, sameSite: 'strict', expires: COOKIE_EXPIRES_DAYS }), - remove: (k) => Cookies.remove(k, { path: '/' }), + Cookies.set(k, v, { + path: "/", + secure: true, + sameSite: "strict", + expires: COOKIE_EXPIRES_DAYS, + }), + remove: (k) => Cookies.remove(k, { path: "/" }), }; -const AUTH_KEY_NAMES = ['auth-token', 'refresh-token', 'auth-user', 'profile-id', 'current-profile']; +const AUTH_KEY_NAMES = [ + "auth-token", + "refresh-token", + "auth-user", + "profile-id", + "current-profile", +]; -let _prefix = 'ema-auth'; -let backend: Backend = localBackend; +let _prefix = "ema-auth"; +let backend: Backend = localStore; export function configureAuthStorage(prefix: string, useCookies = false) { _prefix = prefix; - backend = useCookies ? cookieBackend : localBackend; + backend = useCookies ? cookieStore : localStore; if (useCookies) { // one-time cleanup: drop pre-migration localStorage leftovers so tokens live in cookies only AUTH_KEY_NAMES.forEach((k) => localStorage.removeItem(`${prefix}-${k}`)); @@ -41,41 +51,40 @@ function key(k: string) { } export const authStorage = { - getToken: () => backend.get(key('auth-token')) ?? undefined, - setToken: (token: string) => backend.set(key('auth-token'), token), - getRefreshToken: () => backend.get(key('refresh-token')) ?? undefined, - setRefreshToken: (t: string) => backend.set(key('refresh-token'), t), + getToken: () => backend.get(key("auth-token")) ?? undefined, + setToken: (token: string) => backend.set(key("auth-token"), token), + getRefreshToken: () => backend.get(key("refresh-token")) ?? undefined, + setRefreshToken: (t: string) => backend.set(key("refresh-token"), t), getUser: (): T | null => { try { - return JSON.parse(backend.get(key('auth-user')) ?? 'null') as T | null; + return JSON.parse(backend.get(key("auth-user")) ?? "null") as T | null; } catch { return null; } }, - setUser: (u: T) => backend.set(key('auth-user'), JSON.stringify(u)), - getProfileId: () => backend.get(key('profile-id')) ?? undefined, - setProfileId: (id: string) => backend.set(key('profile-id'), id), + setUser: (u: T) => backend.set(key("auth-user"), JSON.stringify(u)), + getProfileId: () => backend.get(key("profile-id")) ?? undefined, + setProfileId: (id: string) => backend.set(key("profile-id"), id), getProfile: (): T | null => { try { - return JSON.parse(backend.get(key('current-profile')) ?? 'null') as T | null; + return JSON.parse( + backend.get(key("current-profile")) ?? "null", + ) as T | null; } catch { return null; } }, - setProfile: (p: T) => backend.set(key('current-profile'), JSON.stringify(p)), - removeProfile: () => backend.remove(key('current-profile')), + setProfile: (p: T) => + backend.set(key("current-profile"), JSON.stringify(p)), + removeProfile: () => backend.remove(key("current-profile")), clear: () => { - [key('auth-token'), key('refresh-token'), key('auth-user'), key('profile-id'), key('current-profile')].forEach((k) => - backend.remove(k), - ); - Cookies.remove('auth-token', { path: '/' }); + [ + key("auth-token"), + key("refresh-token"), + key("auth-user"), + key("profile-id"), + key("current-profile"), + ].forEach((k) => backend.remove(k)); + Cookies.remove("auth-token", { path: "/" }); }, }; - -export const clearAllCookies = () => { - const cookies = Cookies.get(); - Object.keys(cookies).forEach((name) => { - Cookies.remove(name); - Cookies.remove(name, { path: '/' }); - }); -};