Skip to content

Commit

Permalink
Add option to proxy S3 dowmloads #75
Browse files Browse the repository at this point in the history
  • Loading branch information
Forceu committed May 24, 2024
1 parent ac12cca commit fe3616f
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 19 deletions.
34 changes: 21 additions & 13 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,27 @@ Available environment variables
All values that are described in :ref:`cloudstorage` can be passed as environment variables as well. No values are persistent; therefore, they need to be set on every start.

+-----------------------+-------------------------+-----------------------------+
| Name | Action | Example |
+=======================+=========================+=============================+
| GOKAPI_AWS_BUCKET | Sets the bucket name | gokapi |
+-----------------------+-------------------------+-----------------------------+
| GOKAPI_AWS_REGION | Sets the region name | eu-central-000 |
+-----------------------+-------------------------+-----------------------------+
| GOKAPI_AWS_KEY | Sets the API key | 123456789 |
+-----------------------+-------------------------+-----------------------------+
| GOKAPI_AWS_KEY_SECRET | Sets the API key secret | abcdefg123 |
+-----------------------+-------------------------+-----------------------------+
| GOKAPI_AWS_ENDPOINT | Sets the endpoint | eu-central-000.provider.com |
+-----------------------+-------------------------+-----------------------------+
+---------------------------+-----------------------------------------+-----------------------------+
| Name | Action | Example |
+===========================+=========================================+=============================+
| GOKAPI_AWS_BUCKET | Sets the bucket name | gokapi |
+---------------------------+-----------------------------------------+-----------------------------+
| GOKAPI_AWS_REGION | Sets the region name | eu-central-000 |
+---------------------------+-----------------------------------------+-----------------------------+
| GOKAPI_AWS_KEY | Sets the API key | 123456789 |
+---------------------------+-----------------------------------------+-----------------------------+
| GOKAPI_AWS_KEY_SECRET | Sets the API key secret | abcdefg123 |
+---------------------------+-----------------------------------------+-----------------------------+
| GOKAPI_AWS_ENDPOINT | Sets the endpoint | eu-central-000.provider.com |
+---------------------------+-----------------------------------------+-----------------------------+
| GOKAPI_AWS_PROXY_DOWNLOAD | If true, users will not be redirected | true (default:false) |
| | | |
| | to a pre-signed S3 URL for downloading. | |
| | | |
| | Instead, Gokapi will download the file | |
| | | |
| | and proxy it to the user | |
+---------------------------+-----------------------------------------+-----------------------------+


.. _api:
Expand Down
1 change: 1 addition & 0 deletions internal/environment/Environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Environment struct {
AwsKeyId string `env:"AWS_KEY"`
AwsKeySecret string `env:"AWS_KEY_SECRET"`
AwsEndpoint string `env:"AWS_ENDPOINT"`
AwsProxyDownload bool `env:"AWS_PROXY_DOWNLOAD" envDefault:"false"`
DatabaseName string `env:"DB_NAME" envDefault:"gokapi.sqlite"`
DisableCorsCheck bool `env:"DISABLE_CORS_CHECK" envDefault:"false"`
LogToStdout bool `env:"LOG_STDOUT" envDefault:"false"`
Expand Down
11 changes: 6 additions & 5 deletions internal/models/AwsConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package models

// AwsConfig contains all configuration values / credentials for AWS cloud storage
type AwsConfig struct {
Bucket string `yaml:"Bucket"`
Region string `yaml:"Region"`
KeyId string `yaml:"KeyId"`
KeySecret string `yaml:"KeySecret"`
Endpoint string `yaml:"Endpoint"`
Bucket string `yaml:"Bucket"`
Region string `yaml:"Region"`
KeyId string `yaml:"KeyId"`
KeySecret string `yaml:"KeySecret"`
Endpoint string `yaml:"Endpoint"`
ProxyDownload bool `yaml:"ProxyDownload"`
}

// IsAllProvided returns true if all required variables have been set for using AWS S3 / Backblaze
Expand Down
2 changes: 1 addition & 1 deletion internal/storage/FileServing.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ func ServeFile(file models.File, w http.ResponseWriter, r *http.Request, forceDo
// We are not setting a download complete status as there is no reliable way to
// confirm that the file has been completely downloaded. It expires automatically after 24 hours.
downloadstatus.SetDownload(file)
err := aws.RedirectToDownload(w, r, file, forceDownload)
err := aws.ServeFile(w, r, file, forceDownload)
helper.Check(err)
return
}
Expand Down
39 changes: 39 additions & 0 deletions internal/storage/filesystem/s3filesystem/aws/Aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ func Download(writer io.WriterAt, file models.File) (int64, error) {
return size, nil
}

// ServeFile either redirects the user to a pre-signed download url (default) or downloads the file and serves it as a proxy (if request
func ServeFile(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error {
if !awsConfig.ProxyDownload {
return RedirectToDownload(w, r, file, forceDownload)
}
return ProxyDownload(w, file, forceDownload)
}

// RedirectToDownload creates a presigned link that is valid for 15 seconds and redirects the
// client to this url
func RedirectToDownload(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error {
Expand Down Expand Up @@ -147,6 +155,37 @@ func RedirectToDownload(w http.ResponseWriter, r *http.Request, file models.File
return nil
}

// ProxyDownload streams the file from S3 as a proxy
func ProxyDownload(w http.ResponseWriter, file models.File, forceDownload bool) error {
sess := createSession()
s3svc := s3.New(sess)

contentDisposition := "inline; filename=\"" + file.Name + "\""
if forceDownload {
contentDisposition = "Attachment; filename=\"" + file.Name + "\""
}

req, _ := s3svc.GetObjectRequest(&s3.GetObjectInput{
Bucket: aws.String(file.AwsBucket),
Key: aws.String(file.SHA1),
ResponseContentDisposition: aws.String(contentDisposition),
ResponseCacheControl: aws.String("no-store"),
ResponseContentType: aws.String(file.ContentType),
})

url, err := req.Presign(15 * time.Second)
if err != nil {
return err
}
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
_, _ = io.Copy(w, resp.Body)
return nil
}

func getTimeoutContext() (context.Context, context.CancelFunc) {
ctx := context.Background()
rContext, rCancel := context.WithTimeout(ctx, 5*time.Second)
Expand Down
5 changes: 5 additions & 0 deletions internal/storage/filesystem/s3filesystem/aws/Aws_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ func isUploaded(file models.File) bool {
return false
}

// ServeFile either redirects the user to a pre-signed download url (default) or downloads the file and serves it as a proxy (if request
func ServeFile(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error {
return RedirectToDownload(w, r, file, forceDownload)
}

// RedirectToDownload creates a presigned link that is valid for 15 seconds and redirects the
// client to this url
func RedirectToDownload(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error {
Expand Down
5 changes: 5 additions & 0 deletions internal/storage/filesystem/s3filesystem/aws/Aws_slim.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ func RedirectToDownload(w http.ResponseWriter, r *http.Request, file models.File
return errors.New(errorString)
}

// ServeFile either redirects the user to a pre-signed download url (default) or downloads the file and serves it as a proxy (if request
func ServeFile(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error {
return errors.New(errorString)
}

// FileExists returns true if the object is stored in S3
func FileExists(file models.File) (bool, int64, error) {
return true, 0, errors.New(errorString)
Expand Down

0 comments on commit fe3616f

Please sign in to comment.