mirror of
https://github.com/khairul169/garage-webui.git
synced 2025-11-30 04:51:04 +07:00
feat: must have a 'owner' access key before configuring CORS
This commit is contained in:
parent
9344247d19
commit
48211dd50e
@ -33,7 +33,7 @@ func (b *Browse) GetObjects(w http.ResponseWriter, r *http.Request) {
|
||||
limit = 100
|
||||
}
|
||||
|
||||
client, err := getS3Client(bucket)
|
||||
client, err := getS3Client(bucket, nil)
|
||||
if err != nil {
|
||||
utils.ResponseError(w, err)
|
||||
return
|
||||
@ -88,7 +88,7 @@ func (b *Browse) GetOneObject(w http.ResponseWriter, r *http.Request) {
|
||||
thumbnail := queryParams.Get("thumb") == "1"
|
||||
download := queryParams.Get("dl") == "1"
|
||||
|
||||
client, err := getS3Client(bucket)
|
||||
client, err := getS3Client(bucket, nil)
|
||||
if err != nil {
|
||||
utils.ResponseError(w, err)
|
||||
return
|
||||
@ -184,7 +184,7 @@ func (b *Browse) PutObject(w http.ResponseWriter, r *http.Request) {
|
||||
defer file.Close()
|
||||
}
|
||||
|
||||
client, err := getS3Client(bucket)
|
||||
client, err := getS3Client(bucket, nil)
|
||||
if err != nil {
|
||||
utils.ResponseError(w, err)
|
||||
return
|
||||
@ -220,7 +220,7 @@ func (b *Browse) DeleteObject(w http.ResponseWriter, r *http.Request) {
|
||||
recursive := r.URL.Query().Get("recursive") == "true"
|
||||
isDirectory := strings.HasSuffix(key, "/")
|
||||
|
||||
client, err := getS3Client(bucket)
|
||||
client, err := getS3Client(bucket, nil)
|
||||
if err != nil {
|
||||
utils.ResponseError(w, err)
|
||||
return
|
||||
@ -284,10 +284,15 @@ func (b *Browse) DeleteObject(w http.ResponseWriter, r *http.Request) {
|
||||
utils.ResponseSuccess(w, res)
|
||||
}
|
||||
|
||||
func getBucketCredentials(bucket string) (aws.CredentialsProvider, error) {
|
||||
func getBucketCredentials(bucket string, permission *string) (aws.CredentialsProvider, error) {
|
||||
cacheKey := fmt.Sprintf("key:%s", bucket)
|
||||
cacheData := utils.Cache.Get(cacheKey)
|
||||
|
||||
// Some CORS operations with the SDK are only achievable using a "owner" access key
|
||||
if permission != nil {
|
||||
cacheKey = fmt.Sprintf("key:%s:%s", *permission, bucket)
|
||||
}
|
||||
|
||||
cacheData := utils.Cache.Get(cacheKey)
|
||||
if cacheData != nil {
|
||||
return cacheData.(aws.CredentialsProvider), nil
|
||||
}
|
||||
@ -305,7 +310,10 @@ func getBucketCredentials(bucket string) (aws.CredentialsProvider, error) {
|
||||
var key schema.KeyElement
|
||||
|
||||
for _, k := range bucketData.Keys {
|
||||
if !k.Permissions.Read || !k.Permissions.Write {
|
||||
if permission == nil && (!k.Permissions.Read || !k.Permissions.Write){
|
||||
continue
|
||||
}
|
||||
if permission != nil && !k.Permissions.HasPermission(*permission) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -319,14 +327,18 @@ func getBucketCredentials(bucket string) (aws.CredentialsProvider, error) {
|
||||
break
|
||||
}
|
||||
|
||||
if key.AccessKeyID == "" {
|
||||
return nil, fmt.Errorf("a valid access key was not found for this bucket")
|
||||
}
|
||||
|
||||
credential := credentials.NewStaticCredentialsProvider(key.AccessKeyID, key.SecretAccessKey, "")
|
||||
utils.Cache.Set(cacheKey, credential, time.Hour)
|
||||
|
||||
return credential, nil
|
||||
}
|
||||
|
||||
func getS3Client(bucket string) (*s3.Client, error) {
|
||||
creds, err := getBucketCredentials(bucket)
|
||||
func getS3Client(bucket string, permission *string) (*s3.Client, error) {
|
||||
creds, err := getBucketCredentials(bucket, permission)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get credentials for bucket %s: %w", bucket, err)
|
||||
}
|
||||
|
||||
@ -61,20 +61,33 @@ func (b *Buckets) GetAll(w http.ResponseWriter, r *http.Request) {
|
||||
func (b*Buckets) GetCors(w http.ResponseWriter, r *http.Request){
|
||||
bucket := r.PathValue("bucket")
|
||||
|
||||
client, err := getS3Client(bucket)
|
||||
perm := "owner"
|
||||
client, err := getS3Client(bucket, &perm)
|
||||
|
||||
if err != nil {
|
||||
utils.ResponseError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
out, err := client.GetBucketCors(context.Background(), &s3.GetBucketCorsInput{
|
||||
cors, err := client.GetBucketCors(context.Background(), &s3.GetBucketCorsInput{
|
||||
Bucket: aws.String(bucket),
|
||||
})
|
||||
|
||||
res := []schema.BucketCors{}
|
||||
if err != nil {
|
||||
utils.ResponseError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, rule := range out.CORSRules {
|
||||
var defaultCors = []schema.BucketCors{{
|
||||
AllowedOrigins: []string{},
|
||||
AllowedMethods: []string{},
|
||||
AllowedHeaders: []string{},
|
||||
ExposeHeaders: []string{},
|
||||
MaxAgeSeconds: nil,
|
||||
}}
|
||||
|
||||
res := make([]schema.BucketCors, 0, len(cors.CORSRules))
|
||||
for _, rule := range cors.CORSRules {
|
||||
res = append(res, schema.BucketCors{
|
||||
AllowedOrigins: rule.AllowedOrigins,
|
||||
AllowedMethods: rule.AllowedMethods,
|
||||
@ -84,9 +97,8 @@ func (b*Buckets) GetCors(w http.ResponseWriter, r *http.Request){
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.ResponseError(w, err)
|
||||
return
|
||||
if len(res) == 0 {
|
||||
res = defaultCors
|
||||
}
|
||||
|
||||
utils.ResponseSuccess(w, res)
|
||||
@ -95,7 +107,6 @@ func (b*Buckets) GetCors(w http.ResponseWriter, r *http.Request){
|
||||
func (b*Buckets) PutCors(w http.ResponseWriter, r *http.Request){
|
||||
bucket := r.PathValue("bucket")
|
||||
|
||||
|
||||
var body struct {
|
||||
Rules []schema.BucketCors `json:"rules"`
|
||||
}
|
||||
@ -105,7 +116,8 @@ func (b*Buckets) PutCors(w http.ResponseWriter, r *http.Request){
|
||||
return
|
||||
}
|
||||
|
||||
client, err := getS3Client(bucket)
|
||||
perm := "owner"
|
||||
client, err := getS3Client(bucket, &perm)
|
||||
|
||||
if err != nil {
|
||||
utils.ResponseError(w, err)
|
||||
@ -127,7 +139,7 @@ func (b*Buckets) PutCors(w http.ResponseWriter, r *http.Request){
|
||||
ExposeHeaders: utils.NilIfEmpty(rule.ExposeHeaders),
|
||||
MaxAgeSeconds: rule.MaxAgeSeconds,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
res, err := client.PutBucketCors(context.Background(), &s3.PutBucketCorsInput{
|
||||
Bucket: aws.String(bucket),
|
||||
|
||||
@ -60,3 +60,16 @@ type WebsiteConfig struct {
|
||||
IndexDocument string `json:"indexDocument"`
|
||||
ErrorDocument string `json:"errorDocument"`
|
||||
}
|
||||
|
||||
func (p *Permissions) HasPermission(perm string) bool {
|
||||
switch perm {
|
||||
case "read":
|
||||
return p.Read
|
||||
case "write":
|
||||
return p.Write
|
||||
case "owner":
|
||||
return p.Owner
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -117,6 +117,7 @@ export const useBucketCors = (bucketName?: string) => {
|
||||
queryKey: ["bucket_cors", bucketName],
|
||||
queryFn: () => api.get<BucketCors[]>(`/buckets/${bucketName}/cors`),
|
||||
enabled: !!bucketName,
|
||||
retry: false,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -39,7 +39,9 @@ const tabs: Tab[] = [
|
||||
const ManageBucketPage = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading, refetch } = useBucket(id);
|
||||
const { data: cors } = useBucketCors(data?.globalAliases[0]);
|
||||
const { data: cors, isLoading: corsLoading } = useBucketCors(
|
||||
data?.globalAliases[0]
|
||||
);
|
||||
|
||||
const name = data?.globalAliases[0];
|
||||
|
||||
@ -63,7 +65,7 @@ const ManageBucketPage = () => {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{data && cors && (
|
||||
{data && !corsLoading && (
|
||||
<div className="container">
|
||||
<BucketContext.Provider
|
||||
value={{
|
||||
|
||||
@ -19,7 +19,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const AllowKeyDialog = ({ currentKeys }: Props) => {
|
||||
const { bucket } = useBucketContext();
|
||||
const { bucket, bucketName } = useBucketContext();
|
||||
const { dialogRef, isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { data: keys } = useKeys();
|
||||
const form = useForm<AllowKeysSchema>({
|
||||
@ -37,6 +37,7 @@ const AllowKeyDialog = ({ currentKeys }: Props) => {
|
||||
onClose();
|
||||
toast.success("Key allowed!");
|
||||
queryClient.invalidateQueries({ queryKey: ["bucket", bucket.id] });
|
||||
queryClient.invalidateQueries({ queryKey: ["bucket_cors", bucketName] });
|
||||
},
|
||||
onError: handleError,
|
||||
});
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Card, Modal } from "react-daisyui";
|
||||
import { Alert, Card, Modal } from "react-daisyui";
|
||||
import Button from "@/components/ui/button";
|
||||
import { CheckCircle, Plus } from "lucide-react";
|
||||
import { CheckCircle, CircleXIcon, Plus } from "lucide-react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useBucketContext } from "../context";
|
||||
import { bucketCorsSchema, BucketCorsSchema } from "../schema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Path, useFieldArray, useForm, UseFormReturn } from "react-hook-form";
|
||||
import { Path, useForm, UseFormReturn } from "react-hook-form";
|
||||
import Input, { InputField } from "@/components/ui/input";
|
||||
import FormControl from "@/components/ui/form-control";
|
||||
import { useDisclosure } from "@/hooks/useDisclosure";
|
||||
@ -17,7 +17,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
const CorsConfiguration = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { bucketName, cors } = useBucketContext();
|
||||
const { bucketName, cors, bucket } = useBucketContext();
|
||||
|
||||
const { mutate, isPending } = useBucketCorsMutation({
|
||||
onSuccess: () => {
|
||||
@ -31,30 +31,18 @@ const CorsConfiguration = () => {
|
||||
resolver: zodResolver(bucketCorsSchema),
|
||||
defaultValues: {
|
||||
bucketName: bucketName,
|
||||
rules:
|
||||
cors.length > 0
|
||||
? cors
|
||||
: [
|
||||
{
|
||||
allowedHeaders: [],
|
||||
allowedMethods: [],
|
||||
allowedOrigins: [],
|
||||
exposeHeaders: [],
|
||||
maxAgeSeconds: null,
|
||||
},
|
||||
],
|
||||
rules: cors,
|
||||
},
|
||||
});
|
||||
|
||||
const { fields } = useFieldArray({
|
||||
control: form.control,
|
||||
name: "rules",
|
||||
});
|
||||
const fields = form.watch("rules");
|
||||
|
||||
function handleSubmit(data: BucketCorsSchema) {
|
||||
mutate(data);
|
||||
}
|
||||
|
||||
const allowConfiguration = bucket.keys.some((key) => key.permissions.owner);
|
||||
|
||||
return (
|
||||
<Card className="card-body">
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)}>
|
||||
@ -66,15 +54,24 @@ const CorsConfiguration = () => {
|
||||
icon={CheckCircle}
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
disabled={isPending || !allowConfiguration}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
{fields.map((rule, idx) => (
|
||||
{!allowConfiguration && (
|
||||
<Alert status="warning" icon={<CircleXIcon />} className="mt-5">
|
||||
<span>
|
||||
You must configure an access key with owner permissions for this
|
||||
bucket before setting up CORS.
|
||||
</span>
|
||||
</Alert>
|
||||
)}
|
||||
{allowConfiguration &&
|
||||
fields.map((_, idx) => (
|
||||
<div
|
||||
className="grid md:grid-cols-2 xl:grid-cols-5 gap-5 mt-5"
|
||||
key={rule.id}
|
||||
key={idx}
|
||||
>
|
||||
<FormControl
|
||||
form={form}
|
||||
@ -113,7 +110,6 @@ const CorsConfiguration = () => {
|
||||
)}
|
||||
/>
|
||||
<FormControl
|
||||
key={rule.id}
|
||||
form={form}
|
||||
name={`rules.${idx}.exposeHeaders`}
|
||||
title={"Expose Headers"}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user