feat: finished up oidc auth management

This commit is contained in:
Sheen Capadngan
2024-07-03 21:18:50 +08:00
parent c90e423e4a
commit fc9326272a
11 changed files with 778 additions and 83 deletions
@@ -5,5 +5,6 @@ export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
[IdentityAuthMethod.KUBERNETES_AUTH]: "Kubernetes Auth",
[IdentityAuthMethod.GCP_AUTH]: "GCP Auth",
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth",
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth"
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth",
[IdentityAuthMethod.OIDC_AUTH]: "OIDC Auth"
};
@@ -9,6 +9,7 @@ import {
AddIdentityAzureAuthDTO,
AddIdentityGcpAuthDTO,
AddIdentityKubernetesAuthDTO,
AddIdentityOidcAuthDTO,
AddIdentityUniversalAuthDTO,
ClientSecretData,
CreateIdentityDTO,
@@ -21,12 +22,14 @@ import {
IdentityAzureAuth,
IdentityGcpAuth,
IdentityKubernetesAuth,
IdentityOidcAuth,
IdentityUniversalAuth,
UpdateIdentityAwsAuthDTO,
UpdateIdentityAzureAuthDTO,
UpdateIdentityDTO,
UpdateIdentityGcpAuthDTO,
UpdateIdentityKubernetesAuthDTO,
UpdateIdentityOidcAuthDTO,
UpdateIdentityUniversalAuthDTO
} from "./types";
@@ -330,6 +333,90 @@ export const useUpdateIdentityAwsAuth = () => {
});
};
export const useUpdateIdentityOidcAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityOidcAuth, {}, UpdateIdentityOidcAuthDTO>({
mutationFn: async ({
identityId,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
oidcDiscoveryUrl,
caCert,
boundIssuer,
boundAudiences,
boundClaims,
boundSubject
}) => {
const {
data: { identityOidcAuth }
} = await apiRequest.patch<{ identityOidcAuth: IdentityOidcAuth }>(
`/api/v1/auth/oidc-auth/identities/${identityId}`,
{
oidcDiscoveryUrl,
caCert,
boundIssuer,
boundAudiences,
boundClaims,
boundSubject,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}
);
return identityOidcAuth;
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
}
});
};
export const useAddIdentityOidcAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityOidcAuth, {}, AddIdentityOidcAuthDTO>({
mutationFn: async ({
identityId,
oidcDiscoveryUrl,
caCert,
boundIssuer,
boundAudiences,
boundClaims,
boundSubject,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}) => {
const {
data: { identityOidcAuth }
} = await apiRequest.post<{ identityOidcAuth: IdentityOidcAuth }>(
`/api/v1/auth/oidc-auth/identities/${identityId}`,
{
oidcDiscoveryUrl,
caCert,
boundIssuer,
boundAudiences,
boundClaims,
boundSubject,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}
);
return identityOidcAuth;
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
}
});
};
export const useAddIdentityAzureAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityAzureAuth, {}, AddIdentityAzureAuthDTO>({
+19 -1
View File
@@ -8,7 +8,9 @@ import {
IdentityAzureAuth,
IdentityGcpAuth,
IdentityKubernetesAuth,
IdentityUniversalAuth} from "./types";
IdentityOidcAuth,
IdentityUniversalAuth
} from "./types";
export const identitiesKeys = {
getIdentityUniversalAuth: (identityId: string) =>
@@ -18,6 +20,7 @@ export const identitiesKeys = {
getIdentityKubernetesAuth: (identityId: string) =>
[{ identityId }, "identity-kubernetes-auth"] as const,
getIdentityGcpAuth: (identityId: string) => [{ identityId }, "identity-gcp-auth"] as const,
getIdentityOidcAuth: (identityId: string) => [{ identityId }, "identity-oidc-auth"] as const,
getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const,
getIdentityAzureAuth: (identityId: string) => [{ identityId }, "identity-azure-auth"] as const
};
@@ -111,3 +114,18 @@ export const useGetIdentityKubernetesAuth = (identityId: string) => {
}
});
};
export const useGetIdentityOidcAuth = (identityId: string) => {
return useQuery({
enabled: Boolean(identityId),
queryKey: identitiesKeys.getIdentityOidcAuth(identityId),
queryFn: async () => {
const {
data: { identityOidcAuth }
} = await apiRequest.get<{ identityOidcAuth: IdentityOidcAuth }>(
`/api/v1/auth/oidc-auth/identities/${identityId}`
);
return identityOidcAuth;
}
});
};
@@ -155,6 +155,54 @@ export type UpdateIdentityGcpAuthDTO = {
}[];
};
export type IdentityOidcAuth = {
identityId: string;
oidcDiscoveryUrl: string;
caCert: string;
boundIssuer: string;
boundAudiences: string;
boundClaims: Record<string, string>;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: IdentityTrustedIp[];
};
export type AddIdentityOidcAuthDTO = {
organizationId: string;
identityId: string;
oidcDiscoveryUrl: string;
caCert: string;
boundIssuer: string;
boundAudiences: string;
boundClaims: Record<string, string>;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
export type UpdateIdentityOidcAuthDTO = {
organizationId: string;
identityId: string;
oidcDiscoveryUrl?: string;
caCert?: string;
boundIssuer?: string;
boundAudiences?: string;
boundClaims?: Record<string, string>;
boundSubject?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
export type IdentityAwsAuth = {
identityId: string;
type: "iam";
@@ -6,9 +6,14 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input } from "@app/components/v2";
import { Button, FormControl, IconButton, Input, TextArea } from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context";
import { IdentityAuthMethod } from "@app/hooks/api/identities";
import {
useAddIdentityOidcAuth,
useUpdateIdentityOidcAuth
} from "@app/hooks/api/identities/mutations";
import { useGetIdentityOidcAuth } from "@app/hooks/api/identities/queries";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -60,10 +65,10 @@ export const IdentityOidcAuthForm = ({
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
// const { mutateAsync: addMutateAsync } = useAddIdentityGcpAuth();
// const { mutateAsync: updateMutateAsync } = useUpdateIdentityGcpAuth();
const { mutateAsync: addMutateAsync } = useAddIdentityOidcAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityOidcAuth();
// const { data } = useGetIdentityGcpAuth(identityAuthMethodData?.identityId ?? "");
const { data } = useGetIdentityOidcAuth(identityAuthMethodData?.identityId ?? "");
const {
control,
@@ -95,78 +100,93 @@ export const IdentityOidcAuthForm = ({
remove: removeAccessTokenTrustedIp
} = useFieldArray({ control, name: "accessTokenTrustedIps" });
// useEffect(() => {
// if (data) {
// reset({
// type: data.type,
// allowedServiceAccounts: data.allowedServiceAccounts,
// allowedProjects: data.allowedProjects,
// allowedZones: data.allowedZones,
// accessTokenTTL: String(data.accessTokenTTL),
// accessTokenMaxTTL: String(data.accessTokenMaxTTL),
// accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
// accessTokenTrustedIps: data.accessTokenTrustedIps.map(
// ({ ipAddress, prefix }: IdentityTrustedIp) => {
// return {
// ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
// };
// }
// )
// });
// } else {
// reset({
// type: "iam",
// allowedServiceAccounts: "",
// allowedProjects: "",
// allowedZones: "",
// accessTokenTTL: "2592000",
// accessTokenMaxTTL: "2592000",
// accessTokenNumUsesLimit: "0",
// accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
// });
// }
// }, [data]);
useEffect(() => {
if (data) {
reset({
oidcDiscoveryUrl: data.oidcDiscoveryUrl,
caCert: data.caCert,
boundIssuer: data.boundIssuer,
boundAudiences: data.boundAudiences,
boundClaims: Object.entries(data.boundClaims).map(([key, value]) => ({
key,
value
})),
boundSubject: data.boundSubject,
accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
accessTokenTrustedIps: data.accessTokenTrustedIps.map(
({ ipAddress, prefix }: IdentityTrustedIp) => {
return {
ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
};
}
)
});
} else {
reset({
oidcDiscoveryUrl: "",
caCert: "",
boundIssuer: "",
boundAudiences: "",
boundClaims: [],
boundSubject: "",
accessTokenTTL: "2592000",
accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0",
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
});
}
}, [data]);
const onFormSubmit = async ({
type,
allowedServiceAccounts,
allowedProjects,
allowedZones,
accessTokenTrustedIps,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
oidcDiscoveryUrl,
caCert,
boundIssuer,
boundAudiences,
boundClaims,
boundSubject
}: FormData) => {
try {
if (!identityAuthMethodData) return;
if (!identityAuthMethodData) {
return;
}
// if (data) {
// await updateMutateAsync({
// identityId: identityAuthMethodData.identityId,
// organizationId: orgId,
// type,
// allowedServiceAccounts,
// allowedProjects,
// allowedZones,
// accessTokenTTL: Number(accessTokenTTL),
// accessTokenMaxTTL: Number(accessTokenMaxTTL),
// accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
// accessTokenTrustedIps
// });
// } else {
// await addMutateAsync({
// identityId: identityAuthMethodData.identityId,
// organizationId: orgId,
// type,
// allowedServiceAccounts: allowedServiceAccounts || "",
// allowedProjects: allowedProjects || "",
// allowedZones: allowedZones || "",
// accessTokenTTL: Number(accessTokenTTL),
// accessTokenMaxTTL: Number(accessTokenMaxTTL),
// accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
// accessTokenTrustedIps
// });
// }
if (data) {
await updateMutateAsync({
identityId: identityAuthMethodData.identityId,
organizationId: orgId,
oidcDiscoveryUrl,
caCert,
boundIssuer,
boundAudiences,
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
boundSubject,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps
});
} else {
await addMutateAsync({
identityId: identityAuthMethodData.identityId,
oidcDiscoveryUrl,
caCert,
boundIssuer,
boundAudiences,
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
boundSubject,
organizationId: orgId,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps
});
}
handlePopUpToggle("identityAuthMethod", false);
@@ -198,11 +218,7 @@ export const IdentityOidcAuthForm = ({
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="https://accounts.google.com/.well-known/openid-configuration"
type="text"
/>
<Input {...field} placeholder="https://accounts.google.com" type="text" />
</FormControl>
)}
/>
@@ -216,7 +232,16 @@ export const IdentityOidcAuthForm = ({
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} type="text" />
<Input {...field} type="text" placeholder="https://accounts.google.com" />
</FormControl>
)}
/>
<Controller
control={control}
name="caCert"
render={({ field, fieldState: { error } }) => (
<FormControl label="CA Certificate" errorText={error?.message} isError={Boolean(error)}>
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
</FormControl>
)}
/>