diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts index 0b79425e36..fc5abeb43c 100644 --- a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts @@ -14,13 +14,14 @@ * limitations under the License. */ +import { Handler } from 'express'; +import PromiseRouter from 'express-promise-router'; import { coreServices, createServiceFactory, HttpRouterServiceAuthPolicy, } from '@backstage/backend-plugin-api'; -import { Handler } from 'express'; -import PromiseRouter from 'express-promise-router'; +import { createCookieAuthRefreshMiddleware } from '@backstage/plugin-auth-node'; import { createLifecycleMiddleware } from './createLifecycleMiddleware'; import { createCredentialsBarrier } from './createCredentialsBarrier'; import { createAuthIntegrationRouter } from './createAuthIntegrationRouter'; @@ -85,12 +86,8 @@ export const httpRouterServiceFactory = createServiceFactory( addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void { credentialsBarrier.addAuthPolicy(policy); if (policy.allow === 'user-cookie') { - // Endpoint that sets the cookie for the user - // TODO: Extract this to a separate createCookieAuthRefreshMiddleware function - router.get('/.backstage/v1-cookie', async (_, res) => { - const { expiresAt } = await httpAuth.issueUserCookie(res); - res.json({ expiresAt: expiresAt.toISOString() }); - }); + // TODO: Make sure this is only added once + router.use(createCookieAuthRefreshMiddleware({ httpAuth })); } }, }; diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 3dc38bbcff..ec66d24d88 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -51,6 +51,7 @@ "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-app-node": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "express": "^4.17.1", diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index 6bbfd2ad3c..e46a80c27d 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -40,6 +40,7 @@ import { import { ConfigSchema } from '@backstage/config-loader'; import { AuthService, HttpAuthService } from '@backstage/backend-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import { createCookieAuthRefreshMiddleware } from '@backstage/plugin-auth-node'; // express uses mime v1 while we only have types for mime v2 type Mime = { lookup(arg0: string): string }; @@ -184,8 +185,7 @@ export async function createRouter( if (enablePublicEntryPoint && auth && httpAuth) { const publicRouter = Router(); - // TODO - // publicRouter.use(createCookieAuthRouter({ httpAuth })) + publicRouter.use(createCookieAuthRefreshMiddleware({ httpAuth })); publicRouter.use(async (req, _res, next) => { const credentials = await httpAuth.credentials(req, { diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index e79facc7f9..4cb9db70f3 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -10,6 +10,7 @@ import { Entity } from '@backstage/catalog-model'; import { EntityFilterQuery } from '@backstage/catalog-client'; import express from 'express'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { HttpAuthService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -17,6 +18,7 @@ import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Profile } from 'passport'; import { Request as Request_2 } from 'express'; import { Response as Response_2 } from 'express'; +import { Router } from 'express-serve-static-core'; import { Strategy } from 'passport'; import { ZodSchema } from 'zod'; import { ZodTypeDef } from 'zod'; @@ -148,6 +150,11 @@ export type CookieConfigurer = (ctx: { sameSite?: 'none' | 'lax' | 'strict'; }; +// @public +export function createCookieAuthRefreshMiddleware(options: { + httpAuth: HttpAuthService; +}): Router; + // @public (undocumented) export function createOAuthAuthenticator( authenticator: OAuthAuthenticator, diff --git a/plugins/auth-node/src/identity/createCookieAuthRefreshMiddleware.ts b/plugins/auth-node/src/identity/createCookieAuthRefreshMiddleware.ts new file mode 100644 index 0000000000..85ff5b86d7 --- /dev/null +++ b/plugins/auth-node/src/identity/createCookieAuthRefreshMiddleware.ts @@ -0,0 +1,37 @@ +/* + * 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 { HttpAuthService } from '@backstage/backend-plugin-api'; +import { Router } from 'express'; + +/** + * @public + * Creates a middleware that can be used to refresh the cookie for the user. + */ +export function createCookieAuthRefreshMiddleware(options: { + httpAuth: HttpAuthService; +}) { + const { httpAuth } = options; + const router = Router(); + + // Endpoint that sets the cookie for the user + router.get('/.backstage/v1-cookie', async (_, res) => { + const { expiresAt } = await httpAuth.issueUserCookie(res); + res.json({ expiresAt: expiresAt.toISOString() }); + }); + + return router; +} diff --git a/plugins/auth-node/src/identity/index.ts b/plugins/auth-node/src/identity/index.ts index 24e2dcb691..a81f691698 100644 --- a/plugins/auth-node/src/identity/index.ts +++ b/plugins/auth-node/src/identity/index.ts @@ -22,3 +22,4 @@ export { } from './DefaultIdentityClient'; export { IdentityClient } from './IdentityClient'; export type { IdentityApi, IdentityApiGetIdentityRequest } from './IdentityApi'; +export { createCookieAuthRefreshMiddleware } from './createCookieAuthRefreshMiddleware'; diff --git a/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.test.tsx b/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.test.tsx index 786136261c..ab88361773 100644 --- a/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.test.tsx +++ b/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.test.tsx @@ -119,7 +119,7 @@ describe('CookieAuthRefreshProvider', () => { await waitFor(() => expect(fetchApiMock.fetch).toHaveBeenCalledWith( - 'http://localhost:7000/techdocs/api/cookie', + 'http://localhost:7000/techdocs/api/.backstage/v1-cookie', { credentials: 'include' }, ), ); diff --git a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx index 180de98a46..aeff0be3b0 100644 --- a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx +++ b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx @@ -236,7 +236,7 @@ describe('useCookieAuthRefresh', () => { await waitFor(() => expect(fetchApiMock.fetch).toHaveBeenCalledWith( - 'http://localhost:7000/techdocs/api/cookie', + 'http://localhost:7000/techdocs/api/.backstage/v1-cookie', { credentials: 'include' }, ), ); diff --git a/yarn.lock b/yarn.lock index 606e10254f..9c7429a3c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4693,6 +4693,7 @@ __metadata: "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-app-node": "workspace:^" + "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" "@types/express": ^4.17.6 "@types/supertest": ^2.0.8