mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Add user aliases concept and weave LDAP into it
This commit is contained in:
4
backend/src/@types/knex.d.ts
vendored
4
backend/src/@types/knex.d.ts
vendored
@@ -164,6 +164,9 @@ import {
|
|||||||
TUserActions,
|
TUserActions,
|
||||||
TUserActionsInsert,
|
TUserActionsInsert,
|
||||||
TUserActionsUpdate,
|
TUserActionsUpdate,
|
||||||
|
TUserAliases,
|
||||||
|
TUserAliasesInsert,
|
||||||
|
TUserAliasesUpdate,
|
||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
TUserEncryptionKeysInsert,
|
TUserEncryptionKeysInsert,
|
||||||
TUserEncryptionKeysUpdate,
|
TUserEncryptionKeysUpdate,
|
||||||
@@ -178,6 +181,7 @@ import {
|
|||||||
declare module "knex/types/tables" {
|
declare module "knex/types/tables" {
|
||||||
interface Tables {
|
interface Tables {
|
||||||
[TableName.Users]: Knex.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
|
[TableName.Users]: Knex.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
|
||||||
|
[TableName.UserAliases]: Knex.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>;
|
||||||
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
|
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
|
||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
TUserEncryptionKeysInsert,
|
TUserEncryptionKeysInsert,
|
||||||
|
|||||||
@@ -25,24 +25,38 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.LdapConfig);
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.UserAliases))) {
|
||||||
|
await knex.schema.createTable(TableName.UserAliases, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.uuid("userId").notNullable();
|
||||||
|
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
t.string("username").notNullable();
|
||||||
|
t.string("aliasType").notNullable();
|
||||||
|
t.string("externalId").notNullable();
|
||||||
|
t.specificType("emails", "text[]");
|
||||||
|
t.uuid("orgId").nullable();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.UserAliases);
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.Users, (t) => {
|
await knex.schema.alterTable(TableName.Users, (t) => {
|
||||||
t.string("username").notNullable();
|
t.string("username").unique().notNullable();
|
||||||
t.uuid("orgId").nullable();
|
|
||||||
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
|
||||||
t.string("email").nullable().alter();
|
t.string("email").nullable().alter();
|
||||||
t.unique(["username", "orgId"]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex(TableName.Users).update("username", knex.ref("email"));
|
await knex(TableName.Users).update("username", knex.ref("email"));
|
||||||
|
|
||||||
await createOnUpdateTrigger(knex, TableName.LdapConfig);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
await knex.schema.dropTableIfExists(TableName.LdapConfig);
|
await knex.schema.dropTableIfExists(TableName.LdapConfig);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.UserAliases);
|
||||||
await knex.schema.alterTable(TableName.Users, (t) => {
|
await knex.schema.alterTable(TableName.Users, (t) => {
|
||||||
t.dropColumn("username");
|
t.dropColumn("username");
|
||||||
t.dropColumn("orgId");
|
|
||||||
// t.string("email").notNullable().alter();
|
// t.string("email").notNullable().alter();
|
||||||
});
|
});
|
||||||
await dropOnUpdateTrigger(knex, TableName.LdapConfig);
|
await dropOnUpdateTrigger(knex, TableName.LdapConfig);
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export * from "./service-tokens";
|
|||||||
export * from "./super-admin";
|
export * from "./super-admin";
|
||||||
export * from "./trusted-ips";
|
export * from "./trusted-ips";
|
||||||
export * from "./user-actions";
|
export * from "./user-actions";
|
||||||
|
export * from "./user-aliases";
|
||||||
export * from "./user-encryption-keys";
|
export * from "./user-encryption-keys";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
export * from "./webhooks";
|
export * from "./webhooks";
|
||||||
|
|||||||
@@ -27,5 +27,5 @@ export const LdapConfigsSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export type TLdapConfigs = z.infer<typeof LdapConfigsSchema>;
|
export type TLdapConfigs = z.infer<typeof LdapConfigsSchema>;
|
||||||
export type TLdapConfigsInsert = Omit<TLdapConfigs, TImmutableDBKeys>;
|
export type TLdapConfigsInsert = Omit<z.input<typeof LdapConfigsSchema>, TImmutableDBKeys>;
|
||||||
export type TLdapConfigsUpdate = Partial<Omit<TLdapConfigs, TImmutableDBKeys>>;
|
export type TLdapConfigsUpdate = Partial<Omit<z.input<typeof LdapConfigsSchema>, TImmutableDBKeys>>;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export enum TableName {
|
export enum TableName {
|
||||||
Users = "users",
|
Users = "users",
|
||||||
|
UserAliases = "user_aliases",
|
||||||
UserEncryptionKey = "user_encryption_keys",
|
UserEncryptionKey = "user_encryption_keys",
|
||||||
AuthTokens = "auth_tokens",
|
AuthTokens = "auth_tokens",
|
||||||
AuthTokenSession = "auth_token_sessions",
|
AuthTokenSession = "auth_token_sessions",
|
||||||
|
|||||||
24
backend/src/db/schemas/user-aliases.ts
Normal file
24
backend/src/db/schemas/user-aliases.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const UserAliasesSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
userId: z.string().uuid(),
|
||||||
|
username: z.string(),
|
||||||
|
aliasType: z.string(),
|
||||||
|
externalId: z.string(),
|
||||||
|
emails: z.string().array().nullable().optional(),
|
||||||
|
orgId: z.string().uuid().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TUserAliases = z.infer<typeof UserAliasesSchema>;
|
||||||
|
export type TUserAliasesInsert = Omit<z.input<typeof UserAliasesSchema>, TImmutableDBKeys>;
|
||||||
|
export type TUserAliasesUpdate = Partial<Omit<z.input<typeof UserAliasesSchema>, TImmutableDBKeys>>;
|
||||||
@@ -21,8 +21,7 @@ export const UsersSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
isGhost: z.boolean().default(false),
|
isGhost: z.boolean().default(false),
|
||||||
username: z.string(),
|
username: z.string()
|
||||||
orgId: z.string().uuid().nullable().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TUsers = z.infer<typeof UsersSchema>;
|
export type TUsers = z.infer<typeof UsersSchema>;
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
async (req: IncomingMessage, user, cb) => {
|
async (req: IncomingMessage, user, cb) => {
|
||||||
try {
|
try {
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({
|
const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({
|
||||||
|
externalId: user.uidNumber,
|
||||||
username: user.uid,
|
username: user.uid,
|
||||||
firstName: user.givenName,
|
firstName: user.givenName,
|
||||||
lastName: user.sn,
|
lastName: user.sn,
|
||||||
|
emails: user.mail ? [user.mail] : [],
|
||||||
relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState,
|
relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState,
|
||||||
orgId: (req as unknown as FastifyRequest).ldapConfig.organization
|
orgId: (req as unknown as FastifyRequest).ldapConfig.organization
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
const user = await req.server.services.scim.createScimUser({
|
const user = await req.server.services.scim.createScimUser({
|
||||||
username: req.body.userName,
|
username: req.body.userName,
|
||||||
email: primaryEmail as string,
|
email: primaryEmail,
|
||||||
firstName: req.body.name.givenName,
|
firstName: req.body.name.givenName,
|
||||||
lastName: req.body.name.familyName,
|
lastName: req.body.name.familyName,
|
||||||
orgId: req.permission.orgId as string
|
orgId: req.permission.orgId as string
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
|||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
import { normalizeUsername } from "@app/services/user/user-fns";
|
||||||
|
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
@@ -34,6 +36,7 @@ type TLdapConfigServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
|
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
|
||||||
userDAL: Pick<TUserDALFactory, "create" | "findOne" | "transaction" | "updateById">;
|
userDAL: Pick<TUserDALFactory, "create" | "findOne" | "transaction" | "updateById">;
|
||||||
|
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
@@ -45,6 +48,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
|
userAliasDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService
|
licenseService
|
||||||
}: TLdapConfigServiceFactoryDep) => {
|
}: TLdapConfigServiceFactoryDep) => {
|
||||||
@@ -289,6 +293,8 @@ export const ldapConfigServiceFactory = ({
|
|||||||
const boot = async () => {
|
const boot = async () => {
|
||||||
try {
|
try {
|
||||||
const organization = await orgDAL.findOne({ slug: organizationSlug });
|
const organization = await orgDAL.findOne({ slug: organizationSlug });
|
||||||
|
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
||||||
|
|
||||||
const ldapConfig = await getLdapCfg({
|
const ldapConfig = await getLdapCfg({
|
||||||
orgId: organization.id,
|
orgId: organization.id,
|
||||||
isActive: true
|
isActive: true
|
||||||
@@ -302,7 +308,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
bindCredentials: ldapConfig.bindPass,
|
bindCredentials: ldapConfig.bindPass,
|
||||||
searchBase: ldapConfig.searchBase,
|
searchBase: ldapConfig.searchBase,
|
||||||
searchFilter: "(uid={{username}})",
|
searchFilter: "(uid={{username}})",
|
||||||
searchAttributes: ["uid", "givenName", "sn"],
|
searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"],
|
||||||
...(ldapConfig.caCert !== ""
|
...(ldapConfig.caCert !== ""
|
||||||
? {
|
? {
|
||||||
tlsOptions: {
|
tlsOptions: {
|
||||||
@@ -328,23 +334,25 @@ export const ldapConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const ldapLogin = async ({ username, firstName, lastName, orgId, relayState }: TLdapLoginDTO) => {
|
const ldapLogin = async ({ externalId, username, firstName, lastName, emails, orgId, relayState }: TLdapLoginDTO) => {
|
||||||
|
// externalId + username
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
let user = await userDAL.findOne({
|
let userAlias = await userAliasDAL.findOne({
|
||||||
username,
|
externalId,
|
||||||
orgId
|
orgId,
|
||||||
|
aliasType: AuthMethod.LDAP
|
||||||
});
|
});
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
||||||
|
|
||||||
if (user) {
|
if (userAlias) {
|
||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
const [orgMembership] = await orgDAL.findMembership({ userId: user.id }, { tx });
|
const [orgMembership] = await orgDAL.findMembership({ userId: userAlias.userId }, { tx });
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
await orgDAL.createMembership(
|
await orgDAL.createMembership(
|
||||||
{
|
{
|
||||||
userId: user.id,
|
userId: userAlias.userId,
|
||||||
orgId,
|
orgId,
|
||||||
role: OrgMembershipRole.Member,
|
role: OrgMembershipRole.Member,
|
||||||
status: OrgMembershipStatus.Accepted
|
status: OrgMembershipStatus.Accepted
|
||||||
@@ -362,11 +370,12 @@ export const ldapConfigServiceFactory = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
user = await userDAL.transaction(async (tx) => {
|
userAlias = await userDAL.transaction(async (tx) => {
|
||||||
|
const uniqueUsername = await normalizeUsername(username, userDAL);
|
||||||
const newUser = await userDAL.create(
|
const newUser = await userDAL.create(
|
||||||
{
|
{
|
||||||
username,
|
username: uniqueUsername,
|
||||||
orgId,
|
email: emails[0],
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
authMethods: [AuthMethod.LDAP],
|
authMethods: [AuthMethod.LDAP],
|
||||||
@@ -374,6 +383,18 @@ export const ldapConfigServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
const newUserAlias = await userAliasDAL.create(
|
||||||
|
{
|
||||||
|
userId: newUser.id,
|
||||||
|
username,
|
||||||
|
aliasType: AuthMethod.LDAP,
|
||||||
|
externalId,
|
||||||
|
emails,
|
||||||
|
orgId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
await orgDAL.createMembership(
|
await orgDAL.createMembership(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
@@ -384,10 +405,13 @@ export const ldapConfigServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
return newUser;
|
return newUserAlias;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// query for user here
|
||||||
|
const user = await userDAL.findOne({ id: userAlias.userId });
|
||||||
|
|
||||||
const isUserCompleted = Boolean(user.isAccepted);
|
const isUserCompleted = Boolean(user.isAccepted);
|
||||||
|
|
||||||
const providerAuthToken = jwt.sign(
|
const providerAuthToken = jwt.sign(
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ export type TUpdateLdapCfgDTO = Partial<{
|
|||||||
TOrgPermission;
|
TOrgPermission;
|
||||||
|
|
||||||
export type TLdapLoginDTO = {
|
export type TLdapLoginDTO = {
|
||||||
|
externalId: string;
|
||||||
username: string;
|
username: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
|
emails: string[];
|
||||||
orgId: string;
|
orgId: string;
|
||||||
relayState?: string;
|
relayState?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const getDefaultOnPremFeatures = () => {
|
|||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: true,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: true,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export type TFeatureSet = {
|
|||||||
auditLogsRetentionDays: 0;
|
auditLogsRetentionDays: 0;
|
||||||
samlSSO: false;
|
samlSSO: false;
|
||||||
scim: false;
|
scim: false;
|
||||||
ldap: false;
|
ldap: true;
|
||||||
status: null;
|
status: null;
|
||||||
trial_end: null;
|
trial_end: null;
|
||||||
has_used_trial: true;
|
has_used_trial: true;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const secretScanningQueueFactory = ({
|
|||||||
orgId: organizationId,
|
orgId: organizationId,
|
||||||
role: OrgMembershipRole.Admin
|
role: OrgMembershipRole.Admin
|
||||||
});
|
});
|
||||||
return adminsOfWork.map((userObject) => userObject.email);
|
return adminsOfWork.filter((userObject) => userObject.email).map((userObject) => userObject.email as string);
|
||||||
};
|
};
|
||||||
|
|
||||||
queueService.start(QueueName.SecretPushEventScan, async (job) => {
|
queueService.start(QueueName.SecretPushEventScan, async (job) => {
|
||||||
@@ -149,7 +149,7 @@ export const secretScanningQueueFactory = ({
|
|||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.SecretLeakIncident,
|
template: SmtpTemplates.SecretLeakIncident,
|
||||||
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
|
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
|
||||||
recipients: adminEmails.filter((email) => email).map((email) => email as string),
|
recipients: adminEmails.filter((email) => email).map((email) => email),
|
||||||
substitutions: {
|
substitutions: {
|
||||||
numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
|
numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
|
||||||
pusher_email: pusher.email,
|
pusher_email: pusher.email,
|
||||||
@@ -221,7 +221,7 @@ export const secretScanningQueueFactory = ({
|
|||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.SecretLeakIncident,
|
template: SmtpTemplates.SecretLeakIncident,
|
||||||
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
|
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
|
||||||
recipients: adminEmails.filter((email) => email).map((email) => email as string),
|
recipients: adminEmails.filter((email) => email).map((email) => email),
|
||||||
substitutions: {
|
substitutions: {
|
||||||
numberOfSecrets: findings.length
|
numberOfSecrets: findings.length
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ import { telemetryQueueServiceFactory } from "@app/services/telemetry/telemetry-
|
|||||||
import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
||||||
import { userDALFactory } from "@app/services/user/user-dal";
|
import { userDALFactory } from "@app/services/user/user-dal";
|
||||||
import { userServiceFactory } from "@app/services/user/user-service";
|
import { userServiceFactory } from "@app/services/user/user-service";
|
||||||
|
import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||||
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
||||||
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
|
|
||||||
@@ -128,6 +129,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
// db layers
|
// db layers
|
||||||
const userDAL = userDALFactory(db);
|
const userDAL = userDALFactory(db);
|
||||||
|
const userAliasDAL = userAliasDALFactory(db);
|
||||||
const authDAL = authDALFactory(db);
|
const authDAL = authDALFactory(db);
|
||||||
const authTokenDAL = tokenDALFactory(db);
|
const authTokenDAL = tokenDALFactory(db);
|
||||||
const orgDAL = orgDALFactory(db);
|
const orgDAL = orgDALFactory(db);
|
||||||
@@ -243,6 +245,7 @@ export const registerRoutes = async (
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
|
userAliasDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
await server.services.telemetry.sendPostHogEvents({
|
await server.services.telemetry.sendPostHogEvents({
|
||||||
event: PostHogEventTypes.AdminInit,
|
event: PostHogEventTypes.AdminInit,
|
||||||
distinctId: user.user.email ?? user.user.username ?? "",
|
distinctId: user.user.username ?? "",
|
||||||
properties: {
|
properties: {
|
||||||
email: user.user.email ?? "",
|
email: user.user.email ?? "",
|
||||||
lastName: user.user.lastName || "",
|
lastName: user.user.lastName || "",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
emails: z.string().email().array().default([]).describe("Emails of the users to add to the project."),
|
emails: z.string().email().array().default([]).describe("Emails of the users to add to the project."),
|
||||||
usernames: z.string().email().array().default([]).describe("Usernames of the users to add to the project.")
|
usernames: z.string().array().default([]).describe("Usernames of the users to add to the project.")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -59,7 +59,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
body: z.object({
|
body: z.object({
|
||||||
emails: z.string().email().array().describe("Emails of the users to remove from the project.")
|
emails: z.string().email().array().default([]).describe("Emails of the users to remove from the project."),
|
||||||
|
usernames: z.string().array().default([]).describe("Usernames of the users to remove from the project.")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -74,7 +75,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.params.projectId,
|
projectId: req.params.projectId,
|
||||||
emails: req.body.emails
|
emails: req.body.emails,
|
||||||
|
usernames: req.body.usernames
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const membership of memberships) {
|
for (const membership of memberships) {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
email: z.string().trim(),
|
email: z.string().trim(),
|
||||||
orgId: z.string().optional(),
|
|
||||||
providerAuthToken: z.string().trim().optional(),
|
providerAuthToken: z.string().trim().optional(),
|
||||||
clientPublicKey: z.string().trim()
|
clientPublicKey: z.string().trim()
|
||||||
}),
|
}),
|
||||||
@@ -27,7 +26,6 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { serverPublicKey, salt } = await server.services.login.loginGenServerPublicKey({
|
const { serverPublicKey, salt } = await server.services.login.loginGenServerPublicKey({
|
||||||
email: req.body.email,
|
email: req.body.email,
|
||||||
userOrgId: req.body.orgId,
|
|
||||||
clientPublicKey: req.body.clientPublicKey,
|
clientPublicKey: req.body.clientPublicKey,
|
||||||
providerAuthToken: req.body.providerAuthToken
|
providerAuthToken: req.body.providerAuthToken
|
||||||
});
|
});
|
||||||
@@ -45,7 +43,6 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
email: z.string().trim(),
|
email: z.string().trim(),
|
||||||
orgId: z.string().optional(),
|
|
||||||
providerAuthToken: z.string().trim().optional(),
|
providerAuthToken: z.string().trim().optional(),
|
||||||
clientProof: z.string().trim()
|
clientProof: z.string().trim()
|
||||||
}),
|
}),
|
||||||
@@ -74,7 +71,6 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
const data = await server.services.login.loginExchangeClientProof({
|
const data = await server.services.login.loginExchangeClientProof({
|
||||||
email: req.body.email,
|
email: req.body.email,
|
||||||
userOrgId: req.body.orgId,
|
|
||||||
ip: req.realIp,
|
ip: req.realIp,
|
||||||
userAgent,
|
userAgent,
|
||||||
providerAuthToken: req.body.providerAuthToken,
|
providerAuthToken: req.body.providerAuthToken,
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
void server.services.telemetry.sendPostHogEvents({
|
void server.services.telemetry.sendPostHogEvents({
|
||||||
event: PostHogEventTypes.UserSignedUp,
|
event: PostHogEventTypes.UserSignedUp,
|
||||||
distinctId: user.email ?? user.username ?? "",
|
distinctId: user.username ?? "",
|
||||||
properties: {
|
properties: {
|
||||||
email: user.email ?? "",
|
email: user.email ?? "",
|
||||||
attributionSource: req.body.attributionSource
|
attributionSource: req.body.attributionSource
|
||||||
@@ -202,7 +202,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
void server.services.telemetry.sendPostHogEvents({
|
void server.services.telemetry.sendPostHogEvents({
|
||||||
event: PostHogEventTypes.UserSignedUp,
|
event: PostHogEventTypes.UserSignedUp,
|
||||||
distinctId: user.email ?? user.username ?? "",
|
distinctId: user.username ?? "",
|
||||||
properties: {
|
properties: {
|
||||||
email: user.email ?? "",
|
email: user.email ?? "",
|
||||||
attributionSource: "Team Invite"
|
attributionSource: "Team Invite"
|
||||||
|
|||||||
@@ -130,13 +130,11 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
|
|||||||
*/
|
*/
|
||||||
const loginGenServerPublicKey = async ({
|
const loginGenServerPublicKey = async ({
|
||||||
email,
|
email,
|
||||||
userOrgId,
|
|
||||||
providerAuthToken,
|
providerAuthToken,
|
||||||
clientPublicKey
|
clientPublicKey
|
||||||
}: TLoginGenServerPublicKeyDTO) => {
|
}: TLoginGenServerPublicKeyDTO) => {
|
||||||
const userEnc = await userDAL.findUserEncKeyByUsername({
|
const userEnc = await userDAL.findUserEncKeyByUsername({
|
||||||
username: email,
|
username: email
|
||||||
orgId: userOrgId
|
|
||||||
});
|
});
|
||||||
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
|
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
|
||||||
throw new Error("Failed to find user");
|
throw new Error("Failed to find user");
|
||||||
@@ -159,15 +157,13 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
|
|||||||
*/
|
*/
|
||||||
const loginExchangeClientProof = async ({
|
const loginExchangeClientProof = async ({
|
||||||
email,
|
email,
|
||||||
userOrgId,
|
|
||||||
clientProof,
|
clientProof,
|
||||||
providerAuthToken,
|
providerAuthToken,
|
||||||
ip,
|
ip,
|
||||||
userAgent
|
userAgent
|
||||||
}: TLoginClientProofDTO) => {
|
}: TLoginClientProofDTO) => {
|
||||||
const userEnc = await userDAL.findUserEncKeyByUsername({
|
const userEnc = await userDAL.findUserEncKeyByUsername({
|
||||||
username: email,
|
username: email
|
||||||
orgId: userOrgId
|
|
||||||
});
|
});
|
||||||
if (!userEnc) throw new Error("Failed to find user");
|
if (!userEnc) throw new Error("Failed to find user");
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ import { AuthMethod } from "./auth-type";
|
|||||||
|
|
||||||
export type TLoginGenServerPublicKeyDTO = {
|
export type TLoginGenServerPublicKeyDTO = {
|
||||||
email: string;
|
email: string;
|
||||||
userOrgId?: string;
|
|
||||||
clientPublicKey: string;
|
clientPublicKey: string;
|
||||||
providerAuthToken?: string;
|
providerAuthToken?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TLoginClientProofDTO = {
|
export type TLoginClientProofDTO = {
|
||||||
email: string;
|
email: string;
|
||||||
userOrgId?: string;
|
|
||||||
clientProof: string;
|
clientProof: string;
|
||||||
providerAuthToken?: string;
|
providerAuthToken?: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
|||||||
const findOrgMembersByUsername = async (orgId: string, usernames: string[]) => {
|
const findOrgMembersByUsername = async (orgId: string, usernames: string[]) => {
|
||||||
try {
|
try {
|
||||||
const members = await db(TableName.OrgMembership)
|
const members = await db(TableName.OrgMembership)
|
||||||
.where({ orgId })
|
.where(`${TableName.OrgMembership}.orgId`, orgId)
|
||||||
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
|
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
|
||||||
.leftJoin<TUserEncryptionKeys>(
|
.leftJoin<TUserEncryptionKeys>(
|
||||||
TableName.UserEncryptionKey,
|
TableName.UserEncryptionKey,
|
||||||
|
|||||||
@@ -441,7 +441,7 @@ export const orgServiceFactory = ({
|
|||||||
recipients: [inviteeEmail],
|
recipients: [inviteeEmail],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
inviterFirstName: user.firstName,
|
inviterFirstName: user.firstName,
|
||||||
inviterEmail: user.email,
|
inviterUsername: user.username,
|
||||||
organizationName: org?.name,
|
organizationName: org?.name,
|
||||||
email: inviteeEmail,
|
email: inviteeEmail,
|
||||||
organizationId: org?.id.toString(),
|
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 {
|
try {
|
||||||
const members = await db(TableName.ProjectMembership)
|
const members = await db(TableName.ProjectMembership)
|
||||||
.where({ projectId })
|
.where({ projectId })
|
||||||
@@ -70,18 +70,18 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
selectAllTableCols(TableName.ProjectMembership),
|
selectAllTableCols(TableName.ProjectMembership),
|
||||||
db.ref("id").withSchema(TableName.Users).as("userId"),
|
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 });
|
.where({ isGhost: false });
|
||||||
return members.map(({ userId, email, ...data }) => ({
|
return members.map(({ userId, username, ...data }) => ({
|
||||||
...data,
|
...data,
|
||||||
user: { id: userId, email }
|
user: { id: userId, username }
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Find members by email" });
|
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();
|
const appCfg = getConfig();
|
||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.WorkspaceInvite,
|
template: SmtpTemplates.WorkspaceInvite,
|
||||||
subjectLine: "Infisical workspace invitation",
|
subjectLine: "Infisical project invitation",
|
||||||
recipients: invitees.filter((i) => i.email).map((i) => i.email as string),
|
recipients: invitees.filter((i) => i.email).map((i) => i.email as string),
|
||||||
substitutions: {
|
substitutions: {
|
||||||
workspaceName: project.name,
|
workspaceName: project.name,
|
||||||
@@ -206,10 +206,8 @@ export const projectMembershipServiceFactory = ({
|
|||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.WorkspaceInvite,
|
template: SmtpTemplates.WorkspaceInvite,
|
||||||
subjectLine: "Infisical workspace invitation",
|
subjectLine: "Infisical project invitation",
|
||||||
recipients: orgMembers
|
recipients: orgMembers.filter((i) => i.email).map((i) => i.email as string),
|
||||||
.map(({ email }) => email)
|
|
||||||
.filter((email): email is string => email !== null && email !== undefined),
|
|
||||||
substitutions: {
|
substitutions: {
|
||||||
workspaceName: project.name,
|
workspaceName: project.name,
|
||||||
callback_url: `${appCfg.SITE_URL}/login`
|
callback_url: `${appCfg.SITE_URL}/login`
|
||||||
@@ -237,11 +235,14 @@ export const projectMembershipServiceFactory = ({
|
|||||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
const usernamesAndEmails = [...emails, ...usernames];
|
||||||
|
|
||||||
const orgMembers = await orgDAL.findOrgMembersByUsername(project.orgId, [
|
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 [];
|
if (!orgMembers.length) return [];
|
||||||
|
|
||||||
@@ -320,16 +321,21 @@ export const projectMembershipServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (sendEmails) {
|
if (sendEmails) {
|
||||||
|
const recipients = orgMembers.filter((i) => i.user.email).map((i) => i.user.email as string);
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
await smtpService.sendMail({
|
|
||||||
template: SmtpTemplates.WorkspaceInvite,
|
if (recipients.length) {
|
||||||
subjectLine: "Infisical workspace invitation",
|
await smtpService.sendMail({
|
||||||
recipients: orgMembers.filter(({ user }) => user.email).map(({ user }) => user.email as string),
|
template: SmtpTemplates.WorkspaceInvite,
|
||||||
substitutions: {
|
subjectLine: "Infisical project invitation",
|
||||||
workspaceName: project.name,
|
recipients: orgMembers.filter((i) => i.user.email).map((i) => i.user.email as string),
|
||||||
callback_url: `${appCfg.SITE_URL}/login`
|
substitutions: {
|
||||||
}
|
workspaceName: project.name,
|
||||||
});
|
callback_url: `${appCfg.SITE_URL}/login`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return members;
|
return members;
|
||||||
};
|
};
|
||||||
@@ -412,7 +418,8 @@ export const projectMembershipServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
projectId,
|
projectId,
|
||||||
emails
|
emails,
|
||||||
|
usernames
|
||||||
}: TDeleteProjectMembershipsDTO) => {
|
}: TDeleteProjectMembershipsDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
|
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({
|
throw new BadRequestError({
|
||||||
message: "Some users are not part of project",
|
message: "Some users are not part of project",
|
||||||
name: "Delete project membership"
|
name: "Delete project membership"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export type TDeleteProjectMembershipOldDTO = {
|
|||||||
|
|
||||||
export type TDeleteProjectMembershipsDTO = {
|
export type TDeleteProjectMembershipsDTO = {
|
||||||
emails: string[];
|
emails: string[];
|
||||||
|
usernames: string[];
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TAddUsersToWorkspaceDTO = {
|
export type TAddUsersToWorkspaceDTO = {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Join your organization on Infisical</h2>
|
<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>
|
<a href="{{callback_url}}?token={{token}}&to={{email}}&organization_id={{organizationId}}">Join now</a>
|
||||||
<h3>What is Infisical?</h3>
|
<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>
|
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
|
||||||
|
|||||||
13
backend/src/services/user-alias/user-alias-dal.ts
Normal file
13
backend/src/services/user-alias/user-alias-dal.ts
Normal file
@@ -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
|
||||||
|
};
|
||||||
|
};
|
||||||
0
backend/src/services/user-alias/user-alias-types.ts
Normal file
0
backend/src/services/user-alias/user-alias-types.ts
Normal file
@@ -20,12 +20,11 @@ export const userDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
// USER ENCRYPTION FUNCTIONS
|
// USER ENCRYPTION FUNCTIONS
|
||||||
// -------------------------
|
// -------------------------
|
||||||
const findUserEncKeyByUsername = async ({ username, orgId }: { username: string; orgId?: string }) => {
|
const findUserEncKeyByUsername = async ({ username }: { username: string }) => {
|
||||||
try {
|
try {
|
||||||
return await db(TableName.Users)
|
return await db(TableName.Users)
|
||||||
.where({
|
.where({
|
||||||
username,
|
username,
|
||||||
...(orgId ? { orgId } : { orgId: null }),
|
|
||||||
isGhost: false
|
isGhost: false
|
||||||
})
|
})
|
||||||
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
|
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
|
||||||
|
|||||||
21
backend/src/services/user/user-fns.ts
Normal file
21
backend/src/services/user/user-fns.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -21,12 +21,10 @@ interface IsLoginSuccessful {
|
|||||||
*/
|
*/
|
||||||
const attemptLogin = async ({
|
const attemptLogin = async ({
|
||||||
email,
|
email,
|
||||||
orgId,
|
|
||||||
password,
|
password,
|
||||||
providerAuthToken
|
providerAuthToken
|
||||||
}: {
|
}: {
|
||||||
email: string;
|
email: string;
|
||||||
orgId?: string;
|
|
||||||
password: string;
|
password: string;
|
||||||
providerAuthToken?: string;
|
providerAuthToken?: string;
|
||||||
}): Promise<IsLoginSuccessful> => {
|
}): Promise<IsLoginSuccessful> => {
|
||||||
@@ -40,7 +38,6 @@ const attemptLogin = async ({
|
|||||||
|
|
||||||
const { serverPublicKey, salt } = await login1({
|
const { serverPublicKey, salt } = await login1({
|
||||||
email,
|
email,
|
||||||
orgId,
|
|
||||||
clientPublicKey,
|
clientPublicKey,
|
||||||
providerAuthToken
|
providerAuthToken
|
||||||
});
|
});
|
||||||
@@ -62,7 +59,6 @@ const attemptLogin = async ({
|
|||||||
tag
|
tag
|
||||||
} = await login2({
|
} = await login2({
|
||||||
email,
|
email,
|
||||||
orgId,
|
|
||||||
clientProof,
|
clientProof,
|
||||||
providerAuthToken
|
providerAuthToken
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,14 +25,12 @@ export type VerifyMfaTokenRes = {
|
|||||||
|
|
||||||
export type Login1DTO = {
|
export type Login1DTO = {
|
||||||
email: string;
|
email: string;
|
||||||
orgId?: string;
|
|
||||||
clientPublicKey: string;
|
clientPublicKey: string;
|
||||||
providerAuthToken?: string;
|
providerAuthToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Login2DTO = {
|
export type Login2DTO = {
|
||||||
email: string;
|
email: string;
|
||||||
orgId?: string;
|
|
||||||
clientProof: string;
|
clientProof: string;
|
||||||
providerAuthToken?: string;
|
providerAuthToken?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ export const useAddUserToWsNonE2EE = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, AddUserToWsDTONonE2EE>({
|
return useMutation<{}, {}, AddUserToWsDTONonE2EE>({
|
||||||
mutationFn: async ({ projectId, emails }) => {
|
mutationFn: async ({ projectId, usernames }) => {
|
||||||
const { data } = await apiRequest.post(`/api/v2/workspace/${projectId}/memberships`, {
|
const { data } = await apiRequest.post(`/api/v2/workspace/${projectId}/memberships`, {
|
||||||
emails
|
usernames
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export type AddUserToWsDTOE2EE = {
|
|||||||
|
|
||||||
export type AddUserToWsDTONonE2EE = {
|
export type AddUserToWsDTONonE2EE = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
emails: string[];
|
usernames: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateOrgUserRoleDTO = {
|
export type UpdateOrgUserRoleDTO = {
|
||||||
|
|||||||
@@ -323,11 +323,11 @@ export const useDeleteUserFromWorkspace = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ emails, workspaceId }: { workspaceId: string; emails: string[] }) => {
|
mutationFn: async ({ usernames, workspaceId }: { workspaceId: string; usernames: string[] }) => {
|
||||||
const {
|
const {
|
||||||
data: { deletedMembership }
|
data: { deletedMembership }
|
||||||
} = await apiRequest.delete(`/api/v2/workspace/${workspaceId}/memberships`, {
|
} = await apiRequest.delete(`/api/v2/workspace/${workspaceId}/memberships`, {
|
||||||
data: { emails }
|
data: { usernames }
|
||||||
});
|
});
|
||||||
return deletedMembership;
|
return deletedMembership;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ export const PasswordStep = ({
|
|||||||
} else {
|
} else {
|
||||||
const loginAttempt = await attemptLogin({
|
const loginAttempt = await attemptLogin({
|
||||||
email,
|
email,
|
||||||
orgId: organizationId,
|
|
||||||
password,
|
password,
|
||||||
providerAuthToken,
|
providerAuthToken,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export const MemberListTab = () => {
|
|||||||
const orgUser = (orgUsers || []).find(({ id }) => id === orgMembershipId);
|
const orgUser = (orgUsers || []).find(({ id }) => id === orgMembershipId);
|
||||||
if (!orgUser) return;
|
if (!orgUser) return;
|
||||||
|
|
||||||
try {
|
try { // TODO: update
|
||||||
if (currentWorkspace.version === ProjectVersion.V1) {
|
if (currentWorkspace.version === ProjectVersion.V1) {
|
||||||
await addUserToWorkspace({
|
await addUserToWorkspace({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -122,7 +122,7 @@ export const MemberListTab = () => {
|
|||||||
} else if (currentWorkspace.version === ProjectVersion.V2) {
|
} else if (currentWorkspace.version === ProjectVersion.V2) {
|
||||||
await addUserToWorkspaceNonE2EE({
|
await addUserToWorkspaceNonE2EE({
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
emails: [orgUser.user.username]
|
usernames: [orgUser.user.username]
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -148,11 +148,11 @@ export const MemberListTab = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveUser = async () => {
|
const handleRemoveUser = async () => {
|
||||||
const email = (popUp?.removeMember?.data as { email: string })?.email;
|
const username = (popUp?.removeMember?.data as { username: string })?.username;
|
||||||
if (!currentOrg?.id) return;
|
if (!currentOrg?.id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await removeUserFromWorkspace({ workspaceId, emails: [email] });
|
await removeUserFromWorkspace({ workspaceId, usernames: [username] });
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully removed user from project",
|
text: "Successfully removed user from project",
|
||||||
type: "success"
|
type: "success"
|
||||||
@@ -222,12 +222,12 @@ export const MemberListTab = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const filteredOrgUsers = useMemo(() => {
|
const filteredOrgUsers = useMemo(() => {
|
||||||
const wsUserEmails = new Map();
|
const wsUserUsernames = new Map();
|
||||||
members?.forEach((member) => {
|
members?.forEach((member) => {
|
||||||
wsUserEmails.set(member.user.email, true);
|
wsUserUsernames.set(member.user.username, true);
|
||||||
});
|
});
|
||||||
return (orgUsers || []).filter(
|
return (orgUsers || []).filter(
|
||||||
({ status, user: u }) => status === "accepted" && !wsUserEmails.has(u.email)
|
({ status, user: u }) => status === "accepted" && !wsUserUsernames.has(u.username)
|
||||||
);
|
);
|
||||||
}, [orgUsers, members]);
|
}, [orgUsers, members]);
|
||||||
|
|
||||||
@@ -322,7 +322,7 @@ export const MemberListTab = () => {
|
|||||||
className="ml-4"
|
className="ml-4"
|
||||||
isDisabled={userId === u?.id || !isAllowed}
|
isDisabled={userId === u?.id || !isAllowed}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handlePopUpOpen("removeMember", { email: u.email })
|
handlePopUpOpen("removeMember", { username: u.username })
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faXmark} />
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
@@ -354,20 +354,20 @@ export const MemberListTab = () => {
|
|||||||
<form onSubmit={handleSubmit(onAddMember)}>
|
<form onSubmit={handleSubmit(onAddMember)}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={filteredOrgUsers?.[0]?.user?.email}
|
defaultValue={filteredOrgUsers?.[0]?.user?.username}
|
||||||
name="orgMembershipId"
|
name="orgMembershipId"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl label="Email" isError={Boolean(error)} errorText={error?.message}>
|
<FormControl label="Username" isError={Boolean(error)} errorText={error?.message}>
|
||||||
<Select
|
<Select
|
||||||
position="popper"
|
position="popper"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
defaultValue={filteredOrgUsers?.[0]?.user?.email}
|
defaultValue={filteredOrgUsers?.[0]?.user?.username}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
>
|
>
|
||||||
{filteredOrgUsers.map(({ id: orgUserId, user: u }) => (
|
{filteredOrgUsers.map(({ id: orgUserId, user: u }) => (
|
||||||
<SelectItem value={orgUserId} key={`org-membership-join-${orgUserId}`}>
|
<SelectItem value={orgUserId} key={`org-membership-join-${orgUserId}`}>
|
||||||
{u?.email}
|
{u?.username}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
Reference in New Issue
Block a user