/
t3
/
s3-server
Обзор
Документация
Войти
/
t3
/
s3-server
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
internal/api/presign_handler.go
118 строк
3 KB
Ivan Shibkikh
auth phase 4
25 июл 2026, 23:19
25 июл 2026, 23:19
02d1c69
Код
Авторство
О чём код?
package api import ( "encoding/json" "log/slog" "net/http" "net/url" "strings" "time" "gitverse.ru/t3/s3-server/internal/auth/sigv4" "gitverse.ru/t3/s3-server/internal/config" ) // PresignRequest is the JSON body for POST /presign. type PresignRequest struct { Method string `json:"method"` Bucket string `json:"bucket"` Key string `json:"key"` ExpiresIn int `json:"expires_in"` } // PresignResponse is the JSON body returned by POST /presign. type PresignResponse struct { URL string `json:"url"` Method string `json:"method"` ExpiresIn int `json:"expires_in"` } const maxPresignTTL = 15 * time.Minute // PresignHandler issues a presigned URL signed with the S3 service credentials. func PresignHandler(cfg config.ServiceCredentials, logger *slog.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req PresignRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSONError(w, http.StatusBadRequest, "invalid JSON body") return } method := strings.ToUpper(req.Method) switch method { case http.MethodGet, http.MethodPut, http.MethodHead, http.MethodDelete: default: writeJSONError(w, http.StatusBadRequest, "unsupported method") return } claims := ServiceClaimsFromContext(r.Context()) required := methodToPermission(method) if claims == nil || !hasPermission(claims.Permissions, required) { writeJSONError(w, http.StatusForbidden, "missing required permission: "+required) return } if !validateBucket(req.Bucket) { writeJSONError(w, http.StatusBadRequest, "invalid bucket name") return } if req.Key == "" { writeJSONError(w, http.StatusBadRequest, "key is required") return } expires := time.Duration(req.ExpiresIn) * time.Second if expires <= 0 { expires = 15 * time.Minute } if expires > maxPresignTTL { expires = maxPresignTTL } scheme := "http" if r.TLS != nil { scheme = "https" } base := &url.URL{ Scheme: scheme, Host: r.Host, Path: "/" + req.Bucket + "/" + req.Key, } signedURL, err := sigv4.SignPresigned(base, method, cfg.AccessKey, cfg.SecretKey, cfg.Region, "s3", expires) if err != nil { logger.Error("presign failed", "error", err, "request_id", RequestIDFromContext(r.Context())) writeJSONError(w, http.StatusInternalServerError, "failed to sign URL") return } resp := PresignResponse{ URL: signedURL, Method: method, ExpiresIn: int(expires.Seconds()), } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(resp) } } func methodToPermission(method string) string { switch method { case http.MethodGet, http.MethodHead: return "s3:GetObject" case http.MethodPut: return "s3:PutObject" case http.MethodDelete: return "s3:DeleteObject" default: return "s3:*" } } func writeJSONError(w http.ResponseWriter, status int, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(map[string]string{"error": message}) }