package usermanager import ( "errors" "time" "math/rand" "strconv" "git.mmnx.de/Moe/databaseutils" "git.mmnx.de/Moe/configutils" "github.com/dgrijalva/jwt-go" "github.com/kataras/iris" "golang.org/x/crypto/bcrypt" "fmt" ) var ( Users *[]User // stores all currently logged in users ) const ( // Error constants ERR_USER_NOT_FOUND = "ERR_USER_NOT_FOUND" ERR_PASSWORD_MISMATCH = "ERR_PASSWORD_MISMATCH" ERR_SESSION_TIMED_OUT = "ERR_SESSION_TIMED_OUT" ERR_INVALID_TOKEN = "ERR_INVALID_TOKEN" ERR_USERNAME_TAKEN = "ERR_USERNAME_TAKEN" ) type User struct { // User ID string Username string Password string Admin string TokenUsed string } type PageParams struct{ HasError string Error string ReqDir string Admin string } type PageUserParams struct{ HasError string Error string ReqDir string Username string Admin string Custom []string } type PageUserParamsMessage struct{ HasError string Error string ReqDir string Username string Email string Admin string Message string } func (user *User) Login(username string, password string) (string, error) { hmacSampleSecret := []byte(configutils.Conf.CryptoKey) // crypto key for JWT encryption row, err := databaseutils.DBUtil.GetRow("*", "users", "username", username) // get user from db if err != nil { if err.Error() == databaseutils.ERR_EMPTY_RESULT { // empty result -> user not found return "", errors.New(ERR_USER_NOT_FOUND) } else { return "", errors.New("Unknown error") } } err = bcrypt.CompareHashAndPassword([]byte(row[2]), []byte(password)) if err == nil { // if sent' pw hash == stored pw hash expire, _ := time.ParseDuration("168h") // 7 days token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "username": username, "userid": row[0], "nbf": time.Now().Unix(), "exp": time.Now().Add(expire).Unix(), "token": "nigger", // TODO db based tokens }) tokenString, _ := token.SignedString(hmacSampleSecret) user.ID = row[0] user.Username = row[1] user.Password = string(row[2]) user.Admin = string(row[3]) user.TokenUsed = string(row[4]) if err != nil { fmt.Printf("Error: ", err.Error()) } *Users = append(*Users, *user) // store user in logged-in-users list return tokenString, nil // return tokenString (Cookie) } else { return "", errors.New(ERR_PASSWORD_MISMATCH) // wrong password } } func (user *User) Update() error { colsVals := make([][]string, 2) colsVals[0] = []string{"username", user.Username} colsVals[1] = []string{"password", user.Password} err := databaseutils.DBUtil.UpdateRow("users", "id", string(user.ID), colsVals) if err != nil { fmt.Println("ERROOR UPDATING: " + err.Error()) } return nil } func SearchUser(userID string) int { for i := range *Users { if (*Users)[i].ID == userID { return i } } return -1 } func SearchUserByUsername(username string) int { for i := range *Users { if (*Users)[i].Username == username { return i } } return -1 } func VerifyUserLoggedIn(tokenString string) (bool, string, error) { // TODO renew JWT from time to time preventing expiry if tokenString == "" { // if no tokenString("Cookie") exists fail return false, "-1", errors.New(ERR_INVALID_TOKEN) } hmacSampleSecret := []byte(configutils.Conf.CryptoKey) // crypto key for JWT encryption token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return hmacSampleSecret, nil }) if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { // if token is valid if userID, ok := claims["userid"].(string); ok { // extract userID sliceID := SearchUser(userID) // verify that user has a session on the server if sliceID != -1 { // searchUser returns -1 if there's no such user return true, userID, nil // logged in, TODO: "0" template comparision dynamic } else { return false, "-1", errors.New(ERR_SESSION_TIMED_OUT) // Session probably expired - may also be faked? TODO more checks? } } else { return false, "-1", errors.New("Unknown error") // This should never happen, prolly can't convert something in claims then.. } } else { 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 } } func AuthHandler(ctx *iris.Context) { tokenString := ctx.GetCookie("token") isAuthed, userID, err := VerifyUserLoggedIn(tokenString) ctx.Set("userID", userID) // save userID for in-context use if err != nil { fmt.Println("Auth error: ", err.Error()) } if isAuthed { ctx.Next() // successfully authed, next handler } else { if err := ctx.Render("login_box.html", PageParams{"1", err.Error(), "login", "0"}); err != nil { println(err.Error()) } // failed to auth } } func AdminHandler(ctx *iris.Context) { userID := ctx.GetString("userID") user, err := GetUser(userID) if user.Admin != "1" { // check if user is admin err = errors.New("User no Admin: " + userID) fmt.Println(err.Error()) ctx.Redirect("/") return } else { ctx.Next() } } func GenerateTokens(numTokens int) []string { const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" tokens := make([]string, 0) dbTokens := make([][]string, 0) for i := 0; i < numTokens; i++ { b := make([]byte, 16) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] } tokens = append(tokens, string(b)) dbTokens = [][]string{[]string{"value", string(b)}, []string{"used", "0"}} err := databaseutils.DBUtil.InsertRow("tokens", dbTokens) if err != nil { fmt.Println(err.Error()) return []string{""} } } return tokens } func GetTokens(used bool) []string { dbTokens, err := databaseutils.DBUtil.GetRows("*", "tokens", "used", "0") // get unused tokens if used { dbTokens, err = databaseutils.DBUtil.GetRows("*", "tokens", "used", "1") // get used tokens } if err != nil { fmt.Println(err.Error()) // TODO: nicer / outsource } tokens := make([]string, 0) for i, _ := range dbTokens { tokens = append(tokens, dbTokens[i][1]) } return tokens } func GetUser(userID string) (User, error) { usersArrayID := SearchUser(userID) if usersArrayID == -1 { // TODO check if unneccessary (AuthHandler) (Ddepends on where used: TODO CHECK) return User{}, errors.New("User not logged in") } user := (*Users)[usersArrayID] // user must be logged in to do this -> get from users list return user, nil } func SearchUserByUsernameInDB(username string) int { user, err := databaseutils.DBUtil.GetRow("*", "users", "username", username) if err != nil { if err.Error() != "ERR_EMPTY_RESULT" { fmt.Println(err.Error()) } return -1 } userID, err := strconv.Atoi(user[0]) if err != nil { fmt.Println(err.Error()) } return userID } func RegisterUserWithToken(username string, password string, token string) error { tokenID := databaseutils.DBUtil.GetString("id", "tokens", "value", token) user := [][]string{[]string{"username", username}, []string{"password", password}, []string{"admin", "0"}, []string{"token-id", tokenID}} err := databaseutils.DBUtil.InsertRow("users", user) if err != nil { fmt.Println(err.Error()) return err } err = databaseutils.DBUtil.UpdateRow("tokens", "value", token, [][]string{[]string{"used", "1"}}) if err != nil { fmt.Println(err.Error()) return err } return nil }