diff --git a/README.md b/README.md index fc18b8b64e..111253b48b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ It is designed from the ground up to support Next.js and Serverless. npm install --save next-auth ``` -The easiest way to continue getting started, is to follow the [getting started](https://next-auth.js.org/getting-started/example) section in our docs. +The easiest way to continue getting started, is to follow the [getting started](https://next-auth.js.org/getting-started/example) section in our docs. We also have a section of [tutorials](https://next-auth.js.org/tutorials) for those looking for more specific examples. @@ -38,46 +38,42 @@ See [next-auth.js.org](https://next-auth.js.org) for more information and docume ### Flexible and easy to use -* Designed to work with any OAuth service, it supports OAuth 1.0, 1.0A and 2.0 -* Built-in support for [many popular sign-in services](https://next-auth.js.org/configuration/providers) -* Supports email / passwordless authentication -* Supports stateless authentication with any backend (Active Directory, LDAP, etc) -* Supports both JSON Web Tokens and database sessions -* Designed for Serverless but runs anywhere (AWS Lambda, Docker, Heroku, etc…) +- Designed to work with any OAuth service, it supports OAuth 1.0, 1.0A and 2.0 +- Built-in support for [many popular sign-in services](https://next-auth.js.org/configuration/providers) +- Supports email / passwordless authentication +- Supports stateless authentication with any backend (Active Directory, LDAP, etc) +- Supports both JSON Web Tokens and database sessions +- Designed for Serverless but runs anywhere (AWS Lambda, Docker, Heroku, etc…) ### Own your own data NextAuth.js can be used with or without a database. -* An open source solution that allows you to keep control of your data -* Supports Bring Your Own Database (BYOD) and can be used with any database -* Built-in support for [MySQL, MariaDB, Postgres, Microsoft SQL Server, MongoDB and SQLite](https://next-auth.js.org/configuration/databases) -* Works great with databases from popular hosting providers -* Can also be used *without a database* (e.g. OAuth + JWT) +- An open source solution that allows you to keep control of your data +- Supports Bring Your Own Database (BYOD) and can be used with any database +- Built-in support for [MySQL, MariaDB, Postgres, Microsoft SQL Server, MongoDB and SQLite](https://next-auth.js.org/configuration/databases) +- Works great with databases from popular hosting providers +- Can also be used _without a database_ (e.g. OAuth + JWT) ### Secure by default -* Promotes the use of passwordless sign in mechanisms -* Designed to be secure by default and encourage best practice for safeguarding user data -* Uses Cross Site Request Forgery Tokens on POST routes (sign in, sign out) -* Default cookie policy aims for the most restrictive policy appropriate for each cookie -* When JSON Web Tokens are enabled, they are signed by default (JWS) with HS512 -* Use JWT encryption (JWE) by setting the option `encryption: true` (defaults to A256GCM) -* Auto-generates symmetric signing and encryption keys for developer convenience -* Features tab/window syncing and keepalive messages to support short lived sessions -* Attempts to implement the latest guidance published by [Open Web Application Security Project](https://owasp.org/) +- Promotes the use of passwordless sign in mechanisms +- Designed to be secure by default and encourage best practice for safeguarding user data +- Uses Cross Site Request Forgery Tokens on POST routes (sign in, sign out) +- Default cookie policy aims for the most restrictive policy appropriate for each cookie +- When JSON Web Tokens are enabled, they are signed by default (JWS) with HS512 +- Use JWT encryption (JWE) by setting the option `encryption: true` (defaults to A256GCM) +- Auto-generates symmetric signing and encryption keys for developer convenience +- Features tab/window syncing and keepalive messages to support short lived sessions +- Attempts to implement the latest guidance published by [Open Web Application Security Project](https://owasp.org/) -Advanced options allow you to define your own routines to handle controlling what accounts are allowed to sign in, for encoding and decoding JSON Web Tokens and to set custom cookie security policies and session properties, so you can control who is able to sign in and how often sessions have to be re-validated. +Advanced options allow you to define your own routines to handle controlling what accounts are allowed to sign in, for encoding and decoding JSON Web Tokens and to set custom cookie security policies and session properties, so you can control who is able to sign in and how often sessions have to be re-validated. ### TypeScript -You can install the appropriate types via the following command: +Since version 3.2 Next Auth ships with TS type definitions colocated with the package. From that version on you no longer need to install `@types/next-auth`. -``` -npm install --save-dev @types/next-auth -``` - -As of now, TypeScript is a community effort. If you encounter any problems with the types package, please create an issue at [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/next-auth). Alternatively, you can open a pull request directly with your fixes there. We welcome anyone to start a discussion on migrating this package to TypeScript, or how to improve the TypeScript experience in general. +TypeScript support is a community effort. Please contribute pull-requests to improvement to the typings. ## Example @@ -92,42 +88,44 @@ export default NextAuth({ // OAuth authentication providers Providers.Apple({ clientId: process.env.APPLE_ID, - clientSecret: process.env.APPLE_SECRET + clientSecret: process.env.APPLE_SECRET, }), Providers.Google({ clientId: process.env.GOOGLE_ID, - clientSecret: process.env.GOOGLE_SECRET + clientSecret: process.env.GOOGLE_SECRET, }), // Sign in with passwordless email link Providers.Email({ server: process.env.MAIL_SERVER, - from: '' + from: '', }), ], // SQL or MongoDB database (or leave empty) - database: process.env.DATABASE_URL + database: process.env.DATABASE_URL, }) ``` ### Add React Component ```javascript -import { - useSession, signIn, signOut -} from 'next-auth/client' +import { useSession, signIn, signOut } from 'next-auth/client' export default function Component() { - const [ session, loading ] = useSession() - if(session) { - return <> - Signed in as {session.user.email}
- - + const [session, loading] = useSession() + if (session) { + return ( + <> + Signed in as {session.user.email}
+ + + ) } - return <> - Not signed in
- - + return ( + <> + Not signed in
+ + + ) } ``` diff --git a/_utils.d.ts b/_utils.d.ts new file mode 100644 index 0000000000..ebb5f4b287 --- /dev/null +++ b/_utils.d.ts @@ -0,0 +1,32 @@ +import { IncomingMessage, ServerResponse } from 'http' +import { User } from './index' + +interface GenericObject { + [key: string]: any +} + +interface NextApiRequest extends IncomingMessage, GenericObject { + query: { + [key: string]: string | string[] + } + cookies: { + [key: string]: string + } + body: any +} + +interface NextApiResponse extends ServerResponse, GenericObject { + send: Send + json: Send + status: (statusCode: number) => NextApiResponse +} + +type Send = (body: T) => void + +interface SessionBase { + user: User + accessToken?: string + expires: string +} + +export { GenericObject, SessionBase, NextApiRequest, NextApiResponse } diff --git a/adapters.d.ts b/adapters.d.ts new file mode 100644 index 0000000000..820f07d304 --- /dev/null +++ b/adapters.d.ts @@ -0,0 +1,242 @@ +import type { ConnectionOptions, EntitySchema } from 'typeorm' +import { AppOptions, User } from '.' +import { SessionProvider } from './client' + +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: SessionProvider +} + +export type EmailSessionProvider = SessionProvider & { + sendVerificationRequest: ( + params: SendVerificationRequestParams + ) => Promise + maxAge: number | undefined +} + +export interface AdapterInstance< + TUser, + TProfile, + TSession, + TVerificationRequest +> { + createUser: (profile: TProfile) => Promise + getUser: (id: string) => Promise + getUserByEmail: (email: string) => Promise + getUserByProviderAccountId: ( + providerId: string, + providerAccountId: string + ) => Promise + updateUser: (user: TUser) => Promise + linkAccount: ( + userId: string, + providerId: string, + providerType: string, + providerAccountId: string, + refreshToken: string, + accessToken: string, + accessTokenExpires: number + ) => Promise + createSession: (user: TUser) => Promise + getSession: (sessionToken: string) => Promise + updateSession: (session: TSession, force?: boolean) => Promise + deleteSession: (sessionToken: string) => Promise + createVerificationRequest?: ( + email: string, + url: string, + token: string, + secret: string, + provider: EmailSessionProvider, + options: AppOptions + ) => Promise + getVerificationRequest?: ( + email: string, + verificationToken: string, + secret: string, + provider: SessionProvider + ) => Promise + deleteVerificationRequest?: ( + email: string, + verificationToken: string, + secret: string, + provider: SessionProvider + ) => Promise +} + +interface Adapter< + TUser extends User = any, + TProfile extends Profile = any, + TSession extends Session = any, + TVerificationRequest extends VerificationRequest = any +> { + getAdapter: ( + appOptions: AppOptions + ) => Promise> +} + +type Schema = EntitySchema['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 + } + User?: { + model: U + schema: Schema + } + Session?: { + model: S + schema: Schema + } + VerificationRequest?: { + model: VR + schema: Schema + } + } + } + ) => Adapter + Models: { + Account: { + model: TypeORMAccountModel + schema: Schema + } + User: { + model: TypeORMUserModel + schema: Schema + } + Session: { + model: TypeORMSessionModel + schema: Schema + } + VerificationRequest: { + model: TypeORMVerificationRequestModel + schema: Schema + } + } +} + +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 +} diff --git a/client.d.ts b/client.d.ts new file mode 100644 index 0000000000..9b3baa0ee3 --- /dev/null +++ b/client.d.ts @@ -0,0 +1,79 @@ +import { IncomingMessage } from 'http' +import { FC } from 'react' +import { GenericObject, SessionBase } from './_utils' + +type Session = SessionBase & GenericObject + +interface GetProvidersResponse { + [provider: string]: SessionProvider +} + +interface SessionProvider extends GenericObject { + id: string + name: string + type: string + signinUrl: string + callbackUrl: string +} + +interface ContextProviderProps { + session: Session | null | undefined + options?: SetOptionsParams +} + +interface SetOptionsParams { + baseUrl?: string + basePath?: string + clientMaxAge?: number + keepAlive?: number +} + +type ContextProvider = FC + +interface NextContext { + req?: IncomingMessage + ctx?: { req: IncomingMessage } +} + +declare function useSession (): [Session | null | undefined, boolean] +declare function providers (): Promise +declare const getProviders: typeof providers +declare function session ( + context?: NextContext & { + triggerEvent?: boolean + } +): Promise +declare const getSession: typeof session +declare function csrfToken (context?: NextContext): Promise +declare const getCsrfToken: typeof csrfToken +declare function signin ( + provider?: string, + data?: GenericObject & { + callbackUrl?: string + } +): Promise +declare const signIn: typeof signin +declare function signout (data?: { callbackUrl?: string }): Promise +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, + Session, + SessionProvider +} diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000000..61f27ef056 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,152 @@ +/// + +import { ConnectionOptions } from 'typeorm' +import { Adapter } from './adapters' +import { SessionProvider } from './client' +import { JWTDecodeParams, JWTEncodeParams } from './jwt' +import { PossibleProviders } from './providers' +import { + GenericObject, + NextApiRequest, + NextApiResponse, + SessionBase +} from './_utils' + +interface InitOptions { + providers: ReadonlyArray> + database?: string | ConnectionOptions + secret?: string + session?: Session + jwt?: JWTOptions + pages?: PageOptions + callbacks?: Callbacks + debug?: boolean + adapter?: Adapter + events?: Events + useSecureCookies?: boolean + cookies?: Cookies +} + +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 + verifyRequest?: string + newUser?: string | null +} + +interface Cookies { + [cookieKey: string]: Cookie +} + +interface Cookie { + name: string + options?: CookieOptions +} + +interface CookieOptions { + httpOnly?: boolean + sameSite?: true | 'strict' | 'lax' | 'none' + path?: string + secure?: boolean + maxAge?: number + domain?: string +} + +interface Events { + signIn?: (message: any) => Promise + signOut?: (message: any) => Promise + createUser?: (message: any) => Promise + updateUser?: (message: any) => Promise + linkAccount?: (message: any) => Promise + session?: (message: any) => Promise + error?: (message: any) => Promise +} + +interface Session { + jwt?: boolean + maxAge?: number + updateAge?: number +} + +interface User { + name?: string | null + email?: string | null + image?: string | null +} + +interface JWTOptions { + secret?: string + maxAge?: number + encryption?: boolean + signingKey?: string + encryptionKey?: string + encode?: (options: JWTEncodeParams) => Promise + decode?: (options: JWTDecodeParams) => Promise +} + +// TODO: Improve callback typings +interface Callbacks { + signIn?: ( + user: User, + account: GenericObject, + profile: GenericObject + ) => Promise + redirect?: (url: string, baseUrl: string) => Promise + session?: (session: SessionBase, user: User) => Promise + jwt?: ( + token: GenericObject, + user: User, + account: GenericObject, + profile: GenericObject, + isNewUser: boolean + ) => Promise +} + +declare function NextAuth ( + req: NextApiRequest, + res: NextApiResponse, + options?: InitOptions +): Promise +export default NextAuth +export { + InitOptions, + AppOptions, + PageOptions, + Cookies, + Events, + Session, + JWTOptions, + User, + Callbacks +} diff --git a/jwt.d.ts b/jwt.d.ts new file mode 100644 index 0000000000..ea94f0cc6f --- /dev/null +++ b/jwt.d.ts @@ -0,0 +1,48 @@ +import jose from 'jose' +import { NextApiRequest } from './_utils' + +export interface JWTEncodeParams { + token?: object + maxAge?: number + secret: string | Buffer + signingKey?: string + signingOptions?: jose.JWT.SignOptions + encryptionKey?: string + encryptionOptions?: object + encryption?: boolean +} + +export interface JWTDecodeParams { + token?: string + maxAge?: number + secret: string | Buffer + signingKey?: string + verificationKey?: string + verificationOptions?: jose.JWT.VerifyOptions + encryptionKey?: string + decryptionKey?: string + decryptionOptions?: jose.JWE.DecryptOptions + encryption?: boolean +} + +declare function encode (args?: JWTEncodeParams): Promise +declare function decode ( + args?: JWTDecodeParams & { token: string } +): Promise + +declare function getToken ( + args?: { + req: NextApiRequest + secureCookie?: boolean + cookieName?: string + raw?: string + } & JWTDecodeParams +): Promise +declare function getToken (args?: { + req: NextApiRequest + secureCookie?: boolean + cookieName?: string + raw: true +}): Promise + +export { encode, decode, getToken } diff --git a/package.json b/package.json index b255dabb76..683e4499b0 100644 --- a/package.json +++ b/package.json @@ -35,10 +35,15 @@ "files": [ "dist", "index.js", + "index.d.ts", "providers.js", + "providers.d.ts", "adapters.js", + "adapters.d.ts", "client.js", - "jwt.js" + "client.d.ts", + "jwt.js", + "jwt.d.ts" ], "license": "ISC", "dependencies": { @@ -101,7 +106,8 @@ "project": "./tsconfig.json", "ignore": [ "test/", - "next-env.d.ts" + "next-env.d.ts", + "*.d.ts" ], "globals": [ "fetch" diff --git a/providers.d.ts b/providers.d.ts new file mode 100644 index 0000000000..0d98c27840 --- /dev/null +++ b/providers.d.ts @@ -0,0 +1,353 @@ +import { GenericObject } from './_utils' + +export interface Providers { + Apple: Apple + Auth0: Auth0 + Basecamp: Basecamp + BattleNet: BattleNet + Box: Box + Cognito: Cognito + Credentials: Credentials + Discord: Discord + Email: Email + Facebook: Facebook + GitHub: GitHub + GitLab: GitLab + Google: Google + IdentityServer4: IdentityServer4 + LinkedIn: LinkedIn + Mixer: Mixer + Okta: Okta + Reddit: Reddit + Slack: Slack + Spotify: Spotify + Twitch: Twitch + Twitter: Twitter + Yandex: Yandex +} + +type PossibleProviders = Providers[keyof Providers] + +// TODO: type return objects from providers properly +interface GenericReturnConfig { + [key: string]: any +} + +declare const Providers: Providers +export default Providers +export { PossibleProviders } + +/** + * Email + */ +type Email = (options: ProviderEmailOptions) => GenericReturnConfig + +interface VerificationRequestParams { + identifier: string + url: string + baseUrl: string + token: string + provider: ProviderEmailOptions +} + +interface ProviderEmailOptions extends GenericObject { + name?: string + server?: string | ProviderEmailServer + from?: string + maxAge?: number + sendVerificationRequest?: ( + options: VerificationRequestParams + ) => Promise +} + +interface ProviderEmailServer { + host: string + port: number + auth: ProviderEmailAuth +} + +interface ProviderEmailAuth { + user: string + pass: string +} + +/** + * Credentials + */ +type Credentials = (options: ProviderCredentialsOptions) => GenericReturnConfig + +interface ProviderCredentialsOptions { + id?: string + name: string + credentials: CredentialInput + authorize: ( + credentials: Record + ) => Promise +} + +interface CredentialInput { + [key: string]: { + label?: string + type?: string + value?: string + placeholder?: string + } +} + +/** + * Apple + */ +type Apple = (options: ProviderAppleOptions) => GenericReturnConfig + +interface ProviderAppleOptions extends GenericObject { + name?: string + clientId: string + clientSecret: ProviderAppleSecret +} + +interface ProviderAppleSecret extends GenericObject { + appleId: string + teamId: string + privateKey: string + keyId: string +} + +/** + * Twitter + */ +type Twitter = (options: ProviderTwitterOptions) => GenericReturnConfig + +interface ProviderTwitterOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +} + +/** + * Facebook + */ +type Facebook = (options: ProviderFacebookOptions) => GenericReturnConfig + +interface ProviderFacebookOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +} + +/** + * GitHub + */ +type GitHub = (options: ProviderGitHubOptions) => GenericReturnConfig + +interface ProviderGitHubOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string + scope?: string +} + +/** + * GitLab + */ +type GitLab = (options: ProviderGitLabOptions) => GenericReturnConfig + +interface ProviderGitLabOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +} + +/** + * Slack + */ +type Slack = (options: ProviderSlackOptions) => GenericReturnConfig + +interface ProviderSlackOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +} + +/** + * Google + */ +type Google = (options: ProviderGoogleOptions) => GenericReturnConfig + +interface ProviderGoogleOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string + authorizationUrl?: string +} + +/** + * Auth0 + */ +type Auth0 = (options: ProviderAuth0Options) => GenericReturnConfig + +interface Auth0Profile extends GenericObject { + sub: string + nickname: string + email: string + picture: string +} + +interface ProviderAuth0Options extends GenericObject { + name?: string + clientId: string + clientSecret: string + domain: string + profile?: (profile: Auth0Profile) => GenericObject +} + +/** + * IS4 + */ + +type IdentityServer4 = (options: ProviderIS4Options) => GenericReturnConfig + +interface ProviderIS4Options extends GenericObject { + id: string + name: string + scope: string + domain: string + clientId: string + clientSecret: string +} + +/** + * Discord + */ +type Discord = (options: ProviderDiscordOptions) => GenericReturnConfig + +interface ProviderDiscordOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +} + +/** + * Twitch + */ +type Twitch = (options: ProviderTwitchOptions) => GenericReturnConfig + +interface ProviderTwitchOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +} + +/** + * Mixer + */ +type Mixer = (options: ProviderMixerOptions) => GenericReturnConfig + +interface ProviderMixerOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +} + +/** + * Okta + */ +type Okta = (options: ProviderOktaOptions) => GenericReturnConfig + +interface ProviderOktaOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string + domain: string +} + +/** + * Battle.net + */ +type BattleNet = (options: ProviderBattleNetOptions) => GenericReturnConfig + +interface ProviderBattleNetOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string + region: string +} + +/** + * Box + */ +type Box = (options: ProviderBoxOptions) => GenericReturnConfig + +interface ProviderBoxOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +} + +/** + * Cognito + */ +type Cognito = (options: ProviderCognitoOptions) => GenericReturnConfig + +interface ProviderCognitoOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string + domain: string +} + +/** + * Yandex + */ +type Yandex = (options: ProviderYandexOptions) => GenericReturnConfig + +interface ProviderYandexOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +} + +/** + * LinkedIn + */ +type LinkedIn = (options: ProviderLinkedInOptions) => GenericReturnConfig + +interface ProviderLinkedInOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string + scope?: string +} + +/** + * Spotify + */ +type Spotify = (options: ProviderSpotifyOptions) => GenericReturnConfig + +interface ProviderSpotifyOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string + scope?: string +} + +/** + * Basecamp + */ +type Basecamp = (options: ProviderBasecampOptions) => GenericReturnConfig + +interface ProviderBasecampOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +} + +/** + * Reddit + */ +type Reddit = (options: ProviderRedditOptions) => GenericReturnConfig + +interface ProviderRedditOptions extends GenericObject { + name?: string + clientId: string + clientSecret: string +}