mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
34 Commits
v3.7.0
...
v3.14.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d863d38bc | ||
|
|
6f9f42a85b | ||
|
|
2160be2a8a | ||
|
|
55eb066793 | ||
|
|
5bc8f8b986 | ||
|
|
136361e1f4 | ||
|
|
cc9869592c | ||
|
|
073da60c3d | ||
|
|
aacc34bbfd | ||
|
|
074688d10e | ||
|
|
b3ffe50c03 | ||
|
|
e6d063825d | ||
|
|
985f7b3431 | ||
|
|
237b016378 | ||
|
|
776b9480da | ||
|
|
07a3f76cb3 | ||
|
|
3726d68c49 | ||
|
|
e31db1726a | ||
|
|
a241199c11 | ||
|
|
5385ec20a9 | ||
|
|
810d02e671 | ||
|
|
e5535734f8 | ||
|
|
ba7aed1057 | ||
|
|
a7e08e2a32 | ||
|
|
0d13040264 | ||
|
|
582520f8ef | ||
|
|
95942519a5 | ||
|
|
f3e64f04cc | ||
|
|
ed5cc4aa65 | ||
|
|
0e20b60229 | ||
|
|
3aee24b5dc | ||
|
|
960ca85907 | ||
|
|
f960cc0f6f | ||
|
|
0f64f3eea7 |
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -9,7 +9,7 @@ assignees: ''
|
||||
A clear and concise description of the feature being proposed.
|
||||
|
||||
**Purpose of proposed feature**
|
||||
A clear and concise description description of why this feature is necessary and what problems it solves.
|
||||
A clear and concise description of why this feature is necessary and what problems it solves.
|
||||
|
||||
**Detail about proposed feature**
|
||||
A detailed description of how the proposal might work (if you have one).
|
||||
|
||||
1
.github/stale.yml
vendored
1
.github/stale.yml
vendored
@@ -7,6 +7,7 @@ exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- priority
|
||||
- bug
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -6,10 +6,12 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -13,7 +13,7 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, next ]
|
||||
branches: [ main, beta, next ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
|
||||
11
.github/workflows/integration.yml
vendored
11
.github/workflows/integration.yml
vendored
@@ -2,9 +2,10 @@ name: Integration Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
@@ -17,7 +18,7 @@ jobs:
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
|
||||
# We use self-hosted runners as cloud based runnners (e.g. AWS, GPC)
|
||||
# fail due to IP Address checks done by providers, which enforce
|
||||
# fail due to IP Address checks done by providers, which enforce
|
||||
# CAPTCHA checks on login request from cloud compute IP addresses to
|
||||
# prevent abuse.
|
||||
runs-on: self-hosted
|
||||
@@ -45,7 +46,7 @@ jobs:
|
||||
- run: npm test
|
||||
# TODO Tests should exit out if env vars not set (currently hangs)
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
NEXTAUTH_TWITTER_ID: ${{secrets.NEXTAUTH_TWITTER_ID}}
|
||||
NEXTAUTH_TWITTER_SECRET: ${{secrets.NEXTAUTH_TWITTER_SECRET}}
|
||||
NEXTAUTH_TWITTER_USERNAME: ${{secrets.NEXTAUTH_TWITTER_USERNAME}}
|
||||
|
||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'beta'
|
||||
- 'next'
|
||||
- '3.x'
|
||||
pull_request:
|
||||
|
||||
25
.github/workflows/types.yml
vendored
Normal file
25
.github/workflows/types.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Types
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
|
||||
jobs:
|
||||
lint-and-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
- name: Check types
|
||||
run: npm run test:types
|
||||
3
.npmignore
Normal file
3
.npmignore
Normal file
@@ -0,0 +1,3 @@
|
||||
./types/tests/
|
||||
./types/tests/tsconfig.json
|
||||
./types/tests/tslint.json
|
||||
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"semi": false
|
||||
}
|
||||
23
config/build-types.js
Normal file
23
config/build-types.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const BUILD_TARGETS = [
|
||||
'index.d.ts',
|
||||
'client.d.ts',
|
||||
'adapters.d.ts',
|
||||
'providers.d.ts',
|
||||
'jwt.d.ts',
|
||||
'_next.d.ts',
|
||||
'_utils.d.ts'
|
||||
]
|
||||
|
||||
BUILD_TARGETS.forEach((target) => {
|
||||
fs.copyFile(
|
||||
path.resolve('types', target),
|
||||
path.join(process.cwd(), target),
|
||||
(err) => {
|
||||
if (err) throw err
|
||||
console.log(`[build-types] copying "${target}" to root folder`)
|
||||
}
|
||||
)
|
||||
})
|
||||
26903
package-lock.json
generated
26903
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@@ -7,9 +7,10 @@
|
||||
"author": "Iain Collins <me@iaincollins.com>",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "npm run build:js && npm run build:css",
|
||||
"build": "npm run build:js && npm run build:css && npm run build:types",
|
||||
"build:js": "babel --config-file ./config/babel.config.json src --out-dir dist",
|
||||
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js",
|
||||
"build:types": "node ./config/build-types.js",
|
||||
"dev": "next | npm run watch:css",
|
||||
"watch": "npm run watch:js | npm run watch:css",
|
||||
"watch:js": "babel --config-file ./config/babel.config.json --watch src --out-dir dist",
|
||||
@@ -17,13 +18,14 @@
|
||||
"test:app:start": "docker-compose -f test/docker/app.yml up -d",
|
||||
"test:app:rebuild": "npm run build && docker-compose -f test/docker/app.yml up -d --build",
|
||||
"test:app:stop": "docker-compose -f test/docker/app.yml down",
|
||||
"test": "npm run test:app:rebuild && npm run test:integration && npm run test:app:stop",
|
||||
"test": "npm run test:app:rebuild && npm run test:integration && npm run test:app:stop && npm run test:types",
|
||||
"test:db": "npm run test:db:mysql && npm run test:db:postgres && npm run test:db:mongodb && npm run test:db:mssql",
|
||||
"test:db:mysql": "node test/mysql.js",
|
||||
"test:db:postgres": "node test/postgres.js",
|
||||
"test:db:mongodb": "node test/mongodb.js",
|
||||
"test:db:mssql": "node test/mssql.js",
|
||||
"test:integration": "mocha test/integration",
|
||||
"test:types": "dtslint types",
|
||||
"db:start": "docker-compose -f test/docker/databases.yml up -d",
|
||||
"db:stop": "docker-compose -f test/docker/databases.yml down",
|
||||
"prepublishOnly": "npm run build",
|
||||
@@ -32,8 +34,10 @@
|
||||
"lint": "ts-standard",
|
||||
"lint:fix": "ts-standard --fix"
|
||||
},
|
||||
"types": "types",
|
||||
"files": [
|
||||
"dist",
|
||||
"types",
|
||||
"index.js",
|
||||
"providers.js",
|
||||
"adapters.js",
|
||||
@@ -50,14 +54,14 @@
|
||||
"oauth": "^0.9.15",
|
||||
"pkce-challenge": "^2.1.0",
|
||||
"preact": "^10.4.1",
|
||||
"preact-render-to-string": "^5.1.7",
|
||||
"preact-render-to-string": "^5.1.14",
|
||||
"querystring": "^0.2.0",
|
||||
"require_optional": "^1.0.1",
|
||||
"typeorm": "^0.2.30"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17",
|
||||
"react-dom": "^16.13.1 || ^17"
|
||||
"react-dom": "16.13.1 || ^17"
|
||||
},
|
||||
"peerOptionalDependencies": {
|
||||
"mongodb": "^3.5.9",
|
||||
@@ -81,6 +85,7 @@
|
||||
"conventional-changelog-conventionalcommits": "4.4.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"dotenv": "^8.2.0",
|
||||
"dtslint": "^4.0.8",
|
||||
"eslint": "^7.19.0",
|
||||
"mocha": "^8.1.3",
|
||||
"mongodb": "^3.5.9",
|
||||
@@ -90,6 +95,7 @@
|
||||
"pg": "^8.2.1",
|
||||
"postcss-cli": "^7.1.1",
|
||||
"postcss-nested": "^4.2.1",
|
||||
"prettier": "^2.2.1",
|
||||
"prisma": "^2.16.1",
|
||||
"puppeteer": "^5.2.1",
|
||||
"puppeteer-extra": "^3.1.15",
|
||||
@@ -103,16 +109,19 @@
|
||||
"project": "./tsconfig.json",
|
||||
"ignore": [
|
||||
"test/",
|
||||
"next-env.d.ts"
|
||||
"next-env.d.ts",
|
||||
"types/"
|
||||
],
|
||||
"globals": [
|
||||
"localStorage",
|
||||
"location",
|
||||
"fetch"
|
||||
]
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"type" : "github",
|
||||
"url" : "https://github.com/sponsors/balazsorban44"
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/balazsorban44"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ module.exports = {
|
||||
branches: [
|
||||
'+([0-9])?(.{+([0-9]),x}).x',
|
||||
'main',
|
||||
{ name: 'beta', prerelease: true },
|
||||
{ name: 'next', prerelease: true }
|
||||
]
|
||||
}
|
||||
|
||||
103
src/client/index.d.ts
vendored
Normal file
103
src/client/index.d.ts
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import * as React from 'react'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
|
||||
interface DefaultSession {
|
||||
user: {
|
||||
name: string | null
|
||||
email: string | null
|
||||
image: string | null
|
||||
}
|
||||
expires: Date | string
|
||||
}
|
||||
|
||||
interface BroadcastMessage {
|
||||
event?: 'session'
|
||||
data?: {
|
||||
trigger?: 'signout' | 'getSession'
|
||||
}
|
||||
clientId: string
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
type GetSession<S extends Record<string, unknown> = DefaultSession> = (options: {
|
||||
ctx?: GetServerSidePropsContext
|
||||
req?: GetServerSidePropsContext['req']
|
||||
event?: 'storage' | 'timer' | 'hidden' | string
|
||||
triggerEvent?: boolean
|
||||
}) => Promise<S>
|
||||
|
||||
export interface NextAuthConfig {
|
||||
baseUrl: string
|
||||
basePath: string
|
||||
baseUrlServer: string
|
||||
basePathServer: string
|
||||
/** 0 means disabled (don't send); 60 means send every 60 seconds */
|
||||
keepAlive: number
|
||||
/** 0 means disabled (only use cache); 60 means sync if last checked > 60 seconds ago */
|
||||
clientMaxAge: number
|
||||
/** Used for timestamp since last sycned (in seconds) */
|
||||
_clientLastSync: number
|
||||
/** Stores timer for poll interval */
|
||||
_clientSyncTimer: ReturnType<typeof setTimeout>
|
||||
/** Tracks if event listeners have been added */
|
||||
_eventListenersAdded: boolean
|
||||
/** Stores last session response from hook */
|
||||
_clientSession: DefaultSession | null | undefined
|
||||
/** Used to store to function export by getSession() hook */
|
||||
_getSession: any
|
||||
}
|
||||
|
||||
export type GetCsrfToken = (
|
||||
ctxOrReq: GetServerSidePropsContext & GetServerSidePropsContext['req']
|
||||
) => Promise<string | null>
|
||||
|
||||
export interface SessionOptions {
|
||||
baseUrl?: string
|
||||
basePath?: string
|
||||
clientMaxAge?: number
|
||||
keepAlive?: number
|
||||
}
|
||||
|
||||
export type Provider<S extends Record<string, unknown> = DefaultSession > = (options: {
|
||||
children: React.ReactNode
|
||||
session: S
|
||||
options: SessionOptions
|
||||
}) => React.ReactNode
|
||||
|
||||
export type SetOptions = (options: SessionOptions) => void
|
||||
|
||||
export type SessionContext = React.createContext<[DefaultSession | null, boolean]>
|
||||
|
||||
export type UseSession = () => [any, boolean]
|
||||
|
||||
export type GetProviders = () => Promise<any[]>
|
||||
|
||||
// Sign in types
|
||||
|
||||
export interface SignInOptions {
|
||||
/** Defaults to the current URL. */
|
||||
callbackUrl?: string
|
||||
redirect?: boolean
|
||||
}
|
||||
export interface SignInResponse {
|
||||
error: string | null
|
||||
status: number
|
||||
ok: boolean
|
||||
url: string | null
|
||||
}
|
||||
|
||||
export type SignIn<AuthorizationParams = Record<string, string>> = (
|
||||
provider?: string,
|
||||
options?: SignInOptions,
|
||||
authorizationParams?: AuthorizationParams
|
||||
) => SignInResponse
|
||||
|
||||
// Sign out types
|
||||
|
||||
interface SignOutResponse<RedirectType extends boolean=true> {
|
||||
/** Defaults to the current URL. */
|
||||
callbackUrl?: string
|
||||
redirect?: RedirectType
|
||||
}
|
||||
|
||||
export type SignOut<RedirectType extends boolean = true> = (params: SignOutResponse<RedirectType>) => RedirectType extends true ? Promise<{url?: string} | undefined> : undefined
|
||||
@@ -1,5 +1,3 @@
|
||||
/// Note: fetch() is built in to Next.js 9.4
|
||||
//
|
||||
// Note about signIn() and signOut() methods:
|
||||
//
|
||||
// On signIn() and signOut() we pass 'json: true' to request a response in JSON
|
||||
@@ -20,167 +18,81 @@ import parseUrl from '../lib/parse-url'
|
||||
// relative URLs are valid in that context and so defaults to empty.
|
||||
// 2. When invoked server side the value is picked up from an environment
|
||||
// variable and defaults to 'http://localhost:3000'.
|
||||
/** @type {import(".").NextAuthConfig} */
|
||||
const __NEXTAUTH = {
|
||||
baseUrl: parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl,
|
||||
basePath: parseUrl(process.env.NEXTAUTH_URL).basePath,
|
||||
keepAlive: 0, // 0 == disabled (don't send); 60 == send every 60 seconds
|
||||
clientMaxAge: 0, // 0 == disabled (only use cache); 60 == sync if last checked > 60 seconds ago
|
||||
baseUrlServer: parseUrl(process.env.NEXTAUTH_URL_INTERNAL || process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl,
|
||||
basePathServer: parseUrl(process.env.NEXTAUTH_URL_INTERNAL || process.env.NEXTAUTH_URL).basePath,
|
||||
keepAlive: 0,
|
||||
clientMaxAge: 0,
|
||||
// Properties starting with _ are used for tracking internal app state
|
||||
_clientLastSync: 0, // used for timestamp since last sycned (in seconds)
|
||||
_clientSyncTimer: null, // stores timer for poll interval
|
||||
_eventListenersAdded: false, // tracks if event listeners have been added,
|
||||
_clientSession: undefined, // stores last session response from hook,
|
||||
// Generate a unique ID to make it possible to identify when a message
|
||||
// was sent from this tab/window so it can be ignored to avoid event loops.
|
||||
_clientId: Math.random().toString(36).substring(2) + Date.now().toString(36),
|
||||
// Used to store to function export by getSession() hook
|
||||
_clientLastSync: 0,
|
||||
_clientSyncTimer: null,
|
||||
_eventListenersAdded: false,
|
||||
_clientSession: undefined,
|
||||
_getSession: () => {}
|
||||
}
|
||||
|
||||
const logger = proxyLogger(_logger, __NEXTAUTH.basePath)
|
||||
|
||||
const broadcast = BroadcastChannel()
|
||||
|
||||
// Add event listners on load
|
||||
if (typeof window !== 'undefined') {
|
||||
if (__NEXTAUTH._eventListenersAdded === false) {
|
||||
__NEXTAUTH._eventListenersAdded = true
|
||||
if (typeof window !== 'undefined' && !__NEXTAUTH._eventListenersAdded) {
|
||||
__NEXTAUTH._eventListenersAdded = true
|
||||
// Listen for storage events and update session if event fired from
|
||||
// another window (but suppress firing another event to avoid a loop)
|
||||
// Fetch new session data but tell it to not to fire another event to
|
||||
// avoid an infinite loop.
|
||||
// Note: We could pass session data through and do something like
|
||||
// `setData(message.data)` but that can cause problems depending
|
||||
// on how the session object is being used in the client; it is
|
||||
// more robust to have each window/tab fetch it's own copy of the
|
||||
// session object rather than share it across instances.
|
||||
broadcast.receive(() => __NEXTAUTH._getSession({ event: 'storage' }))
|
||||
|
||||
// Listen for storage events and update session if event fired from
|
||||
// another window (but suppress firing another event to avoid a loop)
|
||||
window.addEventListener('storage', async (event) => {
|
||||
if (event.key === 'nextauth.message') {
|
||||
const message = JSON.parse(event.newValue)
|
||||
if (message?.event === 'session' && message.data) {
|
||||
// Ignore storage events fired from the same window that created them
|
||||
if (__NEXTAUTH._clientId === message.clientId) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch new session data but pass 'true' to it not to fire an event to
|
||||
// avoid an infinite loop.
|
||||
//
|
||||
// Note: We could pass session data through and do something like
|
||||
// `setData(message.data)` but that can cause problems depending
|
||||
// on how the session object is being used in the client; it is
|
||||
// more robust to have each window/tab fetch it's own copy of the
|
||||
// session object rather than share it across instances.
|
||||
await __NEXTAUTH._getSession({ event: 'storage' })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Listen for document visibilitychange events
|
||||
let hidden, visibilityChange
|
||||
if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support
|
||||
hidden = 'hidden'
|
||||
visibilityChange = 'visibilitychange'
|
||||
} else if (typeof document.msHidden !== 'undefined') {
|
||||
hidden = 'msHidden'
|
||||
visibilityChange = 'msvisibilitychange'
|
||||
} else if (typeof document.webkitHidden !== 'undefined') {
|
||||
hidden = 'webkitHidden'
|
||||
visibilityChange = 'webkitvisibilitychange'
|
||||
}
|
||||
const handleVisibilityChange = () => !document[hidden] && __NEXTAUTH._getSession({ event: visibilityChange })
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Method to set options. The documented way is to use the provider, but this
|
||||
// method is being left in as an alternative, that will be helpful if/when we
|
||||
// expose a vanilla JavaScript version that doesn't depend on React.
|
||||
const setOptions = ({
|
||||
baseUrl,
|
||||
basePath,
|
||||
clientMaxAge,
|
||||
keepAlive
|
||||
} = {}) => {
|
||||
if (baseUrl) { __NEXTAUTH.baseUrl = baseUrl }
|
||||
if (basePath) { __NEXTAUTH.basePath = basePath }
|
||||
if (clientMaxAge) { __NEXTAUTH.clientMaxAge = clientMaxAge }
|
||||
if (keepAlive) {
|
||||
__NEXTAUTH.keepAlive = keepAlive
|
||||
|
||||
if (typeof window !== 'undefined' && keepAlive > 0) {
|
||||
// Clear existing timer (if there is one)
|
||||
if (__NEXTAUTH._clientSyncTimer !== null) { clearTimeout(__NEXTAUTH._clientSyncTimer) }
|
||||
|
||||
// Set next timer to trigger in number of seconds
|
||||
__NEXTAUTH._clientSyncTimer = setTimeout(async () => {
|
||||
// Only invoke keepalive when a session exists
|
||||
if (__NEXTAUTH._clientSession) {
|
||||
await __NEXTAUTH._getSession({ event: 'timer' })
|
||||
}
|
||||
}, keepAlive * 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Universal method (client + server)
|
||||
// If passed 'appContext' via getInitialProps() in _app.js then get the req
|
||||
// object from ctx and use that for the req value to allow getSession() to
|
||||
// work seemlessly in getInitialProps() on server side pages *and* in _app.js.
|
||||
export async function getSession ({ ctx, req = ctx?.req, triggerEvent = true } = {}) {
|
||||
const baseUrl = _apiBaseUrl()
|
||||
const fetchOptions = req ? { headers: { cookie: req.headers.cookie } } : {}
|
||||
const session = await _fetchData(`${baseUrl}/session`, fetchOptions)
|
||||
if (triggerEvent) {
|
||||
_sendMessage({ event: 'session', data: { trigger: 'getSession' } })
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
// Universal method (client + server)
|
||||
// If passed 'appContext' via getInitialProps() in _app.js then get the req
|
||||
// object from ctx and use that for the req value to allow getCsrfToken() to
|
||||
// work seemlessly in getInitialProps() on server side pages *and* in _app.js.
|
||||
async function getCsrfToken ({ ctx, req = ctx?.req } = {}) {
|
||||
const baseUrl = _apiBaseUrl()
|
||||
const fetchOptions = req ? { headers: { cookie: req.headers.cookie } } : {}
|
||||
const data = await _fetchData(`${baseUrl}/csrf`, fetchOptions)
|
||||
return data && data.csrfToken ? data.csrfToken : null
|
||||
}
|
||||
|
||||
// Universal method (client + server); does not require request headers
|
||||
const getProviders = async () => {
|
||||
const baseUrl = _apiBaseUrl()
|
||||
return _fetchData(`${baseUrl}/providers`)
|
||||
// Listen for document visibility change events and
|
||||
// if visibility of the document changes, re-fetch the session.
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
!document.hidden && __NEXTAUTH._getSession({ event: 'visibilitychange' })
|
||||
}, false)
|
||||
}
|
||||
|
||||
// Context to store session data globally
|
||||
const SessionContext = createContext()
|
||||
|
||||
// Client side method
|
||||
export const useSession = (session) => {
|
||||
// Try to use context if we can
|
||||
const value = useContext(SessionContext)
|
||||
|
||||
// If we have no Provider in the tree, call the actual hook
|
||||
if (value === undefined) {
|
||||
return _useSessionHook(session)
|
||||
}
|
||||
|
||||
return value
|
||||
/**
|
||||
* React Hook that gives you access
|
||||
* to the logged in user's session data.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#usesession)
|
||||
* @type {import(".").UseSession}
|
||||
*/
|
||||
export function useSession (session) {
|
||||
const context = useContext(SessionContext)
|
||||
if (context) return context
|
||||
return _useSessionHook(session)
|
||||
}
|
||||
|
||||
// Internal hook for getting session from the api.
|
||||
const _useSessionHook = (session) => {
|
||||
function _useSessionHook (session) {
|
||||
const [data, setData] = useState(session)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [loading, setLoading] = useState(!data)
|
||||
|
||||
useEffect(() => {
|
||||
const _getSession = async ({ event = null } = {}) => {
|
||||
__NEXTAUTH._getSession = async ({ event = null } = {}) => {
|
||||
try {
|
||||
const triggredByEvent = (event !== null)
|
||||
const triggeredByStorageEvent = !!((event && event === 'storage'))
|
||||
const triggredByEvent = event !== null
|
||||
const triggeredByStorageEvent = event === 'storage'
|
||||
|
||||
const clientMaxAge = __NEXTAUTH.clientMaxAge
|
||||
const clientLastSync = parseInt(__NEXTAUTH._clientLastSync)
|
||||
const currentTime = Math.floor(new Date().getTime() / 1000)
|
||||
const currentTime = _now()
|
||||
const clientSession = __NEXTAUTH._clientSession
|
||||
|
||||
// Updates triggered by a storage event *always* trigger an update and we
|
||||
// always update if we don't have any value for the current session state.
|
||||
if (triggeredByStorageEvent === false && clientSession !== undefined) {
|
||||
if (!triggeredByStorageEvent && clientSession !== undefined) {
|
||||
if (clientMaxAge === 0 && triggredByEvent !== true) {
|
||||
// If there is no time defined for when a session should be considered
|
||||
// stale, then it's okay to use the value we have until an event is
|
||||
@@ -204,13 +116,14 @@ const _useSessionHook = (session) => {
|
||||
// Update clientLastSync before making response to avoid repeated
|
||||
// invokations that would otherwise be triggered while we are still
|
||||
// waiting for a response.
|
||||
__NEXTAUTH._clientLastSync = Math.floor(new Date().getTime() / 1000)
|
||||
__NEXTAUTH._clientLastSync = _now()
|
||||
|
||||
// If this call was invoked via a storage event (i.e. another window) then
|
||||
// tell getSession not to trigger an event when it calls to avoid an
|
||||
// infinate loop.
|
||||
const triggerEvent = (triggeredByStorageEvent === false)
|
||||
const newClientSessionData = await getSession({ triggerEvent })
|
||||
const newClientSessionData = await getSession({
|
||||
triggerEvent: !triggeredByStorageEvent
|
||||
})
|
||||
|
||||
// Save session state internally, just so we can track that we've checked
|
||||
// if a session exists at least once.
|
||||
@@ -220,27 +133,64 @@ const _useSessionHook = (session) => {
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
logger.error('CLIENT_USE_SESSION_ERROR', error)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
__NEXTAUTH._getSession = _getSession
|
||||
|
||||
_getSession()
|
||||
__NEXTAUTH._getSession()
|
||||
})
|
||||
|
||||
return [data, loading]
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be called client or server side to return a session asynchronously.
|
||||
* It calls `/api/auth/session` and returns a promise with a session object,
|
||||
* or null if no session exists.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#getsession)
|
||||
* @type {import(".").GetSession}
|
||||
*/
|
||||
export async function getSession (ctx) {
|
||||
const session = await _fetchData('session', ctx)
|
||||
if (ctx?.triggerEvent ?? true) {
|
||||
broadcast.post({ event: 'session', data: { trigger: 'getSession' } })
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Cross Site Request Forgery Token (CSRF Token)
|
||||
* required to make POST requests (e.g. for signing in and signing out).
|
||||
* You likely only need to use this if you are not using the built-in
|
||||
* `signIn()` and `signOut()` methods.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#getcsrftoken)
|
||||
* @type {import(".").GetCsrfToken}
|
||||
*/
|
||||
async function getCsrfToken (ctx) {
|
||||
return (await _fetchData('csrf', ctx))?.csrfToken
|
||||
}
|
||||
|
||||
/**
|
||||
* It calls `/api/auth/providers` and returns
|
||||
* a list of the currently configured authentication providers.
|
||||
* It can be useful if you are creating a dynamic custom sign in page.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#getproviders)
|
||||
* @type {import(".").GetProviders}
|
||||
*/
|
||||
export async function getProviders () {
|
||||
return _fetchData('providers')
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-side method to initiate a signin flow
|
||||
* or send the user to the signin page listing all possible providers.
|
||||
* (Automatically adds the CSRF token to the request)
|
||||
* @see https://next-auth.js.org/getting-started/client#signin
|
||||
* @param {string} [provider]
|
||||
* @param {SignInOptions} [options]
|
||||
* @param {object} [authorizationParams]
|
||||
* @return {Promise<SignInResponse | undefined>}
|
||||
* @typedef {{callbackUrl?: string; redirect?: boolean}} SignInOptions
|
||||
* @typedef {{error: string | null; status: number; ok: boolean}} SignInResponse
|
||||
* Automatically adds the CSRF token to the request.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#signin)
|
||||
* @type {import(".").SignIn}
|
||||
*/
|
||||
export async function signIn (provider, options = {}, authorizationParams = {}) {
|
||||
const {
|
||||
@@ -307,10 +257,10 @@ export async function signIn (provider, options = {}, authorizationParams = {})
|
||||
|
||||
/**
|
||||
* Signs the user out, by removing the session cookie.
|
||||
* (Automatically adds the CSRF token to the request)
|
||||
* @param {SignOutOptions} [options]
|
||||
* @returns {Promise<{url?: string} | undefined>}
|
||||
* @typedef {{callbackUrl?: string; redirect?: boolean;}} SignOutOptions
|
||||
* Automatically adds the CSRF token to the request.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#signout)
|
||||
* @type {import(".").SignOut}
|
||||
*/
|
||||
export async function signOut (options = {}) {
|
||||
const {
|
||||
@@ -331,7 +281,7 @@ export async function signOut (options = {}) {
|
||||
}
|
||||
const res = await fetch(`${baseUrl}/signout`, fetchOptions)
|
||||
const data = await res.json()
|
||||
_sendMessage({ event: 'session', data: { trigger: 'signout' } })
|
||||
broadcast.post({ event: 'session', data: { trigger: 'signout' } })
|
||||
if (redirect) {
|
||||
const url = data.url ?? callbackUrl
|
||||
window.location = url
|
||||
@@ -345,40 +295,118 @@ export async function signOut (options = {}) {
|
||||
return data
|
||||
}
|
||||
|
||||
// Provider to wrap the app in to make session data available globally
|
||||
export const Provider = ({ children, session, options }) => {
|
||||
setOptions(options)
|
||||
return createElement(SessionContext.Provider, { value: useSession(session) }, children)
|
||||
}
|
||||
// Method to set options. The documented way is to use the provider, but this
|
||||
// method is being left in as an alternative, that will be helpful if/when we
|
||||
// expose a vanilla JavaScript version that doesn't depend on React.
|
||||
/** @type {import(".").SetOptions} */
|
||||
export function setOptions ({ baseUrl, basePath, clientMaxAge, keepAlive } = {}) {
|
||||
if (baseUrl) __NEXTAUTH.baseUrl = baseUrl
|
||||
if (basePath) __NEXTAUTH.basePath = basePath
|
||||
if (clientMaxAge) __NEXTAUTH.clientMaxAge = clientMaxAge
|
||||
if (keepAlive) {
|
||||
__NEXTAUTH.keepAlive = keepAlive
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
const _fetchData = async (url, options = {}) => {
|
||||
try {
|
||||
const res = await fetch(url, options)
|
||||
const data = await res.json()
|
||||
return Promise.resolve(Object.keys(data).length > 0 ? data : null) // Return null if data empty
|
||||
} catch (error) {
|
||||
logger.error('CLIENT_FETCH_ERROR', url, error)
|
||||
return Promise.resolve(null)
|
||||
// Clear existing timer (if there is one)
|
||||
if (__NEXTAUTH._clientSyncTimer !== null) {
|
||||
clearTimeout(__NEXTAUTH._clientSyncTimer)
|
||||
}
|
||||
|
||||
// Set next timer to trigger in number of seconds
|
||||
__NEXTAUTH._clientSyncTimer = setTimeout(async () => {
|
||||
// Only invoke keepalive when a session exists
|
||||
if (!__NEXTAUTH._clientSession) return
|
||||
await __NEXTAUTH._getSession({ event: 'timer' })
|
||||
}, keepAlive * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
const _apiBaseUrl = () => {
|
||||
/**
|
||||
* Provider to wrap the app in to make session data available globally.
|
||||
* Can also be used to throttle the number of requests to the endpoint
|
||||
* `/api/auth/session`.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/getting-started/client#provider)
|
||||
* @type {import(".").Provider}
|
||||
*/
|
||||
export function Provider ({ children, session, options }) {
|
||||
setOptions(options)
|
||||
return createElement(
|
||||
SessionContext.Provider,
|
||||
{ value: useSession(session) },
|
||||
children
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* If passed 'appContext' via getInitialProps() in _app.js
|
||||
* then get the req object from ctx and use that for the
|
||||
* req value to allow _fetchData to
|
||||
* work seemlessly in getInitialProps() on server side
|
||||
* pages *and* in _app.js.
|
||||
*/
|
||||
async function _fetchData (path, { ctx, req = ctx?.req } = {}) {
|
||||
try {
|
||||
const baseUrl = await _apiBaseUrl()
|
||||
const options = req ? { headers: { cookie: req.headers.cookie } } : {}
|
||||
const res = await fetch(`${baseUrl}/${path}`, options)
|
||||
const data = await res.json()
|
||||
return Object.keys(data).length > 0 ? data : null // Return null if data empty
|
||||
} catch (error) {
|
||||
logger.error('CLIENT_FETCH_ERROR', path, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function _apiBaseUrl () {
|
||||
if (typeof window === 'undefined') {
|
||||
// NEXTAUTH_URL should always be set explicitly to support server side calls - log warning if not set
|
||||
if (!process.env.NEXTAUTH_URL) { logger.warn('NEXTAUTH_URL', 'NEXTAUTH_URL environment variable not set') }
|
||||
if (!process.env.NEXTAUTH_URL) {
|
||||
logger.warn('NEXTAUTH_URL', 'NEXTAUTH_URL environment variable not set')
|
||||
}
|
||||
|
||||
// Return absolute path when called server side
|
||||
return `${__NEXTAUTH.baseUrl}${__NEXTAUTH.basePath}`
|
||||
} else {
|
||||
// Return relative path when called client side
|
||||
return __NEXTAUTH.basePath
|
||||
return `${__NEXTAUTH.baseUrlServer}${__NEXTAUTH.basePathServer}`
|
||||
}
|
||||
// Return relative path when called client side
|
||||
return __NEXTAUTH.basePath
|
||||
}
|
||||
|
||||
const _sendMessage = (message) => {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const timestamp = Math.floor(new Date().getTime() / 1000)
|
||||
localStorage.setItem('nextauth.message', JSON.stringify({ ...message, clientId: __NEXTAUTH._clientId, timestamp })) // eslint-disable-line
|
||||
/** Returns the number of seconds elapsed since January 1, 1970 00:00:00 UTC. */
|
||||
function _now () {
|
||||
return Math.floor(Date.now() / 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspired by [Broadcast Channel API](https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API)
|
||||
* Only not using it directly, because Safari does not support it.
|
||||
*
|
||||
* https://caniuse.com/?search=broadcastchannel
|
||||
*/
|
||||
function BroadcastChannel (name = 'nextauth.message') {
|
||||
return {
|
||||
/**
|
||||
* Get notified by other tabs/windows.
|
||||
* @param {(message: import(".").BroadcastMessage) => void} onReceive
|
||||
*/
|
||||
receive (onReceive) {
|
||||
if (typeof window === 'undefined') return
|
||||
window.addEventListener('storage', async (event) => {
|
||||
if (event.key !== name) return
|
||||
/** @type {import(".").BroadcastMessage} */
|
||||
const message = JSON.parse(event.newValue)
|
||||
if (message?.event !== 'session' || !message?.data) return
|
||||
|
||||
onReceive(message)
|
||||
})
|
||||
},
|
||||
/** Notify other tabs/windows. */
|
||||
post (message) {
|
||||
if (typeof localStorage === 'undefined') return
|
||||
localStorage.setItem(name,
|
||||
JSON.stringify({ ...message, timestamp: _now() })
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
src/lib/logger.d.ts
vendored
5
src/lib/logger.d.ts
vendored
@@ -3,3 +3,8 @@ export interface LoggerInstance {
|
||||
error: (code?: string, ...message: unknown[]) => void
|
||||
debug: (code?: string, ...message: unknown[]) => void
|
||||
}
|
||||
|
||||
export declare function proxyLogger (logger: LoggerInstance, basePath: string): LoggerInstance
|
||||
|
||||
const _logger: LoggerInstance
|
||||
export default _logger
|
||||
|
||||
25
src/providers/faceit.js
Normal file
25
src/providers/faceit.js
Normal file
@@ -0,0 +1,25 @@
|
||||
export default (options) => {
|
||||
return {
|
||||
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')}`
|
||||
},
|
||||
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
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
@@ -12,18 +12,22 @@ 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'
|
||||
@@ -33,6 +37,7 @@ import Twitch from './twitch'
|
||||
import Twitter from './twitter'
|
||||
import VK from './vk'
|
||||
import Yandex from './yandex'
|
||||
import Zoho from './zoho'
|
||||
|
||||
export default {
|
||||
Apple,
|
||||
@@ -49,18 +54,22 @@ export default {
|
||||
Email,
|
||||
EVEOnline,
|
||||
Facebook,
|
||||
FACEIT,
|
||||
Foursquare,
|
||||
FusionAuth,
|
||||
GitHub,
|
||||
GitLab,
|
||||
Google,
|
||||
IdentityServer4,
|
||||
Instagram,
|
||||
Kakao,
|
||||
LINE,
|
||||
LinkedIn,
|
||||
MailRu,
|
||||
Medium,
|
||||
Netlify,
|
||||
Okta,
|
||||
Osso,
|
||||
Reddit,
|
||||
Salesforce,
|
||||
Slack,
|
||||
@@ -69,5 +78,6 @@ export default {
|
||||
Twitch,
|
||||
Twitter,
|
||||
VK,
|
||||
Yandex
|
||||
Yandex,
|
||||
Zoho
|
||||
}
|
||||
|
||||
51
src/providers/instagram.js
Normal file
51
src/providers/instagram.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @param {import("../server").Provider} options
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* // pages/api/auth/[...nextauth].js
|
||||
* import Providers from `next-auth/providers`
|
||||
* ...
|
||||
* providers: [
|
||||
* Providers.Instagram({
|
||||
* clientId: process.env.INSTAGRAM_CLIENT_ID,
|
||||
* clientSecret: process.env.INSTAGRAM_CLIENT_SECRET
|
||||
* })
|
||||
* ]
|
||||
* ...
|
||||
*
|
||||
* // pages/index
|
||||
* import { signIn } from "next-auth/client"
|
||||
* ...
|
||||
* <button onClick={() => signIn("instagram")}>
|
||||
* Sign in
|
||||
* </button>
|
||||
* ...
|
||||
* ```
|
||||
* *Resources:*
|
||||
* - [NextAuth.js Documentation](https://next-auth.js.org/providers/instagram)
|
||||
* - [Instagram Documentation](https://developers.facebook.com/docs/instagram-basic-display-api/getting-started)
|
||||
* - [Configuration](https://developers.facebook.com/apps)
|
||||
*/
|
||||
export default function Instagram (options) {
|
||||
return {
|
||||
id: 'instagram',
|
||||
name: 'Instagram',
|
||||
type: 'oauth',
|
||||
version: '2.0',
|
||||
scope: 'user_profile',
|
||||
params: { grant_type: 'authorization_code' },
|
||||
accessTokenUrl: 'https://api.instagram.com/oauth/access_token',
|
||||
authorizationUrl: 'https://api.instagram.com/oauth/authorize?response_type=code',
|
||||
profileUrl: 'https://graph.instagram.com/me?fields=id,username,account_type,name',
|
||||
async profile (profile) {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.username,
|
||||
email: null,
|
||||
image: null
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
21
src/providers/kakao.js
Normal file
21
src/providers/kakao.js
Normal file
@@ -0,0 +1,21 @@
|
||||
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) => {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.kakao_account?.profile.nickname,
|
||||
email: profile.kakao_account?.email,
|
||||
image: profile.kakao_account?.profile.profile_image_url
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
20
src/providers/osso.js
Normal file
20
src/providers/osso.js
Normal file
@@ -0,0 +1,20 @@
|
||||
export default (options) => {
|
||||
return {
|
||||
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) => {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.name || profile.email,
|
||||
email: profile.email
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
22
src/providers/zoho.js
Normal file
22
src/providers/zoho.js
Normal file
@@ -0,0 +1,22 @@
|
||||
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) => {
|
||||
return {
|
||||
id: profile.ZUID,
|
||||
name: `${profile.First_Name} ${profile.Last_Name}`,
|
||||
email: profile.Email,
|
||||
image: null
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
@@ -225,18 +225,19 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
}
|
||||
break
|
||||
case '_log':
|
||||
try {
|
||||
if (!userOptions.logger) return
|
||||
const {
|
||||
code = 'CLIENT_ERROR',
|
||||
level = 'error',
|
||||
message = '[]'
|
||||
} = req.body
|
||||
if (userOptions.logger) {
|
||||
try {
|
||||
const {
|
||||
code = 'CLIENT_ERROR',
|
||||
level = 'error',
|
||||
message = '[]'
|
||||
} = req.body
|
||||
|
||||
logger[level](code, ...JSON.parse(message))
|
||||
} catch (error) {
|
||||
// If logging itself failed...
|
||||
logger.error('LOGGER_ERROR', error)
|
||||
logger[level](code, ...JSON.parse(message))
|
||||
} catch (error) {
|
||||
// If logging itself failed...
|
||||
logger.error('LOGGER_ERROR', error)
|
||||
}
|
||||
}
|
||||
return res.end()
|
||||
default:
|
||||
|
||||
@@ -167,9 +167,17 @@ async function getOAuth2AccessToken (code, provider, codeVerifier) {
|
||||
raw = querystring.parse(data)
|
||||
}
|
||||
|
||||
const accessToken = provider.id === 'slack'
|
||||
? raw.authed_user.access_token
|
||||
: raw.access_token
|
||||
let accessToken
|
||||
if (provider.id === 'slack') {
|
||||
const { ok, error } = raw
|
||||
if (!ok) {
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
accessToken = raw.authed_user.access_token
|
||||
} else {
|
||||
accessToken = raw.access_token
|
||||
}
|
||||
|
||||
resolve({
|
||||
accessToken,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @ts-check
|
||||
import { h } from 'preact' // eslint-disable-line no-unused-vars
|
||||
import render from 'preact-render-to-string'
|
||||
|
||||
/**
|
||||
* Renders an error page.
|
||||
@@ -57,7 +56,7 @@ export default function error ({ baseUrl, basePath, error = 'default', res }) {
|
||||
|
||||
res.status(statusCode)
|
||||
|
||||
return render(
|
||||
return (
|
||||
<div className='error'>
|
||||
<h1>{heading}</h1>
|
||||
<div className='message'>{message}</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import renderToString from 'preact-render-to-string'
|
||||
import signin from './signin'
|
||||
import signout from './signout'
|
||||
import verifyRequest from './verify-request'
|
||||
@@ -10,7 +11,7 @@ export default function renderPage (req, res) {
|
||||
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
function send ({ html, title }) {
|
||||
res.send(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>${css()}</style><title>${title}</title></head><body class="__next-auth-theme-${theme}"><div class="page">${html}</div></body></html>`)
|
||||
res.send(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>${css()}</style><title>${title}</title></head><body class="__next-auth-theme-${theme}"><div class="page">${renderToString(html)}</div></body></html>`)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { h } from 'preact' // eslint-disable-line no-unused-vars
|
||||
import render from 'preact-render-to-string'
|
||||
|
||||
export default function signin ({ csrfToken, providers, callbackUrl, email, error: errorType }) {
|
||||
// We only want to render providers
|
||||
@@ -30,7 +29,7 @@ export default function signin ({ csrfToken, providers, callbackUrl, email, erro
|
||||
|
||||
const error = errorType && (errors[errorType] ?? errors.default)
|
||||
|
||||
return render(
|
||||
return (
|
||||
<div className='signin'>
|
||||
{error &&
|
||||
<div className='error'>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { h } from 'preact' // eslint-disable-line no-unused-vars
|
||||
import render from 'preact-render-to-string'
|
||||
|
||||
export default function signout ({ baseUrl, basePath, csrfToken }) {
|
||||
return render(
|
||||
return (
|
||||
<div className='signout'>
|
||||
<h1>Are you sure you want to sign out?</h1>
|
||||
<form action={`${baseUrl}${basePath}/signout`} method='POST'>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { h } from 'preact' // eslint-disable-line no-unused-vars
|
||||
import render from 'preact-render-to-string'
|
||||
|
||||
export default function verifyRequest ({ baseUrl }) {
|
||||
return render(
|
||||
return (
|
||||
<div className='verify-request'>
|
||||
<h1>Check your email</h1>
|
||||
<p>A sign in link has been sent to your email address.</p>
|
||||
|
||||
@@ -72,7 +72,7 @@ export default async function callback (req, res) {
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
// TODO: Remove in a future major release
|
||||
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
|
||||
@@ -168,7 +168,7 @@ export default async function callback (req, res) {
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
// TODO: Remove in a future major release
|
||||
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
|
||||
@@ -239,7 +239,7 @@ export default async function callback (req, res) {
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
return res.redirect(error)
|
||||
}
|
||||
@@ -254,7 +254,7 @@ export default async function callback (req, res) {
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
return res.redirect(error)
|
||||
}
|
||||
|
||||
40
types/_next.d.ts
vendored
Normal file
40
types/_next.d.ts
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import { IncomingMessage, ServerResponse } from "http"
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Types from next@10,
|
||||
// see: https://github.com/microsoft/dtslint/issues/297
|
||||
// ------------------------------------------------------
|
||||
export interface NextApiRequest extends IncomingMessage {
|
||||
query: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
cookies: {
|
||||
[key: string]: string
|
||||
}
|
||||
body: any
|
||||
env: any
|
||||
preview?: boolean
|
||||
previewData?: any
|
||||
}
|
||||
|
||||
export type Send<T> = (body: T) => void
|
||||
|
||||
export type NextApiResponse<T = any> = ServerResponse & {
|
||||
send: Send<T>
|
||||
json: Send<T>
|
||||
status: (statusCode: number) => NextApiResponse<T>
|
||||
redirect: ((url: string) => NextApiResponse<T>) &
|
||||
((status: number, url: string) => NextApiResponse<T>)
|
||||
setPreviewData: (
|
||||
data: object | string,
|
||||
options?: {
|
||||
maxAge?: number
|
||||
}
|
||||
) => NextApiResponse<T>
|
||||
clearPreviewData: () => NextApiResponse<T>
|
||||
}
|
||||
|
||||
export type NextApiHandler<T = any> = (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<T>
|
||||
) => void | Promise<void>
|
||||
12
types/_utils.d.ts
vendored
Normal file
12
types/_utils.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
export type NonNullParams<T> = {
|
||||
[K in keyof T]: T[K] extends Record<string, unknown>
|
||||
? NonNullParams<T[K]>
|
||||
: NonNullable<T[K]>
|
||||
}
|
||||
|
||||
export type NullableParams<T> = {
|
||||
[K in keyof T]: T[K] | undefined | null
|
||||
}
|
||||
|
||||
export type WithAdditionalParams<T extends Record<string, any>> = T &
|
||||
Record<string, unknown>
|
||||
242
types/adapters.d.ts
vendored
Normal file
242
types/adapters.d.ts
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
import { ConnectionOptions, EntitySchema } from "typeorm"
|
||||
import { AppOptions, User } from "."
|
||||
import { AppProvider } from "./providers"
|
||||
|
||||
export interface Profile {
|
||||
id: string
|
||||
name: string
|
||||
email: string | null
|
||||
image?: string | null
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
userId: string | number | object
|
||||
expires: Date
|
||||
sessionToken: string
|
||||
accessToken: string
|
||||
}
|
||||
|
||||
export interface VerificationRequest {
|
||||
identifier: string
|
||||
token: string
|
||||
expires: Date
|
||||
}
|
||||
|
||||
export interface SendVerificationRequestParams {
|
||||
identifier: string
|
||||
url: string
|
||||
token: string
|
||||
baseUrl: string
|
||||
provider: AppProvider
|
||||
}
|
||||
|
||||
export type EmailAppProvider = AppProvider & {
|
||||
sendVerificationRequest: (
|
||||
params: SendVerificationRequestParams
|
||||
) => Promise<void>
|
||||
maxAge: number | undefined
|
||||
}
|
||||
|
||||
export interface AdapterInstance<
|
||||
TUser,
|
||||
TProfile,
|
||||
TSession,
|
||||
TVerificationRequest
|
||||
> {
|
||||
createUser: (profile: TProfile) => Promise<TUser>
|
||||
getUser: (id: string) => Promise<TUser | null>
|
||||
getUserByEmail: (email: string) => Promise<TUser | null>
|
||||
getUserByProviderAccountId: (
|
||||
providerId: string,
|
||||
providerAccountId: string
|
||||
) => Promise<TUser | null>
|
||||
updateUser: (user: TUser) => Promise<TUser>
|
||||
linkAccount: (
|
||||
userId: string,
|
||||
providerId: string,
|
||||
providerType: string,
|
||||
providerAccountId: string,
|
||||
refreshToken: string,
|
||||
accessToken: string,
|
||||
accessTokenExpires: number
|
||||
) => Promise<void>
|
||||
createSession: (user: TUser) => Promise<TSession>
|
||||
getSession: (sessionToken: string) => Promise<TSession | null>
|
||||
updateSession: (session: TSession, force?: boolean) => Promise<TSession>
|
||||
deleteSession: (sessionToken: string) => Promise<void>
|
||||
createVerificationRequest?: (
|
||||
email: string,
|
||||
url: string,
|
||||
token: string,
|
||||
secret: string,
|
||||
provider: EmailAppProvider,
|
||||
options: AppOptions
|
||||
) => Promise<TVerificationRequest>
|
||||
getVerificationRequest?: (
|
||||
email: string,
|
||||
verificationToken: string,
|
||||
secret: string,
|
||||
provider: AppProvider
|
||||
) => Promise<TVerificationRequest | null>
|
||||
deleteVerificationRequest?: (
|
||||
email: string,
|
||||
verificationToken: string,
|
||||
secret: string,
|
||||
provider: AppProvider
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
interface Adapter<
|
||||
TUser extends User = any,
|
||||
TProfile extends Profile = any,
|
||||
TSession extends Session = any,
|
||||
TVerificationRequest extends VerificationRequest = any
|
||||
> {
|
||||
getAdapter: (
|
||||
appOptions: AppOptions
|
||||
) => Promise<AdapterInstance<TUser, TProfile, TSession, TVerificationRequest>>
|
||||
}
|
||||
|
||||
type Schema<T = any> = EntitySchema<T>["options"]
|
||||
|
||||
interface Adapters {
|
||||
Default: TypeORMAdapter["Adapter"]
|
||||
TypeORM: TypeORMAdapter
|
||||
Prisma: PrismaAdapter
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: fix auto-type schema
|
||||
*/
|
||||
|
||||
interface TypeORMAdapter<
|
||||
A extends TypeORMAccountModel = any,
|
||||
U extends TypeORMUserModel = any,
|
||||
S extends TypeORMSessionModel = any,
|
||||
VR extends TypeORMVerificationRequestModel = any
|
||||
> {
|
||||
Adapter: (
|
||||
typeOrmConfig: ConnectionOptions,
|
||||
options?: {
|
||||
models?: {
|
||||
Account?: {
|
||||
model: A
|
||||
schema: Schema<A>
|
||||
}
|
||||
User?: {
|
||||
model: U
|
||||
schema: Schema<U>
|
||||
}
|
||||
Session?: {
|
||||
model: S
|
||||
schema: Schema<S>
|
||||
}
|
||||
VerificationRequest?: {
|
||||
model: VR
|
||||
schema: Schema<VR>
|
||||
}
|
||||
}
|
||||
}
|
||||
) => Adapter<U, Profile, S, VR>
|
||||
Models: {
|
||||
Account: {
|
||||
model: TypeORMAccountModel
|
||||
schema: Schema<TypeORMAccountModel>
|
||||
}
|
||||
User: {
|
||||
model: TypeORMUserModel
|
||||
schema: Schema<TypeORMUserModel>
|
||||
}
|
||||
Session: {
|
||||
model: TypeORMSessionModel
|
||||
schema: Schema<TypeORMSessionModel>
|
||||
}
|
||||
VerificationRequest: {
|
||||
model: TypeORMVerificationRequestModel
|
||||
schema: Schema<TypeORMVerificationRequestModel>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface PrismaAdapter {
|
||||
Adapter: (config: {
|
||||
prisma: any
|
||||
modelMapping?: {
|
||||
User: string
|
||||
Account: string
|
||||
Session: string
|
||||
VerificationRequest: string
|
||||
}
|
||||
}) => Adapter
|
||||
}
|
||||
|
||||
declare const Adapters: Adapters
|
||||
|
||||
declare class TypeORMAccountModel {
|
||||
compoundId: string
|
||||
userId: number
|
||||
providerType: string
|
||||
providerId: string
|
||||
providerAccountId: string
|
||||
refreshToken?: string
|
||||
accessToken?: string
|
||||
accessTokenExpires?: Date
|
||||
|
||||
constructor(
|
||||
userId: number,
|
||||
providerId: string,
|
||||
providerType: string,
|
||||
providerAccountId: string,
|
||||
refreshToken?: string,
|
||||
accessToken?: string,
|
||||
accessTokenExpires?: Date
|
||||
)
|
||||
}
|
||||
|
||||
declare class TypeORMUserModel implements User {
|
||||
name?: string
|
||||
email?: string
|
||||
image?: string
|
||||
emailVerified?: Date
|
||||
|
||||
constructor(
|
||||
name?: string,
|
||||
email?: string,
|
||||
image?: string,
|
||||
emailVerified?: Date
|
||||
)
|
||||
}
|
||||
|
||||
declare class TypeORMSessionModel implements Session {
|
||||
userId: number
|
||||
expires: Date
|
||||
sessionToken: string
|
||||
accessToken: string
|
||||
|
||||
constructor(
|
||||
userId: number,
|
||||
expires: Date,
|
||||
sessionToken?: string,
|
||||
accessToken?: string
|
||||
)
|
||||
}
|
||||
|
||||
declare class TypeORMVerificationRequestModel implements VerificationRequest {
|
||||
identifier: string
|
||||
token: string
|
||||
expires: Date
|
||||
|
||||
constructor(identifier: string, token: string, expires: Date)
|
||||
}
|
||||
|
||||
export default Adapters
|
||||
export {
|
||||
Adapter,
|
||||
Adapters,
|
||||
TypeORMAdapter,
|
||||
TypeORMAccountModel,
|
||||
TypeORMUserModel,
|
||||
TypeORMSessionModel,
|
||||
TypeORMVerificationRequestModel,
|
||||
PrismaAdapter,
|
||||
}
|
||||
97
types/client.d.ts
vendored
Normal file
97
types/client.d.ts
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
import { FC } from "react"
|
||||
import { IncomingMessage } from "http"
|
||||
import { WithAdditionalParams } from "./_utils"
|
||||
import { Session } from "."
|
||||
import { AppProvider, DefaultProviders, Providers } from "./providers"
|
||||
|
||||
interface ContextProviderProps {
|
||||
session: WithAdditionalParams<Session> | null | undefined
|
||||
options?: SetOptionsParams
|
||||
}
|
||||
|
||||
interface SetOptionsParams {
|
||||
baseUrl?: string
|
||||
basePath?: string
|
||||
clientMaxAge?: number
|
||||
keepAlive?: number
|
||||
}
|
||||
|
||||
interface SignInResponse {
|
||||
error: string | undefined
|
||||
status: number
|
||||
ok: boolean
|
||||
url: string | null
|
||||
}
|
||||
|
||||
type ContextProvider = FC<ContextProviderProps>
|
||||
|
||||
interface NextContext {
|
||||
req?: IncomingMessage
|
||||
ctx?: { req: IncomingMessage }
|
||||
}
|
||||
|
||||
declare function useSession(): [Session | null | undefined, boolean]
|
||||
|
||||
declare function providers(): Promise<Record<
|
||||
keyof DefaultProviders | string,
|
||||
AppProvider
|
||||
> | null>
|
||||
declare const getProviders: typeof providers
|
||||
declare function session(
|
||||
context?: NextContext & {
|
||||
triggerEvent?: boolean
|
||||
}
|
||||
): Promise<Session | null>
|
||||
declare const getSession: typeof session
|
||||
declare function csrfToken(context?: NextContext): Promise<string | null>
|
||||
declare const getCsrfToken: typeof csrfToken
|
||||
declare function signin(
|
||||
provider: "credentials" | "email",
|
||||
data?: Record<string, unknown> & {
|
||||
callbackUrl?: string
|
||||
redirect?: false
|
||||
},
|
||||
authorizationParams?:
|
||||
| string
|
||||
| string[][]
|
||||
| Record<string, unknown>
|
||||
| URLSearchParams
|
||||
): Promise<SignInResponse>
|
||||
declare function signin(
|
||||
provider?: string,
|
||||
data?: Record<string, unknown> & {
|
||||
callbackUrl?: string
|
||||
redirect?: boolean
|
||||
},
|
||||
authorizationParams?:
|
||||
| string
|
||||
| string[][]
|
||||
| Record<string, unknown>
|
||||
| URLSearchParams
|
||||
): Promise<void>
|
||||
declare const signIn: typeof signin
|
||||
declare function signout(data?: {
|
||||
callbackUrl?: string
|
||||
redirect?: boolean
|
||||
}): Promise<void>
|
||||
declare const signOut: typeof signout
|
||||
declare function options(options: SetOptionsParams): void
|
||||
declare const setOptions: typeof options
|
||||
declare const Provider: ContextProvider
|
||||
|
||||
export {
|
||||
useSession,
|
||||
session,
|
||||
getSession,
|
||||
providers,
|
||||
getProviders,
|
||||
csrfToken,
|
||||
getCsrfToken,
|
||||
signin,
|
||||
signIn,
|
||||
signout,
|
||||
signOut,
|
||||
options,
|
||||
setOptions,
|
||||
Provider,
|
||||
}
|
||||
169
types/index.d.ts
vendored
Normal file
169
types/index.d.ts
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
// Minimum TypeScript Version: 3.5
|
||||
|
||||
/// <reference types="node" />
|
||||
|
||||
import { ConnectionOptions } from "typeorm"
|
||||
import { Adapter } from "./adapters"
|
||||
import { JWTEncodeParams, JWTDecodeParams, JWTOptions, JWT } from "./jwt"
|
||||
import { AppProvider, Providers } from "./providers"
|
||||
import { NextApiRequest, NextApiResponse, NextApiHandler } from "./_next"
|
||||
import { NonNullParams, WithAdditionalParams } from "./_utils"
|
||||
|
||||
export interface NextAuthOptions {
|
||||
providers: Providers
|
||||
database?: string | Record<string, any> | ConnectionOptions
|
||||
secret?: string
|
||||
session?: SessionOptions
|
||||
jwt?: JWTOptions
|
||||
pages?: PagesOptions
|
||||
callbacks?: CallbacksOptions
|
||||
debug?: boolean
|
||||
adapter?: Adapter
|
||||
events?: EventsOptions
|
||||
useSecureCookies?: boolean
|
||||
cookies?: CookiesOptions
|
||||
logger?: LoggerInstance
|
||||
theme?: "light" | "dark" | "auto"
|
||||
}
|
||||
|
||||
export interface LoggerInstance {
|
||||
warn: (code?: string, ...message: unknown[]) => void
|
||||
error: (code?: string, ...message: unknown[]) => void
|
||||
debug: (code?: string, ...message: unknown[]) => void
|
||||
}
|
||||
|
||||
interface InternalOptions
|
||||
extends Omit<
|
||||
NextAuthOptions,
|
||||
"providers" | "database" | "session" | "useSecureCookie"
|
||||
> {
|
||||
pkce: {
|
||||
code_verifier?: string
|
||||
code_challenge_method?: "S256"
|
||||
}
|
||||
provider?: string
|
||||
baseUrl?: string
|
||||
basePath?: string
|
||||
action?:
|
||||
| "providers"
|
||||
| "session"
|
||||
| "csrf"
|
||||
| "signin"
|
||||
| "signout"
|
||||
| "callback"
|
||||
| "verify-request"
|
||||
| "error"
|
||||
csrfToken?: string
|
||||
}
|
||||
|
||||
export interface AppOptions
|
||||
extends Omit<NextApiRequest, "cookies">,
|
||||
NonNullParams<InternalOptions> {
|
||||
providers: AppProvider[]
|
||||
}
|
||||
|
||||
export interface CallbacksOptions {
|
||||
signIn?:
|
||||
| (() => true)
|
||||
| ((
|
||||
user: User,
|
||||
account: Record<string, unknown>,
|
||||
profile: Record<string, unknown>
|
||||
) => Promise<never | string | boolean>)
|
||||
redirect?: (url: string, baseUrl: string) => Promise<string>
|
||||
session?:
|
||||
| ((session: Session) => WithAdditionalParams<Session>)
|
||||
| ((
|
||||
session: Session,
|
||||
userOrToken: User | JWT
|
||||
) => Promise<WithAdditionalParams<Session>>)
|
||||
jwt?:
|
||||
| ((token: JWT) => WithAdditionalParams<JWT>)
|
||||
| ((
|
||||
token: JWT,
|
||||
user: User,
|
||||
account: Record<string, unknown>,
|
||||
profile: Record<string, unknown>,
|
||||
isNewUser: boolean
|
||||
) => Promise<WithAdditionalParams<JWT>>)
|
||||
}
|
||||
|
||||
export interface CookieOption {
|
||||
name: string
|
||||
options: {
|
||||
httpOnly: boolean
|
||||
sameSite: true | "strict" | "lax" | "none"
|
||||
path?: string
|
||||
secure: boolean
|
||||
maxAge?: number
|
||||
domain?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface CookiesOptions {
|
||||
sessionToken?: CookieOption
|
||||
callbackUrl?: CookieOption
|
||||
csrfToken?: CookieOption
|
||||
pkceCodeVerifier?: CookieOption
|
||||
}
|
||||
|
||||
export type EventType =
|
||||
| "signIn"
|
||||
| "signOut"
|
||||
| "createUser"
|
||||
| "updateUser"
|
||||
| "linkAccount"
|
||||
| "session"
|
||||
| "error"
|
||||
|
||||
export type EventCallback = (message: any) => Promise<void>
|
||||
|
||||
export type EventsOptions = Partial<Record<EventType, EventCallback>>
|
||||
|
||||
export interface PagesOptions {
|
||||
signIn?: string
|
||||
signOut?: string
|
||||
error?: string
|
||||
verifyRequest?: string
|
||||
newUser?: string | null
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
user: WithAdditionalParams<User>
|
||||
accessToken?: string
|
||||
expires: string
|
||||
}
|
||||
|
||||
export interface SessionOptions {
|
||||
jwt?: boolean
|
||||
maxAge?: number
|
||||
updateAge?: number
|
||||
}
|
||||
|
||||
export interface User {
|
||||
name?: string | null
|
||||
email?: string | null
|
||||
image?: string | null
|
||||
}
|
||||
|
||||
export interface NextAuthRequest extends NextApiRequest {
|
||||
options: InternalOptions
|
||||
}
|
||||
export type NextAuthResponse = NextApiResponse
|
||||
|
||||
declare function NextAuthHandler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
options?: NextAuthOptions
|
||||
): ReturnType<NextApiHandler>
|
||||
declare function NextAuth(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
options?: NextAuthOptions
|
||||
): ReturnType<NextApiHandler>
|
||||
declare function NextAuth(
|
||||
options: NextAuthOptions
|
||||
): ReturnType<typeof NextAuthHandler>
|
||||
|
||||
export { NextAuthHandler, NextAuth }
|
||||
export default NextAuth
|
||||
67
types/jwt.d.ts
vendored
Normal file
67
types/jwt.d.ts
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import { JWT, JWE } from "jose"
|
||||
import { NextApiRequest } from "./_next"
|
||||
import { WithAdditionalParams } from "./_utils"
|
||||
|
||||
export interface JWT extends Record<string, unknown> {
|
||||
name?: string | null
|
||||
email?: string | null
|
||||
picture?: string | null
|
||||
}
|
||||
|
||||
export interface JWTEncodeParams {
|
||||
token?: WithAdditionalParams<JWT>
|
||||
maxAge?: number
|
||||
secret: string | Buffer
|
||||
signingKey?: string
|
||||
signingOptions?: JWT.SignOptions
|
||||
encryptionKey?: string
|
||||
encryptionOptions?: object
|
||||
encryption?: boolean
|
||||
}
|
||||
|
||||
export interface JWTDecodeParams {
|
||||
token?: string
|
||||
maxAge?: number
|
||||
secret: string | Buffer
|
||||
signingKey?: string
|
||||
verificationKey?: string
|
||||
verificationOptions?: JWT.VerifyOptions<false>
|
||||
encryptionKey?: string
|
||||
decryptionKey?: string
|
||||
decryptionOptions?: JWE.DecryptOptions<false>
|
||||
encryption?: boolean
|
||||
}
|
||||
|
||||
export interface JWTOptions {
|
||||
secret?: string
|
||||
maxAge?: number
|
||||
encryption?: boolean
|
||||
signingKey?: string
|
||||
encryptionKey?: string
|
||||
encode?: (options: JWTEncodeParams) => Promise<string>
|
||||
decode?: (options: JWTDecodeParams) => Promise<WithAdditionalParams<JWT>>
|
||||
}
|
||||
|
||||
declare function encode(args?: JWTEncodeParams): Promise<string>
|
||||
|
||||
declare function decode(
|
||||
args?: JWTDecodeParams & { token: string }
|
||||
): Promise<WithAdditionalParams<JWT>>
|
||||
|
||||
declare function getToken(
|
||||
args?: {
|
||||
req: NextApiRequest
|
||||
secureCookie?: boolean
|
||||
cookieName?: string
|
||||
raw?: string
|
||||
} & JWTDecodeParams
|
||||
): Promise<WithAdditionalParams<JWT>>
|
||||
|
||||
declare function getToken(args?: {
|
||||
req: NextApiRequest
|
||||
secureCookie?: boolean
|
||||
cookieName?: string
|
||||
raw: true
|
||||
}): Promise<string>
|
||||
|
||||
export { encode, decode, getToken }
|
||||
435
types/providers.d.ts
vendored
Normal file
435
types/providers.d.ts
vendored
Normal file
@@ -0,0 +1,435 @@
|
||||
import { User } from "."
|
||||
import { JWT } from "./jwt"
|
||||
import { NonNullParams, NullableParams, WithAdditionalParams } from "./_utils"
|
||||
|
||||
export interface Provider<
|
||||
T extends string | undefined = undefined,
|
||||
U = T extends string ? "oauth" : string
|
||||
> {
|
||||
id: T
|
||||
name: string
|
||||
type: U extends string ? U : "oauth" | "email" | "credentials"
|
||||
version: string
|
||||
scope: string
|
||||
params: { grant_type: string }
|
||||
accessTokenUrl: string
|
||||
requestTokenUrl: string
|
||||
authorizationUrl: string
|
||||
profileUrl: string
|
||||
profile: (
|
||||
profile: Record<string, any>,
|
||||
tokens: any
|
||||
) => (User & { id: string }) | Promise<User & { id: string }>
|
||||
clientId: string
|
||||
clientSecret: string | Record<string, unknown>
|
||||
idToken?: boolean
|
||||
}
|
||||
|
||||
export interface AppProvider extends Pick<Provider, "id" | "name" | "type"> {
|
||||
signinUrl: string
|
||||
callbackUrl: string
|
||||
}
|
||||
|
||||
export interface DefaultProviders {
|
||||
Apple: Apple
|
||||
Attlassian: Atlassian
|
||||
Auth0: Auth0
|
||||
AzureADB2C: AzureADB2C
|
||||
Basecamp: Basecamp
|
||||
BattleNet: BattleNet
|
||||
Box: Box
|
||||
Bungie: Bungie
|
||||
Cognito: Cognito
|
||||
Credentials: Credentials
|
||||
Discord: Discord
|
||||
Email: Email
|
||||
EVEOnline: EVEOnline
|
||||
Facebook: Facebook
|
||||
FACEIT: FACEIT
|
||||
Foursquare: Foursquare
|
||||
FusionAuth: FusionAuth
|
||||
GitHub: GitHub
|
||||
GitLab: GitLab
|
||||
Google: Google
|
||||
IdentityServer4: IdentityServer4
|
||||
Instagram: Instagram
|
||||
Kakao: Kakao
|
||||
LINE: LINE
|
||||
LinkedIn: LinkedIn
|
||||
MailRu: MailRu
|
||||
Medium: Medium
|
||||
Netlify: Netlify
|
||||
Okta: Okta
|
||||
Osso: Osso
|
||||
Reddit: Reddit
|
||||
Salesforce: Salesforce
|
||||
Slack: Slack
|
||||
Spotify: Spotify
|
||||
Strava: Strava
|
||||
Twitch: Twitch
|
||||
Twitter: Twitter
|
||||
VK: VK
|
||||
Yandex: Yandex
|
||||
Zoho: Zoho
|
||||
}
|
||||
|
||||
export type Providers = Array<
|
||||
Provider | ReturnType<DefaultProviders[keyof DefaultProviders]>
|
||||
>
|
||||
|
||||
declare const Providers: DefaultProviders
|
||||
|
||||
export default Providers
|
||||
|
||||
/**
|
||||
* Email
|
||||
*/
|
||||
type Email = (
|
||||
options: ProviderEmailOptions
|
||||
) => NonNullParams<ProviderEmailOptions> & { id: "email"; type: "email" }
|
||||
|
||||
interface VerificationRequestParams extends Provider {
|
||||
identifier: string
|
||||
url: string
|
||||
baseUrl: string
|
||||
token: string
|
||||
provider: ProviderEmailOptions
|
||||
}
|
||||
|
||||
interface ProviderEmailOptions {
|
||||
name?: string
|
||||
server?: string | ProviderEmailServer
|
||||
from?: string
|
||||
maxAge?: number
|
||||
sendVerificationRequest?: (
|
||||
options: VerificationRequestParams
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
interface ProviderEmailServer {
|
||||
host: string
|
||||
port: number
|
||||
auth: {
|
||||
user: string
|
||||
pass: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Credentials
|
||||
*/
|
||||
type Credentials = (
|
||||
options: ProviderCredentialsOptions
|
||||
) => NonNullParams<ProviderCredentialsOptions> & {
|
||||
id: "credentials"
|
||||
type: "credentials"
|
||||
}
|
||||
|
||||
interface ProviderCredentialsOptions {
|
||||
id?: string
|
||||
name: string
|
||||
credentials: CredentialInput
|
||||
authorize: (credentials: Record<string, string>) => Promise<User | null>
|
||||
}
|
||||
|
||||
interface CredentialInput {
|
||||
[key: string]: {
|
||||
label?: string
|
||||
type?: string
|
||||
value?: string
|
||||
placeholder?: string
|
||||
}
|
||||
}
|
||||
|
||||
type OptionsBase = {
|
||||
[K in keyof Omit<Provider, "id">]?: Provider[K]
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider options
|
||||
* @link https://next-auth.js.org/configuration/providers#oauth-provider-options
|
||||
*/
|
||||
interface ProviderCommonOptions extends OptionsBase {
|
||||
authorizationParams?: Record<string, string>
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
headers?: Record<string, any>
|
||||
idToken?: boolean
|
||||
name?: string
|
||||
protection?: "pkce" | "state" | "both" | "none"
|
||||
state?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Apple
|
||||
*/
|
||||
type Apple = (
|
||||
options: ProviderAppleOptions
|
||||
) => Provider<"apple"> & { protection: "none" }
|
||||
|
||||
interface ProviderAppleOptions
|
||||
extends Omit<ProviderCommonOptions, "clientSecret"> {
|
||||
name?: string
|
||||
clientId: string
|
||||
clientSecret: Record<"appleId" | "teamId" | "privateKey" | "keyId", string>
|
||||
}
|
||||
|
||||
interface ProviderAppleSecret {
|
||||
appleId: string
|
||||
teamId: string
|
||||
privateKey: string
|
||||
keyId: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter
|
||||
*/
|
||||
type Twitter = (options: ProviderCommonOptions) => Provider<"twitter">
|
||||
|
||||
/**
|
||||
* Facebook
|
||||
*/
|
||||
type Facebook = (options: ProviderCommonOptions) => Provider<"facebook">
|
||||
|
||||
/**
|
||||
* GitHub
|
||||
*/
|
||||
type GitHub = (options: ProviderGitHubOptions) => Provider<"github">
|
||||
|
||||
interface ProviderGitHubOptions extends Omit<ProviderCommonOptions, "scope"> {
|
||||
scope?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* GitLab
|
||||
*/
|
||||
type GitLab = (options: ProviderCommonOptions) => Provider<"gitlab">
|
||||
|
||||
/**
|
||||
* Slack
|
||||
*/
|
||||
type Slack = (options: ProviderCommonOptions) => Provider<"slack">
|
||||
|
||||
/**
|
||||
* Google
|
||||
*/
|
||||
type Google = (options: ProviderGoogleOptions) => Provider<"google">
|
||||
|
||||
interface ProviderGoogleOptions extends ProviderCommonOptions {
|
||||
authorizationUrl?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth0
|
||||
*/
|
||||
type Auth0 = (
|
||||
options: ProviderAuth0Options
|
||||
) => Provider<"auth0"> & { domain: string }
|
||||
|
||||
interface ProviderAuth0Options extends Omit<ProviderCommonOptions, "profile"> {
|
||||
domain: string
|
||||
profile?: (profile: Auth0Profile) => User & { id: string }
|
||||
}
|
||||
|
||||
interface Auth0Profile {
|
||||
sub: string
|
||||
nickname: string
|
||||
email: string
|
||||
picture: string
|
||||
}
|
||||
|
||||
/**
|
||||
* IS4
|
||||
*/
|
||||
|
||||
type IdentityServer4 = (
|
||||
options: ProviderIS4Options
|
||||
) => Provider<"identity-server4" | string> & { domain: string }
|
||||
|
||||
interface ProviderIS4Options extends Omit<ProviderCommonOptions, "id"> {
|
||||
id: string
|
||||
scope: string
|
||||
domain: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Discord
|
||||
*/
|
||||
type Discord = (options: ProviderCommonOptions) => Provider<"discord">
|
||||
|
||||
/**
|
||||
* Twitch
|
||||
*/
|
||||
type Twitch = (options: ProviderCommonOptions) => Provider<"twitch">
|
||||
|
||||
/**
|
||||
* Okta
|
||||
*/
|
||||
type Okta = (
|
||||
options: ProviderOktaOptions
|
||||
) => Provider<"okta"> & { domain: string }
|
||||
|
||||
interface ProviderOktaOptions extends ProviderCommonOptions {
|
||||
domain: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Battle.net
|
||||
*/
|
||||
type BattleNet = (
|
||||
options: ProviderBattleNetOptions
|
||||
) => Provider<"battlenet"> & { region: string }
|
||||
|
||||
interface ProviderBattleNetOptions extends ProviderCommonOptions {
|
||||
region: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Box
|
||||
*/
|
||||
type Box = (options: ProviderCommonOptions) => Provider<"box">
|
||||
|
||||
/**
|
||||
* Cognito
|
||||
*/
|
||||
type Cognito = (
|
||||
options: ProviderCognitoOptions
|
||||
) => Provider<"cognito"> & { domain: string }
|
||||
|
||||
interface ProviderCognitoOptions extends ProviderCommonOptions {
|
||||
domain: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Yandex
|
||||
*/
|
||||
type Yandex = (options: ProviderCommonOptions) => Provider<"yandex">
|
||||
|
||||
/**
|
||||
* LinkedIn
|
||||
*/
|
||||
type LinkedIn = (options: ProviderLinkedInOptions) => Provider<"linkedin">
|
||||
|
||||
interface ProviderLinkedInOptions extends ProviderCommonOptions {
|
||||
scope?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Spotify
|
||||
*/
|
||||
type Spotify = (options: ProviderSpotifyOptions) => Provider<"spotify">
|
||||
|
||||
interface ProviderSpotifyOptions extends ProviderCommonOptions {
|
||||
scope?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Basecamp
|
||||
*/
|
||||
type Basecamp = (options: ProviderCommonOptions) => Provider<"basecamp">
|
||||
|
||||
/**
|
||||
* Reddit
|
||||
*/
|
||||
type Reddit = (options: ProviderCommonOptions) => Provider<"reddit">
|
||||
|
||||
/**
|
||||
* Atlassian
|
||||
*/
|
||||
type Atlassian = (options: ProviderCommonOptions) => Provider<"atlassian">
|
||||
|
||||
/**
|
||||
* AzureADB2C
|
||||
*/
|
||||
type AzureADB2C = (
|
||||
options: ProviderAzureADB2COptions
|
||||
) => Provider<"azure-ad-b2c">
|
||||
|
||||
interface ProviderAzureADB2COptions extends ProviderCommonOptions {
|
||||
tenantId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Bungie
|
||||
*/
|
||||
type Bungie = (options: ProviderCommonOptions) => Provider<"bungie">
|
||||
|
||||
/**
|
||||
* EVEOnline
|
||||
*/
|
||||
type EVEOnline = (options: ProviderCommonOptions) => Provider<"eveonline">
|
||||
|
||||
/**
|
||||
* FACEIT
|
||||
*/
|
||||
type FACEIT = (options: ProviderCommonOptions) => Provider<"faceit">
|
||||
|
||||
/**
|
||||
* Foursquare
|
||||
*/
|
||||
type Foursquare = (options: ProviderCommonOptions) => Provider<"foursquare">
|
||||
|
||||
/**
|
||||
* FusionAuth
|
||||
*/
|
||||
type FusionAuth = (options: ProviderFusionAuthOptions) => Provider<"fusionauth">
|
||||
|
||||
interface ProviderFusionAuthOptions extends ProviderCommonOptions {
|
||||
tenantId?: string
|
||||
domain?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Instagram
|
||||
*/
|
||||
type Instagram = (options: ProviderCommonOptions) => Provider<"instagram">
|
||||
|
||||
/**
|
||||
* Kakao
|
||||
*/
|
||||
type Kakao = (options: ProviderCommonOptions) => Provider<"kakao">
|
||||
|
||||
/**
|
||||
* LINE
|
||||
*/
|
||||
type LINE = (options: ProviderCommonOptions) => Provider<"line">
|
||||
|
||||
/**
|
||||
* MailRu
|
||||
*/
|
||||
type MailRu = (options: ProviderCommonOptions) => Provider<"mailru">
|
||||
|
||||
/**
|
||||
* Medium
|
||||
*/
|
||||
type Medium = (options: ProviderCommonOptions) => Provider<"medium">
|
||||
|
||||
/**
|
||||
* Netlify
|
||||
*/
|
||||
type Netlify = (options: ProviderCommonOptions) => Provider<"netlify">
|
||||
|
||||
/**
|
||||
* Osso
|
||||
*/
|
||||
type Osso = (options: ProviderCommonOptions) => Provider<"osso">
|
||||
|
||||
/**
|
||||
* Salesforce
|
||||
*/
|
||||
type Salesforce = (options: ProviderCommonOptions) => Provider<"salesforce">
|
||||
|
||||
/**
|
||||
* Strava
|
||||
*/
|
||||
type Strava = (options: ProviderCommonOptions) => Provider<"strava">
|
||||
|
||||
/**
|
||||
* VK
|
||||
*/
|
||||
type VK = (options: ProviderCommonOptions) => Provider<"vk">
|
||||
|
||||
/**
|
||||
* Zoho
|
||||
*/
|
||||
type Zoho = (options: ProviderCommonOptions) => Provider<"zoho">
|
||||
26
types/tests/adapters.test.ts
Normal file
26
types/tests/adapters.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import Adapters, { TypeORMAdapter } from "next-auth/adapters"
|
||||
|
||||
// ExpectType TypeORMAdapter["Adapter"]
|
||||
Adapters.Default({
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
synchronize: true,
|
||||
})
|
||||
|
||||
// ExpectType TypeORMAdapter
|
||||
Adapters.TypeORM.Adapter({
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
synchronize: true,
|
||||
})
|
||||
|
||||
// ExpectType PrismaAdapter
|
||||
Adapters.Prisma.Adapter({
|
||||
prisma: {},
|
||||
modelMapping: {
|
||||
User: "foo",
|
||||
Account: "bar",
|
||||
Session: "session",
|
||||
VerificationRequest: "foo",
|
||||
},
|
||||
})
|
||||
83
types/tests/client.test.ts
Normal file
83
types/tests/client.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import * as client from "next-auth/client"
|
||||
import { nextReq } from "./test-helpers"
|
||||
|
||||
const clientSession = {
|
||||
user: {
|
||||
name: "Bruce",
|
||||
email: "bruce@lee.com",
|
||||
image: "path/to/img",
|
||||
},
|
||||
accessToken: "123z",
|
||||
expires: "1234",
|
||||
}
|
||||
|
||||
// $ExpectType [Session | null | undefined, boolean]
|
||||
client.useSession()
|
||||
|
||||
// $ExpectType Promise<Session | null>
|
||||
client.getSession({ req: nextReq })
|
||||
|
||||
// $ExpectType Promise<Session | null>
|
||||
client.session({ req: nextReq })
|
||||
|
||||
// $ExpectType Promise<Record<string, AppProvider> | null>
|
||||
client.getProviders()
|
||||
|
||||
// $ExpectType Promise<Record<string, AppProvider> | null>
|
||||
client.providers()
|
||||
|
||||
// $ExpectType Promise<string | null>
|
||||
client.getCsrfToken({ req: nextReq })
|
||||
|
||||
// $ExpectType Promise<string | null>
|
||||
client.csrfToken({ req: nextReq })
|
||||
|
||||
// $ExpectType Promise<void>
|
||||
client.signin("github", { data: "foo", redirect: false }, { login: "username" })
|
||||
|
||||
// $ExpectType Promise<SignInResponse>
|
||||
client.signin("credentials", { data: "foo", redirect: false })
|
||||
|
||||
// $ExpectType Promise<SignInResponse>
|
||||
client.signin("email", { data: "foo", redirect: false })
|
||||
|
||||
// $ExpectType Promise<void>
|
||||
client.signin("email", { data: "foo", redirect: true })
|
||||
|
||||
// $ExpectType Promise<void>
|
||||
client.signout()
|
||||
|
||||
// $ExpectType Promise<void>
|
||||
client.signout({ callbackUrl: "https://foo.com/callback", redirect: true })
|
||||
|
||||
// $ExpectType ReactElement<any, any> | null
|
||||
client.Provider({
|
||||
session: clientSession,
|
||||
options: {
|
||||
baseUrl: "https://foo.com",
|
||||
basePath: "/",
|
||||
clientMaxAge: 1234,
|
||||
},
|
||||
})
|
||||
|
||||
// $ExpectType ReactElement<any, any> | null
|
||||
client.Provider({
|
||||
session: clientSession,
|
||||
})
|
||||
|
||||
// $ExpectType ReactElement<any, any> | null
|
||||
client.Provider({
|
||||
session: undefined,
|
||||
options: {},
|
||||
})
|
||||
|
||||
// $ExpectType ReactElement<any, any> | null
|
||||
client.Provider({
|
||||
session: null,
|
||||
options: {
|
||||
baseUrl: "https://foo.com",
|
||||
basePath: "/",
|
||||
clientMaxAge: 1234,
|
||||
keepAlive: 4321,
|
||||
},
|
||||
})
|
||||
26
types/tests/jwt.test.ts
Normal file
26
types/tests/jwt.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as JWTType from "next-auth/jwt"
|
||||
import { nextReq } from "./test-helpers"
|
||||
|
||||
// $ExpectType Promise<string>
|
||||
JWTType.encode({
|
||||
token: { key: "value" },
|
||||
secret: "secret",
|
||||
})
|
||||
|
||||
// $ExpectType Promise<WithAdditionalParams<JWT>>
|
||||
JWTType.decode({
|
||||
token: "token",
|
||||
secret: "secret",
|
||||
})
|
||||
|
||||
// $ExpectType Promise<string>
|
||||
JWTType.getToken({
|
||||
req: nextReq,
|
||||
raw: true,
|
||||
})
|
||||
|
||||
// $ExpectType Promise<WithAdditionalParams<JWT>>
|
||||
JWTType.getToken({
|
||||
req: nextReq,
|
||||
secret: "secret",
|
||||
})
|
||||
259
types/tests/providers.test.ts
Normal file
259
types/tests/providers.test.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
import Providers from "next-auth/providers"
|
||||
|
||||
// $ExpectType NonNullParams<ProviderEmailOptions> & { id: "email"; type: "email"; }
|
||||
Providers.Email({
|
||||
server: "path/to/server",
|
||||
from: "path/from",
|
||||
})
|
||||
|
||||
// $ExpectType NonNullParams<ProviderEmailOptions> & { id: "email"; type: "email"; }
|
||||
Providers.Email({
|
||||
server: {
|
||||
host: "host",
|
||||
port: 123,
|
||||
auth: {
|
||||
user: "foo",
|
||||
pass: "123",
|
||||
},
|
||||
},
|
||||
from: "path/from",
|
||||
})
|
||||
|
||||
// $ExpectType NonNullParams<ProviderCredentialsOptions> & { id: "credentials"; type: "credentials"; }
|
||||
Providers.Credentials({
|
||||
id: "login",
|
||||
name: "account",
|
||||
credentials: {
|
||||
user: {
|
||||
label: "Password",
|
||||
type: "password",
|
||||
},
|
||||
password: {
|
||||
label: "Password",
|
||||
type: "password",
|
||||
},
|
||||
},
|
||||
authorize: async (credentials) => {
|
||||
const user = {
|
||||
/* fetched user */
|
||||
}
|
||||
return user
|
||||
},
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"apple", "oauth"> & { protection: "none"; }
|
||||
Providers.Apple({
|
||||
clientId: "foo123",
|
||||
clientSecret: {
|
||||
appleId: "foo@icloud.com",
|
||||
teamId: "foo",
|
||||
privateKey: "123xyz",
|
||||
keyId: "1234",
|
||||
},
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"twitter", "oauth">
|
||||
Providers.Twitter({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"facebook", "oauth">
|
||||
Providers.Facebook({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"github", "oauth">
|
||||
Providers.GitHub({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"github", "oauth">
|
||||
Providers.GitHub({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
scope: "change:thing read:that",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"gitlab", "oauth">
|
||||
Providers.GitLab({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"slack", "oauth">
|
||||
Providers.Slack({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"google", "oauth">
|
||||
Providers.Google({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"google", "oauth">
|
||||
Providers.Google({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
authorizationUrl: "https://foo.google.com",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"auth0", "oauth"> & { domain: string; }
|
||||
Providers.Auth0({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
domain: "https://foo.auth0.com",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"auth0", "oauth"> & { domain: string; }
|
||||
Providers.Auth0({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
domain: "https://foo.auth0.com",
|
||||
profile: () => ({
|
||||
id: "foo123",
|
||||
name: "foo",
|
||||
email: "foo@bar.io",
|
||||
image: "https://foo.auth0.com/image/1.png",
|
||||
}),
|
||||
})
|
||||
|
||||
// $ExpectType Provider<string, "oauth"> & { domain: string; }
|
||||
Providers.IdentityServer4({
|
||||
id: "identity-server4",
|
||||
name: "IdentityServer4",
|
||||
scope: "change:thing read:that",
|
||||
domain: "https://foo.is4.com",
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"discord", "oauth">
|
||||
Providers.Discord({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
scope: "identify",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"twitch", "oauth">
|
||||
Providers.Twitch({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"okta", "oauth"> & { domain: string; }
|
||||
Providers.Okta({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
domain: "https://foo.auth0.com",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"battlenet", "oauth"> & { region: string; }
|
||||
Providers.BattleNet({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
region: "europe",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"box", "oauth">
|
||||
Providers.Box({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"cognito", "oauth"> & { domain: string; }
|
||||
Providers.Cognito({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
domain: "https://foo.auth0.com",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"yandex", "oauth">
|
||||
Providers.Yandex({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"linkedin", "oauth">
|
||||
Providers.LinkedIn({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
scope: "r_emailaddress r_liteprofile",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"spotify", "oauth">
|
||||
Providers.Spotify({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"spotify", "oauth">
|
||||
Providers.Spotify({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
scope: "user-read-email",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"basecamp", "oauth">
|
||||
Providers.Basecamp({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"reddit", "oauth">
|
||||
Providers.Reddit({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"azure-ad-b2c", "oauth">
|
||||
Providers.AzureADB2C({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
scope: "offline_access User.Read",
|
||||
tenantId: "tenantId",
|
||||
idToken: true,
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"fusionauth", "oauth">
|
||||
Providers.FusionAuth({
|
||||
name: "FusionAuth",
|
||||
domain: "domain",
|
||||
clientId: "clientId",
|
||||
clientSecret: "clientSecret",
|
||||
tenantId: "tenantId",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"faceit", "oauth">
|
||||
Providers.FACEIT({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"instagram", "oauth">
|
||||
Providers.Instagram({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"kakao", "oauth">
|
||||
Providers.Kakao({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"osso", "oauth">
|
||||
Providers.Osso({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType Provider<"zoho", "oauth">
|
||||
Providers.Zoho({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
255
types/tests/server.test.ts
Normal file
255
types/tests/server.test.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import Providers, { AppProvider, Provider } from "next-auth/providers"
|
||||
import Adapters, {
|
||||
Adapter,
|
||||
EmailAppProvider,
|
||||
Profile,
|
||||
Session,
|
||||
VerificationRequest,
|
||||
} 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 "next"
|
||||
|
||||
const req: NextApiRequest = Object.assign(new IncomingMessage(new Socket()), {
|
||||
query: {},
|
||||
cookies: {},
|
||||
body: {},
|
||||
env: {},
|
||||
})
|
||||
|
||||
const res: NextApiResponse = Object.assign(new ServerResponse(req), {
|
||||
send: (body: string) => undefined,
|
||||
json: (body: string) => undefined,
|
||||
status: (code: number) => res,
|
||||
redirect: (statusOrUrl: number | string, url?: string) => res as any,
|
||||
setPreviewData: (data: object | string) => res,
|
||||
clearPreviewData: () => res,
|
||||
})
|
||||
|
||||
const pageOptions = {
|
||||
signin: "path/to/signin",
|
||||
signout: "path/to/signout",
|
||||
error: "path/to/error",
|
||||
verifyRequest: "path/to/verify",
|
||||
newUsers: "path/to/signup",
|
||||
}
|
||||
|
||||
const simpleConfig = {
|
||||
site: "https://foo.com",
|
||||
providers: [
|
||||
Providers.GitHub({
|
||||
clientId: "123",
|
||||
clientSecret: "123",
|
||||
scope:
|
||||
"user public_repo repo repo_deployment repo:status read:repo_hook read:org read:public_key read:gpg_key",
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
const exampleUser: NextAuthTypes.User = {
|
||||
name: "",
|
||||
image: "",
|
||||
email: "",
|
||||
}
|
||||
|
||||
const exampleSession: Session = {
|
||||
userId: "",
|
||||
accessToken: "",
|
||||
sessionToken: "",
|
||||
expires: new Date(),
|
||||
}
|
||||
|
||||
const exampleVerificatoinRequest: VerificationRequest = {
|
||||
identifier: "",
|
||||
token: "",
|
||||
expires: new Date(),
|
||||
}
|
||||
|
||||
const adapter: Adapter<
|
||||
NextAuthTypes.User,
|
||||
Profile,
|
||||
Session,
|
||||
VerificationRequest
|
||||
> = {
|
||||
async getAdapter(appOptions: NextAuthTypes.AppOptions) {
|
||||
return {
|
||||
createUser: async (profile: Profile) => exampleUser,
|
||||
getUser: async (id: string) => exampleUser,
|
||||
getUserByEmail: async (email: string) => exampleUser,
|
||||
getUserByProviderAccountId: async (
|
||||
providerId: string,
|
||||
providerAccountId: string
|
||||
) => exampleUser,
|
||||
updateUser: async (user: NextAuthTypes.User) => exampleUser,
|
||||
linkAccount: async (
|
||||
userId: string,
|
||||
providerId: string,
|
||||
providerType: string,
|
||||
providerAccountId: string,
|
||||
refreshToken: string,
|
||||
accessToken: string,
|
||||
accessTokenExpires: number
|
||||
) => undefined,
|
||||
createSession: async (user: NextAuthTypes.User) => exampleSession,
|
||||
getSession: async (sessionToken: string) => exampleSession,
|
||||
updateSession: async (session: Session, force?: boolean) =>
|
||||
exampleSession,
|
||||
deleteSession: async (sessionToken: string) => undefined,
|
||||
createVerificationRequest: async (
|
||||
email: string,
|
||||
url: string,
|
||||
token: string,
|
||||
secret: string,
|
||||
provider: EmailAppProvider,
|
||||
options: NextAuthTypes.AppOptions
|
||||
) => exampleVerificatoinRequest,
|
||||
getVerificationRequest: async (
|
||||
email: string,
|
||||
verificationToken: string,
|
||||
secret: string,
|
||||
provider: AppProvider
|
||||
) => exampleVerificatoinRequest,
|
||||
deleteVerificationRequest: async (
|
||||
email: string,
|
||||
verificationToken: string,
|
||||
secret: string,
|
||||
provider: AppProvider
|
||||
) => undefined,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const allConfig = {
|
||||
providers: [
|
||||
Providers.Twitter({
|
||||
clientId: "123",
|
||||
clientSecret: "123",
|
||||
}),
|
||||
],
|
||||
database: "path/to/db",
|
||||
debug: true,
|
||||
secret: "my secret",
|
||||
session: {
|
||||
jwt: true,
|
||||
maxAge: 365,
|
||||
updateAge: 60,
|
||||
},
|
||||
jwt: {
|
||||
secret: "secret-thing",
|
||||
maxAge: 365,
|
||||
encryption: true,
|
||||
signingKey: "some-key",
|
||||
encryptionKey: "some-key",
|
||||
encode: async () => "foo",
|
||||
decode: async () => ({}),
|
||||
},
|
||||
pages: pageOptions,
|
||||
callbacks: {
|
||||
async signIn(
|
||||
user: NextAuthTypes.User,
|
||||
account: Record<string, unknown>,
|
||||
profile: Record<string, unknown>
|
||||
) {
|
||||
return true
|
||||
},
|
||||
async redirect(url: string, baseUrl: string) {
|
||||
return "path/to/foo"
|
||||
},
|
||||
async session(
|
||||
session: NextAuthTypes.Session,
|
||||
userOrToken: NextAuthTypes.User
|
||||
) {
|
||||
return { ...session }
|
||||
},
|
||||
async jwt(
|
||||
token: JWTType.JWT,
|
||||
user: NextAuthTypes.User,
|
||||
account: Record<string, unknown>,
|
||||
profile: Record<string, unknown>,
|
||||
isNewUser: boolean
|
||||
) {
|
||||
return token
|
||||
},
|
||||
},
|
||||
events: {
|
||||
async signIn(message: string) {
|
||||
return undefined
|
||||
},
|
||||
async signOut(message: string) {
|
||||
return undefined
|
||||
},
|
||||
async createUser(message: string) {
|
||||
return undefined
|
||||
},
|
||||
async linkAccount(message: string) {
|
||||
return undefined
|
||||
},
|
||||
async session(message: string) {
|
||||
return undefined
|
||||
},
|
||||
async error(message: string) {
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
adapter,
|
||||
useSecureCookies: true,
|
||||
cookies: {
|
||||
sessionToken: {
|
||||
name: "__Secure-next-auth.session-token",
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: true as true,
|
||||
path: "/",
|
||||
secure: true,
|
||||
domain: "foo.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const customProvider: Provider<"google"> = {
|
||||
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",
|
||||
async profile(profile, tokens) {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.name,
|
||||
email: profile.email,
|
||||
image: profile.picture,
|
||||
}
|
||||
},
|
||||
clientId: "",
|
||||
clientSecret: "",
|
||||
}
|
||||
|
||||
const customProviderConfig = {
|
||||
site: "https://foo.com",
|
||||
providers: [customProvider],
|
||||
}
|
||||
|
||||
// $ExpectType void | Promise<void>
|
||||
NextAuth(simpleConfig)
|
||||
|
||||
// $ExpectType void | Promise<void>
|
||||
NextAuth(allConfig)
|
||||
|
||||
// $ExpectType void | Promise<void>
|
||||
NextAuth(customProviderConfig)
|
||||
|
||||
// $ExpectType void | Promise<void>
|
||||
NextAuth(req, res, simpleConfig)
|
||||
|
||||
// $ExpectType void | Promise<void>
|
||||
NextAuth(req, res, allConfig)
|
||||
13
types/tests/test-helpers.ts
Normal file
13
types/tests/test-helpers.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { IncomingMessage, ServerResponse } from "http"
|
||||
import { Socket } from "net"
|
||||
import { NextApiRequest } from "next"
|
||||
|
||||
export const nextReq: NextApiRequest = Object.assign(
|
||||
new IncomingMessage(new Socket()),
|
||||
{
|
||||
query: {},
|
||||
cookies: {},
|
||||
body: {},
|
||||
env: {},
|
||||
}
|
||||
)
|
||||
23
types/tsconfig.json
Normal file
23
types/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"lib": ["es6", "dom"],
|
||||
"jsx": "react",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"esModuleInterop": true,
|
||||
"noEmit": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"next-auth": ["."],
|
||||
"next-auth/providers": ["./providers"],
|
||||
"next-auth/adapters": ["./adapters"],
|
||||
"next-auth/client": ["./client"],
|
||||
"next-auth/jwt": ["./jwt"],
|
||||
"next": ["./_next"]
|
||||
}
|
||||
}
|
||||
}
|
||||
6
types/tslint.json
Normal file
6
types/tslint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "dtslint/dtslint.json",
|
||||
"rules": {
|
||||
"semicolon": false
|
||||
}
|
||||
}
|
||||
@@ -136,17 +136,44 @@ Install module:
|
||||
database: 'mariadb://username:password@127.0.0.1:3306/database_name'
|
||||
```
|
||||
|
||||
### Postgres
|
||||
### Postgres / CockroachDB
|
||||
|
||||
Install module:
|
||||
`npm i pg`
|
||||
|
||||
#### Example
|
||||
|
||||
PostgresDB
|
||||
```js
|
||||
database: 'postgres://username:password@127.0.0.1:5432/database_name'
|
||||
```
|
||||
|
||||
CockroachDB
|
||||
```js
|
||||
database: 'postgres://username:password@127.0.0.1:26257/database_name'
|
||||
```
|
||||
|
||||
If the node is using Self-signed cert
|
||||
|
||||
```js
|
||||
database: {
|
||||
type: "cockroachdb",
|
||||
host: process.env.DATABASE_HOST,
|
||||
port: 26257,
|
||||
username: process.env.DATABASE_USER,
|
||||
password: process.env.DATABASE_PASSWORD,
|
||||
database: process.env.DATABASE_NAME,
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
ca: fs.readFileSync('/path/to/server-certificates/root.crt').toString()
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
Read more: [https://node-postgres.com/features/ssl](https://node-postgres.com/features/ssl)
|
||||
|
||||
---
|
||||
|
||||
### Microsoft SQL Server
|
||||
|
||||
Install module:
|
||||
@@ -166,7 +193,7 @@ Install module:
|
||||
#### Example
|
||||
|
||||
```js
|
||||
database: 'mongodb://username:password@127.0.0.1:27017/database_name'
|
||||
database: 'mongodb://username:password@127.0.0.1:3306/database_name'
|
||||
```
|
||||
|
||||
### SQLite
|
||||
@@ -182,9 +209,6 @@ Install module:
|
||||
database: 'sqlite://localhost/:memory:'
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Other databases
|
||||
|
||||
See the [documentation for adapters](/schemas/adapters) for more information on advanced configuration, including how to use NextAuth.js with other databases using a [custom adapter](/tutorials/creating-a-database-adapter).
|
||||
|
||||
@@ -18,9 +18,17 @@ If your Next.js application uses a custom base path, specify the route to the AP
|
||||
_e.g. `NEXTAUTH_URL=https://example.com/custom-route/api/auth`_
|
||||
|
||||
:::tip
|
||||
To set environment variables on Vercel, you can use the [dashboard](https://vercel.com/dashboard) or the `now env` command.
|
||||
To set environment variables on Vercel, you can use the [dashboard](https://vercel.com/dashboard) or the `vercel env` command.
|
||||
:::
|
||||
|
||||
### NEXTAUTH_URL_INTERNAL
|
||||
|
||||
If provided, server-side calls will use this instead of `NEXTAUTH_URL`. Useful in environments when the server doesn't have access to the canonical URL of your site. Defaults to `NEXTAUTH_URL`.
|
||||
|
||||
```
|
||||
NEXTAUTH_URL_INTERNAL=http://10.240.8.16
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Options
|
||||
|
||||
@@ -42,11 +42,22 @@ export default function SignIn({ providers }) {
|
||||
)
|
||||
}
|
||||
|
||||
// This is the recommended way for Next.js 9.3 or newer
|
||||
export async function getServerSideProps(context){
|
||||
const providers = await providers()
|
||||
return {
|
||||
props: { providers }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// If older than Next.js 9.3
|
||||
SignIn.getInitialProps = async () => {
|
||||
return {
|
||||
providers: await providers()
|
||||
}
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
### Email Sign in
|
||||
@@ -54,7 +65,7 @@ SignIn.getInitialProps = async () => {
|
||||
If you create a custom sign in form for email sign in, you will need to submit both fields for the **email** address and **csrfToken** from **/api/auth/csrf** in a POST request to **/api/auth/signin/email**.
|
||||
|
||||
```jsx title="pages/auth/email-signin.js"
|
||||
import { csrfToken } from 'next-auth/client'
|
||||
import { getCsrfToken } from 'next-auth/client'
|
||||
|
||||
export default function SignIn({ csrfToken }) {
|
||||
return (
|
||||
@@ -62,18 +73,29 @@ export default function SignIn({ csrfToken }) {
|
||||
<input name='csrfToken' type='hidden' defaultValue={csrfToken}/>
|
||||
<label>
|
||||
Email address
|
||||
<input type='text' id='email' name='email'/>
|
||||
<input type='email' id='email' name='email'/>
|
||||
</label>
|
||||
<button type='submit'>Sign in with Email</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
SignIn.getInitialProps = async (context) => {
|
||||
// This is the recommended way for Next.js 9.3 or newer
|
||||
export async function getServerSideProps(context){
|
||||
const csrfToken = await getCsrfToken(context)
|
||||
return {
|
||||
csrfToken: await csrfToken(context)
|
||||
props: { csrfToken }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// If older than Next.js 9.3
|
||||
SignIn.getInitialProps = async (context) => {
|
||||
return {
|
||||
csrfToken: await getCsrfToken(context)
|
||||
}
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
You can also use the `signIn()` function which will handle obtaining the CSRF token for you:
|
||||
@@ -87,7 +109,7 @@ signIn('email', { email: 'jsmith@example.com' })
|
||||
If you create a sign in form for credentials based authentication, you will need to pass a **csrfToken** from **/api/auth/csrf** in a POST request to **/api/auth/callback/credentials**.
|
||||
|
||||
```jsx title="pages/auth/credentials-signin.js"
|
||||
import { csrfToken } from 'next-auth/client'
|
||||
import { getCsrfToken } from 'next-auth/client'
|
||||
|
||||
export default function SignIn({ csrfToken }) {
|
||||
return (
|
||||
@@ -99,18 +121,30 @@ export default function SignIn({ csrfToken }) {
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input name='password' type='text'/>
|
||||
<input name='password' type='password'/>
|
||||
</label>
|
||||
<button type='submit'>Sign in</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
SignIn.getInitialProps = async (context) => {
|
||||
// This is the recommended way for Next.js 9.3 or newer
|
||||
export async function getServerSideProps(context) {
|
||||
return {
|
||||
csrfToken: await csrfToken(context)
|
||||
props: {
|
||||
csrfToken: await getCsrfToken(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// If older than Next.js 9.3
|
||||
SignIn.getInitialProps = async (context) => {
|
||||
return {
|
||||
csrfToken: await getCsrfToken(context)
|
||||
}
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
You can also use the `signIn()` function which will handle obtaining the CSRF token for you:
|
||||
|
||||
@@ -56,15 +56,11 @@ NextAuth.js is designed to work with any OAuth service, it supports OAuth 1.0, 1
|
||||
|
||||
<Image src="/img/signin.png" alt="Signin Screenshot" />
|
||||
|
||||
:::tip
|
||||
If you want to create a custom sign in link you can link to **/api/auth/signin/[provider]** which will sign in the user in directly with that provider.
|
||||
:::
|
||||
|
||||
### Using a custom provider
|
||||
|
||||
You can use an OAuth provider that isn't built-in by using a custom object.
|
||||
|
||||
As an example of what this looks like, this is the the provider object returned for the Google provider:
|
||||
As an example of what this looks like, this is the provider object returned for the Google provider:
|
||||
|
||||
```js
|
||||
{
|
||||
|
||||
@@ -76,13 +76,31 @@ 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
|
||||
|
||||
#### JWT_SESSION_ERROR
|
||||
|
||||
https://next-auth.js.org/errors#jwt_session_error JWKKeySupport: the key does not support HS512 verify algorithm
|
||||
|
||||
The algorithm used for generating your key isn't listed as supported. You can generate a HS512 key using
|
||||
|
||||
````
|
||||
jose newkey -s 512 -t oct -a HS512
|
||||
````
|
||||
|
||||
If you are unable to use an HS512 key (for example to interoperate with other services) you can define what is supported using
|
||||
|
||||
````
|
||||
jwt: {
|
||||
signingKey: {"kty":"oct","kid":"--","alg":"HS256","k":"--"}
|
||||
verificationOptions: {
|
||||
algorithms: ["HS256"]
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
#### SESSION_ERROR
|
||||
|
||||
---
|
||||
@@ -139,4 +157,4 @@ Check your mail server configuration.
|
||||
|
||||
This error happens when `[...nextauth].js` file is not found inside `pages/api/auth`.
|
||||
|
||||
Make sure the file is there and the filename is written correctly.
|
||||
Make sure the file is there and the filename is written correctly.
|
||||
|
||||
@@ -356,7 +356,7 @@ export default function App ({ Component, pageProps }) {
|
||||
:::note
|
||||
**These options have no effect on clients that are not signed in.**
|
||||
|
||||
Every tab/window maintains it's own copy of the local session state; the session it is not stored in shared storage like localStorage or sessionStorage. Any update in one tab/window triggers a message to other tabs/windows to update their own session state.
|
||||
Every tab/window maintains its own copy of the local session state; the session is not stored in shared storage like localStorage or sessionStorage. Any update in one tab/window triggers a message to other tabs/windows to update their own session state.
|
||||
|
||||
Using low values for `clientMaxAge` or `keepAlive` will increase network traffic and load on authenticated clients and may impact hosting costs and performance.
|
||||
:::
|
||||
|
||||
@@ -35,7 +35,7 @@ The `POST` submission requires CSRF token from `/api/auth/csrf`.
|
||||
|
||||
Returns client-safe session object - or an empty object if there is no session.
|
||||
|
||||
The contents of the session object that is returned is configurable with the session callback.
|
||||
The contents of the session object that is returned are configurable with the session callback.
|
||||
|
||||
#### `GET` /api/auth/csrf
|
||||
|
||||
@@ -52,7 +52,7 @@ It can be used to dynamically generate custom sign up pages and to check what ca
|
||||
---
|
||||
|
||||
:::note
|
||||
The default base path is `/api/auth` but it is configurable by specyfing a custom path in `NEXTAUTH_URL`
|
||||
The default base path is `/api/auth` but it is configurable by specifying a custom path in `NEXTAUTH_URL`
|
||||
|
||||
e.g.
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ providers: [
|
||||
Providers.Email({
|
||||
server: process.env.EMAIL_SERVER,
|
||||
from: process.env.EMAIL_FROM,
|
||||
sendVerificationRequest: ({ identifier: email, url, token, site, provider }) => { /* your function */ }
|
||||
sendVerificationRequest: ({ identifier: email, url, token, baseUrl, provider }) => { /* your function */ }
|
||||
})
|
||||
]
|
||||
```
|
||||
|
||||
30
www/docs/providers/faceit.md
Normal file
30
www/docs/providers/faceit.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: faceit
|
||||
title: FACEIT
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://cdn.faceit.com/third_party/docs/FACEIT_Connect_3.0.pdf
|
||||
|
||||
## Configuration
|
||||
|
||||
https://developers.faceit.com/apps
|
||||
|
||||
Grant type: `Authorization Code`
|
||||
|
||||
Scopes to have basic infos (email, nickname, guid and avatar) : `openid`, `email`, `profile`
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.FACEIT({
|
||||
clientId: process.env.FACEIT_CLIENT_ID,
|
||||
clientSecret: process.env.FACEIT_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
42
www/docs/providers/instagram.md
Normal file
42
www/docs/providers/instagram.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
id: instagram
|
||||
title: Instagram
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://developers.facebook.com/docs/instagram-basic-display-api/getting-started
|
||||
|
||||
## Configuration
|
||||
|
||||
https://developers.facebook.com/apps/
|
||||
|
||||
## Example
|
||||
|
||||
```jsx
|
||||
// pages/api/auth/[...nextauth].js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Instagram({
|
||||
clientId: process.env.INSTAGRAM_CLIENT_ID,
|
||||
clientSecret: process.env.INSTAGRAM_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
// pages/index.jsx
|
||||
import { signIn } from "next-auth/client"
|
||||
...
|
||||
<button onClick={() => signIn("instagram")}>
|
||||
Sign in
|
||||
</button>
|
||||
...
|
||||
```
|
||||
|
||||
:::warning
|
||||
Email address is not returned by the Instagram API.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
Instagram display app required callback URL to be configured in your Facebook app and Facebook required you to use **https** even for localhost! In order to do that, you either need to [add an SSL to your localhost](https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/) or use a proxy such as [ngrock](https://ngrok.com/docs).
|
||||
:::
|
||||
32
www/docs/providers/kakao.md
Normal file
32
www/docs/providers/kakao.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: kakao
|
||||
title: Kakao
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://developers.kakao.com/product/kakaoLogin
|
||||
|
||||
## Configuration
|
||||
|
||||
https://developers.kakao.com/docs/latest/en/kakaologin/common
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Kakao({
|
||||
clientId: process.env.KAKAO_CLIENT_ID,
|
||||
clientSecret: process.env.KAKAO_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
## Instructions
|
||||
|
||||
### Configuration
|
||||
|
||||
Create a provider and a Kakao application at `https://developers.kakao.com/console/app`. In the settings of the app under Kakao Login, activate web app, change consent items and configure callback URL.
|
||||
39
www/docs/providers/osso.md
Normal file
39
www/docs/providers/osso.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: osso
|
||||
title: Osso
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
Osso is an open source service that handles SAML authentication against Identity Providers, normalizes profiles, and makes those profiles available to you in an OAuth 2.0 code grant flow.
|
||||
|
||||
If you don't yet have an Osso instance, you can use [Osso's Demo App](https://demo.ossoapp.com) for your testing purposes. For documentation on deploying an Osso instance, see https://ossoapp.com/docs/deploy/overview/
|
||||
|
||||
## Configuration
|
||||
|
||||
You can configure your OAuth Clients on your Osso Admin UI, i.e. https://demo.ossoapp.com/admin/config - you'll need to get a Client ID and Secret and allow-list your redirect URIs.
|
||||
|
||||
[SAML SSO differs a bit from OAuth](https://ossoapp.com/blog/saml-vs-oauth) - for every tenant who wants to sign in to your application using SAML, you and your customer need to perform a multi-step configuration in Osso's Admin UI and the admin dashboard of the tenant's Identity Provider. Osso provides documentation for providers like Okta and OneLogin, cloud-based IDPs who also offer a developer account that's useful for testing. Osso also provides a [Mock IDP](https://idp.ossoapp.com) that you can use for testing without needing to sign up for an Identity Provider service.
|
||||
|
||||
See Osso's complete configuration and testing documentation at https://ossoapp.com/docs/configure/overview
|
||||
|
||||
## Example
|
||||
|
||||
A full example application is available at https://github.com/enterprise-oss/osso-next-auth-example and https://nextjs-demo.ossoapp.com
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Osso({
|
||||
clientId: process.env.OSSO_CLIENT_ID,
|
||||
clientSecret: process.env.OSSO_CLIENT_SECRET,
|
||||
domain: process.env.OSSO_DOMAIN
|
||||
})
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
:::note
|
||||
`domain` should be the fully qualified domain – e.g. `demo.ossoapp.com`
|
||||
:::
|
||||
26
www/docs/providers/zoho.md
Normal file
26
www/docs/providers/zoho.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: zoho
|
||||
title: Zoho
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://www.zoho.com/accounts/protocol/oauth/web-server-applications.html
|
||||
|
||||
## Configuration
|
||||
|
||||
https://api-console.zoho.com/
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Zoho({
|
||||
clientId: process.env.ZOHO_CLIENT_ID,
|
||||
clientSecret: process.env.ZOHO_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
@@ -9,6 +9,14 @@ _These tutorials are contributed by the community and hosted on this site._
|
||||
|
||||
_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.
|
||||
|
||||
### [Create your own NextAuth.js Login Pages](https://youtu.be/kB6YNYZ63fw)
|
||||
|
||||
This tutorial shows you how to jump in and create your own custom login pages versus using the ones provided by NextAuth.js
|
||||
|
||||
### [Refresh Token Rotation](tutorials/refresh-token-rotation)
|
||||
|
||||
How to implement refresh token rotation.
|
||||
|
||||
9895
www/package-lock.json
generated
9895
www/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,16 +11,16 @@
|
||||
"generate-providers": "node ./scripts/generate-providers.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.0.0-alpha.66",
|
||||
"@docusaurus/preset-classic": "^2.0.0-alpha.66",
|
||||
"@docusaurus/core": "^2.0.0-alpha.70",
|
||||
"@docusaurus/preset-classic": "^2.0.0-alpha.70",
|
||||
"classnames": "^2.2.6",
|
||||
"docusaurus-lunr-search": "^2.1.7",
|
||||
"docusaurus-lunr-search": "^2.1.10",
|
||||
"jose": "^2.0.2",
|
||||
"lodash.times": "^4.3.2",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-marquee-slider": "^1.1.2",
|
||||
"styled-components": "^5.2.0"
|
||||
"styled-components": "^5.2.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -35,6 +35,6 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"standard": "^15.0.0"
|
||||
"standard": "^16.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user