/**
 * MCP OAuth Client Provider
 *
 * Implements the OAuthClientProvider interface from the MCP SDK.
 * Stores tokens and client registration in the auth database per user+connector.
 * Supports both dynamic client registration and pre-registered OAuth apps.
 */
export class McpOAuthProvider {
    connectorName;
    userId;
    authService;
    serverUrl;
    /** Set by redirectToAuthorization — the frontend reads this to open the auth URL. */
    authorizationUrl = null;
    preRegistered;
    constructor(connectorName, userId, authService, serverUrl, oauthCredentials) {
        this.connectorName = connectorName;
        this.userId = userId;
        this.authService = authService;
        this.serverUrl = serverUrl;
        this.preRegistered = oauthCredentials;
    }
    get redirectUrl() {
        // Use pre-registered redirect URI if available (e.g. auth.studiograph.com)
        if (this.preRegistered?.redirect_uri)
            return this.preRegistered.redirect_uri;
        return `${this.serverUrl}/api/oauth/callback/${encodeURIComponent(this.connectorName)}`;
    }
    get clientMetadata() {
        return {
            client_name: 'Studiograph',
            redirect_uris: [this.redirectUrl.toString()],
            grant_types: ['authorization_code', 'refresh_token'],
            response_types: ['code'],
            token_endpoint_auth_method: this.preRegistered?.client_secret ? 'client_secret_post' : 'none',
        };
    }
    state() {
        // HMAC-signed so the callback/claim handlers can verify the state was
        // minted by this instance. Without the signature, an attacker who
        // controls a callback URL could forge {c, u, o} and bind their tokens
        // against a victim's userId. The signing key is HKDF-derived from the
        // workspace master key in AuthService — see src/mcp/state-signer.ts.
        return this.authService.signOAuthState({
            connectorName: this.connectorName,
            userId: this.userId,
            origin: this.serverUrl,
        });
    }
    clientInformation() {
        // Pre-registered credentials take priority
        if (this.preRegistered) {
            return {
                client_id: this.preRegistered.client_id,
                client_secret: this.preRegistered.client_secret,
            };
        }
        // Fall back to dynamically registered credentials
        const data = this.authService.getOAuthData(this.userId, this.connectorName);
        return data?.clientInfo ?? undefined;
    }
    saveClientInformation(clientInfo) {
        if (this.preRegistered)
            return; // Don't overwrite pre-registered credentials
        const data = this.authService.getOAuthData(this.userId, this.connectorName) ?? {};
        data.clientInfo = clientInfo;
        // DCR client registration is pre-token machinery during connect/refresh.
        // It never changes the connected-connector set, so persist it silently —
        // notifying would evict the in-flight pooled agent mid-build. See saveTokens.
        this.authService.saveOAuthData(this.userId, this.connectorName, data, { silent: true });
    }
    tokens() {
        const data = this.authService.getOAuthData(this.userId, this.connectorName);
        return data?.tokens ?? undefined;
    }
    saveTokens(tokens) {
        const data = this.authService.getOAuthData(this.userId, this.connectorName) ?? {};
        // A refresh REPLACES an existing access token; an initial grant adds the
        // first one. Only the initial grant changes the user's connected-connector
        // set (and so the agent's tool list); a refresh must persist SILENTLY.
        // Refresh writes fire while ConnectorManager.buildTools constructs the
        // pooled agent (inside withAgent's `await toolsLoaded`); notifying there
        // triggers invalidateUser, which evicts the in-flight pool entry.
        //
        // "Connected" means an access_token is present — the same predicate as
        // hasOAuthTokens(). Keying off `data.tokens` alone would treat a partial
        // row (clientInfo/codeVerifier but no access_token yet) as already
        // connected, so the first real grant would be silently swallowed and the
        // connector's tools would never get rebuilt into the running agent.
        const isRefresh = !!data.tokens?.access_token;
        data.tokens = tokens;
        this.authService.saveOAuthData(this.userId, this.connectorName, data, { silent: isRefresh });
    }
    redirectToAuthorization(authorizationUrl) {
        this.authorizationUrl = authorizationUrl.toString();
    }
    saveCodeVerifier(codeVerifier) {
        const data = this.authService.getOAuthData(this.userId, this.connectorName) ?? {};
        data.codeVerifier = codeVerifier;
        // PKCE verifier is stored during connect initiation, before any tokens
        // exist — silent for the same reason as saveClientInformation.
        this.authService.saveOAuthData(this.userId, this.connectorName, data, { silent: true });
    }
    codeVerifier() {
        const data = this.authService.getOAuthData(this.userId, this.connectorName);
        return data?.codeVerifier ?? '';
    }
}
//# sourceMappingURL=oauth-provider.js.map