auth.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package auth
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "net/http"
  6. "os"
  7. "path/filepath"
  8. "sync"
  9. "github.com/boltdb/bolt"
  10. )
  11. // LogFunc is a function type for logging.
  12. type LogFunc func(format string, v ...interface{})
  13. // Options holds configuration for AuthManager.
  14. type Options struct {
  15. DBPath string
  16. Log LogFunc
  17. }
  18. // AuthManager handles authentication.
  19. type AuthManager struct {
  20. db *bolt.DB
  21. log LogFunc
  22. mu sync.RWMutex
  23. }
  24. const (
  25. authBucket = "auth"
  26. passKey = "password"
  27. )
  28. // NewAuthManager creates a new AuthManager.
  29. func NewAuthManager(opt Options) (*AuthManager, error) {
  30. dir := filepath.Dir(opt.DBPath)
  31. if _, err := os.Stat(dir); os.IsNotExist(err) {
  32. if err := os.MkdirAll(dir, 0755); err != nil {
  33. return nil, err
  34. }
  35. }
  36. db, err := bolt.Open(opt.DBPath, 0755, nil)
  37. if err != nil {
  38. return nil, err
  39. }
  40. // Ensure bucket exists
  41. err = db.Update(func(tx *bolt.Tx) error {
  42. _, err := tx.CreateBucketIfNotExists([]byte(authBucket))
  43. return err
  44. })
  45. if err != nil {
  46. db.Close()
  47. return nil, err
  48. }
  49. return &AuthManager{db: db, log: opt.Log}, nil
  50. }
  51. // SetPassword sets the password (overwrites any existing).
  52. func (a *AuthManager) SetPassword(password string) error {
  53. a.mu.Lock()
  54. defer a.mu.Unlock()
  55. return a.db.Update(func(tx *bolt.Tx) error {
  56. b := tx.Bucket([]byte(authBucket))
  57. return b.Put([]byte(passKey), []byte(password))
  58. })
  59. }
  60. // ChangePassword changes password if oldpassword matches.
  61. func (a *AuthManager) ChangePassword(oldPassword, newPassword string) error {
  62. a.mu.Lock()
  63. defer a.mu.Unlock()
  64. return a.db.Update(func(tx *bolt.Tx) error {
  65. b := tx.Bucket([]byte(authBucket))
  66. stored := b.Get([]byte(passKey))
  67. if stored == nil || string(stored) != oldPassword {
  68. return errors.New("old password incorrect")
  69. }
  70. return b.Put([]byte(passKey), []byte(newPassword))
  71. })
  72. }
  73. // ResetPassword removes the password.
  74. func (a *AuthManager) ResetPassword() error {
  75. a.mu.Lock()
  76. defer a.mu.Unlock()
  77. return a.db.Update(func(tx *bolt.Tx) error {
  78. b := tx.Bucket([]byte(authBucket))
  79. return b.Delete([]byte(passKey))
  80. })
  81. }
  82. // ValidatePassword checks password from request
  83. func (a *AuthManager) ValidatePassword(password string) (bool, error) {
  84. a.mu.RLock()
  85. defer a.mu.RUnlock()
  86. var ok bool
  87. err := a.db.View(func(tx *bolt.Tx) error {
  88. b := tx.Bucket([]byte(authBucket))
  89. stored := b.Get([]byte(passKey))
  90. ok = stored != nil && string(stored) == password
  91. return nil
  92. })
  93. return ok, err
  94. }
  95. // UserIsLoggedIn checks if the user is logged in via cookie.
  96. func (a *AuthManager) UserIsLoggedIn(r *http.Request) bool {
  97. cookie, err := r.Cookie("dezukvm_auth")
  98. return err == nil && cookie.Value == "1"
  99. }
  100. // HandleFunc wraps an http.HandlerFunc with auth check.
  101. func (a *AuthManager) HandleFunc(pattern string, handler http.HandlerFunc, mux *http.ServeMux) {
  102. mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
  103. if a.UserIsLoggedIn(r) {
  104. handler(w, r)
  105. return
  106. }
  107. w.WriteHeader(http.StatusUnauthorized)
  108. w.Header().Set("Content-Type", "application/json")
  109. json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"})
  110. })
  111. }
  112. // LoginUser sets a session/cookie if password is correct
  113. func (a *AuthManager) LoginUser(w http.ResponseWriter, r *http.Request) error {
  114. var req struct {
  115. Password string `json:"password"`
  116. }
  117. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  118. return err
  119. }
  120. a.mu.RLock()
  121. defer a.mu.RUnlock()
  122. var ok bool
  123. err := a.db.View(func(tx *bolt.Tx) error {
  124. b := tx.Bucket([]byte(authBucket))
  125. stored := b.Get([]byte(passKey))
  126. ok = stored != nil && string(stored) == req.Password
  127. return nil
  128. })
  129. if err != nil {
  130. return err
  131. }
  132. if !ok {
  133. return errors.New("unauthorized")
  134. }
  135. // Set a simple session cookie
  136. http.SetCookie(w, &http.Cookie{
  137. Name: "dezukvm_auth",
  138. Value: "1",
  139. Path: "/",
  140. HttpOnly: true,
  141. MaxAge: 86400,
  142. })
  143. return nil
  144. }
  145. // LogoutUser removes the session/cookie for the user.
  146. func (a *AuthManager) LogoutUser(w http.ResponseWriter, r *http.Request) error {
  147. http.SetCookie(w, &http.Cookie{
  148. Name: "dezukvm_auth",
  149. Value: "",
  150. Path: "/",
  151. HttpOnly: true,
  152. MaxAge: -1,
  153. })
  154. return nil
  155. }
  156. // Close closes the underlying DB.
  157. func (a *AuthManager) Close() error {
  158. return a.db.Close()
  159. }