Fixes #3412 Previously the only way to restrict the domains for a Team were with the ALLOWED_DOMAINS environment variable for self hosted instances. This PR migrates this to be a database backed setting on the Team object. This is done through the creation of a TeamDomain model that is associated with the Team and contains the domain name This settings is updated on the Security Tab. Here domains can be added or removed from the Team. On the server side, we take the code paths that previously were using ALLOWED_DOMAINS and switched them to use the Team allowed domains instead
137 lines
3.3 KiB
TypeScript
137 lines
3.3 KiB
TypeScript
import invariant from "invariant";
|
|
import { DomainNotAllowedError, MaximumTeamsError } from "@server/errors";
|
|
import Logger from "@server/logging/logger";
|
|
import { APM } from "@server/logging/tracing";
|
|
import { Team, AuthenticationProvider } from "@server/models";
|
|
import { generateAvatarUrl } from "@server/utils/avatars";
|
|
|
|
type TeamCreatorResult = {
|
|
team: Team;
|
|
authenticationProvider: AuthenticationProvider;
|
|
isNewTeam: boolean;
|
|
};
|
|
|
|
type Props = {
|
|
name: string;
|
|
domain?: string;
|
|
subdomain: string;
|
|
avatarUrl?: string | null;
|
|
authenticationProvider: {
|
|
name: string;
|
|
providerId: string;
|
|
};
|
|
};
|
|
|
|
async function teamCreator({
|
|
name,
|
|
domain,
|
|
subdomain,
|
|
avatarUrl,
|
|
authenticationProvider,
|
|
}: Props): Promise<TeamCreatorResult> {
|
|
let authP = await AuthenticationProvider.findOne({
|
|
where: authenticationProvider,
|
|
include: [
|
|
{
|
|
model: Team,
|
|
as: "team",
|
|
required: true,
|
|
},
|
|
],
|
|
});
|
|
|
|
// This authentication provider already exists which means we have a team and
|
|
// there is nothing left to do but return the existing credentials
|
|
if (authP) {
|
|
return {
|
|
authenticationProvider: authP,
|
|
team: authP.team,
|
|
isNewTeam: false,
|
|
};
|
|
}
|
|
|
|
// This team has never been seen before, if self hosted the logic is different
|
|
// to the multi-tenant version, we want to restrict to a single team that MAY
|
|
// have multiple authentication providers
|
|
if (process.env.DEPLOYMENT !== "hosted") {
|
|
const teamCount = await Team.count();
|
|
|
|
// If the self-hosted installation has a single team and the domain for the
|
|
// new team is allowed then assign the authentication provider to the
|
|
// existing team
|
|
if (teamCount === 1 && domain) {
|
|
const team = await Team.findOne();
|
|
invariant(team, "Team should exist");
|
|
|
|
if (await team.isDomainAllowed(domain)) {
|
|
authP = await team.$create<AuthenticationProvider>(
|
|
"authenticationProvider",
|
|
authenticationProvider
|
|
);
|
|
return {
|
|
authenticationProvider: authP,
|
|
team,
|
|
isNewTeam: false,
|
|
};
|
|
} else {
|
|
throw DomainNotAllowedError();
|
|
}
|
|
}
|
|
|
|
if (teamCount >= 1) {
|
|
throw MaximumTeamsError();
|
|
}
|
|
}
|
|
|
|
// If the service did not provide a logo/avatar then we attempt to generate
|
|
// one via ClearBit, or fallback to colored initials in worst case scenario
|
|
if (!avatarUrl) {
|
|
avatarUrl = await generateAvatarUrl({
|
|
name,
|
|
domain,
|
|
id: subdomain,
|
|
});
|
|
}
|
|
|
|
const transaction = await Team.sequelize!.transaction();
|
|
let team;
|
|
|
|
try {
|
|
team = await Team.create(
|
|
{
|
|
name,
|
|
avatarUrl,
|
|
authenticationProviders: [authenticationProvider],
|
|
},
|
|
{
|
|
include: "authenticationProviders",
|
|
transaction,
|
|
}
|
|
);
|
|
await transaction.commit();
|
|
} catch (err) {
|
|
await transaction.rollback();
|
|
throw err;
|
|
}
|
|
|
|
try {
|
|
await team.provisionSubdomain(subdomain);
|
|
} catch (err) {
|
|
Logger.error("Provisioning subdomain failed", err, {
|
|
teamId: team.id,
|
|
subdomain,
|
|
});
|
|
}
|
|
|
|
return {
|
|
team,
|
|
authenticationProvider: team.authenticationProviders[0],
|
|
isNewTeam: true,
|
|
};
|
|
}
|
|
|
|
export default APM.traceFunction({
|
|
serviceName: "command",
|
|
spanName: "teamCreator",
|
|
})(teamCreator);
|