diff --git a/openspec/changes/user-accounts/tasks.md b/openspec/changes/user-accounts/tasks.md index e4fbc03..f24f284 100644 --- a/openspec/changes/user-accounts/tasks.md +++ b/openspec/changes/user-accounts/tasks.md @@ -16,7 +16,7 @@ - [x] 3.1 `[sonnet]` Create `src/auth.ts` with Auth.js v5 config: JWT strategy, Credentials provider (email/password with bcryptjs verify), Google OAuth provider - [x] 3.2 `[sonnet]` Add JWT callback to embed `user.id` in token and session callback to expose `session.user.id` -- [ ] 3.3 `[sonnet]` Handle Google OAuth sign-in callback: create user on first sign-in, find existing user on returning sign-in +- [x] 3.3 `[sonnet]` Handle Google OAuth sign-in callback: create user on first sign-in, find existing user on returning sign-in - [ ] 3.4 `[haiku]` Create `src/app/api/auth/[...nextauth]/route.ts` exporting GET/POST handlers ## 4. Registration API diff --git a/src/auth.ts b/src/auth.ts index 580834e..55ac105 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -56,10 +56,57 @@ export const { handlers, auth, signIn, signOut } = NextAuth({ }), ], callbacks: { - async jwt({ token, user }) { + async signIn({ user, account, profile }) { + // Only handle Google OAuth sign-ins here; credentials are handled in authorize() + if (account?.provider === "google") { + const email = profile?.email ?? user.email; + if (!email) { + return false; + } + + const [existingUser] = await db + .select() + .from(users) + .where(eq(users.email, email)) + .limit(1); + + if (!existingUser) { + // First Google sign-in: create a new user record + await db.insert(users).values({ + email, + name: profile?.name ?? user.name ?? null, + image: profile?.picture ?? (user.image ?? null), + provider: "google", + provider_account_id: account.providerAccountId, + password_hash: null, + }); + } + // Returning Google sign-in: existing user found, allow sign-in + return true; + } + + return true; + }, + async jwt({ token, user, account }) { if (user) { + // For credentials sign-in the authorize() function already returns the DB uuid as user.id token.id = user.id; } + + // For Google sign-in, user.id is the Google sub ID, not our DB uuid. + // Look up the DB record by email to get the correct uuid. + if (account?.provider === "google" && token.email) { + const [dbUser] = await db + .select({ id: users.id }) + .from(users) + .where(eq(users.email, token.email)) + .limit(1); + + if (dbUser) { + token.id = dbUser.id; + } + } + return token; }, async session({ session, token }) {