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
JavaScript
"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