UNPKG

solobase-js

Version:

A 100% drop-in replacement for the Supabase JavaScript client. Self-hosted Supabase alternative with complete API compatibility.

671 lines 22.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SolobaseAuthAdminClient = exports.SolobaseAuthClient = void 0; class SolobaseAuthClient { constructor(fetch, options = {}, cookieManager) { var _a, _b; this.currentSession = null; this.currentUser = null; this.refreshToken = null; this.refreshTimer = null; this.cookieManager = null; this.onAuthStateChangeCallbacks = []; this.fetch = fetch; this.autoRefreshToken = (_a = options.autoRefreshToken) !== null && _a !== void 0 ? _a : true; this.persistSession = (_b = options.persistSession) !== null && _b !== void 0 ? _b : true; this.cookieManager = cookieManager || null; // Load saved session if persistence is enabled if (this.persistSession) { this.loadSession(); } } /** * Creates a new user with email and password */ async signUp(credentials) { const { email, password, options } = credentials; const response = await this.fetch.post('/auth/signup', { body: { email, password, data: options === null || options === void 0 ? void 0 : options.data, captcha_token: options === null || options === void 0 ? void 0 : options.captchaToken, email_redirect_to: options === null || options === void 0 ? void 0 : options.emailRedirectTo, }, }); if (response.error) { return { data: { user: null, session: null }, error: { message: response.error.message }, }; } const { user, session } = response.data || {}; if (session) { await this.setSession(session); } return { data: { user, session }, error: null, }; } /** * Log in an existing user with email and password */ async signInWithPassword(credentials) { const { email, password, options } = credentials; const response = await this.fetch.post('/auth/login', { body: { email, password, captcha_token: options === null || options === void 0 ? void 0 : options.captchaToken, }, }); if (response.error) { return { data: { user: null, session: null }, error: { message: response.error.message }, }; } const { user, session } = response.data || {}; if (session) { await this.setSession(session); } return { data: { user, session }, error: null, }; } /** * Log in with OAuth provider */ async signInWithOAuth(credentials) { const { provider, options } = credentials; const params = new URLSearchParams({ provider, redirect_to: (options === null || options === void 0 ? void 0 : options.redirectTo) || window.location.origin, }); if (options === null || options === void 0 ? void 0 : options.scopes) { params.set('scopes', options.scopes); } if (options === null || options === void 0 ? void 0 : options.queryParams) { Object.entries(options.queryParams).forEach(([key, value]) => { params.set(key, value); }); } const url = `${this.fetch['baseUrl']}/auth/oauth?${params.toString()}`; if (!(options === null || options === void 0 ? void 0 : options.skipBrowserRedirect) && typeof window !== 'undefined') { window.location.href = url; } return { data: { url }, error: null, }; } /** * Log out the current user */ async signOut() { const response = await this.fetch.post('/auth/logout'); // Clear session regardless of response await this.clearSession(); return { error: response.error ? { message: response.error.message } : null, }; } /** * Get the current user */ async getUser() { var _a; if (!((_a = this.currentSession) === null || _a === void 0 ? void 0 : _a.access_token)) { return { data: { user: null }, error: { message: 'Not authenticated' }, }; } const response = await this.fetch.get('/auth/me'); if (response.error) { return { data: { user: null }, error: { message: response.error.message }, }; } const user = response.data; this.currentUser = user; return { data: { user }, error: null, }; } /** * Get the current session */ async getSession() { if (!this.currentSession) { return { data: { session: null }, error: null, }; } // Check if session is expired if (this.currentSession.expires_at && Date.now() >= this.currentSession.expires_at * 1000) { if (this.autoRefreshToken && this.refreshToken) { const refreshResult = await this.refreshSession(); return { data: { session: refreshResult.data.session }, error: refreshResult.error, }; } else { await this.clearSession(); return { data: { session: null }, error: { message: 'Session expired' }, }; } } return { data: { session: this.currentSession }, error: null, }; } /** * Update user information */ async updateUser(options) { var _a; if (!((_a = this.currentSession) === null || _a === void 0 ? void 0 : _a.access_token)) { return { data: { user: null }, error: { message: 'Not authenticated' }, }; } const response = await this.fetch.put('/auth/me', { body: options, }); if (response.error) { return { data: { user: null }, error: { message: response.error.message }, }; } const user = response.data; this.currentUser = user; this.onAuthStateChangeCallbacks.forEach(callback => { callback('USER_UPDATED', this.currentSession); }); return { data: { user }, error: null, }; } /** * Send password reset email */ async resetPasswordForEmail(options) { var _a, _b; const response = await this.fetch.post('/auth/recover', { body: { email: options.email, captcha_token: (_a = options.options) === null || _a === void 0 ? void 0 : _a.captchaToken, redirect_to: (_b = options.options) === null || _b === void 0 ? void 0 : _b.redirectTo, }, }); return { data: {}, error: response.error ? { message: response.error.message } : null, }; } /** * Verify OTP token (for password reset, email confirmation, etc.) */ async verifyOtp(options) { const response = await this.fetch.post('/auth/verify', { body: { token_hash: options.token_hash, type: options.type, email: options.email, phone: options.phone, }, }); if (response.error) { return { data: { user: null, session: null }, error: { message: response.error.message }, }; } const { user, session } = response.data || {}; if (session) { await this.setSession(session); } return { data: { user, session }, error: null, }; } /** * Verify email address with token */ async verifyEmail(options) { const response = await this.fetch.post('/auth/verify-email', { body: { token: options.token, }, }); if (response.error) { return { data: { user: null, session: null }, error: { message: response.error.message }, }; } const { user, session } = response.data || {}; if (session) { await this.setSession(session); } return { data: { user, session }, error: null, }; } /** * Resend email verification */ async resendVerification(options) { var _a; const response = await this.fetch.post('/auth/resend-verification', { body: { email: options.email, }, }); if (response.error) { return { data: {}, error: { message: response.error.message }, }; } return { data: { message: ((_a = response.data) === null || _a === void 0 ? void 0 : _a.message) || 'Verification email sent' }, error: null, }; } /** * Refresh the current session */ async refreshSession() { if (!this.refreshToken) { return { data: { session: null }, error: { message: 'No refresh token available' }, }; } const response = await this.fetch.post('/auth/refresh', { body: { refresh_token: this.refreshToken, }, }); if (response.error) { await this.clearSession(); return { data: { session: null }, error: { message: response.error.message }, }; } const session = response.data; await this.setSession(session); return { data: { session }, error: null, }; } /** * Listen to auth state changes */ onAuthStateChange(callback) { this.onAuthStateChangeCallbacks.push(callback); // Immediately call with current state if (this.currentSession) { callback('SIGNED_IN', this.currentSession); } return { data: { subscription: { unsubscribe: () => { const index = this.onAuthStateChangeCallbacks.indexOf(callback); if (index > -1) { this.onAuthStateChangeCallbacks.splice(index, 1); } }, }, }, }; } /** * Admin functionality */ get admin() { return new SolobaseAuthAdminClient(this.fetch); } /** * Set session and start auto-refresh if enabled */ async setSession(session) { this.currentSession = session; this.currentUser = session.user; this.refreshToken = session.refresh_token; // Set auth header for future requests this.fetch.setAuth(session.access_token); // Save session if persistence is enabled if (this.persistSession) { if (this.cookieManager) { // Store in secure cookies for SSR const cookieOptions = { maxAge: 60 * 60 * 24 * 7, // 7 days path: '/', secure: typeof window !== 'undefined' && window.location.protocol === 'https:', httpOnly: typeof window === 'undefined', // Only httpOnly on server-side sameSite: 'lax' }; const cookiesToSet = [ { name: 'sb-access-token', value: session.access_token, options: cookieOptions }, ]; if (session.refresh_token) { cookiesToSet.push({ name: 'sb-refresh-token', value: session.refresh_token, options: cookieOptions }); } this.cookieManager.setAll(cookiesToSet); } else if (typeof localStorage !== 'undefined') { // Fallback to localStorage for client-side only localStorage.setItem('solobase.session', JSON.stringify(session)); } } // Setup auto-refresh if (this.autoRefreshToken && session.expires_at) { this.setupRefreshTimer(session.expires_at); } // Notify listeners this.onAuthStateChangeCallbacks.forEach(callback => { callback('SIGNED_IN', session); }); } /** * Clear current session */ async clearSession() { this.currentSession = null; this.currentUser = null; this.refreshToken = null; // Clear auth header this.fetch.setAuth(null); // Clear persisted session if (this.cookieManager) { // Clear cookies this.cookieManager.setAll([ { name: 'sb-access-token', value: '', options: { maxAge: 0, path: '/' } }, { name: 'sb-refresh-token', value: '', options: { maxAge: 0, path: '/' } } ]); } else if (typeof localStorage !== 'undefined') { localStorage.removeItem('solobase.session'); } // Clear refresh timer if (this.refreshTimer) { clearTimeout(this.refreshTimer); this.refreshTimer = null; } // Notify listeners this.onAuthStateChangeCallbacks.forEach(callback => { callback('SIGNED_OUT', null); }); } /** * Load saved session from storage or cookies */ loadSession() { try { let session = null; if (this.cookieManager) { // Try to load from cookies const accessToken = this.cookieManager.get('sb-access-token'); const refreshToken = this.cookieManager.get('sb-refresh-token'); if (accessToken) { // We have a token in cookies, but we need user data // Set the token for requests and let getUser() handle the rest this.fetch.setAuth(accessToken); this.refreshToken = refreshToken; // Reconstruct session object const session = { access_token: accessToken, refresh_token: refreshToken || '', expires_in: 60 * 60 * 24 * 7, // 7 days expires_at: Math.floor(Date.now() / 1000) + (60 * 60 * 24 * 7), token_type: 'bearer', user: null }; this.currentSession = session; // Fetch user data from server to complete the session this.getUser().then(result => { if (result.data.user) { this.currentUser = result.data.user; this.currentSession.user = result.data.user; // Notify listeners that we have a complete session now this.onAuthStateChangeCallbacks.forEach(callback => { callback('SIGNED_IN', this.currentSession); }); } }).catch(() => { // If user fetch fails, clear the invalid session this.clearSession(); }); if (this.autoRefreshToken && session.expires_at) { this.setupRefreshTimer(session.expires_at); } return; // Don't check localStorage if we have cookies } } // Fallback to localStorage if (typeof localStorage !== 'undefined') { const savedSession = localStorage.getItem('solobase.session'); if (savedSession) { session = JSON.parse(savedSession); // Check if session is still valid if (session && (!session.expires_at || Date.now() < session.expires_at * 1000)) { this.currentSession = session; this.currentUser = session.user; this.refreshToken = session.refresh_token; this.fetch.setAuth(session.access_token); if (this.autoRefreshToken && session.expires_at) { this.setupRefreshTimer(session.expires_at); } } else { // Session expired, clear it localStorage.removeItem('solobase.session'); } } } } catch (error) { // Invalid session data, clear it if (this.cookieManager) { this.clearSession(); } else if (typeof localStorage !== 'undefined') { localStorage.removeItem('solobase.session'); } } } /** * Setup automatic token refresh */ setupRefreshTimer(expiresAt) { if (this.refreshTimer) { clearTimeout(this.refreshTimer); } // Refresh 5 minutes before expiry const refreshTime = (expiresAt * 1000) - Date.now() - (5 * 60 * 1000); if (refreshTime > 0) { this.refreshTimer = setTimeout(async () => { await this.refreshSession(); }, refreshTime); } } } exports.SolobaseAuthClient = SolobaseAuthClient; /** * Admin client for managing users and authentication */ class SolobaseAuthAdminClient { constructor(fetch) { this.fetch = fetch; } /** * Create a new user (admin only) */ async createUser(options) { const response = await this.fetch.post('/auth/admin/users', { body: { email: options.email, password: options.password, email_confirm: options.email_confirm, phone_confirm: options.phone_confirm, user_metadata: options.user_metadata, app_metadata: options.app_metadata, }, }); if (response.error) { return { data: { user: null }, error: { message: response.error.message }, }; } return { data: { user: response.data }, error: null, }; } /** * Update a user (admin only) */ async updateUserById(userId, options) { const response = await this.fetch.put(`/auth/admin/users/${userId}`, { body: options, }); if (response.error) { return { data: { user: null }, error: { message: response.error.message }, }; } return { data: { user: response.data }, error: null, }; } /** * Delete a user (admin only) */ async deleteUser(userId) { const response = await this.fetch.delete(`/auth/admin/users/${userId}`); if (response.error) { return { data: { user: null }, error: { message: response.error.message }, }; } return { data: { user: response.data || null }, error: null, }; } /** * Get a user by ID (admin only) */ async getUserById(userId) { const response = await this.fetch.get(`/auth/admin/users/${userId}`); if (response.error) { return { data: { user: null }, error: { message: response.error.message }, }; } return { data: { user: response.data }, error: null, }; } /** * List users (admin only) */ async listUsers(options = {}) { var _a, _b; const params = new URLSearchParams(); if (options.page) { params.append('page', options.page.toString()); } if (options.per_page) { params.append('per_page', options.per_page.toString()); } const queryString = params.toString(); const url = `/auth/admin/users${queryString ? `?${queryString}` : ''}`; const response = await this.fetch.get(url); if (response.error) { return { data: { users: [] }, error: { message: response.error.message }, }; } return { data: { users: ((_a = response.data) === null || _a === void 0 ? void 0 : _a.users) || response.data || [], count: (_b = response.data) === null || _b === void 0 ? void 0 : _b.count, }, error: null, }; } /** * Generate a password reset link for a user (admin only) */ async generateLink(options) { const response = await this.fetch.post('/auth/admin/generate_link', { body: options, }); if (response.error) { return { data: { user: null }, error: { message: response.error.message }, }; } return { data: response.data, error: null, }; } /** * Invite a user by email (admin only) */ async inviteUserByEmail(email, options = {}) { const response = await this.fetch.post('/auth/admin/invite', { body: { email, data: options.data, redirect_to: options.redirect_to, }, }); if (response.error) { return { data: { user: null }, error: { message: response.error.message }, }; } return { data: { user: response.data }, error: null, }; } } exports.SolobaseAuthAdminClient = SolobaseAuthAdminClient; //# sourceMappingURL=SolobaseAuthClient.js.map