feat: SSO for orgs (#13794)
* WIP Adds the capability to add SSO setup in orgs if you are an Admin or an Owner of the org * remove sso setup from teams * allows account creation on first login * auto-add on login and dsync fixes * allow SP & idP initiated login for SAML * revert change to checkIfUserShouldbelongToOrg * fixes SP initiated Login for first time * removed stale comment * code improvement * -- * minor fixes * more fix * add description for non-admin sso settings page
This commit is contained in:
@@ -9,10 +9,12 @@ import EmailProvider from "next-auth/providers/email";
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
|
||||
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
|
||||
import createUsersAndConnectToOrg from "@calcom/features/ee/dsync/lib/users/createUsersAndConnectToOrg";
|
||||
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
|
||||
import { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
import { ENABLE_PROFILE_SWITCHER, IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { symmetricDecrypt, symmetricEncrypt } from "@calcom/lib/crypto";
|
||||
import { defaultCookies } from "@calcom/lib/default-cookies";
|
||||
@@ -42,7 +44,21 @@ const ORGANIZATIONS_AUTOLINK =
|
||||
process.env.ORGANIZATIONS_AUTOLINK === "1" || process.env.ORGANIZATIONS_AUTOLINK === "true";
|
||||
|
||||
const usernameSlug = (username: string) => `${slugify(username)}-${randomString(6).toLowerCase()}`;
|
||||
|
||||
const getDomainFromEmail = (email: string): string => email.split("@")[1];
|
||||
const getVerifiedOrganizationByAutoAcceptEmailDomain = async (domain: string) => {
|
||||
const existingOrg = await prisma.team.findFirst({
|
||||
where: {
|
||||
organizationSettings: {
|
||||
isOrganizationVerified: true,
|
||||
orgAutoAcceptEmail: domain,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
return existingOrg?.id;
|
||||
};
|
||||
const loginWithTotp = async (email: string) =>
|
||||
`/auth/login?totp=${await (await import("./signJwt")).default({ email })}`;
|
||||
|
||||
@@ -269,9 +285,7 @@ if (isSAMLLoginEnabled) {
|
||||
const user = await UserRepository.findByEmailAndIncludeProfilesAndPassword({
|
||||
email: profile.email || "",
|
||||
});
|
||||
if (!user) {
|
||||
throw new Error(ErrorCode.UserNotFound);
|
||||
}
|
||||
if (!user) throw new Error(ErrorCode.UserNotFound);
|
||||
|
||||
const [userProfile] = user.allProfiles;
|
||||
return {
|
||||
@@ -325,7 +339,6 @@ if (isSAMLLoginEnabled) {
|
||||
if (!access_token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetch user info
|
||||
const userInfo = await oauthController.userInfo(access_token);
|
||||
|
||||
@@ -334,13 +347,30 @@ if (isSAMLLoginEnabled) {
|
||||
}
|
||||
|
||||
const { id, firstName, lastName, email } = userInfo;
|
||||
const user = await UserRepository.findByEmailAndIncludeProfilesAndPassword({ email });
|
||||
let user = !email
|
||||
? undefined
|
||||
: await UserRepository.findByEmailAndIncludeProfilesAndPassword({ email });
|
||||
if (!user) {
|
||||
throw new Error(ErrorCode.UserNotFound);
|
||||
const hostedCal = Boolean(HOSTED_CAL_FEATURES);
|
||||
if (hostedCal && email) {
|
||||
const domain = getDomainFromEmail(email);
|
||||
const organizationId = await getVerifiedOrganizationByAutoAcceptEmailDomain(domain);
|
||||
if (organizationId) {
|
||||
const createUsersAndConnectToOrgProps = {
|
||||
emailsToCreate: [email],
|
||||
organizationId,
|
||||
identityProvider: IdentityProvider.SAML,
|
||||
identityProviderId: email,
|
||||
};
|
||||
await createUsersAndConnectToOrg(createUsersAndConnectToOrgProps);
|
||||
user = await UserRepository.findByEmailAndIncludeProfilesAndPassword({
|
||||
email: email,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!user) throw new Error(ErrorCode.UserNotFound);
|
||||
}
|
||||
|
||||
const [profile] = user.allProfiles;
|
||||
|
||||
const [userProfile] = user?.allProfiles;
|
||||
return {
|
||||
id: id as unknown as number,
|
||||
firstName,
|
||||
@@ -348,7 +378,7 @@ if (isSAMLLoginEnabled) {
|
||||
email,
|
||||
name: `${firstName} ${lastName}`.trim(),
|
||||
email_verified: true,
|
||||
profile,
|
||||
profile: userProfile,
|
||||
};
|
||||
},
|
||||
})
|
||||
@@ -430,7 +460,6 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||
account,
|
||||
}) {
|
||||
log.debug("callbacks:jwt", safeStringify({ token, user, account, trigger, session }));
|
||||
|
||||
// The data available in 'session' depends on what data was supplied in update method call of session
|
||||
if (trigger === "update") {
|
||||
return {
|
||||
@@ -619,7 +648,6 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||
if (account?.provider === "email") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// In this case we've already verified the credentials in the authorize
|
||||
// callback so we can sign the user in.
|
||||
// Only if provider is not saml-idp
|
||||
@@ -632,7 +660,6 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!user.email) {
|
||||
return false;
|
||||
}
|
||||
@@ -640,7 +667,6 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||
if (!user.name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (account?.provider) {
|
||||
const idP: IdentityProvider = mapIdentityProvider(account.provider);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -736,6 +762,7 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||
// If there's no existing user for this identity provider and id, create
|
||||
// a new account. If an account already exists with the incoming email
|
||||
// address return an error for now.
|
||||
|
||||
const existingUserWithEmail = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: {
|
||||
|
||||
Reference in New Issue
Block a user