Begin groups phase 2b

This commit is contained in:
Tuan Dang
2024-04-19 16:16:16 -07:00
parent a087deb1eb
commit 54fcc23a6c
24 changed files with 394 additions and 34 deletions
@@ -0,0 +1,15 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.Users, (t) => {
t.boolean("isEmailVerified");
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.Users, (t) => {
t.dropColumn("isEmailVerified");
});
}
+2 -1
View File
@@ -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().nullable().optional()
});
export type TUsers = z.infer<typeof UsersSchema>;
@@ -17,8 +17,8 @@ export const getDefaultOnPremFeatures = () => {
customAlerts: false,
auditLogs: false,
auditLogsRetentionDays: 0,
samlSSO: false,
scim: false,
samlSSO: true,
scim: true,
ldap: false,
groups: false,
status: null,
@@ -24,8 +24,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
customAlerts: false,
auditLogs: false,
auditLogsRetentionDays: 0,
samlSSO: false,
scim: false,
samlSSO: true,
scim: true,
ldap: false,
groups: false,
status: null,
@@ -40,8 +40,8 @@ export type TFeatureSet = {
customAlerts: false;
auditLogs: false;
auditLogsRetentionDays: 0;
samlSSO: false;
scim: false;
samlSSO: true;
scim: true;
ldap: false;
groups: false;
status: null;
+18 -6
View File
@@ -57,6 +57,8 @@ type TScimServiceFactoryDep = {
export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>;
// TODO: finish updating all userId refs to orgMembershipId
export const scimServiceFactory = ({
licenseService,
scimDAL,
@@ -145,6 +147,7 @@ export const scimServiceFactory = ({
// SCIM server endpoints
const listScimUsers = async ({ offset, limit, filter, orgId }: TListScimUsersDTO): Promise<TListScimUsers> => {
console.log("listScimUsers"); // done
const org = await orgDAL.findById(orgId);
if (!org.scimEnabled)
@@ -178,9 +181,11 @@ export const scimServiceFactory = ({
findOpts
);
const scimUsers = users.map(({ userId, username, firstName, lastName, email }) =>
console.log("orgDAL.findMembership users: ", users);
const scimUsers = users.map(({ id, username, firstName, lastName, email }) =>
buildScimUser({
userId: userId ?? "",
userId: id ?? "",
username,
firstName: firstName ?? "",
lastName: lastName ?? "",
@@ -197,6 +202,7 @@ export const scimServiceFactory = ({
};
const getScimUser = async ({ userId, orgId }: TGetScimUserDTO) => {
console.log("getScimUser"); // done
const [membership] = await orgDAL
.findMembership({
userId,
@@ -221,8 +227,10 @@ export const scimServiceFactory = ({
status: 403
});
console.log("getScimUser membership: ", membership);
return buildScimUser({
userId: membership.userId as string,
userId: membership.id,
username: membership.username,
email: membership.email ?? "",
firstName: membership.firstName as string,
@@ -232,6 +240,7 @@ export const scimServiceFactory = ({
};
const createScimUser = async ({ username, email, firstName, lastName, orgId }: TCreateScimUserDTO) => {
console.log("createScimUser"); // TODO: update implementation to always create a new user and be based on orgMembershipId
const org = await orgDAL.findById(orgId);
if (!org)
@@ -331,6 +340,7 @@ export const scimServiceFactory = ({
};
const updateScimUser = async ({ userId, orgId, operations }: TUpdateScimUserDTO) => {
console.log("updateScimUser"); // done
const [membership] = await orgDAL
.findMembership({
userId,
@@ -380,7 +390,7 @@ export const scimServiceFactory = ({
}
return buildScimUser({
userId: membership.userId as string,
userId: membership.id,
username: membership.username,
email: membership.email,
firstName: membership.firstName as string,
@@ -390,6 +400,7 @@ export const scimServiceFactory = ({
};
const replaceScimUser = async ({ userId, active, orgId }: TReplaceScimUserDTO) => {
console.log("replaceScimUser"); // done
const [membership] = await orgDAL
.findMembership({
userId,
@@ -426,7 +437,7 @@ export const scimServiceFactory = ({
}
return buildScimUser({
userId: membership.userId as string,
userId: membership.id,
username: membership.username,
email: membership.email,
firstName: membership.firstName as string,
@@ -436,6 +447,7 @@ export const scimServiceFactory = ({
};
const deleteScimUser = async ({ userId, orgId }: TDeleteScimUserDTO) => {
console.log("deleteScimUser"); // done
const [membership] = await orgDAL
.findMembership({
userId,
@@ -489,7 +501,7 @@ export const scimServiceFactory = ({
buildScimGroup({
groupId: group.id,
name: group.name,
members: []
members: [] // does this need to be populated?
})
);
+1 -1
View File
@@ -315,7 +315,7 @@ export const registerRoutes = async (
});
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL });
const userService = userServiceFactory({ userDAL });
const userService = userServiceFactory({ userDAL, tokenService, smtpService });
const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL, tokenDAL: authTokenDAL });
const passwordService = authPaswordServiceFactory({
tokenService,
+62 -1
View File
@@ -2,11 +2,72 @@ 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({
method: "PATCH",
url: "/me/mfa",
@@ -27,6 +27,12 @@ export const getTokenConfig = (tokenType: TokenType) => {
const expiresAt = new Date(new Date().getTime() + 86400000);
return { token, expiresAt };
}
case TokenType.TOKEN_EMAIL_VERIFICATION: {
// generate random 6-digit code
const token = String(crypto.randomInt(10 ** 5, 10 ** 6 - 1));
const expiresAt = new Date(new Date().getTime() + 86400000);
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
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: {
@@ -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",
@@ -1,17 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Code</title>
</head>
</head>
<body>
<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 [email protected]</p>
</body>
</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 [email protected]</p>
</body>
</html>
+72 -1
View File
@@ -1,15 +1,83 @@
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 { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { AuthMethod } from "../auth/auth-type";
import { TUserDALFactory } from "./user-dal";
type TUserServiceFactoryDep = {
userDAL: TUserDALFactory;
tokenService: TAuthTokenServiceFactory;
smtpService: TSmtpService;
};
export type TUserServiceFactory = ReturnType<typeof userServiceFactory>;
export const userServiceFactory = ({ userDAL }: TUserServiceFactoryDep) => {
export const userServiceFactory = ({ userDAL, tokenService, smtpService }: TUserServiceFactoryDep) => {
const sendEmailVerificationCode = async (userId: string) => {
console.log("sendEmailVerificationCode userId: ", userId);
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" });
console.log("sendEmailVerificationCode user: ", user);
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_VERIFICATION,
userId: user.id
});
console.log("sendEmailVerificationCode 2");
await smtpService.sendMail({
template: SmtpTemplates.EmailVerification,
subjectLine: "Infisical confirmation code",
recipients: [user.email],
substitutions: {
code: token
}
});
};
const verifyEmailVerificationCode = async (userId: string, code: string) => {
console.log("verifyEmailVerificationCode args: ", {
userId,
code
});
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" });
if (!user.isEmailVerified)
throw new BadRequestError({ name: "Failed to list users with same email due to email not verified" });
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 +140,9 @@ export const userServiceFactory = ({ userDAL }: TUserServiceFactoryDep) => {
};
return {
sendEmailVerificationCode,
verifyEmailVerificationCode,
listUsersWithSameEmail,
toggleUserMfa,
updateUserName,
updateAuthMethods,