From 2f06fb8fa1403e127e7024d69138a589eaca0d53 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 11 May 2017 23:18:56 +0200 Subject: [PATCH] Stateless cookies (#372) * Remove eddie4.nl tracker It resolves to the same IP address as leechers-paradise which we already have. * Remove database usage from cookie auth * Hide "Remember Me" as it doesn't work yet --- config/trackers.go | 1 - model/user.go | 7 +- service/user/cookieHelper.go | 153 ++++++++++++++++++----------------- service/user/user.go | 26 +----- templates/FAQ.html | 1 - templates/user/login.html | 6 +- 6 files changed, 92 insertions(+), 102 deletions(-) diff --git a/config/trackers.go b/config/trackers.go index 8fdbb9b7..4f38177f 100644 --- a/config/trackers.go +++ b/config/trackers.go @@ -10,6 +10,5 @@ var Trackers = []string{ "udp://explodie.org:6969", "udp://tracker.opentrackr.org:1337", "udp://tracker.internetwarriors.net:1337/announce", - "udp://eddie4.nl:6969/announce", "http://mgtracker.org:6969/announce", "http://tracker.baka-sub.cf/announce"} diff --git a/model/user.go b/model/user.go index 1e1e3ca7..bf1fd654 100644 --- a/model/user.go +++ b/model/user.go @@ -12,8 +12,9 @@ type User struct { Status int `gorm:"column:status"` CreatedAt time.Time `gorm:"column:created_at"` UpdatedAt time.Time `gorm:"column:updated_at"` - Token string `gorm:"column:api_token"` - TokenExpiration time.Time `gorm:"column:api_token_expiry"` + // Currently unused (auth is stateless now) + /*Token string `gorm:"column:api_token"` + TokenExpiration time.Time `gorm:"column:api_token_expiry"`*/ Language string `gorm:"column:language"` // TODO: move this to PublicUser @@ -42,7 +43,7 @@ func (u User) Size() (s int) { 4*3 + //time.Time 3*2 + // arrays // string arrays - len(u.Username) + len(u.Password) + len(u.Email) + len(u.Token) + len(u.MD5) + len(u.Language) + len(u.Username) + len(u.Password) + len(u.Email) + len(u.MD5) + len(u.Language) s *= 8 // Ignoring foreign key users. Fuck them. diff --git a/service/user/cookieHelper.go b/service/user/cookieHelper.go index b28e5a98..940d5b3d 100644 --- a/service/user/cookieHelper.go +++ b/service/user/cookieHelper.go @@ -5,96 +5,98 @@ import ( "github.com/ewhal/nyaa/db" "github.com/ewhal/nyaa/model" formStruct "github.com/ewhal/nyaa/service/user/form" - "github.com/ewhal/nyaa/util/log" "github.com/ewhal/nyaa/util/modelHelper" + "github.com/ewhal/nyaa/util/timeHelper" "github.com/gorilla/securecookie" "golang.org/x/crypto/bcrypt" "net/http" + "strconv" + "time" ) +const CookieName = "session" + +// If you want to keep login cookies between restarts you need to make these permanent var cookieHandler = securecookie.New( securecookie.GenerateRandomKey(64), securecookie.GenerateRandomKey(32)) -func Token(r *http.Request) (string, error) { - var token string - cookie, err := r.Cookie("session") +// Encoding & Decoding of the cookie value +func DecodeCookie(cookie_value string) (uint, error) { + value := make(map[string]string) + err := cookieHandler.Decode(CookieName, cookie_value, &value) if err != nil { - return token, err + return 0, err } - cookieValue := make(map[string]string) - err = cookieHandler.Decode("session", cookie.Value, &cookieValue) - if err != nil { - return token, err + time_int, _ := strconv.ParseInt(value["t"], 10, 0) + if timeHelper.IsExpired(time.Unix(time_int, 0)) { + return 0, errors.New("Cookie is expired") } - token = cookieValue["token"] - if len(token) == 0 { - return token, errors.New("token is empty") - } - return token, nil + ret, err := strconv.ParseUint(value["u"], 10, 0) + return uint(ret), err } -// SetCookie sets a cookie. -func SetCookie(w http.ResponseWriter, token string) (int, error) { +func EncodeCookie(user_id uint) (string, error) { + validUntil := timeHelper.FewDaysLater(7) // 1 week value := map[string]string{ - "token": token, + "u": strconv.FormatUint(uint64(user_id), 10), + "t": strconv.FormatInt(validUntil.Unix(), 10), } - encoded, err := cookieHandler.Encode("session", value) - if err != nil { - return http.StatusInternalServerError, err - } - cookie := &http.Cookie{ - Name: "session", - Value: encoded, - Path: "/", - } - http.SetCookie(w, cookie) - return http.StatusOK, nil + return cookieHandler.Encode(CookieName, value) } -// ClearCookie clears a cookie. func ClearCookie(w http.ResponseWriter) (int, error) { cookie := &http.Cookie{ - Name: "session", + Name: CookieName, Value: "", Path: "/", + HttpOnly: true, MaxAge: -1, } http.SetCookie(w, cookie) return http.StatusOK, nil } -// SetCookieHandler sets a cookie with email and password. +// SetCookieHandler sets the authentication cookie func SetCookieHandler(w http.ResponseWriter, email string, pass string) (int, error) { - if email != "" && pass != "" { - var user model.User - isValidEmail, _ := formStruct.EmailValidation(email, formStruct.NewErrors()) - if isValidEmail { - log.Debug("User entered valid email.") - if db.ORM.Where("email = ?", email).First(&user).RecordNotFound() { - return http.StatusNotFound, errors.New("User not found") - } - } else { - log.Debug("User entered username.") - if db.ORM.Where("username = ?", email).First(&user).RecordNotFound() { - return http.StatusNotFound, errors.New("User not found") - } - } - err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(pass)) - if err != nil { - return http.StatusUnauthorized, errors.New("Password incorrect") - } - if user.Status == -1 { - return http.StatusUnauthorized, errors.New("Account banned") - } - status, err := SetCookie(w, user.Token) - if err != nil { - return status, err - } - w.Header().Set("X-Auth-Token", user.Token) - return http.StatusOK, nil + if email == "" || pass == "" { + return http.StatusNotFound, errors.New("No username/password entered") } - return http.StatusNotFound, errors.New("user not found") + + var user model.User + // search by email or username + isValidEmail, _ := formStruct.EmailValidation(email, formStruct.NewErrors()) + if isValidEmail { + if db.ORM.Where("email = ?", email).First(&user).RecordNotFound() { + return http.StatusNotFound, errors.New("User not found") + } + } else { + if db.ORM.Where("username = ?", email).First(&user).RecordNotFound() { + return http.StatusNotFound, errors.New("User not found") + } + } + err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(pass)) + if err != nil { + return http.StatusUnauthorized, errors.New("Password incorrect") + } + if user.Status == -1 { + return http.StatusUnauthorized, errors.New("Account banned") + } + + encoded, err := EncodeCookie(user.ID) + if err != nil { + return http.StatusInternalServerError, err + } + cookie := &http.Cookie{ + Name: CookieName, + Value: encoded, + Path: "/", + HttpOnly: true, + } + http.SetCookie(w, cookie) + // also set response header for convenience + w.Header().Set("X-Auth-Token", encoded) + return http.StatusOK, nil } // RegisterHanderFromForm sets cookie from a RegistrationForm. @@ -111,24 +113,31 @@ func RegisterHandler(w http.ResponseWriter, r *http.Request) (int, error) { return RegisterHanderFromForm(w, registrationForm) } -// CurrentUser get a current user. +// CurrentUser determines the current user from the request func CurrentUser(r *http.Request) (model.User, error) { var user model.User - var token string - var err error - token = r.Header.Get("X-Auth-Token") - if len(token) > 0 { - log.Debug("header token exists") - } else { - token, err = Token(r) - log.Debug("header token does not exist") + var encoded string + + encoded = r.Header.Get("X-Auth-Token") + if len(encoded) == 0 { + // check cookie instead + cookie, err := r.Cookie(CookieName) if err != nil { return user, err } + encoded = cookie.Value } - if db.ORM.Where("api_token = ?", token).First(&user).RecordNotFound() { - return user, errors.New("user not found") + user_id, err := DecodeCookie(encoded) + if err != nil { + return user, err } - err = db.ORM.Model(&user).Error - return user, err + if db.ORM.Where("user_id = ?", user_id).First(&user).RecordNotFound() { + return user, errors.New("User not found") + } + + if user.Status == -1 { + // recheck as user might've been banned in the meantime + return user, errors.New("Account banned") + } + return user, nil } diff --git a/service/user/user.go b/service/user/user.go index 88b2ada6..27100c40 100644 --- a/service/user/user.go +++ b/service/user/user.go @@ -7,7 +7,6 @@ import ( "strconv" "time" - "github.com/ewhal/nyaa/config" "github.com/ewhal/nyaa/db" "github.com/ewhal/nyaa/model" formStruct "github.com/ewhal/nyaa/service/user/form" @@ -15,7 +14,6 @@ import ( "github.com/ewhal/nyaa/util/crypto" "github.com/ewhal/nyaa/util/log" "github.com/ewhal/nyaa/util/modelHelper" - "github.com/ewhal/nyaa/util/timeHelper" "golang.org/x/crypto/bcrypt" ) @@ -69,15 +67,8 @@ func CreateUserFromForm(registrationForm formStruct.RegistrationForm) (model.Use return user, err } } - token, err := crypto.GenerateRandomToken32() - if err != nil { - return user, errors.New("token not generated") - } user.Email = "" // unset email because it will be verified later - user.Token = token - user.TokenExpiration = timeHelper.FewDaysLater(config.AuthTokenExpirationDay) - log.Debugf("user %+v\n", user) if db.ORM.Create(&user).Error != nil { return user, errors.New("user not created") } @@ -157,17 +148,13 @@ func UpdateUserCore(user *model.User) (int, error) { } } - token, err := crypto.GenerateRandomToken32() + user.UpdatedAt = time.Now() + err := db.ORM.Save(user).Error if err != nil { return http.StatusInternalServerError, err } - user.Token = token - user.TokenExpiration = timeHelper.FewDaysLater(config.AuthTokenExpirationDay) - if db.ORM.Save(user).Error != nil { - return http.StatusInternalServerError, err - } - user.UpdatedAt = time.Now() + return http.StatusOK, nil } @@ -197,18 +184,13 @@ func UpdateUser(w http.ResponseWriter, form *formStruct.UserForm, currentUser *m form.Username = user.Username } if (form.Email != user.Email) { + // send verification to new email and keep old SendVerificationToUser(user, form.Email) form.Email = user.Email } log.Debugf("form %+v\n", form) modelHelper.AssignValue(&user, form) status, err := UpdateUserCore(&user) - if err != nil { - return user, status, err - } - if userPermission.CurrentUserIdentical(currentUser, user.ID) { - status, err = SetCookie(w, user.Token) - } return user, status, err } diff --git a/templates/FAQ.html b/templates/FAQ.html index b0a58191..f511f968 100644 --- a/templates/FAQ.html +++ b/templates/FAQ.html @@ -45,7 +45,6 @@ udp://tracker.leechers-paradise.org:6969 udp://explodie.org:6969 udp://tracker.opentrackr.org:1337 udp://tracker.internetwarriors.net:1337/announce -udp://eddie4.nl:6969/announce http://mgtracker.org:6969/announce http://tracker.baka-sub.cf/announce diff --git a/templates/user/login.html b/templates/user/login.html index 7ac16973..d720459d 100644 --- a/templates/user/login.html +++ b/templates/user/login.html @@ -1,4 +1,4 @@ -{{define "title"}}{{ T "sign_in_title" }}{{end}} +{{define "title"}}{{ T "sign_in_title" }}{{end}} {{define "contclass"}}cont-view{{end}} {{define "content"}}
@@ -24,8 +24,8 @@ {{end}}
- - + {{ T "forgot_password"}}