diff --git a/lib/Auth.ts b/lib/Auth.ts index 813f972d..d0a3e5f7 100644 --- a/lib/Auth.ts +++ b/lib/Auth.ts @@ -27,67 +27,11 @@ export const AuthenticationOptions: AuthOptions = { clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET, allowDangerousEmailAccountLinking: true, - profile: async (profile) => { - logger.debug("Google profile:", profile); - - const existingUser = await ( - await cachedDb - ).findObject(CollectionId.Users, { email: profile.email }); - if (existingUser) { - existingUser.id = profile.sub; - return existingUser; - } - - const user = new User( - profile.name, - profile.email, - profile.picture, - false, - await GenerateSlug(await cachedDb, CollectionId.Users, profile.name), - [], - [], - ); - - user.id = profile.sub; - - return user; - }, }), SlackProvider({ clientId: process.env.NEXT_PUBLIC_SLACK_CLIENT_ID as string, clientSecret: process.env.SLACK_CLIENT_SECRET as string, allowDangerousEmailAccountLinking: true, - profile: async (profile) => { - logger.debug("Slack profile:", profile); - - const existing = await ( - await cachedDb - ).findObject(CollectionId.Users, { email: profile.email }); - - if (existing) { - existing.slackId = profile.sub; - existing.id = profile.sub; - console.log("Found existing user:", existing); - return existing; - } - - const user = new User( - profile.name, - profile.email, - profile.picture, - false, - await GenerateSlug(await cachedDb, CollectionId.Users, profile.name), - [], - [], - profile.sub, - 10, - 1, - ); - - user.id = profile.sub; - - return user; - }, }), Email({ server: { diff --git a/lib/DbInterfaceAuthAdapter.ts b/lib/DbInterfaceAuthAdapter.ts index b2ca5758..ee3a0a7c 100644 --- a/lib/DbInterfaceAuthAdapter.ts +++ b/lib/DbInterfaceAuthAdapter.ts @@ -1,4 +1,4 @@ -import { format, MongoDBAdapter } from "@next-auth/mongodb-adapter"; +import { _id, format, MongoDBAdapter } from "@next-auth/mongodb-adapter"; import { Adapter, AdapterAccount, @@ -13,8 +13,50 @@ import { GenerateSlug } from "./Utils"; import { ObjectId } from "bson"; import Logger from "./client/Logger"; import { RollbarInterface } from "./client/RollbarUtils"; +import { Profile } from "next-auth"; + +function formatTo(obj: T | undefined) { + if (!obj) return undefined; + + const formatted = format.to(obj); + + if ("_id" in obj) { + formatted._id = obj._id as ObjectId; + } + + if ("id" in obj) { + (formatted as any).id = obj.id; + } + + if ("userId" in obj) { + (formatted as any).userId = new ObjectId((obj.userId as any).toString()); + } + + return formatted; +} + +function formatFrom(obj: T | undefined) { + console.log("formatFrom", obj); + if (!obj) return undefined; + + const formatted = format.from(obj); + if ("_id" in obj) { + (formatted as any)._id = obj._id as ObjectId; + } + if ("id" in obj) { + (formatted as any).id = obj.id; + } + + if ("userId" in obj) { + (formatted as any).userId = obj.userId; + } + + return formatted; +} /** + * Should match the MongoDB adapter as closely as possible + * (https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-mongodb/src/index.ts). * @tested_by tests/lib/DbInterfaceAuthAdapter.test.ts */ export default function DbInterfaceAuthAdapter( @@ -27,264 +69,98 @@ export default function DbInterfaceAuthAdapter( new Logger(["AUTH"], false); const adapter: Adapter = { + /** + * @param data returns from the profile callback of the auth provider + */ createUser: async (data: Record) => { - const db = await dbPromise; - - const adapterUser = format.to(data); - adapterUser._id = data["_id"] as any; - - logger.debug("Creating user:", adapterUser.name); - - // Check if user already exists - const existingUser = await db.findObject(CollectionId.Users, { - email: adapterUser.email, - }); - - if (existingUser) { - // If user exists, return existing user - logger.warn("User already exists:", existingUser.name); - rollbar.warn("User already exists when creating user", { - existingUser, - data, - }); - return format.from(existingUser); - } - - logger.debug("Creating user:", adapterUser); + const profile = formatTo(data)!; const user = new User( - adapterUser.name ?? "Unknown", - adapterUser.email, - adapterUser.image ?? process.env.DEFAULT_IMAGE, + profile.name ?? profile.email ?? "Unknown User", + profile.email, + profile.image, false, await GenerateSlug( - db, + await dbPromise, CollectionId.Users, - adapterUser.name ?? "Unknown", + profile.name ?? profile.email ?? "Unknown User", ), [], [], - adapterUser.id, - 0, + profile.sub, + 10, 1, ); - user._id = new ObjectId(adapterUser._id) as any; + user._id = new ObjectId() as any; - const dbUser = await db.addObject(CollectionId.Users, user); - logger.info("Created user:", dbUser._id!.toString()); - return format.from(dbUser); + // We need the 'id' field to avoid the error "Profile id is missing in OAuth profile response" + user.id = user._id!.toString(); + + await (await dbPromise).addObject(CollectionId.Users, user); + return formatFrom(user); }, getUser: async (id: string) => { - const db = await dbPromise; - - if (id.length !== 24) return null; - - logger.debug("Getting user:", id); - - const user = await db.findObjectById( - CollectionId.Users, - new ObjectId(id), - ); - - if (!user) { - logger.warn("User not found:", id); - return null; - } - user.id = user._id!.toString()!; - return format.from(user); + const user = await ( + await dbPromise + ).findObjectById(CollectionId.Users, _id(id)); + if (!user) return null; + return formatFrom(user) as AdapterUser; }, getUserByEmail: async (email: string) => { - const db = await dbPromise; - - logger.debug("Getting user by email:", email); - - const user = await db.findObject(CollectionId.Users, { email }); - - if (!user) { - logger.warn("User not found by email:", email); - return null; - } - - user.id = user._id!.toString()!; - return format.from(user); + const user = await ( + await dbPromise + ).findObject(CollectionId.Users, { + email, + }); + if (!user) return null; + return formatFrom(user) as AdapterUser; }, getUserByAccount: async ( providerAccountId: Pick, ) => { const db = await dbPromise; - - logger.debug("Getting user by account:", providerAccountId); - - const account = await db.findObject(CollectionId.Accounts, { - providerAccountId: providerAccountId.providerAccountId, - }); - - if (!account) { - logger.warn( - "Account not found by providerAccountId:", - providerAccountId.providerAccountId, - ); - return null; - } - + const account = await db.findObject( + CollectionId.Accounts, + providerAccountId, + ); + if (!account) return null; const user = await db.findObjectById( CollectionId.Users, - account.userId as any as ObjectId, - ); - - if (!user) { - logger.warn("User not found:", account.userId); - return null; - } - - logger.debug( - "Found user by account: Account", - providerAccountId.providerAccountId, - "=> User", - user._id, - user.name, + new ObjectId(account.userId), ); - - user.id = user._id!.toString()!; - return format.from(user); + if (!user) return null; + return formatFrom(user) as AdapterUser; }, updateUser: async ( data: Partial & Pick, ) => { + const { _id, ...user } = formatTo(data as any)!; const db = await dbPromise; - const { _id, ...user } = format.to(data); - - if (!_id) { - logger.error("User ID not found when updating user:", user); - rollbar.error("User ID not found when updating user", { - user, - }); - - return format.from(user); - } - - logger.debug("Updating user:", _id); - const existing = await db.findObjectById( + const result = await db.findObjectAndUpdate( CollectionId.Users, - new ObjectId(_id), + _id, + user as unknown as Partial, ); - if (!existing) { - logger.error("User not found:", _id); - rollbar.error("User not found when updating user", { - _id, - user, - }); - return format.from(user); - } - - user.id = existing._id!.toString()!; - - await db.updateObjectById( - CollectionId.Users, - new ObjectId(_id), - user as Partial, - ); - - return format.from({ ...existing, ...user, _id: _id }); + return formatFrom(result!) as AdapterUser; }, deleteUser: async (id: string) => { + const userId = _id(id); const db = await dbPromise; - - logger.log("Deleting user:", id); - - const user = await db.findObjectById( - CollectionId.Users, - new ObjectId(id), - ); - if (!user) { - logger.error("User not found:", id); - rollbar.error("User not found when deleting user", { - id, - }); - return null; - } - - const account = await db.findObject(CollectionId.Accounts, { - userId: user._id, - }); - - const sessions = await db.findObjects(CollectionId.Sessions, { - userId: user._id, - }); - - const promises: Promise[] = [ - db.deleteObjectById(CollectionId.Users, user._id as any as ObjectId), - ]; - - if (account) { - promises.push( - db.deleteObjectById(CollectionId.Accounts, new ObjectId(account._id)), - ); - } - - if (sessions.length) { - promises.push( - Promise.all( - sessions.map((session) => - db.deleteObjectById( - CollectionId.Sessions, - new ObjectId(session._id), - ), - ), - ), - ); - } - - await Promise.all(promises); - - return format.from(user); + await Promise.all([ + db.deleteObjects(CollectionId.Accounts, { userId: userId as any }), + db.deleteObjects(CollectionId.Sessions, { userId: userId as any }), + db.deleteObjectById(CollectionId.Users, userId), + ]); }, /** * Creates an account */ linkAccount: async (data: Record) => { - const db = await dbPromise; - - const account = format.to(data); - account.userId = data["userId"] as any; // userId gets overwritten for some reason - - logger.debug( - "Linking account: providerAccountId", - account.providerAccountId, - "userId:", - account.userId, - ); - - const existingAccount = await db.findObject(CollectionId.Accounts, { - providerAccountId: account.providerAccountId, - }); - - if (existingAccount) { - logger.warn( - "Account already exists:", - existingAccount.providerAccountId, - ); - rollbar.warn("Account already exists when linking account", { - account, - }); - - let formattedAccount: AdapterAccount; - - // Sometimes gives an error about not finding toHexString. - try { - formattedAccount = format.from(existingAccount); - } catch (e) { - account.userId = new ObjectId(account.userId) as any; - formattedAccount = format.from(account); - } - return formattedAccount; - } - - await db.addObject(CollectionId.Accounts, account); - + const account = formatTo(data as any)!; + await (await dbPromise).addObject(CollectionId.Accounts, account); return account; }, /** @@ -293,211 +169,79 @@ export default function DbInterfaceAuthAdapter( unlinkAccount: async ( providerAccountId: Pick, ) => { - const db = await dbPromise; - - logger.debug("Unlinking account:", providerAccountId.providerAccountId); - - const account = await db.findObject(CollectionId.Accounts, { - providerAccountId: providerAccountId.providerAccountId, - }); - - if (!account) { - logger.warn( - "Account not found by providerAccountId:", - providerAccountId.providerAccountId, - ); - rollbar.warn("Account not found when unlinking account", { - providerAccountId, - }); - return null; - } - - await db.deleteObjectById( - CollectionId.Accounts, - new ObjectId(account._id), - ); - - return format.from(account); + const account = await ( + await dbPromise + ).findObjectAndDelete(CollectionId.Accounts, providerAccountId); + return formatFrom(account!) ?? null; }, getSessionAndUser: async (sessionToken: string) => { const db = await dbPromise; - const session = await db.findObject(CollectionId.Sessions, { sessionToken, }); - - if (!session) { - // Weirdly, this is ok. - logger.warn("Session not found:", sessionToken); - return null; - } + if (!session) return null; const user = await db.findObjectById( CollectionId.Users, new ObjectId(session.userId), ); - - if (!user) { - logger.warn("User not found:", session.userId); - return null; - } - - logger.debug( - "Got session and user. Session Token:", - sessionToken, - "User:", - user._id, - user.name, - ); + if (!user) return null; return { - session: format.from(session), - user: { - ...format.from(user), - _id: user._id, - }, + user: formatFrom(user) as any as AdapterUser, + session: formatFrom(session) as any as AdapterSession, }; }, createSession: async (data: Record) => { - const db = await dbPromise; - - const session = format.to(data); - session.userId = data["userId"] as any; // userId gets overwritten for some reason - - if (!session.userId) { - logger.error("User ID not found in session:", session); - rollbar.error("User ID not found in session when creating session", { - session, - }); - - const dbSession = await db.addObject( - CollectionId.Sessions, - session as unknown as Session, - ); - - return format.from(dbSession); - } - - logger.debug( - "Creating session:", - session._id, - "with user", - session.userId, - ); - - const user = await db.findObjectById( - CollectionId.Users, - new ObjectId(session.userId), - ); - - if (!user) { - logger.warn("User not found:", session.userId); - rollbar.warn("User not found", { - session, - }); - } else session.userId = user._id as any; - - const dbSession = await db.addObject( - CollectionId.Sessions, - session as unknown as Session, - ); - - return format.from(dbSession); + const session = formatTo(data as any)!; + await (await dbPromise).addObject(CollectionId.Sessions, session as any); + return formatFrom(session)!; }, updateSession: async ( data: Partial & Pick, ) => { - const db = await dbPromise; - const { _id, ...session } = format.to(data); - session.userId = data["userId"] as any; // userId gets overwritten for some reason - - logger.debug("Updating session:", session.sessionToken); + const { _id, ...session } = formatTo(data as any)!; + const db = await dbPromise; const existing = await db.findObject(CollectionId.Sessions, { sessionToken: session.sessionToken, }); - if (!existing) { - logger.error("Session not found:", session.sessionToken); - rollbar.error("Session not found when updating session", { - session, - }); - return null; - } - - if (session.userId) { - session.userId = new ObjectId(session.userId) as any; - } - await db.updateObjectById( CollectionId.Sessions, - new ObjectId(existing._id), - session as unknown as Partial, - ); - - return format.from({ ...existing, ...data }); + existing?._id as any, + session as any, + ); + return formatFrom({ + _id, + ...existing, + ...session, + }) as any as AdapterSession; }, deleteSession: async (sessionToken: string) => { - const db = await dbPromise; - - logger.debug("Deleting session:", sessionToken); - - const session = await db.findObject(CollectionId.Sessions, { + const session = await ( + await dbPromise + ).findObjectAndDelete(CollectionId.Sessions, { sessionToken, }); - - if (!session) { - logger.warn("Session not found:", sessionToken); - rollbar.warn("Session not found when deleting session", { - sessionToken, - }); - return null; - } - - await db.deleteObjectById( - CollectionId.Sessions, - new ObjectId(session._id), - ); - - return format.from(session); + return formatFrom(session!) as any as AdapterSession; }, createVerificationToken: async (token: VerificationToken) => { - const db = await dbPromise; - - logger.debug("Creating verification token:", token.identifier); - - await db.addObject( - CollectionId.VerificationTokens, - format.to(token) as VerificationToken, - ); + await ( + await dbPromise + ).addObject(CollectionId.VerificationTokens, format.to(token) as any); return token; }, useVerificationToken: async (token: { identifier: string; token: string; }) => { - const db = await dbPromise; - - logger.info("Using verification token:", token.identifier); - - const existing = await db.findObject(CollectionId.VerificationTokens, { - token: token.token, - }); - - if (!existing) { - logger.warn("Verification token not found:", token.token); - rollbar.warn("Verification token not found when using token", { - token, - }); - return null; - } - - await db.deleteObjectById( - CollectionId.VerificationTokens, - new ObjectId(existing._id), - ); - - return format.from(existing); + const verificationToken = await ( + await dbPromise + ).findObjectAndDelete(CollectionId.VerificationTokens, token); + if (!verificationToken) return null; + const { _id, ...rest } = verificationToken; + return rest; }, }; diff --git a/lib/Types.ts b/lib/Types.ts index 8127ae02..a25a741e 100644 --- a/lib/Types.ts +++ b/lib/Types.ts @@ -54,6 +54,7 @@ export class User implements NextAuthUser { onboardingComplete: boolean = false; resendContactId: string | undefined = undefined; lastSignInDateTime: Date | undefined = undefined; + emailVerified: Date | undefined = undefined; constructor( name: string | undefined, diff --git a/lib/Utils.ts b/lib/Utils.ts index 09503f38..e794fa8b 100644 --- a/lib/Utils.ts +++ b/lib/Utils.ts @@ -113,6 +113,7 @@ export async function populateMissingUserFields( level: user.level ?? 0, resendContactId: user.resendContactId ?? undefined, lastSignInDateTime: user.lastSignInDateTime ?? undefined, + emailVerified: user.emailVerified ?? undefined, }; if (user._id) (filled as User)._id = user._id as unknown as string; diff --git a/lib/client/dbinterfaces/CachedDbInterface.ts b/lib/client/dbinterfaces/CachedDbInterface.ts index 2ae214e5..0903e9f5 100644 --- a/lib/client/dbinterfaces/CachedDbInterface.ts +++ b/lib/client/dbinterfaces/CachedDbInterface.ts @@ -74,4 +74,33 @@ export default class CachedDbInterface >(collection: TId, slug: string): Promise { return findObjectBySlugLookUp(this, collection, slug); } + + addOrUpdateObject( + collection: TId, + object: CollectionIdToType, + ): Promise> { + return super.addOrUpdateObject(collection, object); + } + + findObjectAndUpdate( + collection: TId, + id: ObjectId, + newValues: Partial>, + ): Promise | undefined> { + return super.findObjectAndUpdate(collection, id, newValues); + } + + deleteObjects( + collection: TId, + query: object, + ): Promise { + return super.deleteObjects(collection, query); + } + + findObjectAndDelete( + collection: TId, + query: object, + ): Promise | undefined> { + return super.findObjectAndDelete(collection, query); + } } diff --git a/lib/client/dbinterfaces/DbInterface.ts b/lib/client/dbinterfaces/DbInterface.ts index 698a8a63..29bc3172 100644 --- a/lib/client/dbinterfaces/DbInterface.ts +++ b/lib/client/dbinterfaces/DbInterface.ts @@ -4,7 +4,6 @@ import CollectionId, { SluggedCollectionId, } from "../CollectionId"; import { default as BaseDbInterface } from "mongo-anywhere/DbInterface"; -import slugToId from "@/lib/slugToId"; export type WithStringOrObjectIdId = Omit & { _id?: ObjectId | string; @@ -63,4 +62,25 @@ export default interface DbInterface collection: CollectionId, query: object, ): Promise; + + addOrUpdateObject( + collection: TId, + object: CollectionIdToType, + ): Promise>; + + findObjectAndUpdate( + collection: TId, + id: ObjectId, + newValues: Partial>, + ): Promise | undefined>; + + deleteObjects( + collection: TId, + query: object, + ): Promise; + + findObjectAndDelete( + collection: TId, + query: object, + ): Promise | undefined>; } diff --git a/lib/client/dbinterfaces/InMemoryDbInterface.ts b/lib/client/dbinterfaces/InMemoryDbInterface.ts index 779f931f..306a7204 100644 --- a/lib/client/dbinterfaces/InMemoryDbInterface.ts +++ b/lib/client/dbinterfaces/InMemoryDbInterface.ts @@ -65,4 +65,33 @@ export default class InMemoryDbInterface >(collection: TId, slug: string): Promise { return findObjectBySlugLookUp(this, collection, slug); } + + addOrUpdateObject( + collection: TId, + object: CollectionIdToType, + ): Promise> { + return super.addOrUpdateObject(collection, object); + } + + findObjectAndUpdate( + collection: TId, + id: ObjectId, + newValues: Partial>, + ): Promise | undefined> { + return super.findObjectAndUpdate(collection, id, newValues); + } + + deleteObjects( + collection: TId, + query: object, + ): Promise { + return super.deleteObjects(collection, query); + } + + findObjectAndDelete( + collection: TId, + query: object, + ): Promise | undefined> { + return super.findObjectAndDelete(collection, query); + } } diff --git a/lib/client/dbinterfaces/LocalStorageDbInterface.ts b/lib/client/dbinterfaces/LocalStorageDbInterface.ts index b7975a2d..a5f962a2 100644 --- a/lib/client/dbinterfaces/LocalStorageDbInterface.ts +++ b/lib/client/dbinterfaces/LocalStorageDbInterface.ts @@ -65,4 +65,33 @@ export default class LocalStorageDbInterface >(collection: TId, slug: string): Promise { return findObjectBySlugLookUp(this, collection, slug); } + + addOrUpdateObject( + collection: TId, + object: CollectionIdToType, + ): Promise> { + return super.addOrUpdateObject(collection, object); + } + + findObjectAndUpdate( + collection: TId, + id: ObjectId, + newValues: Partial>, + ): Promise | undefined> { + return super.findObjectAndUpdate(collection, id, newValues); + } + + deleteObjects( + collection: TId, + query: object, + ): Promise { + return super.deleteObjects(collection, query); + } + + findObjectAndDelete( + collection: TId, + query: object, + ): Promise | undefined> { + return super.findObjectAndDelete(collection, query); + } } diff --git a/package-lock.json b/package-lock.json index e1d3d706..78c99b46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "jose": "^6.0.10", "levenary": "^1.1.1", "minimongo": "^7.0.0", - "mongo-anywhere": "^1.1.15", + "mongo-anywhere": "^1.2.0", "mongodb": "^5.0.0", "next": "^15.2.4", "next-auth": "^4.24.11", @@ -8234,9 +8234,9 @@ } }, "node_modules/mongo-anywhere": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/mongo-anywhere/-/mongo-anywhere-1.1.15.tgz", - "integrity": "sha512-wN6E/jN0lae5EqAeaAaE5fdUdb+ZchZKib3FWGOOOQUYZvTv2ino9Aii3GK+uIMxNdnm6N//B0bEGEeCNbBy1g==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mongo-anywhere/-/mongo-anywhere-1.2.0.tgz", + "integrity": "sha512-0D7eooYwPY1Z4ZhTmPExKGOOfQHSEU8GpmFPqSQKlJx90vHaY0P/ABwiycjRfDRxrhg1XI8JrNdwJoWFvvsuxA==", "dependencies": { "bson": "^5.0.0", "minimongo": "^6.19.0", diff --git a/package.json b/package.json index 93953def..0e85e912 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "jose": "^6.0.10", "levenary": "^1.1.1", "minimongo": "^7.0.0", - "mongo-anywhere": "^1.1.15", + "mongo-anywhere": "^1.2.0", "mongodb": "^5.0.0", "next": "^15.2.4", "next-auth": "^4.24.11", diff --git a/tests/unit/lib/DbInterfaceAuthAdapter.test.ts b/tests/unit/lib/DbInterfaceAuthAdapter.test.ts index d1cf93be..c53e8517 100644 --- a/tests/unit/lib/DbInterfaceAuthAdapter.test.ts +++ b/tests/unit/lib/DbInterfaceAuthAdapter.test.ts @@ -84,21 +84,6 @@ describe(prototype.createUser.name, () => { expect(foundUser?.name).toBeDefined(); expect(foundUser?.image).toBeDefined(); }); - - test("Does not create a new user if one already exists", async () => { - const { db, adapter } = await getDeps(); - - const user = { - email: "test@gmail.com", - }; - - await adapter.createUser(user); - await adapter.createUser(user); - - expect( - await db.countObjects(CollectionId.Users, { email: user.email }), - ).toBe(1); - }); }); describe(prototype.getUser!.name, () => { @@ -207,7 +192,10 @@ describe(prototype.getUserByAccount!.name, () => { await db.addObject(CollectionId.Accounts, account); - const foundUser = await adapter.getUserByAccount!(account); + const foundUser = await adapter.getUserByAccount!({ + provider: account.provider, + providerAccountId: account.providerAccountId, + }); expect(foundUser).toMatchObject(addedUser); }); @@ -222,7 +210,10 @@ describe(prototype.getUserByAccount!.name, () => { userId: new ObjectId() as any, }; - const user = await adapter.getUserByAccount!(account); + const user = await adapter.getUserByAccount!({ + provider: account.provider, + providerAccountId: account.providerAccountId, + }); expect(user).toBeNull(); }); @@ -239,7 +230,10 @@ describe(prototype.getUserByAccount!.name, () => { await db.addObject(CollectionId.Accounts, account); - const user = await adapter.getUserByAccount!(account); + const user = await adapter.getUserByAccount!({ + provider: account.provider, + providerAccountId: account.providerAccountId, + }); expect(user).toBeNull(); }); @@ -269,33 +263,10 @@ describe(prototype.updateUser!.name, () => { email: user.email, }); - expect(foundUser).toMatchObject(updatedUser); - }); + // Not sure how id behaves, don't use it + foundUser!.id = foundUser!._id!.toString(); - test("Errors if not given an _id", async () => { - const { adapter, rollbar } = await getDeps(); - - const user = { - name: "Test User", - email: "test@gmail.com", - }; - - await adapter.updateUser!(user as any); - - expect(rollbar.error).toHaveBeenCalled(); - }); - - test("Errors if the user doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - const user = { - name: "Test User", - email: "test@gmail.com", - }; - - await adapter.updateUser!(user as any); - - expect(rollbar.error).toHaveBeenCalled(); + expect(foundUser).toMatchObject(updatedUser); }); test("Returns the updated user without their _id", async () => { @@ -321,21 +292,6 @@ describe(prototype.updateUser!.name, () => { expect(returnedUser).toMatchObject(expectedUser); }); - - test("Errors if no _id is provided", async () => { - const { adapter, db, rollbar } = await getDeps(); - - const user = { - name: "Test User", - email: "test@gmail.com", - }; - - await db.addObject(CollectionId.Users, user as any); - - await adapter.updateUser!({ name: "Test User 2" } as any); - - expect(rollbar.error).toHaveBeenCalled(); - }); }); describe(prototype.deleteUser!.name, () => { @@ -359,15 +315,6 @@ describe(prototype.deleteUser!.name, () => { expect(foundUser).toBeUndefined(); }); - test("Errors but returns null if the user doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - const user = await adapter.deleteUser!(new ObjectId().toString()); - - expect(user).toBeNull(); - expect(rollbar.error).toHaveBeenCalled(); - }); - test("Deletes the user's account", async () => { const { db, adapter } = await getDeps(); @@ -460,24 +407,6 @@ describe(prototype.linkAccount!.name, () => { expect(foundAccount).toEqual(account); }); - test("Warns if the account already exists", async () => { - const { adapter, db, rollbar } = await getDeps(); - - const account: Account = { - _id: new ObjectId(), - provider: "test", - type: "oauth", - providerAccountId: "1234567890", - userId: new ObjectId() as any, - }; - - await db.addObject(CollectionId.Accounts, account); - - await adapter.linkAccount!(account); - - expect(rollbar.warn).toHaveBeenCalled(); - }); - test("Does not create another account if one already exists", async () => { const { db, adapter } = await getDeps(); @@ -544,21 +473,6 @@ describe(prototype.unlinkAccount!.name, () => { expect(foundAccount).toBeUndefined(); }); - test("Warns if the account doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - const account: Account = { - provider: "test", - type: "oauth", - providerAccountId: "1234567890", - userId: new ObjectId() as any, - }; - - await adapter.unlinkAccount!(account); - - expect(rollbar.warn).toHaveBeenCalled(); - }); - test("Returns null if the account doesn't exist", async () => { const { adapter } = await getDeps(); @@ -689,33 +603,6 @@ describe(prototype.createSession!.name, () => { expect(foundSession?.userId).toEqual(session.userId); }); - - test("Errors if not given a userId", async () => { - const { adapter, rollbar } = await getDeps(); - - const session: AdapterSession = { - sessionToken: "1234567890", - userId: undefined as any, - expires: new Date(), - }; - - await adapter.createSession!(session); - expect(rollbar.error).toHaveBeenCalled(); - }); - - test("Warns if the user doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - const session: AdapterSession = { - sessionToken: "1234567890", - userId: new ObjectId() as any, - expires: new Date(), - }; - - await adapter.createSession!(session); - - expect(rollbar.warn).toHaveBeenCalled(); - }); }); describe(prototype.updateSession!.name, () => { @@ -727,21 +614,17 @@ describe(prototype.updateSession!.name, () => { name: "Test User", email: "test@gmail.com", }; - const { _id: userId } = await db.addObject(CollectionId.Users, user as any); + const session: AdapterSession = { sessionToken: "1234567890", userId: userId as any, expires: new Date(), }; - - const { _id: sessionId } = await db.addObject( - CollectionId.Sessions, - session as any, - ); + await db.addObject(CollectionId.Sessions, session as any); const updatedSession = { - sessionToken: "1234567890", + sessionToken: session.sessionToken, userId: new ObjectId() as any, }; @@ -753,34 +636,6 @@ describe(prototype.updateSession!.name, () => { expect(foundSession?.userId).toEqual(updatedSession.userId); }); - - test("Errors if not given a sessionToken", async () => { - const { adapter, rollbar } = await getDeps(); - - const session: AdapterSession = { - sessionToken: undefined as any, - userId: new ObjectId() as any, - expires: new Date(), - }; - - await adapter.updateSession!(session); - - expect(rollbar.error).toHaveBeenCalled(); - }); - - test("Errors if the session doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - const session: AdapterSession = { - sessionToken: "1234567890", - userId: new ObjectId() as any, - expires: new Date(), - }; - - await adapter.updateSession!(session); - - expect(rollbar.error).toHaveBeenCalled(); - }); }); describe(prototype.deleteSession!.name, () => { @@ -801,10 +656,7 @@ describe(prototype.deleteSession!.name, () => { expires: new Date(), }; - const { _id: sessionId } = await db.addObject( - CollectionId.Sessions, - session as any, - ); + await db.addObject(CollectionId.Sessions, session as any); await adapter.deleteSession!(session.sessionToken); @@ -815,14 +667,6 @@ describe(prototype.deleteSession!.name, () => { expect(foundSession).toBeUndefined(); }); - test("Warns if the session doesn't exist", async () => { - const { adapter, rollbar } = await getDeps(); - - await adapter.deleteSession!("1234567890"); - - expect(rollbar.warn).toHaveBeenCalled(); - }); - test("Does not delete the user", async () => { const { db, adapter } = await getDeps(); @@ -882,32 +726,39 @@ describe(prototype.createVerificationToken!.name, () => { describe(prototype.useVerificationToken!.name, () => { test("Returns token", async () => { + const { adapter, db } = await getDeps(); + const testToken = { identifier: "hi", expires: new Date(), token: "hello", }; - - const { adapter, db } = await getDeps(); - await db.addObject(CollectionId.VerificationTokens, testToken); - const foundToken = await adapter.useVerificationToken!(testToken); + + const foundToken = await adapter.useVerificationToken!({ + identifier: testToken.identifier, + token: testToken.token, + }); expect(foundToken?.identifier).toBe(testToken.identifier); expect(foundToken?.token).toBe(testToken.token); }); test("Token is removed from database", async () => { + const { adapter, db } = await getDeps(); + const testToken = { identifier: "hi", expires: new Date(), token: "hello", }; + await db.addObject(CollectionId.VerificationTokens, testToken); - const { adapter, db } = await getDeps(); + await adapter.useVerificationToken!({ + identifier: testToken.identifier, + token: testToken.token, + }); - await db.addObject(CollectionId.VerificationTokens, testToken); - await adapter.useVerificationToken!(testToken); const foundToken = await db.findObject(CollectionId.VerificationTokens, { identifier: testToken.identifier, token: testToken.token, @@ -915,17 +766,4 @@ describe(prototype.useVerificationToken!.name, () => { expect(foundToken).toBeUndefined(); }); - - test("Warns if token doesn't exist", async () => { - const testToken = { - identifier: "hi", - expires: new Date(), - token: "hello", - }; - const { adapter, rollbar } = await getDeps(); - - await adapter.useVerificationToken!(testToken); - - expect(rollbar.warn).toHaveBeenCalled(); - }); });