auth.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. package auth
  2. /*
  3. author: tobychui
  4. */
  5. import (
  6. "crypto/rand"
  7. "crypto/sha512"
  8. "errors"
  9. "net/http"
  10. "net/mail"
  11. "strings"
  12. "encoding/hex"
  13. "log"
  14. "github.com/gorilla/sessions"
  15. db "imuslab.com/zoraxy/mod/database"
  16. "imuslab.com/zoraxy/mod/utils"
  17. )
  18. type AuthAgent struct {
  19. //Session related
  20. SessionName string
  21. SessionStore *sessions.CookieStore
  22. Database *db.Database
  23. LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
  24. }
  25. type AuthEndpoints struct {
  26. Login string
  27. Logout string
  28. Register string
  29. CheckLoggedIn string
  30. Autologin string
  31. }
  32. //Constructor
  33. func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
  34. store := sessions.NewCookieStore(key)
  35. err := sysdb.NewTable("auth")
  36. if err != nil {
  37. log.Println("Failed to create auth database. Terminating.")
  38. panic(err)
  39. }
  40. //Create a new AuthAgent object
  41. newAuthAgent := AuthAgent{
  42. SessionName: sessionName,
  43. SessionStore: store,
  44. Database: sysdb,
  45. LoginRedirectionHandler: loginRedirectionHandler,
  46. }
  47. //Return the authAgent
  48. return &newAuthAgent
  49. }
  50. func GetSessionKey(sysdb *db.Database) (string, error) {
  51. sysdb.NewTable("auth")
  52. sessionKey := ""
  53. if !sysdb.KeyExists("auth", "sessionkey") {
  54. key := make([]byte, 32)
  55. rand.Read(key)
  56. sessionKey = string(key)
  57. sysdb.Write("auth", "sessionkey", sessionKey)
  58. log.Println("[Auth] New authentication session key generated")
  59. } else {
  60. log.Println("[Auth] Authentication session key loaded from database")
  61. err := sysdb.Read("auth", "sessionkey", &sessionKey)
  62. if err != nil {
  63. return "", errors.New("database read error. Is the database file corrupted?")
  64. }
  65. }
  66. return sessionKey, nil
  67. }
  68. //This function will handle an http request and redirect to the given login address if not logged in
  69. func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
  70. if a.CheckAuth(r) {
  71. //User already logged in
  72. handler(w, r)
  73. } else {
  74. //User not logged in
  75. a.LoginRedirectionHandler(w, r)
  76. }
  77. }
  78. //Handle login request, require POST username and password
  79. func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
  80. //Get username from request using POST mode
  81. username, err := utils.PostPara(r, "username")
  82. if err != nil {
  83. //Username not defined
  84. log.Println("[Auth] " + r.RemoteAddr + " trying to login with username: " + username)
  85. utils.SendErrorResponse(w, "Username not defined or empty.")
  86. return
  87. }
  88. //Get password from request using POST mode
  89. password, err := utils.PostPara(r, "password")
  90. if err != nil {
  91. //Password not defined
  92. utils.SendErrorResponse(w, "Password not defined or empty.")
  93. return
  94. }
  95. //Get rememberme settings
  96. rememberme := false
  97. rmbme, _ := utils.PostPara(r, "rmbme")
  98. if rmbme == "true" {
  99. rememberme = true
  100. }
  101. //Check the database and see if this user is in the database
  102. passwordCorrect, rejectionReason := a.ValidateUsernameAndPasswordWithReason(username, password)
  103. //The database contain this user information. Check its password if it is correct
  104. if passwordCorrect {
  105. //Password correct
  106. // Set user as authenticated
  107. a.LoginUserByRequest(w, r, username, rememberme)
  108. //Print the login message to console
  109. log.Println(username + " logged in.")
  110. utils.SendOK(w)
  111. } else {
  112. //Password incorrect
  113. log.Println(username + " login request rejected: " + rejectionReason)
  114. utils.SendErrorResponse(w, rejectionReason)
  115. return
  116. }
  117. }
  118. func (a *AuthAgent) ValidateUsernameAndPassword(username string, password string) bool {
  119. succ, _ := a.ValidateUsernameAndPasswordWithReason(username, password)
  120. return succ
  121. }
  122. //validate the username and password, return reasons if the auth failed
  123. func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, password string) (bool, string) {
  124. hashedPassword := Hash(password)
  125. var passwordInDB string
  126. err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
  127. if err != nil {
  128. //User not found or db exception
  129. log.Println("[Auth] " + username + " login with incorrect password")
  130. return false, "Invalid username or password"
  131. }
  132. if passwordInDB == hashedPassword {
  133. return true, ""
  134. } else {
  135. return false, "Invalid username or password"
  136. }
  137. }
  138. //Login the user by creating a valid session for this user
  139. func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, username string, rememberme bool) {
  140. session, _ := a.SessionStore.Get(r, a.SessionName)
  141. session.Values["authenticated"] = true
  142. session.Values["username"] = username
  143. session.Values["rememberMe"] = rememberme
  144. //Check if remember me is clicked. If yes, set the maxage to 1 week.
  145. if rememberme {
  146. session.Options = &sessions.Options{
  147. MaxAge: 3600 * 24 * 7, //One week
  148. Path: "/",
  149. }
  150. } else {
  151. session.Options = &sessions.Options{
  152. MaxAge: 3600 * 1, //One hour
  153. Path: "/",
  154. }
  155. }
  156. session.Save(r, w)
  157. }
  158. //Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
  159. func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
  160. username, err := a.GetUserName(w, r)
  161. if username != "" {
  162. log.Println(username + " logged out.")
  163. }
  164. // Revoke users authentication
  165. err = a.Logout(w, r)
  166. if err != nil {
  167. utils.SendErrorResponse(w, "Logout failed")
  168. return
  169. }
  170. w.Write([]byte("OK"))
  171. }
  172. func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
  173. session, err := a.SessionStore.Get(r, a.SessionName)
  174. if err != nil {
  175. return err
  176. }
  177. session.Values["authenticated"] = false
  178. session.Values["username"] = nil
  179. session.Save(r, w)
  180. return nil
  181. }
  182. //Get the current session username from request
  183. func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string, error) {
  184. if a.CheckAuth(r) {
  185. //This user has logged in.
  186. session, _ := a.SessionStore.Get(r, a.SessionName)
  187. return session.Values["username"].(string), nil
  188. } else {
  189. //This user has not logged in.
  190. return "", errors.New("user not logged in")
  191. }
  192. }
  193. //Get the current session user email from request
  194. func (a *AuthAgent) GetUserEmail(w http.ResponseWriter, r *http.Request) (string, error) {
  195. if a.CheckAuth(r) {
  196. //This user has logged in.
  197. session, _ := a.SessionStore.Get(r, a.SessionName)
  198. username := session.Values["username"].(string)
  199. userEmail := ""
  200. err := a.Database.Read("auth", "email/"+username, &userEmail)
  201. if err != nil {
  202. return "", err
  203. }
  204. return userEmail, nil
  205. } else {
  206. //This user has not logged in.
  207. return "", errors.New("user not logged in")
  208. }
  209. }
  210. //Check if the user has logged in, return true / false in JSON
  211. func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
  212. if a.CheckAuth(r) {
  213. utils.SendJSONResponse(w, "true")
  214. } else {
  215. utils.SendJSONResponse(w, "false")
  216. }
  217. }
  218. //Handle new user register. Require POST username, password, group.
  219. func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
  220. //Get username from request
  221. newusername, err := utils.PostPara(r, "username")
  222. if err != nil {
  223. utils.SendErrorResponse(w, "Missing 'username' paramter")
  224. return
  225. }
  226. //Get password from request
  227. password, err := utils.PostPara(r, "password")
  228. if err != nil {
  229. utils.SendErrorResponse(w, "Missing 'password' paramter")
  230. return
  231. }
  232. //Get email from request
  233. email, err := utils.PostPara(r, "email")
  234. if err != nil {
  235. utils.SendErrorResponse(w, "Missing 'email' paramter")
  236. return
  237. }
  238. _, err = mail.ParseAddress(email)
  239. if err != nil {
  240. utils.SendErrorResponse(w, "Invalid or malformed email")
  241. return
  242. }
  243. //Ok to proceed create this user
  244. err = a.CreateUserAccount(newusername, password, email)
  245. if err != nil {
  246. utils.SendErrorResponse(w, err.Error())
  247. return
  248. }
  249. //Do callback if exists
  250. if callback != nil {
  251. callback(newusername, email)
  252. }
  253. //Return to the client with OK
  254. utils.SendOK(w)
  255. log.Println("[Auth] New user " + newusername + " added to system.")
  256. }
  257. //Handle new user register without confirmation email. Require POST username, password, group.
  258. func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
  259. //Get username from request
  260. newusername, err := utils.PostPara(r, "username")
  261. if err != nil {
  262. utils.SendErrorResponse(w, "Missing 'username' paramter")
  263. return
  264. }
  265. //Get password from request
  266. password, err := utils.PostPara(r, "password")
  267. if err != nil {
  268. utils.SendErrorResponse(w, "Missing 'password' paramter")
  269. return
  270. }
  271. //Ok to proceed create this user
  272. err = a.CreateUserAccount(newusername, password, "")
  273. if err != nil {
  274. utils.SendErrorResponse(w, err.Error())
  275. return
  276. }
  277. //Do callback if exists
  278. if callback != nil {
  279. callback(newusername, "")
  280. }
  281. //Return to the client with OK
  282. utils.SendOK(w)
  283. log.Println("[Auth] Admin account created: " + newusername)
  284. }
  285. //Check authentication from request header's session value
  286. func (a *AuthAgent) CheckAuth(r *http.Request) bool {
  287. session, err := a.SessionStore.Get(r, a.SessionName)
  288. if err != nil {
  289. return false
  290. }
  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) {
  302. //This user has not logged in
  303. utils.SendErrorResponse(w, "Login required to remove user from the system.")
  304. return
  305. }
  306. //Get username from request
  307. username, err := utils.PostPara(r, "username")
  308. if err != nil {
  309. utils.SendErrorResponse(w, "Missing 'username' paramter")
  310. return
  311. }
  312. err = a.UnregisterUser(username)
  313. if err != nil {
  314. utils.SendErrorResponse(w, err.Error())
  315. return
  316. }
  317. //Return to the client with OK
  318. utils.SendOK(w)
  319. log.Println("[Auth] User " + username + " has been removed from the system.")
  320. }
  321. func (a *AuthAgent) UnregisterUser(username string) error {
  322. //Check if the user exists in the system database.
  323. if !a.Database.KeyExists("auth", "passhash/"+username) {
  324. //This user do not exists.
  325. return errors.New("this user does not exists")
  326. }
  327. //OK! Remove the user from the database
  328. a.Database.Delete("auth", "passhash/"+username)
  329. a.Database.Delete("auth", "email/"+username)
  330. return nil
  331. }
  332. //Get the number of users in the system
  333. func (a *AuthAgent) GetUserCounts() int {
  334. entries, _ := a.Database.ListTable("auth")
  335. usercount := 0
  336. for _, keypairs := range entries {
  337. if strings.Contains(string(keypairs[0]), "passhash/") {
  338. //This is a user registry
  339. usercount++
  340. }
  341. }
  342. if usercount == 0 {
  343. log.Println("There are no user in the database.")
  344. }
  345. return usercount
  346. }
  347. //List all username within the system
  348. func (a *AuthAgent) ListUsers() []string {
  349. entries, _ := a.Database.ListTable("auth")
  350. results := []string{}
  351. for _, keypairs := range entries {
  352. if strings.Contains(string(keypairs[0]), "passhash/") {
  353. username := strings.Split(string(keypairs[0]), "/")[1]
  354. results = append(results, username)
  355. }
  356. }
  357. return results
  358. }
  359. //Check if the given username exists
  360. func (a *AuthAgent) UserExists(username string) bool {
  361. userpasswordhash := ""
  362. err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash)
  363. if err != nil || userpasswordhash == "" {
  364. return false
  365. }
  366. return true
  367. }
  368. //Update the session expire time given the request header.
  369. func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Request) bool {
  370. session, _ := a.SessionStore.Get(r, a.SessionName)
  371. if session.Values["authenticated"].(bool) {
  372. //User authenticated. Extend its expire time
  373. rememberme := session.Values["rememberMe"].(bool)
  374. //Extend the session expire time
  375. if rememberme {
  376. session.Options = &sessions.Options{
  377. MaxAge: 3600 * 24 * 7, //One week
  378. Path: "/",
  379. }
  380. } else {
  381. session.Options = &sessions.Options{
  382. MaxAge: 3600 * 1, //One hour
  383. Path: "/",
  384. }
  385. }
  386. session.Save(r, w)
  387. return true
  388. } else {
  389. return false
  390. }
  391. }
  392. //Create user account
  393. func (a *AuthAgent) CreateUserAccount(newusername string, password string, email string) error {
  394. //Check user already exists
  395. if a.UserExists(newusername) {
  396. return errors.New("user with same name already exists")
  397. }
  398. key := newusername
  399. hashedPassword := Hash(password)
  400. err := a.Database.Write("auth", "passhash/"+key, hashedPassword)
  401. if err != nil {
  402. return err
  403. }
  404. if email != "" {
  405. err = a.Database.Write("auth", "email/"+key, email)
  406. if err != nil {
  407. return err
  408. }
  409. }
  410. return nil
  411. }
  412. //Hash the given raw string into sha512 hash
  413. func Hash(raw string) string {
  414. h := sha512.New()
  415. h.Write([]byte(raw))
  416. return hex.EncodeToString(h.Sum(nil))
  417. }