mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
13 Commits
next-auth@
...
v4.0.0-nex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6a3a72db4 | ||
|
|
edcb10a823 | ||
|
|
2acabe19e0 | ||
|
|
a6f5f4c184 | ||
|
|
9fa93e3b5e | ||
|
|
cb4342fdda | ||
|
|
5f717b3914 | ||
|
|
d09a45ec7c | ||
|
|
930f58eba3 | ||
|
|
c20b7f2930 | ||
|
|
e418cddd96 | ||
|
|
111e7aabdf | ||
|
|
a113ef6fab |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -58,4 +58,4 @@ app/yarn.lock
|
||||
/_work
|
||||
|
||||
# Prisma migrations
|
||||
/prisma/migrations
|
||||
/prisma/migrations
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
{
|
||||
"presets": [
|
||||
["@babel/preset-env", { "targets": { "esmodules": true } }]
|
||||
["@babel/preset-env", { "targets": { "node": "10" } }]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
],
|
||||
"comments": false,
|
||||
"overrides": [
|
||||
{
|
||||
"test": ["../src/client/**"],
|
||||
"comments": true,
|
||||
"presets": [
|
||||
["@babel/preset-env", { "targets": { "ie": "11" } }]
|
||||
]
|
||||
},
|
||||
{
|
||||
"test": ["../src/server/pages/**"],
|
||||
"presets": ["preact"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
2084
package-lock.json
generated
2084
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@@ -74,29 +74,48 @@
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.0.0",
|
||||
"@balazsorban/require-optional": "^1.0.0",
|
||||
"futoin-hkdf": "^1.3.2",
|
||||
"jose": "^1.27.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"nodemailer": "^6.4.16",
|
||||
"oauth": "^0.9.15",
|
||||
"pkce-challenge": "^2.1.0",
|
||||
"preact": "^10.4.1",
|
||||
"preact-render-to-string": "^5.1.14",
|
||||
"querystring": "^0.2.0",
|
||||
"require_optional": "^1.0.1",
|
||||
"typeorm": "^0.2.30"
|
||||
"preact-render-to-string": "^5.1.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17",
|
||||
"react-dom": "16.13.1 || ^17"
|
||||
},
|
||||
"peerOptionalDependencies": {
|
||||
"mongodb": "^3.5.9",
|
||||
"react-dom": "^16.13.1 || ^17",
|
||||
"mongodb": "^3.6.6",
|
||||
"mysql": "^2.18.1",
|
||||
"mssql": "^6.2.1",
|
||||
"pg": "^8.2.1",
|
||||
"@prisma/client": "^2.16.1"
|
||||
"@prisma/client": "^2.16.1",
|
||||
"nodemailer": "^6.4.16",
|
||||
"typeorm": "^0.2.30"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"mongodb": {
|
||||
"optional": true
|
||||
},
|
||||
"mysql": {
|
||||
"optional": true
|
||||
},
|
||||
"mssql": {
|
||||
"optional": true
|
||||
},
|
||||
"pg": {
|
||||
"optional": true
|
||||
},
|
||||
"@prisma/client": {
|
||||
"optional": true
|
||||
},
|
||||
"nodemailer": {
|
||||
"optional": true
|
||||
},
|
||||
"typeorm": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.8.4",
|
||||
@@ -125,7 +144,7 @@
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"mocha": "^8.1.3",
|
||||
"mongodb": "^3.5.9",
|
||||
"mongodb": "^3.6.6",
|
||||
"mssql": "^6.2.1",
|
||||
"mysql": "^2.18.1",
|
||||
"next": "^10.0.5",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createConnection, getConnection } from 'typeorm'
|
||||
import { createHash } from 'crypto'
|
||||
import require_optional from 'require_optional' // eslint-disable-line camelcase
|
||||
|
||||
import { CreateUserError } from '../../lib/errors'
|
||||
import adapterConfig from './lib/config'
|
||||
@@ -9,6 +8,8 @@ import Models from './models'
|
||||
|
||||
import { updateConnectionEntities } from './lib/utils'
|
||||
|
||||
import requireOptional from '@balazsorban/require-optional'
|
||||
|
||||
const Adapter = (typeOrmConfig, options = {}) => {
|
||||
// Ensure typeOrmConfigObject is normalized to an object
|
||||
const typeOrmConfigObject = (typeof typeOrmConfig === 'string')
|
||||
@@ -94,12 +95,12 @@ const Adapter = (typeOrmConfig, options = {}) => {
|
||||
let ObjectId
|
||||
if (config.type === 'mongodb') {
|
||||
idKey = '_id'
|
||||
// Using a dynamic import causes problems for some compilers/bundlers
|
||||
// that don't handle dynamic imports. To try and work around this we are
|
||||
// using the same method mongodb uses to load Object ID type, which is to
|
||||
// use the require_optional loader.
|
||||
const mongodb = require_optional('mongodb')
|
||||
ObjectId = mongodb.ObjectId
|
||||
// We should/could use dynamic import here, but
|
||||
// bundlers like webpack will try to import the module,
|
||||
// even if this conditional branch is never entered.
|
||||
// We work around this with requireOptional.
|
||||
const mongodb = requireOptional('mongodb')
|
||||
ObjectId = mongodb.ObjectID
|
||||
}
|
||||
|
||||
// These values are stored as seconds, but to use them with dates in
|
||||
|
||||
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,5 @@
|
||||
import nodemailer from "nodemailer"
|
||||
import logger from "../lib/logger"
|
||||
import logger from '../lib/logger'
|
||||
import requireOptional from '@balazsorban/require-optional'
|
||||
|
||||
export default function Email(options) {
|
||||
return {
|
||||
@@ -22,34 +22,25 @@ export default function Email(options) {
|
||||
}
|
||||
}
|
||||
|
||||
const sendVerificationRequest = ({
|
||||
identifier: email,
|
||||
url,
|
||||
baseUrl,
|
||||
provider,
|
||||
}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { server, from } = provider
|
||||
// Strip protocol from URL and use domain as site name
|
||||
const site = baseUrl.replace(/^https?:\/\//, "")
|
||||
|
||||
nodemailer.createTransport(server).sendMail(
|
||||
{
|
||||
async function sendVerificationRequest ({ identifier: email, url, baseUrl, provider }) {
|
||||
const { server, from } = provider
|
||||
// Strip protocol from URL and use domain as site name
|
||||
const site = baseUrl.replace(/^https?:\/\//, '')
|
||||
try {
|
||||
const nodemailer = requireOptional('nodemailer')
|
||||
await nodemailer
|
||||
.createTransport(server)
|
||||
.sendMail({
|
||||
to: email,
|
||||
from,
|
||||
subject: `Sign in to ${site}`,
|
||||
text: text({ url, site, email }),
|
||||
html: html({ url, site, email }),
|
||||
},
|
||||
(error) => {
|
||||
if (error) {
|
||||
logger.error("SEND_VERIFICATION_EMAIL_ERROR", email, error)
|
||||
return reject(new Error("SEND_VERIFICATION_EMAIL_ERROR", error))
|
||||
}
|
||||
return resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
html: html({ url, site, email })
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('SEND_VERIFICATION_EMAIL_ERROR', email, error)
|
||||
throw new Error('SEND_VERIFICATION_EMAIL_ERROR')
|
||||
}
|
||||
}
|
||||
|
||||
// Email HTML body
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import adapters from '../adapters'
|
||||
import jwt from '../lib/jwt'
|
||||
import parseUrl from '../lib/parse-url'
|
||||
import logger, { setLogger } from '../lib/logger'
|
||||
@@ -15,6 +14,8 @@ import csrfTokenHandler from './lib/csrf-token-handler'
|
||||
import * as pkce from './lib/oauth/pkce-handler'
|
||||
import * as state from './lib/oauth/state-handler'
|
||||
|
||||
import optionalRequire from '@balazsorban/require-optional'
|
||||
|
||||
// To work properly in production with OAuth providers the NEXTAUTH_URL
|
||||
// environment variable must be set.
|
||||
if (!process.env.NEXTAUTH_URL) {
|
||||
@@ -73,7 +74,7 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
// Protection only works on OAuth 2.x providers
|
||||
if (provider?.type === 'oauth' && provider.version?.startsWith('2')) {
|
||||
// When provider.state is undefined, we still want this to pass
|
||||
if (!provider.protection && provider.state !== false) {
|
||||
if (!provider.protection) {
|
||||
// Default to state, as we did in 3.1 REVIEW: should we use "pkce" or "none" as default?
|
||||
provider.protection = ['state']
|
||||
} else if (typeof provider.protection === 'string') {
|
||||
@@ -86,7 +87,11 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
// Parse database / adapter
|
||||
// If adapter is provided, use it (advanced usage, overrides database)
|
||||
// If database URI or config object is provided, use it (simple usage)
|
||||
const adapter = userOptions.adapter ?? (userOptions.database && adapters.Default(userOptions.database))
|
||||
let adapter = userOptions.adapter
|
||||
if ((!adapter && !!userOptions.database)) {
|
||||
const TypeOrm = optionalRequire('../adapters/typeorm')
|
||||
adapter = TypeOrm.Adapter(userOptions.database)
|
||||
}
|
||||
|
||||
// User provided options are overriden by other options,
|
||||
// except for the options with special handling above
|
||||
|
||||
@@ -65,18 +65,13 @@ export default async function callback (req, res) {
|
||||
|
||||
try {
|
||||
const signInCallbackResponse = await callbacks.signIn(userOrProfile, account, OAuthProfile)
|
||||
if (signInCallbackResponse === false) {
|
||||
if (!signInCallbackResponse) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
|
||||
} else if (typeof signInCallbackResponse === 'string') {
|
||||
return res.redirect(signInCallbackResponse)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
// TODO: Remove in a future major release
|
||||
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
|
||||
return res.redirect(error)
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
|
||||
// Sign user in
|
||||
@@ -161,18 +156,13 @@ export default async function callback (req, res) {
|
||||
// Check if user is allowed to sign in
|
||||
try {
|
||||
const signInCallbackResponse = await callbacks.signIn(profile, account, { email })
|
||||
if (signInCallbackResponse === false) {
|
||||
if (!signInCallbackResponse) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
|
||||
} else if (typeof signInCallbackResponse === 'string') {
|
||||
return res.redirect(signInCallbackResponse)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
// TODO: Remove in a future major release
|
||||
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
|
||||
return res.redirect(error)
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
|
||||
// Sign user in
|
||||
@@ -236,12 +226,11 @@ export default async function callback (req, res) {
|
||||
userObjectReturnedFromAuthorizeHandler = await provider.authorize(credentials)
|
||||
if (!userObjectReturnedFromAuthorizeHandler) {
|
||||
return res.status(401).redirect(`${baseUrl}${basePath}/error?error=CredentialsSignin&provider=${encodeURIComponent(provider.id)}`)
|
||||
} else if (typeof userObjectReturnedFromAuthorizeHandler === 'string') {
|
||||
return res.redirect(userObjectReturnedFromAuthorizeHandler)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
return res.redirect(error)
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
|
||||
const user = userObjectReturnedFromAuthorizeHandler
|
||||
@@ -249,14 +238,13 @@ export default async function callback (req, res) {
|
||||
|
||||
try {
|
||||
const signInCallbackResponse = await callbacks.signIn(user, account, credentials)
|
||||
if (signInCallbackResponse === false) {
|
||||
if (!signInCallbackResponse) {
|
||||
return res.status(403).redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
|
||||
} else if (typeof signInCallbackResponse === 'string') {
|
||||
return res.redirect(signInCallbackResponse)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
return res.redirect(error)
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
|
||||
}
|
||||
|
||||
const defaultJwtPayload = {
|
||||
|
||||
@@ -45,18 +45,13 @@ export default async function signin (req, res) {
|
||||
// Check if user is allowed to sign in
|
||||
try {
|
||||
const signInCallbackResponse = await callbacks.signIn(profile, account, { email, verificationRequest: true })
|
||||
if (signInCallbackResponse === false) {
|
||||
if (!signInCallbackResponse) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
|
||||
} else if (typeof signInCallbackResponse === 'string') {
|
||||
return res.redirect(signInCallbackResponse)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
|
||||
}
|
||||
// TODO: Remove in a future major release
|
||||
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
|
||||
return res.redirect(error)
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -76,7 +76,6 @@ In _most cases_ it does not make sense to specify a database in NextAuth.js opti
|
||||
The provider you tried to use failed when setting [PKCE or Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636#section-4.2).
|
||||
The `code_verifier` is saved in a cookie called (by default) `__Secure-next-auth.pkce.code_verifier` which expires after 15 minutes.
|
||||
Check if `cookies.pkceCodeVerifier` is configured correctly. The default `code_challenge_method` is `"S256"`. This is currently not configurable to `"plain"`, as it is not recommended, and in most cases it is only supported for backward compatibility.
|
||||
|
||||
---
|
||||
|
||||
### Session Handling
|
||||
|
||||
@@ -11,7 +11,7 @@ _New submissions and edits are welcome!_
|
||||
|
||||
### [NextJS Authentication Crash Course with NextAuth.js](https://youtu.be/o_wZIVmWteQ)
|
||||
|
||||
This tutorial dives in to the ins and outs of NextAuth including email, GitHub, Twitter and integrating with Auth0 in under hour.
|
||||
This tutorial dives in to the ins and outs of NextAuth including email, Github, Twitter and integrating with Auth0 in under hour.
|
||||
|
||||
### [Create your own NextAuth.js Login Pages](https://youtu.be/kB6YNYZ63fw)
|
||||
|
||||
|
||||
@@ -46,32 +46,4 @@ You can use [node-jose-tools](https://www.npmjs.com/package/node-jose-tools) to
|
||||
|
||||
**Option 2**: Specify custom encode/decode functions on the jwt object. This gives you complete control over signing / verification / etc.
|
||||
|
||||
#### JWT_AUTO_GENERATED_ENCRYPTION_KEY
|
||||
|
||||
#### SIGNIN_CALLBACK_REJECT_REDIRECT
|
||||
|
||||
You returned something in the `signIn` callback, that is being deprecated.
|
||||
|
||||
You probably had something similar in the callback:
|
||||
```js
|
||||
return Promise.reject("/some/url")
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
throw "/some/url"
|
||||
```
|
||||
|
||||
To remedy this, simply return the url instead:
|
||||
|
||||
```js
|
||||
return "/some/url"
|
||||
```
|
||||
|
||||
|
||||
#### STATE_OPTION_DEPRECATION
|
||||
You provided `state: true` or `state: false` as a provider option. This is being deprecated in a later release in favour of `protection: "state"` and `protection: "none"` respectively. To remedy this warning:
|
||||
|
||||
- If you use `state: true`, just simply remove it. The default is `protection: "state"` already..
|
||||
- If you use `state: false`, set `protection: "none"`.
|
||||
#### JWT_AUTO_GENERATED_ENCRYPTION_KEY
|
||||
Reference in New Issue
Block a user