mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-12 15:16:49 +00:00
🚧 wip
This commit is contained in:
@@ -147,7 +147,7 @@ export enum ActionsEnum {
|
|||||||
setResourcePolicyHeaderAuth = "setResourcePolicyHeaderAuth",
|
setResourcePolicyHeaderAuth = "setResourcePolicyHeaderAuth",
|
||||||
setResourcePolicyWhitelist = "setResourcePolicyWhitelist",
|
setResourcePolicyWhitelist = "setResourcePolicyWhitelist",
|
||||||
setResourcePolicyRules = "setResourcePolicyRules",
|
setResourcePolicyRules = "setResourcePolicyRules",
|
||||||
getResourcePolicies = "getResourcePolicies"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkUserActionPermission(
|
export async function checkUserActionPermission(
|
||||||
|
|||||||
@@ -637,10 +637,10 @@ authenticated.get(
|
|||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/resource/:resourceId/policies",
|
"/resource/:resourceId/default-policy",
|
||||||
verifyResourceAccess,
|
verifyResourceAccess,
|
||||||
verifyUserHasAction(ActionsEnum.getResourcePolicies),
|
verifyUserHasAction(ActionsEnum.getResourcePolicy),
|
||||||
resource.getResourcePolicies
|
resource.getDefaultResourcePolicy
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.put(
|
authenticated.put(
|
||||||
|
|||||||
@@ -454,10 +454,10 @@ authenticated.get(
|
|||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/resource/:resourceId/policies",
|
"/resource/:resourceId/default-policy",
|
||||||
verifyApiKeyResourceAccess,
|
verifyApiKeyResourceAccess,
|
||||||
verifyApiKeyHasAction(ActionsEnum.getResourcePolicies),
|
verifyApiKeyHasAction(ActionsEnum.getResourcePolicy),
|
||||||
resource.getResourcePolicies
|
resource.getDefaultResourcePolicy
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
|
|||||||
@@ -17,13 +17,11 @@ const getResourcePoliciesParamsSchema = z.strictObject({
|
|||||||
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
||||||
});
|
});
|
||||||
|
|
||||||
export type GetResourcePoliciesResponse = {
|
export type GetDefaultResourcePolicyResponse = GetResourcePolicyResponse;
|
||||||
defaultPolicy: GetResourcePolicyResponse | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
method: "get",
|
method: "get",
|
||||||
path: "/resource/{resourceId}/policies",
|
path: "/resource/{resourceId}/default-policy",
|
||||||
description: "Get the default policy for a resource.",
|
description: "Get the default policy for a resource.",
|
||||||
tags: [OpenAPITags.PublicResource, OpenAPITags.Policy],
|
tags: [OpenAPITags.PublicResource, OpenAPITags.Policy],
|
||||||
request: {
|
request: {
|
||||||
@@ -32,7 +30,7 @@ registry.registerPath({
|
|||||||
responses: {}
|
responses: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function getResourcePolicies(
|
export async function getDefaultResourcePolicy(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
@@ -66,14 +64,20 @@ export async function getResourcePolicies(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultPolicy = resource.defaultResourcePolicyId
|
if (!resource.defaultResourcePolicyId) {
|
||||||
? await queryResourcePolicy({
|
return next(
|
||||||
resourcePolicyId: resource.defaultResourcePolicyId
|
createHttpError(
|
||||||
})
|
HttpCode.NOT_FOUND,
|
||||||
: null;
|
"Resource has no default policy"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return response<GetResourcePoliciesResponse>(res, {
|
const defaultPolicy = await queryResourcePolicy({
|
||||||
data: { defaultPolicy },
|
resourcePolicyId: resource.defaultResourcePolicyId
|
||||||
|
});
|
||||||
|
return response<GetDefaultResourcePolicyResponse>(res, {
|
||||||
|
data: defaultPolicy,
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource policies retrieved successfully",
|
message: "Resource policies retrieved successfully",
|
||||||
@@ -31,4 +31,4 @@ export * from "./addUserToResource";
|
|||||||
export * from "./removeUserFromResource";
|
export * from "./removeUserFromResource";
|
||||||
export * from "./listAllResourceNames";
|
export * from "./listAllResourceNames";
|
||||||
export * from "./removeEmailFromResourceWhitelist";
|
export * from "./removeEmailFromResourceWhitelist";
|
||||||
export * from "./getResourcePolicies";
|
export * from "./getDefaultResourcePolicy";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { EditPolicyForm } from "@app/components/resource-policy/EditPolicyForm";
|
||||||
import SetResourceHeaderAuthForm from "@app/components/SetResourceHeaderAuthForm";
|
import SetResourceHeaderAuthForm from "@app/components/SetResourceHeaderAuthForm";
|
||||||
import SetResourcePincodeForm from "@app/components/SetResourcePincodeForm";
|
import SetResourcePincodeForm from "@app/components/SetResourcePincodeForm";
|
||||||
import {
|
import {
|
||||||
@@ -46,7 +47,10 @@ import { toast } from "@app/hooks/useToast";
|
|||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
||||||
import { orgQueries, resourceQueries } from "@app/lib/queries";
|
import { orgQueries, resourceQueries } from "@app/lib/queries";
|
||||||
import { ResourcePolicyContext } from "@app/providers/ResourcePolicyProvider";
|
import {
|
||||||
|
ResourcePolicyContext,
|
||||||
|
ResourcePolicyProvider
|
||||||
|
} from "@app/providers/ResourcePolicyProvider";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
@@ -64,32 +68,19 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
useTransition
|
useTransition
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm, useWatch } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const UsersRolesFormSchema = z.object({
|
const resourceTypeSchema = z
|
||||||
roles: z.array(
|
.object({
|
||||||
|
type: z.literal("inline")
|
||||||
|
})
|
||||||
|
.or(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.string(),
|
type: z.literal("shared"),
|
||||||
text: z.string()
|
resourcePolicyId: z.number()
|
||||||
})
|
})
|
||||||
),
|
);
|
||||||
users: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.string(),
|
|
||||||
text: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
const whitelistSchema = z.object({
|
|
||||||
emails: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.string(),
|
|
||||||
text: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
type ResourcePolicyType = StrategyOption<"inline" | "shared">;
|
type ResourcePolicyType = StrategyOption<"inline" | "shared">;
|
||||||
|
|
||||||
@@ -106,114 +97,14 @@ export default function ResourceAuthenticationPage() {
|
|||||||
|
|
||||||
const { isPaidUser } = usePaidStatus();
|
const { isPaidUser } = usePaidStatus();
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const { data: defaultPolicy, isLoading: isLoadingPolicies } = useQuery(
|
||||||
const { data: resourceRoles = [], isLoading: isLoadingResourceRoles } =
|
resourceQueries.defaultPolicy({
|
||||||
useQuery(
|
|
||||||
resourceQueries.resourceRoles({
|
|
||||||
resourceId: resource.resourceId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const { data: resourceUsers = [], isLoading: isLoadingResourceUsers } =
|
|
||||||
useQuery(
|
|
||||||
resourceQueries.resourceUsers({
|
|
||||||
resourceId: resource.resourceId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: whitelist = [], isLoading: isLoadingWhiteList } = useQuery(
|
|
||||||
resourceQueries.resourceWhitelist({
|
|
||||||
resourceId: resource.resourceId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const { data: policies, isLoading: isLoadingPolicies } = useQuery(
|
|
||||||
resourceQueries.policies({
|
|
||||||
resourceId: resource.resourceId
|
resourceId: resource.resourceId
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: orgRoles = [], isLoading: isLoadingOrgRoles } = useQuery(
|
const pageLoading = isLoadingPolicies || !defaultPolicy;
|
||||||
orgQueries.roles({
|
|
||||||
orgId: org.org.orgId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const { data: orgUsers = [], isLoading: isLoadingOrgUsers } = useQuery(
|
|
||||||
orgQueries.users({
|
|
||||||
orgId: org.org.orgId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery(
|
|
||||||
orgQueries.identityProviders({
|
|
||||||
orgId: org.org.orgId,
|
|
||||||
useOrgOnlyIdp: env.app.identityProviderMode === "org"
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const pageLoading =
|
|
||||||
isLoadingOrgRoles ||
|
|
||||||
isLoadingOrgUsers ||
|
|
||||||
isLoadingResourceRoles ||
|
|
||||||
isLoadingResourceUsers ||
|
|
||||||
isLoadingWhiteList ||
|
|
||||||
isLoadingOrgIdps ||
|
|
||||||
isLoadingPolicies;
|
|
||||||
|
|
||||||
const allRoles = useMemo(() => {
|
|
||||||
return orgRoles
|
|
||||||
.map((role) => ({
|
|
||||||
id: role.roleId.toString(),
|
|
||||||
text: role.name
|
|
||||||
}))
|
|
||||||
.filter((role) => role.text !== "Admin");
|
|
||||||
}, [orgRoles]);
|
|
||||||
|
|
||||||
const allUsers = useMemo(() => {
|
|
||||||
return orgUsers.map((user) => ({
|
|
||||||
id: user.id.toString(),
|
|
||||||
text: `${getUserDisplayName({
|
|
||||||
email: user.email,
|
|
||||||
username: user.username
|
|
||||||
})}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
|
|
||||||
}));
|
|
||||||
}, [orgUsers]);
|
|
||||||
|
|
||||||
const allIdps = useMemo(() => {
|
|
||||||
if (build === "saas") {
|
|
||||||
if (isPaidUser(tierMatrix.orgOidc)) {
|
|
||||||
return orgIdps.map((idp) => ({
|
|
||||||
id: idp.idpId,
|
|
||||||
text: idp.name
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return orgIdps.map((idp) => ({
|
|
||||||
id: idp.idpId,
|
|
||||||
text: idp.name
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}, [orgIdps]);
|
|
||||||
|
|
||||||
const [activeRolesTagIndex, setActiveRolesTagIndex] = useState<
|
|
||||||
number | null
|
|
||||||
>(null);
|
|
||||||
const [activeUsersTagIndex, setActiveUsersTagIndex] = useState<
|
|
||||||
number | null
|
|
||||||
>(null);
|
|
||||||
|
|
||||||
const [ssoEnabled, setSsoEnabled] = useState(resource.sso ?? false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSsoEnabled(resource.sso ?? false);
|
|
||||||
}, [resource.sso]);
|
|
||||||
|
|
||||||
const [selectedIdpId, setSelectedIdpId] = useState<number | null>(
|
|
||||||
resource.skipToIdpId || null
|
|
||||||
);
|
|
||||||
|
|
||||||
const [loadingRemoveResourcePassword, setLoadingRemoveResourcePassword] =
|
|
||||||
useState(false);
|
|
||||||
const [loadingRemoveResourcePincode, setLoadingRemoveResourcePincode] =
|
|
||||||
useState(false);
|
|
||||||
const [
|
const [
|
||||||
loadingRemoveResourceHeaderAuth,
|
loadingRemoveResourceHeaderAuth,
|
||||||
setLoadingRemoveResourceHeaderAuth
|
setLoadingRemoveResourceHeaderAuth
|
||||||
@@ -223,209 +114,6 @@ export default function ResourceAuthenticationPage() {
|
|||||||
const [isSetPincodeOpen, setIsSetPincodeOpen] = useState(false);
|
const [isSetPincodeOpen, setIsSetPincodeOpen] = useState(false);
|
||||||
const [isSetHeaderAuthOpen, setIsSetHeaderAuthOpen] = useState(false);
|
const [isSetHeaderAuthOpen, setIsSetHeaderAuthOpen] = useState(false);
|
||||||
|
|
||||||
const usersRolesForm = useForm({
|
|
||||||
resolver: zodResolver(UsersRolesFormSchema),
|
|
||||||
defaultValues: { roles: [], users: [] }
|
|
||||||
});
|
|
||||||
|
|
||||||
const whitelistForm = useForm({
|
|
||||||
resolver: zodResolver(whitelistSchema),
|
|
||||||
defaultValues: { emails: [] }
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasInitializedRef = useRef(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (pageLoading || hasInitializedRef.current) return;
|
|
||||||
|
|
||||||
usersRolesForm.setValue(
|
|
||||||
"roles",
|
|
||||||
resourceRoles
|
|
||||||
.map((i) => ({
|
|
||||||
id: i.roleId.toString(),
|
|
||||||
text: i.name
|
|
||||||
}))
|
|
||||||
.filter((role) => role.text !== "Admin")
|
|
||||||
);
|
|
||||||
usersRolesForm.setValue(
|
|
||||||
"users",
|
|
||||||
resourceUsers.map((i) => ({
|
|
||||||
id: i.userId.toString(),
|
|
||||||
text: `${getUserDisplayName({
|
|
||||||
email: i.email,
|
|
||||||
username: i.username
|
|
||||||
})}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}`
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
whitelistForm.setValue(
|
|
||||||
"emails",
|
|
||||||
whitelist.map((w) => ({
|
|
||||||
id: w.email,
|
|
||||||
text: w.email
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
hasInitializedRef.current = true;
|
|
||||||
}, [pageLoading, resourceRoles, resourceUsers, whitelist, orgIdps]);
|
|
||||||
|
|
||||||
const [, submitUserRolesForm, loadingSaveUsersRoles] = useActionState(
|
|
||||||
onSubmitUsersRoles,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
async function onSubmitUsersRoles() {
|
|
||||||
const isValid = usersRolesForm.trigger();
|
|
||||||
if (!isValid) return;
|
|
||||||
|
|
||||||
const data = usersRolesForm.getValues();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const jobs = [
|
|
||||||
api.post(`/resource/${resource.resourceId}/roles`, {
|
|
||||||
roleIds: data.roles.map((i) => parseInt(i.id))
|
|
||||||
}),
|
|
||||||
api.post(`/resource/${resource.resourceId}/users`, {
|
|
||||||
userIds: data.users.map((i) => i.id)
|
|
||||||
}),
|
|
||||||
api.post(`/resource/${resource.resourceId}`, {
|
|
||||||
sso: ssoEnabled,
|
|
||||||
skipToIdpId: selectedIdpId
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
await Promise.all(jobs);
|
|
||||||
|
|
||||||
updateResource({
|
|
||||||
sso: ssoEnabled,
|
|
||||||
skipToIdpId: selectedIdpId
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAuthInfo({
|
|
||||||
sso: ssoEnabled
|
|
||||||
});
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: t("resourceAuthSettingsSave"),
|
|
||||||
description: t("resourceAuthSettingsSaveDescription")
|
|
||||||
});
|
|
||||||
// invalidate resource queries
|
|
||||||
await queryClient.invalidateQueries(
|
|
||||||
resourceQueries.resourceUsers({
|
|
||||||
resourceId: resource.resourceId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
await queryClient.invalidateQueries(
|
|
||||||
resourceQueries.resourceRoles({
|
|
||||||
resourceId: resource.resourceId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
router.refresh();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: t("resourceErrorUsersRolesSave"),
|
|
||||||
description: formatAxiosError(
|
|
||||||
e,
|
|
||||||
t("resourceErrorUsersRolesSaveDescription")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeResourcePassword() {
|
|
||||||
setLoadingRemoveResourcePassword(true);
|
|
||||||
|
|
||||||
api.post(`/resource/${resource.resourceId}/password`, {
|
|
||||||
password: null
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
toast({
|
|
||||||
title: t("resourcePasswordRemove"),
|
|
||||||
description: t("resourcePasswordRemoveDescription")
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAuthInfo({
|
|
||||||
password: false
|
|
||||||
});
|
|
||||||
router.refresh();
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: t("resourceErrorPasswordRemove"),
|
|
||||||
description: formatAxiosError(
|
|
||||||
e,
|
|
||||||
t("resourceErrorPasswordRemoveDescription")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => setLoadingRemoveResourcePassword(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeResourcePincode() {
|
|
||||||
setLoadingRemoveResourcePincode(true);
|
|
||||||
|
|
||||||
api.post(`/resource/${resource.resourceId}/pincode`, {
|
|
||||||
pincode: null
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
toast({
|
|
||||||
title: t("resourcePincodeRemove"),
|
|
||||||
description: t("resourcePincodeRemoveDescription")
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAuthInfo({
|
|
||||||
pincode: false
|
|
||||||
});
|
|
||||||
router.refresh();
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: t("resourceErrorPincodeRemove"),
|
|
||||||
description: formatAxiosError(
|
|
||||||
e,
|
|
||||||
t("resourceErrorPincodeRemoveDescription")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => setLoadingRemoveResourcePincode(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeResourceHeaderAuth() {
|
|
||||||
setLoadingRemoveResourceHeaderAuth(true);
|
|
||||||
|
|
||||||
api.post(`/resource/${resource.resourceId}/header-auth`, {
|
|
||||||
user: null,
|
|
||||||
password: null,
|
|
||||||
extendedCompatibility: null
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
toast({
|
|
||||||
title: t("resourceHeaderAuthRemove"),
|
|
||||||
description: t("resourceHeaderAuthRemoveDescription")
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAuthInfo({
|
|
||||||
headerAuth: false
|
|
||||||
});
|
|
||||||
router.refresh();
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: t("resourceErrorHeaderAuthRemove"),
|
|
||||||
description: formatAxiosError(
|
|
||||||
e,
|
|
||||||
t("resourceErrorHeaderAuthRemoveDescription")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => setLoadingRemoveResourceHeaderAuth(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourcePolicyTypes: Array<ResourcePolicyType> = [
|
const resourcePolicyTypes: Array<ResourcePolicyType> = [
|
||||||
{
|
{
|
||||||
id: "inline",
|
id: "inline",
|
||||||
@@ -439,8 +127,17 @@ export default function ResourceAuthenticationPage() {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const [selectedResourceType, setSelectedResourceType] =
|
const form = useForm({
|
||||||
useState<ResourcePolicyType["id"]>("inline");
|
resolver: zodResolver(resourceTypeSchema),
|
||||||
|
defaultValues: {
|
||||||
|
type: "inline"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedResourceType = useWatch({
|
||||||
|
control: form.control,
|
||||||
|
name: "type"
|
||||||
|
});
|
||||||
|
|
||||||
if (pageLoading) {
|
if (pageLoading) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@@ -503,13 +200,9 @@ export default function ResourceAuthenticationPage() {
|
|||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<StrategySelect
|
<StrategySelect
|
||||||
options={resourcePolicyTypes}
|
options={resourcePolicyTypes}
|
||||||
defaultValue="inline"
|
value={selectedResourceType}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
// baseForm.setValue(
|
form.setValue("type", value);
|
||||||
// "http",
|
|
||||||
// value === "http"
|
|
||||||
// );
|
|
||||||
// // Update method default when switching resource type
|
|
||||||
}}
|
}}
|
||||||
cols={2}
|
cols={2}
|
||||||
/>
|
/>
|
||||||
@@ -524,223 +217,10 @@ export default function ResourceAuthenticationPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
{/* <ResourcePolicyContext value={policies?.defaultPolicy}>
|
<ResourcePolicyProvider policy={defaultPolicy}>
|
||||||
|
<EditPolicyForm hidePolicyNameForm />
|
||||||
</ResourcePolicyContext> */}
|
</ResourcePolicyProvider>
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type OneTimePasswordFormSectionProps = Pick<
|
|
||||||
ResourceContextType,
|
|
||||||
"resource" | "updateResource"
|
|
||||||
> & {
|
|
||||||
whitelist: Array<{ email: string }>;
|
|
||||||
isLoadingWhiteList: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
function OneTimePasswordFormSection({
|
|
||||||
resource,
|
|
||||||
updateResource,
|
|
||||||
whitelist,
|
|
||||||
isLoadingWhiteList
|
|
||||||
}: OneTimePasswordFormSectionProps) {
|
|
||||||
const { env } = useEnvContext();
|
|
||||||
const [whitelistEnabled, setWhitelistEnabled] = useState(
|
|
||||||
resource.emailWhitelistEnabled ?? false
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setWhitelistEnabled(resource.emailWhitelistEnabled);
|
|
||||||
}, [resource.emailWhitelistEnabled]);
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const [loadingSaveWhitelist, startTransition] = useTransition();
|
|
||||||
const whitelistForm = useForm({
|
|
||||||
resolver: zodResolver(whitelistSchema),
|
|
||||||
defaultValues: { emails: [] }
|
|
||||||
});
|
|
||||||
const api = createApiClient({ env });
|
|
||||||
const router = useRouter();
|
|
||||||
const t = useTranslations();
|
|
||||||
|
|
||||||
const [activeEmailTagIndex, setActiveEmailTagIndex] = useState<
|
|
||||||
number | null
|
|
||||||
>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isLoadingWhiteList) return;
|
|
||||||
|
|
||||||
whitelistForm.setValue(
|
|
||||||
"emails",
|
|
||||||
whitelist.map((w) => ({
|
|
||||||
id: w.email,
|
|
||||||
text: w.email
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}, [isLoadingWhiteList, whitelist, whitelistForm]);
|
|
||||||
|
|
||||||
async function saveWhitelist() {
|
|
||||||
try {
|
|
||||||
await api.post(`/resource/${resource.resourceId}`, {
|
|
||||||
emailWhitelistEnabled: whitelistEnabled
|
|
||||||
});
|
|
||||||
|
|
||||||
if (whitelistEnabled) {
|
|
||||||
await api.post(`/resource/${resource.resourceId}/whitelist`, {
|
|
||||||
emails: whitelistForm.getValues().emails.map((i) => i.text)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateResource({
|
|
||||||
emailWhitelistEnabled: whitelistEnabled
|
|
||||||
});
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: t("resourceWhitelistSave"),
|
|
||||||
description: t("resourceWhitelistSaveDescription")
|
|
||||||
});
|
|
||||||
router.refresh();
|
|
||||||
await queryClient.invalidateQueries(
|
|
||||||
resourceQueries.resourceWhitelist({
|
|
||||||
resourceId: resource.resourceId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: t("resourceErrorWhitelistSave"),
|
|
||||||
description: formatAxiosError(
|
|
||||||
e,
|
|
||||||
t("resourceErrorWhitelistSaveDescription")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsSection>
|
|
||||||
<SettingsSectionHeader>
|
|
||||||
<SettingsSectionTitle>
|
|
||||||
{t("otpEmailTitle")}
|
|
||||||
</SettingsSectionTitle>
|
|
||||||
<SettingsSectionDescription>
|
|
||||||
{t("otpEmailTitleDescription")}
|
|
||||||
</SettingsSectionDescription>
|
|
||||||
</SettingsSectionHeader>
|
|
||||||
<SettingsSectionBody>
|
|
||||||
<SettingsSectionForm>
|
|
||||||
{!env.email.emailEnabled && (
|
|
||||||
<Alert variant="neutral" className="mb-4">
|
|
||||||
<InfoIcon className="h-4 w-4" />
|
|
||||||
<AlertTitle className="font-semibold">
|
|
||||||
{t("otpEmailSmtpRequired")}
|
|
||||||
</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
{t("otpEmailSmtpRequiredDescription")}
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
<SwitchInput
|
|
||||||
id="whitelist-toggle"
|
|
||||||
label={t("otpEmailWhitelist")}
|
|
||||||
checked={whitelistEnabled}
|
|
||||||
onCheckedChange={setWhitelistEnabled}
|
|
||||||
disabled={!env.email.emailEnabled}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{whitelistEnabled && env.email.emailEnabled && (
|
|
||||||
<Form {...whitelistForm}>
|
|
||||||
<form id="whitelist-form">
|
|
||||||
<FormField
|
|
||||||
control={whitelistForm.control}
|
|
||||||
name="emails"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
<InfoPopup
|
|
||||||
text={t(
|
|
||||||
"otpEmailWhitelistList"
|
|
||||||
)}
|
|
||||||
info={t(
|
|
||||||
"otpEmailWhitelistListDescription"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
{/* @ts-ignore */}
|
|
||||||
<TagInput
|
|
||||||
{...field}
|
|
||||||
activeTagIndex={
|
|
||||||
activeEmailTagIndex
|
|
||||||
}
|
|
||||||
size={"sm"}
|
|
||||||
validateTag={(tag) => {
|
|
||||||
return z
|
|
||||||
.email()
|
|
||||||
.or(
|
|
||||||
z
|
|
||||||
.string()
|
|
||||||
.regex(
|
|
||||||
/^\*@[\w.-]+\.[a-zA-Z]{2,}$/,
|
|
||||||
{
|
|
||||||
message:
|
|
||||||
t(
|
|
||||||
"otpEmailErrorInvalid"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.safeParse(tag)
|
|
||||||
.success;
|
|
||||||
}}
|
|
||||||
setActiveTagIndex={
|
|
||||||
setActiveEmailTagIndex
|
|
||||||
}
|
|
||||||
placeholder={t(
|
|
||||||
"otpEmailEnter"
|
|
||||||
)}
|
|
||||||
tags={
|
|
||||||
whitelistForm.getValues()
|
|
||||||
.emails
|
|
||||||
}
|
|
||||||
setTags={(newRoles) => {
|
|
||||||
whitelistForm.setValue(
|
|
||||||
"emails",
|
|
||||||
newRoles as [
|
|
||||||
Tag,
|
|
||||||
...Tag[]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
allowDuplicates={false}
|
|
||||||
sortTags={true}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
|
||||||
{t("otpEmailEnterDescription")}
|
|
||||||
</FormDescription>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</SettingsSectionForm>
|
|
||||||
</SettingsSectionBody>
|
|
||||||
<SettingsSectionFooter>
|
|
||||||
<Button
|
|
||||||
onClick={() => startTransition(saveWhitelist)}
|
|
||||||
form="whitelist-form"
|
|
||||||
loading={loadingSaveWhitelist}
|
|
||||||
disabled={loadingSaveWhitelist}
|
|
||||||
>
|
|
||||||
{t("otpEmailWhitelistSave")}
|
|
||||||
</Button>
|
|
||||||
</SettingsSectionFooter>
|
|
||||||
</SettingsSection>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -73,7 +73,11 @@ const setHeaderAuthSchema = z.object({
|
|||||||
extendedCompatibility: z.boolean()
|
extendedCompatibility: z.boolean()
|
||||||
});
|
});
|
||||||
|
|
||||||
export function EditPolicyAuthMethodsSectionForm() {
|
export function EditPolicyAuthMethodsSectionForm({
|
||||||
|
readonly
|
||||||
|
}: {
|
||||||
|
readonly?: boolean;
|
||||||
|
}) {
|
||||||
const { policy } = useResourcePolicyContext();
|
const { policy } = useResourcePolicyContext();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -132,6 +136,7 @@ export function EditPolicyAuthMethodsSectionForm() {
|
|||||||
const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
|
if (readonly) return;
|
||||||
const isValid = await form.trigger();
|
const isValid = await form.trigger();
|
||||||
|
|
||||||
if (!isValid) return;
|
if (!isValid) return;
|
||||||
@@ -237,14 +242,16 @@ export function EditPolicyAuthMethodsSectionForm() {
|
|||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<Button
|
{!readonly && (
|
||||||
type="button"
|
<Button
|
||||||
variant="outline"
|
type="button"
|
||||||
onClick={() => setIsExpanded(true)}
|
variant="outline"
|
||||||
>
|
onClick={() => setIsExpanded(true)}
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
>
|
||||||
{t("resourcePolicyAuthMethodAdd")}
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
</Button>
|
{t("resourcePolicyAuthMethodAdd")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
);
|
);
|
||||||
@@ -541,6 +548,7 @@ export function EditPolicyAuthMethodsSectionForm() {
|
|||||||
type="button"
|
type="button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
disabled={readonly}
|
||||||
onClick={
|
onClick={
|
||||||
hasPassword
|
hasPassword
|
||||||
? () =>
|
? () =>
|
||||||
@@ -579,6 +587,7 @@ export function EditPolicyAuthMethodsSectionForm() {
|
|||||||
type="button"
|
type="button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
disabled={readonly}
|
||||||
onClick={
|
onClick={
|
||||||
hasPincode
|
hasPincode
|
||||||
? () =>
|
? () =>
|
||||||
@@ -619,6 +628,7 @@ export function EditPolicyAuthMethodsSectionForm() {
|
|||||||
type="button"
|
type="button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
disabled={readonly}
|
||||||
onClick={
|
onClick={
|
||||||
hasHeaderAuth
|
hasHeaderAuth
|
||||||
? () =>
|
? () =>
|
||||||
@@ -644,7 +654,7 @@ export function EditPolicyAuthMethodsSectionForm() {
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
disabled={isSubmitting}
|
disabled={readonly || isSubmitting}
|
||||||
>
|
>
|
||||||
{t("authMethodsSave")}
|
{t("authMethodsSave")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -28,9 +28,13 @@ import { EditPolicyRulesSectionForm } from "./EditPolicyRulesSectionForm";
|
|||||||
|
|
||||||
export type EditPolicyFormProps = {
|
export type EditPolicyFormProps = {
|
||||||
hidePolicyNameForm?: boolean;
|
hidePolicyNameForm?: boolean;
|
||||||
|
readonly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditPolicyForm({ hidePolicyNameForm }: EditPolicyFormProps) {
|
export function EditPolicyForm({
|
||||||
|
hidePolicyNameForm,
|
||||||
|
readonly
|
||||||
|
}: EditPolicyFormProps) {
|
||||||
const { org } = useOrgContext();
|
const { org } = useOrgContext();
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const { env } = useEnvContext();
|
const { env } = useEnvContext();
|
||||||
@@ -100,23 +104,26 @@ export function EditPolicyForm({ hidePolicyNameForm }: EditPolicyFormProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
{!hidePolicyNameForm && <EditPolicyNameSectionForm />}
|
{!hidePolicyNameForm && <EditPolicyNameSectionForm readonly={readonly} />}
|
||||||
|
|
||||||
<EditPolicyUsersRolesSectionForm
|
<EditPolicyUsersRolesSectionForm
|
||||||
allRoles={allRoles}
|
allRoles={allRoles}
|
||||||
allUsers={allUsers}
|
allUsers={allUsers}
|
||||||
allIdps={allIdps}
|
allIdps={allIdps}
|
||||||
|
readonly={readonly}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EditPolicyAuthMethodsSectionForm />
|
<EditPolicyAuthMethodsSectionForm readonly={readonly} />
|
||||||
|
|
||||||
<EditPolicyOtpEmailSectionForm
|
<EditPolicyOtpEmailSectionForm
|
||||||
emailEnabled={env.email.emailEnabled}
|
emailEnabled={env.email.emailEnabled}
|
||||||
|
readonly={readonly}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EditPolicyRulesSectionForm
|
<EditPolicyRulesSectionForm
|
||||||
isMaxmindAvailable={isMaxmindAvailable}
|
isMaxmindAvailable={isMaxmindAvailable}
|
||||||
isMaxmindAsnAvailable={isMaxmindASNAvailable}
|
isMaxmindAsnAvailable={isMaxmindASNAvailable}
|
||||||
|
readonly={readonly}
|
||||||
/>
|
/>
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import { useForm } from "react-hook-form";
|
|||||||
|
|
||||||
// ─── PolicyNameSection ──────────────────────────────────────────────────
|
// ─── PolicyNameSection ──────────────────────────────────────────────────
|
||||||
|
|
||||||
export function EditPolicyNameSectionForm() {
|
export function EditPolicyNameSectionForm({ readonly }: { readonly?: boolean }) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -61,6 +61,7 @@ export function EditPolicyNameSectionForm() {
|
|||||||
const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
|
if (readonly) return;
|
||||||
const isValid = await form.trigger();
|
const isValid = await form.trigger();
|
||||||
|
|
||||||
if (!isValid) return;
|
if (!isValid) return;
|
||||||
@@ -125,6 +126,7 @@ export function EditPolicyNameSectionForm() {
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
|
disabled={readonly}
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"resourcePolicyNamePlaceholder"
|
"resourcePolicyNamePlaceholder"
|
||||||
)}
|
)}
|
||||||
@@ -141,7 +143,7 @@ export function EditPolicyNameSectionForm() {
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
disabled={isSubmitting}
|
disabled={readonly || isSubmitting}
|
||||||
>
|
>
|
||||||
{t("saveSettings")}
|
{t("saveSettings")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -46,10 +46,12 @@ import { useResourcePolicyContext } from "@app/providers/ResourcePolicyProvider"
|
|||||||
|
|
||||||
type PolicyOtpEmailSectionProps = {
|
type PolicyOtpEmailSectionProps = {
|
||||||
emailEnabled: boolean;
|
emailEnabled: boolean;
|
||||||
|
readonly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditPolicyOtpEmailSectionForm({
|
export function EditPolicyOtpEmailSectionForm({
|
||||||
emailEnabled
|
emailEnabled,
|
||||||
|
readonly
|
||||||
}: PolicyOtpEmailSectionProps) {
|
}: PolicyOtpEmailSectionProps) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
@@ -87,6 +89,7 @@ export function EditPolicyOtpEmailSectionForm({
|
|||||||
const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
|
if (readonly) return;
|
||||||
const isValid = await form.trigger();
|
const isValid = await form.trigger();
|
||||||
|
|
||||||
if (!isValid) return;
|
if (!isValid) return;
|
||||||
@@ -141,14 +144,16 @@ export function EditPolicyOtpEmailSectionForm({
|
|||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<Button
|
{!readonly && (
|
||||||
type="button"
|
<Button
|
||||||
variant="outline"
|
type="button"
|
||||||
onClick={() => setIsExpanded(true)}
|
variant="outline"
|
||||||
>
|
onClick={() => setIsExpanded(true)}
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
>
|
||||||
{t("resourcePolicyOtpEmailAdd")}
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
</Button>
|
{t("resourcePolicyOtpEmailAdd")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
);
|
);
|
||||||
@@ -186,7 +191,7 @@ export function EditPolicyOtpEmailSectionForm({
|
|||||||
onCheckedChange={(val) => {
|
onCheckedChange={(val) => {
|
||||||
form.setValue("emailWhitelistEnabled", val);
|
form.setValue("emailWhitelistEnabled", val);
|
||||||
}}
|
}}
|
||||||
disabled={!emailEnabled}
|
disabled={readonly || !emailEnabled}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{whitelistEnabled && emailEnabled && (
|
{whitelistEnabled && emailEnabled && (
|
||||||
@@ -268,7 +273,9 @@ export function EditPolicyOtpEmailSectionForm({
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
disabled={isSubmitting || !emailEnabled}
|
disabled={
|
||||||
|
readonly || isSubmitting || !emailEnabled
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t("otpEmailWhitelistSave")}
|
{t("otpEmailWhitelistSave")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -108,11 +108,13 @@ type LocalRule = {
|
|||||||
type PolicyRulesSectionProps = {
|
type PolicyRulesSectionProps = {
|
||||||
isMaxmindAvailable: boolean;
|
isMaxmindAvailable: boolean;
|
||||||
isMaxmindAsnAvailable: boolean;
|
isMaxmindAsnAvailable: boolean;
|
||||||
|
readonly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditPolicyRulesSectionForm({
|
export function EditPolicyRulesSectionForm({
|
||||||
isMaxmindAvailable,
|
isMaxmindAvailable,
|
||||||
isMaxmindAsnAvailable
|
isMaxmindAsnAvailable,
|
||||||
|
readonly
|
||||||
}: PolicyRulesSectionProps) {
|
}: PolicyRulesSectionProps) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
@@ -331,6 +333,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
defaultValue={row.original.priority}
|
defaultValue={row.original.priority}
|
||||||
className="w-[75px]"
|
className="w-[75px]"
|
||||||
type="number"
|
type="number"
|
||||||
|
disabled={readonly}
|
||||||
onClick={(e) => e.currentTarget.focus()}
|
onClick={(e) => e.currentTarget.focus()}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
const parsed = z.coerce
|
const parsed = z.coerce
|
||||||
@@ -361,6 +364,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Select
|
<Select
|
||||||
defaultValue={row.original.action}
|
defaultValue={row.original.action}
|
||||||
|
disabled={readonly}
|
||||||
onValueChange={(value: "ACCEPT" | "DROP" | "PASS") =>
|
onValueChange={(value: "ACCEPT" | "DROP" | "PASS") =>
|
||||||
updateRule(row.original.ruleId, { action: value })
|
updateRule(row.original.ruleId, { action: value })
|
||||||
}
|
}
|
||||||
@@ -390,6 +394,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Select
|
<Select
|
||||||
defaultValue={row.original.match}
|
defaultValue={row.original.match}
|
||||||
|
disabled={readonly}
|
||||||
onValueChange={(
|
onValueChange={(
|
||||||
value: "CIDR" | "IP" | "PATH" | "COUNTRY" | "ASN"
|
value: "CIDR" | "IP" | "PATH" | "COUNTRY" | "ASN"
|
||||||
) =>
|
) =>
|
||||||
@@ -439,6 +444,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
|
disabled={readonly}
|
||||||
className="min-w-50 justify-between"
|
className="min-w-50 justify-between"
|
||||||
>
|
>
|
||||||
{row.original.value
|
{row.original.value
|
||||||
@@ -494,6 +500,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
|
disabled={readonly}
|
||||||
className="min-w-50 justify-between"
|
className="min-w-50 justify-between"
|
||||||
>
|
>
|
||||||
{row.original.value
|
{row.original.value
|
||||||
@@ -579,6 +586,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
<Input
|
<Input
|
||||||
defaultValue={row.original.value}
|
defaultValue={row.original.value}
|
||||||
className="min-w-50"
|
className="min-w-50"
|
||||||
|
disabled={readonly}
|
||||||
onBlur={(e) =>
|
onBlur={(e) =>
|
||||||
updateRule(row.original.ruleId, {
|
updateRule(row.original.ruleId, {
|
||||||
value: e.target.value
|
value: e.target.value
|
||||||
@@ -593,6 +601,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Switch
|
<Switch
|
||||||
defaultChecked={row.original.enabled}
|
defaultChecked={row.original.enabled}
|
||||||
|
disabled={readonly}
|
||||||
onCheckedChange={(val) =>
|
onCheckedChange={(val) =>
|
||||||
updateRule(row.original.ruleId, { enabled: val })
|
updateRule(row.original.ruleId, { enabled: val })
|
||||||
}
|
}
|
||||||
@@ -606,6 +615,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
disabled={readonly}
|
||||||
onClick={() => removeRule(row.original.ruleId)}
|
onClick={() => removeRule(row.original.ruleId)}
|
||||||
>
|
>
|
||||||
{t("delete")}
|
{t("delete")}
|
||||||
@@ -621,7 +631,8 @@ export function EditPolicyRulesSectionForm({
|
|||||||
isMaxmindAvailable,
|
isMaxmindAvailable,
|
||||||
isMaxmindAsnAvailable,
|
isMaxmindAsnAvailable,
|
||||||
updateRule,
|
updateRule,
|
||||||
removeRule
|
removeRule,
|
||||||
|
readonly
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -638,6 +649,8 @@ export function EditPolicyRulesSectionForm({
|
|||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
async function saveRules() {
|
async function saveRules() {
|
||||||
|
if (readonly) return;
|
||||||
|
|
||||||
const isValid = form.trigger();
|
const isValid = form.trigger();
|
||||||
if (!isValid) return;
|
if (!isValid) return;
|
||||||
|
|
||||||
@@ -688,14 +701,16 @@ export function EditPolicyRulesSectionForm({
|
|||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<Button
|
{!readonly && (
|
||||||
type="button"
|
<Button
|
||||||
variant="outline"
|
type="button"
|
||||||
onClick={() => setIsExpanded(true)}
|
variant="outline"
|
||||||
>
|
onClick={() => setIsExpanded(true)}
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
>
|
||||||
{t("resourcePolicyRulesAdd")}
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
</Button>
|
{t("resourcePolicyRulesAdd")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
);
|
);
|
||||||
@@ -721,6 +736,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
onCheckedChange={(val) => {
|
onCheckedChange={(val) => {
|
||||||
form.setValue("applyRules", val);
|
form.setValue("applyRules", val);
|
||||||
}}
|
}}
|
||||||
|
disabled={readonly}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -741,6 +757,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Select
|
<Select
|
||||||
value={field.value}
|
value={field.value}
|
||||||
|
disabled={readonly || !rulesEnabled}
|
||||||
onValueChange={
|
onValueChange={
|
||||||
field.onChange
|
field.onChange
|
||||||
}
|
}
|
||||||
@@ -776,6 +793,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Select
|
<Select
|
||||||
value={field.value}
|
value={field.value}
|
||||||
|
disabled={readonly || !rulesEnabled}
|
||||||
onValueChange={
|
onValueChange={
|
||||||
field.onChange
|
field.onChange
|
||||||
}
|
}
|
||||||
@@ -842,6 +860,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
|
disabled={readonly || !rulesEnabled}
|
||||||
aria-expanded={
|
aria-expanded={
|
||||||
openAddRuleCountrySelect
|
openAddRuleCountrySelect
|
||||||
}
|
}
|
||||||
@@ -931,6 +950,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
|
disabled={readonly || !rulesEnabled}
|
||||||
aria-expanded={
|
aria-expanded={
|
||||||
openAddRuleAsnSelect
|
openAddRuleAsnSelect
|
||||||
}
|
}
|
||||||
@@ -1043,7 +1063,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
) : (
|
) : (
|
||||||
<Input {...field} />
|
<Input {...field} disabled={readonly || !rulesEnabled} />
|
||||||
)}
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -1053,7 +1073,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={!rulesEnabled}
|
disabled={readonly || !rulesEnabled}
|
||||||
>
|
>
|
||||||
{t("ruleSubmit")}
|
{t("ruleSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1134,7 +1154,7 @@ export function EditPolicyRulesSectionForm({
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => startTransition(() => saveRules())}
|
onClick={() => startTransition(() => saveRules())}
|
||||||
loading={isPending}
|
loading={isPending}
|
||||||
disabled={isPending}
|
disabled={readonly || isPending}
|
||||||
>
|
>
|
||||||
{t("rulesSave")}
|
{t("rulesSave")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -53,12 +53,14 @@ type PolicyUsersRolesSectionProps = {
|
|||||||
allRoles: { id: string; text: string }[];
|
allRoles: { id: string; text: string }[];
|
||||||
allUsers: { id: string; text: string }[];
|
allUsers: { id: string; text: string }[];
|
||||||
allIdps: { id: number; text: string }[];
|
allIdps: { id: number; text: string }[];
|
||||||
|
readonly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditPolicyUsersRolesSectionForm({
|
export function EditPolicyUsersRolesSectionForm({
|
||||||
allRoles,
|
allRoles,
|
||||||
allUsers,
|
allUsers,
|
||||||
allIdps
|
allIdps,
|
||||||
|
readonly
|
||||||
}: PolicyUsersRolesSectionProps) {
|
}: PolicyUsersRolesSectionProps) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
@@ -106,6 +108,8 @@ export function EditPolicyUsersRolesSectionForm({
|
|||||||
const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
|
if (readonly) return;
|
||||||
|
|
||||||
const isValid = await form.trigger();
|
const isValid = await form.trigger();
|
||||||
|
|
||||||
if (!isValid) return;
|
if (!isValid) return;
|
||||||
@@ -172,6 +176,7 @@ export function EditPolicyUsersRolesSectionForm({
|
|||||||
console.log(`form.setValue("sso", ${val})`);
|
console.log(`form.setValue("sso", ${val})`);
|
||||||
form.setValue("sso", val);
|
form.setValue("sso", val);
|
||||||
}}
|
}}
|
||||||
|
disabled={readonly}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{ssoEnabled && (
|
{ssoEnabled && (
|
||||||
@@ -221,6 +226,7 @@ export function EditPolicyUsersRolesSectionForm({
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
sortTags={true}
|
sortTags={true}
|
||||||
|
disabled={readonly}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -277,6 +283,7 @@ export function EditPolicyUsersRolesSectionForm({
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
sortTags={true}
|
sortTags={true}
|
||||||
|
disabled={readonly}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -292,6 +299,7 @@ export function EditPolicyUsersRolesSectionForm({
|
|||||||
{t("defaultIdentityProvider")}
|
{t("defaultIdentityProvider")}
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<Select
|
||||||
|
disabled={readonly}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
if (value === "none") {
|
if (value === "none") {
|
||||||
form.setValue(
|
form.setValue(
|
||||||
@@ -347,7 +355,7 @@ export function EditPolicyUsersRolesSectionForm({
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
disabled={isSubmitting}
|
disabled={readonly || isSubmitting}
|
||||||
>
|
>
|
||||||
{t("resourceUsersRolesSubmit")}
|
{t("resourceUsersRolesSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { ListClientsResponse } from "@server/routers/client";
|
|||||||
import type { ListDomainsResponse } from "@server/routers/domain";
|
import type { ListDomainsResponse } from "@server/routers/domain";
|
||||||
import type {
|
import type {
|
||||||
GetResourceWhitelistResponse,
|
GetResourceWhitelistResponse,
|
||||||
GetResourcePoliciesResponse,
|
GetDefaultResourcePolicyResponse,
|
||||||
ListResourceNamesResponse,
|
ListResourceNamesResponse,
|
||||||
ListResourcesResponse,
|
ListResourcesResponse,
|
||||||
ListResourceRolesResponse,
|
ListResourceRolesResponse,
|
||||||
@@ -323,13 +323,13 @@ export const resourceQueries = {
|
|||||||
return res.data.data.whitelist;
|
return res.data.data.whitelist;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
policies: ({ resourceId }: { resourceId: number }) =>
|
defaultPolicy: ({ resourceId }: { resourceId: number }) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: ["RESOURCES", resourceId, "POLICIES"] as const,
|
queryKey: ["RESOURCES", resourceId, "POLICIES"] as const,
|
||||||
queryFn: async ({ signal, meta }) => {
|
queryFn: async ({ signal, meta }) => {
|
||||||
const res = await meta!.api.get<
|
const res = await meta!.api.get<
|
||||||
AxiosResponse<GetResourcePoliciesResponse>
|
AxiosResponse<GetDefaultResourcePolicyResponse>
|
||||||
>(`/resource/${resourceId}/policies`, { signal });
|
>(`/resource/${resourceId}/default-policy`, { signal });
|
||||||
|
|
||||||
return res.data.data;
|
return res.data.data;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user