Path: blob/master/src/packages/util/auth-check-required-sso.ts
5838 views
/*1* This file is part of CoCalc: Copyright © 2022-2025 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { is_valid_email_address } from "@cocalc/util/misc";6import { Strategy } from "@cocalc/util/types/sso";78interface Opts {9email: string | undefined;10strategies: Strategy[] | undefined;11specificStrategy?: string;12}1314/**15* If the domain of a given email address belongs to an SSO strategy,16* which is configured to be an "exclusive" domain, then return the Strategy.17* This also matches subdomains, i.e. "[email protected]" is goverend by "baz.edu",18* while "[email protected]" is NOT goverend by "baz.edu".19*20* Special case: an sso domain "*" covers all domains, not covered by any other21* exclusive SSO strategy. If there is just one such "*"-SSO strategy, it will deal with all22* accounts.23*24* Optionally, if @specificStrategy is set, only that strategy or "*" is checked!25*/26export function checkRequiredSSO(opts: Opts): Strategy | undefined {27const { email, strategies, specificStrategy } = opts;28// if the domain of email is contained in any of the strategie's exclusiveDomain array, return that strategy's name29if (!email) return;30if (strategies == null || strategies.length === 0) return;31if (email.indexOf("@") === -1) return;32if (!is_valid_email_address(email)) return;33const emailDomain = getEmailDomain(email);34if (!emailDomain) return;35for (const strategy of strategies) {36if (specificStrategy && specificStrategy !== strategy.name) continue;37for (const ssoDomain of strategy.exclusiveDomains) {38if (ssoDomain === "*") continue; // dealt with below39if (emailBelongsToDomain(emailDomain, ssoDomain)) {40return strategy;41}42}43}44// At this point, we either matched an existing strategy (above) or there is a "*" strategy45for (const strategy of strategies) {46if (specificStrategy && specificStrategy !== strategy.name) continue;47if (strategy.exclusiveDomains.includes("*")) {48return strategy;49}50}51}5253export function getEmailDomain(email: string): string {54return email.trim().toLowerCase().split("@")[1];55}5657/**58* This checks if the email's domain is either exactly the ssoDomain or a subdomain.59* E.g. for "foo.edu", an email "[email protected]" is covered as well.60* Note: Both emailDomain (from getEmailDomain) and ssoDomain (from database queries)61* are normalized to lowercase, so direct comparison is safe.62*/63export function emailBelongsToDomain(64emailDomain: string,65ssoDomain: string,66): boolean {67return emailDomain === ssoDomain || emailDomain.endsWith(`.${ssoDomain}`);68}697071