mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 12:28:56 +00:00
feat: updated ui
This commit is contained in:
@@ -55,7 +55,10 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
users: UsersSchema.extend({ isMyAccount: z.boolean() }).array()
|
users: UsersSchema.extend({
|
||||||
|
isMyAccount: z.boolean(),
|
||||||
|
organizations: z.object({ name: z.string(), slug: z.string() }).array()
|
||||||
|
}).array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import {
|
|||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
TUserEncryptionKeysInsert,
|
TUserEncryptionKeysInsert,
|
||||||
TUserEncryptionKeysUpdate,
|
TUserEncryptionKeysUpdate,
|
||||||
TUsers
|
TUsers,
|
||||||
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TUserDALFactory = ReturnType<typeof userDALFactory>;
|
export type TUserDALFactory = ReturnType<typeof userDALFactory>;
|
||||||
|
|
||||||
@@ -168,6 +169,38 @@ export const userDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findAllMyAccounts = async (email: string) => {
|
||||||
|
try {
|
||||||
|
const doc = await db(TableName.Users)
|
||||||
|
.where({ email })
|
||||||
|
.leftJoin(TableName.OrgMembership, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.Organization, `${TableName.Organization}.id`, `${TableName.OrgMembership}.orgId`)
|
||||||
|
.select(selectAllTableCols(TableName.Users))
|
||||||
|
.select(
|
||||||
|
db.ref("name").withSchema(TableName.Organization).as("orgName"),
|
||||||
|
db.ref("slug").withSchema(TableName.Organization).as("orgSlug")
|
||||||
|
);
|
||||||
|
const formattedDoc = sqlNestRelationships({
|
||||||
|
data: doc,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => UsersSchema.parse(el),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "orgSlug",
|
||||||
|
label: "organizations" as const,
|
||||||
|
mapper: ({ orgSlug, orgName }) => ({
|
||||||
|
slug: orgSlug,
|
||||||
|
name: orgName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return formattedDoc;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Upsert user enc key" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// USER ACTION FUNCTIONS
|
// USER ACTION FUNCTIONS
|
||||||
// ---------------------
|
// ---------------------
|
||||||
const findOneUserAction = (filter: TUserActionsUpdate, tx?: Knex) => {
|
const findOneUserAction = (filter: TUserActionsUpdate, tx?: Knex) => {
|
||||||
@@ -200,6 +233,7 @@ export const userDALFactory = (db: TDbClient) => {
|
|||||||
createUserEncryption,
|
createUserEncryption,
|
||||||
findOneUserAction,
|
findOneUserAction,
|
||||||
createUserAction,
|
createUserAction,
|
||||||
getUsersByFilter
|
getUsersByFilter,
|
||||||
|
findAllMyAccounts
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type TUserServiceFactoryDep = {
|
|||||||
| "createUserAction"
|
| "createUserAction"
|
||||||
| "findUserEncKeyByUserId"
|
| "findUserEncKeyByUserId"
|
||||||
| "delete"
|
| "delete"
|
||||||
|
| "findAllMyAccounts"
|
||||||
>;
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "findByUserId">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "findByUserId">;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "insertMany" | "findOne" | "updateById">;
|
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "insertMany" | "findOne" | "updateById">;
|
||||||
@@ -140,7 +141,7 @@ export const userServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getAllMyAccounts = async (email: string, userId: string) => {
|
const getAllMyAccounts = async (email: string, userId: string) => {
|
||||||
const users = await userDAL.find({ email });
|
const users = await userDAL.findAllMyAccounts(email);
|
||||||
return users?.map((el) => ({ ...el, isMyAccount: el.id === userId }));
|
return users?.map((el) => ({ ...el, isMyAccount: el.id === userId }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,16 @@ export const useGetUser = () =>
|
|||||||
|
|
||||||
export const fetchUserDuplicateAccounts = async () => {
|
export const fetchUserDuplicateAccounts = async () => {
|
||||||
const { data } = await apiRequest.get<{
|
const { data } = await apiRequest.get<{
|
||||||
users: Array<User & { isMyAccount: boolean; devices: { ip: string; userAgent: string }[] }>;
|
users: Array<
|
||||||
|
User & {
|
||||||
|
isMyAccount: boolean;
|
||||||
|
organizations: { name: string; slug: string }[];
|
||||||
|
devices: {
|
||||||
|
ip: string;
|
||||||
|
userAgent: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
}>("/api/v1/user/duplicate-accounts");
|
}>("/api/v1/user/duplicate-accounts");
|
||||||
return data.users;
|
return data.users;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { Link, useNavigate } from "@tanstack/react-router";
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Alert, Button, Tooltip } from "@app/components/v2";
|
import { Button, DeleteActionModal, Tooltip } from "@app/components/v2";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useGetMyDuplicateAccount,
|
useGetMyDuplicateAccount,
|
||||||
useLogoutUser,
|
useLogoutUser,
|
||||||
@@ -24,6 +25,7 @@ export const EmailDuplicationConfirmation = ({ onRemoveDuplicateLater }: Props)
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const logout = useLogoutUser(true);
|
const logout = useLogoutUser(true);
|
||||||
|
const { popUp, handlePopUpToggle } = usePopUp(["removeDuplicateConfirm"] as const);
|
||||||
const handleLogout = useCallback(async () => {
|
const handleLogout = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
console.log("Logging out...");
|
console.log("Logging out...");
|
||||||
@@ -65,13 +67,14 @@ export const EmailDuplicationConfirmation = ({ onRemoveDuplicateLater }: Props)
|
|||||||
<span className="text-slate-300">Your current account is: </span>{" "}
|
<span className="text-slate-300">Your current account is: </span>{" "}
|
||||||
<b>{duplicateAccounts?.data?.myAccount?.username}</b>.
|
<b>{duplicateAccounts?.data?.myAccount?.username}</b>.
|
||||||
</p>
|
</p>
|
||||||
<Alert variant="warning" hideTitle>
|
<div className="mb-4 mt-4 flex flex-col rounded-r border-l-2 border-l-primary bg-mineshaft-300/5 px-4 py-2.5">
|
||||||
<div>
|
<p className="mb-2 mt-1 text-sm text-bunker-300">
|
||||||
We've detected multiple accounts using variations of the same email address.
|
We've detected multiple accounts using variations of the same email address.
|
||||||
Confirm to retain this account <b>{duplicateAccounts?.data?.myAccount?.username}</b>
|
</p>
|
||||||
. Upon confirmation other accounts will be <b>removed</b>.
|
|
||||||
</div>
|
</div>
|
||||||
</Alert>
|
</div>
|
||||||
|
<div className="mb-4 w-full border-b border-mineshaft-400 pb-1 text-sm text-mineshaft-200">
|
||||||
|
Your other accounts
|
||||||
</div>
|
</div>
|
||||||
<div className="thin-scrollbar flex h-full max-h-60 w-full flex-col items-stretch gap-2 overflow-auto rounded-md">
|
<div className="thin-scrollbar flex h-full max-h-60 w-full flex-col items-stretch gap-2 overflow-auto rounded-md">
|
||||||
{duplicateAccounts?.data?.duplicateAccounts?.map((el) => {
|
{duplicateAccounts?.data?.duplicateAccounts?.map((el) => {
|
||||||
@@ -84,7 +87,10 @@ export const EmailDuplicationConfirmation = ({ onRemoveDuplicateLater }: Props)
|
|||||||
<div className="group flex flex-grow flex-col">
|
<div className="group flex flex-grow flex-col">
|
||||||
<div className="truncate text-sm transition-colors">{el.username}</div>
|
<div className="truncate text-sm transition-colors">{el.username}</div>
|
||||||
<div className="mt-2 text-xs">
|
<div className="mt-2 text-xs">
|
||||||
Last login: {format(new Date(el.updatedAt), "Pp")}
|
Last logged in at {format(new Date(el.updatedAt), "Pp")}
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-xs">
|
||||||
|
Organizations: {el?.organizations?.map((i) => i.slug)?.join(",")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -109,26 +115,16 @@ export const EmailDuplicationConfirmation = ({ onRemoveDuplicateLater }: Props)
|
|||||||
<Button
|
<Button
|
||||||
className="flex-1 flex-grow"
|
className="flex-1 flex-grow"
|
||||||
isLoading={removeDuplicateEmails.isPending}
|
isLoading={removeDuplicateEmails.isPending}
|
||||||
onClick={() =>
|
onClick={() => handlePopUpToggle("removeDuplicateConfirm", true)}
|
||||||
removeDuplicateEmails.mutate(undefined, {
|
|
||||||
onSuccess: () => {
|
|
||||||
createNotification({
|
|
||||||
type: "success",
|
|
||||||
text: "Removed duplicate accounts"
|
|
||||||
});
|
|
||||||
onRemoveDuplicateLater();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Confirm
|
Delete all other accounts
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
onClick={() => onRemoveDuplicateLater()}
|
onClick={() => onRemoveDuplicateLater()}
|
||||||
className="flex-1 flex-grow"
|
className="flex-1 flex-grow"
|
||||||
>
|
>
|
||||||
Ask Again Later?
|
Remind me later
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -144,6 +140,25 @@ export const EmailDuplicationConfirmation = ({ onRemoveDuplicateLater }: Props)
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div className="pb-28" />
|
<div className="pb-28" />
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.removeDuplicateConfirm.isOpen}
|
||||||
|
subTitle={`This account ${duplicateAccounts?.data?.myAccount?.username} will be kept. Upon confirmation other accounts will be removed.`}
|
||||||
|
title="Confirmation Required"
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("removeDuplicateConfirm", isOpen)}
|
||||||
|
deleteKey="remove"
|
||||||
|
buttonText="Confirm"
|
||||||
|
onDeleteApproved={() =>
|
||||||
|
removeDuplicateEmails.mutateAsync(undefined, {
|
||||||
|
onSuccess: () => {
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Removed duplicate accounts"
|
||||||
|
});
|
||||||
|
onRemoveDuplicateLater();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user