mirror of
https://github.com/halverneus/static-file-server.git
synced 2024-11-24 09:05:30 +00:00
Rough draft of feature.
This commit is contained in:
parent
4454460785
commit
084257d34d
515
serve.go
515
serve.go
@ -1,15 +1,92 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/scrypt"
|
||||||
|
|
||||||
|
"github.com/howeyc/gopass"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "Version 1.1"
|
version = "Version 1.2"
|
||||||
|
|
||||||
|
authHelp = `
|
||||||
|
NAME
|
||||||
|
static-file-server auth
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
static-file-server auth [ help | -help | --help ]
|
||||||
|
static-file-server auth list
|
||||||
|
static-file-server auth add $username [ $password ]
|
||||||
|
static-file-server auth update $username [ $password ]
|
||||||
|
static-file-server auth remove $username
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
The Static File Server authentication sub-command is used to securely modify
|
||||||
|
a credential file for use with basic authentication.
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
None... not even libc!
|
||||||
|
|
||||||
|
ENVIRONMENT VARIABLES
|
||||||
|
CREDENTIALS
|
||||||
|
The path to a file that contains valid credentials, or the path to a
|
||||||
|
file that will have credentials modified. By having this variable set
|
||||||
|
basic authentication will automatically be used. Credentials must be
|
||||||
|
added to the file using the 'auth add' command prior to use. If no
|
||||||
|
credentials are added, then basic authentication will always fail (fails
|
||||||
|
secure). It is HIGHLY RECOMMENDED to use this with TLS certificates. If
|
||||||
|
you are not using TLS certificates, don't use credentials that are
|
||||||
|
important. Username and password are case-sensitive. Variable must be
|
||||||
|
set to use any authentication sub-commands, with the exception of
|
||||||
|
requesting help.
|
||||||
|
|
||||||
|
COMMANDS
|
||||||
|
add $username [ $password ]
|
||||||
|
Add a new credential to the credential file. If the username is not set
|
||||||
|
or is the same as an existing username, the command will fail. If the
|
||||||
|
password for a user needs to be updated, use 'auth update'. If the
|
||||||
|
password is not supplied, it will be requested during execution.
|
||||||
|
ARGS:
|
||||||
|
$username: The case-sensitive username to be added.
|
||||||
|
$password: The case-sensitive password to be associated with the new
|
||||||
|
username.
|
||||||
|
|
||||||
|
help
|
||||||
|
Prints this help documentation.
|
||||||
|
|
||||||
|
list
|
||||||
|
List all usernames in the credential file.
|
||||||
|
|
||||||
|
remove $username
|
||||||
|
Remove an existing username from the credential file. If the username is
|
||||||
|
not set or doesn't match an existing username, the command will fail.
|
||||||
|
ARGS:
|
||||||
|
$username: The case-sensitive username to be removed.
|
||||||
|
|
||||||
|
update $username [ $password ]
|
||||||
|
Update an existing username with a new password in the credential file.
|
||||||
|
If the username is not set or doesn't match an existing username, the
|
||||||
|
command will fail. If a new user needs to be added, use 'auth add'. If
|
||||||
|
the password is not supplied, it will be requested during execution.
|
||||||
|
ARGS:
|
||||||
|
$username: The case-sensitive username to be updated with a new
|
||||||
|
password.
|
||||||
|
$password: The case-sensitive passwrod to be associated with the
|
||||||
|
existing username.
|
||||||
|
`
|
||||||
|
|
||||||
help = `
|
help = `
|
||||||
NAME
|
NAME
|
||||||
@ -19,6 +96,7 @@ SYNOPSIS
|
|||||||
static-file-server
|
static-file-server
|
||||||
static-file-server [ help | -help | --help ]
|
static-file-server [ help | -help | --help ]
|
||||||
static-file-server [ version | -version | --version ]
|
static-file-server [ version | -version | --version ]
|
||||||
|
static-file-server auth [ help | -help | --help ]
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
The Static File Server is intended to be a tiny, fast and simple solution
|
The Static File Server is intended to be a tiny, fast and simple solution
|
||||||
@ -31,6 +109,22 @@ DEPENDENCIES
|
|||||||
None... not even libc!
|
None... not even libc!
|
||||||
|
|
||||||
ENVIRONMENT VARIABLES
|
ENVIRONMENT VARIABLES
|
||||||
|
CREDENTIALS
|
||||||
|
The path to a file that contains valid credentials, or the path to a
|
||||||
|
file that will have credentials modified. By having this variable set
|
||||||
|
basic authentication will automatically be used. Credentials must be
|
||||||
|
added to the file using the 'auth add' command prior to use. If no
|
||||||
|
credentials are added, then basic authentication will always fail (fails
|
||||||
|
secure). It is HIGHLY RECOMMENDED to use this with TLS certificates. If
|
||||||
|
you are not using TLS certificates, don't use credentials that are
|
||||||
|
important. Username and password are case-sensitive.
|
||||||
|
FAST_AUTH
|
||||||
|
It is recommended to use CREDENTIALS and not FAST_AUTH. FAST_AUTH is
|
||||||
|
only provided for users that want to share files for a short time only
|
||||||
|
using an unimportant password. If CREDENTIALS is set, FAST_AUTH will be
|
||||||
|
ignored. The value of FAST_AUTH is a colon (:) delimited username and
|
||||||
|
password (for example: FAST_AUTH=user:password). Username and password
|
||||||
|
are case-sensitive.
|
||||||
FOLDER
|
FOLDER
|
||||||
The path to the folder containing the contents to be served over
|
The path to the folder containing the contents to be served over
|
||||||
HTTP(s). If not supplied, defaults to '/web' (for Docker reasons).
|
HTTP(s). If not supplied, defaults to '/web' (for Docker reasons).
|
||||||
@ -58,6 +152,19 @@ ENVIRONMENT VARIABLES
|
|||||||
start with a forward-slash and NOT end with a forward-slash. If not
|
start with a forward-slash and NOT end with a forward-slash. If not
|
||||||
supplied then no prefix is used.
|
supplied then no prefix is used.
|
||||||
|
|
||||||
|
COMMANDS
|
||||||
|
[No commands supplied]
|
||||||
|
Serve static files based on passed environment variables. Server will
|
||||||
|
continue to run until shutdown of the service is requested.
|
||||||
|
|
||||||
|
auth
|
||||||
|
The CREDENTIALS environment variable must be set to use the
|
||||||
|
authorization command. The authorization command is used to securely
|
||||||
|
add, modify and remove user credentials for basic authentication. For
|
||||||
|
more information, run 'static-file-server auth help'.
|
||||||
|
|
||||||
|
help
|
||||||
|
Prints this help documentation.
|
||||||
USAGE
|
USAGE
|
||||||
FILE LAYOUT
|
FILE LAYOUT
|
||||||
/var/www/sub/my.file
|
/var/www/sub/my.file
|
||||||
@ -106,37 +213,293 @@ USAGE
|
|||||||
export SHOW_LISTING=false
|
export SHOW_LISTING=false
|
||||||
static-file-server
|
static-file-server
|
||||||
Returns 'NOT FOUND': wget http://my.machine/
|
Returns 'NOT FOUND': wget http://my.machine/
|
||||||
|
|
||||||
|
export CREDENTIALS=credentials.json
|
||||||
|
export FOLDER=/var/www/sub
|
||||||
|
export PORT=443
|
||||||
|
export TLS_CERT=/etc/server/my.machine.crt
|
||||||
|
export TLS_KEY=/etc/server/my.machine.key
|
||||||
|
static-file-server auth add 'user' 'pass'
|
||||||
|
static-file-server auth add 'john' '12345'
|
||||||
|
static-file-server
|
||||||
|
Retrieve with:
|
||||||
|
wget --user 'user' --password 'pass' --auth-no-challenge \
|
||||||
|
https://my.machine/my.file
|
||||||
|
wget --user 'john' --password '12345' --auth-no-challenge \
|
||||||
|
https://my.machine/my.file
|
||||||
|
|
||||||
|
export FAST_AUTH=user:pass
|
||||||
|
export FOLDER=/var/www/sub
|
||||||
|
export PORT=443
|
||||||
|
export TLS_CERT=/etc/server/my.machine.crt
|
||||||
|
export TLS_KEY=/etc/server/my.machine.key
|
||||||
|
static-file-server
|
||||||
|
Retrieve with:
|
||||||
|
wget --user 'user' --password 'pass' --auth-no-challenge \
|
||||||
|
https://my.machine/my.file
|
||||||
`
|
`
|
||||||
|
|
||||||
|
credentials map[string]*credential
|
||||||
|
|
||||||
|
envvar struct {
|
||||||
|
credentialFile string
|
||||||
|
fastAuth string
|
||||||
|
folder string
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
showListing bool
|
||||||
|
tlsCert string
|
||||||
|
tlsKey string
|
||||||
|
urlPrefix string
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// credential that is stored and used for authentication.
|
||||||
|
type credential struct {
|
||||||
|
// Salt (random) for uniquely encrypting each password.
|
||||||
|
Salt string `json:"salt"`
|
||||||
|
// Password after it has been encrypted with the salt.
|
||||||
|
Password string `json:"pass"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// matches the passed uncrypted password against the assigned encrypted
|
||||||
|
// password. Returns true if the passwords match.
|
||||||
|
func (c *credential) matches(password string) (matches bool, err error) {
|
||||||
|
matches = false
|
||||||
|
var enc string
|
||||||
|
if enc, err = c.encrypt(password, c.Salt); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
matches = enc == c.Password
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the encrypted credential password with a new, unencrypted password.
|
||||||
|
func (c *credential) update(password string) (err error) {
|
||||||
|
// Create a new salt value.
|
||||||
|
rawSalt := make([]byte, 24)
|
||||||
|
var n int
|
||||||
|
if n, err = io.ReadFull(rand.Reader, rawSalt); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(rawSalt) != n {
|
||||||
|
err = errors.New("failed to create random password salt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store salt and encrypted password.
|
||||||
|
c.Salt = base64.StdEncoding.EncodeToString(rawSalt)
|
||||||
|
c.Password, err = c.encrypt(password, c.Salt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt a password/salt combination to the encrypted password.
|
||||||
|
func (c *credential) encrypt(password, salt string) (enc string, err error) {
|
||||||
|
// Decode salt from Base64 to raw bytes.
|
||||||
|
var rawSalt []byte
|
||||||
|
if rawSalt, err = base64.StdEncoding.DecodeString(salt); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt password to bytes. Encryption values determined by current
|
||||||
|
// cryptography suggestion.
|
||||||
|
var rawEnc []byte
|
||||||
|
if rawEnc, err = scrypt.Key(
|
||||||
|
[]byte(password), rawSalt, 16384, 8, 1, 32,
|
||||||
|
); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode as Base64.
|
||||||
|
enc = base64.StdEncoding.EncodeToString(rawEnc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func authAddMain(command string, args []string) (err error) {
|
||||||
|
if 0 == len(args) || 2 < len(args) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"wrong number of arguments supplied to: '%s add', try '%s help'",
|
||||||
|
command, command,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
username := args[0]
|
||||||
|
var password string
|
||||||
|
if 1 < len(args) {
|
||||||
|
password = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = loadCredentials(envvar.credentialFile); nil != err {
|
||||||
|
fmt.Printf(
|
||||||
|
"WARNING: Credential file '%s' doesn't already exist... creating.\n",
|
||||||
|
envvar.credentialFile,
|
||||||
|
)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if _, found := credentials[username]; found {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"user '%s' already exists, use 'update' in place of 'add'",
|
||||||
|
username,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if 0 == len(password) {
|
||||||
|
if password, err = getPassword(); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cred := &credential{}
|
||||||
|
if err = cred.update(password); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
credentials[username] = cred
|
||||||
|
return saveCredentials(envvar.credentialFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authListMain(command string, args []string) (err error) {
|
||||||
|
if 0 != len(args) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"'%s list' does not accept any arguments, try '%s help'",
|
||||||
|
command, command,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = loadCredentials(envvar.credentialFile); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for username := range credentials {
|
||||||
|
fmt.Println(username)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func authRemoveMain(command string, args []string) (err error) {
|
||||||
|
if 1 != len(args) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"'%s remove' requires exactly one argument, try '%s help'",
|
||||||
|
command, command,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
username := args[0]
|
||||||
|
if err = loadCredentials(envvar.credentialFile); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, found := credentials[username]; !found {
|
||||||
|
return fmt.Errorf("user '%s' doesn't exist", username)
|
||||||
|
}
|
||||||
|
delete(credentials, username)
|
||||||
|
return saveCredentials(envvar.credentialFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authUpdateMain(command string, args []string) (err error) {
|
||||||
|
if 0 == len(args) || 2 < len(args) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"wrong number of arguments supplied to: '%s update', try '%s help'",
|
||||||
|
command, command,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
username := args[0]
|
||||||
|
var password string
|
||||||
|
if 1 < len(args) {
|
||||||
|
password = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = loadCredentials(envvar.credentialFile); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cred, found := credentials[username]
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"user '%s' doesn't exist, use 'add' in place of 'update'",
|
||||||
|
username,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if 0 == len(password) {
|
||||||
|
if password, err = getPassword(); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = cred.update(password); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return saveCredentials(envvar.credentialFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authMain(command string, args []string) {
|
||||||
|
// Subcommand not supplied. Redirect to help.
|
||||||
|
if 0 == len(args) {
|
||||||
|
log.Fatalf(
|
||||||
|
"no arguments supplied to: '%s', try '%s help'",
|
||||||
|
command, command,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
subCommand := args[0]
|
||||||
|
args = args[1:]
|
||||||
|
|
||||||
|
if strings.Contains(subCommand, "help") {
|
||||||
|
fmt.Println(authHelp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 == len(envvar.credentialFile) {
|
||||||
|
log.Fatalln("credential file required but not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch subCommand {
|
||||||
|
case "add":
|
||||||
|
err = authAddMain(command, args)
|
||||||
|
case "list":
|
||||||
|
err = authListMain(command, args)
|
||||||
|
case "remove":
|
||||||
|
err = authRemoveMain(command, args)
|
||||||
|
case "update":
|
||||||
|
err = authUpdateMain(command, args)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"unrecognized command '%s %s', try '%s help'",
|
||||||
|
command, subCommand, command,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if nil != err {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
// Collect environment variables.
|
||||||
|
envvar.credentialFile = env("CREDENTIALS", "")
|
||||||
|
envvar.fastAuth = env("FAST_AUTH", "")
|
||||||
|
envvar.folder = env("FOLDER", "/web") + "/"
|
||||||
|
envvar.host = env("HOST", "")
|
||||||
|
envvar.port = env("PORT", "8080")
|
||||||
|
envvar.showListing = envAsBool("SHOW_LISTING", true)
|
||||||
|
envvar.tlsCert = env("TLS_CERT", "")
|
||||||
|
envvar.tlsKey = env("TLS_KEY", "")
|
||||||
|
envvar.urlPrefix = env("URL_PREFIX", "")
|
||||||
|
|
||||||
// Evaluate and execute subcommand if supplied.
|
// Evaluate and execute subcommand if supplied.
|
||||||
|
appName := os.Args[0]
|
||||||
if 1 < len(os.Args) {
|
if 1 < len(os.Args) {
|
||||||
arg := os.Args[1]
|
arg := os.Args[1]
|
||||||
|
command := fmt.Sprintf("%s %s", appName, arg)
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(arg, "help"):
|
case strings.Contains(arg, "help"):
|
||||||
fmt.Println(help)
|
fmt.Println(help)
|
||||||
case strings.Contains(arg, "version"):
|
case strings.Contains(arg, "version"):
|
||||||
fmt.Println(version)
|
fmt.Println(version)
|
||||||
|
case "auth" == arg:
|
||||||
|
authMain(command, os.Args[2:])
|
||||||
default:
|
default:
|
||||||
name := os.Args[0]
|
log.Fatalf("Unknown argument: %s. Try '%s help'.", arg, appName)
|
||||||
log.Fatalf("Unknown argument: %s. Try '%s help'.", arg, name)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect environment variables.
|
|
||||||
folder := env("FOLDER", "/web") + "/"
|
|
||||||
host := env("HOST", "")
|
|
||||||
port := env("PORT", "8080")
|
|
||||||
showListing := envAsBool("SHOW_LISTING", true)
|
|
||||||
tlsCert := env("TLS_CERT", "")
|
|
||||||
tlsKey := env("TLS_KEY", "")
|
|
||||||
urlPrefix := env("URL_PREFIX", "")
|
|
||||||
|
|
||||||
// If HTTPS is to be used, verify both TLS_* environment variables are set.
|
// If HTTPS is to be used, verify both TLS_* environment variables are set.
|
||||||
if 0 < len(tlsCert) || 0 < len(tlsKey) {
|
if 0 < len(envvar.tlsCert) || 0 < len(envvar.tlsKey) {
|
||||||
if 0 == len(tlsCert) || 0 == len(tlsKey) {
|
if 0 == len(envvar.tlsCert) || 0 == len(envvar.tlsKey) {
|
||||||
log.Fatalln(
|
log.Fatalln(
|
||||||
"If value for environment variable 'TLS_CERT' or 'TLS_KEY' is set " +
|
"If value for environment variable 'TLS_CERT' or 'TLS_KEY' is set " +
|
||||||
"then value for environment variable 'TLS_KEY' or 'TLS_CERT' must " +
|
"then value for environment variable 'TLS_KEY' or 'TLS_CERT' must " +
|
||||||
@ -146,28 +509,80 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the URL path prefix is to be used, verify it is properly formatted.
|
// If the URL path prefix is to be used, verify it is properly formatted.
|
||||||
if 0 < len(urlPrefix) &&
|
if 0 < len(envvar.urlPrefix) &&
|
||||||
(!strings.HasPrefix(urlPrefix, "/") || strings.HasSuffix(urlPrefix, "/")) {
|
(!strings.HasPrefix(envvar.urlPrefix, "/") || strings.HasSuffix(envvar.urlPrefix, "/")) {
|
||||||
log.Fatalln(
|
log.Fatalln(
|
||||||
"Value for environment variable 'URL_PREFIX' must start " +
|
"Value for environment variable 'URL_PREFIX' must start " +
|
||||||
"with '/' and not end with '/'. Example: '/my/prefix'",
|
"with '/' and not end with '/'. Example: '/my/prefix'",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine whether basic authentication is needed.
|
||||||
|
auth := func(handler http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
if 0 < len(envvar.credentialFile) || 0 < len(envvar.fastAuth) {
|
||||||
|
if 0 < len(envvar.credentialFile) {
|
||||||
|
if err := loadCredentials(envvar.credentialFile); nil != err {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
credentials = make(map[string]*credential)
|
||||||
|
parts := strings.Split(envvar.fastAuth, ":")
|
||||||
|
if 2 != len(parts) || 0 == len(parts[0]) || 0 == len(parts[1]) {
|
||||||
|
log.Fatalln(
|
||||||
|
"'FAST_AUTH' must have exactly one colon (:) to separate " +
|
||||||
|
"the username from the password (username:password)",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
cred := &credential{}
|
||||||
|
if err := cred.update(parts[1]); nil != err {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
credentials[parts[0]] = cred
|
||||||
|
}
|
||||||
|
auth = func(handler http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
if !ok || 0 == len(username) || 0 == len(password) {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cred, ok := credentials[username]
|
||||||
|
if !ok {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
allowed, err := cred.matches(password)
|
||||||
|
if nil != err {
|
||||||
|
log.Println(err)
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Choose and set the appropriate, optimized static file serving function.
|
// Choose and set the appropriate, optimized static file serving function.
|
||||||
var handler http.HandlerFunc
|
var handler http.HandlerFunc
|
||||||
if 0 == len(urlPrefix) {
|
if 0 == len(envvar.urlPrefix) {
|
||||||
handler = handleListing(showListing, basicHandler(folder))
|
handler = handleListing(envvar.showListing, basicHandler(envvar.folder))
|
||||||
} else {
|
} else {
|
||||||
handler = handleListing(showListing, prefixHandler(folder, urlPrefix))
|
handler = handleListing(envvar.showListing, prefixHandler(envvar.folder, envvar.urlPrefix))
|
||||||
}
|
}
|
||||||
http.HandleFunc("/", handler)
|
http.HandleFunc("/", auth(handler))
|
||||||
|
|
||||||
// Serve files over HTTP or HTTPS based on paths to TLS files being provided.
|
// Serve files over HTTP or HTTPS based on paths to TLS files being provided.
|
||||||
if 0 == len(tlsCert) {
|
if 0 == len(envvar.tlsCert) {
|
||||||
log.Fatalln(http.ListenAndServe(host+":"+port, nil))
|
log.Fatalln(http.ListenAndServe(envvar.host+":"+envvar.port, nil))
|
||||||
} else {
|
} else {
|
||||||
log.Fatalln(http.ListenAndServeTLS(host+":"+port, tlsCert, tlsKey, nil))
|
log.Fatalln(http.ListenAndServeTLS(envvar.host+":"+envvar.port, envvar.tlsCert, envvar.tlsKey, nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +591,7 @@ func main() {
|
|||||||
// will attempt to retrieve the index file of that directory.
|
// will attempt to retrieve the index file of that directory.
|
||||||
func handleListing(show bool, serve http.HandlerFunc) http.HandlerFunc {
|
func handleListing(show bool, serve http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if show || strings.HasSuffix(r.URL.Path, "/") {
|
if !show && strings.HasSuffix(r.URL.Path, "/") {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -203,6 +618,58 @@ func prefixHandler(folder, urlPrefix string) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadCredentials from an existing credentials file.
|
||||||
|
func loadCredentials(filename string) (err error) {
|
||||||
|
credentials = make(map[string]*credential)
|
||||||
|
if 0 == len(filename) {
|
||||||
|
return errors.New("credential file name not set but is required")
|
||||||
|
}
|
||||||
|
var contents []byte
|
||||||
|
if contents, err = ioutil.ReadFile(filename); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return json.Unmarshal(contents, &credentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveCredentials to the specified credentials file.
|
||||||
|
func saveCredentials(filename string) (err error) {
|
||||||
|
if 0 == len(filename) {
|
||||||
|
return errors.New("credential file name not set but is required")
|
||||||
|
}
|
||||||
|
var contents []byte
|
||||||
|
if contents, err = json.Marshal(&credentials); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(filename, contents, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPassword from the user via the terminal. Mask all characters.
|
||||||
|
func getPassword() (password string, err error) {
|
||||||
|
maskInput := true
|
||||||
|
var rawPassword []byte
|
||||||
|
var rawConfirmPassword []byte
|
||||||
|
if rawPassword, err = gopass.GetPasswdPrompt(
|
||||||
|
"New password:", maskInput, os.Stdin, os.Stdout,
|
||||||
|
); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if 0 == len(rawPassword) {
|
||||||
|
err = errors.New("password may not be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if rawConfirmPassword, err = gopass.GetPasswdPrompt(
|
||||||
|
"Confirm password:", maskInput, os.Stdin, os.Stdout,
|
||||||
|
); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !bytes.Equal(rawPassword, rawConfirmPassword) {
|
||||||
|
err = errors.New("passwords do not match")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
password = string(rawPassword)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// env returns the value for an environment variable or, if not set, a fallback
|
// env returns the value for an environment variable or, if not set, a fallback
|
||||||
// value.
|
// value.
|
||||||
func env(key, fallback string) string {
|
func env(key, fallback string) string {
|
||||||
|
Loading…
Reference in New Issue
Block a user