révision
a3bf2ec128
|
@ -12,8 +12,8 @@ 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"`
|
||||
ApiToken string `gorm:"column:api_token"`
|
||||
ApiTokenExpiry time.Time `gorm:"column:api_token_expiry"`
|
||||
Language string `gorm:"column:language"`
|
||||
|
||||
// TODO: move this to PublicUser
|
||||
|
@ -42,7 +42,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.ApiToken) + len(u.MD5) + len(u.Language)
|
||||
s *= 8
|
||||
|
||||
// Ignoring foreign key users. Fuck them.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,20 +67,16 @@ 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.CreatedAt = time.Now()
|
||||
// currently unused but needs to be set:
|
||||
user.ApiToken = ""
|
||||
user.ApiTokenExpiry = time.Unix(0, 0)
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
user.CreatedAt = time.Now()
|
||||
return user, nil
|
||||
}
|
||||
|
||||
|
@ -157,17 +151,12 @@ 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 +186,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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
{{define "title"}}{{T "home"}}{{end}}
|
||||
{{define "contclass"}}cont-home{{end}}
|
||||
{{ define "nav_top"}}
|
||||
<nav class="pull-right">
|
||||
<ul class="pagination">
|
||||
{{ genNav .Navigation .URL 5 }}
|
||||
</ul>
|
||||
</nav>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="blockBody">
|
||||
<ul class="list-inline" aria-label="Page navigation">
|
||||
<li><img id="mascot" src="/img/renchon.png" /></li>
|
||||
<li style="padding-top: 7%;" class="pull-right"><ul class="pagination">
|
||||
{{ genNav .Navigation .URL 5 }}
|
||||
</ul></li>
|
||||
</nav>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table custom-table-hover">
|
||||
<tr>
|
||||
|
|
|
@ -72,19 +72,20 @@
|
|||
<div style="padding-top: 10rem"></div>
|
||||
|
||||
<div class="container {{block "contclass" .}}generic{{end}}" id="container">
|
||||
<img id="mascot" class="pull-left" src="/img/renchon.png" />
|
||||
{{if not .Search.HideAdvancedSearch}}
|
||||
<a href="#advanced-search" data-toggle="collapse" style="display:block">{{T "advanced_search"}}</a>
|
||||
<form id="advanced-search" class="navbar-form pull-right collapse" role="search" action="/search" method="get">
|
||||
<div class="blockBody" style="text-align:center">
|
||||
<a href="#advanced-search" data-toggle="collapse">{{T "advanced_search"}}</a><br />
|
||||
<form id="advanced-search" class="navbar-form collapse" role="search" action="/search" method="get">
|
||||
<div class="form-group">
|
||||
{{block "search_common" .}}{{end}}
|
||||
{{block "search_advanced" .}}{{end}}
|
||||
{{block "search_button" .}}{{end}}
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
{{ block "nav_top" .}}{{end}}
|
||||
<div style="clear:both"></div>
|
||||
</div>
|
||||
<div style="margin:0.5em"></div>
|
||||
{{end}}
|
||||
|
||||
{{block "content" .}}{{T "nothing_here"}}{{end}}
|
||||
</div>
|
||||
|
|
Référencer dans un nouveau ticket