Add support for more settings
Signed-off-by: Marcus Eide <eide@spotify.com>
This commit is contained in:
Vendored
+6
@@ -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;
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<FirestoreKeyStore> {
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user