2020-08-14 09:42:45 +02:00
|
|
|
package apikeys
|
2015-10-10 21:26:47 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"encoding/base64"
|
2020-03-07 00:21:49 +01:00
|
|
|
"fmt"
|
2015-10-12 03:36:27 +02:00
|
|
|
"log"
|
2015-10-10 21:26:47 +02:00
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"golang.org/x/crypto/scrypt"
|
2020-03-07 00:21:49 +01:00
|
|
|
|
|
|
|
"github.com/zenazn/goji/web"
|
2015-10-10 21:26:47 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
scryptSalt = "linx-server"
|
|
|
|
scryptN = 16384
|
|
|
|
scryptr = 8
|
|
|
|
scryptp = 1
|
|
|
|
scryptKeyLen = 32
|
|
|
|
)
|
|
|
|
|
|
|
|
type AuthOptions struct {
|
|
|
|
AuthFile string
|
|
|
|
UnauthMethods []string
|
2020-08-14 09:42:45 +02:00
|
|
|
BasicAuth bool
|
|
|
|
SiteName string
|
|
|
|
SitePath string
|
2015-10-10 21:26:47 +02:00
|
|
|
}
|
|
|
|
|
2020-08-14 09:42:45 +02:00
|
|
|
type ApiKeysMiddleware struct {
|
2015-10-10 21:26:47 +02:00
|
|
|
successHandler http.Handler
|
2015-10-12 03:36:27 +02:00
|
|
|
authKeys []string
|
2015-10-10 21:26:47 +02:00
|
|
|
o AuthOptions
|
|
|
|
}
|
|
|
|
|
2020-08-14 09:42:45 +02:00
|
|
|
func ReadAuthKeys(authFile string) []string {
|
2015-10-12 04:30:01 +02:00
|
|
|
var authKeys []string
|
|
|
|
|
|
|
|
f, err := os.Open(authFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Failed to open authfile: ", err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
|
|
for scanner.Scan() {
|
|
|
|
authKeys = append(authKeys, scanner.Text())
|
|
|
|
}
|
|
|
|
|
|
|
|
err = scanner.Err()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Scanner error while reading authfile: ", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return authKeys
|
|
|
|
}
|
|
|
|
|
2020-08-14 09:42:45 +02:00
|
|
|
func CheckAuth(authKeys []string, key string) (result bool, err error) {
|
2015-10-14 22:13:29 +02:00
|
|
|
checkKey, err := scrypt.Key([]byte(key), []byte(scryptSalt), scryptN, scryptr, scryptp, scryptKeyLen)
|
2015-10-10 21:26:47 +02:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedKey := base64.StdEncoding.EncodeToString(checkKey)
|
2015-10-12 03:36:27 +02:00
|
|
|
for _, v := range authKeys {
|
|
|
|
if encodedKey == v {
|
2015-10-10 21:26:47 +02:00
|
|
|
result = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result = false
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-08-14 09:42:45 +02:00
|
|
|
func (a ApiKeysMiddleware) getSitePrefix() string {
|
|
|
|
prefix := a.o.SitePath
|
|
|
|
if len(prefix) <= 0 || prefix[0] != '/' {
|
|
|
|
prefix = "/" + prefix
|
|
|
|
}
|
|
|
|
return prefix
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a ApiKeysMiddleware) goodAuthorizationHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("Location", a.getSitePrefix())
|
|
|
|
w.WriteHeader(http.StatusFound)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a ApiKeysMiddleware) badAuthorizationHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if a.o.BasicAuth {
|
|
|
|
rs := ""
|
|
|
|
if a.o.SiteName != "" {
|
|
|
|
rs = fmt.Sprintf(` realm="%s"`, a.o.SiteName)
|
|
|
|
}
|
|
|
|
w.Header().Set("WWW-Authenticate", `Basic`+rs)
|
|
|
|
}
|
|
|
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a ApiKeysMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var successHandler http.Handler
|
|
|
|
prefix := a.getSitePrefix()
|
|
|
|
|
|
|
|
if r.URL.Path == prefix+"auth" {
|
|
|
|
successHandler = http.HandlerFunc(a.goodAuthorizationHandler)
|
|
|
|
} else {
|
|
|
|
successHandler = a.successHandler
|
|
|
|
}
|
|
|
|
|
|
|
|
if sliceContains(a.o.UnauthMethods, r.Method) && r.URL.Path != prefix+"auth" {
|
2015-10-10 21:26:47 +02:00
|
|
|
// allow unauthenticated methods
|
2020-08-14 09:42:45 +02:00
|
|
|
successHandler.ServeHTTP(w, r)
|
2015-10-10 21:26:47 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-10-14 22:13:29 +02:00
|
|
|
key := r.Header.Get("Linx-Api-Key")
|
2020-08-14 09:42:45 +02:00
|
|
|
if key == "" && a.o.BasicAuth {
|
2020-03-07 00:21:49 +01:00
|
|
|
_, password, ok := r.BasicAuth()
|
|
|
|
if ok {
|
|
|
|
key = password
|
|
|
|
}
|
|
|
|
}
|
2015-10-10 21:26:47 +02:00
|
|
|
|
2020-08-14 09:42:45 +02:00
|
|
|
result, err := CheckAuth(a.authKeys, key)
|
2015-10-10 21:26:47 +02:00
|
|
|
if err != nil || !result {
|
2020-08-14 09:42:45 +02:00
|
|
|
http.HandlerFunc(a.badAuthorizationHandler).ServeHTTP(w, r)
|
2015-10-10 21:26:47 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-08-14 09:42:45 +02:00
|
|
|
successHandler.ServeHTTP(w, r)
|
2015-10-10 21:26:47 +02:00
|
|
|
}
|
|
|
|
|
2020-08-14 09:42:45 +02:00
|
|
|
func NewApiKeysMiddleware(o AuthOptions) func(*web.C, http.Handler) http.Handler {
|
2020-03-07 00:21:49 +01:00
|
|
|
fn := func(c *web.C, h http.Handler) http.Handler {
|
2020-08-14 09:42:45 +02:00
|
|
|
return ApiKeysMiddleware{
|
2015-10-10 21:26:47 +02:00
|
|
|
successHandler: h,
|
2020-08-14 09:42:45 +02:00
|
|
|
authKeys: ReadAuthKeys(o.AuthFile),
|
2015-10-10 21:26:47 +02:00
|
|
|
o: o,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fn
|
|
|
|
}
|
|
|
|
|
|
|
|
func sliceContains(slice []string, s string) bool {
|
|
|
|
for _, v := range slice {
|
|
|
|
if s == v {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|