204 lines
5.2 KiB
Go
204 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/FernandoVideira/LK_API_Temp/internal/mailer"
|
|
"github.com/FernandoVideira/LK_API_Temp/internal/store"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type RegisterUserPayload struct {
|
|
FirstName string `json:"first_name" validate:"required,max=100"`
|
|
LastName string `json:"last_name" validate:"required,max=100"`
|
|
Username string `json:"username" validate:"required,max=100"`
|
|
Email string `json:"email" validate:"required,email,max=255"`
|
|
Password string `json:"password" validate:"required,password"`
|
|
}
|
|
|
|
type UserWithToken struct {
|
|
*store.User
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
// RegisterUser godoc
|
|
//
|
|
// @Summary Register a new User
|
|
// @Description Register a new User
|
|
// @Tags authentication
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param payload body RegisterUserPayload true "User Credentials"
|
|
// @Success 201 {object} UserWithToken "User Registered"
|
|
// @Failure 400 {object} error "User payload error"
|
|
// @Failure 500 {object} error "Internal Server Error"
|
|
// @Router /authentication/user [post]
|
|
func (api *api) registerUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
var payload RegisterUserPayload
|
|
if err := readJSON(w, r, &payload); err != nil {
|
|
api.badRequestError(w, r, err)
|
|
return
|
|
}
|
|
|
|
if err := Validate.Struct(payload); err != nil {
|
|
api.badRequestError(w, r, err)
|
|
return
|
|
}
|
|
|
|
user := &store.User{
|
|
FirstName: payload.FirstName,
|
|
LastName: payload.LastName,
|
|
Username: payload.Username,
|
|
Email: payload.Email,
|
|
Role: store.Role{
|
|
Name: "user",
|
|
},
|
|
}
|
|
|
|
// Hash the user password
|
|
if err := user.Password.Set(payload.Password); err != nil {
|
|
api.internalServerError(w, r, err)
|
|
return
|
|
}
|
|
|
|
ctx := r.Context()
|
|
|
|
plainToken := uuid.New().String()
|
|
|
|
hash := sha256.Sum256([]byte(plainToken))
|
|
hashedToken := hex.EncodeToString(hash[:])
|
|
|
|
// Store the user
|
|
err := api.store.Users.CreateAndInvite(ctx, user, hashedToken, api.config.mail.exp)
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, store.ErrDuplicateEmail):
|
|
api.badRequestError(w, r, err)
|
|
case errors.Is(err, store.ErrDuplicateUsername):
|
|
api.badRequestError(w, r, err)
|
|
default:
|
|
api.internalServerError(w, r, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
userWithToken := UserWithToken{
|
|
User: user,
|
|
Token: plainToken,
|
|
}
|
|
|
|
activationURL := fmt.Sprintf("%s/confirm/%s", api.config.frontEndURL, plainToken)
|
|
|
|
isProdEnv := api.config.env == "production"
|
|
vars := struct {
|
|
ServiceName string
|
|
Username string
|
|
ActivationURL string
|
|
}{
|
|
ServiceName: "LK_API_Temp",
|
|
Username: user.Username,
|
|
ActivationURL: activationURL,
|
|
}
|
|
|
|
// Send Email
|
|
status, err := api.mailer.Send(mailer.UserWelcomeTemplate, user.Username, user.Email, vars, !isProdEnv)
|
|
if err != nil {
|
|
// Log the error
|
|
api.logger.Errorw("failed to send welcome email", "error", err)
|
|
|
|
//Rollback the user creation if the email fails
|
|
if err := api.store.Users.Delete(ctx, user.ID); err != nil {
|
|
api.logger.Errorw("failed to rollback user creation", "error", err)
|
|
}
|
|
|
|
api.internalServerError(w, r, err)
|
|
return
|
|
}
|
|
|
|
api.logger.Infow("email sent", "status code", status)
|
|
|
|
if err := api.jsonResponse(w, http.StatusCreated, userWithToken); err != nil {
|
|
api.internalServerError(w, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
type CredentialsPayload struct {
|
|
Email string `json:"email" validate:"required,email,max=255"`
|
|
Password string `json:"password" validate:"required,min=3,max=72"`
|
|
}
|
|
|
|
// CreateTokenHandler godoc
|
|
//
|
|
// @Summary Create a new token
|
|
// @Description Create a new token for the user
|
|
// @Tags authentication
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param payload body CredentialsPayload true "User Credentials"
|
|
// @Success 201 {string} string "Token Created"
|
|
// @Failure 400 {object} error
|
|
// @Failure 401 {object} error
|
|
// @Failure 500 {object} error
|
|
// @Router /authentication/token [post]
|
|
func (api *api) createTokenHandler(w http.ResponseWriter, r *http.Request) {
|
|
//* parse payload credentials
|
|
var payload CredentialsPayload
|
|
if err := readJSON(w, r, &payload); err != nil {
|
|
api.badRequestError(w, r, err)
|
|
return
|
|
}
|
|
|
|
if err := Validate.Struct(payload); err != nil {
|
|
api.badRequestError(w, r, err)
|
|
return
|
|
}
|
|
|
|
//* fetch the user (check if the user exists) from the payload
|
|
user, err := api.store.Users.GetByEmail(r.Context(), payload.Email)
|
|
if err != nil {
|
|
switch err {
|
|
case store.ErrNotFound:
|
|
api.unauthorizedError(w, r, err)
|
|
default:
|
|
api.internalServerError(w, r, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
//* compare the password from the payload with the user password
|
|
if err := user.Password.Compare(payload.Password); err != nil {
|
|
api.unauthorizedError(w, r, err)
|
|
return
|
|
}
|
|
|
|
claims := &jwt.MapClaims{
|
|
"sub": user.ID,
|
|
"exp": time.Now().Add(api.config.auth.token.exp).Unix(),
|
|
"iat": time.Now().Unix(),
|
|
"nbf": time.Now().Unix(),
|
|
"iss": api.config.auth.token.iss,
|
|
"aud": api.config.auth.token.aud,
|
|
}
|
|
|
|
//* generate a new token -> add the claims
|
|
token, err := api.authenticator.GenerateToken(claims)
|
|
if err != nil {
|
|
api.internalServerError(w, r, err)
|
|
return
|
|
}
|
|
|
|
//* send the token back to the user
|
|
|
|
if err := api.jsonResponse(w, http.StatusCreated, token); err != nil {
|
|
api.internalServerError(w, r, err)
|
|
return
|
|
}
|
|
}
|