mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffd0601ab0 | ||
|
|
7864d4705d | ||
|
|
98dc82e5d6 | ||
|
|
86baefdd9d | ||
|
|
332e237c3e | ||
|
|
2fce08c0b5 | ||
|
|
adf3fb669f | ||
|
|
5323be3594 | ||
|
|
6df0d04a1e | ||
|
|
aa9c1e7c96 | ||
|
|
66473054f5 | ||
|
|
e8ddbc5c11 | ||
|
|
dfe4620056 | ||
|
|
848224e2c5 | ||
|
|
aee376cc57 | ||
|
|
0d2a81cd39 | ||
|
|
61e99c9489 |
@@ -32,7 +32,7 @@ cd next-auth
|
||||
|
||||
2. Install packages:
|
||||
```sh
|
||||
npm i && npm dev:setup
|
||||
npm i && npm run dev:setup
|
||||
```
|
||||
|
||||
3. Populate `.env.local`:
|
||||
|
||||
22
config/babel.config.js
Normal file
22
config/babel.config.js
Normal 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"],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -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
395
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
36
src/adapters/error-handler.js
Normal file
36
src/adapters/error-handler.js
Normal 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()
|
||||
}
|
||||
@@ -355,6 +355,22 @@ function BroadcastChannel (name = 'nextauth.message') {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Some methods are exported with more than one name. This provides some
|
||||
// flexibility over how they can be invoked and backwards compatibility
|
||||
// with earlier releases. These should be removed in a newer release, as it only
|
||||
// creates problems for bundlers and adds confusion to users. TypeScript declarations
|
||||
// will provide sufficient help when importing
|
||||
export {
|
||||
setOptions as options,
|
||||
getSession as session,
|
||||
getProviders as providers,
|
||||
getCsrfToken as csrfToken,
|
||||
signIn as signin,
|
||||
signOut as signout
|
||||
}
|
||||
|
||||
export default {
|
||||
getSession,
|
||||
getCsrfToken,
|
||||
|
||||
54
src/providers/dropbox.js
Normal file
54
src/providers/dropbox.js
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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`)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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`)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
14
types/adapters.d.ts
vendored
14
types/adapters.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import { AppOptions } from "./internals"
|
||||
import { User, Profile, Session } from "."
|
||||
import { EmailConfig, SendVerificationRequest } from "./providers"
|
||||
import { EmailConfig } from "./providers"
|
||||
import { ConnectionOptions } from "typeorm"
|
||||
|
||||
/** Legacy */
|
||||
@@ -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>
|
||||
@@ -118,13 +120,13 @@ export interface AdapterInstance<U = User, P = Profile, S = Session> {
|
||||
* [Create a custom adapter](https://next-auth.js.org/tutorials/creating-a-database-adapter)
|
||||
*/
|
||||
export type Adapter<
|
||||
C = Record<string, unknown>,
|
||||
C = unknown,
|
||||
O = Record<string, unknown>,
|
||||
U = User,
|
||||
P = Profile,
|
||||
S = Session
|
||||
U = unknown,
|
||||
P = unknown,
|
||||
S = unknown
|
||||
> = (
|
||||
config: C,
|
||||
client: C,
|
||||
options?: O
|
||||
) => {
|
||||
getAdapter(appOptions: AppOptions): Promise<AdapterInstance<U, P, S>>
|
||||
|
||||
81
types/index.d.ts
vendored
81
types/index.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
// Minimum TypeScript Version: 3.5
|
||||
// Minimum TypeScript Version: 3.6
|
||||
|
||||
/// <reference types="node" />
|
||||
|
||||
@@ -111,7 +111,7 @@ export interface NextAuthOptions {
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/configuration/options#events) | [Events documentation](https://next-auth.js.org/configuration/events)
|
||||
*/
|
||||
events?: EventsOptions
|
||||
events?: Partial<JWTEventCallbacks | SessionEventCallbacks>
|
||||
/**
|
||||
* By default NextAuth.js uses a database adapter that uses TypeORM and supports MySQL, MariaDB, Postgres and MongoDB and SQLite databases.
|
||||
* An alternative adapter that uses Prisma, which currently supports MySQL, MariaDB and Postgres, is also included.
|
||||
@@ -127,7 +127,7 @@ export interface NextAuthOptions {
|
||||
* [Default adapter](https://next-auth.js.org/schemas/adapters#typeorm-adapter) |
|
||||
* [Community adapters](https://github.com/nextauthjs/adapters)
|
||||
*/
|
||||
adapter?: Adapter
|
||||
adapter?: ReturnType<Adapter>
|
||||
/**
|
||||
* Set debug to true to enable debug messages for authentication and database operations.
|
||||
* * **Default value**: `false`
|
||||
@@ -180,7 +180,7 @@ export interface NextAuthOptions {
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/configuration/options#theme) | [Pages documentation]("https://next-auth.js.org/configuration/pages")
|
||||
*/
|
||||
theme?: "auto" | "dark" | "light"
|
||||
theme?: Theme
|
||||
/**
|
||||
* When set to `true` then all cookies set by NextAuth.js will only be accessible from HTTPS URLs.
|
||||
* This option defaults to `false` on URLs that start with `http://` (e.g. http://localhost:3000) for developer convenience.
|
||||
@@ -215,6 +215,14 @@ export interface NextAuthOptions {
|
||||
cookies?: CookiesOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the theme of the built-in pages.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/configuration/options#theme) |
|
||||
* [Pages](https://next-auth.js.org/configuration/pages)
|
||||
*/
|
||||
export type Theme = "auto" | "dark" | "light"
|
||||
|
||||
/**
|
||||
* Override any of the methods, and the rest will use the default logger.
|
||||
*
|
||||
@@ -342,20 +350,61 @@ export interface CookiesOptions {
|
||||
}
|
||||
|
||||
/** [Documentation](https://next-auth.js.org/configuration/events) */
|
||||
export type EventType =
|
||||
| "signIn"
|
||||
| "signOut"
|
||||
| "createUser"
|
||||
| "updateUser"
|
||||
| "linkAccount"
|
||||
| "session"
|
||||
| "error"
|
||||
export type EventCallback<MessageType = unknown> = (
|
||||
message: MessageType
|
||||
) => Promise<void>
|
||||
|
||||
/** [Documentation](https://next-auth.js.org/configuration/events) */
|
||||
export type EventCallback = (message: any) => Promise<void>
|
||||
/**
|
||||
* If using a `credentials` type auth, the user is the raw response from your
|
||||
* credential provider.
|
||||
* For other providers, you'll get the User object from your adapter, the account,
|
||||
* and an indicator if the user was new to your Adapter.
|
||||
*/
|
||||
export interface SignInEventMessage {
|
||||
user: User
|
||||
account: Account
|
||||
isNewUser?: boolean
|
||||
}
|
||||
|
||||
/** [Documentation](https://next-auth.js.org/configuration/events) */
|
||||
export type EventsOptions = Partial<Record<EventType, EventCallback>>
|
||||
export interface LinkAccountEventMessage {
|
||||
user: User
|
||||
providerAccount: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* The various event callbacks you can register for from next-auth
|
||||
*/
|
||||
export interface CommonEventCallbacks {
|
||||
signIn: EventCallback<SignInEventMessage>
|
||||
createUser: EventCallback<User>
|
||||
updateUser: EventCallback<User>
|
||||
linkAccount: EventCallback<LinkAccountEventMessage>
|
||||
error: EventCallback
|
||||
}
|
||||
/**
|
||||
* The event callbacks will take this form if you are using JWTs:
|
||||
* signOut will receive the JWT and session will receive the session and JWT.
|
||||
*/
|
||||
export interface JWTEventCallbacks extends CommonEventCallbacks {
|
||||
signOut: EventCallback<JWT>
|
||||
session: EventCallback<{
|
||||
session: Session
|
||||
jwt: JWT
|
||||
}>
|
||||
}
|
||||
/**
|
||||
* The event callbacks will take this form if you are using Sessions
|
||||
* and not using JWTs:
|
||||
* signOut will receive the underlying DB adapter's session object, and session
|
||||
* will receive the NextAuth client session with extra data.
|
||||
*/
|
||||
export interface SessionEventCallbacks extends CommonEventCallbacks {
|
||||
signOut: EventCallback<Session | null>
|
||||
session: EventCallback<{ session: Session }>
|
||||
}
|
||||
export type EventCallbacks = JWTEventCallbacks | SessionEventCallbacks
|
||||
|
||||
export type EventType = keyof EventCallbacks
|
||||
|
||||
/** [Documentation](https://next-auth.js.org/configuration/pages) */
|
||||
export interface PagesOptions {
|
||||
|
||||
12
types/internals/index.d.ts
vendored
12
types/internals/index.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
import { NextApiRequest, NextApiResponse } from "./utils"
|
||||
import { NextAuthOptions } from ".."
|
||||
import { LoggerInstance, NextAuthOptions, SessionOptions, Theme } from ".."
|
||||
import { AppProvider } from "../providers"
|
||||
|
||||
/** Options that are the same both in internal and user provided options. */
|
||||
@@ -9,12 +9,7 @@ export type NextAuthSharedOptions =
|
||||
| "events"
|
||||
| "callbacks"
|
||||
| "cookies"
|
||||
| "secret"
|
||||
| "adapter"
|
||||
| "theme"
|
||||
| "debug"
|
||||
| "logger"
|
||||
| "session"
|
||||
|
||||
export interface AppOptions
|
||||
extends Required<Pick<NextAuthOptions, NextAuthSharedOptions>> {
|
||||
@@ -42,6 +37,11 @@ export interface AppOptions
|
||||
provider?: AppProvider
|
||||
csrfToken?: string
|
||||
csrfTokenVerified?: boolean
|
||||
secret: string
|
||||
theme: Theme
|
||||
debug: boolean
|
||||
logger: LoggerInstance
|
||||
session: Required<SessionOptions>
|
||||
}
|
||||
|
||||
export interface NextAuthRequest extends NextApiRequest {
|
||||
|
||||
3
types/providers.d.ts
vendored
3
types/providers.d.ts
vendored
@@ -29,7 +29,7 @@ export interface OAuthConfig<P extends Record<string, unknown> = Profile>
|
||||
scope: string
|
||||
params: { grant_type: string }
|
||||
accessTokenUrl: string
|
||||
requestTokenUrl: string
|
||||
requestTokenUrl?: string
|
||||
authorizationUrl: string
|
||||
profileUrl: string
|
||||
profile(profile: P, tokens: TokenSet): Awaitable<User & { id: string }>
|
||||
@@ -64,6 +64,7 @@ export type OAuthProviderType =
|
||||
| "Bungie"
|
||||
| "Cognito"
|
||||
| "Discord"
|
||||
| "Dropbox"
|
||||
| "EVEOnline"
|
||||
| "Facebook"
|
||||
| "FACEIT"
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import Providers, {
|
||||
AppProvider,
|
||||
EmailConfig,
|
||||
OAuthConfig,
|
||||
} from "next-auth/providers"
|
||||
import { Adapter, AdapterInstance } from "next-auth/adapters"
|
||||
import Providers, { OAuthConfig } from "next-auth/providers"
|
||||
import { Adapter } from "next-auth/adapters"
|
||||
import NextAuth, * as NextAuthTypes from "next-auth"
|
||||
import { IncomingMessage, ServerResponse } from "http"
|
||||
import * as JWTType from "next-auth/jwt"
|
||||
import { Socket } from "net"
|
||||
import { NextApiRequest, NextApiResponse } from "internals/utils"
|
||||
import { AppOptions } from "internals"
|
||||
@@ -65,7 +60,7 @@ const exampleVerificationRequest = {
|
||||
expires: new Date(),
|
||||
}
|
||||
|
||||
const adapter: Adapter = () => {
|
||||
const MyAdapter: Adapter<Record<string, unknown>> = () => {
|
||||
return {
|
||||
async getAdapter(appOptions: AppOptions) {
|
||||
return {
|
||||
@@ -131,6 +126,8 @@ const adapter: Adapter = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const client = {} // Create a fake db client
|
||||
|
||||
const allConfig: NextAuthTypes.NextAuthOptions = {
|
||||
providers: [
|
||||
Providers.Twitter({
|
||||
@@ -171,26 +168,29 @@ const allConfig: NextAuthTypes.NextAuthOptions = {
|
||||
},
|
||||
},
|
||||
events: {
|
||||
async signIn(message) {
|
||||
async signIn(message: NextAuthTypes.SignInEventMessage) {
|
||||
return undefined
|
||||
},
|
||||
async signOut(message) {
|
||||
async signOut(message: NextAuthTypes.Session | null) {
|
||||
return undefined
|
||||
},
|
||||
async createUser(message) {
|
||||
async createUser(message: NextAuthTypes.User) {
|
||||
return undefined
|
||||
},
|
||||
async linkAccount(message) {
|
||||
async updateUser(message: NextAuthTypes.User) {
|
||||
return undefined
|
||||
},
|
||||
async session(message) {
|
||||
async linkAccount(message: NextAuthTypes.LinkAccountEventMessage) {
|
||||
return undefined
|
||||
},
|
||||
async error(message) {
|
||||
async session(message: NextAuthTypes.Session) {
|
||||
return undefined
|
||||
},
|
||||
async error(message: any) {
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
adapter,
|
||||
adapter: MyAdapter(client),
|
||||
useSecureCookies: true,
|
||||
cookies: {
|
||||
sessionToken: {
|
||||
|
||||
@@ -7,17 +7,60 @@ Events are asynchronous functions that do not return a response, they are useful
|
||||
|
||||
You can specify a handler for any of these events below, for debugging or for an audit log.
|
||||
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
...
|
||||
events: {
|
||||
async signIn(message) { /* on successful sign in */ },
|
||||
async signOut(message) { /* on signout */ },
|
||||
async createUser(message) { /* user created */ },
|
||||
async linkAccount(message) { /* account linked to a user */ },
|
||||
async session(message) { /* session is active */ },
|
||||
async error(message) { /* error in authentication flow */ }
|
||||
}
|
||||
...
|
||||
```
|
||||
:::note
|
||||
Execution of your auth API will be blocked by an `await` on your event handler. If your event handler starts any burdensome work it should not block its own promise on that work.
|
||||
:::
|
||||
|
||||
The content of the message object varies depending on the flow (e.g. OAuth or Email authentication flow, JWT or database sessions, etc) but typically contains a user object and/or contents of the JSON Web Token and other information relevant to the event.
|
||||
## Events
|
||||
|
||||
### signIn
|
||||
|
||||
Sent on successful sign in.
|
||||
|
||||
The message will be an object and contain:
|
||||
|
||||
- `user` (from your adapter or from the provider if a `credentials` type provider)
|
||||
- `account` (from your adapter or the provider)
|
||||
- `isNewUser` (whether your adapter had a user for this account already)
|
||||
|
||||
### signOut
|
||||
|
||||
Sent when the user signs out.
|
||||
|
||||
The message object is the JWT, if using them, or the adapter session object for the session that is being ended.
|
||||
|
||||
### createUser
|
||||
|
||||
Sent when the adapter is told to create a new user.
|
||||
|
||||
The message object will be the user.
|
||||
|
||||
### updateUser
|
||||
|
||||
Sent when the adapter is told to update an existing user. Currently this is only sent when the user verifies their email address.
|
||||
|
||||
The message object will be the user.
|
||||
|
||||
### linkAccount
|
||||
|
||||
Sent when an account in a given provider is linked to a user in our userbase. For example, when a user signs up with Twitter or when an existing user links their Google account.
|
||||
|
||||
The message will be an object and contain:
|
||||
|
||||
- `user`: The user object from your adapter
|
||||
- `providerAccount`: The object returned from the provider.
|
||||
|
||||
### session
|
||||
|
||||
Sent at the end of a request for the current session.
|
||||
|
||||
The message will be an object and contain:
|
||||
|
||||
- `session`: The session object from your adapter
|
||||
- `jwt`: If using JWT, the token for this session.
|
||||
|
||||
### error
|
||||
|
||||
Sent when an error occurs
|
||||
|
||||
The message could be any object relevant to describing the error.
|
||||
|
||||
@@ -5,7 +5,7 @@ title: Options
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### NEXTAUTH_URL
|
||||
### NEXTAUTH_URL
|
||||
|
||||
When deploying to production, set the `NEXTAUTH_URL` environment variable to the canonical URL of your site.
|
||||
|
||||
@@ -37,10 +37,10 @@ Options are passed to NextAuth.js when initializing it in an API route.
|
||||
|
||||
### providers
|
||||
|
||||
* **Default value**: `[]`
|
||||
* **Required**: *Yes*
|
||||
- **Default value**: `[]`
|
||||
- **Required**: _Yes_
|
||||
|
||||
#### Description
|
||||
#### Description
|
||||
|
||||
An array of authentication providers for signing in (e.g. Google, Facebook, Twitter, GitHub, Email, etc) in any order. This can be one of the built-in providers or an object with a custom provider.
|
||||
|
||||
@@ -50,10 +50,10 @@ See the [providers documentation](/configuration/providers) for a list of suppor
|
||||
|
||||
### database
|
||||
|
||||
* **Default value**: `null`
|
||||
* **Required**: *No (unless using email provider)*
|
||||
- **Default value**: `null`
|
||||
- **Required**: _No (unless using email provider)_
|
||||
|
||||
#### Description
|
||||
#### Description
|
||||
|
||||
[A database connection string or configuration object.](/configuration/databases)
|
||||
|
||||
@@ -61,14 +61,14 @@ See the [providers documentation](/configuration/providers) for a list of suppor
|
||||
|
||||
### secret
|
||||
|
||||
* **Default value**: `string` (*SHA hash of the "options" object*)
|
||||
* **Required**: *No - but strongly recommended!*
|
||||
- **Default value**: `string` (_SHA hash of the "options" object_)
|
||||
- **Required**: _No - but strongly recommended!_
|
||||
|
||||
#### Description
|
||||
|
||||
A random string used to hash tokens, sign cookies and generate crytographic keys.
|
||||
|
||||
If not specified is uses a hash of all configuration options, including Client ID / Secrets for entropy.
|
||||
If not specified, it uses a hash for all configuration options, including Client ID / Secrets for entropy.
|
||||
|
||||
The default behaviour is volatile, and it is strongly recommended you explicitly specify a value to avoid invalidating end user sessions when configuration changes are deployed.
|
||||
|
||||
@@ -76,8 +76,8 @@ The default behaviour is volatile, and it is strongly recommended you explicitly
|
||||
|
||||
### session
|
||||
|
||||
* **Default value**: `object`
|
||||
* **Required**: *No*
|
||||
- **Default value**: `object`
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -90,14 +90,14 @@ session: {
|
||||
// Use JSON Web Tokens for session instead of database sessions.
|
||||
// This option can be used with or without a database for users/accounts.
|
||||
// Note: `jwt` is automatically set to `true` if no database is specified.
|
||||
jwt: false,
|
||||
|
||||
jwt: false,
|
||||
|
||||
// Seconds - How long until an idle session expires and is no longer valid.
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||
|
||||
|
||||
// Seconds - Throttle how frequently to write to database to extend a session.
|
||||
// Use it to limit write operations. Set to 0 to always update the database.
|
||||
// Note: This option is ignored if using JSON Web Tokens
|
||||
// Note: This option is ignored if using JSON Web Tokens
|
||||
updateAge: 24 * 60 * 60, // 24 hours
|
||||
}
|
||||
```
|
||||
@@ -106,8 +106,8 @@ session: {
|
||||
|
||||
### jwt
|
||||
|
||||
* **Default value**: `object`
|
||||
* **Required**: *No*
|
||||
- **Default value**: `object`
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -124,18 +124,15 @@ jwt: {
|
||||
// This is used to generate the actual signingKey and produces a warning
|
||||
// message if not defined explicitly.
|
||||
// secret: 'INp8IvdIyeMcoGAgFGoA61DdBglwwSqnXJZkgz8PSnw',
|
||||
|
||||
// You can generate a signing key using `jose newkey -s 512 -t oct -a HS512`
|
||||
// This gives you direct knowledge of the key used to sign the token so you can use it
|
||||
// to authenticate indirectly (eg. to a database driver)
|
||||
// signingKey: {"kty":"oct","kid":"Dl893BEV-iVE-x9EC52TDmlJUgGm9oZ99_ZL025Hc5Q","alg":"HS512","k":"K7QqRmJOKRK2qcCKV_pi9PSBv3XP0fpTu30TP8xn4w01xR3ZMZM38yL2DnTVPVw6e4yhdh0jtoah-i4c_pZagA"},
|
||||
|
||||
// If you chose something other than the default algorithm for the signingKey (HS512)
|
||||
// you also need to configure the algorithm
|
||||
// verificationOptions: {
|
||||
// algorithms: ['HS256']
|
||||
// },
|
||||
|
||||
// Set to true to use encryption. Defaults to false (signing only).
|
||||
// encryption: true,
|
||||
// encryptionKey: "",
|
||||
@@ -143,7 +140,6 @@ jwt: {
|
||||
// decryptionOptions = {
|
||||
// algorithms: ['A256GCM']
|
||||
// },
|
||||
|
||||
// You can define your own encode/decode functions for signing and encryption
|
||||
// if you want to override the default behaviour.
|
||||
// async encode({ secret, token, maxAge }) {},
|
||||
@@ -168,13 +164,13 @@ An example JSON Web Token contains a payload like this:
|
||||
You can use the built-in `getToken()` helper method to verify and decrypt the token, like this:
|
||||
|
||||
```js
|
||||
import jwt from 'next-auth/jwt'
|
||||
import jwt from "next-auth/jwt"
|
||||
|
||||
const secret = process.env.JWT_SECRET
|
||||
|
||||
export default async (req, res) => {
|
||||
const token = await jwt.getToken({ req, secret })
|
||||
console.log('JSON Web Token', token)
|
||||
console.log("JSON Web Token", token)
|
||||
res.end()
|
||||
}
|
||||
```
|
||||
@@ -185,10 +181,10 @@ _For convenience, this helper function is also able to read and decode tokens pa
|
||||
|
||||
The getToken() helper requires the following options:
|
||||
|
||||
* `req` - (object) Request object
|
||||
* `secret` - (string) JWT Secret
|
||||
- `req` - (object) Request object
|
||||
- `secret` - (string) JWT Secret
|
||||
|
||||
You must also pass *any options configured on the `jwt` option* to the helper.
|
||||
You must also pass _any options configured on the `jwt` option_ to the helper.
|
||||
|
||||
e.g. Including custom session `maxAge` and custom signing and/or encryption keys or options
|
||||
|
||||
@@ -196,15 +192,15 @@ e.g. Including custom session `maxAge` and custom signing and/or encryption keys
|
||||
|
||||
It also supports the following options:
|
||||
|
||||
* `secureCookie` - (boolean) Use secure prefixed cookie name
|
||||
- `secureCookie` - (boolean) Use secure prefixed cookie name
|
||||
|
||||
By default, the helper function will attempt to determine if it should use the secure prefixed cookie (e.g. `true` in production and `false` in development, unless NEXTAUTH_URL contains an HTTPS URL).
|
||||
|
||||
* `cookieName` - (string) Session token cookie name
|
||||
- `cookieName` - (string) Session token cookie name
|
||||
|
||||
The `secureCookie` option is ignored if `cookieName` is explicitly specified.
|
||||
|
||||
* `raw` - (boolean) Get raw token (not decoded)
|
||||
- `raw` - (boolean) Get raw token (not decoded)
|
||||
|
||||
If set to `true` returns the raw token without decrypting or verifying it.
|
||||
|
||||
@@ -216,8 +212,8 @@ The JWT is stored in the Session Token cookie, the same cookie used for tokens w
|
||||
|
||||
### pages
|
||||
|
||||
* **Default value**: `{}`
|
||||
* **Required**: *No*
|
||||
- **Default value**: `{}`
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -225,7 +221,7 @@ Specify URLs to be used if you want to create custom sign in, sign out and error
|
||||
|
||||
Pages specified will override the corresponding built-in page.
|
||||
|
||||
*For example:*
|
||||
_For example:_
|
||||
|
||||
```js
|
||||
pages: {
|
||||
@@ -243,8 +239,8 @@ See the documentation for the [pages option](/configuration/pages) for more info
|
||||
|
||||
### callbacks
|
||||
|
||||
* **Default value**: `object`
|
||||
* **Required**: *No*
|
||||
- **Default value**: `object`
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -277,8 +273,8 @@ See the [callbacks documentation](/configuration/callbacks) for more information
|
||||
|
||||
### events
|
||||
|
||||
* **Default value**: `object`
|
||||
* **Required**: *No*
|
||||
- **Default value**: `object`
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -286,27 +282,26 @@ Events are asynchronous functions that do not return a response, they are useful
|
||||
|
||||
You can specify a handler for any of these events below - e.g. for debugging or to create an audit log.
|
||||
|
||||
The content of the message object varies depending on the flow (e.g. OAuth or Email authentication flow, JWT or database sessions, etc), but typically contains a user object and/or contents of the JSON Web Token and other information relevant to the event.
|
||||
The content of the message object varies depending on the flow (e.g. OAuth or Email authentication flow, JWT or database sessions, etc). See the [events documentation](/configuration/events) for more information on the form of each message object and how to use the events functions.
|
||||
|
||||
```js
|
||||
events: {
|
||||
async signIn(message) { /* on successful sign in */ },
|
||||
async signOut(message) { /* on signout */ },
|
||||
async createUser(message) { /* user created */ },
|
||||
async linkAccount(message) { /* account linked to a user */ },
|
||||
async updateUser(message) { /* user updated - e.g. their email was verified */ },
|
||||
async linkAccount(message) { /* account (e.g. Twitter) linked to a user */ },
|
||||
async session(message) { /* session is active */ },
|
||||
async error(message) { /* error in authentication flow */ }
|
||||
}
|
||||
```
|
||||
|
||||
See the [events documentation](/configuration/events) for more information on how to use the events functions.
|
||||
|
||||
---
|
||||
|
||||
### adapter
|
||||
|
||||
* **Default value**: *Adapter.Default()*
|
||||
* **Required**: *No*
|
||||
- **Default value**: _Adapter.Default()_
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -324,8 +319,8 @@ If the `adapter` option is specified it overrides the `database` option, only sp
|
||||
|
||||
### debug
|
||||
|
||||
* **Default value**: `false`
|
||||
* **Required**: *No*
|
||||
- **Default value**: `false`
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -335,14 +330,15 @@ Set debug to `true` to enable debug messages for authentication and database ope
|
||||
|
||||
### logger
|
||||
|
||||
* **Default value**: `console`
|
||||
* **Required**: *No*
|
||||
- **Default value**: `console`
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
Override any of the logger levels (`undefined` levels will use the built-in logger), and intercept logs in NextAuth. You can use this to send NextAuth logs to a third-party logging service.
|
||||
|
||||
Example:
|
||||
|
||||
```js title="/pages/api/auth/[...nextauth].js"
|
||||
import log from "logging-service"
|
||||
|
||||
@@ -371,8 +367,8 @@ If the `debug` level is defined by the user, it will be called regardless of the
|
||||
|
||||
### theme
|
||||
|
||||
* **Default value**: `"auto"`
|
||||
* **Required**: *No*
|
||||
- **Default value**: `"auto"`
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -388,8 +384,8 @@ Advanced options are passed the same way as basic options, but may have complex
|
||||
|
||||
### useSecureCookies
|
||||
|
||||
* **Default value**: `true` for HTTPS sites / `false` for HTTP sites
|
||||
* **Required**: *No*
|
||||
- **Default value**: `true` for HTTPS sites / `false` for HTTP sites
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -404,15 +400,15 @@ 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.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
### cookies
|
||||
|
||||
* **Default value**: `{}`
|
||||
* **Required**: *No*
|
||||
- **Default value**: `{}`
|
||||
- **Required**: _No_
|
||||
|
||||
#### Description
|
||||
|
||||
|
||||
@@ -3,59 +3,123 @@ id: providers
|
||||
title: Providers
|
||||
---
|
||||
|
||||
Authentication Providers in NextAuth.js are services that can be used to sign in (OAuth, Email, etc).
|
||||
Authentication Providers in **NextAuth.js** are services that can be used to sign in a user.
|
||||
|
||||
## Sign in with OAuth
|
||||
There's four ways a user can be signed in:
|
||||
|
||||
NextAuth.js is designed to work with any OAuth service, it supports OAuth 1.0, 1.0A and 2.0 and has built-in support for many popular OAuth sign-in services.
|
||||
- [Using a built-in OAuth Provider](#oauth-providers) (e.g Github, Twitter, Google, etc...)
|
||||
- [Using a custom OAuth Provider](#-using-a-custom-provider)
|
||||
- [Using Email](#email-provider)
|
||||
- [Using Credentials](#credentials-provider)
|
||||
|
||||
### Built-in OAuth providers
|
||||
:::note
|
||||
NextAuth.js is designed to work with any OAuth service, it supports **OAuth 1.0**, **1.0A** and **2.0** and has built-in support for most popular sign-in services.
|
||||
:::
|
||||
|
||||
<ul>
|
||||
## OAuth Providers
|
||||
|
||||
### Available providers
|
||||
|
||||
<div className="provider-name-list">
|
||||
{Object.entries(require("../../providers.json"))
|
||||
.filter(([key]) => !["email", "credentials"].includes(key))
|
||||
.sort(([, a], [, b]) => a.localeCompare(b))
|
||||
.map(([key, name]) =>
|
||||
<li key={key}><a href={`/providers/${key}`}>{name}</a></li>
|
||||
.map(([key, name]) => (
|
||||
<span key={key}>
|
||||
<a href={`/providers/${key}`}>{name}</a>
|
||||
<span className="provider-name-list__comma">,</span>
|
||||
</span>
|
||||
)
|
||||
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
### Using a built-in OAuth provider
|
||||
### How to
|
||||
|
||||
1. Register your application at the developer portal of your provider. There are links above to the developer docs for most supported providers with details on how to register your application.
|
||||
|
||||
2. The redirect URI should follow this format:
|
||||
```
|
||||
[origin]/api/auth/callback/[provider]
|
||||
```
|
||||
For example, Twitter on `localhost` this would be:
|
||||
```
|
||||
http://localhost:3000/api/auth/callback/twitter
|
||||
```
|
||||
|
||||
```
|
||||
[origin]/api/auth/callback/[provider]
|
||||
```
|
||||
|
||||
For example, Twitter on `localhost` this would be:
|
||||
|
||||
```
|
||||
http://localhost:3000/api/auth/callback/twitter
|
||||
```
|
||||
|
||||
3. Create a `.env` file at the root of your project and add the client ID and client secret. For Twitter this would be:
|
||||
|
||||
```
|
||||
TWITTER_ID=YOUR_TWITTER_CLIENT_ID
|
||||
TWITTER_SECRET=YOUR_TWITTER_CLIENT_SECRET
|
||||
```
|
||||
```
|
||||
TWITTER_ID=YOUR_TWITTER_CLIENT_ID
|
||||
TWITTER_SECRET=YOUR_TWITTER_CLIENT_SECRET
|
||||
```
|
||||
|
||||
4. Now you can add the provider settings to the NextAuth options object. You can add as many OAuth providers as you like, as you can see `providers` is an array.
|
||||
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Twitter({
|
||||
clientId: process.env.TWITTER_ID,
|
||||
clientSecret: process.env.TWITTER_SECRET
|
||||
})
|
||||
],
|
||||
...
|
||||
```
|
||||
5. Once a provider has been setup, you can sign in at the following URL: `[origin]/api/auth/signin`. This is an unbranded auto-generated page with all the configured providers.
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Twitter({
|
||||
clientId: process.env.TWITTER_ID,
|
||||
clientSecret: process.env.TWITTER_SECRET
|
||||
})
|
||||
],
|
||||
...
|
||||
```
|
||||
|
||||
5. Once a provider has been setup, you can sign in at the following URL: `[origin]/api/auth/signin`. This is an unbranded auto-generated page with all the configured providers.
|
||||
|
||||
<Image src="/img/signin.png" alt="Signin Screenshot" />
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Description | Type | Required |
|
||||
| :-----------------: | :--------------------------------------------------------------: | :---------------------------: | :------: |
|
||||
| id | Unique ID for the provider | `string` | Yes |
|
||||
| name | Descriptive name for the provider | `string` | Yes |
|
||||
| type | Type of provider, in this case `oauth` | `"oauth"` | Yes |
|
||||
| version | OAuth version (e.g. '1.0', '1.0a', '2.0') | `string` | Yes |
|
||||
| scope | OAuth access scopes (expects array or string) | `string` or `string[]` | Yes |
|
||||
| params | Extra URL params sent when calling `accessTokenUrl` | `Object` | Yes |
|
||||
| accessTokenUrl | Endpoint to retrieve an access token | `string` | Yes |
|
||||
| authorizationUrl | Endpoint to request authorization from the user | `string` | Yes |
|
||||
| requestTokenUrl | Endpoint to retrieve a request token | `string` | Yes |
|
||||
| profileUrl | Endpoint to retrieve the user's profile | `string` | Yes |
|
||||
| clientId | Client ID of the OAuth provider | `string` | Yes |
|
||||
| clientSecret | Client Secret of the OAuth provider | `string` | Yes |
|
||||
| profile | A callback returning an object with the user's info | `(profile, tokens) => Object` | Yes |
|
||||
| protection | Additional security for OAuth login flows (defaults to `state`) | `"pkce"`,`"state"`,`"none"` | No |
|
||||
| state | Same as `protection: "state"`. Being deprecated, use protection. | `boolean` | No |
|
||||
| headers | Any headers that should be sent to the OAuth provider | `Object` | No |
|
||||
| authorizationParams | Additional params to be sent to the authorization endpoint | `Object` | No |
|
||||
| idToken | Set to `true` for services that use ID Tokens (e.g. OpenID) | `boolean` | No |
|
||||
| region | Only when using BattleNet | `string` | No |
|
||||
| domain | Only when using certain Providers | `string` | No |
|
||||
| tenantId | Only when using Azure, Active Directory, B2C, FusionAuth | `string` | No |
|
||||
|
||||
:::tip
|
||||
Even if you are using a built-in provider, you can override any of these options to tweak the default configuration.
|
||||
|
||||
```js title=[...nextauth].js
|
||||
import Providers from "next-auth/providers"
|
||||
|
||||
Providers.Auth0({
|
||||
clientId: process.env.CLIENT_ID,
|
||||
clientSecret: process.env.CLIENT_SECRET,
|
||||
domain: process.env.DOMAIN,
|
||||
scope: "openid your_custom_scope", // We do provide a default, but this will override it if defined
|
||||
profile(profile) {
|
||||
return {} // Return the profile in a shape that is different from the built-in one.
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Using a custom provider
|
||||
|
||||
You can use an OAuth provider that isn't built-in by using a custom object.
|
||||
@@ -89,7 +153,8 @@ As an example of what this looks like, this is the provider object returned for
|
||||
clientSecret: ""
|
||||
}
|
||||
```
|
||||
You can replace all the options in this JSON object with the ones from your custom provider - be sure to give it a unique ID and specify the correct OAuth version - and add it to the providers option:
|
||||
|
||||
Replace all the options in this JSON object with the ones from your custom provider - be sure to give it a unique ID and specify the correct OAuth version - and add it to the providers option when initializing the library:
|
||||
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
import Providers from `next-auth/providers`
|
||||
@@ -111,33 +176,24 @@ providers: [
|
||||
...
|
||||
```
|
||||
|
||||
### Adding a new provider
|
||||
|
||||
If you think your custom provider might be useful to others, we encourage you to open a PR and add it to the built-in list so others can discover it much more easily!
|
||||
|
||||
You only need to add two changes:
|
||||
|
||||
### OAuth provider options
|
||||
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/src/providers)<br />
|
||||
• make sure you use a named default export, like this: `export default function YourProvider`
|
||||
2. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/www/docs/providers)
|
||||
3. Add it to our [provider types](https://github.com/nextauthjs/next-auth/blob/main/types/providers.d.ts) (for TS projects)<br />
|
||||
• you just need to add your new provider name to [this list](https://github.com/nextauthjs/next-auth/blob/main/types/providers.d.ts#L56-L97)<br />
|
||||
• in case you new provider accepts some custom options, you can [add them here](https://github.com/nextauthjs/next-auth/blob/main/types/providers.d.ts#L48-L53)
|
||||
|
||||
| Name | Description | Type | Required |
|
||||
| :-----------------: | :--------------------------------------------------------------: | :-----------------------------: | :------: |
|
||||
| id | Unique ID for the provider | `string` | Yes |
|
||||
| name | Descriptive name for the provider | `string` | Yes |
|
||||
| type | Type of provider, in this case it should be `oauth` | `oauth`, `email`, `credentials` | Yes |
|
||||
| version | OAuth version (e.g. '1.0', '1.0a', '2.0') | `string` | Yes |
|
||||
| accessTokenUrl | Endpoint to retrieve an access token | `string` | Yes |
|
||||
| authorizationUrl | Endpoint to request authorization from the user | `string` | Yes |
|
||||
| clientId | Client ID of the OAuth provider | `string` | Yes |
|
||||
| clientSecret | Client Secret of the OAuth provider | `string` | No |
|
||||
| scope | OAuth access scopes (expects array or string) | `string` or `string[]` | No |
|
||||
| params | Additional authorization URL parameters | `object` | No |
|
||||
| requestTokenUrl | Endpoint to retrieve a request token | `string` | No |
|
||||
| authorizationParams | Additional params to be sent to the authorization endpoint | `object` | No |
|
||||
| profileUrl | Endpoint to retrieve the user's profile | `string` | No |
|
||||
| profile | A callback returning an object with the user's info | `object` | No |
|
||||
| idToken | Set to `true` for services that use ID Tokens (e.g. OpenID) | `boolean` | No |
|
||||
| headers | Any headers that should be sent to the OAuth provider | `object` | No |
|
||||
| protection | Additional security for OAuth login flows (defaults to `state`) |`[pkce]`,`[state]`,`[pkce,state]`| No |
|
||||
| state | Same as `protection: "state"`. Being deprecated, use protection. | `boolean` | No |
|
||||
That's it! 🎉 Others will be able to discover this provider much more easily now!
|
||||
|
||||
## Sign in with Email
|
||||
## Email Provider
|
||||
|
||||
### How to
|
||||
|
||||
The Email provider uses email to send "magic links" that can be used sign in, you will likely have seen them before if you have used software like Slack.
|
||||
|
||||
@@ -164,8 +220,21 @@ See the [Email provider documentation](/providers/email) for more information on
|
||||
The email provider requires a database, it cannot be used without one.
|
||||
:::
|
||||
|
||||
### Options
|
||||
|
||||
## Sign in with Credentials
|
||||
| Name | Description | Type | Required |
|
||||
| :---------------------: | :---------------------------------------------------------------------------------: | :------------------------------: | :------: |
|
||||
| id | Unique ID for the provider | `string` | Yes |
|
||||
| name | Descriptive name for the provider | `string` | Yes |
|
||||
| type | Type of provider, in this case `email` | `"email"` | Yes |
|
||||
| server | Path or object pointing to the email server | `string` or `Object` | Yes |
|
||||
| sendVerificationRequest | Callback to execute when a verification request is sent | `(params) => Promise<undefined>` | Yes |
|
||||
| from | The email address from which emails are sent, default: "<no-reply@example.com>" | `string` | No |
|
||||
| maxAge | How long until the e-mail can be used to log the user in seconds. Defaults to 1 day | `number` | No |
|
||||
|
||||
## Credentials Provider
|
||||
|
||||
### How to
|
||||
|
||||
The Credentials provider allows you to handle signing in with arbitrary credentials, such as a username and password, two factor authentication or hardware device (e.g. YubiKey U2F / FIDO).
|
||||
|
||||
@@ -211,26 +280,12 @@ See the [Credentials provider documentation](/providers/credentials) for more in
|
||||
The Credentials provider can only be used if JSON Web Tokens are enabled for sessions. Users authenticated with the Credentials provider are not persisted in the database.
|
||||
:::
|
||||
|
||||
<!-- React Image Component -->
|
||||
export const Image = ({ children, src, alt = '' }) => (
|
||||
<div
|
||||
style={{
|
||||
padding: '0.2rem',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<img alt={alt} src={src} />
|
||||
</div>
|
||||
)
|
||||
### Options
|
||||
|
||||
|
||||
## Adding a new built-in provider
|
||||
|
||||
If you think your custom provider might be useful to others, we encourage you to open a PR and add it to the built-in list so others can discover it much more easily! You only need to add two changes:
|
||||
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/src/providers) (Make sure you use a named default export, like `export default function YourProvider`!)
|
||||
2. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/www/docs/providers)
|
||||
|
||||
That's it! 🎉 Others will be able to discover this provider much more easily now!
|
||||
|
||||
You can look at the existing built-in providers for inspiration.
|
||||
| Name | Description | Type | Required |
|
||||
| :---------: | :-----------------------------------------------: | :------------------------------: | :------: |
|
||||
| id | Unique ID for the provider | `string` | Yes |
|
||||
| name | Descriptive name for the provider | `string` | Yes |
|
||||
| type | Type of provider, in this case `credentials` | `"credentials"` | Yes |
|
||||
| credentials | The credentials to sign-in with | `Object` | Yes |
|
||||
| authorize | Callback to execute once user is to be authorized | `(credentials) => Promise<User>` | Yes |
|
||||
|
||||
@@ -95,7 +95,7 @@ If you are unable to use an HS512 key (for example to interoperate with other se
|
||||
|
||||
````
|
||||
jwt: {
|
||||
signingKey: {"kty":"oct","kid":"--","alg":"HS256","k":"--"}
|
||||
signingKey: {"kty":"oct","kid":"--","alg":"HS256","k":"--"},
|
||||
verificationOptions: {
|
||||
algorithms: ["HS256"]
|
||||
}
|
||||
|
||||
@@ -14,6 +14,43 @@ The types at [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyType
|
||||
|
||||
---
|
||||
|
||||
## Adapters
|
||||
|
||||
If you're writing your own custom Adapter, you can take advantage of the types to make sure your implementation conforms to what's expected:
|
||||
|
||||
```ts
|
||||
import type { Adapter } from "next-auth/adapters"
|
||||
|
||||
const MyAdapter: Adapter = () => {
|
||||
return {
|
||||
async getAdapter() {
|
||||
return {
|
||||
// your adapter methods here
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When writing your own custom Adapter in plain JavaScript, note that you can use **JSDoc** to get helpful editor hints and auto-completion like so:
|
||||
|
||||
```js
|
||||
/** @type { import("next-auth/adapters").Adapter } */
|
||||
const MyAdapter = () => {
|
||||
return {
|
||||
async getAdapter() {
|
||||
return {
|
||||
// your adapter methods here
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
This will work in code editors with a strong TypeScript integration like VSCode or WebStorm. It might not work if you're using more lightweight editors like VIM or Atom.
|
||||
:::
|
||||
|
||||
## Module Augmentation
|
||||
|
||||
`next-auth` comes with certain types/interfaces, that are shared across submodules. Good examples are `Session` and `JWT`. Ideally, you should only need to create these types at a single place, and TS should pick them up in every location where they are referenced. Luckily, this is exactly what Module Augmentation can do for us. Define your shared interfaces in a single location, and get type-safety across your application, when you use `next-auth` (or one of its submodules).
|
||||
|
||||
26
www/docs/providers/dropbox.md
Normal file
26
www/docs/providers/dropbox.md
Normal 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
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
@@ -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"
|
||||
})
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
@@ -5,21 +5,21 @@ title: Database Adapters
|
||||
|
||||
An **Adapter** in NextAuth.js connects your application to whatever database or backend system you want to use to store data for user accounts, sessions, etc.
|
||||
|
||||
You do not need to specify an adapter explicitly unless you want to use advanced options such as custom models or schemas, if you want to use the Prisma adapter instead of the default TypeORM adapter, or if you are creating a custom adapter to connect to a database that is not one of the supported databases.
|
||||
You do not need to specify an Adapter explicitly unless you want to use advanced options such as custom models or schemas, if you want to use the Prisma Adapter instead of the default TypeORM Adapter, or if you are creating a custom Adapter to connect to a database that is not one of the supported databases.
|
||||
|
||||
### Database Schemas
|
||||
|
||||
Configure your database by creating the tables and columns to match the schema expected by NextAuth.js.
|
||||
|
||||
* [MySQL Schema](/schemas/mysql)
|
||||
* [Postgres Schema](/schemas/postgres)
|
||||
* [Microsoft SQL Server Schema](/schemas/mssql)
|
||||
- [MySQL Schema](/schemas/mysql)
|
||||
- [Postgres Schema](/schemas/postgres)
|
||||
- [Microsoft SQL Server Schema](/schemas/mssql)
|
||||
|
||||
## TypeORM Adapter
|
||||
|
||||
NextAuth.js comes with a default adapter that uses [TypeORM](https://typeorm.io/) so that it can be used with many different databases without any further configuration, you simply add the node module for the database driver you want to use to your project and pass a database connection string to NextAuth.js.
|
||||
NextAuth.js comes with a default Adapter that uses [TypeORM](https://typeorm.io/) so that it can be used with many different databases without any further configuration, you simply add the node module for the database driver you want to use to your project and pass a database connection string to NextAuth.js.
|
||||
|
||||
The default adapter is the TypeORM adapter, the following configuration options are exactly equivalent.
|
||||
The default Adapter is the TypeORM Adapter, the following configuration options are exactly equivalent.
|
||||
|
||||
```javascript
|
||||
database: {
|
||||
@@ -31,21 +31,21 @@ database: {
|
||||
|
||||
```javascript
|
||||
adapter: Adapters.Default({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
synchronize: true
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
synchronize: true,
|
||||
})
|
||||
```
|
||||
|
||||
```javascript
|
||||
adapter: Adapters.TypeORM.Adapter({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
synchronize: true
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
synchronize: true,
|
||||
})
|
||||
```
|
||||
|
||||
The tutorial [Custom models with TypeORM](/tutorials/typeorm-custom-models) explains how to extend the built in models and schemas used by the TypeORM adapter. You can use these models in your own code.
|
||||
The tutorial [Custom models with TypeORM](/tutorials/typeorm-custom-models) explains how to extend the built in models and schemas used by the TypeORM Adapter. You can use these models in your own code.
|
||||
|
||||
:::tip
|
||||
The `synchronize` option in TypeORM will generate SQL that exactly matches the documented schemas for MySQL and Postgres.
|
||||
@@ -55,22 +55,22 @@ However, it should not be enabled against production databases as it may cause d
|
||||
|
||||
## Prisma Adapter
|
||||
|
||||
You can also use NextAuth.js with the experimental adapter for [Prisma 2](https://www.prisma.io/docs/).
|
||||
You can also use NextAuth.js with the experimental Adapter for [Prisma 2](https://www.prisma.io/docs/).
|
||||
|
||||
To use this adapter, you need to install Prisma Client and Prisma CLI:
|
||||
To use this Adapter, you need to install Prisma Client and Prisma CLI:
|
||||
|
||||
```
|
||||
npm install @prisma/client
|
||||
npm install prisma --save-dev
|
||||
```
|
||||
|
||||
Configure your NextAuth.js to use the Prisma adapter:
|
||||
Configure your NextAuth.js to use the Prisma Adapter:
|
||||
|
||||
```javascript title="pages/api/auth/[...nextauth].js"
|
||||
import NextAuth from 'next-auth'
|
||||
import Providers from 'next-auth/providers'
|
||||
import Adapters from 'next-auth/adapters'
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import NextAuth from "next-auth"
|
||||
import Providers from "next-auth/providers"
|
||||
import Adapters from "next-auth/adapters"
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
@@ -78,8 +78,8 @@ export default NextAuth({
|
||||
providers: [
|
||||
Providers.Google({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET
|
||||
})
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
}),
|
||||
],
|
||||
adapter: Adapters.Prisma.Adapter({ prisma }),
|
||||
})
|
||||
@@ -175,6 +175,7 @@ datasource db {
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Generate Client
|
||||
@@ -232,9 +233,30 @@ if (process.env.NODE_ENV === "production") {
|
||||
prisma = global.prisma
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::
|
||||
|
||||
## Custom Adapter
|
||||
|
||||
See the tutorial for [creating a database adapter](/tutorials/creating-a-database-adapter) for more information on how to create a custom adapter. Have a look at the [adapters repository](https://github.com/nextauthjs/adapters) to see community maintained custom adapters or add your own.
|
||||
See the tutorial for [creating a database Adapter](/tutorials/creating-a-database-adapter) for more information on how to create a custom Adapter. Have a look at the [Adapter repository](https://github.com/nextauthjs/adapters) to see community maintained custom Adapter or add your own.
|
||||
|
||||
### Editor integration
|
||||
|
||||
When writing your own custom Adapter in plain JavaScript, note that you can use **JSDoc** to get helpful editor hints and auto-completion like so:
|
||||
|
||||
```js
|
||||
/** @type { import("next-auth/adapters").Adapter } */
|
||||
const MyAdapter = () => {
|
||||
return {
|
||||
async getAdapter() {
|
||||
return {
|
||||
// your adapter methods here
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
This will work in code editors with a strong TypeScript integration like VSCode or WebStorm. It might not work if you're using more lightweight editors like VIM or Atom.
|
||||
:::
|
||||
|
||||
6
www/package-lock.json
generated
6
www/package-lock.json
generated
@@ -17485,9 +17485,9 @@
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
},
|
||||
"ssri": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1"
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ html[data-theme="dark"]:root {
|
||||
@import "buttons.css";
|
||||
@import "table-of-contents.css";
|
||||
@import "sidebar.css";
|
||||
@import "providers.css";
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
html {
|
||||
|
||||
9
www/src/css/providers.css
Normal file
9
www/src/css/providers.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.provider-name-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.provider-name-list__comma {
|
||||
display: inline-flex;
|
||||
margin-right: 5px;
|
||||
}
|
||||
Reference in New Issue
Block a user