import { SlackCredentials } from '../types/slack.js';
export interface WorkspaceConfig {
teamId: string;
teamName: string;
token: string;
}
/**
* Auth Provider - Multi-Workspace Support
*
* Supports two modes:
*
* 1. Single workspace (backward compatible):
* SLACK_ACCESS_TOKEN=xoxp-...
* SLACK_TEAM_ID=T123 (optional)
*
* 2. Multiple workspaces:
* SLACK_WORKSPACES=[{"team_id":"T123","team_name":"Workspace1","token":"xoxp-..."},...]
*/
export class AuthProvider {
private static instance: AuthProvider;
private workspaces: Map<string, WorkspaceConfig> = new Map();
private activeWorkspaceId: string | null = null;
static getInstance(): AuthProvider {
if (!AuthProvider.instance) {
AuthProvider.instance = new AuthProvider();
AuthProvider.instance.loadWorkspaces();
}
return AuthProvider.instance;
}
private loadWorkspaces(): void {
// Try multi-workspace config first
const workspacesJson = process.env.SLACK_WORKSPACES;
if (workspacesJson) {
try {
const workspaces = JSON.parse(workspacesJson) as Array<{
team_id: string;
team_name: string;
token: string;
}>;
for (const ws of workspaces) {
if (!ws.token.startsWith('xoxp-')) {
console.error(`Invalid token format for workspace ${ws.team_name}. Skipping.`);
continue;
}
this.workspaces.set(ws.team_id, {
teamId: ws.team_id,
teamName: ws.team_name,
token: ws.token,
});
}
// Set first workspace as active
if (this.workspaces.size > 0) {
this.activeWorkspaceId = this.workspaces.keys().next().value!;
}
return;
} catch (e) {
console.error('Failed to parse SLACK_WORKSPACES:', e);
}
}
// Fall back to single token mode
const accessToken = process.env.SLACK_ACCESS_TOKEN;
const teamId = process.env.SLACK_TEAM_ID || 'default';
if (accessToken) {
if (!accessToken.startsWith('xoxp-')) {
throw new Error('Invalid token format. User token (xoxp-...) is required.');
}
this.workspaces.set(teamId, {
teamId,
teamName: process.env.SLACK_TEAM_NAME || 'Default Workspace',
token: accessToken,
});
this.activeWorkspaceId = teamId;
}
}
getWorkspaces(): WorkspaceConfig[] {
return Array.from(this.workspaces.values());
}
getActiveWorkspace(): WorkspaceConfig | null {
if (!this.activeWorkspaceId) {
return null;
}
return this.workspaces.get(this.activeWorkspaceId) || null;
}
getActiveWorkspaceId(): string | null {
return this.activeWorkspaceId;
}
switchWorkspace(teamId: string): WorkspaceConfig {
const workspace = this.workspaces.get(teamId);
if (!workspace) {
throw new Error(`Workspace not found: ${teamId}. Available: ${Array.from(this.workspaces.keys()).join(', ')}`);
}
this.activeWorkspaceId = teamId;
// Reset the slack client cache when switching
resetSlackClient();
return workspace;
}
getCredentials(): SlackCredentials {
const workspace = this.getActiveWorkspace();
if (!workspace) {
throw new Error(
'No workspace configured. Set SLACK_ACCESS_TOKEN or SLACK_WORKSPACES environment variable.'
);
}
return {
accessToken: workspace.token,
teamId: workspace.teamId,
};
}
getToken(): string {
return this.getCredentials().accessToken;
}
}
// Import from slack-client to reset cache
let resetSlackClient: () => void = () => {};
export function setResetSlackClientFn(fn: () => void): void {
resetSlackClient = fn;
}