feat: initial implementation for totp authenticator

This commit is contained in:
Sheen Capadngan
2024-11-14 00:07:35 +08:00
parent 2d2ad0724f
commit 6bdbac4750
36 changed files with 1250 additions and 117 deletions
+3 -2
View File
@@ -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";
+26
View File
@@ -114,3 +114,29 @@ 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);
}
});
};
+48 -3
View File
@@ -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 && error.response?.data?.statusCode === 404) {
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
};
+2
View File
@@ -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;
};