2020-11-28 22:23:40 +03:00
package routes
import (
"fmt"
2021-04-05 23:57:57 +03:00
"github.com/emvi/logbuch"
2021-01-30 12:34:52 +03:00
"github.com/gorilla/mux"
2020-11-28 22:23:40 +03:00
conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/models/view"
"github.com/muety/wakapi/services"
2021-01-31 16:34:54 +03:00
"github.com/muety/wakapi/utils"
2020-11-28 22:23:40 +03:00
"net/http"
2021-04-05 23:57:57 +03:00
"net/url"
2020-11-28 22:23:40 +03:00
"time"
)
type LoginHandler struct {
config * conf . Config
userSrvc services . IUserService
2021-04-05 23:57:57 +03:00
mailSrvc services . IMailService
2020-11-28 22:23:40 +03:00
}
2021-04-05 23:57:57 +03:00
func NewLoginHandler ( userService services . IUserService , mailService services . IMailService ) * LoginHandler {
2020-11-28 22:23:40 +03:00
return & LoginHandler {
config : conf . Get ( ) ,
userSrvc : userService ,
2021-04-05 23:57:57 +03:00
mailSrvc : mailService ,
2020-11-28 22:23:40 +03:00
}
}
2021-01-30 12:34:52 +03:00
func ( h * LoginHandler ) RegisterRoutes ( router * mux . Router ) {
router . Path ( "/login" ) . Methods ( http . MethodGet ) . HandlerFunc ( h . GetIndex )
router . Path ( "/login" ) . Methods ( http . MethodPost ) . HandlerFunc ( h . PostLogin )
router . Path ( "/logout" ) . Methods ( http . MethodPost ) . HandlerFunc ( h . PostLogout )
router . Path ( "/signup" ) . Methods ( http . MethodGet ) . HandlerFunc ( h . GetSignup )
router . Path ( "/signup" ) . Methods ( http . MethodPost ) . HandlerFunc ( h . PostSignup )
2021-04-05 23:57:57 +03:00
router . Path ( "/set-password" ) . Methods ( http . MethodGet ) . HandlerFunc ( h . GetSetPassword )
router . Path ( "/set-password" ) . Methods ( http . MethodPost ) . HandlerFunc ( h . PostSetPassword )
router . Path ( "/reset-password" ) . Methods ( http . MethodGet ) . HandlerFunc ( h . GetResetPassword )
router . Path ( "/reset-password" ) . Methods ( http . MethodPost ) . HandlerFunc ( h . PostResetPassword )
2021-01-30 12:34:52 +03:00
}
2020-11-28 22:23:40 +03:00
func ( h * LoginHandler ) GetIndex ( w http . ResponseWriter , r * http . Request ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
if cookie , err := r . Cookie ( models . AuthCookieKey ) ; err == nil && cookie . Value != "" {
http . Redirect ( w , r , fmt . Sprintf ( "%s/summary" , h . config . Server . BasePath ) , http . StatusFound )
return
}
templates [ conf . LoginTemplate ] . Execute ( w , h . buildViewModel ( r ) )
}
func ( h * LoginHandler ) PostLogin ( w http . ResponseWriter , r * http . Request ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
if cookie , err := r . Cookie ( models . AuthCookieKey ) ; err == nil && cookie . Value != "" {
http . Redirect ( w , r , fmt . Sprintf ( "%s/summary" , h . config . Server . BasePath ) , http . StatusFound )
return
}
var login models . Login
if err := r . ParseForm ( ) ; err != nil {
w . WriteHeader ( http . StatusBadRequest )
templates [ conf . LoginTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "missing parameters" ) )
return
}
if err := loginDecoder . Decode ( & login , r . PostForm ) ; err != nil {
w . WriteHeader ( http . StatusBadRequest )
templates [ conf . LoginTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "missing parameters" ) )
return
}
user , err := h . userSrvc . GetUserById ( login . Username )
if err != nil {
w . WriteHeader ( http . StatusNotFound )
templates [ conf . LoginTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "resource not found" ) )
return
}
2021-01-31 16:34:54 +03:00
if ! utils . CompareBcrypt ( user . Password , login . Password , h . config . Security . PasswordSalt ) {
2020-11-28 22:23:40 +03:00
w . WriteHeader ( http . StatusUnauthorized )
templates [ conf . LoginTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "invalid credentials" ) )
return
}
2021-01-31 16:34:54 +03:00
encoded , err := h . config . Security . SecureCookie . Encode ( models . AuthCookieKey , login . Username )
2020-11-28 22:23:40 +03:00
if err != nil {
w . WriteHeader ( http . StatusInternalServerError )
templates [ conf . LoginTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "internal server error" ) )
return
}
user . LastLoggedInAt = models . CustomTime ( time . Now ( ) )
h . userSrvc . Update ( user )
2022-01-17 10:25:29 +03:00
http . SetCookie ( w , h . config . CreateCookie ( models . AuthCookieKey , encoded ) )
2020-11-28 22:23:40 +03:00
http . Redirect ( w , r , fmt . Sprintf ( "%s/summary" , h . config . Server . BasePath ) , http . StatusFound )
}
func ( h * LoginHandler ) PostLogout ( w http . ResponseWriter , r * http . Request ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
2022-01-17 10:25:29 +03:00
http . SetCookie ( w , h . config . GetClearCookie ( models . AuthCookieKey ) )
2020-11-28 22:23:40 +03:00
http . Redirect ( w , r , fmt . Sprintf ( "%s/" , h . config . Server . BasePath ) , http . StatusFound )
}
func ( h * LoginHandler ) GetSignup ( w http . ResponseWriter , r * http . Request ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
if cookie , err := r . Cookie ( models . AuthCookieKey ) ; err == nil && cookie . Value != "" {
http . Redirect ( w , r , fmt . Sprintf ( "%s/summary" , h . config . Server . BasePath ) , http . StatusFound )
return
}
templates [ conf . SignupTemplate ] . Execute ( w , h . buildViewModel ( r ) )
}
func ( h * LoginHandler ) PostSignup ( w http . ResponseWriter , r * http . Request ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-02-06 12:59:12 +03:00
if ! h . config . IsDev ( ) && ! h . config . Security . AllowSignup {
w . WriteHeader ( http . StatusForbidden )
templates [ conf . SignupTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "registration is disabled on this server" ) )
return
}
2020-11-28 22:23:40 +03:00
if cookie , err := r . Cookie ( models . AuthCookieKey ) ; err == nil && cookie . Value != "" {
http . Redirect ( w , r , fmt . Sprintf ( "%s/summary" , h . config . Server . BasePath ) , http . StatusFound )
return
}
var signup models . Signup
if err := r . ParseForm ( ) ; err != nil {
w . WriteHeader ( http . StatusBadRequest )
templates [ conf . SignupTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "missing parameters" ) )
return
}
if err := signupDecoder . Decode ( & signup , r . PostForm ) ; err != nil {
w . WriteHeader ( http . StatusBadRequest )
templates [ conf . SignupTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "missing parameters" ) )
return
}
if ! signup . IsValid ( ) {
w . WriteHeader ( http . StatusBadRequest )
templates [ conf . SignupTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "invalid parameters" ) )
return
}
2021-02-12 20:49:47 +03:00
numUsers , _ := h . userSrvc . Count ( )
_ , created , err := h . userSrvc . CreateOrGet ( & signup , numUsers == 0 )
2020-11-28 22:23:40 +03:00
if err != nil {
w . WriteHeader ( http . StatusInternalServerError )
templates [ conf . SignupTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "failed to create new user" ) )
return
}
if ! created {
w . WriteHeader ( http . StatusConflict )
templates [ conf . SignupTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "user already existing" ) )
return
}
http . Redirect ( w , r , fmt . Sprintf ( "%s/?success=%s" , h . config . Server . BasePath , "account created successfully" ) , http . StatusFound )
}
2021-04-05 23:57:57 +03:00
func ( h * LoginHandler ) GetResetPassword ( w http . ResponseWriter , r * http . Request ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
templates [ conf . ResetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) )
}
func ( h * LoginHandler ) GetSetPassword ( w http . ResponseWriter , r * http . Request ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
values , _ := url . ParseQuery ( r . URL . RawQuery )
token := values . Get ( "token" )
if token == "" {
w . WriteHeader ( http . StatusUnauthorized )
templates [ conf . SetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "invalid or missing token" ) )
return
}
vm := & view . SetPasswordViewModel {
LoginViewModel : * h . buildViewModel ( r ) ,
Token : token ,
}
templates [ conf . SetPasswordTemplate ] . Execute ( w , vm )
}
func ( h * LoginHandler ) PostSetPassword ( w http . ResponseWriter , r * http . Request ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
var setRequest models . SetPasswordRequest
if err := r . ParseForm ( ) ; err != nil {
w . WriteHeader ( http . StatusBadRequest )
templates [ conf . SetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "missing parameters" ) )
return
}
if err := signupDecoder . Decode ( & setRequest , r . PostForm ) ; err != nil {
w . WriteHeader ( http . StatusBadRequest )
templates [ conf . SetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "missing parameters" ) )
return
}
user , err := h . userSrvc . GetUserByResetToken ( setRequest . Token )
if err != nil {
w . WriteHeader ( http . StatusUnauthorized )
templates [ conf . SetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "invalid token" ) )
return
}
if ! setRequest . IsValid ( ) {
w . WriteHeader ( http . StatusBadRequest )
templates [ conf . SetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "invalid parameters" ) )
return
}
user . Password = setRequest . Password
2021-04-10 01:07:13 +03:00
user . ResetToken = ""
2021-04-05 23:57:57 +03:00
if hash , err := utils . HashBcrypt ( user . Password , h . config . Security . PasswordSalt ) ; err != nil {
w . WriteHeader ( http . StatusInternalServerError )
templates [ conf . SetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "failed to set new password" ) )
return
} else {
user . Password = hash
}
if _ , err := h . userSrvc . Update ( user ) ; err != nil {
w . WriteHeader ( http . StatusInternalServerError )
templates [ conf . SetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "failed to save new password" ) )
return
}
http . Redirect ( w , r , fmt . Sprintf ( "%s/login?success=%s" , h . config . Server . BasePath , "password updated successfully" ) , http . StatusFound )
}
func ( h * LoginHandler ) PostResetPassword ( w http . ResponseWriter , r * http . Request ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-04-10 01:07:13 +03:00
if ! h . config . Mail . Enabled {
w . WriteHeader ( http . StatusNotImplemented )
templates [ conf . ResetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "mailing is disabled on this server" ) )
return
}
2021-04-05 23:57:57 +03:00
var resetRequest models . ResetPasswordRequest
if err := r . ParseForm ( ) ; err != nil {
w . WriteHeader ( http . StatusBadRequest )
templates [ conf . ResetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "missing parameters" ) )
return
}
if err := resetPasswordDecoder . Decode ( & resetRequest , r . PostForm ) ; err != nil {
w . WriteHeader ( http . StatusBadRequest )
templates [ conf . ResetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "missing parameters" ) )
return
}
if user , err := h . userSrvc . GetUserByEmail ( resetRequest . Email ) ; user != nil && err == nil {
if u , err := h . userSrvc . GenerateResetToken ( user ) ; err != nil {
w . WriteHeader ( http . StatusInternalServerError )
templates [ conf . ResetPasswordTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "failed to generate password reset token" ) )
return
} else {
go func ( user * models . User ) {
link := fmt . Sprintf ( "%s/set-password?token=%s" , h . config . Server . GetPublicUrl ( ) , user . ResetToken )
2021-04-10 11:48:06 +03:00
if err := h . mailSrvc . SendPasswordReset ( user , link ) ; err != nil {
2022-02-17 14:20:22 +03:00
conf . Log ( ) . Request ( r ) . Error ( "failed to send password reset mail to %s - %v" , user . ID , err )
2021-04-05 23:57:57 +03:00
} else {
logbuch . Info ( "sent password reset mail to %s" , user . ID )
}
} ( u )
}
} else {
2021-04-16 16:59:39 +03:00
conf . Log ( ) . Request ( r ) . Warn ( "password reset requested for unregistered address '%s'" , resetRequest . Email )
2021-04-05 23:57:57 +03:00
}
http . Redirect ( w , r , fmt . Sprintf ( "%s/?success=%s" , h . config . Server . BasePath , "an e-mail was sent to you in case your e-mail address was registered" ) , http . StatusFound )
}
2020-11-28 22:23:40 +03:00
func ( h * LoginHandler ) buildViewModel ( r * http . Request ) * view . LoginViewModel {
2021-02-12 20:49:47 +03:00
numUsers , _ := h . userSrvc . Count ( )
2020-11-28 22:23:40 +03:00
return & view . LoginViewModel {
2022-02-16 10:56:27 +03:00
Success : r . URL . Query ( ) . Get ( "success" ) ,
Error : r . URL . Query ( ) . Get ( "error" ) ,
TotalUsers : int ( numUsers ) ,
2022-02-17 11:53:37 +03:00
AllowSignup : h . config . IsDev ( ) || h . config . Security . AllowSignup ,
2020-11-28 22:23:40 +03:00
}
}