Compare commits

...

3 Commits

Author SHA1 Message Date
Balázs Orbán
7b82d6e985 fix(ts): typo in Adapter interface 2021-04-28 11:59:34 +02:00
Balázs Orbán
53b0a7aa74 fix(ts): improve adapter TypeScript support (#1870)
* fix(ts): clean up adapter interfaces

* fix(ts): add accessTokenExpires to TokenSet

* docs(adapter): do not recommend getUserByCredentials

* fix(ts): make whole EmailConfig required in AdapterInstance

* fix(ts): fix tests

* refactor(ts): remove legacy adapter types

Co-authored-by: Lluis Agusti <hi@llu.lu>
2021-04-28 11:20:03 +02:00
Lluis Agusti
fbb09303af docs(website): fix layout on small screens (#1869) 2021-04-27 19:30:01 +02:00
6 changed files with 300 additions and 453 deletions

342
types/adapters.d.ts vendored
View File

@@ -1,245 +1,125 @@
import { AppOptions } from "./internals"
import { ConnectionOptions, EntitySchema } from "typeorm"
import { User } from "."
import { AppProvider } from "./providers"
import { User, Profile, Session } from "."
import { EmailConfig, SendVerificationRequest } from "./providers"
import { ConnectionOptions } from "typeorm"
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 BuiltInAdapters {
Default: TypeORMAdapter["Adapter"]
TypeORM: TypeORMAdapter
Prisma: PrismaAdapter
/** Legacy */
declare const Adapters: {
Default: Adapter<ConnectionOptions>
TypeORM: { Adapter: Adapter<ConnectionOptions> }
Prisma: { Adapter: Adapter }
}
export default Adapters
/**
* TODO: fix auto-type schema
* Using a custom adapter you can connect to any database backend or even several different databases.
* Custom adapters created and maintained by our community can be found in the adapters repository.
* Feel free to add a custom adapter from your project to the repository,
* or even become a maintainer of a certain adapter.
* Custom adapters can still be created and used in a project without being added to the repository.
*
* [Community adapters](https://github.com/nextauthjs/adapters) |
* [Create a custom adapter](https://next-auth.js.org/tutorials/creating-a-database-adapter)
*/
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 class TypeORMAccountModel {
compoundId: string
userId: number
providerType: string
providerId: string
providerAccountId: string
refreshToken?: string
accessToken?: string
accessTokenExpires?: Date
constructor(
userId: number,
export interface AdapterInstance<U = User, P = Profile, S = Session> {
createUser(profile: P): Promise<U>
getUser(id: string): Promise<U | null>
getUserByEmail(email: string): Promise<U | null>
getUserByProviderAccountId(
providerId: string,
providerAccountId: string
): Promise<U | null>
updateUser(user: U): Promise<U>
/** @todo Implement */
deleteUser?(userId: string): Promise<void>
linkAccount(
userId: string,
providerId: string,
providerType: string,
providerAccountId: string,
refreshToken?: string,
accessToken?: string,
accessTokenExpires?: Date
)
accessTokenExpires?: null
): Promise<void>
/** @todo Implement */
unlinkAccount?(
userId: string,
providerId: string,
providerAccountId: string
): Promise<void>
createSession(user: U): Promise<S>
getSession(sessionToken: string): Promise<S | null>
updateSession(session: S, force?: boolean): Promise<S>
deleteSession(sessionToken: string): Promise<void>
createVerificationRequest?: SendVerificationRequest
getVerificationRequest?(
identifier: string,
verificationToken: string,
secret: string,
provider: Required<EmailConfig>
): Promise<{
id: string
identifier: string
token: string
expires: Date
} | null>
deleteVerificationRequest?(
identifier: string,
verificationToken: string,
secret: string,
provider: Required<EmailConfig>
): Promise<void>
}
declare class TypeORMUserModel implements User {
name?: string
email?: string
image?: string
emailVerified?: Date
constructor(
name?: string,
email?: string,
image?: string,
emailVerified?: Date
)
[x: string]: unknown
}
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)
}
declare const Adapters: BuiltInAdapters
export default Adapters
export {
Adapter,
BuiltInAdapters as Adapters,
TypeORMAdapter,
TypeORMAccountModel,
TypeORMUserModel,
TypeORMSessionModel,
TypeORMVerificationRequestModel,
PrismaAdapter,
/**
* From an implementation perspective, an adapter in NextAuth.js is a function
* which returns an async `getAdapter()` method, which in turn returns a list of functions
* used to handle operations such as creating user, linking a user
* and an OAuth account or handling reading and writing sessions.
*
* It uses this approach to allow database connection logic to live in the `getAdapter()` method.
* By calling the function just before an action needs to happen,
* it is possible to check database connection status and handle connecting / reconnecting
* to a database as required.
*
* **Required methods**
*
* _(These methods are required for all sign in flows)_
* - `createUser`
* - `getUser`
* - `getUserByEmail`
* - `getUserByProviderAccountId`
* - `linkAccount`
* - `createSession`
* - `getSession`
* - `updateSession`
* - `deleteSession`
* - `updateUser`
*
* _(Required to support email / passwordless sign in)_
*
* - `createVerificationRequest`
* - `getVerificationRequest`
* - `deleteVerificationRequest`
*
* **Unimplemented methods**
*
* _(These methods will be required in a future release, but are not yet invoked)_
* - `deleteUser`
* - `unlinkAccount`
*
* [Community adapters](https://github.com/nextauthjs/adapters) |
* [Create a custom adapter](https://next-auth.js.org/tutorials/creating-a-database-adapter)
*/
export type Adapter<
C = Record<string, unknown>,
O = Record<string, unknown>,
U = User,
P = Profile,
S = Session
> = (
config: C,
options?: O
) => {
getAdapter(appOptions: AppOptions): Promise<AdapterInstance<U, P, S>>
}

2
types/index.d.ts vendored
View File

@@ -233,6 +233,8 @@ export interface LoggerInstance {
*/
export interface TokenSet {
accessToken: string
/** Kept for historical reasons, check out `expires_in` */
accessTokenExpires: null
idToken?: string
refreshToken?: string
access_token: string

22
types/providers.d.ts vendored
View File

@@ -132,19 +132,27 @@ export interface EmailConfigServerOptions {
}
}
export type SendVerificationRequest = (
identifier: string,
url: string,
baseUrl: string,
token: string,
provider: EmailConfig
) => Awaitable<void>
export interface EmailConfig extends CommonProviderOptions {
type: "email"
// TODO: Make use of https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
server: string | EmailConfigServerOptions
/** @default "NextAuth <no-reply@example.com>" */
from?: string
/**
* How long until the e-mail can be used to log the user in,
* in seconds. Defaults to 1 day
* @default 86400
*/
maxAge?: number
sendVerificationRequest(params: {
identifier: string
url: string
baseUrl: string
token: string
provider: EmailConfig
}): Awaitable<void>
sendVerificationRequest: SendVerificationRequest
}
export type EmailProvider = (options: Partial<EmailConfig>) => EmailConfig

View File

@@ -1,11 +1,9 @@
import Providers, { AppProvider, OAuthConfig } from "next-auth/providers"
import {
Adapter,
EmailAppProvider,
Profile,
Session,
VerificationRequest,
} from "next-auth/adapters"
import Providers, {
AppProvider,
EmailConfig,
OAuthConfig,
} from "next-auth/providers"
import { Adapter, AdapterInstance } from "next-auth/adapters"
import NextAuth, * as NextAuthTypes from "next-auth"
import { IncomingMessage, ServerResponse } from "http"
import * as JWTType from "next-auth/jwt"
@@ -54,74 +52,86 @@ const exampleUser: NextAuthTypes.User = {
email: "",
}
const exampleSession: Session = {
const exampleSession: NextAuthTypes.Session = {
userId: "",
accessToken: "",
sessionToken: "",
expires: new Date(),
}
const exampleVerificatoinRequest: VerificationRequest = {
const exampleVerificationRequest = {
id: "",
identifier: "",
token: "",
expires: new Date(),
}
const adapter: Adapter<
NextAuthTypes.User,
Profile,
Session,
VerificationRequest
> = {
async getAdapter(appOptions: 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: 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 adapter: Adapter = () => {
return {
async getAdapter(appOptions: AppOptions) {
return {
async createUser(profile) {
return exampleUser
},
async getUser(id) {
return exampleUser
},
async getUserByEmail(email) {
return exampleUser
},
async getUserByProviderAccountId(providerId, providerAccountId) {
return exampleUser
},
async updateUser(user) {
return exampleUser
},
async linkAccount(
userId,
providerId,
providerType,
providerAccountId,
refreshToken,
accessToken,
accessTokenExpires
) {
return undefined
},
async createSession(user) {
return exampleSession
},
async getSession(sessionToken) {
return exampleSession
},
async updateSession(session, force) {
return exampleSession
},
async deleteSession(sessionToken) {
return undefined
},
async createVerificationRequest(email, url, token, secret, provider) {
return undefined
},
async getVerificationRequest(
email,
verificationToken,
secret,
provider
) {
return exampleVerificationRequest
},
async deleteVerificationRequest(
email,
verificationToken,
secret,
provider
) {
return undefined
},
}
},
}
}
const allConfig = {
const allConfig: NextAuthTypes.NextAuthOptions = {
providers: [
Providers.Twitter({
clientId: "123",
@@ -147,49 +157,36 @@ const allConfig = {
},
pages: pageOptions,
callbacks: {
async signIn(
user: NextAuthTypes.User,
account: Record<string, unknown>,
profile: Record<string, unknown>
) {
async signIn(user, account, profile) {
return true
},
async redirect(url: string, baseUrl: string) {
async redirect(url, baseUrl) {
return "path/to/foo"
},
async session(
session: NextAuthTypes.Session,
userOrToken: NextAuthTypes.User
) {
async session(session, userOrToken) {
return { ...session }
},
async jwt(
token: JWTType.JWT,
user?: NextAuthTypes.User,
account?: Record<string, unknown>,
profile?: Record<string, unknown>,
isNewUser?: boolean
) {
async jwt(token, user, account, profile, isNewUser) {
return token
},
},
events: {
async signIn(message: string) {
async signIn(message) {
return undefined
},
async signOut(message: string) {
async signOut(message) {
return undefined
},
async createUser(message: string) {
async createUser(message) {
return undefined
},
async linkAccount(message: string) {
async linkAccount(message) {
return undefined
},
async session(message: string) {
async session(message) {
return undefined
},
async error(message: string) {
async error(message) {
return undefined
},
},

View File

@@ -40,141 +40,95 @@ These methods are required to support email / passwordless sign in:
These methods will be required in a future release, but are not yet invoked:
* getUserByCredentials
* deleteUser
* unlinkAccount
### Example code
```js
const Adapter = (config, options = {}) => {
async function getAdapter (appOptions) {
async function createUser (profile) {
return null
}
async function getUser (id) {
return null
}
async function getUserByEmail (email) {
return null
}
async function getUserByProviderAccountId (
providerId,
providerAccountId
) {
return null
}
async function getUserByCredentials (credentials) {
return null
}
async function updateUser (user) {
return null
}
async function deleteUser (userId) {
return null
}
async function linkAccount (
userId,
providerId,
providerType,
providerAccountId,
refreshToken,
accessToken,
accessTokenExpires
) {
return null
}
async function unlinkAccount (
userId,
providerId,
providerAccountId
) {
return null
}
async function createSession (user) {
return null
}
async function getSession (sessionToken) {
return null
}
async function updateSession (
session,
force
) {
return null
}
async function deleteSession (sessionToken) {
return null
}
async function createVerificationRequest (
identifier,
url,
token,
secret,
provider
) {
return null
}
async function getVerificationRequest (
identifier,
token,
secret,
provider
) {
return null
}
async function deleteVerificationRequest (
identifier,
token,
secret,
provider
) {
return null
}
return {
createUser,
getUser,
getUserByEmail,
getUserByProviderAccountId,
getUserByCredentials,
updateUser,
deleteUser,
linkAccount,
unlinkAccount,
createSession,
getSession,
updateSession,
deleteSession,
createVerificationRequest,
getVerificationRequest,
deleteVerificationRequest
}
}
export default function YourAdapter (config, options = {}) {
return {
getAdapter
async getAdapter (appOptions) {
async createUser (profile) {
return null
},
async getUser (id) {
return null
},
async getUserByEmail (email) {
return null
},
async getUserByProviderAccountId (
providerId,
providerAccountId
) {
return null
},
async updateUser (user) {
return null
},
async deleteUser (userId) {
return null
},
async linkAccount (
userId,
providerId,
providerType,
providerAccountId,
refreshToken,
accessToken,
accessTokenExpires
) {
return null
},
async unlinkAccount (
userId,
providerId,
providerAccountId
) {
return null
},
async createSession (user) {
return null
},
async getSession (sessionToken) {
return null
},
async updateSession (
session,
force
) {
return null
},
async deleteSession (sessionToken) {
return null
},
async createVerificationRequest (
identifier,
url,
token,
secret,
provider
) {
return null
},
async getVerificationRequest (
identifier,
token,
secret,
provider
) {
return null
},
async deleteVerificationRequest (
identifier,
token,
secret,
provider
) {
return null
}
}
}
}
export default {
Adapter
}
```

View File

@@ -89,6 +89,12 @@ a:hover,
font-weight: 600;
}
@media screen and (max-width: 996px) {
.main-wrapper > div {
flex-direction: column;
}
}
.docusaurus-highlight-code-line {
background-color: rgb(72, 77, 91);
display: block;