2023-01-24 20:02:43 +00:00
|
|
|
import type { SAMLSSORecord, OIDCSSORecord } from "@boxyhq/saml-jackson";
|
2022-12-08 23:20:24 +00:00
|
|
|
import { PrismaClient } from "@prisma/client";
|
2022-07-22 17:27:06 +00:00
|
|
|
|
2022-10-18 20:34:32 +00:00
|
|
|
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
|
|
|
import { isTeamAdmin } from "@calcom/lib/server/queries/teams";
|
2022-07-22 17:27:06 +00:00
|
|
|
import { TRPCError } from "@calcom/trpc/server";
|
|
|
|
|
|
|
|
export const samlDatabaseUrl = process.env.SAML_DATABASE_URL || "";
|
|
|
|
export const isSAMLLoginEnabled = samlDatabaseUrl.length > 0;
|
|
|
|
|
|
|
|
export const samlTenantID = "Cal.com";
|
|
|
|
export const samlProductID = "Cal.com";
|
2022-10-18 20:34:32 +00:00
|
|
|
export const samlAudience = "https://saml.cal.com";
|
|
|
|
export const samlPath = "/api/auth/saml/callback";
|
2023-01-24 20:02:43 +00:00
|
|
|
export const oidcPath = "/api/auth/oidc";
|
2023-03-07 21:31:39 +00:00
|
|
|
export const clientSecretVerifier = process.env.SAML_CLIENT_SECRET_VERIFIER || "dummy";
|
2022-07-22 17:27:06 +00:00
|
|
|
|
2022-10-18 20:34:32 +00:00
|
|
|
export const hostedCal = Boolean(HOSTED_CAL_FEATURES);
|
2022-07-22 17:27:06 +00:00
|
|
|
export const tenantPrefix = "team-";
|
|
|
|
|
2022-10-18 20:34:32 +00:00
|
|
|
const samlAdmins = (process.env.SAML_ADMINS || "").split(",");
|
|
|
|
|
2022-07-22 17:27:06 +00:00
|
|
|
export const isSAMLAdmin = (email: string) => {
|
|
|
|
for (const admin of samlAdmins) {
|
|
|
|
if (admin.toLowerCase() === email.toLowerCase() && admin.toUpperCase() === email.toUpperCase()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const samlTenantProduct = async (prisma: PrismaClient, email: string) => {
|
|
|
|
const user = await prisma.user.findUnique({
|
|
|
|
where: {
|
|
|
|
email,
|
|
|
|
},
|
|
|
|
select: {
|
|
|
|
id: true,
|
|
|
|
invitedTo: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
throw new TRPCError({
|
|
|
|
code: "UNAUTHORIZED",
|
2022-10-19 15:50:25 +00:00
|
|
|
message: "no_account_exists",
|
2022-07-22 17:27:06 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!user.invitedTo) {
|
|
|
|
throw new TRPCError({
|
|
|
|
code: "BAD_REQUEST",
|
|
|
|
message:
|
|
|
|
"Could not find a SAML Identity Provider for your email. Please contact your admin to ensure you have been given access to Cal",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
tenant: tenantPrefix + user.invitedTo,
|
|
|
|
product: samlProductID,
|
|
|
|
};
|
|
|
|
};
|
2022-10-18 20:34:32 +00:00
|
|
|
|
2022-12-08 23:20:24 +00:00
|
|
|
export const canAccess = async (user: { id: number; email: string }, teamId: number | null) => {
|
|
|
|
const { id: userId, email } = user;
|
2022-10-18 20:34:32 +00:00
|
|
|
|
|
|
|
if (!isSAMLLoginEnabled) {
|
|
|
|
return {
|
|
|
|
message: "To enable this feature, add value for `SAML_DATABASE_URL` and `SAML_ADMINS` to your `.env`",
|
|
|
|
access: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hosted
|
|
|
|
if (HOSTED_CAL_FEATURES) {
|
|
|
|
if (teamId === null || !(await isTeamAdmin(userId, teamId))) {
|
|
|
|
return {
|
|
|
|
message: "dont_have_permission",
|
|
|
|
access: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Self-hosted
|
|
|
|
if (!HOSTED_CAL_FEATURES) {
|
|
|
|
if (!isSAMLAdmin(email)) {
|
|
|
|
return {
|
|
|
|
message: "dont_have_permission",
|
|
|
|
access: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
message: "success",
|
|
|
|
access: true,
|
|
|
|
};
|
|
|
|
};
|
2023-01-24 20:02:43 +00:00
|
|
|
|
|
|
|
export type SSOConnection = (SAMLSSORecord | OIDCSSORecord) & {
|
|
|
|
type: string;
|
|
|
|
acsUrl: string | null;
|
|
|
|
entityId: string | null;
|
|
|
|
callbackUrl: string | null;
|
|
|
|
};
|