usermanager.go 14 KB

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