All files / web/src/lib/auth adapter.ts

0% Statements 0/156
0% Branches 0/1
0% Functions 0/1
0% Lines 0/156

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157                                                                                                                                                                                                                                                                                                                         
import { createId } from '@paralleldrive/cuid2'
import { and, eq } from 'drizzle-orm'
import type { Adapter, AdapterAccount, AdapterUser } from 'next-auth/adapters'
import { db, schema } from '@/db'

/**
 * Custom NextAuth adapter backed by our Drizzle schema.
 *
 * Only implements the methods needed for OAuth + email magic links
 * with JWT sessions. Session-related methods throw since we use
 * stateless JWT sessions, not database sessions.
 */
export const abacisAdapter: Adapter = {
  async createUser(user) {
    const id = createId()
    const [created] = await db
      .insert(schema.users)
      .values({
        id,
        guestId: id,
        email: user.email,
        name: user.name ?? null,
        image: user.image ?? null,
        upgradedAt: user.email ? new Date() : undefined,
      })
      .returning()
    return toAdapterUser(created)
  },

  async getUser(id) {
    const user = await db.query.users.findFirst({
      where: eq(schema.users.id, id),
    })
    return user ? toAdapterUser(user) : null
  },

  async getUserByEmail(email) {
    const user = await db.query.users.findFirst({
      where: eq(schema.users.email, email),
    })
    return user ? toAdapterUser(user) : null
  },

  async getUserByAccount({ provider, providerAccountId }) {
    const account = await db.query.authAccounts.findFirst({
      where: and(
        eq(schema.authAccounts.provider, provider),
        eq(schema.authAccounts.providerAccountId, providerAccountId)
      ),
    })
    if (!account) return null

    const user = await db.query.users.findFirst({
      where: eq(schema.users.id, account.userId),
    })
    return user ? toAdapterUser(user) : null
  },

  async updateUser(user) {
    if (!user.id) throw new Error('updateUser requires user.id')
    const [updated] = await db
      .update(schema.users)
      .set({
        name: user.name ?? undefined,
        email: user.email ?? undefined,
        image: user.image ?? undefined,
      })
      .where(eq(schema.users.id, user.id))
      .returning()
    return toAdapterUser(updated)
  },

  async linkAccount(account: AdapterAccount) {
    await db.insert(schema.authAccounts).values({
      id: createId(),
      userId: account.userId,
      provider: account.provider,
      providerAccountId: account.providerAccountId,
      type: account.type,
    })
  },

  async createVerificationToken(token) {
    const [created] = await db
      .insert(schema.verificationTokens)
      .values({
        identifier: token.identifier,
        token: token.token,
        expires: token.expires,
      })
      .returning()
    return {
      identifier: created.identifier,
      token: created.token,
      expires: created.expires,
    }
  },

  async useVerificationToken({ identifier, token }) {
    const row = await db.query.verificationTokens.findFirst({
      where: and(
        eq(schema.verificationTokens.identifier, identifier),
        eq(schema.verificationTokens.token, token)
      ),
    })
    if (!row) return null

    await db
      .delete(schema.verificationTokens)
      .where(
        and(
          eq(schema.verificationTokens.identifier, identifier),
          eq(schema.verificationTokens.token, token)
        )
      )

    return {
      identifier: row.identifier,
      token: row.token,
      expires: row.expires,
    }
  },

  // --- Session methods: not used with JWT strategy ---
  async createSession() {
    throw new Error('createSession not implemented — using JWT strategy')
  },
  async getSessionAndUser() {
    throw new Error('getSessionAndUser not implemented — using JWT strategy')
  },
  async updateSession() {
    throw new Error('updateSession not implemented — using JWT strategy')
  },
  async deleteSession() {
    throw new Error('deleteSession not implemented — using JWT strategy')
  },
  async deleteUser() {
    throw new Error('deleteUser not implemented')
  },
  async unlinkAccount() {
    throw new Error('unlinkAccount not implemented')
  },
}

/**
 * Convert our DB user to NextAuth's AdapterUser format
 */
function toAdapterUser(user: typeof schema.users.$inferSelect): AdapterUser {
  return {
    id: user.id,
    email: user.email ?? '',
    emailVerified: user.upgradedAt ?? null,
    name: user.name ?? null,
    image: user.image ?? null,
  }
}