250 lines
6.0 KiB
Go
250 lines
6.0 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"database/sql"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/gin-gonic/gin"
|
||
|
"golang.org/x/crypto/bcrypt"
|
||
|
"gopkg.in/mailgun/mailgun-go.v1"
|
||
|
"zxq.co/ripple/rippleapi/common"
|
||
|
"zxq.co/x/rs"
|
||
|
)
|
||
|
|
||
|
func passwordReset(c *gin.Context) {
|
||
|
ctx := getContext(c)
|
||
|
if ctx.User.ID != 0 {
|
||
|
simpleReply(c, errorMessage{T(c, "You're already logged in!")})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
field := "username"
|
||
|
if strings.Contains(c.PostForm("username"), "@") {
|
||
|
field = "email"
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
id int
|
||
|
username string
|
||
|
email string
|
||
|
privileges uint64
|
||
|
)
|
||
|
|
||
|
err := db.QueryRow("SELECT id, username, email, privileges FROM users WHERE "+field+" = ?",
|
||
|
c.PostForm("username")).
|
||
|
Scan(&id, &username, &email, &privileges)
|
||
|
|
||
|
switch err {
|
||
|
case nil:
|
||
|
// ignore
|
||
|
case sql.ErrNoRows:
|
||
|
simpleReply(c, errorMessage{T(c, "That user could not be found.")})
|
||
|
return
|
||
|
default:
|
||
|
c.Error(err)
|
||
|
resp500(c)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if common.UserPrivileges(privileges)&
|
||
|
(common.UserPrivilegeNormal|common.UserPrivilegePendingVerification) == 0 {
|
||
|
simpleReply(c, errorMessage{T(c, "You look pretty banned/locked here.")})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// generate key
|
||
|
key := rs.String(50)
|
||
|
|
||
|
// TODO: WHY THE FUCK DOES THIS USE USERNAME AND NOT ID PLEASE WRITE MIGRATION
|
||
|
_, err = db.Exec("INSERT INTO password_recovery(k, u) VALUES (?, ?)", key, username)
|
||
|
|
||
|
if err != nil {
|
||
|
c.Error(err)
|
||
|
resp500(c)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
content := T(c,
|
||
|
"Hey %s! Someone, which we really hope was you, requested a password reset for your account. In case it was you, please <a href='%s'>click here</a> to reset your password on Ripple. Otherwise, silently ignore this email.",
|
||
|
username,
|
||
|
config.BaseURL+"/pwreset/continue?k="+key,
|
||
|
)
|
||
|
msg := mailgun.NewMessage(
|
||
|
config.MailgunFrom,
|
||
|
T(c, "Ripple password recovery instructions"),
|
||
|
content,
|
||
|
email,
|
||
|
)
|
||
|
msg.SetHtml(content)
|
||
|
_, _, err = mg.Send(msg)
|
||
|
|
||
|
if err != nil {
|
||
|
c.Error(err)
|
||
|
resp500(c)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
addMessage(c, successMessage{T(c, "Done! You should shortly receive an email from us at the email you used to sign up on Ripple.")})
|
||
|
getSession(c).Save()
|
||
|
c.Redirect(302, "/")
|
||
|
}
|
||
|
|
||
|
func passwordResetContinue(c *gin.Context) {
|
||
|
k := c.Query("k")
|
||
|
|
||
|
// todo: check logged in
|
||
|
if k == "" {
|
||
|
respEmpty(c, T(c, "Password reset"), errorMessage{T(c, "Nope.")})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var username string
|
||
|
switch err := db.QueryRow("SELECT u FROM password_recovery WHERE k = ? LIMIT 1", k).
|
||
|
Scan(&username); err {
|
||
|
case nil:
|
||
|
// move on
|
||
|
case sql.ErrNoRows:
|
||
|
respEmpty(c, T(c, "Reset password"), errorMessage{T(c, "That key could not be found. Perhaps it expired?")})
|
||
|
return
|
||
|
default:
|
||
|
c.Error(err)
|
||
|
resp500(c)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
renderResetPassword(c, username, k)
|
||
|
}
|
||
|
|
||
|
func passwordResetContinueSubmit(c *gin.Context) {
|
||
|
// todo: check logged in
|
||
|
var username string
|
||
|
switch err := db.QueryRow("SELECT u FROM password_recovery WHERE k = ? LIMIT 1", c.PostForm("k")).
|
||
|
Scan(&username); err {
|
||
|
case nil:
|
||
|
// move on
|
||
|
case sql.ErrNoRows:
|
||
|
respEmpty(c, T(c, "Reset password"), errorMessage{T(c, "That key could not be found. Perhaps it expired?")})
|
||
|
return
|
||
|
default:
|
||
|
c.Error(err)
|
||
|
resp500(c)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
p := c.PostForm("password")
|
||
|
|
||
|
if s := validatePassword(p); s != "" {
|
||
|
renderResetPassword(c, username, c.PostForm("k"), errorMessage{T(c, s)})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
pass, err := generatePassword(p)
|
||
|
if err != nil {
|
||
|
c.Error(err)
|
||
|
resp500(c)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
_, err = db.Exec("UPDATE users SET password_md5 = ?, salt = '', password_version = '2' WHERE username = ?",
|
||
|
pass, username)
|
||
|
if err != nil {
|
||
|
c.Error(err)
|
||
|
resp500(c)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
_, err = db.Exec("DELETE FROM password_recovery WHERE k = ? LIMIT 1", c.PostForm("k"))
|
||
|
if err != nil {
|
||
|
c.Error(err)
|
||
|
resp500(c)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
addMessage(c, successMessage{T(c, "All right, we have changed your password and you should now be able to login! Have fun!")})
|
||
|
getSession(c).Save()
|
||
|
c.Redirect(302, "/login")
|
||
|
}
|
||
|
|
||
|
func renderResetPassword(c *gin.Context, username, k string, messages ...message) {
|
||
|
simple(c, getSimpleByFilename("pwreset/continue.html"), messages, map[string]interface{}{
|
||
|
"Username": username,
|
||
|
"Key": k,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func generatePassword(p string) (string, error) {
|
||
|
s, err := bcrypt.GenerateFromPassword([]byte(cmd5(p)), bcrypt.DefaultCost)
|
||
|
return string(s), err
|
||
|
}
|
||
|
|
||
|
func changePassword(c *gin.Context) {
|
||
|
ctx := getContext(c)
|
||
|
if ctx.User.ID == 0 {
|
||
|
resp403(c)
|
||
|
}
|
||
|
s, err := qb.QueryRow("SELECT email FROM users WHERE id = ?", ctx.User.ID)
|
||
|
if err != nil {
|
||
|
c.Error(err)
|
||
|
}
|
||
|
simple(c, getSimpleByFilename("settings/password.html"), nil, map[string]interface{}{
|
||
|
"email": s["email"],
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func changePasswordSubmit(c *gin.Context) {
|
||
|
var messages []message
|
||
|
ctx := getContext(c)
|
||
|
if ctx.User.ID == 0 {
|
||
|
resp403(c)
|
||
|
}
|
||
|
defer func() {
|
||
|
s, err := qb.QueryRow("SELECT email FROM users WHERE id = ?", ctx.User.ID)
|
||
|
if err != nil {
|
||
|
c.Error(err)
|
||
|
}
|
||
|
simple(c, getSimpleByFilename("settings/password.html"), messages, map[string]interface{}{
|
||
|
"email": s["email"],
|
||
|
})
|
||
|
}()
|
||
|
|
||
|
if ok, _ := CSRF.Validate(ctx.User.ID, c.PostForm("csrf")); !ok {
|
||
|
addMessage(c, errorMessage{T(c, "Your session has expired. Please try redoing what you were trying to do.")})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var password string
|
||
|
db.Get(&password, "SELECT password_md5 FROM users WHERE id = ? LIMIT 1", ctx.User.ID)
|
||
|
|
||
|
if err := bcrypt.CompareHashAndPassword(
|
||
|
[]byte(password),
|
||
|
[]byte(cmd5(c.PostForm("currentpassword"))),
|
||
|
); err != nil {
|
||
|
messages = append(messages, errorMessage{T(c, "Wrong password.")})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
uq := new(common.UpdateQuery)
|
||
|
uq.Add("email", c.PostForm("email"))
|
||
|
if c.PostForm("newpassword") != "" {
|
||
|
if s := validatePassword(c.PostForm("newpassword")); s != "" {
|
||
|
messages = append(messages, errorMessage{T(c, s)})
|
||
|
return
|
||
|
}
|
||
|
pw, err := generatePassword(c.PostForm("newpassword"))
|
||
|
if err == nil {
|
||
|
uq.Add("password_md5", pw)
|
||
|
}
|
||
|
sess := getSession(c)
|
||
|
sess.Set("pw", cmd5(pw))
|
||
|
sess.Save()
|
||
|
}
|
||
|
_, err := db.Exec("UPDATE users SET "+uq.Fields()+" WHERE id = ? LIMIT 1", append(uq.Parameters, ctx.User.ID)...)
|
||
|
if err != nil {
|
||
|
c.Error(err)
|
||
|
}
|
||
|
|
||
|
db.Exec("UPDATE users SET flags = flags & ~3 WHERE id = ? LIMIT 1", ctx.User.ID)
|
||
|
|
||
|
messages = append(messages, successMessage{T(c, "Your settings have been saved.")})
|
||
|
}
|