232 lines
5.7 KiB
TypeScript
232 lines
5.7 KiB
TypeScript
/* eslint-disable unicorn/no-null */
|
|
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) {
|
|
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) {
|
|
super(message);
|
|
this.name = "CreateUserError";
|
|
this.message = message;
|
|
}
|
|
}
|
|
|
|
const basicHeader = (secret) =>
|
|
"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 (error) {
|
|
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 Promise.resolve({
|
|
createUser,
|
|
getUser,
|
|
getUserByEmail,
|
|
getUserByProviderAccountId,
|
|
updateUser,
|
|
// deleteUser,
|
|
linkAccount,
|
|
// unlinkAccount,
|
|
createSession,
|
|
getSession,
|
|
updateSession,
|
|
deleteSession,
|
|
// @ts-expect-error: Type error
|
|
} as AdapterInstance<Profile, User, Session, unknown>);
|
|
}
|
|
|
|
return {
|
|
// @ts-expect-error: non-existent property
|
|
getAdapter,
|
|
};
|
|
};
|