feat: initial commit
This commit is contained in:
+203
@@ -0,0 +1,203 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user