zerobyte/app/server/lib/auth/middlewares/require-sso-invitation.ts
2026-02-24 11:55:05 +01:00

52 lines
1.6 KiB
TypeScript

import { APIError } from "better-auth/api";
import type { GenericEndpointContext } from "@better-auth/core";
import { db } from "~/server/db/db";
import { logger } from "~/server/utils/logger";
import { extractProviderIdFromContext, normalizeEmail } from "../utils/sso-context";
export function isSsoCallbackRequest(ctx: GenericEndpointContext | null) {
if (!ctx) {
return false;
}
return extractProviderIdFromContext(ctx) !== null;
}
export const requireSsoInvitation = async (userEmail: string, ctx: GenericEndpointContext | null) => {
if (!ctx) {
throw new APIError("BAD_REQUEST", { message: "Missing SSO context" });
}
const providerId = extractProviderIdFromContext(ctx);
if (!providerId) {
throw new APIError("BAD_REQUEST", { message: "Missing providerId in context" });
}
const provider = await db.query.ssoProvider.findFirst({ where: { providerId } });
if (!provider) {
throw new APIError("NOT_FOUND", { message: "SSO provider not found" });
}
const normalizedEmail = normalizeEmail(userEmail);
logger.debug("Checking for pending invitations", normalizedEmail, provider.organizationId);
const pendingInvitation = await db.query.invitation.findFirst({
where: {
AND: [
{ organizationId: provider.organizationId },
{ status: "pending" },
{ expiresAt: { gt: new Date() } },
{ email: normalizedEmail },
],
},
columns: { id: true, email: true },
});
logger.debug("Pending invitation result", { pendingInvitation });
if (!pendingInvitation) {
throw new APIError("FORBIDDEN", {
message: "Access denied. You must be invited to this organization before you can sign in with SSO.",
});
}
};