diff --git a/config/presets/ldp/credentials-extractor.json b/config/presets/ldp/credentials-extractor.json index 7d6b53850..d4e2b8ace 100644 --- a/config/presets/ldp/credentials-extractor.json +++ b/config/presets/ldp/credentials-extractor.json @@ -3,7 +3,10 @@ "@graph": [ { "@id": "urn:solid-server:default:CredentialsExtractor", - "@type": "UnsecureWebIdExtractor" + "@type": "DPoPWebIdExtractor", + "DPoPWebIdExtractor:_targetExtractor": { + "@id": "urn:solid-server:default:TargetExtractor" + } } ] } diff --git a/config/presets/ldp/request-parser.json b/config/presets/ldp/request-parser.json index c0d48a347..f5c0658a1 100644 --- a/config/presets/ldp/request-parser.json +++ b/config/presets/ldp/request-parser.json @@ -5,7 +5,7 @@ "@id": "urn:solid-server:default:RequestParser", "@type": "BasicRequestParser", "BasicRequestParser:_args_targetExtractor": { - "@type": "BasicTargetExtractor" + "@id": "urn:solid-server:default:TargetExtractor" }, "BasicRequestParser:_args_preferenceParser": { "@type": "AcceptPreferenceParser" @@ -24,6 +24,10 @@ } ] } + }, + { + "@id": "urn:solid-server:default:TargetExtractor", + "@type": "BasicTargetExtractor" } ] } diff --git a/index.ts b/index.ts index 489c3ad94..189277523 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,7 @@ // Authentication export * from './src/authentication/Credentials'; export * from './src/authentication/CredentialsExtractor'; +export * from './src/authentication/DPoPWebIdExtractor'; export * from './src/authentication/UnsecureWebIdExtractor'; // Authorization diff --git a/package-lock.json b/package-lock.json index e0a82c13b..bbed4f5c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -444,6 +444,25 @@ "parse-link-header": "^1.0.1" } }, + "@comunica/actor-http-proxy": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-http-proxy/-/actor-http-proxy-1.16.0.tgz", + "integrity": "sha512-5aD5y9b6DvcCX2/ud/aVvUjvipppMmzKRV3C0evbA3NoAZ4VF8ejD+JpIjDsrvrbWeXVPueXvL/yfssUz17iIg==" + }, + "@comunica/actor-rdf-dereference-file": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-dereference-file/-/actor-rdf-dereference-file-1.15.0.tgz", + "integrity": "sha512-hTG+JhPcdhlkEj96xI+0cTIC5uHBev3R1BiLXuOP7daVLLTfUHvBI09xgcVvKMqKJMuQNDVg3+aBRfMcR2bYVQ==" + }, + "@comunica/actor-rdf-dereference-http-parse": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-dereference-http-parse/-/actor-rdf-dereference-http-parse-1.16.0.tgz", + "integrity": "sha512-IKSw5jjQLIDkbd7eX3Sw0oMuPBujQ4JX6UMpDA846T4lOZcUBTovp0PPKL4zyUd88Ea9a84Qn6uREOWed4nKvg==", + "requires": { + "cross-fetch": "^3.0.5", + "relative-to-absolute-iri": "^1.0.5" + } + }, "@comunica/actor-rdf-parse-html": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-parse-html/-/actor-rdf-parse-html-1.15.0.tgz", @@ -463,6 +482,14 @@ } } }, + "@comunica/actor-rdf-parse-html-microdata": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-parse-html-microdata/-/actor-rdf-parse-html-microdata-1.18.0.tgz", + "integrity": "sha512-2IjfPDXqS6IW+9k6RM2mcd8dx9gW4NeBL1teNfSpAIH3mBdRdum+FAfbeWirOOIX+lpKJ3pKo57mXf5eUFNkXw==", + "requires": { + "microdata-rdf-streaming-parser": "^1.1.0" + } + }, "@comunica/actor-rdf-parse-html-rdfa": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-parse-html-rdfa/-/actor-rdf-parse-html-rdfa-1.15.0.tgz", @@ -578,6 +605,24 @@ "resolved": "https://registry.npmjs.org/@comunica/bus-init/-/bus-init-1.15.0.tgz", "integrity": "sha512-HwVTEznx2H7GvcfQAREz52QF8xXT1AqxkJDnI9vTeGvLpWrM+LVOAJZxBcYFFK3X3bEhTsPfDVzikziMGqZuZw==" }, + "@comunica/bus-rdf-dereference": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@comunica/bus-rdf-dereference/-/bus-rdf-dereference-1.15.0.tgz", + "integrity": "sha512-4vXq8e51zK1gkw5lxL2HkqYx/Wig215w8Bi3jguUTM/10EQW7KgebhRxeFU/EQOamFTMeS5anOu5BVfNps8rJQ==", + "requires": { + "@types/rdf-js": "^3.0.0" + }, + "dependencies": { + "@types/rdf-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/rdf-js/-/rdf-js-3.0.3.tgz", + "integrity": "sha512-1dvodNHh/YpLovHA/b046Nu+xERVt0KcYJFQH1A8S4ZcMj+stYtx4ypXw0X2/oatbREUo4JW+cjoBll3CVtwSQ==", + "requires": { + "@types/node": "*" + } + } + } + }, "@comunica/bus-rdf-parse": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@comunica/bus-rdf-parse/-/bus-rdf-parse-1.15.0.tgz", @@ -6005,6 +6050,11 @@ "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=", "dev": true }, + "jose": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-3.1.3.tgz", + "integrity": "sha512-ppasqfGfKZ8hmpJVAuXfQR8S+AiDCcUa2Cj1WDw+I4Ui7J9iIfsg1CIjMSlwxN1PtK6yiSKqIXfi9mTDrXQm/w==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6538,6 +6588,30 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, + "microdata-rdf-streaming-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/microdata-rdf-streaming-parser/-/microdata-rdf-streaming-parser-1.1.0.tgz", + "integrity": "sha512-nvPEFzG4vZWzWJP2x8Ax7mJmdrFkSYrfhdTUDHLtXYZJVl8Ip7ScHUPLkUfX+Ci4g7sOdgHsotkxuccnlxtCAg==", + "requires": { + "@types/rdf-js": "*", + "htmlparser2": "^5.0.0", + "rdf-data-factory": "^1.0.2", + "relative-to-absolute-iri": "^1.0.2" + }, + "dependencies": { + "htmlparser2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", + "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.3.0", + "domutils": "^2.4.2", + "entities": "^2.0.0" + } + } + } + }, "micromatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", @@ -7492,6 +7566,46 @@ "@types/rdf-js": "*" } }, + "rdf-dereference": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/rdf-dereference/-/rdf-dereference-1.6.0.tgz", + "integrity": "sha512-anUzTvHwRfStCQhgEmO5EOnLPaGDgTlb4o84ZTqqXvbI+bhLAKYoCWVVFd9uRuTk7ZPxezHZlDgN5uqAuFaEmQ==", + "requires": { + "@comunica/actor-http-native": "~1.16.2", + "@comunica/actor-http-proxy": "~1.16.0", + "@comunica/actor-rdf-dereference-file": "~1.15.0", + "@comunica/actor-rdf-dereference-http-parse": "~1.16.0", + "@comunica/actor-rdf-parse-html": "~1.15.0", + "@comunica/actor-rdf-parse-html-rdfa": "~1.15.0", + "@comunica/actor-rdf-parse-html-script": "~1.16.1", + "@comunica/actor-rdf-parse-jsonld": "~1.16.0", + "@comunica/actor-rdf-parse-n3": "~1.15.0", + "@comunica/actor-rdf-parse-rdfxml": "~1.15.0", + "@comunica/actor-rdf-parse-xml-rdfa": "~1.15.0", + "@comunica/bus-http": "~1.16.0", + "@comunica/bus-init": "~1.15.0", + "@comunica/bus-rdf-dereference": "~1.15.0", + "@comunica/bus-rdf-parse": "~1.15.0", + "@comunica/bus-rdf-parse-html": "~1.15.0", + "@comunica/core": "~1.15.0", + "@comunica/mediator-combine-union": "~1.15.0", + "@comunica/mediator-number": "~1.15.0", + "@comunica/mediator-race": "~1.15.0", + "@types/rdf-js": "^3.0.0", + "rdf-string": "^1.3.1", + "stream-to-string": "^1.2.0" + }, + "dependencies": { + "@types/rdf-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/rdf-js/-/rdf-js-3.0.3.tgz", + "integrity": "sha512-1dvodNHh/YpLovHA/b046Nu+xERVt0KcYJFQH1A8S4ZcMj+stYtx4ypXw0X2/oatbREUo4JW+cjoBll3CVtwSQ==", + "requires": { + "@types/node": "*" + } + } + } + }, "rdf-isomorphic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/rdf-isomorphic/-/rdf-isomorphic-1.2.0.tgz", @@ -7554,6 +7668,15 @@ "stream-to-string": "^1.2.0" } }, + "rdf-store-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/rdf-store-stream/-/rdf-store-stream-1.1.0.tgz", + "integrity": "sha512-JWvQUv/1yja1TiEzhS1PTactSER9ORjM/6TV8z3KdGWpeQOs9TeUgLzx5PLXSRePFZ8GKNTkG5dD+wC6Yh3sbQ==", + "requires": { + "@types/rdf-js": "*", + "n3": "^1.6.3" + } + }, "rdf-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/rdf-string/-/rdf-string-1.5.0.tgz", @@ -9175,6 +9298,199 @@ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, + "ts-dpop": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ts-dpop/-/ts-dpop-0.1.1.tgz", + "integrity": "sha512-KrIafLRqG9XLiRghAe7TKy9H0fQ1VNxdG+rOc5k/DoN9dJCQKFWKSyggwPoi5y5sQX2OZKaJCgbQO5c3aH1e5g==", + "requires": { + "cross-fetch": "^3.0.6", + "jose": "^3.1.0", + "n3": "^1.6.4", + "rdf-dereference": "^1.6.0", + "rdf-parse": "^1.6.1", + "rdf-store-stream": "^1.1.0", + "ts-guards": "^0.5.1", + "uuid": "^8.3.1" + }, + "dependencies": { + "@comunica/actor-abstract-mediatyped": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-abstract-mediatyped/-/actor-abstract-mediatyped-1.18.0.tgz", + "integrity": "sha512-bu6QorV2OPdHHWuw6lbI87mTTAZKS6TbQA6fNBFBw7LfwMfLugMrjUTxJcJ/UKXV4uPkpJRhhKEG6hns+k0S/g==" + }, + "@comunica/actor-http-native": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-http-native/-/actor-http-native-1.18.0.tgz", + "integrity": "sha512-wooUx1R0VKfDxtiui5dS5JSstQmRxl1fQY9q6iwYlEMcMXST6RsIhcRi5MWqEaJE5cVsTEBMXO4Fqdl51kvgeg==", + "requires": { + "@types/parse-link-header": "^1.0.0", + "cross-fetch": "^3.0.5", + "follow-redirects": "^1.5.1", + "parse-link-header": "^1.0.1" + } + }, + "@comunica/actor-rdf-parse-html": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-parse-html/-/actor-rdf-parse-html-1.18.0.tgz", + "integrity": "sha512-0mdU7msQJwdSVtSSAOGE3xykaW22Zg/bytjBe0sG+/S82wGnZdTYEmchzR4prH/R+IyRhZHVgNzGzmcaasX66A==", + "requires": { + "@comunica/bus-rdf-parse-html": "^1.18.0", + "@types/rdf-js": "*", + "htmlparser2": "^5.0.0" + } + }, + "@comunica/actor-rdf-parse-html-rdfa": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-parse-html-rdfa/-/actor-rdf-parse-html-rdfa-1.18.0.tgz", + "integrity": "sha512-tvfQP01uxcgZIDF6aXO83ckzNf/lrkLavLI2QQciuLXToCDFgTwXqyEPeZs4/nf1L3uuXJOIzP0Bq0ZnVAgLBw==", + "requires": { + "rdfa-streaming-parser": "^1.4.0" + } + }, + "@comunica/actor-rdf-parse-html-script": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-parse-html-script/-/actor-rdf-parse-html-script-1.18.0.tgz", + "integrity": "sha512-j/Pcj3oXPDXjnhshKdtk355HhZDNIUItzkDxH1EBdL9S0Ll6HbEt0DJwXy51JRFxiK/ZpfXWGCHuOC1rOtP1oQ==", + "requires": { + "@comunica/bus-rdf-parse-html": "^1.18.0", + "@types/rdf-js": "*", + "relative-to-absolute-iri": "^1.0.5" + } + }, + "@comunica/actor-rdf-parse-jsonld": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-parse-jsonld/-/actor-rdf-parse-jsonld-1.18.0.tgz", + "integrity": "sha512-SbILO+HnBi88+M3XQQ7ZV3OKy3eqH0VHn6QuKYTcucOzcYtz60CeRRC0IVhEIKIbEHOpupZ3ZWOQzGudKZ71Rw==", + "requires": { + "@types/rdf-js": "*", + "jsonld-context-parser": "^2.1.1", + "jsonld-streaming-parser": "^2.1.1", + "stream-to-string": "^1.2.0" + } + }, + "@comunica/actor-rdf-parse-n3": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-parse-n3/-/actor-rdf-parse-n3-1.18.0.tgz", + "integrity": "sha512-4aud6izDZCzgNuo0ggCCQf4TdW9vyN0BU29kEXvWfX0kX6xIeTENOVuCddRSXEl9ndaD6WrJtnpG2GDu/tlqUA==", + "requires": { + "@types/n3": "^1.4.4", + "n3": "^1.6.3" + } + }, + "@comunica/actor-rdf-parse-rdfxml": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-parse-rdfxml/-/actor-rdf-parse-rdfxml-1.18.0.tgz", + "integrity": "sha512-2bqH5JmCISZbE+9kdE6H42TVYEGArIzosWJrtPrAzhWhAi2hN7c+wpo8arqRe8UB8MxO58JbnuxxGaNJcSYonw==", + "requires": { + "rdfxml-streaming-parser": "^1.4.0" + } + }, + "@comunica/actor-rdf-parse-xml-rdfa": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/actor-rdf-parse-xml-rdfa/-/actor-rdf-parse-xml-rdfa-1.18.0.tgz", + "integrity": "sha512-Sp9VRNL4dVv6IQTSwjAY+4o46WV/SZq4PRebU/enWF82w5Pbj1Q4s1iRNXb+BZFUO7s3mihBcQ62+sJ4Ds7Big==", + "requires": { + "rdfa-streaming-parser": "^1.3.0" + } + }, + "@comunica/bus-http": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/bus-http/-/bus-http-1.18.0.tgz", + "integrity": "sha512-SAfI+1Rk6caHlpRBpxiLpxmBWOA+dho4IurU+++LsmwC25353W8hjOSSg0GPnuLBebFO7YVcB1rTOyJfp53kcg==", + "requires": { + "is-stream": "^2.0.0", + "web-streams-node": "^0.4.0" + } + }, + "@comunica/bus-init": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/bus-init/-/bus-init-1.18.0.tgz", + "integrity": "sha512-BjDX31UWf2iTCVhhe3NllfizYGoE4Vh0YaY4s3RLOKpMCN6UlplmIprUELRSju0E8CRR2KHddNC1j5wt3a4cTw==" + }, + "@comunica/bus-rdf-parse": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/bus-rdf-parse/-/bus-rdf-parse-1.18.0.tgz", + "integrity": "sha512-zXH7AwnkF10agvNEXY08h8HAevjmcILl/S3CovMTtQLMaU1bGSQCjcYJl6QOIqYehYMntsvjeSKTSZ+VhESgOg==", + "requires": { + "@comunica/actor-abstract-mediatyped": "^1.18.0", + "@types/rdf-js": "*" + } + }, + "@comunica/bus-rdf-parse-html": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/bus-rdf-parse-html/-/bus-rdf-parse-html-1.18.0.tgz", + "integrity": "sha512-QGjV3p7O2c1mJOURrXHe+9uifLMPLwfW/jAhBpVt4UVzIoZl5onK9e8ukJBjxsCl1pnH07KOPO1i91VyBOWjWA==", + "requires": { + "@types/rdf-js": "*" + } + }, + "@comunica/core": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/core/-/core-1.18.0.tgz", + "integrity": "sha512-7JCzSJkilbemhIiSSpFuylbEqMntWp+JSs3gNgvb+r7NzxDXlB9l1Ng3UVJg6QvoO8EuoprwRGyeiG2824TarQ==", + "requires": { + "immutable": "^3.8.2" + } + }, + "@comunica/mediator-combine-union": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/mediator-combine-union/-/mediator-combine-union-1.18.0.tgz", + "integrity": "sha512-AO6DxwSQubD4p6dWllom3jXLP7zEYWVsI3pmXsnJYP8TqUtcJ0Mah5JaCnzbHIcRToZIy/WUDUN9Kcxp0ZwCXA==" + }, + "@comunica/mediator-number": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/mediator-number/-/mediator-number-1.18.0.tgz", + "integrity": "sha512-JGoUQRJQSLiAkBPAE2591DhMyv0mT24l91vyfo5TPKNtHXa0ull12fYRpPaXbRTGGAENKvxoEFR491vi1XIb8A==" + }, + "@comunica/mediator-race": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@comunica/mediator-race/-/mediator-race-1.18.0.tgz", + "integrity": "sha512-8iEFxjQdi4wCSz+4yVywhF/DpcWWDxZ0GZZwc2Qp6ShyX79Zn/I2LEDuj50Uuh+0fHDt91H5Em9UhRMI7IRtLQ==" + }, + "htmlparser2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", + "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.3.0", + "domutils": "^2.4.2", + "entities": "^2.0.0" + } + }, + "rdf-parse": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/rdf-parse/-/rdf-parse-1.6.1.tgz", + "integrity": "sha512-WBylJN11cuvo2GAvS/drqus+94tb9evl1QvUSbkNAVxYWUc5chnyV9ZpWPZW/F2pXJlPLqet9kE8U52eXKMo3Q==", + "requires": { + "@comunica/actor-http-native": "~1.18.0", + "@comunica/actor-rdf-parse-html": "~1.18.0", + "@comunica/actor-rdf-parse-html-microdata": "~1.18.0", + "@comunica/actor-rdf-parse-html-rdfa": "~1.18.0", + "@comunica/actor-rdf-parse-html-script": "~1.18.0", + "@comunica/actor-rdf-parse-jsonld": "~1.18.0", + "@comunica/actor-rdf-parse-n3": "~1.18.0", + "@comunica/actor-rdf-parse-rdfxml": "~1.18.0", + "@comunica/actor-rdf-parse-xml-rdfa": "~1.18.0", + "@comunica/bus-http": "~1.18.0", + "@comunica/bus-init": "~1.18.0", + "@comunica/bus-rdf-parse": "~1.18.0", + "@comunica/bus-rdf-parse-html": "~1.18.0", + "@comunica/core": "~1.18.0", + "@comunica/mediator-combine-union": "~1.18.0", + "@comunica/mediator-number": "~1.18.0", + "@comunica/mediator-race": "~1.18.0", + "@types/rdf-js": "*", + "stream-to-string": "^1.2.0" + } + } + } + }, + "ts-guards": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/ts-guards/-/ts-guards-0.5.1.tgz", + "integrity": "sha512-Y6P/VJnwARiPMfxO7rvaYaz5tGQ5TQ0Wnb2cWIxMpFOioYkhsT8XaCrJX6wYPNFACa4UOrN5SPqhwpM8NolAhQ==" + }, "ts-jest": { "version": "26.4.3", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.3.tgz", diff --git a/package.json b/package.json index a95c33582..dc491a116 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "sparqlalgebrajs": "^2.3.1", "sparqljs": "^3.1.2", "streamify-array": "^1.0.1", + "ts-dpop": "^0.1.1", "uuid": "^8.3.0", "winston": "^3.3.3", "winston-transport": "^4.4.0", diff --git a/src/authentication/CredentialsExtractor.ts b/src/authentication/CredentialsExtractor.ts index e7598413c..12fafa265 100644 --- a/src/authentication/CredentialsExtractor.ts +++ b/src/authentication/CredentialsExtractor.ts @@ -4,6 +4,5 @@ import type { Credentials } from './Credentials'; /** * Responsible for extracting credentials from an incoming request. - * Will return `null` if no credentials were found. */ export abstract class CredentialsExtractor extends AsyncHandler {} diff --git a/src/authentication/DPoPWebIdExtractor.ts b/src/authentication/DPoPWebIdExtractor.ts new file mode 100644 index 000000000..dc9675863 --- /dev/null +++ b/src/authentication/DPoPWebIdExtractor.ts @@ -0,0 +1,36 @@ +import { verify } from 'ts-dpop'; +import type { TargetExtractor } from '../ldp/http/TargetExtractor'; +import { getLoggerFor } from '../logging/LogUtil'; +import type { HttpRequest } from '../server/HttpRequest'; +import type { Credentials } from './Credentials'; +import { CredentialsExtractor } from './CredentialsExtractor'; + +/** + * Credentials extractor which extracts a WebID from a DPoP token. + */ +export class DPoPWebIdExtractor extends CredentialsExtractor { + protected readonly logger = getLoggerFor(this); + private readonly targetExtractor: TargetExtractor; + + public constructor(targetExtractor: TargetExtractor) { + super(); + this.targetExtractor = targetExtractor; + } + + public async handle(request: HttpRequest): Promise { + let webID: string | undefined; + const authorizationHeader = request.headers.authorization; + const dpopHeader = request.headers.dpop as string; + if (authorizationHeader && dpopHeader) { + const method = request.method as any; + const resource = await this.targetExtractor.handleSafe(request); + try { + webID = await verify(authorizationHeader, dpopHeader, method, resource.path); + this.logger.info(`Extracted WebID via DPoP token: ${webID}`); + } catch (error: unknown) { + this.logger.warn(`Error verifying WebID via DPoP token: ${(error as Error).message}`); + } + } + return { webID }; + } +} diff --git a/test/__mocks__/.eslintrc.js b/test/__mocks__/.eslintrc.js new file mode 100644 index 000000000..cf7a649eb --- /dev/null +++ b/test/__mocks__/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + rules: { + 'unicorn/filename-case': 'off', + }, +}; diff --git a/test/__mocks__/ts-dpop.ts b/test/__mocks__/ts-dpop.ts new file mode 100644 index 000000000..01b9255e1 --- /dev/null +++ b/test/__mocks__/ts-dpop.ts @@ -0,0 +1 @@ +export const verify = jest.fn((): string => 'http://alice.example/card#me'); diff --git a/test/unit/authentication/DPoPWebIdExtractor.test.ts b/test/unit/authentication/DPoPWebIdExtractor.test.ts new file mode 100644 index 000000000..c7deaad29 --- /dev/null +++ b/test/unit/authentication/DPoPWebIdExtractor.test.ts @@ -0,0 +1,94 @@ +import { verify } from 'ts-dpop'; +import { DPoPWebIdExtractor } from '../../../src/authentication/DPoPWebIdExtractor'; +import { TargetExtractor } from '../../../src/ldp/http/TargetExtractor'; +import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier'; +import type { HttpRequest } from '../../../src/server/HttpRequest'; + +class DummyTargetExtractor extends TargetExtractor { + public async handle(): Promise { + return { path: 'http://example.org/foo/bar' }; + } +} + +describe('A DPoPWebIdExtractor', (): void => { + const targetExtractor = new DummyTargetExtractor(); + const webIdExtractor = new DPoPWebIdExtractor(targetExtractor); + + beforeEach((): void => { + jest.clearAllMocks(); + jest.spyOn(targetExtractor, 'handle'); + }); + + describe('on a request without Authorization header', (): void => { + const request = { + method: 'GET', + headers: { + dpop: 'token-5678', + }, + } as any as HttpRequest; + + it('returns empty credentials.', async(): Promise => { + await expect(webIdExtractor.handle(request)).resolves.toEqual({}); + }); + }); + + describe('on a request without DPoP header', (): void => { + const request = { + method: 'GET', + headers: { + authorization: 'DPoP token-1234', + }, + } as any as HttpRequest; + + it('returns empty credentials.', async(): Promise => { + await expect(webIdExtractor.handle(request)).resolves.toEqual({}); + }); + }); + + describe('on a request with Authorization and DPop headers', (): void => { + const request = { + method: 'GET', + headers: { + authorization: 'DPoP token-1234', + dpop: 'token-5678', + }, + } as any as HttpRequest; + + it('calls the target extractor with the correct parameters.', async(): Promise => { + await webIdExtractor.handle(request); + expect(targetExtractor.handle).toHaveBeenCalledTimes(1); + expect(targetExtractor.handle).toHaveBeenCalledWith(request); + }); + + it('calls the DPoP verifier with the correct parameters.', async(): Promise => { + await webIdExtractor.handle(request); + expect(verify).toHaveBeenCalledTimes(1); + expect(verify).toHaveBeenCalledWith('DPoP token-1234', 'token-5678', 'GET', 'http://example.org/foo/bar'); + }); + + it('returns the extracted WebID.', async(): Promise => { + await expect(webIdExtractor.handle(request)).resolves + .toEqual({ webID: 'http://alice.example/card#me' }); + }); + }); + + describe('when verification throws an error', (): void => { + const request = { + method: 'GET', + headers: { + authorization: 'DPoP token-1234', + dpop: 'token-5678', + }, + } as any as HttpRequest; + + beforeEach((): void => { + (verify as jest.MockedFunction).mockImplementationOnce((): void => { + throw new Error('invalid'); + }); + }); + + it('returns empty credentials.', async(): Promise => { + await expect(webIdExtractor.handle(request)).resolves.toEqual({}); + }); + }); +});