diff --git a/config/identity/handler/interaction/routes.json b/config/identity/handler/interaction/routes.json index 07d40db70..a15a177f9 100644 --- a/config/identity/handler/interaction/routes.json +++ b/config/identity/handler/interaction/routes.json @@ -1,7 +1,7 @@ { "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^2.0.0/components/context.jsonld", "import": [ - "files-scs:config/identity/handler/interaction/routes/existing-login.json", + "files-scs:config/identity/handler/interaction/routes/consent.json", "files-scs:config/identity/handler/interaction/routes/forgot-password.json", "files-scs:config/identity/handler/interaction/routes/index.json", "files-scs:config/identity/handler/interaction/routes/login.json", @@ -47,7 +47,7 @@ { "@id": "urn:solid-server:auth:password:IndexRouteHandler" }, { "@id": "urn:solid-server:auth:password:PromptRouteHandler" }, { "@id": "urn:solid-server:auth:password:LoginRouteHandler" }, - { "@id": "urn:solid-server:auth:password:ExistingLoginRouteHandler" }, + { "@id": "urn:solid-server:auth:password:ConsentRouteHandler" }, { "@id": "urn:solid-server:auth:password:ForgotPasswordRouteHandler" }, { "@id": "urn:solid-server:auth:password:ResetPasswordRouteHandler" } ] diff --git a/config/identity/handler/interaction/routes/existing-login.json b/config/identity/handler/interaction/routes/consent.json similarity index 61% rename from config/identity/handler/interaction/routes/existing-login.json rename to config/identity/handler/interaction/routes/consent.json index 915373ff0..e2fcfb60a 100644 --- a/config/identity/handler/interaction/routes/existing-login.json +++ b/config/identity/handler/interaction/routes/consent.json @@ -3,18 +3,18 @@ "@graph": [ { "comment": "Handles the interaction that occurs when a logged in user wants to authenticate with a new app.", - "@id": "urn:solid-server:auth:password:ExistingLoginRouteHandler", + "@id": "urn:solid-server:auth:password:ConsentRouteHandler", "@type":"InteractionRouteHandler", "route": { - "@id": "urn:solid-server:auth:password:ExistingLoginRoute", + "@id": "urn:solid-server:auth:password:ConsentRoute", "@type": "RelativePathInteractionRoute", "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, "relativePath": "/consent/" }, "source": { - "@id": "urn:solid-server:auth:password:ExistingLoginHandler", - "@type": "ExistingLoginHandler", - "interactionCompleter": { "@type": "BaseInteractionCompleter" } + "@id": "urn:solid-server:auth:password:ConsentHandler", + "@type": "ConsentHandler", + "providerFactory": { "@id": "urn:solid-server:default:IdentityProviderFactory" } } } ] diff --git a/config/identity/handler/interaction/routes/login.json b/config/identity/handler/interaction/routes/login.json index b3bf1b108..ecab17bda 100644 --- a/config/identity/handler/interaction/routes/login.json +++ b/config/identity/handler/interaction/routes/login.json @@ -14,8 +14,7 @@ "source": { "@id": "urn:solid-server:auth:password:LoginHandler", "@type": "LoginHandler", - "accountStore": { "@id": "urn:solid-server:auth:password:AccountStore" }, - "interactionCompleter": { "@type": "BaseInteractionCompleter" } + "accountStore": { "@id": "urn:solid-server:auth:password:AccountStore" } } } ] diff --git a/config/identity/handler/interaction/routes/prompt.json b/config/identity/handler/interaction/routes/prompt.json index 98cecae96..cea3ce87e 100644 --- a/config/identity/handler/interaction/routes/prompt.json +++ b/config/identity/handler/interaction/routes/prompt.json @@ -21,7 +21,7 @@ }, { "PromptHandler:_promptRoutes_key": "consent", - "PromptHandler:_promptRoutes_value": { "@id": "urn:solid-server:auth:password:ExistingLoginRoute" } + "PromptHandler:_promptRoutes_value": { "@id": "urn:solid-server:auth:password:ConsentRoute" } } ] } diff --git a/config/identity/handler/interaction/views/html.json b/config/identity/handler/interaction/views/html.json index ba6e568c9..d70f0170c 100644 --- a/config/identity/handler/interaction/views/html.json +++ b/config/identity/handler/interaction/views/html.json @@ -28,7 +28,7 @@ }, { "HtmlViewHandler:_templates_key": "@css:templates/identity/email-password/consent.html.ejs", - "HtmlViewHandler:_templates_value": { "@id": "urn:solid-server:auth:password:ExistingLoginRoute" } + "HtmlViewHandler:_templates_value": { "@id": "urn:solid-server:auth:password:ConsentRoute" } }, { "HtmlViewHandler:_templates_key": "@css:templates/identity/email-password/forgot-password.html.ejs", diff --git a/config/identity/handler/provider-factory/identity.json b/config/identity/handler/provider-factory/identity.json index 7efe13956..ed9c505f2 100644 --- a/config/identity/handler/provider-factory/identity.json +++ b/config/identity/handler/provider-factory/identity.json @@ -17,7 +17,7 @@ "args_responseWriter": { "@id": "urn:solid-server:default:ResponseWriter" }, "config": { "claims": { - "openid": [ "client_id" ], + "openid": [ "azp" ], "webid": [ "webid" ] }, "cookies": { @@ -27,22 +27,24 @@ "features": { "claimsParameter": { "enabled": true }, "devInteractions": { "enabled": false }, - "dPoP": { "enabled": true, "ack": "draft-01" }, + "dPoP": { "enabled": true, "ack": "draft-03" }, "introspection": { "enabled": true }, "registration": { "enabled": true }, - "revocation": { "enabled": true } - }, - "formats": { - "AccessToken": "jwt" + "revocation": { "enabled": true }, + "userinfo": { "enabled": false } }, "scopes": [ "openid", "profile", "offline_access", "webid" ], "subjectTypes": [ "public" ], "ttl": { "AccessToken": 3600, "AuthorizationCode": 600, + "BackchannelAuthenticationRequest": 600, "DeviceCode": 600, + "Grant": 1209600, "IdToken": 3600, - "RefreshToken": 86400 + "Interaction": 3600, + "RefreshToken": 86400, + "Session": 1209600 } } } diff --git a/package-lock.json b/package-lock.json index b1e825606..34e7d8db0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@types/n3": "^1.10.4", "@types/node": "^14.18.0", "@types/nodemailer": "^6.4.4", + "@types/oidc-provider": "^7.8.1", "@types/pump": "^1.1.1", "@types/punycode": "^2.1.0", "@types/redis": "^2.8.30", @@ -48,7 +49,7 @@ "mime-types": "^2.1.34", "n3": "^1.13.0", "nodemailer": "^6.7.2", - "oidc-provider": "^6.31.1", + "oidc-provider": "^7.10.6", "pump": "^3.0.0", "punycode": "^2.1.1", "rdf-dereference": "^1.9.0", @@ -4177,14 +4178,6 @@ "node": ">= 8" } }, - "node_modules/@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@rdfjs/types": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.0.1.tgz", @@ -4197,6 +4190,7 @@ "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true, "engines": { "node": ">=6" } @@ -4236,6 +4230,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, "dependencies": { "defer-to-connect": "^1.0.1" }, @@ -4341,6 +4336,17 @@ "@types/node": "*" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "node_modules/@types/cheerio": { "version": "0.22.30", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.30.tgz", @@ -4443,6 +4449,11 @@ "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz", "integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==" }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, "node_modules/@types/http-errors": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", @@ -4507,6 +4518,14 @@ "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==" }, + "node_modules/@types/keyv": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", + "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/koa": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.1.tgz", @@ -4605,6 +4624,14 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "node_modules/@types/oidc-provider": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@types/oidc-provider/-/oidc-provider-7.8.1.tgz", + "integrity": "sha512-MsmVKYFN9i27kfJh3hqj7F6aQNue6A/1aBKVJH07I3WYMriUDqMtYU0MWtheFPI1Tm9kwa0JHheaOdNRjuxboA==", + "dependencies": { + "@types/koa": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -4679,6 +4706,14 @@ "@types/bluebird": "*" } }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/semver": { "version": "7.3.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.6.tgz", @@ -5423,11 +5458,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -5898,10 +5928,19 @@ "node": ">= 6.0.0" } }, + "node_modules/cacheable-lookup": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz", + "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==", + "engines": { + "node": ">=10.6.0" + } + }, "node_modules/cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -5919,6 +5958,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, "dependencies": { "pump": "^3.0.0" }, @@ -5933,6 +5973,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, "engines": { "node": ">=8" } @@ -6681,6 +6722,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, "dependencies": { "mimic-response": "^1.0.0" }, @@ -6726,7 +6768,8 @@ "node_modules/defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true }, "node_modules/define-properties": { "version": "1.1.3", @@ -6771,9 +6814,13 @@ } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.1.0.tgz", + "integrity": "sha512-R5QZrOXxSs0JDUIU/VANvRJlQVMts9C0L76HToQdPdlftfZCE7W6dyH0G4GZ5UW9fRqUOhAoCE2aGekuu+3HjQ==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-libc": { "version": "1.0.3", @@ -6939,7 +6986,8 @@ "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true }, "node_modules/ee-first": { "version": "1.1.1", @@ -8443,6 +8491,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, "dependencies": { "pump": "^3.0.0" }, @@ -8485,12 +8534,6 @@ "node": ">=10" } }, - "node_modules/git-raw-commits/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, "node_modules/git-raw-commits/node_modules/through2": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", @@ -8843,6 +8886,7 @@ "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, "dependencies": { "@sindresorhus/is": "^0.14.0", "@szmarczak/http-timer": "^1.1.2", @@ -9009,7 +9053,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -9021,7 +9064,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -9121,67 +9163,50 @@ } }, "node_modules/http-assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", - "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", "dependencies": { "deep-equal": "~1.0.1", - "http-errors": "~1.7.2" + "http-errors": "~1.8.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/http-assert/node_modules/http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-assert/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "node_modules/http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.6" } }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/http-errors/node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/http-errors/node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/http-link-header": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.0.3.tgz", @@ -9203,6 +9228,29 @@ "node": ">= 6" } }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/http2-wrapper/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -9440,9 +9488,9 @@ } }, "node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -9602,9 +9650,12 @@ } }, "node_modules/is-generator-function": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz", - "integrity": "sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -10755,7 +10806,8 @@ "node_modules/json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true }, "node_modules/json-parse-better-errors": { "version": "1.0.2", @@ -10919,6 +10971,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, "dependencies": { "json-buffer": "3.0.0" } @@ -10942,16 +10995,16 @@ } }, "node_modules/koa": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.1.tgz", - "integrity": "sha512-Lb2Dloc72auj5vK4X4qqL7B5jyDPQaZucc9sR/71byg7ryoD1NCaCm63CShk9ID9quQvDEi1bGR/iGjCG7As3w==", + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.4.tgz", + "integrity": "sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==", "dependencies": { "accepts": "^1.3.5", "cache-content-type": "^1.0.0", "content-disposition": "~0.5.2", "content-type": "^1.0.4", "cookies": "~0.8.0", - "debug": "~3.1.0", + "debug": "^4.3.2", "delegates": "^1.0.0", "depd": "^2.0.0", "destroy": "^1.0.4", @@ -10962,7 +11015,7 @@ "http-errors": "^1.6.3", "is-generator-function": "^1.0.7", "koa-compose": "^4.1.0", - "koa-convert": "^1.2.0", + "koa-convert": "^2.0.0", "on-finished": "^2.3.0", "only": "~0.0.2", "parseurl": "^1.3.2", @@ -10980,31 +11033,15 @@ "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" }, "node_modules/koa-convert": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", - "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", "dependencies": { "co": "^4.6.0", - "koa-compose": "^3.0.0" + "koa-compose": "^4.1.0" }, "engines": { - "node": ">= 4" - } - }, - "node_modules/koa-convert/node_modules/koa-compose": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", - "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", - "dependencies": { - "any-promise": "^1.1.0" - } - }, - "node_modules/koa/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" + "node": ">= 10" } }, "node_modules/koa/node_modules/depd": { @@ -11206,6 +11243,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -11565,9 +11603,9 @@ } }, "node_modules/nanoid": { - "version": "3.1.22", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz", - "integrity": "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.0.tgz", + "integrity": "sha512-JzxqqT5u/x+/KOFSd7JP15DOo9nOoHpx6DYatqIHUW2+flybkm+mdcraotSQR5WcnZr+qhGVh8Ted0KdfSMxlg==", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -11806,6 +11844,7 @@ "version": "4.5.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true, "engines": { "node": ">=8" } @@ -11868,9 +11907,9 @@ } }, "node_modules/object-hash": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.1.1.tgz", - "integrity": "sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", "engines": { "node": ">= 6" } @@ -11976,44 +12015,141 @@ } }, "node_modules/oidc-provider": { - "version": "6.31.1", - "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-6.31.1.tgz", - "integrity": "sha512-YYfcJKGrobdaW2v7bx5crd4yfxFTKAoOTHdQs/gsu6fwzc/yD/qjforQX2VgbTX+ySK8nm7EaAwJuJJSFRo1MA==", + "version": "7.10.6", + "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-7.10.6.tgz", + "integrity": "sha512-7fbnormUyTLP34dmR5WXoJtTWtfj6MsFNzIMKVRKv21e18NIXggn14EBUFC5rrMMtmeExb03+lJI/v+opD+0oQ==", "dependencies": { "@koa/cors": "^3.1.0", - "@types/koa": "^2.11.4", - "debug": "^4.1.1", - "ejs": "^3.1.5", - "got": "^9.6.0", - "jose": "^2.0.4", - "jsesc": "^3.0.1", - "koa": "^2.13.0", + "cacheable-lookup": "^6.0.1", + "debug": "^4.3.2", + "ejs": "^3.1.6", + "got": "^11.8.2", + "jose": "^4.1.4", + "jsesc": "^3.0.2", + "koa": "^2.13.3", "koa-compose": "^4.1.0", - "lru-cache": "^6.0.0", - "nanoid": "^3.1.10", - "object-hash": "^2.0.3", + "nanoid": "^3.1.28", + "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.1", + "paseto2": "npm:paseto@^2.1.3", + "quick-lru": "^5.1.1", "raw-body": "^2.4.1" }, "engines": { - "node": "^10.13.0 || >=12.0.0" + "node": "^12.19.0 || ^14.15.0 || ^16.13.0" }, "funding": { "url": "https://github.com/sponsors/panva" + }, + "optionalDependencies": { + "paseto3": "npm:paseto@^3.0.0" } }, - "node_modules/oidc-provider/node_modules/jose": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", - "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", - "dependencies": { - "@panva/asn1.js": "^1.0.0" - }, + "node_modules/oidc-provider/node_modules/@sindresorhus/is": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.4.0.tgz", + "integrity": "sha512-QppPM/8l3Mawvh4rn9CNEYIU9bxpXUCRMaX9yUpvBk1nMKusLKpfXGDEKExKaPhLzcn3lzil7pR6rnJ11HgeRQ==", "engines": { - "node": ">=10.13.0 < 13 || >=13.7.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/panva" + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/oidc-provider/node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/oidc-provider/node_modules/cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/oidc-provider/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oidc-provider/node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/oidc-provider/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oidc-provider/node_modules/got": { + "version": "11.8.3", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz", + "integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/oidc-provider/node_modules/got/node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" } }, "node_modules/oidc-provider/node_modules/jsesc": { @@ -12027,6 +12163,76 @@ "node": ">=6" } }, + "node_modules/oidc-provider/node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/oidc-provider/node_modules/keyv": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.1.1.tgz", + "integrity": "sha512-tGv1yP6snQVDSM4X6yxrv2zzq/EvpW+oYiUz6aueW1u9CtS8RzUQYxxmFwgZlO2jSgCxQbchhxaqXXp2hnKGpQ==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/oidc-provider/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/oidc-provider/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oidc-provider/node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oidc-provider/node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/oidc-provider/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oidc-provider/node_modules/responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + } + }, "node_modules/oidc-token-hash": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz", @@ -12130,6 +12336,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, "engines": { "node": ">=6" } @@ -12255,6 +12462,31 @@ "node": ">= 0.8" } }, + "node_modules/paseto2": { + "name": "paseto", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/paseto/-/paseto-2.1.3.tgz", + "integrity": "sha512-BNkbvr0ZFDbh3oV13QzT5jXIu8xpFc9r0o5mvWBhDU1GBkVt1IzHK1N6dcYmN7XImrUmPQ0HCUXmoe2WPo8xsg==", + "engines": { + "node": "^12.19.0 || >=14.15.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/paseto3": { + "name": "paseto", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/paseto/-/paseto-3.1.0.tgz", + "integrity": "sha512-oVSKoCH89M0WU3I+13NoCP9wGRel0BlQumwxsDZPk1yJtqS76PWKRM7vM9D4bz4PcScT0aIiAipC7lW6hSgkBQ==", + "optional": true, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -12370,6 +12602,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, "engines": { "node": ">=4" } @@ -12571,11 +12804,6 @@ "node": ">= 0.6" } }, - "node_modules/raw-body/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -13047,6 +13275,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -13090,6 +13323,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, "dependencies": { "lowercase-keys": "^1.0.0" } @@ -13972,6 +14206,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true, "engines": { "node": ">=6" } @@ -14349,6 +14584,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, "dependencies": { "prepend-http": "^2.0.0" }, @@ -18080,11 +18316,6 @@ "fastq": "^1.6.0" } }, - "@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" - }, "@rdfjs/types": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.0.1.tgz", @@ -18096,7 +18327,8 @@ "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true }, "@sinonjs/commons": { "version": "1.8.3", @@ -18133,6 +18365,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, "requires": { "defer-to-connect": "^1.0.1" } @@ -18232,6 +18465,17 @@ "@types/node": "*" } }, + "@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "@types/cheerio": { "version": "0.22.30", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.30.tgz", @@ -18334,6 +18578,11 @@ "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz", "integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==" }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, "@types/http-errors": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", @@ -18398,6 +18647,14 @@ "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==" }, + "@types/keyv": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", + "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", + "requires": { + "@types/node": "*" + } + }, "@types/koa": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.1.tgz", @@ -18496,6 +18753,14 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "@types/oidc-provider": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/@types/oidc-provider/-/oidc-provider-7.8.1.tgz", + "integrity": "sha512-MsmVKYFN9i27kfJh3hqj7F6aQNue6A/1aBKVJH07I3WYMriUDqMtYU0MWtheFPI1Tm9kwa0JHheaOdNRjuxboA==", + "requires": { + "@types/koa": "*" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -18569,6 +18834,14 @@ "@types/bluebird": "*" } }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, "@types/semver": { "version": "7.3.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.6.tgz", @@ -19071,11 +19344,6 @@ } } }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -19446,10 +19714,16 @@ "ylru": "^1.2.0" } }, + "cacheable-lookup": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz", + "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==" + }, "cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, "requires": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -19464,6 +19738,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, "requires": { "pump": "^3.0.0" } @@ -19471,7 +19746,8 @@ "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true } } }, @@ -20071,6 +20347,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, "requires": { "mimic-response": "^1.0.0" } @@ -20107,7 +20384,8 @@ "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true }, "define-properties": { "version": "1.1.3", @@ -20140,9 +20418,9 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.1.0.tgz", + "integrity": "sha512-R5QZrOXxSs0JDUIU/VANvRJlQVMts9C0L76HToQdPdlftfZCE7W6dyH0G4GZ5UW9fRqUOhAoCE2aGekuu+3HjQ==" }, "detect-libc": { "version": "1.0.3", @@ -20261,7 +20539,8 @@ "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true }, "ee-first": { "version": "1.1.1", @@ -21402,6 +21681,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, "requires": { "pump": "^3.0.0" } @@ -21429,12 +21709,6 @@ "through2": "^3.0.0" }, "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, "through2": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", @@ -21694,6 +21968,7 @@ "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, "requires": { "@sindresorhus/is": "^0.14.0", "@szmarczak/http-timer": "^1.1.2", @@ -21827,14 +22102,12 @@ "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" }, "has-tostringtag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -21908,31 +22181,12 @@ } }, "http-assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", - "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", "requires": { "deep-equal": "~1.0.1", - "http-errors": "~1.7.2" - }, - "dependencies": { - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - } + "http-errors": "~1.8.0" } }, "http-cache-semantics": { @@ -21941,26 +22195,26 @@ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "requires": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "toidentifier": "1.0.1" }, "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" } } }, @@ -21979,6 +22233,22 @@ "debug": "4" } }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "dependencies": { + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + } + } + }, "https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -22140,9 +22410,9 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -22256,9 +22526,12 @@ "dev": true }, "is-generator-function": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz", - "integrity": "sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-glob": { "version": "4.0.3", @@ -23126,7 +23399,8 @@ "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true }, "json-parse-better-errors": { "version": "1.0.2", @@ -23265,6 +23539,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, "requires": { "json-buffer": "3.0.0" } @@ -23282,16 +23557,16 @@ "dev": true }, "koa": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.1.tgz", - "integrity": "sha512-Lb2Dloc72auj5vK4X4qqL7B5jyDPQaZucc9sR/71byg7ryoD1NCaCm63CShk9ID9quQvDEi1bGR/iGjCG7As3w==", + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.4.tgz", + "integrity": "sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==", "requires": { "accepts": "^1.3.5", "cache-content-type": "^1.0.0", "content-disposition": "~0.5.2", "content-type": "^1.0.4", "cookies": "~0.8.0", - "debug": "~3.1.0", + "debug": "^4.3.2", "delegates": "^1.0.0", "depd": "^2.0.0", "destroy": "^1.0.4", @@ -23302,7 +23577,7 @@ "http-errors": "^1.6.3", "is-generator-function": "^1.0.7", "koa-compose": "^4.1.0", - "koa-convert": "^1.2.0", + "koa-convert": "^2.0.0", "on-finished": "^2.3.0", "only": "~0.0.2", "parseurl": "^1.3.2", @@ -23311,14 +23586,6 @@ "vary": "^1.1.2" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -23332,22 +23599,12 @@ "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" }, "koa-convert": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", - "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", "requires": { "co": "^4.6.0", - "koa-compose": "^3.0.0" - }, - "dependencies": { - "koa-compose": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", - "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", - "requires": { - "any-promise": "^1.1.0" - } - } + "koa-compose": "^4.1.0" } }, "kuler": { @@ -23520,7 +23777,8 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true }, "lru-cache": { "version": "6.0.0", @@ -23788,9 +24046,9 @@ } }, "nanoid": { - "version": "3.1.22", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz", - "integrity": "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.0.tgz", + "integrity": "sha512-JzxqqT5u/x+/KOFSd7JP15DOo9nOoHpx6DYatqIHUW2+flybkm+mdcraotSQR5WcnZr+qhGVh8Ted0KdfSMxlg==" }, "natural-compare": { "version": "1.4.0", @@ -23976,7 +24234,8 @@ "normalize-url": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true }, "npm-run-path": { "version": "4.0.1", @@ -24024,9 +24283,9 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-hash": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.1.1.tgz", - "integrity": "sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" }, "object-inspect": { "version": "1.11.1", @@ -24099,38 +24358,151 @@ } }, "oidc-provider": { - "version": "6.31.1", - "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-6.31.1.tgz", - "integrity": "sha512-YYfcJKGrobdaW2v7bx5crd4yfxFTKAoOTHdQs/gsu6fwzc/yD/qjforQX2VgbTX+ySK8nm7EaAwJuJJSFRo1MA==", + "version": "7.10.6", + "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-7.10.6.tgz", + "integrity": "sha512-7fbnormUyTLP34dmR5WXoJtTWtfj6MsFNzIMKVRKv21e18NIXggn14EBUFC5rrMMtmeExb03+lJI/v+opD+0oQ==", "requires": { "@koa/cors": "^3.1.0", - "@types/koa": "^2.11.4", - "debug": "^4.1.1", - "ejs": "^3.1.5", - "got": "^9.6.0", - "jose": "^2.0.4", - "jsesc": "^3.0.1", - "koa": "^2.13.0", + "cacheable-lookup": "^6.0.1", + "debug": "^4.3.2", + "ejs": "^3.1.6", + "got": "^11.8.2", + "jose": "^4.1.4", + "jsesc": "^3.0.2", + "koa": "^2.13.3", "koa-compose": "^4.1.0", - "lru-cache": "^6.0.0", - "nanoid": "^3.1.10", - "object-hash": "^2.0.3", + "nanoid": "^3.1.28", + "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.1", + "paseto2": "npm:paseto@^2.1.3", + "paseto3": "npm:paseto@^3.0.0", + "quick-lru": "^5.1.1", "raw-body": "^2.4.1" }, "dependencies": { - "jose": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", - "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "@sindresorhus/is": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.4.0.tgz", + "integrity": "sha512-QppPM/8l3Mawvh4rn9CNEYIU9bxpXUCRMaX9yUpvBk1nMKusLKpfXGDEKExKaPhLzcn3lzil7pR6rnJ11HgeRQ==" + }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", "requires": { - "@panva/asn1.js": "^1.0.0" + "defer-to-connect": "^2.0.0" + } + }, + "cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "11.8.3", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz", + "integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "dependencies": { + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + } } }, "jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==" + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "keyv": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.1.1.tgz", + "integrity": "sha512-tGv1yP6snQVDSM4X6yxrv2zzq/EvpW+oYiUz6aueW1u9CtS8RzUQYxxmFwgZlO2jSgCxQbchhxaqXXp2hnKGpQ==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } } } }, @@ -24212,7 +24584,8 @@ "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true }, "p-limit": { "version": "2.3.0", @@ -24307,6 +24680,17 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "paseto2": { + "version": "npm:paseto@2.1.3", + "resolved": "https://registry.npmjs.org/paseto/-/paseto-2.1.3.tgz", + "integrity": "sha512-BNkbvr0ZFDbh3oV13QzT5jXIu8xpFc9r0o5mvWBhDU1GBkVt1IzHK1N6dcYmN7XImrUmPQ0HCUXmoe2WPo8xsg==" + }, + "paseto3": { + "version": "npm:paseto@3.1.0", + "resolved": "https://registry.npmjs.org/paseto/-/paseto-3.1.0.tgz", + "integrity": "sha512-oVSKoCH89M0WU3I+13NoCP9wGRel0BlQumwxsDZPk1yJtqS76PWKRM7vM9D4bz4PcScT0aIiAipC7lW6hSgkBQ==", + "optional": true + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -24388,7 +24772,8 @@ "prepend-http": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true }, "pretty-format": { "version": "27.4.6", @@ -24540,11 +24925,6 @@ "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" } } }, @@ -24943,6 +25323,11 @@ "path-parse": "^1.0.6" } }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -24976,6 +25361,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, "requires": { "lowercase-keys": "^1.0.0" } @@ -25693,7 +26079,8 @@ "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true }, "to-regex-range": { "version": "5.0.1", @@ -25960,6 +26347,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, "requires": { "prepend-http": "^2.0.0" } diff --git a/package.json b/package.json index 1ddbc435d..814bbc0ce 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "@types/n3": "^1.10.4", "@types/node": "^14.18.0", "@types/nodemailer": "^6.4.4", + "@types/oidc-provider": "^7.8.1", "@types/pump": "^1.1.1", "@types/punycode": "^2.1.0", "@types/redis": "^2.8.30", @@ -114,7 +115,7 @@ "mime-types": "^2.1.34", "n3": "^1.13.0", "nodemailer": "^6.7.2", - "oidc-provider": "^6.31.1", + "oidc-provider": "^7.10.6", "pump": "^3.0.0", "punycode": "^2.1.1", "rdf-dereference": "^1.9.0", diff --git a/src/identity/OidcHttpHandler.ts b/src/identity/OidcHttpHandler.ts index a8a3fe924..03fb340d6 100644 --- a/src/identity/OidcHttpHandler.ts +++ b/src/identity/OidcHttpHandler.ts @@ -20,8 +20,8 @@ export class OidcHttpHandler extends HttpHandler { const provider = await this.providerFactory.getProvider(); this.logger.debug(`Sending request to oidc-provider: ${request.url}`); // Even though the typings do not indicate this, this is a Promise that needs to be awaited. - // Otherwise the `BaseHttpServerFactory` will write a 404 before the OIDC library could handle the response. + // Otherwise, the `BaseHttpServerFactory` will write a 404 before the OIDC library could handle the response. // eslint-disable-next-line @typescript-eslint/await-thenable - await provider.callback(request, response); + await provider.callback()(request, response); } } diff --git a/src/identity/configuration/IdentityProviderFactory.ts b/src/identity/configuration/IdentityProviderFactory.ts index fc3125422..3f094e459 100644 --- a/src/identity/configuration/IdentityProviderFactory.ts +++ b/src/identity/configuration/IdentityProviderFactory.ts @@ -4,13 +4,14 @@ import { randomBytes } from 'crypto'; import type { JWK } from 'jose'; import { exportJWK, generateKeyPair } from 'jose'; -import type { AnyObject, +import type { Account, + Adapter, CanBePromise, - KoaContextWithOIDC, Configuration, - Account, ErrorOut, - Adapter } from 'oidc-provider'; + KoaContextWithOIDC, + ResourceServer, + UnknownObject } from 'oidc-provider'; import { Provider } from 'oidc-provider'; import type { Operation } from '../../http/Operation'; import type { ErrorHandler } from '../../http/output/error/ErrorHandler'; @@ -75,6 +76,7 @@ export class IdentityProviderFactory implements ProviderFactory { private readonly errorHandler!: ErrorHandler; private readonly responseWriter!: ResponseWriter; + private readonly jwtAlg = 'ES256'; private provider?: Provider; /** @@ -139,11 +141,23 @@ export class IdentityProviderFactory implements ProviderFactory { keys: await this.generateCookieKeys(), }; + // Solid OIDC requires pkce https://solid.github.io/solid-oidc/#concepts + config.pkce = { + methods: [ 'S256' ], + required: (): true => true, + }; + + // Default client settings that might not be defined. + // Mostly relevant for WebID clients. + config.clientDefaults = { + id_token_signed_response_alg: this.jwtAlg, + }; + return config; } /** - * Generates a JWKS using a single RS256 JWK.. + * Generates a JWKS using a single JWK. * The JWKS will be cached so subsequent calls return the same key. */ private async generateJwks(): Promise<{ keys: JWK[] }> { @@ -153,10 +167,10 @@ export class IdentityProviderFactory implements ProviderFactory { return jwks; } // If they are not, generate and save them - const { privateKey } = await generateKeyPair('RS256'); + const { privateKey } = await generateKeyPair(this.jwtAlg); const jwk = await exportJWK(privateKey); // Required for Solid authn client - jwk.alg = 'RS256'; + jwk.alg = this.jwtAlg; // In node v15.12.0 the JWKS does not get accepted because the JWK is not a plain object, // which is why we convert it into a plain object here. // Potentially this can be changed at a later point in time to `{ keys: [ jwk ]}`. @@ -190,28 +204,51 @@ export class IdentityProviderFactory implements ProviderFactory { } /** - * Adds the necessary claims the to id token and access token based on the Solid OIDC spec. + * Adds the necessary claims the to id and access tokens based on the Solid OIDC spec. */ private configureClaims(config: Configuration): void { - // Access token audience is 'solid', ID token audience is the client_id - config.audiences = (ctx, sub, token, use): string => - use === 'access_token' ? 'solid' : token.clientId!; - // Returns the id_token // See https://solid.github.io/authentication-panel/solid-oidc/#tokens-id + // Some fields are still missing, see https://github.com/solid/community-server/issues/1154#issuecomment-1040233385 config.findAccount = async(ctx: KoaContextWithOIDC, sub: string): Promise => ({ accountId: sub, - claims: async(): Promise<{ sub: string; [key: string]: any }> => - ({ sub, webid: sub }), + async claims(): Promise<{ sub: string; [key: string]: any }> { + return { sub, webid: sub, azp: ctx.oidc.client?.clientId }; + }, }); // Add extra claims in case an AccessToken is being issued. // Specifically this sets the required webid and client_id claims for the access token - // See https://solid.github.io/authentication-panel/solid-oidc/#tokens-access - config.extraAccessTokenClaims = (ctx, token): CanBePromise => + // See https://solid.github.io/solid-oidc/#resource-access-validation + config.extraTokenClaims = (ctx, token): CanBePromise => this.isAccessToken(token) ? - { webid: token.accountId, client_id: token.clientId } : + { webid: token.accountId } : {}; + + config.features = { + ...config.features, + resourceIndicators: { + defaultResource(): string { + // This value is irrelevant, but is necessary to trigger the `getResourceServerInfo` call below, + // where it will be an input parameter in case the client provided no value. + // Note that an empty string is not a valid value. + return 'http://example.com/'; + }, + enabled: true, + // This call is necessary to force the OIDC library to return a JWT access token. + // See https://github.com/panva/node-oidc-provider/discussions/959#discussioncomment-524757 + getResourceServerInfo: (): ResourceServer => ({ + // The scopes of the Resource Server. + // Since this is irrelevant at the moment, an empty string is fine. + scope: '', + audience: 'solid', + accessTokenFormat: 'jwt', + jwt: { + sign: { alg: this.jwtAlg }, + }, + }), + }, + }; } /** @@ -230,6 +267,7 @@ export class IdentityProviderFactory implements ProviderFactory { // When oidc-provider cannot fulfill the authorization request for any of the possible reasons // (missing user session, requested ACR not fulfilled, prompt requested, ...) // it will resolve the interactions.url helper function and redirect the User-Agent to that url. + // Another requirement is that `features.userinfo` is disabled in the configuration. config.interactions = { url: async(ctx, oidcInteraction): Promise => { const operation: Operation = { @@ -255,7 +293,7 @@ export class IdentityProviderFactory implements ProviderFactory { config.routes = { authorization: this.createRoute('auth'), - check_session: this.createRoute('session/check'), + backchannel_authentication: this.createRoute('backchannel'), code_verification: this.createRoute('device'), device_authorization: this.createRoute('device/auth'), end_session: this.createRoute('session/end'), diff --git a/src/identity/interaction/CompletingInteractionHandler.ts b/src/identity/interaction/CompletingInteractionHandler.ts deleted file mode 100644 index 3a460011e..000000000 --- a/src/identity/interaction/CompletingInteractionHandler.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError'; -import { FoundHttpError } from '../../util/errors/FoundHttpError'; -import { BaseInteractionHandler } from './BaseInteractionHandler'; -import type { InteractionHandlerInput } from './InteractionHandler'; -import type { InteractionCompleterInput, InteractionCompleter } from './util/InteractionCompleter'; - -/** - * Abstract extension of {@link BaseInteractionHandler} for handlers that need to call an {@link InteractionCompleter}. - * This is required by handlers that handle IDP behaviour - * and need to complete an OIDC interaction by redirecting back to the client, - * such as when logging in. - * - * Calls the InteractionCompleter with the results returned by the helper function - * and throw a corresponding {@link FoundHttpError}. - */ -export abstract class CompletingInteractionHandler extends BaseInteractionHandler { - protected readonly interactionCompleter: InteractionCompleter; - - protected constructor(view: Record, interactionCompleter: InteractionCompleter) { - super(view); - this.interactionCompleter = interactionCompleter; - } - - public async canHandle(input: InteractionHandlerInput): Promise { - await super.canHandle(input); - if (input.operation.method === 'POST' && !input.oidcInteraction) { - throw new BadRequestHttpError( - 'This action can only be performed as part of an OIDC authentication flow.', - { errorCode: 'E0002' }, - ); - } - } - - public async handlePost(input: InteractionHandlerInput): Promise { - // Interaction is defined due to canHandle call - const parameters = await this.getCompletionParameters(input as Required); - const location = await this.interactionCompleter.handleSafe(parameters); - throw new FoundHttpError(location); - } - - /** - * Generates the parameters necessary to call an InteractionCompleter. - * The input parameters are the same that the `handlePost` function was called with. - * @param input - The original input parameters to the `handle` function. - */ - protected abstract getCompletionParameters(input: Required): - Promise; -} diff --git a/src/identity/interaction/ConsentHandler.ts b/src/identity/interaction/ConsentHandler.ts new file mode 100644 index 000000000..a7cf11144 --- /dev/null +++ b/src/identity/interaction/ConsentHandler.ts @@ -0,0 +1,111 @@ +import type { InteractionResults, KoaContextWithOIDC, UnknownObject } from 'oidc-provider'; +import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError'; +import { FoundHttpError } from '../../util/errors/FoundHttpError'; +import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; +import { readJsonStream } from '../../util/StreamUtil'; +import type { ProviderFactory } from '../configuration/ProviderFactory'; +import { BaseInteractionHandler } from './BaseInteractionHandler'; +import type { Interaction, InteractionHandlerInput } from './InteractionHandler'; + +type Grant = NonNullable; + +/** + * Handles the OIDC consent prompts where the user confirms they want to log in for the given client. + */ +export class ConsentHandler extends BaseInteractionHandler { + private readonly providerFactory: ProviderFactory; + + public constructor(providerFactory: ProviderFactory) { + super({}); + this.providerFactory = providerFactory; + } + + public async canHandle(input: InteractionHandlerInput): Promise { + await super.canHandle(input); + if (input.operation.method === 'POST' && !input.oidcInteraction) { + throw new BadRequestHttpError( + 'This action can only be performed as part of an OIDC authentication flow.', + { errorCode: 'E0002' }, + ); + } + } + + protected async handlePost({ operation, oidcInteraction }: InteractionHandlerInput): Promise { + const { remember } = await readJsonStream(operation.body.data); + + const grant = await this.getGrant(oidcInteraction!); + this.updateGrant(grant, oidcInteraction!.prompt.details, remember); + + const location = await this.updateInteraction(oidcInteraction!, grant); + + throw new FoundHttpError(location); + } + + /** + * Either returns the grant associated with the given interaction or creates a new one if it does not exist yet. + */ + private async getGrant(oidcInteraction: Interaction): Promise { + if (!oidcInteraction.session) { + throw new NotImplementedHttpError('Only interactions with a valid session are supported.'); + } + + const { params, session: { accountId }, grantId } = oidcInteraction; + const provider = await this.providerFactory.getProvider(); + let grant: Grant; + if (grantId) { + grant = (await provider.Grant.find(grantId))!; + } else { + grant = new provider.Grant({ + accountId, + clientId: params.client_id as string, + }); + } + return grant; + } + + /** + * Updates the grant with all the missing scopes and claims requested by the interaction. + * + * Will reject the `offline_access` scope if `remember` is false. + */ + private updateGrant(grant: Grant, details: UnknownObject, remember: boolean): void { + // Reject the offline_access scope if the user does not want to be remembered + if (!remember) { + grant.rejectOIDCScope('offline_access'); + } + + // Grant all the requested scopes and claims + if (details.missingOIDCScope) { + grant.addOIDCScope((details.missingOIDCScope as string[]).join(' ')); + } + if (details.missingOIDCClaims) { + grant.addOIDCClaims(details.missingOIDCClaims as string[]); + } + if (details.missingResourceScopes) { + for (const [ indicator, scopes ] of Object.entries(details.missingResourceScopes as Record)) { + grant.addResourceScope(indicator, scopes.join(' ')); + } + } + } + + /** + * Updates the interaction with the new grant and returns the resulting redirect URL. + */ + private async updateInteraction(oidcInteraction: Interaction, grant: Grant): Promise { + const grantId = await grant.save(); + + const consent: InteractionResults['consent'] = {}; + // Only need to update the grantId if it is new + if (!oidcInteraction.grantId) { + consent.grantId = grantId; + } + + const result: InteractionResults = { consent }; + + // Need to merge with previous submission + oidcInteraction.result = { ...oidcInteraction.lastSubmission, ...result }; + await oidcInteraction.save(oidcInteraction.exp - Math.floor(Date.now() / 1000)); + + return oidcInteraction.returnTo; + } +} diff --git a/src/identity/interaction/ExistingLoginHandler.ts b/src/identity/interaction/ExistingLoginHandler.ts deleted file mode 100644 index 94755405f..000000000 --- a/src/identity/interaction/ExistingLoginHandler.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; -import { readJsonStream } from '../../util/StreamUtil'; -import { CompletingInteractionHandler } from './CompletingInteractionHandler'; -import type { InteractionHandlerInput } from './InteractionHandler'; -import type { InteractionCompleter, InteractionCompleterInput } from './util/InteractionCompleter'; - -/** - * Simple CompletingInteractionRoute that returns the session accountId as webId. - * This is relevant when a client already logged in this session and tries logging in again. - */ -export class ExistingLoginHandler extends CompletingInteractionHandler { - public constructor(interactionCompleter: InteractionCompleter) { - super({}, interactionCompleter); - } - - protected async getCompletionParameters({ operation, oidcInteraction }: Required): - Promise { - if (!oidcInteraction.session) { - throw new NotImplementedHttpError('Only interactions with a valid session are supported.'); - } - - const { remember } = await readJsonStream(operation.body.data); - return { oidcInteraction, webId: oidcInteraction.session.accountId, shouldRemember: Boolean(remember) }; - } -} diff --git a/src/identity/interaction/email-password/handler/ForgotPasswordHandler.ts b/src/identity/interaction/email-password/handler/ForgotPasswordHandler.ts index a4ad43ebc..500691273 100644 --- a/src/identity/interaction/email-password/handler/ForgotPasswordHandler.ts +++ b/src/identity/interaction/email-password/handler/ForgotPasswordHandler.ts @@ -8,8 +8,8 @@ import type { TemplateEngine } from '../../../../util/templates/TemplateEngine'; import { BaseInteractionHandler } from '../../BaseInteractionHandler'; import type { InteractionHandlerInput } from '../../InteractionHandler'; import type { InteractionRoute } from '../../routing/InteractionRoute'; -import type { EmailSender } from '../../util/EmailSender'; import type { AccountStore } from '../storage/AccountStore'; +import type { EmailSender } from '../util/EmailSender'; const forgotPasswordView = { required: { diff --git a/src/identity/interaction/email-password/handler/LoginHandler.ts b/src/identity/interaction/email-password/handler/LoginHandler.ts index 0f8e9f04c..6b6a9c1c3 100644 --- a/src/identity/interaction/email-password/handler/LoginHandler.ts +++ b/src/identity/interaction/email-password/handler/LoginHandler.ts @@ -1,11 +1,12 @@ import assert from 'assert'; +import type { InteractionResults } from 'oidc-provider'; import type { Operation } from '../../../../http/Operation'; import { getLoggerFor } from '../../../../logging/LogUtil'; import { BadRequestHttpError } from '../../../../util/errors/BadRequestHttpError'; +import { FoundHttpError } from '../../../../util/errors/FoundHttpError'; import { readJsonStream } from '../../../../util/StreamUtil'; -import { CompletingInteractionHandler } from '../../CompletingInteractionHandler'; +import { BaseInteractionHandler } from '../../BaseInteractionHandler'; import type { InteractionHandlerInput } from '../../InteractionHandler'; -import type { InteractionCompleterInput, InteractionCompleter } from '../../util/InteractionCompleter'; import type { AccountStore } from '../storage/AccountStore'; const loginView = { @@ -26,19 +27,27 @@ interface LoginInput { * Handles the submission of the Login Form and logs the user in. * Will throw a RedirectHttpError on success. */ -export class LoginHandler extends CompletingInteractionHandler { +export class LoginHandler extends BaseInteractionHandler { protected readonly logger = getLoggerFor(this); private readonly accountStore: AccountStore; - public constructor(accountStore: AccountStore, interactionCompleter: InteractionCompleter) { - super(loginView, interactionCompleter); + public constructor(accountStore: AccountStore) { + super(loginView); this.accountStore = accountStore; } - protected async getCompletionParameters(input: Required): - Promise { - const { operation, oidcInteraction } = input; + public async canHandle(input: InteractionHandlerInput): Promise { + await super.canHandle(input); + if (input.operation.method === 'POST' && !input.oidcInteraction) { + throw new BadRequestHttpError( + 'This action can only be performed as part of an OIDC authentication flow.', + { errorCode: 'E0002' }, + ); + } + } + + public async handlePost({ operation, oidcInteraction }: InteractionHandlerInput): Promise { const { email, password, remember } = await this.parseInput(operation); // Try to log in, will error if email/password combination is invalid const webId = await this.accountStore.authenticate(email, password); @@ -49,7 +58,15 @@ export class LoginHandler extends CompletingInteractionHandler { } this.logger.debug(`Logging in user ${email}`); - return { oidcInteraction, webId, shouldRemember: remember }; + // Update the interaction to get the redirect URL + const login: InteractionResults['login'] = { + accountId: webId, + remember, + }; + oidcInteraction!.result = { login }; + await oidcInteraction!.save(oidcInteraction!.exp - Math.floor(Date.now() / 1000)); + + throw new FoundHttpError(oidcInteraction!.returnTo); } /** diff --git a/src/identity/interaction/util/BaseEmailSender.ts b/src/identity/interaction/email-password/util/BaseEmailSender.ts similarity index 100% rename from src/identity/interaction/util/BaseEmailSender.ts rename to src/identity/interaction/email-password/util/BaseEmailSender.ts diff --git a/src/identity/interaction/util/EmailSender.ts b/src/identity/interaction/email-password/util/EmailSender.ts similarity index 75% rename from src/identity/interaction/util/EmailSender.ts rename to src/identity/interaction/email-password/util/EmailSender.ts index 30a9a748d..ac6198e54 100644 --- a/src/identity/interaction/util/EmailSender.ts +++ b/src/identity/interaction/email-password/util/EmailSender.ts @@ -1,4 +1,4 @@ -import { AsyncHandler } from '../../../util/handlers/AsyncHandler'; +import { AsyncHandler } from '../../../../util/handlers/AsyncHandler'; export interface EmailArgs { recipient: string; diff --git a/src/identity/interaction/util/BaseInteractionCompleter.ts b/src/identity/interaction/util/BaseInteractionCompleter.ts deleted file mode 100644 index f70cc24a9..000000000 --- a/src/identity/interaction/util/BaseInteractionCompleter.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { InteractionResults } from 'oidc-provider'; -import type { InteractionCompleterInput } from './InteractionCompleter'; -import { InteractionCompleter } from './InteractionCompleter'; - -/** - * Creates a simple InteractionResults object based on the input parameters and injects it in the Interaction. - */ -export class BaseInteractionCompleter extends InteractionCompleter { - public async handle(input: InteractionCompleterInput): Promise { - const now = Math.floor(Date.now() / 1000); - const result: InteractionResults = { - login: { - account: input.webId, - // Indicates if a persistent cookie should be used instead of a session cookie. - remember: input.shouldRemember, - ts: now, - }, - consent: { - // When OIDC clients want a refresh token, they need to request the 'offline_access' scope. - // This indicates that this scope is not granted to the client in case they do not want to be remembered. - rejectedScopes: input.shouldRemember ? [] : [ 'offline_access' ], - }, - }; - - // Generates the URL a client needs to be redirected to - // after a successful interaction completion (such as logging in). - // Identical behaviour to calling `provider.interactionResult`. - // We use the code below instead of calling that function - // since that function also uses Request/Response objects to generate the Interaction object, - // which we already have here. - const { oidcInteraction } = input; - oidcInteraction.result = { ...oidcInteraction.lastSubmission, ...result }; - await oidcInteraction.save(oidcInteraction.exp - now); - - return oidcInteraction.returnTo; - } -} diff --git a/src/identity/interaction/util/InteractionCompleter.ts b/src/identity/interaction/util/InteractionCompleter.ts deleted file mode 100644 index 20dd4ecef..000000000 --- a/src/identity/interaction/util/InteractionCompleter.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AsyncHandler } from '../../../util/handlers/AsyncHandler'; -import type { Interaction } from '../InteractionHandler'; - -/** - * Parameters required to specify how the interaction should be completed. - */ -export interface InteractionCompleterInput { - oidcInteraction: Interaction; - webId: string; - shouldRemember?: boolean; -} - -/** - * Class responsible for completing the interaction based on the parameters provided. - */ -export abstract class InteractionCompleter extends AsyncHandler {} diff --git a/src/index.ts b/src/index.ts index 12dd3b8e9..47587d80e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -139,6 +139,8 @@ export * from './identity/interaction/email-password/storage/AccountStore'; export * from './identity/interaction/email-password/storage/BaseAccountStore'; // Identity/Interaction/Email-Password/Util +export * from './identity/interaction/email-password/util/BaseEmailSender'; +export * from './identity/interaction/email-password/util/EmailSender'; export * from './identity/interaction/email-password/util/RegistrationManager'; // Identity/Interaction/Email-Password @@ -150,16 +152,9 @@ export * from './identity/interaction/routing/InteractionRoute'; export * from './identity/interaction/routing/InteractionRouteHandler'; export * from './identity/interaction/routing/RelativePathInteractionRoute'; -// Identity/Interaction/Util -export * from './identity/interaction/util/BaseEmailSender'; -export * from './identity/interaction/util/BaseInteractionCompleter'; -export * from './identity/interaction/util/EmailSender'; -export * from './identity/interaction/util/InteractionCompleter'; - // Identity/Interaction export * from './identity/interaction/BaseInteractionHandler'; -export * from './identity/interaction/CompletingInteractionHandler'; -export * from './identity/interaction/ExistingLoginHandler'; +export * from './identity/interaction/ConsentHandler'; export * from './identity/interaction/ControlHandler'; export * from './identity/interaction/FixedInteractionHandler'; export * from './identity/interaction/HtmlViewHandler'; diff --git a/test/integration/Identity.test.ts b/test/integration/Identity.test.ts index 3be14c145..5e3ddacf0 100644 --- a/test/integration/Identity.test.ts +++ b/test/integration/Identity.test.ts @@ -138,10 +138,11 @@ describe('A Solid server with IDP', (): void => { }); it('initializes the session and logs in.', async(): Promise => { - const url = await state.startSession(); + let url = await state.startSession(); const res = await state.fetchIdp(url); expect(res.status).toBe(200); - await state.login(url, email, password); + url = await state.login(url, email, password); + await state.consent(url); expect(state.session.info?.webId).toBe(webId); }); @@ -162,16 +163,12 @@ describe('A Solid server with IDP', (): void => { it('can log in again.', async(): Promise => { const url = await state.startSession(); - let res = await state.fetchIdp(url); + const res = await state.fetchIdp(url); expect(res.status).toBe(200); // Will receive confirm screen here instead of login screen - res = await state.fetchIdp(url, 'POST', '', APPLICATION_X_WWW_FORM_URLENCODED); - const json = await res.json(); - const nextUrl = json.location; - expect(typeof nextUrl).toBe('string'); + await state.consent(url); - await state.handleLoginRedirect(nextUrl); expect(state.session.info?.webId).toBe(webId); }); }); @@ -223,10 +220,11 @@ describe('A Solid server with IDP', (): void => { }); it('initializes the session and logs in.', async(): Promise => { - const url = await state.startSession(clientId); + let url = await state.startSession(clientId); const res = await state.fetchIdp(url); expect(res.status).toBe(200); - await state.login(url, email, password); + url = await state.login(url, email, password); + await state.consent(url); expect(state.session.info?.webId).toBe(webId); }); @@ -318,7 +316,8 @@ describe('A Solid server with IDP', (): void => { }); it('can log in with the new password.', async(): Promise => { - await state.login(nextUrl, email, password2); + const url = await state.login(nextUrl, email, password2); + await state.consent(url); expect(state.session.info?.webId).toBe(webId); }); }); @@ -397,10 +396,11 @@ describe('A Solid server with IDP', (): void => { it('initializes the session and logs in.', async(): Promise => { state = new IdentityTestState(baseUrl, redirectUrl, oidcIssuer); - const url = await state.startSession(); + let url = await state.startSession(); const res = await state.fetchIdp(url); expect(res.status).toBe(200); - await state.login(url, newMail, password); + url = await state.login(url, newMail, password); + await state.consent(url); expect(state.session.info?.webId).toBe(newWebId); }); diff --git a/test/integration/IdentityTestState.ts b/test/integration/IdentityTestState.ts index 17f4364bb..836fad1ad 100644 --- a/test/integration/IdentityTestState.ts +++ b/test/integration/IdentityTestState.ts @@ -89,7 +89,7 @@ export class IdentityTestState { // Need to catch the redirect so we can copy the cookies let res = await this.fetchIdp(nextUrl); - expect(res.status).toBe(302); + expect(res.status).toBe(303); nextUrl = res.headers.get('location')!; // Handle redirect @@ -109,22 +109,26 @@ export class IdentityTestState { * Logs in by sending the corresponding email and password to the given form action. * The URL should be extracted from the login page. */ - public async login(url: string, email: string, password: string): Promise { + public async login(url: string, email: string, password: string): Promise { const formData = stringify({ email, password }); - const res = await this.fetchIdp(url, 'POST', formData, APPLICATION_X_WWW_FORM_URLENCODED); + let res = await this.fetchIdp(url, 'POST', formData, APPLICATION_X_WWW_FORM_URLENCODED); expect(res.status).toBe(200); const json = await res.json(); - const nextUrl = json.location; - - return this.handleLoginRedirect(nextUrl); + res = await this.fetchIdp(json.location); + expect(res.status).toBe(303); + return res.headers.get('location')!; } /** - * Handles the redirect that happens after logging in. + * Handles the consent screen at the given URL and the followup redirect back to the client. */ - public async handleLoginRedirect(url: string): Promise { - const res = await this.fetchIdp(url); - expect(res.status).toBe(302); + public async consent(url: string): Promise { + let res = await this.fetchIdp(url, 'POST', '', APPLICATION_X_WWW_FORM_URLENCODED); + expect(res.status).toBe(200); + const json = await res.json(); + + res = await this.fetchIdp(json.location); + expect(res.status).toBe(303); const mockUrl = res.headers.get('location')!; expect(mockUrl.startsWith(this.redirectUrl)).toBeTruthy(); diff --git a/test/integration/RestrictedIdentity.test.ts b/test/integration/RestrictedIdentity.test.ts index c70581a44..6295d937f 100644 --- a/test/integration/RestrictedIdentity.test.ts +++ b/test/integration/RestrictedIdentity.test.ts @@ -94,10 +94,11 @@ describe('A server with restricted IDP access', (): void => { it('can still access registration with the correct credentials.', async(): Promise => { // Logging into session const state = new IdentityTestState(baseUrl, 'http://mockedredirect/', baseUrl); - const url = await state.startSession(); + let url = await state.startSession(); let res = await state.fetchIdp(url); expect(res.status).toBe(200); - await state.login(url, settings.email, settings.password); + url = await state.login(url, settings.email, settings.password); + await state.consent(url); expect(state.session.info?.webId).toBe(webId); // Registration still works for this WebID diff --git a/test/unit/identity/OidcHttpHandler.test.ts b/test/unit/identity/OidcHttpHandler.test.ts index 61be21c83..7ceba4473 100644 --- a/test/unit/identity/OidcHttpHandler.test.ts +++ b/test/unit/identity/OidcHttpHandler.test.ts @@ -13,7 +13,7 @@ describe('An OidcHttpHandler', (): void => { beforeEach(async(): Promise => { provider = { - callback: jest.fn(), + callback: jest.fn().mockReturnValue(jest.fn()), } as any; providerFactory = { @@ -26,6 +26,7 @@ describe('An OidcHttpHandler', (): void => { it('sends all requests to the OIDC library.', async(): Promise => { await expect(handler.handle({ request, response })).resolves.toBeUndefined(); expect(provider.callback).toHaveBeenCalledTimes(1); - expect(provider.callback).toHaveBeenLastCalledWith(request, response); + expect(provider.callback.mock.results[0].value).toHaveBeenCalledTimes(1); + expect(provider.callback.mock.results[0].value).toHaveBeenLastCalledWith(request, response); }); }); diff --git a/test/unit/identity/configuration/IdentityProviderFactory.test.ts b/test/unit/identity/configuration/IdentityProviderFactory.test.ts index bffb4787e..ceffb5a6d 100644 --- a/test/unit/identity/configuration/IdentityProviderFactory.test.ts +++ b/test/unit/identity/configuration/IdentityProviderFactory.test.ts @@ -16,7 +16,7 @@ jest.mock('oidc-provider', (): any => ({ const routes = { authorization: '/foo/oidc/auth', - check_session: '/foo/oidc/session/check', + backchannel_authentication: '/foo/oidc/backchannel', code_verification: '/foo/oidc/device', device_authorization: '/foo/oidc/device/auth', end_session: '/foo/oidc/session/end', @@ -100,23 +100,32 @@ describe('An IdentityProviderFactory', (): void => { expect(adapterFactory.createStorageAdapter).toHaveBeenLastCalledWith('test!'); expect(config.cookies?.keys).toEqual([ expect.any(String) ]); - expect(config.jwks).toEqual({ keys: [ expect.objectContaining({ kty: 'RSA' }) ]}); + expect(config.jwks).toEqual({ keys: [ expect.objectContaining({ alg: 'ES256' }) ]}); expect(config.routes).toEqual(routes); + expect(config.pkce?.methods).toEqual([ 'S256' ]); + expect((config.pkce!.required as any)()).toBe(true); + expect(config.clientDefaults?.id_token_signed_response_alg).toBe('ES256'); await expect((config.interactions?.url as any)(ctx, oidcInteraction)).resolves.toBe(redirectUrl); - expect((config.audiences as any)(null, null, {}, 'access_token')).toBe('solid'); - expect((config.audiences as any)(null, null, { clientId: 'clientId' }, 'client_credentials')).toBe('clientId'); - const findResult = await config.findAccount?.({ oidc: { client: { clientId: 'clientId' }}} as any, webId); + let findResult = await config.findAccount?.({ oidc: { client: { clientId: 'clientId' }}} as any, webId); expect(findResult?.accountId).toBe(webId); + await expect((findResult?.claims as any)()).resolves.toEqual({ sub: webId, webid: webId, azp: 'clientId' }); + findResult = await config.findAccount?.({ oidc: {}} as any, webId); await expect((findResult?.claims as any)()).resolves.toEqual({ sub: webId, webid: webId }); - expect((config.extraAccessTokenClaims as any)({}, {})).toEqual({}); - expect((config.extraAccessTokenClaims as any)({}, { kind: 'AccessToken', accountId: webId, clientId: 'clientId' })) - .toEqual({ - webid: webId, - client_id: 'clientId', - }); + expect((config.extraTokenClaims as any)({}, {})).toEqual({}); + expect((config.extraTokenClaims as any)({}, { kind: 'AccessToken', accountId: webId, clientId: 'clientId' })) + .toEqual({ webid: webId }); + + expect(config.features?.resourceIndicators?.enabled).toBe(true); + expect((config.features?.resourceIndicators?.defaultResource as any)()).toBe('http://example.com/'); + expect((config.features?.resourceIndicators?.getResourceServerInfo as any)()).toEqual({ + scope: '', + audience: 'solid', + accessTokenFormat: 'jwt', + jwt: { sign: { alg: 'ES256' }}, + }); // Test the renderError function const response = { } as HttpResponse; diff --git a/test/unit/identity/interaction/CompletingInteractionHandler.test.ts b/test/unit/identity/interaction/CompletingInteractionHandler.test.ts deleted file mode 100644 index 34a221d4f..000000000 --- a/test/unit/identity/interaction/CompletingInteractionHandler.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { Operation } from '../../../../src/http/Operation'; -import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation'; -import { CompletingInteractionHandler } from '../../../../src/identity/interaction/CompletingInteractionHandler'; -import type { - Interaction, - InteractionHandlerInput, -} from '../../../../src/identity/interaction/InteractionHandler'; -import type { - InteractionCompleter, - InteractionCompleterInput, -} from '../../../../src/identity/interaction/util/InteractionCompleter'; -import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError'; - -const webId = 'http://alice.test.com/card#me'; -class DummyCompletingInteractionHandler extends CompletingInteractionHandler { - public constructor(interactionCompleter: InteractionCompleter) { - super({}, interactionCompleter); - } - - public async getCompletionParameters(input: Required): Promise { - return { webId, oidcInteraction: input.oidcInteraction }; - } -} - -describe('A CompletingInteractionHandler', (): void => { - const oidcInteraction: Interaction = {} as any; - const location = 'http://test.com/redirect'; - let operation: Operation; - let interactionCompleter: jest.Mocked; - let handler: DummyCompletingInteractionHandler; - - beforeEach(async(): Promise => { - const representation = new BasicRepresentation('', 'application/json'); - operation = { - method: 'POST', - body: representation, - } as any; - - interactionCompleter = { - handleSafe: jest.fn().mockResolvedValue(location), - } as any; - - handler = new DummyCompletingInteractionHandler(interactionCompleter); - }); - - it('calls the parent JSON canHandle check.', async(): Promise => { - operation.body.metadata.contentType = 'application/x-www-form-urlencoded'; - await expect(handler.canHandle({ operation, oidcInteraction } as any)).rejects.toThrow(NotImplementedHttpError); - }); - - it('can handle GET requests without interaction.', async(): Promise => { - operation.method = 'GET'; - await expect(handler.canHandle({ operation } as any)).resolves.toBeUndefined(); - }); - - it('errors if no OidcInteraction is defined on POST requests.', async(): Promise => { - const error = expect.objectContaining({ - statusCode: 400, - message: 'This action can only be performed as part of an OIDC authentication flow.', - errorCode: 'E0002', - }); - await expect(handler.canHandle({ operation })).rejects.toThrow(error); - - await expect(handler.canHandle({ operation, oidcInteraction })).resolves.toBeUndefined(); - }); - - it('throws a redirect error with the completer location.', async(): Promise => { - const error = expect.objectContaining({ - statusCode: 302, - location, - }); - await expect(handler.handle({ operation, oidcInteraction })).rejects.toThrow(error); - expect(interactionCompleter.handleSafe).toHaveBeenCalledTimes(1); - expect(interactionCompleter.handleSafe).toHaveBeenLastCalledWith({ oidcInteraction, webId }); - }); -}); diff --git a/test/unit/identity/interaction/ConsentHandler.test.ts b/test/unit/identity/interaction/ConsentHandler.test.ts new file mode 100644 index 000000000..28cf92890 --- /dev/null +++ b/test/unit/identity/interaction/ConsentHandler.test.ts @@ -0,0 +1,142 @@ +import type { Provider } from 'oidc-provider'; +import type { ProviderFactory } from '../../../../src/identity/configuration/ProviderFactory'; +import { ConsentHandler } from '../../../../src/identity/interaction/ConsentHandler'; +import type { Interaction } from '../../../../src/identity/interaction/InteractionHandler'; +import { FoundHttpError } from '../../../../src/util/errors/FoundHttpError'; +import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError'; +import { createPostJsonOperation } from './email-password/handler/Util'; + +const newGrantId = 'newGrantId'; +class DummyGrant { + public accountId: string; + public clientId: string; + + public readonly scopes: string[] = []; + public claims: string[] = []; + public readonly rejectedScopes: string[] = []; + public readonly resourceScopes: Record = {}; + + public constructor(props: { accountId: string; clientId: string }) { + this.accountId = props.accountId; + this.clientId = props.clientId; + } + + public rejectOIDCScope(scope: string): void { + this.rejectedScopes.push(scope); + } + + public addOIDCScope(scope: string): void { + this.scopes.push(scope); + } + + public addOIDCClaims(claims: string[]): void { + this.claims = claims; + } + + public addResourceScope(resource: string, scope: string): void { + this.resourceScopes[resource] = scope; + } + + public async save(): Promise { + return newGrantId; + } +} + +describe('A ConsentHandler', (): void => { + const accountId = 'http://example.com/id#me'; + const clientId = 'clientId'; + let grantFn: jest.Mock & { find: jest.Mock }; + let knownGrant: DummyGrant; + let oidcInteraction: Interaction; + let provider: jest.Mocked; + let providerFactory: jest.Mocked; + let handler: ConsentHandler; + + beforeEach(async(): Promise => { + oidcInteraction = { + session: { accountId }, + // eslint-disable-next-line @typescript-eslint/naming-convention + params: { client_id: clientId }, + prompt: { details: {}}, + save: jest.fn(), + } as any; + + knownGrant = new DummyGrant({ accountId, clientId }); + + grantFn = jest.fn((props): DummyGrant => new DummyGrant(props)) as any; + grantFn.find = jest.fn((grantId: string): any => grantId ? knownGrant : undefined); + provider = { + // eslint-disable-next-line @typescript-eslint/naming-convention + Grant: grantFn, + } as any; + + providerFactory = { + getProvider: jest.fn().mockResolvedValue(provider), + }; + + handler = new ConsentHandler(providerFactory); + }); + + it('errors if no oidcInteraction is defined on POST requests.', async(): Promise => { + const error = expect.objectContaining({ + statusCode: 400, + message: 'This action can only be performed as part of an OIDC authentication flow.', + errorCode: 'E0002', + }); + await expect(handler.canHandle({ operation: createPostJsonOperation({}) })).rejects.toThrow(error); + + await expect(handler.canHandle({ operation: createPostJsonOperation({}), oidcInteraction })) + .resolves.toBeUndefined(); + }); + + it('requires an oidcInteraction with a defined session.', async(): Promise => { + oidcInteraction.session = undefined; + await expect(handler.handle({ operation: createPostJsonOperation({}), oidcInteraction })) + .rejects.toThrow(NotImplementedHttpError); + }); + + it('throws a redirect error.', async(): Promise => { + const operation = createPostJsonOperation({}); + await expect(handler.handle({ operation, oidcInteraction })).rejects.toThrow(FoundHttpError); + }); + + it('stores the requested scopes and claims in the grant.', async(): Promise => { + oidcInteraction.prompt.details = { + missingOIDCScope: [ 'scope1', 'scope2' ], + missingOIDCClaims: [ 'claim1', 'claim2' ], + missingResourceScopes: { resource: [ 'scope1', 'scope2' ]}, + }; + + const operation = createPostJsonOperation({ remember: true }); + await expect(handler.handle({ operation, oidcInteraction })).rejects.toThrow(FoundHttpError); + expect(grantFn.mock.results).toHaveLength(1); + expect(grantFn.mock.results[0].value.scopes).toEqual([ 'scope1 scope2' ]); + expect(grantFn.mock.results[0].value.claims).toEqual([ 'claim1', 'claim2' ]); + expect(grantFn.mock.results[0].value.resourceScopes).toEqual({ resource: 'scope1 scope2' }); + expect(grantFn.mock.results[0].value.rejectedScopes).toEqual([]); + }); + + it('creates a new Grant when needed.', async(): Promise => { + const operation = createPostJsonOperation({}); + await expect(handler.handle({ operation, oidcInteraction })).rejects.toThrow(FoundHttpError); + expect(grantFn).toHaveBeenCalledTimes(1); + expect(grantFn).toHaveBeenLastCalledWith({ accountId, clientId }); + expect(grantFn.find).toHaveBeenCalledTimes(0); + }); + + it('reuses existing Grant objects.', async(): Promise => { + const operation = createPostJsonOperation({}); + oidcInteraction.grantId = '123456'; + await expect(handler.handle({ operation, oidcInteraction })).rejects.toThrow(FoundHttpError); + expect(grantFn).toHaveBeenCalledTimes(0); + expect(grantFn.find).toHaveBeenCalledTimes(1); + expect(grantFn.find).toHaveBeenLastCalledWith('123456'); + }); + + it('rejectes offline_access as scope if a user does not want to be remembered.', async(): Promise => { + const operation = createPostJsonOperation({}); + await expect(handler.handle({ operation, oidcInteraction })).rejects.toThrow(FoundHttpError); + expect(grantFn.mock.results).toHaveLength(1); + expect(grantFn.mock.results[0].value.rejectedScopes).toEqual([ 'offline_access' ]); + }); +}); diff --git a/test/unit/identity/interaction/ExistingLoginHandler.test.ts b/test/unit/identity/interaction/ExistingLoginHandler.test.ts deleted file mode 100644 index c649e0a23..000000000 --- a/test/unit/identity/interaction/ExistingLoginHandler.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ExistingLoginHandler } from '../../../../src/identity/interaction/ExistingLoginHandler'; -import type { Interaction } from '../../../../src/identity/interaction/InteractionHandler'; -import type { - InteractionCompleter, -} from '../../../../src/identity/interaction/util/InteractionCompleter'; -import { FoundHttpError } from '../../../../src/util/errors/FoundHttpError'; -import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError'; -import { createPostJsonOperation } from './email-password/handler/Util'; - -describe('An ExistingLoginHandler', (): void => { - const webId = 'http://test.com/id#me'; - let oidcInteraction: Interaction; - let interactionCompleter: jest.Mocked; - let handler: ExistingLoginHandler; - - beforeEach(async(): Promise => { - oidcInteraction = { session: { accountId: webId }} as any; - - interactionCompleter = { - handleSafe: jest.fn().mockResolvedValue('http://test.com/redirect'), - } as any; - - handler = new ExistingLoginHandler(interactionCompleter); - }); - - it('requires an oidcInteraction with a defined session.', async(): Promise => { - oidcInteraction.session = undefined; - await expect(handler.handle({ operation: createPostJsonOperation({}), oidcInteraction })) - .rejects.toThrow(NotImplementedHttpError); - }); - - it('returns the correct completion parameters.', async(): Promise => { - const operation = createPostJsonOperation({ remember: true }); - await expect(handler.handle({ operation, oidcInteraction })).rejects.toThrow(FoundHttpError); - expect(interactionCompleter.handleSafe).toHaveBeenCalledTimes(1); - expect(interactionCompleter.handleSafe).toHaveBeenLastCalledWith({ oidcInteraction, webId, shouldRemember: true }); - }); -}); diff --git a/test/unit/identity/interaction/email-password/handler/ForgotPasswordHandler.test.ts b/test/unit/identity/interaction/email-password/handler/ForgotPasswordHandler.test.ts index 92701c0a3..75a5a7640 100644 --- a/test/unit/identity/interaction/email-password/handler/ForgotPasswordHandler.test.ts +++ b/test/unit/identity/interaction/email-password/handler/ForgotPasswordHandler.test.ts @@ -3,8 +3,8 @@ import { ForgotPasswordHandler, } from '../../../../../../src/identity/interaction/email-password/handler/ForgotPasswordHandler'; import type { AccountStore } from '../../../../../../src/identity/interaction/email-password/storage/AccountStore'; +import type { EmailSender } from '../../../../../../src/identity/interaction/email-password/util/EmailSender'; import type { InteractionRoute } from '../../../../../../src/identity/interaction/routing/InteractionRoute'; -import type { EmailSender } from '../../../../../../src/identity/interaction/util/EmailSender'; import { readJsonStream } from '../../../../../../src/util/StreamUtil'; import type { TemplateEngine } from '../../../../../../src/util/templates/TemplateEngine'; import { createPostJsonOperation } from './Util'; diff --git a/test/unit/identity/interaction/email-password/handler/LoginHandler.test.ts b/test/unit/identity/interaction/email-password/handler/LoginHandler.test.ts index c4a9392e7..2ea04b774 100644 --- a/test/unit/identity/interaction/email-password/handler/LoginHandler.test.ts +++ b/test/unit/identity/interaction/email-password/handler/LoginHandler.test.ts @@ -4,22 +4,23 @@ import type { Interaction, InteractionHandlerInput, } from '../../../../../../src/identity/interaction/InteractionHandler'; -import type { - InteractionCompleter, -} from '../../../../../../src/identity/interaction/util/InteractionCompleter'; import { FoundHttpError } from '../../../../../../src/util/errors/FoundHttpError'; import { createPostJsonOperation } from './Util'; describe('A LoginHandler', (): void => { const webId = 'http://alice.test.com/card#me'; const email = 'alice@test.email'; - const oidcInteraction: Interaction = {} as any; + let oidcInteraction: jest.Mocked; let input: Required; let accountStore: jest.Mocked; - let interactionCompleter: jest.Mocked; let handler: LoginHandler; beforeEach(async(): Promise => { + oidcInteraction = { + exp: 123456, + save: jest.fn(), + } as any; + input = { oidcInteraction } as any; accountStore = { @@ -27,11 +28,18 @@ describe('A LoginHandler', (): void => { getSettings: jest.fn().mockResolvedValue({ useIdp: true }), } as any; - interactionCompleter = { - handleSafe: jest.fn().mockResolvedValue('http://test.com/redirect'), - } as any; + handler = new LoginHandler(accountStore); + }); + it('errors if no oidcInteraction is defined on POST requests.', async(): Promise => { + const error = expect.objectContaining({ + statusCode: 400, + message: 'This action can only be performed as part of an OIDC authentication flow.', + errorCode: 'E0002', + }); + await expect(handler.canHandle({ operation: createPostJsonOperation({}) })).rejects.toThrow(error); - handler = new LoginHandler(accountStore, interactionCompleter); + await expect(handler.canHandle({ operation: createPostJsonOperation({}), oidcInteraction })) + .resolves.toBeUndefined(); }); it('errors on invalid emails.', async(): Promise => { @@ -61,13 +69,13 @@ describe('A LoginHandler', (): void => { .rejects.toThrow('This server is not an identity provider for this account.'); }); - it('returns the correct completion parameters.', async(): Promise => { + it('returns the generated redirect URL.', async(): Promise => { input.operation = createPostJsonOperation({ email, password: 'password!' }); await expect(handler.handle(input)).rejects.toThrow(FoundHttpError); expect(accountStore.authenticate).toHaveBeenCalledTimes(1); expect(accountStore.authenticate).toHaveBeenLastCalledWith(email, 'password!'); - expect(interactionCompleter.handleSafe).toHaveBeenCalledTimes(1); - expect(interactionCompleter.handleSafe).toHaveBeenLastCalledWith({ oidcInteraction, webId, shouldRemember: false }); + expect(oidcInteraction.save).toHaveBeenCalledTimes(1); + expect(oidcInteraction.result).toEqual({ login: { accountId: webId, remember: false }}); }); }); diff --git a/test/unit/identity/interaction/util/BaseEmailSender.test.ts b/test/unit/identity/interaction/email-password/util/BaseEmailSender.test.ts similarity index 82% rename from test/unit/identity/interaction/util/BaseEmailSender.test.ts rename to test/unit/identity/interaction/email-password/util/BaseEmailSender.test.ts index a04fa4424..4a2839f29 100644 --- a/test/unit/identity/interaction/util/BaseEmailSender.test.ts +++ b/test/unit/identity/interaction/email-password/util/BaseEmailSender.test.ts @@ -1,6 +1,6 @@ -import type { EmailSenderArgs } from '../../../../../src/identity/interaction/util/BaseEmailSender'; -import { BaseEmailSender } from '../../../../../src/identity/interaction/util/BaseEmailSender'; -import type { EmailArgs } from '../../../../../src/identity/interaction/util/EmailSender'; +import type { EmailSenderArgs } from '../../../../../../src/identity/interaction/email-password/util/BaseEmailSender'; +import { BaseEmailSender } from '../../../../../../src/identity/interaction/email-password/util/BaseEmailSender'; +import type { EmailArgs } from '../../../../../../src/identity/interaction/email-password/util/EmailSender'; jest.mock('nodemailer'); describe('A BaseEmailSender', (): void => { diff --git a/test/unit/identity/interaction/util/BaseInteractionCompleter.test.ts b/test/unit/identity/interaction/util/BaseInteractionCompleter.test.ts deleted file mode 100644 index 4973e6354..000000000 --- a/test/unit/identity/interaction/util/BaseInteractionCompleter.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { Interaction } from '../../../../../src/identity/interaction/InteractionHandler'; -import { BaseInteractionCompleter } from '../../../../../src/identity/interaction/util/BaseInteractionCompleter'; - -jest.useFakeTimers(); - -describe('A BaseInteractionCompleter', (): void => { - const now = Math.floor(Date.now() / 1000); - const webId = 'http://alice.test.com/#me'; - let oidcInteraction: jest.Mocked; - let completer: BaseInteractionCompleter; - - beforeEach(async(): Promise => { - oidcInteraction = { - lastSubmission: {}, - exp: now + 500, - returnTo: 'http://test.com/redirect', - save: jest.fn(), - } as any; - - completer = new BaseInteractionCompleter(); - }); - - it('stores the correct data in the interaction.', async(): Promise => { - await expect(completer.handle({ oidcInteraction, webId, shouldRemember: true })) - .resolves.toBe(oidcInteraction.returnTo); - expect(oidcInteraction.result).toEqual({ - login: { - account: webId, - remember: true, - ts: now, - }, - consent: { - rejectedScopes: [], - }, - }); - expect(oidcInteraction.save).toHaveBeenCalledTimes(1); - expect(oidcInteraction.save).toHaveBeenLastCalledWith(500); - }); - - it('rejects offline access if shouldRemember is false.', async(): Promise => { - await expect(completer.handle({ oidcInteraction, webId, shouldRemember: false })) - .resolves.toBe(oidcInteraction.returnTo); - expect(oidcInteraction.result).toEqual({ - login: { - account: webId, - remember: false, - ts: now, - }, - consent: { - rejectedScopes: [ 'offline_access' ], - }, - }); - expect(oidcInteraction.save).toHaveBeenCalledTimes(1); - expect(oidcInteraction.save).toHaveBeenLastCalledWith(500); - }); -});