Compare commits

...

20 Commits

Author SHA1 Message Date
Lluis Agusti
0d863d38bc fix(ts): correctly export sub-module types (#1677)
* chore(types): build types script

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

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

* refactor(lint): fix build-types script
2021-04-09 20:28:11 +02:00
Lluis Agusti
6f9f42a85b chore(ci): fix typo on types workflow 2021-04-07 17:05:48 +02:00
Lluis Agusti
2160be2a8a feat(ts): expose types from the package (#1665)
* chore(types): move existing types to the repo
* feat(ts): expose types from the main package
* chore(deps): bring back `react-dom` version range
* chore(ts): cleanup deps and comments
* chore(ci): run types tests on a separate workflow
2021-04-07 17:03:17 +02:00
Balázs Orbán
55eb066793 chore: add beta to release flow/GH actions 2021-04-04 22:08:25 +02:00
Jasper Moelker
5bc8f8b986 docs(page): correct getCsrfToken and input types (#1651)
This fixes the a mismatch between the import (`csrfToken`) and the method (`getCsrfToken`) used in `getInitialProps`/`getServerSideProps`.
In addition the form input fields now have their correct type: `email` for email input (for better autocomplete, virtual keyboard support and native validation) and `password` for the password input (to hide password while typing).
2021-04-04 22:01:53 +02:00
hoangbits
136361e1f4 docs: rename command to vercel cli, now cli is deprecated (#1647) 2021-04-04 11:02:38 +02:00
hoangbits
cc9869592c docs: fix typo in providers.md (#1641) 2021-04-02 17:18:40 +02:00
Jay Liew
073da60c3d docs: Update pages.md (#1592)
* Update pages.md

Updated Credentials Sign-In code example to indicate how to use `getServerSideProps` but still also showing the older `getInitialProps` example

* Update www/docs/configuration/pages.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* update documentation to show example using getServerSideProps()

Co-authored-by: Balázs Orbán <info@balazsorban.com>
Co-authored-by: Jay Liew <jay@haute.tech>
2021-03-31 00:31:31 +02:00
ifly7charlie
aacc34bbfd docs(error): Add missing error message and technique to resolve (#1549)
* Add missing error message and technique to resolve

* Update errors.md

Correct with correct error message and more complete suggestions on resolving it
2021-03-26 23:09:21 +01:00
jgollhardt
074688d10e docs(provider): fix wrong param name in sendVerificationRequest example (#1595) 2021-03-26 23:01:25 +01:00
Macarse, Christian Ryan R
b3ffe50c03 docs(provider): removed misleading provider signin link (#1588) 2021-03-25 22:30:46 +01:00
Shubham Shukla
e6d063825d fix(provider): added options in instagram provider (#1570) 2021-03-23 22:28:54 +01:00
Balázs Orbán
985f7b3431 fix(logger): properly end request every time (#1557)
* fix(logger): properly end request every time

* chore: fix linting
2021-03-20 10:08:12 +01:00
Max
237b016378 fix(provider): reject access token if slack login flow was canceled (#1544)
* fix: reject access token if slack login flow was canceled

* style: fix lint errors in oauth client
2021-03-18 14:59:24 +01:00
Joshua Williams
776b9480da feat(provider): add Zoho provider (#1516)
* feat(provider): add zoho

* fix: use LF instead of CRLF

* fix: crlf to lf line endings

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-03-16 19:27:11 +01:00
Honman Yau
07a3f76cb3 docs: fix typos in REST API guide (#1528) 2021-03-16 19:25:24 +01:00
tclaude94
3726d68c49 feat(provider): add FACEIT provider (#1469) 2021-03-16 00:00:35 +01:00
dependabot[bot]
e31db1726a chore(deps): bump xmldom from 0.3.0 to 0.5.0 (#1510)
Bumps [xmldom](https://github.com/xmldom/xmldom) from 0.3.0 to 0.5.0.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.3.0...0.5.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-13 14:19:22 +01:00
James Perkins
a241199c11 docs(tutorials): Adding two more tutorials to the list.
[skip release]
2021-03-13 03:42:00 +00:00
dependabot[bot]
5385ec20a9 chore(deps): bump elliptic from 6.5.3 to 6.5.4 in /www (#1493)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-10 22:59:10 +01:00
42 changed files with 34537 additions and 4337 deletions

View File

@@ -6,10 +6,12 @@ on:
push:
branches:
- main
- beta
- next
pull_request:
branches:
- main
- beta
- next
jobs:

View File

@@ -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 ]

View File

@@ -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}}

View File

@@ -3,6 +3,7 @@ on:
push:
branches:
- 'main'
- 'beta'
- 'next'
- '3.x'
pull_request:

25
.github/workflows/types.yml vendored Normal file
View 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
View File

@@ -0,0 +1,3 @@
./types/tests/
./types/tests/tsconfig.json
./types/tests/tslint.json

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"semi": false
}

23
config/build-types.js Normal file
View 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`)
}
)
})

26897
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",
@@ -57,7 +61,7 @@
},
"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,7 +109,8 @@
"project": "./tsconfig.json",
"ignore": [
"test/",
"next-env.d.ts"
"next-env.d.ts",
"types/"
],
"globals": [
"localStorage",

View File

@@ -2,6 +2,7 @@ module.exports = {
branches: [
'+([0-9])?(.{+([0-9]),x}).x',
'main',
{ name: 'beta', prerelease: true },
{ name: 'next', prerelease: true }
]
}

25
src/providers/faceit.js Normal file
View 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
}
}

View File

@@ -12,6 +12,7 @@ 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'
@@ -36,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,
@@ -52,6 +54,7 @@ export default {
Email,
EVEOnline,
Facebook,
FACEIT,
Foursquare,
FusionAuth,
GitHub,
@@ -75,5 +78,6 @@ export default {
Twitch,
Twitter,
VK,
Yandex
Yandex,
Zoho
}

View File

@@ -45,6 +45,7 @@ export default function Instagram (options) {
email: null,
image: null
}
}
},
...options
}
}

22
src/providers/zoho.js Normal file
View 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
}
}

View File

@@ -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:

View File

@@ -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,

40
types/_next.d.ts vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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">

View 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",
},
})

View 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
View 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",
})

View 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
View 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)

View 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
View 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
View File

@@ -0,0 +1,6 @@
{
"extends": "dtslint/dtslint.json",
"rules": {
"semicolon": false
}
}

View File

@@ -18,7 +18,7 @@ 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

View File

@@ -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:

View File

@@ -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
{

View File

@@ -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.

View File

@@ -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.

View File

@@ -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 */ }
})
]
```

View 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
})
]
...
```

View 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
})
]
...
```

View File

@@ -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

File diff suppressed because it is too large Load Diff