Skip to content

Commit 1c9bcdd

Browse files
fix: improve client-side submodules (#12249)
* fix: improve TS and allow `redirect: false` for client `signIn` + OAuth * drop auto-generated file * fix solid-start signIn * fix sveltekit * more tweak sveltekit
1 parent 846c9f3 commit 1c9bcdd

File tree

20 files changed

+513
-385
lines changed

20 files changed

+513
-385
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ docs/.next
8787
docs/manifest.mjs
8888

8989
# Core
90-
packages/core/src/providers/oauth-types.ts
90+
packages/core/src/providers/provider-types.ts
9191
packages/core/lib
9292
packages/core/providers
9393
packages/core/src/lib/pages/styles.ts

.prettierignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ packages/**/*.js
3232
!packages/*/scripts/*.js
3333

3434
# @auth/core
35-
packages/core/src/providers/oauth-types.ts
35+
packages/core/src/providers/provider-types.ts
3636
packages/core/src/lib/pages/styles.ts
3737

3838
# @auth/sveltekit
@@ -43,7 +43,7 @@ packages/frameworks-sveltekit/vite.config.{js,ts}.timestamp-*
4343
packages/frameworks-express/providers
4444

4545
# next-auth
46-
packages/next-auth/src/providers/oauth-types.ts
46+
packages/next-auth/src/providers/provider-types.ts
4747
packages/next-auth/css/index.css
4848

4949
# Adapters

apps/dev/nextjs/app/client.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ export default function Client() {
3434
) : (
3535
<>
3636
<button onClick={() => signIn("github")}>Sign in GitHub</button>
37-
<button onClick={() => signIn("credentials", {})}>
37+
<button
38+
onClick={async () => {
39+
await signIn("webauthn", {})
40+
}}
41+
>
3842
Sign in Credentials
3943
</button>
4044
</>

apps/dev/nextjs/auth.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import NextAuth from "next-auth"
2-
import type { NextAuthConfig } from "next-auth"
32
import Credentials from "next-auth/providers/credentials"
43
import Keycloak from "next-auth/providers/keycloak"
4+
import GitHub from "next-auth/providers/github"
55

66
// import { PrismaClient } from "@prisma/client"
77
// import { PrismaAdapter } from "@auth/prisma-adapter"
@@ -66,8 +66,10 @@ export const { handlers, auth, signIn, signOut, unstable_update } = NextAuth({
6666
}
6767
},
6868
}),
69+
GitHub,
6970
Keycloak,
7071
],
72+
7173
callbacks: {
7274
jwt({ token, trigger, session }) {
7375
if (trigger === "update") token.name = session.user.name

eslint.config.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export default tsEslint.config(
164164
"static",
165165
"coverage",
166166
"dist",
167-
"packages/core/src/providers/oauth-types.ts",
167+
"packages/core/src/providers/provider-types.ts",
168168
"packages/core/src/lib/pages/styles.ts",
169169
"packages/frameworks-sveltekit/package",
170170
"packages/frameworks-sveltekit/vite.config.{js,ts}.timestamp-*",

packages/core/scripts/generate-providers.js

+45-12
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,50 @@ const providersPath = join(process.cwd(), "src/providers")
55

66
const files = readdirSync(providersPath, "utf8")
77

8-
const nonOAuthFile = ["oauth-types", "oauth", "index", "email", "credentials"]
9-
const providers = files
10-
.map((file) => {
11-
const strippedProviderName = file.substring(0, file.indexOf("."))
12-
return `"${strippedProviderName}"`
13-
})
14-
.filter((provider) => !nonOAuthFile.includes(provider.replace(/"/g, "")))
15-
16-
const result = `
8+
// TODO: Autogenerate
9+
const emailProvidersFile = [
10+
"email",
11+
"forwardemail",
12+
"mailgun",
13+
"nodemailer",
14+
"passkey",
15+
"postmark",
16+
"resend",
17+
"sendgrid",
18+
]
19+
20+
// TODO: Autogenerate
21+
const nonOAuthFile = [
22+
"provider-types",
23+
"oauth",
24+
"index",
25+
// Credentials
26+
"credentials",
27+
// Webauthn
28+
"webauthn",
29+
// Email providers
30+
...emailProvidersFile,
31+
]
32+
33+
const providers = files.map((file) => {
34+
const strippedProviderName = file.substring(0, file.indexOf("."))
35+
return `"${strippedProviderName}"`
36+
})
37+
38+
const oauthProviders = providers.filter(
39+
(provider) => !nonOAuthFile.includes(provider.replace(/"/g, ""))
40+
)
41+
42+
const emailProviders = providers.filter((provider) =>
43+
emailProvidersFile.includes(provider.replace(/"/g, ""))
44+
)
45+
46+
const content = `
1747
// THIS FILE IS AUTOGENERATED. DO NOT EDIT.
18-
export type OAuthProviderType =
19-
| ${providers.join("\n | ")}`
48+
export type OAuthProviderId =
49+
| ${oauthProviders.join("\n | ")}
50+
51+
export type EmailProviderId =
52+
| ${emailProviders.join("\n | ")}`
2053

21-
writeFileSync(join(providersPath, "oauth-types.ts"), result)
54+
writeFileSync(join(providersPath, "provider-types.ts"), content)

packages/core/src/providers/credentials.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export interface CredentialsConfig<
7474
) => Awaitable<User | null>
7575
}
7676

77-
export type CredentialsProviderType = "Credentials"
77+
export type CredentialsProviderId = "credentials"
7878

7979
/**
8080
* The Credentials provider allows you to handle signing in with arbitrary credentials,

packages/core/src/providers/email.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { CommonProviderOptions } from "./index.js"
22
import type { Awaitable, Theme } from "../types.js"
3+
export type { EmailProviderId } from "./provider-types.js"
34

45
// TODO: Kepts for backwards compatibility
56
// Remove this import and encourage users
@@ -22,13 +23,9 @@ export default function Email(config: NodemailerUserConfig): NodemailerConfig {
2223
}
2324
}
2425

25-
// TODO: Rename to Token provider
26-
// when started working on https://github.com/nextauthjs/next-auth/discussions/1465
27-
export type EmailProviderType = "email"
28-
2926
export interface EmailConfig extends CommonProviderOptions {
3027
id: string
31-
type: EmailProviderType
28+
type: "email"
3229
name: string
3330
from?: string
3431
maxAge?: number

packages/core/src/providers/index.ts

+11-14
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import type { Profile } from "../types.js"
22
import CredentialsProvider from "./credentials.js"
3-
import type {
4-
CredentialsConfig,
5-
CredentialsProviderType,
6-
} from "./credentials.js"
3+
import type { CredentialsConfig, CredentialsProviderId } from "./credentials.js"
74
import type EmailProvider from "./email.js"
8-
import type { EmailConfig, EmailProviderType } from "./email.js"
5+
import type { EmailConfig, EmailProviderId } from "./email.js"
96
import type {
107
OAuth2Config,
118
OAuthConfig,
12-
OAuthProviderType,
9+
OAuthProviderId,
1310
OIDCConfig,
1411
} from "./oauth.js"
1512
import type { WebAuthnConfig, WebAuthnProviderType } from "./webauthn.js"
@@ -91,11 +88,11 @@ export type Provider<P extends Profile = any> = (
9188
InternalProviderOptions
9289

9390
export type BuiltInProviders = Record<
94-
OAuthProviderType,
91+
OAuthProviderId,
9592
(config: Partial<OAuthConfig<any>>) => OAuthConfig<any>
9693
> &
97-
Record<CredentialsProviderType, typeof CredentialsProvider> &
98-
Record<EmailProviderType, typeof EmailProvider> &
94+
Record<CredentialsProviderId, typeof CredentialsProvider> &
95+
Record<EmailProviderId, typeof EmailProvider> &
9996
Record<
10097
WebAuthnProviderType,
10198
(config: Partial<WebAuthnConfig>) => WebAuthnConfig
@@ -110,9 +107,9 @@ export interface AppProvider extends CommonProviderOptions {
110107
callbackUrl: string
111108
}
112109

113-
export type RedirectableProviderType = "email" | "credentials"
114-
115-
export type BuiltInProviderType =
116-
| RedirectableProviderType
117-
| OAuthProviderType
110+
export type ProviderId =
111+
| CredentialsProviderId
112+
| EmailProviderId
113+
| OAuthProviderId
118114
| WebAuthnProviderType
115+
| (string & {}) // HACK: To allow user-defined providers in `signIn`

packages/core/src/providers/oauth.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type IssuerMetadata = any
1111
type OAuthCallbackChecks = any
1212
type OpenIDCallbackChecks = any
1313

14-
export type { OAuthProviderType } from "./oauth-types.js"
14+
export type { OAuthProviderId } from "./provider-types.js"
1515

1616
export type OAuthChecks = OpenIDCallbackChecks | OAuthCallbackChecks
1717

packages/frameworks-solid-start/src/client.ts

+84-51
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
1-
import type {
2-
BuiltInProviderType,
3-
RedirectableProviderType,
4-
} from "@auth/core/providers"
1+
import type { ProviderId } from "@auth/core/providers"
52

6-
type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>)
7-
8-
interface SignInOptions extends Record<string, unknown> {
3+
interface SignInOptions<Redirect extends boolean = true>
4+
extends Record<string, unknown> {
5+
/** @deprecated Use `redirectTo` instead. */
6+
callbackUrl?: string
97
/**
10-
* Specify to which URL the user will be redirected after signing in. Defaults to the page URL the sign-in is initiated from.
8+
* Specify where the user should be redirected to after a successful signin.
119
*
12-
* [Documentation](https://next-auth.js.org/getting-started/client#specifying-a-callbackurl)
10+
* By default, it is the page the sign-in was initiated from.
1311
*/
14-
callbackUrl?: string
15-
/** [Documentation](https://next-auth.js.org/getting-started/client#using-the-redirect-false-option) */
16-
redirect?: boolean
12+
redirectTo?: string
13+
/**
14+
* You might want to deal with the signin response on the same page, instead of redirecting to another page.
15+
* For example, if an error occurs (like wrong credentials given by the user), you might want to show an inline error message on the input field.
16+
*
17+
* For this purpose, you can set this to option `redirect: false`.
18+
*/
19+
redirect?: Redirect
20+
}
21+
22+
export interface SignInResponse {
23+
error: string | undefined
24+
code: string | undefined
25+
status: number
26+
ok: boolean
27+
url: string | null
1728
}
1829

1930
interface SignOutParams<R extends boolean = true> {
@@ -41,59 +52,81 @@ export type SignInAuthorizationParams =
4152
* signIn("provider") // example: signIn("github")
4253
* ```
4354
*/
44-
export async function signIn<
45-
P extends RedirectableProviderType | undefined = undefined,
46-
>(
47-
providerId?: LiteralUnion<
48-
P extends RedirectableProviderType
49-
? P | BuiltInProviderType
50-
: BuiltInProviderType
51-
>,
52-
options?: SignInOptions,
55+
56+
/**
57+
* Initiates a signin flow or sends the user to the signin page listing all possible providers.
58+
* Handles CSRF protection.
59+
*
60+
* @note This method can only be used from Client Components ("use client" or Pages Router).
61+
* For Server Actions, use the `signIn` method imported from the `auth` config.
62+
*/
63+
export async function signIn(
64+
provider?: ProviderId,
65+
options?: SignInOptions<true>,
5366
authorizationParams?: SignInAuthorizationParams
54-
) {
55-
const { callbackUrl = window.location.href, redirect = true } = options ?? {}
67+
): Promise<void>
68+
export async function signIn(
69+
provider?: ProviderId,
70+
options?: SignInOptions<false>,
71+
authorizationParams?: SignInAuthorizationParams
72+
): Promise<SignInResponse>
73+
export async function signIn<Redirect extends boolean = true>(
74+
provider?: ProviderId,
75+
options?: SignInOptions<Redirect>,
76+
authorizationParams?: SignInAuthorizationParams
77+
): Promise<SignInResponse | void> {
78+
const { callbackUrl, ...rest } = options ?? {}
79+
const {
80+
redirect = true,
81+
redirectTo = callbackUrl ?? window.location.href,
82+
...signInParams
83+
} = rest
5684

57-
// TODO: Support custom providers
58-
const isCredentials = providerId === "credentials"
59-
const isEmail = providerId === "email"
60-
const isSupportingReturn = isCredentials || isEmail
85+
const isCredentials = provider === "credentials"
6186

62-
// TODO: Handle custom base path
6387
const signInUrl = `/api/auth/${
6488
isCredentials ? "callback" : "signin"
65-
}/${providerId}`
66-
67-
const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
89+
}/${provider}`
6890

6991
// TODO: Handle custom base path
7092
const csrfTokenResponse = await fetch("/api/auth/csrf")
7193
const { csrfToken } = await csrfTokenResponse.json()
94+
const res = await fetch(
95+
`${signInUrl}?${new URLSearchParams(authorizationParams)}`,
96+
{
97+
method: "post",
98+
headers: {
99+
"Content-Type": "application/x-www-form-urlencoded",
100+
"X-Auth-Return-Redirect": "1",
101+
},
102+
body: new URLSearchParams({
103+
...signInParams,
104+
csrfToken,
105+
callbackUrl: redirectTo,
106+
}),
107+
}
108+
)
72109

73-
const res = await fetch(_signInUrl, {
74-
method: "post",
75-
headers: {
76-
"Content-Type": "application/x-www-form-urlencoded",
77-
"X-Auth-Return-Redirect": "1",
78-
},
79-
// @ts-ignore
80-
body: new URLSearchParams({
81-
...options,
82-
csrfToken,
83-
callbackUrl,
84-
}),
85-
})
110+
const data = await res.json()
86111

87-
const data = await res.clone().json()
88-
const error = new URL(data.url).searchParams.get("error")
89-
if (redirect || !isSupportingReturn || !error) {
90-
// TODO: Do not redirect for Credentials and Email providers by default in next major
91-
window.location.href = data.url ?? data.redirect ?? callbackUrl
112+
if (redirect) {
113+
const url = data.url ?? redirectTo
114+
window.location.href = url
92115
// If url contains a hash, the browser does not reload the page. We reload manually
93-
if (data.url.includes("#")) window.location.reload()
116+
if (url.includes("#")) window.location.reload()
94117
return
95118
}
96-
return res
119+
120+
const error = new URL(data.url).searchParams.get("error") ?? undefined
121+
const code = new URL(data.url).searchParams.get("code") ?? undefined
122+
123+
return {
124+
error,
125+
code,
126+
status: res.status,
127+
ok: res.ok,
128+
url: error ? null : data.url,
129+
}
97130
}
98131

99132
/**

0 commit comments

Comments
 (0)