diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index b15b0da628..280d92a261 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -35,6 +35,12 @@ export interface Config { keyStore?: { provider?: 'postgres' | 'memory' | 'firestore'; firestore?: { + /** The host to connect to */ + host?: string; + /** The port to connect to */ + port?: number; + /** Whether to use SSL when connecting. */ + ssl?: boolean; /** The Google Cloud Project ID */ projectId?: string; /** diff --git a/plugins/auth-backend/src/identity/FirestoreKeyStore.test.ts b/plugins/auth-backend/src/identity/FirestoreKeyStore.test.ts index a12e2ed40e..398e3c2b34 100644 --- a/plugins/auth-backend/src/identity/FirestoreKeyStore.test.ts +++ b/plugins/auth-backend/src/identity/FirestoreKeyStore.test.ts @@ -16,7 +16,10 @@ import { Firestore } from '@google-cloud/firestore'; -import { FirestoreKeyStore } from './FirestoreKeyStore'; +import { + FirestoreKeyStore, + FirestoreKeyStoreSettings, +} from './FirestoreKeyStore'; import { AnyJWK } from './types'; const data = jest.fn().mockReturnValue('data'); @@ -37,7 +40,6 @@ jest.mock('@google-cloud/firestore', () => ({ })); describe('FirestoreKeyStore', () => { - const OLD_ENV = process.env; const key = { kid: '123', use: 'sig', @@ -45,72 +47,45 @@ describe('FirestoreKeyStore', () => { alg: 'Base64', } as AnyJWK; + const settings = { + projectId: 'my-project', + host: 'my-host', + port: 8080, + ssl: false, + keyFilename: 'cred.json', + }; + const path = 'my-path'; + const firestoreSettings = { ...settings, path } as FirestoreKeyStoreSettings; + beforeEach(() => { jest.clearAllMocks(); }); - afterAll(() => { - process.env = OLD_ENV; - }); - it('can create an instance without settings', async () => { const keyStore = await FirestoreKeyStore.create(); expect(keyStore).toBeInstanceOf(FirestoreKeyStore); + expect(Firestore).toHaveBeenCalledWith({}); }); - it('can set the project id', async () => { - await FirestoreKeyStore.create({ projectId: 'my-project' }); + it('can create an instance with settings', async () => { + await FirestoreKeyStore.create(firestoreSettings); - expect(Firestore).toHaveBeenCalledWith( - expect.objectContaining({ - projectId: 'my-project', - }), - ); + expect(Firestore).toHaveBeenCalledWith(settings); }); - it('can handle keyfile file', async () => { - await FirestoreKeyStore.create({ keyFilename: 'keyFile.json' }); - - expect(Firestore).toHaveBeenCalledWith( - expect.objectContaining({ - keyFilename: 'keyFile.json', - }), - ); - }); - - it('can use default google credentials', async () => { - process.env.GOOGLE_APPLICATION_CREDENTIALS = 'cred.json'; - await FirestoreKeyStore.create(); - - expect(Firestore).toHaveBeenCalledWith( - expect.objectContaining({ - keyFilename: 'cred.json', - }), - ); - }); - - it('can uses a default path', async () => { + it('can use a default path', async () => { const keyStore = await FirestoreKeyStore.create(); await keyStore.addKey(key); expect(firestoreMock.collection).toBeCalledWith('sessions'); }); - it('can set the path', async () => { - const keyStore = await FirestoreKeyStore.create({ - path: 'my-path', - }); - await keyStore.addKey(key); - - expect(firestoreMock.collection).toBeCalledWith('my-path'); - }); - it('can add keys', async () => { - const keyStore = await FirestoreKeyStore.create(); + const keyStore = await FirestoreKeyStore.create(firestoreSettings); await keyStore.addKey(key); - expect(firestoreMock.collection).toBeCalledWith('sessions'); + expect(firestoreMock.collection).toBeCalledWith(path); expect(firestoreMock.doc).toBeCalledWith(key.kid); expect(firestoreMock.set).toHaveBeenCalledWith({ kid: key.kid, @@ -119,29 +94,29 @@ describe('FirestoreKeyStore', () => { }); it('can delete a single key', async () => { - const keyStore = await FirestoreKeyStore.create(); + const keyStore = await FirestoreKeyStore.create(firestoreSettings); await keyStore.removeKeys(['123']); - expect(firestoreMock.collection).toBeCalledWith('sessions'); + expect(firestoreMock.collection).toBeCalledWith(path); expect(firestoreMock.doc).toBeCalledWith('123'); expect(firestoreMock.delete).toBeCalledTimes(1); }); it('can delete a multiple keys', async () => { - const keyStore = await FirestoreKeyStore.create(); + const keyStore = await FirestoreKeyStore.create(firestoreSettings); await keyStore.removeKeys(['123', '456']); - expect(firestoreMock.collection).toBeCalledWith('sessions'); + expect(firestoreMock.collection).toBeCalledWith(path); expect(firestoreMock.doc).toBeCalledWith('123'); expect(firestoreMock.doc).toBeCalledWith('456'); expect(firestoreMock.delete).toBeCalledTimes(2); }); it('can list keys', async () => { - const keyStore = await FirestoreKeyStore.create(); + const keyStore = await FirestoreKeyStore.create(firestoreSettings); const items = await keyStore.listKeys(); - expect(firestoreMock.collection).toBeCalledWith('sessions'); + expect(firestoreMock.collection).toBeCalledWith(path); expect(firestoreMock.get).toBeCalledTimes(1); expect(data).toBeCalledTimes(1); expect(toDate).toBeCalledTimes(1); diff --git a/plugins/auth-backend/src/identity/FirestoreKeyStore.ts b/plugins/auth-backend/src/identity/FirestoreKeyStore.ts index 74122f2849..f606bacd38 100644 --- a/plugins/auth-backend/src/identity/FirestoreKeyStore.ts +++ b/plugins/auth-backend/src/identity/FirestoreKeyStore.ts @@ -18,19 +18,16 @@ import { Firestore, Settings } from '@google-cloud/firestore'; import { AnyJWK, KeyStore, StoredKey } from './types'; -type FirestoreSettings = Settings & { +export type FirestoreKeyStoreSettings = Settings & { path?: string; }; export class FirestoreKeyStore implements KeyStore { static async create( - settings?: FirestoreSettings, + settings?: FirestoreKeyStoreSettings, ): Promise { - const { projectId, keyFilename, path } = settings ?? {}; - const database = new Firestore({ - projectId, - keyFilename: keyFilename ?? process.env.GOOGLE_APPLICATION_CREDENTIALS, - }); + const { path, ...firestoreSettings } = settings ?? {}; + const database = new Firestore(firestoreSettings); return new FirestoreKeyStore(database, path ?? 'sessions'); } diff --git a/plugins/auth-backend/src/identity/KeyStores.ts b/plugins/auth-backend/src/identity/KeyStores.ts index ec236e2074..1b19bde911 100644 --- a/plugins/auth-backend/src/identity/KeyStores.ts +++ b/plugins/auth-backend/src/identity/KeyStores.ts @@ -21,7 +21,10 @@ import { Config } from '@backstage/config'; import { DatabaseKeyStore } from './DatabaseKeyStore'; import { MemoryKeyStore } from './MemoryKeyStore'; -import { FirestoreKeyStore } from './FirestoreKeyStore'; +import { + FirestoreKeyStore, + FirestoreKeyStoreSettings, +} from './FirestoreKeyStore'; import { KeyStore } from './types'; type Options = { @@ -44,7 +47,6 @@ export class KeyStores { const ks = config.getOptionalConfig('auth.keyStore'); const provider = ks?.getOptionalString('provider') ?? 'postgres'; - const providerConfig = ks?.getOptionalConfig(provider); logger?.info(`Configuring "${provider}" as KeyStore provider`); @@ -63,11 +65,9 @@ export class KeyStores { } if (provider === 'firestore') { - return await FirestoreKeyStore.create({ - projectId: providerConfig?.getOptionalString('projectId'), - keyFilename: providerConfig?.getOptionalString('keyFilename'), - path: providerConfig?.getOptionalString('path'), - }); + const settings = ks?.getOptional(provider) as FirestoreKeyStoreSettings; + + return await FirestoreKeyStore.create(settings); } throw new Error(`Unknown KeyStore provider: ${provider}`);