auth.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  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. "strings"
  19. "sync"
  20. "encoding/hex"
  21. "log"
  22. "time"
  23. "github.com/gorilla/sessions"
  24. "imuslab.com/arozos/mod/auth/authlogger"
  25. db "imuslab.com/arozos/mod/database"
  26. )
  27. type AuthAgent struct {
  28. //Session related
  29. SessionName string
  30. SessionStore *sessions.CookieStore
  31. Database *db.Database
  32. LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
  33. //Token related
  34. ExpireTime int64 //Set this to 0 to disable token access
  35. tokenStore sync.Map
  36. terminateTokenListener chan bool
  37. mutex *sync.Mutex
  38. //Autologin Related
  39. AllowAutoLogin bool
  40. autoLoginTokens []*AutoLoginToken
  41. //Logger
  42. Logger *authlogger.Logger
  43. }
  44. type AuthEndpoints struct {
  45. Login string
  46. Logout string
  47. Register string
  48. CheckLoggedIn string
  49. Autologin string
  50. }
  51. //Constructor
  52. func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
  53. store := sessions.NewCookieStore(key)
  54. err := sysdb.NewTable("auth")
  55. if err != nil {
  56. log.Println("Failed to create auth database. Terminating.")
  57. panic(err)
  58. }
  59. //Creat a ticker to clean out outdated token every 5 minutes
  60. ticker := time.NewTicker(300 * time.Second)
  61. done := make(chan bool)
  62. //Create a new logger for logging all login request
  63. newLogger, err := authlogger.NewLogger()
  64. if err != nil {
  65. panic(err)
  66. }
  67. //Create a new AuthAgent object
  68. newAuthAgent := AuthAgent{
  69. SessionName: sessionName,
  70. SessionStore: store,
  71. Database: sysdb,
  72. LoginRedirectionHandler: loginRedirectionHandler,
  73. tokenStore: sync.Map{},
  74. ExpireTime: 120,
  75. terminateTokenListener: done,
  76. mutex: &sync.Mutex{},
  77. AllowAutoLogin: false,
  78. autoLoginTokens: []*AutoLoginToken{},
  79. Logger: newLogger,
  80. }
  81. //Create a timer to listen to its token storage
  82. go func(listeningAuthAgent *AuthAgent) {
  83. for {
  84. select {
  85. case <-done:
  86. return
  87. case <-ticker.C:
  88. listeningAuthAgent.ClearTokenStore()
  89. }
  90. }
  91. }(&newAuthAgent)
  92. //Return the authAgent
  93. return &newAuthAgent
  94. }
  95. //Close the authAgent listener
  96. func (a *AuthAgent) Close() {
  97. //Stop the token listening
  98. a.terminateTokenListener <- true
  99. //Close the auth logger database
  100. a.Logger.Close()
  101. }
  102. //This function will handle an http request and redirect to the given login address if not logged in
  103. func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
  104. if a.CheckAuth(r) {
  105. //User already logged in
  106. handler(w, r)
  107. } else {
  108. //User not logged in
  109. a.LoginRedirectionHandler(w, r)
  110. }
  111. }
  112. //Register APIs that requires public access
  113. func (a *AuthAgent) RegisterPublicAPIs(ep AuthEndpoints) {
  114. http.HandleFunc(ep.Login, a.HandleLogin)
  115. http.HandleFunc(ep.Logout, a.HandleLogout)
  116. http.HandleFunc(ep.Register, a.HandleRegister)
  117. http.HandleFunc(ep.CheckLoggedIn, a.CheckLogin)
  118. http.HandleFunc(ep.Autologin, a.HandleAutologinTokenLogin)
  119. }
  120. //Handle login request, require POST username and password
  121. func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
  122. //Get username from request using POST mode
  123. username, err := mv(r, "username", true)
  124. if err != nil {
  125. //Username not defined
  126. log.Println("[System Auth] Someone trying to login with username: " + username)
  127. //Write to log
  128. a.Logger.LogAuth(r, false)
  129. sendErrorResponse(w, "Username not defined or empty.")
  130. return
  131. }
  132. //Get password from request using POST mode
  133. password, err := mv(r, "password", true)
  134. if err != nil {
  135. //Password not defined
  136. a.Logger.LogAuth(r, false)
  137. sendErrorResponse(w, "Password not defined or empty.")
  138. return
  139. }
  140. //Get rememberme settings
  141. rememberme := false
  142. rmbme, _ := mv(r, "rmbme", true)
  143. if rmbme == "true" {
  144. rememberme = true
  145. }
  146. //Check the database and see if this user is in the database
  147. passwordCorrect, rejectionReason := a.ValidateUsernameAndPasswordWithReason(username, password)
  148. //The database contain this user information. Check its password if it is correct
  149. if passwordCorrect {
  150. //Password correct
  151. // Set user as authenticated
  152. a.LoginUserByRequest(w, r, username, rememberme)
  153. //Print the login message to console
  154. log.Println(username + " logged in.")
  155. a.Logger.LogAuth(r, true)
  156. sendOK(w)
  157. } else {
  158. //Password incorrect
  159. log.Println(username + " login request rejected: " + rejectionReason)
  160. sendErrorResponse(w, rejectionReason)
  161. a.Logger.LogAuth(r, false)
  162. return
  163. }
  164. }
  165. func (a *AuthAgent) ValidateUsernameAndPassword(username string, password string) bool {
  166. succ, _ := a.ValidateUsernameAndPasswordWithReason(username, password)
  167. return succ
  168. }
  169. //validate the username and password, return reasons if the auth failed
  170. func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, password string) (bool, string) {
  171. hashedPassword := Hash(password)
  172. var passwordInDB string
  173. err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
  174. if err != nil {
  175. //User not found or db exception
  176. //log.Println("[System Auth] " + username + " login with incorrect password")
  177. return false, "Invalid username or password"
  178. }
  179. if passwordInDB == hashedPassword {
  180. return true, ""
  181. } else {
  182. return false, "Invalid username or password"
  183. }
  184. }
  185. func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, username string, rememberme bool) {
  186. session, _ := a.SessionStore.Get(r, a.SessionName)
  187. session.Values["authenticated"] = true
  188. session.Values["username"] = username
  189. session.Values["rememberMe"] = rememberme
  190. //Check if remember me is clicked. If yes, set the maxage to 1 week.
  191. if rememberme == true {
  192. session.Options = &sessions.Options{
  193. MaxAge: 3600 * 24 * 7, //One week
  194. Path: "/",
  195. }
  196. } else {
  197. session.Options = &sessions.Options{
  198. MaxAge: 3600 * 1, //One hour
  199. Path: "/",
  200. }
  201. }
  202. session.Save(r, w)
  203. }
  204. //Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
  205. func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
  206. username, _ := a.GetUserName(w, r)
  207. if username != "" {
  208. log.Println(username + " logged out.")
  209. }
  210. // Revoke users authentication
  211. err := a.Logout(w, r)
  212. if err != nil {
  213. sendErrorResponse(w, "Logout failed")
  214. return
  215. }
  216. w.Write([]byte("OK"))
  217. }
  218. func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
  219. session, err := a.SessionStore.Get(r, a.SessionName)
  220. if err != nil {
  221. return err
  222. }
  223. session.Values["authenticated"] = false
  224. session.Values["username"] = nil
  225. session.Save(r, w)
  226. return nil
  227. }
  228. //Get the current session username from request
  229. func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string, error) {
  230. if a.CheckAuth(r) {
  231. //This user has logged in.
  232. session, _ := a.SessionStore.Get(r, a.SessionName)
  233. return session.Values["username"].(string), nil
  234. } else {
  235. //This user has not logged in.
  236. return "", errors.New("User not logged in")
  237. }
  238. }
  239. //Check if the user has logged in, return true / false in JSON
  240. func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
  241. if a.CheckAuth(r) != false {
  242. sendJSONResponse(w, "true")
  243. } else {
  244. sendJSONResponse(w, "false")
  245. }
  246. }
  247. //Handle new user register. Require POST username, password, group.
  248. func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request) {
  249. userCount := a.GetUserCounts()
  250. //Get username from request
  251. newusername, err := mv(r, "username", true)
  252. if err != nil {
  253. sendTextResponse(w, "Error. Missing 'username' paramter")
  254. return
  255. }
  256. //Get password from request
  257. password, err := mv(r, "password", true)
  258. if err != nil {
  259. sendTextResponse(w, "Error. Missing 'password' paramter")
  260. return
  261. }
  262. //Set permission group to default
  263. group, err := mv(r, "group", true)
  264. if err != nil {
  265. sendTextResponse(w, "Error. Missing 'group' paramter")
  266. return
  267. }
  268. //Check if the number of users in the system is == 0. If yes, there are no need to login before registering new user
  269. if userCount > 0 {
  270. //Require login to create new user
  271. if a.CheckAuth(r) == false {
  272. //System have more than one person and this user is not logged in
  273. sendErrorResponse(w, "Login is needed to create new user")
  274. return
  275. }
  276. }
  277. //Ok to proceed create this user
  278. err = a.CreateUserAccount(newusername, password, []string{group})
  279. if err != nil {
  280. sendErrorResponse(w, err.Error())
  281. return
  282. }
  283. //Return to the client with OK
  284. sendOK(w)
  285. log.Println("[System Auth] New user " + newusername + " added to system.")
  286. return
  287. }
  288. //Check authentication from request header's session value
  289. func (a *AuthAgent) CheckAuth(r *http.Request) bool {
  290. session, _ := a.SessionStore.Get(r, a.SessionName)
  291. // Check if user is authenticated
  292. if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
  293. return false
  294. }
  295. return true
  296. }
  297. //Handle de-register of users. Require POST username.
  298. //THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
  299. func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
  300. //Check if the user is logged in
  301. if a.CheckAuth(r) == false {
  302. //This user has not logged in
  303. sendErrorResponse(w, "Login required to remove user from the system.")
  304. return
  305. }
  306. //Check for permission of this user.
  307. /*
  308. if !system_permission_checkUserIsAdmin(w,r){
  309. //This user is not admin. No permission to access this function
  310. sendErrorResponse(w, "Permission denied")
  311. }
  312. */
  313. //Get username from request
  314. username, err := mv(r, "username", true)
  315. if err != nil {
  316. sendErrorResponse(w, "Missing 'username' paramter")
  317. return
  318. }
  319. err = a.UnregisterUser(username)
  320. if err != nil {
  321. sendErrorResponse(w, err.Error())
  322. return
  323. }
  324. //Return to the client with OK
  325. sendOK(w)
  326. log.Println("[system_auth] User " + username + " has been removed from the system.")
  327. return
  328. }
  329. func (a *AuthAgent) UnregisterUser(username string) error {
  330. //Check if the user exists in the system database.
  331. if !a.Database.KeyExists("auth", "passhash/"+username) {
  332. //This user do not exists.
  333. return errors.New("This user does not exists.")
  334. }
  335. //OK! Remove the user from the database
  336. a.Database.Delete("auth", "passhash/"+username)
  337. a.Database.Delete("auth", "group/"+username)
  338. a.Database.Delete("auth", "acstatus/"+username)
  339. a.Database.Delete("auth", "profilepic/"+username)
  340. //Remove the user's autologin tokens
  341. a.RemoveAutologinTokenByUsername(username)
  342. return nil
  343. }
  344. //Get the number of users in the system
  345. func (a *AuthAgent) GetUserCounts() int {
  346. entries, _ := a.Database.ListTable("auth")
  347. usercount := 0
  348. for _, keypairs := range entries {
  349. if strings.Contains(string(keypairs[0]), "passhash/") {
  350. //This is a user registry
  351. usercount++
  352. }
  353. }
  354. if usercount == 0 {
  355. log.Println("There are no user in the database.")
  356. }
  357. return usercount
  358. }
  359. //List all username within the system
  360. func (a *AuthAgent) ListUsers() []string {
  361. entries, _ := a.Database.ListTable("auth")
  362. results := []string{}
  363. for _, keypairs := range entries {
  364. if strings.Contains(string(keypairs[0]), "group/") {
  365. username := strings.Split(string(keypairs[0]), "/")[1]
  366. results = append(results, username)
  367. }
  368. }
  369. return results
  370. }
  371. //Check if the given username exists
  372. func (a *AuthAgent) UserExists(username string) bool {
  373. userpasswordhash := ""
  374. err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash)
  375. if err != nil || userpasswordhash == "" {
  376. return false
  377. }
  378. return true
  379. }
  380. //Update the session expire time given the request header.
  381. func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Request) bool {
  382. session, _ := a.SessionStore.Get(r, a.SessionName)
  383. if session.Values["authenticated"].(bool) == true {
  384. //User authenticated. Extend its expire time
  385. rememberme := session.Values["rememberMe"].(bool)
  386. //Extend the session expire time
  387. if rememberme == true {
  388. session.Options = &sessions.Options{
  389. MaxAge: 3600 * 24 * 7, //One week
  390. Path: "/",
  391. }
  392. } else {
  393. session.Options = &sessions.Options{
  394. MaxAge: 3600 * 1, //One hour
  395. Path: "/",
  396. }
  397. }
  398. session.Save(r, w)
  399. return true
  400. } else {
  401. return false
  402. }
  403. }
  404. //Create user account
  405. func (a *AuthAgent) CreateUserAccount(newusername string, password string, group []string) error {
  406. key := newusername
  407. hashedPassword := Hash(password)
  408. err := a.Database.Write("auth", "passhash/"+key, hashedPassword)
  409. if err != nil {
  410. return err
  411. }
  412. //Store this user's usergroup settings
  413. err = a.Database.Write("auth", "group/"+newusername, group)
  414. if err != nil {
  415. return err
  416. }
  417. return nil
  418. }
  419. //Hash the given raw string into sha512 hash
  420. func Hash(raw string) string {
  421. h := sha512.New()
  422. h.Write([]byte(raw))
  423. return hex.EncodeToString(h.Sum(nil))
  424. }