| 
1 |  | -import { OAuthAccountNotLinked } from "../../../errors.js"  | 
 | 1 | +import { AccountNotLinked, OAuthAccountNotLinked } from "../../../errors.js"  | 
2 | 2 | import { fromDate } from "../../utils/date.js"  | 
3 | 3 | 
 
  | 
4 | 4 | import type {  | 
@@ -32,7 +32,7 @@ export async function handleLoginOrRegister(  | 
32 | 32 |   // Input validation  | 
33 | 33 |   if (!_account?.providerAccountId || !_account.type)  | 
34 | 34 |     throw new Error("Missing or invalid provider account")  | 
35 |  | -  if (!["email", "oauth", "oidc"].includes(_account.type))  | 
 | 35 | +  if (!["email", "oauth", "oidc", "webauthn"].includes(_account.type))  | 
36 | 36 |     throw new Error("Provider not supported")  | 
37 | 37 | 
 
  | 
38 | 38 |   const {  | 
@@ -125,6 +125,91 @@ export async function handleLoginOrRegister(  | 
125 | 125 |         })  | 
126 | 126 | 
 
  | 
127 | 127 |     return { session, user, isNewUser }  | 
 | 128 | +  } else if (account.type === "webauthn") {  | 
 | 129 | +    // Check if the account exists  | 
 | 130 | +    const userByAccount = await getUserByAccount({  | 
 | 131 | +      providerAccountId: account.providerAccountId,  | 
 | 132 | +      provider: account.provider,  | 
 | 133 | +    })  | 
 | 134 | +    if (userByAccount) {  | 
 | 135 | +      if (user) {  | 
 | 136 | +        // If the user is already signed in with this account, we don't need to do anything  | 
 | 137 | +        if (userByAccount.id === user.id) {  | 
 | 138 | +          const currentAccount: AdapterAccount = { ...account, userId: user.id }  | 
 | 139 | +          return { session, user, isNewUser, account: currentAccount }  | 
 | 140 | +        }  | 
 | 141 | +        // If the user is currently signed in, but the new account they are signing in  | 
 | 142 | +        // with is already associated with another user, then we cannot link them  | 
 | 143 | +        // and need to return an error.  | 
 | 144 | +        throw new AccountNotLinked(  | 
 | 145 | +          "The account is already associated with another user",  | 
 | 146 | +          { provider: account.provider }  | 
 | 147 | +        )  | 
 | 148 | +      }  | 
 | 149 | +      // If there is no active session, but the account being signed in with is already  | 
 | 150 | +      // associated with a valid user then create session to sign the user in.  | 
 | 151 | +      session = useJwtSession  | 
 | 152 | +        ? {}  | 
 | 153 | +        : await createSession({  | 
 | 154 | +          sessionToken: generateSessionToken(),  | 
 | 155 | +          userId: userByAccount.id,  | 
 | 156 | +          expires: fromDate(options.session.maxAge),  | 
 | 157 | +        })  | 
 | 158 | + | 
 | 159 | +      const currentAccount: AdapterAccount = { ...account, userId: userByAccount.id }  | 
 | 160 | +      return { session, user: userByAccount, isNewUser, account: currentAccount }  | 
 | 161 | +    } else {  | 
 | 162 | +      // If the account doesn't exist, we'll create it  | 
 | 163 | +      if (user) {  | 
 | 164 | +        // If the user is already signed in and the account isn't already associated  | 
 | 165 | +        // with another user account then we can go ahead and link the accounts safely.  | 
 | 166 | +        await linkAccount({ ...account, userId: user.id })  | 
 | 167 | +        await events.linkAccount?.({ user, account, profile })  | 
 | 168 | + | 
 | 169 | +        // As they are already signed in, we don't need to do anything after linking them  | 
 | 170 | +        const currentAccount: AdapterAccount = { ...account, userId: user.id }  | 
 | 171 | +        return { session, user, isNewUser, account: currentAccount }  | 
 | 172 | +      }  | 
 | 173 | + | 
 | 174 | +      // If the user is not signed in and it looks like a new account then we  | 
 | 175 | +      // check there also isn't an user account already associated with the same  | 
 | 176 | +      // email address as the one in the request.  | 
 | 177 | +      const userByEmail = profile.email  | 
 | 178 | +        ? await getUserByEmail(profile.email)  | 
 | 179 | +        : null  | 
 | 180 | +      if (userByEmail) {  | 
 | 181 | +        // We don't trust user-provided email addresses, so we don't want to link accounts  | 
 | 182 | +        // if the email address associated with the new account is already associated with  | 
 | 183 | +        // an existing account.  | 
 | 184 | +        throw new AccountNotLinked(  | 
 | 185 | +          "Another account already exists with the same e-mail address",  | 
 | 186 | +          { provider: account.provider }  | 
 | 187 | +        )  | 
 | 188 | +      } else {  | 
 | 189 | +        // If the current user is not logged in and the profile isn't linked to any user  | 
 | 190 | +        // accounts (by email or provider account id)...  | 
 | 191 | +        //  | 
 | 192 | +        // If no account matching the same [provider].id or .email exists, we can  | 
 | 193 | +        // create a new account for the user, link it to the OAuth account and  | 
 | 194 | +        // create a new session for them so they are signed in with it.  | 
 | 195 | +        user = await createUser({ ...profile })  | 
 | 196 | +      }  | 
 | 197 | +      await events.createUser?.({ user })  | 
 | 198 | + | 
 | 199 | +      await linkAccount({ ...account, userId: user.id })  | 
 | 200 | +      await events.linkAccount?.({ user, account, profile })  | 
 | 201 | + | 
 | 202 | +      session = useJwtSession  | 
 | 203 | +        ? {}  | 
 | 204 | +        : await createSession({  | 
 | 205 | +          sessionToken: generateSessionToken(),  | 
 | 206 | +          userId: user.id,  | 
 | 207 | +          expires: fromDate(options.session.maxAge),  | 
 | 208 | +        })  | 
 | 209 | + | 
 | 210 | +      const currentAccount: AdapterAccount = { ...account, userId: user.id }  | 
 | 211 | +      return { session, user, isNewUser: true, account: currentAccount }  | 
 | 212 | +    }  | 
128 | 213 |   }  | 
129 | 214 | 
 
  | 
130 | 215 |   // If signing in with OAuth account, check to see if the account exists already  | 
 | 
0 commit comments