2023-12-24 10:18:08 +00:00
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2024 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
2020-05-04 14:58:02 +00:00
//
2023-12-24 10:18:08 +00:00
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
2020-05-04 14:58:02 +00:00
//
2023-12-24 10:18:08 +00:00
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
2020-05-04 14:58:02 +00:00
//
2023-12-24 10:18:08 +00:00
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
2020-05-04 14:58:02 +00:00
//
2023-12-24 10:18:08 +00:00
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
2020-05-04 14:58:02 +00:00
2019-09-10 16:20:25 +00:00
package api
import (
2021-12-16 14:53:58 +00:00
"bytes"
2022-10-25 16:16:34 +00:00
"encoding/base64"
2020-05-23 16:47:09 +00:00
"fmt"
2020-09-10 12:44:25 +00:00
"log"
2020-05-23 16:47:09 +00:00
"net/http"
2023-11-28 09:06:08 +00:00
"strconv"
2020-05-23 16:47:09 +00:00
"strings"
2019-09-10 16:20:25 +00:00
2021-05-05 01:48:16 +00:00
"github.com/gin-gonic/gin"
2024-01-16 20:13:24 +00:00
"github.com/rrivera/identicon"
2019-09-10 16:20:25 +00:00
2023-09-07 09:37:18 +00:00
"git.happydns.org/happyDomain/actions"
"git.happydns.org/happyDomain/config"
"git.happydns.org/happyDomain/model"
"git.happydns.org/happyDomain/storage"
2019-09-10 16:20:25 +00:00
)
2021-05-05 01:48:16 +00:00
func declareUsersRoutes ( opts * config . Options , router * gin . RouterGroup ) {
router . POST ( "/users" , func ( c * gin . Context ) {
registerUser ( opts , c )
} )
router . PATCH ( "/users" , func ( c * gin . Context ) {
specialUserOperations ( opts , c )
} )
apiUserRoutes := router . Group ( "/users/:uid" )
2021-12-16 14:53:58 +00:00
apiUserRoutes . Use ( userAuthHandler )
2021-05-05 01:48:16 +00:00
apiUserRoutes . POST ( "/email" , validateUserAddress )
apiUserRoutes . POST ( "/recovery" , recoverUserAccount )
}
func declareUsersAuthRoutes ( opts * config . Options , router * gin . RouterGroup ) {
router . GET ( "/session" , getSession )
router . DELETE ( "/session" , clearSession )
apiUserRoutes := router . Group ( "/users/:uid" )
apiUserRoutes . Use ( userHandler )
apiUserRoutes . GET ( "" , getUser )
2023-11-28 09:06:08 +00:00
apiUserRoutes . GET ( "/avatar.png" , getUserAvatar )
2023-11-24 02:46:18 +00:00
apiSameUserRoutes := router . Group ( "/users/:uid" )
apiSameUserRoutes . Use ( userHandler )
apiSameUserRoutes . Use ( SameUserHandler )
apiSameUserRoutes . GET ( "/settings" , getUserSettings )
apiSameUserRoutes . POST ( "/settings" , changeUserSettings )
2021-12-16 14:53:58 +00:00
apiUserAuthRoutes := router . Group ( "/users/:uid" )
apiUserAuthRoutes . Use ( userAuthHandler )
apiUserAuthRoutes . POST ( "/delete" , func ( c * gin . Context ) {
2021-05-05 01:48:16 +00:00
deleteUser ( opts , c )
} )
2021-12-16 14:53:58 +00:00
apiUserAuthRoutes . POST ( "/new_password" , func ( c * gin . Context ) {
2021-05-05 01:48:16 +00:00
changePassword ( opts , c )
} )
}
func myUser ( c * gin . Context ) ( user * happydns . User ) {
if u , exists := c . Get ( "LoggedUser" ) ; exists {
user = u . ( * happydns . User )
} else if u , exists := c . Get ( "user" ) ; exists {
user = u . ( * happydns . User )
}
return
2019-09-10 16:20:25 +00:00
}
2023-08-05 16:15:52 +00:00
type UserRegistration struct {
2020-12-18 22:30:58 +00:00
Email string
Password string
Language string ` json:"lang,omitempty" `
Newsletter bool ` json:"wantReceiveUpdate,omitempty" `
2019-09-10 16:20:25 +00:00
}
2023-08-05 16:15:52 +00:00
// registerUser checks and appends a user in the database.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Register account.
// @Schemes
// @Description Register a new happyDomain account (when using internal authentication system).
// @Tags users
// @Accept json
// @Produce json
// @Param body body UserRegistration true "Account information"
// @Success 200 {object} happydns.User "The created user"
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 500 {object} happydns.Error
// @Router /users [post]
2021-05-05 01:48:16 +00:00
func registerUser ( opts * config . Options , c * gin . Context ) {
2023-08-05 16:15:52 +00:00
var uu UserRegistration
2021-05-05 01:48:16 +00:00
err := c . ShouldBindJSON ( & uu )
2019-09-10 16:20:25 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid User JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2019-09-10 16:20:25 +00:00
}
2020-05-23 16:47:09 +00:00
if len ( uu . Email ) <= 3 || strings . Index ( uu . Email , "@" ) == - 1 {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "The given email is invalid." } )
return
2019-09-10 16:20:25 +00:00
}
2020-05-23 16:47:09 +00:00
if len ( uu . Password ) <= 7 {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "The given password is invalid." } )
return
2020-05-23 16:47:09 +00:00
}
2021-12-16 14:53:58 +00:00
if storage . MainStore . AuthUserExists ( uu . Email ) {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "An account already exists with the given address. Try login now." } )
return
2020-05-23 16:47:09 +00:00
}
2021-12-16 14:53:58 +00:00
user , err := happydns . NewUserAuth ( uu . Email , uu . Password )
2021-05-05 01:48:16 +00:00
if err != nil {
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : err . Error ( ) } )
return
}
2020-12-18 22:30:58 +00:00
2021-12-16 14:53:58 +00:00
user . AllowCommercials = uu . Newsletter
2021-05-05 01:48:16 +00:00
2021-12-16 14:53:58 +00:00
if err := storage . MainStore . CreateAuthUser ( user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to CreateUser in registerUser: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to create your account. Please try again later." } )
return
2019-09-10 16:20:25 +00:00
}
2021-05-05 01:48:16 +00:00
if actions . SendValidationLink ( opts , user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to SendValidationLink in registerUser: %s" , c . ClientIP ( ) , err . Error ( ) )
2023-02-23 01:21:12 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to send email validation link. Please try again later." } )
2021-05-05 01:48:16 +00:00
return
}
log . Printf ( "%s: registers new user: %s" , c . ClientIP ( ) , user . Email )
c . JSON ( http . StatusOK , user )
2019-09-10 16:20:25 +00:00
}
2020-05-23 16:47:09 +00:00
2023-08-05 16:15:52 +00:00
type UserSpecialAction struct {
// Kind of special action to perform: "recovery" or "validation".
Kind string
// Email on which to perform actions.
Email string
}
// specialUserOperations performs account recovery.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Account recovery.
// @Schemes
// @Description This will send an email to the user either to recover its account or with a new email validation link.
// @Tags users
// @Accept json
// @Produce json
// @Param body body UserSpecialAction true "Description of the action to perform and email of the user"
// @Success 200 {object} happydns.Error "Perhaps something happen"
// @Failure 500 {object} happydns.Error
// @Router /users [patch]
2021-05-05 01:48:16 +00:00
func specialUserOperations ( opts * config . Options , c * gin . Context ) {
2023-08-05 16:15:52 +00:00
var uu UserSpecialAction
2021-05-05 01:48:16 +00:00
err := c . ShouldBindJSON ( & uu )
2020-05-23 16:47:09 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid User JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-05-23 16:47:09 +00:00
}
2021-05-05 01:48:16 +00:00
res := gin . H { "errmsg" : "If this address exists in our database, you'll receive a new e-mail." }
2020-05-23 21:16:41 +00:00
2021-12-16 14:53:58 +00:00
if user , err := storage . MainStore . GetAuthUserByEmail ( uu . Email ) ; err != nil {
2022-11-27 11:43:12 +00:00
log . Printf ( "%s: unable to retrieve user %q: %s" , c . ClientIP ( ) , uu . Email , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , res )
return
2020-05-23 16:47:09 +00:00
} else {
2020-05-23 21:16:41 +00:00
if uu . Kind == "recovery" {
2021-12-16 14:53:58 +00:00
if user . EmailVerification == nil {
2021-04-20 16:04:44 +00:00
if err = actions . SendValidationLink ( opts , user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to SendValidationLink in specialUserOperations: %s" , c . ClientIP ( ) , err . Error ( ) )
2023-02-23 01:21:12 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to send email validation link. Please try again later." } )
2021-05-05 01:48:16 +00:00
return
2020-05-23 21:16:41 +00:00
}
2021-05-05 01:48:16 +00:00
log . Printf ( "%s: Sent validation link to: %s" , c . ClientIP ( ) , user . Email )
2020-05-23 21:16:41 +00:00
} else {
2021-04-20 16:04:44 +00:00
if err = actions . SendRecoveryLink ( opts , user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to SendRecoveryLink in specialUserOperations: %s" , c . ClientIP ( ) , err . Error ( ) )
2023-02-23 01:21:12 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to send accont recovery link. Please try again later." } )
2021-05-05 01:48:16 +00:00
return
2020-05-23 21:16:41 +00:00
}
2021-05-05 01:48:16 +00:00
2021-12-16 14:53:58 +00:00
if err := storage . MainStore . UpdateAuthUser ( user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to UpdateUser in specialUserOperations: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your profile. Please try again later." } )
return
}
log . Printf ( "%s: Sent recovery link to: %s" , c . ClientIP ( ) , user . Email )
2020-05-23 21:16:41 +00:00
}
} else if uu . Kind == "validation" {
2021-05-05 01:48:16 +00:00
// Email have already been validated, do nothing
2021-12-16 14:53:58 +00:00
if user . EmailVerification != nil {
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , res )
return
2020-05-23 21:16:41 +00:00
}
2021-05-05 01:48:16 +00:00
if err = actions . SendValidationLink ( opts , user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to SendValidationLink 2 in specialUserOperations: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to sent email validation link. Please try again later." } )
return
}
log . Printf ( "%s: Sent validation link to: %s" , c . ClientIP ( ) , user . Email )
2020-05-23 16:47:09 +00:00
}
}
2020-05-23 21:16:41 +00:00
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , res )
2020-05-23 16:47:09 +00:00
}
2021-05-05 01:48:16 +00:00
func SameUserHandler ( c * gin . Context ) {
myuser := c . MustGet ( "LoggedUser" ) . ( * happydns . User )
user := c . MustGet ( "user" ) . ( * happydns . User )
2021-12-16 14:53:58 +00:00
if ! bytes . Equal ( user . Id , myuser . Id ) {
2023-05-20 10:43:32 +00:00
log . Printf ( "%s: tries to do action as %s (logged %s)" , c . ClientIP ( ) , myuser . Id , user . Id )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusForbidden , gin . H { "errmsg" : "Not authorized" } )
return
2020-05-23 16:47:09 +00:00
}
2021-05-05 01:48:16 +00:00
c . Next ( )
2020-05-23 16:47:09 +00:00
}
2023-11-28 10:23:37 +00:00
// getUser shows a user in the database.
//
// @Summary Show user.
// @Schemes
// @Description Show a user from the database, information is limited to id and email if this is not the current user.
// @Tags users
// @Accept json
// @Produce json
// @Success 200 {object} happydns.User "The created user"
// @Failure 500 {object} happydns.Error
// @Router /users/{userId} [get]
2021-05-05 01:48:16 +00:00
func getUser ( c * gin . Context ) {
2023-11-24 02:46:18 +00:00
myuser := c . MustGet ( "LoggedUser" ) . ( * happydns . User )
2021-05-05 01:48:16 +00:00
user := c . MustGet ( "user" ) . ( * happydns . User )
2023-11-24 02:46:18 +00:00
if bytes . Equal ( user . Id , myuser . Id ) {
c . JSON ( http . StatusOK , user )
} else {
c . JSON ( http . StatusOK , & happydns . User {
Id : user . Id ,
Email : user . Email ,
} )
}
2020-05-23 16:47:09 +00:00
}
2023-11-28 09:06:08 +00:00
// getUserAvatar returns a unique avatar for the user.
//
// @Summary Show user's avatar.
// @Schemes
// @Description Returns a unique avatar for the user.
// @Tags users
// @Accept json
// @Produce png
// @Param size query int false "Image output desired size"
// @Success 200 {file} png
// @Failure 500 {object} happydns.Error
// @Router /users/{userId}/avatar.png [get]
func getUserAvatar ( c * gin . Context ) {
user := c . MustGet ( "user" ) . ( * happydns . User )
sizequery := c . DefaultQuery ( "size" , "300" )
size , err := strconv . ParseInt ( sizequery , 10 , 32 )
if err != nil {
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Invalid size asked: %s" , err . Error ( ) ) } )
return
} else if size > 2048 {
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "Size too large." } )
return
}
ig , err := identicon . New (
"happydomain" , // namespace
6 , // number of blocks (size)
3 , // density of points
)
if err != nil {
log . Printf ( "Unable to generate user avatar (uid=%s,user=%s): %s" , user . Id . String ( ) , user . Email , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Unable to generate avatar." } )
return
}
ii , err := ig . Draw ( user . Email )
if err != nil {
log . Printf ( "Unable to generate user avatar (uid=%s,user=%s): %s" , user . Id . String ( ) , user . Email , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Unable to generate avatar." } )
return
}
c . Writer . Header ( ) . Set ( "Content-Type" , "image/png" )
c . Writer . WriteHeader ( http . StatusOK )
ii . Png ( int ( size ) , c . Writer )
}
2023-08-05 16:15:52 +00:00
// getUserSettings gets the settings of the given user.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Retrieve user's settings.
// @Schemes
// @Description Retrieve the user's settings.
// @Tags users
// @Accept json
// @Produce json
// @Param userId path string true "User identifier"
// @Security securitydefinitions.basic
// @Success 200 {object} happydns.UserSettings "User settings"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 403 {object} happydns.Error "Not your account"
// @Router /users/{userId}/settings [get]
2021-05-05 01:48:16 +00:00
func getUserSettings ( c * gin . Context ) {
user := c . MustGet ( "user" ) . ( * happydns . User )
c . JSON ( http . StatusOK , user . Settings )
2020-11-30 07:52:58 +00:00
}
2023-08-05 16:15:52 +00:00
// changeUserSettings updates the settings of the given user.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Update user's settings.
// @Schemes
// @Description Update the user's settings.
// @Tags users
// @Accept json
// @Produce json
// @Param userId path string true "User identifier"
// @Param body body happydns.UserSettings true "User settings"
// @Security securitydefinitions.basic
// @Success 200 {object} happydns.UserSettings "User settings"
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 403 {object} happydns.Error "Not your account"
// @Failure 500 {object} happydns.Error
// @Router /users/{userId}/settings [post]
2021-05-05 01:48:16 +00:00
func changeUserSettings ( c * gin . Context ) {
user := c . MustGet ( "user" ) . ( * happydns . User )
2020-11-30 07:52:58 +00:00
var us happydns . UserSettings
2021-05-05 01:48:16 +00:00
if err := c . ShouldBindJSON ( & us ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid UserSettings JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-11-30 07:52:58 +00:00
}
2021-05-05 01:48:16 +00:00
user . Settings = us
2020-11-30 07:52:58 +00:00
2021-05-05 01:48:16 +00:00
if err := storage . MainStore . UpdateUser ( user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to UpdateUser in changeUserSettings: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your profile. Please try again later." } )
return
2020-11-30 07:52:58 +00:00
}
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , user . Settings )
2020-11-30 07:52:58 +00:00
}
2020-07-21 02:02:18 +00:00
type passwordForm struct {
Current string
Password string
PasswordConfirm string
}
2023-08-05 16:15:52 +00:00
// changePassword changes the password of the given account.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Change password
// @Schemes
// @Description Change the password of the given account.
// @Tags users
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param userId path string true "User identifier"
// @Param body body passwordForm true "Password confirmation"
// @Success 204 {null} null
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 403 {object} happydns.Error "Bad current password"
// @Failure 500 {object} happydns.Error
// @Router /users/{userId}/new_password [post]
2021-05-05 01:48:16 +00:00
func changePassword ( opts * config . Options , c * gin . Context ) {
2021-12-16 14:53:58 +00:00
user := c . MustGet ( "authuser" ) . ( * happydns . UserAuth )
2021-05-05 01:48:16 +00:00
2020-07-21 02:02:18 +00:00
var lf passwordForm
2021-05-05 01:48:16 +00:00
if err := c . ShouldBindJSON ( & lf ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid passwordForm JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-07-21 02:02:18 +00:00
}
2021-05-05 01:48:16 +00:00
if ! user . CheckAuth ( lf . Current ) {
c . AbortWithStatusJSON ( http . StatusForbidden , gin . H { "errmsg" : "The given current password is invalid." } )
return
2020-07-21 02:02:18 +00:00
}
if lf . Password != lf . PasswordConfirm {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "The new password and its confirmation are different." } )
return
2020-07-21 02:02:18 +00:00
}
2021-05-05 01:48:16 +00:00
if err := user . DefinePassword ( lf . Password ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to DefinePassword in changePassword: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your profile. Please try again later." } )
return
2020-07-21 02:02:18 +00:00
}
2021-05-05 01:48:16 +00:00
// Retrieve all user's sessions to disconnect them
2021-12-16 14:53:58 +00:00
sessions , err := storage . MainStore . GetAuthUserSessions ( user )
2021-05-05 01:48:16 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to GetUserSessions in changePassword: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your profile. Please try again later." } )
return
2020-12-10 15:43:37 +00:00
}
2021-12-16 14:53:58 +00:00
if err = storage . MainStore . UpdateAuthUser ( user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to DefinePassword in changePassword: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your profile. Please try again later." } )
return
2020-07-21 02:02:18 +00:00
}
2021-05-05 01:48:16 +00:00
log . Printf ( "%s changes password for user %s" , c . ClientIP ( ) , user . Email )
2020-12-10 15:43:37 +00:00
for _ , session := range sessions {
2021-05-05 01:48:16 +00:00
err = storage . MainStore . DeleteSession ( session )
if err != nil {
2023-05-20 10:43:32 +00:00
log . Printf ( "%s: unable to delete session (password changed): %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
}
2020-12-10 15:43:37 +00:00
}
2021-05-05 01:48:16 +00:00
logout ( opts , c )
2020-07-21 02:02:18 +00:00
}
2023-08-05 16:15:52 +00:00
// deleteUser delete the account related to the given user.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Drop account
// @Schemes
// @Description Delete the account related to the given user.
// @Tags users
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param userId path string true "User identifier"
// @Param body body passwordForm true "Password confirmation"
// @Success 204 {null} null
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 403 {object} happydns.Error "Bad current password"
// @Failure 500 {object} happydns.Error
// @Router /users/{userId}/delete [post]
2021-05-05 01:48:16 +00:00
func deleteUser ( opts * config . Options , c * gin . Context ) {
2021-12-16 14:53:58 +00:00
user := c . MustGet ( "authuser" ) . ( * happydns . UserAuth )
2021-05-05 01:48:16 +00:00
2020-07-21 02:02:18 +00:00
var lf passwordForm
2021-05-05 01:48:16 +00:00
if err := c . ShouldBindJSON ( & lf ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid passwordForm JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-07-21 02:02:18 +00:00
}
2021-05-05 01:48:16 +00:00
if ! user . CheckAuth ( lf . Current ) {
c . AbortWithStatusJSON ( http . StatusForbidden , gin . H { "errmsg" : "The given current password is invalid." } )
return
2020-07-21 02:02:18 +00:00
}
2021-05-05 01:48:16 +00:00
// Retrieve all user's sessions to disconnect them
2021-12-16 14:53:58 +00:00
sessions , err := storage . MainStore . GetAuthUserSessions ( user )
2021-05-05 01:48:16 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to GetUserSessions in deleteUser: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your profile. Please try again later." } )
return
2020-12-10 15:43:37 +00:00
}
2021-12-16 14:53:58 +00:00
if err = storage . MainStore . DeleteAuthUser ( user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to DefinePassword in deleteuser: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your profile. Please try again later." } )
return
2020-07-21 02:02:18 +00:00
}
2021-05-05 01:48:16 +00:00
log . Printf ( "%s: deletes user: %s" , c . ClientIP ( ) , user . Email )
2020-12-10 15:43:37 +00:00
for _ , session := range sessions {
2021-05-05 01:48:16 +00:00
err = storage . MainStore . DeleteSession ( session )
if err != nil {
2023-05-20 10:43:32 +00:00
log . Printf ( "%s: unable to delete session (drop account): %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
}
2020-12-10 15:43:37 +00:00
}
2021-05-05 01:48:16 +00:00
logout ( opts , c )
2020-07-21 02:02:18 +00:00
}
2021-05-05 01:48:16 +00:00
func UserHandlerBase ( c * gin . Context ) ( * happydns . User , error ) {
2022-10-25 16:16:34 +00:00
uid , err := base64 . RawURLEncoding . DecodeString ( c . Param ( "uid" ) )
2021-05-05 01:48:16 +00:00
if err != nil {
return nil , fmt . Errorf ( "Invalid user identifier given: %w" , err )
2020-05-23 16:47:09 +00:00
}
2021-05-05 01:48:16 +00:00
user , err := storage . MainStore . GetUser ( uid )
if err != nil {
return nil , fmt . Errorf ( "User not found" )
}
return user , nil
}
func userHandler ( c * gin . Context ) {
user , err := UserHandlerBase ( c )
if err != nil {
c . AbortWithStatusJSON ( http . StatusNotFound , gin . H { "errmsg" : err . Error ( ) } )
return
}
c . Set ( "user" , user )
c . Next ( )
2020-05-23 16:47:09 +00:00
}
2021-12-16 14:53:58 +00:00
func UserAuthHandlerBase ( c * gin . Context ) ( * happydns . UserAuth , error ) {
2022-10-25 16:16:34 +00:00
uid , err := base64 . RawURLEncoding . DecodeString ( c . Param ( "uid" ) )
2021-12-16 14:53:58 +00:00
if err != nil {
return nil , fmt . Errorf ( "Invalid user identifier given: %w" , err )
}
user , err := storage . MainStore . GetAuthUser ( uid )
if err != nil {
return nil , fmt . Errorf ( "User not found" )
}
return user , nil
}
func userAuthHandler ( c * gin . Context ) {
user , err := UserAuthHandlerBase ( c )
if err != nil {
c . AbortWithStatusJSON ( http . StatusNotFound , gin . H { "errmsg" : err . Error ( ) } )
return
}
c . Set ( "authuser" , user )
c . Next ( )
}
2020-05-23 16:47:09 +00:00
type UploadedAddressValidation struct {
2023-08-05 16:15:52 +00:00
// Key able to validate the email address.
2020-05-23 16:47:09 +00:00
Key string
}
2023-08-05 16:15:52 +00:00
// validateUserAddress validates a user address after registration.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Validate e-mail address.
// @Schemes
// @Description This is the route called by the web interface in order to validate the e-mail address of the user.
// @Tags users
// @Accept json
// @Produce json
// @Param userId path string true "User identifier"
// @Param body body UploadedAddressValidation true "Validation form"
// @Success 204 {null} null "Email validated, you can now login"
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 500 {object} happydns.Error
// @Router /users/{userId}/email [post]
2021-05-05 01:48:16 +00:00
func validateUserAddress ( c * gin . Context ) {
2021-12-16 14:53:58 +00:00
user := c . MustGet ( "authuser" ) . ( * happydns . UserAuth )
2021-05-05 01:48:16 +00:00
2020-05-23 16:47:09 +00:00
var uav UploadedAddressValidation
2021-05-05 01:48:16 +00:00
err := c . ShouldBindJSON ( & uav )
2020-05-23 16:47:09 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid AddressValidation JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-05-23 16:47:09 +00:00
}
if err := user . ValidateEmail ( uav . Key ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s bad email validation key: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Bad validation key: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-05-23 16:47:09 +00:00
}
2021-05-05 01:48:16 +00:00
2021-12-16 14:53:58 +00:00
if err := storage . MainStore . UpdateAuthUser ( user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to UpdateUser in ValidateUserAddress: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your profile. Please try again later." } )
return
}
2023-08-05 16:15:52 +00:00
c . Status ( http . StatusNoContent )
2020-05-23 16:47:09 +00:00
}
2020-05-23 21:16:41 +00:00
type UploadedAccountRecovery struct {
2023-08-05 16:15:52 +00:00
// Key is the secret sent by email to the user.
Key string
// Password is the new password to use with this account.
2020-05-23 21:16:41 +00:00
Password string
}
2023-08-05 16:15:52 +00:00
// recoverUserAccount performs account recovery by reseting the password of the account.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Reset password with link in email.
// @Schemes
// @Description This performs account recovery by reseting the password of the account.
// @Tags users
// @Accept json
// @Produce json
// @Param userId path string true "User identifier"
// @Param body body UploadedAccountRecovery true "Recovery form"
// @Success 204 {null} null "Recovery completed, you can now login with your new credentials"
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 500 {object} happydns.Error
// @Router /users/{userId}/recovery [post]
2021-05-05 01:48:16 +00:00
func recoverUserAccount ( c * gin . Context ) {
2021-12-16 14:53:58 +00:00
user := c . MustGet ( "authuser" ) . ( * happydns . UserAuth )
2021-05-05 01:48:16 +00:00
2020-05-23 21:16:41 +00:00
var uar UploadedAccountRecovery
2021-05-05 01:48:16 +00:00
err := c . ShouldBindJSON ( & uar )
2020-05-23 21:16:41 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid AccountRecovey JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-05-23 21:16:41 +00:00
}
if err := user . CanRecoverAccount ( uar . Key ) ; err != nil {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusForbidden , gin . H { "errmsg" : err . Error ( ) } )
return
2020-05-23 21:16:41 +00:00
}
2020-07-17 22:23:58 +00:00
2021-05-05 01:48:16 +00:00
if len ( uar . Password ) == 0 {
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "Password can't be empty!" } )
return
2020-07-17 22:23:58 +00:00
}
2021-05-05 01:48:16 +00:00
if err := user . DefinePassword ( uar . Password ) ; err != nil {
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : err . Error ( ) } )
return
2020-07-17 22:23:58 +00:00
}
2021-05-05 01:48:16 +00:00
2021-12-16 14:53:58 +00:00
if err := storage . MainStore . UpdateAuthUser ( user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to UpdateUser in recoverUserAccount: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your profile. Please try again later." } )
return
}
log . Printf ( "%s: User recovered: %s" , c . ClientIP ( ) , user . Email )
2023-08-05 16:15:52 +00:00
c . Status ( http . StatusNoContent )
2021-05-05 01:48:16 +00:00
}
2023-08-05 16:15:52 +00:00
// getSession gets the content of the current user's session.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Retrieve user's session content
// @Schemes
// @Description Get the content of the current user's session.
// @Tags users
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Success 200 {object} happydns.Session
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Router /sessions [get]
2021-05-05 01:48:16 +00:00
func getSession ( c * gin . Context ) {
session := c . MustGet ( "MySession" ) . ( * happydns . Session )
c . JSON ( http . StatusOK , session )
}
2023-08-05 16:15:52 +00:00
// clearSession removes the content of the current user's session.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Remove user's session content
// @Schemes
// @Description Remove the content of the current user's session.
// @Tags users
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Success 204 {null} null
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Router /sessions [delete]
2021-05-05 01:48:16 +00:00
func clearSession ( c * gin . Context ) {
session := c . MustGet ( "MySession" ) . ( * happydns . Session )
session . ClearSession ( )
2023-08-05 16:15:52 +00:00
c . Status ( http . StatusNoContent )
2020-07-17 22:23:58 +00:00
}