Use stable files API URLs for vendor logo fields

CommonThirdParty.logoUrl and TrustCenterReference.logoUrl were
returning expiring S3 presigned URLs, which break if cached or
shared past their TTL.

Replace with stable /api/files/v1/{id} application URLs.
file.Service now generates these via baseurl; a new filesign
package owns presigning for the files/v1 HTTP handler that
does the internal redirect.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-06-03 10:21:02 +02:00
parent 383ea5a2d4
commit 6c072a2f7b
14 changed files with 115 additions and 116 deletions

View File

@@ -17,51 +17,42 @@ package file
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/baseurl"
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/filemanager"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
) )
type Service struct { type Service struct {
pg *pg.Client pg *pg.Client
fileManager *filemanager.Service baseURL *baseurl.BaseURL
} }
func NewService(pgClient *pg.Client, fileManager *filemanager.Service) *Service { func NewService(pgClient *pg.Client, baseURL *baseurl.BaseURL) *Service {
return &Service{ return &Service{
pg: pgClient, pg: pgClient,
fileManager: fileManager, baseURL: baseURL,
} }
} }
func (s *Service) GetPublicFileURL( func (s *Service) GenerateFileURL(ctx context.Context, fileID gid.GID) (string, error) {
ctx context.Context,
fileID gid.GID,
expiresIn time.Duration,
) (string, error) {
file := &coredata.File{} file := &coredata.File{}
err := s.pg.WithConn( err := s.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
ctx,
func(ctx context.Context, conn pg.Querier) error {
if err := file.LoadPublicByID(ctx, conn, fileID); err != nil { if err := file.LoadPublicByID(ctx, conn, fileID); err != nil {
return fmt.Errorf("cannot load public file: %w", err) return fmt.Errorf("cannot load public file: %w", err)
} }
return nil return nil
}, })
)
if err != nil { if err != nil {
return "", err return "", err
} }
presignedURL, err := s.fileManager.GenerateFileUrl(ctx, file, expiresIn) url, err := s.baseURL.AppendPath("/api/files/v1/" + fileID.String()).String()
if err != nil { if err != nil {
return "", fmt.Errorf("cannot generate file URL: %w", err) return "", fmt.Errorf("cannot build file URL: %w", err)
} }
return presignedURL, nil return url, nil
} }

62
pkg/filesign/service.go Normal file
View File

@@ -0,0 +1,62 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package filesign
import (
"context"
"fmt"
"time"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/filemanager"
"go.probo.inc/probo/pkg/gid"
)
type Service struct {
pg *pg.Client
fileManager *filemanager.Service
}
func NewService(pgClient *pg.Client, fileManager *filemanager.Service) *Service {
return &Service{
pg: pgClient,
fileManager: fileManager,
}
}
// GeneratePresignedFileURL returns a short-lived S3 presigned URL for a PUBLIC file.
// Prefer file.Service.GenerateFileURL in most cases — it returns a stable, cacheable
// application URL. Only use this when a direct S3 URL with a controlled TTL is required
// (e.g. the /api/files/v1 HTTP handler that issues the presign-on-redirect).
func (s *Service) GeneratePresignedFileURL(
ctx context.Context,
fileID gid.GID,
expiresIn time.Duration,
) (string, error) {
file := &coredata.File{}
err := s.pg.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
if err := file.LoadPublicByID(ctx, conn, fileID); err != nil {
return fmt.Errorf("cannot load public file: %w", err)
}
return nil
})
if err != nil {
return "", err
}
return s.fileManager.GenerateFileUrl(ctx, file, expiresIn)
}

View File

@@ -27,6 +27,7 @@ import (
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/crypto/cipher" "go.probo.inc/probo/pkg/crypto/cipher"
"go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/esign"
"go.probo.inc/probo/pkg/file"
"go.probo.inc/probo/pkg/filemanager" "go.probo.inc/probo/pkg/filemanager"
"go.probo.inc/probo/pkg/filevalidation" "go.probo.inc/probo/pkg/filevalidation"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
@@ -79,6 +80,7 @@ type (
html2pdfConverter *html2pdf.Converter html2pdfConverter *html2pdf.Converter
acmeService *certmanager.ACMEService acmeService *certmanager.ACMEService
fileManager *filemanager.Service fileManager *filemanager.Service
file *file.Service
logger *log.Logger logger *log.Logger
slack *slack.Service slack *slack.Service
esign *esign.Service esign *esign.Service
@@ -144,6 +146,7 @@ func NewService(
esignService *esign.Service, esignService *esign.Service,
connectorRegistry *connector.ConnectorRegistry, connectorRegistry *connector.ConnectorRegistry,
invitationTokenValidity time.Duration, invitationTokenValidity time.Duration,
fileService *file.Service,
) (*Service, error) { ) (*Service, error) {
if bucket == "" { if bucket == "" {
return nil, fmt.Errorf("bucket is required") return nil, fmt.Errorf("bucket is required")
@@ -163,6 +166,7 @@ func NewService(
html2pdfConverter: html2pdfConverter, html2pdfConverter: html2pdfConverter,
acmeService: acmeService, acmeService: acmeService,
fileManager: fileManagerService, fileManager: fileManagerService,
file: fileService,
logger: logger, logger: logger,
slack: slackService, slack: slackService,
esign: esignService, esign: esignService,

View File

@@ -20,7 +20,6 @@ import (
"fmt" "fmt"
"io" "io"
"mime" "mime"
"net/url"
"path/filepath" "path/filepath"
"time" "time"
@@ -293,49 +292,20 @@ func (s TrustCenterReferenceService) Delete(
} }
func (s TrustCenterReferenceService) GenerateLogoURL( func (s TrustCenterReferenceService) GenerateLogoURL(
ctx context.Context, scope coredata.Scoper, ctx context.Context,
scope coredata.Scoper,
referenceID gid.GID, referenceID gid.GID,
duration time.Duration,
) (string, error) { ) (string, error) {
reference := &coredata.TrustCenterReference{} reference := &coredata.TrustCenterReference{}
file := &coredata.File{}
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error { err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
err := reference.LoadByID(ctx, tx, scope, referenceID) return reference.LoadByID(ctx, tx, scope, referenceID)
if err != nil {
return fmt.Errorf("cannot load trust center reference: %w", err)
}
err = file.LoadByID(ctx, tx, scope, reference.LogoFileID)
if err != nil {
return fmt.Errorf("cannot load logo file: %w", err)
}
return nil
}) })
if err != nil { if err != nil {
return "", nil return "", fmt.Errorf("cannot load trust center reference: %w", err)
} }
presignClient := s3.NewPresignClient(s.svc.s3) return s.svc.file.GenerateFileURL(ctx, reference.LogoFileID)
encodedFilename := url.PathEscape(file.FileName)
contentDisposition := fmt.Sprintf("inline; filename=\"%s\"; filename*=UTF-8''%s",
encodedFilename, encodedFilename)
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: new(s.svc.bucket),
Key: new(file.FileKey),
ResponseCacheControl: new("max-age=3600, public"),
ResponseContentDisposition: new(contentDisposition),
}, func(opts *s3.PresignOptions) {
opts.Expires = duration
})
if err != nil {
return "", fmt.Errorf("cannot presign GetObject request: %w", err)
}
return presignedReq.URL, nil
} }
func (s TrustCenterReferenceService) uploadLogoFile( func (s TrustCenterReferenceService) uploadLogoFile(

View File

@@ -58,6 +58,7 @@ import (
"go.probo.inc/probo/pkg/evidencedescriber" "go.probo.inc/probo/pkg/evidencedescriber"
"go.probo.inc/probo/pkg/file" "go.probo.inc/probo/pkg/file"
"go.probo.inc/probo/pkg/filemanager" "go.probo.inc/probo/pkg/filemanager"
"go.probo.inc/probo/pkg/filesign"
"go.probo.inc/probo/pkg/geoloc" "go.probo.inc/probo/pkg/geoloc"
"go.probo.inc/probo/pkg/html2pdf" "go.probo.inc/probo/pkg/html2pdf"
"go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/iam"
@@ -504,6 +505,9 @@ func (impl *Implm) Run(
cookieBannerService := cookiebanner.NewService(pgClient, impl.cfg.Branding) cookieBannerService := cookiebanner.NewService(pgClient, impl.cfg.Branding)
fileService := file.NewService(pgClient, baseURL)
filesignService := filesign.NewService(pgClient, fileManagerService)
proboService, err := probo.NewService( proboService, err := probo.NewService(
ctx, ctx,
encryptionKey, encryptionKey,
@@ -527,6 +531,7 @@ func (impl *Implm) Run(
esignService, esignService,
defaultConnectorRegistry, defaultConnectorRegistry,
time.Duration(impl.cfg.Auth.InvitationConfirmationTokenValidity)*time.Second, time.Duration(impl.cfg.Auth.InvitationConfirmationTokenValidity)*time.Second,
fileService,
) )
if err != nil { if err != nil {
return fmt.Errorf("cannot create probo service: %w", err) return fmt.Errorf("cannot create probo service: %w", err)
@@ -544,10 +549,9 @@ func (impl *Implm) Run(
fileManagerService, fileManagerService,
l, l,
slackService, slackService,
fileService,
) )
fileService := file.NewService(pgClient, fileManagerService)
accessReviewService := accessreview.NewService( accessReviewService := accessreview.NewService(
pgClient, pgClient,
encryptionKey, encryptionKey,
@@ -564,7 +568,7 @@ func (impl *Implm) Run(
AllowedOrigins: impl.cfg.Api.Cors.AllowedOrigins, AllowedOrigins: impl.cfg.Api.Cors.AllowedOrigins,
ExtraHeaderFields: impl.cfg.Api.ExtraHeaderFields, ExtraHeaderFields: impl.cfg.Api.ExtraHeaderFields,
Probo: proboService, Probo: proboService,
File: fileService, FileSign: filesignService,
IAM: iamService, IAM: iamService,
Trust: trustService, Trust: trustService,
ESign: esignService, ESign: esignService,

View File

@@ -31,7 +31,7 @@ import (
"go.probo.inc/probo/pkg/connector/provider" "go.probo.inc/probo/pkg/connector/provider"
"go.probo.inc/probo/pkg/cookiebanner" "go.probo.inc/probo/pkg/cookiebanner"
"go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/esign"
"go.probo.inc/probo/pkg/file" "go.probo.inc/probo/pkg/filesign"
"go.probo.inc/probo/pkg/geoloc" "go.probo.inc/probo/pkg/geoloc"
"go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/mailman" "go.probo.inc/probo/pkg/mailman"
@@ -55,7 +55,7 @@ type (
BaseURL *baseurl.BaseURL BaseURL *baseurl.BaseURL
AllowedOrigins []string AllowedOrigins []string
Probo *probo.Service Probo *probo.Service
File *file.Service FileSign *filesign.Service
IAM *iam.Service IAM *iam.Service
Trust *trust.Service Trust *trust.Service
ESign *esign.Service ESign *esign.Service
@@ -207,7 +207,7 @@ func NewServer(cfg Config) (*Server, error) {
), ),
filesHandler: files_v1.NewMux( filesHandler: files_v1.NewMux(
cfg.Logger.Named("files.v1"), cfg.Logger.Named("files.v1"),
cfg.File, cfg.FileSign,
), ),
mcpHandler: mcp_v1.NewMux( mcpHandler: mcp_v1.NewMux(
cfg.Logger.Named("mcp.v1"), cfg.Logger.Named("mcp.v1"),

View File

@@ -7,7 +7,6 @@ package console_v1
import ( import (
"context" "context"
"time"
"go.gearno.de/kit/log" "go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/probo"
@@ -26,7 +25,7 @@ func (r *commonThirdPartyResolver) LogoURL(ctx context.Context, obj *types.Commo
return nil, nil return nil, nil
} }
logoURL, err := r.thirdParty.GenerateLogoURL(ctx, *obj.LogoFileID, 1*time.Hour) logoURL, err := r.thirdParty.GenerateLogoURL(ctx, *obj.LogoFileID)
if err != nil { if err != nil {
r.logger.ErrorCtx(ctx, "cannot generate common third party logo URL", log.Error(err)) r.logger.ErrorCtx(ctx, "cannot generate common third party logo URL", log.Error(err))
return nil, gqlutils.Internal(ctx) return nil, gqlutils.Internal(ctx)

View File

@@ -1183,7 +1183,7 @@ func (r *trustCenterReferenceResolver) LogoURL(ctx context.Context, obj *types.T
return "", err return "", err
} }
fileURL, err := r.probo.TrustCenterReferences.GenerateLogoURL(ctx, scope, obj.ID, 1*time.Hour) fileURL, err := r.probo.TrustCenterReferences.GenerateLogoURL(ctx, scope, obj.ID)
if err != nil { if err != nil {
r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err)) r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err))
return "", gqlutils.Internal(ctx) return "", gqlutils.Internal(ctx)

View File

@@ -22,7 +22,7 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"go.gearno.de/kit/log" "go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/file" "go.probo.inc/probo/pkg/filesign"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
) )
@@ -30,10 +30,10 @@ const presignedURLExpiry = 1 * time.Hour
type Handler struct { type Handler struct {
logger *log.Logger logger *log.Logger
fileSvc *file.Service fileSvc *filesign.Service
} }
func NewMux(logger *log.Logger, fileSvc *file.Service) *chi.Mux { func NewMux(logger *log.Logger, fileSvc *filesign.Service) *chi.Mux {
h := &Handler{ h := &Handler{
logger: logger, logger: logger,
fileSvc: fileSvc, fileSvc: fileSvc,
@@ -55,7 +55,7 @@ func (h *Handler) handleGetPublicFile(w http.ResponseWriter, r *http.Request) {
return return
} }
presignedURL, err := h.fileSvc.GetPublicFileURL(r.Context(), fileID, presignedURLExpiry) presignedURL, err := h.fileSvc.GeneratePresignedFileURL(r.Context(), fileID, presignedURLExpiry)
if err != nil { if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) { if errors.Is(err, coredata.ErrResourceNotFound) {
http.NotFound(w, r) http.NotFound(w, r)

View File

@@ -975,7 +975,7 @@ func (r *trustCenterReferenceResolver) LogoURL(ctx context.Context, obj *types.T
scope := coredata.NewScopeFromObjectID(obj.ID) scope := coredata.NewScopeFromObjectID(obj.ID)
trustService := r.trust trustService := r.trust
logoURL, err := trustService.TrustCenterReferences.GenerateLogoURL(ctx, scope, obj.ID, 1*time.Hour) logoURL, err := trustService.TrustCenterReferences.GenerateLogoURL(ctx, scope, obj.ID)
if err != nil { if err != nil {
r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err)) r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err))
return "", gqlutils.Internal(ctx) return "", gqlutils.Internal(ctx)

View File

@@ -30,7 +30,7 @@ import (
"go.probo.inc/probo/pkg/connector/provider" "go.probo.inc/probo/pkg/connector/provider"
"go.probo.inc/probo/pkg/cookiebanner" "go.probo.inc/probo/pkg/cookiebanner"
"go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/esign"
"go.probo.inc/probo/pkg/file" "go.probo.inc/probo/pkg/filesign"
"go.probo.inc/probo/pkg/geoloc" "go.probo.inc/probo/pkg/geoloc"
"go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/iam/oauth2server" "go.probo.inc/probo/pkg/iam/oauth2server"
@@ -54,7 +54,7 @@ type Config struct {
AllowedOrigins []string AllowedOrigins []string
ExtraHeaderFields map[string]string ExtraHeaderFields map[string]string
Probo *probo.Service Probo *probo.Service
File *file.Service FileSign *filesign.Service
IAM *iam.Service IAM *iam.Service
Trust *trust.Service Trust *trust.Service
ESign *esign.Service ESign *esign.Service
@@ -92,7 +92,7 @@ func NewServer(cfg Config) (*Server, error) {
BaseURL: cfg.BaseURL, BaseURL: cfg.BaseURL,
AllowedOrigins: cfg.AllowedOrigins, AllowedOrigins: cfg.AllowedOrigins,
Probo: cfg.Probo, Probo: cfg.Probo,
File: cfg.File, FileSign: cfg.FileSign,
IAM: cfg.IAM, IAM: cfg.IAM,
Trust: cfg.Trust, Trust: cfg.Trust,
ESign: cfg.ESign, ESign: cfg.ESign,

View File

@@ -17,7 +17,6 @@ package thirdparty
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
@@ -46,9 +45,8 @@ func NewService(pgClient *pg.Client, fileSvc *file.Service, vetter Vetter) *Serv
func (s *Service) GenerateLogoURL( func (s *Service) GenerateLogoURL(
ctx context.Context, ctx context.Context,
logoFileID gid.GID, logoFileID gid.GID,
expiresIn time.Duration,
) (*string, error) { ) (*string, error) {
url, err := s.file.GetPublicFileURL(ctx, logoFileID, expiresIn) url, err := s.file.GenerateFileURL(ctx, logoFileID)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot generate logo URL: %w", err) return nil, fmt.Errorf("cannot generate logo URL: %w", err)
} }

View File

@@ -26,6 +26,7 @@ import (
"go.probo.inc/probo/packages/emails" "go.probo.inc/probo/packages/emails"
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/esign"
"go.probo.inc/probo/pkg/file"
"go.probo.inc/probo/pkg/filemanager" "go.probo.inc/probo/pkg/filemanager"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/html2pdf" "go.probo.inc/probo/pkg/html2pdf"
@@ -46,6 +47,7 @@ type (
esign *esign.Service esign *esign.Service
html2pdfConverter *html2pdf.Converter html2pdfConverter *html2pdf.Converter
fileManager *filemanager.Service fileManager *filemanager.Service
file *file.Service
logger *log.Logger logger *log.Logger
slack *slack.Service slack *slack.Service
TrustCenters *TrustCenterService TrustCenters *TrustCenterService
@@ -75,6 +77,7 @@ func NewService(
fileManagerService *filemanager.Service, fileManagerService *filemanager.Service,
logger *log.Logger, logger *log.Logger,
slack *slack.Service, slack *slack.Service,
fileService *file.Service,
) *Service { ) *Service {
svc := &Service{ svc := &Service{
pg: pgClient, pg: pgClient,
@@ -86,6 +89,7 @@ func NewService(
esign: esignSvc, esign: esignSvc,
html2pdfConverter: html2pdfConverter, html2pdfConverter: html2pdfConverter,
fileManager: fileManagerService, fileManager: fileManagerService,
file: fileService,
logger: logger, logger: logger,
slack: slack, slack: slack,
} }

View File

@@ -17,10 +17,7 @@ package trust
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"time"
"github.com/aws/aws-sdk-go-v2/service/s3"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
@@ -58,47 +55,17 @@ func (s TrustCenterReferenceService) GenerateLogoURL(
ctx context.Context, ctx context.Context,
scope coredata.Scoper, scope coredata.Scoper,
referenceID gid.GID, referenceID gid.GID,
duration time.Duration,
) (string, error) { ) (string, error) {
reference := &coredata.TrustCenterReference{} reference := &coredata.TrustCenterReference{}
file := &coredata.File{}
err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error { err := s.svc.pg.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
err := reference.LoadByID(ctx, tx, scope, referenceID) return reference.LoadByID(ctx, tx, scope, referenceID)
if err != nil {
return fmt.Errorf("cannot load trust center reference: %w", err)
}
err = file.LoadByID(ctx, tx, scope, reference.LogoFileID)
if err != nil {
return fmt.Errorf("cannot load logo file: %w", err)
}
return nil
}) })
if err != nil { if err != nil {
return "", nil return "", fmt.Errorf("cannot load trust center reference: %w", err)
} }
presignClient := s3.NewPresignClient(s.svc.s3) return s.svc.file.GenerateFileURL(ctx, reference.LogoFileID)
encodedFilename := url.PathEscape(file.FileName)
contentDisposition := fmt.Sprintf("inline; filename=\"%s\"; filename*=UTF-8''%s",
encodedFilename, encodedFilename)
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: new(s.svc.bucket),
Key: new(file.FileKey),
ResponseCacheControl: new("max-age=3600, public"),
ResponseContentDisposition: new(contentDisposition),
}, func(opts *s3.PresignOptions) {
opts.Expires = duration
})
if err != nil {
return "", fmt.Errorf("cannot presign GetObject request: %w", err)
}
return presignedReq.URL, nil
} }
func (s TrustCenterReferenceService) Get( func (s TrustCenterReferenceService) Get(