mirror of
https://github.com/minio/minio.git
synced 2024-12-25 14:45:54 -05:00
Merge pull request #1136 from harshavardhana/add-proxy
presigned: Fix a bug in presigned request verification.
This commit is contained in:
commit
1c33926239
25
Docker.md
25
Docker.md
@ -9,7 +9,7 @@ sudo apt-get install Docker.io
|
|||||||
### Generating `minio configs` for the first time.
|
### Generating `minio configs` for the first time.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -p 9000:9001 minio/minio:latest
|
docker run -p 9000 minio/minio:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Persist `minio configs`.
|
### Persist `minio configs`.
|
||||||
@ -28,5 +28,26 @@ docker create -v /export --name minio-export minio/my-minio /bin/true
|
|||||||
You can then use the `--volumes-from` flag to mount the `/export` volume in another container.
|
You can then use the `--volumes-from` flag to mount the `/export` volume in another container.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -p 9000:9001 --volumes-from minio-export --name minio1 minio/my-minio
|
docker run -p 9000 --volumes-from minio-export --name minio1 minio/my-minio
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup a sample proxy in front using Caddy.
|
||||||
|
|
||||||
|
Please download [Caddy Server](https://caddyserver.com/download)
|
||||||
|
|
||||||
|
Create a caddy configuration file as below, change the ip addresses for your environment.
|
||||||
|
```bash
|
||||||
|
cat Caddyfile
|
||||||
|
10.0.0.3:2015 {
|
||||||
|
proxy / localhost:9000 {
|
||||||
|
proxy_header Host {host}
|
||||||
|
}
|
||||||
|
tls off
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./caddy
|
||||||
|
Activating privacy features... done.
|
||||||
|
10.0.0.3:2015
|
||||||
```
|
```
|
||||||
|
@ -3,6 +3,5 @@ FROM scratch
|
|||||||
ADD minio.dockerimage /minio
|
ADD minio.dockerimage /minio
|
||||||
ADD export /export
|
ADD export /export
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
EXPOSE 9001
|
|
||||||
ENTRYPOINT ["/minio"]
|
ENTRYPOINT ["/minio"]
|
||||||
CMD ["server", "/export"]
|
CMD ["server", "/export"]
|
||||||
|
@ -16,12 +16,7 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
jwtgo "github.com/dgrijalva/jwt-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
signV4Algorithm = "AWS4-HMAC-SHA256"
|
signV4Algorithm = "AWS4-HMAC-SHA256"
|
||||||
@ -69,15 +64,8 @@ func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Verify JWT authorization header is present.
|
// Verify JWT authorization header is present.
|
||||||
if isRequestJWT(r) {
|
if isRequestJWT(r) {
|
||||||
// Validate Authorization header to be valid.
|
// Validate Authorization header if its valid.
|
||||||
jwt := InitJWT()
|
if !isJWTReqAuthenticated(r) {
|
||||||
token, e := jwtgo.ParseFromRequest(r, func(token *jwtgo.Token) (interface{}, error) {
|
|
||||||
if _, ok := token.Method.(*jwtgo.SigningMethodHMAC); !ok {
|
|
||||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
|
||||||
}
|
|
||||||
return jwt.secretAccessKey, nil
|
|
||||||
})
|
|
||||||
if e != nil || !token.Valid {
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ import (
|
|||||||
// GetBucketLocationHandler - GET Bucket location.
|
// GetBucketLocationHandler - GET Bucket location.
|
||||||
// -------------------------
|
// -------------------------
|
||||||
// This operation returns bucket location.
|
// This operation returns bucket location.
|
||||||
func (api CloudStorageAPI) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ func (api CloudStorageAPI) GetBucketLocationHandler(w http.ResponseWriter, r *ht
|
|||||||
// completed or aborted. This operation returns at most 1,000 multipart
|
// completed or aborted. This operation returns at most 1,000 multipart
|
||||||
// uploads in the response.
|
// uploads in the response.
|
||||||
//
|
//
|
||||||
func (api CloudStorageAPI) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ func (api CloudStorageAPI) ListMultipartUploadsHandler(w http.ResponseWriter, r
|
|||||||
// of the objects in a bucket. You can use the request parameters as selection
|
// of the objects in a bucket. You can use the request parameters as selection
|
||||||
// criteria to return a subset of the objects in a bucket.
|
// criteria to return a subset of the objects in a bucket.
|
||||||
//
|
//
|
||||||
func (api CloudStorageAPI) ListObjectsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) ListObjectsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ func (api CloudStorageAPI) ListObjectsHandler(w http.ResponseWriter, r *http.Req
|
|||||||
// -----------
|
// -----------
|
||||||
// This implementation of the GET operation returns a list of all buckets
|
// This implementation of the GET operation returns a list of all buckets
|
||||||
// owned by the authenticated sender of the request.
|
// owned by the authenticated sender of the request.
|
||||||
func (api CloudStorageAPI) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if isRequestRequiresACLCheck(r) {
|
if isRequestRequiresACLCheck(r) {
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
||||||
return
|
return
|
||||||
@ -215,7 +215,7 @@ func (api CloudStorageAPI) ListBucketsHandler(w http.ResponseWriter, r *http.Req
|
|||||||
// PutBucketHandler - PUT Bucket
|
// PutBucketHandler - PUT Bucket
|
||||||
// ----------
|
// ----------
|
||||||
// This implementation of the PUT operation creates a new bucket for authenticated request
|
// This implementation of the PUT operation creates a new bucket for authenticated request
|
||||||
func (api CloudStorageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
@ -323,7 +323,7 @@ func extractHTTPFormValues(reader *multipart.Reader) (io.Reader, map[string]stri
|
|||||||
// ----------
|
// ----------
|
||||||
// This implementation of the POST operation handles object creation with a specified
|
// This implementation of the POST operation handles object creation with a specified
|
||||||
// signature policy in multipart/form-data
|
// signature policy in multipart/form-data
|
||||||
func (api CloudStorageAPI) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// if body of request is non-nil then check for validity of Content-Length
|
// if body of request is non-nil then check for validity of Content-Length
|
||||||
if r.Body != nil {
|
if r.Body != nil {
|
||||||
/// if Content-Length is unknown/missing, deny the request
|
/// if Content-Length is unknown/missing, deny the request
|
||||||
@ -403,7 +403,7 @@ func (api CloudStorageAPI) PostPolicyBucketHandler(w http.ResponseWriter, r *htt
|
|||||||
// PutBucketACLHandler - PUT Bucket ACL
|
// PutBucketACLHandler - PUT Bucket ACL
|
||||||
// ----------
|
// ----------
|
||||||
// This implementation of the PUT operation modifies the bucketACL for authenticated request
|
// This implementation of the PUT operation modifies the bucketACL for authenticated request
|
||||||
func (api CloudStorageAPI) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
@ -445,7 +445,7 @@ func (api CloudStorageAPI) PutBucketACLHandler(w http.ResponseWriter, r *http.Re
|
|||||||
// of a bucket. One must have permission to access the bucket to
|
// of a bucket. One must have permission to access the bucket to
|
||||||
// know its ``acl``. This operation willl return response of 404
|
// know its ``acl``. This operation willl return response of 404
|
||||||
// if bucket not found and 403 for invalid credentials.
|
// if bucket not found and 403 for invalid credentials.
|
||||||
func (api CloudStorageAPI) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
@ -487,7 +487,7 @@ func (api CloudStorageAPI) GetBucketACLHandler(w http.ResponseWriter, r *http.Re
|
|||||||
// The operation returns a 200 OK if the bucket exists and you
|
// The operation returns a 200 OK if the bucket exists and you
|
||||||
// have permission to access it. Otherwise, the operation might
|
// have permission to access it. Otherwise, the operation might
|
||||||
// return responses such as 404 Not Found and 403 Forbidden.
|
// return responses such as 404 Not Found and 403 Forbidden.
|
||||||
func (api CloudStorageAPI) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
@ -520,7 +520,7 @@ func (api CloudStorageAPI) HeadBucketHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteBucketHandler - Delete bucket
|
// DeleteBucketHandler - Delete bucket
|
||||||
func (api CloudStorageAPI) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
4
jwt.go
4
jwt.go
@ -37,8 +37,8 @@ const (
|
|||||||
tokenExpires time.Duration = 10
|
tokenExpires time.Duration = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitJWT - initialize.
|
// initJWT - initialize.
|
||||||
func InitJWT() *JWT {
|
func initJWT() *JWT {
|
||||||
jwt := &JWT{}
|
jwt := &JWT{}
|
||||||
// Load credentials.
|
// Load credentials.
|
||||||
config, err := loadConfigV2()
|
config, err := loadConfigV2()
|
||||||
|
@ -51,7 +51,7 @@ func setResponseHeaders(w http.ResponseWriter, reqParams url.Values) {
|
|||||||
// ----------
|
// ----------
|
||||||
// This implementation of the GET operation retrieves object. To use GET,
|
// This implementation of the GET operation retrieves object. To use GET,
|
||||||
// you must have READ access to the object.
|
// you must have READ access to the object.
|
||||||
func (api CloudStorageAPI) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var object, bucket string
|
var object, bucket string
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket = vars["bucket"]
|
bucket = vars["bucket"]
|
||||||
@ -109,7 +109,7 @@ func (api CloudStorageAPI) GetObjectHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
// HeadObjectHandler - HEAD Object
|
// HeadObjectHandler - HEAD Object
|
||||||
// -----------
|
// -----------
|
||||||
// The HEAD operation retrieves metadata from an object without returning the object itself.
|
// The HEAD operation retrieves metadata from an object without returning the object itself.
|
||||||
func (api CloudStorageAPI) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var object, bucket string
|
var object, bucket string
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket = vars["bucket"]
|
bucket = vars["bucket"]
|
||||||
@ -150,7 +150,7 @@ func (api CloudStorageAPI) HeadObjectHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
// PutObjectHandler - PUT Object
|
// PutObjectHandler - PUT Object
|
||||||
// ----------
|
// ----------
|
||||||
// This implementation of the PUT operation adds an object to a bucket.
|
// This implementation of the PUT operation adds an object to a bucket.
|
||||||
func (api CloudStorageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var object, bucket string
|
var object, bucket string
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket = vars["bucket"]
|
bucket = vars["bucket"]
|
||||||
@ -231,10 +231,10 @@ func (api CloudStorageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
writeSuccessResponse(w, nil)
|
writeSuccessResponse(w, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Multipart CloudStorageAPI
|
/// Multipart storageAPI
|
||||||
|
|
||||||
// NewMultipartUploadHandler - New multipart upload
|
// NewMultipartUploadHandler - New multipart upload
|
||||||
func (api CloudStorageAPI) NewMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) NewMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var object, bucket string
|
var object, bucket string
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket = vars["bucket"]
|
bucket = vars["bucket"]
|
||||||
@ -281,7 +281,7 @@ func (api CloudStorageAPI) NewMultipartUploadHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PutObjectPartHandler - Upload part
|
// PutObjectPartHandler - Upload part
|
||||||
func (api CloudStorageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
@ -373,7 +373,7 @@ func (api CloudStorageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AbortMultipartUploadHandler - Abort multipart upload
|
// AbortMultipartUploadHandler - Abort multipart upload
|
||||||
func (api CloudStorageAPI) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
@ -414,7 +414,7 @@ func (api CloudStorageAPI) AbortMultipartUploadHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListObjectPartsHandler - List object parts
|
// ListObjectPartsHandler - List object parts
|
||||||
func (api CloudStorageAPI) ListObjectPartsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) ListObjectPartsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
@ -472,7 +472,7 @@ func (api CloudStorageAPI) ListObjectPartsHandler(w http.ResponseWriter, r *http
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CompleteMultipartUploadHandler - Complete multipart upload
|
// CompleteMultipartUploadHandler - Complete multipart upload
|
||||||
func (api CloudStorageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
@ -542,10 +542,10 @@ func (api CloudStorageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter,
|
|||||||
writeSuccessResponse(w, encodedSuccessResponse)
|
writeSuccessResponse(w, encodedSuccessResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete CloudStorageAPI
|
/// Delete storageAPI
|
||||||
|
|
||||||
// DeleteObjectHandler - Delete object
|
// DeleteObjectHandler - Delete object
|
||||||
func (api CloudStorageAPI) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
|
func (api storageAPI) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
|
@ -273,7 +273,7 @@ func (s *Signature) DoesPresignedSignatureMatch() (bool, *probe.Error) {
|
|||||||
query := make(url.Values)
|
query := make(url.Values)
|
||||||
query.Set("X-Amz-Algorithm", signV4Algorithm)
|
query.Set("X-Amz-Algorithm", signV4Algorithm)
|
||||||
|
|
||||||
if time.Now().UTC().Sub(preSignV4Values.Date) > time.Duration(preSignV4Values.Expires)/time.Second {
|
if time.Now().UTC().Sub(preSignV4Values.Date) > time.Duration(preSignV4Values.Expires) {
|
||||||
return false, ErrExpiredPresignRequest("Presigned request already expired, please initiate a new request.")
|
return false, ErrExpiredPresignRequest("Presigned request already expired, please initiate a new request.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,6 +281,7 @@ func (s *Signature) DoesPresignedSignatureMatch() (bool, *probe.Error) {
|
|||||||
t := preSignV4Values.Date
|
t := preSignV4Values.Date
|
||||||
expireSeconds := int(time.Duration(preSignV4Values.Expires) / time.Second)
|
expireSeconds := int(time.Duration(preSignV4Values.Expires) / time.Second)
|
||||||
|
|
||||||
|
// Construct the query.
|
||||||
query.Set("X-Amz-Date", t.Format(iso8601Format))
|
query.Set("X-Amz-Date", t.Format(iso8601Format))
|
||||||
query.Set("X-Amz-Expires", strconv.Itoa(expireSeconds))
|
query.Set("X-Amz-Expires", strconv.Itoa(expireSeconds))
|
||||||
query.Set("X-Amz-SignedHeaders", s.getSignedHeaders(s.extractedSignedHeaders))
|
query.Set("X-Amz-SignedHeaders", s.getSignedHeaders(s.extractedSignedHeaders))
|
||||||
@ -293,6 +294,8 @@ func (s *Signature) DoesPresignedSignatureMatch() (bool, *probe.Error) {
|
|||||||
}
|
}
|
||||||
query[k] = v
|
query[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the encoded query.
|
||||||
encodedQuery := query.Encode()
|
encodedQuery := query.Encode()
|
||||||
|
|
||||||
// Verify if date query is same.
|
// Verify if date query is same.
|
||||||
|
42
routers.go
42
routers.go
@ -30,8 +30,8 @@ import (
|
|||||||
signV4 "github.com/minio/minio/pkg/signature"
|
signV4 "github.com/minio/minio/pkg/signature"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CloudStorageAPI container for S3 compatible API.
|
// storageAPI container for S3 compatible API.
|
||||||
type CloudStorageAPI struct {
|
type storageAPI struct {
|
||||||
// Once true log all incoming requests.
|
// Once true log all incoming requests.
|
||||||
AccessLog bool
|
AccessLog bool
|
||||||
// Filesystem instance.
|
// Filesystem instance.
|
||||||
@ -42,8 +42,8 @@ type CloudStorageAPI struct {
|
|||||||
Region string
|
Region string
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebAPI container for Web API.
|
// webAPI container for Web API.
|
||||||
type WebAPI struct {
|
type webAPI struct {
|
||||||
// FSPath filesystem path.
|
// FSPath filesystem path.
|
||||||
FSPath string
|
FSPath string
|
||||||
// Once true log all incoming request.
|
// Once true log all incoming request.
|
||||||
@ -59,8 +59,8 @@ type WebAPI struct {
|
|||||||
secretAccessKey string
|
secretAccessKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerCloudStorageAPI - register all the handlers to their respective paths
|
// registerAPIHandlers - register all the handlers to their respective paths
|
||||||
func registerCloudStorageAPI(mux *router.Router, a CloudStorageAPI, w *WebAPI) {
|
func registerAPIHandlers(mux *router.Router, a storageAPI, w *webAPI) {
|
||||||
// Minio rpc router
|
// Minio rpc router
|
||||||
minio := mux.NewRoute().PathPrefix(privateBucket).Subrouter()
|
minio := mux.NewRoute().PathPrefix(privateBucket).Subrouter()
|
||||||
|
|
||||||
@ -110,8 +110,8 @@ func registerCloudStorageAPI(mux *router.Router, a CloudStorageAPI, w *WebAPI) {
|
|||||||
api.Methods("GET").HandlerFunc(a.ListBucketsHandler)
|
api.Methods("GET").HandlerFunc(a.ListBucketsHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNewWebAPI instantiate a new WebAPI.
|
// initWeb instantiate a new Web.
|
||||||
func getNewWebAPI(conf cloudServerConfig) *WebAPI {
|
func initWeb(conf cloudServerConfig) *webAPI {
|
||||||
// Split host port.
|
// Split host port.
|
||||||
host, port, e := net.SplitHostPort(conf.Address)
|
host, port, e := net.SplitHostPort(conf.Address)
|
||||||
fatalIf(probe.NewError(e), "Unable to parse web addess.", nil)
|
fatalIf(probe.NewError(e), "Unable to parse web addess.", nil)
|
||||||
@ -126,7 +126,7 @@ func getNewWebAPI(conf cloudServerConfig) *WebAPI {
|
|||||||
client, e := minio.NewV4(net.JoinHostPort(host, port), conf.AccessKeyID, conf.SecretAccessKey, inSecure)
|
client, e := minio.NewV4(net.JoinHostPort(host, port), conf.AccessKeyID, conf.SecretAccessKey, inSecure)
|
||||||
fatalIf(probe.NewError(e), "Unable to initialize minio client", nil)
|
fatalIf(probe.NewError(e), "Unable to initialize minio client", nil)
|
||||||
|
|
||||||
w := &WebAPI{
|
w := &webAPI{
|
||||||
FSPath: conf.Path,
|
FSPath: conf.Path,
|
||||||
AccessLog: conf.AccessLog,
|
AccessLog: conf.AccessLog,
|
||||||
Client: client,
|
Client: client,
|
||||||
@ -138,15 +138,15 @@ func getNewWebAPI(conf cloudServerConfig) *WebAPI {
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNewCloudStorageAPI instantiate a new CloudStorageAPI.
|
// initAPI instantiate a new StorageAPI.
|
||||||
func getNewCloudStorageAPI(conf cloudServerConfig) CloudStorageAPI {
|
func initAPI(conf cloudServerConfig) storageAPI {
|
||||||
fs, err := fs.New(conf.Path, conf.MinFreeDisk)
|
fs, err := fs.New(conf.Path, conf.MinFreeDisk)
|
||||||
fatalIf(err.Trace(), "Initializing filesystem failed.", nil)
|
fatalIf(err.Trace(), "Initializing filesystem failed.", nil)
|
||||||
|
|
||||||
sign, err := signV4.New(conf.AccessKeyID, conf.SecretAccessKey, conf.Region)
|
sign, err := signV4.New(conf.AccessKeyID, conf.SecretAccessKey, conf.Region)
|
||||||
fatalIf(err.Trace(conf.AccessKeyID, conf.SecretAccessKey, conf.Region), "Initializing signature version '4' failed.", nil)
|
fatalIf(err.Trace(conf.AccessKeyID, conf.SecretAccessKey, conf.Region), "Initializing signature version '4' failed.", nil)
|
||||||
|
|
||||||
return CloudStorageAPI{
|
return storageAPI{
|
||||||
AccessLog: conf.AccessLog,
|
AccessLog: conf.AccessLog,
|
||||||
Filesystem: fs,
|
Filesystem: fs,
|
||||||
Signature: sign,
|
Signature: sign,
|
||||||
@ -154,7 +154,14 @@ func getNewCloudStorageAPI(conf cloudServerConfig) CloudStorageAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCloudStorageAPIHandler(api CloudStorageAPI, web *WebAPI) http.Handler {
|
// server handler returns final handler before initializing server.
|
||||||
|
func serverHandler(conf cloudServerConfig) http.Handler {
|
||||||
|
// Initialize API.
|
||||||
|
api := initAPI(conf)
|
||||||
|
|
||||||
|
// Initialize Web.
|
||||||
|
web := initWeb(conf)
|
||||||
|
|
||||||
var handlerFns = []HandlerFunc{
|
var handlerFns = []HandlerFunc{
|
||||||
// Redirect some pre-defined browser request paths to a static
|
// Redirect some pre-defined browser request paths to a static
|
||||||
// location prefix.
|
// location prefix.
|
||||||
@ -175,8 +182,13 @@ func getCloudStorageAPIHandler(api CloudStorageAPI, web *WebAPI) http.Handler {
|
|||||||
// invalid/unsupported signatures.
|
// invalid/unsupported signatures.
|
||||||
setAuthHandler,
|
setAuthHandler,
|
||||||
}
|
}
|
||||||
handlerFns = append(handlerFns, setCorsHandler)
|
|
||||||
|
// Initialize router.
|
||||||
mux := router.NewRouter()
|
mux := router.NewRouter()
|
||||||
registerCloudStorageAPI(mux, api, web)
|
|
||||||
|
// Register all API handlers.
|
||||||
|
registerAPIHandlers(mux, api, web)
|
||||||
|
|
||||||
|
// Register rest of the handlers.
|
||||||
return registerHandlers(mux, handlerFns...)
|
return registerHandlers(mux, handlerFns...)
|
||||||
}
|
}
|
||||||
|
@ -90,12 +90,12 @@ type cloudServerConfig struct {
|
|||||||
KeyFile string // Domain key
|
KeyFile string // Domain key
|
||||||
}
|
}
|
||||||
|
|
||||||
// configureAPIServer configure a new server instance
|
// configureServer configure a new server instance
|
||||||
func configureAPIServer(conf cloudServerConfig) (*http.Server, *probe.Error) {
|
func configureServer(conf cloudServerConfig) (*http.Server, *probe.Error) {
|
||||||
// Minio server config
|
// Minio server config
|
||||||
apiServer := &http.Server{
|
apiServer := &http.Server{
|
||||||
Addr: conf.Address,
|
Addr: conf.Address,
|
||||||
Handler: getCloudStorageAPIHandler(getNewCloudStorageAPI(conf), getNewWebAPI(conf)),
|
Handler: serverHandler(conf),
|
||||||
MaxHeaderBytes: 1 << 20,
|
MaxHeaderBytes: 1 << 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,13 +285,16 @@ func serverMain(c *cli.Context) {
|
|||||||
KeyFile: keyFile,
|
KeyFile: keyFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure API server.
|
// configure server.
|
||||||
apiServer, err := configureAPIServer(serverConfig)
|
apiServer, err := configureServer(serverConfig)
|
||||||
errorIf(err.Trace(), "Failed to configure API server.", nil)
|
errorIf(err.Trace(), "Failed to configure API server.", nil)
|
||||||
|
|
||||||
Println("\nMinio Object Storage:")
|
Println("\nMinio Object Storage:")
|
||||||
printServerMsg(apiServer)
|
printServerMsg(apiServer)
|
||||||
|
|
||||||
|
Println("\nMinio Browser:")
|
||||||
|
printServerMsg(apiServer)
|
||||||
|
|
||||||
Println("\nTo configure Minio Client:")
|
Println("\nTo configure Minio Client:")
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
Println(" Download \"mc\" from https://dl.minio.io/client/mc/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/mc.exe")
|
Println(" Download \"mc\" from https://dl.minio.io/client/mc/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/mc.exe")
|
||||||
|
@ -101,9 +101,7 @@ func (s *MyAPIFSCacheSuite) SetUpSuite(c *C) {
|
|||||||
SecretAccessKey: s.secretAccessKey,
|
SecretAccessKey: s.secretAccessKey,
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
}
|
}
|
||||||
cloudStorageAPI := getNewCloudStorageAPI(cloudServer)
|
httpHandler := serverHandler(cloudServer)
|
||||||
webAPI := getNewWebAPI(cloudServer)
|
|
||||||
httpHandler := getCloudStorageAPIHandler(cloudStorageAPI, webAPI)
|
|
||||||
testAPIFSCacheServer = httptest.NewServer(httpHandler)
|
testAPIFSCacheServer = httptest.NewServer(httpHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
signature.go
22
signature.go
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -9,6 +25,7 @@ import (
|
|||||||
signV4 "github.com/minio/minio/pkg/signature"
|
signV4 "github.com/minio/minio/pkg/signature"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Verify if request has JWT.
|
||||||
func isRequestJWT(r *http.Request) bool {
|
func isRequestJWT(r *http.Request) bool {
|
||||||
if _, ok := r.Header["Authorization"]; ok {
|
if _, ok := r.Header["Authorization"]; ok {
|
||||||
if strings.HasPrefix(r.Header.Get("Authorization"), jwtAlgorithm) {
|
if strings.HasPrefix(r.Header.Get("Authorization"), jwtAlgorithm) {
|
||||||
@ -18,6 +35,7 @@ func isRequestJWT(r *http.Request) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify if request has AWS Signature Version '4'.
|
||||||
func isRequestSignatureV4(r *http.Request) bool {
|
func isRequestSignatureV4(r *http.Request) bool {
|
||||||
if _, ok := r.Header["Authorization"]; ok {
|
if _, ok := r.Header["Authorization"]; ok {
|
||||||
if strings.HasPrefix(r.Header.Get("Authorization"), signV4Algorithm) {
|
if strings.HasPrefix(r.Header.Get("Authorization"), signV4Algorithm) {
|
||||||
@ -27,6 +45,7 @@ func isRequestSignatureV4(r *http.Request) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify if request has AWS Presignature Version '4'.
|
||||||
func isRequestPresignedSignatureV4(r *http.Request) bool {
|
func isRequestPresignedSignatureV4(r *http.Request) bool {
|
||||||
if _, ok := r.URL.Query()["X-Amz-Credential"]; ok {
|
if _, ok := r.URL.Query()["X-Amz-Credential"]; ok {
|
||||||
return ok
|
return ok
|
||||||
@ -34,6 +53,7 @@ func isRequestPresignedSignatureV4(r *http.Request) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify if request has AWS Post policy Signature Version '4'.
|
||||||
func isRequestPostPolicySignatureV4(r *http.Request) bool {
|
func isRequestPostPolicySignatureV4(r *http.Request) bool {
|
||||||
if _, ok := r.Header["Content-Type"]; ok {
|
if _, ok := r.Header["Content-Type"]; ok {
|
||||||
if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") {
|
if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") {
|
||||||
@ -43,6 +63,7 @@ func isRequestPostPolicySignatureV4(r *http.Request) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify if request requires ACL check.
|
||||||
func isRequestRequiresACLCheck(r *http.Request) bool {
|
func isRequestRequiresACLCheck(r *http.Request) bool {
|
||||||
if isRequestSignatureV4(r) || isRequestPresignedSignatureV4(r) || isRequestPostPolicySignatureV4(r) {
|
if isRequestSignatureV4(r) || isRequestPresignedSignatureV4(r) || isRequestPostPolicySignatureV4(r) {
|
||||||
return false
|
return false
|
||||||
@ -50,6 +71,7 @@ func isRequestRequiresACLCheck(r *http.Request) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify if request has valid AWS Signature Version '4'.
|
||||||
func isSignV4ReqAuthenticated(sign *signV4.Signature, r *http.Request) bool {
|
func isSignV4ReqAuthenticated(sign *signV4.Signature, r *http.Request) bool {
|
||||||
auth := sign.SetHTTPRequestToVerify(r)
|
auth := sign.SetHTTPRequestToVerify(r)
|
||||||
if isRequestSignatureV4(r) {
|
if isRequestSignatureV4(r) {
|
||||||
|
@ -36,11 +36,11 @@ import (
|
|||||||
"github.com/minio/minio/pkg/probe"
|
"github.com/minio/minio/pkg/probe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isJWTReqAuthencatied validates if any incoming request to be a valid JWT
|
// isJWTReqAuthenticated validates if any incoming request to be a
|
||||||
// authenticated request.
|
// valid JWT authenticated request.
|
||||||
func isJWTReqAuthencatied(req *http.Request) bool {
|
func isJWTReqAuthenticated(req *http.Request) bool {
|
||||||
jwt := InitJWT()
|
jwt := initJWT()
|
||||||
tokenRequest, e := jwtgo.ParseFromRequest(req, func(token *jwtgo.Token) (interface{}, error) {
|
token, e := jwtgo.ParseFromRequest(req, func(token *jwtgo.Token) (interface{}, error) {
|
||||||
if _, ok := token.Method.(*jwtgo.SigningMethodHMAC); !ok {
|
if _, ok := token.Method.(*jwtgo.SigningMethodHMAC); !ok {
|
||||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
||||||
}
|
}
|
||||||
@ -49,18 +49,18 @@ func isJWTReqAuthencatied(req *http.Request) bool {
|
|||||||
if e != nil {
|
if e != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return tokenRequest.Valid
|
return token.Valid
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUIVersion - get UI version
|
// GetUIVersion - get UI version
|
||||||
func (web WebAPI) GetUIVersion(r *http.Request, args *GenericArgs, reply *GenericRep) error {
|
func (web webAPI) GetUIVersion(r *http.Request, args *GenericArgs, reply *GenericRep) error {
|
||||||
reply.UIVersion = uiVersion
|
reply.UIVersion = uiVersion
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerInfo - get server info.
|
// ServerInfo - get server info.
|
||||||
func (web *WebAPI) ServerInfo(r *http.Request, args *ServerInfoArgs, reply *ServerInfoRep) error {
|
func (web *webAPI) ServerInfo(r *http.Request, args *ServerInfoArgs, reply *ServerInfoRep) error {
|
||||||
if !isJWTReqAuthencatied(r) {
|
if !isJWTReqAuthenticated(r) {
|
||||||
return &json2.Error{Message: "Unauthorized request"}
|
return &json2.Error{Message: "Unauthorized request"}
|
||||||
}
|
}
|
||||||
host, err := os.Hostname()
|
host, err := os.Hostname()
|
||||||
@ -88,8 +88,8 @@ func (web *WebAPI) ServerInfo(r *http.Request, args *ServerInfoArgs, reply *Serv
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DiskInfo - get disk statistics.
|
// DiskInfo - get disk statistics.
|
||||||
func (web *WebAPI) DiskInfo(r *http.Request, args *DiskInfoArgs, reply *DiskInfoRep) error {
|
func (web *webAPI) DiskInfo(r *http.Request, args *DiskInfoArgs, reply *DiskInfoRep) error {
|
||||||
if !isJWTReqAuthencatied(r) {
|
if !isJWTReqAuthenticated(r) {
|
||||||
return &json2.Error{Message: "Unauthorized request"}
|
return &json2.Error{Message: "Unauthorized request"}
|
||||||
}
|
}
|
||||||
info, e := disk.GetInfo(web.FSPath)
|
info, e := disk.GetInfo(web.FSPath)
|
||||||
@ -102,8 +102,8 @@ func (web *WebAPI) DiskInfo(r *http.Request, args *DiskInfoArgs, reply *DiskInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MakeBucket - make a bucket.
|
// MakeBucket - make a bucket.
|
||||||
func (web *WebAPI) MakeBucket(r *http.Request, args *MakeBucketArgs, reply *GenericRep) error {
|
func (web *webAPI) MakeBucket(r *http.Request, args *MakeBucketArgs, reply *GenericRep) error {
|
||||||
if !isJWTReqAuthencatied(r) {
|
if !isJWTReqAuthenticated(r) {
|
||||||
return &json2.Error{Message: "Unauthorized request"}
|
return &json2.Error{Message: "Unauthorized request"}
|
||||||
}
|
}
|
||||||
reply.UIVersion = uiVersion
|
reply.UIVersion = uiVersion
|
||||||
@ -115,8 +115,8 @@ func (web *WebAPI) MakeBucket(r *http.Request, args *MakeBucketArgs, reply *Gene
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListBuckets - list buckets api.
|
// ListBuckets - list buckets api.
|
||||||
func (web *WebAPI) ListBuckets(r *http.Request, args *ListBucketsArgs, reply *ListBucketsRep) error {
|
func (web *webAPI) ListBuckets(r *http.Request, args *ListBucketsArgs, reply *ListBucketsRep) error {
|
||||||
if !isJWTReqAuthencatied(r) {
|
if !isJWTReqAuthenticated(r) {
|
||||||
return &json2.Error{Message: "Unauthorized request"}
|
return &json2.Error{Message: "Unauthorized request"}
|
||||||
}
|
}
|
||||||
buckets, e := web.Client.ListBuckets()
|
buckets, e := web.Client.ListBuckets()
|
||||||
@ -134,8 +134,8 @@ func (web *WebAPI) ListBuckets(r *http.Request, args *ListBucketsArgs, reply *Li
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListObjects - list objects api.
|
// ListObjects - list objects api.
|
||||||
func (web *WebAPI) ListObjects(r *http.Request, args *ListObjectsArgs, reply *ListObjectsRep) error {
|
func (web *webAPI) ListObjects(r *http.Request, args *ListObjectsArgs, reply *ListObjectsRep) error {
|
||||||
if !isJWTReqAuthencatied(r) {
|
if !isJWTReqAuthenticated(r) {
|
||||||
return &json2.Error{Message: "Unauthorized request"}
|
return &json2.Error{Message: "Unauthorized request"}
|
||||||
}
|
}
|
||||||
doneCh := make(chan struct{})
|
doneCh := make(chan struct{})
|
||||||
@ -182,8 +182,8 @@ func getTargetHost(apiAddress, targetHost string) (string, *probe.Error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PutObjectURL - generates url for upload access.
|
// PutObjectURL - generates url for upload access.
|
||||||
func (web *WebAPI) PutObjectURL(r *http.Request, args *PutObjectURLArgs, reply *PutObjectURLRep) error {
|
func (web *webAPI) PutObjectURL(r *http.Request, args *PutObjectURLArgs, reply *PutObjectURLRep) error {
|
||||||
if !isJWTReqAuthencatied(r) {
|
if !isJWTReqAuthenticated(r) {
|
||||||
return &json2.Error{Message: "Unauthorized request"}
|
return &json2.Error{Message: "Unauthorized request"}
|
||||||
}
|
}
|
||||||
targetHost, err := getTargetHost(web.apiAddress, args.TargetHost)
|
targetHost, err := getTargetHost(web.apiAddress, args.TargetHost)
|
||||||
@ -204,8 +204,8 @@ func (web *WebAPI) PutObjectURL(r *http.Request, args *PutObjectURLArgs, reply *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectURL - generates url for download access.
|
// GetObjectURL - generates url for download access.
|
||||||
func (web *WebAPI) GetObjectURL(r *http.Request, args *GetObjectURLArgs, reply *GetObjectURLRep) error {
|
func (web *webAPI) GetObjectURL(r *http.Request, args *GetObjectURLArgs, reply *GetObjectURLRep) error {
|
||||||
if !isJWTReqAuthencatied(r) {
|
if !isJWTReqAuthenticated(r) {
|
||||||
return &json2.Error{Message: "Unauthorized request"}
|
return &json2.Error{Message: "Unauthorized request"}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,8 +236,8 @@ func (web *WebAPI) GetObjectURL(r *http.Request, args *GetObjectURLArgs, reply *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RemoveObject - removes an object.
|
// RemoveObject - removes an object.
|
||||||
func (web *WebAPI) RemoveObject(r *http.Request, args *RemoveObjectArgs, reply *GenericRep) error {
|
func (web *webAPI) RemoveObject(r *http.Request, args *RemoveObjectArgs, reply *GenericRep) error {
|
||||||
if !isJWTReqAuthencatied(r) {
|
if !isJWTReqAuthenticated(r) {
|
||||||
return &json2.Error{Message: "Unauthorized request"}
|
return &json2.Error{Message: "Unauthorized request"}
|
||||||
}
|
}
|
||||||
reply.UIVersion = uiVersion
|
reply.UIVersion = uiVersion
|
||||||
@ -249,8 +249,8 @@ func (web *WebAPI) RemoveObject(r *http.Request, args *RemoveObjectArgs, reply *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Login - user login handler.
|
// Login - user login handler.
|
||||||
func (web *WebAPI) Login(r *http.Request, args *LoginArgs, reply *LoginRep) error {
|
func (web *webAPI) Login(r *http.Request, args *LoginArgs, reply *LoginRep) error {
|
||||||
jwt := InitJWT()
|
jwt := initJWT()
|
||||||
if jwt.Authenticate(args.Username, args.Password) {
|
if jwt.Authenticate(args.Username, args.Password) {
|
||||||
token, err := jwt.GenerateToken(args.Username)
|
token, err := jwt.GenerateToken(args.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user