mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
chore: Move JWK generation to separate class
This commit is contained in:
88
test/unit/identity/configuration/CachedJwkGenerator.test.ts
Normal file
88
test/unit/identity/configuration/CachedJwkGenerator.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { generateKeyPair, importJWK, jwtVerify, SignJWT } from 'jose';
|
||||
import * as jose from 'jose';
|
||||
import { CachedJwkGenerator } from '../../../../src/identity/configuration/CachedJwkGenerator';
|
||||
import type { AlgJwk } from '../../../../src/identity/configuration/JwkGenerator';
|
||||
import type { KeyValueStorage } from '../../../../src/storage/keyvalue/KeyValueStorage';
|
||||
|
||||
describe('A CachedJwkGenerator', (): void => {
|
||||
const alg = 'ES256';
|
||||
const storageKey = 'jwks';
|
||||
let storageMap: Map<string, AlgJwk>;
|
||||
let storage: jest.Mocked<KeyValueStorage<string, AlgJwk>>;
|
||||
let generator: CachedJwkGenerator;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
storageMap = new Map();
|
||||
storage = {
|
||||
get: jest.fn(async(key: string): Promise<AlgJwk | undefined> => storageMap.get(key)),
|
||||
set: jest.fn(async(key: string, value: AlgJwk): Promise<any> => storageMap.set(key, value)),
|
||||
} as any;
|
||||
|
||||
generator = new CachedJwkGenerator(alg, storageKey, storage);
|
||||
});
|
||||
|
||||
it('generates a matching key set.', async(): Promise<void> => {
|
||||
const privateKey = await generator.getPrivateKey();
|
||||
expect(privateKey.alg).toBe(alg);
|
||||
|
||||
const publicKey = await generator.getPublicKey();
|
||||
expect(publicKey.alg).toBe(alg);
|
||||
|
||||
const privateObject = await importJWK(privateKey);
|
||||
const publicObject = await importJWK(publicKey);
|
||||
|
||||
const signed = await new SignJWT({ data: 'signed data' }).setProtectedHeader({ alg }).sign(privateObject);
|
||||
await expect(jwtVerify(signed, publicObject)).resolves.toMatchObject({
|
||||
payload: {
|
||||
data: 'signed data',
|
||||
},
|
||||
});
|
||||
|
||||
const otherKey = (await generateKeyPair(alg)).publicKey;
|
||||
await expect(jwtVerify(signed, otherKey)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('caches the private key in memory.', async(): Promise<void> => {
|
||||
const spy = jest.spyOn(jose, 'generateKeyPair');
|
||||
const privateKey = await generator.getPrivateKey();
|
||||
// 1 call from checking the storage
|
||||
expect(storage.get).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const privateKey2 = await generator.getPrivateKey();
|
||||
expect(privateKey).toBe(privateKey2);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(storage.get).toHaveBeenCalledTimes(1);
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('caches the public key in memory.', async(): Promise<void> => {
|
||||
const spy = jest.spyOn(jose, 'generateKeyPair');
|
||||
const publicKey = await generator.getPublicKey();
|
||||
// 1 call from checking the storage
|
||||
expect(storage.get).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const publicKey2 = await generator.getPublicKey();
|
||||
expect(publicKey).toBe(publicKey2);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(storage.get).toHaveBeenCalledTimes(1);
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('caches the key in storage in case of server restart.', async(): Promise<void> => {
|
||||
const spy = jest.spyOn(jose, 'generateKeyPair');
|
||||
const privateKey = await generator.getPrivateKey();
|
||||
// 1 call from checking the storage
|
||||
expect(storage.get).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const generator2 = new CachedJwkGenerator(alg, storageKey, storage);
|
||||
|
||||
const privateKey2 = await generator2.getPrivateKey();
|
||||
expect(privateKey).toBe(privateKey2);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(storage.get).toHaveBeenCalledTimes(2);
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Readable } from 'stream';
|
||||
import { exportJWK, generateKeyPair } from 'jose';
|
||||
import type * as Koa from 'koa';
|
||||
import type { errors, Configuration, KoaContextWithOIDC } from 'oidc-provider';
|
||||
import type { ErrorHandler } from '../../../../src/http/output/error/ErrorHandler';
|
||||
import type { ResponseWriter } from '../../../../src/http/output/ResponseWriter';
|
||||
import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation';
|
||||
import { IdentityProviderFactory } from '../../../../src/identity/configuration/IdentityProviderFactory';
|
||||
import type { JwkGenerator } from '../../../../src/identity/configuration/JwkGenerator';
|
||||
import type {
|
||||
ClientCredentials,
|
||||
} from '../../../../src/identity/interaction/email-password/credentials/ClientCredentialsAdapterFactory';
|
||||
@@ -45,6 +47,7 @@ describe('An IdentityProviderFactory', (): void => {
|
||||
let interactionHandler: jest.Mocked<InteractionHandler>;
|
||||
let adapterFactory: jest.Mocked<AdapterFactory>;
|
||||
let storage: jest.Mocked<KeyValueStorage<string, any>>;
|
||||
let jwkGenerator: jest.Mocked<JwkGenerator>;
|
||||
let credentialStorage: jest.Mocked<KeyValueStorage<string, ClientCredentials>>;
|
||||
let errorHandler: jest.Mocked<ErrorHandler>;
|
||||
let responseWriter: jest.Mocked<ResponseWriter>;
|
||||
@@ -77,6 +80,13 @@ describe('An IdentityProviderFactory', (): void => {
|
||||
set: jest.fn((id: string, value: any): any => map.set(id, value)),
|
||||
} as any;
|
||||
|
||||
const { privateKey, publicKey } = await generateKeyPair('ES256');
|
||||
jwkGenerator = {
|
||||
alg: 'ES256',
|
||||
getPrivateKey: jest.fn().mockResolvedValue({ ...await exportJWK(privateKey), alg: 'ES256' }),
|
||||
getPublicKey: jest.fn().mockResolvedValue({ ...await exportJWK(publicKey), alg: 'ES256' }),
|
||||
};
|
||||
|
||||
credentialStorage = {
|
||||
get: jest.fn((id: string): any => map.get(id)),
|
||||
set: jest.fn((id: string, value: any): any => map.set(id, value)),
|
||||
@@ -94,6 +104,7 @@ describe('An IdentityProviderFactory', (): void => {
|
||||
oidcPath,
|
||||
interactionHandler,
|
||||
storage,
|
||||
jwkGenerator,
|
||||
credentialStorage,
|
||||
showStackTrace: true,
|
||||
errorHandler,
|
||||
@@ -179,6 +190,7 @@ describe('An IdentityProviderFactory', (): void => {
|
||||
oidcPath,
|
||||
interactionHandler,
|
||||
storage,
|
||||
jwkGenerator,
|
||||
credentialStorage,
|
||||
showStackTrace: true,
|
||||
errorHandler,
|
||||
@@ -203,6 +215,7 @@ describe('An IdentityProviderFactory', (): void => {
|
||||
oidcPath,
|
||||
interactionHandler,
|
||||
storage,
|
||||
jwkGenerator,
|
||||
credentialStorage,
|
||||
showStackTrace: true,
|
||||
errorHandler,
|
||||
@@ -210,10 +223,8 @@ describe('An IdentityProviderFactory', (): void => {
|
||||
});
|
||||
const result2 = await factory2.getProvider() as unknown as { issuer: string; config: Configuration };
|
||||
expect(result1.config.cookies).toEqual(result2.config.cookies);
|
||||
expect(result1.config.jwks).toEqual(result2.config.jwks);
|
||||
expect(storage.get).toHaveBeenCalledTimes(4);
|
||||
expect(storage.set).toHaveBeenCalledTimes(2);
|
||||
expect(storage.set).toHaveBeenCalledWith('jwks', result1.config.jwks);
|
||||
expect(storage.get).toHaveBeenCalledTimes(2);
|
||||
expect(storage.set).toHaveBeenCalledTimes(1);
|
||||
expect(storage.set).toHaveBeenCalledWith('cookie-secret', result1.config.cookies?.keys);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user