/* eslint-disable unicorn/no-null */ /* eslint-disable max-params */ import type { Adapter } from "next-auth/adapters"; // @ts-expect-error: Missing export import type { AppOptions } from "next-auth"; import * as Wreck from "@hapi/wreck"; import * as Boom from "@hapi/boom"; import type { IAppConfig } from "@digiresilience/metamigo-config"; export interface Profile { name: string; email: string; emailVerified: string; userRole: string; avatar?: string; image?: string; createdBy: string; } export type User = Profile & { id: string; createdAt: Date; updatedAt: Date; }; export interface Session { userId: string; expires: Date; sessionToken: string; accessToken: string; createdAt: Date; updatedAt: Date; } // from https://github.com/nextauthjs/next-auth/blob/main/src/lib/errors.js class UnknownError extends Error { constructor(message: any) { super(message); this.name = "UnknownError"; this.message = message; } toJSON() { return { error: { name: this.name, message: this.message, // stack: this.stack }, }; } } class CreateUserError extends UnknownError { constructor(message: any) { super(message); this.name = "CreateUserError"; this.message = message; } } const basicHeader = (secret: any) => "Basic " + Buffer.from(secret + ":", "utf8").toString("base64"); export const MetamigoAdapter = (config: IAppConfig): Adapter => { if (!config) throw new Error("MetamigoAdapter: config is not defined."); const wreck = Wreck.defaults({ headers: { authorization: basicHeader(config.nextAuth.secret), }, baseUrl: `${config.frontend.apiUrl}/api/nextauth/`, maxBytes: 1024 * 1024, json: "force", }); async function getAdapter(_appOptions: AppOptions) { async function createUser(profile: Profile) { try { if (!profile.createdBy) profile = { ...profile, createdBy: "nextauth" }; profile.avatar = profile.image; delete profile.image; const { payload } = await wreck.post("createUser", { payload: profile, }); return payload; } catch { throw new CreateUserError("CREATE_USER_ERROR"); } } async function getUser(id: string) { try { const { payload } = await wreck.get(`getUser/${id}`); return payload; } catch (error) { if (Boom.isBoom(error, 404)) return null; throw new Error("GET_USER_BY_ID_ERROR"); } } async function getUserByEmail(email: string) { try { const { payload } = await wreck.get(`getUserByEmail/${email}`); return payload; } catch (error) { if (Boom.isBoom(error, 404)) return null; throw new Error("GET_USER_BY_EMAIL_ERROR"); } } async function getUserByProviderAccountId( providerId: string, providerAccountId: string ) { try { const { payload } = await wreck.get( `getUserByProviderAccountId/${providerId}/${providerAccountId}` ); return payload; } catch (error) { if (Boom.isBoom(error, 404)) return null; throw new Error("GET_USER_BY_PROVIDER_ACCOUNT_ID"); } } async function updateUser(user: User) { try { const { payload } = await wreck.put("updateUser", { payload: user, }); return payload; } catch { throw new Error("UPDATE_USER"); } } async function linkAccount( userId: string, providerId: string, providerType: string, providerAccountId: string, refreshToken: string, accessToken: string, accessTokenExpires: number ) { try { const payload = { userId, providerId, providerType, providerAccountId: `${providerAccountId}`, // must be a string refreshToken, accessToken, accessTokenExpires, }; await wreck.put("linkAccount", { payload, }); } catch { throw new Error("LINK_ACCOUNT_ERROR"); } } async function createSession(user: User) { try { const { payload } = await wreck.post("createSession", { payload: user, }); return payload; } catch { throw new Error("CREATE_SESSION_ERROR"); } } async function getSession(sessionToken: string) { try { const { payload } = await wreck.get(`getSession/${sessionToken}`); return payload; } catch (error) { if (Boom.isBoom(error, 404)) return null; throw new Error("GET_SESSION_ERROR"); } } async function updateSession(session: Session, force: boolean) { try { const payload = { ...session, expires: new Date(session.expires).getTime(), }; const { payload: result } = await wreck.put( `updateSession?force=${Boolean(force)}`, { payload, } ); return result; } catch { throw new Error("UPDATE_SESSION_ERROR"); } } async function deleteSession(sessionToken: string) { try { await wreck.delete(`deleteSession/${sessionToken}`); } catch { throw new Error("DELETE_SESSION_ERROR"); } } return { createUser, getUser, getUserByEmail, getUserByProviderAccountId, updateUser, // deleteUser, linkAccount, // unlinkAccount, createSession, getSession, updateSession, deleteSession, // @ts-expect-error: Type error } as AdapterInstance; } return { // @ts-expect-error: non-existent property getAdapter, }; };