feat: Full rework of account management

Complete rewrite of the account management and related systems.
Makes the architecture more modular,
allowing for easier extensions and configurations.
This commit is contained in:
Joachim Van Herwegen
2022-03-16 10:12:13 +01:00
parent ade977bb4f
commit a47f5236ef
366 changed files with 12345 additions and 5111 deletions

View File

@@ -1,4 +1,3 @@
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
import { getLoggerFor } from '../logging/LogUtil';
import type { KeyValueStorage } from '../storage/keyvalue/KeyValueStorage';
import type { ResourceStore } from '../storage/ResourceStore';
@@ -40,15 +39,15 @@ export class ConfigPodManager implements PodManager {
this.store = store;
}
public async createPod(identifier: ResourceIdentifier, settings: PodSettings): Promise<void> {
this.logger.info(`Creating pod ${identifier.path}`);
public async createPod(settings: PodSettings): Promise<void> {
this.logger.info(`Creating pod ${settings.base.path}`);
// Will error in case there already is a store for the given identifier
const store = await this.podGenerator.generate(identifier, settings);
const store = await this.podGenerator.generate(settings);
await this.routingStorage.set(identifier.path, store);
const count = await addGeneratedResources(identifier, settings, this.resourcesGenerator, this.store);
await this.routingStorage.set(settings.base.path, store);
const count = await addGeneratedResources(settings, this.resourcesGenerator, this.store);
this.logger.info(`Added ${count} resources to ${identifier.path}`);
this.logger.info(`Added ${count} resources to ${settings.base.path}`);
}
}

View File

@@ -1,4 +1,3 @@
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
import { getLoggerFor } from '../logging/LogUtil';
import type { ResourceStore } from '../storage/ResourceStore';
import { ConflictHttpError } from '../util/errors/ConflictHttpError';
@@ -26,13 +25,13 @@ export class GeneratedPodManager implements PodManager {
* Creates a new pod, pre-populating it with the resources created by the data generator.
* Will throw an error if the given identifier already has a resource.
*/
public async createPod(identifier: ResourceIdentifier, settings: PodSettings, overwrite: boolean): Promise<void> {
this.logger.info(`Creating pod ${identifier.path}`);
if (!overwrite && await this.store.hasResource(identifier)) {
throw new ConflictHttpError(`There already is a resource at ${identifier.path}`);
public async createPod(settings: PodSettings, overwrite: boolean): Promise<void> {
this.logger.info(`Creating pod ${settings.base.path}`);
if (!overwrite && await this.store.hasResource(settings.base)) {
throw new ConflictHttpError(`There already is a resource at ${settings.base.path}`);
}
const count = await addGeneratedResources(identifier, settings, this.resourcesGenerator, this.store);
this.logger.info(`Added ${count} resources to ${identifier.path}`);
const count = await addGeneratedResources(settings, this.resourcesGenerator, this.store);
this.logger.info(`Added ${count} resources to ${settings.base.path}`);
}
}

View File

@@ -1,4 +1,3 @@
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
import type { PodSettings } from './settings/PodSettings';
/**
@@ -8,9 +7,8 @@ import type { PodSettings } from './settings/PodSettings';
export interface PodManager {
/**
* Creates a pod for the given settings.
* @param identifier - Root identifier indicating where the pod should be created.
* @param settings - Settings describing the pod.
* @param overwrite - If the creation should proceed if there already is a resource there.
*/
createPod: (identifier: ResourceIdentifier, settings: PodSettings, overwrite: boolean) => Promise<void>;
createPod: (settings: PodSettings, overwrite: boolean) => Promise<void>;
}

View File

@@ -93,7 +93,7 @@ export class BaseResourcesGenerator implements TemplatedResourcesGenerator {
this.store = args.store;
}
public async* generate(templateFolder: string, location: ResourceIdentifier, options: Dict<string>):
public async* generate(templateFolder: string, location: ResourceIdentifier, options: Dict<unknown>):
AsyncIterable<Resource> {
templateFolder = resolveAssetPath(templateFolder);
@@ -111,7 +111,7 @@ export class BaseResourcesGenerator implements TemplatedResourcesGenerator {
/**
* Generates results for all entries in the given folder, including the folder itself.
*/
private async* processFolder(folderLink: TemplateResourceLink, mapper: FileIdentifierMapper, options: Dict<string>):
private async* processFolder(folderLink: TemplateResourceLink, mapper: FileIdentifierMapper, options: Dict<unknown>):
AsyncIterable<Resource> {
// Group resource links with their corresponding metadata links
const links = await this.groupLinks(folderLink.filePath, mapper);
@@ -175,7 +175,7 @@ export class BaseResourcesGenerator implements TemplatedResourcesGenerator {
* If a ResourceLink of metadata is provided the corresponding metadata resource
* will be yielded as a separate resource.
*/
private async* generateResource(link: TemplateResourceLink, options: Dict<string>, metaLink?: TemplateResourceLink):
private async* generateResource(link: TemplateResourceLink, options: Dict<unknown>, metaLink?: TemplateResourceLink):
AsyncIterable<Resource> {
let data: Guarded<Readable> | undefined;
const metadata = new RepresentationMetadata(link.identifier);
@@ -211,7 +211,7 @@ export class BaseResourcesGenerator implements TemplatedResourcesGenerator {
/**
* Generates a RepresentationMetadata using the given template.
*/
private async generateMetadata(metaLink: TemplateResourceLink, options: Dict<string>):
private async generateMetadata(metaLink: TemplateResourceLink, options: Dict<unknown>):
Promise<RepresentationMetadata> {
const metadata = new RepresentationMetadata(metaLink.identifier);
@@ -226,7 +226,7 @@ export class BaseResourcesGenerator implements TemplatedResourcesGenerator {
/**
* Creates a read stream from the file and applies the template if necessary.
*/
private async processFile(link: TemplateResourceLink, contents: Dict<string>): Promise<Guarded<Readable>> {
private async processFile(link: TemplateResourceLink, contents: Dict<unknown>): Promise<Guarded<Readable>> {
if (link.isTemplate) {
const rendered = await this.templateEngine.handleSafe({ contents, template: { templateFile: link.filePath }});
return guardedStreamFrom(rendered);

View File

@@ -1,19 +1,18 @@
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
import type { ResourceStore } from '../../storage/ResourceStore';
import type { PodSettings } from '../settings/PodSettings';
import type { ResourcesGenerator } from './ResourcesGenerator';
/**
* Generates resources with the given generator and adds them to the given store.
* @param identifier - Identifier of the pod.
* @param settings - Settings from which the pod is being created.
* @param generator - Generator to be used.
* @param store - Store to be updated.
*
* @returns The amount of resources that were added.
*/
export async function addGeneratedResources(identifier: ResourceIdentifier, settings: NodeJS.Dict<string>,
generator: ResourcesGenerator, store: ResourceStore): Promise<number> {
const resources = generator.generate(identifier, settings);
export async function addGeneratedResources(settings: PodSettings, generator: ResourcesGenerator,
store: ResourceStore): Promise<number> {
const resources = generator.generate(settings.base, settings);
let count = 0;
for await (const { identifier: resourceId, representation } of resources) {
await store.setRepresentation(resourceId, representation);

View File

@@ -1,4 +1,3 @@
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
import type { ResourceStore } from '../../storage/ResourceStore';
import type { PodSettings } from '../settings/PodSettings';
@@ -12,10 +11,9 @@ export interface PodGenerator {
* Creates a ResourceStore based on the given input.
* Should error if there already is a store for the given identifier.
*
* @param identifier - Identifier of the new pod.
* @param settings - Parameters to be used for the new pod.
*
* @returns A new ResourceStore to be used for the new pod.
*/
generate: (identifier: ResourceIdentifier, settings: PodSettings) => Promise<ResourceStore>;
generate: (settings: PodSettings) => Promise<ResourceStore>;
}

View File

@@ -20,5 +20,5 @@ export interface ResourcesGenerator {
*
* @returns A map where the keys are the identifiers and the values the corresponding representations to store.
*/
generate: (location: ResourceIdentifier, options: Dict<string>) => AsyncIterable<Resource>;
generate: (location: ResourceIdentifier, options: Dict<unknown>) => AsyncIterable<Resource>;
}

View File

@@ -15,7 +15,7 @@ export class StaticFolderGenerator implements ResourcesGenerator {
this.templateFolder = templateFolder;
}
public generate(location: ResourceIdentifier, options: Dict<string>): AsyncIterable<Resource> {
public generate(location: ResourceIdentifier, options: Dict<unknown>): AsyncIterable<Resource> {
return this.resourcesGenerator.generate(this.templateFolder, location, options);
}
}

View File

@@ -30,7 +30,7 @@ export class SubfolderResourcesGenerator implements TemplatedResourcesGenerator
this.subfolders = subfolders;
}
public async* generate(templateFolder: string, location: ResourceIdentifier, options: Dict<string>):
public async* generate(templateFolder: string, location: ResourceIdentifier, options: Dict<unknown>):
AsyncIterable<Resource> {
const root = resolveAssetPath(templateFolder);
const templateSubfolders = this.subfolders.map((subfolder): string => joinFilePath(root, subfolder));

View File

@@ -1,4 +1,3 @@
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
import { getLoggerFor } from '../../logging/LogUtil';
import type { KeyValueStorage } from '../../storage/keyvalue/KeyValueStorage';
import type { ResourceStore } from '../../storage/ResourceStore';
@@ -48,7 +47,8 @@ export class TemplatedPodGenerator implements PodGenerator {
this.configTemplatePath = configTemplatePath ?? DEFAULT_CONFIG_PATH;
}
public async generate(identifier: ResourceIdentifier, settings: PodSettings): Promise<ResourceStore> {
public async generate(settings: PodSettings): Promise<ResourceStore> {
const identifier = settings.base;
if (!settings.template) {
throw new BadRequestHttpError('Settings require template field.');
}
@@ -61,7 +61,7 @@ export class TemplatedPodGenerator implements PodGenerator {
await this.variableHandler.handleSafe({ identifier, settings });
// Filter out irrelevant data in the agent
const variables: NodeJS.Dict<string> = {};
const variables: NodeJS.Dict<unknown> = {};
for (const key of Object.keys(settings)) {
if (isValidVariable(key)) {
variables[key] = settings[key];
@@ -78,7 +78,7 @@ export class TemplatedPodGenerator implements PodGenerator {
const store: ResourceStore =
await this.storeFactory.generate(
variables[TEMPLATE_VARIABLE.templateConfig]!,
variables[TEMPLATE_VARIABLE.templateConfig] as string,
TEMPLATE.ResourceStore,
{ ...variables, 'urn:solid-server:default:variable:baseUrl': this.baseUrl },
);

View File

@@ -17,5 +17,5 @@ export interface TemplatedResourcesGenerator {
*
* @returns A map where the keys are the identifiers and the values the corresponding representations to store.
*/
generate: (templateFolder: string, location: ResourceIdentifier, options: Dict<string>) => AsyncIterable<Resource>;
generate: (templateFolder: string, location: ResourceIdentifier, options: Dict<unknown>) => AsyncIterable<Resource>;
}

View File

@@ -1,9 +1,13 @@
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
/**
* Metadata related to pod generation.
* Although the optional fields are not that relevant since this extends Dict,
* they give an indication of what is sometimes expected.
*/
export interface PodSettings extends NodeJS.Dict<string> {
export interface PodSettings extends NodeJS.Dict<unknown> {
/**
* The root of the pod. Determines where the pod will be created.
*/
base: ResourceIdentifier;
/**
* The WebId of the owner of this pod.
*/
@@ -22,7 +26,7 @@ export interface PodSettings extends NodeJS.Dict<string> {
*/
email?: string;
/**
* The OIDC issuer of the owner's WebId.
* The OIDC issuer of the owner's WebId. Necessary if the WebID in the pod is registered with the IDP.
*/
oidcIssuer?: string;
}