import Cookies from "js-cookie"; import { getCustomerByUserId, getOwnerByUserId, loginWithEmail, loginWithPhone } from "../utils/api"; /** * AuthService * Manages authentication tokens and user role detection via JWT decoding. * * Roles (from JWT claims): * - Owner: roles array contains "Owner" * - Customer: authenticated but no "Owner" role * - Guest: no token * * Methods: * addToken(token) — store JWT token * getToken() — retrieve JWT token * deleteToken() — remove JWT token * decodeToken() — decode JWT payload * getUser() — get decoded user info * getRoles() — get roles array from JWT * isOwner() — check if user has Owner role * isCustomer() — check if user is authenticated but not Owner * isGuest() — check if no token exists * isAuthenticated() — check if token exists */ const TOKEN_KEY = "auth_token"; const USER_KEY = "cached_user"; const isSecureContext = () => typeof window !== "undefined" && window.location?.protocol === "https:"; const AuthService = Object.freeze({ addToken(token) { if (!token || typeof token !== "string") return; Cookies.set(TOKEN_KEY, token, { expires: 7, secure: isSecureContext(), sameSite: "lax", path: "/", }); }, getToken() { if (typeof window === "undefined") return null; return Cookies.get(TOKEN_KEY); }, deleteToken() { Cookies.remove(USER_KEY); Cookies.remove(TOKEN_KEY); }, /** * Cache full user profile (from API) * @param {object} user — { name, email, phone, ... } */ cacheUser(user) { Cookies.set(USER_KEY, JSON.stringify(user), { expires: 7, secure: isSecureContext(), sameSite: "lax", path: "/", }); }, /** * Get cached user profile * @returns {object|null} */ getCachedUser() { const user = Cookies.get(USER_KEY); if (!user) return null; try { return JSON.parse(user); } catch { return null; } }, /** * Decode JWT payload (base64) * @returns {object|null} */ decodeToken() { const token = this.getToken(); if (!token) return null; try { const payload = token.split(".")[1]; return JSON.parse(atob(payload)); } catch { return null; } }, /** * Extract user info from JWT * @returns {object|null} — { id, name, email, phone, roles } */ getUser() { const payload = this.decodeToken(); if (!payload) return null; const cached = this.getCachedUser(); return { id: payload["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"] || payload.sub || null, name: cached?.name || payload["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"] || null, email: cached?.email || payload["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"] || null, phone: cached?.phone || payload["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone"] || null, roles: cached?.roles, }; }, /** * Get current authenticated user id * @returns {number|string|null} */ getUserId() { const user = this.getUser(); if (!user?.id) return null; const parsedId = Number(user.id); return Number.isFinite(parsedId) ? parsedId : user.id; }, /** * Get roles array from JWT * @returns {string[]} */ getRoles() { const payload = this.decodeToken(); if (!payload) return []; const roles = payload["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]; if (Array.isArray(roles)) return roles; if (typeof roles === "string") return [roles]; return []; }, async cacheCurrentUser(getOwnerByUserId, getCustomerByUserId) { const authUser = this.getUser(); if (!authUser?.id) { return; } const fetchFn = this.isOwner() ? getOwnerByUserId : getCustomerByUserId; const profile = await fetchFn(authUser.id); this.cacheUser({ name: profile.firstName + profile.lastName, email: profile.email, phone: profile.phoneNumber, roles: this.getRoles(), }); }, /** * User has Owner role * @returns {boolean} */ isOwner() { return this.getRoles().includes("Owner"); }, /** * User has RealEstateAgent role * @returns {boolean} */ isAgent() { return this.getRoles().includes("RealEstateAgent"); }, /** * Authenticated user without Owner role (i.e. customer) * @returns {boolean} */ isCustomer() { return this.isAuthenticated() && !this.isOwner() && !this.isAgent(); }, /** * No token — guest user * @returns {boolean} */ isGuest() { return !this.getToken(); }, /** * Token exists * @returns {boolean} */ isAuthenticated() { return !!this.getToken(); }, async login({ loginMethod, credential, password }) { const loginFn = loginMethod === "email" ? loginWithEmail : loginWithPhone; const result = await loginFn(credential, password); switch (result.status) { case 200: { const token = typeof result.data === "string" ? result.data : result.data.token; this.addToken(token); await this.cacheCurrentUser(getOwnerByUserId, getCustomerByUserId); return { type: "SUCCESS", }; } case 206: { const token = typeof result.data === "string" ? result.data : result.data.token; this.addToken(token); if (loginMethod === "email") await sendEmailOTP(); else await sendPhoneOTP(); return { type: "OTP_REQUIRED", }; } default: return { type: "ERROR", result, }; } }, }); export default AuthService;