Compare commits

...

4 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
26 changed files with 28112 additions and 618 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`)
}
)
})

26891
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 }
]
}

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