move handlers to separate package
This commit is contained in:
parent
ef006abd10
commit
d1a33b18dc
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server"
|
httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server"
|
||||||
|
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
@ -43,7 +44,7 @@ func main() {
|
||||||
store := repository.NewStore(db)
|
store := repository.NewStore(db)
|
||||||
v := customvalidator.NewCustomValidator(validator.New())
|
v := customvalidator.NewCustomValidator(validator.New())
|
||||||
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
||||||
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, httpserver.JwtConfig{
|
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
||||||
JwtAccessKey: cfg.JwtKey,
|
JwtAccessKey: cfg.JwtKey,
|
||||||
JwtAccessExpiry: cfg.AccessExpiry,
|
JwtAccessExpiry: cfg.AccessExpiry,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,20 @@ CREATE TABLE users (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
first_name VARCHAR(255) NOT NULL,
|
first_name VARCHAR(255) NOT NULL,
|
||||||
last_name VARCHAR(255) NOT NULL,
|
last_name VARCHAR(255) NOT NULL,
|
||||||
email VARCHAR(255) UNIQUE NOT NULL,
|
email VARCHAR(255) UNIQUE ,
|
||||||
phone_number VARCHAR(20) UNIQUE NOT NULL,
|
phone_number VARCHAR(20) UNIQUE,
|
||||||
password BYTEA NOT NULL,
|
password BYTEA NOT NULL,
|
||||||
role VARCHAR(50) NOT NULL,
|
role VARCHAR(50) NOT NULL,
|
||||||
verified BOOLEAN DEFAULT FALSE,
|
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
created_at TIMESTAMPTZ ,
|
phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
updated_at TIMESTAMPTZ ,
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
CONSTRAINT unique_email_phone UNIQUE (email, phone_number)
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
suspended_at TIMESTAMPTZ NULL, -- this can be NULL if the user is not suspended
|
||||||
|
suspended BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
CHECK (
|
||||||
|
(email IS NOT NULL AND phone_number IS NULL) OR
|
||||||
|
(email IS NULL AND phone_number IS NOT NULL)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
CREATE TABLE refresh_tokens (
|
CREATE TABLE refresh_tokens (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
|
|
||||||
10
internal/domain/common.go
Normal file
10
internal/domain/common.go
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type ValidString struct {
|
||||||
|
Value string
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
type ValidBool struct {
|
||||||
|
Value bool
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
29
internal/domain/otp.go
Normal file
29
internal/domain/otp.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type OtpFor string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OtpReset OtpFor = "reset"
|
||||||
|
OtpRegister OtpFor = "register"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OtpMedium string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OtpMediumEmail OtpMedium = "email"
|
||||||
|
OtpMediumSms OtpMedium = "sms"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Otp struct {
|
||||||
|
ID int64
|
||||||
|
SentTo string
|
||||||
|
Medium OtpMedium
|
||||||
|
For OtpFor
|
||||||
|
Otp string
|
||||||
|
Used bool
|
||||||
|
UsedAt time.Time
|
||||||
|
CreatedAt time.Time
|
||||||
|
ExpiresAt time.Time
|
||||||
|
}
|
||||||
11
internal/domain/role.go
Normal file
11
internal/domain/role.go
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type Role string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleAdmin Role = "admin"
|
||||||
|
RoleCustomer Role = "customer"
|
||||||
|
RoleSuperAdmin Role = "super_admin"
|
||||||
|
RoleBranchManager Role = "branch_manager"
|
||||||
|
RoleCashier Role = "cashier"
|
||||||
|
)
|
||||||
|
|
@ -9,8 +9,38 @@ type User struct {
|
||||||
Email string
|
Email string
|
||||||
PhoneNumber string
|
PhoneNumber string
|
||||||
Password []byte
|
Password []byte
|
||||||
Role string
|
Role Role
|
||||||
Verified bool
|
//
|
||||||
CreatedAt time.Time
|
EmailVerified bool
|
||||||
UpdatedAt time.Time
|
PhoneVerified bool
|
||||||
|
//
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
//
|
||||||
|
SuspendedAt time.Time
|
||||||
|
Suspended bool
|
||||||
|
}
|
||||||
|
type RegisterUserReq struct {
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email string
|
||||||
|
PhoneNumber string
|
||||||
|
Password string
|
||||||
|
//Role string
|
||||||
|
Otp string
|
||||||
|
ReferalCode string
|
||||||
|
//
|
||||||
|
OtpMedium OtpMedium
|
||||||
|
}
|
||||||
|
type ResetPasswordReq struct {
|
||||||
|
Email string
|
||||||
|
PhoneNumber string
|
||||||
|
Password string
|
||||||
|
Otp string
|
||||||
|
OtpMedium OtpMedium
|
||||||
|
}
|
||||||
|
type UpdateUserReq struct {
|
||||||
|
FirstName ValidString
|
||||||
|
LastName ValidString
|
||||||
|
Suspended ValidBool
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ func (s *Store) GetUserByEmailPhone(ctx context.Context, email, phone string) (d
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber,
|
||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
Role: user.Role,
|
Role: domain.Role(user.Role),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ func (s *Store) CreateUser(ctx context.Context, firstName, lastName, email, phon
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber,
|
||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
Role: user.Role,
|
// Role: user.Role,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error)
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber,
|
||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
Role: user.Role,
|
// Role: user.Role,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
func (s *Store) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
func (s *Store) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
||||||
|
|
@ -59,7 +59,7 @@ func (s *Store) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber,
|
||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
Role: user.Role,
|
// Role: user.Role,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ var (
|
||||||
|
|
||||||
type LoginSuccess struct {
|
type LoginSuccess struct {
|
||||||
UserId int64
|
UserId int64
|
||||||
Role string
|
Role domain.Role
|
||||||
RfToken string
|
RfToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,25 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserStore interface {
|
type UserStore interface {
|
||||||
CreateUser(ctx context.Context, CfirstName, lastName, email, phoneNumber, password, role string, verified bool) (domain.User, error)
|
CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error)
|
||||||
GetUserByID(ctx context.Context, id int64) (domain.User, error)
|
GetUserByID(ctx context.Context, id int64) (domain.User, error)
|
||||||
GetAllUsers(ctx context.Context) ([]domain.User, error)
|
GetAllUsers(ctx context.Context) ([]domain.User, error)
|
||||||
UpdateUser(ctx context.Context, id int64, firstName, lastName, email, phoneNumber, password, role string, verified bool) error
|
UpdateUser(ctx context.Context, user domain.UpdateUserReq) error
|
||||||
DeleteUser(ctx context.Context, id int64) error
|
DeleteUser(ctx context.Context, id int64) error
|
||||||
|
CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error)
|
||||||
|
GetUserByEmail(ctx context.Context, email string) (domain.User, error)
|
||||||
|
GetUserByPhone(ctx context.Context, phoneNum string) (domain.User, error)
|
||||||
//
|
//
|
||||||
//GetUserByEmailPhone(ctx context.Context, emailPhone EmailPhone) (domain.User, error)
|
UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64) error // identifier verified email or phone
|
||||||
|
}
|
||||||
|
type SmsGateway interface {
|
||||||
|
SendSMSOTP(ctx context.Context, phoneNumber, otp string) error
|
||||||
|
}
|
||||||
|
type EmailGateway interface {
|
||||||
|
SendEmailOTP(ctx context.Context, email string, otp string) error
|
||||||
|
}
|
||||||
|
type OtpStore interface {
|
||||||
|
CreateOtp(ctx context.Context, otp domain.Otp) error
|
||||||
|
GetOtp(ctx context.Context, sentTo string, sentfor domain.OtpFor, medium domain.OtpMedium) (domain.Otp, error)
|
||||||
|
MarkUsed(ctx context.Context, id int64) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,36 +2,199 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
OtpExpiry = 5 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrOtpAlreadyUsed = errors.New("otp already used")
|
||||||
|
ErrInvalidOtp = errors.New("invalid otp")
|
||||||
|
ErrOtpExpired = errors.New("otp expired")
|
||||||
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
userStore UserStore
|
userStore UserStore
|
||||||
|
otpStore OtpStore
|
||||||
|
smsGateway SmsGateway
|
||||||
|
emailGateway EmailGateway
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(userStore UserStore, RefreshExpiry int) *Service {
|
func NewService(
|
||||||
|
userStore UserStore, RefreshExpiry int,
|
||||||
|
otpStore OtpStore, smsGateway SmsGateway,
|
||||||
|
emailGateway EmailGateway,
|
||||||
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
userStore: userStore,
|
userStore: userStore,
|
||||||
|
otpStore: otpStore,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) CreateUser(ctx context.Context, firstName, lastName, email, phoneNumber, password, role string, verified bool) (domain.User, error) {
|
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error
|
||||||
return s.userStore.CreateUser(ctx, firstName, lastName, email, phoneNumber, password, role, verified)
|
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email)
|
||||||
|
}
|
||||||
|
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error {
|
||||||
|
var err error
|
||||||
|
// check if user exists
|
||||||
|
switch medium {
|
||||||
|
case domain.OtpMediumEmail:
|
||||||
|
_, err = s.userStore.GetUserByEmail(ctx, sentTo)
|
||||||
|
case domain.OtpMediumSms:
|
||||||
|
_, err = s.userStore.GetUserByPhone(ctx, sentTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send otp based on the medium
|
||||||
|
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium)
|
||||||
|
}
|
||||||
|
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal
|
||||||
|
// get otp
|
||||||
|
var sentTo string
|
||||||
|
if registerReq.OtpMedium == domain.OtpMediumEmail {
|
||||||
|
sentTo = registerReq.Email
|
||||||
|
} else {
|
||||||
|
sentTo = registerReq.PhoneNumber
|
||||||
|
}
|
||||||
|
//
|
||||||
|
otp, err := s.otpStore.GetOtp(
|
||||||
|
ctx, sentTo,
|
||||||
|
domain.OtpRegister, registerReq.OtpMedium)
|
||||||
|
if err != nil {
|
||||||
|
return domain.User{}, err
|
||||||
|
}
|
||||||
|
// verify otp
|
||||||
|
if otp.Used {
|
||||||
|
return domain.User{}, ErrOtpAlreadyUsed
|
||||||
|
}
|
||||||
|
if time.Now().After(otp.ExpiresAt) {
|
||||||
|
return domain.User{}, ErrOtpExpired
|
||||||
|
}
|
||||||
|
if otp.Otp != registerReq.Otp {
|
||||||
|
return domain.User{}, ErrInvalidOtp
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPassword, err := hashPassword(registerReq.Password)
|
||||||
|
if err != nil {
|
||||||
|
return domain.User{}, err
|
||||||
|
}
|
||||||
|
userR := domain.User{
|
||||||
|
FirstName: registerReq.FirstName,
|
||||||
|
LastName: registerReq.LastName,
|
||||||
|
Email: registerReq.Email,
|
||||||
|
PhoneNumber: registerReq.PhoneNumber,
|
||||||
|
Password: hashedPassword,
|
||||||
|
Role: "user",
|
||||||
|
EmailVerified: registerReq.OtpMedium == domain.OtpMediumEmail,
|
||||||
|
PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms,
|
||||||
|
}
|
||||||
|
// create the user and mark otp as used
|
||||||
|
user, err := s.userStore.CreateUser(ctx, userR, otp.ID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.User{}, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// check if user exists
|
||||||
|
switch medium {
|
||||||
|
case domain.OtpMediumEmail:
|
||||||
|
_, err = s.userStore.GetUserByEmail(ctx, sentTo)
|
||||||
|
case domain.OtpMediumSms:
|
||||||
|
_, err = s.userStore.GetUserByPhone(ctx, sentTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswordReq) error {
|
||||||
|
var sentTo string
|
||||||
|
if resetReq.OtpMedium == domain.OtpMediumEmail {
|
||||||
|
sentTo = resetReq.Email
|
||||||
|
} else {
|
||||||
|
sentTo = resetReq.PhoneNumber
|
||||||
|
}
|
||||||
|
otp, err := s.otpStore.GetOtp(
|
||||||
|
ctx, sentTo,
|
||||||
|
domain.OtpRegister, resetReq.OtpMedium)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//
|
||||||
|
if otp.Used {
|
||||||
|
return ErrOtpAlreadyUsed
|
||||||
|
}
|
||||||
|
if time.Now().After(otp.ExpiresAt) {
|
||||||
|
return ErrOtpExpired
|
||||||
|
}
|
||||||
|
if otp.Otp != resetReq.Otp {
|
||||||
|
return ErrInvalidOtp
|
||||||
|
}
|
||||||
|
// hash password
|
||||||
|
hashedPassword, err := hashPassword(resetReq.Password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// reset pass and mark otp as used
|
||||||
|
err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium) error {
|
||||||
|
otpCode := "123456" // Generate OTP code
|
||||||
|
|
||||||
|
otp := domain.Otp{
|
||||||
|
SentTo: sentTo,
|
||||||
|
Medium: medium,
|
||||||
|
For: otpFor,
|
||||||
|
Otp: otpCode,
|
||||||
|
Used: false,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
ExpiresAt: time.Now().Add(OtpExpiry),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.otpStore.CreateOtp(ctx, otp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch medium {
|
||||||
|
case domain.OtpMediumSms:
|
||||||
|
return s.smsGateway.SendSMSOTP(ctx, sentTo, otpCode)
|
||||||
|
case domain.OtpMediumEmail:
|
||||||
|
return s.emailGateway.SendEmailOTP(ctx, sentTo, otpCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error {
|
||||||
|
// update user
|
||||||
|
return s.userStore.UpdateUser(ctx, user)
|
||||||
|
|
||||||
}
|
}
|
||||||
func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
||||||
return s.userStore.GetUserByID(ctx, id)
|
return s.userStore.GetUserByID(ctx, id)
|
||||||
}
|
}
|
||||||
func (s *Service) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
|
||||||
return s.userStore.GetAllUsers(ctx)
|
|
||||||
}
|
|
||||||
func (s *Service) UpdateUser(ctx context.Context, id int64, firstName, lastName, email, phoneNumber, password, role string, verified bool) error {
|
|
||||||
return s.userStore.UpdateUser(ctx, id, firstName, lastName, email, phoneNumber, password, role, verified)
|
|
||||||
}
|
|
||||||
func (s *Service) DeleteUser(ctx context.Context, id int64) error {
|
|
||||||
return s.userStore.DeleteUser(ctx, id)
|
|
||||||
}
|
|
||||||
func hashPassword(plaintextPassword string) ([]byte, error) {
|
func hashPassword(plaintextPassword string) ([]byte, error) {
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -5,29 +5,26 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
|
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||||
"github.com/bytedance/sonic"
|
"github.com/bytedance/sonic"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type JwtConfig struct {
|
|
||||||
JwtAccessKey string
|
|
||||||
JwtAccessExpiry int
|
|
||||||
}
|
|
||||||
type App struct {
|
type App struct {
|
||||||
fiber *fiber.App
|
fiber *fiber.App
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
port int
|
port int
|
||||||
authSvc *authentication.Service
|
authSvc *authentication.Service
|
||||||
validator *customvalidator.CustomValidator
|
validator *customvalidator.CustomValidator
|
||||||
JwtConfig JwtConfig
|
JwtConfig jwtutil.JwtConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(
|
func NewApp(
|
||||||
port int, validator *customvalidator.CustomValidator,
|
port int, validator *customvalidator.CustomValidator,
|
||||||
authSvc *authentication.Service,
|
authSvc *authentication.Service,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
JwtConfig JwtConfig,
|
JwtConfig jwtutil.JwtConfig,
|
||||||
) *App {
|
) *App {
|
||||||
app := fiber.New(fiber.Config{
|
app := fiber.New(fiber.Config{
|
||||||
CaseSensitive: true,
|
CaseSensitive: true,
|
||||||
|
|
|
||||||
183
internal/web_server/handlers/auth_handler.go
Normal file
183
internal/web_server/handlers/auth_handler.go
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
|
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||||
|
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type loginCustomerReq struct {
|
||||||
|
Email string `json:"email" example:"john.doe@example.com"`
|
||||||
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||||
|
Password string `json:"password" example:"password123"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type loginCustomerRes struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginCustomer godoc
|
||||||
|
// @Summary Login customer
|
||||||
|
// @Description Login customer
|
||||||
|
// @Tags auth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param login body loginCustomerReq true "Login customer"
|
||||||
|
// @Success 200 {object} loginCustomerRes
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 401 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /auth/login [post]
|
||||||
|
func LoginCustomer(
|
||||||
|
logger *slog.Logger, authSvc *authentication.Service,
|
||||||
|
validator *customvalidator.CustomValidator, JwtConfig jwtutil.JwtConfig) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
var req loginCustomerReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
logger.Error("Login failed", "error", err)
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||||
|
}
|
||||||
|
valErrs, ok := validator.Validate(c, req)
|
||||||
|
if !ok {
|
||||||
|
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
successRes, err := authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
|
||||||
|
if err != nil {
|
||||||
|
logger.Info("Login failed", "error", err)
|
||||||
|
if errors.Is(err, authentication.ErrInvalidPassword) {
|
||||||
|
response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid password or not registered", nil, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, authentication.ErrUserNotFound) {
|
||||||
|
response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid password or not registered", nil, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logger.Error("Login failed", "error", err)
|
||||||
|
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
accessToken, err := jwtutil.CreateJwt(strconv.Itoa(int(successRes.UserId)), successRes.Role, JwtConfig.JwtAccessKey, JwtConfig.JwtAccessExpiry)
|
||||||
|
res := loginCustomerRes{
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: successRes.RfToken,
|
||||||
|
}
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type refreshToken struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshToken godoc
|
||||||
|
// @Summary Refresh token
|
||||||
|
// @Description Refresh token
|
||||||
|
// @Tags auth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param refresh body refreshToken true "tokens"
|
||||||
|
// @Success 200 {object} loginCustomerRes
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 401 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /auth/refresh [post]
|
||||||
|
func RefreshToken(logger *slog.Logger, authSvc *authentication.Service,
|
||||||
|
validator *customvalidator.CustomValidator, JwtConfig jwtutil.JwtConfig) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
var req refreshToken
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||||
|
}
|
||||||
|
valErrs, ok := validator.Validate(c, req)
|
||||||
|
if !ok {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rf, err := authSvc.RefreshToken(c.Context(), req.RefreshToken)
|
||||||
|
if err != nil {
|
||||||
|
logger.Info("Refresh token failed", "error", err)
|
||||||
|
if errors.Is(err, authentication.ErrExpiredToken) {
|
||||||
|
response.WriteJSON(c, fiber.StatusUnauthorized, "The refresh token has expired", nil, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, authentication.ErrRefreshTokenNotFound) {
|
||||||
|
response.WriteJSON(c, fiber.StatusUnauthorized, "Refresh token not found", nil, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logger.Error("Refresh token failed", "error", err)
|
||||||
|
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
accessToken, err := jwtutil.CreateJwt("", "", JwtConfig.JwtAccessKey, JwtConfig.JwtAccessExpiry)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Create jwt failed", "error", err)
|
||||||
|
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := loginCustomerRes{
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: rf,
|
||||||
|
}
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "refresh successful", res, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type logoutReq struct {
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogOutCustomer godoc
|
||||||
|
// @Summary Logout customer
|
||||||
|
// @Description Logout customer
|
||||||
|
// @Tags auth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param logout body logoutReq true "Logout customer"
|
||||||
|
// @Success 200 {object} response.APIResponse
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 401 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /auth/logout [post]
|
||||||
|
func LogOutCustomer(
|
||||||
|
logger *slog.Logger, authSvc *authentication.Service,
|
||||||
|
validator *customvalidator.CustomValidator) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
var req logoutReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||||
|
}
|
||||||
|
valErrs, ok := validator.Validate(c, req)
|
||||||
|
if !ok {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := authSvc.Logout(c.Context(), req.RefreshToken)
|
||||||
|
if err != nil {
|
||||||
|
logger.Info("Logout failed", "error", err)
|
||||||
|
if errors.Is(err, authentication.ErrExpiredToken) {
|
||||||
|
response.WriteJSON(c, fiber.StatusUnauthorized, "The refresh token has expired", nil, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, authentication.ErrRefreshTokenNotFound) {
|
||||||
|
response.WriteJSON(c, fiber.StatusUnauthorized, "Refresh token not found", nil, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logger.Error("Logout failed", "error", err)
|
||||||
|
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "Logout successful", nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -20,10 +21,14 @@ var (
|
||||||
type UserClaim struct {
|
type UserClaim struct {
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
UserId string
|
UserId string
|
||||||
Role string
|
Role domain.Role
|
||||||
|
}
|
||||||
|
type JwtConfig struct {
|
||||||
|
JwtAccessKey string
|
||||||
|
JwtAccessExpiry int
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateJwt(userId string, Role string, key string, expiry int) (string, error) {
|
func CreateJwt(userId string, Role domain.Role, key string, expiry int) (string, error) {
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{RegisteredClaims: jwt.RegisteredClaims{Issuer: "github.com/lafetz/snippitstash",
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{RegisteredClaims: jwt.RegisteredClaims{Issuer: "github.com/lafetz/snippitstash",
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
Audience: jwt.ClaimStrings{"fortune.com"},
|
Audience: jwt.ClaimStrings{"fortune.com"},
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@ package httpserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "github.com/SamuelTariku/FortuneBet-Backend/docs"
|
_ "github.com/SamuelTariku/FortuneBet-Backend/docs"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/handlers"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
fiberSwagger "github.com/swaggo/fiber-swagger"
|
fiberSwagger "github.com/swaggo/fiber-swagger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) initAppRoutes() {
|
func (a *App) initAppRoutes() {
|
||||||
a.fiber.Post("/auth/login", a.LoginCustomer)
|
a.fiber.Post("/auth/login", handlers.LoginCustomer(a.logger, a.authSvc, a.validator, a.JwtConfig))
|
||||||
a.fiber.Post("/auth/refresh", a.authMiddleware, a.RefreshToken)
|
a.fiber.Post("/auth/refresh", a.authMiddleware, handlers.RefreshToken(a.logger, a.authSvc, a.validator, a.JwtConfig))
|
||||||
a.fiber.Post("/auth/logout", a.authMiddleware, a.LogOutCustomer)
|
a.fiber.Post("/auth/logout", a.authMiddleware, handlers.LogOutCustomer(a.logger, a.authSvc, a.validator))
|
||||||
a.fiber.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
|
a.fiber.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
|
||||||
userId := c.Locals("user_id")
|
userId := c.Locals("user_id")
|
||||||
role := c.Locals("role")
|
role := c.Locals("role")
|
||||||
|
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
package httpserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
|
||||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type loginCustomerReq struct {
|
|
||||||
Email string `json:"email" example:"john.doe@example.com"`
|
|
||||||
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
|
||||||
Password string `json:"password" example:"password123"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type loginCustomerRes struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginCustomer godoc
|
|
||||||
// @Summary Login customer
|
|
||||||
// @Description Login customer
|
|
||||||
// @Tags auth
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Param login body loginCustomerReq true "Login customer"
|
|
||||||
// @Success 200 {object} loginCustomerRes
|
|
||||||
// @Failure 400 {object} response.APIResponse
|
|
||||||
// @Failure 401 {object} response.APIResponse
|
|
||||||
// @Failure 500 {object} response.APIResponse
|
|
||||||
// @Router /auth/login [post]
|
|
||||||
func (a *App) LoginCustomer(c *fiber.Ctx) error {
|
|
||||||
var req loginCustomerReq
|
|
||||||
if err := c.BodyParser(&req); err != nil {
|
|
||||||
a.logger.Error("Login failed", "error", err)
|
|
||||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
|
||||||
}
|
|
||||||
valErrs, ok := a.validator.Validate(c, req)
|
|
||||||
if !ok {
|
|
||||||
|
|
||||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
successRes, err := a.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Info("Login failed", "error", err)
|
|
||||||
if errors.Is(err, authentication.ErrInvalidPassword) {
|
|
||||||
response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid password or not registered", nil, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if errors.Is(err, authentication.ErrUserNotFound) {
|
|
||||||
response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid password or not registered", nil, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
a.logger.Error("Login failed", "error", err)
|
|
||||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
accessToken, err := jwtutil.CreateJwt(strconv.Itoa(int(successRes.UserId)), successRes.Role, a.JwtConfig.JwtAccessKey, a.JwtConfig.JwtAccessExpiry)
|
|
||||||
res := loginCustomerRes{
|
|
||||||
AccessToken: accessToken,
|
|
||||||
RefreshToken: successRes.RfToken,
|
|
||||||
}
|
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
type refreshToken struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshToken godoc
|
|
||||||
// @Summary Refresh token
|
|
||||||
// @Description Refresh token
|
|
||||||
// @Tags auth
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Param refresh body refreshToken true "tokens"
|
|
||||||
// @Success 200 {object} loginCustomerRes
|
|
||||||
// @Failure 400 {object} response.APIResponse
|
|
||||||
// @Failure 401 {object} response.APIResponse
|
|
||||||
// @Failure 500 {object} response.APIResponse
|
|
||||||
// @Router /auth/refresh [post]
|
|
||||||
func (a *App) RefreshToken(c *fiber.Ctx) error {
|
|
||||||
var req refreshToken
|
|
||||||
if err := c.BodyParser(&req); err != nil {
|
|
||||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
|
||||||
}
|
|
||||||
valErrs, ok := a.validator.Validate(c, req)
|
|
||||||
if !ok {
|
|
||||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
rf, err := a.authSvc.RefreshToken(c.Context(), req.RefreshToken)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Info("Refresh token failed", "error", err)
|
|
||||||
if errors.Is(err, authentication.ErrExpiredToken) {
|
|
||||||
response.WriteJSON(c, fiber.StatusUnauthorized, "The refresh token has expired", nil, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if errors.Is(err, authentication.ErrRefreshTokenNotFound) {
|
|
||||||
response.WriteJSON(c, fiber.StatusUnauthorized, "Refresh token not found", nil, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
a.logger.Error("Refresh token failed", "error", err)
|
|
||||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
accessToken, err := jwtutil.CreateJwt("", "", a.JwtConfig.JwtAccessKey, a.JwtConfig.JwtAccessExpiry)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Error("Create jwt failed", "error", err)
|
|
||||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res := loginCustomerRes{
|
|
||||||
AccessToken: accessToken,
|
|
||||||
RefreshToken: rf,
|
|
||||||
}
|
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "refresh successful", res, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
type logoutReq struct {
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogOutCustomer godoc
|
|
||||||
// @Summary Logout customer
|
|
||||||
// @Description Logout customer
|
|
||||||
// @Tags auth
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Param logout body logoutReq true "Logout customer"
|
|
||||||
// @Success 200 {object} response.APIResponse
|
|
||||||
// @Failure 400 {object} response.APIResponse
|
|
||||||
// @Failure 401 {object} response.APIResponse
|
|
||||||
// @Failure 500 {object} response.APIResponse
|
|
||||||
// @Router /auth/logout [post]
|
|
||||||
func (a *App) LogOutCustomer(c *fiber.Ctx) error {
|
|
||||||
var req logoutReq
|
|
||||||
if err := c.BodyParser(&req); err != nil {
|
|
||||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
|
||||||
}
|
|
||||||
valErrs, ok := a.validator.Validate(c, req)
|
|
||||||
if !ok {
|
|
||||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err := a.authSvc.Logout(c.Context(), req.RefreshToken)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Info("Logout failed", "error", err)
|
|
||||||
if errors.Is(err, authentication.ErrExpiredToken) {
|
|
||||||
response.WriteJSON(c, fiber.StatusUnauthorized, "The refresh token has expired", nil, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if errors.Is(err, authentication.ErrRefreshTokenNotFound) {
|
|
||||||
response.WriteJSON(c, fiber.StatusUnauthorized, "Refresh token not found", nil, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
a.logger.Error("Logout failed", "error", err)
|
|
||||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "Logout successful", nil, nil)
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user