Skip to content

Commit b052d4c

Browse files
fix(providers): make string endpoint handlers overrideable (#2842)
* chore: remove `console.log` * chore(ts): improve `InternalProvider` type * refactor(ts): convert some files to TypeScript * fix(providers): make string endpoint handlers overrideable
1 parent 5066726 commit b052d4c

File tree

10 files changed

+163
-98
lines changed

10 files changed

+163
-98
lines changed

src/lib/types.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,38 @@ import type {
1111
Awaitable,
1212
} from ".."
1313

14-
import type { Provider } from "../providers"
14+
import type {
15+
OAuthConfig,
16+
EmailConfig,
17+
CredentialsConfig,
18+
AuthorizationEndpointHandler,
19+
TokenEndpointHandler,
20+
UserinfoEndpointHandler,
21+
ProviderType,
22+
} from "../providers"
1523
import type { JWTOptions } from "../jwt"
1624
import type { Adapter } from "../adapters"
1725

1826
// Below are types that are only supposed be used by next-auth internally
1927

2028
/** @internal */
21-
export type InternalProvider = Provider & {
29+
export type InternalProvider<T extends ProviderType = any> = (T extends "oauth"
30+
? Omit<OAuthConfig<any>, "authorization" | "token" | "userinfo"> & {
31+
authorization: AuthorizationEndpointHandler
32+
token: TokenEndpointHandler
33+
userinfo: UserinfoEndpointHandler
34+
}
35+
: T extends "email"
36+
? EmailConfig
37+
: T extends "credentials"
38+
? CredentialsConfig
39+
: never) & {
2240
signinUrl: string
2341
callbackUrl: string
2442
}
2543

2644
/** @internal */
27-
export interface InternalOptions<
28-
P extends InternalProvider = InternalProvider
29-
> {
45+
export interface InternalOptions<T extends ProviderType = any> {
3046
providers: InternalProvider[]
3147
baseUrl: string
3248
basePath: string
@@ -39,7 +55,9 @@ export interface InternalOptions<
3955
| "callback"
4056
| "verify-request"
4157
| "error"
42-
provider: P
58+
provider: T extends string
59+
? InternalProvider<T>
60+
: InternalProvider<T> | undefined
4361
csrfToken?: string
4462
csrfTokenVerified?: boolean
4563
secret: string

src/providers/email.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ export default function Email(options: EmailUserConfig): EmailConfig {
7272
provider: { server, from },
7373
}) {
7474
const { host } = new URL(url)
75-
console.log(server)
7675
const transport = createTransport(server)
7776
await transport.sendMail({
7877
to: email,

src/providers/oauth.ts

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,39 @@ interface AdvancedEndpointHandler<P extends UrlParams, C, R> {
5050
}
5151

5252
/** Either an URL (containing all the parameters) or an object with more granular control. */
53-
type EndpointHandler<P extends UrlParams, C = any, R = any> =
54-
| string
55-
| AdvancedEndpointHandler<P, C, R>
53+
export type EndpointHandler<
54+
P extends UrlParams,
55+
C = any,
56+
R = any
57+
> = AdvancedEndpointHandler<P, C, R>
58+
59+
export type AuthorizationEndpointHandler =
60+
EndpointHandler<AuthorizationParameters>
61+
62+
export type TokenEndpointHandler = EndpointHandler<
63+
UrlParams,
64+
{
65+
/**
66+
* Parameters extracted from the request to the `/api/auth/callback/:providerId` endpoint.
67+
* Contains params like `state`.
68+
*/
69+
params: CallbackParamsType
70+
/**
71+
* When using this custom flow, make sure to do all the necessary security checks.
72+
* Thist object contains parameters you have to match against the request to make sure it is valid.
73+
*/
74+
checks: OAuthChecks
75+
},
76+
{
77+
tokens: TokenSet
78+
}
79+
>
80+
81+
export type UserinfoEndpointHandler = EndpointHandler<
82+
UrlParams,
83+
{ tokens: TokenSet },
84+
Profile
85+
>
5686

5787
export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
5888
/**
@@ -70,40 +100,11 @@ export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
70100
*
71101
* [Authorization endpoint](https://datatracker.ietf.org/doc/html/rfc6749#section-3.1)
72102
*/
73-
authorization?: EndpointHandler<AuthorizationParameters>
74-
/**
75-
* Endpoint that returns OAuth 2/OIDC tokens and information about them.
76-
* This includes `access_token`, `id_token`, `refresh_token`, etc.
77-
*
78-
* [Token endpoint](https://datatracker.ietf.org/doc/html/rfc6749#section-3.2)
79-
*/
80-
token?: EndpointHandler<
81-
UrlParams,
82-
{
83-
/**
84-
* Parameters extracted from the request to the `/api/auth/callback/:providerId` endpoint.
85-
* Contains params like `state`.
86-
*/
87-
params: CallbackParamsType
88-
/**
89-
* When using this custom flow, make sure to do all the necessary security checks.
90-
* Thist object contains parameters you have to match against the request to make sure it is valid.
91-
*/
92-
checks: OAuthChecks
93-
},
94-
{ tokens: TokenSet }
95-
>
96-
/**
97-
* When using an OAuth 2 provider, the user information must be requested
98-
* through an additional request from the userinfo endpoint.
99-
*
100-
* [Userinfo endpoint](https://www.oauth.com/oauth2-servers/signing-in-with-google/verifying-the-user-info)
101-
*/
102-
userinfo?: EndpointHandler<UrlParams, { tokens: TokenSet }, Profile>
103+
authorization?: string | AuthorizationEndpointHandler
104+
token?: string | TokenEndpointHandler
105+
userinfo?: string | UserinfoEndpointHandler
103106
type: "oauth"
104107
version?: string
105-
accessTokenUrl?: string
106-
requestTokenUrl?: string
107108
profile?: (profile: P, tokens: TokenSet) => Awaitable<User & { id: string }>
108109
checks?: ChecksType | ChecksType[]
109110
clientId?: string
@@ -133,6 +134,11 @@ export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
133134
* with the default configuration.
134135
*/
135136
options?: OAuthUserConfig<P>
137+
138+
// These are kept around for backwards compatibility with OAuth 1.x
139+
accessTokenUrl?: string
140+
requestTokenUrl?: string
141+
encoding?: string
136142
}
137143

138144
export type OAuthUserConfig<P> = Omit<

src/providers/spotify.js renamed to src/providers/spotify.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
1-
/** @type {import(".").OAuthProvider} */
2-
export default function Spotify(options) {
1+
import { OAuthConfig, OAuthUserConfig } from "."
2+
3+
export interface SpotifyImage {
4+
url: string
5+
}
6+
7+
export interface SpotifyProfile {
8+
id: string
9+
display_name: string
10+
email: string
11+
images: SpotifyImage[]
12+
}
13+
export default function Spotify<P extends Record<string, any> = SpotifyProfile>(
14+
options: OAuthUserConfig<P>
15+
): OAuthConfig<P> {
316
return {
417
id: "spotify",
518
name: "Spotify",

src/server/index.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,33 +73,23 @@ async function NextAuthHandler(
7373

7474
const secret = createSecret({ userOptions, basePath, baseUrl })
7575

76-
const providers = parseProviders({
76+
const { providers, provider } = parseProviders({
7777
providers: userOptions.providers,
7878
base: `${baseUrl}${basePath}`,
79+
providerId: providerId as string | undefined,
7980
})
8081

81-
const provider = providers.find(({ id }) => id === providerId)
82-
83-
// Checks only work on OAuth 2.x + OIDC providers
84-
if (
85-
provider?.type === "oauth" &&
86-
!provider.version?.startsWith("1.") &&
87-
!provider.checks
88-
) {
89-
provider.checks = ["state"]
90-
}
91-
9282
const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle by default
9383

9484
// User provided options are overriden by other options,
9585
// except for the options with special handling above
96-
const options: InternalOptions<any> = {
86+
const options: InternalOptions = {
9787
debug: false,
9888
pages: {},
9989
theme: {
10090
colorScheme: "auto",
101-
logo: '',
102-
brandColor: ''
91+
logo: "",
92+
brandColor: "",
10393
},
10494
// Custom options override defaults
10595
...userOptions,

src/server/lib/email/signin.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { randomBytes } from "crypto"
2-
import { EmailConfig } from "src/providers"
3-
import { InternalOptions, InternalProvider } from "src/lib/types"
2+
import { InternalOptions } from "src/lib/types"
43
import { hashToken } from "../utils"
54

65
/**
@@ -9,7 +8,7 @@ import { hashToken } from "../utils"
98
*/
109
export default async function email(
1110
identifier: string,
12-
options: InternalOptions<EmailConfig & InternalProvider>
11+
options: InternalOptions<"email">
1312
) {
1413
const { baseUrl, basePath, adapter, provider, logger, callbackUrl } = options
1514

src/server/lib/oauth/client-legacy.js renamed to src/server/lib/oauth/client-legacy.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@
22
// We have the intentions to provide only minor fixes for this in the future.
33

44
import { OAuth } from "oauth"
5+
import { InternalOptions } from "src/lib/types"
56

67
/**
78
* Client supporting OAuth 1.x
8-
* @param {import("src/lib/types").InternalOptions} options
99
*/
10-
export function oAuth1Client(options) {
11-
/** @type {import("src/providers").OAuthConfig} */
10+
export function oAuth1Client(options: InternalOptions<"oauth">) {
1211
const provider = options.provider
1312

1413
const oauth1Client = new OAuth(
15-
provider.requestTokenUrl,
16-
provider.accessTokenUrl,
17-
provider.clientId,
18-
provider.clientSecret,
19-
provider.version || "1.0",
14+
provider.requestTokenUrl as string,
15+
provider.accessTokenUrl as string,
16+
provider.clientId as string,
17+
provider.clientSecret as string,
18+
provider.version ?? "1.0",
2019
provider.callbackUrl,
21-
provider.encoding || "HMAC-SHA1"
20+
provider.encoding ?? "HMAC-SHA1"
2221
)
2322

2423
// Promisify get() for OAuth1
2524
const originalGet = oauth1Client.get.bind(oauth1Client)
26-
oauth1Client.get = (...args) => {
27-
return new Promise((resolve, reject) => {
25+
// @ts-expect-error
26+
oauth1Client.get = async (...args) => {
27+
return await new Promise((resolve, reject) => {
2828
originalGet(...args, (error, result) => {
2929
if (error) {
3030
return reject(error)
@@ -36,8 +36,8 @@ export function oAuth1Client(options) {
3636
// Promisify getOAuth1AccessToken() for OAuth1
3737
const originalGetOAuth1AccessToken =
3838
oauth1Client.getOAuthAccessToken.bind(oauth1Client)
39-
oauth1Client.getOAuthAccessToken = (...args) => {
40-
return new Promise((resolve, reject) => {
39+
oauth1Client.getOAuthAccessToken = async (...args: any[]) => {
40+
return await new Promise((resolve, reject) => {
4141
originalGetOAuth1AccessToken(
4242
...args,
4343
(error, oauth_token, oauth_token_secret) => {
@@ -52,8 +52,8 @@ export function oAuth1Client(options) {
5252

5353
const originalGetOAuthRequestToken =
5454
oauth1Client.getOAuthRequestToken.bind(oauth1Client)
55-
oauth1Client.getOAuthRequestToken = (params = {}) => {
56-
return new Promise((resolve, reject) => {
55+
oauth1Client.getOAuthRequestToken = async (params = {}) => {
56+
return await new Promise((resolve, reject) => {
5757
originalGetOAuthRequestToken(
5858
params,
5959
(error, oauth_token, oauth_token_secret, params) => {

src/server/lib/oauth/client.js renamed to src/server/lib/oauth/client.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
import { Issuer } from "openid-client"
2+
import { InternalOptions } from "src/lib/types"
23

34
/**
45
* NOTE: We can add auto discovery of the provider's endpoint
56
* that requires only one endpoint to be specified by the user.
67
* Check out `Issuer.discover`
78
*
89
* Client supporting OAuth 2.x and OIDC
9-
* @param {import("src/lib/types").InternalOptions} options
1010
*/
11-
export async function openidClient(options) {
12-
/** @type {import("src/providers").OAuthConfig} */
11+
export async function openidClient(options: InternalOptions<"oauth">) {
1312
const provider = options.provider
1413

1514
let issuer
1615
if (provider.wellKnown) {
1716
issuer = await Issuer.discover(provider.wellKnown)
1817
} else {
1918
issuer = new Issuer({
20-
issuer: provider.issuer,
21-
authorization_endpoint:
22-
provider.authorization.url ?? provider.authorization,
23-
token_endpoint: provider.token.url ?? provider.token,
24-
userinfo_endpoint: provider.userinfo.url ?? provider.userinfo,
19+
issuer: provider.issuer as string,
20+
authorization_endpoint: provider.authorization.url,
21+
token_endpoint: provider.token.url,
22+
userinfo_endpoint: provider.userinfo.url,
2523
})
2624
}
2725

0 commit comments

Comments
 (0)