package main import ( "database/sql" "html/template" "net/http" "strconv" "strings" "time" "net/url" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" "zxq.co/ripple/rippleapi/common" "zxq.co/x/rs" ) func loginSubmit(c *gin.Context) { if getContext(c).User.ID != 0 { simpleReply(c, errorMessage{T(c, "You're already logged in!")}) return } if c.PostForm("username") == "" || c.PostForm("password") == "" { simpleReply(c, errorMessage{T(c, "Username or password not set.")}) return } param := "username_safe" u := c.PostForm("username") if strings.Contains(u, "@") { param = "email" } else { u = safeUsername(u) } var data struct { ID int Username string Password string PasswordVersion int Country string pRaw int64 Privileges common.UserPrivileges Flags uint } err := db.QueryRow(` SELECT u.id, u.password_md5, u.username, u.password_version, s.country, u.privileges, u.flags FROM users u LEFT JOIN users_stats s ON s.id = u.id WHERE u.`+param+` = ? LIMIT 1`, strings.TrimSpace(u)).Scan( &data.ID, &data.Password, &data.Username, &data.PasswordVersion, &data.Country, &data.pRaw, &data.Flags, ) data.Privileges = common.UserPrivileges(data.pRaw) switch { case err == sql.ErrNoRows: if param == "username_safe" { param = "username" } simpleReply(c, errorMessage{T(c, "No user with such %s!", param)}) return case err != nil: c.Error(err) resp500(c) return } if data.PasswordVersion == 1 { addMessage(c, warningMessage{T(c, "Your password is sooooooo old, that we don't even know how to deal with it anymore. Could you please change it?")}) c.Redirect(302, "/pwreset") return } if err := bcrypt.CompareHashAndPassword( []byte(data.Password), []byte(cmd5(c.PostForm("password"))), ); err != nil { simpleReply(c, errorMessage{T(c, "Wrong password.")}) return } // update password if cost is < bcrypt.DefaultCost if i, err := bcrypt.Cost([]byte(data.Password)); err == nil && i < bcrypt.DefaultCost { pass, err := bcrypt.GenerateFromPassword([]byte(cmd5(c.PostForm("password"))), bcrypt.DefaultCost) if err == nil { if _, err := db.Exec("UPDATE users SET password_md5 = ? WHERE id = ?", string(pass), data.ID); err == nil { data.Password = string(pass) } } } sess := getSession(c) if data.Privileges&common.UserPrivilegePendingVerification > 0 { setYCookie(data.ID, c) addMessage(c, warningMessage{T(c, "You will need to verify your account first.")}) sess.Save() c.Redirect(302, "/register/verify?u="+strconv.Itoa(data.ID)) return } if data.Privileges&common.UserPrivilegeNormal == 0 { simpleReply(c, errorMessage{T(c, "You are not allowed to login. This means your account is either banned or locked.")}) return } setYCookie(data.ID, c) sess.Set("userid", data.ID) sess.Set("pw", cmd5(data.Password)) sess.Set("logout", rs.String(15)) tfaEnabled := is2faEnabled(data.ID) if tfaEnabled == 0 { afterLogin(c, data.ID, data.Country, data.Flags) } else { sess.Set("2fa_must_validate", true) } redir := c.PostForm("redir") if len(redir) > 0 && redir[0] != '/' { redir = "" } if tfaEnabled > 0 { sess.Save() c.Redirect(302, "/2fa_gateway?redir="+url.QueryEscape(redir)) } else { addMessage(c, successMessage{T(c, "Hey %s! You are now logged in.", template.HTMLEscapeString(data.Username))}) sess.Save() if redir == "" { redir = "/" } c.Redirect(302, redir) } return } func afterLogin(c *gin.Context, id int, country string, flags uint) { s, err := generateToken(id, c) if err != nil { resp500(c) c.Error(err) return } getSession(c).Set("token", s) if country == "XX" { setCountry(c, id) } logIP(c, id) } func safeUsername(u string) string { return strings.Replace(strings.TrimSpace(strings.ToLower(u)), " ", "_", -1) } func logout(c *gin.Context) { ctx := getContext(c) if ctx.User.ID == 0 { respEmpty(c, "Log out", warningMessage{T(c, "You're already logged out!")}) return } sess := getSession(c) s, _ := sess.Get("logout").(string) if s != c.Query("k") { // todo: return "are you sure you want to log out?" page respEmpty(c, "Log out", warningMessage{T(c, "Your session has expired. Please try redoing what you were trying to do.")}) return } sess.Clear() http.SetCookie(c.Writer, &http.Cookie{ Name: "rt", Value: "", Expires: time.Now().Add(-time.Hour), }) addMessage(c, successMessage{T(c, "Successfully logged out.")}) sess.Save() c.Redirect(302, "/") } func generateToken(id int, c *gin.Context) (string, error) { tok := common.RandomString(32) _, err := db.Exec( `INSERT INTO tokens(user, privileges, description, token, private) VALUES ( ?, '0', ?, ?, '1');`, id, clientIP(c), cmd5(tok)) if err != nil { return "", err } return tok, nil } func checkToken(s string, id int, c *gin.Context) (string, error) { if s == "" { return generateToken(id, c) } if err := db.QueryRow("SELECT 1 FROM tokens WHERE token = ?", cmd5(s)).Scan(new(int)); err == sql.ErrNoRows { return generateToken(id, c) } else if err != nil { return "", err } return s, nil }