diff --git a/packages/backend-common/src/tokens/ServerTokenManager.test.ts b/packages/backend-common/src/tokens/ServerTokenManager.test.ts index 925c770a0d..fe724a0a4b 100644 --- a/packages/backend-common/src/tokens/ServerTokenManager.test.ts +++ b/packages/backend-common/src/tokens/ServerTokenManager.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { ConfigReader } from '@backstage/config'; +import { TokenManager } from './types'; import { ServerTokenManager } from './ServerTokenManager'; const emptyConfig = new ConfigReader({}); @@ -122,8 +123,40 @@ describe('ServerTokenManager', () => { }); }); + describe('ServerTokenManager.fromConfig', () => { + it('should throw if backend auth configuration is missing', () => { + expect(() => + ServerTokenManager.fromConfig(new ConfigReader({})), + ).toThrow(); + }); + + it('should throw if no keys are included in the configuration', () => { + expect(() => + ServerTokenManager.fromConfig( + new ConfigReader({ + backend: { auth: { keys: [] } }, + }), + ), + ).toThrow(); + }); + + it('should throw if any key is missing a secret property', () => { + expect(() => + ServerTokenManager.fromConfig( + new ConfigReader({ + backend: { + auth: { + keys: [{ secret: '123' }, {}, { secret: '789' }], + }, + }, + }), + ), + ).toThrow(); + }); + }); + describe('ServerTokenManager.noop', () => { - let noopTokenManager: ServerTokenManager; + let noopTokenManager: TokenManager; beforeEach(() => { noopTokenManager = ServerTokenManager.noop(); @@ -146,11 +179,11 @@ describe('ServerTokenManager', () => { ).resolves.not.toThrow(); }); - it('should not accept signed tokens', async () => { + it('should accept signed tokens', async () => { const tokenManager = ServerTokenManager.fromConfig(configWithSecret); await expect( noopTokenManager.authenticate((await tokenManager.getToken()).token), - ).rejects.toThrowError(/invalid server token/i); + ).resolves.not.toThrow(); }); }); }); diff --git a/packages/backend-common/src/tokens/ServerTokenManager.ts b/packages/backend-common/src/tokens/ServerTokenManager.ts index 7796b675b8..edd781671b 100644 --- a/packages/backend-common/src/tokens/ServerTokenManager.ts +++ b/packages/backend-common/src/tokens/ServerTokenManager.ts @@ -19,6 +19,14 @@ import { Config } from '@backstage/config'; import { AuthenticationError } from '@backstage/errors'; import { TokenManager } from './types'; +class NoopTokenManager implements TokenManager { + async getToken() { + return { token: '' }; + } + + async authenticate() {} +} + /** * Creates and validates tokens for use during backend-to-backend * authentication. @@ -26,12 +34,11 @@ import { TokenManager } from './types'; * @public */ export class ServerTokenManager implements TokenManager { - private readonly verificationKeys: JWKS.KeyStore | JWK.NoneKey; - private readonly signingKey: JWK.Key | JWK.NoneKey; - private readonly signingAlgorithm: string | undefined; + private readonly verificationKeys: JWKS.KeyStore; + private readonly signingKey: JWK.Key; static noop() { - return new ServerTokenManager(); + return new NoopTokenManager(); } static fromConfig(config: Config) { @@ -43,20 +50,21 @@ export class ServerTokenManager implements TokenManager { } private constructor(secrets?: string[]) { - if (secrets?.length) { - this.verificationKeys = new JWKS.KeyStore( - secrets.map(k => JWK.asKey({ kty: 'oct', k })), + if (!secrets?.length) { + throw new Error( + 'No secrets provided when constructing ServerTokenManager', ); - this.signingKey = this.verificationKeys.all()[0]; - this.signingAlgorithm = 'HS256'; - } else { - this.verificationKeys = this.signingKey = JWK.None; } + + this.verificationKeys = new JWKS.KeyStore( + secrets.map(k => JWK.asKey({ kty: 'oct', k })), + ); + this.signingKey = this.verificationKeys.all()[0]; } async getToken(): Promise<{ token: string }> { const jwt = JWT.sign({ sub: 'backstage-server' }, this.signingKey, { - algorithm: this.signingAlgorithm, + algorithm: 'HS256', }); return { token: jwt };