mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-10-28 15:51:23 +00:00
feat: Presigned object URLs (s3 driver)
POST /buckets/:id/object-presigns mints SigV4 query-signed GET/PUT URLs so clients move bytes directly against S3 — large transfers skip the backend entirely. - storage.Presigner optional interface; S3Store implements, local driver returns a clear error (no external surface to sign). - TTL 60s–1h (default 15m); download requires read role + existing object row (no key probing); upload requires write role. - S3_PUBLIC_ENDPOINT rewrites the client-facing host for internal S3_ENDPOINTs — signatures bind the host, URLs are valid only where minted. - CLI: primora objects:presign <bucket> <key> [--upload] [--ttl]. Verified live against cloudserver: GET fetched the object with no auth headers; PUT pushed 1 MB direct to S3; unsigned list → 403; untracked-key download presign → 404. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
a84049b8fc
commit
fd36389328
22 files changed
+446
-7
No files matched your search
@@ -142,6 +142,7 @@ func (h *HTTPHandler) Register(router *gin.Engine) {
|
||||
api.GET("/buckets/:bucketID/objects", h.listObjects)
|
||||
api.POST("/buckets/:bucketID/objects", h.uploadObject)
|
||||
api.POST("/buckets/:bucketID/object-copies", h.copyObject)
|
||||
api.POST("/buckets/:bucketID/object-presigns", h.presignObject)
|
||||
api.GET("/buckets/:bucketID/objects/*objectKey", h.downloadObject)
|
||||
api.PATCH("/buckets/:bucketID/objects/*objectKey", h.updateObject)
|
||||
api.DELETE("/buckets/:bucketID/objects/*objectKey", h.deleteObject)
|
||||
@@ -891,6 +892,39 @@ func (h *HTTPHandler) updateObject(c *gin.Context) {
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
type presignObjectRequest struct {
|
||||
Key string `json:"key" validate:"required"`
|
||||
Op string `json:"op" validate:"omitempty,oneof=download upload"`
|
||||
TTLSeconds int `json:"ttl_seconds" validate:"omitempty,min=60,max=3600"`
|
||||
}
|
||||
|
||||
// presignObject returns a short-lived URL clients call against storage
|
||||
// directly — keeps large transfers off the backend. S3 driver only.
|
||||
func (h *HTTPHandler) presignObject(c *gin.Context) {
|
||||
actor, ok := middleware.RequireActor(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
bucketID, ok := parseUUIDParam(c, "bucketID")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var body presignObjectRequest
|
||||
if !h.bindAndValidate(c, &body) {
|
||||
return
|
||||
}
|
||||
out, err := h.Platform.PresignObject(c.Request.Context(), actor, bucketID, strings.TrimSpace(body.Key), body.Op, body.TTLSeconds)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
apperrors.Abort(c, http.StatusNotFound, "not_found", "bucket or object not found")
|
||||
return
|
||||
}
|
||||
h.handleError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, out)
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) deleteObject(c *gin.Context) {
|
||||
actor, ok := middleware.RequireActor(c)
|
||||
if !ok {
|
||||
|
||||
Reference in new issue
Block a user