diff --git a/.changeset/spotty-kids-pay.md b/.changeset/spotty-kids-pay.md new file mode 100644 index 0000000000..199d0fdff0 --- /dev/null +++ b/.changeset/spotty-kids-pay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Ability for user to configure backstage token expiration diff --git a/plugins/auth-backend/README.md b/plugins/auth-backend/README.md index ce7525fb6b..e9dca26a5b 100644 --- a/plugins/auth-backend/README.md +++ b/plugins/auth-backend/README.md @@ -165,3 +165,12 @@ To try out SAML, you can use the mock identity provider: ## Links - [The Backstage homepage](https://backstage.io) + +## Configuring Token Expiration in App Config + +The expiration feature is not enabled unless you set this in your config file: + +``` +auth: + backstageTokenExpiration: { minutes: } +``` diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index 34139593d3..d24b2c64b7 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { HumanDuration } from '@backstage/types'; + export interface Config { /** Configuration options for the auth plugin */ auth?: { @@ -212,6 +214,10 @@ export interface Config { cfaccess?: { teamName: string; }; + /** + * The backstage token expiration. + */ + backstageTokenExpiration?: HumanDuration; }; }; } diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index f05551736a..07d80ddabe 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -48,6 +48,7 @@ "@backstage/plugin-auth-backend-module-okta-provider": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", + "@backstage/types": "workspace:^", "@google-cloud/firestore": "^7.0.0", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", diff --git a/plugins/auth-backend/src/service/router.test.ts b/plugins/auth-backend/src/service/router.test.ts index 3a1fde881e..fd339e349e 100644 --- a/plugins/auth-backend/src/service/router.test.ts +++ b/plugins/auth-backend/src/service/router.test.ts @@ -15,7 +15,11 @@ */ import { ConfigReader } from '@backstage/config'; -import { createOriginFilter } from './router'; +import { + createOriginFilter, + getDefaultBackstageTokenExpiryTime, +} from './router'; +import { BACKSTAGE_SESSION_EXPIRATION } from '../lib/session'; describe('Auth origin filtering', () => { const config = new ConfigReader({ @@ -52,3 +56,28 @@ describe('Auth origin filtering', () => { expect(createOriginFilter(config)(origin)).toBeTruthy(); }); }); + +describe('Test for default backstage token expiry time', () => { + it('Will return default backstage session expiration', () => { + const config = new ConfigReader({ + app: { + baseUrl: 'http://example.com/extra-path', + }, + }); + expect(getDefaultBackstageTokenExpiryTime(config)).toBe( + BACKSTAGE_SESSION_EXPIRATION, + ); + }); + + it('Will return user defined backstage session expiration', () => { + const config = new ConfigReader({ + app: { + baseUrl: 'http://example.com/extra-path', + }, + auth: { + backstageTokenExpiration: { minutes: 120 }, + }, + }); + expect(getDefaultBackstageTokenExpiryTime(config)).toBe(7200); + }); +}); diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 320870b3ee..eea858dd9c 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -29,7 +29,6 @@ import { } from '@backstage/backend-common'; import { assertError, NotFoundError } from '@backstage/errors'; import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; -import { Config } from '@backstage/config'; import { createOidcRouter, TokenFactory, KeyStores } from '../identity'; import session from 'express-session'; import connectSessionKnex from 'connect-session-knex'; @@ -41,6 +40,8 @@ import { BACKSTAGE_SESSION_EXPIRATION } from '../lib/session'; import { TokenIssuer } from '../identity/types'; import { StaticTokenIssuer } from '../identity/StaticTokenIssuer'; import { StaticKeyStore } from '../identity/StaticKeyStore'; +import { Config, readDurationFromConfig } from '@backstage/config'; +import { durationToMilliseconds } from '@backstage/types'; /** @public */ export type ProviderFactories = { [s: string]: AuthProviderFactory }; @@ -76,9 +77,8 @@ export async function createRouter( const appUrl = config.getString('app.baseUrl'); const authUrl = await discovery.getExternalBaseUrl('auth'); - + const backstageTokenExpiration = getDefaultBackstageTokenExpiryTime(config); const authDb = AuthDatabase.create(database); - const sessionExpirationSeconds = BACKSTAGE_SESSION_EXPIRATION; const keyStore = await KeyStores.fromConfig(config, { logger, @@ -91,7 +91,7 @@ export async function createRouter( { logger: logger.child({ component: 'token-factory' }), issuer: authUrl, - sessionExpirationSeconds: sessionExpirationSeconds, + sessionExpirationSeconds: backstageTokenExpiration, }, keyStore as StaticKeyStore, ); @@ -99,7 +99,7 @@ export async function createRouter( tokenIssuer = new TokenFactory({ issuer: authUrl, keyStore, - keyDurationSeconds: sessionExpirationSeconds, + keyDurationSeconds: backstageTokenExpiration, logger: logger.child({ component: 'token-factory' }), algorithm: tokenFactoryAlgorithm ?? @@ -249,3 +249,22 @@ export function createOriginFilter( return allowedOriginPatterns.some(pattern => pattern.match(origin)); }; } + +/** @internal */ +export function getDefaultBackstageTokenExpiryTime(config: Config) { + const processingIntervalKey = 'auth.backstageTokenExpiration'; + + if (!config.has(processingIntervalKey)) { + return BACKSTAGE_SESSION_EXPIRATION; + } + + const duration = readDurationFromConfig(config, { + key: processingIntervalKey, + }); + const seconds = Math.max( + 600, + Math.round(durationToMilliseconds(duration) / 1000), + ); + + return seconds; +} diff --git a/yarn.lock b/yarn.lock index edb50887c1..c0bb552222 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4859,6 +4859,7 @@ __metadata: "@backstage/plugin-auth-backend-module-okta-provider": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" + "@backstage/types": "workspace:^" "@google-cloud/firestore": ^7.0.0 "@types/body-parser": ^1.19.0 "@types/cookie-parser": ^1.4.2