Files
metabase/enterprise/frontend/src/metabase-enterprise/embedding_iframe_sdk/auth-manager/AuthManager.ts
2026-10-05 22:41:31 +13:00

112 lines
3.1 KiB
TypeScript

import {
INVALID_AUTH_METHOD,
MetabaseError,
} from "embedding-sdk-shared/errors";
import {
connectToInstanceAuthSso,
jwtDefaultRefreshTokenFunction,
openSamlLoginPopup,
validateSession,
} from "embedding/auth-common";
import type { MetabaseEmbeddingSessionToken } from "metabase/embedding-sdk/types/refresh-token";
import type { EmbedAuthManagerContext } from "metabase/embedding/embedding-iframe-sdk/types/auth-manager";
/**
* Manages authentication for embedded Metabase components.
* Handles SSO authentication via SAML or JWT methods.
*/
export class EmbedAuthManager {
constructor(private context: EmbedAuthManagerContext) {}
/**
* Authenticate the user and send the session token to the iframe.
* If using an API key, authentication is skipped.
*/
async authenticate(): Promise<void> {
// If we are using an API key, we don't need to authenticate via SSO.
if (this.context.properties.apiKey) {
return;
}
try {
const { method, sessionToken } = await this.getMetabaseSessionToken();
validateSession(sessionToken);
if (sessionToken) {
this.context.sendMessage("metabase.embed.submitSessionToken", {
authMethod: method,
sessionToken,
});
}
} catch (error) {
// if the error is an authentication error, show it to the iframe too
if (error instanceof MetabaseError) {
this.context.sendMessage("metabase.embed.reportAuthenticationError", {
error,
});
}
}
}
private async getMetabaseSessionToken(): Promise<{
method: "saml" | "jwt";
sessionToken: MetabaseEmbeddingSessionToken;
}> {
const {
instanceUrl: metabaseInstanceUrl,
preferredAuthMethod,
fetchRequestToken,
jwtProviderUri,
} = this.context.properties;
const shouldSkipSsoDiscovery = jwtProviderUri !== undefined;
const urlResponseJson = shouldSkipSsoDiscovery
? { method: "jwt", url: jwtProviderUri }
: await connectToInstanceAuthSso(metabaseInstanceUrl, {
headers: this.getAuthRequestHeader(),
preferredAuthMethod,
});
const {
method,
url: responseUrl,
hash,
"saml-popup-url": samlPopupUrl,
} = urlResponseJson || {};
if (method === "saml") {
const sessionToken = await openSamlLoginPopup(
responseUrl,
metabaseInstanceUrl,
samlPopupUrl,
);
return { method, sessionToken };
}
if (method === "jwt" && responseUrl) {
const sessionToken = await jwtDefaultRefreshTokenFunction(
responseUrl,
metabaseInstanceUrl,
this.getAuthRequestHeader(hash),
fetchRequestToken,
);
return { method, sessionToken };
}
throw INVALID_AUTH_METHOD({ method });
}
private getAuthRequestHeader(hash?: string) {
return {
// eslint-disable-next-line metabase/no-literal-metabase-strings -- header name
"X-Metabase-Client": "embedding-simple",
// eslint-disable-next-line metabase/no-literal-metabase-strings -- header name
...(hash && { "X-Metabase-SDK-JWT-Hash": hash }),
};
}
}