52 lines
1.8 KiB
TypeScript
52 lines
1.8 KiB
TypeScript
import { runWithRlsContext } from "@hivegrid/db/rls-context.server";
|
|
|
|
import { TenantUserStatus } from "~/application/enums/tenants/TenantUserStatus";
|
|
import { db } from "~/utils/db.server";
|
|
import { getUserInfo } from "~/utils/session.server";
|
|
|
|
export type ProviderOAuthCallbackAccessFailureCode = "session_mismatch" | "membership_inactive";
|
|
|
|
export class ProviderOAuthCallbackAccessError extends Error {
|
|
readonly failureCode: ProviderOAuthCallbackAccessFailureCode;
|
|
|
|
constructor(failureCode: ProviderOAuthCallbackAccessFailureCode) {
|
|
super("Provider OAuth callback identity is no longer authorized");
|
|
this.name = "ProviderOAuthCallbackAccessError";
|
|
this.failureCode = failureCode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Re-binds signed OAuth state to the current active web session and membership.
|
|
* The provider exchange and every subsequent local DB operation run only after
|
|
* these checks and inherit the exact tenant/user RLS context.
|
|
*/
|
|
export async function runAuthenticatedProviderOAuthCallback<T>(input: {
|
|
request: Request;
|
|
tenantId: string;
|
|
userId: string;
|
|
callback: () => Promise<T>;
|
|
}): Promise<T> {
|
|
const session = await getUserInfo(input.request);
|
|
if (!session.userId || session.userId !== input.userId) {
|
|
throw new ProviderOAuthCallbackAccessError("session_mismatch");
|
|
}
|
|
|
|
return runWithRlsContext({ tenantId: input.tenantId, userId: input.userId }, async () => {
|
|
const membership = await db.tenantUser.findFirst({
|
|
where: {
|
|
tenantId: input.tenantId,
|
|
userId: input.userId,
|
|
status: TenantUserStatus.ACTIVE,
|
|
tenant: { active: true },
|
|
user: { active: true },
|
|
},
|
|
select: { id: true },
|
|
});
|
|
if (!membership) {
|
|
throw new ProviderOAuthCallbackAccessError("membership_inactive");
|
|
}
|
|
return input.callback();
|
|
});
|
|
}
|