52 lines
1.6 KiB
TypeScript
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.",
|
|
});
|
|
}
|
|
};
|