2017-05-06 21:21:39 +02:00
package userService
import (
"errors"
2017-05-26 12:12:52 +02:00
"net/http"
"strconv"
"time"
2017-05-17 07:58:40 +02:00
"github.com/NyaaPantsu/nyaa/db"
"github.com/NyaaPantsu/nyaa/model"
formStruct "github.com/NyaaPantsu/nyaa/service/user/form"
2017-05-21 20:20:40 +02:00
msg "github.com/NyaaPantsu/nyaa/util/messages"
2017-05-17 07:58:40 +02:00
"github.com/NyaaPantsu/nyaa/util/modelHelper"
"github.com/NyaaPantsu/nyaa/util/timeHelper"
2017-05-20 13:45:15 +02:00
"github.com/gorilla/context"
2017-05-09 19:23:21 +02:00
"github.com/gorilla/securecookie"
"golang.org/x/crypto/bcrypt"
2017-05-06 21:21:39 +02:00
)
2017-05-20 13:45:15 +02:00
const (
2017-05-26 12:12:52 +02:00
// CookieName : Name of cookie
CookieName = "session"
// UserContextKey : key for user context
2017-05-20 13:45:15 +02:00
UserContextKey = "user"
2017-05-24 09:11:13 +02:00
)
2017-05-12 12:40:31 +02:00
// If you want to keep login cookies between restarts you need to make these permanent
2017-05-06 21:21:39 +02:00
var cookieHandler = securecookie . New (
securecookie . GenerateRandomKey ( 64 ) ,
securecookie . GenerateRandomKey ( 32 ) )
2017-05-26 12:12:52 +02:00
// DecodeCookie : Encoding & Decoding of the cookie value
func DecodeCookie ( cookieValue string ) ( uint , error ) {
2017-05-12 12:40:31 +02:00
value := make ( map [ string ] string )
2017-05-26 12:12:52 +02:00
err := cookieHandler . Decode ( CookieName , cookieValue , & value )
2017-05-06 21:21:39 +02:00
if err != nil {
2017-05-12 12:40:31 +02:00
return 0 , err
2017-05-06 21:21:39 +02:00
}
2017-05-26 12:12:52 +02:00
timeInt , _ := strconv . ParseInt ( value [ "t" ] , 10 , 0 )
if timeHelper . IsExpired ( time . Unix ( timeInt , 0 ) ) {
2017-05-12 12:40:31 +02:00
return 0 , errors . New ( "Cookie is expired" )
2017-05-06 21:21:39 +02:00
}
2017-05-12 12:40:31 +02:00
ret , err := strconv . ParseUint ( value [ "u" ] , 10 , 0 )
return uint ( ret ) , err
2017-05-06 21:21:39 +02:00
}
2017-05-26 12:12:52 +02:00
// EncodeCookie : Encoding of the cookie value
func EncodeCookie ( userID uint ) ( string , error ) {
2017-05-12 12:40:31 +02:00
validUntil := timeHelper . FewDaysLater ( 7 ) // 1 week
2017-05-06 21:21:39 +02:00
value := map [ string ] string {
2017-05-26 12:12:52 +02:00
"u" : strconv . FormatUint ( uint64 ( userID ) , 10 ) ,
2017-05-12 12:40:31 +02:00
"t" : strconv . FormatInt ( validUntil . Unix ( ) , 10 ) ,
2017-05-06 21:21:39 +02:00
}
2017-05-12 12:40:31 +02:00
return cookieHandler . Encode ( CookieName , value )
2017-05-06 21:21:39 +02:00
}
2017-05-26 12:12:52 +02:00
// ClearCookie : Erase cookie session
2017-05-06 21:21:39 +02:00
func ClearCookie ( w http . ResponseWriter ) ( int , error ) {
cookie := & http . Cookie {
2017-05-17 07:58:40 +02:00
Name : CookieName ,
Value : "" ,
Path : "/" ,
2017-05-12 12:40:31 +02:00
HttpOnly : true ,
2017-05-17 07:58:40 +02:00
MaxAge : - 1 ,
2017-05-06 21:21:39 +02:00
}
http . SetCookie ( w , cookie )
return http . StatusOK , nil
}
2017-05-12 12:40:31 +02:00
// SetCookieHandler sets the authentication cookie
2017-05-21 20:20:40 +02:00
func SetCookieHandler ( w http . ResponseWriter , r * http . Request , email string , pass string ) ( int , error ) {
2017-05-12 12:40:31 +02:00
if email == "" || pass == "" {
return http . StatusNotFound , errors . New ( "No username/password entered" )
}
var user model . User
2017-05-21 20:20:40 +02:00
messages := msg . GetMessages ( r )
2017-05-12 12:40:31 +02:00
// search by email or username
2017-05-22 10:15:18 +02:00
isValidEmail := formStruct . EmailValidation ( email , messages )
2017-05-12 12:40:31 +02:00
if isValidEmail {
if db . ORM . Where ( "email = ?" , email ) . First ( & user ) . RecordNotFound ( ) {
return http . StatusNotFound , errors . New ( "User not found" )
2017-05-12 08:42:15 +02:00
}
2017-05-12 12:40:31 +02:00
} else {
if db . ORM . Where ( "username = ?" , email ) . First ( & user ) . RecordNotFound ( ) {
return http . StatusNotFound , errors . New ( "User not found" )
2017-05-12 08:42:15 +02:00
}
2017-05-06 21:21:39 +02:00
}
2017-05-12 12:40:31 +02:00
err := bcrypt . CompareHashAndPassword ( [ ] byte ( user . Password ) , [ ] byte ( pass ) )
if err != nil {
return http . StatusUnauthorized , errors . New ( "Password incorrect" )
}
2017-05-20 01:10:16 +02:00
if user . IsBanned ( ) {
2017-05-12 12:40:31 +02:00
return http . StatusUnauthorized , errors . New ( "Account banned" )
}
encoded , err := EncodeCookie ( user . ID )
if err != nil {
return http . StatusInternalServerError , err
}
cookie := & http . Cookie {
2017-05-17 07:58:40 +02:00
Name : CookieName ,
Value : encoded ,
Path : "/" ,
2017-05-12 12:40:31 +02:00
HttpOnly : true ,
}
http . SetCookie ( w , cookie )
// also set response header for convenience
w . Header ( ) . Set ( "X-Auth-Token" , encoded )
return http . StatusOK , nil
2017-05-06 21:21:39 +02:00
}
// RegisterHanderFromForm sets cookie from a RegistrationForm.
2017-05-21 20:20:40 +02:00
func RegisterHanderFromForm ( w http . ResponseWriter , r * http . Request , registrationForm formStruct . RegistrationForm ) ( int , error ) {
2017-05-10 21:16:30 +02:00
username := registrationForm . Username // email isn't set at this point
2017-05-06 21:21:39 +02:00
pass := registrationForm . Password
2017-05-21 20:20:40 +02:00
return SetCookieHandler ( w , r , username , pass )
2017-05-06 21:21:39 +02:00
}
// RegisterHandler sets a cookie when user registered.
func RegisterHandler ( w http . ResponseWriter , r * http . Request ) ( int , error ) {
2017-05-07 00:10:40 +02:00
var registrationForm formStruct . RegistrationForm
2017-05-06 21:21:39 +02:00
modelHelper . BindValueForm ( & registrationForm , r )
2017-05-21 20:20:40 +02:00
return RegisterHanderFromForm ( w , r , registrationForm )
2017-05-06 21:21:39 +02:00
}
2017-05-20 13:45:15 +02:00
// CurrentUser determines the current user from the request or context
2017-05-06 21:21:39 +02:00
func CurrentUser ( r * http . Request ) ( model . User , error ) {
var user model . User
2017-05-12 12:40:31 +02:00
var encoded string
2017-05-24 00:23:50 +02:00
2017-05-12 12:40:31 +02:00
encoded = r . Header . Get ( "X-Auth-Token" )
if len ( encoded ) == 0 {
// check cookie instead
cookie , err := r . Cookie ( CookieName )
2017-05-06 21:21:39 +02:00
if err != nil {
return user , err
}
2017-05-12 12:40:31 +02:00
encoded = cookie . Value
2017-05-06 21:21:39 +02:00
}
2017-05-26 12:12:52 +02:00
userID , err := DecodeCookie ( encoded )
2017-05-12 12:40:31 +02:00
if err != nil {
return user , err
}
2017-05-20 13:45:15 +02:00
userFromContext := getUserFromContext ( r )
2017-05-26 12:12:52 +02:00
if userFromContext . ID > 0 && userID == userFromContext . ID {
2017-05-20 13:45:15 +02:00
user = userFromContext
} else {
2017-05-26 12:12:52 +02:00
if db . ORM . Preload ( "Notifications" ) . Where ( "user_id = ?" , userID ) . First ( & user ) . RecordNotFound ( ) { // We only load unread notifications
2017-05-20 13:45:15 +02:00
return user , errors . New ( "User not found" )
}
2017-05-26 12:12:52 +02:00
setUserToContext ( r , user )
2017-05-12 12:40:31 +02:00
}
2017-05-20 01:10:16 +02:00
if user . IsBanned ( ) {
2017-05-12 12:40:31 +02:00
// recheck as user might've been banned in the meantime
return user , errors . New ( "Account banned" )
2017-05-06 21:21:39 +02:00
}
2017-05-12 12:40:31 +02:00
return user , nil
2017-05-06 21:21:39 +02:00
}
2017-05-20 13:45:15 +02:00
func getUserFromContext ( r * http . Request ) model . User {
if rv := context . Get ( r , UserContextKey ) ; rv != nil {
2017-05-24 09:11:13 +02:00
return rv . ( model . User )
}
return model . User { }
2017-05-20 13:45:15 +02:00
}
func setUserToContext ( r * http . Request , val model . User ) {
2017-05-24 09:11:13 +02:00
context . Set ( r , UserContextKey , val )
2017-05-24 00:23:50 +02:00
}