112 lines
3.1 KiB
TypeScript
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 }),
|
|
};
|
|
}
|
|
}
|