Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SmsProvider and AnonymousProviders (WIP) #12520

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
28 changes: 27 additions & 1 deletion packages/core/src/adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,28 @@ export interface AdapterUser extends User {
* It is `null` if the user has not signed in with the Email provider yet, or the date of the first successful signin.
*/
emailVerified: Date | null
/** The user's phoneNumber. */
phoneNumber?: string
/**
* Whether the user has verified their phoneNumber via an [SMS provider](https://authjs.dev/getting-started/authentication/sms).
* It is `null` if the user has not signed in with the SMS provider yet, or the date of the first successful signin.
*/
phoneNumberVerified?: Date | null
/** A unique identifier for the anonymous user. */
anonymousId?: string
/**
* Whether the user has a verified anonymousId.
* It is `null` if the user has not signed in with the Anonymous provider yet, or the date of the first successful signin.
*/
anonymousIdVerified?: Date | null
}

/**
* The type of account.
*/
export type AdapterAccountType = Extract<
ProviderType,
"oauth" | "oidc" | "email" | "webauthn"
"oauth" | "oidc" | "email" | "webauthn" | "sms" | "anonymous"
>

/**
Expand Down Expand Up @@ -298,6 +312,18 @@ export interface Adapter {
* See also [Verification tokens](https://authjs.dev/guides/creating-a-database-adapter#verification-tokens)
*/
getUserByEmail?(email: string): Awaitable<AdapterUser | null>
/**
* Returns a user from the database via the user's phone number address.
*
* See also [Verification tokens](https://authjs.dev/guides/creating-a-database-adapter#verification-tokens)
*/
getUserByPhoneNumber?(phone_number: string): Awaitable<AdapterUser | null>
/**
* Returns a user from the database via the user's anonymous id.
*
* See also [Verification tokens](https://authjs.dev/guides/creating-a-database-adapter#verification-tokens)
*/
getUserByAnonymousId?(anonymous_id: string): Awaitable<AdapterUser | null>
/**
* Using the provider id and the id of the user for a specific account, get the user.
*
Expand Down
85 changes: 84 additions & 1 deletion packages/core/src/lib/actions/callback/handle-login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export async function handleLoginOrRegister(
// Input validation
if (!_account?.providerAccountId || !_account.type)
throw new Error("Missing or invalid provider account")
if (!["email", "oauth", "oidc", "webauthn"].includes(_account.type))
if (
!["email", "sms", "anonymous", "oauth", "oidc", "webauthn"].includes(
_account.type
)
)
throw new Error("Provider not supported")

const {
Expand All @@ -57,6 +61,8 @@ export async function handleLoginOrRegister(
getUser,
getUserByAccount,
getUserByEmail,
getUserByPhoneNumber,
getUserByAnonymousId,
linkAccount,
createSession,
getSessionAndUser,
Expand Down Expand Up @@ -115,6 +121,83 @@ export async function handleLoginOrRegister(
isNewUser = true
}

// Create new session
session = useJwtSession
? {}
: await createSession({
sessionToken: generateSessionToken(),
userId: user.id,
expires: fromDate(options.session.maxAge),
})

return { session, user, isNewUser }
} else if (account.type === "sms") {
// If signing in with an email, check if an account with the same email address exists already
const userByPhoneNumber = await getUserByPhoneNumber(profile.phoneNumber!)
if (userByPhoneNumber) {
// If they are not already signed in as the same user, this flow will
// sign them out of the current session and sign them in as the new user
if (user?.id !== userByPhoneNumber.id && !useJwtSession && sessionToken) {
// Delete existing session if they are currently signed in as another user.
// This will switch user accounts for the session in cases where the user was
// already logged in with a different account.
await deleteSession(sessionToken)
}

// Update emailVerified property on the user object
user = await updateUser({
id: userByPhoneNumber.id,
phoneNumberVerified: new Date(),
})
await events.updateUser?.({ user })
} else {
// Create user account if there isn't one for the email address already
user = await createUser({ ...profile, phoneNumberVerified: new Date() })
await events.createUser?.({ user })
isNewUser = true
}

// Create new session
session = useJwtSession
? {}
: await createSession({
sessionToken: generateSessionToken(),
userId: user.id,
expires: fromDate(options.session.maxAge),
})

return { session, user, isNewUser }
} else if (account.type === "anonymous") {
// If signing in as anonymous, check if an account already exists
const userByAnonymousId = await getUserByAnonymousId(profile.anonymousId!)
if (userByAnonymousId) {
// If they are not already signed in as the same user, this flow will
// sign them out of the current session and sign them in as the new user
if (user?.id !== userByAnonymousId.id && !useJwtSession && sessionToken) {
// Delete existing session if they are currently signed in as another user.
// This will switch user accounts for the session in cases where the user was
// already logged in with a different account.
await deleteSession(sessionToken)
}

// Update emailVerified property on the user object
user = await updateUser({
id: userByAnonymousId.id,
anonymousId: profile.anonymousId,
anonymousIdVerified: new Date(),
})
await events.updateUser?.({ user })
} else {
// Create user account if there isn't one for the anonymousId already
user = await createUser({
...profile,
anonymousId: profile.anonymousId,
anonymousIdVerified: new Date(),
})
await events.createUser?.({ user })
isNewUser = true
}

// Create new session
session = useJwtSession
? {}
Expand Down
Loading