From b592d449ebece81875e37ccc0fe8dfa4a3124a70 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Wed, 15 Sep 2021 16:56:18 +0200 Subject: [PATCH] feat: Integrate setup behaviour This adds options for enabling setup to the config folder. All default configs with permanent storage (file/sparql) are configured to require setup at server start. Memory-based configs merely have it as an option. --- config/app/README.md | 13 +- config/app/init/default.json | 8 +- .../app/init/initialize-prefilled-root.json | 17 ++ config/app/init/initialize-root.json | 17 ++ .../app/init/initializers/prefilled-root.json | 18 ++ config/app/init/initializers/root.json | 2 +- config/app/setup/disabled.json | 13 ++ config/app/setup/handlers/redirect.json | 14 ++ config/app/setup/handlers/setup.json | 34 +++ config/app/setup/optional.json | 24 +++ config/app/setup/required.json | 31 +++ config/default.json | 3 +- config/dynamic.json | 1 + config/example-https-file.json | 1 + config/file-no-setup.json | 38 ++++ config/file.json | 1 + config/http/handler/default.json | 4 + config/memory-subdomains.json | 3 +- config/path-routing.json | 3 +- config/sparql-endpoint-no-setup.json | 41 ++++ config/sparql-endpoint.json | 1 + config/storage/key-value/memory.json | 5 + config/storage/key-value/resource-store.json | 8 + src/index.ts | 1 + src/util/handlers/StaticHandler.ts | 22 ++ .../email-password/register-partial.html.ejs | 203 ++++++++++++++++++ .../register-response-partial.html.ejs | 38 ++++ .../email-password/register-response.html.ejs | 39 +--- .../identity/email-password/register.html.ejs | 179 +-------------- templates/root/{ => empty}/.acl | 7 +- templates/root/{ => empty}/.meta | 0 templates/root/prefilled/.acl | 10 + templates/root/prefilled/.meta | 7 + templates/root/{ => prefilled}/index.html | 2 +- templates/setup/index.html.ejs | 88 ++++++++ templates/setup/response.html.ejs | 24 +++ .../integration/LdpHandlerWithoutAuth.test.ts | 10 - test/integration/Setup.test.ts | 117 ++++++++++ test/integration/config/ldp-with-auth.json | 3 +- test/integration/config/run-with-redlock.json | 3 +- .../config/server-dynamic-unsafe.json | 3 +- test/integration/config/server-memory.json | 3 +- .../config/server-subdomains-unsafe.json | 3 +- .../config/server-without-auth.json | 3 +- test/integration/config/setup-memory.json | 45 ++++ test/unit/util/handlers/StaticHandler.test.ts | 18 ++ test/util/Util.ts | 1 + 47 files changed, 883 insertions(+), 246 deletions(-) create mode 100644 config/app/init/initialize-prefilled-root.json create mode 100644 config/app/init/initialize-root.json create mode 100644 config/app/init/initializers/prefilled-root.json create mode 100644 config/app/setup/disabled.json create mode 100644 config/app/setup/handlers/redirect.json create mode 100644 config/app/setup/handlers/setup.json create mode 100644 config/app/setup/optional.json create mode 100644 config/app/setup/required.json create mode 100644 config/file-no-setup.json create mode 100644 config/sparql-endpoint-no-setup.json create mode 100644 src/util/handlers/StaticHandler.ts create mode 100644 templates/identity/email-password/register-partial.html.ejs create mode 100644 templates/identity/email-password/register-response-partial.html.ejs rename templates/root/{ => empty}/.acl (59%) rename templates/root/{ => empty}/.meta (100%) create mode 100644 templates/root/prefilled/.acl create mode 100644 templates/root/prefilled/.meta rename templates/root/{ => prefilled}/index.html (97%) create mode 100644 templates/setup/index.html.ejs create mode 100644 templates/setup/response.html.ejs create mode 100644 test/integration/Setup.test.ts create mode 100644 test/integration/config/setup-memory.json create mode 100644 test/unit/util/handlers/StaticHandler.test.ts diff --git a/config/app/README.md b/config/app/README.md index 29bc738f7..1e7b2965a 100644 --- a/config/app/README.md +++ b/config/app/README.md @@ -8,4 +8,15 @@ This is the entry point to the main server setup. ## Init Contains a list of initializer that need to be run when starting the server. -* *default*: The default setup that makes sure the root container has the necessary resources. +* *default*: The default setup. The ParallelHandler can be used to add custom Initializers. +* *initialize-root*: Makes sure the root container has the necessary resources to function properly. + This is only relevant if setup is disabled but root container access is still required. +* *initialize-prefilled-root*: Similar to `initialize-root` but adds some introductory resources to the root container. + +## 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. diff --git a/config/app/init/default.json b/config/app/init/default.json index 2eecaf4c5..6a41eecf4 100644 --- a/config/app/init/default.json +++ b/config/app/init/default.json @@ -1,8 +1,7 @@ { "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "import": [ - "files-scs:config/app/init/base/init.json", - "files-scs:config/app/init/initializers/root.json" + "files-scs:config/app/init/base/init.json" ], "@graph": [ { @@ -10,7 +9,10 @@ "@id": "urn:solid-server:default:ParallelInitializer", "@type": "ParallelHandler", "handlers": [ - { "@id": "urn:solid-server:default:RootInitializer" } + { + "comment": "This handler is here because having this array empty gives Components.js errors.", + "@type": "StaticHandler" + } ] } ] diff --git a/config/app/init/initialize-prefilled-root.json b/config/app/init/initialize-prefilled-root.json new file mode 100644 index 000000000..a9ef56542 --- /dev/null +++ b/config/app/init/initialize-prefilled-root.json @@ -0,0 +1,17 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs:config/app/init/base/init.json", + "files-scs:config/app/init/initializers/prefilled-root.json" + ], + "@graph": [ + { + "comment": "These handlers are called whenever the server is started, and can be used to ensure that all necessary resources for booting are available.", + "@id": "urn:solid-server:default:ParallelInitializer", + "@type": "ParallelHandler", + "handlers": [ + { "@id": "urn:solid-server:default:RootInitializer" } + ] + } + ] +} diff --git a/config/app/init/initialize-root.json b/config/app/init/initialize-root.json new file mode 100644 index 000000000..2eecaf4c5 --- /dev/null +++ b/config/app/init/initialize-root.json @@ -0,0 +1,17 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs:config/app/init/base/init.json", + "files-scs:config/app/init/initializers/root.json" + ], + "@graph": [ + { + "comment": "These handlers are called whenever the server is started, and can be used to ensure that all necessary resources for booting are available.", + "@id": "urn:solid-server:default:ParallelInitializer", + "@type": "ParallelHandler", + "handlers": [ + { "@id": "urn:solid-server:default:RootInitializer" } + ] + } + ] +} diff --git a/config/app/init/initializers/prefilled-root.json b/config/app/init/initializers/prefilled-root.json new file mode 100644 index 000000000..ab25afa90 --- /dev/null +++ b/config/app/init/initializers/prefilled-root.json @@ -0,0 +1,18 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "@graph": [ + { + "comment": "Makes sure the root container exists and contains the necessary resources.", + "@id": "urn:solid-server:default:RootInitializer", + "@type": "RootInitializer", + "baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }, + "store": { "@id": "urn:solid-server:default:ResourceStore" }, + "generator": { + "@type": "TemplatedResourcesGenerator", + "templateFolder": "@css:templates/root/prefilled", + "factory": { "@type": "ExtensionBasedMapperFactory" }, + "templateEngine": { "@type": "HandlebarsTemplateEngine" } + } + } + ] +} diff --git a/config/app/init/initializers/root.json b/config/app/init/initializers/root.json index ef92f7275..852c44771 100644 --- a/config/app/init/initializers/root.json +++ b/config/app/init/initializers/root.json @@ -9,7 +9,7 @@ "store": { "@id": "urn:solid-server:default:ResourceStore" }, "generator": { "@type": "TemplatedResourcesGenerator", - "templateFolder": "@css:templates/root", + "templateFolder": "@css:templates/root/empty", "factory": { "@type": "ExtensionBasedMapperFactory" }, "templateEngine": { "@type": "HandlebarsTemplateEngine" } } diff --git a/config/app/setup/disabled.json b/config/app/setup/disabled.json new file mode 100644 index 000000000..93e8e08d4 --- /dev/null +++ b/config/app/setup/disabled.json @@ -0,0 +1,13 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs:config/app/init/initializers/root.json" + ], + "@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 new file mode 100644 index 000000000..35d391b0a --- /dev/null +++ b/config/app/setup/handlers/redirect.json @@ -0,0 +1,14 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "@graph": [ + { + "comment": "Redirects all request to the setup.", + "@id": "urn:solid-server:default:SetupRedirectHandler", + "@type": "RedirectAllHttpHandler", + "args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }, + "args_target": "/setup", + "args_targetExtractor": { "@id": "urn:solid-server:default:TargetExtractor" }, + "args_responseWriter": { "@id": "urn:solid-server:default:ResponseWriter" } + }, + ] +} diff --git a/config/app/setup/handlers/setup.json b/config/app/setup/handlers/setup.json new file mode 100644 index 000000000..a1c82a25e --- /dev/null +++ b/config/app/setup/handlers/setup.json @@ -0,0 +1,34 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs:config/app/init/initializers/root.json" + ], + "@graph": [ + { + "comment": "Handles everything related to the first-time server setup.", + "@id": "urn:solid-server:default:SetupHttpHandler", + "@type": "SetupHttpHandler", + "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_initializer": { "@id": "urn:solid-server:default:RootInitializer" }, + "args_registrationManager": { "@id": "urn:solid-server:default:SetupRegistrationManager" }, + "args_converter": { "@id": "urn:solid-server:default:RepresentationConverter" }, + "args_storageKey": "setupCompleted-1.0", + "args_storage": { "@id": "urn:solid-server:default:SetupStorage" }, + "args_viewTemplate": "@css:templates/setup/index.html.ejs", + "args_responseTemplate": "@css:templates/setup/response.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" } + } + ] +} diff --git a/config/app/setup/optional.json b/config/app/setup/optional.json new file mode 100644 index 000000000..a52a08a76 --- /dev/null +++ b/config/app/setup/optional.json @@ -0,0 +1,24 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs: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-1.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_allowedMethods": [ "*" ], + "args_allowedPathNames": [ "/setup" ], + "args_handler": { "@id": "urn:solid-server:default:SetupHttpHandler" } + } + } + ] +} diff --git a/config/app/setup/required.json b/config/app/setup/required.json new file mode 100644 index 000000000..33e4c5c9d --- /dev/null +++ b/config/app/setup/required.json @@ -0,0 +1,31 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs:config/app/setup/handlers/redirect.json", + "files-scs: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-1.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_allowedMethods": [ "*" ], + "args_allowedPathNames": [ "/setup" ], + "args_handler": { "@id": "urn:solid-server:default:SetupHttpHandler" } + } + ] + } + } + ] +} diff --git a/config/default.json b/config/default.json index 71f0a800f..1e519ead6 100644 --- a/config/default.json +++ b/config/default.json @@ -2,7 +2,8 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "import": [ "files-scs:config/app/main/default.json", - "files-scs:config/app/init/default.json", + "files-scs:config/app/init/initialize-prefilled-root.json", + "files-scs:config/app/setup/optional.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/websockets.json", "files-scs:config/http/server-factory/websockets.json", diff --git a/config/dynamic.json b/config/dynamic.json index 05248946f..3dc1fc05b 100644 --- a/config/dynamic.json +++ b/config/dynamic.json @@ -3,6 +3,7 @@ "import": [ "files-scs:config/app/main/default.json", "files-scs:config/app/init/default.json", + "files-scs:config/app/setup/required.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/websockets.json", "files-scs:config/http/server-factory/websockets.json", diff --git a/config/example-https-file.json b/config/example-https-file.json index 04c1fe27b..ea2d1ce56 100644 --- a/config/example-https-file.json +++ b/config/example-https-file.json @@ -3,6 +3,7 @@ "import": [ "files-scs:config/app/main/default.json", "files-scs:config/app/init/default.json", + "files-scs:config/app/setup/required.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/websockets.json", diff --git a/config/file-no-setup.json b/config/file-no-setup.json new file mode 100644 index 000000000..b2cc81153 --- /dev/null +++ b/config/file-no-setup.json @@ -0,0 +1,38 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs:config/app/main/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/disabled.json", + "files-scs:config/http/handler/default.json", + "files-scs:config/http/middleware/websockets.json", + "files-scs:config/http/server-factory/websockets.json", + "files-scs:config/http/static/default.json", + "files-scs:config/identity/email/default.json", + "files-scs:config/identity/handler/default.json", + "files-scs:config/identity/ownership/token.json", + "files-scs:config/identity/pod/static.json", + "files-scs:config/identity/registration/enabled.json", + "files-scs:config/ldp/authentication/dpop-bearer.json", + "files-scs:config/ldp/authorization/webacl.json", + "files-scs:config/ldp/handler/default.json", + "files-scs:config/ldp/metadata-parser/default.json", + "files-scs:config/ldp/metadata-writer/default.json", + "files-scs:config/ldp/permissions/acl.json", + "files-scs:config/storage/backend/file.json", + "files-scs:config/storage/key-value/resource-store.json", + "files-scs:config/storage/middleware/default.json", + "files-scs:config/util/auxiliary/acl.json", + "files-scs:config/util/identifiers/suffix.json", + "files-scs:config/util/index/default.json", + "files-scs:config/util/logging/winston.json", + "files-scs:config/util/representation-conversion/default.json", + "files-scs:config/util/resource-locker/memory.json", + "files-scs:config/util/variables/default.json" + ], + "@graph": [ + { + "comment": "A single-pod server that stores its resources on disk." + } + ] +} diff --git a/config/file.json b/config/file.json index 03bf127a9..b8190db4a 100644 --- a/config/file.json +++ b/config/file.json @@ -3,6 +3,7 @@ "import": [ "files-scs:config/app/main/default.json", "files-scs:config/app/init/default.json", + "files-scs:config/app/setup/required.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/websockets.json", "files-scs:config/http/server-factory/websockets.json", diff --git a/config/http/handler/default.json b/config/http/handler/default.json index 14001848d..0de9adf89 100644 --- a/config/http/handler/default.json +++ b/config/http/handler/default.json @@ -1,5 +1,8 @@ { "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs:config/app/init/initializers/root.json" + ], "@graph": [ { "comment": "These are all the handlers a request will go through until it is handled.", @@ -11,6 +14,7 @@ "@type": "WaterfallHandler", "handlers": [ { "@id": "urn:solid-server:default:StaticAssetHandler" }, + { "@id": "urn:solid-server:default:SetupHandler" }, { "@id": "urn:solid-server:default:IdentityProviderHandler" }, { "@id": "urn:solid-server:default:LdpHandler" } ] diff --git a/config/memory-subdomains.json b/config/memory-subdomains.json index 51fbe6611..a60168ccd 100644 --- a/config/memory-subdomains.json +++ b/config/memory-subdomains.json @@ -2,7 +2,8 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "import": [ "files-scs:config/app/main/default.json", - "files-scs:config/app/init/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/optional.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/websockets.json", "files-scs:config/http/server-factory/websockets.json", diff --git a/config/path-routing.json b/config/path-routing.json index 8714594eb..8cca10147 100644 --- a/config/path-routing.json +++ b/config/path-routing.json @@ -2,7 +2,8 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "import": [ "files-scs:config/app/main/default.json", - "files-scs:config/app/init/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/disabled.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/websockets.json", "files-scs:config/http/server-factory/websockets.json", diff --git a/config/sparql-endpoint-no-setup.json b/config/sparql-endpoint-no-setup.json new file mode 100644 index 000000000..15ce12c11 --- /dev/null +++ b/config/sparql-endpoint-no-setup.json @@ -0,0 +1,41 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs:config/app/main/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/disabled.json", + "files-scs:config/http/handler/default.json", + "files-scs:config/http/middleware/websockets.json", + "files-scs:config/http/server-factory/websockets.json", + "files-scs:config/http/static/default.json", + "files-scs:config/identity/email/default.json", + "files-scs:config/identity/handler/default.json", + "files-scs:config/identity/ownership/token.json", + "files-scs:config/identity/pod/static.json", + "files-scs:config/identity/registration/enabled.json", + "files-scs:config/ldp/authentication/dpop-bearer.json", + "files-scs:config/ldp/authorization/webacl.json", + "files-scs:config/ldp/handler/default.json", + "files-scs:config/ldp/metadata-parser/default.json", + "files-scs:config/ldp/metadata-writer/default.json", + "files-scs:config/ldp/permissions/acl.json", + "files-scs:config/storage/backend/sparql.json", + "files-scs:config/storage/key-value/memory.json", + "files-scs:config/storage/middleware/default.json", + "files-scs:config/util/auxiliary/acl.json", + "files-scs:config/util/identifiers/suffix.json", + "files-scs:config/util/index/default.json", + "files-scs:config/util/logging/winston.json", + "files-scs:config/util/representation-conversion/default.json", + "files-scs:config/util/resource-locker/memory.json", + "files-scs:config/util/variables/default.json" + ], + "@graph": [ + { + "comment": [ + "A single-pod server that stores its resources in a SPARQL endpoint.", + "This server only supports RDF data. For this reason it can not use its resource store for internal key/value storage." + ] + } + ] +} diff --git a/config/sparql-endpoint.json b/config/sparql-endpoint.json index e27fc343a..5d82ac86b 100644 --- a/config/sparql-endpoint.json +++ b/config/sparql-endpoint.json @@ -3,6 +3,7 @@ "import": [ "files-scs:config/app/main/default.json", "files-scs:config/app/init/default.json", + "files-scs:config/app/setup/required.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/websockets.json", "files-scs:config/http/server-factory/websockets.json", diff --git a/config/storage/key-value/memory.json b/config/storage/key-value/memory.json index 5777bb5f1..783f86b79 100644 --- a/config/storage/key-value/memory.json +++ b/config/storage/key-value/memory.json @@ -28,6 +28,11 @@ "comment": "Storage used for account management.", "@id": "urn:solid-server:default:AccountStorage", "@type": "MemoryMapStorage" + }, + { + "comment": "Storage used by setup components.", + "@id": "urn:solid-server:default:SetupStorage", + "@type": "MemoryMapStorage" } ] } diff --git a/config/storage/key-value/resource-store.json b/config/storage/key-value/resource-store.json index 38efa90a3..be4a89174 100644 --- a/config/storage/key-value/resource-store.json +++ b/config/storage/key-value/resource-store.json @@ -47,6 +47,14 @@ "baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }, "container": "/.internal/accounts/" }, + { + "comment": "Storage used by setup components.", + "@id": "urn:solid-server:default:SetupStorage", + "@type": "JsonResourceStorage", + "source": { "@id": "urn:solid-server:default:ResourceStore" }, + "baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }, + "container": "/.internal/setup/" + }, { "comment": "Block external access to the storage containers to avoid exposing internal data.", "@id": "urn:solid-server:default:PathBasedAuthorizer", diff --git a/src/index.ts b/src/index.ts index 82e90e6a6..703558162 100644 --- a/src/index.ts +++ b/src/index.ts @@ -317,6 +317,7 @@ export * from './util/handlers/BooleanHandler'; export * from './util/handlers/ConditionalHandler'; export * from './util/handlers/ParallelHandler'; export * from './util/handlers/SequenceHandler'; +export * from './util/handlers/StaticHandler'; export * from './util/handlers/UnsupportedAsyncHandler'; export * from './util/handlers/WaterfallHandler'; diff --git a/src/util/handlers/StaticHandler.ts b/src/util/handlers/StaticHandler.ts new file mode 100644 index 000000000..38f47a610 --- /dev/null +++ b/src/util/handlers/StaticHandler.ts @@ -0,0 +1,22 @@ +import { AsyncHandler } from './AsyncHandler'; + +/** + * A handler that always resolves and always returns the stored value. + * Will return undefined if no value is stored. + * + * The generic type extends `any` due to Components.js requirements. + */ +// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint +export class StaticHandler extends AsyncHandler { + private readonly value?: T; + + public constructor(value?: T) { + super(); + this.value = value; + } + + public async handle(): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + return this.value!; + } +} diff --git a/templates/identity/email-password/register-partial.html.ejs b/templates/identity/email-password/register-partial.html.ejs new file mode 100644 index 000000000..638f3a5b4 --- /dev/null +++ b/templates/identity/email-password/register-partial.html.ejs @@ -0,0 +1,203 @@ +<% const isBlankForm = !('prefilled' in locals); %> +<% prefilled = locals.prefilled || {}; %> + +
+ Your WebID +

+ A WebID is a unique identifier for you + in the form of a URL. +
+ You WebID lets you log in to Solid apps + and access non-public data in Pods. +

+
    +
  1. + +

    + Please also create a Pod below, since your WebID will be stored there. +

    +
  2. +
  3. + +
      +
    1. + + +
    2. +
    3. + +
    4. +
    +
  4. +
+
+ +
+ Your Pod +

+ A Pod is a place to store your data. +
+ If you create a new WebID, you must also create a Pod to store that WebID. +

+
    +
  1. + +
      +
    1. + +
    2. +
    3. + +
        +
      1. + + +
      2. +
      +
    4. +
    +
  2. +
+
+ +
+ Your account +
+

+ Choose the credentials you want to use to log in to this server in the future. +

+
    +
  1. + + +
  2. +
+
    +
  1. + + +
  2. +
  3. + + +
  4. +
+
+ +
+ + diff --git a/templates/identity/email-password/register-response-partial.html.ejs b/templates/identity/email-password/register-response-partial.html.ejs new file mode 100644 index 000000000..0c141edc9 --- /dev/null +++ b/templates/identity/email-password/register-response-partial.html.ejs @@ -0,0 +1,38 @@ +<% if (createPod) { %> +

Your new Pod

+

+ Your new Pod is located at <%= podBaseUrl %>. +
+ You can store your documents and data there. +

+<% } %> + +<% if (createWebId) { %> +

Your new WebID

+

+ Your new WebID is <%= webId %>. +
+ You can use this identifier to interact with Solid pods and apps. +

+<% } %> + +<% if (register) { %> +

Your new account

+

+ Via your email address <%= email %>, + <% if (authenticating) { %> + you can now log in + <% } else { %> + this server lets you log in to Solid apps + <% } %> + with your WebID <%= webId %> +

+ <% if (!createWebId) { %> +

+ You will need to add the triple + <%= `<${webId}> <${oidcIssuer}>.`%> + to your existing WebID document <%= webId %> + to indicate that you trust this server as a login provider. +

+ <% } %> +<% } %> diff --git a/templates/identity/email-password/register-response.html.ejs b/templates/identity/email-password/register-response.html.ejs index b6f667dd5..bb0689c8d 100644 --- a/templates/identity/email-password/register-response.html.ejs +++ b/templates/identity/email-password/register-response.html.ejs @@ -4,41 +4,4 @@ We wish you an exciting experience!

-<% if (createPod) { %> -

Your new Pod

-

- Your new Pod is located at <%= podBaseUrl %>. -
- You can store your documents and data there. -

-<% } %> - -<% if (createWebId) { %> -

Your new WebID

-

- Your new WebID is <%= webId %>. -
- You can use this identifier to interact with Solid pods and apps. -

-<% } %> - -<% if (register) { %> -

Your new account

-

- Via your email address <%= email %>, - <% if (authenticating) { %> - you can now log in - <% } else { %> - this server lets you log in to Solid apps - <% } %> - with your WebID <%= webId %> -

- <% if (!createWebId) { %> -

- You will need to add the triple - <%= `<${webId}> <${oidcIssuer}>.`%> - to your existing WebID document <%= webId %> - to indicate that you trust this server as a login provider. -

- <% } %> -<% } %> +<%- include('./register-response-partial.html.ejs') %> diff --git a/templates/identity/email-password/register.html.ejs b/templates/identity/email-password/register.html.ejs index 7ce12accb..23d0bbbca 100644 --- a/templates/identity/email-password/register.html.ejs +++ b/templates/identity/email-password/register.html.ejs @@ -1,188 +1,11 @@

Sign up

- <% const isBlankForm = !('prefilled' in locals); %> - <% prefilled = locals.prefilled || {}; %> <% if (locals.message) { %>

Error: <%= message %>

<% } %> -
- Your WebID -

- A WebID is a unique identifier for you - in the form of a URL. -
- You WebID lets you log in to Solid apps - and access non-public data in Pods. -

-
    -
  1. - -

    - Please also create a Pod below, since your WebID will be stored there. -

    -
  2. -
  3. - -
      -
    1. - - -
    2. -
    3. - -
    4. -
    -
  4. -
-
- -
- Your Pod -

- A Pod is a place to store your data. -
- If you create a new WebID, you must also create a Pod to store that WebID. -

-
    -
  1. - -
      -
    1. - - -
    2. -
    -
  2. -
-
- -
- Your account -
-

- Choose the credentials you want to use to log in to this server in the future. -

-
    -
  1. - - -
  2. -
-
    -
  1. - - -
  2. -
  3. - - -
  4. -
-
- -
+ <%- include('./register-partial.html.ejs', { allowRoot: false, formId: 'mainForm' }) %>

- - diff --git a/templates/root/.acl b/templates/root/empty/.acl similarity index 59% rename from templates/root/.acl rename to templates/root/empty/.acl index 107adcc82..65b4fa2b8 100644 --- a/templates/root/.acl +++ b/templates/root/empty/.acl @@ -1,13 +1,10 @@ +# Root ACL resource generated by the Community Server to allow public access @prefix acl: . @prefix foaf: . <#authorization> a acl:Authorization; acl:agentClass foaf:Agent; - acl:mode acl:Read; - acl:mode acl:Write; - acl:mode acl:Append; - acl:mode acl:Delete; - acl:mode acl:Control; + acl:mode acl:Read, acl:Write, acl:Append, acl:Control; acl:accessTo <./>; acl:default <./>. diff --git a/templates/root/.meta b/templates/root/empty/.meta similarity index 100% rename from templates/root/.meta rename to templates/root/empty/.meta diff --git a/templates/root/prefilled/.acl b/templates/root/prefilled/.acl new file mode 100644 index 000000000..65b4fa2b8 --- /dev/null +++ b/templates/root/prefilled/.acl @@ -0,0 +1,10 @@ +# Root ACL resource generated by the Community Server to allow public access +@prefix acl: . +@prefix foaf: . + +<#authorization> + a acl:Authorization; + acl:agentClass foaf:Agent; + acl:mode acl:Read, acl:Write, acl:Append, acl:Control; + acl:accessTo <./>; + acl:default <./>. diff --git a/templates/root/prefilled/.meta b/templates/root/prefilled/.meta new file mode 100644 index 000000000..8d5a9bc50 --- /dev/null +++ b/templates/root/prefilled/.meta @@ -0,0 +1,7 @@ +@prefix pim: . + +# It is imperative the root container is marked as a pim:Storage : +# Solid, §4.1: "Servers exposing the storage resource MUST advertise by including the HTTP Link header +# with rel="type" targeting http://www.w3.org/ns/pim/space#Storage when responding to storage’s request URI." +# https://solid.github.io/specification/protocol#storage +<> a pim:Storage. diff --git a/templates/root/index.html b/templates/root/prefilled/index.html similarity index 97% rename from templates/root/index.html rename to templates/root/prefilled/index.html index 91bb1b163..905b39078 100644 --- a/templates/root/index.html +++ b/templates/root/prefilled/index.html @@ -59,7 +59,7 @@
  • Prevent public write and control access to the root Pod - by modifying .acl. + by modifying .acl.
  • Disable Pod registration diff --git a/templates/setup/index.html.ejs b/templates/setup/index.html.ejs new file mode 100644 index 000000000..78632009f --- /dev/null +++ b/templates/setup/index.html.ejs @@ -0,0 +1,88 @@ +

    Welcome to Solid

    +

    + This server implements + the Solid protocol + so you can create your own Solid Pod + and identity. +

    + +

    Making this server public

    +

    + Before making this server public, + you might want to disable Pod registration + by changing + the configuration. +

    + +

    Setting up the server

    +

    + The default configuration stores data only in memory, + so be sure to choose a configuration that saves data to disk. + If you are exposing this server publicly, + read the guidelines below. +

    +

    + When using the file-based version of the server, + you can easily choose any folder on your disk to use as root. +
    + Use the --help switch to learn more. +

    +

    + To make sure the server is set up exactly as you want it, + please fill in the form below. +

    +

    + In case you want to automate the server initialization and want to get rid of this setup screen, + update your config with new imports from config/app/setup/ and possibly config/app/init/. +

    + +
    + <% const safePrefilled = locals.prefilled || {}; %> + + <% if (locals.message) { %> +

    <%= message %>

    + <% } %> +
    + Choose options +
      +
    1. + +

      + This defaults to public access for everyone. + Disabling this makes it impossible to access the root container and add resources, + but new pods can still be created through registration, + which is ideal if you only want data to be edited in the pods. + This option is irrelevant when creating a root pod with the option below. +

      +
    2. +
    3. + +
    4. +
    +
    + +
    + <%- include('../identity/email-password/register-partial.html.ejs', { allowRoot: true, formId: 'mainForm' }) %> +
    + +

    +
    + + diff --git a/templates/setup/response.html.ejs b/templates/setup/response.html.ejs new file mode 100644 index 000000000..b3568f651 --- /dev/null +++ b/templates/setup/response.html.ejs @@ -0,0 +1,24 @@ +

    Server successfully set up

    + +<% if (initialize && !registration) { %> +

    + You have chosen to allow the root container to be accessible. + Prevent public write and control access to the root + by modifying .acl. +

    +<% } %> + +<% if (registration) { %> +<%- include('../identity/email-password/register-response-partial.html.ejs', { authenticating: false }) %> +<% } %> + +

    Have a wonderful Solid experience

    +

    + Learn more about Solid + at solidproject.org. +

    +

    + You are warmly invited + to share your experiences + and to report any bugs you encounter. +

    diff --git a/test/integration/LdpHandlerWithoutAuth.test.ts b/test/integration/LdpHandlerWithoutAuth.test.ts index 762626296..a2d3603a7 100644 --- a/test/integration/LdpHandlerWithoutAuth.test.ts +++ b/test/integration/LdpHandlerWithoutAuth.test.ts @@ -72,13 +72,6 @@ describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeC expect(response.headers.get('link')).toContain(`<${PIM.Storage}>; rel="type"`); }); - it('can read the root container index page when asking for HTML.', async(): Promise => { - const response = await getResource(baseUrl, { accept: 'text/html' }, { contentType: 'text/html' }); - - await expect(response.text()).resolves.toContain('Welcome to Solid'); - expect(response.headers.get('link')).toContain(`<${PIM.Storage}>; rel="type"`); - }); - it('can read a container listing with a query string.', async(): Promise => { // Helper functions would fail due to query params const response = await fetch(`${baseUrl}?abc=def&xyz`, { headers: { accept: 'text/turtle' }}); @@ -92,9 +85,6 @@ describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeC const quads = parser.parse(await response.text()); const store = new Store(quads); expect(store.countQuads(namedNode(baseUrl), RDF.terms.type, LDP.terms.Container, null)).toBe(1); - const contains = store.getObjects(namedNode(baseUrl), LDP.terms.contains, null); - expect(contains).toHaveLength(1); - expect(contains[0].value).toBe(`${baseUrl}index.html`); }); it('can add a document to the store, read it and delete it.', async(): Promise => { diff --git a/test/integration/Setup.test.ts b/test/integration/Setup.test.ts new file mode 100644 index 000000000..831862406 --- /dev/null +++ b/test/integration/Setup.test.ts @@ -0,0 +1,117 @@ +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); + await expect(res.text()).resolves.toContain('Welcome to Solid'); + + res = await fetch(joinUrl(baseUrl, '/random/path/'), { method: 'GET', headers: { accept: 'text/html' }}); + expect(res.status).toBe(200); + await expect(res.text()).resolves.toContain('Welcome to Solid'); + + res = await fetch(joinUrl(baseUrl, '/random/path/'), { method: 'PUT', headers: { accept: 'text/html' }}); + expect(res.status).toBe(405); + await expect(res.text()).resolves.toContain('Welcome to Solid'); + }); + + it('can create a server that disables root but allows registration.', async(): Promise => { + let res = await fetch(setupUrl, { method: 'POST', headers: { accept: 'text/html' }}); + expect(res.status).toBe(200); + await expect(res.text()).resolves.toContain('Server successfully set up'); + + // Root access disabled + res = await fetch(baseUrl); + expect(res.status).toBe(403); + + // Registration still possible + const registerParams = { email, podName, password, confirmPassword: password, createWebId: true }; + res = await fetch(joinUrl(baseUrl, 'idp/register'), { + method: 'POST', + headers: { accept: 'text/html', '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: { accept: 'text/html', 'content-type': 'application/json' }, + body: JSON.stringify({ initialize: true }), + }); + expect(res.status).toBe(200); + await expect(res.text()).resolves.toContain('Server successfully set up'); + + // 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: { accept: 'text/html', '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: { accept: 'text/html', 'content-type': 'application/json' }, + body: JSON.stringify({ registration: true, initialize: true, ...registerParams }), + }); + expect(res.status).toBe(200); + await expect(res.text()).resolves.toContain('Server successfully set up'); + + // 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: { accept: 'text/html', 'content-type': 'text/plain' }, + body: 'random data', + }); + expect(res.status).toBe(401); + }); +}); diff --git a/test/integration/config/ldp-with-auth.json b/test/integration/config/ldp-with-auth.json index 4b6188346..8437d4fba 100644 --- a/test/integration/config/ldp-with-auth.json +++ b/test/integration/config/ldp-with-auth.json @@ -2,7 +2,8 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "import": [ "files-scs:config/app/main/default.json", - "files-scs:config/app/init/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/disabled.json", "files-scs:config/http/handler/simple.json", "files-scs:config/http/middleware/no-websockets.json", "files-scs:config/http/server-factory/no-websockets.json", diff --git a/test/integration/config/run-with-redlock.json b/test/integration/config/run-with-redlock.json index 253c971fc..164ada566 100644 --- a/test/integration/config/run-with-redlock.json +++ b/test/integration/config/run-with-redlock.json @@ -2,7 +2,8 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "import": [ "files-scs:config/app/main/default.json", - "files-scs:config/app/init/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/disabled.json", "files-scs:config/http/handler/simple.json", "files-scs:config/http/middleware/no-websockets.json", "files-scs:config/http/server-factory/no-websockets.json", diff --git a/test/integration/config/server-dynamic-unsafe.json b/test/integration/config/server-dynamic-unsafe.json index 0288173a2..6157cde8a 100644 --- a/test/integration/config/server-dynamic-unsafe.json +++ b/test/integration/config/server-dynamic-unsafe.json @@ -2,7 +2,8 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "import": [ "files-scs:config/app/main/default.json", - "files-scs:config/app/init/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/disabled.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/no-websockets.json", "files-scs:config/http/server-factory/no-websockets.json", diff --git a/test/integration/config/server-memory.json b/test/integration/config/server-memory.json index 440b146a6..f4f997e5e 100644 --- a/test/integration/config/server-memory.json +++ b/test/integration/config/server-memory.json @@ -2,7 +2,8 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "import": [ "files-scs:config/app/main/default.json", - "files-scs:config/app/init/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/disabled.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/websockets.json", "files-scs:config/http/server-factory/websockets.json", diff --git a/test/integration/config/server-subdomains-unsafe.json b/test/integration/config/server-subdomains-unsafe.json index 88b70b2a7..a98c87113 100644 --- a/test/integration/config/server-subdomains-unsafe.json +++ b/test/integration/config/server-subdomains-unsafe.json @@ -2,7 +2,8 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "import": [ "files-scs:config/app/main/default.json", - "files-scs:config/app/init/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/disabled.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/no-websockets.json", "files-scs:config/http/server-factory/no-websockets.json", diff --git a/test/integration/config/server-without-auth.json b/test/integration/config/server-without-auth.json index 173693a07..f69143e43 100644 --- a/test/integration/config/server-without-auth.json +++ b/test/integration/config/server-without-auth.json @@ -2,7 +2,8 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "import": [ "files-scs:config/app/main/default.json", - "files-scs:config/app/init/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/disabled.json", "files-scs:config/http/handler/default.json", "files-scs:config/http/middleware/websockets.json", "files-scs:config/http/server-factory/websockets.json", diff --git a/test/integration/config/setup-memory.json b/test/integration/config/setup-memory.json new file mode 100644 index 000000000..7b9334ef1 --- /dev/null +++ b/test/integration/config/setup-memory.json @@ -0,0 +1,45 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", + "import": [ + "files-scs:config/app/main/default.json", + "files-scs:config/app/init/default.json", + "files-scs:config/app/setup/required.json", + "files-scs:config/http/handler/default.json", + "files-scs:config/http/middleware/websockets.json", + "files-scs:config/http/server-factory/websockets.json", + "files-scs:config/http/static/default.json", + "files-scs:config/identity/email/default.json", + "files-scs:config/identity/handler/default.json", + "files-scs:config/identity/ownership/token.json", + "files-scs:config/identity/pod/static.json", + "files-scs:config/identity/registration/enabled.json", + "files-scs:config/ldp/authentication/dpop-bearer.json", + "files-scs:config/ldp/authorization/webacl.json", + "files-scs:config/ldp/handler/default.json", + "files-scs:config/ldp/metadata-parser/default.json", + "files-scs:config/ldp/metadata-writer/default.json", + "files-scs:config/ldp/permissions/acl.json", + "files-scs:config/storage/backend/memory.json", + "files-scs:config/storage/key-value/resource-store.json", + "files-scs:config/storage/middleware/default.json", + "files-scs:config/util/auxiliary/acl.json", + "files-scs:config/util/identifiers/suffix.json", + "files-scs:config/util/index/default.json", + "files-scs:config/util/logging/winston.json", + "files-scs:config/util/representation-conversion/default.json", + "files-scs:config/util/resource-locker/memory.json", + "files-scs:config/util/variables/default.json" + ], + "@graph": [ + { + "@id": "urn:solid-server:test:Instances", + "@type": "RecordObject", + "RecordObject:_record": [ + { + "RecordObject:_record_key": "app", + "RecordObject:_record_value": { "@id": "urn:solid-server:default:App" } + } + ] + } + ] +} diff --git a/test/unit/util/handlers/StaticHandler.test.ts b/test/unit/util/handlers/StaticHandler.test.ts new file mode 100644 index 000000000..3f0db8726 --- /dev/null +++ b/test/unit/util/handlers/StaticHandler.test.ts @@ -0,0 +1,18 @@ +import { StaticHandler } from '../../../../src/util/handlers/StaticHandler'; + +describe('A StaticHandler', (): void => { + it('can handle everything.', async(): Promise => { + const handler = new StaticHandler(); + await expect(handler.canHandle(null)).resolves.toBeUndefined(); + }); + + it('returns the stored value.', async(): Promise => { + const handler = new StaticHandler('apple'); + await expect(handler.handle()).resolves.toEqual('apple'); + }); + + it('returns undefined if there is no stored value.', async(): Promise => { + const handler = new StaticHandler(); + await expect(handler.handle()).resolves.toBeUndefined(); + }); +}); diff --git a/test/util/Util.ts b/test/util/Util.ts index 9e99e3862..5bf7b856a 100644 --- a/test/util/Util.ts +++ b/test/util/Util.ts @@ -13,6 +13,7 @@ const portNames = [ 'PodCreation', 'RedisResourceLocker', 'ServerFetch', + 'SetupMemory', 'SparqlStorage', 'Subdomains', 'WebSocketsProtocol',