auth.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. package raft
  2. import (
  3. "crypto/hmac"
  4. "crypto/sha1"
  5. "crypto/sha256"
  6. "encoding/base32"
  7. "encoding/binary"
  8. "encoding/hex"
  9. "encoding/json"
  10. "fmt"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "time"
  15. "igit.com/xbase/raft/db"
  16. )
  17. // ==================== Auth Constants ====================
  18. const (
  19. ActionRead = "read"
  20. ActionWrite = "write" // Includes Set, Del
  21. ActionAdmin = "admin" // Cluster ops
  22. SystemKeyPrefix = "system."
  23. AuthUserPrefix = "system.user."
  24. AuthRolePrefix = "system.role."
  25. AuthSessionPrefix = "system.session."
  26. AuthLockPrefix = "system.lock."
  27. AuthConfigKey = "system.config"
  28. )
  29. // ==================== Auth Data Structures ====================
  30. // LoginState tracks login failures (Synced via Raft)
  31. type LoginState struct {
  32. FailureCount int64 `json:"failure_count"`
  33. LastFailure int64 `json:"last_failure"` // Unix timestamp
  34. LockUntil int64 `json:"lock_until"` // Unix timestamp
  35. }
  36. // Constraint defines numeric constraints on values
  37. type Constraint struct {
  38. Min *float64 `json:"min,omitempty"`
  39. Max *float64 `json:"max,omitempty"`
  40. }
  41. // Permission defines access to a key pattern
  42. type Permission struct {
  43. KeyPattern string `json:"key"` // Prefix match. "*" means all.
  44. Actions []string `json:"actions"` // "read", "write", "admin"
  45. Constraint *Constraint `json:"constraint,omitempty"`
  46. }
  47. // Role defines a set of permissions
  48. type Role struct {
  49. Name string `json:"name"`
  50. Permissions []Permission `json:"permissions"`
  51. ParentRoles []string `json:"parent_roles,omitempty"` // Inherit permissions from these roles
  52. }
  53. // User defines a system user
  54. type User struct {
  55. Username string `json:"username"`
  56. PasswordHash string `json:"password_hash"` // SHA256(salt + password)
  57. Salt string `json:"salt"`
  58. Roles []string `json:"roles"`
  59. // Specific overrides
  60. AllowPermissions []Permission `json:"allow_permissions"`
  61. DenyPermissions []Permission `json:"deny_permissions"` // Higher priority
  62. Email string `json:"email"`
  63. Icon string `json:"icon"`
  64. IsOnline bool `json:"is_online"`
  65. MFASecret string `json:"mfa_secret"` // Base32 encoded TOTP secret
  66. MFAEnabled bool `json:"mfa_enabled"`
  67. WhitelistIPs []string `json:"whitelist_ips"`
  68. BlacklistIPs []string `json:"blacklist_ips"`
  69. // Optimized In-Memory Structure (Not serialized)
  70. EffectivePermissions []Permission `json:"-"`
  71. }
  72. // Session represents an active login session (Local Only)
  73. type Session struct {
  74. Token string `json:"token"`
  75. Username string `json:"username"`
  76. LoginIP string `json:"login_ip"`
  77. Expiry int64 `json:"expiry"` // Unix timestamp
  78. // Permission Cache: key+action -> bool
  79. permCache sync.Map `json:"-"`
  80. }
  81. // AuthConfig defines global auth settings
  82. type AuthConfig struct {
  83. Enabled bool `json:"enabled"`
  84. }
  85. // ==================== Auth Manager ====================
  86. // AuthManager handles authentication and authorization
  87. type AuthManager struct {
  88. server *KVServer
  89. mu sync.RWMutex
  90. // In-memory cache
  91. users map[string]*User
  92. roles map[string]*Role
  93. sessions map[string]*Session // Local sessions
  94. loginStates map[string]*LoginState // Synced login states
  95. enabled bool
  96. }
  97. // NewAuthManager creates a new AuthManager
  98. func NewAuthManager(server *KVServer) *AuthManager {
  99. return &AuthManager{
  100. server: server,
  101. users: make(map[string]*User),
  102. roles: make(map[string]*Role),
  103. sessions: make(map[string]*Session),
  104. loginStates: make(map[string]*LoginState),
  105. enabled: false,
  106. }
  107. }
  108. // HashPassword hashes a password with salt (Exported for tools)
  109. func HashPassword(password, salt string) string {
  110. hash := sha256.Sum256([]byte(salt + password))
  111. return hex.EncodeToString(hash[:])
  112. }
  113. // Internal helper
  114. func hashPassword(password, salt string) string {
  115. return HashPassword(password, salt)
  116. }
  117. // Helper to generate token
  118. func generateToken() string {
  119. // In a real system use crypto/rand, but here we use math/rand seeded in main or just simple timestamp mix for example
  120. // Since we can't easily access crypto/rand without import and "no external lib" usually implies standard lib only,
  121. // crypto/rand IS standard lib.
  122. h := sha256.Sum256([]byte(fmt.Sprintf("%d", time.Now().UnixNano())))
  123. return hex.EncodeToString(h[:])
  124. }
  125. // Helper for TOTP (Google Authenticator)
  126. func validateTOTP(secret string, code string) bool {
  127. // Decode base32 secret
  128. key, err := base32.StdEncoding.DecodeString(strings.ToUpper(secret))
  129. if err != nil {
  130. return false
  131. }
  132. // Current time steps (30s window)
  133. // Check current, prev, and next window to allow for slight skew
  134. now := time.Now().Unix()
  135. return checkTOTP(key, now, code) || checkTOTP(key, now-30, code) || checkTOTP(key, now+30, code)
  136. }
  137. func checkTOTP(key []byte, timestamp int64, code string) bool {
  138. step := timestamp / 30
  139. buf := make([]byte, 8)
  140. binary.BigEndian.PutUint64(buf, uint64(step))
  141. h := hmac.New(sha1.New, key)
  142. h.Write(buf)
  143. sum := h.Sum(nil)
  144. offset := sum[len(sum)-1] & 0xf
  145. val := int64(((int(sum[offset]) & 0x7f) << 24) |
  146. ((int(sum[offset+1]) & 0xff) << 16) |
  147. ((int(sum[offset+2]) & 0xff) << 8) |
  148. (int(sum[offset+3]) & 0xff))
  149. otp := val % 1000000
  150. expected := fmt.Sprintf("%06d", otp)
  151. return expected == code
  152. }
  153. // UpdateCache updates the internal cache based on key-value changes
  154. // Called by the state machine when applying logs
  155. func (am *AuthManager) UpdateCache(key, value string, isDelete bool) {
  156. am.mu.Lock()
  157. defer am.mu.Unlock()
  158. if key == AuthConfigKey {
  159. if isDelete {
  160. am.enabled = false
  161. } else {
  162. var config AuthConfig
  163. if err := json.Unmarshal([]byte(value), &config); err == nil {
  164. am.enabled = config.Enabled
  165. }
  166. }
  167. return
  168. }
  169. if strings.HasPrefix(key, AuthUserPrefix) {
  170. username := strings.TrimPrefix(key, AuthUserPrefix)
  171. if isDelete {
  172. delete(am.users, username)
  173. } else {
  174. var user User
  175. if err := json.Unmarshal([]byte(value), &user); err == nil {
  176. // Rebuild effective permissions immediately
  177. am.rebuildEffectivePermissions(&user)
  178. am.users[username] = &user
  179. am.server.Raft.config.Logger.Info("Updated user cache for %s", username)
  180. } else {
  181. am.server.Raft.config.Logger.Error("Failed to unmarshal user %s: %v", username, err)
  182. }
  183. }
  184. return
  185. }
  186. if strings.HasPrefix(key, AuthRolePrefix) {
  187. rolename := strings.TrimPrefix(key, AuthRolePrefix)
  188. if isDelete {
  189. delete(am.roles, rolename)
  190. } else {
  191. var role Role
  192. if err := json.Unmarshal([]byte(value), &role); err == nil {
  193. am.roles[rolename] = &role
  194. }
  195. }
  196. // When roles change, we must rebuild ALL users' effective permissions
  197. // This might be expensive if there are many users, but for "backend system" it's fine.
  198. for _, u := range am.users {
  199. am.rebuildEffectivePermissions(u)
  200. }
  201. return
  202. }
  203. if strings.HasPrefix(key, AuthLockPrefix) {
  204. username := strings.TrimPrefix(key, AuthLockPrefix)
  205. if isDelete {
  206. delete(am.loginStates, username)
  207. } else {
  208. var state LoginState
  209. if err := json.Unmarshal([]byte(value), &state); err == nil {
  210. am.loginStates[username] = &state
  211. }
  212. }
  213. return
  214. }
  215. // Session is local only, ignore AuthSessionPrefix from Raft (unless we decide to sync sessions later)
  216. if strings.HasPrefix(key, AuthSessionPrefix) {
  217. return
  218. }
  219. // Invalidate permission cache on any auth change
  220. // For simplicity, we clear all local session caches when Users/Roles change
  221. // (We could be more granular but this guarantees safety)
  222. for _, s := range am.sessions {
  223. s.permCache.Range(func(key, value interface{}) bool {
  224. s.permCache.Delete(key)
  225. return true
  226. })
  227. }
  228. }
  229. // rebuildEffectivePermissions flattens roles into User.EffectivePermissions
  230. // This is the "Special Memory Design" to ensure high performance
  231. func (am *AuthManager) rebuildEffectivePermissions(user *User) {
  232. // Optimization: Pre-allocate slice?
  233. var effective []Permission
  234. // 1. User Deny Permissions (Keep separate or handle in check? Logic says Deny > Allow)
  235. // We'll handle Deny explicitly in CheckPermission, but for Allow, we merge Roles.
  236. // Actually, the prompt says "efficient... prevent traversing role keys for every access".
  237. // So we should collect all ALLOW rules.
  238. // Collect all allow permissions
  239. // Order: User Allow -> Role Allow -> Parent Role Allow
  240. // User Allow
  241. effective = append(effective, user.AllowPermissions...)
  242. // Roles (Recursive collection with loop detection)
  243. visited := make(map[string]bool)
  244. var collectRoles func(roles []string)
  245. collectRoles = func(roles []string) {
  246. for _, rName := range roles {
  247. if visited[rName] {
  248. continue
  249. }
  250. visited[rName] = true
  251. if role, ok := am.roles[rName]; ok {
  252. effective = append(effective, role.Permissions...)
  253. collectRoles(role.ParentRoles)
  254. }
  255. }
  256. }
  257. collectRoles(user.Roles)
  258. user.EffectivePermissions = effective
  259. }
  260. // IsEnabled checks if auth is enabled
  261. func (am *AuthManager) IsEnabled() bool {
  262. am.mu.RLock()
  263. defer am.mu.RUnlock()
  264. return am.enabled
  265. }
  266. // CheckPermission checks if a user has permission to perform an action on a key
  267. func (am *AuthManager) CheckPermission(token string, key string, action string, value string) error {
  268. am.mu.RLock()
  269. enabled := am.enabled
  270. am.mu.RUnlock()
  271. if !enabled {
  272. return nil
  273. }
  274. // Internal system bypass
  275. if token == "SYSTEM_INTERNAL" {
  276. return nil
  277. }
  278. am.mu.RLock()
  279. session, ok := am.sessions[token]
  280. am.mu.RUnlock()
  281. if !ok {
  282. return fmt.Errorf("invalid or expired session")
  283. }
  284. if time.Now().Unix() > session.Expiry {
  285. return fmt.Errorf("session expired")
  286. }
  287. // Check Session Local Cache
  288. // Cache key: "action:key" (value is harder to cache due to constraints, but if value is empty/read, we can cache)
  289. // For writes with value constraints, caching is risky unless we include value in cache key or don't cache constraint checks.
  290. // For READS (action="read"), value is empty, so we can cache.
  291. // For WRITES without value (Del), or simple sets, we can cache if no constraints.
  292. // Let's safe cache only for Read or if we verify constraint logic is stable.
  293. // To be safe and simple: Cache the result of (key, action). If constraints exist, the checkPerms function will fail if value invalid.
  294. // But if we cache "true", we skip checkPerms.
  295. // So we can only cache if the permission granted did NOT depend on value constraints.
  296. // That's complex.
  297. // Simplified: Cache only "read" and "admin" actions?
  298. cacheKey := action + ":" + key
  299. if action == ActionRead {
  300. if val, hit := session.permCache.Load(cacheKey); hit {
  301. if allowed, ok := val.(bool); ok {
  302. if allowed {
  303. return nil
  304. } else {
  305. return fmt.Errorf("permission denied (cached)")
  306. }
  307. }
  308. }
  309. }
  310. am.mu.RLock()
  311. user, userOk := am.users[session.Username]
  312. am.mu.RUnlock()
  313. if !userOk {
  314. return fmt.Errorf("user not found")
  315. }
  316. err := am.checkUserPermission(user, key, action, value)
  317. // Update Cache for Read
  318. if action == ActionRead {
  319. session.permCache.Store(cacheKey, err == nil)
  320. }
  321. return err
  322. }
  323. func (am *AuthManager) checkUserPermission(user *User, key, action, value string) error {
  324. // 1. Check Deny Permissions (User level - Highest Priority)
  325. for _, perm := range user.DenyPermissions {
  326. if matchKey(perm.KeyPattern, key) && hasAction(perm.Actions, action) {
  327. return fmt.Errorf("permission denied (explicit deny)")
  328. }
  329. }
  330. // 2. Check Effective Permissions (Flattened Allow Rules)
  331. // This uses the pre-calculated list, avoiding role traversal
  332. if checkPerms(user.EffectivePermissions, key, action, value) {
  333. return nil
  334. }
  335. return fmt.Errorf("permission denied")
  336. }
  337. // checkRole and checkRoleFullAccess are deprecated by effective permissions model
  338. // but we keep checkRoleFullAccess logic optimized or use effective permissions?
  339. // Effective permissions might be large, but "HasFullAccess" usually looks for "*".
  340. // We can scan EffectivePermissions for "*".
  341. func matchKey(pattern, key string) bool {
  342. if pattern == "*" {
  343. return true
  344. }
  345. if pattern == key {
  346. return true
  347. }
  348. // Prefix match: "user.asin" matches "user.asin.china"
  349. if strings.HasSuffix(pattern, "*") {
  350. prefix := strings.TrimSuffix(pattern, "*")
  351. return strings.HasPrefix(key, prefix)
  352. }
  353. // Hierarchy match: pattern "a.b" matches "a.b.c"
  354. if strings.HasPrefix(key, pattern+".") {
  355. return true
  356. }
  357. return false
  358. }
  359. func hasAction(actions []string, target string) bool {
  360. for _, a := range actions {
  361. if a == target || a == "*" {
  362. return true
  363. }
  364. }
  365. return false
  366. }
  367. func checkPerms(perms []Permission, key, action, value string) bool {
  368. for _, perm := range perms {
  369. if matchKey(perm.KeyPattern, key) && hasAction(perm.Actions, action) {
  370. // Check Value Constraints if action is write
  371. if action == ActionWrite && perm.Constraint != nil && value != "" {
  372. // Try to parse value as float
  373. val, err := strconv.ParseFloat(value, 64)
  374. if err == nil {
  375. if perm.Constraint.Min != nil && val < *perm.Constraint.Min {
  376. return false
  377. }
  378. if perm.Constraint.Max != nil && val > *perm.Constraint.Max {
  379. return false
  380. }
  381. } else {
  382. // If value is not a number but constraint exists, what to do?
  383. // Strict mode: fail.
  384. return false
  385. }
  386. }
  387. return true
  388. }
  389. }
  390. return false
  391. }
  392. // Login authenticates a user and returns a token
  393. // Modified: Sessions are LOCAL, Lock State is CLUSTER-WIDE
  394. func (am *AuthManager) Login(username, password, totpCode, ip string) (string, error) {
  395. am.mu.RLock()
  396. if !am.enabled {
  397. am.mu.RUnlock()
  398. return "", fmt.Errorf("auth not enabled")
  399. }
  400. user, ok := am.users[username]
  401. lockState := am.loginStates[username]
  402. am.mu.RUnlock()
  403. if !ok {
  404. return "", fmt.Errorf("invalid credentials")
  405. }
  406. // 1. Check Lock State
  407. if lockState != nil && time.Now().Unix() < lockState.LockUntil {
  408. return "", fmt.Errorf("account locked, please try again in %d seconds", lockState.LockUntil-time.Now().Unix())
  409. }
  410. // 2. Check IP (Allow/Deny)
  411. if len(user.WhitelistIPs) > 0 {
  412. allowed := false
  413. for _, w := range user.WhitelistIPs {
  414. if w == ip {
  415. allowed = true
  416. break
  417. }
  418. }
  419. if !allowed {
  420. return "", fmt.Errorf("ip not allowed")
  421. }
  422. }
  423. if len(user.BlacklistIPs) > 0 {
  424. for _, b := range user.BlacklistIPs {
  425. if b == ip {
  426. return "", fmt.Errorf("ip blacklisted")
  427. }
  428. }
  429. }
  430. // 3. Verify Password
  431. pwdValid := hashPassword(password, user.Salt) == user.PasswordHash
  432. if !pwdValid {
  433. // Handle Failure Logic (Sync to Cluster)
  434. go am.handleLoginFailure(username, lockState)
  435. return "", fmt.Errorf("invalid credentials")
  436. }
  437. // 4. Verify TOTP
  438. if user.MFAEnabled {
  439. if totpCode == "" {
  440. return "", fmt.Errorf("mfa code required")
  441. }
  442. if !validateTOTP(user.MFASecret, totpCode) {
  443. // TOTP failure counts as login failure
  444. go am.handleLoginFailure(username, lockState)
  445. return "", fmt.Errorf("invalid mfa code")
  446. }
  447. }
  448. // 5. Login Success - Clear Lock if needed
  449. if lockState != nil && lockState.FailureCount > 0 {
  450. go am.handleLoginSuccess(username)
  451. }
  452. // 6. Create Session (Local Only)
  453. token := generateToken()
  454. session := &Session{
  455. Token: token,
  456. Username: username,
  457. LoginIP: ip,
  458. Expiry: time.Now().Add(24 * time.Hour).Unix(),
  459. }
  460. am.mu.Lock()
  461. am.sessions[token] = session
  462. am.mu.Unlock()
  463. return token, nil
  464. }
  465. func (am *AuthManager) handleLoginFailure(username string, state *LoginState) {
  466. // Calculate new state
  467. now := time.Now().Unix()
  468. newState := LoginState{
  469. LastFailure: now,
  470. }
  471. if state != nil {
  472. newState.FailureCount = state.FailureCount + 1
  473. } else {
  474. newState.FailureCount = 1
  475. }
  476. // Logic: 3 consecutive errors -> lock 1 min. Each subsequent error -> lock 1 min.
  477. if newState.FailureCount >= 3 {
  478. newState.LockUntil = now + 60 // 1 minute lock
  479. }
  480. data, _ := json.Marshal(newState)
  481. // We use Set (Async) because we don't want to block login failure response too long,
  482. // but SetSync is safer to ensure next attempt sees the lock.
  483. // Given the requirement "lock immediately", we should probably use SetSync or just accept slight delay.
  484. // Using Set for now to avoid blocking the user response too much, but for strictness SetSync is better.
  485. am.server.Set(AuthLockPrefix+username, string(data))
  486. }
  487. func (am *AuthManager) handleLoginSuccess(username string) {
  488. // Clear failure count
  489. // We could delete the key, or set failure count to 0
  490. am.server.Del(AuthLockPrefix + username)
  491. }
  492. // Logout invalidates a session (Local Only)
  493. func (am *AuthManager) Logout(token string) error {
  494. am.mu.Lock()
  495. defer am.mu.Unlock()
  496. delete(am.sessions, token)
  497. return nil
  498. }
  499. // GetSession returns session info
  500. func (am *AuthManager) GetSession(token string) (*Session, error) {
  501. am.mu.RLock()
  502. defer am.mu.RUnlock()
  503. session, ok := am.sessions[token]
  504. if !ok {
  505. return nil, fmt.Errorf("invalid session")
  506. }
  507. if time.Now().Unix() > session.Expiry {
  508. return nil, fmt.Errorf("session expired")
  509. }
  510. return session, nil
  511. }
  512. // GetUser returns user info (Public for internal use)
  513. func (am *AuthManager) GetUser(username string) (*User, error) {
  514. am.mu.RLock()
  515. defer am.mu.RUnlock()
  516. user, ok := am.users[username]
  517. if !ok {
  518. return nil, fmt.Errorf("user not found")
  519. }
  520. return user, nil
  521. }
  522. // RegisterUser creates a new user
  523. func (am *AuthManager) RegisterUser(username, password string, roles []string) error {
  524. am.mu.RLock()
  525. if _, exists := am.users[username]; exists {
  526. am.mu.RUnlock()
  527. return fmt.Errorf("user already exists")
  528. }
  529. am.mu.RUnlock()
  530. salt := fmt.Sprintf("%d", time.Now().UnixNano()) // Simple salt
  531. user := User{
  532. Username: username,
  533. Salt: salt,
  534. PasswordHash: hashPassword(password, salt),
  535. Roles: roles,
  536. }
  537. data, _ := json.Marshal(user)
  538. // Use SetSync to ensure user is created before returning
  539. return am.server.SetSync(AuthUserPrefix+username, string(data))
  540. }
  541. // ChangePassword updates a user's password
  542. func (am *AuthManager) ChangePassword(username, newPassword string) error {
  543. am.mu.RLock()
  544. user, exists := am.users[username]
  545. am.mu.RUnlock()
  546. if !exists {
  547. return fmt.Errorf("user not found")
  548. }
  549. newUser := *user // Shallow copy to modify
  550. newUser.Salt = fmt.Sprintf("%d", time.Now().UnixNano())
  551. newUser.PasswordHash = hashPassword(newPassword, newUser.Salt)
  552. data, _ := json.Marshal(newUser)
  553. // Use SetSync to ensure password is updated before returning
  554. return am.server.SetSync(AuthUserPrefix+username, string(data))
  555. }
  556. // UpdateUser updates generic fields of a user (including roles)
  557. func (am *AuthManager) UpdateUser(user User) error {
  558. am.mu.RLock()
  559. _, exists := am.users[user.Username]
  560. am.mu.RUnlock()
  561. if !exists {
  562. return fmt.Errorf("user not found")
  563. }
  564. // Ensure we don't overwrite passwordhash/salt blindly if they are empty
  565. // But usually UpdateUser is called with a fully populated user object from GetUser + mods
  566. // So we just save it.
  567. data, _ := json.Marshal(user)
  568. return am.server.SetSync(AuthUserPrefix+user.Username, string(data))
  569. }
  570. // CreateRole creates a new role
  571. func (am *AuthManager) CreateRole(name string) error {
  572. am.mu.RLock()
  573. if _, exists := am.roles[name]; exists {
  574. am.mu.RUnlock()
  575. return fmt.Errorf("role already exists")
  576. }
  577. am.mu.RUnlock()
  578. role := Role{Name: name}
  579. data, _ := json.Marshal(role)
  580. return am.server.SetSync(AuthRolePrefix+name, string(data))
  581. }
  582. // DeleteRole deletes a role
  583. func (am *AuthManager) DeleteRole(name string) error {
  584. am.mu.RLock()
  585. if _, exists := am.roles[name]; !exists {
  586. am.mu.RUnlock()
  587. return fmt.Errorf("role not found")
  588. }
  589. am.mu.RUnlock()
  590. return am.server.DelSync(AuthRolePrefix + name)
  591. }
  592. // UpdateRole updates an existing role
  593. func (am *AuthManager) UpdateRole(role Role) error {
  594. am.mu.RLock()
  595. if _, exists := am.roles[role.Name]; !exists {
  596. am.mu.RUnlock()
  597. return fmt.Errorf("role not found")
  598. }
  599. am.mu.RUnlock()
  600. data, _ := json.Marshal(role)
  601. return am.server.SetSync(AuthRolePrefix+role.Name, string(data))
  602. }
  603. // GetRole returns a role definition
  604. func (am *AuthManager) GetRole(name string) (*Role, error) {
  605. am.mu.RLock()
  606. defer am.mu.RUnlock()
  607. role, ok := am.roles[name]
  608. if !ok {
  609. return nil, fmt.Errorf("role not found")
  610. }
  611. return role, nil
  612. }
  613. // ListUsers lists all users
  614. func (am *AuthManager) ListUsers() []*User {
  615. am.mu.RLock()
  616. defer am.mu.RUnlock()
  617. users := make([]*User, 0, len(am.users))
  618. for _, u := range am.users {
  619. // Calculate IsOnline based on active sessions
  620. isOnline := false
  621. for _, s := range am.sessions {
  622. if s.Username == u.Username {
  623. if time.Now().Unix() <= s.Expiry {
  624. isOnline = true
  625. break
  626. }
  627. }
  628. }
  629. // Create a copy to avoid race conditions and not modify cache
  630. userCopy := *u
  631. userCopy.IsOnline = isOnline
  632. users = append(users, &userCopy)
  633. }
  634. return users
  635. }
  636. // ListRoles lists all roles
  637. func (am *AuthManager) ListRoles() []*Role {
  638. am.mu.RLock()
  639. defer am.mu.RUnlock()
  640. roles := make([]*Role, 0, len(am.roles))
  641. for _, r := range am.roles {
  642. roles = append(roles, r)
  643. }
  644. return roles
  645. }
  646. // HasFullAccess checks if the user has "*" permission on all keys (superuser)
  647. // allowing optimizations like pushing limits to DB engine.
  648. func (am *AuthManager) HasFullAccess(token string) bool {
  649. am.mu.RLock()
  650. defer am.mu.RUnlock()
  651. if !am.enabled {
  652. return true
  653. }
  654. if token == "SYSTEM_INTERNAL" {
  655. return true
  656. }
  657. session, ok := am.sessions[token]
  658. if !ok || time.Now().Unix() > session.Expiry {
  659. return false
  660. }
  661. user, ok := am.users[session.Username]
  662. if !ok {
  663. return false
  664. }
  665. // Must not have any deny permissions that could block "*"
  666. // Actually if Deny is empty, and Allow has "*", it's full access.
  667. // If Deny has something, we can't assume full access blindly.
  668. if len(user.DenyPermissions) > 0 {
  669. return false
  670. }
  671. // Check Allow
  672. // Use EffectivePermissions instead of looping user.AllowPermissions
  673. for _, perm := range user.EffectivePermissions {
  674. if perm.KeyPattern == "*" && hasAction(perm.Actions, "*") {
  675. return true
  676. }
  677. }
  678. // Check Roles - No longer needed as EffectivePermissions includes them
  679. // BUT, if user has many roles, flattening helps.
  680. return false
  681. }
  682. func (am *AuthManager) checkRoleFullAccess(roleName string, visited map[string]bool) bool {
  683. // Deprecated / Unused in new model
  684. return false
  685. }
  686. // Admin helpers to bootstrap
  687. func (am *AuthManager) CreateRootUser(password string) error {
  688. salt := "somesalt" // In production use random
  689. user := User{
  690. Username: "root",
  691. Salt: salt,
  692. PasswordHash: hashPassword(password, salt),
  693. AllowPermissions: []Permission{
  694. {KeyPattern: "*", Actions: []string{"*"}},
  695. },
  696. }
  697. data, _ := json.Marshal(user)
  698. return am.server.Set(AuthUserPrefix+"root", string(data))
  699. }
  700. func (am *AuthManager) EnableAuth() error {
  701. config := AuthConfig{Enabled: true}
  702. data, _ := json.Marshal(config)
  703. return am.server.Set(AuthConfigKey, string(data))
  704. }
  705. func (am *AuthManager) DisableAuth() error {
  706. // Only admin can do this via normal SetAuthenticated
  707. config := AuthConfig{Enabled: false}
  708. data, _ := json.Marshal(config)
  709. return am.server.Set(AuthConfigKey, string(data))
  710. }
  711. // LoadFromDB loads auth data from existing DB
  712. func (am *AuthManager) LoadFromDB() error {
  713. prefix := SystemKeyPrefix
  714. // We use the DB engine's index to find all system keys
  715. am.server.DB.Index.WalkPrefix(prefix, func(key string, entry db.IndexEntry) bool {
  716. // Read value from storage
  717. // We need to access Storage via Engine.
  718. // Engine.Storage is exported.
  719. val, err := am.server.DB.Storage.ReadValue(entry.ValueOffset)
  720. if err != nil {
  721. // Skip corrupted or missing values
  722. return true
  723. }
  724. // Update Cache
  725. // Note: We are essentially replaying the state into the cache
  726. am.UpdateCache(key, val, false)
  727. return true
  728. })
  729. return nil
  730. }