14.04.2026 Update
This commit is contained in:
211
handlers/auth.go
Normal file
211
handlers/auth.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"nano-router/auth"
|
||||
"nano-router/config"
|
||||
)
|
||||
|
||||
func HandleAuthChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
nonce := auth.Global.CreateChallenge()
|
||||
ok(w, map[string]string{"nonce": nonce})
|
||||
}
|
||||
|
||||
func HandleAuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Response string `json:"response"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
fail(w, http.StatusBadRequest, "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
clientIP := auth.GetClientIP(r)
|
||||
sessionID, loginOk := auth.Global.Login(req.Nonce, req.Response, clientIP)
|
||||
if !loginOk {
|
||||
fail(w, http.StatusUnauthorized, "invalid credentials or expired challenge")
|
||||
return
|
||||
}
|
||||
|
||||
auth.SetSessionCookie(w, sessionID)
|
||||
username, _, isDefault := auth.Global.GetCredentials()
|
||||
ok(w, map[string]interface{}{
|
||||
"message": "authenticated",
|
||||
"default_password": isDefault,
|
||||
"username": username,
|
||||
})
|
||||
}
|
||||
|
||||
func HandleAuthLogout(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
token := auth.GetSessionToken(r)
|
||||
if token != "" {
|
||||
auth.Global.DestroySession(token)
|
||||
}
|
||||
auth.ClearSessionCookie(w)
|
||||
ok(w, map[string]string{"message": "logged out"})
|
||||
}
|
||||
|
||||
func HandleAuthStatus(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
username, apiKey, isDefault := auth.Global.GetCredentials()
|
||||
ok(w, map[string]interface{}{
|
||||
"authenticated": true,
|
||||
"username": username,
|
||||
"default_password": isDefault,
|
||||
"has_api_key": apiKey != "",
|
||||
})
|
||||
}
|
||||
|
||||
func HandleAuthProfile(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
username, apiKey, isDefault := auth.Global.GetCredentials()
|
||||
ok(w, map[string]interface{}{
|
||||
"username": username,
|
||||
"default_password": isDefault,
|
||||
"has_api_key": apiKey != "",
|
||||
"api_key_prefix": apiKeyPrefix(apiKey),
|
||||
})
|
||||
|
||||
case http.MethodPost:
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
OldPassword string `json:"old_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
NewPassword2 string `json:"new_password2"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
fail(w, http.StatusBadRequest, "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Username == "" && req.NewPassword == "" {
|
||||
fail(w, http.StatusBadRequest, "nothing to update")
|
||||
return
|
||||
}
|
||||
|
||||
usernameChanged := false
|
||||
passwordChanged := false
|
||||
|
||||
if req.NewPassword != "" {
|
||||
if req.OldPassword == "" {
|
||||
fail(w, http.StatusBadRequest, "current password required")
|
||||
return
|
||||
}
|
||||
oldHash := auth.HashPassword(req.OldPassword)
|
||||
if !auth.Global.VerifyPassword(oldHash) {
|
||||
fail(w, http.StatusUnauthorized, "current password incorrect")
|
||||
return
|
||||
}
|
||||
if req.NewPassword != req.NewPassword2 {
|
||||
fail(w, http.StatusBadRequest, "passwords do not match")
|
||||
return
|
||||
}
|
||||
if len(req.NewPassword) < 4 {
|
||||
fail(w, http.StatusBadRequest, "password too short (min 4 chars)")
|
||||
return
|
||||
}
|
||||
|
||||
newHash := auth.HashPassword(req.NewPassword)
|
||||
clientIP := auth.GetClientIP(r)
|
||||
newSessionID := auth.Global.UpdatePassword(newHash, clientIP)
|
||||
auth.SetSessionCookie(w, newSessionID)
|
||||
passwordChanged = true
|
||||
|
||||
cfg, _ := config.Load()
|
||||
cfg.Auth.PasswordHash = newHash
|
||||
if req.Username != "" {
|
||||
cfg.Auth.Username = req.Username
|
||||
auth.Global.UpdateUsername(req.Username)
|
||||
usernameChanged = true
|
||||
}
|
||||
config.Save(cfg)
|
||||
} else if req.Username != "" {
|
||||
auth.Global.UpdateUsername(req.Username)
|
||||
usernameChanged = true
|
||||
cfg, _ := config.Load()
|
||||
cfg.Auth.Username = req.Username
|
||||
config.Save(cfg)
|
||||
}
|
||||
|
||||
username, apiKey, isDefault := auth.Global.GetCredentials()
|
||||
result := map[string]interface{}{
|
||||
"username": username,
|
||||
"default_password": isDefault,
|
||||
"has_api_key": apiKey != "",
|
||||
"api_key_prefix": apiKeyPrefix(apiKey),
|
||||
}
|
||||
if passwordChanged {
|
||||
result["password_changed"] = true
|
||||
}
|
||||
if usernameChanged {
|
||||
result["username_changed"] = true
|
||||
}
|
||||
ok(w, result)
|
||||
|
||||
default:
|
||||
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func HandleAuthAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
key := generateAPIKey()
|
||||
auth.Global.SetAPIKey(key)
|
||||
|
||||
cfg, _ := config.Load()
|
||||
cfg.Auth.APIKey = key
|
||||
config.Save(cfg)
|
||||
|
||||
ok(w, map[string]interface{}{
|
||||
"api_key": key,
|
||||
})
|
||||
|
||||
case http.MethodDelete:
|
||||
auth.Global.SetAPIKey("")
|
||||
|
||||
cfg, _ := config.Load()
|
||||
cfg.Auth.APIKey = ""
|
||||
config.Save(cfg)
|
||||
|
||||
ok(w, map[string]string{"message": "api key revoked"})
|
||||
|
||||
default:
|
||||
fail(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func generateAPIKey() string {
|
||||
b := make([]byte, 32)
|
||||
rand.Read(b)
|
||||
return "ar_" + hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
func apiKeyPrefix(key string) string {
|
||||
if len(key) > 8 {
|
||||
return key[:8] + "..."
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user