Add user aliases concept and weave LDAP into it

This commit is contained in:
Tuan Dang
2024-03-06 12:06:40 -08:00
parent 327c5e2429
commit 76bd85efa7
39 changed files with 206 additions and 102 deletions
@@ -130,13 +130,11 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
*/
const loginGenServerPublicKey = async ({
email,
userOrgId,
providerAuthToken,
clientPublicKey
}: TLoginGenServerPublicKeyDTO) => {
const userEnc = await userDAL.findUserEncKeyByUsername({
username: email,
orgId: userOrgId
username: email
});
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
throw new Error("Failed to find user");
@@ -159,15 +157,13 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
*/
const loginExchangeClientProof = async ({
email,
userOrgId,
clientProof,
providerAuthToken,
ip,
userAgent
}: TLoginClientProofDTO) => {
const userEnc = await userDAL.findUserEncKeyByUsername({
username: email,
orgId: userOrgId
username: email
});
if (!userEnc) throw new Error("Failed to find user");
const cfg = getConfig();
@@ -2,14 +2,12 @@ import { AuthMethod } from "./auth-type";
export type TLoginGenServerPublicKeyDTO = {
email: string;
userOrgId?: string;
clientPublicKey: string;
providerAuthToken?: string;
};
export type TLoginClientProofDTO = {
email: string;
userOrgId?: string;
clientProof: string;
providerAuthToken?: string;
ip: string;
+1 -1
View File
@@ -92,7 +92,7 @@ export const orgDALFactory = (db: TDbClient) => {
const findOrgMembersByUsername = async (orgId: string, usernames: string[]) => {
try {
const members = await db(TableName.OrgMembership)
.where({ orgId })
.where(`${TableName.OrgMembership}.orgId`, orgId)
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.leftJoin<TUserEncryptionKeys>(
TableName.UserEncryptionKey,
+1 -1
View File
@@ -441,7 +441,7 @@ export const orgServiceFactory = ({
recipients: [inviteeEmail],
substitutions: {
inviterFirstName: user.firstName,
inviterEmail: user.email,
inviterUsername: user.username,
organizationName: org?.name,
email: inviteeEmail,
organizationId: org?.id.toString(),
@@ -57,7 +57,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
}
};
const findMembershipsByEmail = async (projectId: string, emails: string[]) => {
const findMembershipsByUsername = async (projectId: string, usernames: string[]) => {
try {
const members = await db(TableName.ProjectMembership)
.where({ projectId })
@@ -70,18 +70,18 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
.select(
selectAllTableCols(TableName.ProjectMembership),
db.ref("id").withSchema(TableName.Users).as("userId"),
db.ref("email").withSchema(TableName.Users)
db.ref("username").withSchema(TableName.Users)
)
.whereIn("email", emails)
.whereIn("username", usernames)
.where({ isGhost: false });
return members.map(({ userId, email, ...data }) => ({
return members.map(({ userId, username, ...data }) => ({
...data,
user: { id: userId, email }
user: { id: userId, username }
}));
} catch (error) {
throw new DatabaseError({ error, name: "Find members by email" });
}
};
return { ...projectMemberOrm, findAllProjectMembers, findProjectGhostUser, findMembershipsByEmail };
return { ...projectMemberOrm, findAllProjectMembers, findProjectGhostUser, findMembershipsByUsername };
};
@@ -134,7 +134,7 @@ export const projectMembershipServiceFactory = ({
const appCfg = getConfig();
await smtpService.sendMail({
template: SmtpTemplates.WorkspaceInvite,
subjectLine: "Infisical workspace invitation",
subjectLine: "Infisical project invitation",
recipients: invitees.filter((i) => i.email).map((i) => i.email as string),
substitutions: {
workspaceName: project.name,
@@ -206,10 +206,8 @@ export const projectMembershipServiceFactory = ({
const appCfg = getConfig();
await smtpService.sendMail({
template: SmtpTemplates.WorkspaceInvite,
subjectLine: "Infisical workspace invitation",
recipients: orgMembers
.map(({ email }) => email)
.filter((email): email is string => email !== null && email !== undefined),
subjectLine: "Infisical project invitation",
recipients: orgMembers.filter((i) => i.email).map((i) => i.email as string),
substitutions: {
workspaceName: project.name,
callback_url: `${appCfg.SITE_URL}/login`
@@ -237,11 +235,14 @@ export const projectMembershipServiceFactory = ({
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
const usernamesAndEmails = [...emails, ...usernames];
const orgMembers = await orgDAL.findOrgMembersByUsername(project.orgId, [
...new Set([...emails, ...usernames].map((element) => element.toLowerCase()))
...new Set(usernamesAndEmails.map((element) => element.toLowerCase()))
]);
if (orgMembers.length !== emails.length) throw new BadRequestError({ message: "Some users are not part of org" });
if (orgMembers.length !== usernamesAndEmails.length)
throw new BadRequestError({ message: "Some users are not part of org" });
if (!orgMembers.length) return [];
@@ -320,16 +321,21 @@ export const projectMembershipServiceFactory = ({
});
if (sendEmails) {
const recipients = orgMembers.filter((i) => i.user.email).map((i) => i.user.email as string);
const appCfg = getConfig();
await smtpService.sendMail({
template: SmtpTemplates.WorkspaceInvite,
subjectLine: "Infisical workspace invitation",
recipients: orgMembers.filter(({ user }) => user.email).map(({ user }) => user.email as string),
substitutions: {
workspaceName: project.name,
callback_url: `${appCfg.SITE_URL}/login`
}
});
if (recipients.length) {
await smtpService.sendMail({
template: SmtpTemplates.WorkspaceInvite,
subjectLine: "Infisical project invitation",
recipients: orgMembers.filter((i) => i.user.email).map((i) => i.user.email as string),
substitutions: {
workspaceName: project.name,
callback_url: `${appCfg.SITE_URL}/login`
}
});
}
}
return members;
};
@@ -412,7 +418,8 @@ export const projectMembershipServiceFactory = ({
actor,
actorOrgId,
projectId,
emails
emails,
usernames
}: TDeleteProjectMembershipsDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
@@ -426,9 +433,13 @@ export const projectMembershipServiceFactory = ({
});
}
const projectMembers = await projectMembershipDAL.findMembershipsByEmail(projectId, emails);
const usernamesAndEmails = [...emails, ...usernames];
if (projectMembers.length !== emails.length) {
const projectMembers = await projectMembershipDAL.findMembershipsByUsername(projectId, [
...new Set(usernamesAndEmails.map((element) => element.toLowerCase()))
]);
if (projectMembers.length !== usernamesAndEmails.length) {
throw new BadRequestError({
message: "Some users are not part of project",
name: "Delete project membership"
@@ -18,6 +18,7 @@ export type TDeleteProjectMembershipOldDTO = {
export type TDeleteProjectMembershipsDTO = {
emails: string[];
usernames: string[];
} & TProjectPermission;
export type TAddUsersToWorkspaceDTO = {
@@ -8,7 +8,7 @@
</head>
<body>
<h2>Join your organization on Infisical</h2>
<p>{{inviterFirstName}} ({{inviterEmail}}) has invited you to their Infisical organization — {{organizationName}}</p>
<p>{{inviterFirstName}} ({{inviterUsername}}) has invited you to their Infisical organization — {{organizationName}}</p>
<a href="{{callback_url}}?token={{token}}&to={{email}}&organization_id={{organizationId}}">Join now</a>
<h3>What is Infisical?</h3>
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
@@ -0,0 +1,13 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TUserAliasDALFactory = ReturnType<typeof userAliasDALFactory>;
export const userAliasDALFactory = (db: TDbClient) => {
const userAliasOrm = ormify(db, TableName.UserAliases);
return {
...userAliasOrm
};
};
+1 -2
View File
@@ -20,12 +20,11 @@ export const userDALFactory = (db: TDbClient) => {
// USER ENCRYPTION FUNCTIONS
// -------------------------
const findUserEncKeyByUsername = async ({ username, orgId }: { username: string; orgId?: string }) => {
const findUserEncKeyByUsername = async ({ username }: { username: string }) => {
try {
return await db(TableName.Users)
.where({
username,
...(orgId ? { orgId } : { orgId: null }),
isGhost: false
})
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
+21
View File
@@ -0,0 +1,21 @@
import slugify from "@sindresorhus/slugify";
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 user = await userDAL.findOne({ username: attempt });
if (!user) return attempt;
while (true) {
attempt = slugify(`${username}-${alphaNumericNanoId(4)}`);
// eslint-disable-next-line no-await-in-loop
user = await userDAL.findOne({ username: attempt });
if (!user) {
return attempt;
}
}
};