From 5eff035cb3095b43bf76e35dd55abf6e299d02ba Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Mon, 17 Jul 2023 10:00:48 +0200 Subject: [PATCH] feat: Remove setup --- config/app/README.md | 10 -- config/app/setup/disabled.json | 10 -- config/app/setup/handlers/redirect.json | 20 --- config/app/setup/handlers/setup.json | 70 ---------- config/app/setup/optional.json | 23 ---- config/app/setup/required.json | 30 ---- config/default.json | 1 - config/dynamic.json | 1 - config/example-https-file.json | 1 - config/file-acp.json | 1 - config/{file-no-setup.json => file-root.json} | 3 +- config/file.json | 1 - config/http/handler/default.json | 1 - config/http/handler/simple.json | 1 - config/https-file-cli.json | 1 - config/identity/registration/enabled.json | 6 - config/memory-subdomains.json | 1 - config/path-routing.json | 1 - config/quota-file.json | 1 - config/restrict-idp.json | 1 - ...o-setup.json => sparql-endpoint-root.json} | 3 +- config/sparql-endpoint.json | 1 - config/sparql-file-storage.json | 1 - .../architecture/features/http-handler.md | 15 +- .../markdown/usage/identity-provider.md | 4 +- package.json | 2 +- src/index.ts | 4 - src/init/setup/SetupHandler.ts | 83 ------------ src/init/setup/SetupHttpHandler.ts | 116 ---------------- templates/setup/index.html.ejs | 45 ------ templates/setup/input-partial.html.ejs | 79 ----------- test/integration/Setup.test.ts | 128 ------------------ test/integration/config/ldp-with-acp.json | 1 - test/integration/config/ldp-with-auth.json | 1 - .../integration/config/legacy-websockets.json | 1 - test/integration/config/permission-table.json | 1 - test/integration/config/quota-global.json | 1 - test/integration/config/quota-pod.json | 1 - test/integration/config/restricted-idp.json | 1 - .../config/server-dynamic-unsafe.json | 1 - test/integration/config/server-file.json | 1 - test/integration/config/server-memory.json | 1 - .../integration/config/server-middleware.json | 1 - .../integration/config/server-redis-lock.json | 1 - .../config/server-subdomains-unsafe.json | 1 - .../config/server-without-auth.json | 1 - test/integration/config/setup-memory.json | 47 ------- .../config/webhook-notifications.json | 1 - .../config/websocket-notifications.json | 1 - test/unit/init/setup/SetupHandler.test.ts | 88 ------------ test/unit/init/setup/SetupHttpHandler.test.ts | 119 ---------------- 51 files changed, 5 insertions(+), 930 deletions(-) delete mode 100644 config/app/setup/disabled.json delete mode 100644 config/app/setup/handlers/redirect.json delete mode 100644 config/app/setup/handlers/setup.json delete mode 100644 config/app/setup/optional.json delete mode 100644 config/app/setup/required.json rename config/{file-no-setup.json => file-root.json} (90%) rename config/{sparql-endpoint-no-setup.json => sparql-endpoint-root.json} (91%) delete mode 100644 src/init/setup/SetupHandler.ts delete mode 100644 src/init/setup/SetupHttpHandler.ts delete mode 100644 templates/setup/index.html.ejs delete mode 100644 templates/setup/input-partial.html.ejs delete mode 100644 test/integration/Setup.test.ts delete mode 100644 test/integration/config/setup-memory.json delete mode 100644 test/unit/init/setup/SetupHandler.test.ts delete mode 100644 test/unit/init/setup/SetupHttpHandler.test.ts diff --git a/config/app/README.md b/config/app/README.md index 4962643e0..a47b59f72 100644 --- a/config/app/README.md +++ b/config/app/README.md @@ -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. diff --git a/config/app/setup/disabled.json b/config/app/setup/disabled.json deleted file mode 100644 index 38c0df51f..000000000 --- a/config/app/setup/disabled.json +++ /dev/null @@ -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" - } - ] -} diff --git a/config/app/setup/handlers/redirect.json b/config/app/setup/handlers/redirect.json deleted file mode 100644 index b9e816cd9..000000000 --- a/config/app/setup/handlers/redirect.json +++ /dev/null @@ -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 - } - ] -} diff --git a/config/app/setup/handlers/setup.json b/config/app/setup/handlers/setup.json deleted file mode 100644 index e11da9efc..000000000 --- a/config/app/setup/handlers/setup.json +++ /dev/null @@ -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" } - } - ] -} diff --git a/config/app/setup/optional.json b/config/app/setup/optional.json deleted file mode 100644 index adff11b23..000000000 --- a/config/app/setup/optional.json +++ /dev/null @@ -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" } - } - } - ] -} diff --git a/config/app/setup/required.json b/config/app/setup/required.json deleted file mode 100644 index aecd1e1c6..000000000 --- a/config/app/setup/required.json +++ /dev/null @@ -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" } - } - ] - } - } - ] -} diff --git a/config/default.json b/config/default.json index 9db94d94b..1cf9076f6 100644 --- a/config/default.json +++ b/config/default.json @@ -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", diff --git a/config/dynamic.json b/config/dynamic.json index 325995248..7aaf8ea60 100644 --- a/config/dynamic.json +++ b/config/dynamic.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", diff --git a/config/example-https-file.json b/config/example-https-file.json index d902ac5ee..c22177850 100644 --- a/config/example-https-file.json +++ b/config/example-https-file.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", diff --git a/config/file-acp.json b/config/file-acp.json index 070957448..c24601489 100644 --- a/config/file-acp.json +++ b/config/file-acp.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", diff --git a/config/file-no-setup.json b/config/file-root.json similarity index 90% rename from config/file-no-setup.json rename to config/file-root.json index 751d8281e..008be28e5 100644 --- a/config/file-no-setup.json +++ b/config/file-root.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." ] } ] diff --git a/config/file.json b/config/file.json index 1322a772b..94a1d719c 100644 --- a/config/file.json +++ b/config/file.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", diff --git a/config/http/handler/default.json b/config/http/handler/default.json index 5d1740737..c90f00267 100644 --- a/config/http/handler/default.json +++ b/config/http/handler/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" }, diff --git a/config/http/handler/simple.json b/config/http/handler/simple.json index da53d12d7..7976e41e6 100644 --- a/config/http/handler/simple.json +++ b/config/http/handler/simple.json @@ -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" } ] diff --git a/config/https-file-cli.json b/config/https-file-cli.json index 522759a0b..d54421e36 100644 --- a/config/https-file-cli.json +++ b/config/https-file-cli.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", diff --git a/config/identity/registration/enabled.json b/config/identity/registration/enabled.json index 7dd9fb766..969c786b8 100644 --- a/config/identity/registration/enabled.json +++ b/config/identity/registration/enabled.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 } ] } diff --git a/config/memory-subdomains.json b/config/memory-subdomains.json index 9d6216e3e..8af72a0cc 100644 --- a/config/memory-subdomains.json +++ b/config/memory-subdomains.json @@ -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", diff --git a/config/path-routing.json b/config/path-routing.json index 8c061e46d..b127e5add 100644 --- a/config/path-routing.json +++ b/config/path-routing.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", diff --git a/config/quota-file.json b/config/quota-file.json index d282cfa5f..ba1b7d252 100644 --- a/config/quota-file.json +++ b/config/quota-file.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", diff --git a/config/restrict-idp.json b/config/restrict-idp.json index 577ea0140..39329bd07 100644 --- a/config/restrict-idp.json +++ b/config/restrict-idp.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", diff --git a/config/sparql-endpoint-no-setup.json b/config/sparql-endpoint-root.json similarity index 91% rename from config/sparql-endpoint-no-setup.json rename to config/sparql-endpoint-root.json index 6ebfee124..f54420173 100644 --- a/config/sparql-endpoint-no-setup.json +++ b/config/sparql-endpoint-root.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." ] } ] diff --git a/config/sparql-endpoint.json b/config/sparql-endpoint.json index 8825ab468..bf89a3670 100644 --- a/config/sparql-endpoint.json +++ b/config/sparql-endpoint.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", diff --git a/config/sparql-file-storage.json b/config/sparql-file-storage.json index 640d86a84..ceef7628e 100644 --- a/config/sparql-file-storage.json +++ b/config/sparql-file-storage.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", diff --git a/documentation/markdown/architecture/features/http-handler.md b/documentation/markdown/architecture/features/http-handler.md index beb87415a..f9ad06582 100644 --- a/documentation/markdown/architecture/features/http-handler.md +++ b/documentation/markdown/architecture/features/http-handler.md @@ -19,7 +19,6 @@ flowchart LR subgraph WaterfallHandlerArgs[" "] direction TB StaticAssetHandler("StaticAssetHandler
StaticAssetHandler") - SetupHandler("SetupHandler
HttpHandler") OidcHandler("OidcHandler
HttpHandler") NotificationHttpHandler("NotificationHttpHandler
HttpHandler") StorageDescriptionHandler("StorageDescriptionHandler
HttpHandler") @@ -28,8 +27,7 @@ flowchart LR LdpHandler("LdpHandler
HttpHandler") 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 diff --git a/documentation/markdown/usage/identity-provider.md b/documentation/markdown/usage/identity-provider.md index 8dee1d359..efdc636b5 100644 --- a/documentation/markdown/usage/identity-provider.md +++ b/documentation/markdown/usage/identity-provider.md @@ -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. diff --git a/package.json b/package.json index a562e34ee..90de22317 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.ts b/src/index.ts index e94976a5e..fae12556e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'; diff --git a/src/init/setup/SetupHandler.ts b/src/init/setup/SetupHandler.ts deleted file mode 100644 index 9d7f798f4..000000000 --- a/src/init/setup/SetupHandler.ts +++ /dev/null @@ -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 { - const json = operation.body.isEmpty ? {} : await readJsonStream(operation.body.data); - - const output: Record = { 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 { - 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): Promise> { - 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); - } -} diff --git a/src/init/setup/SetupHttpHandler.ts b/src/init/setup/SetupHttpHandler.ts deleted file mode 100644 index 3c8cbbb51..000000000 --- a/src/init/setup/SetupHttpHandler.ts +++ /dev/null @@ -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; - /** - * 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; - 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 { - 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 { - 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 { - // 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); - } -} diff --git a/templates/setup/index.html.ejs b/templates/setup/index.html.ejs deleted file mode 100644 index b5b9923fb..000000000 --- a/templates/setup/index.html.ejs +++ /dev/null @@ -1,45 +0,0 @@ -
- <%- include('./input-partial.html.ejs') %> -
-
-

Server setup complete

-

- Congratulations! - Your Solid server is now ready to use. -
- You can now visit its homepage. -

- -
-

Root Pod

-

- Warning: the root Pod is publicly accessible. -
- Prevent public write and control access to the root - by modifying its ACL document. -

-
- -
- <%- include('../identity/email-password/register-response-partial.html.ejs', { idpIndex: '' }) %> -
-
- - diff --git a/templates/setup/input-partial.html.ejs b/templates/setup/input-partial.html.ejs deleted file mode 100644 index ddbbe8728..000000000 --- a/templates/setup/input-partial.html.ejs +++ /dev/null @@ -1,79 +0,0 @@ -

Set up your Solid server

-

- Your Solid server needs a one-time setup - so it acts exactly the way you want. -

- -
-

- -
- Accounts on this server -
    -
  1. - -

    - This can only be changed in the configuration. - See the general configuration documentation - and the identity specific options to find out how. -

    -
  2. -
  3. - -

    - Any existing root Pod will be disabled. -

    -
  4. -
  5. - -

    - By default, the public has read and write access to the root Pod. -
    - You typically only want to choose this - for rapid testing and development. -
    - This requires registration to be disabled. -

    -
  6. -
-
- -
- Sign up - <%- - include('../identity/email-password/register-partial.html.ejs', { - allowRootPod: allowRootPod, - }) - %> -
- -

-
- - - diff --git a/test/integration/Setup.test.ts b/test/integration/Setup.test.ts deleted file mode 100644 index f8976e975..000000000 --- a/test/integration/Setup.test.ts +++ /dev/null @@ -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 => { - const instances = await instantiateFromConfig( - 'urn:solid-server:test:Instances', - getTestConfigPath('setup-memory.json'), - getDefaultVariables(port, baseUrl), - ) as Record; - ({ app } = instances); - await app.start(); - }); - - afterEach(async(): Promise => { - await app.stop(); - }); - - it('catches all requests.', async(): Promise => { - 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 => { - 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 => { - 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 '); - - // 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 => { - 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); - }); -}); diff --git a/test/integration/config/ldp-with-acp.json b/test/integration/config/ldp-with-acp.json index 896a17e47..01f7abae7 100644 --- a/test/integration/config/ldp-with-acp.json +++ b/test/integration/config/ldp-with-acp.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", "css:config/http/notifications/disabled.json", diff --git a/test/integration/config/ldp-with-auth.json b/test/integration/config/ldp-with-auth.json index ea15659db..fd5eec8d0 100644 --- a/test/integration/config/ldp-with-auth.json +++ b/test/integration/config/ldp-with-auth.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", diff --git a/test/integration/config/legacy-websockets.json b/test/integration/config/legacy-websockets.json index 602ced466..19f899f22 100644 --- a/test/integration/config/legacy-websockets.json +++ b/test/integration/config/legacy-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/legacy-websockets.json", diff --git a/test/integration/config/permission-table.json b/test/integration/config/permission-table.json index ad95277ea..1cbd3553e 100644 --- a/test/integration/config/permission-table.json +++ b/test/integration/config/permission-table.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", diff --git a/test/integration/config/quota-global.json b/test/integration/config/quota-global.json index a0a4c0218..3a98fa6ac 100644 --- a/test/integration/config/quota-global.json +++ b/test/integration/config/quota-global.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", diff --git a/test/integration/config/quota-pod.json b/test/integration/config/quota-pod.json index 6e8e3fc28..de0688acb 100644 --- a/test/integration/config/quota-pod.json +++ b/test/integration/config/quota-pod.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", diff --git a/test/integration/config/restricted-idp.json b/test/integration/config/restricted-idp.json index f2b4ebc71..de0143a05 100644 --- a/test/integration/config/restricted-idp.json +++ b/test/integration/config/restricted-idp.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", diff --git a/test/integration/config/server-dynamic-unsafe.json b/test/integration/config/server-dynamic-unsafe.json index 63b22160d..304acfcd2 100644 --- a/test/integration/config/server-dynamic-unsafe.json +++ b/test/integration/config/server-dynamic-unsafe.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", diff --git a/test/integration/config/server-file.json b/test/integration/config/server-file.json index da753c254..647c68516 100644 --- a/test/integration/config/server-file.json +++ b/test/integration/config/server-file.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", diff --git a/test/integration/config/server-memory.json b/test/integration/config/server-memory.json index 8ee1c548e..f4f4de76e 100644 --- a/test/integration/config/server-memory.json +++ b/test/integration/config/server-memory.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", diff --git a/test/integration/config/server-middleware.json b/test/integration/config/server-middleware.json index e2a23835b..02d0f44a0 100644 --- a/test/integration/config/server-middleware.json +++ b/test/integration/config/server-middleware.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", diff --git a/test/integration/config/server-redis-lock.json b/test/integration/config/server-redis-lock.json index 86c89e086..114b7d7da 100644 --- a/test/integration/config/server-redis-lock.json +++ b/test/integration/config/server-redis-lock.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", diff --git a/test/integration/config/server-subdomains-unsafe.json b/test/integration/config/server-subdomains-unsafe.json index aed0fe9ee..95a243ad1 100644 --- a/test/integration/config/server-subdomains-unsafe.json +++ b/test/integration/config/server-subdomains-unsafe.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", diff --git a/test/integration/config/server-without-auth.json b/test/integration/config/server-without-auth.json index bcb231de3..debfe7f1f 100644 --- a/test/integration/config/server-without-auth.json +++ b/test/integration/config/server-without-auth.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", diff --git a/test/integration/config/setup-memory.json b/test/integration/config/setup-memory.json deleted file mode 100644 index b59f8b9af..000000000 --- a/test/integration/config/setup-memory.json +++ /dev/null @@ -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" } - } - ] - } - ] -} diff --git a/test/integration/config/webhook-notifications.json b/test/integration/config/webhook-notifications.json index d3f21adeb..49b50bc2d 100644 --- a/test/integration/config/webhook-notifications.json +++ b/test/integration/config/webhook-notifications.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/webhooks.json", diff --git a/test/integration/config/websocket-notifications.json b/test/integration/config/websocket-notifications.json index 80b7acdfd..f55e843e4 100644 --- a/test/integration/config/websocket-notifications.json +++ b/test/integration/config/websocket-notifications.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", diff --git a/test/unit/init/setup/SetupHandler.test.ts b/test/unit/init/setup/SetupHandler.test.ts deleted file mode 100644 index e703e273b..000000000 --- a/test/unit/init/setup/SetupHandler.test.ts +++ /dev/null @@ -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; - let initializer: jest.Mocked; - let handler: SetupHandler; - - beforeEach(async(): Promise => { - 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 => { - 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 => { - 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 => { - 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 => { - 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 => { - 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); - }); -}); diff --git a/test/unit/init/setup/SetupHttpHandler.test.ts b/test/unit/init/setup/SetupHttpHandler.test.ts deleted file mode 100644 index cebe6435a..000000000 --- a/test/unit/init/setup/SetupHttpHandler.test.ts +++ /dev/null @@ -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; - let templateEngine: jest.Mocked; - let converter: jest.Mocked; - let storage: jest.Mocked>; - let handler: SetupHttpHandler; - - beforeEach(async(): Promise => { - operation = { - method: 'GET', - target: { path: 'http://example.com/setup' }, - preferences: {}, - body: new BasicRepresentation(), - }; - - templateEngine = { - handleSafe: jest.fn().mockReturnValue(Promise.resolve('')), - } 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() as any; - - handler = new SetupHttpHandler({ - converter, - storageKey, - storage, - handler: interactionHandler, - templateEngine, - }); - }); - - it('only accepts GET and POST operations.', async(): Promise => { - 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 => { - const result = await handler.handle({ operation, request, response }); - expect(result.data).toBeDefined(); - await expect(readableToString(result.data!)).resolves.toBe(''); - 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 => { - 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 => { - 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); - }); -});