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 } func (s *Signer) GetPreview(b Bucket, p Preview) (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 }