mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00

Each IDP class using storage now has a different storage. This way those classes don't have to worry about clashing keys anymore. All internal storage is now in the /.internal/ container, thereby making it easier to take the location of the internal data into account: only 1 path needs to be blocked and a regex router handling internal data differently only has to match 1 path as well.
123 lines
3.9 KiB
TypeScript
123 lines
3.9 KiB
TypeScript
import type { Adapter, AdapterPayload } from 'oidc-provider';
|
|
import { getLoggerFor } from '../../logging/LogUtil';
|
|
import type { ExpiringStorage } from '../../storage/keyvalue/ExpiringStorage';
|
|
import type { AdapterFactory } from './AdapterFactory';
|
|
|
|
/**
|
|
* An IDP storage adapter that uses an ExpiringStorage
|
|
* to persist data.
|
|
*/
|
|
export class ExpiringAdapter implements Adapter {
|
|
protected readonly logger = getLoggerFor(this);
|
|
|
|
private readonly name: string;
|
|
private readonly storage: ExpiringStorage<string, unknown>;
|
|
|
|
public constructor(name: string, storage: ExpiringStorage<string, unknown>) {
|
|
this.name = name;
|
|
this.storage = storage;
|
|
}
|
|
|
|
private grantKeyFor(id: string): string {
|
|
return `grant/${encodeURIComponent(id)}`;
|
|
}
|
|
|
|
private userCodeKeyFor(userCode: string): string {
|
|
return `user_code/${encodeURIComponent(userCode)}`;
|
|
}
|
|
|
|
private uidKeyFor(uid: string): string {
|
|
return `uid/${encodeURIComponent(uid)}`;
|
|
}
|
|
|
|
private keyFor(id: string): string {
|
|
return `${this.name}/${encodeURIComponent(id)}`;
|
|
}
|
|
|
|
public async upsert(id: string, payload: AdapterPayload, expiresIn?: number): Promise<void> {
|
|
// Despite what the typings say, `expiresIn` can be undefined
|
|
const expiration = expiresIn ? expiresIn * 1000 : undefined;
|
|
const key = this.keyFor(id);
|
|
|
|
this.logger.debug(`Storing payload data for ${id}`);
|
|
|
|
const storagePromises: Promise<unknown>[] = [
|
|
this.storage.set(key, payload, expiration),
|
|
];
|
|
if (payload.grantId) {
|
|
storagePromises.push(
|
|
(async(): Promise<void> => {
|
|
const grantKey = this.grantKeyFor(payload.grantId!);
|
|
const grants = (await this.storage.get(grantKey) || []) as string[];
|
|
grants.push(key);
|
|
await this.storage.set(grantKey, grants, expiration);
|
|
})(),
|
|
);
|
|
}
|
|
if (payload.userCode) {
|
|
storagePromises.push(this.storage.set(this.userCodeKeyFor(payload.userCode), id, expiration));
|
|
}
|
|
if (payload.uid) {
|
|
storagePromises.push(this.storage.set(this.uidKeyFor(payload.uid), id, expiration));
|
|
}
|
|
await Promise.all(storagePromises);
|
|
}
|
|
|
|
public async find(id: string): Promise<AdapterPayload | void> {
|
|
return await this.storage.get(this.keyFor(id)) as AdapterPayload | undefined;
|
|
}
|
|
|
|
public async findByUserCode(userCode: string): Promise<AdapterPayload | void> {
|
|
const id = await this.storage.get(this.userCodeKeyFor(userCode)) as string;
|
|
return this.find(id);
|
|
}
|
|
|
|
public async findByUid(uid: string): Promise<AdapterPayload | void> {
|
|
const id = await this.storage.get(this.uidKeyFor(uid)) as string;
|
|
return this.find(id);
|
|
}
|
|
|
|
public async destroy(id: string): Promise<void> {
|
|
await this.storage.delete(this.keyFor(id));
|
|
}
|
|
|
|
public async revokeByGrantId(grantId: string): Promise<void> {
|
|
this.logger.debug(`Revoking grantId ${grantId}`);
|
|
const grantKey = this.grantKeyFor(grantId);
|
|
const grants = await this.storage.get(grantKey) as string[] | undefined;
|
|
if (!grants) {
|
|
return;
|
|
}
|
|
const deletePromises: Promise<unknown>[] = [];
|
|
grants.forEach((grant): void => {
|
|
deletePromises.push(this.storage.delete(grant));
|
|
});
|
|
deletePromises.push(this.storage.delete(grantKey));
|
|
await Promise.all(deletePromises);
|
|
}
|
|
|
|
public async consume(id: string): Promise<void> {
|
|
const payload = await this.find(id);
|
|
if (!payload) {
|
|
return;
|
|
}
|
|
payload.consumed = Math.floor(Date.now() / 1000);
|
|
await this.storage.set(this.keyFor(id), payload);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The factory for a ExpiringStorageAdapter
|
|
*/
|
|
export class ExpiringAdapterFactory implements AdapterFactory {
|
|
private readonly storage: ExpiringStorage<string, unknown>;
|
|
|
|
public constructor(storage: ExpiringStorage<string, unknown>) {
|
|
this.storage = storage;
|
|
}
|
|
|
|
public createStorageAdapter(name: string): ExpiringAdapter {
|
|
return new ExpiringAdapter(name, this.storage);
|
|
}
|
|
}
|