🤖 Merge PR #47310 [next-auth] Important fixes and Adapter Types by @JuanM04

* JWT support

* Added Next types

* other fixes

* removed next.js (again)

* sameSite union

* More adapter types

* keepAlive as a number (from #47289)

* Moved JWT interface

* Added Adapter type

* Added myself as a mantainer 👉👈

* Export more

* Fixed some typos

* Update client.d.ts

* Update client.d.ts

* Finally!
This commit is contained in:
JuanM04 2020-09-17 12:01:41 -03:00 committed by GitHub
parent 5edd06b6b7
commit 8ea3802414
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 317 additions and 235 deletions

25
types/next-auth/_utils.d.ts vendored Normal file
View File

@ -0,0 +1,25 @@
import { IncomingMessage, ServerResponse } from 'http';
interface GenericObject {
[key: string]: any;
}
interface NextApiRequest extends IncomingMessage, GenericObject {
query: {
[key: string]: string | string[];
};
cookies: {
[key: string]: string;
};
body: any;
}
interface NextApiResponse<T = any> extends ServerResponse, GenericObject {
send: Send<T>;
json: Send<T>;
status: (statusCode: number) => NextApiResponse<T>;
}
type Send<T> = (body: T) => void;
export { GenericObject, NextApiRequest, NextApiResponse };

View File

@ -1,24 +1,183 @@
import { ConnectionOptions } from 'typeorm';
import { ConnectionOptions, EntitySchema } from 'typeorm';
import { AppOptions } from '.';
import { SessionProvider } from './client';
/**
* TODO: type adapters correctly
* @see https://next-auth.js.org/schemas/adapters
*/
interface GenericObject {
[key: string]: any;
// NOTE: There are a lot of `any`. That's because there could be any schema for each entity
interface Adapter {
getAdapter(
appOptions: AppOptions,
): Promise<{
createUser(profile: any): Promise<any>;
getUser(id: string): Promise<any>;
getUserByEmail(email: string): Promise<any>;
getUserByProviderAccountId(providerId: string, providerAccountId: string): Promise<any>;
updateUser(profile: any): Promise<any>;
linkAccount(
userId: string,
providerId: string,
providerType: string,
providerAccountId: string,
refreshToken: string,
accessToken: string,
accessTokenExpires: number,
): Promise<void>;
createSession(user: any): Promise<any>;
getSession(sessionToken: string): Promise<any>;
updateSession(session: any): Promise<void>;
deleteSession(sessionToken: string): Promise<void>;
createVerificationRequest?(
email: string,
url: string,
token: string,
secret: string,
provider: SessionProvider,
options: AppOptions,
): Promise<void>;
getVerificationRequest?(
email: string,
verificationToken: string,
secret: string,
provider: SessionProvider,
): Promise<any>;
deleteVerificationRequest?(
email: string,
verificationToken: string,
secret: string,
provider: SessionProvider,
): Promise<any>;
}>;
}
type Adapter = (config: ConnectionOptions) => any;
type Schema<T = any> = EntitySchema<T>['options'];
interface Adapters {
Default: Adapter;
TypeORM: {
Adapter: Adapter;
Models: GenericObject;
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;
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 {
name?: string;
email?: string;
image?: string;
emailVerified?: Date;
constructor(name?: string, email?: string, image?: string, emailVerified?: Date);
}
declare class TypeORMSessionModel {
userId: number;
expires: Date;
sessionToken: string;
accessToken: string;
constructor(userId: number, expires: Date, sessionToken?: string, accessToken?: string);
}
declare class TypeORMVerificationRequestModel {
identifier?: string;
token?: string;
expires?: Date;
constructor(identifier?: string, token?: string, expires?: Date);
}
export default Adapters;
export { Adapter };
export {
Adapter,
Adapters,
TypeORMAdapter,
TypeORMAccountModel,
TypeORMUserModel,
TypeORMSessionModel,
TypeORMVerificationRequestModel,
PrismaAdapter,
};

View File

@ -1,6 +1,8 @@
import { FC } from 'react';
import { IncomingMessage } from 'http';
import { GenericObject } from './_utils';
export interface Session {
interface Session {
user: {
name: string;
email: string;
@ -14,7 +16,7 @@ interface GetProvidersResponse {
[provider: string]: SessionProvider;
}
interface SessionProvider {
interface SessionProvider extends GenericObject {
id: string;
name: string;
type: string;
@ -22,37 +24,35 @@ interface SessionProvider {
callbackUrl: string;
}
interface GenericObject {
[key: string]: any;
}
interface ContextProviderProps {
session: Session;
options?: ContextProviderOptions;
}
interface ContextProviderOptions {
site?: string;
basePath?: string;
clientMaxAge?: number;
keepAlive?: number;
options?: SetOptionsParams;
}
interface SetOptionsParams {
baseUrl?: string;
basePath?: string;
clientMaxAge?: string;
keepAlive?: boolean;
clientMaxAge?: number;
keepAlive?: number;
}
type ContextProvider = FC<ContextProviderProps>;
interface NextContext {
req?: IncomingMessage;
ctx?: { req: IncomingMessage };
}
declare function useSession(): [Session, boolean];
declare function providers(context?: NextPageContext): Promise<GetProvidersResponse | null>;
declare function providers(): Promise<GetProvidersResponse | null>;
declare const getProviders: typeof providers;
declare function session(context?: NextPageContext): Promise<Session | null>;
declare function session(
context?: NextContext & {
triggerEvent?: boolean;
},
): Promise<Session | null>;
declare const getSession: typeof session;
declare function csrfToken(context?: NextPageContext): Promise<string | null>;
declare function csrfToken(context?: NextContext): Promise<string | null>;
declare const getCsrfToken: typeof csrfToken;
declare function signin(
provider?: string,
@ -82,30 +82,6 @@ export {
options,
setOptions,
Provider,
Session,
SessionProvider,
};
/**
* TODO: `dtslint` throws when parsing Next types... the following types are copied directly from `next/types` ...
* @see https://github.com/microsoft/dtslint/issues/297
*/
interface NextApiRequest {
query: {
[key: string]: string | string[];
};
cookies: {
[key: string]: string;
};
body: any;
env: Env;
}
interface NextPageContext {
req?: NextApiRequest;
ctx?: NextPageContext;
triggerEvent?: boolean;
}
interface Env {
[key: string]: string;
}

View File

@ -2,6 +2,7 @@
// Project: https://github.com/iaincollins/next-auth#readme
// Definitions by: Lluis <https://github.com/lluia>
// Iain <https://github.com/iaincollins>
// Juan <https://github.com/JuanM04>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 3.1
@ -10,10 +11,13 @@
import { ConnectionOptions } from 'typeorm';
import { PossibleProviders } from './providers';
import { Adapter } from './adapters';
import { GenericObject, NextApiRequest, NextApiResponse } from './_utils';
import { SessionProvider } from './client';
import { JWTEncodeParams, JWTDecodeParams } from './jwt';
export interface InitOptions {
interface InitOptions {
providers: Array<ReturnType<PossibleProviders>>;
database?: ConnectionOptions | string;
database?: string | ConnectionOptions;
secret?: string;
session?: Session;
jwt?: JWTOptions;
@ -26,7 +30,29 @@ export interface InitOptions {
cookies?: Cookies;
}
export interface PageOptions {
interface AppOptions {
debug: boolean;
pages: PageOptions;
adapter: Adapter;
baseUrl: string;
basePath: string;
action: 'providers' | 'session' | 'csrf' | 'signin' | 'signout' | 'callback' | 'verify-request' | 'error';
provider?: string;
cookies: Cookies;
secret: string;
csrfToken: string;
providers: {
[provider: string]: SessionProvider;
};
session: Session;
jwt: JWTOptions;
events: Events;
callbacks: Callbacks;
callbackUrl: string;
// redirect?(redirectUrl: string): any;
}
interface PageOptions {
signIn?: string;
signOut?: string;
error?: string;
@ -43,22 +69,22 @@ interface Cookie {
options: CookieOptions;
}
export interface CookieOptions {
interface CookieOptions {
httpOnly?: boolean;
// TODO: type available `sameSite` identifiers
sameSite: string;
path: string;
secure: boolean;
sameSite?: true | 'strict' | 'lax' | 'none';
path?: string;
secure?: boolean;
maxAge?: number;
}
interface Events {
signIn?(message: string): Promise<void>;
signOut?(message: string): Promise<void>;
createUser?(message: string): Promise<void>;
updateUser?(message: string): Promise<void>;
linkAccount?(message: string): Promise<void>;
session?(message: string): Promise<void>;
error?(message: string): Promise<void>;
signIn?(message: any): Promise<void>;
signOut?(message: any): Promise<void>;
createUser?(message: any): Promise<void>;
updateUser?(message: any): Promise<void>;
linkAccount?(message: any): Promise<void>;
session?(message: any): Promise<void>;
error?(message: any): Promise<void>;
}
interface Session {
@ -68,54 +94,14 @@ interface Session {
}
interface JWTOptions {
secret: string;
secret?: string;
maxAge?: number;
encode?(options: JWTEncodeParams): Promise<string>;
decode?(options: JWTDecodeParams): Promise<string>;
}
interface JWTSignInOptions {
expiresIn: string;
}
interface JWTVerificationOptions {
maxTokenAge: string;
algorithms: string[];
}
interface JWTDecryptionOptions {
algorithms: string[];
}
interface JWTDecodeParams {
secret: string;
token: GenericObject;
maxAge?: number;
signingKey?: string;
verificationKey?: string;
verificationOptions?: JWTVerificationOptions;
decryptionKey?: string;
decryptionOptions?: JWTDecryptionOptions;
encryption?: boolean;
}
interface JWTEncodeParams {
secret: string;
token: GenericObject;
signingKey?: string;
encryptionKey?: string;
signingOptions?: JWTSignInOptions;
encryptionOptions?: GenericObject;
encryption?: boolean;
maxAge?: number;
}
interface GenericObject {
[key: string]: any;
}
// TODO: Improve callback typings
export interface Callbacks {
interface Callbacks {
signIn?(user: GenericObject, account: GenericObject, profile: GenericObject): Promise<boolean>;
redirect?(url: string, baseUrl: string): Promise<string>;
session?(session: Session, user: GenericObject): Promise<GenericObject>;
@ -130,38 +116,4 @@ export interface Callbacks {
declare function NextAuth(req: NextApiRequest, res: NextApiResponse, options?: InitOptions): Promise<void>;
export default NextAuth;
/**
* TODO: `dtslint` throws when parsing Next types... the following types are copied directly from `next/types` ...
* @see https://github.com/microsoft/dtslint/issues/297
*/
interface NextApiRequest {
query: {
[key: string]: string | string[];
};
cookies: {
[key: string]: string;
};
body: any;
env: Env;
}
interface NextApiResponse<T = any> {
send: Send<T>;
json: Send<T>;
status: (statusCode: number) => NextApiResponse<T>;
setPreviewData: (
data: object | string,
options?: {
maxAge?: number;
},
) => NextApiResponse<T>;
clearPreviewData: () => NextApiResponse<T>;
}
interface Env {
[key: string]: string;
}
type Send<T> = (body: T) => void;
export { InitOptions, AppOptions, PageOptions, Cookies, Events, Session, JWTOptions, Callbacks };

View File

@ -1,24 +1,18 @@
import jose from 'jose';
import { NextApiRequest } from './_utils';
/**
* TODO: `dtslint` throws when parsing Next types... the following types are copied directly from `next/types` ...
* @see https://github.com/microsoft/dtslint/issues/297
*/
interface NextApiRequest {
query: {
[key: string]: string | string[];
};
cookies: {
[key: string]: string;
};
body: any;
env: {
[key: string]: string;
};
export interface JWTEncodeParams {
token?: object;
maxAge?: number;
secret: string | Buffer;
signingKey?: string;
signingOptions?: jose.JWT.SignOptions;
encryptionKey?: string;
encryptionOptions?: object;
encryption?: boolean;
}
interface DecodeArgs {
export interface JWTDecodeParams {
maxAge?: number;
secret: string | Buffer;
signingKey?: string;
@ -30,21 +24,8 @@ interface DecodeArgs {
encryption?: boolean;
}
declare function encode(args?: {
token?: object;
maxAge?: number;
secret: string | Buffer;
signingKey?: string;
signingOptions?: jose.JWT.SignOptions;
encryptionKey?: string;
encryptionOptions?: object;
encryption?: boolean;
}): Promise<string>;
declare function decode(
args?: DecodeArgs & {
token: string;
},
): Promise<object>;
declare function encode(args?: JWTEncodeParams): Promise<string>;
declare function decode(args?: JWTDecodeParams & { token: string }): Promise<object>;
declare function getToken(
args?: {
@ -52,7 +33,7 @@ declare function getToken(
secureCookie?: boolean;
cookieName?: string;
raw?: string;
} & DecodeArgs,
} & JWTDecodeParams,
): Promise<object>;
declare function getToken(args?: {
req: NextApiRequest;

View File

@ -5,48 +5,30 @@
* in the sense of typing and call signature consistency. They
* are not intended as functional tests.
*/
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import Adapters from 'next-auth/adapters';
import * as client from 'next-auth/client';
import * as JWT from 'next-auth/jwt';
import NextAuth, * as NextAuthTypes from 'next-auth';
import { GenericObject, NextApiRequest, NextApiResponse } from 'next-auth/_utils';
import { IncomingMessage, ServerResponse } from 'http';
import { Socket } from 'net';
// --------------------------------------------------------------------------
// Server
// --------------------------------------------------------------------------
interface GenericObject {
[key: string]: any;
}
const req: NextApiRequest = Object.assign(new IncomingMessage(new Socket()), {
query: {},
cookies: {},
body: {},
});
interface Session {
jwt?: boolean;
maxAge?: number;
updateAge?: number;
}
const req = {
query: {
foo: 'bar',
},
cookies: {
bar: 'baz',
},
body: {
bam: 'bom',
},
env: {
SOMETHING: 'SOMETHING',
},
};
const res = {
const res: NextApiResponse = Object.assign(new ServerResponse(req), {
send: () => undefined,
json: () => undefined,
status: (code: number) => res,
setPreviewData: (data: object | string) => res,
clearPreviewData: () => res,
};
});
const pageOptions = {
signin: 'path/to/signin',
@ -94,7 +76,7 @@ const allConfig = {
callbacks: {
signIgn: (user: GenericObject, account: GenericObject, profile: GenericObject) => Promise.resolve(true),
redirect: (url: string, baseUrl: string) => Promise.resolve('path/to/foo'),
session: (session: Session, user: GenericObject) => Promise.resolve<any>(user),
session: (session: NextAuthTypes.Session, user: GenericObject) => Promise.resolve<any>(user),
jwt: (
token: GenericObject,
user: GenericObject,
@ -123,17 +105,29 @@ const allConfig = {
return undefined;
},
},
adapter: () => {
async function getAdapter() {
adapter: {
getAdapter: (appOptions: NextAuthTypes.AppOptions) => {
return Promise.resolve({
getSession: async function getSession() {
return null;
},
createUser: (profile: any) => Promise.resolve({}),
getUser: (id: string) => Promise.resolve({}),
getUserByEmail: (email: string) => Promise.resolve({}),
getUserByProviderAccountId: (providerId: string, providerAccountId: string) => Promise.resolve({}),
updateUser: (profile: any) => Promise.resolve({}),
linkAccount: (
userId: string,
providerId: string,
providerType: string,
providerAccountId: string,
refreshToken: string,
accessToken: string,
accessTokenExpires: number,
) => Promise.resolve(),
createSession: (user: any) => Promise.resolve({}),
getSession: (sessionToken: string) => Promise.resolve({}),
updateSession: (session: any) => Promise.resolve(),
deleteSession: (sessionToken: string) => Promise.resolve(),
});
}
return {
getAdapter,
};
},
},
useSecureCookies: true,
cookies: {
@ -141,7 +135,7 @@ const allConfig = {
name: `__Secure-next-auth.session-token`,
options: {
httpOnly: true,
sameSite: 'foo',
sameSite: true as true,
path: '/',
secure: true,
},
@ -177,11 +171,6 @@ const baseContext = {
triggerEvent: false,
};
const pageContext = {
...baseContext,
ctx: { ...baseContext },
};
const githubProvider = {
id: '123',
name: 'github',
@ -204,22 +193,22 @@ const session = {
client.useSession();
// $ExpectType Promise<Session | null>
client.getSession(pageContext);
client.getSession({ req });
// $ExpectType Promise<Session | null>
client.session(pageContext);
client.session({ req });
// $ExpectType Promise<GetProvidersResponse | null>
client.getProviders(pageContext);
client.getProviders();
// $ExpectType Promise<GetProvidersResponse | null>
client.providers(pageContext);
client.providers();
// $ExpectType Promise<string | null>
client.getCsrfToken(pageContext);
client.getCsrfToken({ req });
// $ExpectType Promise<string | null>
client.csrfToken(pageContext);
client.csrfToken({ req });
// $ExpectType Promise<void>
client.signin('github', { data: 'foo' });
@ -234,7 +223,7 @@ client.signout({ callbackUrl: 'https://foo.com/callback' });
client.Provider({
session,
options: {
site: 'https://foo.com',
baseUrl: 'https://foo.com',
basePath: '/',
clientMaxAge: 1234,
},