usermanager.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. package usermanager
  2. /** provides user management utilities and iris handlers **/
  3. import (
  4. "errors"
  5. "time"
  6. "math/rand"
  7. "strconv"
  8. "git.mmnx.de/Moe/databaseutils"
  9. "git.mmnx.de/Moe/configutils"
  10. "git.mmnx.de/Moe/errorhelpers"
  11. "github.com/dgrijalva/jwt-go"
  12. "github.com/kataras/iris"
  13. "golang.org/x/crypto/bcrypt"
  14. "fmt"
  15. )
  16. var (
  17. Users *[]User // stores all currently logged in users
  18. )
  19. type User struct { // User
  20. ID string
  21. Username string
  22. Password string
  23. Admin string
  24. TokenUsed string
  25. }
  26. func (user *User) Login(username string, password string) (string, error) {
  27. hmacSampleSecret := []byte(configutils.Conf.CryptoKey) // crypto key for JWT encryption
  28. row, err := databaseutils.DBUtil.GetRow("*", "users", "username", username) // get user from db
  29. if err != nil {
  30. if err.Error() == databaseutils.ERR_EMPTY_RESULT { // empty result -> user not found
  31. return "", errors.New(errorhelpers.ERR_USER_NOT_FOUND)
  32. } else {
  33. return "", errors.New("Unknown error")
  34. }
  35. }
  36. err = bcrypt.CompareHashAndPassword([]byte(row[2]), []byte(password))
  37. if err == nil { // if sent' pw hash == stored pw hash
  38. expire, _ := time.ParseDuration("168h") // 7 days
  39. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
  40. "username": username,
  41. "userid": row[0],
  42. "nbf": time.Now().Unix(),
  43. "exp": time.Now().Add(expire).Unix(),
  44. "token": "nigger", // TODO db based tokens
  45. })
  46. tokenString, _ := token.SignedString(hmacSampleSecret)
  47. user.ID = row[0]
  48. user.Username = row[1]
  49. user.Password = string(row[2])
  50. user.Admin = string(row[3])
  51. user.TokenUsed = string(row[4])
  52. if err != nil {
  53. fmt.Printf("Error: ", err.Error())
  54. }
  55. *Users = append(*Users, *user) // store user in logged-in-users list
  56. return tokenString, nil // return tokenString (Cookie)
  57. } else {
  58. return "", errors.New(errorhelpers.ERR_PASSWORD_MISMATCH) // wrong password
  59. }
  60. }
  61. func LoginHandler(ctx *iris.Context) {
  62. username := ctx.FormValueString("username") // POST values from login form
  63. password := ctx.FormValueString("password")
  64. user := User{} // new user
  65. tokenString, err := user.Login(username, password) // try to login
  66. ctx.SetCookieKV("token", tokenString)
  67. ctx.Set("token", tokenString) // set tokenstring as ctx-param as cookie can't be read in next context(s)
  68. errorhelpers.HandleError(err, ctx)
  69. }
  70. func (user *User) Logout(userID string) {
  71. userArrayID := SearchUser(userID) // get logged in users list index
  72. user.ID = "" // empty
  73. user.Username = ""
  74. user.Password = ""
  75. user.Admin = ""
  76. user.TokenUsed = ""
  77. (*Users)[userArrayID] = *user
  78. return
  79. }
  80. func LogoutHandler(ctx *iris.Context) {
  81. userID := ctx.GetString("userID")
  82. user, err := GetUserFromDB(userID)
  83. errorhelpers.HandleError(err, ctx)
  84. user.Logout(userID)
  85. ctx.SetCookieKV("token", "")
  86. params := ctx.Get("params").(map[string]string) // TODO cleaner way? outsource to user.logout?
  87. params["username"] = ""
  88. params["admin"] = ""
  89. ctx.Set("params", params)
  90. err = errors.New(errorhelpers.SUCCESS_LOGOUT)
  91. errorhelpers.HandleError(err, ctx)
  92. }
  93. func (user *User) Update() error {
  94. colsVals := make([][]string, 2)
  95. colsVals[0] = []string{"username", user.Username}
  96. colsVals[1] = []string{"password", user.Password}
  97. err := databaseutils.DBUtil.UpdateRow("users", "id", string(user.ID), colsVals)
  98. return err
  99. }
  100. func AccountUpdateHandler(ctx *iris.Context) {
  101. username := ctx.FormValueString("username") // POST values
  102. password := ctx.FormValueString("password")
  103. userID := ctx.GetString("userID")
  104. err := UserUpdateProcessor(username, password, userID)
  105. errorhelpers.HandleError(err, ctx)
  106. err = errors.New(errorhelpers.SUCCESS_UPDATE)
  107. errorhelpers.HandleError(err, ctx)
  108. }
  109. func SearchUser(userID string) int {
  110. for i := range *Users {
  111. if (*Users)[i].ID == userID {
  112. return i
  113. }
  114. }
  115. return -1
  116. }
  117. func SearchUserByUsername(username string) int {
  118. for i := range *Users {
  119. if (*Users)[i].Username == username {
  120. return i
  121. }
  122. }
  123. return -1
  124. }
  125. func VerifyUserLoggedIn(tokenString string) (bool, string, error) { // TODO renew JWT from time to time preventing expiry
  126. if tokenString == "" { // if no tokenString("Cookie") exists fail
  127. return false, "-1", errors.New(errorhelpers.ERR_INVALID_TOKEN)
  128. }
  129. hmacSampleSecret := []byte(configutils.Conf.CryptoKey) // crypto key for JWT encryption
  130. token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
  131. if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
  132. return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
  133. }
  134. return hmacSampleSecret, nil
  135. })
  136. if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { // if token is valid
  137. if userID, ok := claims["userid"].(string); ok { // extract userID
  138. sliceID := SearchUser(userID) // verify that user has a session on the server
  139. if sliceID != -1 { // searchUser returns -1 if there's no such user
  140. return true, userID, nil // logged in, TODO: "0" template comparision dynamic
  141. } else {
  142. return false, "-1", errors.New(errorhelpers.ERR_SESSION_TIMED_OUT) // Session probably expired - may also be faked? TODO more checks?
  143. }
  144. } else {
  145. return false, "-1", errors.New("Unknown error") // This should never happen, prolly can't convert something in claims then..
  146. }
  147. } else {
  148. return false, "-1", errors.New(errorhelpers.ERR_INVALID_TOKEN) // Token is invalid, expired or whatever, TODO switch with ERR_SESSION_TIMED_OUT when database based session system
  149. }
  150. }
  151. func AuthHandler(ctx *iris.Context) {
  152. inLogin := false
  153. tokenString := ctx.GetCookie("token")
  154. if tokenString == "" { // when coming from login form cookie doesn't work yet
  155. tokenString = ctx.GetString("token")
  156. if tokenString != "" {
  157. inLogin = true
  158. }
  159. }
  160. isAuthed, userID, err := VerifyUserLoggedIn(tokenString)
  161. if isAuthed {
  162. params := ctx.Get("params").(map[string]string)
  163. if inLogin {
  164. err = errors.New(errorhelpers.SUCCESS_LOGIN)
  165. }
  166. ctx.Set("userID", userID) // save userID for in-context use
  167. userArrayID := SearchUser(userID)
  168. params["username"] = (*Users)[userArrayID].Username
  169. params["admin"] = (*Users)[userArrayID].Admin // TODO rename to isAdmin ?
  170. ctx.Set("params", params)
  171. }
  172. errorhelpers.HandleError(err, ctx) // if error, show error, otherwise next middleware
  173. }
  174. func CanBeAuthedHandler(ctx *iris.Context) {
  175. tokenString := ctx.GetCookie("token")
  176. if tokenString == "" { // when coming from login form cookie doesn't work yet
  177. tokenString = ctx.GetString("token")
  178. }
  179. isAuthed, userID, err := VerifyUserLoggedIn(tokenString)
  180. if isAuthed {
  181. ctx.Set("userID", userID) // save userID for in-context use
  182. userArrayID := SearchUser(userID)
  183. params := ctx.Get("params").(map[string]string)
  184. params["username"] = (*Users)[userArrayID].Username
  185. params["admin"] = (*Users)[userArrayID].Admin // TODO rename to isAdmin ?
  186. ctx.Set("params", params)
  187. } else if err != nil {
  188. if !((err.Error() != "ERR_SESSION_TIMED_OUT") || (err.Error() != "ERR_INVALID_TOKEN")) { // ignore ERR_SESSION_TIMED_OUT and ERR_INVALID_TOKEN
  189. errorhelpers.HandleError(err, ctx)
  190. return
  191. }
  192. }
  193. ctx.Next() // authed users can now use their accounts, next handler
  194. }
  195. func AdminHandler(ctx *iris.Context) { // TODO remake this
  196. userID := ctx.GetString("userID")
  197. user, err := GetUser(userID)
  198. if user.Admin != "1" { // check if user is admin
  199. err = errors.New("User no Admin: " + userID)
  200. fmt.Println(err.Error())
  201. ctx.Redirect("/")
  202. return
  203. } else {
  204. ctx.Next()
  205. }
  206. }
  207. func GenerateTokens(numTokens int) ([]string, error) {
  208. const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  209. tokens := make([]string, 0)
  210. dbTokens := make([][]string, 0)
  211. for i := 0; i < numTokens; i++ {
  212. b := make([]byte, 16)
  213. for i := range b {
  214. b[i] = letterBytes[rand.Intn(len(letterBytes))]
  215. }
  216. tokens = append(tokens, string(b))
  217. dbTokens = [][]string{[]string{"value", string(b)}, []string{"used", "0"}}
  218. err := databaseutils.DBUtil.InsertRow("tokens", dbTokens)
  219. if err != nil {
  220. return []string{""}, err
  221. }
  222. }
  223. return tokens, nil
  224. }
  225. func GetTokens(used bool) ([]string, error) {
  226. dbTokens, err := databaseutils.DBUtil.GetRows("*", "tokens", "used", "0") // get unused tokens
  227. if used {
  228. dbTokens, err = databaseutils.DBUtil.GetRows("*", "tokens", "used", "1") // get used tokens
  229. }
  230. if err != nil {
  231. return []string{}, err
  232. }
  233. tokens := make([]string, 0)
  234. for i, _ := range dbTokens {
  235. tokens = append(tokens, dbTokens[i][1])
  236. }
  237. return tokens, nil
  238. }
  239. func GetTokensAsString(used bool) (string, error) {
  240. tokens, err := GetTokens(used)
  241. ret := ""
  242. for i := range tokens {
  243. ret += fmt.Sprintf("%s\n", tokens[i])
  244. }
  245. return ret, err
  246. }
  247. func GetUser(userID string) (User, error) {
  248. usersArrayID := SearchUser(userID)
  249. if usersArrayID == -1 { // TODO check if unneccessary (AuthHandler) (Ddepends on where used: TODO CHECK)
  250. return User{}, errors.New("User not logged in")
  251. }
  252. user := (*Users)[usersArrayID] // user must be logged in to do this -> get from users list
  253. return user, nil
  254. }
  255. func GetUserFromDB(userID string) (User, error) {
  256. row, err := databaseutils.DBUtil.GetRow("*", "users", "id", userID) // get user from db
  257. if err != nil {
  258. return User{}, err
  259. }
  260. return User{row[0], row[1], string(row[2]), string(row[3]), string(row[4])}, nil
  261. }
  262. func SearchUserByUsernameInDB(username string) int {
  263. user, err := databaseutils.DBUtil.GetRow("*", "users", "username", username)
  264. if err != nil {
  265. if err.Error() != "ERR_EMPTY_RESULT" {
  266. fmt.Println(err.Error())
  267. }
  268. return -1
  269. }
  270. userID, err := strconv.Atoi(user[0])
  271. if err != nil {
  272. fmt.Println(err.Error())
  273. }
  274. return userID
  275. }
  276. func SearchUserByTokenInDB(token string) (int, error) {
  277. user, err := databaseutils.DBUtil.GetRowsDoubleCond("users", "tokens", "`token-id` = `tokens`.`id`", "`tokens`.`value` = '" + token + "'")
  278. if err != nil {
  279. return -1, err
  280. }
  281. userID, err := strconv.Atoi(user[0][0])
  282. if err != nil {
  283. return -1, err
  284. }
  285. return userID, nil
  286. }
  287. func RegisterUserWithToken(username string, password string, token string) error {
  288. tokenID := databaseutils.DBUtil.GetString("id", "tokens", "value", token)
  289. user := [][]string{[]string{"username", username}, []string{"password", password}, []string{"admin", "0"}, []string{"token-id", tokenID}}
  290. err := databaseutils.DBUtil.InsertRow("users", user)
  291. if err != nil {
  292. fmt.Println(err.Error())
  293. return err
  294. }
  295. err = databaseutils.DBUtil.UpdateRow("tokens", "value", token, [][]string{[]string{"used", "1"}})
  296. if err != nil {
  297. fmt.Println(err.Error())
  298. return err
  299. }
  300. return nil
  301. }
  302. func VerifyUserUpdate(username string, password string, userID string) error {
  303. tmpUser, err := GetUserFromDB(userID)
  304. if err != nil {
  305. return err
  306. }
  307. if SearchUserByUsernameInDB(username) != -1 && username != tmpUser.Username { // username can't be changed as there already exists a user with that name or it's the old name
  308. return errors.New(errorhelpers.ERR_USERNAME_TAKEN)
  309. }
  310. if username == "" { // if not left empty change
  311. return errors.New(errorhelpers.ERR_INVALID_PARAM)
  312. }
  313. if password == "" { // if not left empty we change it
  314. return errors.New(errorhelpers.ERR_INVALID_PARAM)
  315. }
  316. return nil
  317. }
  318. // Processes the update of an user, username and password are the new "wanted" values, can also be empty string ("")
  319. func UserUpdateProcessor(username string, password string, userID string) error {
  320. user, err := GetUserFromDB(userID)
  321. hashedPassword := ""
  322. if err != nil {
  323. return err
  324. }
  325. if username == "" {
  326. username = user.Username
  327. }
  328. if password == "" {
  329. password = user.Password
  330. hashedPassword = user.Password // we dont need to / _can't_ re-hash the hash
  331. }
  332. if err = VerifyUserUpdate(username, password, userID); err != nil {
  333. return err
  334. }
  335. if hashedPassword == "" {
  336. hashedPassword, err = func (hashedPassword []byte, err error) (string, error) { // hash password, we use an anonymous function to convert int to string
  337. if err != nil { // should never happen
  338. return "", err
  339. }
  340. return string(hashedPassword), nil
  341. }(bcrypt.GenerateFromPassword([]byte(password), 15)) // this is the actual hashing call
  342. }
  343. user.Username = username
  344. user.Password = hashedPassword
  345. if err = user.Update(); err != nil {
  346. return err
  347. }
  348. return nil
  349. }
  350. func IsTokenUsed(tokens []string, token string) bool {
  351. usedToken := false
  352. for i, _ := range tokens {
  353. if token == tokens[i] {
  354. usedToken = true
  355. break
  356. }
  357. }
  358. return usedToken
  359. }
  360. func RegisterHandler(ctx *iris.Context) {
  361. token := ctx.FormValueString("token") // POST values from login form
  362. username := ctx.FormValueString("username")
  363. password := ctx.FormValueString("password")
  364. unusedTokens, err := GetTokens(false) // get all unused tokens
  365. errorhelpers.HandleError(err, ctx)
  366. usedTokens, err := GetTokens(true) // get all used tokens
  367. errorhelpers.HandleError(err, ctx)
  368. unusedToken := IsTokenUsed(unusedTokens, token) // check if token is unused
  369. usedToken := IsTokenUsed(usedTokens, token) // check if token is used
  370. if !unusedToken && !usedToken { // token doesnt exist
  371. err := errors.New(errorhelpers.ERR_INVALID_TOKEN) // TODO rename this to differ from cookie-token?
  372. errorhelpers.HandleError(err, ctx)
  373. return
  374. }
  375. tokenUserID, err := SearchUserByTokenInDB(token)
  376. if err != nil { // id of user, we're going to change if exists
  377. if err.Error() != "ERR_EMPTY_RESULT" { // if no user found for that token let them register
  378. errorhelpers.HandleError(err, ctx)
  379. return
  380. }
  381. }
  382. tokenUserIDStr := strconv.FormatInt(int64(tokenUserID), 10)
  383. user := User{} // new user
  384. if tokenUserIDStr == "-1" { // register a new account
  385. passwordBin, _ := bcrypt.GenerateFromPassword([]byte(password), 15) // hash password
  386. err := RegisterUserWithToken(username, string(passwordBin), token) // register user
  387. if err != nil {
  388. errorhelpers.HandleError(err, ctx)
  389. return
  390. }
  391. tokenString, err := user.Login(username, password) // try to login
  392. if err != nil {
  393. errorhelpers.HandleError(err, ctx)
  394. } else {
  395. ctx.SetCookieKV("token", tokenString) // set tokenString as cookie
  396. err = errors.New(errorhelpers.SUCCESS_REGISTER)
  397. errorhelpers.HandleError(err, ctx)
  398. }
  399. } else { // used token -> update
  400. if err := UserUpdateProcessor(username, password, tokenUserIDStr); err != nil { // simply try to update
  401. errorhelpers.HandleError(err, ctx)
  402. return
  403. } else {
  404. user.Logout(tokenUserIDStr) // log user out from system
  405. err = errors.New(errorhelpers.SUCCESS_UPDATE)
  406. errorhelpers.HandleError(err, ctx)
  407. }
  408. }
  409. }