feat: Remove setup

This commit is contained in:
Joachim Van Herwegen
2023-07-17 10:00:48 +02:00
parent ea83ea59a1
commit 5eff035cb3
51 changed files with 5 additions and 930 deletions

View File

@@ -209,10 +209,6 @@ export * from './init/final/Finalizable';
export * from './init/final/FinalizableHandler';
export * from './init/final/Finalizer';
// Init/Setup
export * from './init/setup/SetupHandler';
export * from './init/setup/SetupHttpHandler';
// Init/Cli
export * from './init/cli/CliExtractor';
export * from './init/cli/YargsCliExtractor';

View File

@@ -1,83 +0,0 @@
import { BasicRepresentation } from '../../http/representation/BasicRepresentation';
import type { Representation } from '../../http/representation/Representation';
import { BaseInteractionHandler } from '../../identity/interaction/BaseInteractionHandler';
import type { RegistrationManager } from '../../identity/interaction/email-password/util/RegistrationManager';
import type { InteractionHandlerInput } from '../../identity/interaction/InteractionHandler';
import { getLoggerFor } from '../../logging/LogUtil';
import { APPLICATION_JSON } from '../../util/ContentTypes';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { readJsonStream } from '../../util/StreamUtil';
import type { Initializer } from '../Initializer';
export interface SetupHandlerArgs {
/**
* Used for registering a pod during setup.
*/
registrationManager?: RegistrationManager;
/**
* Initializer to call in case no registration procedure needs to happen.
* This Initializer should make sure the necessary resources are there so the server can work correctly.
*/
initializer?: Initializer;
}
/**
* On POST requests, runs an initializer and/or performs a registration step, both optional.
*/
export class SetupHandler extends BaseInteractionHandler {
protected readonly logger = getLoggerFor(this);
private readonly registrationManager?: RegistrationManager;
private readonly initializer?: Initializer;
public constructor(args: SetupHandlerArgs) {
super({});
this.registrationManager = args.registrationManager;
this.initializer = args.initializer;
}
protected async handlePost({ operation }: InteractionHandlerInput): Promise<Representation> {
const json = operation.body.isEmpty ? {} : await readJsonStream(operation.body.data);
const output: Record<string, any> = { initialize: false, registration: false };
if (json.registration) {
Object.assign(output, await this.register(json));
output.registration = true;
} else if (json.initialize) {
// We only want to initialize if no registration happened
await this.initialize();
output.initialize = true;
}
this.logger.debug(`Output: ${JSON.stringify(output)}`);
return new BasicRepresentation(JSON.stringify(output), APPLICATION_JSON);
}
/**
* Call the initializer.
* Errors if no initializer was defined.
*/
private async initialize(): Promise<void> {
if (!this.initializer) {
throw new NotImplementedHttpError('This server is not configured with a setup initializer.');
}
await this.initializer.handleSafe();
}
/**
* Register a user based on the given input.
* Errors if no registration manager is defined.
*/
private async register(json: NodeJS.Dict<any>): Promise<Record<string, any>> {
if (!this.registrationManager) {
throw new NotImplementedHttpError('This server is not configured to support registration during setup.');
}
// Validate the input JSON
const validated = this.registrationManager.validateInput(json, true);
this.logger.debug(`Validated input: ${JSON.stringify(validated)}`);
// Register and/or create a pod as requested. Potentially does nothing if all booleans are false.
return this.registrationManager.register(validated, true);
}
}

View File

@@ -1,116 +0,0 @@
import type { Operation } from '../../http/Operation';
import { OkResponseDescription } from '../../http/output/response/OkResponseDescription';
import type { ResponseDescription } from '../../http/output/response/ResponseDescription';
import { BasicRepresentation } from '../../http/representation/BasicRepresentation';
import type { InteractionHandler } from '../../identity/interaction/InteractionHandler';
import { getLoggerFor } from '../../logging/LogUtil';
import type { OperationHttpHandlerInput } from '../../server/OperationHttpHandler';
import { OperationHttpHandler } from '../../server/OperationHttpHandler';
import type { RepresentationConverter } from '../../storage/conversion/RepresentationConverter';
import type { KeyValueStorage } from '../../storage/keyvalue/KeyValueStorage';
import { APPLICATION_JSON, TEXT_HTML } from '../../util/ContentTypes';
import { MethodNotAllowedHttpError } from '../../util/errors/MethodNotAllowedHttpError';
import type { TemplateEngine } from '../../util/templates/TemplateEngine';
export interface SetupHttpHandlerArgs {
/**
* Used for converting the input data.
*/
converter: RepresentationConverter;
/**
* Handles the requests.
*/
handler: InteractionHandler;
/**
* Key that is used to store the boolean in the storage indicating setup is finished.
*/
storageKey: string;
/**
* Used to store setup status.
*/
storage: KeyValueStorage<string, boolean>;
/**
* Renders the main view.
*/
templateEngine: TemplateEngine;
/**
* Determines if pods can be created in the root of the server.
* Relevant to make sure there are no nested storages if registration is enabled for example.
* Defaults to `true`.
*/
allowRootPod?: boolean;
}
/**
* Handles the initial setup of a server.
* Will capture all requests until setup is finished,
* this to prevent accidentally running unsafe servers.
*
* GET requests will return the view template which should contain the setup information for the user.
* POST requests will be sent to the InteractionHandler.
* After successfully completing a POST request this handler will disable itself and become unreachable.
* All other methods will be rejected.
*/
export class SetupHttpHandler extends OperationHttpHandler {
protected readonly logger = getLoggerFor(this);
private readonly handler: InteractionHandler;
private readonly converter: RepresentationConverter;
private readonly storageKey: string;
private readonly storage: KeyValueStorage<string, boolean>;
private readonly templateEngine: TemplateEngine;
private readonly allowRootPod: boolean;
public constructor(args: SetupHttpHandlerArgs) {
super();
this.handler = args.handler;
this.converter = args.converter;
this.storageKey = args.storageKey;
this.storage = args.storage;
this.templateEngine = args.templateEngine;
this.allowRootPod = args.allowRootPod ?? true;
}
public async handle({ operation }: OperationHttpHandlerInput): Promise<ResponseDescription> {
switch (operation.method) {
case 'GET': return this.handleGet(operation);
case 'POST': return this.handlePost(operation);
default: throw new MethodNotAllowedHttpError([ operation.method ]);
}
}
/**
* Returns the HTML representation of the setup page.
*/
private async handleGet(operation: Operation): Promise<ResponseDescription> {
const result = await this.templateEngine.handleSafe({ contents: { allowRootPod: this.allowRootPod }});
const representation = new BasicRepresentation(result, operation.target, TEXT_HTML);
return new OkResponseDescription(representation.metadata, representation.data);
}
/**
* Converts the input data to JSON and calls the setup handler.
* On success `true` will be written to the storage key.
*/
private async handlePost(operation: Operation): Promise<ResponseDescription> {
// Convert input data to JSON
// Allows us to still support form data
if (operation.body.metadata.contentType) {
const args = {
representation: operation.body,
preferences: { type: { [APPLICATION_JSON]: 1 }},
identifier: operation.target,
};
operation = {
...operation,
body: await this.converter.handleSafe(args),
};
}
const representation = await this.handler.handleSafe({ operation });
await this.storage.set(this.storageKey, true);
return new OkResponseDescription(representation.metadata, representation.data);
}
}