mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 11:38:55 +00:00
Merge pull request #2731 from Infisical/feat/totp-authenticator
feat: TOTP authenticator
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
Login2Res,
|
||||
LoginLDAPDTO,
|
||||
LoginLDAPRes,
|
||||
MfaMethod,
|
||||
ResetPasswordDTO,
|
||||
SendMfaTokenDTO,
|
||||
SRP1DTO,
|
||||
@@ -65,10 +66,11 @@ export const selectOrganization = async (data: {
|
||||
organizationId: string;
|
||||
userAgent?: UserAgentType;
|
||||
}) => {
|
||||
const { data: res } = await apiRequest.post<{ token: string; isMfaEnabled: boolean }>(
|
||||
"/api/v3/auth/select-organization",
|
||||
data
|
||||
);
|
||||
const { data: res } = await apiRequest.post<{
|
||||
token: string;
|
||||
isMfaEnabled: boolean;
|
||||
mfaMethod?: MfaMethod;
|
||||
}>("/api/v3/auth/select-organization", data);
|
||||
return res;
|
||||
};
|
||||
|
||||
@@ -154,10 +156,19 @@ export const useSendMfaToken = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const verifyMfaToken = async ({ email, mfaCode }: { email: string; mfaCode: string }) => {
|
||||
export const verifyMfaToken = async ({
|
||||
email,
|
||||
mfaCode,
|
||||
mfaMethod
|
||||
}: {
|
||||
email: string;
|
||||
mfaCode: string;
|
||||
mfaMethod?: string;
|
||||
}) => {
|
||||
const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", {
|
||||
email,
|
||||
mfaToken: mfaCode
|
||||
mfaToken: mfaCode,
|
||||
mfaMethod
|
||||
});
|
||||
|
||||
return data;
|
||||
@@ -165,10 +176,11 @@ export const verifyMfaToken = async ({ email, mfaCode }: { email: string; mfaCod
|
||||
|
||||
export const useVerifyMfaToken = () => {
|
||||
return useMutation<VerifyMfaTokenRes, {}, VerifyMfaTokenDTO>({
|
||||
mutationFn: async ({ email, mfaCode }) => {
|
||||
mutationFn: async ({ email, mfaCode, mfaMethod }) => {
|
||||
return verifyMfaToken({
|
||||
email,
|
||||
mfaCode
|
||||
mfaCode,
|
||||
mfaMethod
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -302,3 +314,9 @@ export const useGetAuthToken = () =>
|
||||
onSuccess: (data) => setAuthToken(data.token),
|
||||
retry: 0
|
||||
});
|
||||
|
||||
export const checkUserTotpMfa = async () => {
|
||||
const { data } = await apiRequest.get<{ isVerified: boolean }>("/api/v2/auth/mfa/check/totp");
|
||||
|
||||
return data.isVerified;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ export type SendMfaTokenDTO = {
|
||||
export type VerifyMfaTokenDTO = {
|
||||
email: string;
|
||||
mfaCode: string;
|
||||
mfaMethod: MfaMethod;
|
||||
};
|
||||
|
||||
export type VerifyMfaTokenRes = {
|
||||
@@ -149,3 +150,8 @@ export type GetBackupEncryptedPrivateKeyDTO = {
|
||||
export enum UserAgentType {
|
||||
CLI = "cli"
|
||||
}
|
||||
|
||||
export enum MfaMethod {
|
||||
EMAIL = "email",
|
||||
TOTP = "totp"
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ export const useUpdateOrg = () => {
|
||||
slug,
|
||||
orgId,
|
||||
defaultMembershipRoleSlug,
|
||||
enforceMfa
|
||||
enforceMfa,
|
||||
selectedMfaMethod
|
||||
}) => {
|
||||
return apiRequest.patch(`/api/v1/organization/${orgId}`, {
|
||||
name,
|
||||
@@ -99,7 +100,8 @@ export const useUpdateOrg = () => {
|
||||
scimEnabled,
|
||||
slug,
|
||||
defaultMembershipRoleSlug,
|
||||
enforceMfa
|
||||
enforceMfa,
|
||||
selectedMfaMethod
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { IdentityMembershipOrg } from "@app/hooks/api/identities/types";
|
||||
|
||||
import { MfaMethod } from "../auth/types";
|
||||
|
||||
export type Organization = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -12,6 +14,7 @@ export type Organization = {
|
||||
slug: string;
|
||||
defaultMembershipRole: string;
|
||||
enforceMfa: boolean;
|
||||
selectedMfaMethod?: MfaMethod;
|
||||
};
|
||||
|
||||
export type UpdateOrgDTO = {
|
||||
@@ -22,6 +25,7 @@ export type UpdateOrgDTO = {
|
||||
slug?: string;
|
||||
defaultMembershipRoleSlug?: string;
|
||||
enforceMfa?: boolean;
|
||||
selectedMfaMethod?: MfaMethod;
|
||||
};
|
||||
|
||||
export type BillingDetails = {
|
||||
|
||||
@@ -21,12 +21,13 @@ export {
|
||||
useGetOrgUsers,
|
||||
useGetUser,
|
||||
useGetUserAction,
|
||||
useGetUserTotpRegistration,
|
||||
useListUserGroupMemberships,
|
||||
useLogoutUser,
|
||||
useRegisterUserAction,
|
||||
useRevokeMySessions,
|
||||
useUpdateMfaEnabled,
|
||||
useUpdateOrgMembership,
|
||||
useUpdateUserAuthMethods
|
||||
useUpdateUserAuthMethods,
|
||||
useUpdateUserMfa
|
||||
} from "./queries";
|
||||
export { userKeys } from "./query-keys";
|
||||
|
||||
@@ -114,3 +114,43 @@ export const useUpdateUserProjectFavorites = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useVerifyUserTotpRegistration = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ totp }: { totp: string }) => {
|
||||
await apiRequest.post("/api/v1/user/me/totp/verify", {
|
||||
totp
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteUserTotpConfiguration = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
await apiRequest.delete("/api/v1/user/me/totp");
|
||||
|
||||
return {};
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(userKeys.totpConfiguration);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateNewTotpRecoveryCodes = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
await apiRequest.post("/api/v1/user/me/totp/recovery-codes");
|
||||
|
||||
return {};
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(userKeys.totpConfiguration);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { SessionStorageKeys } from "@app/const";
|
||||
import { setAuthToken } from "@app/reactQuery";
|
||||
|
||||
import { APIKeyDataV2 } from "../apiKeys/types";
|
||||
import { MfaMethod } from "../auth/types";
|
||||
import { TGroupWithProjectMemberships } from "../groups/types";
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import { userKeys } from "./query-keys";
|
||||
@@ -390,14 +392,21 @@ export const useRevokeMySessions = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateMfaEnabled = () => {
|
||||
export const useUpdateUserMfa = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ isMfaEnabled }: { isMfaEnabled: boolean }) => {
|
||||
mutationFn: async ({
|
||||
isMfaEnabled,
|
||||
selectedMfaMethod
|
||||
}: {
|
||||
isMfaEnabled?: boolean;
|
||||
selectedMfaMethod?: MfaMethod;
|
||||
}) => {
|
||||
const {
|
||||
data: { user }
|
||||
} = await apiRequest.patch("/api/v2/users/me/mfa", {
|
||||
isMfaEnabled
|
||||
isMfaEnabled,
|
||||
selectedMfaMethod
|
||||
});
|
||||
|
||||
return user;
|
||||
@@ -446,3 +455,39 @@ export const useListUserGroupMemberships = (username: string) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetUserTotpRegistration = () => {
|
||||
return useQuery({
|
||||
queryKey: userKeys.totpRegistration,
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.post<{ otpUrl: string; recoveryCodes: string[] }>(
|
||||
"/api/v1/user/me/totp/register"
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetUserTotpConfiguration = () => {
|
||||
return useQuery({
|
||||
queryKey: userKeys.totpConfiguration,
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const { data } = await apiRequest.get<{ isVerified: boolean; recoveryCodes: string[] }>(
|
||||
"/api/v1/user/me/totp"
|
||||
);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError && [404, 400].includes(error.response?.data?.statusCode)) {
|
||||
return {
|
||||
isVerified: false,
|
||||
recoveryCodes: []
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@ export const userKeys = {
|
||||
myAPIKeysV2: ["api-keys-v2"] as const,
|
||||
mySessions: ["sessions"] as const,
|
||||
listUsers: ["user-list"] as const,
|
||||
totpRegistration: ["totp-registration"],
|
||||
totpConfiguration: ["totp-configuration"],
|
||||
listUserGroupMemberships: (username: string) => [{ username }, "user-group-memberships"] as const,
|
||||
myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MfaMethod } from "../auth/types";
|
||||
import { UserWsKeyPair } from "../keys/types";
|
||||
import { ProjectUserMembershipTemporaryMode } from "../workspace/types";
|
||||
|
||||
@@ -26,6 +27,7 @@ export type User = {
|
||||
authProvider?: AuthMethod;
|
||||
authMethods: AuthMethod[];
|
||||
isMfaEnabled: boolean;
|
||||
selectedMfaMethod?: MfaMethod;
|
||||
seenIps: string[];
|
||||
id: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user