Compare commits

..

21 Commits

Author SHA1 Message Date
Balázs Orbán
8cc3c7f8ad fix(ts): add missing callbacks JSDoc 2021-04-19 22:26:48 +02:00
Balázs Orbán
3cc11277d8 test(ts): use absolute imports in test files 2021-04-19 21:54:54 +02:00
Balázs Orbán
5212cfd138 fix(build): fix tsconfig.json formatting 2021-04-19 21:54:35 +02:00
Balázs Orbán
26ec1ced8d test(ts): update JWT getToken test 2021-04-19 21:53:59 +02:00
Balázs Orbán
80282e2622 fix(ts): fix typos, add more links to documentation 2021-04-19 21:50:50 +02:00
Balázs Orbán
480d317bf0 Merge branch 'main' into beta 2021-04-19 17:40:08 +02:00
Balázs Orbán
d25aef5179 Merge main into beta 2021-04-19 17:39:44 +02:00
Balázs Orbán
2f0cdbdc35 Merge branch 'main' into beta 2021-04-19 17:38:18 +02:00
Balázs Orbán
59d4205be5 fix(ts): use relative imports 2021-04-19 17:14:40 +02:00
Balázs Orbán
7335759b04 fix(ts): extract EmailConfigServerOptions to interface 2021-04-19 17:02:53 +02:00
Balázs Orbán
97c77ff44b refactor(ts): de-duplicate types (#1690)
* refactor(ts): deduplicate internal types

* refactor(ts): ease up providers typings

* test(ts): fix failing TS tests

* test(ts): rename TS property to fix test

* docs(ts): mention TS docs in README.md

* feat(ts): move/update client types

* refactor(TS): rename some types

* test(ts): fix client tests

* docs(ts): move function descriptions to .d.ts

* chore: fix lint error

* refactor(ts): separate internal types

* chore: simplify build-types script

* chore: update type import paths in src

* chore(build): create root files at build

* chore: remove unnecessary .npmignore

* chore: run prettier on types

* fix(ts): clean up jwt types

* fix(ts): make getToken return type depend on raw param

* docs(page): explain page errors, add theming note

* docs(ts): add JSDoc to NextAuthOptions props

* chore(ts): remove unused import

* docs(ts): change JSDOC docs notation

* refactor(build): extract module entries into enum

* chore(ts): move ClientSafeProvider

* chore(ts): simplify GetTokenParams generic

* style(lint): fix linting errors

* chore: re-add generic extension to GetTokenParams
2021-04-19 16:54:03 +02:00
Nico Domino
b6963abda7 docs(lint): update contributing.md (#1760)
Regarding ESLint / Prettier use and link to their VSCode extensions
2021-04-18 13:47:02 +02:00
Nico Domino
bdb12adb28 style: replace ts-standard with eslint/prettier (#1724)
* style: move from ts-standard to eslint/prettier

* fix: install remaining eslint-config-standard peer deps

* fix: add remaining missing dependencies/config

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-04-14 20:22:51 +02:00
Balázs Orbán
a1e30507c2 feat(ts): support module augmentation (#1681)
* chore(ts): remove unused imports

* refactor(ts): clean up CallbackOptions

* docs(ts): explain Module Augmentation

* docs(ts): don't use @ in folder name "types"

* test(ts): make jwt params optional

* docs(ts): fix typo (TypeScript -> NextAuth.js)
2021-04-10 23:48:26 +02:00
Balázs Orbán
2c4fce3699 fix(build): fix release 2021-04-09 21:26:00 +02:00
Balázs Orbán
8fa71512d1 fix(built): typo in package.json 2021-04-09 21:20:41 +02:00
Balázs Orbán
d420eeff9d fix(ts): add .d.ts sub-module files to package.json
#1677 seemed to miss this
2021-04-09 21:10:43 +02:00
Lluis Agusti
0d863d38bc fix(ts): correctly export sub-module types (#1677)
* chore(types): build types script

Adds a script that moves the declaration files we have in `./types` to `./dist` relative to the files they intend to type.

This is the first step, we still need to change what we declare in `package.json`, add the script to the CI pipeline if we're happy with it and figure out how to type `next-auth/jwt`.

* refactor(lint): fix build-types script
2021-04-09 20:28:11 +02:00
Lluis Agusti
6f9f42a85b chore(ci): fix typo on types workflow 2021-04-07 17:05:48 +02:00
Lluis Agusti
2160be2a8a feat(ts): expose types from the package (#1665)
* chore(types): move existing types to the repo
* feat(ts): expose types from the main package
* chore(deps): bring back `react-dom` version range
* chore(ts): cleanup deps and comments
* chore(ci): run types tests on a separate workflow
2021-04-07 17:03:17 +02:00
Balázs Orbán
55eb066793 chore: add beta to release flow/GH actions 2021-04-04 22:08:25 +02:00
54 changed files with 726 additions and 713 deletions

View File

@@ -2,14 +2,14 @@ name: Release
on:
push:
branches:
- "main"
- "beta"
- "next"
- "3.x"
- 'main'
- 'beta'
- 'next'
- '3.x'
pull_request:
jobs:
release:
name: "Release"
name: 'Release'
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -20,6 +20,7 @@ jobs:
node-version: 14
- name: Install dependencies
uses: bahmutov/npm-install@v1
- run: npm run build
- run: npx semantic-release@17
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

1
.gitignore vendored
View File

@@ -27,7 +27,6 @@ node_modules
.cache-loader
.next
www/providers.json
src/providers/index.js
# VS
/.vs/slnx.sqlite-journal

View File

@@ -59,19 +59,9 @@ When running `npm run dev`, you start a Next.js dev server on `http://localhost:
>NOTE: When working on CSS, you will need to manually refresh the page after changes. (Improving this through a PR is very welcome!)
#### Providers
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.
#### Databases
Included is a Docker Compose file that starts up MySQL, PostgreSQL, and MongoDB databases on localhost.
Included is a Docker Compose file that starts up MySQL, Postgres, and MongoDB databases on localhost.
It will use port `3306`, `5432`, and `27017` on localhost respectively; please make sure those ports are not used by other services on localhost.

View File

@@ -9,8 +9,6 @@ const MODULE_ENTRIES = {
JWT: "jwt",
}
// Building submodule entries
const BUILD_TARGETS = {
[`${MODULE_ENTRIES.SERVER}.js`]: "module.exports = require('./dist/server').default\n",
[`${MODULE_ENTRIES.CLIENT}.js`]: "module.exports = require('./dist/client').default\n",
@@ -26,8 +24,6 @@ Object.entries(BUILD_TARGETS).forEach(([target, content]) => {
})
})
// Building types
const TYPES_TARGETS = [
`${MODULE_ENTRIES.SERVER}.d.ts`,
`${MODULE_ENTRIES.CLIENT}.d.ts`,
@@ -47,40 +43,3 @@ TYPES_TARGETS.forEach((target) => {
}
)
})
// Building providers
const providersDir = path.join(process.cwd(), "/src/providers")
const files = fs.readdirSync(providersDir, "utf8")
let importLines = ""
let exportLines = `export default {\n`
files.forEach((file) => {
const provider = fs.readFileSync(path.join(providersDir, file), "utf8")
try {
// NOTE: If this fails, the default export probably wasn't a named function.
// Always use a named function as default export.
// Eg.: export default function YourProvider ...
const { functionName } = provider.match(
/export default function (?<functionName>.+)\s?\(/
).groups
importLines += `import ${functionName} from "./${file}"\n`
exportLines += ` ${functionName},\n`
} catch (error) {
console.error(
[
`\nThe provider file '${file}' should have a single named default export`,
"Example: 'export default function YourProvider'\n\n",
].join("\n")
)
process.exit(1)
}
})
exportLines += `}\n`
fs.writeFile(
path.join(process.cwd(), "src/providers/index.js"),
[importLines, exportLines].join("\n")
)

12
package-lock.json generated
View File

@@ -6074,12 +6074,6 @@
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true
},
"y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
@@ -8437,9 +8431,9 @@
}
},
"jose": {
"version": "1.28.1",
"resolved": "https://registry.npmjs.org/jose/-/jose-1.28.1.tgz",
"integrity": "sha512-6JK28rFu5ENp/yxMwM+iN7YeaInnY9B9Bggjkz5fuwLiJhbVrl2O4SJr65bdNBPl9y27fdC3Mymh+FVCvozLIg==",
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/jose/-/jose-1.28.0.tgz",
"integrity": "sha512-JmfDRzt/HSj8ipd9TsDtEHoLUnLYavG+7e8F6s1mx2jfVSfXOTaFQsJUydbjJpTnTDHP1+yKL9Ke7ktS/a0Eiw==",
"requires": {
"@panva/asn1.js": "^1.0.0"
}

View File

@@ -8,7 +8,7 @@
"main": "index.js",
"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": "babel --config-file ./config/babel.config.json src --out-dir dist && node ./config/build.js",
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js",
"dev:with-css": "next | npm run watch:css",
"dev": "next",

View File

@@ -1,23 +1,26 @@
/** @type {import("types").LoggerInstance} */
const _logger = {
error(code, ...message) {
error (code, ...message) {
console.error(
`[next-auth][error][${code.toLowerCase()}]`,
`\nhttps://next-auth.js.org/errors#${code.toLowerCase()}`,
...message
)
},
warn(code, ...message) {
warn (code, ...message) {
console.warn(
`[next-auth][warn][${code.toLowerCase()}]`,
`\nhttps://next-auth.js.org/warnings#${code.toLowerCase()}`,
...message
)
},
debug(code, ...message) {
debug (code, ...message) {
if (!process?.env?._NEXTAUTH_DEBUG) return
console.log(`[next-auth][debug][${code.toLowerCase()}]`, ...message)
},
console.log(
`[next-auth][debug][${code.toLowerCase()}]`,
...message
)
}
}
/**
@@ -25,7 +28,7 @@ const _logger = {
* Any `undefined` level will use the default logger.
* @param {Partial<import("types").LoggerInstance>} newLogger
*/
export function setLogger(newLogger = {}) {
export function setLogger (newLogger = {}) {
if (newLogger.error) _logger.error = newLogger.error
if (newLogger.warn) _logger.warn = newLogger.warn
if (newLogger.debug) _logger.debug = newLogger.debug
@@ -39,13 +42,13 @@ export default _logger
* @param {string} basePath
* @return {import("types").LoggerInstance}
*/
export function proxyLogger(logger = _logger, basePath) {
export function proxyLogger (logger = _logger, basePath) {
try {
if (typeof window === "undefined") {
if (typeof window === 'undefined') {
return logger
}
const clientLogger = {}
const clientLogger = console
for (const level in logger) {
clientLogger[level] = (code, ...message) => {
_logger[level](code, ...message) // Log on client as usual
@@ -54,23 +57,21 @@ export function proxyLogger(logger = _logger, basePath) {
const body = new URLSearchParams({
level,
code,
message: JSON.stringify(
message.map((m) => {
if (m instanceof Error) {
// Serializing errors: https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
return { name: m.name, message: m.message, stack: m.stack }
}
return m
})
),
message: JSON.stringify(message.map(m => {
if (m instanceof Error) {
// Serializing errors: https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
return { name: m.name, message: m.message, stack: m.stack }
}
return m
}))
})
if (navigator.sendBeacon) {
return navigator.sendBeacon(url, body)
}
return fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body
})
}
}

View File

@@ -1,34 +1,30 @@
export default function Apple(options) {
export default (options) => {
return {
id: "apple",
name: "Apple",
type: "oauth",
version: "2.0",
scope: "name email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://appleid.apple.com/auth/token",
authorizationUrl:
"https://appleid.apple.com/auth/authorize?response_type=code&id_token&response_mode=form_post",
id: 'apple',
name: 'Apple',
type: 'oauth',
version: '2.0',
scope: 'name email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://appleid.apple.com/auth/token',
authorizationUrl: 'https://appleid.apple.com/auth/authorize?response_type=code&id_token&response_mode=form_post',
profileUrl: null,
idToken: true,
profile(profile) {
profile: (profile) => {
// The name of the user will only return on first login
return {
id: profile.sub,
name:
profile.user != null
? profile.user.name.firstName + " " + profile.user.name.lastName
: null,
email: profile.email,
name: profile.user != null ? profile.user.name.firstName + ' ' + profile.user.name.lastName : null,
email: profile.email
}
},
clientId: null,
clientSecret: {
teamId: null,
privateKey: null,
keyId: null,
keyId: null
},
protection: "none", // REVIEW: Apple does not support state, as far as I know. Can we use "pkce" then?
...options,
protection: 'none', // REVIEW: Apple does not support state, as far as I know. Can we use "pkce" then?
...options
}
}

View File

@@ -1,24 +1,24 @@
export default function Atlassian(options) {
export default (options) => {
return {
id: "atlassian",
name: "Atlassian",
type: "oauth",
version: "2.0",
id: 'atlassian',
name: 'Atlassian',
type: 'oauth',
version: '2.0',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code'
},
accessTokenUrl: "https://auth.atlassian.com/oauth/token",
accessTokenUrl: 'https://auth.atlassian.com/oauth/token',
authorizationUrl:
"https://auth.atlassian.com/authorize?audience=api.atlassian.com&response_type=code&prompt=consent",
profileUrl: "https://api.atlassian.com/me",
profile(profile) {
'https://auth.atlassian.com/authorize?audience=api.atlassian.com&response_type=code&prompt=consent',
profileUrl: 'https://api.atlassian.com/me',
profile: (profile) => {
return {
id: profile.account_id,
name: profile.name,
email: profile.email,
image: profile.picture,
image: profile.picture
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,22 @@
export default function Auth0(options) {
export default (options) => {
return {
id: "auth0",
name: "Auth0",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
scope: "openid email profile",
id: 'auth0',
name: 'Auth0',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
scope: 'openid email profile',
accessTokenUrl: `https://${options.domain}/oauth/token`,
authorizationUrl: `https://${options.domain}/authorize?response_type=code`,
profileUrl: `https://${options.domain}/userinfo`,
profile(profile) {
profile: (profile) => {
return {
id: profile.sub,
name: profile.nickname,
email: profile.email,
image: profile.picture,
image: profile.picture
}
},
...options,
...options
}
}

View File

@@ -1,24 +1,24 @@
export default function AzureADB2C(options) {
const tenant = options.tenantId ? options.tenantId : "common"
export default (options) => {
const tenant = options.tenantId ? options.tenantId : 'common'
return {
id: "azure-ad-b2c",
name: "Azure Active Directory B2C",
type: "oauth",
version: "2.0",
id: 'azure-ad-b2c',
name: 'Azure Active Directory B2C',
type: 'oauth',
version: '2.0',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code'
},
accessTokenUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,
authorizationUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query`,
profileUrl: "https://graph.microsoft.com/v1.0/me/",
profile(profile) {
profileUrl: 'https://graph.microsoft.com/v1.0/me/',
profile: (profile) => {
return {
id: profile.id,
name: profile.displayName,
email: profile.userPrincipalName,
email: profile.userPrincipalName
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,20 @@
export default function Basecamp(options) {
export default (options) => {
return {
id: "basecamp",
name: "Basecamp",
type: "oauth",
version: "2.0",
accessTokenUrl:
"https://launchpad.37signals.com/authorization/token?type=web_server",
authorizationUrl:
"https://launchpad.37signals.com/authorization/new?type=web_server",
profileUrl: "https://launchpad.37signals.com/authorization.json",
profile(profile) {
id: 'basecamp',
name: 'Basecamp',
type: 'oauth',
version: '2.0',
accessTokenUrl: 'https://launchpad.37signals.com/authorization/token?type=web_server',
authorizationUrl: 'https://launchpad.37signals.com/authorization/new?type=web_server',
profileUrl: 'https://launchpad.37signals.com/authorization.json',
profile: (profile) => {
return {
id: profile.identity.id,
name: `${profile.identity.first_name} ${profile.identity.last_name}`,
email: profile.identity.email_address,
image: null,
image: null
}
},
...options,
...options
}
}

View File

@@ -1,29 +1,29 @@
export default function BattleNet(options) {
export default (options) => {
const { region } = options
return {
id: "battlenet",
name: "Battle.net",
type: "oauth",
version: "2.0",
scope: "openid",
params: { grant_type: "authorization_code" },
id: 'battlenet',
name: 'Battle.net',
type: 'oauth',
version: '2.0',
scope: 'openid',
params: { grant_type: 'authorization_code' },
accessTokenUrl:
region === "CN"
? "https://www.battlenet.com.cn/oauth/token"
region === 'CN'
? 'https://www.battlenet.com.cn/oauth/token'
: `https://${region}.battle.net/oauth/token`,
authorizationUrl:
region === "CN"
? "https://www.battlenet.com.cn/oauth/authorize?response_type=code"
region === 'CN'
? 'https://www.battlenet.com.cn/oauth/authorize?response_type=code'
: `https://${region}.battle.net/oauth/authorize?response_type=code`,
profileUrl: "https://us.battle.net/oauth/userinfo",
profile(profile) {
profileUrl: 'https://us.battle.net/oauth/userinfo',
profile: (profile) => {
return {
id: profile.id,
name: profile.battletag,
email: null,
image: null,
image: null
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,22 @@
export default function Box(options) {
export default (options) => {
return {
id: "box",
name: "Box",
type: "oauth",
version: "2.0",
scope: "",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.box.com/oauth2/token",
authorizationUrl:
"https://account.box.com/api/oauth2/authorize?response_type=code",
profileUrl: "https://api.box.com/2.0/users/me",
profile(profile) {
id: 'box',
name: 'Box',
type: 'oauth',
version: '2.0',
scope: '',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.box.com/oauth2/token',
authorizationUrl: 'https://account.box.com/api/oauth2/authorize?response_type=code',
profileUrl: 'https://api.box.com/2.0/users/me',
profile: (profile) => {
return {
id: profile.id,
name: profile.name,
email: profile.login,
image: profile.avatar_url,
image: profile.avatar_url
}
},
...options,
...options
}
}

View File

@@ -1,34 +1,30 @@
export default function Bungie(options) {
export default (options) => {
return {
id: "bungie",
name: "Bungie",
type: "oauth",
version: "2.0",
scope: "",
params: { reauth: "true", grant_type: "authorization_code" },
accessTokenUrl: "https://www.bungie.net/platform/app/oauth/token/",
requestTokenUrl: "https://www.bungie.net/platform/app/oauth/token/",
authorizationUrl:
"https://www.bungie.net/en/OAuth/Authorize?response_type=code",
profileUrl:
"https://www.bungie.net/platform/User/GetBungieAccount/{membershipId}/254/",
profile(profile) {
id: 'bungie',
name: 'Bungie',
type: 'oauth',
version: '2.0',
scope: '',
params: { reauth: 'true', grant_type: 'authorization_code' },
accessTokenUrl: 'https://www.bungie.net/platform/app/oauth/token/',
requestTokenUrl: 'https://www.bungie.net/platform/app/oauth/token/',
authorizationUrl: 'https://www.bungie.net/en/OAuth/Authorize?response_type=code',
profileUrl: 'https://www.bungie.net/platform/User/GetBungieAccount/{membershipId}/254/',
profile: (profile) => {
const { bungieNetUser: user } = profile.Response
return {
id: user.membershipId,
name: user.displayName,
image: `https://www.bungie.net${
user.profilePicturePath.startsWith("/") ? "" : "/"
}${user.profilePicturePath}`,
email: null,
image: `https://www.bungie.net${user.profilePicturePath.startsWith('/') ? '' : '/'}${user.profilePicturePath}`,
email: null
}
},
headers: {
"X-API-Key": null,
'X-API-Key': null
},
clientId: null,
clientSecret: null,
...options,
...options
}
}

View File

@@ -1,23 +1,23 @@
export default function Cognito(options) {
export default (options) => {
const { domain } = options
return {
id: "cognito",
name: "Cognito",
type: "oauth",
version: "2.0",
scope: "openid profile email",
params: { grant_type: "authorization_code" },
id: 'cognito',
name: 'Cognito',
type: 'oauth',
version: '2.0',
scope: 'openid profile email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: `https://${domain}/oauth2/token`,
authorizationUrl: `https://${domain}/oauth2/authorize?response_type=code`,
profileUrl: `https://${domain}/oauth2/userInfo`,
profile(profile) {
profile: (profile) => {
return {
id: profile.sub,
name: profile.username,
email: profile.email,
image: null,
image: null
}
},
...options,
...options
}
}

View File

@@ -1,10 +1,10 @@
export default function Credentials(options) {
export default (options) => {
return {
id: "credentials",
name: "Credentials",
type: "credentials",
id: 'credentials',
name: 'Credentials',
type: 'credentials',
authorize: null,
credentials: null,
...options,
...options
}
}

View File

@@ -1,30 +1,29 @@
export default function Discord(options) {
export default (options) => {
return {
id: "discord",
name: "Discord",
type: "oauth",
version: "2.0",
scope: "identify email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://discord.com/api/oauth2/token",
authorizationUrl:
"https://discord.com/api/oauth2/authorize?response_type=code&prompt=none",
profileUrl: "https://discord.com/api/users/@me",
profile(profile) {
id: 'discord',
name: 'Discord',
type: 'oauth',
version: '2.0',
scope: 'identify email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://discord.com/api/oauth2/token',
authorizationUrl: 'https://discord.com/api/oauth2/authorize?response_type=code&prompt=none',
profileUrl: 'https://discord.com/api/users/@me',
profile: (profile) => {
if (profile.avatar === null) {
const defaultAvatarNumber = parseInt(profile.discriminator) % 5
profile.image_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNumber}.png`
} else {
const format = profile.avatar.startsWith("a_") ? "gif" : "png"
const format = profile.avatar.startsWith('a_') ? 'gif' : 'png'
profile.image_url = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`
}
return {
id: profile.id,
name: profile.username,
image: profile.image_url,
email: profile.email,
email: profile.email
}
},
...options,
...options
}
}

View File

@@ -1,54 +1,48 @@
import nodemailer from "nodemailer"
import logger from "../lib/logger"
import nodemailer from 'nodemailer'
import logger from '../lib/logger'
export default function Email(options) {
export default (options) => {
return {
id: "email",
type: "email",
name: "Email",
id: 'email',
type: 'email',
name: 'Email',
// Server can be an SMTP connection string or a nodemailer config object
server: {
host: "localhost",
host: 'localhost',
port: 25,
auth: {
user: "",
pass: "",
},
user: '',
pass: ''
}
},
from: "NextAuth <no-reply@example.com>",
maxAge: 24 * 60 * 60,
from: 'NextAuth <no-reply@example.com>',
maxAge: 24 * 60 * 60, // How long email links are valid for (default 24h)
sendVerificationRequest,
...options,
...options
}
}
const sendVerificationRequest = ({
identifier: email,
url,
baseUrl,
provider,
}) => {
const sendVerificationRequest = ({ identifier: email, url, baseUrl, provider }) => {
return new Promise((resolve, reject) => {
const { server, from } = provider
// Strip protocol from URL and use domain as site name
const site = baseUrl.replace(/^https?:\/\//, "")
const site = baseUrl.replace(/^https?:\/\//, '')
nodemailer.createTransport(server).sendMail(
{
nodemailer
.createTransport(server)
.sendMail({
to: email,
from,
subject: `Sign in to ${site}`,
text: text({ url, site, email }),
html: html({ url, site, email }),
},
(error) => {
html: html({ url, site, email })
}, (error) => {
if (error) {
logger.error("SEND_VERIFICATION_EMAIL_ERROR", email, error)
return reject(new Error("SEND_VERIFICATION_EMAIL_ERROR", error))
logger.error('SEND_VERIFICATION_EMAIL_ERROR', email, error)
return reject(new Error('SEND_VERIFICATION_EMAIL_ERROR', error))
}
return resolve()
}
)
})
})
}
@@ -58,16 +52,16 @@ const html = ({ url, site, email }) => {
// email address and the domain from being turned into a hyperlink by email
// clients like Outlook and Apple mail, as this is confusing because it seems
// like they are supposed to click on their email address to sign in.
const escapedEmail = `${email.replace(/\./g, "&#8203;.")}`
const escapedSite = `${site.replace(/\./g, "&#8203;.")}`
const escapedEmail = `${email.replace(/\./g, '&#8203;.')}`
const escapedSite = `${site.replace(/\./g, '&#8203;.')}`
// Some simple styling options
const backgroundColor = "#f9f9f9"
const textColor = "#444444"
const mainBackgroundColor = "#ffffff"
const buttonBackgroundColor = "#346df1"
const buttonBorderColor = "#346df1"
const buttonTextColor = "#ffffff"
const backgroundColor = '#f9f9f9'
const textColor = '#444444'
const mainBackgroundColor = '#ffffff'
const buttonBackgroundColor = '#346df1'
const buttonBorderColor = '#346df1'
const buttonTextColor = '#ffffff'
return `
<body style="background: ${backgroundColor};">

View File

@@ -1,22 +1,21 @@
export default function EVEOnline(options) {
export default (options) => {
return {
id: "eveonline",
name: "EVE Online",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://login.eveonline.com/oauth/token",
authorizationUrl:
"https://login.eveonline.com/oauth/authorize?response_type=code",
profileUrl: "https://login.eveonline.com/oauth/verify",
profile(profile) {
id: 'eveonline',
name: 'EVE Online',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://login.eveonline.com/oauth/token',
authorizationUrl: 'https://login.eveonline.com/oauth/authorize?response_type=code',
profileUrl: 'https://login.eveonline.com/oauth/verify',
profile: (profile) => {
return {
id: profile.CharacterID,
name: profile.CharacterName,
image: `https://image.eveonline.com/Character/${profile.CharacterID}_128.jpg`,
email: null,
email: null
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,21 @@
export default function Facebook(options) {
export default (options) => {
return {
id: "facebook",
name: "Facebook",
type: "oauth",
version: "2.0",
scope: "email",
accessTokenUrl: "https://graph.facebook.com/oauth/access_token",
authorizationUrl:
"https://www.facebook.com/v7.0/dialog/oauth?response_type=code",
profileUrl: "https://graph.facebook.com/me?fields=email,name,picture",
profile(profile) {
id: 'facebook',
name: 'Facebook',
type: 'oauth',
version: '2.0',
scope: 'email',
accessTokenUrl: 'https://graph.facebook.com/oauth/access_token',
authorizationUrl: 'https://www.facebook.com/v7.0/dialog/oauth?response_type=code',
profileUrl: 'https://graph.facebook.com/me?fields=email,name,picture',
profile: (profile) => {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.picture.data.url,
image: profile.picture.data.url
}
},
...options,
...options
}
}

View File

@@ -1,28 +1,25 @@
export default function FACEIT(options) {
export default (options) => {
return {
id: "faceit",
name: "FACEIT",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
id: 'faceit',
name: 'FACEIT',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
headers: {
Authorization: `Basic ${Buffer.from(
`${options.clientId}:${options.clientSecret}`
).toString("base64")}`,
Authorization: `Basic ${Buffer.from(`${options.clientId}:${options.clientSecret}`).toString('base64')}`
},
accessTokenUrl: "https://api.faceit.com/auth/v1/oauth/token",
authorizationUrl:
"https://accounts.faceit.com/accounts?redirect_popup=true&response_type=code",
profileUrl: "https://api.faceit.com/auth/v1/resources/userinfo",
profile(profile) {
accessTokenUrl: 'https://api.faceit.com/auth/v1/oauth/token',
authorizationUrl: 'https://accounts.faceit.com/accounts?redirect_popup=true&response_type=code',
profileUrl: 'https://api.faceit.com/auth/v1/resources/userinfo',
profile (profile) {
const { guid: id, nickname: name, email, picture: image } = profile
return {
id,
name,
email,
image,
image
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,22 @@
export default function Foursquare(options) {
const { apiVersion } = options
export default ({ apiVersion, ...options }) => {
return {
id: "foursquare",
name: "Foursquare",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://foursquare.com/oauth2/access_token",
id: 'foursquare',
name: 'Foursquare',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://foursquare.com/oauth2/access_token',
authorizationUrl:
"https://foursquare.com/oauth2/authenticate?response_type=code",
'https://foursquare.com/oauth2/authenticate?response_type=code',
profileUrl: `https://api.foursquare.com/v2/users/self?v=${apiVersion}`,
profile(profile) {
profile: (profile) => {
return {
id: profile.id,
name: `${profile.firstName} ${profile.lastName}`,
image: `${profile.prefix}original${profile.suffix}`,
email: profile.contact.email,
email: profile.contact.email
}
},
...options,
...options
}
}

View File

@@ -1,27 +1,27 @@
export default function FusionAuth(options) {
export default (options) => {
let authorizationUrl = `https://${options.domain}/oauth2/authorize?response_type=code`
if (options.tenantId) {
authorizationUrl += `&tenantId=${options.tenantId}`
}
return {
id: "fusionauth",
name: "FusionAuth",
type: "oauth",
version: "2.0",
scope: "openid",
params: { grant_type: "authorization_code" },
id: 'fusionauth',
name: 'FusionAuth',
type: 'oauth',
version: '2.0',
scope: 'openid',
params: { grant_type: 'authorization_code' },
accessTokenUrl: `https://${options.domain}/oauth2/token`,
authorizationUrl,
profileUrl: `https://${options.domain}/oauth2/userinfo`,
profile(profile) {
profile: (profile) => {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
image: profile.picture
}
},
...options,
...options
}
}

View File

@@ -1,21 +1,21 @@
export default function GitHub(options) {
export default (options) => {
return {
id: "github",
name: "GitHub",
type: "oauth",
version: "2.0",
scope: "user",
accessTokenUrl: "https://github.com/login/oauth/access_token",
authorizationUrl: "https://github.com/login/oauth/authorize",
profileUrl: "https://api.github.com/user",
profile(profile) {
id: 'github',
name: 'GitHub',
type: 'oauth',
version: '2.0',
scope: 'user',
accessTokenUrl: 'https://github.com/login/oauth/access_token',
authorizationUrl: 'https://github.com/login/oauth/authorize',
profileUrl: 'https://api.github.com/user',
profile: (profile) => {
return {
id: profile.id,
name: profile.name || profile.login,
email: profile.email,
image: profile.avatar_url,
image: profile.avatar_url
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,22 @@
export default function GitLab(options) {
export default (options) => {
return {
id: "gitlab",
name: "GitLab",
type: "oauth",
version: "2.0",
scope: "read_user",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://gitlab.com/oauth/token",
authorizationUrl: "https://gitlab.com/oauth/authorize?response_type=code",
profileUrl: "https://gitlab.com/api/v4/user",
profile(profile) {
id: 'gitlab',
name: 'GitLab',
type: 'oauth',
version: '2.0',
scope: 'read_user',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://gitlab.com/oauth/token',
authorizationUrl: 'https://gitlab.com/oauth/authorize?response_type=code',
profileUrl: 'https://gitlab.com/api/v4/user',
profile: (profile) => {
return {
id: profile.id,
name: profile.username,
email: profile.email,
image: profile.avatar_url,
image: profile.avatar_url
}
},
...options,
...options
}
}

View File

@@ -1,25 +1,23 @@
export default function Google(options) {
export default (options) => {
return {
id: "google",
name: "Google",
type: "oauth",
version: "2.0",
scope:
"https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://accounts.google.com/o/oauth2/token",
requestTokenUrl: "https://accounts.google.com/o/oauth2/auth",
authorizationUrl:
"https://accounts.google.com/o/oauth2/auth?response_type=code",
profileUrl: "https://www.googleapis.com/oauth2/v1/userinfo?alt=json",
profile(profile) {
id: 'google',
name: 'Google',
type: 'oauth',
version: '2.0',
scope: 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://accounts.google.com/o/oauth2/token',
requestTokenUrl: 'https://accounts.google.com/o/oauth2/auth',
authorizationUrl: 'https://accounts.google.com/o/oauth2/auth?response_type=code',
profileUrl: 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json',
profile: (profile) => {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.picture,
image: profile.picture
}
},
...options,
...options
}
}

View File

@@ -1,17 +1,17 @@
export default function IdentityServer4(options) {
export default (options) => {
return {
id: "identity-server4",
name: "IdentityServer4",
type: "oauth",
version: "2.0",
scope: "openid profile email",
params: { grant_type: "authorization_code" },
id: 'identity-server4',
name: 'IdentityServer4',
type: 'oauth',
version: '2.0',
scope: 'openid profile email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: `https://${options.domain}/connect/token`,
authorizationUrl: `https://${options.domain}/connect/authorize?response_type=code`,
profileUrl: `https://${options.domain}/connect/userinfo`,
profile(profile) {
profile: (profile) => {
return { ...profile, id: profile.sub }
},
...options,
...options
}
}

83
src/providers/index.js Normal file
View File

@@ -0,0 +1,83 @@
import Apple from './apple'
import Atlassian from './atlassian'
import Auth0 from './auth0'
import AzureADB2C from './azure-ad-b2c'
import Basecamp from './basecamp'
import BattleNet from './battlenet'
import Box from './box'
import Bungie from './bungie'
import Cognito from './cognito'
import Credentials from './credentials'
import Discord from './discord'
import Email from './email'
import EVEOnline from './eveonline'
import Facebook from './facebook'
import FACEIT from './faceit'
import Foursquare from './foursquare'
import FusionAuth from './fusionauth'
import GitHub from './github'
import GitLab from './gitlab'
import Google from './google'
import IdentityServer4 from './identity-server4'
import Instagram from './instagram'
import Kakao from './kakao'
import LINE from './line'
import LinkedIn from './linkedin'
import MailRu from './mailru'
import Medium from './medium'
import Netlify from './netlify'
import Okta from './okta'
import Osso from './osso'
import Reddit from './reddit'
import Salesforce from './salesforce'
import Slack from './slack'
import Spotify from './spotify'
import Strava from './strava'
import Twitch from './twitch'
import Twitter from './twitter'
import VK from './vk'
import Yandex from './yandex'
import Zoho from './zoho'
export default {
Apple,
Atlassian,
Auth0,
AzureADB2C,
Basecamp,
BattleNet,
Box,
Bungie,
Cognito,
Credentials,
Discord,
Email,
EVEOnline,
Facebook,
FACEIT,
Foursquare,
FusionAuth,
GitHub,
GitLab,
Google,
IdentityServer4,
Instagram,
Kakao,
LINE,
LinkedIn,
MailRu,
Medium,
Netlify,
Okta,
Osso,
Reddit,
Salesforce,
Slack,
Spotify,
Strava,
Twitch,
Twitter,
VK,
Yandex,
Zoho
}

View File

@@ -1,22 +1,21 @@
export default function Kakao(options) {
export default (options) => {
return {
id: "kakao",
name: "Kakao",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://kauth.kakao.com/oauth/token",
authorizationUrl:
"https://kauth.kakao.com/oauth/authorize?response_type=code",
profileUrl: "https://kapi.kakao.com/v2/user/me",
profile(profile) {
id: 'kakao',
name: 'Kakao',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://kauth.kakao.com/oauth/token',
authorizationUrl: 'https://kauth.kakao.com/oauth/authorize?response_type=code',
profileUrl: 'https://kapi.kakao.com/v2/user/me',
profile: (profile) => {
return {
id: profile.id,
name: profile.kakao_account?.profile.nickname,
email: profile.kakao_account?.email,
image: profile.kakao_account?.profile.profile_image_url,
image: profile.kakao_account?.profile.profile_image_url
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,22 @@
export default function LINE(options) {
export default (options) => {
return {
id: "line",
name: "LINE",
type: "oauth",
version: "2.0",
scope: "profile openid",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.line.me/oauth2/v2.1/token",
authorizationUrl:
"https://access.line.me/oauth2/v2.1/authorize?response_type=code",
profileUrl: "https://api.line.me/v2/profile",
profile(profile) {
id: 'line',
name: 'LINE',
type: 'oauth',
version: '2.0',
scope: 'profile openid',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.line.me/oauth2/v2.1/token',
authorizationUrl: 'https://access.line.me/oauth2/v2.1/authorize?response_type=code',
profileUrl: 'https://api.line.me/v2/profile',
profile: (profile) => {
return {
id: profile.userId,
name: profile.displayName,
email: null,
image: profile.pictureUrl,
image: profile.pictureUrl
}
},
...options,
...options
}
}

View File

@@ -1,28 +1,26 @@
export default function LinkedIn(options) {
export default (options) => {
return {
id: "linkedin",
name: "LinkedIn",
type: "oauth",
version: "2.0",
scope: "r_liteprofile",
id: 'linkedin',
name: 'LinkedIn',
type: 'oauth',
version: '2.0',
scope: 'r_liteprofile',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code',
client_id: options.clientId,
client_secret: options.clientSecret,
client_secret: options.clientSecret
},
accessTokenUrl: "https://www.linkedin.com/oauth/v2/accessToken",
authorizationUrl:
"https://www.linkedin.com/oauth/v2/authorization?response_type=code",
profileUrl:
"https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName)",
profile(profile) {
accessTokenUrl: 'https://www.linkedin.com/oauth/v2/accessToken',
authorizationUrl: 'https://www.linkedin.com/oauth/v2/authorization?response_type=code',
profileUrl: 'https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName)',
profile: (profile) => {
return {
id: profile.id,
name: profile.localizedFirstName + " " + profile.localizedLastName,
name: profile.localizedFirstName + ' ' + profile.localizedLastName,
email: null,
image: null,
image: null
}
},
...options,
...options
}
}

View File

@@ -1,25 +1,25 @@
export default function MailRu(options) {
export default (options) => {
return {
id: "mailru",
name: "Mail.ru",
type: "oauth",
version: "2.0",
scope: "userinfo",
id: 'mailru',
name: 'Mail.ru',
type: 'oauth',
version: '2.0',
scope: 'userinfo',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code'
},
accessTokenUrl: "https://oauth.mail.ru/token",
requestTokenUrl: "https://oauth.mail.ru/token",
authorizationUrl: "https://oauth.mail.ru/login?response_type=code",
profileUrl: "https://oauth.mail.ru/userinfo",
profile(profile) {
accessTokenUrl: 'https://oauth.mail.ru/token',
requestTokenUrl: 'https://oauth.mail.ru/token',
authorizationUrl: 'https://oauth.mail.ru/login?response_type=code',
profileUrl: 'https://oauth.mail.ru/userinfo',
profile: (profile) => {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.image,
image: profile.image
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,22 @@
export default function Medium(options) {
export default (options) => {
return {
id: "medium",
name: "Medium",
type: "oauth",
version: "2.0",
scope: "basicProfile",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.medium.com/v1/tokens",
authorizationUrl: "https://medium.com/m/oauth/authorize?response_type=code",
profileUrl: "https://api.medium.com/v1/me",
profile(profile) {
id: 'medium',
name: 'Medium',
type: 'oauth',
version: '2.0',
scope: 'basicProfile',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.medium.com/v1/tokens',
authorizationUrl: 'https://medium.com/m/oauth/authorize?response_type=code',
profileUrl: 'https://api.medium.com/v1/me',
profile: (profile) => {
return {
id: profile.data.id,
name: profile.data.name,
email: null,
image: profile.data.imageUrl,
image: profile.data.imageUrl
}
},
...options,
...options
}
}

View File

@@ -1,21 +1,21 @@
export default function Netlify(options) {
export default (options) => {
return {
id: "netlify",
name: "Netlify",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.netlify.com/oauth/token",
authorizationUrl: "https://app.netlify.com/authorize?response_type=code",
profileUrl: "https://api.netlify.com/api/v1/user",
profile(profile) {
id: 'netlify',
name: 'Netlify',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.netlify.com/oauth/token',
authorizationUrl: 'https://app.netlify.com/authorize?response_type=code',
profileUrl: 'https://api.netlify.com/api/v1/user',
profile: (profile) => {
return {
id: profile.id,
name: profile.full_name,
email: profile.email,
image: profile.avatar_url,
image: profile.avatar_url
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,22 @@
export default function Okta(options) {
export default (options) => {
return {
id: "okta",
name: "Okta",
type: "oauth",
version: "2.0",
scope: "openid profile email",
id: 'okta',
name: 'Okta',
type: 'oauth',
version: '2.0',
scope: 'openid profile email',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code',
client_id: options.clientId,
client_secret: options.clientSecret,
client_secret: options.clientSecret
},
// These will be different depending on the Org.
accessTokenUrl: `https://${options.domain}/v1/token`,
authorizationUrl: `https://${options.domain}/v1/authorize/?response_type=code`,
profileUrl: `https://${options.domain}/v1/userinfo/`,
profile(profile) {
profile: (profile) => {
return { ...profile, id: profile.sub }
},
...options,
...options
}
}

View File

@@ -1,20 +1,20 @@
export default function Osso(options) {
export default (options) => {
return {
id: "osso",
name: "SAML SSO",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
id: 'osso',
name: 'SAML SSO',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: `https://${options.domain}/oauth/token`,
authorizationUrl: `https://${options.domain}/oauth/authorize?response_type=code`,
profileUrl: `https://${options.domain}/oauth/me`,
profile(profile) {
profile: (profile) => {
return {
id: profile.id,
name: profile.name || profile.email,
email: profile.email,
email: profile.email
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,23 @@
export default function Reddit(options) {
export default (options) => {
return {
id: "reddit",
name: "Reddit",
type: "oauth",
version: "2.0",
scope: "identity",
params: { grant_type: "authorization_code" },
accessTokenUrl: " https://www.reddit.com/api/v1/access_token",
id: 'reddit',
name: 'Reddit',
type: 'oauth',
version: '2.0',
scope: 'identity',
params: { grant_type: 'authorization_code' },
accessTokenUrl: ' https://www.reddit.com/api/v1/access_token',
authorizationUrl:
"https://www.reddit.com/api/v1/authorize?response_type=code",
profileUrl: "https://oauth.reddit.com/api/v1/me",
profile(profile) {
'https://www.reddit.com/api/v1/authorize?response_type=code',
profileUrl: 'https://oauth.reddit.com/api/v1/me',
profile: (profile) => {
return {
id: profile.id,
name: profile.name,
image: null,
email: null,
email: null
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,21 @@
export default function Salesforce(options) {
export default (options) => {
return {
id: "salesforce",
name: "Salesforce",
type: "oauth",
version: "2.0",
params: { display: "page", grant_type: "authorization_code" },
accessTokenUrl: "https://login.salesforce.com/services/oauth2/token",
authorizationUrl:
"https://login.salesforce.com/services/oauth2/authorize?response_type=code",
profileUrl: "https://login.salesforce.com/services/oauth2/userinfo",
protection: "none",
profile(profile) {
id: 'salesforce',
name: 'Salesforce',
type: 'oauth',
version: '2.0',
params: { display: 'page', grant_type: 'authorization_code' },
accessTokenUrl: 'https://login.salesforce.com/services/oauth2/token',
authorizationUrl: 'https://login.salesforce.com/services/oauth2/authorize?response_type=code',
profileUrl: 'https://login.salesforce.com/services/oauth2/userinfo',
protection: 'none', // REVIEW: Can we use "pkce" ?
profile: (profile) => {
return {
...profile,
id: profile.user_id,
image: profile.picture,
image: profile.picture
}
},
...options,
...options
}
}

View File

@@ -1,26 +1,24 @@
export default function Slack(options) {
export default (options) => {
return {
id: "slack",
name: "Slack",
type: "oauth",
version: "2.0",
id: 'slack',
name: 'Slack',
type: 'oauth',
version: '2.0',
scope: [],
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://slack.com/api/oauth.v2.access",
authorizationUrl: "https://slack.com/oauth/v2/authorize",
authorizationParams: {
user_scope: "identity.basic,identity.email,identity.avatar",
},
profileUrl: "https://slack.com/api/users.identity",
profile(profile) {
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://slack.com/api/oauth.v2.access',
authorizationUrl: 'https://slack.com/oauth/v2/authorize',
authorizationParams: { user_scope: 'identity.basic,identity.email,identity.avatar' },
profileUrl: 'https://slack.com/api/users.identity',
profile: (profile) => {
const { user } = profile
return {
id: user.id,
name: user.name,
image: user.image_512,
email: user.email,
email: user.email
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,23 @@
export default function Spotify(options) {
export default (options) => {
return {
id: "spotify",
name: "Spotify",
type: "oauth",
version: "2.0",
scope: "user-read-email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://accounts.spotify.com/api/token",
id: 'spotify',
name: 'Spotify',
type: 'oauth',
version: '2.0',
scope: 'user-read-email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://accounts.spotify.com/api/token',
authorizationUrl:
"https://accounts.spotify.com/authorize?response_type=code",
profileUrl: "https://api.spotify.com/v1/me",
profile(profile) {
'https://accounts.spotify.com/authorize?response_type=code',
profileUrl: 'https://api.spotify.com/v1/me',
profile: (profile) => {
return {
id: profile.id,
name: profile.display_name,
email: profile.email,
image: profile.images?.[0]?.url,
image: profile.images?.[0]?.url
}
},
...options,
...options
}
}

View File

@@ -1,22 +1,22 @@
export default function Strava(options) {
export default (options) => {
return {
id: "strava",
name: "Strava",
type: "oauth",
version: "2.0",
scope: "read",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://www.strava.com/api/v3/oauth/token",
id: 'strava',
name: 'Strava',
type: 'oauth',
version: '2.0',
scope: 'read',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://www.strava.com/api/v3/oauth/token',
authorizationUrl:
"https://www.strava.com/api/v3/oauth/authorize?response_type=code",
profileUrl: "https://www.strava.com/api/v3/athlete",
profile(profile) {
'https://www.strava.com/api/v3/oauth/authorize?response_type=code',
profileUrl: 'https://www.strava.com/api/v3/athlete',
profile: (profile) => {
return {
id: profile.id,
name: profile.firstname,
image: profile.profile,
image: profile.profile
}
},
...options,
...options
}
}

View File

@@ -1,24 +1,24 @@
export default function Twitch(options) {
export default (options) => {
return {
id: "twitch",
name: "Twitch",
type: "oauth",
version: "2.0",
scope: "user:read:email",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://id.twitch.tv/oauth2/token",
id: 'twitch',
name: 'Twitch',
type: 'oauth',
version: '2.0',
scope: 'user:read:email',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://id.twitch.tv/oauth2/token',
authorizationUrl:
"https://id.twitch.tv/oauth2/authorize?response_type=code",
profileUrl: "https://api.twitch.tv/helix/users",
profile(profile) {
'https://id.twitch.tv/oauth2/authorize?response_type=code',
profileUrl: 'https://api.twitch.tv/helix/users',
profile: (profile) => {
const data = profile.data[0]
return {
id: data.id,
name: data.display_name,
image: data.profile_image_url,
email: data.email,
email: data.email
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,23 @@
export default function Twitter(options) {
export default (options) => {
return {
id: "twitter",
name: "Twitter",
type: "oauth",
version: "1.0A",
scope: "",
accessTokenUrl: "https://api.twitter.com/oauth/access_token",
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
authorizationUrl: "https://api.twitter.com/oauth/authenticate",
id: 'twitter',
name: 'Twitter',
type: 'oauth',
version: '1.0A',
scope: '',
accessTokenUrl: 'https://api.twitter.com/oauth/access_token',
requestTokenUrl: 'https://api.twitter.com/oauth/request_token',
authorizationUrl: 'https://api.twitter.com/oauth/authenticate',
profileUrl:
"https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true",
profile(profile) {
'https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true',
profile: (profile) => {
return {
id: profile.id_str,
name: profile.name,
email: profile.email,
image: profile.profile_image_url_https.replace(/_normal\.jpg$/, ".jpg"),
image: profile.profile_image_url_https.replace(/_normal\.jpg$/, '.jpg')
}
},
...options,
...options
}
}

View File

@@ -1,29 +1,30 @@
export default function VK(options) {
const apiVersion = "5.126" // https://vk.com/dev/versions
export default (options) => {
const apiVersion = '5.126' // https://vk.com/dev/versions
return {
id: "vk",
name: "VK",
type: "oauth",
version: "2.0",
scope: "email",
id: 'vk',
name: 'VK',
type: 'oauth',
version: '2.0',
scope: 'email',
params: {
grant_type: "authorization_code",
grant_type: 'authorization_code'
},
accessTokenUrl: `https://oauth.vk.com/access_token?v=${apiVersion}`,
requestTokenUrl: `https://oauth.vk.com/access_token?v=${apiVersion}`,
authorizationUrl: `https://oauth.vk.com/authorize?response_type=code&v=${apiVersion}`,
authorizationUrl:
`https://oauth.vk.com/authorize?response_type=code&v=${apiVersion}`,
profileUrl: `https://api.vk.com/method/users.get?fields=photo_100&v=${apiVersion}`,
profile: (result) => {
const profile = result.response?.[0] ?? {}
return {
id: profile.id,
name: [profile.first_name, profile.last_name].filter(Boolean).join(" "),
name: [profile.first_name, profile.last_name].filter(Boolean).join(' '),
email: profile.email,
image: profile.photo_100,
image: profile.photo_100
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,23 @@
export default function Yandex(options) {
export default (options) => {
return {
id: "yandex",
name: "Yandex",
type: "oauth",
version: "2.0",
scope: "login:email login:info",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://oauth.yandex.ru/token",
requestTokenUrl: "https://oauth.yandex.ru/token",
authorizationUrl: "https://oauth.yandex.ru/authorize?response_type=code",
profileUrl: "https://login.yandex.ru/info?format=json",
profile(profile) {
id: 'yandex',
name: 'Yandex',
type: 'oauth',
version: '2.0',
scope: 'login:email login:info',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://oauth.yandex.ru/token',
requestTokenUrl: 'https://oauth.yandex.ru/token',
authorizationUrl: 'https://oauth.yandex.ru/authorize?response_type=code',
profileUrl: 'https://login.yandex.ru/info?format=json',
profile: (profile) => {
return {
id: profile.id,
name: profile.real_name,
email: profile.default_email,
image: null,
image: null
}
},
...options,
...options
}
}

View File

@@ -1,23 +1,22 @@
export default function Zoho(options) {
export default (options) => {
return {
id: "zoho",
name: "Zoho",
type: "oauth",
version: "2.0",
scope: "AaaServer.profile.Read",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://accounts.zoho.com/oauth/v2/token",
authorizationUrl:
"https://accounts.zoho.com/oauth/v2/auth?response_type=code",
profileUrl: "https://accounts.zoho.com/oauth/user/info",
profile(profile) {
id: 'zoho',
name: 'Zoho',
type: 'oauth',
version: '2.0',
scope: 'AaaServer.profile.Read',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://accounts.zoho.com/oauth/v2/token',
authorizationUrl: 'https://accounts.zoho.com/oauth/v2/auth?response_type=code',
profileUrl: 'https://accounts.zoho.com/oauth/user/info',
profile: (profile) => {
return {
id: profile.ZUID,
name: `${profile.First_Name} ${profile.Last_Name}`,
email: profile.Email,
image: null,
image: null
}
},
...options,
...options
}
}

95
src/server/index.d.ts vendored Normal file
View File

@@ -0,0 +1,95 @@
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
import { LoggerInstance } from 'src/lib/logger'
import { CallbacksOptions } from './lib/callbacks'
import { CookiesOptions } from './lib/cookie'
import { EventsOptions } from './lib/events'
export interface Provider {
id: string
name: string
type: string
version: string
params: Record<string, unknown>
scope: string
accessTokenUrl: string
authorizationUrl: string
profileUrl?: string
grant_type?: string
profile?: (profile: any) => Promise<any>
}
/** @docs https://next-auth.js.org/configuration/options */
export interface NextAuthOptions {
/** @docs https://next-auth.js.org/configuration/options#theme */
theme?: 'auto' | 'dark' | 'light'
/** @docs https://next-auth.js.org/configuration/options#providers */
providers: Provider[]
/** @docs https://next-auth.js.org/configuration/options#database */
database?: any
/** @docs https://next-auth.js.org/configuration/options#secret */
secret?: any
/** @docs https://next-auth.js.org/configuration/options#session */
session?: any
/** @docs https://next-auth.js.org/configuration/options#jwt */
jwt?: any
/** @docs https://next-auth.js.org/configuration/options#pages */
pages?: {
signIn?: string
signOut?: string
/** Error code passed in query string as ?error= */
error?: string
verifyRequest?: string
/** If set, new users will be directed here on first sign in */
newUser?: string
}
/**
* Callbacks are asynchronous functions you can use to control what happens when an action is performed.
* Callbacks are extremely powerful, especially in scenarios involving JSON Web Tokens as
* they allow you to implement access controls without a database and
* to integrate with external databases or APIs.
* @docs https://next-auth.js.org/configuration/options#callbacks
*/
callbacks?: CallbacksOptions
/** @docs https://next-auth.js.org/configuration/options#events */
events?: EventsOptions
/** @docs https://next-auth.js.org/configuration/options#adapter */
adapter?: any
/** @docs https://next-auth.js.org/configuration/options#debug */
debug?: boolean
/** @docs https://next-auth.js.org/configuration/options#usesecurecookies */
useSecureCookies?: boolean
/** @docs https://next-auth.js.org/configuration/options#cookies */
cookies?: CookiesOptions
/** @docs https://next-auth.js.org/configuration/options#logger */
logger: LoggerInstance
}
/** Options that are the same both in internal and user provided options. */
export type NextAuthSharedOptions = 'pages' | 'jwt' | 'events' | 'callbacks' | 'cookies' | 'secret' | 'adapter' | 'theme' | 'debug' | 'logger'
export interface NextAuthInternalOptions extends Pick<NextAuthOptions, NextAuthSharedOptions> {
pkce?: {
code_verifier?: string
/**
* Could be `"plain"`, but not recommended.
* We ignore it for now.
* @spec https://tools.ietf.org/html/rfc7636#section-4.2.
*/
code_challenge_method?: 'S256'
}
provider?: Provider
baseUrl?: string
basePath?: string
action?: string
csrfToken?: string
csrfTokenVerified?: boolean
}
export interface NextAuthRequest extends NextApiRequest {
options: NextAuthInternalOptions
}
export interface NextAuthResponse extends NextApiResponse {}
export declare function NextAuthHandler (req: NextAuthRequest, res: NextAuthResponse, options: NextAuthOptions): ReturnType<NextApiHandler>
export declare function NextAuthHandler (options: NextAuthOptions): ReturnType<NextApiHandler>

View File

@@ -262,8 +262,7 @@ export default async function callback (req, res) {
const defaultJwtPayload = {
name: user.name,
email: user.email,
picture: user.image,
sub: user.id?.toString()
picture: user.image
}
const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, userObjectReturnedFromAuthorizeHandler, false)

View File

@@ -111,7 +111,14 @@ providers: [
...
```
:::tip
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 three changes:
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/src/providers)
2. Re-export your config: at [`src/providers/index.js`](https://github.com/nextauthjs/next-auth/blob/main/src/providers/index.js)
3. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/www/docs/providers)
You can look at the existing built-in providers for inspiration.
:::
### OAuth provider options
@@ -223,14 +230,3 @@ export const Image = ({ children, src, alt = '' }) => (
<img alt={alt} src={src} />
</div>
)
## 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.

View File

@@ -76,7 +76,6 @@ In _most cases_ it does not make sense to specify a database in NextAuth.js opti
The provider you tried to use failed when setting [PKCE or Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636#section-4.2).
The `code_verifier` is saved in a cookie called (by default) `__Secure-next-auth.pkce.code_verifier` which expires after 15 minutes.
Check if `cookies.pkceCodeVerifier` is configured correctly. The default `code_challenge_method` is `"S256"`. This is currently not configurable to `"plain"`, as it is not recommended, and in most cases it is only supported for backward compatibility.
---
### Session Handling

View File

@@ -54,15 +54,10 @@ NextAuth.js does not currently support automatically signing into sites on diffe
### Can I use NextAuth.js with React Native?
NextAuth.js is designed as a secure, confidential client and implements a server side authentication flow.
NextAuth.js is designed as a secure, confidental client and implements a server side authentication flow.
It is not intended to be used in native applications on desktop or mobile applications, which typically implement public clients (e.g. with client / secrets embedded in the application).
### Is NextAuth.js supporting TypeScript?
Yes! Check out the [TypeScript docs](/getting-started/typescript)
---
## Databases
@@ -75,7 +70,7 @@ It also provides an Adapter API which allows you to connect it to any database.
### What does NextAuth.js use databases for?
Databases in NextAuth.js are used for persisting users, OAuth accounts, email sign in tokens and sessions.
Databases in NextAuth.js are used for persisting users, oauth accounts, email sign in tokens and sessions.
Specifying a database is optional if you don't need to persist user data or support email sign in. If you don't specify a database then JSON Web Tokens will be enabled for session storage and used to store session data.
@@ -131,7 +126,7 @@ When an email address is associated with an OAuth account it does not necessaril
With automatic account linking on sign in, this can be exploited by bad actors to hijack accounts by creating an OAuth account associated with the email address of another user.
For this reason it is not secure to automatically link accounts between arbitrary providers on sign in, which is why this feature is generally not provided by authentication service and is not provided by NextAuth.js.
For this reason it is not secure to automatically link accounts between abitrary providers on sign in, which is why this feature is generally not provided by authentication service and is not provided by NextAuth.js.
Automatic account linking is seen on some sites, sometimes insecurely. It can be technically possible to do automatic account linking securely if you trust all the providers involved to ensure they have securely verified the email address associated with the account, but requires placing trust (and transferring the risk) to those providers to handle the process securely.
@@ -174,7 +169,7 @@ NextAuth.js supports both database session tokens and JWT session tokens.
* If a database is specified, database session tokens will be used by default.
* If no database is specified, JWT session tokens will be used by default.
You can also choose to use JSON Web Tokens as session tokens with using a database, by explicitly setting the `session: { jwt: true }` option.
You can also choose to use JSON Web Tokens as session tokens with using a database, by explictly setting the `session: { jwt: true }` option.
### What are the advantages of JSON Web Tokens?
@@ -206,13 +201,13 @@ JSON Web Tokens can be used for session tokens, but are also used for lots of ot
Avoid storing any data in a token that might be problematic if it were to be decrypted in the future.
* If you do not explicitly specify a secret for for NextAuth.js, existing sessions will be invalidated any time your NextAuth.js configuration changes, as NextAuth.js will default to an auto-generated secret.
* If you do not explictly specify a secret for for NextAuth.js, existing sessions will be invalidated any time your NextAuth.js configuration changes, as NextAuth.js will default to an auto-generated secret.
If using JSON Web Token you should at least specify a secret and ideally configure public/private keys.
### Are JSON Web Tokens secure?
By default tokens are signed (JWS) but not encrypted (JWE), as encryption adds additional overhead and reduces the amount of space available to store data (total cookie size for a domain is limited to 4KB).
By default tokens are signed (JWS) but not encrypted (JWE), as encryption adds additional overhead and reduces the amount of space avalible to store data (total cookie size for a domain is limited to 4KB).
* JSON Web Tokens in NextAuth.js use JWS and are signed using HS512 with an auto-generated key.
@@ -222,7 +217,7 @@ You can specify other valid algorithms - [as specified in RFC 7518](https://tool
NextAuth.js will generate keys for you, but this will generate a warning at start up.
Using explicit public/private keys for signing is strongly recommended.
Using explict public/private keys for signing is strongly recommended.
### What signing and encryption standards does NextAuth.js support?

View File

@@ -11,7 +11,7 @@ _New submissions and edits are welcome!_
### [NextJS Authentication Crash Course with NextAuth.js](https://youtu.be/o_wZIVmWteQ)
This tutorial dives in to the ins and outs of NextAuth including email, GitHub, Twitter and integrating with Auth0 in under hour.
This tutorial dives in to the ins and outs of NextAuth including email, Github, Twitter and integrating with Auth0 in under hour.
### [Create your own NextAuth.js Login Pages](https://youtu.be/kB6YNYZ63fw)
@@ -43,10 +43,6 @@ This approach can be used to authenticate existing user accounts against any bac
How to write tests using Cypress.
### [Usage with class components](tutorials/usage-with-class-components)
How to use `useSession()` hook with class components.
## Other tutorials and explainers
_These are tutorials and explainers that have been submitted or that we have found on the web and are hosted elsewhere They include articles, videos and example projects. Submissions for inclusion are welcome!_
@@ -81,7 +77,7 @@ This example shows how to implement a fullstack app in TypeScript with Next.js u
### [Adding Authentication to an existing Next.js Application in no time!](https://dev.to/ndom91/adding-authentication-to-an-existing-serverless-next-js-app-in-no-time-with-nextauth-js-192h)
This `dev.to` tutorial walks one through adding NextAuth.js to an existing project. Including setting up the OAuth client id and secret, adding the API routes for authentication, protecting pages and API routes behind that authentication, etc.
This `dev.to` tutorial walks one through adding NextAuth.js to an existing project. Including setting up the OAuth client id and secret, adding the API routes for authentication, protecting pages and api routes behind that authentication, etc.
### [Adding Sign in With Apple Next JS](https://thesiddd.com/blog/apple-auth)

View File

@@ -1,61 +0,0 @@
---
id: usage-with-class-components
title: Usage with class components
---
If you want to use the `useSession()` hook in your class components you can do so with the help of a higher order component or with a render prop.
## Higher Order Component
```js
import { useSession } from "next-auth/client"
const withSession = Component => props => {
const [session, loading] = useSession()
// if the component has a render property, we are good
if (Component.prototype.render) {
return <Component session={session} loading={loading} {...props} />
}
// if the passed component is a function component, there is no need for this wrapper
throw new Error([
"You passed a function component, `withSession` is not needed.",
"You can `useSession` directly in your component."
].join("\n"))
};
// Usage
class ClassComponent extends React.Component {
render() {
const {session, loading} = this.props
return null
}
}
const ClassComponentWithSession = withSession(ClassComponent)
```
## Render Prop
```js
import { useSession } from "next-auth/client"
const UseSession = ({ children }) => {
const [session, loading] = useSession()
return children({ session, loading })
};
// Usage
class ClassComponent extends React.Component {
render() {
return (
<UseSession>
{({ session, loading }) => (
<pre>{JSON.stringify(session, null, 2)}</pre>
)}
</UseSession>
)
}
}
```