Skip to content

Commit

Permalink
feat(prisma): support webauthn (#9876)
Browse files Browse the repository at this point in the history
passkey adapter stuff

Co-authored-by: Nico Domino <yo@ndo.dev>
  • Loading branch information
Maronato and ndom91 committed Feb 5, 2024
1 parent b4699fa commit b2705f2
Show file tree
Hide file tree
Showing 10 changed files with 441 additions and 14 deletions.
@@ -0,0 +1,71 @@
-- CreateTable
CREATE TABLE "Account" (
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,

PRIMARY KEY ("provider", "providerAccountId"),
CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL PRIMARY KEY,
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" DATETIME NOT NULL,
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT,
"email" TEXT,
"emailVerified" DATETIME,
"image" TEXT
);

-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" DATETIME NOT NULL
);

-- CreateTable
CREATE TABLE "Authenticator" (
"id" TEXT NOT NULL PRIMARY KEY,
"credentialID" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"credentialPublicKey" TEXT NOT NULL,
"counter" INTEGER NOT NULL,
"credentialDeviceType" TEXT NOT NULL,
"credentialBackedUp" BOOLEAN NOT NULL,
"transports" TEXT,
CONSTRAINT "Authenticator_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");

-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");

-- CreateIndex
CREATE UNIQUE INDEX "Authenticator_credentialID_key" ON "Authenticator"("credentialID");
21 changes: 18 additions & 3 deletions apps/dev/nextjs/prisma/schema.prisma
Expand Up @@ -8,7 +8,6 @@ generator client {
}

model Account {
id String @id @default(cuid())
userId String
type String
provider String
Expand All @@ -20,9 +19,10 @@ model Account {
scope String?
id_token String?
session_state String?
user User @relation(fields: [userId], references: [id])
@@unique([provider, providerAccountId])
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([provider, providerAccountId])
}

model Session {
Expand All @@ -41,6 +41,7 @@ model User {
image String?
accounts Account[]
sessions Session[]
Authenticator Authenticator[]
}

model VerificationToken {
Expand All @@ -50,3 +51,17 @@ model VerificationToken {
@@unique([identifier, token])
}

model Authenticator {
id String @id @default(cuid())
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
1 change: 1 addition & 0 deletions packages/adapter-hasura/schema.gql
Expand Up @@ -1192,6 +1192,7 @@ enum provider_type_enum {
email
oauth
oidc
webauthn
}

"""
Expand Down
15 changes: 15 additions & 0 deletions packages/adapter-prisma/prisma/custom.prisma
Expand Up @@ -17,6 +17,7 @@ model User {
role String?
accounts Account[]
sessions Session[]
Authenticator Authenticator[]
}

model Account {
Expand Down Expand Up @@ -51,3 +52,17 @@ model VerificationToken {
@@id([identifier, token])
}

model Authenticator {
id String @id @default(cuid())
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
15 changes: 15 additions & 0 deletions packages/adapter-prisma/prisma/mongodb.prisma
Expand Up @@ -41,6 +41,7 @@ model User {
image String?
accounts Account[]
sessions Session[]
Authenticator Authenticator[]
}

model VerificationToken {
Expand All @@ -51,3 +52,17 @@ model VerificationToken {
@@unique([identifier, token])
}

model Authenticator {
id String @id @default(auto()) @map("_id") @db.ObjectId
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
15 changes: 15 additions & 0 deletions packages/adapter-prisma/prisma/schema.prisma
Expand Up @@ -15,6 +15,7 @@ model User {
image String?
accounts Account[]
sessions Session[]
Authenticator Authenticator[]
}

model Account {
Expand Down Expand Up @@ -49,3 +50,17 @@ model VerificationToken {
@@id([identifier, token])
}

model Authenticator {
id String @id @default(cuid())
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
61 changes: 56 additions & 5 deletions packages/adapter-prisma/src/index.ts
Expand Up @@ -16,7 +16,7 @@
* @module @auth/prisma-adapter
*/
import type { PrismaClient, Prisma } from "@prisma/client"
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
import type { Adapter, AdapterAccount, AdapterAuthenticator, AdapterSession, AdapterUser } from "@auth/core/adapters"

/**
* ## Setup
Expand Down Expand Up @@ -215,6 +215,20 @@ import type { Adapter, AdapterAccount } from "@auth/core/adapters"
* @@unique([identifier, token])
* @@map("verificationtokens")
* }
*
* model Authenticator {
* id String @id @default(cuid())
* credentialID String @unique
* userId String
* providerAccountId String
* credentialPublicKey String
* counter Int
* credentialDeviceType String
* credentialBackedUp Boolean
* transports String?
*
* user User @relation(fields: [userId], references: [id], onDelete: Cascade)
* }
* ```
*
**/
Expand All @@ -234,10 +248,10 @@ export function PrismaAdapter(
where: { provider_providerAccountId },
select: { user: true },
})
return account?.user ?? null
return account?.user as AdapterUser ?? null
},
updateUser: ({ id, ...data }) => p.user.update({ where: { id }, data }),
deleteUser: (id) => p.user.delete({ where: { id } }),
updateUser: ({ id, ...data }) => p.user.update({ where: { id }, data }) as Promise<AdapterUser>,
deleteUser: (id) => p.user.delete({ where: { id } }) as Promise<AdapterUser>,
linkAccount: (data) =>
p.account.create({ data }) as unknown as AdapterAccount,
unlinkAccount: (provider_providerAccountId) =>
Expand All @@ -251,7 +265,7 @@ export function PrismaAdapter(
})
if (!userAndSession) return null
const { user, ...session } = userAndSession
return { user, session }
return { user, session } as { user: AdapterUser; session: AdapterSession }
},
createSession: (data) => p.session.create({ data }),
updateSession: (data) =>
Expand Down Expand Up @@ -280,5 +294,42 @@ export function PrismaAdapter(
throw error
}
},
async getAccount(providerAccountId, provider) {
return p.account.findFirst({
where: { providerAccountId, provider }
}) as Promise<AdapterAccount | null>
},
async createAuthenticator(authenticator) {
return p.authenticator.create({
data: authenticator
}).then(fromDBAuthenticator)
},
async getAuthenticator(credentialID) {
const authenticator = await p.authenticator.findUnique({ where: { credentialID } })
return authenticator ? fromDBAuthenticator(authenticator) : null
},
async listAuthenticatorsByUserId(userId) {
const authenticators = await p.authenticator.findMany({ where: { userId } })

return authenticators.map(fromDBAuthenticator)
},
async updateAuthenticatorCounter(credentialID, counter) {
return p.authenticator.update({
where: { credentialID: credentialID },
data: { counter },
}).then(fromDBAuthenticator)
}
}
}

type BasePrismaAuthenticator = Parameters<PrismaClient['authenticator']['create']>[0]['data']
type PrismaAuthenticator = BasePrismaAuthenticator & Required<Pick<BasePrismaAuthenticator, 'userId'>>

function fromDBAuthenticator(authenticator: PrismaAuthenticator): AdapterAuthenticator {
const { transports, id, user, ...other } = authenticator

return {
...other,
transports: transports || undefined,
}
}
5 changes: 5 additions & 0 deletions packages/adapter-prisma/test/index.test.ts
Expand Up @@ -8,6 +8,7 @@ const prisma = new PrismaClient().$extends(withAccelerate())

runBasicTests({
adapter: PrismaAdapter(prisma),
testWebAuthnMethods: true,
db: {
id() {
if (process.env.CONTAINER_NAME !== "authjs-mongodb-test") return
Expand All @@ -19,6 +20,7 @@ runBasicTests({
prisma.account.deleteMany({}),
prisma.session.deleteMany({}),
prisma.verificationToken.deleteMany({}),
prisma.authenticator.deleteMany({}),
])
},
disconnect: async () => {
Expand All @@ -27,6 +29,7 @@ runBasicTests({
prisma.account.deleteMany({}),
prisma.session.deleteMany({}),
prisma.verificationToken.deleteMany({}),
prisma.authenticator.deleteMany({}),
])
await prisma.$disconnect()
},
Expand All @@ -44,5 +47,7 @@ runBasicTests({
delete result.id
return result
},
authenticator: (credentialID) =>
prisma.authenticator.findUnique({ where: { credentialID } }),
},
})
2 changes: 1 addition & 1 deletion packages/adapter-surrealdb/src/index.ts
Expand Up @@ -31,7 +31,7 @@ export type AccountDoc<T = string> = {
userId: T
refresh_token?: string
access_token?: string
type: Extract<ProviderType, "oauth" | "oidc" | "email">
type: Extract<ProviderType, "oauth" | "oidc" | "email" | "webauthn">
provider: string
providerAccountId: string
expires_at?: number
Expand Down

0 comments on commit b2705f2

Please sign in to comment.