mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 10:27:26 +00:00
feat: implemented backend updates for email casing issue
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasEmail = await knex.schema.hasColumn(TableName.Users, "email");
|
||||||
|
const hasUsername = await knex.schema.hasColumn(TableName.Users, "username");
|
||||||
|
if (hasEmail) {
|
||||||
|
await knex(TableName.Users)
|
||||||
|
.where({ isGhost: false })
|
||||||
|
.update({
|
||||||
|
// @ts-expect-error email assume string this is expected
|
||||||
|
email: knex.raw("lower(email)")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hasUsername) {
|
||||||
|
await knex.schema.raw(`
|
||||||
|
CREATE INDEX IF NOT EXISTS ${TableName.Users}_lower_username_idx
|
||||||
|
ON ${TableName.Users} (LOWER(username))
|
||||||
|
`);
|
||||||
|
|
||||||
|
const duplicatesSubquery = knex(TableName.Users)
|
||||||
|
.select(knex.raw("lower(username) as lowercase_username"))
|
||||||
|
.groupBy("lowercase_username")
|
||||||
|
.having(knex.raw("count(*)"), ">", 1);
|
||||||
|
|
||||||
|
// Update usernames to lowercase where they won't create duplicates
|
||||||
|
await knex(TableName.Users)
|
||||||
|
.where({ isGhost: false })
|
||||||
|
.whereRaw("username <> lower(username)") // Only update if not already lowercase
|
||||||
|
// @ts-expect-error username assume string this is expected
|
||||||
|
.whereNotIn(knex.raw("lower(username)"), duplicatesSubquery)
|
||||||
|
.update({
|
||||||
|
// @ts-expect-error username assume string this is expected
|
||||||
|
username: knex.raw("lower(username)")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasUsername = await knex.schema.hasColumn(TableName.Users, "username");
|
||||||
|
if (hasUsername) {
|
||||||
|
await knex.schema.raw(`
|
||||||
|
DROP INDEX IF EXISTS ${TableName.Users}_lower_username_idx
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -111,9 +111,9 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
void query.andWhereRaw(`CONCAT_WS(' ', "firstName", "lastName", "username") ilike ?`, [`%${search}%`]);
|
void query.andWhereRaw(`CONCAT_WS(' ', "firstName", "lastName", lower("username")) ilike ?`, [`%${search}%`]);
|
||||||
} else if (username) {
|
} else if (username) {
|
||||||
void query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`);
|
void query.andWhereRaw(`lower("${TableName.Users}"."username") ilike ?`, `%${username}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (filter) {
|
switch (filter) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
import { TUserGroupMembershipDALFactory } from "./user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "./user-group-membership-dal";
|
||||||
|
|
||||||
type TGroupServiceFactoryDep = {
|
type TGroupServiceFactoryDep = {
|
||||||
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction" | "findOne">;
|
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction" | "findUserByUsername">;
|
||||||
groupDAL: Pick<
|
groupDAL: Pick<
|
||||||
TGroupDALFactory,
|
TGroupDALFactory,
|
||||||
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById" | "transaction"
|
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById" | "transaction"
|
||||||
@@ -380,7 +380,10 @@ export const groupServiceFactory = ({
|
|||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await userDAL.findOne({ username });
|
const usersWithUsername = await userDAL.findUserByUsername(username);
|
||||||
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const user =
|
||||||
|
usersWithUsername?.length > 1 ? usersWithUsername.find((el) => el.username === username) : usersWithUsername?.[0];
|
||||||
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
|
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
|
||||||
|
|
||||||
const users = await addUsersToGroupByUserIds({
|
const users = await addUsersToGroupByUserIds({
|
||||||
@@ -461,7 +464,10 @@ export const groupServiceFactory = ({
|
|||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await userDAL.findOne({ username });
|
const usersWithUsername = await userDAL.findUserByUsername(username);
|
||||||
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const user =
|
||||||
|
usersWithUsername?.length > 1 ? usersWithUsername.find((el) => el.username === username) : usersWithUsername?.[0];
|
||||||
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
|
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
|
||||||
|
|
||||||
const users = await removeUsersFromGroupByUserIds({
|
const users = await removeUsersFromGroupByUserIds({
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
if (serverCfg.trustLdapEmails) {
|
if (serverCfg.trustLdapEmails) {
|
||||||
newUser = await userDAL.findOne(
|
newUser = await userDAL.findOne(
|
||||||
{
|
{
|
||||||
email,
|
email: email.toLowerCase(),
|
||||||
isEmailVerified: true
|
isEmailVerified: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -391,8 +391,8 @@ export const ldapConfigServiceFactory = ({
|
|||||||
const uniqueUsername = await normalizeUsername(username, userDAL);
|
const uniqueUsername = await normalizeUsername(username, userDAL);
|
||||||
newUser = await userDAL.create(
|
newUser = await userDAL.create(
|
||||||
{
|
{
|
||||||
username: serverCfg.trustLdapEmails ? email : uniqueUsername,
|
username: serverCfg.trustLdapEmails ? email.toLowerCase() : uniqueUsername,
|
||||||
email,
|
email: email.toLowerCase(),
|
||||||
isEmailVerified: serverCfg.trustLdapEmails,
|
isEmailVerified: serverCfg.trustLdapEmails,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
@@ -429,7 +429,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
await orgMembershipDAL.create(
|
await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
inviteEmail: email,
|
inviteEmail: email.toLowerCase(),
|
||||||
orgId,
|
orgId,
|
||||||
role,
|
role,
|
||||||
roleId,
|
roleId,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
auditLogStreams: false,
|
auditLogStreams: false,
|
||||||
auditLogStreamLimit: 3,
|
auditLogStreamLimit: 3,
|
||||||
samlSSO: false,
|
samlSSO: true,
|
||||||
hsm: false,
|
hsm: false,
|
||||||
oidcSSO: false,
|
oidcSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
|
|||||||
@@ -171,8 +171,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const oidcLogin = async ({
|
const oidcLogin = async ({
|
||||||
externalId,
|
|
||||||
email,
|
email,
|
||||||
|
externalId,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
orgId,
|
orgId,
|
||||||
@@ -181,6 +181,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
manageGroupMemberships
|
manageGroupMemberships
|
||||||
}: TOidcLoginDTO) => {
|
}: TOidcLoginDTO) => {
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
const sanitizedEmail = email.toLowerCase();
|
||||||
|
|
||||||
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.OIDC)) {
|
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.OIDC)) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
@@ -215,7 +216,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
await orgMembershipDAL.create(
|
await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
userId: userAlias.userId,
|
||||||
inviteEmail: email,
|
inviteEmail: sanitizedEmail,
|
||||||
orgId,
|
orgId,
|
||||||
role,
|
role,
|
||||||
roleId,
|
roleId,
|
||||||
@@ -245,7 +246,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
// we prioritize getting the most complete user to create the new alias under
|
// we prioritize getting the most complete user to create the new alias under
|
||||||
newUser = await userDAL.findOne(
|
newUser = await userDAL.findOne(
|
||||||
{
|
{
|
||||||
email,
|
email: sanitizedEmail,
|
||||||
isEmailVerified: true
|
isEmailVerified: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -255,7 +256,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
// this fetches user entries created via invites
|
// this fetches user entries created via invites
|
||||||
newUser = await userDAL.findOne(
|
newUser = await userDAL.findOne(
|
||||||
{
|
{
|
||||||
username: email
|
username: sanitizedEmail
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -273,10 +274,10 @@ export const oidcConfigServiceFactory = ({
|
|||||||
const uniqueUsername = await normalizeUsername(externalId, userDAL);
|
const uniqueUsername = await normalizeUsername(externalId, userDAL);
|
||||||
newUser = await userDAL.create(
|
newUser = await userDAL.create(
|
||||||
{
|
{
|
||||||
email,
|
email: sanitizedEmail,
|
||||||
firstName,
|
firstName,
|
||||||
isEmailVerified: serverCfg.trustOidcEmails,
|
isEmailVerified: serverCfg.trustOidcEmails,
|
||||||
username: serverCfg.trustOidcEmails ? email : uniqueUsername,
|
username: serverCfg.trustOidcEmails ? sanitizedEmail : uniqueUsername,
|
||||||
lastName,
|
lastName,
|
||||||
authMethods: [],
|
authMethods: [],
|
||||||
isGhost: false
|
isGhost: false
|
||||||
@@ -290,7 +291,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
aliasType: UserAliasType.OIDC,
|
aliasType: UserAliasType.OIDC,
|
||||||
externalId,
|
externalId,
|
||||||
emails: email ? [email] : [],
|
emails: sanitizedEmail ? [sanitizedEmail] : [],
|
||||||
orgId
|
orgId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -310,7 +311,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
await orgMembershipDAL.create(
|
await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
inviteEmail: email,
|
inviteEmail: sanitizedEmail,
|
||||||
orgId,
|
orgId,
|
||||||
role,
|
role,
|
||||||
roleId,
|
roleId,
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
await orgMembershipDAL.create(
|
await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
userId: userAlias.userId,
|
||||||
inviteEmail: email,
|
inviteEmail: email.toLowerCase(),
|
||||||
orgId,
|
orgId,
|
||||||
role,
|
role,
|
||||||
roleId,
|
roleId,
|
||||||
@@ -324,7 +324,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
if (serverCfg.trustSamlEmails) {
|
if (serverCfg.trustSamlEmails) {
|
||||||
newUser = await userDAL.findOne(
|
newUser = await userDAL.findOne(
|
||||||
{
|
{
|
||||||
email,
|
email: email.toLowerCase(),
|
||||||
isEmailVerified: true
|
isEmailVerified: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -335,8 +335,8 @@ export const samlConfigServiceFactory = ({
|
|||||||
const uniqueUsername = await normalizeUsername(`${firstName ?? ""}-${lastName ?? ""}`, userDAL);
|
const uniqueUsername = await normalizeUsername(`${firstName ?? ""}-${lastName ?? ""}`, userDAL);
|
||||||
newUser = await userDAL.create(
|
newUser = await userDAL.create(
|
||||||
{
|
{
|
||||||
username: serverCfg.trustSamlEmails ? email : uniqueUsername,
|
username: serverCfg.trustSamlEmails ? email.toLowerCase() : uniqueUsername,
|
||||||
email,
|
email: email.toLowerCase(),
|
||||||
isEmailVerified: serverCfg.trustSamlEmails,
|
isEmailVerified: serverCfg.trustSamlEmails,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
@@ -352,7 +352,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
aliasType: UserAliasType.SAML,
|
aliasType: UserAliasType.SAML,
|
||||||
externalId,
|
externalId,
|
||||||
emails: email ? [email] : [],
|
emails: email ? [email.toLowerCase()] : [],
|
||||||
orgId
|
orgId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -372,7 +372,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
await orgMembershipDAL.create(
|
await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
inviteEmail: email,
|
inviteEmail: email.toLowerCase(),
|
||||||
orgId,
|
orgId,
|
||||||
role,
|
role,
|
||||||
roleId,
|
roleId,
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ export const scimServiceFactory = ({
|
|||||||
orgMembership = await orgMembershipDAL.create(
|
orgMembership = await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
userId: userAlias.userId,
|
||||||
inviteEmail: email,
|
inviteEmail: email.toLowerCase(),
|
||||||
orgId,
|
orgId,
|
||||||
role,
|
role,
|
||||||
roleId,
|
roleId,
|
||||||
@@ -364,7 +364,7 @@ export const scimServiceFactory = ({
|
|||||||
if (trustScimEmails) {
|
if (trustScimEmails) {
|
||||||
user = await userDAL.findOne(
|
user = await userDAL.findOne(
|
||||||
{
|
{
|
||||||
email,
|
email: email.toLowerCase(),
|
||||||
isEmailVerified: true
|
isEmailVerified: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -379,8 +379,8 @@ export const scimServiceFactory = ({
|
|||||||
);
|
);
|
||||||
user = await userDAL.create(
|
user = await userDAL.create(
|
||||||
{
|
{
|
||||||
username: trustScimEmails ? email : uniqueUsername,
|
username: trustScimEmails ? email.toLowerCase() : uniqueUsername,
|
||||||
email,
|
email: email.toLowerCase(),
|
||||||
isEmailVerified: trustScimEmails,
|
isEmailVerified: trustScimEmails,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
@@ -396,7 +396,7 @@ export const scimServiceFactory = ({
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
aliasType,
|
aliasType,
|
||||||
externalId,
|
externalId,
|
||||||
emails: email ? [email] : [],
|
emails: email ? [email.toLowerCase()] : [],
|
||||||
orgId
|
orgId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -418,7 +418,7 @@ export const scimServiceFactory = ({
|
|||||||
orgMembership = await orgMembershipDAL.create(
|
orgMembership = await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
inviteEmail: email,
|
inviteEmail: email.toLowerCase(),
|
||||||
orgId,
|
orgId,
|
||||||
role,
|
role,
|
||||||
roleId,
|
roleId,
|
||||||
@@ -529,7 +529,7 @@ export const scimServiceFactory = ({
|
|||||||
membership.userId,
|
membership.userId,
|
||||||
{
|
{
|
||||||
firstName: scimUser.name.givenName,
|
firstName: scimUser.name.givenName,
|
||||||
email: scimUser.emails[0].value,
|
email: scimUser.emails[0].value.toLowerCase(),
|
||||||
lastName: scimUser.name.familyName,
|
lastName: scimUser.name.familyName,
|
||||||
isEmailVerified: hasEmailChanged ? trustScimEmails : undefined
|
isEmailVerified: hasEmailChanged ? trustScimEmails : undefined
|
||||||
},
|
},
|
||||||
@@ -606,7 +606,7 @@ export const scimServiceFactory = ({
|
|||||||
membership.userId,
|
membership.userId,
|
||||||
{
|
{
|
||||||
firstName,
|
firstName,
|
||||||
email,
|
email: email?.toLowerCase(),
|
||||||
lastName,
|
lastName,
|
||||||
isEmailVerified:
|
isEmailVerified:
|
||||||
org.orgAuthMethod === OrgAuthMethod.OIDC ? serverCfg.trustOidcEmails : serverCfg.trustSamlEmails
|
org.orgAuthMethod === OrgAuthMethod.OIDC ? serverCfg.trustOidcEmails : serverCfg.trustSamlEmails
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import { Compare, Filter, parse } from "scim2-parse-filter";
|
import { Compare, Filter, parse } from "scim2-parse-filter";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
const appendParentToGroupingOperator = (parentPath: string, filter: Filter) => {
|
const appendParentToGroupingOperator = (parentPath: string, filter: Filter) => {
|
||||||
if (filter.op !== "[]" && filter.op !== "and" && filter.op !== "or" && filter.op !== "not") {
|
if (filter.op !== "[]" && filter.op !== "and" && filter.op !== "or" && filter.op !== "not") {
|
||||||
return { ...filter, attrPath: `${parentPath}.${(filter as Compare).attrPath}` };
|
return { ...filter, attrPath: `${parentPath}.${(filter as Compare).attrPath}` };
|
||||||
@@ -27,8 +29,12 @@ const processDynamicQuery = (
|
|||||||
const { scimFilterAst, query } = stack.pop()!;
|
const { scimFilterAst, query } = stack.pop()!;
|
||||||
switch (scimFilterAst.op) {
|
switch (scimFilterAst.op) {
|
||||||
case "eq": {
|
case "eq": {
|
||||||
|
let sanitizedValue = scimFilterAst.compValue;
|
||||||
const attrPath = getAttributeField(scimFilterAst.attrPath);
|
const attrPath = getAttributeField(scimFilterAst.attrPath);
|
||||||
if (attrPath) void query.where(attrPath, scimFilterAst.compValue);
|
if (attrPath === `${TableName.Users}.email` && typeof sanitizedValue === "string") {
|
||||||
|
sanitizedValue = sanitizedValue.toLowerCase();
|
||||||
|
}
|
||||||
|
if (attrPath) void query.where(attrPath, sanitizedValue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "pr": {
|
case "pr": {
|
||||||
@@ -62,18 +68,30 @@ const processDynamicQuery = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "ew": {
|
case "ew": {
|
||||||
|
let sanitizedValue = scimFilterAst.compValue;
|
||||||
const attrPath = getAttributeField(scimFilterAst.attrPath);
|
const attrPath = getAttributeField(scimFilterAst.attrPath);
|
||||||
if (attrPath) void query.whereILike(attrPath, `%${scimFilterAst.compValue}`);
|
if (attrPath === `${TableName.Users}.email` && typeof sanitizedValue === "string") {
|
||||||
|
sanitizedValue = sanitizedValue.toLowerCase();
|
||||||
|
}
|
||||||
|
if (attrPath) void query.whereILike(attrPath, `%${sanitizedValue}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "co": {
|
case "co": {
|
||||||
|
let sanitizedValue = scimFilterAst.compValue;
|
||||||
const attrPath = getAttributeField(scimFilterAst.attrPath);
|
const attrPath = getAttributeField(scimFilterAst.attrPath);
|
||||||
if (attrPath) void query.whereILike(attrPath, `%${scimFilterAst.compValue}%`);
|
if (attrPath === `${TableName.Users}.email` && typeof sanitizedValue === "string") {
|
||||||
|
sanitizedValue = sanitizedValue.toLowerCase();
|
||||||
|
}
|
||||||
|
if (attrPath) void query.whereILike(attrPath, `%${sanitizedValue}%`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "ne": {
|
case "ne": {
|
||||||
|
let sanitizedValue = scimFilterAst.compValue;
|
||||||
const attrPath = getAttributeField(scimFilterAst.attrPath);
|
const attrPath = getAttributeField(scimFilterAst.attrPath);
|
||||||
if (attrPath) void query.whereNot(attrPath, "=", scimFilterAst.compValue);
|
if (attrPath === `${TableName.Users}.email` && typeof sanitizedValue === "string") {
|
||||||
|
sanitizedValue = sanitizedValue.toLowerCase();
|
||||||
|
}
|
||||||
|
if (attrPath) void query.whereNot(attrPath, "=", sanitizedValue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "and": {
|
case "and": {
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
inviteeEmails: z.array(z.string().trim().email()),
|
inviteeEmails: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.email()
|
||||||
|
.array()
|
||||||
|
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
projects: z
|
projects: z
|
||||||
.object({
|
.object({
|
||||||
@@ -115,7 +120,11 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
email: z.string().trim().email(),
|
email: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.email()
|
||||||
|
.refine((val) => val === val.toLowerCase(), "Email must be lowercase"),
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
code: z.string().trim()
|
code: z.string().trim()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -46,6 +46,51 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/duplicate-accounts",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
users: UsersSchema.extend({ isMyAccount: z.boolean() }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }),
|
||||||
|
handler: async (req) => {
|
||||||
|
if (req.auth.authMode === AuthMode.JWT && req.auth.user.email) {
|
||||||
|
const users = await server.services.user.getAllMyAccounts(req.auth.user.email, req.permission.id);
|
||||||
|
return { users };
|
||||||
|
}
|
||||||
|
return { users: [] };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/remove-duplicate-accounts",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }),
|
||||||
|
handler: async (req) => {
|
||||||
|
if (req.auth.authMode === AuthMode.JWT && req.auth.user.email) {
|
||||||
|
await server.services.user.removeMyDuplicateAccounts(req.auth.user.email, req.permission.id);
|
||||||
|
}
|
||||||
|
return { message: "Removed all duplicate accounts" };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/private-key",
|
url: "/private-key",
|
||||||
|
|||||||
@@ -27,8 +27,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
projectId: z.string().describe(PROJECT_USERS.INVITE_MEMBER.projectId)
|
projectId: z.string().describe(PROJECT_USERS.INVITE_MEMBER.projectId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
emails: z.string().email().array().default([]).describe(PROJECT_USERS.INVITE_MEMBER.emails),
|
emails: z
|
||||||
usernames: z.string().array().default([]).describe(PROJECT_USERS.INVITE_MEMBER.usernames),
|
.string()
|
||||||
|
.email()
|
||||||
|
.array()
|
||||||
|
.default([])
|
||||||
|
.describe(PROJECT_USERS.INVITE_MEMBER.emails)
|
||||||
|
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
|
||||||
|
usernames: z
|
||||||
|
.string()
|
||||||
|
.array()
|
||||||
|
.default([])
|
||||||
|
.describe(PROJECT_USERS.INVITE_MEMBER.usernames)
|
||||||
|
.refine((val) => val.every((el) => el === el.toLowerCase()), "Username must be lowercase"),
|
||||||
roleSlugs: z.string().array().min(1).optional().describe(PROJECT_USERS.INVITE_MEMBER.roleSlugs)
|
roleSlugs: z.string().array().min(1).optional().describe(PROJECT_USERS.INVITE_MEMBER.roleSlugs)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -92,7 +103,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
projectId: z.string().describe(PROJECT_USERS.REMOVE_MEMBER.projectId)
|
projectId: z.string().describe(PROJECT_USERS.REMOVE_MEMBER.projectId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
emails: z.string().email().array().default([]).describe(PROJECT_USERS.REMOVE_MEMBER.emails),
|
emails: z
|
||||||
|
.string()
|
||||||
|
.email()
|
||||||
|
.array()
|
||||||
|
.default([])
|
||||||
|
.describe(PROJECT_USERS.REMOVE_MEMBER.emails)
|
||||||
|
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
|
||||||
usernames: z.string().array().default([]).describe(PROJECT_USERS.REMOVE_MEMBER.usernames)
|
usernames: z.string().array().default([]).describe(PROJECT_USERS.REMOVE_MEMBER.usernames)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -199,9 +199,12 @@ export const authLoginServiceFactory = ({
|
|||||||
providerAuthToken,
|
providerAuthToken,
|
||||||
clientPublicKey
|
clientPublicKey
|
||||||
}: TLoginGenServerPublicKeyDTO) => {
|
}: TLoginGenServerPublicKeyDTO) => {
|
||||||
const userEnc = await userDAL.findUserEncKeyByUsername({
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const usersByUsername = await userDAL.findUserEncKeyByUsername({
|
||||||
username: email
|
username: email
|
||||||
});
|
});
|
||||||
|
const userEnc =
|
||||||
|
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === email) : usersByUsername?.[0];
|
||||||
|
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
@@ -250,9 +253,12 @@ export const authLoginServiceFactory = ({
|
|||||||
}: TLoginClientProofDTO) => {
|
}: TLoginClientProofDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const userEnc = await userDAL.findUserEncKeyByUsername({
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const usersByUsername = await userDAL.findUserEncKeyByUsername({
|
||||||
username: email
|
username: email
|
||||||
});
|
});
|
||||||
|
const userEnc =
|
||||||
|
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === email) : usersByUsername?.[0];
|
||||||
if (!userEnc) throw new Error("Failed to find user");
|
if (!userEnc) throw new Error("Failed to find user");
|
||||||
const user = await userDAL.findById(userEnc.userId);
|
const user = await userDAL.findById(userEnc.userId);
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
@@ -649,10 +655,12 @@ export const authLoginServiceFactory = ({
|
|||||||
* OAuth2 login for google,github, and other oauth2 provider
|
* OAuth2 login for google,github, and other oauth2 provider
|
||||||
* */
|
* */
|
||||||
const oauth2Login = async ({ email, firstName, lastName, authMethod, callbackPort }: TOauthLoginDTO) => {
|
const oauth2Login = async ({ email, firstName, lastName, authMethod, callbackPort }: TOauthLoginDTO) => {
|
||||||
let user = await userDAL.findUserByUsername(email);
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const usersByUsername = await userDAL.findUserByUsername(email);
|
||||||
|
let user = usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === email) : usersByUsername?.[0];
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
if (serverCfg.enabledLoginMethods) {
|
if (serverCfg.enabledLoginMethods && user) {
|
||||||
switch (authMethod) {
|
switch (authMethod) {
|
||||||
case AuthMethod.GITHUB: {
|
case AuthMethod.GITHUB: {
|
||||||
if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GITHUB)) {
|
if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GITHUB)) {
|
||||||
@@ -715,8 +723,8 @@ export const authLoginServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
user = await userDAL.create({
|
user = await userDAL.create({
|
||||||
username: email,
|
username: email.toLowerCase(),
|
||||||
email,
|
email: email.toLowerCase(),
|
||||||
isEmailVerified: true,
|
isEmailVerified: true,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
@@ -814,11 +822,14 @@ export const authLoginServiceFactory = ({
|
|||||||
? decodedProviderToken.orgId
|
? decodedProviderToken.orgId
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const userEnc = await userDAL.findUserEncKeyByUsername({
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const usersByUsername = await userDAL.findUserEncKeyByUsername({
|
||||||
username: email
|
username: email
|
||||||
});
|
});
|
||||||
if (!userEnc) throw new BadRequestError({ message: "Invalid token" });
|
const userEnc =
|
||||||
if (!userEnc.serverEncryptedPrivateKey)
|
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === email) : usersByUsername?.[0];
|
||||||
|
|
||||||
|
if (!userEnc?.serverEncryptedPrivateKey)
|
||||||
throw new BadRequestError({ message: "Key handoff incomplete. Please try logging in again." });
|
throw new BadRequestError({ message: "Key handoff incomplete. Please try logging in again." });
|
||||||
|
|
||||||
const token = await generateUserTokens({
|
const token = await generateUserTokens({
|
||||||
|
|||||||
@@ -121,7 +121,10 @@ export const authPaswordServiceFactory = ({
|
|||||||
*/
|
*/
|
||||||
const sendPasswordResetEmail = async (email: string) => {
|
const sendPasswordResetEmail = async (email: string) => {
|
||||||
const sendEmail = async () => {
|
const sendEmail = async () => {
|
||||||
const user = await userDAL.findUserByUsername(email);
|
const users = await userDAL.findUserByUsername(email);
|
||||||
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const user = users?.length > 1 ? users.find((el) => el.username === email) : users?.[0];
|
||||||
|
if (!user) throw new BadRequestError({ message: "Failed to find user data" });
|
||||||
|
|
||||||
if (user && user.isAccepted) {
|
if (user && user.isAccepted) {
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
@@ -152,7 +155,10 @@ export const authPaswordServiceFactory = ({
|
|||||||
* */
|
* */
|
||||||
const verifyPasswordResetEmail = async (email: string, code: string) => {
|
const verifyPasswordResetEmail = async (email: string, code: string) => {
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
const user = await userDAL.findUserByUsername(email);
|
const users = await userDAL.findUserByUsername(email);
|
||||||
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const user = users?.length > 1 ? users.find((el) => el.username === email) : users?.[0];
|
||||||
|
if (!user) throw new BadRequestError({ message: "Failed to find user data" });
|
||||||
|
|
||||||
const userEnc = await userDAL.findUserEncKeyByUserId(user.id);
|
const userEnc = await userDAL.findUserEncKeyByUserId(user.id);
|
||||||
|
|
||||||
|
|||||||
@@ -73,18 +73,27 @@ export const authSignupServiceFactory = ({
|
|||||||
}: TAuthSignupDep) => {
|
}: TAuthSignupDep) => {
|
||||||
// first step of signup. create user and send email
|
// first step of signup. create user and send email
|
||||||
const beginEmailSignupProcess = async (email: string) => {
|
const beginEmailSignupProcess = async (email: string) => {
|
||||||
const isEmailInvalid = await isDisposableEmail(email);
|
const sanitizedEmail = email.toLowerCase();
|
||||||
|
const isEmailInvalid = await isDisposableEmail(sanitizedEmail);
|
||||||
if (isEmailInvalid) {
|
if (isEmailInvalid) {
|
||||||
throw new Error("Provided a disposable email");
|
throw new Error("Provided a disposable email");
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await userDAL.findUserByUsername(email);
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const usersByUsername = await userDAL.findUserByUsername(sanitizedEmail);
|
||||||
|
let user =
|
||||||
|
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === sanitizedEmail) : usersByUsername?.[0];
|
||||||
if (user && user.isAccepted) {
|
if (user && user.isAccepted) {
|
||||||
// TODO(akhilmhdh-pg): copy as old one. this needs to be changed due to security issues
|
// TODO(akhilmhdh-pg): copy as old one. this needs to be changed due to security issues
|
||||||
throw new Error("Failed to send verification code for complete account");
|
throw new BadRequestError({ message: "Failed to send verification code for complete account" });
|
||||||
}
|
}
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await userDAL.create({ authMethods: [AuthMethod.EMAIL], username: email, email, isGhost: false });
|
user = await userDAL.create({
|
||||||
|
authMethods: [AuthMethod.EMAIL],
|
||||||
|
username: sanitizedEmail,
|
||||||
|
email: sanitizedEmail,
|
||||||
|
isGhost: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (!user) throw new Error("Failed to create user");
|
if (!user) throw new Error("Failed to create user");
|
||||||
|
|
||||||
@@ -96,7 +105,7 @@ export const authSignupServiceFactory = ({
|
|||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.SignupEmailVerification,
|
template: SmtpTemplates.SignupEmailVerification,
|
||||||
subjectLine: "Infisical confirmation code",
|
subjectLine: "Infisical confirmation code",
|
||||||
recipients: [user.email as string],
|
recipients: [sanitizedEmail],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
code: token
|
code: token
|
||||||
}
|
}
|
||||||
@@ -104,11 +113,15 @@ export const authSignupServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const verifyEmailSignup = async (email: string, code: string) => {
|
const verifyEmailSignup = async (email: string, code: string) => {
|
||||||
const user = await userDAL.findUserByUsername(email);
|
const sanitizedEmail = email.toLowerCase();
|
||||||
|
const usersByUsername = await userDAL.findUserByUsername(sanitizedEmail);
|
||||||
|
const user =
|
||||||
|
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === sanitizedEmail) : usersByUsername?.[0];
|
||||||
if (!user || (user && user.isAccepted)) {
|
if (!user || (user && user.isAccepted)) {
|
||||||
// TODO(akhilmhdh): copy as old one. this needs to be changed due to security issues
|
// TODO(akhilmhdh): copy as old one. this needs to be changed due to security issues
|
||||||
throw new Error("Failed to send verification code for complete account");
|
throw new Error("Failed to send verification code for complete account");
|
||||||
}
|
}
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
await tokenService.validateTokenForUser({
|
await tokenService.validateTokenForUser({
|
||||||
type: TokenType.TOKEN_EMAIL_CONFIRMATION,
|
type: TokenType.TOKEN_EMAIL_CONFIRMATION,
|
||||||
@@ -153,12 +166,15 @@ export const authSignupServiceFactory = ({
|
|||||||
authorization,
|
authorization,
|
||||||
useDefaultOrg
|
useDefaultOrg
|
||||||
}: TCompleteAccountSignupDTO) => {
|
}: TCompleteAccountSignupDTO) => {
|
||||||
|
const sanitizedEmail = email.toLowerCase();
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
const user = await userDAL.findOne({ username: email });
|
const usersByUsername = await userDAL.findUserByUsername(sanitizedEmail);
|
||||||
|
const user =
|
||||||
|
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === sanitizedEmail) : usersByUsername?.[0];
|
||||||
if (!user || (user && user.isAccepted)) {
|
if (!user || (user && user.isAccepted)) {
|
||||||
throw new Error("Failed to complete account for complete user");
|
throw new BadRequestError({ message: "Failed to complete account for complete user" });
|
||||||
}
|
}
|
||||||
|
|
||||||
let organizationId: string | null = null;
|
let organizationId: string | null = null;
|
||||||
@@ -315,7 +331,7 @@ export const authSignupServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatedMembersips = await orgDAL.updateMembership(
|
const updatedMembersips = await orgDAL.updateMembership(
|
||||||
{ inviteEmail: email, status: OrgMembershipStatus.Invited },
|
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited },
|
||||||
{ userId: user.id, status: OrgMembershipStatus.Accepted }
|
{ userId: user.id, status: OrgMembershipStatus.Accepted }
|
||||||
);
|
);
|
||||||
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
|
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
|
||||||
@@ -382,9 +398,9 @@ export const authSignupServiceFactory = ({
|
|||||||
* User signup flow when they are invited to join the org
|
* User signup flow when they are invited to join the org
|
||||||
* */
|
* */
|
||||||
const completeAccountInvite = async ({
|
const completeAccountInvite = async ({
|
||||||
|
email,
|
||||||
ip,
|
ip,
|
||||||
salt,
|
salt,
|
||||||
email,
|
|
||||||
password,
|
password,
|
||||||
verifier,
|
verifier,
|
||||||
firstName,
|
firstName,
|
||||||
@@ -399,7 +415,10 @@ export const authSignupServiceFactory = ({
|
|||||||
encryptedPrivateKeyTag,
|
encryptedPrivateKeyTag,
|
||||||
authorization
|
authorization
|
||||||
}: TCompleteAccountInviteDTO) => {
|
}: TCompleteAccountInviteDTO) => {
|
||||||
const user = await userDAL.findUserByUsername(email);
|
const sanitizedEmail = email.toLowerCase();
|
||||||
|
const usersByUsername = await userDAL.findUserByUsername(sanitizedEmail);
|
||||||
|
const user =
|
||||||
|
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === sanitizedEmail) : usersByUsername?.[0];
|
||||||
if (!user || (user && user.isAccepted)) {
|
if (!user || (user && user.isAccepted)) {
|
||||||
throw new Error("Failed to complete account for complete user");
|
throw new Error("Failed to complete account for complete user");
|
||||||
}
|
}
|
||||||
@@ -407,7 +426,7 @@ export const authSignupServiceFactory = ({
|
|||||||
validateSignUpAuthorization(authorization, user.id);
|
validateSignUpAuthorization(authorization, user.id);
|
||||||
|
|
||||||
const [orgMembership] = await orgDAL.findMembership({
|
const [orgMembership] = await orgDAL.findMembership({
|
||||||
inviteEmail: email,
|
inviteEmail: sanitizedEmail,
|
||||||
status: OrgMembershipStatus.Invited
|
status: OrgMembershipStatus.Invited
|
||||||
});
|
});
|
||||||
if (!orgMembership)
|
if (!orgMembership)
|
||||||
@@ -454,7 +473,7 @@ export const authSignupServiceFactory = ({
|
|||||||
const serverGeneratedPrivateKey = await getUserPrivateKey(serverGeneratedPassword, {
|
const serverGeneratedPrivateKey = await getUserPrivateKey(serverGeneratedPassword, {
|
||||||
...systemGeneratedUserEncryptionKey
|
...systemGeneratedUserEncryptionKey
|
||||||
});
|
});
|
||||||
const encKeys = await generateUserSrpKeys(email, password, {
|
const encKeys = await generateUserSrpKeys(sanitizedEmail, password, {
|
||||||
publicKey: systemGeneratedUserEncryptionKey.publicKey,
|
publicKey: systemGeneratedUserEncryptionKey.publicKey,
|
||||||
privateKey: serverGeneratedPrivateKey
|
privateKey: serverGeneratedPrivateKey
|
||||||
});
|
});
|
||||||
@@ -505,7 +524,7 @@ export const authSignupServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatedMembersips = await orgDAL.updateMembership(
|
const updatedMembersips = await orgDAL.updateMembership(
|
||||||
{ inviteEmail: email, status: OrgMembershipStatus.Invited },
|
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited },
|
||||||
{ userId: us.id, status: OrgMembershipStatus.Accepted },
|
{ userId: us.id, status: OrgMembershipStatus.Accepted },
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -811,7 +811,11 @@ export const orgServiceFactory = ({
|
|||||||
const users: Pick<TUsers, "id" | "firstName" | "lastName" | "email" | "username">[] = [];
|
const users: Pick<TUsers, "id" | "firstName" | "lastName" | "email" | "username">[] = [];
|
||||||
|
|
||||||
for await (const inviteeEmail of inviteeEmails) {
|
for await (const inviteeEmail of inviteeEmails) {
|
||||||
let inviteeUser = await userDAL.findUserByUsername(inviteeEmail, tx);
|
const usersByUsername = await userDAL.findUserByUsername(inviteeEmail, tx);
|
||||||
|
let inviteeUser =
|
||||||
|
usersByUsername?.length > 1
|
||||||
|
? usersByUsername.find((el) => el.username === inviteeEmail)
|
||||||
|
: usersByUsername?.[0];
|
||||||
|
|
||||||
// if the user doesn't exist we create the user with the email
|
// if the user doesn't exist we create the user with the email
|
||||||
if (!inviteeUser) {
|
if (!inviteeUser) {
|
||||||
@@ -1223,10 +1227,13 @@ export const orgServiceFactory = ({
|
|||||||
* magic link and issue a temporary signup token for user to complete setting up their account
|
* magic link and issue a temporary signup token for user to complete setting up their account
|
||||||
*/
|
*/
|
||||||
const verifyUserToOrg = async ({ orgId, email, code }: TVerifyUserToOrgDTO) => {
|
const verifyUserToOrg = async ({ orgId, email, code }: TVerifyUserToOrgDTO) => {
|
||||||
const user = await userDAL.findUserByUsername(email);
|
const usersByUsername = await userDAL.findUserByUsername(email);
|
||||||
|
const user =
|
||||||
|
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === email) : usersByUsername?.[0];
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new NotFoundError({ message: "User not found" });
|
throw new NotFoundError({ message: "User not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const [orgMembership] = await orgDAL.findMembership({
|
const [orgMembership] = await orgDAL.findMembership({
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
|
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
|
||||||
status: OrgMembershipStatus.Invited,
|
status: OrgMembershipStatus.Invited,
|
||||||
|
|||||||
@@ -257,8 +257,8 @@ export const superAdminServiceFactory = ({
|
|||||||
const adminSignUp = async ({
|
const adminSignUp = async ({
|
||||||
lastName,
|
lastName,
|
||||||
firstName,
|
firstName,
|
||||||
salt,
|
|
||||||
email,
|
email,
|
||||||
|
salt,
|
||||||
password,
|
password,
|
||||||
verifier,
|
verifier,
|
||||||
publicKey,
|
publicKey,
|
||||||
@@ -272,7 +272,8 @@ export const superAdminServiceFactory = ({
|
|||||||
userAgent
|
userAgent
|
||||||
}: TAdminSignUpDTO) => {
|
}: TAdminSignUpDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const existingUser = await userDAL.findOne({ email });
|
const sanitizedEmail = email.toLowerCase();
|
||||||
|
const existingUser = await userDAL.findOne({ username: sanitizedEmail });
|
||||||
if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exists" });
|
if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exists" });
|
||||||
|
|
||||||
const privateKey = await getUserPrivateKey(password, {
|
const privateKey = await getUserPrivateKey(password, {
|
||||||
@@ -292,8 +293,8 @@ export const superAdminServiceFactory = ({
|
|||||||
{
|
{
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
username: email,
|
username: sanitizedEmail,
|
||||||
email,
|
email: sanitizedEmail,
|
||||||
superAdmin: true,
|
superAdmin: true,
|
||||||
isGhost: false,
|
isGhost: false,
|
||||||
isAccepted: true,
|
isAccepted: true,
|
||||||
@@ -348,12 +349,13 @@ export const superAdminServiceFactory = ({
|
|||||||
|
|
||||||
const bootstrapInstance = async ({ email, password, organizationName }: TAdminBootstrapInstanceDTO) => {
|
const bootstrapInstance = async ({ email, password, organizationName }: TAdminBootstrapInstanceDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
const sanitizedEmail = email.toLowerCase();
|
||||||
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
if (serverCfg?.initialized) {
|
if (serverCfg?.initialized) {
|
||||||
throw new BadRequestError({ message: "Instance has already been set up" });
|
throw new BadRequestError({ message: "Instance has already been set up" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingUser = await userDAL.findOne({ email });
|
const existingUser = await userDAL.findOne({ email: sanitizedEmail });
|
||||||
if (existingUser) throw new BadRequestError({ name: "Instance initialization", message: "User already exists" });
|
if (existingUser) throw new BadRequestError({ name: "Instance initialization", message: "User already exists" });
|
||||||
|
|
||||||
const userInfo = await userDAL.transaction(async (tx) => {
|
const userInfo = await userDAL.transaction(async (tx) => {
|
||||||
@@ -361,8 +363,8 @@ export const superAdminServiceFactory = ({
|
|||||||
{
|
{
|
||||||
firstName: "Admin",
|
firstName: "Admin",
|
||||||
lastName: "User",
|
lastName: "User",
|
||||||
username: email,
|
username: sanitizedEmail,
|
||||||
email,
|
email: sanitizedEmail,
|
||||||
superAdmin: true,
|
superAdmin: true,
|
||||||
isGhost: false,
|
isGhost: false,
|
||||||
isAccepted: true,
|
isAccepted: true,
|
||||||
@@ -372,7 +374,7 @@ export const superAdminServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(password);
|
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(password);
|
||||||
const encKeys = await generateUserSrpKeys(email, password);
|
const encKeys = await generateUserSrpKeys(sanitizedEmail, password);
|
||||||
|
|
||||||
const userEnc = await userDAL.createUserEncryption(
|
const userEnc = await userDAL.createUserEncryption(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ export type TUserDALFactory = ReturnType<typeof userDALFactory>;
|
|||||||
|
|
||||||
export const userDALFactory = (db: TDbClient) => {
|
export const userDALFactory = (db: TDbClient) => {
|
||||||
const userOrm = ormify(db, TableName.Users);
|
const userOrm = ormify(db, TableName.Users);
|
||||||
const findUserByUsername = async (username: string, tx?: Knex) => userOrm.findOne({ username }, tx);
|
const findUserByUsername = async (username: string, tx?: Knex) =>
|
||||||
|
(tx || db)(TableName.Users).whereRaw('lower("username") = :username', { username: username.toLowerCase() });
|
||||||
|
|
||||||
const getUsersByFilter = async ({
|
const getUsersByFilter = async ({
|
||||||
limit,
|
limit,
|
||||||
@@ -41,7 +42,7 @@ export const userDALFactory = (db: TDbClient) => {
|
|||||||
.whereILike("email", `%${searchTerm}%`)
|
.whereILike("email", `%${searchTerm}%`)
|
||||||
.orWhereILike("firstName", `%${searchTerm}%`)
|
.orWhereILike("firstName", `%${searchTerm}%`)
|
||||||
.orWhereILike("lastName", `%${searchTerm}%`)
|
.orWhereILike("lastName", `%${searchTerm}%`)
|
||||||
.orWhereLike("username", `%${searchTerm}%`);
|
.orWhereRaw('lower("username") like ?', `%${searchTerm}%`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,12 +66,11 @@ export const userDALFactory = (db: TDbClient) => {
|
|||||||
try {
|
try {
|
||||||
return await db
|
return await db
|
||||||
.replicaNode()(TableName.Users)
|
.replicaNode()(TableName.Users)
|
||||||
|
.whereRaw('lower("username") = :username', { username: username.toLowerCase() })
|
||||||
.where({
|
.where({
|
||||||
username,
|
|
||||||
isGhost: false
|
isGhost: false
|
||||||
})
|
})
|
||||||
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
|
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`);
|
||||||
.first();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Find user enc by email" });
|
throw new DatabaseError({ error, name: "Find user enc by email" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type TUserServiceFactoryDep = {
|
|||||||
userDAL: Pick<
|
userDAL: Pick<
|
||||||
TUserDALFactory,
|
TUserDALFactory,
|
||||||
| "find"
|
| "find"
|
||||||
| "findOne"
|
| "findUserByUsername"
|
||||||
| "findById"
|
| "findById"
|
||||||
| "transaction"
|
| "transaction"
|
||||||
| "updateById"
|
| "updateById"
|
||||||
@@ -54,8 +54,11 @@ export const userServiceFactory = ({
|
|||||||
permissionService
|
permissionService
|
||||||
}: TUserServiceFactoryDep) => {
|
}: TUserServiceFactoryDep) => {
|
||||||
const sendEmailVerificationCode = async (username: string) => {
|
const sendEmailVerificationCode = async (username: string) => {
|
||||||
const user = await userDAL.findOne({ username });
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const users = await userDAL.findUserByUsername(username);
|
||||||
|
const user = users?.length > 1 ? users.find((el) => el.username === username) : users?.[0];
|
||||||
if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` });
|
if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` });
|
||||||
|
|
||||||
if (!user.email)
|
if (!user.email)
|
||||||
throw new BadRequestError({ name: "Failed to send email verification code due to no email on user" });
|
throw new BadRequestError({ name: "Failed to send email verification code due to no email on user" });
|
||||||
if (user.isEmailVerified)
|
if (user.isEmailVerified)
|
||||||
@@ -77,7 +80,10 @@ export const userServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const verifyEmailVerificationCode = async (username: string, code: string) => {
|
const verifyEmailVerificationCode = async (username: string, code: string) => {
|
||||||
const user = await userDAL.findOne({ username });
|
// akhilmhdh: case sensitive email resolution
|
||||||
|
const usersByusername = await userDAL.findUserByUsername(username);
|
||||||
|
const user =
|
||||||
|
usersByusername?.length > 1 ? usersByusername.find((el) => el.username === username) : usersByusername?.[0];
|
||||||
if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` });
|
if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` });
|
||||||
if (!user.email)
|
if (!user.email)
|
||||||
throw new BadRequestError({ name: "Failed to verify email verification code due to no email on user" });
|
throw new BadRequestError({ name: "Failed to verify email verification code due to no email on user" });
|
||||||
@@ -104,7 +110,7 @@ export const userServiceFactory = ({
|
|||||||
// check if there are verified users with the same email.
|
// check if there are verified users with the same email.
|
||||||
const users = await userDAL.find(
|
const users = await userDAL.find(
|
||||||
{
|
{
|
||||||
email,
|
email: email.toLowerCase(),
|
||||||
isEmailVerified: true
|
isEmailVerified: true
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
@@ -163,7 +169,7 @@ export const userServiceFactory = ({
|
|||||||
await userDAL.updateById(
|
await userDAL.updateById(
|
||||||
user.id,
|
user.id,
|
||||||
{
|
{
|
||||||
username: email
|
username: email.toLowerCase()
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -212,6 +218,23 @@ export const userServiceFactory = ({
|
|||||||
return updatedUser;
|
return updatedUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAllMyAccounts = async (email: string, userId: string) => {
|
||||||
|
const users = await userDAL.find({ email });
|
||||||
|
return users?.map((el) => ({ ...el, isMyAccount: el.id === userId }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeMyDuplicateAccounts = async (email: string, userId: string) => {
|
||||||
|
const users = await userDAL.find({ email });
|
||||||
|
const duplicatedAccounts = users?.filter((el) => el.id !== userId);
|
||||||
|
const myAccount = users?.find((el) => el.id === userId);
|
||||||
|
if (duplicatedAccounts.length && myAccount) {
|
||||||
|
await userDAL.transaction(async (tx) => {
|
||||||
|
await userDAL.delete({ $in: { id: duplicatedAccounts?.map((el) => el.id) } }, tx);
|
||||||
|
await userDAL.updateById(userId, { username: myAccount.username.toLowerCase() });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getMe = async (userId: string) => {
|
const getMe = async (userId: string) => {
|
||||||
const user = await userDAL.findUserEncKeyByUserId(userId);
|
const user = await userDAL.findUserEncKeyByUserId(userId);
|
||||||
if (!user) throw new NotFoundError({ message: `User with ID '${userId}' not found`, name: "GetMe" });
|
if (!user) throw new NotFoundError({ message: `User with ID '${userId}' not found`, name: "GetMe" });
|
||||||
@@ -313,9 +336,11 @@ export const userServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listUserGroups = async ({ username, actorOrgId, actor, actorId, actorAuthMethod }: TListUserGroupsDTO) => {
|
const listUserGroups = async ({ username, actorOrgId, actor, actorId, actorAuthMethod }: TListUserGroupsDTO) => {
|
||||||
const user = await userDAL.findOne({
|
// akhilmhdh: case sensitive email resolution
|
||||||
username
|
const usersByusername = await userDAL.findUserByUsername(username);
|
||||||
});
|
const user =
|
||||||
|
usersByusername?.length > 1 ? usersByusername.find((el) => el.username === username) : usersByusername?.[0];
|
||||||
|
if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` });
|
||||||
|
|
||||||
// This makes it so the user can always read information about themselves, but no one else if they don't have the Members Read permission.
|
// This makes it so the user can always read information about themselves, but no one else if they don't have the Members Read permission.
|
||||||
if (user.id !== actorId) {
|
if (user.id !== actorId) {
|
||||||
@@ -346,7 +371,9 @@ export const userServiceFactory = ({
|
|||||||
getUserAction,
|
getUserAction,
|
||||||
unlockUser,
|
unlockUser,
|
||||||
getUserPrivateKey,
|
getUserPrivateKey,
|
||||||
|
getAllMyAccounts,
|
||||||
getUserProjectFavorites,
|
getUserProjectFavorites,
|
||||||
|
removeMyDuplicateAccounts,
|
||||||
updateUserProjectFavorites
|
updateUserProjectFavorites
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user