mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Remove setup
This commit is contained in:
parent
ea83ea59a1
commit
5eff035cb3
@ -19,16 +19,6 @@ This is the entry point to the main server setup.
|
||||
* *default*: The main application. This should only be changed/replaced
|
||||
if you want to start from a different kind of class.
|
||||
|
||||
## Setup
|
||||
|
||||
Handles the setup page the first time the server is started.
|
||||
|
||||
* *disabled*: Disables the setup page. Root container access will be impossible unless handled by the Init config above.
|
||||
Registration and pod creation is still possible if that feature is enabled.
|
||||
* *optional*: Setup is available at `/setup` but the server can already be used.
|
||||
Everyone can access the setup page so make sure to complete that as soon as possible.
|
||||
* *required*: All requests will be redirected to the setup page until setup is completed.
|
||||
|
||||
## Variables
|
||||
|
||||
Handles parsing CLI parameters and assigning values to Components.js variables.
|
||||
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Completely disables the setup page.",
|
||||
"@id": "urn:solid-server:default:SetupHandler",
|
||||
"@type": "UnsupportedAsyncHandler"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Redirects all request to the setup.",
|
||||
"@id": "urn:solid-server:default:SetupRedirectHandler",
|
||||
"@type": "RedirectingHttpHandler",
|
||||
"redirects": [
|
||||
{
|
||||
"RedirectingHttpHandler:_redirects_key": ".*",
|
||||
"RedirectingHttpHandler:_redirects_value": "/setup"
|
||||
}
|
||||
],
|
||||
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"targetExtractor": { "@id": "urn:solid-server:default:TargetExtractor" },
|
||||
"responseWriter": { "@id": "urn:solid-server:default:ResponseWriter" },
|
||||
"statusCode": 302
|
||||
}
|
||||
]
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Handles everything related to the first-time server setup.",
|
||||
"@id": "urn:solid-server:default:SetupParsingHandler",
|
||||
"@type": "ParsingHttpHandler",
|
||||
"args_requestParser": { "@id": "urn:solid-server:default:RequestParser" },
|
||||
"args_errorHandler": { "@id": "urn:solid-server:default:ErrorHandler" },
|
||||
"args_responseWriter": { "@id": "urn:solid-server:default:ResponseWriter" },
|
||||
"args_operationHandler": {
|
||||
"@id": "urn:solid-server:default:SetupHttpHandler",
|
||||
"@type": "SetupHttpHandler",
|
||||
"args_handler": {
|
||||
"@type": "SetupHandler",
|
||||
"args_initializer": { "@id": "urn:solid-server:default:SetupInitializer" },
|
||||
"args_registrationManager": { "@id": "urn:solid-server:default:SetupRegistrationManager" }
|
||||
},
|
||||
"args_converter": { "@id": "urn:solid-server:default:RepresentationConverter" },
|
||||
"args_storageKey": "setupCompleted-2.0",
|
||||
"args_storage": { "@id": "urn:solid-server:default:SetupStorage" },
|
||||
"args_templateEngine": {
|
||||
"comment": "Renders the specific page and embeds it into the main HTML body.",
|
||||
"@type": "ChainedTemplateEngine",
|
||||
"renderedName": "htmlBody",
|
||||
"engines": [
|
||||
{
|
||||
"comment": "Renders the main setup template.",
|
||||
"@type": "StaticTemplateEngine",
|
||||
"templateEngine": { "@id": "urn:solid-server:default:TemplateEngine" },
|
||||
"template": "@css:templates/setup/index.html.ejs"
|
||||
},
|
||||
{
|
||||
"comment": "Will embed the result of the first engine into the main HTML template.",
|
||||
"@type": "StaticTemplateEngine",
|
||||
"templateEngine": { "@id": "urn:solid-server:default:TemplateEngine" },
|
||||
"template": "@css:templates/main.html.ejs"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "Separate manager from the RegistrationHandler in case registration is disabled.",
|
||||
"@id": "urn:solid-server:default:SetupRegistrationManager",
|
||||
"@type": "RegistrationManager",
|
||||
"args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"args_webIdSuffix": "/profile/card#me",
|
||||
"args_identifierGenerator": { "@id": "urn:solid-server:default:IdentifierGenerator" },
|
||||
"args_ownershipValidator": { "@id": "urn:solid-server:auth:password:OwnershipValidator" },
|
||||
"args_accountStore": { "@id": "urn:solid-server:auth:password:AccountStore" },
|
||||
"args_podManager": { "@id": "urn:solid-server:default:PodManager" }
|
||||
},
|
||||
{
|
||||
"comment": "Separate initializer as we only want a simple one that sets the root .acl.",
|
||||
"@id": "urn:solid-server:default:SetupInitializer",
|
||||
"@type": "ContainerInitializer",
|
||||
"args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"args_path": "/",
|
||||
"args_store": { "@id": "urn:solid-server:default:ResourceStore" },
|
||||
"args_generator": {
|
||||
"@type": "StaticFolderGenerator",
|
||||
"templateFolder": "@css:templates/root/empty",
|
||||
"resourcesGenerator": { "@id": "urn:solid-server:default:TemplatedResourcesGenerator" }
|
||||
},
|
||||
"args_storageKey": "rootInitialized",
|
||||
"args_storage": { "@id": "urn:solid-server:default:SetupStorage" }
|
||||
}
|
||||
]
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||
"import": [
|
||||
"css:config/app/setup/handlers/setup.json"
|
||||
],
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Combines both the redirect and the setup.",
|
||||
"@id": "urn:solid-server:default:SetupHandler",
|
||||
"@type": "ConditionalHandler",
|
||||
"storageKey": "setupCompleted-2.0",
|
||||
"storageValue": true,
|
||||
"storage": { "@id": "urn:solid-server:default:SetupStorage" },
|
||||
"source": {
|
||||
"@type": "RouterHandler",
|
||||
"args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"args_targetExtractor": { "@id": "urn:solid-server:default:TargetExtractor" },
|
||||
"args_allowedPathNames": [ "/setup" ],
|
||||
"args_handler": { "@id": "urn:solid-server:default:SetupParsingHandler" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||
"import": [
|
||||
"css:config/app/setup/handlers/redirect.json",
|
||||
"css:config/app/setup/handlers/setup.json"
|
||||
],
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Combines both the redirect and the setup.",
|
||||
"@id": "urn:solid-server:default:SetupHandler",
|
||||
"@type": "ConditionalHandler",
|
||||
"storageKey": "setupCompleted-2.0",
|
||||
"storageValue": true,
|
||||
"storage": { "@id": "urn:solid-server:default:SetupStorage" },
|
||||
"source": {
|
||||
"@type": "WaterfallHandler",
|
||||
"handlers": [
|
||||
{ "@id": "urn:solid-server:default:SetupRedirectHandler" },
|
||||
{
|
||||
"@type": "RouterHandler",
|
||||
"args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"args_targetExtractor": { "@id": "urn:solid-server:default:TargetExtractor" },
|
||||
"args_allowedPathNames": [ "/setup" ],
|
||||
"args_handler": { "@id": "urn:solid-server:default:SetupParsingHandler" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-prefilled-root.json",
|
||||
"css:config/app/setup/optional.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/static-root.json",
|
||||
"css:config/app/setup/required.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/static-root.json",
|
||||
"css:config/app/setup/required.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/static-root.json",
|
||||
"css:config/app/setup/required.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
@ -37,7 +36,7 @@
|
||||
{
|
||||
"comment": [
|
||||
"A Solid server that stores its resources on disk and uses WAC for authorization.",
|
||||
"No setup is required and the root container is initialized to allow full access for everyone so make sure to change this."
|
||||
"No registration and the root container is initialized to allow full access for everyone so make sure to change this."
|
||||
]
|
||||
}
|
||||
]
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/static-root.json",
|
||||
"css:config/app/setup/required.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -16,7 +16,6 @@
|
||||
"@type": "WaterfallHandler",
|
||||
"handlers": [
|
||||
{ "@id": "urn:solid-server:default:StaticAssetHandler" },
|
||||
{ "@id": "urn:solid-server:default:SetupHandler" },
|
||||
{ "@id": "urn:solid-server:default:OidcHandler" },
|
||||
{ "@id": "urn:solid-server:default:NotificationHttpHandler" },
|
||||
{ "@id": "urn:solid-server:default:StorageDescriptionHandler" },
|
||||
|
@ -15,7 +15,6 @@
|
||||
"@type": "WaterfallHandler",
|
||||
"handlers": [
|
||||
{ "@id": "urn:solid-server:default:StaticAssetHandler" },
|
||||
{ "@id": "urn:solid-server:default:SetupHandler" },
|
||||
{ "@id": "urn:solid-server:default:StorageDescriptionHandler" },
|
||||
{ "@id": "urn:solid-server:default:LdpHandler" }
|
||||
]
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/static-root.json",
|
||||
"css:config/app/setup/required.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -36,12 +36,6 @@
|
||||
"HtmlViewHandler:_templates_value": { "@id": "urn:solid-server:auth:password:RegistrationRoute" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"comment": "Root access is disabled when registration is enabled to prevent nested storage containers.",
|
||||
"@id": "urn:solid-server:default:SetupHttpHandler",
|
||||
"@type": "SetupHttpHandler",
|
||||
"allowRootPod": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/optional.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/static-root.json",
|
||||
"css:config/app/setup/required.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/static-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
@ -38,7 +37,7 @@
|
||||
"comment": [
|
||||
"A single-pod server that stores its resources in a SPARQL endpoint and uses WAC for authorization.",
|
||||
"This server only supports RDF data. For this reason it can not use its resource store for internal key/value storage.",
|
||||
"No setup is required and the root container is initialized to allow full access for everyone so make sure to change this."
|
||||
"No registration and the root container is initialized to allow full access for everyone so make sure to change this."
|
||||
]
|
||||
}
|
||||
]
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/static-root.json",
|
||||
"css:config/app/setup/required.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/static-root.json",
|
||||
"css:config/app/setup/required.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -19,7 +19,6 @@ flowchart LR
|
||||
subgraph WaterfallHandlerArgs[" "]
|
||||
direction TB
|
||||
StaticAssetHandler("<strong>StaticAssetHandler</strong><br>StaticAssetHandler")
|
||||
SetupHandler("<strong>SetupHandler</strong><br><i>HttpHandler</i>")
|
||||
OidcHandler("<strong>OidcHandler</strong><br><i>HttpHandler</i>")
|
||||
NotificationHttpHandler("<strong>NotificationHttpHandler</strong><br><i>HttpHandler</i>")
|
||||
StorageDescriptionHandler("<strong>StorageDescriptionHandler</strong><br><i>HttpHandler</i>")
|
||||
@ -28,8 +27,7 @@ flowchart LR
|
||||
LdpHandler("<strong>LdpHandler</strong><br><i>HttpHandler</i>")
|
||||
end
|
||||
|
||||
StaticAssetHandler --> SetupHandler
|
||||
SetupHandler --> OidcHandler
|
||||
StaticAssetHandler --> OidcHandler
|
||||
OidcHandler --> NotificationHttpHandler
|
||||
NotificationHttpHandler --> StorageDescriptionHandler
|
||||
StorageDescriptionHandler --> AuthResourceHttpHandler
|
||||
@ -52,17 +50,6 @@ An example of this is the favicon, where the `/favicon.ico` URL
|
||||
is directed to the favicon file at `/templates/images/favicon.ico`.
|
||||
It can also map entire folders to a specific path, such as `/.well-known/css/styles/` which contains all stylesheets.
|
||||
|
||||
## SetupHandler
|
||||
|
||||
The `urn:solid-server:default:SetupHandler` is responsible
|
||||
for redirecting all requests to `/setup` until setup is finished,
|
||||
thereby ensuring that setup needs to be finished before anything else can be done on the server,
|
||||
and handling the actual setup request that is sent to `/setup`.
|
||||
Once setup is finished, this handler will reject all requests and thus no longer be relevant.
|
||||
|
||||
If the server is configured to not have setup enabled,
|
||||
the corresponding identifier will point to a handler that always rejects all requests.
|
||||
|
||||
## OidcHandler
|
||||
|
||||
The `urn:solid-server:default:OidcHandler` handles all requests related
|
||||
|
@ -134,7 +134,7 @@ To register a user, you can do a POST request with a JSON body containing the co
|
||||
|
||||
Two fields here that are not covered on the HTML page above are `rootPod` and `template`.
|
||||
`rootPod` tells the server to put the pod in the root of the server instead of a location based on the `podName`.
|
||||
By default the server will reject requests where this is `true`, except during setup.
|
||||
By default the server will reject requests where this is `true`.
|
||||
`template` is only used by servers running the `config/dynamic.json` configuration,
|
||||
which is a very custom setup where every pod can have a different Components.js configuration,
|
||||
so this value can usually be ignored.
|
||||
@ -186,5 +186,3 @@ so they can be recreated when the server restarts.
|
||||
### registration
|
||||
|
||||
This setting allows you to enable/disable registration on the server.
|
||||
Disabling registration here does not disable registration during setup,
|
||||
meaning you can still use this server as an IDP with the account created there.
|
||||
|
@ -56,7 +56,7 @@
|
||||
"postrelease": "ts-node ./scripts/finalizeRelease.ts",
|
||||
"start": "node ./bin/server.js",
|
||||
"start:file": "node ./bin/server.js -c config/file.json -f ./data",
|
||||
"start:file-no-setup": "node ./bin/server.js -c config/file-no-setup.json -f ./data",
|
||||
"start:file-root": "node ./bin/server.js -c config/file-root.json -f ./data",
|
||||
"test": "npm run test:ts && npm run jest",
|
||||
"test:deploy": "test/deploy/validate-configs.sh",
|
||||
"test:ts": "tsc -p test --noEmit",
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
<div id="input-partial">
|
||||
<%- include('./input-partial.html.ejs') %>
|
||||
</div>
|
||||
<div id="response-partial">
|
||||
<h1 id="public">Server setup complete</h1>
|
||||
<p>
|
||||
Congratulations!
|
||||
Your Solid server is now ready to use.
|
||||
<br>
|
||||
You can now visit its <a href="./">homepage</a>.
|
||||
</p>
|
||||
|
||||
<div id="response-initialize">
|
||||
<h2>Root Pod</h2>
|
||||
<p>
|
||||
<strong>Warning: the root Pod is publicly accessible.</strong>
|
||||
<br>
|
||||
Prevent public write and control access to the root
|
||||
by modifying its <a href=".acl">ACL document</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="response-registration">
|
||||
<%- include('../identity/email-password/register-response-partial.html.ejs', { idpIndex: '' }) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateResponse(json) {
|
||||
// Swap visibility
|
||||
setVisibility('input-partial', false);
|
||||
setVisibility('response-partial', true);
|
||||
|
||||
setVisibility('response-initialize', json.initialize);
|
||||
|
||||
setVisibility('response-registration', json.registration);
|
||||
if (json.registration) {
|
||||
updateResponseFields(json);
|
||||
}
|
||||
}
|
||||
|
||||
setVisibility('response-partial', false);
|
||||
|
||||
addPostListener('mainForm', 'error', '', updateResponse);
|
||||
</script>
|
@ -1,79 +0,0 @@
|
||||
<h1>Set up your Solid server</h1>
|
||||
<p>
|
||||
Your Solid server needs a <strong>one-time setup</strong>
|
||||
so it acts exactly the way you want.
|
||||
</p>
|
||||
|
||||
<form method="post" id="mainForm">
|
||||
<p class="error" id="error"></p>
|
||||
|
||||
<fieldset>
|
||||
<legend>Accounts on this server</legend>
|
||||
<ol>
|
||||
<li class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" <% if (!allowRootPod) { %> checked <% } %> disabled>
|
||||
Enable account registration.
|
||||
</label>
|
||||
<p>
|
||||
This can only be changed in the configuration.
|
||||
See the <a href="https://github.com/CommunitySolidServer/CommunitySolidServer/blob/main/config/README.md">general configuration documentation</a>
|
||||
and the <a href="https://github.com/CommunitySolidServer/CommunitySolidServer/blob/main/config/identity/README.md">identity specific options</a> to find out how.
|
||||
</p>
|
||||
</li>
|
||||
<li class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="registration" name="registration">
|
||||
Sign me up for an account.
|
||||
</label>
|
||||
<p>
|
||||
Any existing root Pod will be disabled.
|
||||
</p>
|
||||
</li>
|
||||
<li class="checkbox" id="initializeForm">
|
||||
<label>
|
||||
<input type="checkbox" id="initialize" name="initialize" <% if (!allowRootPod) { %> disabled <% } %>>
|
||||
Expose a public root Pod.
|
||||
</label>
|
||||
<p>
|
||||
By default, the public has read and write access to the root Pod.
|
||||
<br>
|
||||
You typically only want to choose this
|
||||
for rapid testing and development.
|
||||
<br>
|
||||
This requires registration to be disabled.
|
||||
</p>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="registrationForm">
|
||||
<legend>Sign up</legend>
|
||||
<%-
|
||||
include('../identity/email-password/register-partial.html.ejs', {
|
||||
allowRootPod: allowRootPod,
|
||||
})
|
||||
%>
|
||||
</fieldset>
|
||||
|
||||
<p class="actions"><button type="submit">Complete setup</button></p>
|
||||
</form>
|
||||
|
||||
<!-- Show or hide the account creation form when needed -->
|
||||
<script>
|
||||
[
|
||||
'registration', 'registrationForm', 'initializeForm',
|
||||
].forEach(registerElement);
|
||||
|
||||
Object.assign(visibilityConditions, {
|
||||
registrationForm: () => elements.registration.checked,
|
||||
});
|
||||
|
||||
// Assigning elements to `visibilityConditions` causes the `disabled` to be removed.
|
||||
// We don't want this for the initialize form.
|
||||
const registration = document.getElementById('registration');
|
||||
registration.addEventListener('change', () => {
|
||||
const initializeForm = document.getElementById('initializeForm');
|
||||
initializeForm.classList[elements.registration.checked ? 'add' : 'remove']('hidden');
|
||||
});
|
||||
</script>
|
@ -1,128 +0,0 @@
|
||||
import fetch from 'cross-fetch';
|
||||
import type { App } from '../../src/init/App';
|
||||
import { joinUrl } from '../../src/util/PathUtil';
|
||||
import { getPort } from '../util/Util';
|
||||
import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
|
||||
|
||||
const port = getPort('SetupMemory');
|
||||
const baseUrl = `http://localhost:${port}/`;
|
||||
|
||||
// Some tests with real Requests/Responses until the mocking library has been removed from the tests
|
||||
describe('A Solid server with setup', (): void => {
|
||||
const email = 'test@test.email';
|
||||
const password = 'password!';
|
||||
const podName = 'test';
|
||||
const setupUrl = joinUrl(baseUrl, '/setup');
|
||||
let app: App;
|
||||
|
||||
// `beforeEach` since the server needs to restart to reset setup
|
||||
beforeEach(async(): Promise<void> => {
|
||||
const instances = await instantiateFromConfig(
|
||||
'urn:solid-server:test:Instances',
|
||||
getTestConfigPath('setup-memory.json'),
|
||||
getDefaultVariables(port, baseUrl),
|
||||
) as Record<string, any>;
|
||||
({ app } = instances);
|
||||
await app.start();
|
||||
});
|
||||
|
||||
afterEach(async(): Promise<void> => {
|
||||
await app.stop();
|
||||
});
|
||||
|
||||
it('catches all requests.', async(): Promise<void> => {
|
||||
let res = await fetch(baseUrl, { method: 'GET', headers: { accept: 'text/html' }});
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.url).toBe(setupUrl);
|
||||
await expect(res.text()).resolves.toContain('Set up your Solid server');
|
||||
|
||||
res = await fetch(joinUrl(baseUrl, '/random/path/'), { method: 'GET', headers: { accept: 'text/html' }});
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.url).toBe(setupUrl);
|
||||
await expect(res.text()).resolves.toContain('Set up your Solid server');
|
||||
|
||||
res = await fetch(joinUrl(baseUrl, '/random/path/'), { method: 'PUT' });
|
||||
expect(res.status).toBe(405);
|
||||
expect(res.url).toBe(setupUrl);
|
||||
await expect(res.json()).resolves.toEqual(expect.objectContaining({ name: 'MethodNotAllowedHttpError' }));
|
||||
});
|
||||
|
||||
it('can create a server that disables root but allows registration.', async(): Promise<void> => {
|
||||
let res = await fetch(setupUrl, { method: 'POST' });
|
||||
expect(res.status).toBe(200);
|
||||
await expect(res.json()).resolves.toEqual({ initialize: false, registration: false });
|
||||
|
||||
// Root access disabled
|
||||
res = await fetch(baseUrl);
|
||||
expect(res.status).toBe(401);
|
||||
|
||||
// Registration still possible
|
||||
const registerParams = { email, podName, password, confirmPassword: password, createWebId: true };
|
||||
res = await fetch(joinUrl(baseUrl, 'idp/register/'), {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(registerParams),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
res = await fetch(joinUrl(baseUrl, podName, '/profile/card'));
|
||||
expect(res.status).toBe(200);
|
||||
await expect(res.text()).resolves.toContain('foaf:PersonalProfileDocument');
|
||||
});
|
||||
|
||||
it('can create a server with a public root.', async(): Promise<void> => {
|
||||
let res = await fetch(setupUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ initialize: true }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
await expect(res.json()).resolves.toEqual({ initialize: true, registration: false });
|
||||
|
||||
// Root access enabled
|
||||
res = await fetch(baseUrl);
|
||||
expect(res.status).toBe(200);
|
||||
await expect(res.text()).resolves.toContain('<> a <http://www.w3.org/ns/pim/space#Storage>');
|
||||
|
||||
// Root pod registration is never allowed
|
||||
const registerParams = { email, podName, password, confirmPassword: password, createWebId: true, rootPod: true };
|
||||
res = await fetch(joinUrl(baseUrl, 'idp/register/'), {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(registerParams),
|
||||
});
|
||||
expect(res.status).toBe(500);
|
||||
});
|
||||
|
||||
it('can create a server with a root pod.', async(): Promise<void> => {
|
||||
const registerParams = { email, podName, password, confirmPassword: password, createWebId: true, rootPod: true };
|
||||
let res = await fetch(setupUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ registration: true, initialize: true, ...registerParams }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const json = await res.json();
|
||||
expect(json).toEqual(expect.objectContaining({
|
||||
registration: true,
|
||||
initialize: false,
|
||||
oidcIssuer: baseUrl,
|
||||
webId: `${baseUrl}profile/card#me`,
|
||||
email,
|
||||
podBaseUrl: baseUrl,
|
||||
}));
|
||||
|
||||
// Root profile created
|
||||
res = await fetch(joinUrl(baseUrl, '/profile/card'));
|
||||
expect(res.status).toBe(200);
|
||||
await expect(res.text()).resolves.toContain('foaf:PersonalProfileDocument');
|
||||
|
||||
// Pod root is not accessible even though initialize was set to true
|
||||
res = await fetch(joinUrl(baseUrl, 'resource'), {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'text/plain' },
|
||||
body: 'random data',
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/default.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/simple.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/disabled.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/simple.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/disabled.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/legacy-websockets.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/default.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
|
||||
"css:config/http/handler/simple.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/websockets.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/websockets.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/default.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/websockets.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/disabled.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/disabled.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/websockets.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/simple.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/websockets.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/simple.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/disabled.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/disabled.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/simple.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/websockets.json",
|
||||
|
@ -1,47 +0,0 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/default.json",
|
||||
"css:config/app/setup/required.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/websockets.json",
|
||||
"css:config/http/server-factory/http.json",
|
||||
"css:config/http/static/default.json",
|
||||
"css:config/identity/access/public.json",
|
||||
"css:config/identity/email/default.json",
|
||||
"css:config/identity/handler/default.json",
|
||||
"css:config/identity/ownership/token.json",
|
||||
"css:config/identity/pod/static.json",
|
||||
"css:config/identity/registration/enabled.json",
|
||||
"css:config/ldp/authentication/dpop-bearer.json",
|
||||
"css:config/ldp/authorization/webacl.json",
|
||||
"css:config/ldp/handler/default.json",
|
||||
"css:config/ldp/metadata-parser/default.json",
|
||||
"css:config/ldp/metadata-writer/default.json",
|
||||
"css:config/ldp/modes/default.json",
|
||||
"css:config/storage/backend/memory.json",
|
||||
"css:config/storage/key-value/resource-store.json",
|
||||
"css:config/storage/middleware/default.json",
|
||||
"css:config/util/auxiliary/acl.json",
|
||||
"css:config/util/identifiers/suffix.json",
|
||||
"css:config/util/index/default.json",
|
||||
"css:config/util/logging/winston.json",
|
||||
"css:config/util/representation-conversion/default.json",
|
||||
"css:config/util/resource-locker/memory.json",
|
||||
"css:config/util/variables/default.json"
|
||||
],
|
||||
"@graph": [
|
||||
{
|
||||
"@id": "urn:solid-server:test:Instances",
|
||||
"@type": "RecordObject",
|
||||
"record": [
|
||||
{
|
||||
"RecordObject:_record_key": "app",
|
||||
"RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/webhooks.json",
|
||||
|
@ -3,7 +3,6 @@
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root.json",
|
||||
"css:config/app/setup/disabled.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/websockets.json",
|
||||
|
@ -1,88 +0,0 @@
|
||||
import type { Operation } from '../../../../src/http/Operation';
|
||||
import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation';
|
||||
import type { RegistrationResponse,
|
||||
RegistrationManager } from '../../../../src/identity/interaction/email-password/util/RegistrationManager';
|
||||
import type { Initializer } from '../../../../src/init/Initializer';
|
||||
import { SetupHandler } from '../../../../src/init/setup/SetupHandler';
|
||||
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
|
||||
import { readJsonStream } from '../../../../src/util/StreamUtil';
|
||||
|
||||
describe('A SetupHandler', (): void => {
|
||||
let operation: Operation;
|
||||
let details: RegistrationResponse;
|
||||
let registrationManager: jest.Mocked<RegistrationManager>;
|
||||
let initializer: jest.Mocked<Initializer>;
|
||||
let handler: SetupHandler;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
operation = {
|
||||
method: 'POST',
|
||||
target: { path: 'http://example.com/setup' },
|
||||
preferences: {},
|
||||
body: new BasicRepresentation(),
|
||||
};
|
||||
|
||||
initializer = {
|
||||
handleSafe: jest.fn(),
|
||||
} as any;
|
||||
|
||||
details = {
|
||||
email: 'alice@test.email',
|
||||
createWebId: true,
|
||||
register: true,
|
||||
createPod: true,
|
||||
};
|
||||
|
||||
registrationManager = {
|
||||
validateInput: jest.fn((input): any => input),
|
||||
register: jest.fn().mockResolvedValue(details),
|
||||
} as any;
|
||||
|
||||
handler = new SetupHandler({ registrationManager, initializer });
|
||||
});
|
||||
|
||||
it('error if no Initializer is defined and initialization is requested.', async(): Promise<void> => {
|
||||
handler = new SetupHandler({});
|
||||
operation.body = new BasicRepresentation(JSON.stringify({ initialize: true }), 'application/json');
|
||||
await expect(handler.handle({ operation })).rejects.toThrow(NotImplementedHttpError);
|
||||
});
|
||||
|
||||
it('error if no RegistrationManager is defined and registration is requested.', async(): Promise<void> => {
|
||||
handler = new SetupHandler({});
|
||||
operation.body = new BasicRepresentation(JSON.stringify({ registration: true }), 'application/json');
|
||||
await expect(handler.handle({ operation })).rejects.toThrow(NotImplementedHttpError);
|
||||
});
|
||||
|
||||
it('calls the Initializer when requested.', async(): Promise<void> => {
|
||||
operation.body = new BasicRepresentation(JSON.stringify({ initialize: true }), 'application/json');
|
||||
const result = await handler.handle({ operation });
|
||||
await expect(readJsonStream(result.data)).resolves.toEqual({ initialize: true, registration: false });
|
||||
expect(result.metadata.contentType).toBe('application/json');
|
||||
expect(initializer.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(registrationManager.validateInput).toHaveBeenCalledTimes(0);
|
||||
expect(registrationManager.register).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('calls the RegistrationManager when requested.', async(): Promise<void> => {
|
||||
const body = { registration: true, email: 'test@example.com' };
|
||||
operation.body = new BasicRepresentation(JSON.stringify(body), 'application/json');
|
||||
const result = await handler.handle({ operation });
|
||||
await expect(readJsonStream(result.data)).resolves.toEqual({ initialize: false, registration: true, ...details });
|
||||
expect(result.metadata.contentType).toBe('application/json');
|
||||
expect(initializer.handleSafe).toHaveBeenCalledTimes(0);
|
||||
expect(registrationManager.validateInput).toHaveBeenCalledTimes(1);
|
||||
expect(registrationManager.register).toHaveBeenCalledTimes(1);
|
||||
expect(registrationManager.validateInput).toHaveBeenLastCalledWith(body, true);
|
||||
expect(registrationManager.register).toHaveBeenLastCalledWith(body, true);
|
||||
});
|
||||
|
||||
it('defaults to an empty JSON body if no data is provided.', async(): Promise<void> => {
|
||||
operation.body = new BasicRepresentation();
|
||||
const result = await handler.handle({ operation });
|
||||
await expect(readJsonStream(result.data)).resolves.toEqual({ initialize: false, registration: false });
|
||||
expect(result.metadata.contentType).toBe('application/json');
|
||||
expect(initializer.handleSafe).toHaveBeenCalledTimes(0);
|
||||
expect(registrationManager.validateInput).toHaveBeenCalledTimes(0);
|
||||
expect(registrationManager.register).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
@ -1,119 +0,0 @@
|
||||
import type { Operation } from '../../../../src/http/Operation';
|
||||
import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation';
|
||||
import type { Representation } from '../../../../src/http/representation/Representation';
|
||||
import { RepresentationMetadata } from '../../../../src/http/representation/RepresentationMetadata';
|
||||
import type { InteractionHandler } from '../../../../src/identity/interaction/InteractionHandler';
|
||||
import { SetupHttpHandler } from '../../../../src/init/setup/SetupHttpHandler';
|
||||
import type { HttpRequest } from '../../../../src/server/HttpRequest';
|
||||
import type { HttpResponse } from '../../../../src/server/HttpResponse';
|
||||
import { getBestPreference } from '../../../../src/storage/conversion/ConversionUtil';
|
||||
import type { RepresentationConverterArgs,
|
||||
RepresentationConverter } from '../../../../src/storage/conversion/RepresentationConverter';
|
||||
import type { KeyValueStorage } from '../../../../src/storage/keyvalue/KeyValueStorage';
|
||||
import { APPLICATION_JSON, APPLICATION_X_WWW_FORM_URLENCODED } from '../../../../src/util/ContentTypes';
|
||||
import { MethodNotAllowedHttpError } from '../../../../src/util/errors/MethodNotAllowedHttpError';
|
||||
import { readableToString } from '../../../../src/util/StreamUtil';
|
||||
import type { TemplateEngine } from '../../../../src/util/templates/TemplateEngine';
|
||||
import { CONTENT_TYPE } from '../../../../src/util/Vocabularies';
|
||||
|
||||
describe('A SetupHttpHandler', (): void => {
|
||||
const request: HttpRequest = {} as any;
|
||||
const response: HttpResponse = {} as any;
|
||||
let operation: Operation;
|
||||
const storageKey = 'completed';
|
||||
let representation: Representation;
|
||||
let interactionHandler: jest.Mocked<InteractionHandler>;
|
||||
let templateEngine: jest.Mocked<TemplateEngine>;
|
||||
let converter: jest.Mocked<RepresentationConverter>;
|
||||
let storage: jest.Mocked<KeyValueStorage<string, any>>;
|
||||
let handler: SetupHttpHandler;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
operation = {
|
||||
method: 'GET',
|
||||
target: { path: 'http://example.com/setup' },
|
||||
preferences: {},
|
||||
body: new BasicRepresentation(),
|
||||
};
|
||||
|
||||
templateEngine = {
|
||||
handleSafe: jest.fn().mockReturnValue(Promise.resolve('<html>')),
|
||||
} as any;
|
||||
|
||||
converter = {
|
||||
handleSafe: jest.fn((input: RepresentationConverterArgs): Representation => {
|
||||
// Just find the best match;
|
||||
const type = getBestPreference(input.preferences.type!, { '*/*': 1 })!;
|
||||
const metadata = new RepresentationMetadata(input.representation.metadata, { [CONTENT_TYPE]: type.value });
|
||||
return new BasicRepresentation(input.representation.data, metadata);
|
||||
}),
|
||||
} as any;
|
||||
|
||||
representation = new BasicRepresentation();
|
||||
interactionHandler = {
|
||||
handleSafe: jest.fn().mockResolvedValue(representation),
|
||||
} as any;
|
||||
|
||||
storage = new Map<string, any>() as any;
|
||||
|
||||
handler = new SetupHttpHandler({
|
||||
converter,
|
||||
storageKey,
|
||||
storage,
|
||||
handler: interactionHandler,
|
||||
templateEngine,
|
||||
});
|
||||
});
|
||||
|
||||
it('only accepts GET and POST operations.', async(): Promise<void> => {
|
||||
operation = {
|
||||
method: 'DELETE',
|
||||
target: { path: 'http://example.com/setup' },
|
||||
preferences: {},
|
||||
body: new BasicRepresentation(),
|
||||
};
|
||||
await expect(handler.handle({ operation, request, response })).rejects.toThrow(MethodNotAllowedHttpError);
|
||||
});
|
||||
|
||||
it('calls the template engine for GET requests.', async(): Promise<void> => {
|
||||
const result = await handler.handle({ operation, request, response });
|
||||
expect(result.data).toBeDefined();
|
||||
await expect(readableToString(result.data!)).resolves.toBe('<html>');
|
||||
expect(result.metadata?.contentType).toBe('text/html');
|
||||
|
||||
// Setup is still enabled since this was a GET request
|
||||
expect(storage.get(storageKey)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns the handler result as 200 response.', async(): Promise<void> => {
|
||||
operation.method = 'POST';
|
||||
const result = await handler.handle({ operation, request, response });
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.data).toBe(representation.data);
|
||||
expect(result.metadata).toBe(representation.metadata);
|
||||
expect(interactionHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(interactionHandler.handleSafe).toHaveBeenLastCalledWith({ operation });
|
||||
|
||||
// Handler is now disabled due to successful POST
|
||||
expect(storage.get(storageKey)).toBe(true);
|
||||
});
|
||||
|
||||
it('converts input bodies to JSON.', async(): Promise<void> => {
|
||||
operation.method = 'POST';
|
||||
operation.body.metadata.contentType = APPLICATION_X_WWW_FORM_URLENCODED;
|
||||
const result = await handler.handle({ operation, request, response });
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.data).toBe(representation.data);
|
||||
expect(result.metadata).toBe(representation.metadata);
|
||||
expect(interactionHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { body, ...partialOperation } = operation;
|
||||
expect(interactionHandler.handleSafe).toHaveBeenLastCalledWith(
|
||||
{ operation: expect.objectContaining(partialOperation) },
|
||||
);
|
||||
expect(interactionHandler.handleSafe.mock.calls[0][0].operation.body.metadata.contentType).toBe(APPLICATION_JSON);
|
||||
|
||||
// Handler is now disabled due to successful POST
|
||||
expect(storage.get(storageKey)).toBe(true);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user