Next-auth: Add Prisma Adapter

Created on 23 May 2020  路  3Comments  路  Source: nextauthjs/next-auth

The following was written by @Fumler and shared in the announcements board.

Once the default (TypeORM) adapter is production ready, and the adapter API is more stable I'd like to have a go at implementing, so am saving a copy of it here where it's easy to find.

Note: This example probably doesn't work exactly as is with the current version as a few things have changed during the beta, but it should be close enough it's possible to adapt it easily!

schema.prisma

model User {
  id            String         @default(uuid()) @id
  email         String         @unique
  avatarUrl     String?
  name          String?
  sessions      Session[]
  accounts      Account[]
  createdAt     DateTime       @default(now())
}

model Session {
  id                 String   @default(uuid()) @id
  userId             String
  user               User     @relation(fields: [userId], references: [id])
  accessToken        String   @default(cuid()) @unique
  accessTokenExpires DateTime
  expires            DateTime
  createdAt          DateTime @default(now())
}

model Account {
  id                 String    @default(uuid()) @id
  userId             String
  user               User      @relation(fields: [userId], references: [id])
  providerId         String
  providerType       String
  providerAccountId  String    @unique
  refreshToken       String?
  accessToken        String
  accessTokenExpires DateTime?
  createdAt          DateTime  @default(now())
}

Adapter

import { PrismaClient, User } from '@prisma/client'

interface Profile {
  name: string
  email: string
  image: string
}

const PrismaAdapter = () => {
  function debug(...args) {
    if (process.env.NODE_ENV === 'development') console.log(...args)
  }
  let connection = new PrismaClient()

  async function getAdapter() {
    if (!connection) {
      connection = new PrismaClient()
    }

    // Called when a user signs in
    async function createUser(profile: Profile) {
      debug('Create user account', profile)
      return connection.user.create({
        data: {
          name: profile.name,
          email: profile.email,
          avatarUrl: profile.image,
        },
      })
    }

    async function updateUser(user: User) {
      debug('Update user account', user)
      return new Promise((resolve, reject) => {
        // @TODO Save changes to user object in DB
        resolve(true)
      })
    }

    async function getUserById(id = '') {
      debug('Get user account by ID', id)
      return connection.user.findOne({ where: { id } })
    }

    async function getUserByProviderAccountId(providerId: string, providerAccountId: string) {
      debug('Get user account by provider account ID', providerId, providerAccountId)
      return connection.account
        .findOne({
          where: { providerAccountId },
        })
        .user()
    }

    async function getUserByEmail(email: string) {
      debug('Get user account by email address', email)
      return new Promise((resolve, reject) => {
        // @TODO Get user from DB
        resolve(false)
      })
    }

    async function getUserByCredentials(credentials: string) {
      debug('Get user account by credentials', credentials)
      return new Promise((resolve, reject) => {
        // @TODO Get user from DB
        resolve(true)
      })
    }

    async function deleteUserById(userId: string) {
      debug('Delete user account', userId)
      return new Promise((resolve, reject) => {
        // @TODO Delete user from DB
        resolve(true)
      })
    }

    async function linkAccount(
      userId: string,
      providerId: string,
      providerType: string,
      providerAccountId: string,
      refreshToken: string,
      accessToken: string,
      accessTokenExpires: string
    ) {
      debug(
        'Link provider account',
        userId,
        providerId,
        providerType,
        providerAccountId,
        refreshToken,
        accessToken,
        accessTokenExpires
      )
      return connection.account.create({
        data: {
          accessToken,
          refreshToken,
          providerAccountId,
          providerId,
          providerType,
          user: {
            connect: {
              id: userId,
            },
          },
          accessTokenExpires,
        },
      })
    }

    async function unlinkAccount(userId: string, providerId: string, providerAccountId: string) {
      debug('Unlink provider account', userId, providerId, providerAccountId)
      return new Promise((resolve, reject) => {
        // @TODO Get current user from DB
        // @TODO Delete [provider] object from user object
        // @TODO Save changes to user object in DB
        resolve(true)
      })
    }

    async function createSession(user: User) {
      debug('Create session for user', user)
      const date = new Date()
      const sessionExpiryInDays = 30
      const accessTokenExpiryInDays = 30
      return connection.session.create({
        data: {
          accessTokenExpires: new Date(
            date.setDate(date.getDate() + accessTokenExpiryInDays)
          ).toISOString(),
          expires: new Date(date.setDate(date.getDate() + sessionExpiryInDays)).toISOString(),
          user: {
            connect: {
              id: user.id,
            },
          },
        },
      })
    }

    async function getSessionById(id = '') {
      debug('Get session by ID', id)

      return connection.session.findOne({ where: { id } })
    }

    async function deleteSessionById(id: string) {
      debug('Delete session by ID', id)
      return connection.session.delete({ where: { id } })
    }

    return Promise.resolve({
      createUser,
      updateUser,
      getUserById,
      getUserByProviderAccountId,
      getUserByEmail,
      getUserByCredentials,
      deleteUserById,
      linkAccount,
      unlinkAccount,
      createSession,
      getSessionById,
      deleteSessionById,
    })
  }

  return {
    getAdapter,
  }
}

export default PrismaAdapter
enhancement

Most helpful comment

This is now implemented in the v3 beta #302

All 3 comments

Very excited about the possibility of a first-party Prisma adapter for this amazing library!

Cool! I had just ran prisma instrospect on the nextauth tables haha

This is now implemented in the v3 beta #302

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dmi3y picture dmi3y  路  3Comments

ghoshnirmalya picture ghoshnirmalya  路  3Comments

loonskai picture loonskai  路  3Comments

readywater picture readywater  路  3Comments

jimmiejackson414 picture jimmiejackson414  路  3Comments