diff --git a/src/lib/auth/user.ts b/src/lib/auth/user.ts index 9e5823d..84f1937 100644 --- a/src/lib/auth/user.ts +++ b/src/lib/auth/user.ts @@ -3,9 +3,10 @@ import { getAuth, onAuthStateChanged, signOut, type User } from 'firebase/auth'; import { doc, getFirestore, serverTimestamp, updateDoc } from 'firebase/firestore'; import { getFirebaseApp } from '../init'; -import { docStore } from '../firestore/stores'; +import { docStore } from '../firestore/stores/doc-store'; import type { IBaseUser } from '../interfaces'; import { setCookie } from '../helpers/cookies'; +import { firebaseConfig } from '../config'; export const authState = writable(undefined, (set) => { if (typeof window !== 'undefined') { @@ -20,8 +21,10 @@ export const authState = writable(undefined, (set) => { /** * Subscribes to current Firebase user, pulls their data from the users collection, caches it to local storage as well as sets a cookie to allow for server-side rendering (not authenticated routes, just basic UI stuff like a name in a header). It also denotes their visit as a `lastVisit` timestamp in Firestore. */ -export function createUserStore({ userKey = 'firebase_user', log = false }) { - const { subscribe, set } = writable(null); +export function createUserStore(options: { userKey?: string; log?: boolean; startWith?: T }) { + const { userKey = `${firebaseConfig.projectId}_firebase_user`, log = false, startWith = null } = options; + + const { subscribe, set } = writable(startWith); let unsub: Unsubscriber; if (typeof window !== 'undefined') { @@ -62,8 +65,9 @@ function cacheUser(user: IBaseUser, userKey: string) { displayName: user.displayName, email: user.email, photoURL: user.photoURL || null, + roles: user.roles || null, }; // Cookies are limited to 4kb, about 1,000-4000 characters - setCookie('user', JSON.stringify(minimalUser), { 'max-age': 31536000 }); + setCookie('user', JSON.stringify(minimalUser), { maxAge: 365 * 24 * 60 * 60 }); } function removeCachedUser(userKey: string) { diff --git a/src/lib/helpers/cookies.ts b/src/lib/helpers/cookies.ts index e6a1218..1678c6c 100644 --- a/src/lib/helpers/cookies.ts +++ b/src/lib/helpers/cookies.ts @@ -16,18 +16,35 @@ export function getCookie(name: string, cookies?) { return decodeURIComponent(cookieValue.trim()); } -export function setCookie(name: string, value, options: any = {}) { - if (options.expires instanceof Date) { - options.expires = options.expires.toUTCString(); - } +export function setCookie(name: string, value: string, options: CookieOptions = {}) { + document.cookie = formatCookie(name, value, options) +} + +interface CookieOptions { + domain?: string; + expires?: string | Date; + httpOnly?: boolean; + maxAge?: number; + path?: string; + sameSite?: 'strict' | 'lax' | 'none'; + secure?: boolean; +} + +function formatCookie(name: string, value: string, options: CookieOptions = {}) { + if (options.expires instanceof Date) + options.expires = options.expires.toUTCString() const updatedCookie = { [encodeURIComponent(name)]: encodeURIComponent(value), sameSite: 'strict', + path: '/', ...options, - }; + } - document.cookie = Object.entries(updatedCookie) + const cookie = Object.entries(updatedCookie) + .filter(([key]) => key !== 'secure') .map((kv) => kv.join('=')) - .join(';'); + .join(';') + + return options.secure === false ? cookie : `${cookie};secure` } diff --git a/src/lib/interfaces/user.interface.ts b/src/lib/interfaces/user.interface.ts index c5823c9..1948da2 100644 --- a/src/lib/interfaces/user.interface.ts +++ b/src/lib/interfaces/user.interface.ts @@ -12,6 +12,7 @@ interface User { providerIds?: SignInMethods[]; // 'emailLink' method will still say 'password' emailVerified?: boolean; emailLink?: boolean; // set to true if they are both a new user and email is verified; this is the only way to distinguish apart from users who use email+pass and then later manually verify their email if the app provides that option. + roles?: { admin?: number }; // not from Firebase - used by 2 projects downstream - most users can ignore this } export type SignInMethods = 'google.com' | 'password' | 'emailLink' | 'facebook.com' | 'github.com' | 'phone' | 'twitter.com'; \ No newline at end of file