UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

190 lines 8.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SupabaseClient = void 0; const jose_1 = require("jose"); const Logger_1 = require("../utils/Logger"); const JsonUtils_1 = require("../utils/JsonUtils"); /** * A utility class that, given a Supabase service role key, can generate a custom JWT to impersonate * a user. */ class SupabaseTokenImpersonator { /** * Constructs a new SupabaseTokenImpersonator. * * @param supabasePublishableApiKey - This is a non-secret API key to be sent as the `apikey` * header field when fetching user data. * @param supabaseJwtSecretKey - The Supabase JWT secret key (from your project's API settings). * This key should be handled with utmost care. * @param supabaseProjectUrl - The Supabase project's base URL, for example: * "https://<your-project>.supabase.co" */ constructor(supabasePublishableApiKey, supabaseJwtSecretKey, supabaseProjectUrl) { this.supabasePublishableApiKey = supabasePublishableApiKey; this.supabaseJwtSecretKey = new TextEncoder().encode(supabaseJwtSecretKey); this.authApiUrl = `${supabaseProjectUrl}/auth/v1/user`; } /** * Creates a custom JWT that impersonates the user identified by the provided access token. * * 1. It verifies the user's token by making a request to /auth/v1/user. * 2. If valid, Supabase returns the user's profile; we extract the user ID from there. * 3. We then return a new custom JWT signed with the service role key. * * @param userAccessToken - The user's existing JWT access token. * @returns A custom JWT signed with the service role key, containing the user's ID as the subject. * @throws Error If the provided userAccessToken is invalid/expired or if there are any issues * during the process. */ async createCustomUserToken(userAccessToken) { // Validate the user's token by calling Supabase. const userId = await this.fetchUserIdFromSupabase(userAccessToken); if (!userId) { throw new Error('Unable to fetch a valid user ID from the provided token.'); } const now = Math.floor(Date.now() / 1000); // NOTE: This expiry is coupled with the expiry defined in the // `AccessTokenManager::getValidAccessToken` method. const exp = now + 3600; // 1 hour expiration const jwt = new jose_1.SignJWT({ role: 'authenticated' }) .setProtectedHeader({ alg: 'HS256' }) .setSubject(userId) .setIssuedAt(now) .setExpirationTime(exp) .setAudience('authenticated') .setIssuer('https://www.donobu.com'); return jwt.sign(this.supabaseJwtSecretKey); } /** * Uses the user token in a Bearer header to call Supabase's /auth/v1/user endpoint. If * successful, returns the user's ID from the JSON response. * * @param userAccessToken - The user's JWT to validate. * @returns The user's ID if successful. * @throws Error If the user token is invalid/expired or if there are any issues during the process. */ async fetchUserIdFromSupabase(userAccessToken) { try { const response = await fetch(this.authApiUrl, { headers: { apikey: this.supabasePublishableApiKey, Authorization: `Bearer ${userAccessToken}`, }, }); if (!response.ok) { throw new Error(`User token is invalid or expired. HTTP status: ${response.status}`); } const userData = await response.json(); if (!userData?.id) { throw new Error("Failed to retrieve 'id' from user details"); } return userData.id; } catch (error) { Logger_1.appLogger.error('Error fetching user ID from Supabase:', error); throw error; } } } class SupabaseClient { constructor(tokenManager, supabaseBaseUrl, supabasePublishableApiKey) { this.tokenManager = tokenManager; this.supabaseBaseUrl = supabaseBaseUrl; this.supabasePublishableApiKey = supabasePublishableApiKey; } static createClient(userAccessToken, supabaseJwtSecretKey) { return this.createClientWithConfig(userAccessToken, this.DEFAULT_SUPABASE_URL, this.DEFAULT_SUPABASE_PUBLISHABLE_API_KEY, supabaseJwtSecretKey); } static createClientWithConfig(userAccessToken, supabaseBaseUrl, supabasePublishableApiKey, supabaseJwtSecretKey) { const tokenManager = new AccessTokenManager(userAccessToken, supabaseBaseUrl, supabasePublishableApiKey, supabaseJwtSecretKey); return new SupabaseClient(tokenManager, supabaseBaseUrl, supabasePublishableApiKey); } /** * Makes a request with automatic token refresh handling. */ async makeRequest(path, method, customHeaders, body) { const makeRequestWithToken = async (token) => { const headers = new Headers({ 'Content-Type': 'application/json', Prefer: 'resolution=merge-duplicates', apikey: this.supabasePublishableApiKey, Authorization: `Bearer ${token}`, }); // Add custom headers if provided. if (customHeaders) { Object.entries(customHeaders).forEach(([key, value]) => { headers.append(key, value); }); } return fetch(`${this.supabaseBaseUrl}${path}`, { method, headers, body, }); }; // Initial request. let token = await this.tokenManager.getValidAccessToken(); let response = await makeRequestWithToken(token); // Handle 401 with retry. if (response.status === 401) { // Get fresh token and retry once. token = await this.tokenManager.getValidAccessToken(); response = await makeRequestWithToken(token); } return response; } async executeGet(path, headers) { try { // Modify makeRequest to accept custom headers const response = await this.makeRequest(path, 'GET', headers); if (response.status === 404) { return null; } if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.text(); } catch (error) { Logger_1.appLogger.error(`GET request failed: ${JSON.stringify(JsonUtils_1.JsonUtils.objectToJson(error))}`); throw error; } } async executeMethod(path, method, body) { try { const response = await this.makeRequest(path, method, undefined, body); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.text(); } catch (error) { Logger_1.appLogger.error(`Request failed: ${error}`); throw error; } } } exports.SupabaseClient = SupabaseClient; SupabaseClient.DEFAULT_SUPABASE_URL = 'https://pxxliqamgggjpeltoeus.supabase.co'; SupabaseClient.DEFAULT_SUPABASE_PUBLISHABLE_API_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB4eGxpcWFtZ2dnanBlbHRvZXVzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MjUyOTA1MjMsImV4cCI6MjA0MDg2NjUyM30.iQrTLwdzVO1zN7mawZ7hSUnZP-GrVU_vrJR4iACML3A'; class AccessTokenManager { constructor(originalUserAccessToken, supabaseBaseUrl, supabasePublishableApiKey, supabaseJwtSecretKey) { this.originalUserAccessToken = originalUserAccessToken; this.supabaseBaseUrl = supabaseBaseUrl; this.supabasePublishableApiKey = supabasePublishableApiKey; this.supabaseJwtSecretKey = supabaseJwtSecretKey; this.accessToken = null; this.accessTokenExpiresAt = new Date(0); } async getValidAccessToken() { if (this.accessToken && new Date() < this.accessTokenExpiresAt) { return this.accessToken; } const tokenImpersonator = new SupabaseTokenImpersonator(this.supabasePublishableApiKey, this.supabaseJwtSecretKey, this.supabaseBaseUrl); this.accessTokenExpiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour from now const tmp = await tokenImpersonator.createCustomUserToken(this.originalUserAccessToken); this.accessToken = tmp; return tmp; } } //# sourceMappingURL=SupabaseClient.js.map