f94f37507d
Signed-off-by: Tobias Erbshäußer <tobias@tesoft.dev>
137 lines
3.5 KiB
Go
137 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
)
|
|
|
|
type ApiHandler struct {
|
|
db *Database
|
|
authToken *string
|
|
}
|
|
|
|
const authTokenCookieName = "auth-token"
|
|
const isAuthorizedContextKey = "is-authorized"
|
|
const contentTypeHeaderKey = "Content-Type"
|
|
const JsonMimeType = "application/json"
|
|
|
|
func (h *ApiHandler) ServeLoginPost(writer http.ResponseWriter, request *http.Request) {
|
|
if !HasContentType(request, JsonMimeType) {
|
|
WriteError(writer, http.StatusBadRequest, "expected json body", nil)
|
|
return
|
|
}
|
|
|
|
bodyReader := request.Body
|
|
body, err := io.ReadAll(bodyReader)
|
|
_ = bodyReader.Close()
|
|
if err != nil {
|
|
WriteError(writer, http.StatusBadRequest, "failed to read body", err)
|
|
return
|
|
}
|
|
|
|
type LoginBody struct {
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
loginBody := LoginBody{}
|
|
err = json.Unmarshal(body, &loginBody)
|
|
if err != nil {
|
|
WriteError(writer, http.StatusBadRequest, "failed to read body", err)
|
|
return
|
|
}
|
|
|
|
success, err := h.db.ValidateRootPassword(loginBody.Password)
|
|
if err != nil {
|
|
WriteError(writer, http.StatusInternalServerError, "failed to read database", err)
|
|
return
|
|
}
|
|
|
|
if !success {
|
|
log.Printf("failed login from '%s'", request.RemoteAddr)
|
|
WriteError(writer, http.StatusUnauthorized, "invalid password", nil)
|
|
return
|
|
}
|
|
|
|
rawAuthToken := make([]byte, 128)
|
|
_, _ = rand.Read(rawAuthToken)
|
|
authToken := hex.EncodeToString(rawAuthToken)
|
|
h.authToken = &authToken
|
|
|
|
cookie := http.Cookie{}
|
|
cookie.Name = authTokenCookieName
|
|
cookie.Value = authToken
|
|
cookie.Secure = true
|
|
cookie.HttpOnly = true
|
|
http.SetCookie(writer, &cookie)
|
|
|
|
WriteResponse(writer, http.StatusOK, map[string]interface{}{})
|
|
|
|
log.Printf("successful login from '%s'", request.RemoteAddr)
|
|
}
|
|
|
|
func (h *ApiHandler) ServeLogoutPost(writer http.ResponseWriter, request *http.Request) {
|
|
cookie, _ := request.Cookie(authTokenCookieName)
|
|
if cookie != nil {
|
|
cookie := http.Cookie{}
|
|
cookie.Name = authTokenCookieName
|
|
cookie.Value = ""
|
|
cookie.Secure = true
|
|
cookie.HttpOnly = true
|
|
http.SetCookie(writer, &cookie)
|
|
}
|
|
|
|
h.authToken = nil
|
|
|
|
WriteResponse(writer, http.StatusOK, map[string]interface{}{})
|
|
|
|
log.Printf("successful logout from '%s'", request.RemoteAddr)
|
|
}
|
|
|
|
func (h *ApiHandler) ProcessAuth(next http.Handler, required bool) http.Handler {
|
|
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
isAuthorized := false
|
|
cookie, _ := request.Cookie(authTokenCookieName)
|
|
if cookie != nil {
|
|
isAuthorized = h.authToken != nil && *h.authToken == cookie.Value
|
|
}
|
|
|
|
if !isAuthorized && required {
|
|
WriteError(writer, http.StatusUnauthorized, "authentication required", nil)
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(writer, request.WithContext(context.WithValue(request.Context(), isAuthorizedContextKey, isAuthorized)))
|
|
})
|
|
}
|
|
|
|
func IsAuthorized(request *http.Request) bool {
|
|
value := request.Context().Value(isAuthorizedContextKey)
|
|
return value != nil && value.(bool)
|
|
}
|
|
|
|
func WriteResponse(writer http.ResponseWriter, code int, body any) {
|
|
writer.Header().Set(contentTypeHeaderKey, "application/json")
|
|
writer.WriteHeader(code)
|
|
_ = json.NewEncoder(writer).Encode(body)
|
|
}
|
|
|
|
func WriteError(writer http.ResponseWriter, code int, message string, err error) {
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
|
|
WriteResponse(writer, code, map[string]interface{}{
|
|
"message": message,
|
|
})
|
|
}
|
|
|
|
func HasContentType(request *http.Request, mimeType string) bool {
|
|
contentType := request.Header.Get(contentTypeHeaderKey)
|
|
return contentType == mimeType
|
|
}
|