auth.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. package auth
  2. /*
  3. ArOZ Online Authentication Module
  4. author: tobychui
  5. This system make use of sessions (similar to PHP SESSION) to remember the user login.
  6. See https://gowebexamples.com/sessions/ for detail.
  7. Auth database are stored as the following key
  8. auth/login/{username}/passhash => hashed password
  9. auth/login/{username}/permission => permission level
  10. Other system variables related to auth
  11. auth/users/usercount => Number of users in the system
  12. Pre-requirement: imuslab.com/arozos/mod/database
  13. */
  14. import (
  15. "crypto/sha512"
  16. "errors"
  17. "net/http"
  18. "strconv"
  19. "strings"
  20. "sync"
  21. "encoding/hex"
  22. "log"
  23. "time"
  24. "github.com/gorilla/sessions"
  25. "imuslab.com/arozos/mod/auth/accesscontrol/blacklist"
  26. "imuslab.com/arozos/mod/auth/accesscontrol/whitelist"
  27. "imuslab.com/arozos/mod/auth/authlogger"
  28. "imuslab.com/arozos/mod/auth/explogin"
  29. db "imuslab.com/arozos/mod/database"
  30. "imuslab.com/arozos/mod/network"
  31. "imuslab.com/arozos/mod/utils"
  32. )
  33. type AuthAgent struct {
  34. //Session related
  35. SessionName string
  36. SessionStore *sessions.CookieStore
  37. Database *db.Database
  38. LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
  39. //Token related
  40. ExpireTime int64 //Set this to 0 to disable token access
  41. tokenStore sync.Map
  42. terminateTokenListener chan bool
  43. mutex *sync.Mutex
  44. //Autologin Related
  45. AllowAutoLogin bool
  46. autoLoginTokens []*AutoLoginToken
  47. //Exponential Delay Retry Handler
  48. ExpDelayHandler *explogin.ExpLoginHandler
  49. //IPLists manager
  50. WhitelistManager *whitelist.WhiteList
  51. BlacklistManager *blacklist.BlackList
  52. //Account Switcher
  53. SwitchableAccountManager *SwitchableAccountPoolManager
  54. //Logger
  55. Logger *authlogger.Logger
  56. }
  57. type AuthEndpoints struct {
  58. Login string
  59. Logout string
  60. Register string
  61. CheckLoggedIn string
  62. Autologin string
  63. }
  64. // Constructor
  65. func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
  66. store := sessions.NewCookieStore(key)
  67. err := sysdb.NewTable("auth")
  68. if err != nil {
  69. log.Println("Failed to create auth database. Terminating.")
  70. panic(err)
  71. }
  72. //Creat a ticker to clean out outdated token every 5 minutes
  73. ticker := time.NewTicker(300 * time.Second)
  74. done := make(chan bool)
  75. //Create a exponential login delay handler
  76. expLoginHandler := explogin.NewExponentialLoginHandler(2, 10800)
  77. //Create a new whitelist manager
  78. thisWhitelistManager := whitelist.NewWhitelistManager(sysdb)
  79. //Create a new blacklist manager
  80. thisBlacklistManager := blacklist.NewBlacklistManager(sysdb)
  81. //Create a new logger for logging all login request
  82. newLogger, err := authlogger.NewLogger()
  83. if err != nil {
  84. panic(err)
  85. }
  86. //Create a new AuthAgent object
  87. newAuthAgent := AuthAgent{
  88. SessionName: sessionName,
  89. SessionStore: store,
  90. Database: sysdb,
  91. LoginRedirectionHandler: loginRedirectionHandler,
  92. tokenStore: sync.Map{},
  93. ExpireTime: 120,
  94. terminateTokenListener: done,
  95. mutex: &sync.Mutex{},
  96. //Auto login management
  97. AllowAutoLogin: false,
  98. autoLoginTokens: []*AutoLoginToken{},
  99. //Blacklist management
  100. WhitelistManager: thisWhitelistManager,
  101. BlacklistManager: thisBlacklistManager,
  102. ExpDelayHandler: expLoginHandler,
  103. //Switchable Account Pool Manager
  104. Logger: newLogger,
  105. }
  106. poolManager := NewSwitchableAccountPoolManager(sysdb, &newAuthAgent, key)
  107. newAuthAgent.SwitchableAccountManager = poolManager
  108. //Create a timer to listen to its token storage
  109. go func(listeningAuthAgent *AuthAgent) {
  110. for {
  111. select {
  112. case <-done:
  113. return
  114. case <-ticker.C:
  115. listeningAuthAgent.ClearTokenStore()
  116. }
  117. }
  118. }(&newAuthAgent)
  119. //Return the authAgent
  120. return &newAuthAgent
  121. }
  122. // Close the authAgent listener
  123. func (a *AuthAgent) Close() {
  124. //Stop the token listening
  125. a.terminateTokenListener <- true
  126. //Close the auth logger database
  127. a.Logger.Close()
  128. }
  129. // This function will handle an http request and redirect to the given login address if not logged in
  130. func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
  131. if a.CheckAuth(r) {
  132. //User already logged in
  133. handler(w, r)
  134. } else {
  135. //User not logged in
  136. a.LoginRedirectionHandler(w, r)
  137. }
  138. }
  139. // Handle login request, require POST username and password
  140. func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
  141. //Get username from request using POST mode
  142. username, err := utils.PostPara(r, "username")
  143. if err != nil {
  144. //Username not defined
  145. log.Println("[System Auth] Someone trying to login with username: " + username)
  146. //Write to log
  147. a.Logger.LogAuth(r, false)
  148. sendErrorResponse(w, "Username not defined or empty.")
  149. return
  150. }
  151. //Get password from request using POST mode
  152. password, err := utils.PostPara(r, "password")
  153. if err != nil {
  154. //Password not defined
  155. a.Logger.LogAuth(r, false)
  156. sendErrorResponse(w, "Password not defined or empty.")
  157. return
  158. }
  159. //Get rememberme settings
  160. rememberme := false
  161. rmbme, _ := utils.PostPara(r, "rmbme")
  162. if rmbme == "true" {
  163. rememberme = true
  164. }
  165. //Check Exponential Login Handler
  166. ok, nextRetryIn := a.ExpDelayHandler.AllowImmediateAccess(username, r)
  167. if !ok {
  168. //Too many request! (maybe the account is under brute force attack?)
  169. a.ExpDelayHandler.AddUserRetrycount(username, r)
  170. sendErrorResponse(w, "Too many request! Next retry in "+strconv.Itoa(int(nextRetryIn))+" seconds")
  171. return
  172. }
  173. //Check the database and see if this user is in the database
  174. passwordCorrect, rejectionReason := a.ValidateUsernameAndPasswordWithReason(username, password)
  175. //The database contain this user information. Check its password if it is correct
  176. if passwordCorrect {
  177. //Password correct
  178. //Check if this request origin is allowed to access
  179. ok, reasons := a.ValidateLoginRequest(w, r)
  180. if !ok {
  181. sendErrorResponse(w, reasons.Error())
  182. return
  183. }
  184. // Set user as authenticated
  185. a.LoginUserByRequest(w, r, username, rememberme)
  186. //Reset user retry count if any
  187. a.ExpDelayHandler.ResetUserRetryCount(username, r)
  188. //Check if the current switchable account pool owner is this user.
  189. a.SwitchableAccountManager.MatchPoolCreatorOrResetPoolID(username, w, r)
  190. //Print the login message to console
  191. log.Println(username + " logged in.")
  192. a.Logger.LogAuth(r, true)
  193. sendOK(w)
  194. } else {
  195. //Password incorrect
  196. log.Println(username + " login request rejected: " + rejectionReason)
  197. //Add to retry count
  198. a.ExpDelayHandler.AddUserRetrycount(username, r)
  199. sendErrorResponse(w, rejectionReason)
  200. a.Logger.LogAuth(r, false)
  201. return
  202. }
  203. }
  204. func (a *AuthAgent) ValidateUsernameAndPassword(username string, password string) bool {
  205. succ, _ := a.ValidateUsernameAndPasswordWithReason(username, password)
  206. return succ
  207. }
  208. // validate the username and password, return reasons if the auth failed
  209. func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, password string) (bool, string) {
  210. hashedPassword := Hash(password)
  211. var passwordInDB string
  212. err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
  213. if err != nil {
  214. //User not found or db exception
  215. //log.Println("[System Auth] " + username + " login with incorrect password")
  216. return false, "Invalid username or password"
  217. }
  218. if passwordInDB == hashedPassword {
  219. return true, ""
  220. } else {
  221. return false, "Invalid username or password"
  222. }
  223. }
  224. // Validate the user request for login, return true if the target request original is not blocked
  225. func (a *AuthAgent) ValidateLoginRequest(w http.ResponseWriter, r *http.Request) (bool, error) {
  226. //Get the ip address of the request
  227. clientIP, err := network.GetIpFromRequest(r)
  228. if err != nil {
  229. return false, nil
  230. }
  231. return a.ValidateLoginIpAccess(clientIP)
  232. }
  233. func (a *AuthAgent) ValidateLoginIpAccess(ipv4 string) (bool, error) {
  234. ipv4 = strings.ReplaceAll(ipv4, " ", "")
  235. //Check if the account is whitelisted
  236. if a.WhitelistManager.Enabled && !a.WhitelistManager.IsWhitelisted(ipv4) {
  237. //Whitelist enabled but this IP is not whitelisted
  238. return false, errors.New("Your IP is not whitelisted on this host")
  239. }
  240. //Check if the account is banned
  241. if a.BlacklistManager.Enabled && a.BlacklistManager.IsBanned(ipv4) {
  242. //This user is banned
  243. return false, errors.New("Your IP is banned by this host")
  244. }
  245. return true, nil
  246. }
  247. // Login the user by creating a valid session for this user
  248. func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, username string, rememberme bool) {
  249. session, _ := a.SessionStore.Get(r, a.SessionName)
  250. session.Values["authenticated"] = true
  251. session.Values["username"] = username
  252. session.Values["rememberMe"] = rememberme
  253. //Check if remember me is clicked. If yes, set the maxage to 1 week.
  254. if rememberme {
  255. session.Options = &sessions.Options{
  256. MaxAge: 3600 * 24 * 7, //One week
  257. Path: "/",
  258. }
  259. } else {
  260. session.Options = &sessions.Options{
  261. MaxAge: 3600 * 1, //One hour
  262. Path: "/",
  263. }
  264. }
  265. session.Save(r, w)
  266. }
  267. // Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
  268. func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
  269. username, _ := a.GetUserName(w, r)
  270. if username != "" {
  271. log.Println(username + " logged out.")
  272. }
  273. //Clear user switchable account pools
  274. fallbackAccount, _ := a.SwitchableAccountManager.HandleLogoutforUser(w, r)
  275. // Revoke users authentication
  276. err := a.Logout(w, r)
  277. if err != nil {
  278. sendErrorResponse(w, "Logout failed")
  279. return
  280. }
  281. if fallbackAccount != "" {
  282. //Switch to fallback account
  283. a.LoginUserByRequest(w, r, fallbackAccount, true)
  284. }
  285. w.Write([]byte("OK"))
  286. }
  287. func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
  288. session, err := a.SessionStore.Get(r, a.SessionName)
  289. if err != nil {
  290. return err
  291. }
  292. session.Values["authenticated"] = false
  293. session.Values["username"] = nil
  294. session.Save(r, w)
  295. return nil
  296. }
  297. // Get the current session username from request
  298. func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string, error) {
  299. if a.CheckAuth(r) {
  300. //This user has logged in.
  301. session, _ := a.SessionStore.Get(r, a.SessionName)
  302. return session.Values["username"].(string), nil
  303. } else {
  304. //This user has not logged in.
  305. return "", errors.New("User not logged in")
  306. }
  307. }
  308. // Check if the user has logged in, return true / false in JSON
  309. func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
  310. if a.CheckAuth(r) {
  311. sendJSONResponse(w, "true")
  312. } else {
  313. sendJSONResponse(w, "false")
  314. }
  315. }
  316. // Handle new user register. Require POST username, password, group.
  317. func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request) {
  318. userCount := a.GetUserCounts()
  319. //Get username from request
  320. newusername, err := utils.PostPara(r, "username")
  321. if err != nil {
  322. sendTextResponse(w, "Error. Missing 'username' paramter")
  323. return
  324. }
  325. //Get password from request
  326. password, err := utils.PostPara(r, "password")
  327. if err != nil {
  328. sendTextResponse(w, "Error. Missing 'password' paramter")
  329. return
  330. }
  331. //Set permission group to default
  332. group, err := utils.PostPara(r, "group")
  333. if err != nil {
  334. sendTextResponse(w, "Error. Missing 'group' paramter")
  335. return
  336. }
  337. //Check if the number of users in the system is == 0. If yes, there are no need to login before registering new user
  338. if userCount > 0 {
  339. //Require login to create new user
  340. if a.CheckAuth(r) == false {
  341. //System have more than one person and this user is not logged in
  342. sendErrorResponse(w, "Login is needed to create new user")
  343. return
  344. }
  345. }
  346. //Ok to proceed create this user
  347. err = a.CreateUserAccount(newusername, password, []string{group})
  348. if err != nil {
  349. sendErrorResponse(w, err.Error())
  350. return
  351. }
  352. //Return to the client with OK
  353. sendOK(w)
  354. log.Println("[System Auth] New user " + newusername + " added to system.")
  355. return
  356. }
  357. // Check authentication from request header's session value
  358. func (a *AuthAgent) CheckAuth(r *http.Request) bool {
  359. session, _ := a.SessionStore.Get(r, a.SessionName)
  360. // Check if user is authenticated
  361. if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
  362. return false
  363. }
  364. return true
  365. }
  366. // Handle de-register of users. Require POST username.
  367. // THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
  368. func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
  369. //Check if the user is logged in
  370. if !a.CheckAuth(r) {
  371. //This user has not logged in
  372. sendErrorResponse(w, "Login required to remove user from the system.")
  373. return
  374. }
  375. //Check for permission of this user.
  376. /*
  377. if !system_permission_checkUserIsAdmin(w,r){
  378. //This user is not admin. No permission to access this function
  379. sendErrorResponse(w, "Permission denied")
  380. }
  381. */
  382. //Get username from request
  383. username, err := utils.PostPara(r, "username")
  384. if err != nil {
  385. sendErrorResponse(w, "Missing 'username' paramter")
  386. return
  387. }
  388. err = a.UnregisterUser(username)
  389. if err != nil {
  390. sendErrorResponse(w, err.Error())
  391. return
  392. }
  393. //Return to the client with OK
  394. sendOK(w)
  395. log.Println("[system_auth] User " + username + " has been removed from the system.")
  396. return
  397. }
  398. func (a *AuthAgent) UnregisterUser(username string) error {
  399. //Check if the user exists in the system database.
  400. if !a.Database.KeyExists("auth", "passhash/"+username) {
  401. //This user do not exists.
  402. return errors.New("This user does not exists.")
  403. }
  404. //OK! Remove the user from the database
  405. a.Database.Delete("auth", "passhash/"+username)
  406. a.Database.Delete("auth", "group/"+username)
  407. a.Database.Delete("auth", "acstatus/"+username)
  408. a.Database.Delete("auth", "profilepic/"+username)
  409. //Remove the user's autologin tokens
  410. a.RemoveAutologinTokenByUsername(username)
  411. //Remove user from switchable accounts
  412. a.SwitchableAccountManager.RemoveUserFromAllSwitchableAccountPool(username)
  413. return nil
  414. }
  415. // Get the number of users in the system
  416. func (a *AuthAgent) GetUserCounts() int {
  417. entries, _ := a.Database.ListTable("auth")
  418. usercount := 0
  419. for _, keypairs := range entries {
  420. if strings.Contains(string(keypairs[0]), "passhash/") {
  421. //This is a user registry
  422. usercount++
  423. }
  424. }
  425. if usercount == 0 {
  426. log.Println("There are no user in the database.")
  427. }
  428. return usercount
  429. }
  430. // List all username within the system
  431. func (a *AuthAgent) ListUsers() []string {
  432. entries, _ := a.Database.ListTable("auth")
  433. results := []string{}
  434. for _, keypairs := range entries {
  435. if strings.Contains(string(keypairs[0]), "group/") {
  436. username := strings.Split(string(keypairs[0]), "/")[1]
  437. results = append(results, username)
  438. }
  439. }
  440. return results
  441. }
  442. // Check if the given username exists
  443. func (a *AuthAgent) UserExists(username string) bool {
  444. userpasswordhash := ""
  445. err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash)
  446. if err != nil || userpasswordhash == "" {
  447. return false
  448. }
  449. return true
  450. }
  451. // Update the session expire time given the request header.
  452. func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Request) bool {
  453. session, _ := a.SessionStore.Get(r, a.SessionName)
  454. if session.Values["authenticated"].(bool) {
  455. //User authenticated. Extend its expire time
  456. rememberme := session.Values["rememberMe"].(bool)
  457. //Extend the session expire time
  458. if rememberme {
  459. session.Options = &sessions.Options{
  460. MaxAge: 3600 * 24 * 7, //One week
  461. Path: "/",
  462. }
  463. } else {
  464. session.Options = &sessions.Options{
  465. MaxAge: 3600 * 1, //One hour
  466. Path: "/",
  467. }
  468. }
  469. session.Save(r, w)
  470. return true
  471. } else {
  472. return false
  473. }
  474. }
  475. // Create user account
  476. func (a *AuthAgent) CreateUserAccount(newusername string, password string, group []string) error {
  477. key := newusername
  478. hashedPassword := Hash(password)
  479. err := a.Database.Write("auth", "passhash/"+key, hashedPassword)
  480. if err != nil {
  481. return err
  482. }
  483. //Store this user's usergroup settings
  484. err = a.Database.Write("auth", "group/"+newusername, group)
  485. if err != nil {
  486. return err
  487. }
  488. return nil
  489. }
  490. // Hash the given raw string into sha512 hash
  491. func Hash(raw string) string {
  492. h := sha512.New()
  493. h.Write([]byte(raw))
  494. return hex.EncodeToString(h.Sum(nil))
  495. }