sermoni

"Service monitor" / cronjob status service
Log | Files | Refs

auth.go (3811B)


      1 package http
      2 
      3 import (
      4 	"crypto/rand"
      5 	"crypto/sha256"
      6 	"encoding/hex"
      7 	"encoding/json"
      8 	"fmt"
      9 	"io/ioutil"
     10 	"net/http"
     11 )
     12 
     13 // Deal with login, logout, and general security stuff
     14 
     15 const (
     16 	keyAuthenticated = "authenticated"
     17 	keyCSRFToken     = "csrfToken"
     18 	keyPassphrase    = "passphrase"
     19 	keySessionName   = "session"
     20 	headerCSRFToken  = "X-Csrf-Token"
     21 	headerPassHash   = "Pass-Hash"
     22 )
     23 
     24 // initHandler checks two things:
     25 // 1. If a CSRF token exists for the given session. Otherwise it creates it
     26 // 2. Whether the session is authenticated
     27 // It then returns an object on the form {"auth": true, "csrftoken": "<long string>", "pagetitle": "Page title"}
     28 // This is requested immediately when the website is loaded.
     29 func initHandler(w http.ResponseWriter, r *http.Request) {
     30 	session, _ := store.Get(r, keySessionName)
     31 	val := session.Values[keyCSRFToken]
     32 	token, ok := val.(string)
     33 	if !ok {
     34 		token = generateCSRFToken()
     35 		session.Values[keyCSRFToken] = token
     36 		session.Save(r, w) // TODO: Error handling, as always
     37 	}
     38 	b, _ := json.Marshal(struct {
     39 		PageTitle     string `json:"pagetitle"`
     40 		CSRFToken     string `json:"csrftoken"`
     41 		Authenticated bool   `json:"authenticated"`
     42 	}{
     43 		string(conf.PageTitle),
     44 		token,
     45 		authorized(r),
     46 	})
     47 	w.Write(b)
     48 }
     49 
     50 func loginHandler(w http.ResponseWriter, r *http.Request) {
     51 	if authorized(r) {
     52 		return
     53 	}
     54 	defer r.Body.Close() // needed?
     55 	content, err := ioutil.ReadAll(r.Body)
     56 	check(err)
     57 	var data map[string]string
     58 	json.Unmarshal(content, &data)
     59 	passphrase := data[keyPassphrase]
     60 	passhash := sha256.Sum256([]byte(passphrase))
     61 	// Converting byte slices to string is fine, it's how bytes.Equal does it
     62 	if string(passhash[:]) == string(conf.PassHash) {
     63 		session, _ := store.Get(r, keySessionName)
     64 		session.Values[keyAuthenticated] = true
     65 		err = session.Save(r, w)
     66 		check(err)
     67 		w.WriteHeader(http.StatusOK) // Not needed, just for readability?
     68 	} else {
     69 		w.WriteHeader(http.StatusUnauthorized)
     70 	}
     71 }
     72 
     73 func logoutHandler(w http.ResponseWriter, r *http.Request) {
     74 	session, _ := store.Get(r, keySessionName)
     75 	session.Values[keyAuthenticated] = false
     76 	err := session.Save(r, w)
     77 	check(err)
     78 	b, _ := json.Marshal(struct {
     79 		Info string `json:"info"`
     80 	}{"Logged out"})
     81 	w.Write(b)
     82 }
     83 
     84 func authorized(r *http.Request) bool {
     85 	session, _ := store.Get(r, keySessionName)
     86 	val := session.Values[keyAuthenticated]
     87 	auth, ok := val.(bool)
     88 	return ok && auth
     89 }
     90 
     91 // Middleware for the simple sermoni authentication scheme
     92 // Accepts authentication both with session cookie (typically web browser) and
     93 // a header on the format `Pass-Hash: <sha256sum of pass phrase>`
     94 func auth(handler http.HandlerFunc) http.Handler {
     95 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     96 		passHashHeader := r.Header[headerPassHash]
     97 		if passHashHeader != nil {
     98 			// compare to hex formatted version of correct hash
     99 			if passHashHeader[0] != fmt.Sprintf("%x", conf.PassHash) {
    100 				status := http.StatusUnauthorized
    101 				http.Error(w, "Invalid passphrase hash", status)
    102 				return
    103 			}
    104 		} else {
    105 			// store is the global CookieStore
    106 			if !authorized(r) || !csrfCheckPassed(r) {
    107 				status := http.StatusUnauthorized
    108 				http.Error(w, http.StatusText(status), status)
    109 				return
    110 			}
    111 		}
    112 		handler.ServeHTTP(w, r)
    113 	})
    114 }
    115 
    116 func csrfCheckPassed(r *http.Request) bool {
    117 	session, _ := store.Get(r, keySessionName)
    118 	// CSRF protect anything but GET requests
    119 	if r.Method == http.MethodGet {
    120 		return true
    121 	}
    122 	val := session.Values[keyCSRFToken]
    123 	rightToken, ok := val.(string)
    124 	if !ok {
    125 		panic("no CSRF token found")
    126 	}
    127 	tokenHeader := r.Header[headerCSRFToken]
    128 	if tokenHeader == nil {
    129 		return false
    130 	}
    131 	return tokenHeader[0] == rightToken
    132 }
    133 
    134 func generateCSRFToken() string {
    135 	randBytes := make([]byte, 32)
    136 	rand.Read(randBytes)
    137 	return hex.EncodeToString(randBytes)
    138 }