mirror of
https://github.com/coalaura/whiskr.git
synced 2025-09-08 08:39:53 +00:00
126 lines
2.4 KiB
Go
126 lines
2.4 KiB
Go
![]() |
package main
|
||
|
|
||
|
import (
|
||
|
"crypto/hmac"
|
||
|
"crypto/sha256"
|
||
|
"encoding/hex"
|
||
|
"encoding/json"
|
||
|
"net/http"
|
||
|
"strings"
|
||
|
|
||
|
"golang.org/x/crypto/bcrypt"
|
||
|
)
|
||
|
|
||
|
type AuthenticationRequest struct {
|
||
|
Username string `json:"username"`
|
||
|
Password string `json:"password"`
|
||
|
}
|
||
|
|
||
|
func (u *EnvUser) Signature(secret string) []byte {
|
||
|
mac := hmac.New(sha256.New, []byte(secret))
|
||
|
|
||
|
mac.Write([]byte(u.Password))
|
||
|
mac.Write([]byte(u.Username))
|
||
|
|
||
|
return mac.Sum(nil)
|
||
|
}
|
||
|
|
||
|
func (e *Environment) Authenticate(username, password string) *EnvUser {
|
||
|
user, ok := e.Authentication.lookup[username]
|
||
|
if !ok {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) != nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return user
|
||
|
}
|
||
|
|
||
|
func (e *Environment) SignAuthToken(user *EnvUser) string {
|
||
|
signature := user.Signature(e.Tokens.Secret)
|
||
|
|
||
|
return user.Username + ":" + hex.EncodeToString(signature)
|
||
|
}
|
||
|
|
||
|
func (e *Environment) VerifyAuthToken(token string) bool {
|
||
|
index := strings.Index(token, ":")
|
||
|
if index == -1 {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
username := token[:index]
|
||
|
|
||
|
user, ok := e.Authentication.lookup[username]
|
||
|
if !ok {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
signature, err := hex.DecodeString(token[index+1:])
|
||
|
if err != nil {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
expected := user.Signature(e.Tokens.Secret)
|
||
|
|
||
|
return hmac.Equal(signature, expected)
|
||
|
}
|
||
|
|
||
|
func IsAuthenticated(r *http.Request) bool {
|
||
|
if !env.Authentication.Enabled {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
cookie, err := r.Cookie("whiskr_token")
|
||
|
if err != nil {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return env.VerifyAuthToken(cookie.Value)
|
||
|
}
|
||
|
|
||
|
func Authenticate(next http.Handler) http.Handler {
|
||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
if !IsAuthenticated(r) {
|
||
|
RespondJson(w, http.StatusUnauthorized, map[string]any{
|
||
|
"error": "unauthorized",
|
||
|
})
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
next.ServeHTTP(w, r)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func HandleAuthentication(w http.ResponseWriter, r *http.Request) {
|
||
|
var request AuthenticationRequest
|
||
|
|
||
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||
|
RespondJson(w, http.StatusBadRequest, map[string]any{
|
||
|
"error": "missing username or password",
|
||
|
})
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
user := env.Authenticate(request.Username, request.Password)
|
||
|
if user == nil {
|
||
|
RespondJson(w, http.StatusUnauthorized, map[string]any{
|
||
|
"error": "invalid username or password",
|
||
|
})
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
http.SetCookie(w, &http.Cookie{
|
||
|
Name: "whiskr_token",
|
||
|
Value: env.SignAuthToken(user),
|
||
|
})
|
||
|
|
||
|
RespondJson(w, http.StatusOK, map[string]any{
|
||
|
"authenticated": true,
|
||
|
})
|
||
|
}
|