import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api'; import { LoggerService, DiscoveryService } from '@backstage/backend-plugin-api'; import { EntityFilterQuery } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { JsonValue, JsonObject } from '@backstage/types'; import express, { Request, Response } from 'express'; import { Profile, Strategy } from 'passport'; import { ZodType, z } from 'zod/v3'; /** * A representation of a successful Backstage sign-in. * * Compared to the {@link BackstageIdentityResponse} this type omits * the decoded identity information embedded in the token. * * @public */ interface BackstageSignInResult { /** * The token used to authenticate the user within Backstage. */ token: string; /** * Identity information to pass to the client rather than using the * information that's embeeded in the token. */ identity?: BackstageUserIdentity; } /** * Response object containing the {@link BackstageUserIdentity} and the token * from the authentication provider. * * @public */ interface BackstageIdentityResponse extends BackstageSignInResult { /** * The number of seconds until the token expires. If not set, it can be assumed that the token does not expire. */ expiresInSeconds?: number; /** * A plaintext description of the identity that is encapsulated within the token. */ identity: BackstageUserIdentity; } /** * User identity information within Backstage. * * @public */ type BackstageUserIdentity = { /** * The type of identity that this structure represents. In the frontend app * this will currently always be 'user'. */ type: 'user'; /** * The entityRef of the user in the catalog. * For example User:default/sandra */ userEntityRef: string; /** * The user and group entities that the user claims ownership through */ ownershipEntityRefs: string[]; }; /** * A query for a single user in the catalog. * * If `entityRef` is used, the default kind is `'User'`. * * If `annotations` are used, all annotations must be present and * match the provided value exactly. Only entities of kind `'User'` will be considered. * * If `filter` are used, only entities of kind `'User'` will be considered unless it is explicitly specified differently in the filter. * * Regardless of the query method, the query must match exactly one entity * in the catalog, or an error will be thrown. * * @public */ type AuthResolverCatalogUserQuery = { entityRef: string | { kind?: string; namespace?: string; name: string; }; } | { annotations: Record; } | { filter: EntityFilterQuery; }; /** * Parameters used to issue new Backstage Tokens * * @public */ type TokenParams = { /** * The claims that will be embedded within the token. At a minimum, this should include * the subject claim, `sub`. It is common to also list entity ownership relations in the * `ent` list. Additional claims may also be added at the developer's discretion except * for the following list, which will be overwritten by the TokenIssuer: `iss`, `aud`, * `iat`, and `exp`. The Backstage team also maintains the right add new claims in the future * without listing the change as a "breaking change". */ claims: { /** The token subject, i.e. User ID */ sub: string; /** A list of entity references that the user claims ownership through */ ent?: string[]; } & Record; }; /** * The context that is used for auth processing. * * @public */ type AuthResolverContext = { /** * Issues a Backstage token using the provided parameters. */ issueToken(params: TokenParams): Promise; /** * Finds a single user in the catalog using the provided query. * * See {@link AuthResolverCatalogUserQuery} for details. */ findCatalogUser(query: AuthResolverCatalogUserQuery): Promise<{ entity: Entity; }>; /** * Finds a single user in the catalog using the provided query, and then * issues an identity for that user using default ownership resolution. * * If the user is not found, an optional `dangerousEntityRefFallback` * entity ref can be provided to allow sign-in to proceed by issuing an * identity based on the given ref. This bypasses the requirement for the * user to exist in the catalog and should be used with caution. * * See {@link AuthResolverCatalogUserQuery} for details. */ signInWithCatalogUser(query: AuthResolverCatalogUserQuery, options?: { dangerousEntityRefFallback?: { entityRef: string | { kind?: string; namespace?: string; name: string; }; }; }): Promise; /** * Resolves the ownership entity references for the provided entity. * This will use the `AuthOwnershipResolver` if one is installed, and otherwise fall back to the default resolution logic. */ resolveOwnershipEntityRefs(entity: Entity): Promise<{ ownershipEntityRefs: string[]; }>; }; /** * Resolver interface for resolving the ownership entity references for entity * * @public */ interface AuthOwnershipResolver { resolveOwnershipEntityRefs(entity: Entity): Promise<{ ownershipEntityRefs: string[]; }>; } /** * Any Auth provider needs to implement this interface which handles the routes in the * auth backend. Any auth API requests from the frontend reaches these methods. * * The routes in the auth backend API are tied to these methods like below * * `/auth/[provider]/start -> start` * `/auth/[provider]/handler/frame -> frameHandler` * `/auth/[provider]/refresh -> refresh` * `/auth/[provider]/logout -> logout` * * @public */ interface AuthProviderRouteHandlers { /** * Handles the start route of the API. This initiates a sign in request with an auth provider. * * Request * - scopes for the auth request (Optional) * Response * - redirect to the auth provider for the user to sign in or consent. * - sets a nonce cookie and also pass the nonce as 'state' query parameter in the redirect request */ start(req: Request, res: Response): Promise; /** * Once the user signs in or consents in the OAuth screen, the auth provider redirects to the * callbackURL which is handled by this method. * * Request * - to contain a nonce cookie and a 'state' query parameter * Response * - postMessage to the window with a payload that contains accessToken, expiryInSeconds?, idToken? and scope. * - sets a refresh token cookie if the auth provider supports refresh tokens */ frameHandler(req: Request, res: Response): Promise; /** * (Optional) If the auth provider supports refresh tokens then this method handles * requests to get a new access token. * * Other types of providers may also use this method to implement its own logic to create new sessions * upon request. For example, this can be used to create a new session for a provider that handles requests * from an authenticating proxy. * * Request * - to contain a refresh token cookie and scope (Optional) query parameter. * Response * - payload with accessToken, expiryInSeconds?, idToken?, scope and user profile information. */ refresh?(req: Request, res: Response): Promise; /** * (Optional) Handles sign out requests * * Response * - removes the refresh token cookie */ logout?(req: Request, res: Response): Promise; } /** * @public * @deprecated Use top-level properties passed to `AuthProviderFactory` instead */ type AuthProviderConfig = { /** * The protocol://domain[:port] where the app is hosted. This is used to construct the * callbackURL to redirect to once the user signs in to the auth provider. */ baseUrl: string; /** * The base URL of the app as provided by app.baseUrl */ appUrl: string; /** * A function that is called to check whether an origin is allowed to receive the authentication result. */ isOriginAllowed: (origin: string) => boolean; /** * The function used to resolve cookie configuration based on the auth provider options. */ cookieConfigurer?: CookieConfigurer; }; /** @public */ type AuthProviderFactory = (options: { providerId: string; /** @deprecated Use top-level properties instead */ globalConfig: AuthProviderConfig; config: Config; logger: LoggerService; resolverContext: AuthResolverContext; /** * The protocol://domain[:port] where the app is hosted. This is used to construct the * callbackURL to redirect to once the user signs in to the auth provider. */ baseUrl: string; /** * The base URL of the app as provided by app.baseUrl */ appUrl: string; /** * A function that is called to check whether an origin is allowed to receive the authentication result. */ isOriginAllowed: (origin: string) => boolean; /** * The function used to resolve cookie configuration based on the auth provider options. */ cookieConfigurer?: CookieConfigurer; }) => AuthProviderRouteHandlers; /** @public */ type ClientAuthResponse = { providerInfo: TProviderInfo; profile: ProfileInfo; backstageIdentity?: BackstageIdentityResponse; }; /** * Type of sign in information context. Includes the profile information and * authentication result which contains auth related information. * * @public */ type SignInInfo = { /** * The simple profile passed down for use in the frontend. */ profile: ProfileInfo; /** * The authentication result that was received from the authentication * provider. */ result: TAuthResult; }; /** * Describes the function which handles the result of a successful * authentication. Must return a valid {@link @backstage/plugin-auth-node#BackstageSignInResult}. * * @public */ type SignInResolver = (info: SignInInfo, context: AuthResolverContext) => Promise; /** * Describes the function that transforms the result of a successful * authentication into a {@link ProfileInfo} object. * * This function may optionally throw an error in order to reject authentication. * * @public */ type ProfileTransform = (result: TResult, context: AuthResolverContext) => Promise<{ profile: ProfileInfo; }>; /** * Used to display login information to user, i.e. sidebar popup. * * It is also temporarily used as the profile of the signed-in user's Backstage * identity, but we want to replace that with data from identity and/org catalog * service * * @public */ type ProfileInfo = { /** * Email ID of the signed in user. */ email?: string; /** * Display name that can be presented to the signed in user. */ displayName?: string; /** * URL to an image that can be used as the display image or avatar of the * signed in user. */ picture?: string; }; /** * The callback used to resolve the cookie configuration for auth providers that use cookies. * @public */ type CookieConfigurer = (ctx: { /** ID of the auth provider that this configuration applies to */ providerId: string; /** The externally reachable base URL of the auth-backend plugin */ baseUrl: string; /** The configured callback URL of the auth provider */ callbackUrl: string; /** The origin URL of the app */ appOrigin: string; }) => { domain?: string; path: string; secure: boolean; sameSite?: 'none' | 'lax' | 'strict'; }; /** * Core properties of various token types. * * @public */ declare const tokenTypes: Readonly<{ user: Readonly<{ typParam: "vnd.backstage.user"; audClaim: "backstage"; }>; limitedUser: Readonly<{ typParam: "vnd.backstage.limited-user"; }>; plugin: Readonly<{ typParam: "vnd.backstage.plugin"; }>; }>; /** @public */ interface AuthProviderRegistrationOptions { providerId: string; factory: AuthProviderFactory; } /** @public */ interface AuthProvidersExtensionPoint { registerProvider(options: AuthProviderRegistrationOptions): void; } /** @public */ declare const authProvidersExtensionPoint: _backstage_backend_plugin_api.ExtensionPoint; /** @public */ interface AuthOwnershipResolutionExtensionPoint { setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void; } /** @public */ declare const authOwnershipResolutionExtensionPoint: _backstage_backend_plugin_api.ExtensionPoint; /** * Payload sent as a post message after the auth request is complete. * If successful then has a valid payload with Auth information else contains an error. * * @public */ type WebMessageResponse = { type: 'authorization_response'; response: ClientAuthResponse; } | { type: 'authorization_response'; error: Error; }; /** @public */ declare function sendWebMessageResponse(res: Response, appOrigin: string, response: WebMessageResponse): void; /** * Parses a Backstage-issued token and decorates the * {@link @backstage/plugin-auth-node#BackstageIdentityResponse} with identity information sourced from the * token. * * @public */ declare function prepareBackstageIdentityResponse(result: BackstageSignInResult): BackstageIdentityResponse; /** * Parses the given authorization header and returns the bearer token, or * undefined if no bearer token is given. * * @remarks * * This function is explicitly built to tolerate bad inputs safely, so you may * call it directly with e.g. the output of `req.header('authorization')` * without first checking that it exists. * * @deprecated Use the `credentials` method of `HttpAuthService` from `@backstage/backend-plugin-api` instead * @public */ declare function getBearerTokenFromAuthorizationHeader(authorizationHeader: unknown): string | undefined; /** * Options to request the identity from a Backstage backend request * * @public */ type IdentityApiGetIdentityRequest = { request: Request; }; /** * An identity client api to authenticate Backstage * tokens * * @experimental This is not a stable API yet * @public */ interface IdentityApi { /** * Verifies the given backstage identity token * Returns a BackstageIdentity (user) matching the token. * The method throws an error if verification fails. */ getIdentity(options: IdentityApiGetIdentityRequest): Promise; } /** * An identity client options object which allows extra configurations * * @experimental This is not a stable API yet * @public */ type IdentityClientOptions = { discovery: DiscoveryService; issuer?: string; /** JWS "alg" (Algorithm) Header Parameter values. Defaults to an array containing just ES256. * More info on supported algorithms: https://github.com/panva/jose */ algorithms?: string[]; }; /** * An identity client to interact with auth-backend and authenticate Backstage * tokens * * @experimental This is not a stable API yet * @public */ declare class DefaultIdentityClient implements IdentityApi { private readonly discovery; private readonly issuer?; private readonly algorithms?; private keyStore?; private keyStoreUpdated; /** * Create a new {@link DefaultIdentityClient} instance. */ static create(options: IdentityClientOptions): DefaultIdentityClient; private constructor(); getIdentity(options: IdentityApiGetIdentityRequest): Promise; /** * Verifies the given backstage identity token * Returns a BackstageIdentity (user) matching the token. * The method throws an error if verification fails. * * @deprecated You should start to use getIdentity instead of authenticate to retrieve the user * identity. */ authenticate(token: string | undefined): Promise; /** * If the last keystore refresh is stale, update the keystore URL to the latest */ private refreshKeyStore; } /** * An identity client to interact with auth-backend and authenticate Backstage * tokens * * @public * @experimental This is not a stable API yet * @deprecated Please migrate to the DefaultIdentityClient. */ declare class IdentityClient { private readonly defaultIdentityClient; static create(options: IdentityClientOptions): IdentityClient; private constructor(); /** * Verifies the given backstage identity token * Returns a BackstageIdentity (user) matching the token. * The method throws an error if verification fails. * * @deprecated You should start to use IdentityApi#getIdentity instead of authenticate * to retrieve the user identity. */ authenticate(token: string | undefined): Promise; } /** * A type for the serialized value in the `state` parameter of the OAuth authorization flow * @public */ type OAuthState = { nonce: string; env: string; origin?: string; scope?: string; redirectUrl?: string; flow?: string; audience?: string; }; /** @public */ type OAuthStateTransform = (state: OAuthState, context: { req: Request; }) => Promise<{ state: OAuthState; }>; /** @public */ declare function encodeOAuthState(state: OAuthState): string; /** @public */ declare function decodeOAuthState(encodedState: string): OAuthState; /** @public */ interface OAuthSession { accessToken: string; tokenType: string; idToken?: string; scope: string; expiresInSeconds?: number; refreshToken?: string; refreshTokenExpiresInSeconds?: number; } /** @public */ interface OAuthAuthenticatorScopeOptions { persist?: boolean; required?: string[]; transform?: (options: { /** Scopes requested by the client */ requested: Iterable; /** Scopes which have already been granted */ granted: Iterable; /** Scopes that are required for the authenticator to function */ required: Iterable; /** Additional scopes added through configuration */ additional: Iterable; }) => Iterable; } /** @public */ interface OAuthAuthenticatorStartInput { scope: string; state: string; req: Request; } /** @public */ interface OAuthAuthenticatorAuthenticateInput { req: Request; } /** @public */ interface OAuthAuthenticatorRefreshInput { /** * Signals whether the requested scope has already been granted for the session. Will only be set if the `scopes.persist` option is enabled. */ scopeAlreadyGranted?: boolean; scope: string; refreshToken: string; req: Request; } /** @public */ interface OAuthAuthenticatorLogoutInput { accessToken?: string; refreshToken?: string; req: Request; } /** @public */ interface OAuthAuthenticatorLogoutResult { /** * If set, the frontend will redirect the browser to this URL after clearing * the Backstage session. Use this to terminate provider-side sessions (e.g. * Auth0's `/v2/logout` endpoint). */ logoutUrl?: string; } /** @public */ interface OAuthAuthenticatorResult { fullProfile: TProfile; session: OAuthSession; } /** @public */ interface OAuthAuthenticator { defaultProfileTransform: ProfileTransform>; /** @deprecated use `scopes.persist` instead */ shouldPersistScopes?: boolean; scopes?: OAuthAuthenticatorScopeOptions; initialize(ctx: { callbackUrl: string; config: Config; }): TContext; start(input: OAuthAuthenticatorStartInput, ctx: TContext): Promise<{ url: string; status?: number; }>; authenticate(input: OAuthAuthenticatorAuthenticateInput, ctx: TContext): Promise>; refresh(input: OAuthAuthenticatorRefreshInput, ctx: TContext): Promise>; logout?(input: OAuthAuthenticatorLogoutInput, ctx: TContext): Promise; } /** @public */ declare function createOAuthAuthenticator(authenticator: OAuthAuthenticator): OAuthAuthenticator; /** @public */ interface OAuthRouteHandlersOptions { authenticator: OAuthAuthenticator; appUrl: string; baseUrl: string; isOriginAllowed: (origin: string) => boolean; providerId: string; config: Config; resolverContext: AuthResolverContext; additionalScopes?: string[]; stateTransform?: OAuthStateTransform; profileTransform?: ProfileTransform>; cookieConfigurer?: CookieConfigurer; signInResolver?: SignInResolver>; } /** @public */ declare function createOAuthRouteHandlers(options: OAuthRouteHandlersOptions): AuthProviderRouteHandlers; /** @public */ type PassportProfile = Profile & { avatarUrl?: string; email?: string; photo?: string; }; /** @public */ type PassportDoneCallback = (err?: Error, result?: TResult, privateInfo?: TPrivateInfo) => void; /** @public */ declare class PassportHelpers { private constructor(); static transformProfile: (profile: PassportProfile, idToken?: string) => ProfileInfo; static executeRedirectStrategy(req: Request, providerStrategy: Strategy, options: Record): Promise<{ /** * URL to redirect to */ url: string; /** * Status code to use for the redirect */ status?: number; }>; static executeFrameHandlerStrategy(req: Request, providerStrategy: Strategy, options?: Record): Promise<{ result: TResult; privateInfo: TPrivateInfo; }>; static executeRefreshTokenStrategy(providerStrategy: Strategy, refreshToken: string, scope: string): Promise<{ /** * An access token issued for the signed in user. */ accessToken: string; /** * Optionally, the server can issue a new Refresh Token for the user */ refreshToken?: string; params: any; }>; static executeFetchUserProfileStrategy(providerStrategy: Strategy, accessToken: string): Promise; } /** @public */ type PassportOAuthResult = { fullProfile: PassportProfile; params: { id_token?: string; scope: string; token_type?: string; expires_in: number; }; accessToken: string; }; /** @public */ type PassportOAuthPrivateInfo = { refreshToken?: string; }; /** @public */ type PassportOAuthDoneCallback = PassportDoneCallback; /** @public */ declare class PassportOAuthAuthenticatorHelper { #private; static defaultProfileTransform: ProfileTransform>; static from(strategy: Strategy): PassportOAuthAuthenticatorHelper; private constructor(); start(input: OAuthAuthenticatorStartInput, options: Record): Promise<{ url: string; status?: number; }>; authenticate(input: OAuthAuthenticatorAuthenticateInput, options?: Record): Promise>; refresh(input: OAuthAuthenticatorRefreshInput): Promise>; fetchProfile(accessToken: string): Promise; } /** @public */ declare class OAuthEnvironmentHandler implements AuthProviderRouteHandlers { private readonly handlers; static mapConfig(config: Config, factoryFunc: (envConfig: Config) => AuthProviderRouteHandlers): OAuthEnvironmentHandler; constructor(handlers: Map); start(req: express.Request, res: express.Response): Promise; frameHandler(req: express.Request, res: express.Response): Promise; refresh(req: express.Request, res: express.Response): Promise; logout(req: express.Request, res: express.Response): Promise; private getEnvFromRequest; private getProviderForEnv; } /** @public */ interface SignInResolverFactory { (...options: undefined extends TOptions ? [options?: TOptions] : [options: TOptions]): SignInResolver; optionsJsonSchema?: JsonObject; } /** @public */ interface SignInResolverFactoryOptions> { optionsSchema?: TSchema; create(options: z.output): SignInResolver; } /** @public */ declare function createSignInResolverFactory>(options: SignInResolverFactoryOptions): SignInResolverFactory>; /** @public */ declare function createOAuthProviderFactory(options: { authenticator: OAuthAuthenticator; additionalScopes?: string[]; stateTransform?: OAuthStateTransform; profileTransform?: ProfileTransform>; signInResolver?: SignInResolver>; signInResolverFactories?: { [name in string]: SignInResolverFactory; }; }): AuthProviderFactory; /** @public */ interface ProxyAuthenticator { defaultProfileTransform: ProfileTransform; initialize(ctx: { config: Config; }): TContext; authenticate(options: { req: Request; }, ctx: TContext): Promise<{ result: TResult; providerInfo?: TProviderInfo; }>; } /** @public */ declare function createProxyAuthenticator(authenticator: ProxyAuthenticator): ProxyAuthenticator; /** @public */ interface ReadDeclarativeSignInResolverOptions { config: Config; signInResolverFactories: { [name in string]: SignInResolverFactory; }; } /** @public */ declare function readDeclarativeSignInResolver(options: ReadDeclarativeSignInResolverOptions): SignInResolver | undefined; /** * A collection of common sign-in resolvers that work with any auth provider. * * @public */ declare namespace commonSignInResolvers { /** * A common sign-in resolver that looks up the user using their email address * as email of the entity. */ const emailMatchingUserEntityProfileEmail: SignInResolverFactory; /** * A common sign-in resolver that looks up the user using the local part of * their email address as the entity name. */ const emailLocalPartMatchingUserEntityName: SignInResolverFactory; } /** @public */ declare function createProxyAuthProviderFactory(options: { authenticator: ProxyAuthenticator; profileTransform?: ProfileTransform; signInResolver?: SignInResolver; signInResolverFactories?: Record; }): AuthProviderFactory; /** @public */ interface ProxyAuthRouteHandlersOptions { authenticator: ProxyAuthenticator; config: Config; resolverContext: AuthResolverContext; signInResolver: SignInResolver; profileTransform?: ProfileTransform; } /** @public */ declare function createProxyAuthRouteHandlers(options: ProxyAuthRouteHandlersOptions): AuthProviderRouteHandlers; export { DefaultIdentityClient, IdentityClient, OAuthEnvironmentHandler, PassportHelpers, PassportOAuthAuthenticatorHelper, authOwnershipResolutionExtensionPoint, authProvidersExtensionPoint, commonSignInResolvers, createOAuthAuthenticator, createOAuthProviderFactory, createOAuthRouteHandlers, createProxyAuthProviderFactory, createProxyAuthRouteHandlers, createProxyAuthenticator, createSignInResolverFactory, decodeOAuthState, encodeOAuthState, getBearerTokenFromAuthorizationHeader, prepareBackstageIdentityResponse, readDeclarativeSignInResolver, sendWebMessageResponse, tokenTypes }; export type { AuthOwnershipResolutionExtensionPoint, AuthOwnershipResolver, AuthProviderConfig, AuthProviderFactory, AuthProviderRegistrationOptions, AuthProviderRouteHandlers, AuthProvidersExtensionPoint, AuthResolverCatalogUserQuery, AuthResolverContext, BackstageIdentityResponse, BackstageSignInResult, BackstageUserIdentity, ClientAuthResponse, CookieConfigurer, IdentityApi, IdentityApiGetIdentityRequest, IdentityClientOptions, OAuthAuthenticator, OAuthAuthenticatorAuthenticateInput, OAuthAuthenticatorLogoutInput, OAuthAuthenticatorLogoutResult, OAuthAuthenticatorRefreshInput, OAuthAuthenticatorResult, OAuthAuthenticatorScopeOptions, OAuthAuthenticatorStartInput, OAuthRouteHandlersOptions, OAuthSession, OAuthState, OAuthStateTransform, PassportDoneCallback, PassportOAuthDoneCallback, PassportOAuthPrivateInfo, PassportOAuthResult, PassportProfile, ProfileInfo, ProfileTransform, ProxyAuthRouteHandlersOptions, ProxyAuthenticator, ReadDeclarativeSignInResolverOptions, SignInInfo, SignInResolver, SignInResolverFactory, SignInResolverFactoryOptions, TokenParams, WebMessageResponse };