auth.go 29 KB

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