diff --git a/README.md b/README.md index 24ef4ea..144dd40 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ allowhotlink = true #### Require API Keys for uploads - ```-authfile path/to/authfile``` -- (optionally) require authorization for upload/delete by providing a newline-separated file of scrypted auth keys - ```-remoteauthfile path/to/remoteauthfile``` -- (optionally) require authorization for remote uploads by providing a newline-separated file of scrypted auth keys +- ```-basicauth``` -- (optionally) allow basic authorization to upload or paste files from browser when `-authfile` is enabled A helper utility ```linx-genkey``` is provided which hashes keys to the format required in the auth files. diff --git a/auth.go b/auth.go index 102f892..463dddb 100644 --- a/auth.go +++ b/auth.go @@ -3,11 +3,14 @@ package main import ( "bufio" "encoding/base64" + "fmt" "log" "net/http" "os" "golang.org/x/crypto/scrypt" + + "github.com/zenazn/goji/web" ) const ( @@ -78,6 +81,12 @@ func (a auth) ServeHTTP(w http.ResponseWriter, r *http.Request) { } key := r.Header.Get("Linx-Api-Key") + if key == "" && Config.basicAuth { + _, password, ok := r.BasicAuth() + if ok { + key = password + } + } result, err := checkAuth(a.authKeys, key) if err != nil || !result { @@ -88,8 +97,8 @@ func (a auth) ServeHTTP(w http.ResponseWriter, r *http.Request) { a.successHandler.ServeHTTP(w, r) } -func UploadAuth(o AuthOptions) func(http.Handler) http.Handler { - fn := func(h http.Handler) http.Handler { +func UploadAuth(o AuthOptions) func(*web.C, http.Handler) http.Handler { + fn := func(c *web.C, h http.Handler) http.Handler { return auth{ successHandler: h, failureHandler: http.HandlerFunc(badAuthorizationHandler), @@ -101,7 +110,13 @@ func UploadAuth(o AuthOptions) func(http.Handler) http.Handler { } func badAuthorizationHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusUnauthorized) + if Config.basicAuth { + rs := "" + if Config.siteName != "" { + rs = fmt.Sprintf(` realm="%s"`, Config.siteName) + } + w.Header().Set("WWW-Authenticate", `Basic` + rs) + } http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) } diff --git a/server.go b/server.go index 71a9c4d..59b7b6b 100644 --- a/server.go +++ b/server.go @@ -59,6 +59,7 @@ var Config struct { allowHotlink bool fastcgi bool remoteUploads bool + basicAuth bool authFile string remoteAuthFile string addHeaders headerList @@ -168,7 +169,7 @@ func setup() *web.Mux { selifIndexRe := regexp.MustCompile("^" + Config.sitePath + Config.selifPath + `$`) torrentRe := regexp.MustCompile("^" + Config.sitePath + `(?P[a-z0-9-\.]+)/torrent$`) - if Config.authFile == "" { + if Config.authFile == "" || Config.basicAuth { mux.Get(Config.sitePath, indexHandler) mux.Get(Config.sitePath+"paste/", pasteHandler) } else { @@ -189,6 +190,27 @@ func setup() *web.Mux { } } + if Config.basicAuth { + options := AuthOptions{ + AuthFile: Config.authFile, + UnauthMethods: []string{}, + } + okFunc := func (w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", Config.sitePath) + w.WriteHeader(http.StatusFound) + } + authHandler := auth { + successHandler: http.HandlerFunc(okFunc), + failureHandler: http.HandlerFunc(badAuthorizationHandler), + authKeys: readAuthKeys(Config.authFile), + o: options, + } + mux.Head(Config.sitePath+"auth", authHandler) + mux.Head(Config.sitePath+"auth/", authHandler) + mux.Get(Config.sitePath+"auth", authHandler) + mux.Get(Config.sitePath+"auth/", authHandler) + } + mux.Post(Config.sitePath+"upload", uploadPostHandler) mux.Post(Config.sitePath+"upload/", uploadPostHandler) mux.Put(Config.sitePath+"upload", uploadPutHandler) @@ -217,6 +239,8 @@ func main() { "path to files directory") flag.StringVar(&Config.metaDir, "metapath", "meta/", "path to metadata directory") + flag.BoolVar(&Config.basicAuth, "basicauth", false, + "allow logging by basic auth password") flag.BoolVar(&Config.noLogs, "nologs", false, "remove stdout output for each request") flag.BoolVar(&Config.allowHotlink, "allowhotlink", false, diff --git a/static/js/upload.js b/static/js/upload.js index 125123c..fce6e77 100644 --- a/static/js/upload.js +++ b/static/js/upload.js @@ -6,6 +6,23 @@ Dropzone.options.dropzone = { dzone.style.display = "block"; }, addedfile: function(file) { + if (!this.options.autoProcessQueue) { + var dropzone = this; + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.readyState !== XMLHttpRequest.DONE) { + return; + } + if (xhr.status < 400) { + dropzone.processQueue() + dropzone.options.autoProcessQueue = true; + } else { + dropzone.cancelUpload(file) + } + }; + xhr.open("HEAD", "auth/", true); + xhr.send() + } var upload = document.createElement("div"); upload.className = "upload"; @@ -80,6 +97,9 @@ Dropzone.options.dropzone = { file.cancelActionElement = deleteAction; file.fileActions.appendChild(deleteAction); }, + canceled: function(file) { + this.options.error(file); + }, error: function(file, resp, xhrO) { file.fileActions.removeChild(file.cancelActionElement); file.fileActions.removeChild(file.progressElement); @@ -101,6 +121,7 @@ Dropzone.options.dropzone = { file.fileLabel.className = "error"; }, + autoProcessQueue: document.getElementById("dropzone").getAttribute("data-auth") !== "basic", maxFilesize: Math.round(parseInt(document.getElementById("dropzone").getAttribute("data-maxsize"), 10) / 1024 / 1024), previewsContainer: "#uploads", parallelUploads: 5, diff --git a/templates.go b/templates.go index 79c90ce..0ab1359 100644 --- a/templates.go +++ b/templates.go @@ -84,7 +84,15 @@ func renderTemplate(tpl *pongo2.Template, context pongo2.Context, r *http.Reques context["sitepath"] = Config.sitePath context["selifpath"] = Config.selifPath - context["using_auth"] = Config.authFile != "" + var a string + if Config.authFile == "" { + a = "none" + } else if Config.basicAuth { + a = "basic" + } else { + a = "header" + } + context["auth"] = a return tpl.ExecuteWriter(context, writer) } diff --git a/templates/API.html b/templates/API.html index 251cbaf..ab3a356 100644 --- a/templates/API.html +++ b/templates/API.html @@ -14,7 +14,7 @@

Client

To simplify uploading and deleting files, you can use linx-client, which uses this API.

- {% if using_auth %} + {% if auth != "none" %}

Keys

This instance uses API Keys, therefore you will need to provide a key for uploading and deleting files.
To do so, add the Linx-Api-Key header with your key.

{% endif %} @@ -56,7 +56,7 @@

Uploading myphoto.jpg

- {% if using_auth %} + {% if auth != "none" %}
$ curl -H "Linx-Api-Key: mysecretkey" -T myphoto.jpg {{ siteurl }}upload/  
 {{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}7z4h4ut.jpg{% endif %}
{% else %} @@ -66,7 +66,7 @@

Uploading myphoto.jpg with an expiry of 20 minutes

- {% if using_auth %} + {% if auth != "none" %}
$ curl -H "Linx-Api-Key: mysecretkey" -H "Linx-Expiry: 1200" -T myphoto.jpg {{ siteurl }}upload/
 {{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}jm295snf.jpg{% endif %}
{% else %} @@ -76,7 +76,7 @@

Uploading myphoto.jpg with a random filename and getting a json response:

- {% if using_auth %} + {% if auth != "none" %}
$ curl -H "Linx-Api-Key: mysecretkey" -H "Accept: application/json"{% if not forcerandom %} -H "Linx-Randomize: yes"{% endif %} -T myphoto.jpg {{ siteurl }}upload/  
 {"delete_key":"...","expiry":"0","filename":"f34h4iu.jpg","mimetype":"image/jpeg",
 "sha256sum":"...","size":"...","url":"{{ siteurl }}f34h4iu.jpg"}
@@ -94,7 +94,7 @@

To overwrite myphoto.jpg

- {% if using_auth %} + {% if auth != "none" %}
$ curl -H "Linx-Api-Key: mysecretkey" -H "Linx-Delete-Key: mysecret" -T myphoto.jpg {{ siteurl }}upload/
 {{ siteurl }}myphoto.jpg
{% else %} @@ -110,7 +110,7 @@

To delete myphoto.jpg

- {% if using_auth %} + {% if auth != "none" %}
$ curl -H "Linx-Api-Key: mysecretkey" -H "Linx-Delete-Key: mysecret" -X DELETE {{ siteurl }}myphoto.jpg
 DELETED
{% else %} diff --git a/templates/base.html b/templates/base.html index d1411d4..cda6746 100644 --- a/templates/base.html +++ b/templates/base.html @@ -15,7 +15,7 @@