From a8d046319e52902f95bc8c0139239512c958b703 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 15:38:48 -0500 Subject: [PATCH 01/26] create a new guest auth provider. running into an issue on reload of an active state Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- app-config.yaml | 4 + packages/app-defaults/src/defaults/apis.ts | 17 +++ packages/app/src/App.tsx | 2 +- packages/app/src/identityProviders.ts | 7 ++ packages/backend/package.json | 1 + packages/backend/src/plugins/auth.ts | 2 + .../implementations/auth/guest/GuestAuth.ts | 113 ++++++++++++++++++ .../apis/implementations/auth/guest/index.ts | 16 +++ .../src/apis/implementations/auth/index.ts | 1 + .../src/apis/definitions/auth.ts | 12 ++ .../.eslintrc.js | 1 + .../README.md | 5 + .../package.json | 42 +++++++ .../src/createGuestAuthFactory.ts | 54 +++++++++ .../src/createGuestAuthRouteHandlers.ts | 111 +++++++++++++++++ .../src/index.ts | 25 ++++ .../src/module.ts | 44 +++++++ .../src/resolvers.ts | 40 +++++++ .../src/types.ts | 25 ++++ .../auth-backend/src/providers/guest/index.ts | 16 +++ .../src/providers/guest/provider.ts | 49 ++++++++ .../auth-backend/src/providers/providers.ts | 3 + yarn.lock | 34 +++++- 23 files changed, 620 insertions(+), 4 deletions(-) create mode 100644 packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts create mode 100644 packages/core-app-api/src/apis/implementations/auth/guest/index.ts create mode 100644 plugins/auth-backend-module-guest-provider/.eslintrc.js create mode 100644 plugins/auth-backend-module-guest-provider/README.md create mode 100644 plugins/auth-backend-module-guest-provider/package.json create mode 100644 plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts create mode 100644 plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts create mode 100644 plugins/auth-backend-module-guest-provider/src/index.ts create mode 100644 plugins/auth-backend-module-guest-provider/src/module.ts create mode 100644 plugins/auth-backend-module-guest-provider/src/resolvers.ts create mode 100644 plugins/auth-backend-module-guest-provider/src/types.ts create mode 100644 plugins/auth-backend/src/providers/guest/index.ts create mode 100644 plugins/auth-backend/src/providers/guest/provider.ts diff --git a/app-config.yaml b/app-config.yaml index 9b059da216..df0fd153f8 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -399,6 +399,10 @@ auth: scopes: ${AUTH_ATLASSIAN_SCOPES} myproxy: development: {} + guest: + development: + clientId: t123 + clientSecret: test123 costInsights: engineerCost: 200000 engineerThreshold: 0.5 diff --git a/packages/app-defaults/src/defaults/apis.ts b/packages/app-defaults/src/defaults/apis.ts index 4e9e1a492c..3285b05dcf 100644 --- a/packages/app-defaults/src/defaults/apis.ts +++ b/packages/app-defaults/src/defaults/apis.ts @@ -35,6 +35,7 @@ import { createFetchApi, FetchMiddlewares, VMwareCloudAuth, + GuestAuth, } from '@backstage/core-app-api'; import { @@ -58,6 +59,7 @@ import { bitbucketServerAuthApiRef, atlassianAuthApiRef, vmwareCloudAuthApiRef, + guestAuthApiRef, } from '@backstage/core-plugin-api'; import { permissionApiRef, @@ -277,6 +279,21 @@ export const apis = [ }); }, }), + + createApiFactory({ + api: guestAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, configApi }) => { + return GuestAuth.create({ + configApi, + discoveryApi, + environment: configApi.getOptionalString('auth.environment'), + }); + }, + }), createApiFactory({ api: permissionApiRef, deps: { diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 3d8bd45e5a..5357ad4d16 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -128,7 +128,7 @@ const app = createApp({ return ( diff --git a/packages/app/src/identityProviders.ts b/packages/app/src/identityProviders.ts index 66f1460210..9f2ed58e8d 100644 --- a/packages/app/src/identityProviders.ts +++ b/packages/app/src/identityProviders.ts @@ -23,6 +23,7 @@ import { oneloginAuthApiRef, bitbucketAuthApiRef, bitbucketServerAuthApiRef, + guestAuthApiRef, } from '@backstage/core-plugin-api'; export const providers = [ @@ -74,4 +75,10 @@ export const providers = [ message: 'Sign In using Bitbucket Server', apiRef: bitbucketServerAuthApiRef, }, + { + id: 'guest-auth-provider', + title: 'Guest', + message: 'Sign in as a guest', + apiRef: guestAuthApiRef, + }, ]; diff --git a/packages/backend/package.json b/packages/backend/package.json index e6102b69cf..989d64eeec 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -35,6 +35,7 @@ "@backstage/plugin-adr-backend": "workspace:^", "@backstage/plugin-app-backend": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", + "@backstage/plugin-auth-backend-module-guest-provider": "^0.0.0", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-azure-devops-backend": "workspace:^", "@backstage/plugin-azure-sites-common": "workspace:^", diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts index 0d92315f92..773d3f4270 100644 --- a/packages/backend/src/plugins/auth.ts +++ b/packages/backend/src/plugins/auth.ts @@ -141,6 +141,8 @@ export default async function createPlugin( }, }, }), + + guest: providers.guest.create(), }, }); } diff --git a/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts b/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts new file mode 100644 index 0000000000..88d4612973 --- /dev/null +++ b/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts @@ -0,0 +1,113 @@ +/* + * 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 { + AuthRequestOptions, + BackstageIdentityApi, + ProfileInfo, + ProfileInfoApi, + SessionApi, + SessionState, + BackstageIdentityResponse, +} from '@backstage/core-plugin-api'; +import { Observable } from '@backstage/types'; +import { DirectAuthConnector } from '../../../../lib/AuthConnector'; +import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; +import { SessionManager } from '../../../../lib/AuthSessionManager/types'; +import { AuthApiCreateOptions } from '../types'; + +type GuestSession = { + profile: ProfileInfo; + backstageIdentity: BackstageIdentityResponse; +}; + +const DEFAULT_PROVIDER = { + id: 'guest', + title: 'Guest', + icon: () => null, +}; + +/** + * Implements a guest auth flow. + * + * @public + */ +export default class GuestAuth + implements ProfileInfoApi, BackstageIdentityApi, SessionApi +{ + static create(options: AuthApiCreateOptions) { + const { + discoveryApi, + environment = 'development', + provider = DEFAULT_PROVIDER, + } = options; + + const connector = new DirectAuthConnector({ + discoveryApi, + environment, + provider, + }); + + const sessionManager = new RefreshingAuthSessionManager({ + connector, + defaultScopes: new Set([]), + sessionScopes: (_: GuestSession) => new Set(), + sessionShouldRefresh: (session: GuestSession) => { + let min = Infinity; + if (session.backstageIdentity?.expiresAt) { + min = Math.min( + min, + (session.backstageIdentity.expiresAt.getTime() - Date.now()) / 1000, + ); + } + return min < 60 * 5; + }, + }); + + return new GuestAuth({ sessionManager }); + } + + sessionState$(): Observable { + return this.sessionManager.sessionState$(); + } + + private readonly sessionManager: SessionManager; + + private constructor(options: { + sessionManager: SessionManager; + }) { + this.sessionManager = options.sessionManager; + } + + async signIn() { + await this.getBackstageIdentity({}); + } + async signOut() { + await this.sessionManager.removeSession(); + } + + async getBackstageIdentity( + options: AuthRequestOptions = {}, + ): Promise { + const session = await this.sessionManager.getSession(options); + return session?.backstageIdentity; + } + + async getProfile(options: AuthRequestOptions = {}) { + const session = await this.sessionManager.getSession(options); + return session?.profile; + } +} diff --git a/packages/core-app-api/src/apis/implementations/auth/guest/index.ts b/packages/core-app-api/src/apis/implementations/auth/guest/index.ts new file mode 100644 index 0000000000..42db58cfe6 --- /dev/null +++ b/packages/core-app-api/src/apis/implementations/auth/guest/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { default as GuestAuth } from './GuestAuth'; diff --git a/packages/core-app-api/src/apis/implementations/auth/index.ts b/packages/core-app-api/src/apis/implementations/auth/index.ts index e02e07961a..58db084760 100644 --- a/packages/core-app-api/src/apis/implementations/auth/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/index.ts @@ -26,4 +26,5 @@ export * from './bitbucket'; export * from './bitbucketServer'; export * from './atlassian'; export * from './vmwareCloud'; +export * from './guest'; export type { OAuthApiCreateOptions, AuthApiCreateOptions } from './types'; diff --git a/packages/core-plugin-api/src/apis/definitions/auth.ts b/packages/core-plugin-api/src/apis/definitions/auth.ts index d89544cf68..b11352b373 100644 --- a/packages/core-plugin-api/src/apis/definitions/auth.ts +++ b/packages/core-plugin-api/src/apis/definitions/auth.ts @@ -469,3 +469,15 @@ export const vmwareCloudAuthApiRef: ApiRef< > = createApiRef({ id: 'core.auth.vmware-cloud', }); + +/** + * Provides guest authentication support. + * + * @public + * @remarks + */ +export const guestAuthApiRef: ApiRef< + ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.guest', +}); diff --git a/plugins/auth-backend-module-guest-provider/.eslintrc.js b/plugins/auth-backend-module-guest-provider/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/auth-backend-module-guest-provider/README.md b/plugins/auth-backend-module-guest-provider/README.md new file mode 100644 index 0000000000..65da015958 --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/README.md @@ -0,0 +1,5 @@ +# backstage-plugin-auth-backend-module-guest-provider + +The guest-provider backend module for the auth plugin. + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json new file mode 100644 index 0000000000..c35162fd71 --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -0,0 +1,42 @@ +{ + "name": "@backstage/plugin-auth-backend-module-guest-provider", + "description": "The guest-provider backend module for the auth plugin.", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin-module" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/catalog-model": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", + "passport-oauth2": "^1.7.0" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "express": "^4.18.2" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts b/plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts new file mode 100644 index 0000000000..8331870528 --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts @@ -0,0 +1,54 @@ +/* + * 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 { SignInResolverFactory } from '@backstage/plugin-auth-node'; +import type { + AuthProviderFactory, + ProfileTransform, + SignInResolver, +} from '@backstage/plugin-auth-node'; +import { createGuestAuthRouteHandlers } from './createGuestAuthRouteHandlers'; +import { GuestInfo } from './types'; +import { guestResolver } from './resolvers'; + +/** @public */ +export function createGuestAuthProviderFactory(options?: { + profileTransform?: ProfileTransform; + signInResolver?: SignInResolver; + signInResolverFactories?: Record< + string, + SignInResolverFactory + >; +}): AuthProviderFactory { + return ctx => { + const signInResolver = options?.signInResolver ?? guestResolver(); + + if (!signInResolver) { + throw new Error( + `No sign-in resolver configured for guest auth provider '${ctx.providerId}'`, + ); + } + + return createGuestAuthRouteHandlers({ + signInResolver, + baseUrl: ctx.baseUrl, + appUrl: ctx.appUrl, + config: ctx.config, + resolverContext: ctx.resolverContext, + profileTransform: options?.profileTransform, + }); + }; +} diff --git a/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts b/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts new file mode 100644 index 0000000000..07d0d3e87a --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts @@ -0,0 +1,111 @@ +/* + * 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 type { Request, Response } from 'express'; +import type { Config } from '@backstage/config'; +import { + AuthProviderRouteHandlers, + AuthResolverContext, + ClientAuthResponse, + ProfileTransform, + SignInResolver, + prepareBackstageIdentityResponse, + sendWebMessageResponse, +} from '@backstage/plugin-auth-node'; +import { GuestInfo } from './types'; + +/** @public */ +export interface GuestAuthRouteHandlersOptions { + config: Config; + baseUrl: string; + appUrl: string; + resolverContext: AuthResolverContext; + signInResolver: SignInResolver; + profileTransform?: ProfileTransform; +} + +const DEFAULT_RESULT: GuestInfo = { name: 'Guest' }; + +/** @public */ +export function createGuestAuthRouteHandlers( + options: GuestAuthRouteHandlersOptions, +): AuthProviderRouteHandlers { + const { resolverContext, signInResolver, appUrl } = options; + + const defaultTransform: ProfileTransform = async result => { + return { + profile: { + displayName: result.name, + }, + }; + }; + + const profileTransform = options.profileTransform ?? defaultTransform; + return { + async start(_, res): Promise { + res.redirect('handler/frame'); + }, + + async frameHandler(_, res): Promise { + const { profile } = await profileTransform( + DEFAULT_RESULT, + resolverContext, + ); + const response: ClientAuthResponse = { + profile, + providerInfo: { + name: 'Guest', + }, + }; + if (signInResolver) { + const identity = await signInResolver( + { profile, result: DEFAULT_RESULT }, + resolverContext, + ); + response.backstageIdentity = prepareBackstageIdentityResponse(identity); + } + // post message back to popup if successful + sendWebMessageResponse(res, appUrl, { + type: 'authorization_response', + response, + }); + }, + + async refresh(this: never, _: Request, res: Response): Promise { + const { profile } = await profileTransform( + DEFAULT_RESULT, + resolverContext, + ); + + const identity = await signInResolver( + { profile, result: DEFAULT_RESULT }, + resolverContext, + ); + + const response: ClientAuthResponse<{}> = { + profile, + providerInfo: {}, + backstageIdentity: prepareBackstageIdentityResponse(identity), + }; + + res.status(200).json(response); + }, + + async logout(_, res) { + res.end(); + }, + }; +} diff --git a/plugins/auth-backend-module-guest-provider/src/index.ts b/plugins/auth-backend-module-guest-provider/src/index.ts new file mode 100644 index 0000000000..b1a89763b9 --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/src/index.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** + * The guest-provider backend module for the auth plugin. + * + * @packageDocumentation + */ + +export { createGuestAuthProviderFactory } from './createGuestAuthFactory'; +export type { GuestInfo } from './types'; +export { authModuleGuestProvider as default } from './module'; diff --git a/plugins/auth-backend-module-guest-provider/src/module.ts b/plugins/auth-backend-module-guest-provider/src/module.ts new file mode 100644 index 0000000000..c9fd3feea4 --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/src/module.ts @@ -0,0 +1,44 @@ +/* + * 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { + createOAuthProviderFactory, + commonSignInResolvers, + authProvidersExtensionPoint, +} from '@backstage/plugin-auth-node'; +import { createGuestAuthProviderFactory } from './createGuestAuthFactory'; + +export const authModuleGuestProvider = createBackendModule({ + pluginId: 'auth', + moduleId: 'guest-provider', + register(reg) { + reg.registerInit({ + deps: { + logger: coreServices.logger, + providers: authProvidersExtensionPoint, + }, + async init({ providers }) { + providers.registerProvider({ + providerId: 'guest', + factory: createGuestAuthProviderFactory(), + }); + }, + }); + }, +}); diff --git a/plugins/auth-backend-module-guest-provider/src/resolvers.ts b/plugins/auth-backend-module-guest-provider/src/resolvers.ts new file mode 100644 index 0000000000..47d340f013 --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/src/resolvers.ts @@ -0,0 +1,40 @@ +/* + * 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 { stringifyEntityRef } from '@backstage/catalog-model'; +import { createSignInResolverFactory } from '@backstage/plugin-auth-node'; + +export const guestResolver = createSignInResolverFactory({ + create() { + return async (_, ctx) => { + const userRef = stringifyEntityRef({ + kind: 'user', + name: 'guest', + }); + try { + return ctx.signInWithCatalogUser({ entityRef: userRef }); + } catch (err) { + // We can't guarantee that a guest user exists in the catalog, so we issue a token directly, + return ctx.issueToken({ + claims: { + sub: userRef, + ent: [userRef], + }, + }); + } + }; + }, +}); diff --git a/plugins/auth-backend-module-guest-provider/src/types.ts b/plugins/auth-backend-module-guest-provider/src/types.ts new file mode 100644 index 0000000000..9d0ace0a33 --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/src/types.ts @@ -0,0 +1,25 @@ +/* + * 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 { ProfileTransform } from '@backstage/plugin-auth-node'; + +export type GuestInfo = { + name: string; +}; + +export interface GuestAuthenticator { + defaultProfileTransform: ProfileTransform; +} diff --git a/plugins/auth-backend/src/providers/guest/index.ts b/plugins/auth-backend/src/providers/guest/index.ts new file mode 100644 index 0000000000..7b384798b0 --- /dev/null +++ b/plugins/auth-backend/src/providers/guest/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { guest } from './provider'; diff --git a/plugins/auth-backend/src/providers/guest/provider.ts b/plugins/auth-backend/src/providers/guest/provider.ts new file mode 100644 index 0000000000..7d3a9e724c --- /dev/null +++ b/plugins/auth-backend/src/providers/guest/provider.ts @@ -0,0 +1,49 @@ +/* + * 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 { createAuthProviderIntegration } from '../createAuthProviderIntegration'; +import { AuthHandler, SignInResolver } from '../types'; +import { createGuestAuthProviderFactory } from '@backstage/plugin-auth-backend-module-guest-provider'; +import { GuestInfo } from '@backstage/plugin-auth-backend-module-guest-provider'; + +/** + * Auth provider integration for Google auth + * + * @public + */ +export const guest = 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 createGuestAuthProviderFactory({ + profileTransform: options?.authHandler, + signInResolver: options?.signIn?.resolver, + }); + }, +}); diff --git a/plugins/auth-backend/src/providers/providers.ts b/plugins/auth-backend/src/providers/providers.ts index 76ac51f662..d527bf8b13 100644 --- a/plugins/auth-backend/src/providers/providers.ts +++ b/plugins/auth-backend/src/providers/providers.ts @@ -30,6 +30,7 @@ import { oidc } from './oidc'; import { okta } from './okta'; import { onelogin } from './onelogin'; import { saml } from './saml'; +import { guest } from './guest'; import { bitbucketServer } from './bitbucketServer'; import { easyAuth } from './azure-easyauth'; import { AuthProviderFactory } from '@backstage/plugin-auth-node'; @@ -58,6 +59,7 @@ export const providers = Object.freeze({ onelogin, saml, easyAuth, + guest, }); /** @@ -83,4 +85,5 @@ export const defaultAuthProviderFactories: { bitbucket: bitbucket.create(), bitbucketServer: bitbucketServer.create(), atlassian: atlassian.create(), + guest: guest.create(), }; diff --git a/yarn.lock b/yarn.lock index 73413df542..d1af60c782 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,6 +1,3 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - __metadata: version: 6 cacheKey: 8 @@ -4682,6 +4679,22 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-auth-backend-module-guest-provider@^0.0.0, @backstage/plugin-auth-backend-module-guest-provider@workspace:plugins/auth-backend-module-guest-provider": + version: 0.0.0-use.local + resolution: "@backstage/plugin-auth-backend-module-guest-provider@workspace:plugins/auth-backend-module-guest-provider" + dependencies: + "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/plugin-auth-node": "workspace:^" + express: ^4.18.2 + passport-oauth2: ^1.7.0 + 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": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-microsoft-provider@workspace:plugins/auth-backend-module-microsoft-provider" @@ -4838,6 +4851,7 @@ __metadata: "@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-guest-provider": ^0.0.0 "@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:^" @@ -27446,6 +27460,7 @@ __metadata: "@backstage/plugin-adr-backend": "workspace:^" "@backstage/plugin-app-backend": "workspace:^" "@backstage/plugin-auth-backend": "workspace:^" + "@backstage/plugin-auth-backend-module-guest-provider": ^0.0.0 "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-azure-devops-backend": "workspace:^" "@backstage/plugin-azure-sites-common": "workspace:^" @@ -37346,6 +37361,19 @@ __metadata: languageName: node linkType: hard +"passport-oauth2@npm:1.x.x, passport-oauth2@npm:^1.1.2, passport-oauth2@npm:^1.4.0, passport-oauth2@npm:^1.6.0, passport-oauth2@npm:^1.6.1, passport-oauth2@npm:^1.7.0": + version: 1.7.0 + resolution: "passport-oauth2@npm:1.7.0" + dependencies: + base64url: 3.x.x + oauth: 0.10.x + passport-strategy: 1.x.x + uid2: 0.0.x + utils-merge: 1.x.x + checksum: a9a80b968343c9c1906f74ef613b346ec2d6a6acfe17af81e673fd774779b436729252485755c3ce182f2cdba2434d75067418952d722404d65b93c0360ca02b + languageName: node + linkType: hard + "passport-oauth@npm:1.0.0, passport-oauth@npm:^1.0.0": version: 1.0.0 resolution: "passport-oauth@npm:1.0.0" From 08b7c8a59434b0a3502dd70cd0d2dc7a9158d5ec Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 16:08:35 -0500 Subject: [PATCH 02/26] needed to support refreshing the token Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- .../implementations/auth/guest/GuestAuth.ts | 4 +- .../lib/AuthConnector/DirectAuthConnector.ts | 2 +- .../RefreshingDirectAuthConnector.ts | 54 +++++++++++++++++++ .../src/createGuestAuthRouteHandlers.ts | 7 ++- .../src/module.ts | 6 +-- 5 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts diff --git a/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts b/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts index 88d4612973..880aa0f675 100644 --- a/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts @@ -24,10 +24,10 @@ import { BackstageIdentityResponse, } from '@backstage/core-plugin-api'; import { Observable } from '@backstage/types'; -import { DirectAuthConnector } from '../../../../lib/AuthConnector'; import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { AuthApiCreateOptions } from '../types'; +import { RefreshingDirectAuthConnector } from '../../../../lib/AuthConnector/RefreshingDirectAuthConnector'; type GuestSession = { profile: ProfileInfo; @@ -55,7 +55,7 @@ export default class GuestAuth provider = DEFAULT_PROVIDER, } = options; - const connector = new DirectAuthConnector({ + const connector = new RefreshingDirectAuthConnector({ discoveryApi, environment, provider, diff --git a/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts index 200ba755ac..4cb0553efc 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts @@ -70,7 +70,7 @@ export class DirectAuthConnector { } } - private async buildUrl(path: string): Promise { + protected async buildUrl(path: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('auth'); return `${baseUrl}/${this.provider.id}${path}?env=${this.environment}`; } diff --git a/packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts new file mode 100644 index 0000000000..34969bf356 --- /dev/null +++ b/packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts @@ -0,0 +1,54 @@ +/* + * 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 { DirectAuthConnector } from './DirectAuthConnector'; + +export class RefreshingDirectAuthConnector< + DirectAuthResponse, +> extends DirectAuthConnector { + async refreshSession(): Promise { + const res = await fetch( + `${await this.buildUrl('/refresh')}&optional=true`, + { + headers: { + 'x-requested-with': 'XMLHttpRequest', + }, + credentials: 'include', + }, + ).catch(error => { + throw new Error(`Auth refresh request failed, ${error}`); + }); + + if (!res.ok) { + const error: any = new Error( + `Auth refresh request failed, ${res.statusText}`, + ); + error.status = res.status; + throw error; + } + + const authInfo = await res.json(); + + if (authInfo.error) { + const error = new Error(authInfo.error.message); + if (authInfo.error.name) { + error.name = authInfo.error.name; + } + throw error; + } + return authInfo; + } +} diff --git a/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts b/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts index 07d0d3e87a..8d1cfe78b4 100644 --- a/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts +++ b/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts @@ -56,6 +56,7 @@ export function createGuestAuthRouteHandlers( const profileTransform = options.profileTransform ?? defaultTransform; return { async start(_, res): Promise { + // We are the auth provider for guests, skip this step. res.redirect('handler/frame'); }, @@ -66,9 +67,7 @@ export function createGuestAuthRouteHandlers( ); const response: ClientAuthResponse = { profile, - providerInfo: { - name: 'Guest', - }, + providerInfo: DEFAULT_RESULT, }; if (signInResolver) { const identity = await signInResolver( @@ -97,7 +96,7 @@ export function createGuestAuthRouteHandlers( const response: ClientAuthResponse<{}> = { profile, - providerInfo: {}, + providerInfo: DEFAULT_RESULT, backstageIdentity: prepareBackstageIdentityResponse(identity), }; diff --git a/plugins/auth-backend-module-guest-provider/src/module.ts b/plugins/auth-backend-module-guest-provider/src/module.ts index c9fd3feea4..69b5eba48d 100644 --- a/plugins/auth-backend-module-guest-provider/src/module.ts +++ b/plugins/auth-backend-module-guest-provider/src/module.ts @@ -17,11 +17,7 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { - createOAuthProviderFactory, - commonSignInResolvers, - authProvidersExtensionPoint, -} from '@backstage/plugin-auth-node'; +import { authProvidersExtensionPoint } from '@backstage/plugin-auth-node'; import { createGuestAuthProviderFactory } from './createGuestAuthFactory'; export const authModuleGuestProvider = createBackendModule({ From 1bedb23da027f2ad5b9e29fcfcb8ab84e9f6d47e Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 16:10:36 -0500 Subject: [PATCH 03/26] add changesets Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- .changeset/cold-boats-sell.md | 5 +++++ .changeset/gentle-starfishes-camp.md | 8 ++++++++ 2 files changed, 13 insertions(+) create mode 100644 .changeset/cold-boats-sell.md create mode 100644 .changeset/gentle-starfishes-camp.md diff --git a/.changeset/cold-boats-sell.md b/.changeset/cold-boats-sell.md new file mode 100644 index 0000000000..112d9022bc --- /dev/null +++ b/.changeset/cold-boats-sell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-guest-provider': patch +--- + +Adds a new guest provider that maps guest users to actual tokens. diff --git a/.changeset/gentle-starfishes-camp.md b/.changeset/gentle-starfishes-camp.md new file mode 100644 index 0000000000..229d4c39c9 --- /dev/null +++ b/.changeset/gentle-starfishes-camp.md @@ -0,0 +1,8 @@ +--- +'@backstage/core-plugin-api': minor +'@backstage/app-defaults': minor +'@backstage/core-app-api': minor +'@backstage/plugin-auth-backend': minor +--- + +Adds in support for the new guest provider added by `@backstage/plugin-auth-backend-module-guest-provider`. From 1aedf6c5245f12f80c1f070870da83447605b4d1 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 16:11:37 -0500 Subject: [PATCH 04/26] update app config to remove unnecessary keys Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- app-config.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index df0fd153f8..57d4941f2f 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -401,8 +401,7 @@ auth: development: {} guest: development: - clientId: t123 - clientSecret: test123 + costInsights: engineerCost: 200000 engineerThreshold: 0.5 From 085ddf4084f67d008d7c9d05d4bd597e8c7d389f Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 16:31:08 -0500 Subject: [PATCH 05/26] add comments Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- packages/app/src/identityProviders.ts | 12 ++-- .../implementations/auth/guest/GuestAuth.ts | 2 +- .../RefreshingDirectAuthConnector.ts | 6 ++ .../src/createGuestAuthFactory.ts | 21 +++--- .../src/createGuestAuthRouteHandlers.ts | 71 +++++++------------ .../src/index.ts | 1 - .../src/resolvers.ts | 6 ++ .../src/types.ts | 6 +- 8 files changed, 60 insertions(+), 65 deletions(-) diff --git a/packages/app/src/identityProviders.ts b/packages/app/src/identityProviders.ts index 9f2ed58e8d..c59a55b9d1 100644 --- a/packages/app/src/identityProviders.ts +++ b/packages/app/src/identityProviders.ts @@ -27,6 +27,12 @@ import { } from '@backstage/core-plugin-api'; export const providers = [ + { + id: 'guest-auth-provider', + title: 'Guest', + message: 'Sign in as a guest', + apiRef: guestAuthApiRef, + }, { id: 'google-auth-provider', title: 'Google', @@ -75,10 +81,4 @@ export const providers = [ message: 'Sign In using Bitbucket Server', apiRef: bitbucketServerAuthApiRef, }, - { - id: 'guest-auth-provider', - title: 'Guest', - message: 'Sign in as a guest', - apiRef: guestAuthApiRef, - }, ]; diff --git a/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts b/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts index 880aa0f675..b054187373 100644 --- a/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts @@ -41,7 +41,7 @@ const DEFAULT_PROVIDER = { }; /** - * Implements a guest auth flow. + * Implements a guest auth flow. Heavily based on SAML flow with added support for refreshing the token. * * @public */ diff --git a/packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts index 34969bf356..94f7486210 100644 --- a/packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts @@ -16,9 +16,15 @@ import { DirectAuthConnector } from './DirectAuthConnector'; +/** + * Add support for refreshing direct tokens. Used for guest authentication. + */ export class RefreshingDirectAuthConnector< DirectAuthResponse, > extends DirectAuthConnector { + /** + * Pulled from DefaultAuthConnector and adapted for use with DirectAuthConnector. + */ async refreshSession(): Promise { const res = await fetch( `${await this.buildUrl('/refresh')}&optional=true`, diff --git a/plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts b/plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts index 8331870528..acd08940a3 100644 --- a/plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts +++ b/plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts @@ -21,17 +21,21 @@ import type { SignInResolver, } from '@backstage/plugin-auth-node'; import { createGuestAuthRouteHandlers } from './createGuestAuthRouteHandlers'; -import { GuestInfo } from './types'; import { guestResolver } from './resolvers'; +const defaultTransform: ProfileTransform<{}> = async () => { + return { + profile: { + displayName: 'Guest', + }, + }; +}; + /** @public */ export function createGuestAuthProviderFactory(options?: { - profileTransform?: ProfileTransform; - signInResolver?: SignInResolver; - signInResolverFactories?: Record< - string, - SignInResolverFactory - >; + profileTransform?: ProfileTransform<{}>; + signInResolver?: SignInResolver<{}>; + signInResolverFactories?: Record>; }): AuthProviderFactory { return ctx => { const signInResolver = options?.signInResolver ?? guestResolver(); @@ -41,6 +45,7 @@ export function createGuestAuthProviderFactory(options?: { `No sign-in resolver configured for guest auth provider '${ctx.providerId}'`, ); } + const profileTransform = options?.profileTransform ?? defaultTransform; return createGuestAuthRouteHandlers({ signInResolver, @@ -48,7 +53,7 @@ export function createGuestAuthProviderFactory(options?: { appUrl: ctx.appUrl, config: ctx.config, resolverContext: ctx.resolverContext, - profileTransform: options?.profileTransform, + profileTransform, }); }; } diff --git a/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts b/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts index 8d1cfe78b4..ab4f9922cd 100644 --- a/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts +++ b/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts @@ -25,7 +25,6 @@ import { prepareBackstageIdentityResponse, sendWebMessageResponse, } from '@backstage/plugin-auth-node'; -import { GuestInfo } from './types'; /** @public */ export interface GuestAuthRouteHandlersOptions { @@ -33,77 +32,61 @@ export interface GuestAuthRouteHandlersOptions { baseUrl: string; appUrl: string; resolverContext: AuthResolverContext; - signInResolver: SignInResolver; - profileTransform?: ProfileTransform; + signInResolver: SignInResolver<{}>; + profileTransform: ProfileTransform<{}>; } -const DEFAULT_RESULT: GuestInfo = { name: 'Guest' }; - /** @public */ export function createGuestAuthRouteHandlers( options: GuestAuthRouteHandlersOptions, ): AuthProviderRouteHandlers { - const { resolverContext, signInResolver, appUrl } = options; + const { resolverContext, signInResolver, appUrl, profileTransform } = options; + + const createGuestSession = async (): Promise> => { + const { profile } = await profileTransform({}, resolverContext); + + const identity = await signInResolver( + { profile, result: {} }, + resolverContext, + ); - const defaultTransform: ProfileTransform = async result => { return { - profile: { - displayName: result.name, - }, + profile, + providerInfo: {}, + backstageIdentity: prepareBackstageIdentityResponse(identity), }; }; - const profileTransform = options.profileTransform ?? defaultTransform; return { async start(_, res): Promise { // We are the auth provider for guests, skip this step. res.redirect('handler/frame'); }, + /** + * This is where we create the token for the guest user. You can override the + * entityRef for the guest user with `signInResolver`. + */ async frameHandler(_, res): Promise { - const { profile } = await profileTransform( - DEFAULT_RESULT, - resolverContext, - ); - const response: ClientAuthResponse = { - profile, - providerInfo: DEFAULT_RESULT, - }; - if (signInResolver) { - const identity = await signInResolver( - { profile, result: DEFAULT_RESULT }, - resolverContext, - ); - response.backstageIdentity = prepareBackstageIdentityResponse(identity); - } + const session = await createGuestSession(); // post message back to popup if successful sendWebMessageResponse(res, appUrl, { type: 'authorization_response', - response, + response: session, }); }, + /** + * Support refreshing the guest user's token. This should just improve the experience of + * browsing while in guest mode. + */ async refresh(this: never, _: Request, res: Response): Promise { - const { profile } = await profileTransform( - DEFAULT_RESULT, - resolverContext, - ); - - const identity = await signInResolver( - { profile, result: DEFAULT_RESULT }, - resolverContext, - ); - - const response: ClientAuthResponse<{}> = { - profile, - providerInfo: DEFAULT_RESULT, - backstageIdentity: prepareBackstageIdentityResponse(identity), - }; - - res.status(200).json(response); + const session = await createGuestSession(); + res.status(200).json(session); }, async logout(_, res) { + // If we don't send a response or it gets cached into a 204, the page will hang. res.end(); }, }; diff --git a/plugins/auth-backend-module-guest-provider/src/index.ts b/plugins/auth-backend-module-guest-provider/src/index.ts index b1a89763b9..0c4a382a87 100644 --- a/plugins/auth-backend-module-guest-provider/src/index.ts +++ b/plugins/auth-backend-module-guest-provider/src/index.ts @@ -21,5 +21,4 @@ */ export { createGuestAuthProviderFactory } from './createGuestAuthFactory'; -export type { GuestInfo } from './types'; export { authModuleGuestProvider as default } from './module'; diff --git a/plugins/auth-backend-module-guest-provider/src/resolvers.ts b/plugins/auth-backend-module-guest-provider/src/resolvers.ts index 47d340f013..5922530c5c 100644 --- a/plugins/auth-backend-module-guest-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-guest-provider/src/resolvers.ts @@ -17,6 +17,12 @@ import { stringifyEntityRef } from '@backstage/catalog-model'; import { createSignInResolverFactory } from '@backstage/plugin-auth-node'; +/** + * Provide a default implementation of the user to resolve to. By default, this + * is `user:default/guest`. We will attempt to get that user if they're in the + * catalog. If that user doesn't exist in the catalog, we will still create a + * token for them so they can keep viewing. + */ export const guestResolver = createSignInResolverFactory({ create() { return async (_, ctx) => { diff --git a/plugins/auth-backend-module-guest-provider/src/types.ts b/plugins/auth-backend-module-guest-provider/src/types.ts index 9d0ace0a33..c831014cb5 100644 --- a/plugins/auth-backend-module-guest-provider/src/types.ts +++ b/plugins/auth-backend-module-guest-provider/src/types.ts @@ -16,10 +16,6 @@ import { ProfileTransform } from '@backstage/plugin-auth-node'; -export type GuestInfo = { - name: string; -}; - export interface GuestAuthenticator { - defaultProfileTransform: ProfileTransform; + defaultProfileTransform: ProfileTransform<{}>; } From bb710815b2fd76562c6fa1cdc225fff7f6ff7811 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 16:53:43 -0500 Subject: [PATCH 06/26] adding more documentation Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- .../src/layout/SignInPage/providers.tsx | 1 + .../README.md | 78 ++++++++++++++++++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/packages/core-components/src/layout/SignInPage/providers.tsx b/packages/core-components/src/layout/SignInPage/providers.tsx index e456a6948b..20613b7509 100644 --- a/packages/core-components/src/layout/SignInPage/providers.tsx +++ b/packages/core-components/src/layout/SignInPage/providers.tsx @@ -43,6 +43,7 @@ export type SignInProviderType = { }; const signInProviders: { [key: string]: SignInProvider } = { + /** @deprecated Use `@backstage/plugin-auth-backend-module-guest-provider` */ guest: guestProvider, custom: customProvider, common: commonProvider, diff --git a/plugins/auth-backend-module-guest-provider/README.md b/plugins/auth-backend-module-guest-provider/README.md index 65da015958..9c297aebe6 100644 --- a/plugins/auth-backend-module-guest-provider/README.md +++ b/plugins/auth-backend-module-guest-provider/README.md @@ -1,5 +1,77 @@ -# backstage-plugin-auth-backend-module-guest-provider +# Auth Module: Guest Provider -The guest-provider backend module for the auth plugin. +This module provides a guest auth provider implementation for `@backstage/plugin-auth-backend`. This is meant to supersede the existing `'guest'` option for authentication that does not emit tokens and is completely stored as frontend state. -_This plugin was created through the Backstage CLI_ +**NOTE**: + +## Installation + +### Backend + +#### New Backend + +```diff +const backend = createBackend(); +... + ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); + +... +backend.start(); +``` + +#### Old Backend + +This module was also backported for the old backend and can be used like so, + +```diff ++import { ++ providers, ++} from '@backstage/plugin-auth-backend'; + .... + return await createRouter({ + ... + providerFactories: { + gitlab: providers.gitlab(), ++ guest: providers.guest(), + ... + } + ... +``` + +### Frontend + +Add the following to your `SignInPage` providers, + +```diff ++import { ++ guestAuthApiRef, ++} from '@backstage/core-plugin-api'; + +const providers = [ ++ { ++ id: 'guest-auth-provider', ++ title: 'Guest', ++ message: 'Sign in as a guest', ++ apiRef: guestAuthApiRef, ++ }, + ... +``` + +### Config + +Similar to the other authentication providers, you have to enable the provider in config. Add the following to your `app-config.local.yaml`, + +```diff +auth: + providers: ++ guest: ++ development: {} +``` + +We need to specify that the provider is enabled for the given environment, and as there are no config values for this provider yet, you can just specify an empty object. + +## Links + +- [Backstage](https://backstage.io) +- [Repository](https://github.com/backstage/backstage/tree/master/plugins/auth-backend-module-guest-provider) From d1be48bcfabb3df44006afdf75636dbe31da68e8 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 16:59:44 -0500 Subject: [PATCH 07/26] add warning and prevent startup in production. Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- plugins/auth-backend-module-guest-provider/README.md | 2 +- plugins/auth-backend-module-guest-provider/src/module.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend-module-guest-provider/README.md b/plugins/auth-backend-module-guest-provider/README.md index 9c297aebe6..471b522859 100644 --- a/plugins/auth-backend-module-guest-provider/README.md +++ b/plugins/auth-backend-module-guest-provider/README.md @@ -2,7 +2,7 @@ This module provides a guest auth provider implementation for `@backstage/plugin-auth-backend`. This is meant to supersede the existing `'guest'` option for authentication that does not emit tokens and is completely stored as frontend state. -**NOTE**: +**NOTE**: This provider should only ever be enabled for `development` or `test`. Enabling this for production is strongly discouraged as it would give everyone a way to bypass your other authentication methods. ## Installation diff --git a/plugins/auth-backend-module-guest-provider/src/module.ts b/plugins/auth-backend-module-guest-provider/src/module.ts index 69b5eba48d..ad5c8c95a0 100644 --- a/plugins/auth-backend-module-guest-provider/src/module.ts +++ b/plugins/auth-backend-module-guest-provider/src/module.ts @@ -30,6 +30,11 @@ export const authModuleGuestProvider = createBackendModule({ providers: authProvidersExtensionPoint, }, async init({ providers }) { + if (process.env.NODE_ENV === 'production') { + throw new Error( + 'Guest provider does not support authenticating production workloads.', + ); + } providers.registerProvider({ providerId: 'guest', factory: createGuestAuthProviderFactory(), From a83eb21b89bc05135329fa07493d48d821a7483b Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 17:02:11 -0500 Subject: [PATCH 08/26] add object instead of empty Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- app-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-config.yaml b/app-config.yaml index 57d4941f2f..e18c45aefa 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -400,7 +400,7 @@ auth: myproxy: development: {} guest: - development: + development: {} costInsights: engineerCost: 200000 From a8f7904588980b8a0f13e9e9cedf191ecdb36585 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 17:07:59 -0500 Subject: [PATCH 09/26] fix build issues Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- .changeset/selfish-glasses-cheer.md | 5 +++++ packages/backend/package.json | 1 - plugins/auth-backend-module-guest-provider/package.json | 1 - yarn.lock | 4 +--- 4 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 .changeset/selfish-glasses-cheer.md diff --git a/.changeset/selfish-glasses-cheer.md b/.changeset/selfish-glasses-cheer.md new file mode 100644 index 0000000000..0a56a8b489 --- /dev/null +++ b/.changeset/selfish-glasses-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': minor +--- + +**DEPRECATED** `SignInPage`'s `'guest'` provider is deprecated. Use `@backstage/plugin-auth-backend-module-guest-provider` instead. diff --git a/packages/backend/package.json b/packages/backend/package.json index 989d64eeec..e6102b69cf 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -35,7 +35,6 @@ "@backstage/plugin-adr-backend": "workspace:^", "@backstage/plugin-app-backend": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", - "@backstage/plugin-auth-backend-module-guest-provider": "^0.0.0", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-azure-devops-backend": "workspace:^", "@backstage/plugin-azure-sites-common": "workspace:^", diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index c35162fd71..ae50007225 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -5,7 +5,6 @@ "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", - "private": true, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/yarn.lock b/yarn.lock index d1af60c782..f2f121736c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4679,7 +4679,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-guest-provider@^0.0.0, @backstage/plugin-auth-backend-module-guest-provider@workspace:plugins/auth-backend-module-guest-provider": +"@backstage/plugin-auth-backend-module-guest-provider@workspace:plugins/auth-backend-module-guest-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-guest-provider@workspace:plugins/auth-backend-module-guest-provider" dependencies: @@ -4851,7 +4851,6 @@ __metadata: "@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-guest-provider": ^0.0.0 "@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:^" @@ -27460,7 +27459,6 @@ __metadata: "@backstage/plugin-adr-backend": "workspace:^" "@backstage/plugin-app-backend": "workspace:^" "@backstage/plugin-auth-backend": "workspace:^" - "@backstage/plugin-auth-backend-module-guest-provider": ^0.0.0 "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-azure-devops-backend": "workspace:^" "@backstage/plugin-azure-sites-common": "workspace:^" From 8d8e37abcc57c82746c5c22b24da5ba65a0a3b2f Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 17:12:58 -0500 Subject: [PATCH 10/26] fix tsc issue Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- plugins/auth-backend/src/providers/guest/provider.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/auth-backend/src/providers/guest/provider.ts b/plugins/auth-backend/src/providers/guest/provider.ts index 7d3a9e724c..2efc584d5d 100644 --- a/plugins/auth-backend/src/providers/guest/provider.ts +++ b/plugins/auth-backend/src/providers/guest/provider.ts @@ -16,7 +16,6 @@ import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; import { AuthHandler, SignInResolver } from '../types'; import { createGuestAuthProviderFactory } from '@backstage/plugin-auth-backend-module-guest-provider'; -import { GuestInfo } from '@backstage/plugin-auth-backend-module-guest-provider'; /** * Auth provider integration for Google auth @@ -29,7 +28,7 @@ export const guest = createAuthProviderIntegration({ * The profile transformation function used to verify and convert the auth response * into the profile that will be presented to the user. */ - authHandler?: AuthHandler; + authHandler?: AuthHandler<{}>; /** * Configure sign-in for this provider, without it the provider can not be used to sign users in. @@ -38,7 +37,7 @@ export const guest = createAuthProviderIntegration({ /** * Maps an auth result to a Backstage identity for the user. */ - resolver: SignInResolver; + resolver: SignInResolver<{}>; }; }) { return createGuestAuthProviderFactory({ From 4506a1b2241c390d7e18e6338d09b9401f263cce Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 17:13:38 -0500 Subject: [PATCH 11/26] add dependency Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- plugins/auth-backend/package.json | 1 + yarn.lock | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 456cc18af2..3ea1d26475 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -49,6 +49,7 @@ "@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-guest-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:^", diff --git a/yarn.lock b/yarn.lock index f2f121736c..769d1f52a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4679,7 +4679,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-guest-provider@workspace:plugins/auth-backend-module-guest-provider": +"@backstage/plugin-auth-backend-module-guest-provider@workspace:^, @backstage/plugin-auth-backend-module-guest-provider@workspace:plugins/auth-backend-module-guest-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-guest-provider@workspace:plugins/auth-backend-module-guest-provider" dependencies: @@ -4851,6 +4851,7 @@ __metadata: "@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-guest-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:^" From d4b0688c6d9f1fdf11448803f600452f51b9d808 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 17:33:34 -0500 Subject: [PATCH 12/26] fix test case Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- packages/frontend-app-api/src/wiring/createApp.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx index 70f8cfa2ae..af9bfaf751 100644 --- a/packages/frontend-app-api/src/wiring/createApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx @@ -292,6 +292,7 @@ describe('createApp', () => { + ] " From 68c6f67f0cacd0b138cc7a80d37aaf5387d33a3b Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 17:38:13 -0500 Subject: [PATCH 13/26] fix visibility tags Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- .../api-report.md | 22 +++++++++++++++++++ .../src/createGuestAuthRouteHandlers.ts | 2 -- .../src/module.ts | 1 + 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 plugins/auth-backend-module-guest-provider/api-report.md diff --git a/plugins/auth-backend-module-guest-provider/api-report.md b/plugins/auth-backend-module-guest-provider/api-report.md new file mode 100644 index 0000000000..adaf4313fb --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/api-report.md @@ -0,0 +1,22 @@ +## API Report File for "@backstage/plugin-auth-backend-module-guest-provider" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import type { AuthProviderFactory } from '@backstage/plugin-auth-node'; +import { BackendFeature } from '@backstage/backend-plugin-api'; +import type { ProfileTransform } from '@backstage/plugin-auth-node'; +import type { SignInResolver } from '@backstage/plugin-auth-node'; +import { SignInResolverFactory } from '@backstage/plugin-auth-node'; + +// @public (undocumented) +const authModuleGuestProvider: () => BackendFeature; +export default authModuleGuestProvider; + +// @public (undocumented) +export function createGuestAuthProviderFactory(options?: { + profileTransform?: ProfileTransform<{}>; + signInResolver?: SignInResolver<{}>; + signInResolverFactories?: Record>; +}): AuthProviderFactory; +``` diff --git a/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts b/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts index ab4f9922cd..76a69ca75b 100644 --- a/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts +++ b/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts @@ -26,7 +26,6 @@ import { sendWebMessageResponse, } from '@backstage/plugin-auth-node'; -/** @public */ export interface GuestAuthRouteHandlersOptions { config: Config; baseUrl: string; @@ -36,7 +35,6 @@ export interface GuestAuthRouteHandlersOptions { profileTransform: ProfileTransform<{}>; } -/** @public */ export function createGuestAuthRouteHandlers( options: GuestAuthRouteHandlersOptions, ): AuthProviderRouteHandlers { diff --git a/plugins/auth-backend-module-guest-provider/src/module.ts b/plugins/auth-backend-module-guest-provider/src/module.ts index ad5c8c95a0..4d191ac9e1 100644 --- a/plugins/auth-backend-module-guest-provider/src/module.ts +++ b/plugins/auth-backend-module-guest-provider/src/module.ts @@ -20,6 +20,7 @@ import { import { authProvidersExtensionPoint } from '@backstage/plugin-auth-node'; import { createGuestAuthProviderFactory } from './createGuestAuthFactory'; +/** @public */ export const authModuleGuestProvider = createBackendModule({ pluginId: 'auth', moduleId: 'guest-provider', From 215a37bc3795ba963eacff41c968403225461886 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 18:06:29 -0500 Subject: [PATCH 14/26] fix api reports again Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- packages/core-app-api/api-report.md | 20 ++++++++++++++++++++ packages/core-plugin-api/api-report.md | 5 +++++ plugins/auth-backend/api-report.md | 15 +++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 8b8cc991c8..0ecb0193db 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -464,6 +464,26 @@ export class GoogleAuth { static create(options: OAuthApiCreateOptions): typeof googleAuthApiRef.T; } +// @public +export class GuestAuth + implements ProfileInfoApi, BackstageIdentityApi, SessionApi +{ + // (undocumented) + static create(options: AuthApiCreateOptions): GuestAuth; + // (undocumented) + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; + // (undocumented) + getProfile(options?: AuthRequestOptions): Promise; + // (undocumented) + sessionState$(): Observable; + // (undocumented) + signIn(): Promise; + // (undocumented) + signOut(): Promise; +} + // @public export class LocalStorageFeatureFlags implements FeatureFlagsApi { // (undocumented) diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index 2a993687b3..ad91a0431e 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -503,6 +503,11 @@ export const googleAuthApiRef: ApiRef< SessionApi >; +// @public +export const guestAuthApiRef: ApiRef< + ProfileInfoApi & BackstageIdentityApi & SessionApi +>; + // @public export type IconComponent = ComponentType< | { diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 1beddb6419..431caf3e91 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -644,6 +644,21 @@ export const providers: Readonly<{ ) => AuthProviderFactory_2; resolvers: never; }>; + guest: Readonly<{ + create: ( + options?: + | { + authHandler?: AuthHandler<{}> | undefined; + signIn?: + | { + resolver: SignInResolver<{}>; + } + | undefined; + } + | undefined, + ) => AuthProviderFactory_2; + resolvers: never; + }>; }>; // @public @deprecated (undocumented) From 875d1137c771421a83ad86fba0f6a8613fc97590 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 27 Jan 2024 21:25:44 -0500 Subject: [PATCH 15/26] add catalog-info file Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- .../catalog-info.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 plugins/auth-backend-module-guest-provider/catalog-info.yaml diff --git a/plugins/auth-backend-module-guest-provider/catalog-info.yaml b/plugins/auth-backend-module-guest-provider/catalog-info.yaml new file mode 100644 index 0000000000..5d3513296b --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-guest-provider + title: '@backstage/plugin-auth-backend-module-guest-provider' + description: The guest-provider backend module for the auth plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers From 1c64b2af45d88056655d169027534dc72874bfb6 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sun, 11 Feb 2024 12:45:38 -0500 Subject: [PATCH 16/26] update to a proxied sign in identity instead of a separate guest provider Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- app-config.yaml | 5 +- packages/app-defaults/src/defaults/apis.ts | 17 --- packages/app/src/App.tsx | 2 +- packages/app/src/identityProviders.ts | 7 -- packages/backend-next/package.json | 1 + packages/backend-next/src/index.ts | 3 + .../implementations/auth/guest/GuestAuth.ts | 113 ------------------ .../apis/implementations/auth/guest/index.ts | 16 --- .../src/apis/implementations/auth/index.ts | 1 - .../lib/AuthConnector/DirectAuthConnector.ts | 2 +- .../layout/SignInPage/GuestUserIdentity.ts | 3 + .../src/layout/SignInPage/guestProvider.tsx | 78 +++++++----- .../src/layout/SignInPage/providers.tsx | 1 - .../src/apis/definitions/auth.ts | 12 -- .../src/wiring/createApp.test.tsx | 1 - .../README.md | 33 +---- .../src/authenticator.ts} | 12 +- .../src/createGuestAuthFactory.ts | 59 --------- .../src/createGuestAuthRouteHandlers.ts | 91 -------------- .../src/index.ts | 1 - .../src/module.ts | 15 ++- .../src/resolvers.ts | 40 +++---- .../src/providers/guest/provider.ts | 48 -------- .../auth-backend/src/providers/providers.ts | 3 - yarn.lock | 19 +-- 25 files changed, 108 insertions(+), 475 deletions(-) delete mode 100644 packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts delete mode 100644 packages/core-app-api/src/apis/implementations/auth/guest/index.ts rename plugins/{auth-backend/src/providers/guest/index.ts => auth-backend-module-guest-provider/src/authenticator.ts} (67%) delete mode 100644 plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts delete mode 100644 plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts delete mode 100644 plugins/auth-backend/src/providers/guest/provider.ts diff --git a/app-config.yaml b/app-config.yaml index e18c45aefa..b864c3b610 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -400,7 +400,10 @@ auth: myproxy: development: {} guest: - development: {} + development: + signIn: + resolvers: + - resolver: guestUser costInsights: engineerCost: 200000 diff --git a/packages/app-defaults/src/defaults/apis.ts b/packages/app-defaults/src/defaults/apis.ts index 3285b05dcf..4e9e1a492c 100644 --- a/packages/app-defaults/src/defaults/apis.ts +++ b/packages/app-defaults/src/defaults/apis.ts @@ -35,7 +35,6 @@ import { createFetchApi, FetchMiddlewares, VMwareCloudAuth, - GuestAuth, } from '@backstage/core-app-api'; import { @@ -59,7 +58,6 @@ import { bitbucketServerAuthApiRef, atlassianAuthApiRef, vmwareCloudAuthApiRef, - guestAuthApiRef, } from '@backstage/core-plugin-api'; import { permissionApiRef, @@ -279,21 +277,6 @@ export const apis = [ }); }, }), - - createApiFactory({ - api: guestAuthApiRef, - deps: { - discoveryApi: discoveryApiRef, - configApi: configApiRef, - }, - factory: ({ discoveryApi, configApi }) => { - return GuestAuth.create({ - configApi, - discoveryApi, - environment: configApi.getOptionalString('auth.environment'), - }); - }, - }), createApiFactory({ api: permissionApiRef, deps: { diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 5357ad4d16..3d8bd45e5a 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -128,7 +128,7 @@ const app = createApp({ return ( diff --git a/packages/app/src/identityProviders.ts b/packages/app/src/identityProviders.ts index c59a55b9d1..66f1460210 100644 --- a/packages/app/src/identityProviders.ts +++ b/packages/app/src/identityProviders.ts @@ -23,16 +23,9 @@ import { oneloginAuthApiRef, bitbucketAuthApiRef, bitbucketServerAuthApiRef, - guestAuthApiRef, } from '@backstage/core-plugin-api'; export const providers = [ - { - id: 'guest-auth-provider', - title: 'Guest', - message: 'Sign in as a guest', - apiRef: guestAuthApiRef, - }, { id: 'google-auth-provider', title: 'Google', diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index 74016539b0..333484051b 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -33,6 +33,7 @@ "@backstage/plugin-app-backend": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", "@backstage/plugin-auth-backend-module-github-provider": "workspace:^", + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-azure-devops-backend": "workspace:^", "@backstage/plugin-badges-backend": "workspace:^", diff --git a/packages/backend-next/src/index.ts b/packages/backend-next/src/index.ts index e4dd127208..58fa994658 100644 --- a/packages/backend-next/src/index.ts +++ b/packages/backend-next/src/index.ts @@ -57,4 +57,7 @@ backend.add(import('@backstage/plugin-sonarqube-backend')); backend.add(import('@backstage/plugin-signals-backend')); backend.add(import('@backstage/plugin-notifications-backend')); +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); + backend.start(); diff --git a/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts b/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts deleted file mode 100644 index b054187373..0000000000 --- a/packages/core-app-api/src/apis/implementations/auth/guest/GuestAuth.ts +++ /dev/null @@ -1,113 +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 { - AuthRequestOptions, - BackstageIdentityApi, - ProfileInfo, - ProfileInfoApi, - SessionApi, - SessionState, - BackstageIdentityResponse, -} from '@backstage/core-plugin-api'; -import { Observable } from '@backstage/types'; -import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; -import { SessionManager } from '../../../../lib/AuthSessionManager/types'; -import { AuthApiCreateOptions } from '../types'; -import { RefreshingDirectAuthConnector } from '../../../../lib/AuthConnector/RefreshingDirectAuthConnector'; - -type GuestSession = { - profile: ProfileInfo; - backstageIdentity: BackstageIdentityResponse; -}; - -const DEFAULT_PROVIDER = { - id: 'guest', - title: 'Guest', - icon: () => null, -}; - -/** - * Implements a guest auth flow. Heavily based on SAML flow with added support for refreshing the token. - * - * @public - */ -export default class GuestAuth - implements ProfileInfoApi, BackstageIdentityApi, SessionApi -{ - static create(options: AuthApiCreateOptions) { - const { - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - } = options; - - const connector = new RefreshingDirectAuthConnector({ - discoveryApi, - environment, - provider, - }); - - const sessionManager = new RefreshingAuthSessionManager({ - connector, - defaultScopes: new Set([]), - sessionScopes: (_: GuestSession) => new Set(), - sessionShouldRefresh: (session: GuestSession) => { - let min = Infinity; - if (session.backstageIdentity?.expiresAt) { - min = Math.min( - min, - (session.backstageIdentity.expiresAt.getTime() - Date.now()) / 1000, - ); - } - return min < 60 * 5; - }, - }); - - return new GuestAuth({ sessionManager }); - } - - sessionState$(): Observable { - return this.sessionManager.sessionState$(); - } - - private readonly sessionManager: SessionManager; - - private constructor(options: { - sessionManager: SessionManager; - }) { - this.sessionManager = options.sessionManager; - } - - async signIn() { - await this.getBackstageIdentity({}); - } - async signOut() { - await this.sessionManager.removeSession(); - } - - async getBackstageIdentity( - options: AuthRequestOptions = {}, - ): Promise { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity; - } - - async getProfile(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.profile; - } -} diff --git a/packages/core-app-api/src/apis/implementations/auth/guest/index.ts b/packages/core-app-api/src/apis/implementations/auth/guest/index.ts deleted file mode 100644 index 42db58cfe6..0000000000 --- a/packages/core-app-api/src/apis/implementations/auth/guest/index.ts +++ /dev/null @@ -1,16 +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 { default as GuestAuth } from './GuestAuth'; diff --git a/packages/core-app-api/src/apis/implementations/auth/index.ts b/packages/core-app-api/src/apis/implementations/auth/index.ts index 58db084760..e02e07961a 100644 --- a/packages/core-app-api/src/apis/implementations/auth/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/index.ts @@ -26,5 +26,4 @@ export * from './bitbucket'; export * from './bitbucketServer'; export * from './atlassian'; export * from './vmwareCloud'; -export * from './guest'; export type { OAuthApiCreateOptions, AuthApiCreateOptions } from './types'; diff --git a/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts index 4cb0553efc..200ba755ac 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts @@ -70,7 +70,7 @@ export class DirectAuthConnector { } } - protected async buildUrl(path: string): Promise { + private async buildUrl(path: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('auth'); return `${baseUrl}/${this.provider.id}${path}?env=${this.environment}`; } diff --git a/packages/core-components/src/layout/SignInPage/GuestUserIdentity.ts b/packages/core-components/src/layout/SignInPage/GuestUserIdentity.ts index db4731704c..6abb412090 100644 --- a/packages/core-components/src/layout/SignInPage/GuestUserIdentity.ts +++ b/packages/core-components/src/layout/SignInPage/GuestUserIdentity.ts @@ -20,6 +20,9 @@ import { BackstageUserIdentity, } from '@backstage/core-plugin-api'; +/** + * @deprecated Use `@backstage/plugin-auth-backend-module-guest-provider` instead. + */ export class GuestUserIdentity implements IdentityApi { getUserId(): string { return 'guest'; diff --git a/packages/core-components/src/layout/SignInPage/guestProvider.tsx b/packages/core-components/src/layout/SignInPage/guestProvider.tsx index 5393c1ea89..018feb2241 100644 --- a/packages/core-components/src/layout/SignInPage/guestProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/guestProvider.tsx @@ -20,39 +20,55 @@ import Button from '@material-ui/core/Button'; import { InfoCard } from '../InfoCard/InfoCard'; import { GridItem } from './styles'; import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; -import { GuestUserIdentity } from './GuestUserIdentity'; +import { ProxiedSignInIdentity } from '../ProxiedSignInPage/ProxiedSignInIdentity'; +import { discoveryApiRef, useApi } from '@backstage/core-plugin-api'; -const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => ( - - { - onSignInStarted(); - onSignInSuccess(new GuestUserIdentity()); - }} - > - Enter - - } - > - - Enter as a Guest User. -
- You will not have a verified identity, -
- meaning some features might be unavailable. -
-
-
-); +const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { + const discoveryApi = useApi(discoveryApiRef); + return ( + + { + onSignInStarted(); + onSignInSuccess( + new ProxiedSignInIdentity({ + provider: 'guest', + discoveryApi, + }), + ); + }} + > + Enter + + } + > + Sign in as a Guest. + + + ); +}; -const loader: ProviderLoader = async () => { - return new GuestUserIdentity(); +const loader: ProviderLoader = async apis => { + const identity = new ProxiedSignInIdentity({ + provider: 'guest', + discoveryApi: apis.get(discoveryApiRef)!, + }); + + await identity.start(); + + const identityResponse = await identity.getBackstageIdentity(); + + if (!identityResponse) { + return undefined; + } + + return identity; }; export const guestProvider: SignInProvider = { Component, loader }; diff --git a/packages/core-components/src/layout/SignInPage/providers.tsx b/packages/core-components/src/layout/SignInPage/providers.tsx index 20613b7509..e456a6948b 100644 --- a/packages/core-components/src/layout/SignInPage/providers.tsx +++ b/packages/core-components/src/layout/SignInPage/providers.tsx @@ -43,7 +43,6 @@ export type SignInProviderType = { }; const signInProviders: { [key: string]: SignInProvider } = { - /** @deprecated Use `@backstage/plugin-auth-backend-module-guest-provider` */ guest: guestProvider, custom: customProvider, common: commonProvider, diff --git a/packages/core-plugin-api/src/apis/definitions/auth.ts b/packages/core-plugin-api/src/apis/definitions/auth.ts index b11352b373..d89544cf68 100644 --- a/packages/core-plugin-api/src/apis/definitions/auth.ts +++ b/packages/core-plugin-api/src/apis/definitions/auth.ts @@ -469,15 +469,3 @@ export const vmwareCloudAuthApiRef: ApiRef< > = createApiRef({ id: 'core.auth.vmware-cloud', }); - -/** - * Provides guest authentication support. - * - * @public - * @remarks - */ -export const guestAuthApiRef: ApiRef< - ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.guest', -}); diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx index af9bfaf751..70f8cfa2ae 100644 --- a/packages/frontend-app-api/src/wiring/createApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx @@ -292,7 +292,6 @@ describe('createApp', () => { - ] " diff --git a/plugins/auth-backend-module-guest-provider/README.md b/plugins/auth-backend-module-guest-provider/README.md index 471b522859..20c1eb2f15 100644 --- a/plugins/auth-backend-module-guest-provider/README.md +++ b/plugins/auth-backend-module-guest-provider/README.md @@ -2,7 +2,7 @@ This module provides a guest auth provider implementation for `@backstage/plugin-auth-backend`. This is meant to supersede the existing `'guest'` option for authentication that does not emit tokens and is completely stored as frontend state. -**NOTE**: This provider should only ever be enabled for `development` or `test`. Enabling this for production is strongly discouraged as it would give everyone a way to bypass your other authentication methods. +**NOTE**: This provider should only ever be enabled for `development`. This package is explicitly disabled for non-development environments. ## Installation @@ -20,42 +20,15 @@ const backend = createBackend(); backend.start(); ``` -#### Old Backend - -This module was also backported for the old backend and can be used like so, - -```diff -+import { -+ providers, -+} from '@backstage/plugin-auth-backend'; - .... - return await createRouter({ - ... - providerFactories: { - gitlab: providers.gitlab(), -+ guest: providers.guest(), - ... - } - ... -``` - ### Frontend Add the following to your `SignInPage` providers, ```diff -+import { -+ guestAuthApiRef, -+} from '@backstage/core-plugin-api'; - const providers = [ -+ { -+ id: 'guest-auth-provider', -+ title: 'Guest', -+ message: 'Sign in as a guest', -+ apiRef: guestAuthApiRef, -+ }, ++ 'guest', ... +] ``` ### Config diff --git a/plugins/auth-backend/src/providers/guest/index.ts b/plugins/auth-backend-module-guest-provider/src/authenticator.ts similarity index 67% rename from plugins/auth-backend/src/providers/guest/index.ts rename to plugins/auth-backend-module-guest-provider/src/authenticator.ts index 7b384798b0..d33fc2c7c9 100644 --- a/plugins/auth-backend/src/providers/guest/index.ts +++ b/plugins/auth-backend-module-guest-provider/src/authenticator.ts @@ -1,3 +1,5 @@ +import { createProxyAuthenticator } from '@backstage/plugin-auth-node'; + /* * Copyright 2024 The Backstage Authors * @@ -13,4 +15,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { guest } from './provider'; +export const guestAuthenticator = createProxyAuthenticator({ + defaultProfileTransform: async () => { + return { profile: {} }; + }, + initialize() {}, + async authenticate() { + return { result: {} }; + }, +}); diff --git a/plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts b/plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts deleted file mode 100644 index acd08940a3..0000000000 --- a/plugins/auth-backend-module-guest-provider/src/createGuestAuthFactory.ts +++ /dev/null @@ -1,59 +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 { SignInResolverFactory } from '@backstage/plugin-auth-node'; -import type { - AuthProviderFactory, - ProfileTransform, - SignInResolver, -} from '@backstage/plugin-auth-node'; -import { createGuestAuthRouteHandlers } from './createGuestAuthRouteHandlers'; -import { guestResolver } from './resolvers'; - -const defaultTransform: ProfileTransform<{}> = async () => { - return { - profile: { - displayName: 'Guest', - }, - }; -}; - -/** @public */ -export function createGuestAuthProviderFactory(options?: { - profileTransform?: ProfileTransform<{}>; - signInResolver?: SignInResolver<{}>; - signInResolverFactories?: Record>; -}): AuthProviderFactory { - return ctx => { - const signInResolver = options?.signInResolver ?? guestResolver(); - - if (!signInResolver) { - throw new Error( - `No sign-in resolver configured for guest auth provider '${ctx.providerId}'`, - ); - } - const profileTransform = options?.profileTransform ?? defaultTransform; - - return createGuestAuthRouteHandlers({ - signInResolver, - baseUrl: ctx.baseUrl, - appUrl: ctx.appUrl, - config: ctx.config, - resolverContext: ctx.resolverContext, - profileTransform, - }); - }; -} diff --git a/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts b/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts deleted file mode 100644 index 76a69ca75b..0000000000 --- a/plugins/auth-backend-module-guest-provider/src/createGuestAuthRouteHandlers.ts +++ /dev/null @@ -1,91 +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 type { Request, Response } from 'express'; -import type { Config } from '@backstage/config'; -import { - AuthProviderRouteHandlers, - AuthResolverContext, - ClientAuthResponse, - ProfileTransform, - SignInResolver, - prepareBackstageIdentityResponse, - sendWebMessageResponse, -} from '@backstage/plugin-auth-node'; - -export interface GuestAuthRouteHandlersOptions { - config: Config; - baseUrl: string; - appUrl: string; - resolverContext: AuthResolverContext; - signInResolver: SignInResolver<{}>; - profileTransform: ProfileTransform<{}>; -} - -export function createGuestAuthRouteHandlers( - options: GuestAuthRouteHandlersOptions, -): AuthProviderRouteHandlers { - const { resolverContext, signInResolver, appUrl, profileTransform } = options; - - const createGuestSession = async (): Promise> => { - const { profile } = await profileTransform({}, resolverContext); - - const identity = await signInResolver( - { profile, result: {} }, - resolverContext, - ); - - return { - profile, - providerInfo: {}, - backstageIdentity: prepareBackstageIdentityResponse(identity), - }; - }; - - return { - async start(_, res): Promise { - // We are the auth provider for guests, skip this step. - res.redirect('handler/frame'); - }, - - /** - * This is where we create the token for the guest user. You can override the - * entityRef for the guest user with `signInResolver`. - */ - async frameHandler(_, res): Promise { - const session = await createGuestSession(); - // post message back to popup if successful - sendWebMessageResponse(res, appUrl, { - type: 'authorization_response', - response: session, - }); - }, - - /** - * Support refreshing the guest user's token. This should just improve the experience of - * browsing while in guest mode. - */ - async refresh(this: never, _: Request, res: Response): Promise { - const session = await createGuestSession(); - res.status(200).json(session); - }, - - async logout(_, res) { - // If we don't send a response or it gets cached into a 204, the page will hang. - res.end(); - }, - }; -} diff --git a/plugins/auth-backend-module-guest-provider/src/index.ts b/plugins/auth-backend-module-guest-provider/src/index.ts index 0c4a382a87..c6ada31c3a 100644 --- a/plugins/auth-backend-module-guest-provider/src/index.ts +++ b/plugins/auth-backend-module-guest-provider/src/index.ts @@ -20,5 +20,4 @@ * @packageDocumentation */ -export { createGuestAuthProviderFactory } from './createGuestAuthFactory'; export { authModuleGuestProvider as default } from './module'; diff --git a/plugins/auth-backend-module-guest-provider/src/module.ts b/plugins/auth-backend-module-guest-provider/src/module.ts index 4d191ac9e1..a52b8a4ac2 100644 --- a/plugins/auth-backend-module-guest-provider/src/module.ts +++ b/plugins/auth-backend-module-guest-provider/src/module.ts @@ -17,8 +17,12 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { authProvidersExtensionPoint } from '@backstage/plugin-auth-node'; -import { createGuestAuthProviderFactory } from './createGuestAuthFactory'; +import { + authProvidersExtensionPoint, + createProxyAuthProviderFactory, +} from '@backstage/plugin-auth-node'; +import { guestAuthenticator } from './authenticator'; +import { signInAsGuestUser } from './resolvers'; /** @public */ export const authModuleGuestProvider = createBackendModule({ @@ -31,14 +35,17 @@ export const authModuleGuestProvider = createBackendModule({ providers: authProvidersExtensionPoint, }, async init({ providers }) { - if (process.env.NODE_ENV === 'production') { + if (process.env.NODE_ENV !== 'development') { throw new Error( 'Guest provider does not support authenticating production workloads.', ); } providers.registerProvider({ providerId: 'guest', - factory: createGuestAuthProviderFactory(), + factory: createProxyAuthProviderFactory({ + authenticator: guestAuthenticator, + signInResolver: signInAsGuestUser, + }), }); }, }); diff --git a/plugins/auth-backend-module-guest-provider/src/resolvers.ts b/plugins/auth-backend-module-guest-provider/src/resolvers.ts index 5922530c5c..05acf676ec 100644 --- a/plugins/auth-backend-module-guest-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-guest-provider/src/resolvers.ts @@ -15,7 +15,7 @@ */ import { stringifyEntityRef } from '@backstage/catalog-model'; -import { createSignInResolverFactory } from '@backstage/plugin-auth-node'; +import { SignInResolver } from '@backstage/plugin-auth-node'; /** * Provide a default implementation of the user to resolve to. By default, this @@ -23,24 +23,20 @@ import { createSignInResolverFactory } from '@backstage/plugin-auth-node'; * catalog. If that user doesn't exist in the catalog, we will still create a * token for them so they can keep viewing. */ -export const guestResolver = createSignInResolverFactory({ - create() { - return async (_, ctx) => { - const userRef = stringifyEntityRef({ - kind: 'user', - name: 'guest', - }); - try { - return ctx.signInWithCatalogUser({ entityRef: userRef }); - } catch (err) { - // We can't guarantee that a guest user exists in the catalog, so we issue a token directly, - return ctx.issueToken({ - claims: { - sub: userRef, - ent: [userRef], - }, - }); - } - }; - }, -}); +export const signInAsGuestUser: SignInResolver<{}> = async (_, ctx) => { + const userRef = stringifyEntityRef({ + kind: 'user', + name: 'guest', + }); + try { + return ctx.signInWithCatalogUser({ entityRef: userRef }); + } catch (err) { + // We can't guarantee that a guest user exists in the catalog, so we issue a token directly, + return ctx.issueToken({ + claims: { + sub: userRef, + ent: [userRef], + }, + }); + } +}; diff --git a/plugins/auth-backend/src/providers/guest/provider.ts b/plugins/auth-backend/src/providers/guest/provider.ts deleted file mode 100644 index 2efc584d5d..0000000000 --- a/plugins/auth-backend/src/providers/guest/provider.ts +++ /dev/null @@ -1,48 +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 { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { AuthHandler, SignInResolver } from '../types'; -import { createGuestAuthProviderFactory } from '@backstage/plugin-auth-backend-module-guest-provider'; - -/** - * Auth provider integration for Google auth - * - * @public - */ -export const guest = 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 createGuestAuthProviderFactory({ - profileTransform: options?.authHandler, - signInResolver: options?.signIn?.resolver, - }); - }, -}); diff --git a/plugins/auth-backend/src/providers/providers.ts b/plugins/auth-backend/src/providers/providers.ts index d527bf8b13..76ac51f662 100644 --- a/plugins/auth-backend/src/providers/providers.ts +++ b/plugins/auth-backend/src/providers/providers.ts @@ -30,7 +30,6 @@ import { oidc } from './oidc'; import { okta } from './okta'; import { onelogin } from './onelogin'; import { saml } from './saml'; -import { guest } from './guest'; import { bitbucketServer } from './bitbucketServer'; import { easyAuth } from './azure-easyauth'; import { AuthProviderFactory } from '@backstage/plugin-auth-node'; @@ -59,7 +58,6 @@ export const providers = Object.freeze({ onelogin, saml, easyAuth, - guest, }); /** @@ -85,5 +83,4 @@ export const defaultAuthProviderFactories: { bitbucket: bitbucket.create(), bitbucketServer: bitbucketServer.create(), atlassian: atlassian.create(), - guest: guest.create(), }; diff --git a/yarn.lock b/yarn.lock index 769d1f52a3..7e674683b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,3 +1,6 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + __metadata: version: 6 cacheKey: 8 @@ -27411,6 +27414,7 @@ __metadata: "@backstage/plugin-app-backend": "workspace:^" "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-backend-module-github-provider": "workspace:^" + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-azure-devops-backend": "workspace:^" "@backstage/plugin-badges-backend": "workspace:^" @@ -37347,7 +37351,7 @@ __metadata: languageName: node linkType: hard -"passport-oauth2@npm:1.x.x, passport-oauth2@npm:^1.1.2, passport-oauth2@npm:^1.4.0, passport-oauth2@npm:^1.6.0, passport-oauth2@npm:^1.6.1": +"passport-oauth2@npm:1.x.x, passport-oauth2@npm:^1.1.2, passport-oauth2@npm:^1.4.0, passport-oauth2@npm:^1.6.0, passport-oauth2@npm:^1.6.1, passport-oauth2@npm:^1.7.0": version: 1.8.0 resolution: "passport-oauth2@npm:1.8.0" dependencies: @@ -37360,19 +37364,6 @@ __metadata: languageName: node linkType: hard -"passport-oauth2@npm:1.x.x, passport-oauth2@npm:^1.1.2, passport-oauth2@npm:^1.4.0, passport-oauth2@npm:^1.6.0, passport-oauth2@npm:^1.6.1, passport-oauth2@npm:^1.7.0": - version: 1.7.0 - resolution: "passport-oauth2@npm:1.7.0" - dependencies: - base64url: 3.x.x - oauth: 0.10.x - passport-strategy: 1.x.x - uid2: 0.0.x - utils-merge: 1.x.x - checksum: a9a80b968343c9c1906f74ef613b346ec2d6a6acfe17af81e673fd774779b436729252485755c3ce182f2cdba2434d75067418952d722404d65b93c0360ca02b - languageName: node - linkType: hard - "passport-oauth@npm:1.0.0, passport-oauth@npm:^1.0.0": version: 1.0.0 resolution: "passport-oauth@npm:1.0.0" From 4f4fce91cb55d9beec426c653991d17a3397266b Mon Sep 17 00:00:00 2001 From: Aramis Date: Sun, 11 Feb 2024 12:56:51 -0500 Subject: [PATCH 17/26] small fixes Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- app-config.yaml | 5 +- packages/backend/src/plugins/auth.ts | 2 - packages/core-app-api/api-report.md | 20 ------- .../RefreshingDirectAuthConnector.ts | 60 ------------------- packages/core-plugin-api/api-report.md | 5 -- .../api-report.md | 11 ---- plugins/auth-backend/api-report.md | 15 ----- 7 files changed, 1 insertion(+), 117 deletions(-) delete mode 100644 packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts diff --git a/app-config.yaml b/app-config.yaml index b864c3b610..e18c45aefa 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -400,10 +400,7 @@ auth: myproxy: development: {} guest: - development: - signIn: - resolvers: - - resolver: guestUser + development: {} costInsights: engineerCost: 200000 diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts index 773d3f4270..0d92315f92 100644 --- a/packages/backend/src/plugins/auth.ts +++ b/packages/backend/src/plugins/auth.ts @@ -141,8 +141,6 @@ export default async function createPlugin( }, }, }), - - guest: providers.guest.create(), }, }); } diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 0ecb0193db..8b8cc991c8 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -464,26 +464,6 @@ export class GoogleAuth { static create(options: OAuthApiCreateOptions): typeof googleAuthApiRef.T; } -// @public -export class GuestAuth - implements ProfileInfoApi, BackstageIdentityApi, SessionApi -{ - // (undocumented) - static create(options: AuthApiCreateOptions): GuestAuth; - // (undocumented) - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; - // (undocumented) - getProfile(options?: AuthRequestOptions): Promise; - // (undocumented) - sessionState$(): Observable; - // (undocumented) - signIn(): Promise; - // (undocumented) - signOut(): Promise; -} - // @public export class LocalStorageFeatureFlags implements FeatureFlagsApi { // (undocumented) diff --git a/packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts deleted file mode 100644 index 94f7486210..0000000000 --- a/packages/core-app-api/src/lib/AuthConnector/RefreshingDirectAuthConnector.ts +++ /dev/null @@ -1,60 +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 { DirectAuthConnector } from './DirectAuthConnector'; - -/** - * Add support for refreshing direct tokens. Used for guest authentication. - */ -export class RefreshingDirectAuthConnector< - DirectAuthResponse, -> extends DirectAuthConnector { - /** - * Pulled from DefaultAuthConnector and adapted for use with DirectAuthConnector. - */ - async refreshSession(): Promise { - const res = await fetch( - `${await this.buildUrl('/refresh')}&optional=true`, - { - headers: { - 'x-requested-with': 'XMLHttpRequest', - }, - credentials: 'include', - }, - ).catch(error => { - throw new Error(`Auth refresh request failed, ${error}`); - }); - - if (!res.ok) { - const error: any = new Error( - `Auth refresh request failed, ${res.statusText}`, - ); - error.status = res.status; - throw error; - } - - const authInfo = await res.json(); - - if (authInfo.error) { - const error = new Error(authInfo.error.message); - if (authInfo.error.name) { - error.name = authInfo.error.name; - } - throw error; - } - return authInfo; - } -} diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index ad91a0431e..2a993687b3 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -503,11 +503,6 @@ export const googleAuthApiRef: ApiRef< SessionApi >; -// @public -export const guestAuthApiRef: ApiRef< - ProfileInfoApi & BackstageIdentityApi & SessionApi ->; - // @public export type IconComponent = ComponentType< | { diff --git a/plugins/auth-backend-module-guest-provider/api-report.md b/plugins/auth-backend-module-guest-provider/api-report.md index adaf4313fb..773b80b9ba 100644 --- a/plugins/auth-backend-module-guest-provider/api-report.md +++ b/plugins/auth-backend-module-guest-provider/api-report.md @@ -3,20 +3,9 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import type { AuthProviderFactory } from '@backstage/plugin-auth-node'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import type { ProfileTransform } from '@backstage/plugin-auth-node'; -import type { SignInResolver } from '@backstage/plugin-auth-node'; -import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) const authModuleGuestProvider: () => BackendFeature; export default authModuleGuestProvider; - -// @public (undocumented) -export function createGuestAuthProviderFactory(options?: { - profileTransform?: ProfileTransform<{}>; - signInResolver?: SignInResolver<{}>; - signInResolverFactories?: Record>; -}): AuthProviderFactory; ``` diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 431caf3e91..1beddb6419 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -644,21 +644,6 @@ export const providers: Readonly<{ ) => AuthProviderFactory_2; resolvers: never; }>; - guest: Readonly<{ - create: ( - options?: - | { - authHandler?: AuthHandler<{}> | undefined; - signIn?: - | { - resolver: SignInResolver<{}>; - } - | undefined; - } - | undefined, - ) => AuthProviderFactory_2; - resolvers: never; - }>; }>; // @public @deprecated (undocumented) From 10d56c1d7c46852baa012d0a63577fec96a9b3a4 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sun, 11 Feb 2024 13:07:55 -0500 Subject: [PATCH 18/26] more clean up Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- .changeset/gentle-starfishes-camp.md | 8 -------- .changeset/selfish-glasses-cheer.md | 10 +++++++++- plugins/auth-backend/package.json | 1 - yarn.lock | 1 - 4 files changed, 9 insertions(+), 11 deletions(-) delete mode 100644 .changeset/gentle-starfishes-camp.md diff --git a/.changeset/gentle-starfishes-camp.md b/.changeset/gentle-starfishes-camp.md deleted file mode 100644 index 229d4c39c9..0000000000 --- a/.changeset/gentle-starfishes-camp.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/core-plugin-api': minor -'@backstage/app-defaults': minor -'@backstage/core-app-api': minor -'@backstage/plugin-auth-backend': minor ---- - -Adds in support for the new guest provider added by `@backstage/plugin-auth-backend-module-guest-provider`. diff --git a/.changeset/selfish-glasses-cheer.md b/.changeset/selfish-glasses-cheer.md index 0a56a8b489..ffd9bb74e0 100644 --- a/.changeset/selfish-glasses-cheer.md +++ b/.changeset/selfish-glasses-cheer.md @@ -2,4 +2,12 @@ '@backstage/core-components': minor --- -**DEPRECATED** `SignInPage`'s `'guest'` provider is deprecated. Use `@backstage/plugin-auth-backend-module-guest-provider` instead. +**BREAKING** `SignInPage`'s `'guest'` provider now uses `@backstage/plugin-auth-backend-module-guest-provider` to generate tokens. You must install that provider into your backend to continue using the `'guest'` option. + +```diff +const backend = createBackend(); + ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); + +backend.start(); +``` diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 3ea1d26475..456cc18af2 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -49,7 +49,6 @@ "@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-guest-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:^", diff --git a/yarn.lock b/yarn.lock index 7e674683b6..1bd1a644bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4854,7 +4854,6 @@ __metadata: "@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-guest-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:^" From 4fa994f91584548ac1a270f21ccc8e5943b8e1f9 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sun, 11 Feb 2024 14:34:23 -0500 Subject: [PATCH 19/26] run yarn fix Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- plugins/auth-backend-module-guest-provider/package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index ae50007225..edf3210759 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -10,6 +10,11 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/auth-backend-module-guest-provider" + }, "backstage": { "role": "backend-plugin-module" }, From 9fc765af207bf9b36e95eae0b3ae2f42debb1982 Mon Sep 17 00:00:00 2001 From: Aramis Date: Mon, 12 Feb 2024 10:02:11 -0500 Subject: [PATCH 20/26] update with a guide Signed-off-by: Aramis Signed-off-by: aramissennyeydd --- docs/auth/guest/provider.md | 65 +++++++++++++++++++ microsite/sidebars.json | 1 + mkdocs.yml | 1 + .../README.md | 42 ------------ .../src/authenticator.ts | 5 +- 5 files changed, 70 insertions(+), 44 deletions(-) create mode 100644 docs/auth/guest/provider.md diff --git a/docs/auth/guest/provider.md b/docs/auth/guest/provider.md new file mode 100644 index 0000000000..09c52fcd3b --- /dev/null +++ b/docs/auth/guest/provider.md @@ -0,0 +1,65 @@ +--- +id: provider +title: Guest Authentication Provider +sidebar_label: Guest +description: Adding a guest authentication provider in Backstage +--- + +Audience: Admins or developers + +## Summary + +The goal of this guide is to get you set up with a guest authentication provider that emits tokens. This is different than the old guest authentication that is purely stored on the frontend and does not have tokens. The main reason you'd want to use this provider is to use permissioned plugins. + +:::caution +This provider should only ever be enabled for `development`. To prevent unauthorized access to your data, this package is _explicitly_ disabled for non-development environments. +::: + +## Installation + +### Backend + +:::note +This will only work with the new backend system. There is no support for this in the old backend. +::: + +Add the `@backstage/plugin-auth-backend-module-guest-provider` to your backend installation. + +``` +yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-guest-provider +``` + +Then, add it to your backend's `index.ts` file, + +```diff +const backend = createBackend(); + +backend.add('@backstage/plugin-auth-backend'); ++backend.add('@backstage/plugin-auth-backend-module-guest-provider'); + +await backend.start(); +``` + +### Frontend + +Add the following to your `SignInPage` providers, + +```diff +const providers = [ ++ 'guest', + ... +] +``` + +### Config + +Similar to the other authentication providers, you have to enable the provider in config. Add the following to your `app-config.local.yaml`, + +```diff +auth: + providers: ++ guest: ++ development: {} +``` + +We need to specify that the provider is enabled for the given environment, and as there are no config values for this provider yet, you can just specify an empty object. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 4f2724c0b6..fd13fcb57b 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -307,6 +307,7 @@ "auth/gitlab/provider", "auth/google/provider", "auth/google/gcp-iap-auth", + "auth/guest/provider", "auth/okta/provider", "auth/oauth2-proxy/provider", "auth/onelogin/provider", diff --git a/mkdocs.yml b/mkdocs.yml index 2a17613672..a2b2749233 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -161,6 +161,7 @@ nav: - GitLab: 'auth/gitlab/provider.md' - Google: 'auth/google/provider.md' - Google IAP: 'auth/google/gcp-iap-auth.md' + - Guest: 'auth/guest/provider.md' - OAuth2Proxy: 'auth/oauth2-proxy/provider.md' - Okta: 'auth/okta/provider.md' - OneLogin: 'auth/onelogin/provider.md' diff --git a/plugins/auth-backend-module-guest-provider/README.md b/plugins/auth-backend-module-guest-provider/README.md index 20c1eb2f15..79591c987b 100644 --- a/plugins/auth-backend-module-guest-provider/README.md +++ b/plugins/auth-backend-module-guest-provider/README.md @@ -2,48 +2,6 @@ This module provides a guest auth provider implementation for `@backstage/plugin-auth-backend`. This is meant to supersede the existing `'guest'` option for authentication that does not emit tokens and is completely stored as frontend state. -**NOTE**: This provider should only ever be enabled for `development`. This package is explicitly disabled for non-development environments. - -## Installation - -### Backend - -#### New Backend - -```diff -const backend = createBackend(); -... - -+backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); - -... -backend.start(); -``` - -### Frontend - -Add the following to your `SignInPage` providers, - -```diff -const providers = [ -+ 'guest', - ... -] -``` - -### Config - -Similar to the other authentication providers, you have to enable the provider in config. Add the following to your `app-config.local.yaml`, - -```diff -auth: - providers: -+ guest: -+ development: {} -``` - -We need to specify that the provider is enabled for the given environment, and as there are no config values for this provider yet, you can just specify an empty object. - ## Links - [Backstage](https://backstage.io) diff --git a/plugins/auth-backend-module-guest-provider/src/authenticator.ts b/plugins/auth-backend-module-guest-provider/src/authenticator.ts index d33fc2c7c9..436b72617d 100644 --- a/plugins/auth-backend-module-guest-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-guest-provider/src/authenticator.ts @@ -1,5 +1,3 @@ -import { createProxyAuthenticator } from '@backstage/plugin-auth-node'; - /* * Copyright 2024 The Backstage Authors * @@ -15,6 +13,9 @@ import { createProxyAuthenticator } from '@backstage/plugin-auth-node'; * See the License for the specific language governing permissions and * limitations under the License. */ + +import { createProxyAuthenticator } from '@backstage/plugin-auth-node'; + export const guestAuthenticator = createProxyAuthenticator({ defaultProfileTransform: async () => { return { profile: {} }; From d622690f8cc944ca7b2309335891d9ff4dbfeaea Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 17 Feb 2024 17:38:36 -0500 Subject: [PATCH 21/26] code review updates Signed-off-by: aramissennyeydd --- .changeset/cold-boats-sell.md | 7 +++- .changeset/selfish-glasses-cheer.md | 10 +---- app-config.yaml | 2 +- docs/auth/guest/provider.md | 4 +- .../examples/acme/team-a-group.yaml | 14 +++++++ .../templates/default-app/examples/org.yaml | 9 +++++ .../config.d.ts | 27 +++++++++++++ .../src/module.ts | 11 +++-- .../src/resolvers.ts | 40 ++++++++++--------- 9 files changed, 90 insertions(+), 34 deletions(-) create mode 100644 plugins/auth-backend-module-guest-provider/config.d.ts diff --git a/.changeset/cold-boats-sell.md b/.changeset/cold-boats-sell.md index 112d9022bc..e47517140d 100644 --- a/.changeset/cold-boats-sell.md +++ b/.changeset/cold-boats-sell.md @@ -2,4 +2,9 @@ '@backstage/plugin-auth-backend-module-guest-provider': patch --- -Adds a new guest provider that maps guest users to actual tokens. +Adds a new guest provider that maps guest users to actual tokens. This also shifts the default guest login to `user:development/guest` to reduce overlap with your production/real data. To change that (or set it back to the old default, use the new `auth.guestEntityRef` config key) like so, + +```yaml title=app-config.yaml +auth: + guestEntityRef: user:default/guest +``` diff --git a/.changeset/selfish-glasses-cheer.md b/.changeset/selfish-glasses-cheer.md index ffd9bb74e0..4d880f0523 100644 --- a/.changeset/selfish-glasses-cheer.md +++ b/.changeset/selfish-glasses-cheer.md @@ -2,12 +2,4 @@ '@backstage/core-components': minor --- -**BREAKING** `SignInPage`'s `'guest'` provider now uses `@backstage/plugin-auth-backend-module-guest-provider` to generate tokens. You must install that provider into your backend to continue using the `'guest'` option. - -```diff -const backend = createBackend(); - -+backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); - -backend.start(); -``` +`SignInPage`'s `'guest'` provider now supports the `@backstage/plugin-auth-backend-module-guest-provider` package to generate tokens. It will continue to use the old frontend-only auth as a fallback. diff --git a/app-config.yaml b/app-config.yaml index e18c45aefa..4b92687653 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -242,7 +242,7 @@ catalog: - Domain - Location providers: - openapi: + backstageOpenapi: plugins: - catalog - search diff --git a/docs/auth/guest/provider.md b/docs/auth/guest/provider.md index 09c52fcd3b..ca5bee1d09 100644 --- a/docs/auth/guest/provider.md +++ b/docs/auth/guest/provider.md @@ -59,7 +59,9 @@ Similar to the other authentication providers, you have to enable the provider i auth: providers: + guest: -+ development: {} ++ development: + // new optional property to override the default value. ++ loginAs: user:default/guest ``` We need to specify that the provider is enabled for the given environment, and as there are no config values for this provider yet, you can just specify an empty object. diff --git a/packages/catalog-model/examples/acme/team-a-group.yaml b/packages/catalog-model/examples/acme/team-a-group.yaml index 7fe0e7b3f3..eb95c47093 100644 --- a/packages/catalog-model/examples/acme/team-a-group.yaml +++ b/packages/catalog-model/examples/acme/team-a-group.yaml @@ -57,3 +57,17 @@ spec: displayName: Guest User email: guest@example.com memberOf: [team-a] +--- +# This user is added as an example, to make it more easy for the "Guest" +# sign-in option to demonstrate some entities being owned. In a regular org, +# a guest user would probably not be registered like this. +apiVersion: backstage.io/v1alpha1 +kind: User +metadata: + name: guest + namespace: development +spec: + profile: + displayName: Guest User + email: guest@example.com + memberOf: [group:default/team-a] diff --git a/packages/create-app/templates/default-app/examples/org.yaml b/packages/create-app/templates/default-app/examples/org.yaml index a10e81fc7f..1c4fb91a1e 100644 --- a/packages/create-app/templates/default-app/examples/org.yaml +++ b/packages/create-app/templates/default-app/examples/org.yaml @@ -7,6 +7,15 @@ metadata: spec: memberOf: [guests] --- +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user +apiVersion: backstage.io/v1alpha1 +kind: User +metadata: + name: guest + namespace: development +spec: + memberOf: [guests] +--- # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group apiVersion: backstage.io/v1alpha1 kind: Group diff --git a/plugins/auth-backend-module-guest-provider/config.d.ts b/plugins/auth-backend-module-guest-provider/config.d.ts new file mode 100644 index 0000000000..d6de29bed6 --- /dev/null +++ b/plugins/auth-backend-module-guest-provider/config.d.ts @@ -0,0 +1,27 @@ +/* + * 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 interface Config { + /** Configuration options for the auth plugin */ + auth?: { + /** + * EXPERIMENTAL value: Allow users to configure what the guest provider logs in as. + * @visibility frontend + * @default user:default/guest + */ + guestEntityRef?: string; + }; +} diff --git a/plugins/auth-backend-module-guest-provider/src/module.ts b/plugins/auth-backend-module-guest-provider/src/module.ts index a52b8a4ac2..75eb4f849c 100644 --- a/plugins/auth-backend-module-guest-provider/src/module.ts +++ b/plugins/auth-backend-module-guest-provider/src/module.ts @@ -33,18 +33,21 @@ export const authModuleGuestProvider = createBackendModule({ deps: { logger: coreServices.logger, providers: authProvidersExtensionPoint, + config: coreServices.rootConfig, }, - async init({ providers }) { + async init({ providers, logger, config }) { if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Guest provider does not support authenticating production workloads.', + logger.warn( + 'You should NOT be using the guest provider outside of a development environment.', ); } providers.registerProvider({ providerId: 'guest', factory: createProxyAuthProviderFactory({ authenticator: guestAuthenticator, - signInResolver: signInAsGuestUser, + signInResolver: signInAsGuestUser( + config.getOptionalString('auth.guestEntityRef'), + ), }), }); }, diff --git a/plugins/auth-backend-module-guest-provider/src/resolvers.ts b/plugins/auth-backend-module-guest-provider/src/resolvers.ts index 05acf676ec..b2c2f8cf7d 100644 --- a/plugins/auth-backend-module-guest-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-guest-provider/src/resolvers.ts @@ -19,24 +19,28 @@ import { SignInResolver } from '@backstage/plugin-auth-node'; /** * Provide a default implementation of the user to resolve to. By default, this - * is `user:default/guest`. We will attempt to get that user if they're in the + * is `user:development/guest`. We will attempt to get that user if they're in the * catalog. If that user doesn't exist in the catalog, we will still create a * token for them so they can keep viewing. */ -export const signInAsGuestUser: SignInResolver<{}> = async (_, ctx) => { - const userRef = stringifyEntityRef({ - kind: 'user', - name: 'guest', - }); - try { - return ctx.signInWithCatalogUser({ entityRef: userRef }); - } catch (err) { - // We can't guarantee that a guest user exists in the catalog, so we issue a token directly, - return ctx.issueToken({ - claims: { - sub: userRef, - ent: [userRef], - }, - }); - } -}; +export const signInAsGuestUser: (entityRef?: string) => SignInResolver<{}> = + (entityRef?: string) => async (_, ctx) => { + const userRef = + entityRef ?? + stringifyEntityRef({ + kind: 'user', + namespace: 'development', + name: 'guest', + }); + try { + return ctx.signInWithCatalogUser({ entityRef: userRef }); + } catch (err) { + // We can't guarantee that a guest user exists in the catalog, so we issue a token directly, + return ctx.issueToken({ + claims: { + sub: userRef, + ent: [userRef], + }, + }); + } + }; From 24cc1a472a8107e893f98f90c9577e780f333c47 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sun, 18 Feb 2024 00:20:19 -0500 Subject: [PATCH 22/26] update guest provider to support both old and new guest sessions Signed-off-by: aramissennyeydd --- .../src/layout/SignInPage/guestProvider.tsx | 81 ++++++++++++++----- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/packages/core-components/src/layout/SignInPage/guestProvider.tsx b/packages/core-components/src/layout/SignInPage/guestProvider.tsx index 018feb2241..a2ec2c1078 100644 --- a/packages/core-components/src/layout/SignInPage/guestProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/guestProvider.tsx @@ -22,28 +22,61 @@ import { GridItem } from './styles'; import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; import { ProxiedSignInIdentity } from '../ProxiedSignInPage/ProxiedSignInIdentity'; import { discoveryApiRef, useApi } from '@backstage/core-plugin-api'; +import { GuestUserIdentity } from './GuestUserIdentity'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { ResponseError } from '@backstage/errors'; + +const getIdentity = async (identity: ProxiedSignInIdentity) => { + try { + const identityResponse = await identity.getBackstageIdentity(); + return identityResponse; + } catch (error) { + if ( + error instanceof ResponseError && + error.cause.name === 'NotFoundError' + ) { + return undefined; + } + throw error; + } +}; const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { const discoveryApi = useApi(discoveryApiRef); + const [_, setUseLegacyGuestToken] = useLocalStorage('enableLegacyGuestToken'); + + const handle = async () => { + onSignInStarted(); + + const identity = new ProxiedSignInIdentity({ + provider: 'guest', + discoveryApi, + }); + + const identityResponse = await getIdentity(identity); + + if (!identityResponse) { + // eslint-disable-next-line no-alert + const useLegacyGuestTokenResponse = confirm( + 'Failed to sign in as a guest using the auth backend. Do you want to fallback to the legacy guest token?', + ); + if (useLegacyGuestTokenResponse) { + setUseLegacyGuestToken(true); + onSignInSuccess(new GuestUserIdentity()); + return; + } + } + + onSignInSuccess(identity); + }; + return ( { - onSignInStarted(); - onSignInSuccess( - new ProxiedSignInIdentity({ - provider: 'guest', - discoveryApi, - }), - ); - }} - > + } @@ -55,17 +88,29 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { }; const loader: ProviderLoader = async apis => { + const useLegacyGuestToken = + localStorage.getItem('enableLegacyGuestToken') === 'true'; + const identity = new ProxiedSignInIdentity({ provider: 'guest', discoveryApi: apis.get(discoveryApiRef)!, }); + const identityResponse = await getIdentity(identity); - await identity.start(); - - const identityResponse = await identity.getBackstageIdentity(); - - if (!identityResponse) { + if (!identityResponse && !useLegacyGuestToken) { return undefined; + } else if (identityResponse && useLegacyGuestToken) { + // eslint-disable-next-line no-alert + const switchToNewGuestToken = confirm( + 'You are currently using the legacy guest token, but you have the new guest backend module installed. Do you want to use the new module?', + ); + if (switchToNewGuestToken) { + localStorage.removeItem('enableLegacyGuestToken'); + } else { + return new GuestUserIdentity(); + } + } else if (useLegacyGuestToken) { + return new GuestUserIdentity(); } return identity; From 6f2fbff528867aae12540dfc9abd949bb9ed0db1 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sun, 18 Feb 2024 00:35:37 -0500 Subject: [PATCH 23/26] add signin failure error Signed-off-by: aramissennyeydd --- .../src/layout/SignInPage/guestProvider.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core-components/src/layout/SignInPage/guestProvider.tsx b/packages/core-components/src/layout/SignInPage/guestProvider.tsx index a2ec2c1078..adb5f4b027 100644 --- a/packages/core-components/src/layout/SignInPage/guestProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/guestProvider.tsx @@ -41,7 +41,11 @@ const getIdentity = async (identity: ProxiedSignInIdentity) => { } }; -const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { +const Component: ProviderComponent = ({ + onSignInStarted, + onSignInSuccess, + onSignInFailure, +}) => { const discoveryApi = useApi(discoveryApiRef); const [_, setUseLegacyGuestToken] = useLocalStorage('enableLegacyGuestToken'); @@ -65,6 +69,10 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { onSignInSuccess(new GuestUserIdentity()); return; } + onSignInFailure(); + throw new Error( + `You cannot sign in as a guest, you must either enable the legacy guest token or configure the auth backend to support guest sign in.`, + ); } onSignInSuccess(identity); From 4b277033714facd9144c9ea93ed78b0a3114f812 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:59:01 -0500 Subject: [PATCH 24/26] Apply suggestions from code review Co-authored-by: Patrik Oldsberg Signed-off-by: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> --- .changeset/cold-boats-sell.md | 2 +- .changeset/selfish-glasses-cheer.md | 2 +- .../core-components/src/layout/SignInPage/guestProvider.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/cold-boats-sell.md b/.changeset/cold-boats-sell.md index e47517140d..af0dd1e295 100644 --- a/.changeset/cold-boats-sell.md +++ b/.changeset/cold-boats-sell.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-auth-backend-module-guest-provider': patch +'@backstage/plugin-auth-backend-module-guest-provider': minor --- Adds a new guest provider that maps guest users to actual tokens. This also shifts the default guest login to `user:development/guest` to reduce overlap with your production/real data. To change that (or set it back to the old default, use the new `auth.guestEntityRef` config key) like so, diff --git a/.changeset/selfish-glasses-cheer.md b/.changeset/selfish-glasses-cheer.md index 4d880f0523..be267eb0f3 100644 --- a/.changeset/selfish-glasses-cheer.md +++ b/.changeset/selfish-glasses-cheer.md @@ -1,5 +1,5 @@ --- -'@backstage/core-components': minor +'@backstage/core-components': patch --- `SignInPage`'s `'guest'` provider now supports the `@backstage/plugin-auth-backend-module-guest-provider` package to generate tokens. It will continue to use the old frontend-only auth as a fallback. diff --git a/packages/core-components/src/layout/SignInPage/guestProvider.tsx b/packages/core-components/src/layout/SignInPage/guestProvider.tsx index adb5f4b027..563d1cc124 100644 --- a/packages/core-components/src/layout/SignInPage/guestProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/guestProvider.tsx @@ -32,8 +32,8 @@ const getIdentity = async (identity: ProxiedSignInIdentity) => { return identityResponse; } catch (error) { if ( - error instanceof ResponseError && - error.cause.name === 'NotFoundError' + error.name === 'ResponseError' && + (error as ResponseError).cause.name === 'NotFoundError' ) { return undefined; } From 46138c2bd73eaa477f23954a0e79abb817232bb2 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 26 Feb 2024 15:21:35 -0500 Subject: [PATCH 25/26] update to `auth.provider.guest.*` login Signed-off-by: aramissennyeydd --- .changeset/cold-boats-sell.md | 17 ++++++++++-- docs/auth/guest/provider.md | 5 ++-- packages/backend-next/src/index.ts | 4 +-- .../templates/default-app/examples/org.yaml | 9 ------- .../config.d.ts | 27 ++++++++++++++----- .../src/module.ts | 2 +- .../src/resolvers.ts | 21 ++++++++++++--- 7 files changed, 57 insertions(+), 28 deletions(-) diff --git a/.changeset/cold-boats-sell.md b/.changeset/cold-boats-sell.md index af0dd1e295..5b0eba7301 100644 --- a/.changeset/cold-boats-sell.md +++ b/.changeset/cold-boats-sell.md @@ -2,9 +2,22 @@ '@backstage/plugin-auth-backend-module-guest-provider': minor --- -Adds a new guest provider that maps guest users to actual tokens. This also shifts the default guest login to `user:development/guest` to reduce overlap with your production/real data. To change that (or set it back to the old default, use the new `auth.guestEntityRef` config key) like so, +Adds a new guest provider that maps guest users to actual tokens. This also shifts the default guest login to `user:development/guest` to reduce overlap with your production/real data. To change that (or set it back to the old default, use the new `auth.providers.guest.userEntityRef` config key) like so, ```yaml title=app-config.yaml auth: - guestEntityRef: user:default/guest + providers: + guest: + userEntityRef: user:default/guest +``` + +This also adds a new property to control the ownership entity refs, + +```yaml title=app-config.yaml +auth: + providers: + guest: + ownershipEntityRefs: + - guests + - development/custom ``` diff --git a/docs/auth/guest/provider.md b/docs/auth/guest/provider.md index ca5bee1d09..c730877a47 100644 --- a/docs/auth/guest/provider.md +++ b/docs/auth/guest/provider.md @@ -59,9 +59,8 @@ Similar to the other authentication providers, you have to enable the provider i auth: providers: + guest: -+ development: - // new optional property to override the default value. -+ loginAs: user:default/guest ++ userEntityRef: user:default/guest ++ development: {} ``` We need to specify that the provider is enabled for the given environment, and as there are no config values for this provider yet, you can just specify an empty object. diff --git a/packages/backend-next/src/index.ts b/packages/backend-next/src/index.ts index 58fa994658..403b122ddf 100644 --- a/packages/backend-next/src/index.ts +++ b/packages/backend-next/src/index.ts @@ -20,6 +20,7 @@ const backend = createBackend(); backend.add(import('@backstage/plugin-auth-backend')); backend.add(import('./authModuleGithubProvider')); +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); backend.add(import('@backstage/plugin-adr-backend')); backend.add(import('@backstage/plugin-app-backend/alpha')); @@ -57,7 +58,4 @@ backend.add(import('@backstage/plugin-sonarqube-backend')); backend.add(import('@backstage/plugin-signals-backend')); backend.add(import('@backstage/plugin-notifications-backend')); -backend.add(import('@backstage/plugin-auth-backend')); -backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); - backend.start(); diff --git a/packages/create-app/templates/default-app/examples/org.yaml b/packages/create-app/templates/default-app/examples/org.yaml index 1c4fb91a1e..a10e81fc7f 100644 --- a/packages/create-app/templates/default-app/examples/org.yaml +++ b/packages/create-app/templates/default-app/examples/org.yaml @@ -7,15 +7,6 @@ metadata: spec: memberOf: [guests] --- -# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user -apiVersion: backstage.io/v1alpha1 -kind: User -metadata: - name: guest - namespace: development -spec: - memberOf: [guests] ---- # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group apiVersion: backstage.io/v1alpha1 kind: Group diff --git a/plugins/auth-backend-module-guest-provider/config.d.ts b/plugins/auth-backend-module-guest-provider/config.d.ts index d6de29bed6..eb60492393 100644 --- a/plugins/auth-backend-module-guest-provider/config.d.ts +++ b/plugins/auth-backend-module-guest-provider/config.d.ts @@ -17,11 +17,26 @@ export interface Config { /** Configuration options for the auth plugin */ auth?: { - /** - * EXPERIMENTAL value: Allow users to configure what the guest provider logs in as. - * @visibility frontend - * @default user:default/guest - */ - guestEntityRef?: string; + providers: { + guest?: { + /** + * The entity reference to use for the guest user. + * @default user:development/guest + */ + userEntityRef?: string; + + /** + * A list of entity references to user for ownership of the guest user if the user + * is not found in the catalog. + * @default [userEntityRef] + */ + ownershipEntityRefs?: string[]; + + /** + * Allow users to sign in with the guest provider outside of their development environments. + */ + dangerouslyAllowOutsideDevelopment?: boolean; + }; + }; }; } diff --git a/plugins/auth-backend-module-guest-provider/src/module.ts b/plugins/auth-backend-module-guest-provider/src/module.ts index 75eb4f849c..7eac99b0a3 100644 --- a/plugins/auth-backend-module-guest-provider/src/module.ts +++ b/plugins/auth-backend-module-guest-provider/src/module.ts @@ -46,7 +46,7 @@ export const authModuleGuestProvider = createBackendModule({ factory: createProxyAuthProviderFactory({ authenticator: guestAuthenticator, signInResolver: signInAsGuestUser( - config.getOptionalString('auth.guestEntityRef'), + config.getConfig('auth.providers.guest'), ), }), }); diff --git a/plugins/auth-backend-module-guest-provider/src/resolvers.ts b/plugins/auth-backend-module-guest-provider/src/resolvers.ts index b2c2f8cf7d..bec2ffe12c 100644 --- a/plugins/auth-backend-module-guest-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-guest-provider/src/resolvers.ts @@ -15,7 +15,9 @@ */ import { stringifyEntityRef } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; import { SignInResolver } from '@backstage/plugin-auth-node'; +import { NotImplementedError } from '@backstage/errors'; /** * Provide a default implementation of the user to resolve to. By default, this @@ -23,15 +25,26 @@ import { SignInResolver } from '@backstage/plugin-auth-node'; * catalog. If that user doesn't exist in the catalog, we will still create a * token for them so they can keep viewing. */ -export const signInAsGuestUser: (entityRef?: string) => SignInResolver<{}> = - (entityRef?: string) => async (_, ctx) => { +export const signInAsGuestUser: (config: Config) => SignInResolver<{}> = + (config: Config) => async (_, ctx) => { + if ( + process.env.NODE_ENV !== 'development' && + config.getOptionalBoolean('dangerouslyAllowOutsideDevelopment') !== true + ) { + throw new NotImplementedError( + 'The guest provider is NOT recommended for use outside of a development environment. If you want to enable this, set `auth.providers.guest.dangerouslyAllowOutsideDevelopment: true` in your app config.', + ); + } const userRef = - entityRef ?? + config.getOptionalString('userEntityRef') ?? stringifyEntityRef({ kind: 'user', namespace: 'development', name: 'guest', }); + const ownershipRefs = config.getOptionalStringArray( + 'ownershipEntityRefs', + ) ?? [userRef]; try { return ctx.signInWithCatalogUser({ entityRef: userRef }); } catch (err) { @@ -39,7 +52,7 @@ export const signInAsGuestUser: (entityRef?: string) => SignInResolver<{}> = return ctx.issueToken({ claims: { sub: userRef, - ent: [userRef], + ent: ownershipRefs, }, }); } From 5d1046dd206e1f264120a2ff28ef5acb89e8c3c3 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 26 Feb 2024 15:22:38 -0500 Subject: [PATCH 26/26] add config to guest provider Signed-off-by: aramissennyeydd --- plugins/auth-backend-module-guest-provider/package.json | 1 + plugins/auth-backend-module-guest-provider/src/resolvers.ts | 2 +- yarn.lock | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index edf3210759..bab85c2bd0 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -38,6 +38,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", + "@backstage/config": "workspace:^", "express": "^4.18.2" }, "files": [ diff --git a/plugins/auth-backend-module-guest-provider/src/resolvers.ts b/plugins/auth-backend-module-guest-provider/src/resolvers.ts index bec2ffe12c..35f724f746 100644 --- a/plugins/auth-backend-module-guest-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-guest-provider/src/resolvers.ts @@ -15,7 +15,7 @@ */ import { stringifyEntityRef } from '@backstage/catalog-model'; -import { Config } from '@backstage/config'; +import type { Config } from '@backstage/config'; import { SignInResolver } from '@backstage/plugin-auth-node'; import { NotImplementedError } from '@backstage/errors'; diff --git a/yarn.lock b/yarn.lock index 1bd1a644bb..3a1d1a2d65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4691,6 +4691,7 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" express: ^4.18.2