From 284a04ba2ab4c81c2e8b8a4c517bd2f996db3753 Mon Sep 17 00:00:00 2001
From: Skye
Date: Wed, 22 Feb 2023 00:03:16 +0900
Subject: [PATCH] init: from
https://github.com/nextauthjs/next-auth/tree/main/packages/adapter-mongodb
---
CHANGELOG.md | 8 ++
README.md | 88 ++++++++++++++++++++
logo.svg | 1 +
package.json | 47 +++++++++++
src/index.ts | 193 +++++++++++++++++++++++++++++++++++++++++++
tests/custom.test.ts | 54 ++++++++++++
tests/index.test.ts | 51 ++++++++++++
tests/test.sh | 34 ++++++++
tsconfig.json | 8 ++
9 files changed, 484 insertions(+)
create mode 100644 CHANGELOG.md
create mode 100644 README.md
create mode 100644 logo.svg
create mode 100644 package.json
create mode 100644 src/index.ts
create mode 100644 tests/custom.test.ts
create mode 100644 tests/index.test.ts
create mode 100755 tests/test.sh
create mode 100644 tsconfig.json
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..0a20d83
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,8 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [1.0.1](https://github.com/nextauthjs/adapters/compare/@next-auth/mongodb-adapter@1.0.0...@next-auth/mongodb-adapter@1.0.1) (2021-12-06)
+
+**Note:** Version bump only for package @next-auth/mongodb-adapter
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0455e62
--- /dev/null
+++ b/README.md
@@ -0,0 +1,88 @@
+
+
+
+
MongoDB Adapter - NextAuth.js
+
+ Open Source. Full Stack. Own Your Data.
+
+
+
+
+
+
+
+
+## Overview
+
+This is the MongoDB Adapter for [`auth.js`](https://authjs.dev). This package can only be used in conjunction with the primary `auth.js` package. It is not a standalone package.
+
+## Getting Started
+
+1. Install `mongodb`, `next-auth` and `@next-auth/mongodb-adapter`
+
+```js
+npm install mongodb next-auth @next-auth/mongodb-adapter@next
+```
+
+2. Add `lib/mongodb.js`
+
+```js
+// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
+import { MongoClient } from "mongodb"
+
+const uri = process.env.MONGODB_URI
+const options = {
+ useUnifiedTopology: true,
+ useNewUrlParser: true,
+}
+
+let client
+let clientPromise
+
+if (!process.env.MONGODB_URI) {
+ throw new Error("Please add your Mongo URI to .env.local")
+}
+
+if (process.env.NODE_ENV === "development") {
+ // In development mode, use a global variable so that the value
+ // is preserved across module reloads caused by HMR (Hot Module Replacement).
+ if (!global._mongoClientPromise) {
+ client = new MongoClient(uri, options)
+ global._mongoClientPromise = client.connect()
+ }
+ clientPromise = global._mongoClientPromise
+} else {
+ // In production mode, it's best to not use a global variable.
+ client = new MongoClient(uri, options)
+ clientPromise = client.connect()
+}
+
+// Export a module-scoped MongoClient promise. By doing this in a
+// separate module, the client can be shared across functions.
+export default clientPromise
+```
+
+3. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
+
+```js
+import NextAuth from "next-auth"
+import { MongoDBAdapter } from "@next-auth/mongodb-adapter"
+import clientPromise from "lib/mongodb"
+
+// For more information on each option (and a full list of options) go to
+// https://authjs.dev/reference/configuration/auth-options
+export default NextAuth({
+ adapter: MongoDBAdapter(clientPromise, {
+ databaseName: 'my-data-base-name'
+ }),
+ ...
+})
+```
+
+## Contributing
+
+We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md).
+
+## License
+
+ISC
diff --git a/logo.svg b/logo.svg
new file mode 100644
index 0000000..bed4f09
--- /dev/null
+++ b/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..ba8ea7f
--- /dev/null
+++ b/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "@next-auth/mongodb-adapter",
+ "version": "1.1.1",
+ "description": "mongoDB adapter for next-auth.",
+ "homepage": "https://authjs.dev",
+ "repository": "https://github.com/nextauthjs/next-auth",
+ "bugs": {
+ "url": "https://github.com/nextauthjs/next-auth/issues"
+ },
+ "author": "Balázs Orbán ",
+ "main": "dist/index.js",
+ "license": "ISC",
+ "keywords": [
+ "next-auth",
+ "next.js",
+ "oauth",
+ "mongodb",
+ "adapter"
+ ],
+ "private": false,
+ "publishConfig": {
+ "access": "public"
+ },
+ "scripts": {
+ "test": "./tests/test.sh",
+ "test:watch": "./tests/test.sh -w",
+ "build": "tsc"
+ },
+ "files": [
+ "README.md",
+ "dist"
+ ],
+ "peerDependencies": {
+ "mongodb": "^4.1.1",
+ "next-auth": "^4"
+ },
+ "devDependencies": {
+ "@next-auth/adapter-test": "workspace:*",
+ "@next-auth/tsconfig": "workspace:*",
+ "jest": "^27.4.3",
+ "mongodb": "^4.4.0",
+ "next-auth": "workspace:*"
+ },
+ "jest": {
+ "preset": "@next-auth/adapter-test/jest"
+ }
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..a8f8af0
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,193 @@
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
+import { ObjectId } from "mongodb"
+
+import type {
+ Adapter,
+ AdapterUser,
+ AdapterAccount,
+ AdapterSession,
+ VerificationToken,
+} from "next-auth/adapters"
+import type { MongoClient } from "mongodb"
+
+export interface MongoDBAdapterOptions {
+ collections?: {
+ Users?: string
+ Accounts?: string
+ Sessions?: string
+ VerificationTokens?: string
+ }
+ databaseName?: string
+}
+
+export const defaultCollections: Required<
+ Required["collections"]
+> = {
+ Users: "users",
+ Accounts: "accounts",
+ Sessions: "sessions",
+ VerificationTokens: "verification_tokens",
+}
+
+export const format = {
+ /** Takes a mongoDB object and returns a plain old JavaScript object */
+ from>(object: Record): T {
+ const newObject: Record = {}
+ for (const key in object) {
+ const value = object[key]
+ if (key === "_id") {
+ newObject.id = value.toHexString()
+ } else if (key === "userId") {
+ newObject[key] = value.toHexString()
+ } else {
+ newObject[key] = value
+ }
+ }
+ return newObject as T
+ },
+ /** Takes a plain old JavaScript object and turns it into a mongoDB object */
+ to>(object: Record) {
+ const newObject: Record = {
+ _id: _id(object.id),
+ }
+ for (const key in object) {
+ const value = object[key]
+ if (key === "userId") newObject[key] = _id(value)
+ else if (key === "id") continue
+ else newObject[key] = value
+ }
+ return newObject as T & { _id: ObjectId }
+ },
+}
+
+/** Converts from string to ObjectId */
+export function _id(hex?: string) {
+ if (hex?.length !== 24) return new ObjectId()
+ return new ObjectId(hex)
+}
+
+export function MongoDBAdapter(
+ client: Promise,
+ options: MongoDBAdapterOptions = {}
+): Adapter {
+ const { collections } = options
+ const { from, to } = format
+
+ const db = (async () => {
+ const _db = (await client).db(options.databaseName)
+ const c = { ...defaultCollections, ...collections }
+ return {
+ U: _db.collection(c.Users),
+ A: _db.collection(c.Accounts),
+ S: _db.collection(c.Sessions),
+ V: _db.collection(c?.VerificationTokens),
+ }
+ })()
+
+ return {
+ async createUser(data) {
+ const user = to(data)
+ await (await db).U.insertOne(user)
+ return from(user)
+ },
+ async getUser(id) {
+ const user = await (await db).U.findOne({ _id: _id(id) })
+ if (!user) return null
+ return from(user)
+ },
+ async getUserByEmail(email) {
+ const user = await (await db).U.findOne({ email })
+ if (!user) return null
+ return from(user)
+ },
+ async getUserByAccount(provider_providerAccountId) {
+ const account = await (await db).A.findOne(provider_providerAccountId)
+ if (!account) return null
+ const user = await (
+ await db
+ ).U.findOne({ _id: new ObjectId(account.userId) })
+ if (!user) return null
+ return from(user)
+ },
+ async updateUser(data) {
+ const { _id, ...user } = to(data)
+
+ const result = await (
+ await db
+ ).U.findOneAndUpdate({ _id }, { $set: user }, { returnDocument: "after" })
+
+ return from(result.value!)
+ },
+ async deleteUser(id) {
+ const userId = _id(id)
+ const m = await db
+ await Promise.all([
+ m.A.deleteMany({ userId }),
+ m.S.deleteMany({ userId: userId as any }),
+ m.U.deleteOne({ _id: userId }),
+ ])
+ },
+ linkAccount: async (data) => {
+ const account = to(data)
+ await (await db).A.insertOne(account)
+ return account
+ },
+ async unlinkAccount(provider_providerAccountId) {
+ const { value: account } = await (
+ await db
+ ).A.findOneAndDelete(provider_providerAccountId)
+ return from(account!)
+ },
+ async getSessionAndUser(sessionToken) {
+ const session = await (await db).S.findOne({ sessionToken })
+ if (!session) return null
+ const user = await (
+ await db
+ ).U.findOne({ _id: new ObjectId(session.userId) })
+ if (!user) return null
+ return {
+ user: from(user),
+ session: from(session),
+ }
+ },
+ async createSession(data) {
+ const session = to(data)
+ await (await db).S.insertOne(session)
+ return from(session)
+ },
+ async updateSession(data) {
+ const { _id, ...session } = to(data)
+
+ const result = await (
+ await db
+ ).S.findOneAndUpdate(
+ { sessionToken: session.sessionToken },
+ { $set: session },
+ { returnDocument: "after" }
+ )
+ return from(result.value!)
+ },
+ async deleteSession(sessionToken) {
+ const { value: session } = await (
+ await db
+ ).S.findOneAndDelete({
+ sessionToken,
+ })
+ return from(session!)
+ },
+ async createVerificationToken(data) {
+ await (await db).V.insertOne(to(data))
+ return data
+ },
+ async useVerificationToken(identifier_token) {
+ const { value: verificationToken } = await (
+ await db
+ ).V.findOneAndDelete(identifier_token)
+
+ if (!verificationToken) return null
+ // @ts-expect-error
+ delete verificationToken._id
+ return verificationToken
+ },
+ }
+}
diff --git a/tests/custom.test.ts b/tests/custom.test.ts
new file mode 100644
index 0000000..c4269a1
--- /dev/null
+++ b/tests/custom.test.ts
@@ -0,0 +1,54 @@
+import { runBasicTests } from "@next-auth/adapter-test"
+import { defaultCollections, format, MongoDBAdapter, _id } from "../src"
+import { MongoClient } from "mongodb"
+const name = "custom-test"
+const client = new MongoClient(`mongodb://localhost:27017/${name}`)
+const clientPromise = client.connect()
+
+const collections = { ...defaultCollections, Users: "some_userz" }
+
+runBasicTests({
+ adapter: MongoDBAdapter(clientPromise, {
+ collections,
+ }),
+ db: {
+ async disconnect() {
+ await client.db().dropDatabase()
+ await client.close()
+ },
+ async user(id) {
+ const user = await client
+ .db()
+ .collection(collections.Users)
+ .findOne({ _id: _id(id) })
+
+ if (!user) return null
+ return format.from(user)
+ },
+ async account(provider_providerAccountId) {
+ const account = await client
+ .db()
+ .collection(collections.Accounts)
+ .findOne(provider_providerAccountId)
+ if (!account) return null
+ return format.from(account)
+ },
+ async session(sessionToken) {
+ const session = await client
+ .db()
+ .collection(collections.Sessions)
+ .findOne({ sessionToken })
+ if (!session) return null
+ return format.from(session)
+ },
+ async verificationToken(identifier_token) {
+ const token = await client
+ .db()
+ .collection(collections.VerificationTokens)
+ .findOne(identifier_token)
+ if (!token) return null
+ const { _id, ...rest } = token
+ return rest
+ },
+ },
+})
diff --git a/tests/index.test.ts b/tests/index.test.ts
new file mode 100644
index 0000000..aeabccf
--- /dev/null
+++ b/tests/index.test.ts
@@ -0,0 +1,51 @@
+import { runBasicTests } from "@next-auth/adapter-test"
+import { defaultCollections, format, MongoDBAdapter, _id } from "../src"
+import { MongoClient } from "mongodb"
+
+const name = "test"
+const client = new MongoClient(`mongodb://localhost:27017/${name}`)
+const clientPromise = client.connect()
+
+runBasicTests({
+ adapter: MongoDBAdapter(clientPromise),
+ db: {
+ async disconnect() {
+ await client.db().dropDatabase()
+ await client.close()
+ },
+ async user(id) {
+ const user = await client
+ .db()
+ .collection(defaultCollections.Users)
+ .findOne({ _id: _id(id) })
+
+ if (!user) return null
+ return format.from(user)
+ },
+ async account(provider_providerAccountId) {
+ const account = await client
+ .db()
+ .collection(defaultCollections.Accounts)
+ .findOne(provider_providerAccountId)
+ if (!account) return null
+ return format.from(account)
+ },
+ async session(sessionToken) {
+ const session = await client
+ .db()
+ .collection(defaultCollections.Sessions)
+ .findOne({ sessionToken })
+ if (!session) return null
+ return format.from(session)
+ },
+ async verificationToken(identifier_token) {
+ const token = await client
+ .db()
+ .collection(defaultCollections.VerificationTokens)
+ .findOne(identifier_token)
+ if (!token) return null
+ const { _id, ...rest } = token
+ return rest
+ },
+ },
+})
diff --git a/tests/test.sh b/tests/test.sh
new file mode 100755
index 0000000..6ca0a6d
--- /dev/null
+++ b/tests/test.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+CONTAINER_NAME=next-auth-mongodb-test
+
+JEST_WATCH=false
+
+# Is the watch flag passed to the script?
+while getopts w flag
+do
+ case "${flag}" in
+ w) JEST_WATCH=true;;
+ *) continue;;
+ esac
+done
+
+# Start db
+docker run -d --rm -p 27017:27017 --name ${CONTAINER_NAME} mongo
+
+echo "Waiting 3 sec for db to start..."
+sleep 3
+
+if $JEST_WATCH; then
+ # Run jest in watch mode
+ npx jest tests --watch
+ # Only stop the container after jest has been quit
+ docker stop "${CONTAINER_NAME}"
+else
+ # Always stop container, but exit with 1 when tests are failing
+ if npx jest;then
+ docker stop ${CONTAINER_NAME}
+ else
+ docker stop ${CONTAINER_NAME} && exit 1
+ fi
+fi
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..0b19a11
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@next-auth/tsconfig/tsconfig.adapters.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "dist"
+ },
+ "exclude": ["tests", "dist", "jest.config.js"]
+}