feat: implement tm list remote (#1185)
This commit is contained in:
155
packages/tm-core/src/auth/supabase-session-storage.ts
Normal file
155
packages/tm-core/src/auth/supabase-session-storage.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Custom storage adapter for Supabase Auth sessions in CLI environment
|
||||
* Implements the SupportedStorage interface required by Supabase Auth
|
||||
*
|
||||
* This adapter bridges Supabase's session management with our existing
|
||||
* auth.json credential storage, maintaining backward compatibility
|
||||
*/
|
||||
|
||||
import { SupportedStorage } from '@supabase/supabase-js';
|
||||
import { CredentialStore } from './credential-store';
|
||||
import { AuthCredentials } from './types';
|
||||
import { getLogger } from '../logger';
|
||||
|
||||
const STORAGE_KEY = 'sb-taskmaster-auth-token';
|
||||
|
||||
export class SupabaseSessionStorage implements SupportedStorage {
|
||||
private store: CredentialStore;
|
||||
private logger = getLogger('SupabaseSessionStorage');
|
||||
|
||||
constructor(store: CredentialStore) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Supabase session object from our credentials
|
||||
*/
|
||||
private buildSessionFromCredentials(credentials: AuthCredentials): any {
|
||||
// Create a session object that Supabase expects
|
||||
const session = {
|
||||
access_token: credentials.token,
|
||||
refresh_token: credentials.refreshToken || '',
|
||||
expires_at: credentials.expiresAt
|
||||
? Math.floor(new Date(credentials.expiresAt).getTime() / 1000)
|
||||
: Math.floor(Date.now() / 1000) + 3600, // Default to 1 hour
|
||||
token_type: 'bearer',
|
||||
user: {
|
||||
id: credentials.userId,
|
||||
email: credentials.email || '',
|
||||
aud: 'authenticated',
|
||||
role: 'authenticated',
|
||||
email_confirmed_at: new Date().toISOString(),
|
||||
app_metadata: {},
|
||||
user_metadata: {},
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a Supabase session back to our credentials
|
||||
*/
|
||||
private parseSessionToCredentials(
|
||||
sessionData: any
|
||||
): Partial<AuthCredentials> {
|
||||
try {
|
||||
const session = JSON.parse(sessionData);
|
||||
return {
|
||||
token: session.access_token,
|
||||
refreshToken: session.refresh_token,
|
||||
userId: session.user?.id || 'unknown',
|
||||
email: session.user?.email,
|
||||
expiresAt: session.expires_at
|
||||
? new Date(session.expires_at * 1000).toISOString()
|
||||
: undefined
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Error parsing session:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item from storage - Supabase will request the session with a specific key
|
||||
*/
|
||||
getItem(key: string): string | null {
|
||||
// Supabase uses a specific key pattern for sessions
|
||||
if (key === STORAGE_KEY || key.includes('auth-token')) {
|
||||
try {
|
||||
const credentials = this.store.getCredentials({ allowExpired: true });
|
||||
if (credentials && credentials.token) {
|
||||
// Build and return a session object from our stored credentials
|
||||
const session = this.buildSessionFromCredentials(credentials);
|
||||
return JSON.stringify(session);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Error getting session:', error);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item in storage - Supabase will store the session with a specific key
|
||||
*/
|
||||
setItem(key: string, value: string): void {
|
||||
// Only handle Supabase session keys
|
||||
if (key === STORAGE_KEY || key.includes('auth-token')) {
|
||||
try {
|
||||
// Parse the session and update our credentials
|
||||
const sessionUpdates = this.parseSessionToCredentials(value);
|
||||
const existingCredentials = this.store.getCredentials({
|
||||
allowExpired: true
|
||||
});
|
||||
|
||||
if (sessionUpdates.token) {
|
||||
const updatedCredentials: AuthCredentials = {
|
||||
...existingCredentials,
|
||||
...sessionUpdates,
|
||||
savedAt: new Date().toISOString(),
|
||||
selectedContext: existingCredentials?.selectedContext
|
||||
} as AuthCredentials;
|
||||
|
||||
this.store.saveCredentials(updatedCredentials);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Error setting session:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from storage - Called when signing out
|
||||
*/
|
||||
removeItem(key: string): void {
|
||||
if (key === STORAGE_KEY || key.includes('auth-token')) {
|
||||
// Don't actually remove credentials, just clear the tokens
|
||||
// This preserves other data like selectedContext
|
||||
try {
|
||||
const credentials = this.store.getCredentials({ allowExpired: true });
|
||||
if (credentials) {
|
||||
// Keep context but clear auth tokens
|
||||
const clearedCredentials: AuthCredentials = {
|
||||
...credentials,
|
||||
token: '',
|
||||
refreshToken: undefined,
|
||||
expiresAt: undefined
|
||||
} as AuthCredentials;
|
||||
this.store.saveCredentials(clearedCredentials);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Error removing session:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all session data
|
||||
*/
|
||||
clear(): void {
|
||||
// Clear auth tokens but preserve context
|
||||
this.removeItem(STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user