Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): implement user authentication and authorization #21

Merged
merged 4 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
APP_NAME=go-pos
APP_ENV=local
APP_URL=127.0.0.1
APP_PORT=8080
APP_ENV=development

HTTP_URL=127.0.0.1
HTTP_PORT=8080
HTTP_ALLOWED_ORIGINS="http://localhost:3000,http://localhost:5173"

DB_CONNECTION=postgres
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=gopos
DB_USERNAME=root
DB_USERNAME=
DB_PASSWORD=

ALLOWED_ORIGINS="http://localhost:3000,http://localhost:5173"
28 changes: 18 additions & 10 deletions cmd/http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

handler "github.com/bagashiz/go-pos/internal/adapter/handler/http"
repo "github.com/bagashiz/go-pos/internal/adapter/repository/postgres"
token "github.com/bagashiz/go-pos/internal/adapter/token/paseto"
"github.com/bagashiz/go-pos/internal/core/service"
"github.com/joho/godotenv"
)
Expand Down Expand Up @@ -39,9 +40,10 @@ func main() {
appName := os.Getenv("APP_NAME")
env := os.Getenv("APP_ENV")
dbConn := os.Getenv("DB_CONNECTION")
url := os.Getenv("APP_URL")
port := os.Getenv("APP_PORT")
listenAddr := url + ":" + port
tokenSymmetricKey := os.Getenv("TOKEN_SYMMETRIC_KEY")
httpUrl := os.Getenv("HTTP_URL")
httpPort := os.Getenv("HTTP_PORT")
listenAddr := httpUrl + ":" + httpPort

slog.Info("Starting the application", "app", appName, "env", env)

Expand All @@ -54,12 +56,13 @@ func main() {
}
defer db.Close()

err = db.Ping(ctx)
slog.Info("Successfully connected to the database", "db", dbConn)

// Init token service
tokenService, err := token.NewToken(tokenSymmetricKey)
if err != nil {
slog.Error("Error connecting to database", "error", err)
slog.Error("Error initializing token service", "error", err)
os.Exit(1)
} else {
slog.Info("Successfully connected to the database", "db", dbConn)
}

// Dependency injection
Expand All @@ -68,6 +71,10 @@ func main() {
userService := service.NewUserService(userRepo)
userHandler := handler.NewUserHandler(userService)

// Auth
authService := service.NewAuthService(userRepo, tokenService)
authHandler := handler.NewAuthHandler(authService)

// Payment
paymentRepo := repo.NewPaymentRepository(db)
paymentService := service.NewPaymentService(paymentRepo)
Expand All @@ -89,9 +96,10 @@ func main() {
orderHandler := handler.NewOrderHandler(orderService)

// Init router
router := handler.NewRouter()
router.InitRoutes(
router := handler.NewRouter(
tokenService,
*userHandler,
*authHandler,
*paymentHandler,
*categoryHandler,
*productHandler,
Expand All @@ -100,7 +108,7 @@ func main() {

// Start server
slog.Info("Starting the HTTP server", "listen_address", listenAddr)
err = router.Run(listenAddr)
err = router.Serve(listenAddr)
if err != nil {
slog.Error("Error starting the HTTP server", "error", err)
os.Exit(1)
Expand Down
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ require (
golang.org/x/crypto v0.9.0
)

require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb // indirect
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/pkg/errors v0.8.0 // indirect
)

require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
Expand All @@ -32,6 +39,7 @@ require (
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/o1egl/paseto v1.0.0
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb h1:6Z/wqhPFZ7y5ksCEV/V5MXOazLaeu/EW97CU5rz8NWk=
github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
Expand Down Expand Up @@ -78,10 +84,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0=
github.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
Expand Down Expand Up @@ -110,6 +120,7 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
Expand All @@ -118,6 +129,7 @@ golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
63 changes: 63 additions & 0 deletions internal/adapter/handler/http/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package handler

import (
"net/http"

"github.com/bagashiz/go-pos/internal/core/domain"
"github.com/bagashiz/go-pos/internal/core/port"
"github.com/gin-gonic/gin"
)

// AuthHandler represents the HTTP handler for authentication-related requests
type AuthHandler struct {
svc port.AuthService
}

// NewAuthHandler creates a new AuthHandler instance
func NewAuthHandler(svc port.AuthService) *AuthHandler {
return &AuthHandler{
svc,
}
}

// authResponse represents an authentication response body
type authResponse struct {
AccessToken string `json:"token"`
}

// newAuthResponse is a helper function to create a response body for handling authentication data
func newAuthResponse(token string) authResponse {
return authResponse{
AccessToken: token,
}
}

// loginRequest represents the request body for logging in a user
type loginRequest struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8"`
}

// Login gives a registered user an access token if the credentials are valid
func (ah *AuthHandler) Login(ctx *gin.Context) {
var req loginRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
errorResponse(ctx, http.StatusBadRequest, err)
return
}

token, err := ah.svc.Login(ctx, req.Email, req.Password)
if err != nil {
if err == domain.ErrInvalidCredentials {
errorResponse(ctx, http.StatusUnauthorized, err)
return
}

errorResponse(ctx, http.StatusInternalServerError, err)
return
}

rsp := newAuthResponse(token)

successResponse(ctx, http.StatusOK, rsp)
}
24 changes: 17 additions & 7 deletions internal/adapter/handler/http/category.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ type CategoryHandler struct {
}

// NewCategoryHandler creates a new CategoryHandler instance
func NewCategoryHandler(CategoryService port.CategoryService) *CategoryHandler {
func NewCategoryHandler(svc port.CategoryService) *CategoryHandler {
return &CategoryHandler{
svc: CategoryService,
svc,
}
}

Expand Down Expand Up @@ -53,6 +53,11 @@ func (ch *CategoryHandler) CreateCategory(ctx *gin.Context) {

_, err := ch.svc.CreateCategory(ctx, &category)
if err != nil {
if err == domain.ErrConflictingData {
errorResponse(ctx, http.StatusConflict, err)
return
}

errorResponse(ctx, http.StatusInternalServerError, err)
return
}
Expand All @@ -77,7 +82,7 @@ func (ch *CategoryHandler) GetCategory(ctx *gin.Context) {

category, err := ch.svc.GetCategory(ctx, req.ID)
if err != nil {
if err.Error() == "category not found" {
if err == domain.ErrDataNotFound {
errorResponse(ctx, http.StatusNotFound, err)
return
}
Expand Down Expand Up @@ -138,7 +143,7 @@ func (ch *CategoryHandler) UpdateCategory(ctx *gin.Context) {
}

idStr := ctx.Param("id")
id, err := convertStringToUint64(idStr)
id, err := stringToUint64(idStr)
if err != nil {
errorResponse(ctx, http.StatusBadRequest, err)
return
Expand All @@ -151,16 +156,21 @@ func (ch *CategoryHandler) UpdateCategory(ctx *gin.Context) {

_, err = ch.svc.UpdateCategory(ctx, &category)
if err != nil {
if err.Error() == "category not found" {
if err == domain.ErrDataNotFound {
errorResponse(ctx, http.StatusNotFound, err)
return
}

if err.Error() == "no data to update" {
if err == domain.ErrNoUpdatedData {
errorResponse(ctx, http.StatusBadRequest, err)
return
}

if err == domain.ErrConflictingData {
errorResponse(ctx, http.StatusConflict, err)
return
}

errorResponse(ctx, http.StatusInternalServerError, err)
return
}
Expand All @@ -185,7 +195,7 @@ func (ch *CategoryHandler) DeleteCategory(ctx *gin.Context) {

err := ch.svc.DeleteCategory(ctx, req.ID)
if err != nil {
if err.Error() == "category not found" {
if err == domain.ErrDataNotFound {
errorResponse(ctx, http.StatusNotFound, err)
return
}
Expand Down
28 changes: 21 additions & 7 deletions internal/adapter/handler/http/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@ package handler
import (
"strconv"

"github.com/bagashiz/go-pos/internal/core/domain"
"github.com/gin-gonic/gin"
)

// stringToUint64 is a helper function to convert a string to uint64
func stringToUint64(str string) (uint64, error) {
num, err := strconv.ParseUint(str, 10, 64)

return num, err
}

// getAuthPayload is a helper function to get the auth payload from the context
func getAuthPayload(ctx *gin.Context, key string) *domain.TokenPayload {
return ctx.MustGet(key).(*domain.TokenPayload)
}

// meta represents metadata for a paginated response
type meta struct {
Total uint64 `json:"total"`
Expand All @@ -22,13 +35,6 @@ func newMeta(total, limit, skip uint64) meta {
}
}

// convertStringToUint64 is a helper function to convert a string to uint64
func convertStringToUint64(str string) (uint64, error) {
num, err := strconv.ParseUint(str, 10, 64)

return num, err
}

// toMap is a helper function to add meta and data to a map
func toMap(m meta, data any, key string) map[string]any {
return map[string]any{
Expand All @@ -45,6 +51,14 @@ func errorResponse(ctx *gin.Context, statusCode int, err error) {
})
}

// abortResponse returns an abort JSON response with the error message and status code
func abortResponse(ctx *gin.Context, statusCode int, err error) {
ctx.AbortWithStatusJSON(statusCode, gin.H{
"success": false,
"error": err.Error(),
})
}

// successResponse returns a JSON response with the success message and data
func successResponse(ctx *gin.Context, statusCode int, data any) {
if data == nil {
Expand Down
Loading
Loading