auth.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  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. CookieSetSameSitePolicy := http.SameSiteNoneMode
  254. if r.TLS == nil {
  255. //Connection is done via http
  256. CookieSetSameSitePolicy = http.SameSiteLaxMode
  257. }
  258. //Check if remember me is clicked. If yes, set the maxage to 1 week.
  259. if rememberme {
  260. session.Options = &sessions.Options{
  261. MaxAge: 3600 * 24 * 7, //One week
  262. Path: "/",
  263. SameSite: CookieSetSameSitePolicy,
  264. }
  265. } else {
  266. session.Options = &sessions.Options{
  267. MaxAge: 3600 * 1, //One hour
  268. Path: "/",
  269. SameSite: CookieSetSameSitePolicy,
  270. }
  271. }
  272. session.Save(r, w)
  273. }
  274. // Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
  275. func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
  276. username, _ := a.GetUserName(w, r)
  277. if username != "" {
  278. log.Println(username + " logged out.")
  279. }
  280. //Clear user switchable account pools
  281. fallbackAccount, _ := a.SwitchableAccountManager.HandleLogoutforUser(w, r)
  282. // Revoke users authentication
  283. err := a.Logout(w, r)
  284. if err != nil {
  285. sendErrorResponse(w, "Logout failed")
  286. return
  287. }
  288. if fallbackAccount != "" {
  289. //Switch to fallback account
  290. a.LoginUserByRequest(w, r, fallbackAccount, true)
  291. }
  292. w.Write([]byte("OK"))
  293. }
  294. func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
  295. session, err := a.SessionStore.Get(r, a.SessionName)
  296. if err != nil {
  297. return err
  298. }
  299. session.Values["authenticated"] = false
  300. session.Values["username"] = nil
  301. session.Save(r, w)
  302. return nil
  303. }
  304. // Get the current session username from request
  305. func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string, error) {
  306. if a.CheckAuth(r) {
  307. //This user has logged in.
  308. session, _ := a.SessionStore.Get(r, a.SessionName)
  309. return session.Values["username"].(string), nil
  310. } else {
  311. //This user has not logged in.
  312. return "", errors.New("User not logged in")
  313. }
  314. }
  315. // Check if the user has logged in, return true / false in JSON
  316. func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
  317. if a.CheckAuth(r) {
  318. sendJSONResponse(w, "true")
  319. } else {
  320. sendJSONResponse(w, "false")
  321. }
  322. }
  323. // Handle new user register. Require POST username, password, group.
  324. func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request) {
  325. userCount := a.GetUserCounts()
  326. //Get username from request
  327. newusername, err := utils.PostPara(r, "username")
  328. if err != nil {
  329. sendTextResponse(w, "Error. Missing 'username' paramter")
  330. return
  331. }
  332. //Get password from request
  333. password, err := utils.PostPara(r, "password")
  334. if err != nil {
  335. sendTextResponse(w, "Error. Missing 'password' paramter")
  336. return
  337. }
  338. //Set permission group to default
  339. group, err := utils.PostPara(r, "group")
  340. if err != nil {
  341. sendTextResponse(w, "Error. Missing 'group' paramter")
  342. return
  343. }
  344. //Check if the number of users in the system is == 0. If yes, there are no need to login before registering new user
  345. if userCount > 0 {
  346. //Require login to create new user
  347. if a.CheckAuth(r) == false {
  348. //System have more than one person and this user is not logged in
  349. sendErrorResponse(w, "Login is needed to create new user")
  350. return
  351. }
  352. }
  353. //Ok to proceed create this user
  354. err = a.CreateUserAccount(newusername, password, []string{group})
  355. if err != nil {
  356. sendErrorResponse(w, err.Error())
  357. return
  358. }
  359. //Return to the client with OK
  360. sendOK(w)
  361. log.Println("[System Auth] New user " + newusername + " added to system.")
  362. return
  363. }
  364. // Check authentication from request header's session value
  365. func (a *AuthAgent) CheckAuth(r *http.Request) bool {
  366. session, _ := a.SessionStore.Get(r, a.SessionName)
  367. // Check if user is authenticated
  368. if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
  369. return false
  370. }
  371. return true
  372. }
  373. // Handle de-register of users. Require POST username.
  374. // THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
  375. func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
  376. //Check if the user is logged in
  377. if !a.CheckAuth(r) {
  378. //This user has not logged in
  379. sendErrorResponse(w, "Login required to remove user from the system.")
  380. return
  381. }
  382. //Check for permission of this user.
  383. /*
  384. if !system_permission_checkUserIsAdmin(w,r){
  385. //This user is not admin. No permission to access this function
  386. sendErrorResponse(w, "Permission denied")
  387. }
  388. */
  389. //Get username from request
  390. username, err := utils.PostPara(r, "username")
  391. if err != nil {
  392. sendErrorResponse(w, "Missing 'username' paramter")
  393. return
  394. }
  395. err = a.UnregisterUser(username)
  396. if err != nil {
  397. sendErrorResponse(w, err.Error())
  398. return
  399. }
  400. //Return to the client with OK
  401. sendOK(w)
  402. log.Println("[system_auth] User " + username + " has been removed from the system.")
  403. return
  404. }
  405. func (a *AuthAgent) UnregisterUser(username string) error {
  406. //Check if the user exists in the system database.
  407. if !a.Database.KeyExists("auth", "passhash/"+username) {
  408. //This user do not exists.
  409. return errors.New("This user does not exists.")
  410. }
  411. //OK! Remove the user from the database
  412. a.Database.Delete("auth", "passhash/"+username)
  413. a.Database.Delete("auth", "group/"+username)
  414. a.Database.Delete("auth", "acstatus/"+username)
  415. a.Database.Delete("auth", "profilepic/"+username)
  416. //Remove the user's autologin tokens
  417. a.RemoveAutologinTokenByUsername(username)
  418. //Remove user from switchable accounts
  419. a.SwitchableAccountManager.RemoveUserFromAllSwitchableAccountPool(username)
  420. return nil
  421. }
  422. // Get the number of users in the system
  423. func (a *AuthAgent) GetUserCounts() int {
  424. entries, _ := a.Database.ListTable("auth")
  425. usercount := 0
  426. for _, keypairs := range entries {
  427. if strings.Contains(string(keypairs[0]), "passhash/") {
  428. //This is a user registry
  429. usercount++
  430. }
  431. }
  432. if usercount == 0 {
  433. log.Println("There are no user in the database.")
  434. }
  435. return usercount
  436. }
  437. // List all username within the system
  438. func (a *AuthAgent) ListUsers() []string {
  439. entries, _ := a.Database.ListTable("auth")
  440. results := []string{}
  441. for _, keypairs := range entries {
  442. if strings.Contains(string(keypairs[0]), "group/") {
  443. username := strings.Split(string(keypairs[0]), "/")[1]
  444. results = append(results, username)
  445. }
  446. }
  447. return results
  448. }
  449. // Check if the given username exists
  450. func (a *AuthAgent) UserExists(username string) bool {
  451. userpasswordhash := ""
  452. err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash)
  453. if err != nil || userpasswordhash == "" {
  454. return false
  455. }
  456. return true
  457. }
  458. // Update the session expire time given the request header.
  459. func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Request) bool {
  460. session, _ := a.SessionStore.Get(r, a.SessionName)
  461. if session.Values["authenticated"].(bool) {
  462. //User authenticated. Extend its expire time
  463. rememberme := session.Values["rememberMe"].(bool)
  464. //Extend the session expire time
  465. if rememberme {
  466. session.Options = &sessions.Options{
  467. MaxAge: 3600 * 24 * 7, //One week
  468. Path: "/",
  469. }
  470. } else {
  471. session.Options = &sessions.Options{
  472. MaxAge: 3600 * 1, //One hour
  473. Path: "/",
  474. }
  475. }
  476. session.Save(r, w)
  477. return true
  478. } else {
  479. return false
  480. }
  481. }
  482. // Create user account
  483. func (a *AuthAgent) CreateUserAccount(newusername string, password string, group []string) error {
  484. key := newusername
  485. hashedPassword := Hash(password)
  486. err := a.Database.Write("auth", "passhash/"+key, hashedPassword)
  487. if err != nil {
  488. return err
  489. }
  490. //Store this user's usergroup settings
  491. err = a.Database.Write("auth", "group/"+newusername, group)
  492. if err != nil {
  493. return err
  494. }
  495. return nil
  496. }
  497. // Hash the given raw string into sha512 hash
  498. func Hash(raw string) string {
  499. h := sha512.New()
  500. h.Write([]byte(raw))
  501. return hex.EncodeToString(h.Sum(nil))
  502. }