1
0
Fork 0
photos/pkg/bucket/signer.go

177 lines
4.2 KiB
Go

package bucket
import (
"errors"
"net"
"net/http"
"net/url"
"os"
"strconv"
"time"
internal_s3utils "git.makerforce.io/photos/photos/internal/s3utils"
"github.com/minio/minio-go/v6/pkg/s3utils"
"github.com/minio/minio-go/v6/pkg/signer"
)
type Signer struct {
accessKey string
secretKey string
regionName string
bucketSecure bool
expirations Expirations
}
type Expirations struct {
// Expiration time for list and read in time.Duration
Read time.Duration
// Expiration time for write in time.Duration
Write time.Duration
}
var ErrorExpirationTooLow = errors.New("expiration time too low")
func NewSigner(accessKey, secretKey, regionName string, bucketSecure bool, expirations Expirations) (*Signer, error) {
if expirations.Read == 0 {
expirations.Read = 30 * time.Minute
}
if expirations.Write == 0 {
expirations.Write = 5 * time.Minute
}
if expirations.Read < time.Second || expirations.Write < time.Second {
return nil, ErrorExpirationTooLow
}
signer := &Signer{
accessKey: accessKey,
secretKey: secretKey,
regionName: regionName,
bucketSecure: bucketSecure,
expirations: expirations,
}
return signer, nil
}
func NewSignerFromEnv() (*Signer, error) {
accessKey := os.Getenv("MINIO_ACCESS_KEY")
secretKey := os.Getenv("MINIO_SECRET_KEY")
regionName := os.Getenv("MINIO_REGION_NAME")
bucketSecure := os.Getenv("BUCKET_SECURE") == "true"
expirationRead, _ := strconv.Atoi(os.Getenv("EXPIRATION_READ"))
expirationWrite, _ := strconv.Atoi(os.Getenv("EXPIRATION_WRITE"))
expirations := Expirations{Read: time.Duration(expirationRead), Write: time.Duration(expirationWrite)}
return NewSigner(accessKey, secretKey, regionName, bucketSecure, expirations)
}
func (s *Signer) baseBucket(b Bucket) url.URL {
url := url.URL{
Scheme: "http",
Host: b.String(),
Path: "",
}
if s.bucketSecure {
url.Scheme = "https"
}
return url
}
func (s *Signer) preSignV4(req http.Request) http.Request {
domain := os.Getenv("MINIO_DOMAIN")
// Validate host
host, _, err := net.SplitHostPort(req.Host)
if err != nil {
host = req.Host
}
originalPath := req.URL.Path
if len(domain) < 1 {
// If we are using domains, rewrite the host into path
req.URL.Path = internal_s3utils.PathFromHost(req.URL.Path, host)
}
// If we are using subdomains, sign the direct URL
signedReq := signer.PreSignV4(
req,
s.accessKey, s.secretKey, "",
"sgp1",
int64(s.expirations.Read/time.Second),
)
signedReq.URL.Path = originalPath
return *signedReq
}
func listBucketParams(prefix, startAfter string, maxKeys int) url.Values {
params := make(url.Values)
params.Set("list-type", "2")
params.Set("metadata", "true")
params.Set("encoding-type", "url")
params.Set("prefix", prefix)
params.Set("delimiter", "")
params.Set("max-keys", strconv.Itoa(maxKeys))
params.Set("start-after", startAfter)
return params
}
func (s *Signer) GetBucketPhotos(b Bucket, startAfter string, maxKeys int) (string, error) {
err := b.Validate()
if err != nil {
return "", err
}
url := s.baseBucket(b)
url.Path += "/"
params := listBucketParams(photoPrefix, startAfter, maxKeys)
url.RawQuery = s3utils.QueryEncode(params)
req, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
return "", err
}
signedReq := s.preSignV4(*req)
return signedReq.URL.String(), nil
}
func (s *Signer) GetPhoto(b Bucket, p Photo) (string, error) {
err := b.Validate()
if err != nil {
return "", err
}
err = p.Validate()
if err != nil {
return "", err
}
url := s.baseBucket(b)
url.Path += p.Path()
req, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
return "", err
}
signedReq := signer.PreSignV4(*req, s.accessKey, s.secretKey, "", "sgp1", int64(s.expirations.Read/time.Second))
return signedReq.URL.String(), nil
}
func (s *Signer) PutPhoto(b Bucket, p Photo) (string, error) {
err := b.Validate()
if err != nil {
return "", err
}
err = p.Validate()
if err != nil {
return "", err
}
url := s.baseBucket(b)
url.Path += p.Path()
req, err := http.NewRequest("PUT", url.String(), nil)
if err != nil {
return "", err
}
signedReq := signer.PreSignV4(*req, s.accessKey, s.secretKey, "", "sgp1", int64(s.expirations.Write/time.Second))
return signedReq.URL.String(), nil
}