mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-17 06:24:32 +00:00
🚧 WIP
This commit is contained in:
@@ -766,6 +766,12 @@
|
|||||||
"resourcePincodeSetupTitle": "Set Pincode",
|
"resourcePincodeSetupTitle": "Set Pincode",
|
||||||
"resourcePincodeSetupTitleDescription": "Set a pincode to protect this resource",
|
"resourcePincodeSetupTitleDescription": "Set a pincode to protect this resource",
|
||||||
"resourceRoleDescription": "Admins can always access this resource.",
|
"resourceRoleDescription": "Admins can always access this resource.",
|
||||||
|
"resourcePolicySelectTitle": "Resource Access Policy",
|
||||||
|
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
|
||||||
|
"resourcePolicyInline": "Inline Resource Policy",
|
||||||
|
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
|
||||||
|
"resourcePolicyShared": "Shared Resource Policy",
|
||||||
|
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
|
||||||
"resourceUsersRoles": "Access Controls",
|
"resourceUsersRoles": "Access Controls",
|
||||||
"resourceUsersRolesDescription": "Configure which users and roles can visit this resource",
|
"resourceUsersRolesDescription": "Configure which users and roles can visit this resource",
|
||||||
"resourceUsersRolesSubmit": "Save Access Controls",
|
"resourceUsersRolesSubmit": "Save Access Controls",
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ export enum ActionsEnum {
|
|||||||
setResourcePolicyPincode = "setResourcePolicyPincode",
|
setResourcePolicyPincode = "setResourcePolicyPincode",
|
||||||
setResourcePolicyHeaderAuth = "setResourcePolicyHeaderAuth",
|
setResourcePolicyHeaderAuth = "setResourcePolicyHeaderAuth",
|
||||||
setResourcePolicyWhitelist = "setResourcePolicyWhitelist",
|
setResourcePolicyWhitelist = "setResourcePolicyWhitelist",
|
||||||
setResourcePolicyRules = "setResourcePolicyRules"
|
setResourcePolicyRules = "setResourcePolicyRules",
|
||||||
|
getResourcePolicies = "getResourcePolicies"
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkUserActionPermission(
|
export async function checkUserActionPermission(
|
||||||
|
|||||||
@@ -636,6 +636,13 @@ authenticated.get(
|
|||||||
policy.getResourcePolicy
|
policy.getResourcePolicy
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/resource/:resourceId/policies",
|
||||||
|
verifyResourceAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.getResourcePolicies),
|
||||||
|
resource.getResourcePolicies
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.put(
|
authenticated.put(
|
||||||
"/resource-policy/:resourcePolicyId",
|
"/resource-policy/:resourcePolicyId",
|
||||||
verifyResourcePolicyAccess,
|
verifyResourcePolicyAccess,
|
||||||
|
|||||||
@@ -453,6 +453,13 @@ authenticated.get(
|
|||||||
policy.getResourcePolicy
|
policy.getResourcePolicy
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/resource/:resourceId/policies",
|
||||||
|
verifyApiKeyResourceAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.getResourcePolicies),
|
||||||
|
resource.getResourcePolicies
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/resource/:resourceId",
|
"/resource/:resourceId",
|
||||||
verifyApiKeyResourceAccess,
|
verifyApiKeyResourceAccess,
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ const getResourcePolicySchema = z
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
async function query(params: z.infer<typeof getResourcePolicySchema>) {
|
export async function queryResourcePolicy(
|
||||||
|
params: z.infer<typeof getResourcePolicySchema>
|
||||||
|
) {
|
||||||
const conditions: SQL<unknown>[] = [];
|
const conditions: SQL<unknown>[] = [];
|
||||||
if ("resourcePolicyId" in params) {
|
if ("resourcePolicyId" in params) {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
@@ -158,7 +160,7 @@ async function query(params: z.infer<typeof getResourcePolicySchema>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type GetResourcePolicyResponse = NonNullable<
|
export type GetResourcePolicyResponse = NonNullable<
|
||||||
Awaited<ReturnType<typeof query>>
|
Awaited<ReturnType<typeof queryResourcePolicy>>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
@@ -205,7 +207,7 @@ export async function getResourcePolicy(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const policy = await query(parsedParams.data);
|
const policy = await queryResourcePolicy(parsedParams.data);
|
||||||
|
|
||||||
if (!policy) {
|
if (!policy) {
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
88
server/routers/resource/getResourcePolicies.ts
Normal file
88
server/routers/resource/getResourcePolicies.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { db, resources } from "@server/db";
|
||||||
|
import {
|
||||||
|
queryResourcePolicy,
|
||||||
|
type GetResourcePolicyResponse
|
||||||
|
} from "@server/routers/policy/getResourcePolicy";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import type { NextFunction, Request, Response } from "express";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import z from "zod";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
|
const getResourcePoliciesParamsSchema = z.strictObject({
|
||||||
|
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GetResourcePoliciesResponse = {
|
||||||
|
defaultPolicy: GetResourcePolicyResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.registerPath({
|
||||||
|
method: "get",
|
||||||
|
path: "/resource/{resourceId}/policies",
|
||||||
|
description: "Get the default policy for a resource.",
|
||||||
|
tags: [OpenAPITags.PublicResource, OpenAPITags.Policy],
|
||||||
|
request: {
|
||||||
|
params: getResourcePoliciesParamsSchema
|
||||||
|
},
|
||||||
|
responses: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function getResourcePolicies(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedParams = getResourcePoliciesParamsSchema.safeParse(
|
||||||
|
req.params
|
||||||
|
);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { resourceId } = parsedParams.data;
|
||||||
|
|
||||||
|
const [resource] = await db
|
||||||
|
.select({
|
||||||
|
defaultResourcePolicyId: resources.defaultResourcePolicyId
|
||||||
|
})
|
||||||
|
.from(resources)
|
||||||
|
.where(eq(resources.resourceId, resourceId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!resource) {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPolicy = resource.defaultResourcePolicyId
|
||||||
|
? await queryResourcePolicy({
|
||||||
|
resourcePolicyId: resource.defaultResourcePolicyId
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return response<GetResourcePoliciesResponse>(res, {
|
||||||
|
data: { defaultPolicy },
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Resource policies retrieved successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,3 +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";
|
||||||
|
|||||||
3
solo.yml
Normal file
3
solo.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name: pangolin
|
||||||
|
icon: public/logo/pangolin_profile_picture.png
|
||||||
|
processes: {}
|
||||||
@@ -12,6 +12,10 @@ import {
|
|||||||
SettingsSectionHeader,
|
SettingsSectionHeader,
|
||||||
SettingsSectionTitle
|
SettingsSectionTitle
|
||||||
} from "@app/components/Settings";
|
} from "@app/components/Settings";
|
||||||
|
import {
|
||||||
|
StrategySelect,
|
||||||
|
type StrategyOption
|
||||||
|
} from "@app/components/StrategySelect";
|
||||||
import { SwitchInput } from "@app/components/SwitchInput";
|
import { SwitchInput } from "@app/components/SwitchInput";
|
||||||
import { Tag, TagInput } from "@app/components/tags/tag-input";
|
import { Tag, TagInput } from "@app/components/tags/tag-input";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
||||||
@@ -42,6 +46,7 @@ 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 { 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";
|
||||||
@@ -86,6 +91,8 @@ const whitelistSchema = z.object({
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type ResourcePolicyType = StrategyOption<"inline" | "shared">;
|
||||||
|
|
||||||
export default function ResourceAuthenticationPage() {
|
export default function ResourceAuthenticationPage() {
|
||||||
const { org } = useOrgContext();
|
const { org } = useOrgContext();
|
||||||
const { resource, updateResource, authInfo, updateAuthInfo } =
|
const { resource, updateResource, authInfo, updateAuthInfo } =
|
||||||
@@ -118,6 +125,11 @@ export default function ResourceAuthenticationPage() {
|
|||||||
resourceId: resource.resourceId
|
resourceId: resource.resourceId
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
const { data: policies, isLoading: isLoadingPolicies } = useQuery(
|
||||||
|
resourceQueries.policies({
|
||||||
|
resourceId: resource.resourceId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const { data: orgRoles = [], isLoading: isLoadingOrgRoles } = useQuery(
|
const { data: orgRoles = [], isLoading: isLoadingOrgRoles } = useQuery(
|
||||||
orgQueries.roles({
|
orgQueries.roles({
|
||||||
@@ -142,7 +154,8 @@ export default function ResourceAuthenticationPage() {
|
|||||||
isLoadingResourceRoles ||
|
isLoadingResourceRoles ||
|
||||||
isLoadingResourceUsers ||
|
isLoadingResourceUsers ||
|
||||||
isLoadingWhiteList ||
|
isLoadingWhiteList ||
|
||||||
isLoadingOrgIdps;
|
isLoadingOrgIdps ||
|
||||||
|
isLoadingPolicies;
|
||||||
|
|
||||||
const allRoles = useMemo(() => {
|
const allRoles = useMemo(() => {
|
||||||
return orgRoles
|
return orgRoles
|
||||||
@@ -413,6 +426,22 @@ export default function ResourceAuthenticationPage() {
|
|||||||
.finally(() => setLoadingRemoveResourceHeaderAuth(false));
|
.finally(() => setLoadingRemoveResourceHeaderAuth(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resourcePolicyTypes: Array<ResourcePolicyType> = [
|
||||||
|
{
|
||||||
|
id: "inline",
|
||||||
|
title: t("resourcePolicyInline"),
|
||||||
|
description: t("resourcePolicyInlineDescription")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "shared",
|
||||||
|
title: t("resourcePolicyShared"),
|
||||||
|
description: t("resourcePolicySharedDescription")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const [selectedResourceType, setSelectedResourceType] =
|
||||||
|
useState<ResourcePolicyType["id"]>("inline");
|
||||||
|
|
||||||
if (pageLoading) {
|
if (pageLoading) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@@ -465,324 +494,39 @@ export default function ResourceAuthenticationPage() {
|
|||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
{t("resourceUsersRoles")}
|
{t("resourcePolicySelectTitle")}
|
||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
<SettingsSectionDescription>
|
<SettingsSectionDescription>
|
||||||
{t("resourceUsersRolesDescription")}
|
{t("resourcePolicySelectDescription")}
|
||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<SettingsSectionForm>
|
<StrategySelect
|
||||||
<SwitchInput
|
options={resourcePolicyTypes}
|
||||||
id="sso-toggle"
|
defaultValue="inline"
|
||||||
label={t("ssoUse")}
|
onChange={(value) => {
|
||||||
checked={ssoEnabled}
|
// baseForm.setValue(
|
||||||
onCheckedChange={(val) => setSsoEnabled(val)}
|
// "http",
|
||||||
/>
|
// value === "http"
|
||||||
|
// );
|
||||||
<Form {...usersRolesForm}>
|
// // Update method default when switching resource type
|
||||||
<form
|
}}
|
||||||
action={submitUserRolesForm}
|
cols={2}
|
||||||
id="users-roles-form"
|
/>
|
||||||
className="space-y-4"
|
|
||||||
>
|
|
||||||
{ssoEnabled && (
|
|
||||||
<>
|
|
||||||
<FormField
|
|
||||||
control={usersRolesForm.control}
|
|
||||||
name="roles"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-col items-start">
|
|
||||||
<FormLabel>
|
|
||||||
{t("roles")}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<TagInput
|
|
||||||
{...field}
|
|
||||||
activeTagIndex={
|
|
||||||
activeRolesTagIndex
|
|
||||||
}
|
|
||||||
setActiveTagIndex={
|
|
||||||
setActiveRolesTagIndex
|
|
||||||
}
|
|
||||||
placeholder={t(
|
|
||||||
"accessRoleSelect2"
|
|
||||||
)}
|
|
||||||
size="sm"
|
|
||||||
tags={
|
|
||||||
usersRolesForm.getValues()
|
|
||||||
.roles
|
|
||||||
}
|
|
||||||
setTags={(
|
|
||||||
newRoles
|
|
||||||
) => {
|
|
||||||
usersRolesForm.setValue(
|
|
||||||
"roles",
|
|
||||||
newRoles as [
|
|
||||||
Tag,
|
|
||||||
...Tag[]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
enableAutocomplete={
|
|
||||||
true
|
|
||||||
}
|
|
||||||
autocompleteOptions={
|
|
||||||
allRoles
|
|
||||||
}
|
|
||||||
allowDuplicates={
|
|
||||||
false
|
|
||||||
}
|
|
||||||
restrictTagsToAutocompleteOptions={
|
|
||||||
true
|
|
||||||
}
|
|
||||||
sortTags={true}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
<FormDescription>
|
|
||||||
{t(
|
|
||||||
"resourceRoleDescription"
|
|
||||||
)}
|
|
||||||
</FormDescription>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={usersRolesForm.control}
|
|
||||||
name="users"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-col items-start">
|
|
||||||
<FormLabel>
|
|
||||||
{t("users")}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<TagInput
|
|
||||||
{...field}
|
|
||||||
activeTagIndex={
|
|
||||||
activeUsersTagIndex
|
|
||||||
}
|
|
||||||
setActiveTagIndex={
|
|
||||||
setActiveUsersTagIndex
|
|
||||||
}
|
|
||||||
placeholder={t(
|
|
||||||
"accessUserSelect"
|
|
||||||
)}
|
|
||||||
tags={
|
|
||||||
usersRolesForm.getValues()
|
|
||||||
.users
|
|
||||||
}
|
|
||||||
size="sm"
|
|
||||||
setTags={(
|
|
||||||
newUsers
|
|
||||||
) => {
|
|
||||||
usersRolesForm.setValue(
|
|
||||||
"users",
|
|
||||||
newUsers as [
|
|
||||||
Tag,
|
|
||||||
...Tag[]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
enableAutocomplete={
|
|
||||||
true
|
|
||||||
}
|
|
||||||
autocompleteOptions={
|
|
||||||
allUsers
|
|
||||||
}
|
|
||||||
allowDuplicates={
|
|
||||||
false
|
|
||||||
}
|
|
||||||
restrictTagsToAutocompleteOptions={
|
|
||||||
true
|
|
||||||
}
|
|
||||||
sortTags={true}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{ssoEnabled && allIdps.length > 0 && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium">
|
|
||||||
{t("defaultIdentityProvider")}
|
|
||||||
</label>
|
|
||||||
<Select
|
|
||||||
onValueChange={(value) => {
|
|
||||||
if (value === "none") {
|
|
||||||
setSelectedIdpId(null);
|
|
||||||
} else {
|
|
||||||
setSelectedIdpId(
|
|
||||||
parseInt(value)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
value={
|
|
||||||
selectedIdpId
|
|
||||||
? selectedIdpId.toString()
|
|
||||||
: "none"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full mt-1">
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"selectIdpPlaceholder"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="none">
|
|
||||||
{t("none")}
|
|
||||||
</SelectItem>
|
|
||||||
{allIdps.map((idp) => (
|
|
||||||
<SelectItem
|
|
||||||
key={idp.id}
|
|
||||||
value={idp.id.toString()}
|
|
||||||
>
|
|
||||||
{idp.text}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t(
|
|
||||||
"defaultIdentityProviderDescription"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</SettingsSectionForm>
|
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
<SettingsSectionFooter>
|
<SettingsSectionFooter>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={loadingSaveUsersRoles}
|
disabled
|
||||||
disabled={loadingSaveUsersRoles}
|
form="policies-type-form"
|
||||||
form="users-roles-form"
|
|
||||||
>
|
>
|
||||||
{t("resourceUsersRolesSubmit")}
|
{t("resourceUsersRolesSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
{/* <ResourcePolicyContext value={policies?.defaultPolicy}>
|
||||||
|
|
||||||
<SettingsSection>
|
</ResourcePolicyContext> */}
|
||||||
<SettingsSectionHeader>
|
|
||||||
<SettingsSectionTitle>
|
|
||||||
{t("resourceAuthMethods")}
|
|
||||||
</SettingsSectionTitle>
|
|
||||||
<SettingsSectionDescription>
|
|
||||||
{t("resourceAuthMethodsDescriptions")}
|
|
||||||
</SettingsSectionDescription>
|
|
||||||
</SettingsSectionHeader>
|
|
||||||
<SettingsSectionBody>
|
|
||||||
<SettingsSectionForm>
|
|
||||||
{/* Password Protection */}
|
|
||||||
<div className="flex items-center justify-between border rounded-md p-2 mb-4">
|
|
||||||
<div
|
|
||||||
className={`flex items-center ${!authInfo.password ? "" : "text-green-500"} text-sm space-x-2`}
|
|
||||||
>
|
|
||||||
<Key size="14" />
|
|
||||||
<span>
|
|
||||||
{t("resourcePasswordProtection", {
|
|
||||||
status: authInfo.password
|
|
||||||
? t("enabled")
|
|
||||||
: t("disabled")
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="sm"
|
|
||||||
onClick={
|
|
||||||
authInfo.password
|
|
||||||
? removeResourcePassword
|
|
||||||
: () => setIsSetPasswordOpen(true)
|
|
||||||
}
|
|
||||||
loading={loadingRemoveResourcePassword}
|
|
||||||
>
|
|
||||||
{authInfo.password
|
|
||||||
? t("passwordRemove")
|
|
||||||
: t("passwordAdd")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* PIN Code Protection */}
|
|
||||||
<div className="flex items-center justify-between border rounded-md p-2">
|
|
||||||
<div
|
|
||||||
className={`flex items-center ${!authInfo.pincode ? "" : "text-green-500"} space-x-2 text-sm`}
|
|
||||||
>
|
|
||||||
<Binary size="14" />
|
|
||||||
<span>
|
|
||||||
{t("resourcePincodeProtection", {
|
|
||||||
status: authInfo.pincode
|
|
||||||
? t("enabled")
|
|
||||||
: t("disabled")
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="sm"
|
|
||||||
onClick={
|
|
||||||
authInfo.pincode
|
|
||||||
? removeResourcePincode
|
|
||||||
: () => setIsSetPincodeOpen(true)
|
|
||||||
}
|
|
||||||
loading={loadingRemoveResourcePincode}
|
|
||||||
>
|
|
||||||
{authInfo.pincode
|
|
||||||
? t("pincodeRemove")
|
|
||||||
: t("pincodeAdd")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Header Authentication Protection */}
|
|
||||||
<div className="flex items-center justify-between border rounded-md p-2">
|
|
||||||
<div
|
|
||||||
className={`flex items-center ${!authInfo.headerAuth ? "" : "text-green-500"} space-x-2 text-sm`}
|
|
||||||
>
|
|
||||||
<Bot size="14" />
|
|
||||||
<span>
|
|
||||||
{authInfo.headerAuth
|
|
||||||
? t(
|
|
||||||
"resourceHeaderAuthProtectionEnabled"
|
|
||||||
)
|
|
||||||
: t(
|
|
||||||
"resourceHeaderAuthProtectionDisabled"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="sm"
|
|
||||||
onClick={
|
|
||||||
authInfo.headerAuth
|
|
||||||
? removeResourceHeaderAuth
|
|
||||||
: () => setIsSetHeaderAuthOpen(true)
|
|
||||||
}
|
|
||||||
loading={loadingRemoveResourceHeaderAuth}
|
|
||||||
>
|
|
||||||
{authInfo.headerAuth
|
|
||||||
? t("headerAuthRemove")
|
|
||||||
: t("headerAuthAdd")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</SettingsSectionForm>
|
|
||||||
</SettingsSectionBody>
|
|
||||||
</SettingsSection>
|
|
||||||
|
|
||||||
<OneTimePasswordFormSection
|
|
||||||
resource={resource}
|
|
||||||
updateResource={updateResource}
|
|
||||||
whitelist={whitelist}
|
|
||||||
isLoadingWhiteList={isLoadingWhiteList}
|
|
||||||
/>
|
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,11 +25,15 @@ export function StrategySelect<TValue extends string>({
|
|||||||
value: controlledValue,
|
value: controlledValue,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
onChange,
|
onChange,
|
||||||
cols
|
cols = 1
|
||||||
}: StrategySelectProps<TValue>) {
|
}: StrategySelectProps<TValue>) {
|
||||||
const [uncontrolledSelected, setUncontrolledSelected] = useState<TValue | undefined>(defaultValue);
|
const [uncontrolledSelected, setUncontrolledSelected] = useState<
|
||||||
|
TValue | undefined
|
||||||
|
>(defaultValue);
|
||||||
const isControlled = controlledValue !== undefined;
|
const isControlled = controlledValue !== undefined;
|
||||||
const selected = isControlled ? (controlledValue ?? undefined) : uncontrolledSelected;
|
const selected = isControlled
|
||||||
|
? (controlledValue ?? undefined)
|
||||||
|
: uncontrolledSelected;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
@@ -39,7 +43,11 @@ export function StrategySelect<TValue extends string>({
|
|||||||
if (!isControlled) setUncontrolledSelected(typedValue);
|
if (!isControlled) setUncontrolledSelected(typedValue);
|
||||||
onChange?.(typedValue);
|
onChange?.(typedValue);
|
||||||
}}
|
}}
|
||||||
className={`grid md:grid-cols-${cols ? cols : 1} gap-4`}
|
style={{
|
||||||
|
// @ts-expect-error
|
||||||
|
"--cols": `repeat(${cols}, 1fr)`
|
||||||
|
}}
|
||||||
|
className="grid md:grid-cols-(--cols) gap-4"
|
||||||
>
|
>
|
||||||
{options.map((option: StrategyOption<TValue>) => (
|
{options.map((option: StrategyOption<TValue>) => (
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -4,6 +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,
|
||||||
ListResourceNamesResponse,
|
ListResourceNamesResponse,
|
||||||
ListResourcesResponse,
|
ListResourcesResponse,
|
||||||
ListResourceRolesResponse,
|
ListResourceRolesResponse,
|
||||||
@@ -322,6 +323,17 @@ export const resourceQueries = {
|
|||||||
return res.data.data.whitelist;
|
return res.data.data.whitelist;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
policies: ({ resourceId }: { resourceId: number }) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ["RESOURCES", resourceId, "POLICIES"] as const,
|
||||||
|
queryFn: async ({ signal, meta }) => {
|
||||||
|
const res = await meta!.api.get<
|
||||||
|
AxiosResponse<GetResourcePoliciesResponse>
|
||||||
|
>(`/resource/${resourceId}/policies`, { signal });
|
||||||
|
|
||||||
|
return res.data.data;
|
||||||
|
}
|
||||||
|
}),
|
||||||
listNamesPerOrg: (orgId: string) =>
|
listNamesPerOrg: (orgId: string) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: ["RESOURCES_NAMES", orgId] as const,
|
queryKey: ["RESOURCES_NAMES", orgId] as const,
|
||||||
|
|||||||
Reference in New Issue
Block a user