centralize email parsing logic
This commit is contained in:
@@ -7,6 +7,7 @@ import { Link } from "react-router-dom";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { UserRole } from "@shared/types";
|
import { UserRole } from "@shared/types";
|
||||||
|
import { parseEmail } from "@shared/utils/email";
|
||||||
import { UserValidation } from "@shared/validations";
|
import { UserValidation } from "@shared/validations";
|
||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
@@ -41,7 +42,7 @@ function Invite({ onSubmit }: Props) {
|
|||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const predictedDomain = user.email.split("@")[1];
|
const predictedDomain = parseEmail(user.email).domain;
|
||||||
const can = usePolicy(team);
|
const can = usePolicy(team);
|
||||||
const [role, setRole] = React.useState<UserRole>(UserRole.Member);
|
const [role, setRole] = React.useState<UserRole>(UserRole.Member);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { Context } from "koa";
|
|||||||
import Router from "koa-router";
|
import Router from "koa-router";
|
||||||
import { Profile } from "passport";
|
import { Profile } from "passport";
|
||||||
import { slugifyDomain } from "@shared/utils/domains";
|
import { slugifyDomain } from "@shared/utils/domains";
|
||||||
|
import { parseEmail } from "@shared/utils/email";
|
||||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||||
import { MicrosoftGraphError } from "@server/errors";
|
import { MicrosoftGraphError } from "@server/errors";
|
||||||
import passportMiddleware from "@server/middlewares/passport";
|
import passportMiddleware from "@server/middlewares/passport";
|
||||||
@@ -91,7 +92,7 @@ if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
|
|||||||
const team = await getTeamFromContext(ctx);
|
const team = await getTeamFromContext(ctx);
|
||||||
const client = getClientFromContext(ctx);
|
const client = getClientFromContext(ctx);
|
||||||
|
|
||||||
const domain = email.split("@")[1];
|
const domain = parseEmail(email).domain;
|
||||||
const subdomain = slugifyDomain(domain);
|
const subdomain = slugifyDomain(domain);
|
||||||
|
|
||||||
const teamName = organization.displayName;
|
const teamName = organization.displayName;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Router from "koa-router";
|
|||||||
import { Strategy } from "passport-oauth2";
|
import { Strategy } from "passport-oauth2";
|
||||||
import { languages } from "@shared/i18n";
|
import { languages } from "@shared/i18n";
|
||||||
import { slugifyDomain } from "@shared/utils/domains";
|
import { slugifyDomain } from "@shared/utils/domains";
|
||||||
|
import { parseEmail } from "@shared/utils/email";
|
||||||
import slugify from "@shared/utils/slugify";
|
import slugify from "@shared/utils/slugify";
|
||||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||||
import { InvalidRequestError, TeamDomainRequiredError } from "@server/errors";
|
import { InvalidRequestError, TeamDomainRequiredError } from "@server/errors";
|
||||||
@@ -77,8 +78,7 @@ if (env.DISCORD_CLIENT_ID && env.DISCORD_CLIENT_SECRET) {
|
|||||||
/** We have the email scope, so this should never happen */
|
/** We have the email scope, so this should never happen */
|
||||||
throw InvalidRequestError("Discord profile email is missing");
|
throw InvalidRequestError("Discord profile email is missing");
|
||||||
}
|
}
|
||||||
const parts = email.toLowerCase().split("@");
|
const { domain } = parseEmail(email);
|
||||||
const domain = parts.length && parts[1];
|
|
||||||
|
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
throw TeamDomainRequiredError();
|
throw TeamDomainRequiredError();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Router from "koa-router";
|
|||||||
import get from "lodash/get";
|
import get from "lodash/get";
|
||||||
import { Strategy } from "passport-oauth2";
|
import { Strategy } from "passport-oauth2";
|
||||||
import { slugifyDomain } from "@shared/utils/domains";
|
import { slugifyDomain } from "@shared/utils/domains";
|
||||||
|
import { parseEmail } from "@shared/utils/email";
|
||||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||||
import {
|
import {
|
||||||
OIDCMalformedUserInfoError,
|
OIDCMalformedUserInfoError,
|
||||||
@@ -92,9 +93,7 @@ if (
|
|||||||
}
|
}
|
||||||
const team = await getTeamFromContext(ctx);
|
const team = await getTeamFromContext(ctx);
|
||||||
const client = getClientFromContext(ctx);
|
const client = getClientFromContext(ctx);
|
||||||
|
const { domain } = parseEmail(profile.email);
|
||||||
const parts = profile.email.toLowerCase().split("@");
|
|
||||||
const domain = parts.length && parts[1];
|
|
||||||
|
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
throw OIDCMalformedUserInfoError();
|
throw OIDCMalformedUserInfoError();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { InferCreationAttributes } from "sequelize";
|
import { InferCreationAttributes } from "sequelize";
|
||||||
import { UserRole } from "@shared/types";
|
import { UserRole } from "@shared/types";
|
||||||
|
import { parseEmail } from "@shared/utils/email";
|
||||||
import InviteAcceptedEmail from "@server/emails/templates/InviteAcceptedEmail";
|
import InviteAcceptedEmail from "@server/emails/templates/InviteAcceptedEmail";
|
||||||
import {
|
import {
|
||||||
DomainNotAllowedError,
|
DomainNotAllowedError,
|
||||||
@@ -226,7 +227,7 @@ export default async function userProvisioner({
|
|||||||
|
|
||||||
// If the team settings do not allow this domain,
|
// If the team settings do not allow this domain,
|
||||||
// throw an error and fail user creation.
|
// throw an error and fail user creation.
|
||||||
const domain = email.split("@")[1];
|
const { domain } = parseEmail(email);
|
||||||
if (team && !(await team.isDomainAllowed(domain))) {
|
if (team && !(await team.isDomainAllowed(domain))) {
|
||||||
throw DomainNotAllowedError();
|
throw DomainNotAllowedError();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import "./bootstrap";
|
import "./bootstrap";
|
||||||
import { UserRole } from "@shared/types";
|
import { UserRole } from "@shared/types";
|
||||||
|
import { parseEmail } from "@shared/utils/email";
|
||||||
import teamCreator from "@server/commands/teamCreator";
|
import teamCreator from "@server/commands/teamCreator";
|
||||||
import env from "@server/env";
|
import env from "@server/env";
|
||||||
import { Team, User } from "@server/models";
|
import { Team, User } from "@server/models";
|
||||||
@@ -10,6 +11,7 @@ const email = process.argv[2];
|
|||||||
export default async function main(exit = false) {
|
export default async function main(exit = false) {
|
||||||
const teamCount = await Team.count();
|
const teamCount = await Team.count();
|
||||||
if (teamCount === 0) {
|
if (teamCount === 0) {
|
||||||
|
const name = parseEmail(email).local;
|
||||||
const user = await sequelize.transaction(async (transaction) => {
|
const user = await sequelize.transaction(async (transaction) => {
|
||||||
const team = await teamCreator({
|
const team = await teamCreator({
|
||||||
name: "Wiki",
|
name: "Wiki",
|
||||||
@@ -22,7 +24,7 @@ export default async function main(exit = false) {
|
|||||||
return await User.create(
|
return await User.create(
|
||||||
{
|
{
|
||||||
teamId: team.id,
|
teamId: team.id,
|
||||||
name: email.split("@")[0],
|
name,
|
||||||
email,
|
email,
|
||||||
role: UserRole.Admin,
|
role: UserRole.Admin,
|
||||||
},
|
},
|
||||||
|
|||||||
24
shared/utils/email.test.ts
Normal file
24
shared/utils/email.test.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { parseEmail } from "./email";
|
||||||
|
|
||||||
|
describe("parseEmail", () => {
|
||||||
|
it("should correctly parse email", () => {
|
||||||
|
expect(parseEmail("tom@example.com")).toEqual({
|
||||||
|
local: "tom",
|
||||||
|
domain: "example.com",
|
||||||
|
});
|
||||||
|
expect(parseEmail("tom.m@example.com")).toEqual({
|
||||||
|
local: "tom.m",
|
||||||
|
domain: "example.com",
|
||||||
|
});
|
||||||
|
expect(parseEmail("tom@subdomain.domain.com")).toEqual({
|
||||||
|
local: "tom",
|
||||||
|
domain: "subdomain.domain.com",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error for invalid email", () => {
|
||||||
|
expect(() => parseEmail("")).toThrow();
|
||||||
|
expect(() => parseEmail("invalid")).toThrow();
|
||||||
|
expect(() => parseEmail("invalid@")).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
15
shared/utils/email.ts
Normal file
15
shared/utils/email.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Parse an email address into its local and domain parts.
|
||||||
|
*
|
||||||
|
* @param email The email address to parse
|
||||||
|
* @returns The local and domain parts of the email address, in lowercase
|
||||||
|
*/
|
||||||
|
export function parseEmail(email: string): { local: string; domain: string } {
|
||||||
|
const [local, domain] = email.toLowerCase().split("@");
|
||||||
|
|
||||||
|
if (!domain) {
|
||||||
|
throw new Error("Invalid email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { local, domain };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user