Skip to content

Commit 87b037f

Browse files
committed
feat: add default user id generation (#9380)
* fix: add default user id generation * refactor: simplify `createUser` in adapters * fix typeorm * fix next-auth * fix tests * revert adapter changes * revert * revert * simplify adapter util * fix build errors
1 parent a1b2282 commit 87b037f

File tree

12 files changed

+52
-39
lines changed

12 files changed

+52
-39
lines changed

packages/adapter-neo4j/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export function Neo4jAdapter(session: Session): Adapter {
130130

131131
return {
132132
async createUser(data) {
133-
const user = { id: crypto.randomUUID(), ...data }
133+
const user = { ...data, id: crypto.randomUUID() }
134134
await write(`CREATE (u:User $data)`, user)
135135
return user
136136
},

packages/adapter-typeorm/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,6 @@ export function TypeORMAdapter(
311311
const m = await getManager(c)
312312
await m.connection.close()
313313
},
314-
// @ts-expect-error
315314
createUser: async (data) => {
316315
const m = await getManager(c)
317316
const user = await m.save(UserEntityName, data)
@@ -382,7 +381,11 @@ export function TypeORMAdapter(
382381
},
383382
async updateSession(data) {
384383
const m = await getManager(c)
385-
await m.update(SessionEntityName, { sessionToken: data.sessionToken }, data)
384+
await m.update(
385+
SessionEntityName,
386+
{ sessionToken: data.sessionToken },
387+
data
388+
)
386389
// TODO: Try to return?
387390
return null
388391
},

packages/core/src/adapters.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ export interface VerificationToken {
263263
* :::
264264
*/
265265
export interface Adapter {
266-
createUser?(user: Omit<AdapterUser, "id">): Awaitable<AdapterUser>
266+
createUser?(user: AdapterUser): Awaitable<AdapterUser>
267267
getUser?(id: string): Awaitable<AdapterUser | null>
268268
getUserByEmail?(email: string): Awaitable<AdapterUser | null>
269269
/** Using the provider id and the id of the user for a specific account, get the user. */

packages/core/src/lib/actions/callback/handle-login.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,8 @@ export async function handleLoginOrRegister(
106106
user = await updateUser({ id: userByEmail.id, emailVerified: new Date() })
107107
await events.updateUser?.({ user })
108108
} else {
109-
const { id: _, ...newUser } = { ...profile, emailVerified: new Date() }
110109
// Create user account if there isn't one for the email address already
111-
user = await createUser(newUser)
110+
user = await createUser({ ...profile, emailVerified: new Date() })
112111
await events.createUser?.({ user })
113112
isNewUser = true
114113
}
@@ -217,8 +216,7 @@ export async function handleLoginOrRegister(
217216
// If no account matching the same [provider].id or .email exists, we can
218217
// create a new account for the user, link it to the OAuth account and
219218
// create a new session for them so they are signed in with it.
220-
const { id: _, ...newUser } = { ...profile, emailVerified: null }
221-
user = await createUser(newUser)
219+
user = await createUser({ ...profile, emailVerified: null })
222220
}
223221
await events.createUser?.({ user })
224222

packages/core/src/lib/actions/callback/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ export async function callback(
203203
if (invalidInvite) throw new Verification({ hasInvite, expired })
204204

205205
const user = (await adapter!.getUserByEmail(identifier)) ?? {
206-
id: identifier,
206+
id: crypto.randomUUID(),
207207
email: identifier,
208208
emailVerified: null,
209209
}
@@ -296,11 +296,15 @@ export async function callback(
296296
Object.entries(query ?? {}).forEach(([k, v]) =>
297297
url.searchParams.set(k, v)
298298
)
299-
const user = await provider.authorize(
299+
const userFromAuthorize = await provider.authorize(
300300
credentials,
301301
// prettier-ignore
302302
new Request(url, { headers, method, body: JSON.stringify(body) })
303303
)
304+
const user = userFromAuthorize && {
305+
...userFromAuthorize,
306+
id: userFromAuthorize?.id?.toString() ?? crypto.randomUUID(),
307+
}
304308

305309
if (!user) throw new CredentialsSignin()
306310

@@ -319,7 +323,7 @@ export async function callback(
319323
name: user.name,
320324
email: user.email,
321325
picture: user.image,
322-
sub: user.id?.toString(),
326+
sub: user.id,
323327
}
324328

325329
const token = await callbacks.jwt({

packages/core/src/lib/actions/callback/oauth/callback.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
Profile,
1313
RequestInternal,
1414
TokenSet,
15+
User,
1516
} from "../../../../types.js"
1617
import type { OAuthConfigInternal } from "../../../../providers/index.js"
1718
import type { Cookie } from "../../../utils/cookie.js"
@@ -193,14 +194,12 @@ async function getUserAndAccount(
193194
logger: LoggerInstance
194195
) {
195196
try {
196-
const user = await provider.profile(OAuthProfile, tokens)
197-
user.email = user.email?.toLowerCase()
198-
199-
if (!user.id) {
200-
throw new TypeError(
201-
`User id is missing in ${provider.name} OAuth profile response`
202-
)
203-
}
197+
const userFromProfile = await provider.profile(OAuthProfile, tokens)
198+
const user = {
199+
...userFromProfile,
200+
id: userFromProfile.id?.toString() ?? crypto.randomUUID(),
201+
email: userFromProfile.email?.toLowerCase(),
202+
} satisfies User
204203

205204
return {
206205
user,

packages/core/src/lib/actions/session.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ export async function session(
126126
// By default, only exposes a limited subset of information to the client
127127
// as needed for presentation purposes (e.g. "you are logged in as...").
128128
session: {
129-
// @ts-expect-error missing `id`.
130129
user: { name: user.name, email: user.email, image: user.image },
131130
expires: session.expires.toISOString(),
132131
},

packages/core/src/lib/actions/signin/send-token.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { AuthorizedCallbackError } from "../../../errors.js"
33

44
import type { InternalOptions, RequestInternal } from "../../../types.js"
55
import type { Account } from "../../../types.js"
6-
import type { AdapterUser } from "../../../adapters.js"
76

87
/**
98
* Starts an e-mail login flow, by generating a token,
@@ -19,21 +18,23 @@ export async function sendToken(
1918
const normalizer = provider.normalizeIdentifier ?? defaultNormalizer
2019
const email = normalizer(body?.email)
2120

22-
const defaultUser = { id: email, email, emailVerified: null }
23-
const user = ((await adapter!.getUserByEmail(email)) ??
24-
defaultUser) satisfies AdapterUser
21+
const defaultUser = { id: crypto.randomUUID(), email, emailVerified: null }
22+
const user = (await adapter!.getUserByEmail(email)) ?? defaultUser
2523

26-
const account: Account = {
24+
const account = {
2725
providerAccountId: email,
2826
userId: user.id,
2927
type: "email",
3028
provider: provider.id,
31-
}
29+
} satisfies Account
3230

3331
let authorized
3432
try {
35-
const params = { user, account, email: { verificationRequest: true } }
36-
authorized = await callbacks.signIn(params)
33+
authorized = await callbacks.signIn({
34+
user,
35+
account,
36+
email: { verificationRequest: true },
37+
})
3738
} catch (e) {
3839
throw new AuthorizedCallbackError(e as Error)
3940
}

packages/core/src/providers/oauth.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,9 @@ export interface OAuth2Config<Profile>
239239
export interface OIDCConfig<Profile>
240240
extends Omit<OAuth2Config<Profile>, "type" | "checks"> {
241241
type: "oidc"
242-
checks?: Array<(Exclude<OAuth2Config<Profile>["checks"], undefined>)[number] | "nonce">
242+
checks?: Array<
243+
Exclude<OAuth2Config<Profile>["checks"], undefined>[number] | "nonce"
244+
>
243245
}
244246

245247
export type OAuthConfig<Profile> = OIDCConfig<Profile> | OAuth2Config<Profile>

packages/core/src/types.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,18 @@ export interface CallbacksOptions<P = Profile, A = Account> {
249249
*/
250250
session: (
251251
params:
252-
| {
253-
session: Session
254-
/** Available when {@link AuthConfig.session} is set to `strategy: "jwt"` */
255-
token: JWT
256-
/** Available when {@link AuthConfig.session} is set to `strategy: "database"`. */
257-
user: AdapterUser
258-
} & {
252+
| (
253+
| {
254+
session: Session
255+
/** Available when {@link AuthConfig.session} is set to `strategy: "database"`. */
256+
user: AdapterUser
257+
}
258+
| {
259+
session: Session
260+
/** Available when {@link AuthConfig.session} is set to `strategy: "jwt"` */
261+
token: JWT
262+
}
263+
) & {
259264
/**
260265
* Available when using {@link AuthConfig.session} `strategy: "database"` and an update is triggered for the session.
261266
*
@@ -264,7 +269,7 @@ export interface CallbacksOptions<P = Profile, A = Account> {
264269
* :::
265270
*/
266271
newSession: any
267-
trigger: "update"
272+
trigger?: "update"
268273
}
269274
) => Awaitable<Session | DefaultSession>
270275
/**
@@ -461,7 +466,7 @@ export interface Session extends DefaultSession {}
461466
* [`profile` OAuth provider callback](https://authjs.dev/guides/providers/custom-provider)
462467
*/
463468
export interface User {
464-
id: string
469+
id?: string
465470
name?: string | null
466471
email?: string | null
467472
image?: string | null

packages/next-auth/src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ async function getSession(headers: Headers, config: NextAuthConfig) {
7777
const session =
7878
// If the user defined a custom session callback, use that instead
7979
(await config.callbacks?.session?.(...args)) ?? args[0].session
80+
// @ts-expect-error either user or token will be defined
8081
const user = args[0].user ?? args[0].token
8182
return { user, ...session } satisfies Session
8283
},

packages/utils/adapter/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ export async function runBasicTests(options: TestOptions) {
7474
await options.db.disconnect?.()
7575
})
7676

77-
let user: any = options.fixtures?.user ?? {
77+
let user = options.fixtures?.user ?? {
78+
id: id(),
7879
7980
image: "https://www.fillmurray.com/460/300",
8081
name: "Fill Murray",

0 commit comments

Comments
 (0)