Compare commits

...

5 Commits

Author SHA1 Message Date
Balázs Orbán
86baefdd9d feat(adapter): take away error handling from adapters (#1871) 2021-05-05 19:45:11 +02:00
Manish Chiniwalar
332e237c3e feat(provider): add Dropbox (#1756)
Co-authored-by: Balázs Orbán <info@balazsorban.com>
Co-authored-by: Adam Bergman <adam@fransvilhelm.com>
2021-05-05 19:42:55 +02:00
Mathis Møller
2fce08c0b5 docs(provider): Update IdentityServer 4 demo configuration (#1932) 2021-05-05 15:17:22 +02:00
Ernie Miranda
adf3fb669f docs(www): fix typo (#1922) 2021-05-04 19:34:06 +02:00
Anubisoft
5323be3594 fix(page): don't pass params to custom signout page (#1912)
* For the custom signout page addressed two issues with the query params being added to the signout url. A conditional check on the error value is now made before adding it as a query param. Also added a conditional check on the callbackUrl and if present that then gets appended as a query param to the signout api call.

* Changed fix for bug #192 to have no querystring params in the custom signout page url.

Co-authored-by: anubisoft <anubisoftprez@gmail.com>
Co-authored-by: Lluis Agusti <hi@llu.lu>
2021-05-03 22:43:38 +02:00
19 changed files with 940 additions and 195 deletions

22
config/babel.config.js Normal file
View File

@@ -0,0 +1,22 @@
// We aim to have the same support as Next.js
// https://nextjs.org/docs/getting-started#system-requirements
// https://nextjs.org/docs/basic-features/supported-browsers-features
module.exports = {
presets: [["@babel/preset-env", { targets: { node: "10.13" } }]],
plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-runtime",
],
comments: false,
overrides: [
{
test: ["../src/client/**"],
presets: [["@babel/preset-env", { targets: { ie: "11" } }]],
},
{
test: ["../src/server/pages/**"],
presets: ["preact"],
},
],
}

View File

@@ -1,15 +0,0 @@
{
"presets": [
["@babel/preset-env", { "targets": { "esmodules": true } }]
],
"plugins": [
"@babel/plugin-proposal-class-properties"
],
"comments": false,
"overrides": [
{
"test": ["../src/server/pages/**"],
"presets": ["preact"]
}
]
}

395
package-lock.json generated
View File

@@ -489,6 +489,227 @@
}
}
},
"@babel/helper-define-polyfill-provider": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz",
"integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==",
"dev": true,
"requires": {
"@babel/helper-compilation-targets": "^7.13.0",
"@babel/helper-module-imports": "^7.12.13",
"@babel/helper-plugin-utils": "^7.13.0",
"@babel/traverse": "^7.13.0",
"debug": "^4.1.1",
"lodash.debounce": "^4.0.8",
"resolve": "^1.14.2",
"semver": "^6.1.2"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
"integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
"dev": true,
"requires": {
"@babel/highlight": "^7.12.13"
}
},
"@babel/compat-data": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz",
"integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==",
"dev": true
},
"@babel/generator": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.0.tgz",
"integrity": "sha512-C6u00HbmsrNPug6A+CiNl8rEys7TsdcXwg12BHi2ca5rUfAs3+UwZsuDQSXnc+wCElCXMB8gMaJ3YXDdh8fAlg==",
"dev": true,
"requires": {
"@babel/types": "^7.14.0",
"jsesc": "^2.5.1",
"source-map": "^0.5.0"
}
},
"@babel/helper-compilation-targets": {
"version": "7.13.16",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz",
"integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==",
"dev": true,
"requires": {
"@babel/compat-data": "^7.13.15",
"@babel/helper-validator-option": "^7.12.17",
"browserslist": "^4.14.5",
"semver": "^6.3.0"
}
},
"@babel/helper-function-name": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz",
"integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==",
"dev": true,
"requires": {
"@babel/helper-get-function-arity": "^7.12.13",
"@babel/template": "^7.12.13",
"@babel/types": "^7.12.13"
}
},
"@babel/helper-get-function-arity": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz",
"integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==",
"dev": true,
"requires": {
"@babel/types": "^7.12.13"
}
},
"@babel/helper-module-imports": {
"version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz",
"integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==",
"dev": true,
"requires": {
"@babel/types": "^7.13.12"
}
},
"@babel/helper-plugin-utils": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
"integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
"dev": true
},
"@babel/helper-split-export-declaration": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz",
"integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==",
"dev": true,
"requires": {
"@babel/types": "^7.12.13"
}
},
"@babel/helper-validator-identifier": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz",
"integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==",
"dev": true
},
"@babel/helper-validator-option": {
"version": "7.12.17",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz",
"integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==",
"dev": true
},
"@babel/highlight": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz",
"integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.14.0",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.0.tgz",
"integrity": "sha512-AHbfoxesfBALg33idaTBVUkLnfXtsgvJREf93p4p0Lwsz4ppfE7g1tpEXVm4vrxUcH4DVhAa9Z1m1zqf9WUC7Q==",
"dev": true
},
"@babel/template": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
"integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/parser": "^7.12.13",
"@babel/types": "^7.12.13"
}
},
"@babel/traverse": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz",
"integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.14.0",
"@babel/helper-function-name": "^7.12.13",
"@babel/helper-split-export-declaration": "^7.12.13",
"@babel/parser": "^7.14.0",
"@babel/types": "^7.14.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.0.tgz",
"integrity": "sha512-O2LVLdcnWplaGxiPBz12d0HcdN8QdxdsWYhz5LSeuukV/5mn2xUUc3gBeU4QBYPJ18g/UToe8F532XJ608prmg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.14.0",
"to-fast-properties": "^2.0.0"
}
},
"browserslist": {
"version": "4.16.6",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
"integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001219",
"colorette": "^1.2.2",
"electron-to-chromium": "^1.3.723",
"escalade": "^3.1.1",
"node-releases": "^1.1.71"
}
},
"caniuse-lite": {
"version": "1.0.30001219",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001219.tgz",
"integrity": "sha512-c0yixVG4v9KBc/tQ2rlbB3A/bgBFRvl8h8M4IeUbqCca4gsiCfvtaheUssbnux/Mb66Vjz7x8yYjDgYcNQOhyQ==",
"dev": true
},
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
"dev": true
},
"electron-to-chromium": {
"version": "1.3.723",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.723.tgz",
"integrity": "sha512-L+WXyXI7c7+G1V8ANzRsPI5giiimLAUDC6Zs1ojHHPhYXb3k/iTABFmWjivEtsWrRQymjnO66/rO2ZTABGdmWg==",
"dev": true
},
"globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"node-releases": {
"version": "1.1.71",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz",
"integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==",
"dev": true
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"@babel/helper-explode-assignable-expression": {
"version": "7.12.1",
"resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz",
@@ -2029,6 +2250,59 @@
}
}
},
"@babel/plugin-transform-runtime": {
"version": "7.13.15",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz",
"integrity": "sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA==",
"dev": true,
"requires": {
"@babel/helper-module-imports": "^7.13.12",
"@babel/helper-plugin-utils": "^7.13.0",
"babel-plugin-polyfill-corejs2": "^0.2.0",
"babel-plugin-polyfill-corejs3": "^0.2.0",
"babel-plugin-polyfill-regenerator": "^0.2.0",
"semver": "^6.3.0"
},
"dependencies": {
"@babel/helper-module-imports": {
"version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz",
"integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==",
"dev": true,
"requires": {
"@babel/types": "^7.13.12"
}
},
"@babel/helper-plugin-utils": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
"integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz",
"integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==",
"dev": true
},
"@babel/types": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.0.tgz",
"integrity": "sha512-O2LVLdcnWplaGxiPBz12d0HcdN8QdxdsWYhz5LSeuukV/5mn2xUUc3gBeU4QBYPJ18g/UToe8F532XJ608prmg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.14.0",
"to-fast-properties": "^2.0.0"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"@babel/plugin-transform-shorthand-properties": {
"version": "7.12.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz",
@@ -2272,10 +2546,9 @@
}
},
"@babel/runtime": {
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
"dev": true,
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
"integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -2514,9 +2787,9 @@
"integrity": "sha512-rIIisYBvVtxDbY9Lbm+HOLbZyOaaEmtGc9wDN3tJLDUu3sLJOXNN7Pz29ThS+gf2lpMxXnfvk587hxGrnhCghQ=="
},
"@next-auth/typeorm-legacy-adapter": {
"version": "0.0.2-canary.116",
"resolved": "https://registry.npmjs.org/@next-auth/typeorm-legacy-adapter/-/typeorm-legacy-adapter-0.0.2-canary.116.tgz",
"integrity": "sha512-06knawdYdHkiMVw5GfR6Ku5ryITdaOLWIGCbRwAtgCZE5Pf6am+nhnqQVeGq18odu84SmzA+hTtkGSAYbR6MIA==",
"version": "0.0.2-canary.117",
"resolved": "https://registry.npmjs.org/@next-auth/typeorm-legacy-adapter/-/typeorm-legacy-adapter-0.0.2-canary.117.tgz",
"integrity": "sha512-sYJZPWMsM1ZTJcl749UojYDF4q8+ZiYcrR7rM4SACc0qiA9VBdOYUUMUMpQoazKOiwo1rWDKu4wHPt6CdV0ctQ==",
"requires": {
"crypto-js": "^4.0.0",
"require_optional": "^1.0.1",
@@ -4080,6 +4353,105 @@
"object.assign": "^4.1.0"
}
},
"babel-plugin-polyfill-corejs2": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz",
"integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==",
"dev": true,
"requires": {
"@babel/compat-data": "^7.13.11",
"@babel/helper-define-polyfill-provider": "^0.2.0",
"semver": "^6.1.1"
},
"dependencies": {
"@babel/compat-data": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz",
"integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==",
"dev": true
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"babel-plugin-polyfill-corejs3": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz",
"integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==",
"dev": true,
"requires": {
"@babel/helper-define-polyfill-provider": "^0.2.0",
"core-js-compat": "^3.9.1"
},
"dependencies": {
"browserslist": {
"version": "4.16.6",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
"integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001219",
"colorette": "^1.2.2",
"electron-to-chromium": "^1.3.723",
"escalade": "^3.1.1",
"node-releases": "^1.1.71"
}
},
"caniuse-lite": {
"version": "1.0.30001219",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001219.tgz",
"integrity": "sha512-c0yixVG4v9KBc/tQ2rlbB3A/bgBFRvl8h8M4IeUbqCca4gsiCfvtaheUssbnux/Mb66Vjz7x8yYjDgYcNQOhyQ==",
"dev": true
},
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
"dev": true
},
"core-js-compat": {
"version": "3.11.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.11.1.tgz",
"integrity": "sha512-aZ0e4tmlG/aOBHj92/TuOuZwp6jFvn1WNabU5VOVixzhu5t5Ao+JZkQOPlgNXu6ynwLrwJxklT4Gw1G1VGEh+g==",
"dev": true,
"requires": {
"browserslist": "^4.16.5",
"semver": "7.0.0"
}
},
"electron-to-chromium": {
"version": "1.3.723",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.723.tgz",
"integrity": "sha512-L+WXyXI7c7+G1V8ANzRsPI5giiimLAUDC6Zs1ojHHPhYXb3k/iTABFmWjivEtsWrRQymjnO66/rO2ZTABGdmWg==",
"dev": true
},
"node-releases": {
"version": "1.1.71",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz",
"integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==",
"dev": true
},
"semver": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
"dev": true
}
}
},
"babel-plugin-polyfill-regenerator": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz",
"integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==",
"dev": true,
"requires": {
"@babel/helper-define-polyfill-provider": "^0.2.0"
}
},
"babel-plugin-syntax-jsx": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
@@ -8903,6 +9275,12 @@
"integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=",
"dev": true
},
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
"dev": true
},
"lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
@@ -15935,8 +16313,7 @@
"regenerator-runtime": {
"version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
"dev": true
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
},
"regenerator-transform": {
"version": "0.14.5",

View File

@@ -30,12 +30,12 @@
},
"scripts": {
"build": "npm run build:js && npm run build:css",
"build:js": "node ./config/build.js && babel --config-file ./config/babel.config.json src --out-dir dist",
"build:js": "node ./config/build.js && babel --config-file ./config/babel.config.js src --out-dir dist",
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js",
"dev:setup": "npm run build:css && cd app && npm i",
"dev": "cd app && npm run dev",
"watch": "npm run watch:js | npm run watch:css",
"watch:js": "babel --config-file ./config/babel.config.json --watch src --out-dir dist",
"watch:js": "babel --config-file ./config/babel.config.js --watch src --out-dir dist",
"watch:css": "postcss --config config/postcss.config.js --watch src/**/*.css --base src --dir dist",
"test": "echo \"Write some tests...\"; npm run test:types",
"test:types": "dtslint types",
@@ -61,6 +61,7 @@
],
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.14.0",
"@next-auth/prisma-legacy-adapter": "canary",
"@next-auth/typeorm-legacy-adapter": "canary",
"crypto-js": "^4.0.0",
@@ -91,6 +92,7 @@
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.6",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-transform-runtime": "^7.13.15",
"@babel/preset-env": "^7.9.6",
"@prisma/client": "^2.16.1",
"@semantic-release/commit-analyzer": "^8.0.1",

View File

@@ -0,0 +1,36 @@
import { UnknownError } from "../lib/errors"
/**
* Handles adapter induced errors.
* @param {import("types/adapters").AdapterInstance} adapter
* @param {import("types").LoggerInstance} logger
* @return {import("types/adapters").AdapterInstance}
*/
export default function adapterErrorHandler(adapter, logger) {
return Object.keys(adapter).reduce((acc, method) => {
const name = capitalize(method)
const code = upperSnake(name, adapter.displayName)
const adapterMethod = adapter[method]
acc[method] = async (...args) => {
try {
logger.debug(code, ...args)
return await adapterMethod(...args)
} catch (error) {
logger.error(`${code}_ERROR`, error)
const e = new UnknownError(error)
e.name = `${name}Error`
throw e
}
}
return acc
}, {})
}
function capitalize(s) {
return `${s[0].toUpperCase()}${s.slice(1)}`
}
function upperSnake(s, prefix = "ADAPTER") {
return `${prefix}_${s.replace(/([A-Z])/g, "_$1")}`.toUpperCase()
}

54
src/providers/dropbox.js Normal file
View File

@@ -0,0 +1,54 @@
/**
* @param {import("../server").Provider} options
* @example
*
* ```js
* // pages/api/auth/[...nextauth].js
* import Providers from `next-auth/providers`
* ...
* providers: [
* Providers.Dropbox({
* clientId: process.env.DROPBOX_CLIENT_ID,
* clientSecret: process.env.DROPBOX_CLIENT_SECRET
* })
* ]
* ...
*
* // pages/index
* import { signIn } from "next-auth/client"
* ...
* <button onClick={() => signIn("dropbox")}>
* Sign in
* </button>
* ...
* ```
* *Resources:*
* - [NextAuth.js Documentation](https://next-auth.js.org/providers/dropbox)
* - [Dropbox Documentation](https://developers.dropbox.com/oauth-guide)
* - [Configuration](https://www.dropbox.com/developers/apps)
*/
export default function Dropbox(options) {
return {
id: 'dropbox',
name: 'Dropbox',
type: 'oauth',
version: '2.0',
scope: 'account_info.read',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.dropboxapi.com/oauth2/token',
authorizationUrl:
'https://www.dropbox.com/oauth2/authorize?token_access_type=offline&response_type=code',
profileUrl: 'https://api.dropboxapi.com/2/users/get_current_account',
profile: (profile) => {
return {
id: profile.account_id,
name: profile.name.display_name,
email: profile.email,
image: profile.profile_photo_url,
email_verified: profile.email_verified
}
},
protection: ["state", "pkce"],
...options
}
}

View File

@@ -158,9 +158,8 @@ async function NextAuthHandler (req, res, userOptions) {
return render.signin()
case 'signout':
if (pages.signOut) {
return res.redirect(`${pages.signOut}${pages.signOut.includes('?') ? '&' : '?'}error=${error}`)
}
if (pages.signOut) return res.redirect(pages.signOut)
return render.signout()
case 'callback':
if (provider) {

View File

@@ -1,5 +1,6 @@
import { AccountNotLinkedError } from '../../lib/errors'
import dispatchEvent from '../lib/dispatch-event'
import { AccountNotLinkedError } from "../../lib/errors"
import dispatchEvent from "../lib/dispatch-event"
import adapterErrorHandler from "../../adapters/error-handler"
/**
* This function handles the complex flow of signing users in, and either creating,
@@ -12,20 +13,29 @@ import dispatchEvent from '../lib/dispatch-event'
* All verification (e.g. OAuth flows or email address verificaiton flows) are
* done prior to this handler being called to avoid additonal complexity in this
* handler.
* @param {import("types").Session} sessionToken
* @param {import("types").Profile} profile
* @param {import("types").Account} account
* @param {import("types/internals").AppOptions} options
*/
export default async function callbackHandler (sessionToken, profile, providerAccount, options) {
export default async function callbackHandler(
sessionToken,
profile,
providerAccount,
options
) {
// Input validation
if (!profile) throw new Error('Missing profile')
if (!providerAccount?.id || !providerAccount.type) throw new Error('Missing or invalid provider account')
if (!['email', 'oauth'].includes(providerAccount.type)) throw new Error('Provider not supported')
if (!profile) throw new Error("Missing profile")
if (!providerAccount?.id || !providerAccount.type)
throw new Error("Missing or invalid provider account")
if (!["email", "oauth"].includes(providerAccount.type))
throw new Error("Provider not supported")
const {
adapter,
jwt,
events,
session: {
jwt: useJwtSession
}
session: { jwt: useJwtSession },
} = options
// If no adapter is configured then we don't have a database and cannot
@@ -34,7 +44,7 @@ export default async function callbackHandler (sessionToken, profile, providerAc
return {
user: profile,
account: providerAccount,
session: {}
session: {},
}
}
@@ -47,8 +57,8 @@ export default async function callbackHandler (sessionToken, profile, providerAc
linkAccount,
createSession,
getSession,
deleteSession
} = await adapter.getAdapter(options)
deleteSession,
} = adapterErrorHandler(await adapter.getAdapter(options), options.logger)
let session = null
let user = null
@@ -74,9 +84,11 @@ export default async function callbackHandler (sessionToken, profile, providerAc
}
}
if (providerAccount.type === 'email') {
if (providerAccount.type === "email") {
// If signing in with an email, check if an account with the same email address exists already
const userByEmail = profile.email ? await getUserByEmail(profile.email) : null
const userByEmail = profile.email
? await getUserByEmail(profile.email)
: null
if (userByEmail) {
// If they are not already signed in as the same user, this flow will
// sign them out of the current session and sign them in as the new user
@@ -107,11 +119,14 @@ export default async function callbackHandler (sessionToken, profile, providerAc
return {
session,
user,
isNewUser
isNewUser,
}
} else if (providerAccount.type === 'oauth') {
} else if (providerAccount.type === "oauth") {
// If signing in with oauth account, check to see if the account exists already
const userByProviderAccountId = await getUserByProviderAccountId(providerAccount.provider, providerAccount.id)
const userByProviderAccountId = await getUserByProviderAccountId(
providerAccount.provider,
providerAccount.id
)
if (userByProviderAccountId) {
if (isSignedIn) {
// If the user is already signed in with this account, we don't need to do anything
@@ -122,7 +137,7 @@ export default async function callbackHandler (sessionToken, profile, providerAc
return {
session,
user,
isNewUser
isNewUser,
}
}
// If the user is currently signed in, but the new account they are signing in
@@ -132,11 +147,13 @@ export default async function callbackHandler (sessionToken, profile, providerAc
}
// If there is no active session, but the account being signed in with is already
// associated with a valid user then create session to sign the user in.
session = useJwtSession ? {} : await createSession(userByProviderAccountId)
session = useJwtSession
? {}
: await createSession(userByProviderAccountId)
return {
session,
user: userByProviderAccountId,
isNewUser
isNewUser,
}
} else {
if (isSignedIn) {
@@ -151,13 +168,16 @@ export default async function callbackHandler (sessionToken, profile, providerAc
providerAccount.accessToken,
providerAccount.accessTokenExpires
)
await dispatchEvent(events.linkAccount, { user, providerAccount: providerAccount })
await dispatchEvent(events.linkAccount, {
user,
providerAccount: providerAccount,
})
// As they are already signed in, we don't need to do anything after linking them
return {
session,
user,
isNewUser
isNewUser,
}
}
@@ -178,7 +198,9 @@ export default async function callbackHandler (sessionToken, profile, providerAc
//
// OAuth providers should require email address verification to prevent this, but in
// practice that is not always the case; this helps protect against that.
const userByEmail = profile.email ? await getUserByEmail(profile.email) : null
const userByEmail = profile.email
? await getUserByEmail(profile.email)
: null
if (userByEmail) {
// We end up here when we don't have an account with the same [provider].id *BUT*
// we do already have an account with the same email address as the one in the
@@ -207,14 +229,17 @@ export default async function callbackHandler (sessionToken, profile, providerAc
providerAccount.accessToken,
providerAccount.accessTokenExpires
)
await dispatchEvent(events.linkAccount, { user, providerAccount: providerAccount })
await dispatchEvent(events.linkAccount, {
user,
providerAccount: providerAccount,
})
session = useJwtSession ? {} : await createSession(user)
isNewUser = true
return {
session,
user,
isNewUser
isNewUser,
}
}
}

View File

@@ -215,6 +215,7 @@ async function getOAuth2AccessToken (code, provider, codeVerifier) {
*/
async function getOAuth2 (provider, accessToken, results) {
let url = provider.profileUrl
let httpMethod = 'GET'
const headers = { ...provider.headers }
if (this._useAuthorizationHeaderForGET) {
@@ -238,8 +239,15 @@ async function getOAuth2 (provider, accessToken, results) {
url = prepareProfileUrl({ provider, url, results })
}
/** Dropbox requires POST instead of GET
* Read more: https://www.dropbox.com/developers/reference/auth-types#user
*/
if (provider.id === 'dropbox') {
httpMethod = 'POST'
}
return new Promise((resolve, reject) => {
this._request('GET', url, headers, null, accessToken, (error, profileData) => {
this._request(httpMethod, url, headers, null, accessToken, (error, profileData) => {
if (error) {
return reject(error)
}

View File

@@ -1,22 +1,44 @@
import { randomBytes } from 'crypto'
import { randomBytes } from "crypto"
import adapterErrorHandler from "../../../adapters/error-handler"
export default async function email (email, provider, options) {
/**
*
* @param {string} email
* @param {import("types/providers").EmailConfig} provider
* @param {import("types/internals").AppOptions} options
* @returns
*/
export default async function email(email, provider, options) {
try {
const { baseUrl, basePath, adapter } = options
const { baseUrl, basePath, adapter, logger } = options
const { createVerificationRequest } = await adapter.getAdapter(options)
const { createVerificationRequest } = adapterErrorHandler(
await adapter.getAdapter(options),
logger
)
// Prefer provider specific secret, but use default secret if none specified
const secret = provider.secret || options.secret
// Generate token
const token = await provider.generateVerificationToken?.() ?? randomBytes(32).toString('hex')
const token =
(await provider.generateVerificationToken?.()) ??
randomBytes(32).toString("hex")
// Send email with link containing token (the unhashed version)
const url = `${baseUrl}${basePath}/callback/${encodeURIComponent(provider.id)}?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}`
const url = `${baseUrl}${basePath}/callback/${encodeURIComponent(
provider.id
)}?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}`
// @TODO Create invite (send secret so can be hashed)
await createVerificationRequest(email, url, token, secret, provider, options)
await createVerificationRequest(
email,
url,
token,
secret,
provider,
options
)
// Return promise
return Promise.resolve()

View File

@@ -1,15 +1,15 @@
import oAuthCallback from '../lib/oauth/callback'
import callbackHandler from '../lib/callback-handler'
import * as cookie from '../lib/cookie'
import logger from '../../lib/logger'
import dispatchEvent from '../lib/dispatch-event'
import oAuthCallback from "../lib/oauth/callback"
import callbackHandler from "../lib/callback-handler"
import * as cookie from "../lib/cookie"
import dispatchEvent from "../lib/dispatch-event"
import adapterErrorHandler from "../../adapters/error-handler"
/**
* Handle callbacks from login services
* @param {import("types/internals").NextAuthRequest} req
* @param {import("types/internals").NextAuthResponse} res
*/
export default async function callback (req, res) {
export default async function callback(req, res) {
const {
provider,
adapter,
@@ -22,21 +22,23 @@ export default async function callback (req, res) {
jwt,
events,
callbacks,
session: {
jwt: useJwtSession,
maxAge: sessionMaxAge
}
session: { jwt: useJwtSession, maxAge: sessionMaxAge },
logger,
} = req.options
// Get session ID (if set)
const sessionToken = req.cookies?.[cookies.sessionToken.name] ?? null
if (provider.type === 'oauth') {
if (provider.type === "oauth") {
try {
const { profile, account, OAuthProfile } = await oAuthCallback(req)
try {
// Make it easier to debug when adding a new provider
logger.debug('OAUTH_CALLBACK_RESPONSE', { profile, account, OAuthProfile })
logger.debug("OAUTH_CALLBACK_RESPONSE", {
profile,
account,
OAuthProfile,
})
// If we don't have a profile object then either something went wrong
// or the user cancelled signing in. We don't know which, so we just
@@ -56,52 +58,85 @@ export default async function callback (req, res) {
// (that just means it's a new user signing in for the first time).
let userOrProfile = profile
if (adapter) {
const { getUserByProviderAccountId } = await adapter.getAdapter(req.options)
const userFromProviderAccountId = await getUserByProviderAccountId(account.provider, account.id)
const { getUserByProviderAccountId } = adapterErrorHandler(
await adapter.getAdapter(req.options),
logger
)
const userFromProviderAccountId = await getUserByProviderAccountId(
account.provider,
account.id
)
if (userFromProviderAccountId) {
userOrProfile = userFromProviderAccountId
}
}
try {
const signInCallbackResponse = await callbacks.signIn(userOrProfile, account, OAuthProfile)
const signInCallbackResponse = await callbacks.signIn(
userOrProfile,
account,
OAuthProfile
)
if (signInCallbackResponse === false) {
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
} else if (typeof signInCallbackResponse === 'string') {
return res.redirect(
`${baseUrl}${basePath}/error?error=AccessDenied`
)
} else if (typeof signInCallbackResponse === "string") {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(
error.message
)}`
)
}
// TODO: Remove in a future major release
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT")
return res.redirect(error)
}
// Sign user in
const { user, session, isNewUser } = await callbackHandler(sessionToken, profile, account, req.options)
const { user, session, isNewUser } = await callbackHandler(
sessionToken,
profile,
account,
req.options
)
if (useJwtSession) {
const defaultJwtPayload = {
name: user.name,
email: user.email,
picture: user.image,
sub: user.id?.toString()
sub: user.id?.toString(),
}
const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, OAuthProfile, isNewUser)
const jwtPayload = await callbacks.jwt(
defaultJwtPayload,
user,
account,
OAuthProfile,
isNewUser
)
// Sign and encrypt token
const newEncodedJwt = await jwt.encode({ ...jwt, token: jwtPayload })
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + (sessionMaxAge * 1000))
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { expires: cookieExpires.toISOString(), ...cookies.sessionToken.options })
cookie.set(res, cookies.sessionToken.name, newEncodedJwt, {
expires: cookieExpires.toISOString(),
...cookies.sessionToken.options,
})
} else {
// Save Session Token in cookie
cookie.set(res, cookies.sessionToken.name, session.sessionToken, { expires: session.expires || null, ...cookies.sessionToken.options })
cookie.set(res, cookies.sessionToken.name, session.sessionToken, {
expires: session.expires || null,
...cookies.sessionToken.options,
})
}
await dispatchEvent(events.signIn, { user, account, isNewUser })
@@ -110,94 +145,145 @@ export default async function callback (req, res) {
// e.g. option to send users to a new account landing page on initial login
// Note that the callback URL is preserved, so the journey can still be resumed
if (isNewUser && pages.newUser) {
return res.redirect(`${pages.newUser}${pages.newUser.includes('?') ? '&' : '?'}callbackUrl=${encodeURIComponent(callbackUrl)}`)
return res.redirect(
`${pages.newUser}${
pages.newUser.includes("?") ? "&" : "?"
}callbackUrl=${encodeURIComponent(callbackUrl)}`
)
}
// Callback URL is already verified at this point, so safe to use if specified
return res.redirect(callbackUrl || baseUrl)
} catch (error) {
if (error.name === 'AccountNotLinkedError') {
if (error.name === "AccountNotLinkedError") {
// If the email on the account is already linked, but not with this OAuth account
return res.redirect(`${baseUrl}${basePath}/error?error=OAuthAccountNotLinked`)
} else if (error.name === 'CreateUserError') {
return res.redirect(`${baseUrl}${basePath}/error?error=OAuthCreateAccount`)
return res.redirect(
`${baseUrl}${basePath}/error?error=OAuthAccountNotLinked`
)
} else if (error.name === "CreateUserError") {
return res.redirect(
`${baseUrl}${basePath}/error?error=OAuthCreateAccount`
)
}
logger.error('OAUTH_CALLBACK_HANDLER_ERROR', error)
logger.error("OAUTH_CALLBACK_HANDLER_ERROR", error)
return res.redirect(`${baseUrl}${basePath}/error?error=Callback`)
}
} catch (error) {
if (error.name === 'OAuthCallbackError') {
logger.error('CALLBACK_OAUTH_ERROR', error)
if (error.name === "OAuthCallbackError") {
logger.error("CALLBACK_OAUTH_ERROR", error)
return res.redirect(`${baseUrl}${basePath}/error?error=OAuthCallback`)
}
logger.error('OAUTH_CALLBACK_ERROR', error)
logger.error("OAUTH_CALLBACK_ERROR", error)
return res.redirect(`${baseUrl}${basePath}/error?error=Callback`)
}
} else if (provider.type === 'email') {
} else if (provider.type === "email") {
try {
if (!adapter) {
logger.error('EMAIL_REQUIRES_ADAPTER_ERROR')
logger.error("EMAIL_REQUIRES_ADAPTER_ERROR")
return res.redirect(`${baseUrl}${basePath}/error?error=Configuration`)
}
const { getVerificationRequest, deleteVerificationRequest, getUserByEmail } = await adapter.getAdapter(req.options)
const {
getVerificationRequest,
deleteVerificationRequest,
getUserByEmail,
} = adapterErrorHandler(await adapter.getAdapter(req.options), logger)
const verificationToken = req.query.token
const email = req.query.email
// Verify email and verification token exist in database
const invite = await getVerificationRequest(email, verificationToken, secret, provider)
const invite = await getVerificationRequest(
email,
verificationToken,
secret,
provider
)
if (!invite) {
return res.redirect(`${baseUrl}${basePath}/error?error=Verification`)
}
// If verification token is valid, delete verification request token from
// the database so it cannot be used again
await deleteVerificationRequest(email, verificationToken, secret, provider)
await deleteVerificationRequest(
email,
verificationToken,
secret,
provider
)
// If is an existing user return a user object (otherwise use placeholder)
const profile = await getUserByEmail(email) || { email }
const account = { id: provider.id, type: 'email', providerAccountId: email }
const profile = (await getUserByEmail(email)) || { email }
const account = {
id: provider.id,
type: "email",
providerAccountId: email,
}
// Check if user is allowed to sign in
try {
const signInCallbackResponse = await callbacks.signIn(profile, account, { email })
const signInCallbackResponse = await callbacks.signIn(
profile,
account,
{ email }
)
if (signInCallbackResponse === false) {
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
} else if (typeof signInCallbackResponse === 'string') {
} else if (typeof signInCallbackResponse === "string") {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(
error.message
)}`
)
}
// TODO: Remove in a future major release
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT")
return res.redirect(error)
}
// Sign user in
const { user, session, isNewUser } = await callbackHandler(sessionToken, profile, account, req.options)
const { user, session, isNewUser } = await callbackHandler(
sessionToken,
profile,
account,
req.options
)
if (useJwtSession) {
const defaultJwtPayload = {
name: user.name,
email: user.email,
picture: user.image,
sub: user.id?.toString()
sub: user.id?.toString(),
}
const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, profile, isNewUser)
const jwtPayload = await callbacks.jwt(
defaultJwtPayload,
user,
account,
profile,
isNewUser
)
// Sign and encrypt token
const newEncodedJwt = await jwt.encode({ ...jwt, token: jwtPayload })
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + (sessionMaxAge * 1000))
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { expires: cookieExpires.toISOString(), ...cookies.sessionToken.options })
cookie.set(res, cookies.sessionToken.name, newEncodedJwt, {
expires: cookieExpires.toISOString(),
...cookies.sessionToken.options,
})
} else {
// Save Session Token in cookie
cookie.set(res, cookies.sessionToken.name, session.sessionToken, { expires: session.expires || null, ...cookies.sessionToken.options })
cookie.set(res, cookies.sessionToken.name, session.sessionToken, {
expires: session.expires || null,
...cookies.sessionToken.options,
})
}
await dispatchEvent(events.signIn, { user, account, isNewUser })
@@ -206,55 +292,93 @@ export default async function callback (req, res) {
// e.g. option to send users to a new account landing page on initial login
// Note that the callback URL is preserved, so the journey can still be resumed
if (isNewUser && pages.newUser) {
return res.redirect(`${pages.newUser}${pages.newUser.includes('?') ? '&' : '?'}callbackUrl=${encodeURIComponent(callbackUrl)}`)
return res.redirect(
`${pages.newUser}${
pages.newUser.includes("?") ? "&" : "?"
}callbackUrl=${encodeURIComponent(callbackUrl)}`
)
}
// Callback URL is already verified at this point, so safe to use if specified
return res.redirect(callbackUrl || baseUrl)
} catch (error) {
if (error.name === 'CreateUserError') {
return res.redirect(`${baseUrl}${basePath}/error?error=EmailCreateAccount`)
if (error.name === "CreateUserError") {
return res.redirect(
`${baseUrl}${basePath}/error?error=EmailCreateAccount`
)
}
logger.error('CALLBACK_EMAIL_ERROR', error)
logger.error("CALLBACK_EMAIL_ERROR", error)
return res.redirect(`${baseUrl}${basePath}/error?error=Callback`)
}
} else if (provider.type === 'credentials' && req.method === 'POST') {
} else if (provider.type === "credentials" && req.method === "POST") {
if (!useJwtSession) {
logger.error('CALLBACK_CREDENTIALS_JWT_ERROR', 'Signin in with credentials is only supported if JSON Web Tokens are enabled')
return res.status(500).redirect(`${baseUrl}${basePath}/error?error=Configuration`)
logger.error(
"CALLBACK_CREDENTIALS_JWT_ERROR",
"Signin in with credentials is only supported if JSON Web Tokens are enabled"
)
return res
.status(500)
.redirect(`${baseUrl}${basePath}/error?error=Configuration`)
}
if (!provider.authorize) {
logger.error('CALLBACK_CREDENTIALS_HANDLER_ERROR', 'Must define an authorize() handler to use credentials authentication provider')
return res.status(500).redirect(`${baseUrl}${basePath}/error?error=Configuration`)
logger.error(
"CALLBACK_CREDENTIALS_HANDLER_ERROR",
"Must define an authorize() handler to use credentials authentication provider"
)
return res
.status(500)
.redirect(`${baseUrl}${basePath}/error?error=Configuration`)
}
const credentials = req.body
let userObjectReturnedFromAuthorizeHandler
try {
userObjectReturnedFromAuthorizeHandler = await provider.authorize(credentials)
userObjectReturnedFromAuthorizeHandler = await provider.authorize(
credentials
)
if (!userObjectReturnedFromAuthorizeHandler) {
return res.status(401).redirect(`${baseUrl}${basePath}/error?error=CredentialsSignin&provider=${encodeURIComponent(provider.id)}`)
return res
.status(401)
.redirect(
`${baseUrl}${basePath}/error?error=CredentialsSignin&provider=${encodeURIComponent(
provider.id
)}`
)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(
error.message
)}`
)
}
return res.redirect(error)
}
const user = userObjectReturnedFromAuthorizeHandler
const account = { id: provider.id, type: 'credentials' }
const account = { id: provider.id, type: "credentials" }
try {
const signInCallbackResponse = await callbacks.signIn(user, account, credentials)
const signInCallbackResponse = await callbacks.signIn(
user,
account,
credentials
)
if (signInCallbackResponse === false) {
return res.status(403).redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
return res
.status(403)
.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(
error.message
)}`
)
}
return res.redirect(error)
}
@@ -263,22 +387,33 @@ export default async function callback (req, res) {
name: user.name,
email: user.email,
picture: user.image,
sub: user.id?.toString()
sub: user.id?.toString(),
}
const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, userObjectReturnedFromAuthorizeHandler, false)
const jwtPayload = await callbacks.jwt(
defaultJwtPayload,
user,
account,
userObjectReturnedFromAuthorizeHandler,
false
)
// Sign and encrypt token
const newEncodedJwt = await jwt.encode({ ...jwt, token: jwtPayload })
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + (sessionMaxAge * 1000))
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { expires: cookieExpires.toISOString(), ...cookies.sessionToken.options })
cookie.set(res, cookies.sessionToken.name, newEncodedJwt, {
expires: cookieExpires.toISOString(),
...cookies.sessionToken.options,
})
await dispatchEvent(events.signIn, { user, account })
return res.redirect(callbackUrl || baseUrl)
}
return res.status(500).end(`Error: Callback for provider type ${provider.type} not supported`)
return res
.status(500)
.end(`Error: Callback for provider type ${provider.type} not supported`)
}

View File

@@ -1,13 +1,15 @@
import * as cookie from '../lib/cookie'
import logger from '../../lib/logger'
import dispatchEvent from '../lib/dispatch-event'
import * as cookie from "../lib/cookie"
import dispatchEvent from "../lib/dispatch-event"
import adapterErrorHandler from "../../adapters/error-handler"
/**
* Return a session object (without any private fields)
* for Single Page App clients
* @param {import("types/internals").NextAuthRequest} req
* @param {import("types/internals").NextAuthResponse} res
*/
export default async function session (req, res) {
const { cookies, adapter, jwt, events, callbacks } = req.options
export default async function session(req, res) {
const { cookies, adapter, jwt, events, callbacks, logger } = req.options
const useJwtSession = req.options.session.jwt
const sessionMaxAge = req.options.session.maxAge
const sessionToken = req.cookies[cookies.sessionToken.name]
@@ -24,7 +26,9 @@ export default async function session (req, res) {
// Generate new session expiry date
const sessionExpiresDate = new Date()
sessionExpiresDate.setTime(sessionExpiresDate.getTime() + (sessionMaxAge * 1000))
sessionExpiresDate.setTime(
sessionExpiresDate.getTime() + sessionMaxAge * 1000
)
const sessionExpires = sessionExpiresDate.toISOString()
// By default, only exposes a limited subset of information to the client
@@ -33,14 +37,17 @@ export default async function session (req, res) {
user: {
name: decodedJwt.name || null,
email: decodedJwt.email || null,
image: decodedJwt.picture || null
image: decodedJwt.picture || null,
},
expires: sessionExpires
expires: sessionExpires,
}
// Pass Session and JSON Web Token through to the session callback
const jwtPayload = await callbacks.jwt(decodedJwt)
const sessionPayload = await callbacks.session(defaultSessionPayload, jwtPayload)
const sessionPayload = await callbacks.session(
defaultSessionPayload,
jwtPayload
)
// Return session payload as response
response = sessionPayload
@@ -49,17 +56,29 @@ export default async function session (req, res) {
const newEncodedJwt = await jwt.encode({ ...jwt, token: jwtPayload })
// Set cookie, to also update expiry date on cookie
cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { expires: sessionExpires, ...cookies.sessionToken.options })
cookie.set(res, cookies.sessionToken.name, newEncodedJwt, {
expires: sessionExpires,
...cookies.sessionToken.options,
})
await dispatchEvent(events.session, { session: sessionPayload, jwt: jwtPayload })
await dispatchEvent(events.session, {
session: sessionPayload,
jwt: jwtPayload,
})
} catch (error) {
// If JWT not verifiable, make sure the cookie for it is removed and return empty object
logger.error('JWT_SESSION_ERROR', error)
cookie.set(res, cookies.sessionToken.name, '', { ...cookies.sessionToken.options, maxAge: 0 })
logger.error("JWT_SESSION_ERROR", error)
cookie.set(res, cookies.sessionToken.name, "", {
...cookies.sessionToken.options,
maxAge: 0,
})
}
} else {
try {
const { getUser, getSession, updateSession } = await adapter.getAdapter(req.options)
const { getUser, getSession, updateSession } = adapterErrorHandler(
await adapter.getAdapter(req.options),
logger
)
const session = await getSession(sessionToken)
if (session) {
// Trigger update to session object to update session expiry
@@ -73,29 +92,38 @@ export default async function session (req, res) {
user: {
name: user.name,
email: user.email,
image: user.image
image: user.image,
},
accessToken: session.accessToken,
expires: session.expires
expires: session.expires,
}
// Pass Session through to the session callback
const sessionPayload = await callbacks.session(defaultSessionPayload, user)
const sessionPayload = await callbacks.session(
defaultSessionPayload,
user
)
// Return session payload as response
response = sessionPayload
// Set cookie again to update expiry
cookie.set(res, cookies.sessionToken.name, sessionToken, { expires: session.expires, ...cookies.sessionToken.options })
cookie.set(res, cookies.sessionToken.name, sessionToken, {
expires: session.expires,
...cookies.sessionToken.options,
})
await dispatchEvent(events.session, { session: sessionPayload })
} else if (sessionToken) {
// If sessionToken was found set but it's not valid for a session then
// remove the sessionToken cookie from browser.
cookie.set(res, cookies.sessionToken.name, '', { ...cookies.sessionToken.options, maxAge: 0 })
cookie.set(res, cookies.sessionToken.name, "", {
...cookies.sessionToken.options,
maxAge: 0,
})
}
} catch (error) {
logger.error('SESSION_ERROR', error)
logger.error("SESSION_ERROR", error)
}
}

View File

@@ -1,35 +1,43 @@
import getAuthorizationUrl from '../lib/signin/oauth'
import emailSignin from '../lib/signin/email'
import logger from '../../lib/logger'
import getAuthorizationUrl from "../lib/signin/oauth"
import emailSignin from "../lib/signin/email"
import adapterErrorHandler from "../../adapters/error-handler"
/** Handle requests to /api/auth/signin */
export default async function signin (req, res) {
/**
* Handle requests to /api/auth/signin
* @param {import("types/internals").NextAuthRequest} req
* @param {import("types/internals").NextAuthResponse} res
*/
export default async function signin(req, res) {
const {
provider,
baseUrl,
basePath,
adapter,
callbacks
callbacks,
logger,
} = req.options
if (!provider.type) {
return res.status(500).end(`Error: Type not specified for ${provider.name}`)
}
if (provider.type === 'oauth' && req.method === 'POST') {
if (provider.type === "oauth" && req.method === "POST") {
try {
const authorizationUrl = await getAuthorizationUrl(req)
return res.redirect(authorizationUrl)
} catch (error) {
logger.error('SIGNIN_OAUTH_ERROR', error)
logger.error("SIGNIN_OAUTH_ERROR", error)
return res.redirect(`${baseUrl}${basePath}/error?error=OAuthSignin`)
}
} else if (provider.type === 'email' && req.method === 'POST') {
} else if (provider.type === "email" && req.method === "POST") {
if (!adapter) {
logger.error('EMAIL_REQUIRES_ADAPTER_ERROR')
logger.error("EMAIL_REQUIRES_ADAPTER_ERROR")
return res.redirect(`${baseUrl}${basePath}/error?error=Configuration`)
}
const { getUserByEmail } = await adapter.getAdapter(req.options)
const { getUserByEmail } = adapterErrorHandler(
await adapter.getAdapter(req.options),
logger
)
// Note: Technically the part of the email address local mailbox element
// (everything before the @ symbol) should be treated as 'case sensitive'
@@ -39,36 +47,43 @@ export default async function signin (req, res) {
const email = req.body.email?.toLowerCase() ?? null
// If is an existing user return a user object (otherwise use placeholder)
const profile = await getUserByEmail(email) || { email }
const account = { id: provider.id, type: 'email', providerAccountId: email }
const profile = (await getUserByEmail(email)) || { email }
const account = { id: provider.id, type: "email", providerAccountId: email }
// Check if user is allowed to sign in
try {
const signInCallbackResponse = await callbacks.signIn(profile, account, { email, verificationRequest: true })
const signInCallbackResponse = await callbacks.signIn(profile, account, {
email,
verificationRequest: true,
})
if (signInCallbackResponse === false) {
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
} else if (typeof signInCallbackResponse === 'string') {
} else if (typeof signInCallbackResponse === "string") {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`
)
}
// TODO: Remove in a future major release
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT")
return res.redirect(error)
}
try {
await emailSignin(email, provider, req.options)
} catch (error) {
logger.error('SIGNIN_EMAIL_ERROR', error)
logger.error("SIGNIN_EMAIL_ERROR", error)
return res.redirect(`${baseUrl}${basePath}/error?error=EmailSignin`)
}
return res.redirect(`${baseUrl}${basePath}/verify-request?provider=${encodeURIComponent(
provider.id
)}&type=${encodeURIComponent(provider.type)}`)
return res.redirect(
`${baseUrl}${basePath}/verify-request?provider=${encodeURIComponent(
provider.id
)}&type=${encodeURIComponent(provider.type)}`
)
}
return res.redirect(`${baseUrl}${basePath}/signin`)
}

View File

@@ -1,10 +1,14 @@
import * as cookie from '../lib/cookie'
import logger from '../../lib/logger'
import dispatchEvent from '../lib/dispatch-event'
import * as cookie from "../lib/cookie"
import dispatchEvent from "../lib/dispatch-event"
import adapterErrorHandler from "../../adapters/error-handler"
/** Handle requests to /api/auth/signout */
export default async function signout (req, res) {
const { adapter, cookies, events, jwt, callbackUrl } = req.options
/**
* Handle requests to /api/auth/signout
* @param {import("types/internals").NextAuthRequest} req
* @param {import("types/internals").NextAuthResponse} res
*/
export default async function signout(req, res) {
const { adapter, cookies, events, jwt, callbackUrl, logger } = req.options
const useJwtSession = req.options.session.jwt
const sessionToken = req.cookies[cookies.sessionToken.name]
@@ -18,7 +22,10 @@ export default async function signout (req, res) {
}
} else {
// Get session from database
const { getSession, deleteSession } = await adapter.getAdapter(req.options)
const { getSession, deleteSession } = adapterErrorHandler(
await adapter.getAdapter(req.options),
logger
)
try {
// Dispatch signout event
@@ -33,14 +40,14 @@ export default async function signout (req, res) {
await deleteSession(sessionToken)
} catch (error) {
// If error, log it but continue
logger.error('SIGNOUT_ERROR', error)
logger.error("SIGNOUT_ERROR", error)
}
}
// Remove Session Token
cookie.set(res, cookies.sessionToken.name, '', {
cookie.set(res, cookies.sessionToken.name, "", {
...cookies.sessionToken.options,
maxAge: 0
maxAge: 0,
})
return res.redirect(callbackUrl)

2
types/adapters.d.ts vendored
View File

@@ -22,6 +22,8 @@ export default Adapters
* [Create a custom adapter](https://next-auth.js.org/tutorials/creating-a-database-adapter)
*/
export interface AdapterInstance<U = User, P = Profile, S = Session> {
/** Used as a prefix for adapter related log messages. (Defaults to `ADAPTER_`) */
displayName?: string
createUser(profile: P): Promise<U>
getUser(id: string): Promise<U | null>
getUserByEmail(email: string): Promise<U | null>

View File

@@ -64,6 +64,7 @@ export type OAuthProviderType =
| "Bungie"
| "Cognito"
| "Discord"
| "Dropbox"
| "EVEOnline"
| "Facebook"
| "FACEIT"

View File

@@ -404,7 +404,7 @@ Properties on any custom `cookies` that are specified override this option.
:::
:::warning
Setting this option to *false* in production is a security risk and may allow sessions to hijacked if used in production. It is intended to support development and testing. Using this option is not recommended.
Setting this option to *false* in production is a security risk and may allow sessions to be hijacked if used in production. It is intended to support development and testing. Using this option is not recommended.
:::
---

View File

@@ -0,0 +1,26 @@
---
id: dropbox
title: Dropbox
---
## Documentation
https://developers.dropbox.com/oauth-guide
## Configuration
https://www.dropbox.com/developers/apps
## Example
```js
import Providers from `next-auth/providers`
...
providers: [
Providers.Dropbox({
clientId: process.env.DROPBOX_CLIENT_ID,
clientSecret: process.env.DROPBOX_CLIENT_SECRET
})
]
...
```

View File

@@ -7,7 +7,6 @@ title: IdentityServer4
https://identityserver4.readthedocs.io/en/latest/
## Example
```js
@@ -15,8 +14,8 @@ import Providers from `next-auth/providers`
...
providers: [
Providers.IdentityServer4({
id: "identity-server4",
name: "IdentityServer4",
id: "identity-server4",
name: "IdentityServer4",
scope: "openid profile email api offline_access", // Allowed Scopes
domain: process.env.IdentityServer4_Domain,
clientId: process.env.IdentityServer4_CLIENT_ID,
@@ -33,18 +32,20 @@ The configuration below is for the demo server at https://demo.identityserver.io
If you want to try it out, you can copy and paste the configuration below.
You can sign in to the demo service with either <b>bob/bob</b> or <b>alice/alice</b>.
```js
import Providers from `next-auth/providers`
...
providers: [
Providers.IdentityServer4({
id: "demo-identity-server",
name: "Demo IdentityServer4",
scope: "openid profile email api offline_access",
id: "demo-identity-server",
name: "Demo IdentityServer4",
scope: "openid profile email api offline_access",
domain: "demo.identityserver.io",
clientId: "server.code",
clientSecret: "secret"
clientId: "interactive.confidential",
clientSecret: "secret",
protection: "pkce"
})
}
...
```