Read credentials from Redis
parent
ce971a32b8
commit
bf94a2e2bf
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/* types */
|
||||
|
||||
type credential struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
// Region is critical when signing requests.
|
||||
Region string
|
||||
// Endpoint is the base URL of the bucket, including the bucket name (in either the domain or path).
|
||||
//
|
||||
// Example:
|
||||
// https://bucketname.s3.us-west-2.amazonaws.com
|
||||
// http://my-minio.example.com/bucket-name
|
||||
Endpoint string
|
||||
// ACL is an optional canned ACL to set on objects
|
||||
ACL string
|
||||
// Prefix is a string to prepend to object keys
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (cred credential) validate() error {
|
||||
if strings.HasSuffix(cred.Endpoint, "/") {
|
||||
return fmt.Errorf("%w: endpoint should not end with slash", errBadRequest)
|
||||
}
|
||||
if strings.HasPrefix(cred.Prefix, "/") {
|
||||
return fmt.Errorf("%w: prefix should not start with slash", errBadRequest)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
@ -40,6 +39,13 @@ type createMultipartUploadRes struct {
|
|||
}
|
||||
|
||||
func handleCreateMultipartUpload(w http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
cred, err := getCredential(vars["id"])
|
||||
if err != nil {
|
||||
errorResponse(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
r := createMultipartUploadReq{}
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
if err := decoder.Decode(&r); err != nil {
|
||||
|
@ -52,14 +58,6 @@ func handleCreateMultipartUpload(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
cred := credential{
|
||||
AccessKey: os.Getenv("MINIO_ACCESS_KEY"),
|
||||
SecretKey: os.Getenv("MINIO_SECRET_KEY"),
|
||||
Region: os.Getenv("MINIO_REGION_NAME"),
|
||||
Endpoint: os.Getenv("MINIO_ENDPOINT"),
|
||||
Prefix: os.Getenv("PREFIX"),
|
||||
}
|
||||
|
||||
// Derive the object key
|
||||
key := cred.Prefix + r.Filename
|
||||
|
||||
|
@ -82,6 +80,12 @@ type getUploadedPartsRes []part
|
|||
|
||||
func handleGetUploadedParts(w http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
cred, err := getCredential(vars["id"])
|
||||
if err != nil {
|
||||
errorResponse(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
uploadID := vars["uploadID"]
|
||||
key := req.URL.Query().Get("key")
|
||||
|
||||
|
@ -90,14 +94,6 @@ func handleGetUploadedParts(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
cred := credential{
|
||||
AccessKey: os.Getenv("MINIO_ACCESS_KEY"),
|
||||
SecretKey: os.Getenv("MINIO_SECRET_KEY"),
|
||||
Region: os.Getenv("MINIO_REGION_NAME"),
|
||||
Endpoint: os.Getenv("MINIO_ENDPOINT"),
|
||||
Prefix: os.Getenv("PREFIX"),
|
||||
}
|
||||
|
||||
parts := make(getUploadedPartsRes, 0, 0)
|
||||
var nextPartNumberMarker uint32
|
||||
for {
|
||||
|
@ -127,6 +123,12 @@ type signPartUploadRes struct {
|
|||
|
||||
func handleSignPartUpload(w http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
cred, err := getCredential(vars["id"])
|
||||
if err != nil {
|
||||
errorResponse(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
uploadID := vars["uploadID"]
|
||||
key := req.URL.Query().Get("key")
|
||||
partNumber, err := strconv.ParseUint(vars["uploadPart"], 10, 16)
|
||||
|
@ -140,14 +142,6 @@ func handleSignPartUpload(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
cred := credential{
|
||||
AccessKey: os.Getenv("MINIO_ACCESS_KEY"),
|
||||
SecretKey: os.Getenv("MINIO_SECRET_KEY"),
|
||||
Region: os.Getenv("MINIO_REGION_NAME"),
|
||||
Endpoint: os.Getenv("MINIO_ENDPOINT"),
|
||||
Prefix: os.Getenv("PREFIX"),
|
||||
}
|
||||
|
||||
params := make(url.Values)
|
||||
params.Add("partNumber", strconv.FormatUint(partNumber, 10))
|
||||
params.Add("uploadId", uploadID)
|
||||
|
@ -182,6 +176,12 @@ func (r completeMultipartUploadReq) validate() error {
|
|||
|
||||
func handleCompleteMultipartUpload(w http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
cred, err := getCredential(vars["id"])
|
||||
if err != nil {
|
||||
errorResponse(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
uploadID := vars["uploadID"]
|
||||
key := req.URL.Query().Get("key")
|
||||
|
||||
|
@ -202,14 +202,6 @@ func handleCompleteMultipartUpload(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
cred := credential{
|
||||
AccessKey: os.Getenv("MINIO_ACCESS_KEY"),
|
||||
SecretKey: os.Getenv("MINIO_SECRET_KEY"),
|
||||
Region: os.Getenv("MINIO_REGION_NAME"),
|
||||
Endpoint: os.Getenv("MINIO_ENDPOINT"),
|
||||
Prefix: os.Getenv("PREFIX"),
|
||||
}
|
||||
|
||||
result, err := completeMultipartUpload(key, uploadID, r.Parts, cred)
|
||||
if err != nil {
|
||||
errorResponse(w, req, fmt.Errorf("%w: %s", errInternalServerError, err))
|
||||
|
@ -224,6 +216,12 @@ func handleCompleteMultipartUpload(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
func handleAbortMultipartUpload(w http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
cred, err := getCredential(vars["id"])
|
||||
if err != nil {
|
||||
errorResponse(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
uploadID := vars["uploadID"]
|
||||
key := req.URL.Query().Get("key")
|
||||
|
||||
|
@ -232,15 +230,7 @@ func handleAbortMultipartUpload(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
cred := credential{
|
||||
AccessKey: os.Getenv("MINIO_ACCESS_KEY"),
|
||||
SecretKey: os.Getenv("MINIO_SECRET_KEY"),
|
||||
Region: os.Getenv("MINIO_REGION_NAME"),
|
||||
Endpoint: os.Getenv("MINIO_ENDPOINT"),
|
||||
Prefix: os.Getenv("PREFIX"),
|
||||
}
|
||||
|
||||
err := abortMultipartUpload(key, uploadID, cred)
|
||||
err = abortMultipartUpload(key, uploadID, cred)
|
||||
if err != nil {
|
||||
errorResponse(w, req, fmt.Errorf("%w: %s", errInternalServerError, err))
|
||||
return
|
||||
|
|
55
handlers.go
55
handlers.go
|
@ -1,9 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var globalStore store
|
||||
|
@ -16,6 +21,38 @@ func setupHandlers() {
|
|||
}
|
||||
}
|
||||
|
||||
/* credentials */
|
||||
|
||||
func getCredential(id string) (credential, error) {
|
||||
cred := credential{}
|
||||
|
||||
b, err := globalStore.get(id)
|
||||
if err != nil {
|
||||
return cred, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, &cred)
|
||||
if err != nil {
|
||||
return cred, err
|
||||
}
|
||||
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
func setCredential(id string, cred credential, expire time.Duration) error {
|
||||
b, err := json.Marshal(cred)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = globalStore.put(id, b, expire)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/* templates */
|
||||
|
||||
var tmpl = template.Must(template.ParseFS(assets, "web/*.tmpl"))
|
||||
|
@ -23,5 +60,23 @@ var tmpl = template.Must(template.ParseFS(assets, "web/*.tmpl"))
|
|||
/* upload template */
|
||||
|
||||
func handleUpload(w http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
_, err := getCredential(vars["id"])
|
||||
if errors.Is(err, errNotFound) {
|
||||
errorResponseStatus(w, req, err)
|
||||
tmpl.ExecuteTemplate(w, "upload-not-found.tmpl", nil)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
errorResponse(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.ExecuteTemplate(w, "upload.tmpl", nil)
|
||||
}
|
||||
|
||||
/* create template */
|
||||
|
||||
func handleCreate(w http.ResponseWriter, req *http.Request) {
|
||||
tmpl.ExecuteTemplate(w, "create.tmpl", nil)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ var errNotFound = errors.New("not found")
|
|||
var errBadRequest = errors.New("bad request")
|
||||
var errInternalServerError = errors.New("internal server error")
|
||||
|
||||
func errorResponse(w http.ResponseWriter, req *http.Request, err error) {
|
||||
func errorResponseStatus(w http.ResponseWriter, req *http.Request, err error) {
|
||||
errorMessage := err.Error()
|
||||
errorStatus := http.StatusInternalServerError
|
||||
|
||||
|
@ -22,6 +22,11 @@ func errorResponse(w http.ResponseWriter, req *http.Request, err error) {
|
|||
errorStatus = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
log.Printf("%s %s: %s", req.Method, req.URL.Path, errorMessage)
|
||||
log.Printf("%s %s: %s", req.Method, req.RequestURI, errorMessage)
|
||||
w.WriteHeader(errorStatus)
|
||||
}
|
||||
|
||||
func errorResponse(w http.ResponseWriter, req *http.Request, err error) {
|
||||
errorResponseStatus(w, req, err)
|
||||
w.Write([]byte(err.Error()))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func middlewareLogger(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Do stuff here
|
||||
log.Printf("%s: %s", r.Method, r.RequestURI)
|
||||
// Call the next handler, which can be another middleware in the chain, or the final handler.
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
16
main.go
16
main.go
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
@ -20,16 +21,22 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
setupS3()
|
||||
setupHandlers()
|
||||
setupS3()
|
||||
|
||||
router := mux.NewRouter()
|
||||
uploadRouter := router.PathPrefix("/{id}").Subrouter()
|
||||
router.PathPrefix("/assets").Handler(http.FileServer(http.FS(assetsWeb)))
|
||||
router.Use(middlewareLogger)
|
||||
|
||||
uploadRouter.Path("").HandlerFunc(handleUpload)
|
||||
router.Methods(http.MethodGet).PathPrefix("/assets").Handler(http.FileServer(http.FS(assetsWeb)))
|
||||
router.Methods(http.MethodGet).Path("/favicon.ico").Handler(http.FileServer(http.FS(assetsWeb)))
|
||||
router.Methods(http.MethodGet).Path("/").HandlerFunc(handleCreate)
|
||||
uploadRouter := router.PathPrefix("/{id}").Subrouter()
|
||||
|
||||
uploadTemplateRouter := uploadRouter.Path("").Subrouter()
|
||||
s3Router := uploadRouter.PathPrefix("/s3/multipart").Subrouter()
|
||||
|
||||
uploadTemplateRouter.Methods(http.MethodGet).Path("").HandlerFunc(handleUpload)
|
||||
|
||||
s3Router.Methods(http.MethodPost).Path("").HandlerFunc(handleCreateMultipartUpload)
|
||||
s3Router.Methods(http.MethodGet).Path("/{uploadID}").HandlerFunc(handleGetUploadedParts)
|
||||
s3Router.Methods(http.MethodGet).Path("/{uploadID}/{uploadPart}").HandlerFunc(handleSignPartUpload)
|
||||
|
@ -42,6 +49,7 @@ func main() {
|
|||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
log.Printf("listeining on %s", listen)
|
||||
err = server.ListenAndServe()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
26
s3.go
26
s3.go
|
@ -52,32 +52,6 @@ func stripETag(t string) string {
|
|||
return strings.TrimSuffix(strings.TrimPrefix(t, "\""), "\"")
|
||||
}
|
||||
|
||||
/* types */
|
||||
|
||||
type credential struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
// Region is critical when signing requests.
|
||||
Region string
|
||||
// Endpoint is the base URL of the bucket, including the bucket name (in either the domain or path).
|
||||
//
|
||||
// Example:
|
||||
// https://bucketname.s3.us-west-2.amazonaws.com
|
||||
// http://my-minio.example.com/bucket-name
|
||||
Endpoint string
|
||||
// ACL is an optional canned ACL to set on objects
|
||||
ACL string
|
||||
// Prefix is a string to prepend to object keys
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (cred credential) validate() error {
|
||||
if strings.HasSuffix(cred.Endpoint, "/") {
|
||||
return fmt.Errorf("%w: endpoint should not end with slash", errBadRequest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/* initiateMultipartUpload */
|
||||
|
||||
type initiateMultipartUploadResult struct {
|
||||
|
|
12
store.go
12
store.go
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
var errKeyCollision = errors.New("key collision")
|
||||
var errKeyNotFound = fmt.Errorf("key %w", errNotFound)
|
||||
var errInvalidConnectionType = errors.New("invalid connection type")
|
||||
|
||||
type store interface {
|
||||
put(key string, data []byte, expire time.Duration) error
|
||||
|
@ -38,15 +39,14 @@ func newRedisStore(connection string) (*redisStore, error) {
|
|||
var err error
|
||||
if rtype == "simple" {
|
||||
client, err = (radix.PoolConfig{}).New(ctx, "tcp", connectionParts[1])
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
} else if rtype == "cluster" {
|
||||
clusterAddrs := strings.Split(connectionParts[1], ",")
|
||||
client, err = (radix.ClusterConfig{}).New(ctx, clusterAddrs)
|
||||
} else {
|
||||
err = fmt.Errorf("%w: %#v of string %#v", errInvalidConnectionType, rtype, connection)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
return nil, fmt.Errorf("unable to initialize redis store: %w", err)
|
||||
}
|
||||
return &redisStore{client}, nil
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func (s *redisStore) put(key string, data []byte, expire time.Duration) error {
|
|||
defer cancel()
|
||||
|
||||
exists := 0
|
||||
err := s.client.Do(ctx, radix.Cmd(&exists, "EXISTS", key))
|
||||
err := s.client.Do(ctx, radix.Cmd(&exists, "EXISTS", "upl:"+key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ func (s *redisStore) get(key string) ([]byte, error) {
|
|||
defer cancel()
|
||||
|
||||
var data []byte
|
||||
err := s.client.Do(ctx, radix.Cmd(&data, "GET", key))
|
||||
err := s.client.Do(ctx, radix.Cmd(&data, "GET", "upl:"+key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ import filesize from 'filesize';
|
|||
import './log.css';
|
||||
|
||||
class Log {
|
||||
constructor(selector) {
|
||||
constructor(selector, key) {
|
||||
this.key = key;
|
||||
this.target = document.querySelector(selector);
|
||||
this.items = [];
|
||||
|
||||
|
@ -12,12 +13,12 @@ class Log {
|
|||
}
|
||||
|
||||
localStorageLoad() {
|
||||
const loaded = JSON.parse(window.localStorage.getItem("log") || "[]");
|
||||
const loaded = JSON.parse(window.localStorage.getItem("log" + this.key) || "[]");
|
||||
this.items.push(...loaded);
|
||||
}
|
||||
|
||||
localStorageSave() {
|
||||
window.localStorage.setItem("log", JSON.stringify(this.items));
|
||||
window.localStorage.setItem("log" + this.key, JSON.stringify(this.items));
|
||||
}
|
||||
|
||||
static renderItem(item) {
|
||||
|
|
|
@ -25,7 +25,7 @@ uppy.use(AwsS3Multipart, {
|
|||
companionUrl: window.location.pathname,
|
||||
});
|
||||
|
||||
const log = new Log('#log-area');
|
||||
const log = new Log('#log-area', window.location.pathname);
|
||||
|
||||
uppy.on('upload-success', (f, res) => {
|
||||
log.add({
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dropbox Not Found</title>
|
||||
<link rel="stylesheet" href="assets/bundle.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="upload-wrapper">
|
||||
<div class="upload">
|
||||
<h1>Dropbox Not Found</h1>
|
||||
<p>The dropbox you are looking for doesn't exist or has expired.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
Loading…
Reference in New Issue