mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-15 19:33:32 +00:00
Compare commits
1 Commits
daniel/cus
...
groups-pha
Author | SHA1 | Date | |
---|---|---|---|
beb54e8a12 |
@ -21,7 +21,8 @@ export const UsersSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
isGhost: z.boolean().default(false),
|
||||
username: z.string()
|
||||
username: z.string(),
|
||||
isEmailVerified: z.boolean().default(false).nullable().optional()
|
||||
});
|
||||
|
||||
export type TUsers = z.infer<typeof UsersSchema>;
|
||||
|
@ -29,6 +29,7 @@ export async function seed(knex: Knex): Promise<void> {
|
||||
lastName: "",
|
||||
authMethods: [AuthMethod.EMAIL],
|
||||
isAccepted: true,
|
||||
isEmailVerified: true,
|
||||
isMfaEnabled: false,
|
||||
mfaMethods: null,
|
||||
devices: null
|
||||
|
@ -220,7 +220,8 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
// displayName: z.string().trim(),
|
||||
externalId: z.string().trim(),
|
||||
displayName: z.string().trim(),
|
||||
active: z.boolean()
|
||||
}),
|
||||
response: {
|
||||
@ -249,11 +250,12 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
const primaryEmail = req.body.emails?.find((email) => email.primary)?.value;
|
||||
|
||||
const user = await req.server.services.scim.createScimUser({
|
||||
orgId: req.permission.orgId,
|
||||
username: req.body.userName,
|
||||
email: primaryEmail,
|
||||
firstName: req.body.name.givenName,
|
||||
lastName: req.body.name.familyName,
|
||||
orgId: req.permission.orgId
|
||||
externalId: req.body.externalId
|
||||
});
|
||||
|
||||
return user;
|
||||
@ -400,7 +402,12 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
schemas: z.array(z.string()),
|
||||
id: z.string().trim(),
|
||||
displayName: z.string().trim(),
|
||||
members: z.array(z.any()).length(0)
|
||||
members: z.array(
|
||||
z.object({
|
||||
value: z.string(),
|
||||
display: z.string()
|
||||
})
|
||||
)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -482,8 +489,6 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||
handler: async (req) => {
|
||||
// console.log("PATCH /Groups/:groupId req.body: ", req.body);
|
||||
// console.log("PATCH /Groups/:groupId req.body: ", req.body.Operations[0]);
|
||||
const group = await req.server.services.scim.updateScimGroupNamePatch({
|
||||
groupId: req.params.groupId,
|
||||
orgId: req.permission.orgId,
|
||||
|
@ -24,10 +24,10 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
customAlerts: false,
|
||||
auditLogs: false,
|
||||
auditLogsRetentionDays: 0,
|
||||
samlSSO: false,
|
||||
scim: false,
|
||||
samlSSO: true,
|
||||
scim: true,
|
||||
ldap: false,
|
||||
groups: false,
|
||||
groups: true,
|
||||
status: null,
|
||||
trial_end: null,
|
||||
has_used_trial: true,
|
||||
|
@ -40,10 +40,10 @@ export type TFeatureSet = {
|
||||
customAlerts: false;
|
||||
auditLogs: false;
|
||||
auditLogsRetentionDays: 0;
|
||||
samlSSO: false;
|
||||
scim: false;
|
||||
samlSSO: true;
|
||||
scim: true;
|
||||
ldap: false;
|
||||
groups: false;
|
||||
groups: true;
|
||||
status: null;
|
||||
trial_end: null;
|
||||
has_used_trial: true;
|
||||
|
@ -16,6 +16,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
@ -43,6 +44,7 @@ import {
|
||||
type TScimServiceFactoryDep = {
|
||||
scimDAL: Pick<TScimDALFactory, "create" | "find" | "findById" | "deleteById">;
|
||||
userDAL: Pick<TUserDALFactory, "findOne" | "create" | "transaction">;
|
||||
userAliasDAL: Pick<TUserAliasDALFactory, "create">;
|
||||
orgDAL: Pick<
|
||||
TOrgDALFactory,
|
||||
"createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction"
|
||||
@ -231,7 +233,7 @@ export const scimServiceFactory = ({
|
||||
});
|
||||
};
|
||||
|
||||
const createScimUser = async ({ username, email, firstName, lastName, orgId }: TCreateScimUserDTO) => {
|
||||
const createScimUser = async ({ orgId, username, email, firstName, lastName }: TCreateScimUserDTO) => {
|
||||
const org = await orgDAL.findById(orgId);
|
||||
|
||||
if (!org)
|
||||
@ -473,7 +475,19 @@ export const scimServiceFactory = ({
|
||||
};
|
||||
|
||||
const listScimGroups = async ({ orgId, offset, limit }: TListScimGroupsDTO) => {
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.groups)
|
||||
throw new BadRequestError({
|
||||
message: "Failed to list SCIM groups due to plan restriction. Upgrade plan to list SCIM groups."
|
||||
});
|
||||
|
||||
const org = await orgDAL.findById(orgId);
|
||||
if (!org) {
|
||||
throw new ScimRequestError({
|
||||
detail: "Organization Not Found",
|
||||
status: 404
|
||||
});
|
||||
}
|
||||
|
||||
if (!org.scimEnabled)
|
||||
throw new ScimRequestError({
|
||||
@ -501,7 +515,19 @@ export const scimServiceFactory = ({
|
||||
};
|
||||
|
||||
const createScimGroup = async ({ displayName, orgId }: TCreateScimGroupDTO) => {
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.groups)
|
||||
throw new BadRequestError({
|
||||
message: "Failed to create a SCIM group due to plan restriction. Upgrade plan to create a SCIM group."
|
||||
});
|
||||
|
||||
const org = await orgDAL.findById(orgId);
|
||||
if (!org) {
|
||||
throw new ScimRequestError({
|
||||
detail: "Organization Not Found",
|
||||
status: 404
|
||||
});
|
||||
}
|
||||
|
||||
if (!org.scimEnabled)
|
||||
throw new ScimRequestError({
|
||||
@ -524,6 +550,12 @@ export const scimServiceFactory = ({
|
||||
};
|
||||
|
||||
const getScimGroup = async ({ groupId, orgId }: TGetScimGroupDTO) => {
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.groups)
|
||||
throw new BadRequestError({
|
||||
message: "Failed to get SCIM group due to plan restriction. Upgrade plan to get SCIM group."
|
||||
});
|
||||
|
||||
const group = await groupDAL.findOne({
|
||||
id: groupId,
|
||||
orgId
|
||||
@ -554,6 +586,26 @@ export const scimServiceFactory = ({
|
||||
};
|
||||
|
||||
const updateScimGroupNamePut = async ({ groupId, orgId, displayName }: TUpdateScimGroupNamePutDTO) => {
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.groups)
|
||||
throw new BadRequestError({
|
||||
message: "Failed to update SCIM group due to plan restriction. Upgrade plan to update SCIM group."
|
||||
});
|
||||
|
||||
const org = await orgDAL.findById(orgId);
|
||||
if (!org) {
|
||||
throw new ScimRequestError({
|
||||
detail: "Organization Not Found",
|
||||
status: 404
|
||||
});
|
||||
}
|
||||
|
||||
if (!org.scimEnabled)
|
||||
throw new ScimRequestError({
|
||||
detail: "SCIM is disabled for the organization",
|
||||
status: 403
|
||||
});
|
||||
|
||||
const [group] = await groupDAL.update(
|
||||
{
|
||||
id: groupId,
|
||||
@ -580,7 +632,19 @@ export const scimServiceFactory = ({
|
||||
|
||||
// TODO: add support for add/remove op
|
||||
const updateScimGroupNamePatch = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => {
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.groups)
|
||||
throw new BadRequestError({
|
||||
message: "Failed to update SCIM group due to plan restriction. Upgrade plan to update SCIM group."
|
||||
});
|
||||
|
||||
const org = await orgDAL.findById(orgId);
|
||||
if (!org) {
|
||||
throw new ScimRequestError({
|
||||
detail: "Organization Not Found",
|
||||
status: 404
|
||||
});
|
||||
}
|
||||
|
||||
if (!org.scimEnabled)
|
||||
throw new ScimRequestError({
|
||||
@ -635,6 +699,26 @@ export const scimServiceFactory = ({
|
||||
};
|
||||
|
||||
const deleteScimGroup = async ({ groupId, orgId }: TDeleteScimGroupDTO) => {
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.groups)
|
||||
throw new BadRequestError({
|
||||
message: "Failed to delete SCIM group due to plan restriction. Upgrade plan to delete SCIM group."
|
||||
});
|
||||
|
||||
const org = await orgDAL.findById(orgId);
|
||||
if (!org) {
|
||||
throw new ScimRequestError({
|
||||
detail: "Organization Not Found",
|
||||
status: 404
|
||||
});
|
||||
}
|
||||
|
||||
if (!org.scimEnabled)
|
||||
throw new ScimRequestError({
|
||||
detail: "SCIM is disabled for the organization",
|
||||
status: 403
|
||||
});
|
||||
|
||||
const [group] = await groupDAL.delete({
|
||||
id: groupId,
|
||||
orgId
|
||||
|
@ -37,6 +37,7 @@ export type TCreateScimUserDTO = {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
orgId: string;
|
||||
externalId: string;
|
||||
};
|
||||
|
||||
export type TUpdateScimUserDTO = {
|
||||
|
@ -41,8 +41,8 @@ export const secretsLimit: RateLimitOptions = {
|
||||
};
|
||||
|
||||
export const authRateLimit: RateLimitOptions = {
|
||||
timeWindow: 60 * 1000,
|
||||
max: 60,
|
||||
timeWindow: 5 * 60 * 1000,
|
||||
max: 30,
|
||||
keyGenerator: (req) => req.realIp
|
||||
};
|
||||
|
||||
|
@ -286,6 +286,7 @@ export const registerRoutes = async (
|
||||
licenseService,
|
||||
scimDAL,
|
||||
userDAL,
|
||||
userAliasDAL,
|
||||
orgDAL,
|
||||
projectDAL,
|
||||
projectMembershipDAL,
|
||||
@ -315,7 +316,14 @@ export const registerRoutes = async (
|
||||
});
|
||||
|
||||
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL });
|
||||
const userService = userServiceFactory({ userDAL });
|
||||
const userService = userServiceFactory({
|
||||
userDAL,
|
||||
orgDAL,
|
||||
projectMembershipDAL,
|
||||
projectUserMembershipRoleDAL,
|
||||
tokenService,
|
||||
smtpService
|
||||
});
|
||||
const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL, tokenDAL: authTokenDAL });
|
||||
const passwordService = authPaswordServiceFactory({
|
||||
tokenService,
|
||||
|
@ -2,11 +2,93 @@ import { z } from "zod";
|
||||
|
||||
import { AuthTokenSessionsSchema, OrganizationsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
|
||||
import { ApiKeysSchema } from "@app/db/schemas/api-keys";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMethod, AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerUserRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/me/emails/code",
|
||||
config: {
|
||||
rateLimit: authRateLimit
|
||||
},
|
||||
schema: {
|
||||
response: {
|
||||
200: z.object({})
|
||||
}
|
||||
},
|
||||
preHandler: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
await server.services.user.sendEmailVerificationCode(req.permission.id);
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/me/emails/verify",
|
||||
config: {
|
||||
rateLimit: authRateLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
code: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({})
|
||||
}
|
||||
},
|
||||
preHandler: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
await server.services.user.verifyEmailVerificationCode(req.permission.id, req.body.code);
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/me/users/same-email",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
response: {
|
||||
200: z.object({
|
||||
users: UsersSchema.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
preHandler: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const users = await server.services.user.listUsersWithSameEmail(req.permission.id);
|
||||
return {
|
||||
users
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// server.route({ // TODO(dangtony98)
|
||||
// method: "POST",
|
||||
// url: "/me/users/merge",
|
||||
// config: {
|
||||
// rateLimit: authRateLimit
|
||||
// },
|
||||
// schema: {
|
||||
// body: z.object({
|
||||
// username: z.string().trim()
|
||||
// }),
|
||||
// response: {
|
||||
// 200: z.object({})
|
||||
// }
|
||||
// },
|
||||
// preHandler: verifyAuth([AuthMode.JWT]),
|
||||
// handler: async (req) => {
|
||||
// console.log("POST /me/users/merge req.body: ", req.body);
|
||||
// return {};
|
||||
// }
|
||||
// });
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/me/mfa",
|
||||
|
@ -27,6 +27,11 @@ export const getTokenConfig = (tokenType: TokenType) => {
|
||||
const expiresAt = new Date(new Date().getTime() + 86400000);
|
||||
return { token, expiresAt };
|
||||
}
|
||||
case TokenType.TOKEN_EMAIL_VERIFICATION: {
|
||||
const token = crypto.randomBytes(16).toString("hex");
|
||||
const expiresAt = new Date(new Date().getTime() + 300000);
|
||||
return { token, expiresAt };
|
||||
}
|
||||
case TokenType.TOKEN_EMAIL_MFA: {
|
||||
// generate random 6-digit code
|
||||
const token = String(crypto.randomInt(10 ** 5, 10 ** 6 - 1));
|
||||
|
@ -1,5 +1,6 @@
|
||||
export enum TokenType {
|
||||
TOKEN_EMAIL_CONFIRMATION = "emailConfirmation",
|
||||
TOKEN_EMAIL_VERIFICATION = "emailVerification", // unverified -> verified email
|
||||
TOKEN_EMAIL_MFA = "emailMfa",
|
||||
TOKEN_EMAIL_ORG_INVITATION = "organizationInvitation",
|
||||
TOKEN_EMAIL_PASSWORD_RESET = "passwordReset"
|
||||
|
@ -60,7 +60,7 @@ export const authSignupServiceFactory = ({
|
||||
});
|
||||
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.EmailVerification,
|
||||
template: SmtpTemplates.SignupEmailVerification,
|
||||
subjectLine: "Infisical confirmation code",
|
||||
recipients: [email],
|
||||
substitutions: {
|
||||
@ -129,7 +129,16 @@ export const authSignupServiceFactory = ({
|
||||
}
|
||||
|
||||
const updateduser = await authDAL.transaction(async (tx) => {
|
||||
const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx);
|
||||
const us = await userDAL.updateById(
|
||||
user.id,
|
||||
{
|
||||
firstName,
|
||||
lastName,
|
||||
isAccepted: true,
|
||||
isEmailVerified: true
|
||||
},
|
||||
tx
|
||||
);
|
||||
if (!us) throw new Error("User not found");
|
||||
const userEncKey = await userDAL.upsertUserEncryptionKey(
|
||||
us.id,
|
||||
@ -243,7 +252,16 @@ export const authSignupServiceFactory = ({
|
||||
});
|
||||
|
||||
const updateduser = await authDAL.transaction(async (tx) => {
|
||||
const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx);
|
||||
const us = await userDAL.updateById(
|
||||
user.id,
|
||||
{
|
||||
firstName,
|
||||
lastName,
|
||||
isAccepted: true,
|
||||
isEmailVerified: true
|
||||
},
|
||||
tx
|
||||
);
|
||||
if (!us) throw new Error("User not found");
|
||||
const userEncKey = await userDAL.upsertUserEncryptionKey(
|
||||
us.id,
|
||||
|
@ -175,7 +175,8 @@ export const orgServiceFactory = ({
|
||||
authMethods: [AuthMethod.EMAIL],
|
||||
username: email,
|
||||
email,
|
||||
isAccepted: true
|
||||
isAccepted: true,
|
||||
isEmailVerified: false
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
@ -17,6 +17,7 @@ export type TSmtpSendMail = {
|
||||
export type TSmtpService = ReturnType<typeof smtpServiceFactory>;
|
||||
|
||||
export enum SmtpTemplates {
|
||||
SignupEmailVerification = "signupEmailVerification.handlebars",
|
||||
EmailVerification = "emailVerification.handlebars",
|
||||
SecretReminder = "secretReminder.handlebars",
|
||||
EmailMfa = "emailMfa.handlebars",
|
||||
|
@ -9,9 +9,8 @@
|
||||
|
||||
<body>
|
||||
<h2>Confirm your email address</h2>
|
||||
<p>Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.</p>
|
||||
<p>Your confirmation code is below — enter it in the browser window where you've started confirming your email.</p>
|
||||
<h1>{{code}}</h1>
|
||||
<p>Questions about setting up Infisical? Email us at support@infisical.com</p>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>Code</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Confirm your email address</h2>
|
||||
<p>Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.</p>
|
||||
<h1>{{code}}</h1>
|
||||
<p>Questions about setting up Infisical? Email us at support@infisical.com</p>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -102,7 +102,8 @@ export const superAdminServiceFactory = ({
|
||||
superAdmin: true,
|
||||
isGhost: false,
|
||||
isAccepted: true,
|
||||
authMethods: [AuthMethod.EMAIL]
|
||||
authMethods: [AuthMethod.EMAIL],
|
||||
isEmailVerified: false
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
@ -4,13 +4,17 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
|
||||
export const normalizeUsername = async (username: string, userDAL: Pick<TUserDALFactory, "findOne">) => {
|
||||
let attempt = slugify(username);
|
||||
let attempt = slugify(username, {
|
||||
preserveCharacters: ["@", "."]
|
||||
});
|
||||
|
||||
let user = await userDAL.findOne({ username: attempt });
|
||||
if (!user) return attempt;
|
||||
|
||||
while (true) {
|
||||
attempt = slugify(`${username}-${alphaNumericNanoId(4)}`);
|
||||
attempt = slugify(`${username}-${alphaNumericNanoId(4)}`, {
|
||||
preserveCharacters: ["@", "."]
|
||||
});
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
user = await userDAL.findOne({ username: attempt });
|
||||
|
||||
|
@ -1,15 +1,79 @@
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||
import { TProjectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
|
||||
import { AuthMethod } from "../auth/auth-type";
|
||||
import { TUserDALFactory } from "./user-dal";
|
||||
|
||||
type TUserServiceFactoryDep = {
|
||||
userDAL: TUserDALFactory;
|
||||
orgDAL: TOrgDALFactory;
|
||||
projectMembershipDAL: TProjectMembershipDALFactory;
|
||||
projectUserMembershipRoleDAL: TProjectUserMembershipRoleDALFactory;
|
||||
tokenService: TAuthTokenServiceFactory;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
};
|
||||
|
||||
export type TUserServiceFactory = ReturnType<typeof userServiceFactory>;
|
||||
|
||||
export const userServiceFactory = ({ userDAL }: TUserServiceFactoryDep) => {
|
||||
export const userServiceFactory = ({ userDAL, tokenService, smtpService }: TUserServiceFactoryDep) => {
|
||||
const sendEmailVerificationCode = async (userId: string) => {
|
||||
const user = await userDAL.findById(userId);
|
||||
if (!user) throw new BadRequestError({ name: "Failed to find user" });
|
||||
if (!user.email)
|
||||
throw new BadRequestError({ name: "Failed to send email verification code due to no email on user" });
|
||||
if (user.isEmailVerified)
|
||||
throw new BadRequestError({ name: "Failed to send email verification code due to email already verified" });
|
||||
|
||||
const token = await tokenService.createTokenForUser({
|
||||
type: TokenType.TOKEN_EMAIL_VERIFICATION,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.EmailVerification,
|
||||
subjectLine: "Infisical confirmation code",
|
||||
recipients: [user.email],
|
||||
substitutions: {
|
||||
code: token
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const verifyEmailVerificationCode = async (userId: string, code: string) => {
|
||||
const user = await userDAL.findById(userId);
|
||||
if (!user) throw new BadRequestError({ name: "Failed to find user" });
|
||||
if (user.isEmailVerified)
|
||||
throw new BadRequestError({ name: "Failed to verify email verification code due to email already verified" });
|
||||
|
||||
await tokenService.validateTokenForUser({
|
||||
type: TokenType.TOKEN_EMAIL_VERIFICATION,
|
||||
userId: user.id,
|
||||
code
|
||||
});
|
||||
|
||||
await userDAL.updateById(userId, { isEmailVerified: true });
|
||||
};
|
||||
|
||||
// lists users with same verified email only
|
||||
const listUsersWithSameEmail = async (userId: string) => {
|
||||
const user = await userDAL.findById(userId);
|
||||
if (!user) throw new BadRequestError({ name: "Failed to find user" });
|
||||
if (!user.email)
|
||||
throw new BadRequestError({ name: "Failed to list users with same email due to no email on user" });
|
||||
|
||||
const users = await userDAL.find({
|
||||
email: user.email,
|
||||
isEmailVerified: true
|
||||
});
|
||||
|
||||
return users;
|
||||
};
|
||||
|
||||
const toggleUserMfa = async (userId: string, isMfaEnabled: boolean) => {
|
||||
const user = await userDAL.findById(userId);
|
||||
|
||||
@ -72,6 +136,9 @@ export const userServiceFactory = ({ userDAL }: TUserServiceFactoryDep) => {
|
||||
};
|
||||
|
||||
return {
|
||||
sendEmailVerificationCode,
|
||||
verifyEmailVerificationCode,
|
||||
listUsersWithSameEmail,
|
||||
toggleUserMfa,
|
||||
updateUserName,
|
||||
updateAuthMethods,
|
||||
|
Reference in New Issue
Block a user