Allow Basic authentication in browser (#195)
This commit is contained in:
parent
fabb8f2dd7
commit
597bec430c
|
@ -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.
|
||||
|
||||
|
|
21
auth.go
21
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)
|
||||
}
|
||||
|
||||
|
|
26
server.go
26
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<name>[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,
|
||||
|
|
|
@ -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,
|
||||
|
|
10
templates.go
10
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)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<h3>Client</h3>
|
||||
<p>To simplify uploading and deleting files, you can use <a target="_blank" href="https://github.com/andreimarcu/linx-client">linx-client</a>, which uses this API.</p>
|
||||
|
||||
{% if using_auth %}
|
||||
{% if auth != "none" %}
|
||||
<h3>Keys</h3>
|
||||
<p>This instance uses API Keys, therefore you will need to provide a key for uploading and deleting files.<br/> To do so, add the <code>Linx-Api-Key</code> header with your key.</p>
|
||||
{% endif %}
|
||||
|
@ -56,7 +56,7 @@
|
|||
|
||||
<p>Uploading myphoto.jpg</p>
|
||||
|
||||
{% if using_auth %}
|
||||
{% if auth != "none" %}
|
||||
<pre><code>$ curl -H "Linx-Api-Key: mysecretkey" -T myphoto.jpg {{ siteurl }}upload/
|
||||
{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}7z4h4ut.jpg{% endif %}</code></pre>
|
||||
{% else %}
|
||||
|
@ -66,7 +66,7 @@
|
|||
|
||||
<p>Uploading myphoto.jpg with an expiry of 20 minutes</p>
|
||||
|
||||
{% if using_auth %}
|
||||
{% if auth != "none" %}
|
||||
<pre><code>$ 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 %}</code></pre>
|
||||
{% else %}
|
||||
|
@ -76,7 +76,7 @@
|
|||
|
||||
<p>Uploading myphoto.jpg with a random filename and getting a json response:</p>
|
||||
|
||||
{% if using_auth %}
|
||||
{% if auth != "none" %}
|
||||
<pre><code>$ 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"}</code></pre>
|
||||
|
@ -94,7 +94,7 @@
|
|||
|
||||
<p>To overwrite myphoto.jpg</p>
|
||||
|
||||
{% if using_auth %}
|
||||
{% if auth != "none" %}
|
||||
<pre><code>$ curl -H "Linx-Api-Key: mysecretkey" -H "Linx-Delete-Key: mysecret" -T myphoto.jpg {{ siteurl }}upload/
|
||||
{{ siteurl }}myphoto.jpg</code></pre>
|
||||
{% else %}
|
||||
|
@ -110,7 +110,7 @@
|
|||
|
||||
<p>To delete myphoto.jpg</p>
|
||||
|
||||
{% if using_auth %}
|
||||
{% if auth != "none" %}
|
||||
<pre><code>$ curl -H "Linx-Api-Key: mysecretkey" -H "Linx-Delete-Key: mysecret" -X DELETE {{ siteurl }}myphoto.jpg
|
||||
DELETED</code></pre>
|
||||
{% else %}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<div id="container">
|
||||
<div id="header">
|
||||
<div id="navigation" class="right">
|
||||
{% if !using_auth %}
|
||||
{% if auth != "header" %}
|
||||
<a href="{{ sitepath }}">Upload</a> |
|
||||
<a href="{{ sitepath }}paste/">Paste</a> |
|
||||
{% endif %}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
{% block content %}
|
||||
<div id="fileupload">
|
||||
<form action="{{ sitepath }}upload" class="dropzone" id="dropzone" method="POST" enctype="multipart/form-data" data-maxsize="{{ maxsize }}">
|
||||
<form action="{{ sitepath }}upload" class="dropzone" id="dropzone" method="POST" enctype="multipart/form-data" data-maxsize="{{ maxsize }}" data-auth="{{ auth }}">
|
||||
<div class="fallback">
|
||||
<input id="fileinput" name="file" type="file" /><br />
|
||||
<input id="submitbtn" type="submit" value="Upload">
|
||||
|
|
15
upload.go
15
upload.go
|
@ -157,9 +157,20 @@ func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func uploadRemote(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
if Config.remoteAuthFile != "" {
|
||||
result, err := checkAuth(remoteAuthKeys, r.FormValue("key"))
|
||||
key := r.FormValue("key")
|
||||
if key == "" && Config.basicAuth {
|
||||
_, password, ok := r.BasicAuth()
|
||||
if ok {
|
||||
key = password
|
||||
}
|
||||
}
|
||||
result, err := checkAuth(remoteAuthKeys, key)
|
||||
if err != nil || !result {
|
||||
unauthorizedHandler(c, w, r)
|
||||
if Config.basicAuth {
|
||||
badAuthorizationHandler(w, r)
|
||||
} else {
|
||||
unauthorizedHandler(c, w, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue