diff --git a/.changeset/cruel-lights-sip.md b/.changeset/cruel-lights-sip.md new file mode 100644 index 0000000000..b38ab052a9 --- /dev/null +++ b/.changeset/cruel-lights-sip.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-github-provider': patch +--- + +Added missing types package diff --git a/.changeset/new-hands-scream.md b/.changeset/new-hands-scream.md new file mode 100644 index 0000000000..03291648db --- /dev/null +++ b/.changeset/new-hands-scream.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +**BREAKING**: Removed support for the old backend system, and removed all deprecated exports. + +If you were using one of the deprecated imports from this package, you will have to follow the instructions in their respective deprecation notices before upgrading. Most of the general utilities are available from `@backstage/plugin-auth-node`, and the specific auth providers are available from dedicated packages such as for example `@backstage/plugin-auth-backend-module-github-provider`. See [the auth docs](https://backstage.io/docs/auth/) for specific instructions. diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md index 51424f1c10..7cfa21dd4a 100644 --- a/docs/auth/add-auth-provider.md +++ b/docs/auth/add-auth-provider.md @@ -66,10 +66,6 @@ function takes an `express.Response`, a `WebMessageResponse` and the URL of the frontend (`appOrigin`) as parameters and return an HTML page with the script and the message. -There is a helper class for [OAuth2](https://oauth.net/2/) based authentication providers, [OAuthAdapter](../reference/plugin-auth-backend.oauthadapter.md). This class implements the `AuthProviderRouteHandlers` interface -for you, and instead requires you to implement [OAuthHandlers](../reference/plugin-auth-backend.oauthhandlers.md), which -is significantly easier. - ### Auth Environment Separation The concept of an `env` is core to the way the auth backend works. It uses an diff --git a/docs/auth/identity-resolver--old.md b/docs/auth/identity-resolver--old.md index f6310ace64..ce41fbcf2b 100644 --- a/docs/auth/identity-resolver--old.md +++ b/docs/auth/identity-resolver--old.md @@ -22,7 +22,7 @@ be mapped to user identities within Backstage. ## Quick Start -> See [providers](../reference/plugin-auth-backend.providers.md) +> See [the auth docs](./index.md) > for a full list of auth providers and their built-in sign-in resolvers. Backstage projects created with `npx @backstage/create-app` come configured with a diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index b39c1192b7..a5a05f05ac 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -35,7 +35,6 @@ "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/integration": "workspace:^", - "@backstage/plugin-auth-backend": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^", diff --git a/packages/backend-legacy/src/index.ts b/packages/backend-legacy/src/index.ts index 0ff6c08f84..1f66616520 100644 --- a/packages/backend-legacy/src/index.ts +++ b/packages/backend-legacy/src/index.ts @@ -38,7 +38,6 @@ import { import { Config } from '@backstage/config'; import healthcheck from './plugins/healthcheck'; import { metricsHandler, metricsInit } from './metrics'; -import authPlugin from './plugins/auth'; import catalog from './plugins/catalog'; import events from './plugins/events'; import kubernetes from './plugins/kubernetes'; @@ -125,7 +124,6 @@ async function main() { const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck')); const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); - const authEnv = useHotMemoize(module, () => createEnv('auth')); const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes')); const permissionEnv = useHotMemoize(module, () => createEnv('permission')); const eventsEnv = useHotMemoize(module, () => createEnv('events')); @@ -134,7 +132,6 @@ async function main() { apiRouter.use('/catalog', await catalog(catalogEnv)); apiRouter.use('/events', await events(eventsEnv)); apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); - apiRouter.use('/auth', await authPlugin(authEnv)); apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv)); apiRouter.use('/permission', await permission(permissionEnv)); apiRouter.use(notFoundHandler()); diff --git a/packages/backend-legacy/src/plugins/auth.ts b/packages/backend-legacy/src/plugins/auth.ts deleted file mode 100644 index 0d92315f92..0000000000 --- a/packages/backend-legacy/src/plugins/auth.ts +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - DEFAULT_NAMESPACE, - stringifyEntityRef, -} from '@backstage/catalog-model'; -import { - createRouter, - providers, - defaultAuthProviderFactories, -} from '@backstage/plugin-auth-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, - discovery: env.discovery, - tokenManager: env.tokenManager, - providerFactories: { - ...defaultAuthProviderFactories, - - // NOTE: DO NOT add this many resolvers in your own instance! - // It is important that each real user always gets resolved to - // the same sign-in identity. The code below will not do that. - // It is here for demo purposes only. - github: providers.github.create({ - signIn: { - async resolver({ result: { fullProfile } }, ctx) { - const userId = fullProfile.username; - if (!userId) { - throw new Error( - `GitHub user profile does not contain a username`, - ); - } - - const userEntityRef = stringifyEntityRef({ - kind: 'User', - name: userId, - namespace: DEFAULT_NAMESPACE, - }); - - return ctx.issueToken({ - claims: { - sub: userEntityRef, - ent: [userEntityRef], - }, - }); - }, - }, - }), - gitlab: providers.gitlab.create({ - signIn: { - async resolver({ result: { fullProfile } }, ctx) { - return ctx.signInWithCatalogUser({ - entityRef: { - name: fullProfile.id, - }, - }); - }, - }, - }), - microsoft: providers.microsoft.create({ - signIn: { - resolver: - providers.microsoft.resolvers.emailMatchingUserEntityAnnotation(), - }, - }), - google: providers.google.create({ - signIn: { - resolver: - providers.google.resolvers.emailLocalPartMatchingUserEntityName(), - }, - }), - okta: providers.okta.create({ - signIn: { - resolver: - providers.okta.resolvers.emailMatchingUserEntityAnnotation(), - }, - }), - bitbucket: providers.bitbucket.create({ - signIn: { - resolver: - providers.bitbucket.resolvers.usernameMatchingUserEntityAnnotation(), - }, - }), - onelogin: providers.onelogin.create({ - signIn: { - async resolver({ result: { fullProfile } }, ctx) { - return ctx.signInWithCatalogUser({ - entityRef: { - name: fullProfile.id, - }, - }); - }, - }, - }), - - bitbucketServer: providers.bitbucketServer.create({ - signIn: { - resolver: - providers.bitbucketServer.resolvers.emailMatchingUserEntityProfileEmail(), - }, - }), - - // This is an example of how to configure the OAuth2Proxy provider as well - // as how to sign a user in without a matching user entity in the catalog. - // You can try it out using `` - myproxy: providers.oauth2Proxy.create({ - signIn: { - async resolver({ result }, ctx) { - const entityRef = stringifyEntityRef({ - kind: 'user', - namespace: DEFAULT_NAMESPACE, - name: result.getHeader('x-forwarded-user')!, - }); - return ctx.issueToken({ - claims: { - sub: entityRef, - ent: [entityRef], - }, - }); - }, - }, - }), - }, - }); -} diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index efc9c5146c..dd0ec788a3 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -44,6 +44,7 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", "@backstage/types": "workspace:^", + "@types/passport-github2": "^1.2.4", "supertest": "^7.0.0" }, "configSchema": "config.d.ts" diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index 0e01bf038c..bf91780c34 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -84,64 +84,6 @@ export interface Config { }; }; - /** - * The available auth-provider options and attributes - * @additionalProperties true - */ - providers?: { - /** @visibility frontend */ - saml?: { - entryPoint: string; - logoutUrl?: string; - issuer: string; - /** - * @visibility secret - */ - cert: string; - audience?: string; - /** - * @visibility secret - */ - privateKey?: string; - authnContext?: string[]; - identifierFormat?: string; - /** - * @visibility secret - */ - decryptionPvk?: string; - signatureAlgorithm?: 'sha256' | 'sha512'; - digestAlgorithm?: string; - acceptedClockSkewMs?: number; - }; - /** @visibility frontend */ - auth0?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - domain: string; - callbackUrl?: string; - audience?: string; - connection?: string; - connectionScope?: string; - }; - }; - /** @visibility frontend */ - onelogin?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - issuer: string; - callbackUrl?: string; - }; - }; - }; - /** * The backstage token expiration. */ diff --git a/plugins/auth-backend/knip-report.md b/plugins/auth-backend/knip-report.md index fd6926afb9..2661c35327 100644 --- a/plugins/auth-backend/knip-report.md +++ b/plugins/auth-backend/knip-report.md @@ -1,34 +1,2 @@ # Knip report -## Unused dependencies (14) - -| Name | Location | Severity | -| :---------------------- | :----------- | :------- | -| passport-google-oauth20 | package.json | error | -| passport-onelogin-oauth | package.json | error | -| google-auth-library | package.json | error | -| passport-microsoft | package.json | error | -| passport-github2 | package.json | error | -| passport-auth0 | package.json | error | -| openid-client | package.json | error | -| compression | package.json | error | -| node-cache | package.json | error | -| fs-extra | package.json | error | -| winston | package.json | error | -| morgan | package.json | error | -| cors | package.json | error | -| yn | package.json | error | - -## Unused devDependencies (8) - -| Name | Location | Severity | -| :----------------------------- | :----------- | :------- | -| @types/passport-google-oauth20 | package.json | error | -| @types/passport-microsoft | package.json | error | -| @types/passport-strategy | package.json | error | -| @types/passport-github2 | package.json | error | -| @types/passport-auth0 | package.json | error | -| @types/passport-saml | package.json | error | -| @types/body-parser | package.json | error | -| @types/xml2js | package.json | error | - diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 84441992e3..e6da6d2eb6 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -43,79 +43,36 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "^0.25.0", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", - "@backstage/plugin-auth-backend-module-atlassian-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-auth0-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-aws-alb-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-bitbucket-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-gcp-iap-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-github-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-gitlab-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-google-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-microsoft-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-oauth2-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-oidc-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-okta-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-onelogin-provider": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "@google-cloud/firestore": "^7.0.0", - "@node-saml/passport-saml": "^5.0.0", - "@types/express": "^4.17.6", - "@types/passport": "^1.0.3", - "compression": "^1.7.4", "connect-session-knex": "^4.0.0", "cookie-parser": "^1.4.5", - "cors": "^2.8.5", "express": "^4.17.1", "express-promise-router": "^4.1.0", "express-session": "^1.17.1", - "fs-extra": "^11.2.0", - "google-auth-library": "^9.0.0", "jose": "^5.0.0", "knex": "^3.0.0", "lodash": "^4.17.21", "luxon": "^3.0.0", "minimatch": "^9.0.0", - "morgan": "^1.10.0", - "node-cache": "^5.1.2", - "openid-client": "^5.2.1", "passport": "^0.7.0", - "passport-auth0": "^1.4.3", - "passport-github2": "^0.1.12", - "passport-google-oauth20": "^2.0.0", - "passport-microsoft": "^1.0.0", - "passport-oauth2": "^1.6.1", - "passport-onelogin-oauth": "^0.0.1", - "uuid": "^11.0.0", - "winston": "^3.2.1", - "yn": "^4.0.0" + "uuid": "^11.0.0" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/body-parser": "^1.19.0", + "@backstage/plugin-auth-backend-module-google-provider": "workspace:^", "@types/cookie-parser": "^1.4.2", + "@types/express": "^4.17.6", "@types/express-session": "^1.17.2", - "@types/passport-auth0": "^1.0.5", - "@types/passport-github2": "^1.2.4", - "@types/passport-google-oauth20": "^2.0.3", - "@types/passport-microsoft": "^1.0.0", - "@types/passport-saml": "^1.1.3", - "@types/passport-strategy": "^0.2.35", - "@types/xml2js": "^0.4.7", - "msw": "^1.0.0", + "@types/passport": "^1.0.3", "supertest": "^7.0.0" }, "configSchema": "config.d.ts" diff --git a/plugins/auth-backend/report.api.md b/plugins/auth-backend/report.api.md index fb35ea2905..127961d664 100644 --- a/plugins/auth-backend/report.api.md +++ b/plugins/auth-backend/report.api.md @@ -3,675 +3,9 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AuthOwnershipResolver } from '@backstage/plugin-auth-node'; -import { AuthProviderConfig as AuthProviderConfig_2 } from '@backstage/plugin-auth-node'; -import { AuthProviderFactory as AuthProviderFactory_2 } from '@backstage/plugin-auth-node'; -import { AuthProviderRouteHandlers as AuthProviderRouteHandlers_2 } from '@backstage/plugin-auth-node'; -import { AuthResolverCatalogUserQuery as AuthResolverCatalogUserQuery_2 } from '@backstage/plugin-auth-node'; -import { AuthResolverContext as AuthResolverContext_2 } from '@backstage/plugin-auth-node'; -import { AuthService } from '@backstage/backend-plugin-api'; -import { AwsAlbResult as AwsAlbResult_2 } from '@backstage/plugin-auth-backend-module-aws-alb-provider'; -import { AzureEasyAuthResult } from '@backstage/plugin-auth-backend-module-azure-easyauth-provider'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { BackstageSignInResult } from '@backstage/plugin-auth-node'; -import { CacheService } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; -import { ClientAuthResponse } from '@backstage/plugin-auth-node'; -import { cloudflareAccessSignInResolvers } from '@backstage/plugin-auth-backend-module-cloudflare-access-provider'; -import { Config } from '@backstage/config'; -import { CookieConfigurer as CookieConfigurer_2 } from '@backstage/plugin-auth-node'; -import { DatabaseService } from '@backstage/backend-plugin-api'; -import { decodeOAuthState } from '@backstage/plugin-auth-node'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; -import { encodeOAuthState } from '@backstage/plugin-auth-node'; -import { Entity } from '@backstage/catalog-model'; -import express from 'express'; -import { GcpIapResult as GcpIapResult_2 } from '@backstage/plugin-auth-backend-module-gcp-iap-provider'; -import { GcpIapTokenInfo as GcpIapTokenInfo_2 } from '@backstage/plugin-auth-backend-module-gcp-iap-provider'; -import { HttpAuthService } from '@backstage/backend-plugin-api'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { OAuth2ProxyResult as OAuth2ProxyResult_2 } from '@backstage/plugin-auth-backend-module-oauth2-proxy-provider'; -import { OAuthEnvironmentHandler as OAuthEnvironmentHandler_2 } from '@backstage/plugin-auth-node'; -import { OAuthState as OAuthState_2 } from '@backstage/plugin-auth-node'; -import { OidcAuthResult as OidcAuthResult_2 } from '@backstage/plugin-auth-backend-module-oidc-provider'; -import { prepareBackstageIdentityResponse as prepareBackstageIdentityResponse_2 } from '@backstage/plugin-auth-node'; -import { Profile } from 'passport'; -import { ProfileInfo as ProfileInfo_2 } from '@backstage/plugin-auth-node'; -import { RootConfigService } from '@backstage/backend-plugin-api'; -import { SignInInfo as SignInInfo_2 } from '@backstage/plugin-auth-node'; -import { SignInResolver as SignInResolver_2 } from '@backstage/plugin-auth-node'; -import { TokenManager } from '@backstage/backend-common'; -import { TokenParams as TokenParams_2 } from '@backstage/plugin-auth-node'; -import { UserEntity } from '@backstage/catalog-model'; -import { WebMessageResponse as WebMessageResponse_2 } from '@backstage/plugin-auth-node'; - -// @public @deprecated -export type AuthHandler = ( - input: TAuthResult, - context: AuthResolverContext_2, -) => Promise; - -// @public @deprecated -export type AuthHandlerResult = { - profile: ProfileInfo_2; -}; // @public const authPlugin: BackendFeature; export default authPlugin; - -// @public @deprecated (undocumented) -export type AuthProviderConfig = AuthProviderConfig_2; - -// @public @deprecated (undocumented) -export type AuthProviderFactory = AuthProviderFactory_2; - -// @public @deprecated (undocumented) -export type AuthProviderRouteHandlers = AuthProviderRouteHandlers_2; - -// @public @deprecated (undocumented) -export type AuthResolverCatalogUserQuery = AuthResolverCatalogUserQuery_2; - -// @public @deprecated (undocumented) -export type AuthResolverContext = AuthResolverContext_2; - -// @public @deprecated (undocumented) -export type AuthResponse = ClientAuthResponse; - -// @public @deprecated -export type AwsAlbResult = AwsAlbResult_2; - -// @public @deprecated (undocumented) -export type BitbucketOAuthResult = { - fullProfile: BitbucketPassportProfile; - params: { - id_token?: string; - scope: string; - expires_in: number; - }; - accessToken: string; - refreshToken?: string; -}; - -// @public @deprecated (undocumented) -export type BitbucketPassportProfile = Profile & { - id?: string; - displayName?: string; - username?: string; - avatarUrl?: string; - _json?: { - links?: { - avatar?: { - href?: string; - }; - }; - }; -}; - -// @public @deprecated (undocumented) -export type BitbucketServerOAuthResult = { - fullProfile: Profile; - params: { - scope: string; - access_token?: string; - token_type?: string; - expires_in?: number; - }; - accessToken: string; - refreshToken?: string; -}; - -// @public @deprecated -export class CatalogIdentityClient { - constructor(options: { - catalogApi: CatalogApi; - tokenManager?: TokenManager; - discovery: DiscoveryService; - auth?: AuthService; - httpAuth?: HttpAuthService; - }); - findUser(query: { annotations: Record }): Promise; - resolveCatalogMembership(query: { - entityRefs: string[]; - logger?: LoggerService; - }): Promise; -} - -// @public @deprecated -export type CloudflareAccessClaims = { - aud: string[]; - email: string; - exp: number; - iat: number; - nonce: string; - identity_nonce: string; - sub: string; - iss: string; - custom: string; -}; - -// @public @deprecated -export type CloudflareAccessGroup = { - id: string; - name: string; - email: string; -}; - -// @public @deprecated -export type CloudflareAccessIdentityProfile = { - id: string; - name: string; - email: string; - groups: CloudflareAccessGroup[]; -}; - -// @public @deprecated (undocumented) -export type CloudflareAccessResult = { - claims: CloudflareAccessClaims; - cfIdentity: CloudflareAccessIdentityProfile; - expiresInSeconds?: number; - token: string; -}; - -// @public @deprecated (undocumented) -export type CookieConfigurer = CookieConfigurer_2; - -// @public @deprecated -export function createAuthProviderIntegration< - TCreateOptions extends unknown[], - TResolvers extends { - [name in string]: (...args: any[]) => SignInResolver_2; - }, ->(config: { - create: (...args: TCreateOptions) => AuthProviderFactory_2; - resolvers?: TResolvers; -}): Readonly<{ - create: (...args: TCreateOptions) => AuthProviderFactory_2; - resolvers: Readonly; -}>; - -// @public @deprecated (undocumented) -export function createOriginFilter(config: Config): (origin: string) => boolean; - -// @public @deprecated (undocumented) -export function createRouter(options: RouterOptions): Promise; - -// @public @deprecated -export const defaultAuthProviderFactories: { - [providerId: string]: AuthProviderFactory_2; -}; - -// @public @deprecated (undocumented) -export type EasyAuthResult = AzureEasyAuthResult; - -// @public @deprecated (undocumented) -export const encodeState: typeof encodeOAuthState; - -// @public @deprecated (undocumented) -export const ensuresXRequestedWith: (req: express.Request) => boolean; - -// @public @deprecated -export type GcpIapResult = GcpIapResult_2; - -// @public @deprecated -export type GcpIapTokenInfo = GcpIapTokenInfo_2; - -// @public @deprecated -export function getDefaultOwnershipEntityRefs(entity: Entity): string[]; - -// @public @deprecated (undocumented) -export type GithubOAuthResult = { - fullProfile: Profile; - params: { - scope: string; - expires_in?: string; - refresh_token_expires_in?: string; - }; - accessToken: string; - refreshToken?: string; -}; - -// @public @deprecated (undocumented) -export type OAuth2ProxyResult = OAuth2ProxyResult_2; - -// @public @deprecated (undocumented) -export class OAuthAdapter implements AuthProviderRouteHandlers_2 { - constructor(handlers: OAuthHandlers, options: OAuthAdapterOptions); - // (undocumented) - frameHandler(req: express.Request, res: express.Response): Promise; - // (undocumented) - static fromConfig( - config: AuthProviderConfig_2, - handlers: OAuthHandlers, - options: Pick< - OAuthAdapterOptions, - 'providerId' | 'persistScopes' | 'callbackUrl' - >, - ): OAuthAdapter; - // (undocumented) - logout(req: express.Request, res: express.Response): Promise; - // (undocumented) - refresh(req: express.Request, res: express.Response): Promise; - // (undocumented) - start(req: express.Request, res: express.Response): Promise; -} - -// @public @deprecated (undocumented) -export type OAuthAdapterOptions = { - providerId: string; - persistScopes?: boolean; - appOrigin: string; - baseUrl: string; - cookieConfigurer: CookieConfigurer_2; - isOriginAllowed: (origin: string) => boolean; - callbackUrl: string; -}; - -// @public @deprecated (undocumented) -export const OAuthEnvironmentHandler: typeof OAuthEnvironmentHandler_2; - -// @public @deprecated (undocumented) -export interface OAuthHandlers { - handler(req: express.Request): Promise<{ - response: OAuthResponse; - refreshToken?: string; - }>; - logout?(req: OAuthLogoutRequest): Promise; - refresh?(req: OAuthRefreshRequest): Promise<{ - response: OAuthResponse; - refreshToken?: string; - }>; - start(req: OAuthStartRequest): Promise; -} - -// @public @deprecated (undocumented) -export type OAuthLogoutRequest = express.Request<{}> & { - refreshToken: string; -}; - -// @public @deprecated (undocumented) -export type OAuthProviderInfo = { - accessToken: string; - idToken?: string; - expiresInSeconds?: number; - scope: string; -}; - -// @public @deprecated -export type OAuthProviderOptions = { - clientId: string; - clientSecret: string; - callbackUrl: string; -}; - -// @public @deprecated (undocumented) -export type OAuthRefreshRequest = express.Request<{}> & { - scope: string; - refreshToken: string; -}; - -// @public @deprecated (undocumented) -export type OAuthResponse = { - profile: ProfileInfo_2; - providerInfo: OAuthProviderInfo; - backstageIdentity?: BackstageSignInResult; -}; - -// @public @deprecated (undocumented) -export type OAuthResult = { - fullProfile: Profile; - params: { - id_token?: string; - scope: string; - token_type?: string; - expires_in: number; - }; - accessToken: string; - refreshToken?: string; -}; - -// @public @deprecated (undocumented) -export type OAuthStartRequest = express.Request<{}> & { - scope: string; - state: OAuthState; -}; - -// @public @deprecated (undocumented) -export type OAuthStartResponse = { - url: string; - status?: number; -}; - -// @public @deprecated (undocumented) -export type OAuthState = OAuthState_2; - -// @public @deprecated (undocumented) -export type OidcAuthResult = OidcAuthResult_2; - -// @public @deprecated (undocumented) -export const postMessageResponse: ( - res: express.Response, - appOrigin: string, - response: WebMessageResponse, -) => void; - -// @public @deprecated (undocumented) -export const prepareBackstageIdentityResponse: typeof prepareBackstageIdentityResponse_2; - -// @public @deprecated (undocumented) -export type ProfileInfo = ProfileInfo_2; - -// @public @deprecated (undocumented) -export type ProviderFactories = { - [s: string]: AuthProviderFactory_2; -}; - -// @public @deprecated -export const providers: Readonly<{ - atlassian: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: never; - }>; - auth0: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: never; - }>; - awsAlb: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: never; - }>; - bitbucket: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: Readonly<{ - userIdMatchingUserEntityAnnotation: () => SignInResolver_2; - usernameMatchingUserEntityAnnotation: () => SignInResolver_2; - }>; - }>; - bitbucketServer: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: Readonly<{ - emailMatchingUserEntityProfileEmail: () => SignInResolver_2; - }>; - }>; - cfAccess: Readonly<{ - create: (options: { - authHandler?: AuthHandler; - signIn: { - resolver: SignInResolver_2; - }; - cache?: CacheService; - }) => AuthProviderFactory_2; - resolvers: Readonly; - }>; - gcpIap: Readonly<{ - create: (options: { - authHandler?: AuthHandler; - signIn: { - resolver: SignInResolver_2; - }; - }) => AuthProviderFactory_2; - resolvers: never; - }>; - github: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - stateEncoder?: StateEncoder; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: Readonly<{ - usernameMatchingUserEntityName: () => SignInResolver_2; - }>; - }>; - gitlab: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: never; - }>; - google: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: Readonly<{ - emailMatchingUserEntityProfileEmail: () => SignInResolver_2; - emailLocalPartMatchingUserEntityName: () => SignInResolver_2; - emailMatchingUserEntityAnnotation: () => SignInResolver_2; - }>; - }>; - microsoft: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: Readonly<{ - emailMatchingUserEntityProfileEmail: () => SignInResolver_2; - emailLocalPartMatchingUserEntityName: () => SignInResolver_2; - userIdMatchingUserEntityAnnotation: () => SignInResolver_2; - emailMatchingUserEntityAnnotation: () => SignInResolver_2; - }>; - }>; - oauth2: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: never; - }>; - oauth2Proxy: Readonly<{ - create: (options: { - authHandler?: AuthHandler; - signIn: { - resolver: SignInResolver_2; - }; - }) => AuthProviderFactory_2; - resolvers: never; - }>; - oidc: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: Readonly<{ - emailLocalPartMatchingUserEntityName: () => SignInResolver_2; - emailMatchingUserEntityProfileEmail: () => SignInResolver_2; - }>; - }>; - okta: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: Readonly<{ - emailLocalPartMatchingUserEntityName: () => SignInResolver_2; - emailMatchingUserEntityProfileEmail: () => SignInResolver_2; - emailMatchingUserEntityAnnotation(): SignInResolver_2; - }>; - }>; - onelogin: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: never; - }>; - saml: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn?: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: Readonly<{ - nameIdMatchingUserEntityName(): SignInResolver_2; - }>; - }>; - easyAuth: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler; - signIn: { - resolver: SignInResolver_2; - }; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: never; - }>; -}>; - -// @public @deprecated (undocumented) -export const readState: typeof decodeOAuthState; - -// @public @deprecated (undocumented) -export interface RouterOptions { - // (undocumented) - auth?: AuthService; - // (undocumented) - catalogApi?: CatalogApi; - // (undocumented) - config: RootConfigService; - // (undocumented) - database: DatabaseService; - // (undocumented) - disableDefaultProviderFactories?: boolean; - // (undocumented) - discovery: DiscoveryService; - // (undocumented) - httpAuth?: HttpAuthService; - // (undocumented) - logger: LoggerService; - // (undocumented) - ownershipResolver?: AuthOwnershipResolver; - // (undocumented) - providerFactories?: ProviderFactories; - // (undocumented) - tokenFactoryAlgorithm?: string; - // (undocumented) - tokenManager?: TokenManager; -} - -// @public @deprecated (undocumented) -export type SamlAuthResult = { - fullProfile: any; -}; - -// @public @deprecated (undocumented) -export type SignInInfo = SignInInfo_2; - -// @public @deprecated (undocumented) -export type SignInResolver = SignInResolver_2; - -// @public @deprecated (undocumented) -export type StateEncoder = (req: OAuthStartRequest) => Promise<{ - encodedState: string; -}>; - -// @public @deprecated (undocumented) -export type TokenParams = TokenParams_2; - -// @public @deprecated (undocumented) -export const verifyNonce: (req: express.Request, providerId: string) => void; - -// @public @deprecated (undocumented) -export type WebMessageResponse = WebMessageResponse_2; ``` diff --git a/plugins/auth-backend/src/authPlugin.ts b/plugins/auth-backend/src/authPlugin.ts index 89b7e9a780..f3877d48cd 100644 --- a/plugins/auth-backend/src/authPlugin.ts +++ b/plugins/auth-backend/src/authPlugin.ts @@ -24,7 +24,7 @@ import { AuthProviderFactory, authProvidersExtensionPoint, } from '@backstage/plugin-auth-node'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import { createRouter } from './service/router'; /** @@ -66,8 +66,7 @@ export const authPlugin = createBackendPlugin({ database: coreServices.database, discovery: coreServices.discovery, auth: coreServices.auth, - httpAuth: coreServices.httpAuth, - catalogApi: catalogServiceRef, + catalog: catalogServiceRef, }, async init({ httpRouter, @@ -76,8 +75,7 @@ export const authPlugin = createBackendPlugin({ database, discovery, auth, - httpAuth, - catalogApi, + catalog, }) { const router = await createRouter({ logger, @@ -85,10 +83,8 @@ export const authPlugin = createBackendPlugin({ database, discovery, auth, - httpAuth, - catalogApi, + catalog, providerFactories: Object.fromEntries(providers), - disableDefaultProviderFactories: true, ownershipResolver, }); httpRouter.addAuthPolicy({ diff --git a/plugins/auth-backend/src/database/AuthDatabase.ts b/plugins/auth-backend/src/database/AuthDatabase.ts index 43b48401e2..a09f28c1cd 100644 --- a/plugins/auth-backend/src/database/AuthDatabase.ts +++ b/plugins/auth-backend/src/database/AuthDatabase.ts @@ -14,12 +14,10 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; import { DatabaseService, resolvePackagePath, } from '@backstage/backend-plugin-api'; -import { ConfigReader } from '@backstage/config'; import { Knex } from 'knex'; const migrationsDir = resolvePackagePath( @@ -39,21 +37,6 @@ export class AuthDatabase { return new AuthDatabase(database); } - /** @internal */ - static forTesting(): AuthDatabase { - const config = new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', - useNullAsDefault: true, - }, - }, - }); - const database = DatabaseManager.fromConfig(config).forPlugin('auth'); - return new AuthDatabase(database); - } - static async runMigrations(knex: Knex): Promise { await knex.migrate.latest({ directory: migrationsDir, diff --git a/plugins/auth-backend/src/identity/KeyStores.test.ts b/plugins/auth-backend/src/identity/KeyStores.test.ts index 0d4ffac4d8..13818ff79c 100644 --- a/plugins/auth-backend/src/identity/KeyStores.test.ts +++ b/plugins/auth-backend/src/identity/KeyStores.test.ts @@ -20,9 +20,13 @@ import { DatabaseKeyStore } from './DatabaseKeyStore'; import { FirestoreKeyStore } from './FirestoreKeyStore'; import { KeyStores } from './KeyStores'; import { MemoryKeyStore } from './MemoryKeyStore'; -import { mockServices } from '@backstage/backend-test-utils'; +import { mockServices, TestDatabases } from '@backstage/backend-test-utils'; + +jest.setTimeout(60_000); describe('KeyStores', () => { + const databases = TestDatabases.create(); + const defaultConfigOptions = { auth: { keyStore: { @@ -32,65 +36,77 @@ describe('KeyStores', () => { }; const defaultConfig = new ConfigReader(defaultConfigOptions); - it('reads auth section from config', async () => { - const configSpy = jest.spyOn(defaultConfig, 'getOptionalConfig'); - const keyStore = await KeyStores.fromConfig(defaultConfig, { - logger: mockServices.logger.mock(), - database: AuthDatabase.forTesting(), - }); + it.each(databases.eachSupportedId())( + 'reads auth section from config, %p', + async databaseId => { + const knex = await databases.init(databaseId); + const configSpy = jest.spyOn(defaultConfig, 'getOptionalConfig'); + const keyStore = await KeyStores.fromConfig(defaultConfig, { + logger: mockServices.logger.mock(), + database: AuthDatabase.create(mockServices.database({ knex })), + }); - expect(keyStore).toBeInstanceOf(MemoryKeyStore); - expect(configSpy).toHaveBeenCalledWith('auth.keyStore'); - expect( - defaultConfig - .getOptionalConfig('auth.keyStore') - ?.getOptionalString('provider'), - ).toBe(defaultConfigOptions.auth.keyStore.provider); - }); + expect(keyStore).toBeInstanceOf(MemoryKeyStore); + expect(configSpy).toHaveBeenCalledWith('auth.keyStore'); + expect( + defaultConfig + .getOptionalConfig('auth.keyStore') + ?.getOptionalString('provider'), + ).toBe(defaultConfigOptions.auth.keyStore.provider); + }, + ); - it('can handle without auth config', async () => { - const keyStore = await KeyStores.fromConfig(new ConfigReader({}), { - logger: mockServices.logger.mock(), - database: AuthDatabase.forTesting(), - }); - expect(keyStore).toBeInstanceOf(DatabaseKeyStore); - }); + it.each(databases.eachSupportedId())( + 'can handle without auth config, %p', + async databaseId => { + const knex = await databases.init(databaseId); + const keyStore = await KeyStores.fromConfig(new ConfigReader({}), { + logger: mockServices.logger.mock(), + database: AuthDatabase.create(mockServices.database({ knex })), + }); + expect(keyStore).toBeInstanceOf(DatabaseKeyStore); + }, + ); - it('can handle additional provider config', async () => { - jest.spyOn(FirestoreKeyStore, 'verifyConnection').mockResolvedValue(); - const createSpy = jest.spyOn(FirestoreKeyStore, 'create'); + it.each(databases.eachSupportedId())( + 'can handle additional provider config, %p', + async databaseId => { + const knex = await databases.init(databaseId); + jest.spyOn(FirestoreKeyStore, 'verifyConnection').mockResolvedValue(); + const createSpy = jest.spyOn(FirestoreKeyStore, 'create'); - const configOptions = { - auth: { - keyStore: { - provider: 'firestore', - firestore: { - projectId: 'my-project', - keyFilename: 'cred.json', - path: 'my-path', - timeout: 100, - host: 'localhost', - port: 8088, - ssl: false, + const configOptions = { + auth: { + keyStore: { + provider: 'firestore', + firestore: { + projectId: 'my-project', + keyFilename: 'cred.json', + path: 'my-path', + timeout: 100, + host: 'localhost', + port: 8088, + ssl: false, + }, }, }, - }, - }; - const config = new ConfigReader(configOptions); - const keyStore = await KeyStores.fromConfig(config, { - logger: mockServices.logger.mock(), - database: AuthDatabase.forTesting(), - }); + }; + const config = new ConfigReader(configOptions); + const keyStore = await KeyStores.fromConfig(config, { + logger: mockServices.logger.mock(), + database: AuthDatabase.create(mockServices.database({ knex })), + }); - expect(keyStore).toBeInstanceOf(FirestoreKeyStore); - expect(createSpy).toHaveBeenCalledWith( - configOptions.auth.keyStore.firestore, - ); - expect( - config - .getOptionalConfig('auth.keyStore') - ?.getOptionalConfig('firestore') - ?.getOptionalString('projectId'), - ).toBe(configOptions.auth.keyStore.firestore.projectId); - }); + expect(keyStore).toBeInstanceOf(FirestoreKeyStore); + expect(createSpy).toHaveBeenCalledWith( + configOptions.auth.keyStore.firestore, + ); + expect( + config + .getOptionalConfig('auth.keyStore') + ?.getOptionalConfig('firestore') + ?.getOptionalString('projectId'), + ).toBe(configOptions.auth.keyStore.firestore.projectId); + }, + ); }); diff --git a/plugins/auth-backend/src/identity/index.ts b/plugins/auth-backend/src/identity/index.ts deleted file mode 100644 index 6ea025aae9..0000000000 --- a/plugins/auth-backend/src/identity/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { bindOidcRouter } from './router'; -export { TokenFactory } from './TokenFactory'; -export { DatabaseKeyStore } from './DatabaseKeyStore'; -export { MemoryKeyStore } from './MemoryKeyStore'; -export { FirestoreKeyStore } from './FirestoreKeyStore'; -export { KeyStores } from './KeyStores'; -export type { KeyStore, TokenParams } from './types'; -export { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler'; diff --git a/plugins/auth-backend/src/identity/types.ts b/plugins/auth-backend/src/identity/types.ts index 059b9fca84..0693a2aba0 100644 --- a/plugins/auth-backend/src/identity/types.ts +++ b/plugins/auth-backend/src/identity/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TokenParams as _TokenParams } from '@backstage/plugin-auth-node'; +import { TokenParams } from '@backstage/plugin-auth-node'; /** Represents any form of serializable JWK */ export interface AnyJWK extends Record { @@ -24,12 +24,6 @@ export interface AnyJWK extends Record { kty: string; } -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type TokenParams = _TokenParams; - /** * A TokenIssuer is able to issue verifiable ID Tokens on demand. */ @@ -37,7 +31,7 @@ export type TokenIssuer = { /** * Issues a new ID Token */ - issueToken(params: _TokenParams): Promise; + issueToken(params: TokenParams): Promise; /** * List all public keys that are currently being used to sign tokens, or have been used diff --git a/plugins/auth-backend/src/index.ts b/plugins/auth-backend/src/index.ts index 6c3f866031..1d453bde45 100644 --- a/plugins/auth-backend/src/index.ts +++ b/plugins/auth-backend/src/index.ts @@ -21,17 +21,3 @@ */ export { authPlugin as default } from './authPlugin'; -export * from './service'; -export type { TokenParams } from './identity'; -export * from './providers'; - -// flow package provides 2 functions -// ensuresXRequestedWith and postMessageResponse to safely handle CORS requests for login. The WebMessageResponse type in flow is used to type the response from the login-popup -export * from './lib/flow'; - -// OAuth wrapper over a passport or a custom `strategy`. -export * from './lib/oauth'; - -export * from './lib/catalog'; - -export { getDefaultOwnershipEntityRefs } from './lib/resolvers'; diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts index 749b684d6e..ddfa792fa1 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { TokenManager } from '@backstage/backend-common'; import { RELATION_MEMBER_OF, UserEntityV1alpha1, @@ -24,15 +23,12 @@ import { CatalogIdentityClient } from './CatalogIdentityClient'; import { mockServices } from '@backstage/backend-test-utils'; describe('CatalogIdentityClient', () => { - const tokenManager: jest.Mocked = { - getToken: jest.fn(), - authenticate: jest.fn(), - }; + const auth = mockServices.auth({ pluginId: 'auth' }); afterEach(() => jest.resetAllMocks()); it('findUser passes through the correct search params', async () => { - const catalogApi = catalogServiceMock({ + const catalog = catalogServiceMock({ entities: [ { apiVersion: 'backstage.io/v1beta1', @@ -46,27 +42,26 @@ describe('CatalogIdentityClient', () => { }, ], }); - jest.spyOn(catalogApi, 'getEntities'); + jest.spyOn(catalog, 'getEntities'); - tokenManager.getToken.mockResolvedValue({ token: 'my-token' }); const client = new CatalogIdentityClient({ - discovery: mockServices.discovery(), - catalogApi, - tokenManager, + catalog, + auth, }); await client.findUser({ annotations: { key: 'value' } }); - expect(catalogApi.getEntities).toHaveBeenCalledWith( + expect(catalog.getEntities).toHaveBeenCalledWith( { filter: { kind: 'user', 'metadata.annotations.key': 'value', }, }, - { token: 'my-token' }, + { + credentials: await auth.getOwnServiceCredentials(), + }, ); - expect(tokenManager.getToken).toHaveBeenCalledWith(); }); it('resolveCatalogMembership resolves membership', async () => { @@ -105,21 +100,19 @@ describe('CatalogIdentityClient', () => { ], }, ]; - const catalogApi = catalogServiceMock({ entities: mockUsers }); - jest.spyOn(catalogApi, 'getEntities'); - tokenManager.getToken.mockResolvedValue({ token: 'my-token' }); + const catalog = catalogServiceMock({ entities: mockUsers }); + jest.spyOn(catalog, 'getEntities'); const client = new CatalogIdentityClient({ - discovery: {} as any, - catalogApi, - tokenManager, + catalog, + auth, }); const claims = await client.resolveCatalogMembership({ entityRefs: ['inigom', 'User:default/imontoya', 'User:reality/mpatinkin'], }); - expect(catalogApi.getEntities).toHaveBeenCalledWith( + expect(catalog.getEntities).toHaveBeenCalledWith( { filter: [ { @@ -139,7 +132,9 @@ describe('CatalogIdentityClient', () => { }, ], }, - { token: 'my-token' }, + { + credentials: await auth.getOwnServiceCredentials(), + }, ); expect(claims).toMatchObject([ diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts index 252cae88af..bdec031e6a 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts @@ -14,14 +14,9 @@ * limitations under the License. */ -import { - AuthService, - DiscoveryService, - HttpAuthService, - LoggerService, -} from '@backstage/backend-plugin-api'; +import { AuthService, LoggerService } from '@backstage/backend-plugin-api'; import { ConflictError, NotFoundError } from '@backstage/errors'; -import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { CompoundEntityRef, parseEntityRef, @@ -29,38 +24,17 @@ import { stringifyEntityRef, UserEntity, } from '@backstage/catalog-model'; -import { - TokenManager, - createLegacyAuthAdapters, -} from '@backstage/backend-common'; /** * A catalog client tailored for reading out identity data from the catalog. - * - * @public - * @deprecated Use the provided `AuthResolverContext` instead, see https://backstage.io/docs/auth/identity-resolver#building-custom-resolvers */ export class CatalogIdentityClient { - private readonly catalogApi: CatalogApi; + private readonly catalog: CatalogService; private readonly auth: AuthService; - constructor(options: { - catalogApi: CatalogApi; - tokenManager?: TokenManager; - discovery: DiscoveryService; - auth?: AuthService; - httpAuth?: HttpAuthService; - }) { - this.catalogApi = options.catalogApi; - - const { auth } = createLegacyAuthAdapters({ - auth: options.auth, - httpAuth: options.httpAuth, - discovery: options.discovery, - tokenManager: options.tokenManager, - }); - - this.auth = auth; + constructor(options: { catalog: CatalogService; auth: AuthService }) { + this.catalog = options.catalog; + this.auth = options.auth; } /** @@ -78,12 +52,10 @@ export class CatalogIdentityClient { filter[`metadata.annotations.${key}`] = value; } - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); - - const { items } = await this.catalogApi.getEntities({ filter }, { token }); + const { items } = await this.catalog.getEntities( + { filter }, + { credentials: await this.auth.getOwnServiceCredentials() }, + ); if (items.length !== 1) { if (items.length > 1) { @@ -129,13 +101,11 @@ export class CatalogIdentityClient { 'metadata.name': ref.name, })); - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); - - const entities = await this.catalogApi - .getEntities({ filter }, { token }) + const entities = await this.catalog + .getEntities( + { filter }, + { credentials: await this.auth.getOwnServiceCredentials() }, + ) .then(r => r.items); if (entityRefs.length !== entities.length) { diff --git a/plugins/auth-backend/src/lib/catalog/index.ts b/plugins/auth-backend/src/lib/catalog/index.ts deleted file mode 100644 index f0f5b10808..0000000000 --- a/plugins/auth-backend/src/lib/catalog/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { CatalogIdentityClient } from './CatalogIdentityClient'; diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts deleted file mode 100644 index a61d51b27b..0000000000 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import { - safelyEncodeURIComponent, - ensuresXRequestedWith, - postMessageResponse, -} from './authFlowHelpers'; -import { WebMessageResponse } from './types'; - -describe('oauth helpers', () => { - describe('safelyEncodeURIComponent', () => { - it('encodes all occurrences of single quotes', () => { - expect(safelyEncodeURIComponent("a'ö'b")).toBe('a%27%C3%B6%27b'); - }); - }); - - describe('postMessageResponse', () => { - const appOrigin = 'http://localhost:3000'; - it('should post a message back with payload success', () => { - const mockResponse = { - end: jest.fn().mockReturnThis(), - setHeader: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - const data: WebMessageResponse = { - type: 'authorization_response', - response: { - providerInfo: { - accessToken: 'ACCESS_TOKEN', - idToken: 'ID_TOKEN', - expiresInSeconds: 10, - scope: 'email', - }, - profile: { - email: 'foo@bar.com', - }, - backstageIdentity: { - token: 'a.b.c', - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: 'a', - }, - }, - }, - }; - const encoded = safelyEncodeURIComponent(JSON.stringify(data)); - - postMessageResponse(mockResponse, appOrigin, data); - expect(mockResponse.setHeader).toHaveBeenCalledTimes(3); - expect(mockResponse.end).toHaveBeenCalledTimes(1); - expect(mockResponse.end).toHaveBeenCalledWith( - expect.stringContaining(encoded), - ); - }); - - it('should post a message back with payload error', () => { - const mockResponse = { - end: jest.fn().mockReturnThis(), - setHeader: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - const data: WebMessageResponse = { - type: 'authorization_response', - error: new Error('Unknown error occurred'), - }; - const encoded = safelyEncodeURIComponent(JSON.stringify(data)); - - postMessageResponse(mockResponse, appOrigin, data); - expect(mockResponse.setHeader).toHaveBeenCalledTimes(3); - expect(mockResponse.end).toHaveBeenCalledTimes(1); - expect(mockResponse.end).toHaveBeenCalledWith( - expect.stringContaining(encoded), - ); - }); - - it('should call postMessage twice but only one of them with target *', () => { - let responseBody = ''; - - const mockResponse = { - end: jest.fn(body => { - responseBody = body; - return this; - }), - setHeader: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - const data: WebMessageResponse = { - type: 'authorization_response', - response: { - providerInfo: { - accessToken: 'ACCESS_TOKEN', - idToken: 'ID_TOKEN', - expiresInSeconds: 10, - scope: 'email', - }, - profile: { - email: 'foo@bar.com', - }, - backstageIdentity: { - token: 'a.b.c', - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: 'a', - }, - }, - }, - }; - postMessageResponse(mockResponse, appOrigin, data); - expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2); - expect( - responseBody.match(/.postMessage\([a-zA-Z.()]*, \'\*\'\)/g), - ).toHaveLength(1); - - const errData: WebMessageResponse = { - type: 'authorization_response', - error: new Error('Unknown error occurred'), - }; - postMessageResponse(mockResponse, appOrigin, errData); - expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2); - expect( - responseBody.match(/.postMessage\([a-zA-Z.()]*, \'\*\'\)/g), - ).toHaveLength(1); - }); - - it('handles single quotes and unicode chars safely', () => { - const mockResponse = { - end: jest.fn().mockReturnThis(), - setHeader: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - const data: WebMessageResponse = { - type: 'authorization_response', - response: { - providerInfo: { - accessToken: 'ACCESS_TOKEN', - idToken: 'ID_TOKEN', - expiresInSeconds: 10, - scope: 'email', - }, - profile: { - email: 'foo@bar.com', - displayName: "Adam l'Hôpital", - }, - backstageIdentity: { - token: 'a.b.c', - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: 'a', - }, - }, - }, - }; - - postMessageResponse(mockResponse, appOrigin, data); - expect(mockResponse.setHeader).toHaveBeenCalledTimes(3); - expect(mockResponse.end).toHaveBeenCalledTimes(1); - expect(mockResponse.end).toHaveBeenCalledWith( - expect.stringContaining('Adam%20l%27H%C3%B4pital'), - ); - }); - }); - - describe('ensuresXRequestedWith', () => { - it('should return false if no header present', () => { - const mockRequest = { - header: () => jest.fn(), - } as unknown as express.Request; - expect(ensuresXRequestedWith(mockRequest)).toBe(false); - }); - - it('should return false if header present with incorrect value', () => { - const mockRequest = { - header: () => 'INVALID', - } as unknown as express.Request; - expect(ensuresXRequestedWith(mockRequest)).toBe(false); - }); - - it('should return true if header present with correct value', () => { - const mockRequest = { - header: () => 'XMLHttpRequest', - } as unknown as express.Request; - expect(ensuresXRequestedWith(mockRequest)).toBe(true); - }); - }); -}); diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts deleted file mode 100644 index 2f1770a3d4..0000000000 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import crypto from 'crypto'; -import { WebMessageResponse } from './types'; - -export const safelyEncodeURIComponent = (value: string) => { - // Note the g at the end of the regex; all occurrences of single quotes must - // be replaced, which encodeURIComponent does not do itself by default - return encodeURIComponent(value).replace(/'/g, '%27'); -}; - -/** - * @public - * @deprecated Use `sendWebMessageResponse` from `@backstage/plugin-auth-node` instead - */ -export const postMessageResponse = ( - res: express.Response, - appOrigin: string, - response: WebMessageResponse, -) => { - const jsonData = JSON.stringify(response); - const base64Data = safelyEncodeURIComponent(jsonData); - const base64Origin = safelyEncodeURIComponent(appOrigin); - - // NOTE: It is absolutely imperative that we use the safe encoder above, to - // be sure that the js code below does not allow the injection of malicious - // data. - - // TODO: Make target app origin configurable globally - - // - // postMessage fails silently if the targetOrigin is disallowed. - // So 2 postMessages are sent from the popup to the parent window. - // First, the origin being used to post the actual authorization response is - // shared with the parent window with a postMessage with targetOrigin '*'. - // Second, the actual authorization response is sent with the app origin - // as the targetOrigin. - // If the first message was received but the actual auth response was - // never received, the event listener can conclude that targetOrigin - // was disallowed, indicating potential misconfiguration. - // - const script = ` - var authResponse = decodeURIComponent('${base64Data}'); - var origin = decodeURIComponent('${base64Origin}'); - var originInfo = {'type': 'config_info', 'targetOrigin': origin}; - (window.opener || window.parent).postMessage(originInfo, '*'); - (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin); - setTimeout(() => { - window.close(); - }, 100); // same as the interval of the core-app-api lib/loginPopup.ts (to address race conditions) - `; - const hash = crypto.createHash('sha256').update(script).digest('base64'); - - res.setHeader('Content-Type', 'text/html'); - res.setHeader('X-Frame-Options', 'sameorigin'); - res.setHeader('Content-Security-Policy', `script-src 'sha256-${hash}'`); - res.end(``); -}; - -/** - * @public - * @deprecated Use inline logic to check that the `X-Requested-With` header is set to `'XMLHttpRequest'` instead. - */ -export const ensuresXRequestedWith = (req: express.Request) => { - const requiredHeader = req.header('X-Requested-With'); - if (!requiredHeader || requiredHeader !== 'XMLHttpRequest') { - return false; - } - return true; -}; diff --git a/plugins/auth-backend/src/lib/flow/index.ts b/plugins/auth-backend/src/lib/flow/index.ts deleted file mode 100644 index f7b4491edb..0000000000 --- a/plugins/auth-backend/src/lib/flow/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { ensuresXRequestedWith, postMessageResponse } from './authFlowHelpers'; - -export type { WebMessageResponse } from './types'; diff --git a/plugins/auth-backend/src/lib/flow/types.ts b/plugins/auth-backend/src/lib/flow/types.ts deleted file mode 100644 index 36f3033b21..0000000000 --- a/plugins/auth-backend/src/lib/flow/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { WebMessageResponse as _WebMessageResponse } from '@backstage/plugin-auth-node'; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type WebMessageResponse = _WebMessageResponse; diff --git a/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthHandler.test.ts b/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthHandler.test.ts deleted file mode 100644 index 5c5de6a890..0000000000 --- a/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthHandler.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AuthResolverContext } from '@backstage/plugin-auth-node'; -import { AuthHandler } from '../../providers'; -import { OAuthResult } from '../oauth'; -import { PassportProfile } from '../passport/types'; -import { adaptLegacyOAuthHandler } from './adaptLegacyOAuthHandler'; - -describe('adaptLegacyOAuthHandler', () => { - it('should pass through undefined', () => { - expect(adaptLegacyOAuthHandler(undefined)).toBeUndefined(); - }); - - it('should convert an old auth handler to a new profile transform', () => { - const authHandler: AuthHandler = jest.fn(); - const profileTransform = adaptLegacyOAuthHandler(authHandler); - - profileTransform?.( - { - fullProfile: { id: 'id' } as PassportProfile, - session: { - accessToken: 'token', - expiresInSeconds: 3, - scope: 'sco pe', - tokenType: 'bear', - idToken: 'id-token', - refreshToken: 'refresh-token', - }, - }, - { ctx: 'ctx' } as unknown as AuthResolverContext, - ); - - expect(authHandler).toHaveBeenCalledWith( - { - fullProfile: { id: 'id' }, - accessToken: 'token', - params: { - scope: 'sco pe', - id_token: 'id-token', - expires_in: 3, - token_type: 'bear', - }, - }, - { ctx: 'ctx' }, - ); - }); -}); diff --git a/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthHandler.ts b/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthHandler.ts deleted file mode 100644 index 3b8eca0a95..0000000000 --- a/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthHandler.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - OAuthAuthenticatorResult, - ProfileTransform, -} from '@backstage/plugin-auth-node'; -import { AuthHandler } from '../../providers'; -import { OAuthResult } from '../oauth'; -import { PassportProfile } from '../passport/types'; - -/** @internal */ -export function adaptLegacyOAuthHandler( - authHandler?: AuthHandler, -): ProfileTransform> | undefined { - return ( - authHandler && - (async (result, ctx) => - authHandler( - { - fullProfile: result.fullProfile, - accessToken: result.session.accessToken, - params: { - scope: result.session.scope, - id_token: result.session.idToken, - token_type: result.session.tokenType, - expires_in: result.session.expiresInSeconds!, - }, - }, - ctx, - )) - ); -} diff --git a/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthSignInResolver.test.ts b/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthSignInResolver.test.ts deleted file mode 100644 index 749cf96cbb..0000000000 --- a/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthSignInResolver.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AuthResolverContext, - PassportProfile, -} from '@backstage/plugin-auth-node'; -import { adaptLegacyOAuthSignInResolver } from './adaptLegacyOAuthSignInResolver'; - -describe('adaptLegacyOAuthSignInResolver', () => { - it('should pass through undefined', () => { - expect(adaptLegacyOAuthSignInResolver(undefined)).toBeUndefined(); - }); - - it('should convert a legacy resolver to a new one', () => { - const legacyResolver = jest.fn(); - - const newResolver = adaptLegacyOAuthSignInResolver(legacyResolver); - - newResolver?.( - { - profile: { email: 'em@i.l' }, - result: { - fullProfile: { id: 'id' } as PassportProfile, - session: { - accessToken: 'token', - expiresInSeconds: 3, - scope: 'sco pe', - tokenType: 'bear', - idToken: 'id-token', - refreshToken: 'refresh-token', - }, - }, - }, - { ctx: 'ctx' } as unknown as AuthResolverContext, - ); - - expect(legacyResolver).toHaveBeenCalledWith( - { - profile: { email: 'em@i.l' }, - result: { - fullProfile: { id: 'id' }, - accessToken: 'token', - refreshToken: 'refresh-token', - params: { - scope: 'sco pe', - id_token: 'id-token', - expires_in: 3, - token_type: 'bear', - }, - }, - }, - { ctx: 'ctx' }, - ); - }); -}); diff --git a/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthSignInResolver.ts b/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthSignInResolver.ts deleted file mode 100644 index e671a62d52..0000000000 --- a/plugins/auth-backend/src/lib/legacy/adaptLegacyOAuthSignInResolver.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - OAuthAuthenticatorResult, - PassportProfile, - SignInResolver, -} from '@backstage/plugin-auth-node'; -import { OAuthResult } from '../oauth'; - -/** @internal */ -export function adaptLegacyOAuthSignInResolver( - signInResolver?: SignInResolver, -): SignInResolver> | undefined { - return ( - signInResolver && - (async (input, ctx) => - signInResolver( - { - profile: input.profile, - result: { - fullProfile: input.result.fullProfile, - accessToken: input.result.session.accessToken, - refreshToken: input.result.session.refreshToken, - params: { - scope: input.result.session.scope, - id_token: input.result.session.idToken, - token_type: input.result.session.tokenType, - expires_in: input.result.session.expiresInSeconds!, - }, - }, - }, - ctx, - )) - ); -} diff --git a/plugins/auth-backend/src/lib/legacy/adaptOAuthSignInResolverToLegacy.test.ts b/plugins/auth-backend/src/lib/legacy/adaptOAuthSignInResolverToLegacy.test.ts deleted file mode 100644 index 521dcf6395..0000000000 --- a/plugins/auth-backend/src/lib/legacy/adaptOAuthSignInResolverToLegacy.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AuthResolverContext, - PassportProfile, -} from '@backstage/plugin-auth-node'; -import { adaptOAuthSignInResolverToLegacy } from './adaptOAuthSignInResolverToLegacy'; - -describe('adaptOAuthSignInResolverToLegacy', () => { - it('should pass through an empty object', () => { - const legacyResolvers = adaptOAuthSignInResolverToLegacy({}); - expect(legacyResolvers).toEqual({}); - - // @ts-expect-error - legacyResolvers.missing?.(); - }); - - it('should adapt a collection of sign-in resolvers', () => { - const resolverA = jest.fn(); - const resolverB = jest.fn(); - - const legacyResolvers = adaptOAuthSignInResolverToLegacy({ - resolverA, - resolverB, - }); - - const legacyResolverA = legacyResolvers.resolverA(); - legacyResolverA( - { - profile: { email: 'em@i.l' }, - result: { - fullProfile: { id: 'id' } as PassportProfile, - accessToken: 'token', - refreshToken: 'refresh-token', - params: { - scope: 'sco pe', - id_token: 'id-token', - expires_in: 3, - token_type: 'bear', - }, - }, - }, - { ctx: 'ctx' } as unknown as AuthResolverContext, - ); - - expect(resolverA).toHaveBeenCalledWith( - { - profile: { email: 'em@i.l' }, - result: { - fullProfile: { id: 'id' } as PassportProfile, - session: { - accessToken: 'token', - expiresInSeconds: 3, - scope: 'sco pe', - tokenType: 'bear', - idToken: 'id-token', - refreshToken: 'refresh-token', - }, - }, - }, - { ctx: 'ctx' }, - ); - - expect(resolverB).not.toHaveBeenCalled(); - legacyResolvers.resolverB()( - { profile: {}, result: { params: {} } } as any, - {} as any, - ); - expect(resolverB).toHaveBeenCalled(); - }); -}); diff --git a/plugins/auth-backend/src/lib/legacy/adaptOAuthSignInResolverToLegacy.ts b/plugins/auth-backend/src/lib/legacy/adaptOAuthSignInResolverToLegacy.ts deleted file mode 100644 index 8be6049348..0000000000 --- a/plugins/auth-backend/src/lib/legacy/adaptOAuthSignInResolverToLegacy.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - OAuthAuthenticatorResult, - PassportProfile, - SignInResolver, -} from '@backstage/plugin-auth-node'; -import { OAuthResult } from '../oauth'; - -/** @internal */ -export function adaptOAuthSignInResolverToLegacy< - TKeys extends string, ->(resolvers: { - [key in TKeys]: SignInResolver>; -}): { [key in TKeys]: () => SignInResolver } { - const legacyResolvers = {} as { - [key in TKeys]: () => SignInResolver; - }; - for (const name of Object.keys(resolvers) as TKeys[]) { - const resolver = resolvers[name]; - legacyResolvers[name] = () => async (input, ctx) => - resolver( - { - profile: input.profile, - result: { - fullProfile: input.result.fullProfile, - session: { - accessToken: input.result.accessToken, - expiresInSeconds: input.result.params.expires_in, - scope: input.result.params.scope, - idToken: input.result.params.id_token, - tokenType: input.result.params.token_type ?? 'bearer', - refreshToken: input.result.refreshToken, - }, - }, - }, - ctx, - ); - } - return legacyResolvers; -} diff --git a/plugins/auth-backend/src/lib/legacy/index.ts b/plugins/auth-backend/src/lib/legacy/index.ts deleted file mode 100644 index 8bd8b5c62e..0000000000 --- a/plugins/auth-backend/src/lib/legacy/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { adaptLegacyOAuthHandler } from './adaptLegacyOAuthHandler'; -export { adaptLegacyOAuthSignInResolver } from './adaptLegacyOAuthSignInResolver'; -export { adaptOAuthSignInResolverToLegacy } from './adaptOAuthSignInResolverToLegacy'; diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts deleted file mode 100644 index 48163e6e82..0000000000 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ /dev/null @@ -1,549 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import { THOUSAND_DAYS_MS, TEN_MINUTES_MS, OAuthAdapter } from './OAuthAdapter'; -import { encodeState } from './helpers'; -import { OAuthHandlers, OAuthLogoutRequest } from './types'; -import { CookieConfigurer, OAuthState } from '@backstage/plugin-auth-node'; - -const mockResponseData = { - providerInfo: { - accessToken: 'ACCESS_TOKEN', - token: 'ID_TOKEN', - expiresInSeconds: 10, - scope: 'email', - }, - profile: { - email: 'foo@bar.com', - }, - backstageIdentity: { - token: - 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', - }, -}; - -describe('OAuthAdapter', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - class MyAuthProvider implements OAuthHandlers { - async start() { - return { - url: '/url', - status: 301, - }; - } - async handler() { - return { - response: mockResponseData, - refreshToken: 'token', - }; - } - async refresh() { - return { - response: mockResponseData, - refreshToken: 'token', - }; - } - async logout(_: OAuthLogoutRequest) {} - } - const providerInstance = new MyAuthProvider(); - const mockCookieConfig: ReturnType = { - domain: 'domain.org', - path: '/auth/test-provider', - secure: false, - }; - const mockCookieConfigurer = jest.fn().mockReturnValue(mockCookieConfig); - - const oAuthProviderOptions = { - providerId: 'test-provider', - appOrigin: 'http://localhost:3000', - baseUrl: 'http://domain.org/auth', - cookieConfigurer: mockCookieConfigurer, - tokenIssuer: { - issueToken: async () => 'my-id-token', - listPublicKeys: async () => ({ keys: [] }), - }, - isOriginAllowed: () => false, - callbackUrl: 'http://domain.org/auth/test-provider/handler/frame', - }; - - const defaultState = { nonce: 'nonce', env: 'development' }; - - const createEncodedQueryMockRequest = (state: any) => { - return { - cookies: { - 'test-provider-nonce': 'nonce', - }, - query: { - state: encodeState(state), - }, - } as unknown as express.Request; - }; - - const mockResponse = { - cookie: jest.fn().mockReturnThis(), - end: jest.fn().mockReturnThis(), - setHeader: jest.fn().mockReturnThis(), - statusCode: jest.fn().mockReturnThis(), - redirect: jest.fn().mockReturnThis(), - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - const mockStartRequest = { - query: { - scope: 'user', - env: 'development', - }, - } as unknown as express.Request; - - const expectedStartAuthCookieData = { - httpOnly: true, - path: '/auth/test-provider/handler', - maxAge: TEN_MINUTES_MS, - domain: 'domain.org', - sameSite: 'lax', - secure: false, - }; - - it('sets the correct headers in start', async () => { - const oauthProvider = new OAuthAdapter( - providerInstance, - oAuthProviderOptions, - ); - - await oauthProvider.start(mockStartRequest, mockResponse); - // nonce cookie checks - expect(mockResponse.cookie).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledWith( - `${oAuthProviderOptions.providerId}-nonce`, - expect.any(String), - expect.objectContaining(expectedStartAuthCookieData), - ); - expect(mockResponse.setHeader).toHaveBeenCalledTimes(2); - expect(mockResponse.setHeader).toHaveBeenCalledWith('Location', '/url'); - expect(mockResponse.setHeader).toHaveBeenCalledWith('Content-Length', '0'); - expect(mockResponse.statusCode).toEqual(301); - expect(mockResponse.end).toHaveBeenCalledTimes(1); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - const refreshCookieData = { - ...expectedStartAuthCookieData, - path: '/auth/test-provider', - maxAge: THOUSAND_DAYS_MS, - }; - - it('sets the refresh cookie if refresh is enabled', async () => { - const oauthProvider = new OAuthAdapter(providerInstance, { - ...oAuthProviderOptions, - isOriginAllowed: () => false, - }); - - const mockRequest = createEncodedQueryMockRequest(defaultState); - - await oauthProvider.frameHandler(mockRequest, mockResponse); - expect(mockCookieConfigurer).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledWith( - expect.stringContaining('test-provider-refresh-token'), - expect.stringContaining('token'), - expect.objectContaining(refreshCookieData), - ); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - it('sets the refresh cookie if refresh is enabled with redirect', async () => { - const oauthProvider = new OAuthAdapter(providerInstance, { - ...oAuthProviderOptions, - isOriginAllowed: () => false, - }); - - const state = { - ...defaultState, - redirectUrl: 'http://localhost:3000', - flow: 'redirect', - }; - const mockRequest = createEncodedQueryMockRequest(state); - - await oauthProvider.frameHandler(mockRequest, mockResponse); - expect(mockResponse.redirect).toHaveBeenCalledTimes(1); - }); - - it('persists scope through cookie if enabled', async () => { - const handlers = { - start: jest.fn(async (_req: { state: OAuthState }) => ({ - url: '/url', - status: 301, - })), - handler: jest.fn(async () => ({ response: mockResponseData })), - refresh: jest.fn(async () => ({ response: mockResponseData })), - }; - const oauthProvider = new OAuthAdapter(handlers, { - ...oAuthProviderOptions, - persistScopes: true, - }); - - // First we test the /start request, making sure state is set - await oauthProvider.start(mockStartRequest, mockResponse); - - expect(handlers.start).toHaveBeenCalledTimes(1); - expect(handlers.start).toHaveBeenCalledWith({ - ...mockStartRequest, - scope: 'user', - state: { - nonce: expect.any(String), - env: 'development', - scope: 'user', - }, - }); - - // Then test the /handler, making sure the granted scope cookie is set - const providedState = handlers.start.mock.calls[0][0].state; - const mockHandleReq = { - cookies: { - 'test-provider-nonce': providedState.nonce, - }, - query: { - state: encodeState(providedState), - }, - } as unknown as express.Request; - const mockHandleRes = { - cookie: jest.fn().mockReturnThis(), - setHeader: jest.fn().mockReturnThis(), - end: jest.fn().mockReturnThis(), - redirect: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - await oauthProvider.frameHandler(mockHandleReq, mockHandleRes); - expect(mockHandleRes.cookie).toHaveBeenCalledTimes(1); - expect(mockHandleRes.cookie).toHaveBeenCalledWith( - 'test-provider-granted-scope', - 'user', - expect.objectContaining(refreshCookieData), - ); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - - // Then make sure scopes are forwarded correctly during refresh - const mockRefreshReq = { - query: { scope: 'ignore-me' }, - cookies: { - 'test-provider-granted-scope': 'user', - 'test-provider-refresh-token': 'refresh-token', - }, - header: jest.fn().mockReturnValue('XMLHttpRequest'), - } as unknown as express.Request; - const mockRefreshRes = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - redirect: jest.fn().mockReturnThis(), - } as unknown as express.Response; - await oauthProvider.refresh(mockRefreshReq, mockRefreshRes); - expect(handlers.refresh).toHaveBeenCalledTimes(1); - expect(handlers.refresh).toHaveBeenCalledWith( - expect.objectContaining({ - scope: 'user', - refreshToken: 'refresh-token', - }), - ); - expect(mockRefreshRes.redirect).not.toHaveBeenCalled(); - }); - - const mockRequestWithHeader = { - header: () => 'XMLHttpRequest', - cookies: { - 'test-provider-refresh-token': 'token', - }, - query: {}, - get: jest.fn(), - } as unknown as express.Request; - - it('removes refresh cookie and calls logout handler when logging out', async () => { - const logoutSpy = jest.spyOn(providerInstance, 'logout'); - const oauthProvider = new OAuthAdapter(providerInstance, { - ...oAuthProviderOptions, - isOriginAllowed: () => false, - }); - - await oauthProvider.logout(mockRequestWithHeader, mockResponse); - expect(mockRequestWithHeader.get).toHaveBeenCalledTimes(1); - expect(logoutSpy).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledWith( - expect.stringContaining('test-provider-refresh-token'), - '', - expect.objectContaining({ path: '/auth/test-provider' }), - ); - expect(mockResponse.end).toHaveBeenCalledTimes(1); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - it('gets new access-token when refreshing', async () => { - const oauthProvider = new OAuthAdapter(providerInstance, { - ...oAuthProviderOptions, - isOriginAllowed: () => false, - }); - - await oauthProvider.refresh(mockRequestWithHeader, mockResponse); - expect(mockResponse.json).toHaveBeenCalledTimes(1); - expect(mockResponse.json).toHaveBeenCalledWith({ - ...mockResponseData, - backstageIdentity: { - token: mockResponseData.backstageIdentity.token, - identity: { - type: 'user', - userEntityRef: 'user:default/jimmymarkum', - ownershipEntityRefs: ['user:default/jimmymarkum'], - }, - }, - }); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - it('sets new access-token when old cookie exists', async () => { - const oauthProvider = new OAuthAdapter(providerInstance, { - ...oAuthProviderOptions, - isOriginAllowed: () => false, - }); - - const mockRequest = { - ...mockRequestWithHeader, - cookies: { - 'test-provider-refresh-token': 'old-token', - }, - } as unknown as express.Request; - - await oauthProvider.refresh(mockRequest, mockResponse); - expect(mockRequest.get).toHaveBeenCalledTimes(1); - expect(mockCookieConfigurer).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledWith( - 'test-provider-refresh-token', - 'token', - expect.objectContaining(refreshCookieData), - ); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - it('sets the correct nonce cookie configuration', async () => { - const config = { - baseUrl: 'http://domain.org/auth', - appUrl: 'http://domain.org', - isOriginAllowed: () => false, - }; - - const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, { - ...oAuthProviderOptions, - }); - - await oauthProvider.start(mockStartRequest, mockResponse); - expect(mockCookieConfigurer).not.toHaveBeenCalled(); - expect(mockResponse.cookie).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledWith( - `${oAuthProviderOptions.providerId}-nonce`, - expect.any(String), - expect.objectContaining(expectedStartAuthCookieData), - ); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - const config = { - baseUrl: 'http://domain.org/auth', - appUrl: 'http://domain.org', - isOriginAllowed: () => false, - }; - - const mockStartRequestWithOrigin = { - query: { - scope: 'user', - env: 'development', - origin: 'http://other.domain', - }, - } as unknown as express.Request; - - it('sets the correct nonce cookie configuration using origin from request', async () => { - const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, { - ...oAuthProviderOptions, - callbackUrl: 'https://domain.org/auth/test-provider/handler/frame', - }); - - await oauthProvider.start(mockStartRequestWithOrigin, mockResponse); - expect(mockCookieConfigurer).not.toHaveBeenCalled(); - expect(mockResponse.cookie).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledWith( - `${oAuthProviderOptions.providerId}-nonce`, - expect.any(String), - expect.objectContaining({ - ...expectedStartAuthCookieData, - secure: true, - sameSite: 'none', - }), - ); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - const secureCookieData = { - ...refreshCookieData, - secure: true, - sameSite: 'lax', - maxAge: THOUSAND_DAYS_MS, - }; - - it('sets the correct cookie configuration using an secure callbackUrl', async () => { - const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, { - ...oAuthProviderOptions, - callbackUrl: 'https://domain.org/auth/test-provider/handler/frame', - }); - - const mockRequest = createEncodedQueryMockRequest(defaultState); - await oauthProvider.frameHandler(mockRequest, mockResponse); - expect(mockCookieConfigurer).not.toHaveBeenCalled(); - expect(mockResponse.cookie).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledWith( - expect.stringContaining('test-provider-refresh-token'), - expect.stringContaining('token'), - expect.objectContaining(secureCookieData), - ); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - const secureSameSiteNoneCookieData = { - ...secureCookieData, - sameSite: 'none', - }; - - it('sets the correct cookie configuration when on different domains and secure', async () => { - const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, { - ...oAuthProviderOptions, - callbackUrl: 'https://authdomain.org/auth/test-provider/handler/frame', - }); - - const mockRequest = createEncodedQueryMockRequest(defaultState); - await oauthProvider.frameHandler(mockRequest, mockResponse); - expect(mockCookieConfigurer).not.toHaveBeenCalled(); - expect(mockResponse.cookie).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledWith( - expect.stringContaining('test-provider-refresh-token'), - expect.stringContaining('token'), - expect.objectContaining({ - ...secureSameSiteNoneCookieData, - domain: 'authdomain.org', - }), - ); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - const configOriginAllowed = { - ...config, - isOriginAllowed: () => true, - }; - - it('sets the correct cookie configuration using origin from state', async () => { - const oauthProvider = OAuthAdapter.fromConfig( - configOriginAllowed, - providerInstance, - { - ...oAuthProviderOptions, - callbackUrl: 'https://domain.org/auth/test-provider/handler/frame', - }, - ); - - const mockRequest = createEncodedQueryMockRequest({ - ...defaultState, - origin: 'http://other.domain', - }); - await oauthProvider.frameHandler(mockRequest, mockResponse); - expect(mockCookieConfigurer).not.toHaveBeenCalled(); - expect(mockResponse.cookie).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledWith( - expect.stringContaining('test-provider-refresh-token'), - expect.stringContaining('token'), - expect.objectContaining(secureSameSiteNoneCookieData), - ); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - const mockRequestWithGetMockReturn = { - header: () => 'XMLHttpRequest', - cookies: { - 'test-provider-refresh-token': 'old-token', - }, - query: {}, - get: jest.fn().mockReturnValue('http://other.domain'), - } as unknown as express.Request; - - it('sets the correct cookie configuration using origin from header', async () => { - const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, { - ...oAuthProviderOptions, - callbackUrl: 'https://domain.org/auth/test-provider/handler/frame', - }); - - await oauthProvider.refresh(mockRequestWithGetMockReturn, mockResponse); - expect(mockRequestWithGetMockReturn.get).toHaveBeenCalledTimes(1); - expect(mockCookieConfigurer).not.toHaveBeenCalled(); - expect(mockResponse.cookie).toHaveBeenCalledTimes(1); - expect(mockResponse.cookie).toHaveBeenCalledWith( - 'test-provider-refresh-token', - 'token', - expect.objectContaining(secureSameSiteNoneCookieData), - ); - expect(mockResponse.redirect).not.toHaveBeenCalled(); - }); - - it('executed a response redirect when flow query string is set to "redirect"', async () => { - const handlers = { - start: jest.fn(async (_req: { state: OAuthState }) => ({ - url: '/url', - status: 301, - })), - handler: jest.fn(async () => ({ response: mockResponseData })), - refresh: jest.fn(async () => ({ response: mockResponseData })), - }; - const configWithNoPopupEnabled = { - ...configOriginAllowed, - }; - const oauthProvider = OAuthAdapter.fromConfig( - configWithNoPopupEnabled, - handlers, - { - ...oAuthProviderOptions, - callbackUrl: 'https://domain.org/auth/test-provider/handler/frame', - }, - ); - - const state = { - ...defaultState, - origin: 'http://other.domain', - redirectUrl: 'http://domain.org', - flow: 'redirect', - }; - - const mockRequest = { - ...createEncodedQueryMockRequest(state), - get: jest.fn().mockReturnValue('http://other.domain'), - } as unknown as express.Request; - - await oauthProvider.frameHandler(mockRequest, mockResponse); - expect(mockRequest.get).not.toHaveBeenCalled(); - expect(mockCookieConfigurer).not.toHaveBeenCalled(); - expect(mockResponse.cookie).not.toHaveBeenCalled(); - expect(mockResponse.redirect).toHaveBeenCalledTimes(1); - expect(mockResponse.redirect).toHaveBeenCalledWith('http://domain.org'); - }); -}); diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts deleted file mode 100644 index b5c3642024..0000000000 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express, { CookieOptions } from 'express'; -import crypto from 'crypto'; -import { URL } from 'url'; -import { - AuthProviderConfig, - AuthProviderRouteHandlers, - BackstageIdentityResponse, - BackstageSignInResult, - CookieConfigurer, - OAuthState, -} from '@backstage/plugin-auth-node'; -import { - AuthenticationError, - InputError, - isError, - NotAllowedError, -} from '@backstage/errors'; -import { defaultCookieConfigurer, readState, verifyNonce } from './helpers'; -import { - postMessageResponse, - ensuresXRequestedWith, - WebMessageResponse, -} from '../flow'; -import { - OAuthHandlers, - OAuthStartRequest, - OAuthRefreshRequest, - OAuthLogoutRequest, -} from './types'; -import { prepareBackstageIdentityResponse } from '../../providers/prepareBackstageIdentityResponse'; - -export const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000; -export const TEN_MINUTES_MS = 600 * 1000; - -/** - * @public - * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead - */ -export type OAuthAdapterOptions = { - providerId: string; - persistScopes?: boolean; - appOrigin: string; - baseUrl: string; - cookieConfigurer: CookieConfigurer; - isOriginAllowed: (origin: string) => boolean; - callbackUrl: string; -}; - -/** - * @public - * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead - */ -export class OAuthAdapter implements AuthProviderRouteHandlers { - static fromConfig( - config: AuthProviderConfig, - handlers: OAuthHandlers, - options: Pick< - OAuthAdapterOptions, - 'providerId' | 'persistScopes' | 'callbackUrl' - >, - ): OAuthAdapter { - const { appUrl, baseUrl, isOriginAllowed } = config; - const { origin: appOrigin } = new URL(appUrl); - - const cookieConfigurer = config.cookieConfigurer ?? defaultCookieConfigurer; - - return new OAuthAdapter(handlers, { - ...options, - appOrigin, - baseUrl, - cookieConfigurer, - isOriginAllowed, - }); - } - - private readonly baseCookieOptions: CookieOptions; - - constructor( - private readonly handlers: OAuthHandlers, - private readonly options: OAuthAdapterOptions, - ) { - this.baseCookieOptions = { - httpOnly: true, - sameSite: 'lax', - }; - } - - async start(req: express.Request, res: express.Response): Promise { - // retrieve scopes from request - const scope = req.query.scope?.toString() ?? ''; - const env = req.query.env?.toString(); - const origin = req.query.origin?.toString(); - const redirectUrl = req.query.redirectUrl?.toString(); - const flow = req.query.flow?.toString(); - - if (!env) { - throw new InputError('No env provided in request query parameters'); - } - - const cookieConfig = this.getCookieConfig(origin); - - const nonce = crypto.randomBytes(16).toString('base64'); - // set a nonce cookie before redirecting to oauth provider - this.setNonceCookie(res, nonce, cookieConfig); - - const state: OAuthState = { nonce, env, origin, redirectUrl, flow }; - - // If scopes are persisted then we pass them through the state so that we - // can set the cookie on successful auth - if (this.options.persistScopes) { - state.scope = scope; - } - const forwardReq = Object.assign(req, { scope, state }); - - const { url, status } = await this.handlers.start( - forwardReq as OAuthStartRequest, - ); - - res.statusCode = status || 302; - res.setHeader('Location', url); - res.setHeader('Content-Length', '0'); - res.end(); - } - - async frameHandler( - req: express.Request, - res: express.Response, - ): Promise { - let appOrigin = this.options.appOrigin; - - try { - const state: OAuthState = readState(req.query.state?.toString() ?? ''); - - if (state.origin) { - try { - appOrigin = new URL(state.origin).origin; - } catch { - throw new NotAllowedError('App origin is invalid, failed to parse'); - } - if (!this.options.isOriginAllowed(appOrigin)) { - throw new NotAllowedError(`Origin '${appOrigin}' is not allowed`); - } - } - - // verify nonce cookie and state cookie on callback - verifyNonce(req, this.options.providerId); - - const { response, refreshToken } = await this.handlers.handler(req); - - const cookieConfig = this.getCookieConfig(appOrigin); - - // Store the scope that we have been granted for this session. This is useful if - // the provider does not return granted scopes on refresh or if they are normalized. - if (this.options.persistScopes && state.scope) { - this.setGrantedScopeCookie(res, state.scope, cookieConfig); - response.providerInfo.scope = state.scope; - } - - if (refreshToken) { - // set new refresh token - this.setRefreshTokenCookie(res, refreshToken, cookieConfig); - } - - const identity = await this.populateIdentity(response.backstageIdentity); - - const responseObj: WebMessageResponse = { - type: 'authorization_response', - response: { ...response, backstageIdentity: identity }, - }; - - if (state.flow === 'redirect') { - if (!state.redirectUrl) { - throw new InputError( - 'No redirectUrl provided in request query parameters', - ); - } - res.redirect(state.redirectUrl); - return undefined; - } - // post message back to popup if successful - return postMessageResponse(res, appOrigin, responseObj); - } catch (error) { - const { name, message } = isError(error) - ? error - : new Error('Encountered invalid error'); // Being a bit safe and not forwarding the bad value - // post error message back to popup if failure - return postMessageResponse(res, appOrigin, { - type: 'authorization_response', - error: { name, message }, - }); - } - } - - async logout(req: express.Request, res: express.Response): Promise { - if (!ensuresXRequestedWith(req)) { - throw new AuthenticationError('Invalid X-Requested-With header'); - } - - if (this.handlers.logout) { - const refreshToken = this.getRefreshTokenFromCookie(req); - const revokeRequest: OAuthLogoutRequest = Object.assign(req, { - refreshToken, - }); - await this.handlers.logout(revokeRequest); - } - - // remove refresh token cookie if it is set - const origin = req.get('origin'); - const cookieConfig = this.getCookieConfig(origin); - this.removeRefreshTokenCookie(res, cookieConfig); - - res.status(200).end(); - } - - async refresh(req: express.Request, res: express.Response): Promise { - if (!ensuresXRequestedWith(req)) { - throw new AuthenticationError('Invalid X-Requested-With header'); - } - - if (!this.handlers.refresh) { - throw new InputError( - `Refresh token is not supported for provider ${this.options.providerId}`, - ); - } - - try { - const refreshToken = this.getRefreshTokenFromCookie(req); - - // throw error if refresh token is missing in the request - if (!refreshToken) { - throw new InputError('Missing session cookie'); - } - - let scope = req.query.scope?.toString() ?? ''; - if (this.options.persistScopes) { - scope = this.getGrantedScopeFromCookie(req); - } - const forwardReq = Object.assign(req, { scope, refreshToken }); - - // get new access_token - const { response, refreshToken: newRefreshToken } = - await this.handlers.refresh(forwardReq as OAuthRefreshRequest); - - const backstageIdentity = await this.populateIdentity( - response.backstageIdentity, - ); - - if (newRefreshToken && newRefreshToken !== refreshToken) { - const origin = req.get('origin'); - const cookieConfig = this.getCookieConfig(origin); - this.setRefreshTokenCookie(res, newRefreshToken, cookieConfig); - } - - res.status(200).json({ ...response, backstageIdentity }); - } catch (error) { - throw new AuthenticationError('Refresh failed', error); - } - } - - /** - * If the response from the OAuth provider includes a Backstage identity, we - * make sure it's populated with all the information we can derive from the user ID. - */ - private async populateIdentity( - identity?: BackstageSignInResult, - ): Promise { - if (!identity) { - return undefined; - } - if (!identity.token) { - throw new InputError(`Identity response must return a token`); - } - - return prepareBackstageIdentityResponse(identity); - } - - private setNonceCookie = ( - res: express.Response, - nonce: string, - cookieConfig: ReturnType, - ) => { - res.cookie(`${this.options.providerId}-nonce`, nonce, { - maxAge: TEN_MINUTES_MS, - ...this.baseCookieOptions, - ...cookieConfig, - path: `${cookieConfig.path}/handler`, - }); - }; - - private setGrantedScopeCookie = ( - res: express.Response, - scope: string, - cookieConfig: ReturnType, - ) => { - res.cookie(`${this.options.providerId}-granted-scope`, scope, { - maxAge: THOUSAND_DAYS_MS, - ...this.baseCookieOptions, - ...cookieConfig, - }); - }; - - private getRefreshTokenFromCookie = (req: express.Request) => { - return req.cookies[`${this.options.providerId}-refresh-token`]; - }; - - private getGrantedScopeFromCookie = (req: express.Request) => { - return req.cookies[`${this.options.providerId}-granted-scope`]; - }; - - private setRefreshTokenCookie = ( - res: express.Response, - refreshToken: string, - cookieConfig: ReturnType, - ) => { - res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, { - maxAge: THOUSAND_DAYS_MS, - ...this.baseCookieOptions, - ...cookieConfig, - }); - }; - - private removeRefreshTokenCookie = ( - res: express.Response, - cookieConfig: ReturnType, - ) => { - res.cookie(`${this.options.providerId}-refresh-token`, '', { - maxAge: 0, - ...this.baseCookieOptions, - ...cookieConfig, - }); - }; - - private getCookieConfig = (origin?: string) => { - return this.options.cookieConfigurer({ - providerId: this.options.providerId, - baseUrl: this.options.baseUrl, - callbackUrl: this.options.callbackUrl, - appOrigin: origin ?? this.options.appOrigin, - }); - }; -} diff --git a/plugins/auth-backend/src/lib/oauth/OAuthEnvironmentHandler.ts b/plugins/auth-backend/src/lib/oauth/OAuthEnvironmentHandler.ts deleted file mode 100644 index c9244eb29e..0000000000 --- a/plugins/auth-backend/src/lib/oauth/OAuthEnvironmentHandler.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { OAuthEnvironmentHandler as _OAuthEnvironmentHandler } from '@backstage/plugin-auth-node'; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export const OAuthEnvironmentHandler = _OAuthEnvironmentHandler; diff --git a/plugins/auth-backend/src/lib/oauth/helpers.test.ts b/plugins/auth-backend/src/lib/oauth/helpers.test.ts deleted file mode 100644 index c8db34e2cc..0000000000 --- a/plugins/auth-backend/src/lib/oauth/helpers.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import { - verifyNonce, - encodeState, - readState, - defaultCookieConfigurer, -} from './helpers'; - -describe('OAuthProvider Utils', () => { - describe('encodeState', () => { - it('should serialized values', () => { - const state = { - nonce: '123', - env: 'development', - origin: 'https://example.com', - }; - - const encoded = encodeState(state); - expect(encoded).toBe( - Buffer.from( - 'nonce=123&env=development&origin=https%3A%2F%2Fexample.com', - ).toString('hex'), - ); - - expect(readState(encoded)).toEqual(state); - }); - - it('should not include undefined values', () => { - const state = { nonce: '123', env: 'development', origin: undefined }; - - const encoded = encodeState(state); - expect(encoded).toBe( - Buffer.from('nonce=123&env=development').toString('hex'), - ); - - expect(readState(encoded)).toEqual(state); - }); - }); - - describe('verifyNonce', () => { - it('should throw error if cookie nonce missing', () => { - const state = { nonce: 'NONCE', env: 'development' }; - const mockRequest = { - cookies: {}, - query: { - state: encodeState(state), - }, - } as unknown as express.Request; - expect(() => { - verifyNonce(mockRequest, 'providera'); - }).toThrow('Auth response is missing cookie nonce'); - }); - - it('should throw error if state nonce missing', () => { - const mockRequest = { - cookies: { - 'providera-nonce': 'NONCE', - }, - query: {}, - } as unknown as express.Request; - expect(() => { - verifyNonce(mockRequest, 'providera'); - }).toThrow('OAuth state is invalid, missing env'); - }); - - it('should throw error if nonce mismatch', () => { - const state = { nonce: 'NONCEB', env: 'development' }; - const mockRequest = { - cookies: { - 'providera-nonce': 'NONCEA', - }, - query: { - state: encodeState(state), - }, - } as unknown as express.Request; - expect(() => { - verifyNonce(mockRequest, 'providera'); - }).toThrow('Invalid nonce'); - }); - - it('should not throw any error if nonce matches', () => { - const state = { nonce: 'NONCE', env: 'development' }; - const mockRequest = { - cookies: { - 'providera-nonce': 'NONCE', - }, - query: { - state: encodeState(state), - }, - } as unknown as express.Request; - expect(() => { - verifyNonce(mockRequest, 'providera'); - }).not.toThrow(); - }); - }); - - describe('defaultCookieConfigurer', () => { - it('should set the correct domain and path for a base url', () => { - expect( - defaultCookieConfigurer({ - baseUrl: '', - providerId: 'test-provider', - callbackUrl: 'http://domain.org/auth', - appOrigin: 'http://domain.org', - }), - ).toMatchObject({ - domain: 'domain.org', - path: '/auth/test-provider', - secure: false, - }); - }); - - it('should set the correct domain and path for a url containing a frame handler', () => { - expect( - defaultCookieConfigurer({ - baseUrl: '', - providerId: 'test-provider', - callbackUrl: 'http://domain.org/auth/test-provider/handler/frame', - appOrigin: 'http://domain.org', - }), - ).toMatchObject({ - domain: 'domain.org', - path: '/auth/test-provider', - secure: false, - }); - }); - - it('should set the secure flag if url is using https', () => { - expect( - defaultCookieConfigurer({ - baseUrl: '', - providerId: 'test-provider', - callbackUrl: 'https://domain.org/auth', - appOrigin: 'http://domain.org', - }), - ).toMatchObject({ - secure: true, - }); - }); - - it('should set sameSite to lax for https on the same domain', () => { - expect( - defaultCookieConfigurer({ - baseUrl: '', - providerId: 'test-provider', - callbackUrl: 'https://domain.org/auth', - appOrigin: 'http://domain.org', - }), - ).toMatchObject({ - sameSite: 'lax', - secure: true, - }); - }); - - it('should set sameSite to lax for http on the same domain', () => { - expect( - defaultCookieConfigurer({ - baseUrl: '', - providerId: 'test-provider', - callbackUrl: 'http://domain.org/auth', - appOrigin: 'http://domain.org', - }), - ).toMatchObject({ - sameSite: 'lax', - secure: false, - }); - }); - - it('should set sameSite to lax if not secure and on different domains', () => { - expect( - defaultCookieConfigurer({ - baseUrl: '', - providerId: 'test-provider', - callbackUrl: 'http://authdomain.org/auth', - appOrigin: 'http://domain.org', - }), - ).toMatchObject({ - sameSite: 'lax', - secure: false, - }); - }); - - it('should set sameSite to none if secure and on different domains', () => { - expect( - defaultCookieConfigurer({ - baseUrl: '', - providerId: 'test-provider', - callbackUrl: 'https://authdomain.org/auth', - appOrigin: 'http://domain.org', - }), - ).toMatchObject({ - sameSite: 'none', - secure: true, - }); - }); - }); -}); diff --git a/plugins/auth-backend/src/lib/oauth/helpers.ts b/plugins/auth-backend/src/lib/oauth/helpers.ts deleted file mode 100644 index fef6dd04ae..0000000000 --- a/plugins/auth-backend/src/lib/oauth/helpers.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import { - CookieConfigurer, - OAuthState, - decodeOAuthState, - encodeOAuthState, -} from '@backstage/plugin-auth-node'; - -/** - * @public - * @deprecated Use `decodeOAuthState` from `@backstage/plugin-auth-node` instead - */ -export const readState = decodeOAuthState; - -/** - * @public - * @deprecated Use `encodeOAuthState` from `@backstage/plugin-auth-node` instead - */ -export const encodeState = encodeOAuthState; - -/** - * @public - * @deprecated Use inline logic to make sure the session and state nonce matches instead. - */ -export const verifyNonce = (req: express.Request, providerId: string) => { - const cookieNonce = req.cookies[`${providerId}-nonce`]; - const state: OAuthState = readState(req.query.state?.toString() ?? ''); - const stateNonce = state.nonce; - - if (!cookieNonce) { - throw new Error('Auth response is missing cookie nonce'); - } - if (stateNonce.length === 0) { - throw new Error('Auth response is missing state nonce'); - } - if (cookieNonce !== stateNonce) { - throw new Error('Invalid nonce'); - } -}; - -export const defaultCookieConfigurer: CookieConfigurer = ({ - callbackUrl, - providerId, - appOrigin, -}) => { - const { hostname: domain, pathname, protocol } = new URL(callbackUrl); - const secure = protocol === 'https:'; - - // For situations where the auth-backend is running on a - // different domain than the app, we set the SameSite attribute - // to 'none' to allow third-party access to the cookie, but - // only if it's in a secure context (https). - let sameSite: ReturnType['sameSite'] = 'lax'; - if (new URL(appOrigin).hostname !== domain && secure) { - sameSite = 'none'; - } - - // If the provider supports callbackUrls, the pathname will - // contain the complete path to the frame handler so we need - // to slice off the trailing part of the path. - const path = pathname.endsWith(`${providerId}/handler/frame`) - ? pathname.slice(0, -'/handler/frame'.length) - : `${pathname}/${providerId}`; - - return { domain, path, secure, sameSite }; -}; diff --git a/plugins/auth-backend/src/lib/oauth/index.ts b/plugins/auth-backend/src/lib/oauth/index.ts deleted file mode 100644 index 3643898bed..0000000000 --- a/plugins/auth-backend/src/lib/oauth/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { OAuthEnvironmentHandler } from './OAuthEnvironmentHandler'; -export type { OAuthAdapterOptions } from './OAuthAdapter'; -export { OAuthAdapter } from './OAuthAdapter'; -export { encodeState, verifyNonce, readState } from './helpers'; -export type { - OAuthHandlers, - OAuthProviderInfo, - OAuthProviderOptions, - OAuthResponse, - OAuthState, - OAuthStartRequest, - OAuthRefreshRequest, - OAuthLogoutRequest, - OAuthResult, -} from './types'; diff --git a/plugins/auth-backend/src/lib/oauth/types.ts b/plugins/auth-backend/src/lib/oauth/types.ts deleted file mode 100644 index 76689abcdc..0000000000 --- a/plugins/auth-backend/src/lib/oauth/types.ts +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import { Profile as PassportProfile } from 'passport'; -import { - BackstageSignInResult, - ProfileInfo, - OAuthState as _OAuthState, -} from '@backstage/plugin-auth-node'; -import { OAuthStartResponse } from '../../providers/types'; - -/** - * Common options for passport.js-based OAuth providers - * - * @public - * @deprecated No longer in use - */ -export type OAuthProviderOptions = { - /** - * Client ID of the auth provider. - */ - clientId: string; - /** - * Client Secret of the auth provider. - */ - clientSecret: string; - /** - * Callback URL to be passed to the auth provider to redirect to after the user signs in. - */ - callbackUrl: string; -}; - -/** - * @public - * @deprecated Use `OAuthAuthenticatorResult` from `@backstage/plugin-auth-node` instead - */ -export type OAuthResult = { - fullProfile: PassportProfile; - params: { - id_token?: string; - scope: string; - token_type?: string; - expires_in: number; - }; - accessToken: string; - refreshToken?: string; -}; - -/** - * @public - * @deprecated Use `ClientAuthResponse` from `@backstage/plugin-auth-node` instead - */ -export type OAuthResponse = { - profile: ProfileInfo; - providerInfo: OAuthProviderInfo; - backstageIdentity?: BackstageSignInResult; -}; - -/** - * @public - * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead - */ -export type OAuthProviderInfo = { - /** - * An access token issued for the signed in user. - */ - accessToken: string; - /** - * (Optional) Id token issued for the signed in user. - */ - idToken?: string; - /** - * Expiry of the access token in seconds. - */ - expiresInSeconds?: number; - /** - * Scopes granted for the access token. - */ - scope: string; -}; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type OAuthState = _OAuthState; - -/** - * @public - * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead - */ -export type OAuthStartRequest = express.Request<{}> & { - scope: string; - state: OAuthState; -}; - -/** - * @public - * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead - */ -export type OAuthRefreshRequest = express.Request<{}> & { - scope: string; - refreshToken: string; -}; - -/** - * @public - * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead - */ -export type OAuthLogoutRequest = express.Request<{}> & { - refreshToken: string; -}; - -/** - * @public - * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead - */ -export interface OAuthHandlers { - /** - * Initiate a sign in request with an auth provider. - */ - start(req: OAuthStartRequest): Promise; - - /** - * Handle the redirect from the auth provider when the user has signed in. - */ - handler(req: express.Request): Promise<{ - response: OAuthResponse; - refreshToken?: string; - }>; - - /** - * (Optional) Given a refresh token and scope fetches a new access token from the auth provider. - */ - refresh?(req: OAuthRefreshRequest): Promise<{ - response: OAuthResponse; - refreshToken?: string; - }>; - - /** - * (Optional) Sign out of the auth provider. - */ - logout?(req: OAuthLogoutRequest): Promise; -} diff --git a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.test.ts b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.test.ts deleted file mode 100644 index d9e80263ee..0000000000 --- a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.test.ts +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import { UnsecuredJWT } from 'jose'; -import passport from 'passport'; -import { InternalOAuthError } from 'passport-oauth2'; -import { - executeRedirectStrategy, - executeFrameHandlerStrategy, - executeRefreshTokenStrategy, - makeProfileInfo, -} from './PassportStrategyHelper'; -import { PassportProfile } from './types'; - -const mockRequest = {} as unknown as express.Request; - -describe('PassportStrategyHelper', () => { - describe('makeProfileInfo', () => { - it('retrieves email from passport profile', () => { - const profile: PassportProfile = { - emails: [{ value: 'email' }], - provider: '', - id: '', - displayName: '', - }; - - const profileInfo = makeProfileInfo(profile); - - expect(profileInfo.email).toEqual('email'); - }); - - it('retrieves picture from passport profile avatarUrl', () => { - const profile: PassportProfile = { - avatarUrl: 'avatarUrl', - provider: '', - id: '', - displayName: '', - }; - - const profileInfo = makeProfileInfo(profile); - - expect(profileInfo.picture).toEqual('avatarUrl'); - }); - - it('falls back to picture from passport profile photos field', () => { - const profile: PassportProfile = { - photos: [{ value: 'picture' }], - provider: '', - id: '', - displayName: '', - }; - - const profileInfo = makeProfileInfo(profile); - - expect(profileInfo.picture).toEqual('picture'); - }); - - it('falls back to email from ID token', async () => { - const profile: PassportProfile = { - provider: '', - id: '', - displayName: '', - }; - - const profileInfo = makeProfileInfo( - profile, - await new UnsecuredJWT({ email: 'email' }).encode(), - ); - - expect(profileInfo.email).toEqual('email'); - }); - - it('falls back to picture from ID token', async () => { - const profile: PassportProfile = { - provider: '', - id: '', - displayName: '', - }; - - const profileInfo = makeProfileInfo( - profile, - await new UnsecuredJWT({ picture: 'picture' }).encode(), - ); - - expect(profileInfo.picture).toEqual('picture'); - }); - - it('falls back to name from ID token', async () => { - const profile: PassportProfile = { - provider: '', - id: '', - displayName: '', - }; - - const profileInfo = makeProfileInfo( - profile, - await new UnsecuredJWT({ name: 'name' }).encode(), - ); - - expect(profileInfo.displayName).toEqual('name'); - }); - - it('fails when attempting to fall back to invalid JWT', () => { - const profile: PassportProfile = { - provider: '', - id: '', - displayName: '', - }; - - expect(() => makeProfileInfo(profile, 'invalid JWT')).toThrow( - 'Failed to parse id token and get profile info', - ); - }); - }); - - class MyCustomRedirectStrategy extends passport.Strategy { - authenticate() { - this.redirect('a', 302); - } - } - - describe('executeRedirectStrategy', () => { - it('should call authenticate and resolve with OAuthStartResponse', async () => { - const mockStrategy = new MyCustomRedirectStrategy(); - const spyAuthenticate = jest.spyOn(mockStrategy, 'authenticate'); - const redirectStrategyPromise = executeRedirectStrategy( - mockRequest, - mockStrategy, - {}, - ); - expect(spyAuthenticate).toHaveBeenCalledTimes(1); - await expect(redirectStrategyPromise).resolves.toStrictEqual( - expect.objectContaining({ url: 'a', status: 302 }), - ); - }); - }); - - describe('executeFrameHandlerStrategy', () => { - class MyCustomAuthSuccessStrategy extends passport.Strategy { - authenticate() { - this.success( - { accessToken: 'ACCESS_TOKEN' }, - { refreshToken: 'REFRESH_TOKEN' }, - ); - } - } - class MyCustomAuthErrorStrategy extends passport.Strategy { - authenticate() { - this.error( - new InternalOAuthError('MyCustomAuth error', { - data: '{ "message": "Custom message" }', - }), - ); - } - } - class MyCustomAuthRedirectStrategy extends passport.Strategy { - authenticate() { - this.redirect('URL', 302); - } - } - class MyCustomAuthFailStrategy extends passport.Strategy { - authenticate() { - this.fail('challenge', 302); - } - } - - it('should resolve with user and info on success', async () => { - const mockStrategy = new MyCustomAuthSuccessStrategy(); - const spyAuthenticate = jest.spyOn(mockStrategy, 'authenticate'); - const frameHandlerStrategyPromise = executeFrameHandlerStrategy( - mockRequest, - mockStrategy, - ); - expect(spyAuthenticate).toHaveBeenCalledTimes(1); - await expect(frameHandlerStrategyPromise).resolves.toStrictEqual( - expect.objectContaining({ - result: { accessToken: 'ACCESS_TOKEN' }, - privateInfo: { refreshToken: 'REFRESH_TOKEN' }, - }), - ); - }); - - it('should reject on error', async () => { - const mockStrategy = new MyCustomAuthErrorStrategy(); - const spyAuthenticate = jest.spyOn(mockStrategy, 'authenticate'); - const frameHandlerStrategyPromise = executeFrameHandlerStrategy( - mockRequest, - mockStrategy, - ); - expect(spyAuthenticate).toHaveBeenCalledTimes(1); - await expect(frameHandlerStrategyPromise).rejects.toThrow( - 'Authentication failed, MyCustomAuth error - Custom message', - ); - }); - - it('should reject on redirect', async () => { - const mockStrategy = new MyCustomAuthRedirectStrategy(); - const spyAuthenticate = jest.spyOn(mockStrategy, 'authenticate'); - const frameHandlerStrategyPromise = executeFrameHandlerStrategy( - mockRequest, - mockStrategy, - ); - expect(spyAuthenticate).toHaveBeenCalledTimes(1); - await expect(frameHandlerStrategyPromise).rejects.toThrow( - 'Unexpected redirect', - ); - }); - - it('should reject on fail', async () => { - const mockStrategy = new MyCustomAuthFailStrategy(); - const spyAuthenticate = jest.spyOn(mockStrategy, 'authenticate'); - const frameHandlerStrategyPromise = executeFrameHandlerStrategy( - mockRequest, - mockStrategy, - ); - expect(spyAuthenticate).toHaveBeenCalledTimes(1); - await expect(frameHandlerStrategyPromise).rejects.toThrow(); - }); - }); - - describe('executeRefreshTokenStrategy', () => { - it('should resolve with a new access token, scope and expiry', async () => { - class MyCustomOAuth2Success { - getOAuthAccessToken( - _refreshToken: string, - _options: any, - callback: Function, - ) { - callback(null, 'ACCESS_TOKEN', 'REFRESH_TOKEN', { - scope: 'a', - expires_in: 10, - }); - } - } - class MyCustomRefreshTokenSuccess extends passport.Strategy { - _oauth2 = new MyCustomOAuth2Success(); - userProfile(_accessToken: string, callback: Function) { - callback(null, { - provider: 'a', - email: 'b', - name: 'c', - picture: 'd', - }); - } - } - - const mockStrategy = new MyCustomRefreshTokenSuccess(); - const refreshTokenPromise = executeRefreshTokenStrategy( - mockStrategy, - 'REFRESH_TOKEN', - 'a', - ); - await expect(refreshTokenPromise).resolves.toStrictEqual( - expect.objectContaining({ - accessToken: 'ACCESS_TOKEN', - params: expect.objectContaining({ scope: 'a', expires_in: 10 }), - }), - ); - }); - - it('should forward simple errors', async () => { - class MyCustomRefreshTokenSuccess extends passport.Strategy { - _oauth2 = new (class { - getOAuthAccessToken(_r: string, _o: any, cb: Function) { - cb(new Error('Unknown error')); - } - })(); - } - - await expect( - executeRefreshTokenStrategy( - new MyCustomRefreshTokenSuccess(), - 'REFRESH_TOKEN', - 'a', - ), - ).rejects.toThrow( - 'Failed to refresh access token; caused by Error: Unknown error', - ); - }); - - it('should forward string errors', async () => { - class MyCustomRefreshTokenSuccess extends passport.Strategy { - _oauth2 = new (class { - getOAuthAccessToken(_r: string, _o: any, cb: Function) { - cb('some silly string error'); - } - })(); - } - - await expect( - executeRefreshTokenStrategy( - new MyCustomRefreshTokenSuccess(), - 'REFRESH_TOKEN', - 'a', - ), - ).rejects.toThrow( - "Failed to refresh access token; caused by unknown error 'some silly string error'", - ); - }); - - it('should forward object errors', async () => { - class MyCustomRefreshTokenSuccess extends passport.Strategy { - _oauth2 = new (class { - getOAuthAccessToken(_r: string, _o: any, cb: Function) { - cb({ name: 'SomeError', message: 'some message' }); - } - })(); - } - - await expect( - executeRefreshTokenStrategy( - new MyCustomRefreshTokenSuccess(), - 'REFRESH_TOKEN', - 'a', - ), - ).rejects.toThrow( - 'Failed to refresh access token; caused by SomeError: some message', - ); - }); - - it('should reject with an error if access token missing in refresh callback', async () => { - class MyCustomOAuth2AccessTokenMissing { - getOAuthAccessToken( - _refreshToken: string, - _options: any, - callback: Function, - ) { - callback(null, ''); - } - } - class MyCustomRefreshTokenSuccess extends passport.Strategy { - _oauth2 = new MyCustomOAuth2AccessTokenMissing(); - } - - const mockStrategy = new MyCustomRefreshTokenSuccess(); - const refreshTokenPromise = executeRefreshTokenStrategy( - mockStrategy, - 'REFRESH_TOKEN', - 'a', - ); - await expect(refreshTokenPromise).rejects.toThrow( - 'Failed to refresh access token, no access token received', - ); - }); - }); -}); diff --git a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts deleted file mode 100644 index ef9284c1fb..0000000000 --- a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import passport from 'passport'; -import { decodeJwt } from 'jose'; -import { InternalOAuthError } from 'passport-oauth2'; -import { ProfileInfo } from '@backstage/plugin-auth-node'; -import { PassportProfile } from './types'; -import { OAuthStartResponse } from '../../providers/types'; -import { ForwardedError } from '@backstage/errors'; - -export type PassportDoneCallback = ( - err?: Error, - response?: Res, - privateInfo?: Private, -) => void; - -export const makeProfileInfo = ( - profile: PassportProfile, - idToken?: string, -): ProfileInfo => { - let email: string | undefined = undefined; - if (profile.emails && profile.emails.length > 0) { - const [firstEmail] = profile.emails; - email = firstEmail.value; - } - - let picture: string | undefined = undefined; - if (profile.avatarUrl) { - picture = profile.avatarUrl; - } else if (profile.photos && profile.photos.length > 0) { - const [firstPhoto] = profile.photos; - picture = firstPhoto.value; - } - - let displayName: string | undefined = - profile.displayName ?? profile.username ?? profile.id; - - if ((!email || !picture || !displayName) && idToken) { - try { - const decoded = decodeJwt(idToken) as { - email?: string; - name?: string; - picture?: string; - }; - if (!email && decoded.email) { - email = decoded.email; - } - if (!picture && decoded.picture) { - picture = decoded.picture; - } - if (!displayName && decoded.name) { - displayName = decoded.name; - } - } catch (e) { - throw new ForwardedError( - `Failed to parse id token and get profile info`, - e, - ); - } - } - - return { - email, - picture, - displayName, - }; -}; - -export const executeRedirectStrategy = async ( - req: express.Request, - providerStrategy: passport.Strategy, - options: Record, -): Promise => { - return new Promise(resolve => { - const strategy = Object.create(providerStrategy); - strategy.redirect = (url: string, status?: number) => { - resolve({ url, status: status ?? undefined }); - }; - - strategy.authenticate(req, { ...options }); - }); -}; - -export const executeFrameHandlerStrategy = async ( - req: express.Request, - providerStrategy: passport.Strategy, - options?: Record, -) => { - return new Promise<{ result: Result; privateInfo: PrivateInfo }>( - (resolve, reject) => { - const strategy = Object.create(providerStrategy); - strategy.success = (result: any, privateInfo: any) => { - resolve({ result, privateInfo }); - }; - strategy.fail = ( - info: { type: 'success' | 'error'; message?: string }, - // _status: number, - ) => { - reject(new Error(`Authentication rejected, ${info.message ?? ''}`)); - }; - strategy.error = (error: InternalOAuthError) => { - let message = `Authentication failed, ${error.message}`; - - if (error.oauthError?.data) { - try { - const errorData = JSON.parse(error.oauthError.data); - - if (errorData.message) { - message += ` - ${errorData.message}`; - } - } catch (parseError) { - message += ` - ${error.oauthError}`; - } - } - - reject(new Error(message)); - }; - strategy.redirect = () => { - reject(new Error('Unexpected redirect')); - }; - strategy.authenticate(req, { ...(options ?? {}) }); - }, - ); -}; - -type RefreshTokenResponse = { - /** - * 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; -}; - -export const executeRefreshTokenStrategy = async ( - providerStrategy: passport.Strategy, - refreshToken: string, - scope: string, -): Promise => { - return new Promise((resolve, reject) => { - const anyStrategy = providerStrategy as any; - const OAuth2 = anyStrategy._oauth2.constructor; - const oauth2 = new OAuth2( - anyStrategy._oauth2._clientId, - anyStrategy._oauth2._clientSecret, - anyStrategy._oauth2._baseSite, - anyStrategy._oauth2._authorizeUrl, - anyStrategy._refreshURL || anyStrategy._oauth2._accessTokenUrl, - anyStrategy._oauth2._customHeaders, - ); - - oauth2.getOAuthAccessToken( - refreshToken, - { - scope, - grant_type: 'refresh_token', - }, - ( - err: Error | null, - accessToken: string, - newRefreshToken: string, - params: any, - ) => { - if (err) { - reject(new ForwardedError(`Failed to refresh access token`, err)); - } - if (!accessToken) { - reject( - new Error( - `Failed to refresh access token, no access token received`, - ), - ); - } - - resolve({ - accessToken, - refreshToken: newRefreshToken, - params, - }); - }, - ); - }); -}; - -type ProviderStrategy = { - userProfile(accessToken: string, callback: Function): void; -}; - -export const executeFetchUserProfileStrategy = async ( - providerStrategy: passport.Strategy, - accessToken: string, -): Promise => { - return new Promise((resolve, reject) => { - const anyStrategy = providerStrategy as unknown as ProviderStrategy; - anyStrategy.userProfile( - accessToken, - (error: Error, rawProfile: PassportProfile) => { - if (error) { - reject(error); - } else { - resolve(rawProfile); - } - }, - ); - }); -}; diff --git a/plugins/auth-backend/src/lib/passport/index.ts b/plugins/auth-backend/src/lib/passport/index.ts deleted file mode 100644 index 17ab71f51f..0000000000 --- a/plugins/auth-backend/src/lib/passport/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { - executeFetchUserProfileStrategy, - executeFrameHandlerStrategy, - executeRedirectStrategy, - executeRefreshTokenStrategy, - makeProfileInfo, -} from './PassportStrategyHelper'; -export type { PassportDoneCallback } from './PassportStrategyHelper'; diff --git a/plugins/auth-backend/src/lib/passport/types.ts b/plugins/auth-backend/src/lib/passport/types.ts deleted file mode 100644 index 55fe4543fc..0000000000 --- a/plugins/auth-backend/src/lib/passport/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import passport from 'passport'; - -export type PassportProfile = passport.Profile & { - avatarUrl?: string; -}; diff --git a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts index c51d99a558..4426cb7815 100644 --- a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts +++ b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts @@ -17,7 +17,6 @@ import { CatalogAuthResolverContext } from './CatalogAuthResolverContext'; import { mockServices } from '@backstage/backend-test-utils'; import { TokenIssuer } from '../../identity/types'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { NotFoundError } from '@backstage/errors'; @@ -26,17 +25,16 @@ describe('CatalogAuthResolverContext', () => { jest.clearAllMocks(); }); - const catalogApi = catalogServiceMock(); - jest.spyOn(catalogApi, 'getEntities'); + const catalog = catalogServiceMock(); + jest.spyOn(catalog, 'getEntities'); it('adds kind to filter when missing', async () => { + const auth = mockServices.auth(); const context = CatalogAuthResolverContext.create({ logger: mockServices.logger.mock(), - catalogApi, + catalog, tokenIssuer: {} as TokenIssuer, - discovery: {} as DiscoveryService, - auth: mockServices.auth(), - httpAuth: mockServices.httpAuth(), + auth, }); await expect( @@ -44,11 +42,11 @@ describe('CatalogAuthResolverContext', () => { filter: [{}, { kind: 'group' }, { KIND: 'USER' }], }), ).rejects.toThrow(NotFoundError); - expect(catalogApi.getEntities).toHaveBeenCalledWith( + expect(catalog.getEntities).toHaveBeenCalledWith( { filter: [{ kind: 'user' }, { kind: 'group' }, { KIND: 'USER' }], }, - { token: 'mock-service-token:{"sub":"plugin:test","target":"catalog"}' }, + { credentials: await auth.getOwnServiceCredentials() }, ); }); }); diff --git a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts index 7f66179d81..bbf42c70eb 100644 --- a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts +++ b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { TokenManager } from '@backstage/backend-common'; -import { CatalogApi } from '@backstage/catalog-client'; import { DEFAULT_NAMESPACE, Entity, @@ -24,12 +22,8 @@ import { stringifyEntityRef, } from '@backstage/catalog-model'; import { ConflictError, InputError, NotFoundError } from '@backstage/errors'; -import { - AuthService, - DiscoveryService, - HttpAuthService, - LoggerService, -} from '@backstage/backend-plugin-api'; +import { AuthService, LoggerService } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { TokenIssuer } from '../../identity/types'; import { AuthOwnershipResolver, @@ -37,18 +31,9 @@ import { AuthResolverContext, TokenParams, } from '@backstage/plugin-auth-node'; -import { CatalogIdentityClient } from '../catalog'; +import { CatalogIdentityClient } from '../catalog/CatalogIdentityClient'; -/** - * Uses the default ownership resolution logic to return an array - * of entity refs that the provided entity claims ownership through. - * - * A reference to the entity itself will also be included in the returned array. - * - * @public - * @deprecated use `ctx.resolveOwnershipEntityRefs(entity)` from the provided `AuthResolverContext` instead. - */ -export function getDefaultOwnershipEntityRefs(entity: Entity) { +function getDefaultOwnershipEntityRefs(entity: Entity) { const membershipRefs = entity.relations ?.filter( @@ -59,33 +44,24 @@ export function getDefaultOwnershipEntityRefs(entity: Entity) { return Array.from(new Set([stringifyEntityRef(entity), ...membershipRefs])); } -/** - * @internal - */ export class CatalogAuthResolverContext implements AuthResolverContext { static create(options: { logger: LoggerService; - catalogApi: CatalogApi; + catalog: CatalogService; tokenIssuer: TokenIssuer; - tokenManager?: TokenManager; - discovery: DiscoveryService; auth: AuthService; - httpAuth: HttpAuthService; ownershipResolver?: AuthOwnershipResolver; }): CatalogAuthResolverContext { const catalogIdentityClient = new CatalogIdentityClient({ - catalogApi: options.catalogApi, - tokenManager: options.tokenManager, - discovery: options.discovery, + catalog: options.catalog, auth: options.auth, - httpAuth: options.httpAuth, }); return new CatalogAuthResolverContext( options.logger, options.tokenIssuer, catalogIdentityClient, - options.catalogApi, + options.catalog, options.auth, options.ownershipResolver, ); @@ -95,7 +71,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext { public readonly logger: LoggerService, public readonly tokenIssuer: TokenIssuer, public readonly catalogIdentityClient: CatalogIdentityClient, - private readonly catalogApi: CatalogApi, + private readonly catalog: CatalogService, private readonly auth: AuthService, private readonly ownershipResolver?: AuthOwnershipResolver, ) {} @@ -107,17 +83,15 @@ export class CatalogAuthResolverContext implements AuthResolverContext { async findCatalogUser(query: AuthResolverCatalogUserQuery) { let result: Entity[] | Entity | undefined = undefined; - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); if ('entityRef' in query) { const entityRef = parseEntityRef(query.entityRef, { defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - result = await this.catalogApi.getEntityByRef(entityRef, { token }); + result = await this.catalog.getEntityByRef(entityRef, { + credentials: await this.auth.getOwnServiceCredentials(), + }); } else if ('annotations' in query) { const filter: Record = { kind: 'user', @@ -125,7 +99,10 @@ export class CatalogAuthResolverContext implements AuthResolverContext { for (const [key, value] of Object.entries(query.annotations)) { filter[`metadata.annotations.${key}`] = value; } - const res = await this.catalogApi.getEntities({ filter }, { token }); + const res = await this.catalog.getEntities( + { filter }, + { credentials: await this.auth.getOwnServiceCredentials() }, + ); result = res.items; } else if ('filter' in query) { const filter = [query.filter].flat().map(value => { @@ -141,9 +118,9 @@ export class CatalogAuthResolverContext implements AuthResolverContext { } return value; }); - const res = await this.catalogApi.getEntities( + const res = await this.catalog.getEntities( { filter: filter }, - { token }, + { credentials: await this.auth.getOwnServiceCredentials() }, ); result = res.items; } else { diff --git a/plugins/auth-backend/src/lib/resolvers/index.ts b/plugins/auth-backend/src/lib/resolvers/index.ts deleted file mode 100644 index c1ca59cb25..0000000000 --- a/plugins/auth-backend/src/lib/resolvers/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { - CatalogAuthResolverContext, - getDefaultOwnershipEntityRefs, -} from './CatalogAuthResolverContext'; diff --git a/plugins/auth-backend/src/providers/atlassian/index.ts b/plugins/auth-backend/src/providers/atlassian/index.ts deleted file mode 100644 index f001463694..0000000000 --- a/plugins/auth-backend/src/providers/atlassian/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { atlassian } from './provider'; diff --git a/plugins/auth-backend/src/providers/atlassian/provider.ts b/plugins/auth-backend/src/providers/atlassian/provider.ts deleted file mode 100644 index 539f055f75..0000000000 --- a/plugins/auth-backend/src/providers/atlassian/provider.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { atlassianAuthenticator } from '@backstage/plugin-auth-backend-module-atlassian-provider'; -import { - SignInResolver, - createOAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { - adaptLegacyOAuthHandler, - adaptLegacyOAuthSignInResolver, -} from '../../lib/legacy'; -import { OAuthResult } from '../../lib/oauth'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { AuthHandler } from '../types'; - -/** - * Auth provider integration for Atlassian auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const atlassian = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - resolver: SignInResolver; - }; - }) { - return createOAuthProviderFactory({ - authenticator: atlassianAuthenticator, - profileTransform: adaptLegacyOAuthHandler(options?.authHandler), - signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), - }); - }, -}); diff --git a/plugins/auth-backend/src/providers/atlassian/strategy.ts b/plugins/auth-backend/src/providers/atlassian/strategy.ts deleted file mode 100644 index d07b09c5f8..0000000000 --- a/plugins/auth-backend/src/providers/atlassian/strategy.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import OAuth2Strategy, { InternalOAuthError } from 'passport-oauth2'; -import { Profile } from 'passport'; - -interface ProfileResponse { - account_id: string; - email: string; - name: string; - picture: string; - nickname: string; -} - -interface AtlassianStrategyOptions { - clientID: string; - clientSecret: string; - callbackURL: string; - scope: string; -} - -const defaultScopes = ['offline_access', 'read:me']; - -export default class AtlassianStrategy extends OAuth2Strategy { - private readonly profileURL: string; - - constructor( - options: AtlassianStrategyOptions, - verify: OAuth2Strategy.VerifyFunction, - ) { - if (!options.scope) { - throw new TypeError('Atlassian requires a scope option'); - } - - const scopes = options.scope.split(' '); - - const optionsWithURLs = { - ...options, - authorizationURL: `https://auth.atlassian.com/authorize`, - tokenURL: `https://auth.atlassian.com/oauth/token`, - scope: Array.from(new Set([...defaultScopes, ...scopes])), - }; - - super(optionsWithURLs, verify); - this.profileURL = 'https://api.atlassian.com/me'; - this.name = 'atlassian'; - - this._oauth2.useAuthorizationHeaderforGET(true); - } - - authorizationParams() { - return { - audience: 'api.atlassian.com', - prompt: 'consent', - }; - } - - userProfile( - accessToken: string, - done: (err?: Error | null, profile?: any) => void, - ): void { - this._oauth2.get(this.profileURL, accessToken, (err, body) => { - if (err) { - return done( - new InternalOAuthError( - 'Failed to fetch user profile', - err.statusCode, - ), - ); - } - - if (!body) { - return done( - new Error('Failed to fetch user profile, body cannot be empty'), - ); - } - - try { - const json = typeof body !== 'string' ? body.toString() : body; - const profile = AtlassianStrategy.parse(json); - return done(null, profile); - } catch (e) { - return done(new Error('Failed to parse user profile')); - } - }); - } - - static parse(json: string): Profile { - const resp = JSON.parse(json) as ProfileResponse; - - return { - id: resp.account_id, - provider: 'atlassian', - username: resp.nickname, - displayName: resp.name, - emails: [{ value: resp.email }], - photos: [{ value: resp.picture }], - }; - } -} diff --git a/plugins/auth-backend/src/providers/auth0/index.ts b/plugins/auth-backend/src/providers/auth0/index.ts deleted file mode 100644 index 94a08a5809..0000000000 --- a/plugins/auth-backend/src/providers/auth0/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { auth0 } from './provider'; diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts deleted file mode 100644 index 9ae31b305d..0000000000 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { OAuthProviderOptions, OAuthResult } from '../../lib/oauth'; - -import { AuthHandler } from '../types'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { - AuthResolverContext, - createOAuthProviderFactory, - SignInResolver, -} from '@backstage/plugin-auth-node'; -import { - adaptLegacyOAuthHandler, - adaptLegacyOAuthSignInResolver, -} from '../../lib/legacy'; -import { auth0Authenticator } from '@backstage/plugin-auth-backend-module-auth0-provider'; - -/** - * @public - * @deprecated The Auth0 auth provider was extracted to `@backstage/plugin-auth-backend-module-auth0-provider`. - */ -export type Auth0AuthProviderOptions = OAuthProviderOptions & { - domain: string; - signInResolver?: SignInResolver; - authHandler: AuthHandler; - resolverContext: AuthResolverContext; - audience?: string; - connection?: string; - connectionScope?: string; -}; - -/** - * Auth provider integration for auth0 auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const auth0 = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return createOAuthProviderFactory({ - authenticator: auth0Authenticator, - profileTransform: adaptLegacyOAuthHandler(options?.authHandler), - signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), - }); - }, -}); diff --git a/plugins/auth-backend/src/providers/auth0/strategy.ts b/plugins/auth-backend/src/providers/auth0/strategy.ts deleted file mode 100644 index cf5b522ec5..0000000000 --- a/plugins/auth-backend/src/providers/auth0/strategy.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import Auth0InternalStrategy from 'passport-auth0'; -import { StateStore } from 'passport-oauth2'; - -export interface Auth0StrategyOptionsWithRequest { - clientID: string; - clientSecret: string; - callbackURL: string; - domain: string; - passReqToCallback: true; - store: StateStore; -} - -export default class Auth0Strategy extends Auth0InternalStrategy { - constructor( - options: Auth0StrategyOptionsWithRequest, - verify: Auth0InternalStrategy.VerifyFunction, - ) { - const optionsWithURLs = { - ...options, - authorizationURL: `https://${options.domain}/authorize`, - tokenURL: `https://${options.domain}/oauth/token`, - userInfoURL: `https://${options.domain}/userinfo`, - apiUrl: `https://${options.domain}/api`, - }; - super(optionsWithURLs, verify); - } -} diff --git a/plugins/auth-backend/src/providers/aws-alb/index.ts b/plugins/auth-backend/src/providers/aws-alb/index.ts deleted file mode 100644 index 6784888b1f..0000000000 --- a/plugins/auth-backend/src/providers/aws-alb/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { awsAlb } from './provider'; -export type { AwsAlbResult } from './types'; diff --git a/plugins/auth-backend/src/providers/aws-alb/provider.ts b/plugins/auth-backend/src/providers/aws-alb/provider.ts deleted file mode 100644 index 18d4f42f32..0000000000 --- a/plugins/auth-backend/src/providers/aws-alb/provider.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AwsAlbResult, - awsAlbAuthenticator, -} from '@backstage/plugin-auth-backend-module-aws-alb-provider'; -import { - SignInResolver, - createProxyAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { AuthHandler } from '../types'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; - -/** - * Auth provider integration for AWS ALB auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const awsAlb = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth - * response into the profile that will be presented to the user. The default - * implementation just provides the authenticated email that the IAP - * presented. - */ - authHandler?: AuthHandler; - /** - * Configures sign-in for this provider. - */ - signIn: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return createProxyAuthProviderFactory({ - authenticator: awsAlbAuthenticator, - profileTransform: options?.authHandler, - signInResolver: options?.signIn?.resolver, - }); - }, -}); diff --git a/plugins/auth-backend/src/providers/aws-alb/types.ts b/plugins/auth-backend/src/providers/aws-alb/types.ts deleted file mode 100644 index 2640a4a7be..0000000000 --- a/plugins/auth-backend/src/providers/aws-alb/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AwsAlbResult as _AwsAlbResult } from '@backstage/plugin-auth-backend-module-aws-alb-provider'; - -/** - * The result of the initial auth challenge. This is the input to the auth - * callbacks. - * - * @public - * @deprecated import from `@backstage/plugin-auth-backend-module-aws-alb-provider` instead - */ -export type AwsAlbResult = _AwsAlbResult; diff --git a/plugins/auth-backend/src/providers/azure-easyauth/index.ts b/plugins/auth-backend/src/providers/azure-easyauth/index.ts deleted file mode 100644 index de50e32745..0000000000 --- a/plugins/auth-backend/src/providers/azure-easyauth/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { easyAuth } from './provider'; -import { AzureEasyAuthResult } from '@backstage/plugin-auth-backend-module-azure-easyauth-provider'; - -/** - * @public - * @deprecated import AzureEasyAuthResult from `@backstage/plugin-auth-backend-module-azure-easyauth-provider` instead - */ -export type EasyAuthResult = AzureEasyAuthResult; diff --git a/plugins/auth-backend/src/providers/azure-easyauth/provider.ts b/plugins/auth-backend/src/providers/azure-easyauth/provider.ts deleted file mode 100644 index 202f99a7e1..0000000000 --- a/plugins/auth-backend/src/providers/azure-easyauth/provider.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - SignInResolver, - createProxyAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { AuthHandler } from '../types'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { - AzureEasyAuthResult, - azureEasyAuthAuthenticator, -} from '@backstage/plugin-auth-backend-module-azure-easyauth-provider'; - -/** - * Auth provider integration for Azure EasyAuth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const easyAuth = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return createProxyAuthProviderFactory({ - authenticator: azureEasyAuthAuthenticator, - profileTransform: options?.authHandler, - signInResolver: options?.signIn?.resolver, - }); - }, -}); diff --git a/plugins/auth-backend/src/providers/bitbucket/index.ts b/plugins/auth-backend/src/providers/bitbucket/index.ts deleted file mode 100644 index 9d82c1066c..0000000000 --- a/plugins/auth-backend/src/providers/bitbucket/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { bitbucket } from './provider'; -export type { - BitbucketPassportProfile, - BitbucketOAuthResult, -} from './provider'; diff --git a/plugins/auth-backend/src/providers/bitbucket/provider.ts b/plugins/auth-backend/src/providers/bitbucket/provider.ts deleted file mode 100644 index 69ac2cf23b..0000000000 --- a/plugins/auth-backend/src/providers/bitbucket/provider.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - bitbucketAuthenticator, - bitbucketSignInResolvers, -} from '@backstage/plugin-auth-backend-module-bitbucket-provider'; -import { - SignInResolver, - createOAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { Profile as PassportProfile } from 'passport'; -import { - adaptLegacyOAuthHandler, - adaptLegacyOAuthSignInResolver, - adaptOAuthSignInResolverToLegacy, -} from '../../lib/legacy'; -import { OAuthResult } from '../../lib/oauth'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { AuthHandler } from '../types'; - -/** - * @public - * @deprecated The Bitbucket auth provider was extracted to `@backstage/plugin-auth-backend-module-bitbucket-provider`. - */ -export type BitbucketOAuthResult = { - fullProfile: BitbucketPassportProfile; - params: { - id_token?: string; - scope: string; - expires_in: number; - }; - accessToken: string; - refreshToken?: string; -}; - -/** - * @public - * @deprecated The Bitbucket auth provider was extracted to `@backstage/plugin-auth-backend-module-bitbucket-provider`. - */ -export type BitbucketPassportProfile = PassportProfile & { - id?: string; - displayName?: string; - username?: string; - avatarUrl?: string; - _json?: { - links?: { - avatar?: { - href?: string; - }; - }; - }; -}; - -/** - * Auth provider integration for Bitbucket auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const bitbucket = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - resolver: SignInResolver; - }; - }) { - return createOAuthProviderFactory({ - authenticator: bitbucketAuthenticator, - profileTransform: adaptLegacyOAuthHandler(options?.authHandler), - signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), - }); - }, - resolvers: adaptOAuthSignInResolverToLegacy({ - userIdMatchingUserEntityAnnotation: - bitbucketSignInResolvers.userIdMatchingUserEntityAnnotation(), - usernameMatchingUserEntityAnnotation: - bitbucketSignInResolvers.usernameMatchingUserEntityAnnotation(), - }), -}); diff --git a/plugins/auth-backend/src/providers/bitbucketServer/index.ts b/plugins/auth-backend/src/providers/bitbucketServer/index.ts deleted file mode 100644 index cef12cd50d..0000000000 --- a/plugins/auth-backend/src/providers/bitbucketServer/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { bitbucketServer } from './provider'; -export type { BitbucketServerOAuthResult } from './provider'; diff --git a/plugins/auth-backend/src/providers/bitbucketServer/provider.ts b/plugins/auth-backend/src/providers/bitbucketServer/provider.ts deleted file mode 100644 index c49cf5b7b1..0000000000 --- a/plugins/auth-backend/src/providers/bitbucketServer/provider.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Profile as PassportProfile } from 'passport'; -import { - AuthResolverContext, - createOAuthProviderFactory, - SignInResolver, -} from '@backstage/plugin-auth-node'; -import { - bitbucketServerAuthenticator, - bitbucketServerSignInResolvers, -} from '@backstage/plugin-auth-backend-module-bitbucket-server-provider'; -import { OAuthProviderOptions } from '../../lib/oauth'; -import { - adaptLegacyOAuthHandler, - adaptLegacyOAuthSignInResolver, -} from '../../lib/legacy'; -import { AuthHandler } from '../types'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; - -/** - * @public - * @deprecated The Bitbucket Server auth provider was extracted to `@backstage/plugin-auth-backend-module-bitbucket-server-provider`. - */ -export type BitbucketServerOAuthResult = { - fullProfile: PassportProfile; - params: { - scope: string; - access_token?: string; - token_type?: string; - expires_in?: number; - }; - accessToken: string; - refreshToken?: string; -}; - -/** - * @public - * @deprecated The Bitbucket Server auth provider was extracted to `@backstage/plugin-auth-backend-module-bitbucket-server-provider`. - */ -export type BitbucketServerAuthProviderOptions = OAuthProviderOptions & { - host: string; - authorizationUrl: string; - tokenUrl: string; - authHandler: AuthHandler; - signInResolver?: SignInResolver; - resolverContext: AuthResolverContext; -}; - -export const bitbucketServer = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return createOAuthProviderFactory({ - authenticator: bitbucketServerAuthenticator, - profileTransform: adaptLegacyOAuthHandler(options?.authHandler), - signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), - }); - }, - resolvers: { - /** - * Looks up the user by matching their email to the entity email. - */ - emailMatchingUserEntityProfileEmail: - (): SignInResolver => { - const resolver = - bitbucketServerSignInResolvers.emailMatchingUserEntityProfileEmail(); - return async (info, ctx) => { - return resolver( - { - profile: info.profile, - result: { - fullProfile: info.result.fullProfile, - session: { - accessToken: info.result.accessToken, - tokenType: info.result.params.token_type ?? 'bearer', - scope: info.result.params.scope, - expiresInSeconds: info.result.params.expires_in, - refreshToken: info.result.refreshToken, - }, - }, - }, - ctx, - ); - }; - }, - }, -}); diff --git a/plugins/auth-backend/src/providers/cloudflare-access/index.ts b/plugins/auth-backend/src/providers/cloudflare-access/index.ts deleted file mode 100644 index 19b56bd825..0000000000 --- a/plugins/auth-backend/src/providers/cloudflare-access/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { cfAccess } from './provider'; -export type { - CloudflareAccessClaims, - CloudflareAccessGroup, - CloudflareAccessResult, - CloudflareAccessIdentityProfile, -} from './provider'; diff --git a/plugins/auth-backend/src/providers/cloudflare-access/provider.ts b/plugins/auth-backend/src/providers/cloudflare-access/provider.ts deleted file mode 100644 index 4710d60d47..0000000000 --- a/plugins/auth-backend/src/providers/cloudflare-access/provider.ts +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - cloudflareAccessSignInResolvers, - createCloudflareAccessAuthenticator, -} from '@backstage/plugin-auth-backend-module-cloudflare-access-provider'; -import { - SignInResolver, - createProxyAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { AuthHandler } from '../types'; -import { CacheService } from '@backstage/backend-plugin-api'; - -/** - * CloudflareAccessClaims - * - * Can be used in externally provided auth handler or sign in resolver to - * enrich user profile for sign-in user entity - * - * @public - * @deprecated import from `@backstage/plugin-auth-backend-module-cloudflare-access-provider` instead - */ -export type CloudflareAccessClaims = { - /** - * `aud` identifies the application to which the JWT is issued. - */ - aud: string[]; - /** - * `email` contains the email address of the authenticated user. - */ - email: string; - /** - * iat and exp are the issuance and expiration timestamps. - */ - exp: number; - iat: number; - /** - * `nonce` is the session identifier. - */ - nonce: string; - /** - * `identity_nonce` is available in the Application Token and can be used to - * query all group membership for a given user. - */ - identity_nonce: string; - /** - * `sub` contains the identifier of the authenticated user. - */ - sub: string; - /** - * `iss` the issuer is the application’s Cloudflare Access Domain URL. - */ - iss: string; - /** - * `custom` contains SAML attributes in the Application Token specified by an - * administrator in the identity provider configuration. - */ - custom: string; -}; - -/** - * CloudflareAccessGroup - * - * @public - * @deprecated import from `@backstage/plugin-auth-backend-module-cloudflare-access-provider` instead - */ -export type CloudflareAccessGroup = { - /** - * Group id - */ - id: string; - /** - * Name of group as defined in Cloudflare zero trust dashboard - */ - name: string; - /** - * Access group email address - */ - email: string; -}; - -/** - * CloudflareAccessIdentityProfile - * - * Can be used in externally provided auth handler or sign in resolver to - * enrich user profile for sign-in user entity - * - * @public - * @deprecated import from `@backstage/plugin-auth-backend-module-cloudflare-access-provider` instead - */ -export type CloudflareAccessIdentityProfile = { - id: string; - name: string; - email: string; - groups: CloudflareAccessGroup[]; -}; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-backend-module-cloudflare-access-provider` instead - */ -export type CloudflareAccessResult = { - claims: CloudflareAccessClaims; - cfIdentity: CloudflareAccessIdentityProfile; - expiresInSeconds?: number; - token: string; -}; - -/** - * Auth provider integration for Cloudflare Access auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const cfAccess = createAuthProviderIntegration({ - create(options: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - - /** - * Cache service object that was configured for the Backstage backend, - * should be provided via the backend auth plugin. - */ - cache?: CacheService; - }) { - return createProxyAuthProviderFactory({ - authenticator: createCloudflareAccessAuthenticator({ - cache: options.cache, - }), - profileTransform: options?.authHandler, - signInResolver: options?.signIn?.resolver, - signInResolverFactories: cloudflareAccessSignInResolvers, - }); - }, - resolvers: cloudflareAccessSignInResolvers, -}); diff --git a/plugins/auth-backend/src/providers/createAuthProviderIntegration.ts b/plugins/auth-backend/src/providers/createAuthProviderIntegration.ts deleted file mode 100644 index 2f7bb4a4ec..0000000000 --- a/plugins/auth-backend/src/providers/createAuthProviderIntegration.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AuthProviderFactory, - SignInResolver, -} from '@backstage/plugin-auth-node'; - -/** - * Creates a standardized representation of an integration with a third-party - * auth provider. - * - * The returned object facilitates the creation of provider instances, and - * supplies built-in sign-in resolvers for the specific provider. - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export function createAuthProviderIntegration< - TCreateOptions extends unknown[], - TResolvers extends - | { - [name in string]: (...args: any[]) => SignInResolver; - }, ->(config: { - create: (...args: TCreateOptions) => AuthProviderFactory; - resolvers?: TResolvers; -}): Readonly<{ - create: (...args: TCreateOptions) => AuthProviderFactory; - // If no resolvers are defined, this receives the type `never` - resolvers: Readonly; -}> { - return Object.freeze({ - ...config, - resolvers: Object.freeze(config.resolvers ?? ({} as any)), - }); -} diff --git a/plugins/auth-backend/src/providers/gcp-iap/index.ts b/plugins/auth-backend/src/providers/gcp-iap/index.ts deleted file mode 100644 index 12f76ec142..0000000000 --- a/plugins/auth-backend/src/providers/gcp-iap/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { gcpIap } from './provider'; -export type { GcpIapResult, GcpIapTokenInfo } from './types'; diff --git a/plugins/auth-backend/src/providers/gcp-iap/provider.ts b/plugins/auth-backend/src/providers/gcp-iap/provider.ts deleted file mode 100644 index f40cb4ed25..0000000000 --- a/plugins/auth-backend/src/providers/gcp-iap/provider.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { gcpIapAuthenticator } from '@backstage/plugin-auth-backend-module-gcp-iap-provider'; -import { - SignInResolver, - createProxyAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { AuthHandler } from '../types'; -import { GcpIapResult } from './types'; - -/** - * Auth provider integration for Google Identity-Aware Proxy auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const gcpIap = createAuthProviderIntegration({ - create(options: { - /** - * The profile transformation function used to verify and convert the auth - * response into the profile that will be presented to the user. The default - * implementation just provides the authenticated email that the IAP - * presented. - */ - authHandler?: AuthHandler; - - /** - * Configures sign-in for this provider. - */ - signIn: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return createProxyAuthProviderFactory({ - authenticator: gcpIapAuthenticator, - profileTransform: options?.authHandler, - signInResolver: options?.signIn?.resolver, - }); - }, -}); diff --git a/plugins/auth-backend/src/providers/gcp-iap/types.ts b/plugins/auth-backend/src/providers/gcp-iap/types.ts deleted file mode 100644 index b1f69318fc..0000000000 --- a/plugins/auth-backend/src/providers/gcp-iap/types.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - GcpIapTokenInfo as _GcpIapTokenInfo, - GcpIapResult as _GcpIapResult, -} from '@backstage/plugin-auth-backend-module-gcp-iap-provider'; - -/** - * The data extracted from an IAP token. - * - * @public - * @deprecated import from `@backstage/plugin-auth-backend-module-gcp-iap-provider` instead - */ -export type GcpIapTokenInfo = _GcpIapTokenInfo; - -/** - * The result of the initial auth challenge. This is the input to the auth - * callbacks. - * - * @public - * @deprecated import from `@backstage/plugin-auth-backend-module-gcp-iap-provider` instead - */ -export type GcpIapResult = _GcpIapResult; diff --git a/plugins/auth-backend/src/providers/github/index.ts b/plugins/auth-backend/src/providers/github/index.ts deleted file mode 100644 index 2f4bb1f6cd..0000000000 --- a/plugins/auth-backend/src/providers/github/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { github } from './provider'; -export type { GithubOAuthResult } from './provider'; diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts deleted file mode 100644 index 6c6738ad57..0000000000 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Profile as PassportProfile } from 'passport'; -import { AuthHandler, StateEncoder } from '../types'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { - createOAuthProviderFactory, - OAuthAuthenticatorResult, - ProfileTransform, - SignInResolver, -} from '@backstage/plugin-auth-node'; -import { githubAuthenticator } from '@backstage/plugin-auth-backend-module-github-provider'; - -/** - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export type GithubOAuthResult = { - fullProfile: PassportProfile; - params: { - scope: string; - expires_in?: string; - refresh_token_expires_in?: string; - }; - accessToken: string; - refreshToken?: string; -}; - -/** - * Auth provider integration for GitHub auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const github = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - - /** - * The state encoder used to encode the 'state' parameter on the OAuth request. - * - * It should return a string that takes the state params (from the request), url encodes the params - * and finally base64 encodes them. - * - * Providing your own stateEncoder will allow you to add addition parameters to the state field. - * - * It is typed as follows: - * `export type StateEncoder = (input: OAuthState) => Promise<{encodedState: string}>;` - * - * Note: the stateEncoder must encode a 'nonce' value and an 'env' value. Without this, the OAuth flow will fail - * (These two values will be set by the req.state by default) - * - * For more information, please see the helper module in ../../oauth/helpers #readState - */ - stateEncoder?: StateEncoder; - }) { - const authHandler = options?.authHandler; - const signInResolver = options?.signIn?.resolver; - return createOAuthProviderFactory({ - authenticator: githubAuthenticator, - profileTransform: - authHandler && - ((async (result, ctx) => - authHandler!( - { - fullProfile: result.fullProfile, - accessToken: result.session.accessToken, - params: { - scope: result.session.scope, - expires_in: result.session.expiresInSeconds - ? String(result.session.expiresInSeconds) - : '', - refresh_token_expires_in: result.session - .refreshTokenExpiresInSeconds - ? String(result.session.refreshTokenExpiresInSeconds) - : '', - }, - }, - ctx, - )) as ProfileTransform>), - signInResolver: - signInResolver && - ((async ({ profile, result }, ctx) => - signInResolver( - { - profile: profile, - result: { - fullProfile: result.fullProfile, - accessToken: result.session.accessToken, - refreshToken: result.session.refreshToken, - params: { - scope: result.session.scope, - expires_in: result.session.expiresInSeconds - ? String(result.session.expiresInSeconds) - : '', - refresh_token_expires_in: result.session - .refreshTokenExpiresInSeconds - ? String(result.session.refreshTokenExpiresInSeconds) - : '', - }, - }, - }, - ctx, - )) as SignInResolver>), - }); - }, - resolvers: { - /** - * Looks up the user by matching their GitHub username to the entity name. - */ - usernameMatchingUserEntityName: (): SignInResolver => { - return async (info, ctx) => { - const { fullProfile } = info.result; - - const userId = fullProfile.username; - if (!userId) { - throw new Error(`GitHub user profile does not contain a username`); - } - - return ctx.signInWithCatalogUser({ entityRef: { name: userId } }); - }; - }, - }, -}); diff --git a/plugins/auth-backend/src/providers/gitlab/index.ts b/plugins/auth-backend/src/providers/gitlab/index.ts deleted file mode 100644 index 9b60d1f18a..0000000000 --- a/plugins/auth-backend/src/providers/gitlab/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { gitlab } from './provider'; diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts deleted file mode 100644 index 503145a805..0000000000 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AuthHandler } from '../types'; -import { OAuthResult } from '../../lib/oauth'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { - SignInResolver, - createOAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { - adaptLegacyOAuthHandler, - adaptLegacyOAuthSignInResolver, -} from '../../lib/legacy'; -import { gitlabAuthenticator } from '@backstage/plugin-auth-backend-module-gitlab-provider'; - -/** - * Auth provider integration for GitLab auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const gitlab = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - resolver: SignInResolver; - }; - }) { - return createOAuthProviderFactory({ - authenticator: gitlabAuthenticator, - profileTransform: adaptLegacyOAuthHandler(options?.authHandler), - signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), - }); - }, -}); diff --git a/plugins/auth-backend/src/providers/google/index.ts b/plugins/auth-backend/src/providers/google/index.ts deleted file mode 100644 index 5e8c3236e4..0000000000 --- a/plugins/auth-backend/src/providers/google/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { google } from './provider'; diff --git a/plugins/auth-backend/src/providers/google/provider.test.ts b/plugins/auth-backend/src/providers/google/provider.test.ts deleted file mode 100644 index abde2b66d1..0000000000 --- a/plugins/auth-backend/src/providers/google/provider.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { googleAuthenticator } from '@backstage/plugin-auth-backend-module-google-provider'; -import { createOAuthProviderFactory } from '@backstage/plugin-auth-node'; -import { google } from './provider'; - -jest.mock('@backstage/plugin-auth-node', () => ({ - ...jest.requireActual('@backstage/plugin-auth-node'), - createOAuthProviderFactory: jest.fn(() => 'provider-factory'), -})); - -describe('createGoogleProvider', () => { - afterEach(() => jest.clearAllMocks()); - - it('should be created', async () => { - expect(google.create()).toBe('provider-factory'); - - expect(createOAuthProviderFactory).toHaveBeenCalledWith({ - authenticator: googleAuthenticator, - }); - }); - - it('should be created with sign-in resolver', async () => { - expect(google.create({ signIn: { resolver: jest.fn() } })).toBe( - 'provider-factory', - ); - - expect(createOAuthProviderFactory).toHaveBeenCalledWith({ - authenticator: googleAuthenticator, - signInResolver: expect.any(Function), - }); - }); - - it('should be created with sign-in resolver and auth handler', async () => { - expect( - google.create({ - signIn: { resolver: jest.fn() }, - authHandler: jest.fn(), - }), - ).toBe('provider-factory'); - - expect(createOAuthProviderFactory).toHaveBeenCalledWith({ - authenticator: googleAuthenticator, - signInResolver: expect.any(Function), - profileTransform: expect.any(Function), - }); - }); - - it('should have resolvers', () => { - expect(google.resolvers).toEqual({ - emailLocalPartMatchingUserEntityName: expect.any(Function), - emailMatchingUserEntityAnnotation: expect.any(Function), - emailMatchingUserEntityProfileEmail: expect.any(Function), - }); - }); -}); diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts deleted file mode 100644 index 99a9c40ecb..0000000000 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - googleAuthenticator, - googleSignInResolvers, -} from '@backstage/plugin-auth-backend-module-google-provider'; -import { - SignInResolver, - commonSignInResolvers, - createOAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { - adaptLegacyOAuthHandler, - adaptLegacyOAuthSignInResolver, - adaptOAuthSignInResolverToLegacy, -} from '../../lib/legacy'; -import { OAuthResult } from '../../lib/oauth'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { AuthHandler } from '../types'; - -/** - * Auth provider integration for Google auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const google = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return createOAuthProviderFactory({ - authenticator: googleAuthenticator, - profileTransform: adaptLegacyOAuthHandler(options?.authHandler), - signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), - }); - }, - resolvers: adaptOAuthSignInResolverToLegacy({ - emailLocalPartMatchingUserEntityName: - commonSignInResolvers.emailLocalPartMatchingUserEntityName(), - emailMatchingUserEntityProfileEmail: - commonSignInResolvers.emailMatchingUserEntityProfileEmail(), - emailMatchingUserEntityAnnotation: - googleSignInResolvers.emailMatchingUserEntityAnnotation(), - }), -}); diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts deleted file mode 100644 index b9543c275c..0000000000 --- a/plugins/auth-backend/src/providers/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export type { AwsAlbResult } from './aws-alb'; -export type { EasyAuthResult } from './azure-easyauth'; -export type { - BitbucketOAuthResult, - BitbucketPassportProfile, -} from './bitbucket'; -export type { BitbucketServerOAuthResult } from './bitbucketServer'; -export type { - CloudflareAccessClaims, - CloudflareAccessGroup, - CloudflareAccessResult, - CloudflareAccessIdentityProfile, -} from './cloudflare-access'; -export type { GithubOAuthResult } from './github'; -export type { OAuth2ProxyResult } from './oauth2-proxy'; -export type { OidcAuthResult } from './oidc'; -export type { SamlAuthResult } from './saml'; -export type { GcpIapResult, GcpIapTokenInfo } from './gcp-iap'; - -export { providers, defaultAuthProviderFactories } from './providers'; -export { createOriginFilter, type ProviderFactories } from './router'; - -export { createAuthProviderIntegration } from './createAuthProviderIntegration'; - -export type { - AuthProviderConfig, - AuthProviderRouteHandlers, - AuthProviderFactory, - AuthHandler, - AuthResolverCatalogUserQuery, - AuthResolverContext, - AuthHandlerResult, - SignInResolver, - SignInInfo, - CookieConfigurer, - StateEncoder, - AuthResponse, - ProfileInfo, - OAuthStartResponse, -} from './types'; - -export { prepareBackstageIdentityResponse } from './prepareBackstageIdentityResponse'; diff --git a/plugins/auth-backend/src/providers/microsoft/index.ts b/plugins/auth-backend/src/providers/microsoft/index.ts deleted file mode 100644 index 16dadc3bb0..0000000000 --- a/plugins/auth-backend/src/providers/microsoft/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { microsoft } from './provider'; diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts deleted file mode 100644 index f461fe8341..0000000000 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AuthHandler } from '../types'; -import { OAuthResult } from '../../lib/oauth'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { - SignInResolver, - commonSignInResolvers, - createOAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { - adaptLegacyOAuthHandler, - adaptLegacyOAuthSignInResolver, - adaptOAuthSignInResolverToLegacy, -} from '../../lib/legacy'; -import { - microsoftAuthenticator, - microsoftSignInResolvers, -} from '@backstage/plugin-auth-backend-module-microsoft-provider'; - -/** - * Auth provider integration for Microsoft auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const microsoft = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - resolver: SignInResolver; - }; - }) { - return createOAuthProviderFactory({ - authenticator: microsoftAuthenticator, - profileTransform: adaptLegacyOAuthHandler(options?.authHandler), - signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), - }); - }, - resolvers: adaptOAuthSignInResolverToLegacy({ - emailLocalPartMatchingUserEntityName: - commonSignInResolvers.emailLocalPartMatchingUserEntityName(), - emailMatchingUserEntityProfileEmail: - commonSignInResolvers.emailMatchingUserEntityProfileEmail(), - emailMatchingUserEntityAnnotation: - microsoftSignInResolvers.emailMatchingUserEntityAnnotation(), - userIdMatchingUserEntityAnnotation: - microsoftSignInResolvers.userIdMatchingUserEntityAnnotation(), - }), -}); diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/index.ts b/plugins/auth-backend/src/providers/oauth2-proxy/index.ts deleted file mode 100644 index 2e4e7d016f..0000000000 --- a/plugins/auth-backend/src/providers/oauth2-proxy/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { oauth2Proxy } from './provider'; -import { OAuth2ProxyResult as _OAuth2ProxyResult } from '@backstage/plugin-auth-backend-module-oauth2-proxy-provider'; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-backend-module-oauth2-proxy-provider` instead - */ -export type OAuth2ProxyResult = _OAuth2ProxyResult; diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts b/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts deleted file mode 100644 index cbd02d18ea..0000000000 --- a/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - SignInResolver, - createProxyAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { AuthHandler } from '../types'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { - type OAuth2ProxyResult, - oauth2ProxyAuthenticator, -} from '@backstage/plugin-auth-backend-module-oauth2-proxy-provider'; - -/** - * Auth provider integration for oauth2-proxy auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const oauth2Proxy = createAuthProviderIntegration({ - create(options: { - /** - * Configure an auth handler to generate a profile for the user. - * - * The default implementation uses the value of the `X-Forwarded-Preferred-Username` - * header as the display name, falling back to `X-Forwarded-User`, and the value of - * the `X-Forwarded-Email` header as the email address. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return createProxyAuthProviderFactory({ - authenticator: oauth2ProxyAuthenticator, - profileTransform: options?.authHandler, - signInResolver: options?.signIn?.resolver, - }); - }, -}); diff --git a/plugins/auth-backend/src/providers/oauth2/index.ts b/plugins/auth-backend/src/providers/oauth2/index.ts deleted file mode 100644 index 14485e04ff..0000000000 --- a/plugins/auth-backend/src/providers/oauth2/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { oauth2 } from './provider'; diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts deleted file mode 100644 index b9ab928730..0000000000 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { OAuthResult } from '../../lib/oauth'; -import { AuthHandler } from '../types'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { - adaptLegacyOAuthHandler, - adaptLegacyOAuthSignInResolver, -} from '../../lib/legacy'; -import { - SignInResolver, - createOAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { oauth2Authenticator } from '@backstage/plugin-auth-backend-module-oauth2-provider'; - -/** - * Auth provider integration for generic OAuth2 auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const oauth2 = createAuthProviderIntegration({ - create(options?: { - authHandler?: AuthHandler; - - signIn?: { - resolver: SignInResolver; - }; - }) { - return createOAuthProviderFactory({ - authenticator: oauth2Authenticator, - profileTransform: adaptLegacyOAuthHandler(options?.authHandler), - signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), - }); - }, -}); diff --git a/plugins/auth-backend/src/providers/oidc/index.ts b/plugins/auth-backend/src/providers/oidc/index.ts deleted file mode 100644 index 501f223fb3..0000000000 --- a/plugins/auth-backend/src/providers/oidc/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { oidc } from './provider'; - -import { OidcAuthResult as OidcAuthResult_ } from '@backstage/plugin-auth-backend-module-oidc-provider'; - -/** - * @public - * @deprecated Use OidcAuthResult from `@backstage/plugin-auth-backend-module-oidc-provider` instead - */ -export type OidcAuthResult = OidcAuthResult_; diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts deleted file mode 100644 index 773c5c8bb9..0000000000 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - mockServices, - registerMswTestHooks, -} from '@backstage/backend-test-utils'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { Config, ConfigReader } from '@backstage/config'; -import { - AuthProviderConfig, - AuthResolverContext, - CookieConfigurer, -} from '@backstage/plugin-auth-node'; -import express from 'express'; -import { JWK, SignJWT, exportJWK, generateKeyPair } from 'jose'; -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; -import { oidc } from './provider'; - -describe('oidc.create', () => { - const userinfo = { - sub: 'test', - iss: 'https://oidc.test', - aud: 'clientId', - nonce: 'foo', - }; - const server = setupServer(); - registerMswTestHooks(server); - - let publicKey: JWK; - let tokenset: object; - let providerFactoryOptions: { - providerId: string; - globalConfig: AuthProviderConfig; - config: Config; - logger: LoggerService; - resolverContext: AuthResolverContext; - baseUrl: string; - appUrl: string; - isOriginAllowed: (origin: string) => boolean; - cookieConfigurer?: CookieConfigurer; - }; - - beforeAll(async () => { - const keyPair = await generateKeyPair('RS256'); - const privateKey = await exportJWK(keyPair.privateKey); - publicKey = await exportJWK(keyPair.publicKey); - publicKey.alg = privateKey.alg = 'RS256'; - - tokenset = { - id_token: await new SignJWT({ - iat: Date.now(), - exp: Date.now() + 10000, - ...userinfo, - }) - .setProtectedHeader({ alg: privateKey.alg, kid: privateKey.kid }) - .sign(keyPair.privateKey), - access_token: 'accessToken', - }; - }); - - beforeEach(() => { - server.use( - rest.get( - 'https://oidc.test/.well-known/openid-configuration', - (_req, res, ctx) => - res( - ctx.json({ - issuer: 'https://oidc.test', - token_endpoint: 'https://oidc.test/oauth2/token', - userinfo_endpoint: 'https://oidc.test/idp/userinfo.openid', - jwks_uri: 'https://oidc.test/jwks.json', - }), - ), - ), - rest.post('https://oidc.test/oauth2/token', (_req, res, ctx) => - res(ctx.json(tokenset)), - ), - rest.get('https://oidc.test/jwks.json', async (_req, res, ctx) => - res(ctx.json({ keys: [{ ...publicKey }] })), - ), - rest.get( - 'https://oidc.test/idp/userinfo.openid', - async (_req, res, ctx) => res(ctx.json(userinfo)), - ), - ); - providerFactoryOptions = { - providerId: 'myoidc', - baseUrl: 'http://backstage.test/api/auth', - appUrl: 'http://backstage.test', - isOriginAllowed: _ => true, - globalConfig: { - baseUrl: 'http://backstage.test/api/auth', - appUrl: 'http://backstage.test', - isOriginAllowed: _ => true, - }, - config: new ConfigReader({ - development: { - metadataUrl: 'https://oidc.test/.well-known/openid-configuration', - clientId: 'clientId', - clientSecret: 'clientSecret', - }, - }), - logger: mockServices.logger.mock(), - resolverContext: { - issueToken: jest.fn(), - findCatalogUser: jest.fn(), - signInWithCatalogUser: jest.fn(), - resolveOwnershipEntityRefs: jest.fn(), - }, - }; - }); - - it('invokes authHandler with tokenset and userinfo response', async () => { - const authHandler = jest.fn(); - const provider = oidc.create({ authHandler })(providerFactoryOptions); - const state = Buffer.from('nonce=foo&env=development').toString('hex'); - - await provider.frameHandler( - { - method: 'GET', - url: `http://backstage.test/api/auth/myoidc/handler/frame?code=blahblah&state=${state}`, - query: { state }, - cookies: { 'myoidc-nonce': 'foo' }, - session: { 'oidc:oidc.test': { state, nonce: 'foo' } }, - } as unknown as express.Request, - { setHeader: jest.fn(), end: jest.fn() } as unknown as express.Response, - ); - - expect(authHandler).toHaveBeenCalledWith( - { tokenset, userinfo }, - providerFactoryOptions.resolverContext, - ); - }); - - it('invokes sign-in resolver with tokenset and userinfo response', async () => { - const resolver = jest.fn(); - const provider = oidc.create({ signIn: { resolver } })( - providerFactoryOptions, - ); - const state = Buffer.from('nonce=foo&env=development').toString('hex'); - - await provider.frameHandler( - { - method: 'GET', - url: `http://backstage.test/api/auth/myoidc/handler/frame?code=blahblah&state=${state}`, - query: { state }, - cookies: { 'myoidc-nonce': 'foo' }, - session: { 'oidc:oidc.test': { state, nonce: 'foo' } }, - } as unknown as express.Request, - { setHeader: jest.fn(), end: jest.fn() } as unknown as express.Response, - ); - - expect(resolver).toHaveBeenCalledWith( - expect.objectContaining({ result: { tokenset, userinfo } }), - providerFactoryOptions.resolverContext, - ); - }); -}); diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts deleted file mode 100644 index 6caf97ef7c..0000000000 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AuthHandler } from '../types'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { - createOAuthProviderFactory, - AuthResolverContext, - BackstageSignInResult, - OAuthAuthenticatorResult, - SignInInfo, - SignInResolver, -} from '@backstage/plugin-auth-node'; -import { - oidcAuthenticator, - OidcAuthResult, -} from '@backstage/plugin-auth-backend-module-oidc-provider'; -import { - commonByEmailLocalPartResolver, - commonByEmailResolver, -} from '../resolvers'; - -/** - * Auth provider integration for generic OpenID Connect auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const oidc = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider; convert user profile respones into - * Backstage identities. - */ - signIn?: { - resolver: SignInResolver; - }; - }) { - const authHandler = options?.authHandler; - const signInResolver = options?.signIn?.resolver; - return createOAuthProviderFactory({ - authenticator: oidcAuthenticator, - profileTransform: - authHandler && - (( - result: OAuthAuthenticatorResult, - context: AuthResolverContext, - ) => authHandler(result.fullProfile, context)), - signInResolver: - signInResolver && - (( - info: SignInInfo>, - context: AuthResolverContext, - ): Promise => - signInResolver( - { - result: info.result.fullProfile, - profile: info.profile, - }, - context, - )), - }); - }, - resolvers: { - /** - * Looks up the user by matching their email local part to the entity name. - */ - emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver, - /** - * Looks up the user by matching their email to the entity email. - */ - emailMatchingUserEntityProfileEmail: () => commonByEmailResolver, - }, -}); diff --git a/plugins/auth-backend/src/providers/okta/index.ts b/plugins/auth-backend/src/providers/okta/index.ts deleted file mode 100644 index 3387fa3668..0000000000 --- a/plugins/auth-backend/src/providers/okta/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { okta } from './provider'; diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts deleted file mode 100644 index 5746e12710..0000000000 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AuthHandler } from '../types'; -import { OAuthResult } from '../../lib/oauth'; - -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { - SignInResolver, - createOAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { - adaptLegacyOAuthHandler, - adaptLegacyOAuthSignInResolver, -} from '../../lib/legacy'; -import { oktaAuthenticator } from '@backstage/plugin-auth-backend-module-okta-provider'; -import { - commonByEmailLocalPartResolver, - commonByEmailResolver, -} from '../resolvers'; - -/** - * Auth provider integration for Okta auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const okta = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - resolver: SignInResolver; - }; - }) { - return createOAuthProviderFactory({ - authenticator: oktaAuthenticator, - profileTransform: adaptLegacyOAuthHandler(options?.authHandler), - signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), - }); - }, - resolvers: { - /** - * Looks up the user by matching their email local part to the entity name. - */ - emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver, - /** - * Looks up the user by matching their email to the entity email. - */ - emailMatchingUserEntityProfileEmail: () => commonByEmailResolver, - /** - * Looks up the user by matching their email to the `okta.com/email` annotation. - */ - emailMatchingUserEntityAnnotation(): SignInResolver { - return async (info, ctx) => { - const { profile } = info; - - if (!profile.email) { - throw new Error('Okta profile contained no email'); - } - - return ctx.signInWithCatalogUser({ - annotations: { - 'okta.com/email': profile.email, - }, - }); - }; - }, - }, -}); diff --git a/plugins/auth-backend/src/providers/onelogin/index.ts b/plugins/auth-backend/src/providers/onelogin/index.ts deleted file mode 100644 index 3f356029fb..0000000000 --- a/plugins/auth-backend/src/providers/onelogin/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { onelogin } from './provider'; diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts deleted file mode 100644 index 808e6c15d3..0000000000 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { oneLoginAuthenticator } from '@backstage/plugin-auth-backend-module-onelogin-provider'; -import { - SignInResolver, - createOAuthProviderFactory, -} from '@backstage/plugin-auth-node'; -import { - adaptLegacyOAuthHandler, - adaptLegacyOAuthSignInResolver, -} from '../../lib/legacy'; -import { OAuthResult } from '../../lib/oauth'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { AuthHandler } from '../types'; - -/** - * Auth provider integration for OneLogin auth - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const onelogin = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return createOAuthProviderFactory({ - authenticator: oneLoginAuthenticator, - profileTransform: adaptLegacyOAuthHandler(options?.authHandler), - signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), - }); - }, -}); diff --git a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts deleted file mode 100644 index 1fa8f4a2fa..0000000000 --- a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { prepareBackstageIdentityResponse as _prepareBackstageIdentityResponse } from '@backstage/plugin-auth-node'; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export const prepareBackstageIdentityResponse = - _prepareBackstageIdentityResponse; diff --git a/plugins/auth-backend/src/providers/providers.ts b/plugins/auth-backend/src/providers/providers.ts deleted file mode 100644 index ce513e6ac7..0000000000 --- a/plugins/auth-backend/src/providers/providers.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { atlassian } from './atlassian'; -import { auth0 } from './auth0'; -import { awsAlb } from './aws-alb'; -import { bitbucket } from './bitbucket'; -import { cfAccess } from './cloudflare-access'; -import { gcpIap } from './gcp-iap'; -import { github } from './github'; -import { gitlab } from './gitlab'; -import { google } from './google'; -import { microsoft } from './microsoft'; -import { oauth2 } from './oauth2'; -import { oauth2Proxy } from './oauth2-proxy'; -import { oidc } from './oidc'; -import { okta } from './okta'; -import { onelogin } from './onelogin'; -import { saml } from './saml'; -import { bitbucketServer } from './bitbucketServer'; -import { easyAuth } from './azure-easyauth'; -import { AuthProviderFactory } from '@backstage/plugin-auth-node'; - -/** - * All built-in auth provider integrations. - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const providers = Object.freeze({ - atlassian, - auth0, - awsAlb, - bitbucket, - bitbucketServer, - cfAccess, - gcpIap, - github, - gitlab, - google, - microsoft, - oauth2, - oauth2Proxy, - oidc, - okta, - onelogin, - saml, - easyAuth, -}); - -/** - * All auth provider factories that are installed by default. - * - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export const defaultAuthProviderFactories: { - [providerId: string]: AuthProviderFactory; -} = { - google: google.create(), - github: github.create(), - gitlab: gitlab.create(), - saml: saml.create(), - okta: okta.create(), - auth0: auth0.create(), - microsoft: microsoft.create(), - easyAuth: easyAuth.create(), - oauth2: oauth2.create(), - oidc: oidc.create(), - onelogin: onelogin.create(), - awsalb: awsAlb.create(), - bitbucket: bitbucket.create(), - bitbucketServer: bitbucketServer.create(), - atlassian: atlassian.create(), -}; diff --git a/plugins/auth-backend/src/providers/resolvers.ts b/plugins/auth-backend/src/providers/resolvers.ts deleted file mode 100644 index 54c78ff182..0000000000 --- a/plugins/auth-backend/src/providers/resolvers.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { SignInResolver } from '@backstage/plugin-auth-node'; - -/** - * A common sign-in resolver that looks up the user using the local part of - * their email address as the entity name. - */ -export const commonByEmailLocalPartResolver: SignInResolver = async ( - info, - ctx, -) => { - const { profile } = info; - - if (!profile.email) { - throw new Error('Login failed, user profile does not contain an email'); - } - const [localPart] = profile.email.split('@'); - - return ctx.signInWithCatalogUser({ - entityRef: { name: localPart }, - }); -}; - -/** - * A common sign-in resolver that looks up the user using their email address - * as email of the entity. - */ -export const commonByEmailResolver: SignInResolver = async ( - info, - ctx, -) => { - const { profile } = info; - - if (!profile.email) { - throw new Error('Login failed, user profile does not contain an email'); - } - - return ctx.signInWithCatalogUser({ - filter: { - 'spec.profile.email': profile.email, - }, - }); -}; diff --git a/plugins/auth-backend/src/providers/router.ts b/plugins/auth-backend/src/providers/router.ts index ceab20553d..d04347e7b3 100644 --- a/plugins/auth-backend/src/providers/router.ts +++ b/plugins/auth-backend/src/providers/router.ts @@ -14,30 +14,20 @@ * limitations under the License. */ -import { TokenManager } from '@backstage/backend-common'; -import { - AuthService, - DiscoveryService, - HttpAuthService, - LoggerService, -} from '@backstage/backend-plugin-api'; -import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; +import { AuthService, LoggerService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { assertError, NotFoundError } from '@backstage/errors'; import { AuthOwnershipResolver, AuthProviderFactory, } from '@backstage/plugin-auth-node'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import express from 'express'; import Router from 'express-promise-router'; import { Minimatch } from 'minimatch'; import { CatalogAuthResolverContext } from '../lib/resolvers/CatalogAuthResolverContext'; import { TokenIssuer } from '../identity/types'; -/** - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ export type ProviderFactories = { [s: string]: AuthProviderFactory }; export function bindProviderRouters( @@ -48,13 +38,10 @@ export function bindProviderRouters( baseUrl: string; config: Config; logger: LoggerService; - discovery: DiscoveryService; auth: AuthService; - httpAuth: HttpAuthService; - tokenManager?: TokenManager; tokenIssuer: TokenIssuer; ownershipResolver?: AuthOwnershipResolver; - catalogApi?: CatalogApi; + catalog: CatalogService; }, ) { const { @@ -63,12 +50,9 @@ export function bindProviderRouters( baseUrl, config, logger, - discovery, auth, - httpAuth, - tokenManager, tokenIssuer, - catalogApi, + catalog, ownershipResolver, } = options; @@ -94,13 +78,9 @@ export function bindProviderRouters( logger, resolverContext: CatalogAuthResolverContext.create({ logger, - catalogApi: - catalogApi ?? new CatalogClient({ discoveryApi: discovery }), + catalog, tokenIssuer, - tokenManager, - discovery, auth, - httpAuth, ownershipResolver, }), }); @@ -148,10 +128,6 @@ export function bindProviderRouters( } } -/** - * @public - * @deprecated this export will be removed - */ export function createOriginFilter( config: Config, ): (origin: string) => boolean { diff --git a/plugins/auth-backend/src/providers/saml/index.ts b/plugins/auth-backend/src/providers/saml/index.ts deleted file mode 100644 index d59f7bf0a7..0000000000 --- a/plugins/auth-backend/src/providers/saml/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { saml } from './provider'; -export type { SamlAuthResult } from './provider'; diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts deleted file mode 100644 index 7797de897b..0000000000 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import { SamlConfig, VerifiedCallback } from '@node-saml/passport-saml'; -import { - Strategy as SamlStrategy, - Profile as SamlProfile, - VerifyWithoutRequest, -} from '@node-saml/passport-saml'; -import { - executeFrameHandlerStrategy, - executeRedirectStrategy, -} from '../../lib/passport'; -import { AuthHandler } from '../types'; -import { postMessageResponse } from '../../lib/flow'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { AuthenticationError, isError } from '@backstage/errors'; -import { prepareBackstageIdentityResponse } from '../prepareBackstageIdentityResponse'; -import { - AuthProviderRouteHandlers, - AuthResolverContext, - ClientAuthResponse, - SignInResolver, -} from '@backstage/plugin-auth-node'; - -/** - * @public - * @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin - */ -export type SamlAuthResult = { - fullProfile: any; -}; - -type Options = SamlConfig & { - signInResolver?: SignInResolver; - authHandler: AuthHandler; - resolverContext: AuthResolverContext; - appUrl: string; -}; - -export class SamlAuthProvider implements AuthProviderRouteHandlers { - private readonly strategy: SamlStrategy; - private readonly signInResolver?: SignInResolver; - private readonly authHandler: AuthHandler; - private readonly resolverContext: AuthResolverContext; - private readonly appUrl: string; - - constructor(options: Options) { - this.appUrl = options.appUrl; - this.signInResolver = options.signInResolver; - this.authHandler = options.authHandler; - this.resolverContext = options.resolverContext; - - const verifier: VerifyWithoutRequest = ( - profile: SamlProfile | null, - done: VerifiedCallback, - ) => { - // TODO: There's plenty more validation and profile handling to do here, - // this provider is currently only intended to validate the provider pattern - // for non-oauth auth flows. - // TODO: This flow doesn't issue an identity token that can be used to validate - // the identity of the user in other backends, which we need in some form. - done(null, { fullProfile: profile }); - }; - this.strategy = new SamlStrategy(options, verifier, verifier); - } - - async start(req: express.Request, res: express.Response): Promise { - const { url } = await executeRedirectStrategy(req, this.strategy, {}); - res.redirect(url); - } - - async frameHandler( - req: express.Request, - res: express.Response, - ): Promise { - try { - const { result } = await executeFrameHandlerStrategy( - req, - this.strategy, - ); - - const { profile } = await this.authHandler(result, this.resolverContext); - - const response: ClientAuthResponse<{}> = { - profile, - providerInfo: {}, - }; - - if (this.signInResolver) { - const signInResponse = await this.signInResolver( - { - result, - profile, - }, - this.resolverContext, - ); - - response.backstageIdentity = - prepareBackstageIdentityResponse(signInResponse); - } - - return postMessageResponse(res, this.appUrl, { - type: 'authorization_response', - response, - }); - } catch (error) { - const { name, message } = isError(error) - ? error - : new Error('Encountered invalid error'); // Being a bit safe and not forwarding the bad value - return postMessageResponse(res, this.appUrl, { - type: 'authorization_response', - error: { name, message }, - }); - } - } - - async logout(_req: express.Request, res: express.Response): Promise { - res.end(); - } -} - -type SignatureAlgorithm = 'sha1' | 'sha256' | 'sha512'; - -/** - * Auth provider integration for SAML auth - * - * @public - */ -export const saml = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return ({ providerId, globalConfig, config, resolverContext }) => { - const authHandler: AuthHandler = options?.authHandler - ? options.authHandler - : async ({ fullProfile }) => ({ - profile: { - email: fullProfile.email, - displayName: fullProfile.displayName, - }, - }); - - return new SamlAuthProvider({ - callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`, - entryPoint: config.getString('entryPoint'), - logoutUrl: config.getOptionalString('logoutUrl'), - audience: config.getString('audience'), - issuer: config.getString('issuer'), - idpCert: config.getString('cert'), - privateKey: config.getOptionalString('privateKey'), - authnContext: config.getOptionalStringArray('authnContext'), - identifierFormat: config.getOptionalString('identifierFormat'), - decryptionPvk: config.getOptionalString('decryptionPvk'), - signatureAlgorithm: config.getOptionalString('signatureAlgorithm') as - | SignatureAlgorithm - | undefined, - digestAlgorithm: config.getOptionalString('digestAlgorithm'), - acceptedClockSkewMs: config.getOptionalNumber('acceptedClockSkewMs'), - wantAuthnResponseSigned: config.getOptionalBoolean( - 'wantAuthnResponseSigned', - ), - wantAssertionsSigned: config.getOptionalBoolean('wantAssertionsSigned'), - appUrl: globalConfig.appUrl, - authHandler, - signInResolver: options?.signIn?.resolver, - resolverContext, - }); - }; - }, - resolvers: { - /** - * Looks up the user by matching their nameID to the entity name. - */ - nameIdMatchingUserEntityName(): SignInResolver { - return async (info, ctx) => { - const id = info.result.fullProfile.nameID; - - if (!id) { - throw new AuthenticationError('No nameID found in SAML response'); - } - - return ctx.signInWithCatalogUser({ - entityRef: { name: id }, - }); - }; - }, - }, -}); diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts deleted file mode 100644 index 40c693506e..0000000000 --- a/plugins/auth-backend/src/providers/types.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AuthProviderConfig as _AuthProviderConfig, - AuthProviderRouteHandlers as _AuthProviderRouteHandlers, - AuthProviderFactory as _AuthProviderFactory, - AuthResolverCatalogUserQuery as _AuthResolverCatalogUserQuery, - AuthResolverContext as _AuthResolverContext, - ClientAuthResponse as _ClientAuthResponse, - CookieConfigurer as _CookieConfigurer, - ProfileInfo as _ProfileInfo, - SignInInfo as _SignInInfo, - SignInResolver as _SignInResolver, -} from '@backstage/plugin-auth-node'; -import { OAuthStartRequest } from '../lib/oauth/types'; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type AuthResolverCatalogUserQuery = _AuthResolverCatalogUserQuery; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type AuthResolverContext = _AuthResolverContext; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type CookieConfigurer = _CookieConfigurer; - -/** - * @public - * @deprecated Use `createOAuthAuthenticator` from `@backstage/plugin-auth-node` instead - */ -export type OAuthStartResponse = { - /** - * URL to redirect to - */ - url: string; - /** - * Status code to use for the redirect - */ - status?: number; -}; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type AuthProviderConfig = _AuthProviderConfig; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type AuthProviderRouteHandlers = _AuthProviderRouteHandlers; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type AuthProviderFactory = _AuthProviderFactory; - -/** - * @public - * @deprecated import `ClientAuthResponse` from `@backstage/plugin-auth-node` instead - */ -export type AuthResponse = _ClientAuthResponse; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type ProfileInfo = _ProfileInfo; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type SignInInfo = _SignInInfo; - -/** - * @public - * @deprecated import from `@backstage/plugin-auth-node` instead - */ -export type SignInResolver = _SignInResolver; - -/** - * The return type of an authentication handler. Must contain valid profile - * information. - * - * @public - * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead - */ -export type AuthHandlerResult = { profile: _ProfileInfo }; - -/** - * The AuthHandler function is called every time the user authenticates using - * the provider. - * - * The handler should return a profile that represents the session for the user - * in the frontend. - * - * Throwing an error in the function will cause the authentication to fail, - * making it possible to use this function as a way to limit access to a certain - * group of users. - * - * @public - * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead - */ -export type AuthHandler = ( - input: TAuthResult, - context: _AuthResolverContext, -) => Promise; - -/** - * @public - * @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead - */ -export type StateEncoder = ( - req: OAuthStartRequest, -) => Promise<{ encodedState: string }>; diff --git a/plugins/auth-backend/src/service/index.ts b/plugins/auth-backend/src/service/index.ts deleted file mode 100644 index d26055aa59..0000000000 --- a/plugins/auth-backend/src/service/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { createRouter, type RouterOptions } from './router'; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index af34ffc310..1952e9d648 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -21,24 +21,16 @@ import { AuthService, DatabaseService, DiscoveryService, - HttpAuthService, LoggerService, RootConfigService, } from '@backstage/backend-plugin-api'; -import { defaultAuthProviderFactories } from '../providers'; import { AuthOwnershipResolver } from '@backstage/plugin-auth-node'; -import { - TokenManager, - createLegacyAuthAdapters, -} from '@backstage/backend-common'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { NotFoundError } from '@backstage/errors'; -import { CatalogApi } from '@backstage/catalog-client'; -import { - bindOidcRouter, - KeyStores, - TokenFactory, - UserInfoDatabaseHandler, -} from '../identity'; +import { bindOidcRouter } from '../identity/router'; +import { KeyStores } from '../identity/KeyStores'; +import { TokenFactory } from '../identity/TokenFactory'; +import { UserInfoDatabaseHandler } from '../identity/UserInfoDatabaseHandler'; import session from 'express-session'; import connectSessionKnex from 'connect-session-knex'; import passport from 'passport'; @@ -49,29 +41,18 @@ import { StaticTokenIssuer } from '../identity/StaticTokenIssuer'; import { StaticKeyStore } from '../identity/StaticKeyStore'; import { bindProviderRouters, ProviderFactories } from '../providers/router'; -/** - * @public - * @deprecated Please migrate to the new backend system as this will be removed in the future. - */ -export interface RouterOptions { +interface RouterOptions { logger: LoggerService; database: DatabaseService; config: RootConfigService; discovery: DiscoveryService; - tokenManager?: TokenManager; - auth?: AuthService; - httpAuth?: HttpAuthService; + auth: AuthService; tokenFactoryAlgorithm?: string; providerFactories?: ProviderFactories; - disableDefaultProviderFactories?: boolean; - catalogApi?: CatalogApi; + catalog: CatalogService; ownershipResolver?: AuthOwnershipResolver; } -/** - * @public - * @deprecated Please migrate to the new backend system as this will be removed in the future. - */ export async function createRouter( options: RouterOptions, ): Promise { @@ -84,8 +65,6 @@ export async function createRouter( providerFactories = {}, } = options; - const { auth, httpAuth } = createLegacyAuthAdapters(options); - const router = Router(); const appUrl = config.getString('app.baseUrl'); @@ -151,25 +130,17 @@ export async function createRouter( router.use(express.urlencoded({ extended: false })); router.use(express.json()); - const providers = options.disableDefaultProviderFactories - ? providerFactories - : { - ...defaultAuthProviderFactories, - ...providerFactories, - }; - bindProviderRouters(router, { - providers, + providers: providerFactories, appUrl, baseUrl: authUrl, tokenIssuer, ...options, - auth, - httpAuth, + auth: options.auth, }); bindOidcRouter(router, { - auth, + auth: options.auth, tokenIssuer, baseUrl: authUrl, userInfoDatabaseHandler, diff --git a/yarn.lock b/yarn.lock index d7a27b9f85..9e7f324dc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4902,7 +4902,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-atlassian-provider@workspace:^, @backstage/plugin-auth-backend-module-atlassian-provider@workspace:plugins/auth-backend-module-atlassian-provider": +"@backstage/plugin-auth-backend-module-atlassian-provider@workspace:plugins/auth-backend-module-atlassian-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-atlassian-provider@workspace:plugins/auth-backend-module-atlassian-provider" dependencies: @@ -4920,7 +4920,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-auth0-provider@workspace:^, @backstage/plugin-auth-backend-module-auth0-provider@workspace:plugins/auth-backend-module-auth0-provider": +"@backstage/plugin-auth-backend-module-auth0-provider@workspace:plugins/auth-backend-module-auth0-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-auth0-provider@workspace:plugins/auth-backend-module-auth0-provider" dependencies: @@ -4940,7 +4940,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-aws-alb-provider@workspace:^, @backstage/plugin-auth-backend-module-aws-alb-provider@workspace:plugins/auth-backend-module-aws-alb-provider": +"@backstage/plugin-auth-backend-module-aws-alb-provider@workspace:plugins/auth-backend-module-aws-alb-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-aws-alb-provider@workspace:plugins/auth-backend-module-aws-alb-provider" dependencies: @@ -4959,7 +4959,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-azure-easyauth-provider@workspace:^, @backstage/plugin-auth-backend-module-azure-easyauth-provider@workspace:plugins/auth-backend-module-azure-easyauth-provider": +"@backstage/plugin-auth-backend-module-azure-easyauth-provider@workspace:plugins/auth-backend-module-azure-easyauth-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-azure-easyauth-provider@workspace:plugins/auth-backend-module-azure-easyauth-provider" dependencies: @@ -4977,7 +4977,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-bitbucket-provider@workspace:^, @backstage/plugin-auth-backend-module-bitbucket-provider@workspace:plugins/auth-backend-module-bitbucket-provider": +"@backstage/plugin-auth-backend-module-bitbucket-provider@workspace:plugins/auth-backend-module-bitbucket-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-bitbucket-provider@workspace:plugins/auth-backend-module-bitbucket-provider" dependencies: @@ -4995,7 +4995,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-bitbucket-server-provider@workspace:^, @backstage/plugin-auth-backend-module-bitbucket-server-provider@workspace:plugins/auth-backend-module-bitbucket-server-provider": +"@backstage/plugin-auth-backend-module-bitbucket-server-provider@workspace:plugins/auth-backend-module-bitbucket-server-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-bitbucket-server-provider@workspace:plugins/auth-backend-module-bitbucket-server-provider" dependencies: @@ -5013,7 +5013,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:^, @backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:plugins/auth-backend-module-cloudflare-access-provider": +"@backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:plugins/auth-backend-module-cloudflare-access-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:plugins/auth-backend-module-cloudflare-access-provider" dependencies: @@ -5034,7 +5034,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-gcp-iap-provider@workspace:^, @backstage/plugin-auth-backend-module-gcp-iap-provider@workspace:plugins/auth-backend-module-gcp-iap-provider": +"@backstage/plugin-auth-backend-module-gcp-iap-provider@workspace:plugins/auth-backend-module-gcp-iap-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-gcp-iap-provider@workspace:plugins/auth-backend-module-gcp-iap-provider" dependencies: @@ -5060,12 +5060,13 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" + "@types/passport-github2": "npm:^1.2.4" passport-github2: "npm:^0.1.12" supertest: "npm:^7.0.0" languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-gitlab-provider@workspace:^, @backstage/plugin-auth-backend-module-gitlab-provider@workspace:plugins/auth-backend-module-gitlab-provider": +"@backstage/plugin-auth-backend-module-gitlab-provider@workspace:plugins/auth-backend-module-gitlab-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-gitlab-provider@workspace:plugins/auth-backend-module-gitlab-provider" dependencies: @@ -5116,7 +5117,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-microsoft-provider@workspace:^, @backstage/plugin-auth-backend-module-microsoft-provider@workspace:plugins/auth-backend-module-microsoft-provider": +"@backstage/plugin-auth-backend-module-microsoft-provider@workspace:plugins/auth-backend-module-microsoft-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-microsoft-provider@workspace:plugins/auth-backend-module-microsoft-provider" dependencies: @@ -5137,7 +5138,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-oauth2-provider@workspace:^, @backstage/plugin-auth-backend-module-oauth2-provider@workspace:plugins/auth-backend-module-oauth2-provider": +"@backstage/plugin-auth-backend-module-oauth2-provider@workspace:plugins/auth-backend-module-oauth2-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-oauth2-provider@workspace:plugins/auth-backend-module-oauth2-provider" dependencies: @@ -5154,7 +5155,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-oauth2-proxy-provider@workspace:^, @backstage/plugin-auth-backend-module-oauth2-proxy-provider@workspace:plugins/auth-backend-module-oauth2-proxy-provider": +"@backstage/plugin-auth-backend-module-oauth2-proxy-provider@workspace:plugins/auth-backend-module-oauth2-proxy-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-oauth2-proxy-provider@workspace:plugins/auth-backend-module-oauth2-proxy-provider" dependencies: @@ -5167,7 +5168,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-oidc-provider@workspace:^, @backstage/plugin-auth-backend-module-oidc-provider@workspace:plugins/auth-backend-module-oidc-provider": +"@backstage/plugin-auth-backend-module-oidc-provider@workspace:plugins/auth-backend-module-oidc-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-oidc-provider@workspace:plugins/auth-backend-module-oidc-provider" dependencies: @@ -5191,7 +5192,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-okta-provider@workspace:^, @backstage/plugin-auth-backend-module-okta-provider@workspace:plugins/auth-backend-module-okta-provider": +"@backstage/plugin-auth-backend-module-okta-provider@workspace:plugins/auth-backend-module-okta-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-okta-provider@workspace:plugins/auth-backend-module-okta-provider" dependencies: @@ -5209,7 +5210,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-onelogin-provider@workspace:^, @backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider": +"@backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider" dependencies: @@ -5277,78 +5278,35 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend@workspace:plugins/auth-backend" dependencies: - "@backstage/backend-common": "npm:^0.25.0" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" - "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" - "@backstage/plugin-auth-backend-module-atlassian-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-auth0-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-aws-alb-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-bitbucket-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-gcp-iap-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-github-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-gitlab-provider": "workspace:^" "@backstage/plugin-auth-backend-module-google-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-microsoft-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-oauth2-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-oidc-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-okta-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-onelogin-provider": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@google-cloud/firestore": "npm:^7.0.0" - "@node-saml/passport-saml": "npm:^5.0.0" - "@types/body-parser": "npm:^1.19.0" "@types/cookie-parser": "npm:^1.4.2" "@types/express": "npm:^4.17.6" "@types/express-session": "npm:^1.17.2" "@types/passport": "npm:^1.0.3" - "@types/passport-auth0": "npm:^1.0.5" - "@types/passport-github2": "npm:^1.2.4" - "@types/passport-google-oauth20": "npm:^2.0.3" - "@types/passport-microsoft": "npm:^1.0.0" - "@types/passport-saml": "npm:^1.1.3" - "@types/passport-strategy": "npm:^0.2.35" - "@types/xml2js": "npm:^0.4.7" - compression: "npm:^1.7.4" connect-session-knex: "npm:^4.0.0" cookie-parser: "npm:^1.4.5" - cors: "npm:^2.8.5" express: "npm:^4.17.1" express-promise-router: "npm:^4.1.0" express-session: "npm:^1.17.1" - fs-extra: "npm:^11.2.0" - google-auth-library: "npm:^9.0.0" jose: "npm:^5.0.0" knex: "npm:^3.0.0" lodash: "npm:^4.17.21" luxon: "npm:^3.0.0" minimatch: "npm:^9.0.0" - morgan: "npm:^1.10.0" - msw: "npm:^1.0.0" - node-cache: "npm:^5.1.2" - openid-client: "npm:^5.2.1" passport: "npm:^0.7.0" - passport-auth0: "npm:^1.4.3" - passport-github2: "npm:^0.1.12" - passport-google-oauth20: "npm:^2.0.0" - passport-microsoft: "npm:^1.0.0" - passport-oauth2: "npm:^1.6.1" - passport-onelogin-oauth: "npm:^0.0.1" supertest: "npm:^7.0.0" uuid: "npm:^11.0.0" - winston: "npm:^3.2.1" - yn: "npm:^4.0.0" languageName: unknown linkType: soft @@ -12273,40 +12231,6 @@ __metadata: languageName: node linkType: hard -"@node-saml/node-saml@npm:^5.0.1": - version: 5.0.1 - resolution: "@node-saml/node-saml@npm:5.0.1" - dependencies: - "@types/debug": "npm:^4.1.12" - "@types/qs": "npm:^6.9.11" - "@types/xml-encryption": "npm:^1.2.4" - "@types/xml2js": "npm:^0.4.14" - "@xmldom/is-dom-node": "npm:^1.0.1" - "@xmldom/xmldom": "npm:^0.8.10" - debug: "npm:^4.3.4" - xml-crypto: "npm:^6.0.1" - xml-encryption: "npm:^3.0.2" - xml2js: "npm:^0.6.2" - xmlbuilder: "npm:^15.1.1" - xpath: "npm:^0.0.34" - checksum: 10/65b31123733582ddb159fe2139349c766a5327fe12a141fac637ec8b5636be061c644e53486f17d2ffd67ef62d5497c90f76b5a6692a30a94d4114eae6124165 - languageName: node - linkType: hard - -"@node-saml/passport-saml@npm:^5.0.0": - version: 5.0.1 - resolution: "@node-saml/passport-saml@npm:5.0.1" - dependencies: - "@node-saml/node-saml": "npm:^5.0.1" - "@types/express": "npm:^4.17.21" - "@types/passport": "npm:^1.0.16" - "@types/passport-strategy": "npm:^0.2.38" - passport: "npm:^0.7.0" - passport-strategy: "npm:^1.0.0" - checksum: 10/b921ceeff68326539591ec6ef805535e907b3a63182c8df3418d8b449b88b82fb0137117f087268a3ebc76ef294a37b3e1b4df675f1f1a57b95455b87366db8d - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -19277,7 +19201,7 @@ __metadata: languageName: node linkType: hard -"@types/body-parser@npm:*, @types/body-parser@npm:^1.19.0": +"@types/body-parser@npm:*": version: 1.19.5 resolution: "@types/body-parser@npm:1.19.5" dependencies: @@ -19531,7 +19455,7 @@ __metadata: languageName: node linkType: hard -"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.12, @types/debug@npm:^4.1.7": +"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.7": version: 4.1.12 resolution: "@types/debug@npm:4.1.12" dependencies: @@ -20326,26 +20250,6 @@ __metadata: languageName: node linkType: hard -"@types/passport-saml@npm:^1.1.3": - version: 1.1.7 - resolution: "@types/passport-saml@npm:1.1.7" - dependencies: - "@types/express": "npm:*" - "@types/passport": "npm:*" - checksum: 10/cc112ac43d8a9ed586cfa04342afefef45ec96b7125a554534fb763986e3426b0316ee3d9e4705240c9c3df3b2b19914dba48ef6abccbd6946e9ae634f790332 - languageName: node - linkType: hard - -"@types/passport-strategy@npm:^0.2.35, @types/passport-strategy@npm:^0.2.38": - version: 0.2.38 - resolution: "@types/passport-strategy@npm:0.2.38" - dependencies: - "@types/express": "npm:*" - "@types/passport": "npm:*" - checksum: 10/b580e165182b137a6e57b6b7511904e6c875a5e372f08679ec54f456dc5c2a72d86f23d9373a52d8286b207fe8240946686f9e3d50b0bc1b4f7316f336a06fa2 - languageName: node - linkType: hard - "@types/passport@npm:*, @types/passport@npm:^1.0.16, @types/passport@npm:^1.0.3": version: 1.0.17 resolution: "@types/passport@npm:1.0.17" @@ -20437,7 +20341,7 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:*, @types/qs@npm:^6.9.11, @types/qs@npm:^6.9.6": +"@types/qs@npm:*, @types/qs@npm:^6.9.6": version: 6.9.18 resolution: "@types/qs@npm:6.9.18" checksum: 10/152fab96efd819cc82ae67c39f089df415da6deddb48f1680edaaaa4e86a2a597de7b2ff0ad391df66d11a07006a08d52c9405e86b8cb8f3d5ba15881fe56cc7 @@ -21019,24 +20923,6 @@ __metadata: languageName: node linkType: hard -"@types/xml-encryption@npm:^1.2.4": - version: 1.2.4 - resolution: "@types/xml-encryption@npm:1.2.4" - dependencies: - "@types/node": "npm:*" - checksum: 10/1ef957dfb47cf55b12e114755e271a2343f73eb4c59ab6c68b0b7d1b8111d7e1bd8d2bfe0601d2aea09be83c66355bc77fc59f9b71aeff9bb9e15371bcfef5d3 - languageName: node - linkType: hard - -"@types/xml2js@npm:^0.4.14, @types/xml2js@npm:^0.4.7": - version: 0.4.14 - resolution: "@types/xml2js@npm:0.4.14" - dependencies: - "@types/node": "npm:*" - checksum: 10/d76338b8d6ce8540c7af6a32aacf96c38f6de48254568f58f6e5ac2af3f88e6bd1490e5346d3bb336990f91267d23c5cc09e8bf7e80840a63c7855dbf174ecbb - languageName: node - linkType: hard - "@types/yargs-parser@npm:*": version: 15.0.0 resolution: "@types/yargs-parser@npm:15.0.0" @@ -21883,14 +21769,7 @@ __metadata: languageName: node linkType: hard -"@xmldom/is-dom-node@npm:^1.0.1": - version: 1.0.1 - resolution: "@xmldom/is-dom-node@npm:1.0.1" - checksum: 10/45683a6a192e4eff0f5189d4e3ef5272fcf8e3458f598f99614810490a8163c9a7ebe4ecaf241286fb74fcd762610b46c062ad3c7fddaa6eafa9a9f1537e338a - languageName: node - linkType: hard - -"@xmldom/xmldom@npm:^0.8.10, @xmldom/xmldom@npm:^0.8.3, @xmldom/xmldom@npm:^0.8.5": +"@xmldom/xmldom@npm:^0.8.3": version: 0.8.10 resolution: "@xmldom/xmldom@npm:0.8.10" checksum: 10/62400bc5e0e75b90650e33a5ceeb8d94829dd11f9b260962b71a784cd014ddccec3e603fe788af9c1e839fa4648d8c521ebd80d8b752878d3a40edabc9ce7ccf @@ -29083,7 +28962,6 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/integration": "workspace:^" - "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^" @@ -38659,7 +38537,7 @@ __metadata: languageName: node linkType: hard -"openid-client@npm:^5.2.1, openid-client@npm:^5.3.0, openid-client@npm:^5.4.3, openid-client@npm:^5.5.0, openid-client@npm:^5.6.5": +"openid-client@npm:^5.3.0, openid-client@npm:^5.4.3, openid-client@npm:^5.5.0, openid-client@npm:^5.6.5": version: 5.7.1 resolution: "openid-client@npm:5.7.1" dependencies: @@ -39277,7 +39155,7 @@ __metadata: languageName: node linkType: hard -"passport-strategy@npm:1.x.x, passport-strategy@npm:^1.0.0": +"passport-strategy@npm:1.x.x": version: 1.0.0 resolution: "passport-strategy@npm:1.0.0" checksum: 10/5086693f2508e538dffa55a338c89fe8192fb5f4478c71f80cd5890b8573419a098f4fec88b505374f60bbe9049f6f24b9f3992678612528a3370b4dc73354a2 @@ -43126,13 +43004,6 @@ __metadata: languageName: node linkType: hard -"sax@npm:>=0.6.0": - version: 1.2.4 - resolution: "sax@npm:1.2.4" - checksum: 10/09b79ff6dc09689a24323352117c94593c69db348997b2af0edbd82fa08aba47d778055bf9616b57285bb73d25d790900c044bf631a8f10c8252412e3f3fe5dd - languageName: node - linkType: hard - "saxes@npm:^6.0.0": version: 6.0.0 resolution: "saxes@npm:6.0.0" @@ -47931,28 +47802,6 @@ __metadata: languageName: node linkType: hard -"xml-crypto@npm:^6.0.1": - version: 6.0.1 - resolution: "xml-crypto@npm:6.0.1" - dependencies: - "@xmldom/is-dom-node": "npm:^1.0.1" - "@xmldom/xmldom": "npm:^0.8.10" - xpath: "npm:^0.0.33" - checksum: 10/703d40b54333a50f74f2d4f3e37a7b9de7aa2dfde48b0418205261d2f66c9e3869babd96d7be18ec1661aaa3aea03a018279846f844cea607a57183a21a4748a - languageName: node - linkType: hard - -"xml-encryption@npm:^3.0.2": - version: 3.0.2 - resolution: "xml-encryption@npm:3.0.2" - dependencies: - "@xmldom/xmldom": "npm:^0.8.5" - escape-html: "npm:^1.0.3" - xpath: "npm:0.0.32" - checksum: 10/081a42ca7d7e81d23229f2a1149313e934d872c33da57eda25113a613f3940ff66f73e4e2f62d37a3a38c3c7d291784047b5b729988f346fef96c7124f6dbe83 - languageName: node - linkType: hard - "xml-name-validator@npm:^4.0.0": version: 4.0.0 resolution: "xml-name-validator@npm:4.0.0" @@ -47967,16 +47816,6 @@ __metadata: languageName: node linkType: hard -"xml2js@npm:^0.6.2": - version: 0.6.2 - resolution: "xml2js@npm:0.6.2" - dependencies: - sax: "npm:>=0.6.0" - xmlbuilder: "npm:~11.0.0" - checksum: 10/df29de8eeedb762c367d87945c39bcf54db19a2c522607491c266ed6184b5a749e37ff29cfaed0ac149da9ba332ac3dcf8e5ff2bd0a206be3343eca95faa941d - languageName: node - linkType: hard - "xml@npm:=1.0.1": version: 1.0.1 resolution: "xml@npm:1.0.1" @@ -47984,20 +47823,6 @@ __metadata: languageName: node linkType: hard -"xmlbuilder@npm:^15.1.1": - version: 15.1.1 - resolution: "xmlbuilder@npm:15.1.1" - checksum: 10/e6f4bab2504afdd5f80491bda948894d2146756532521dbe7db33ae0931cd3000e3b4da19b3f5b3f51bedbd9ee06582144d28136d68bd1df96579ecf4d4404a2 - languageName: node - linkType: hard - -"xmlbuilder@npm:~11.0.0": - version: 11.0.1 - resolution: "xmlbuilder@npm:11.0.1" - checksum: 10/c8c3d208783718db5b285101a736cd8e6b69a5c265199a0739abaa93d1a1b7de5489fd16df4e776e18b2c98cb91f421a7349e99fd8c1ebeb44ecfed72a25091a - languageName: node - linkType: hard - "xmlchars@npm:^2.2.0": version: 2.2.0 resolution: "xmlchars@npm:2.2.0" @@ -48005,27 +47830,6 @@ __metadata: languageName: node linkType: hard -"xpath@npm:0.0.32": - version: 0.0.32 - resolution: "xpath@npm:0.0.32" - checksum: 10/9d8be7adde4500e9ee96db963838269021f89ef1ad222fdfd41b7266336e851a38416b4a710c194dcf9eb35cf58ad11e023e5951e919151b76ffcd6eb3b2cbf4 - languageName: node - linkType: hard - -"xpath@npm:^0.0.33": - version: 0.0.33 - resolution: "xpath@npm:0.0.33" - checksum: 10/09c539661cafc0d75bb48d13fee7ce6e7593d88f4387c401a3b15d46d543e81f46680be5c6ecf868c11f6090ee67ea78e0c327c4e0ffceb2969308a2d1e238bb - languageName: node - linkType: hard - -"xpath@npm:^0.0.34": - version: 0.0.34 - resolution: "xpath@npm:0.0.34" - checksum: 10/77ce03c4494dab97b70fa443761c35a6bd484538a449714b981387a532a6eb22e245b29164f5d8a4a82f4f3cfd71d27ba71d09ed2b6fe933654585c6e46c0a25 - languageName: node - linkType: hard - "xtend@npm:^4.0.0, xtend@npm:^4.0.2": version: 4.0.2 resolution: "xtend@npm:4.0.2"