link-stack/apps/metamigo-frontend/lib/nextauth-adapter.ts

234 lines
5.7 KiB
TypeScript
Raw Normal View History

2023-02-13 12:41:30 +00:00
/* eslint-disable unicorn/no-null */
2023-05-25 08:57:24 +00:00
/* eslint-disable max-params */
2023-02-13 12:41:30 +00:00
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";
2023-02-13 12:41:30 +00:00
export interface Profile {
name: string;
email: string;
emailVerified: string;
userRole: string;
avatar?: string;
image?: string;
createdBy: string;
}
2023-05-25 08:57:24 +00:00
export type User = Profile & { id: string; createdAt: Date; updatedAt: Date; };
2023-02-13 12:41:30 +00:00
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 {
2023-03-15 12:17:43 +00:00
constructor(message: any) {
2023-02-13 12:41:30 +00:00
super(message);
this.name = "UnknownError";
this.message = message;
}
toJSON() {
return {
error: {
name: this.name,
message: this.message,
// stack: this.stack
},
};
}
}
class CreateUserError extends UnknownError {
2023-03-15 12:17:43 +00:00
constructor(message: any) {
2023-02-13 12:41:30 +00:00
super(message);
this.name = "CreateUserError";
this.message = message;
}
}
2023-03-15 12:17:43 +00:00
const basicHeader = (secret: any) =>
2023-02-13 12:41:30 +00:00
"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,
});
2023-03-15 12:17:43 +00:00
} catch {
2023-02-13 12:41:30 +00:00
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");
}
}
2023-03-15 12:17:43 +00:00
return {
2023-02-13 12:41:30 +00:00
createUser,
getUser,
getUserByEmail,
getUserByProviderAccountId,
updateUser,
// deleteUser,
linkAccount,
// unlinkAccount,
createSession,
getSession,
updateSession,
deleteSession,
// @ts-expect-error: Type error
2023-03-15 12:17:43 +00:00
} as AdapterInstance<Profile, User, Session, unknown>;
2023-02-13 12:41:30 +00:00
}
return {
// @ts-expect-error: non-existent property
getAdapter,
};
};