auth.go 13 KB

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