Skip to content
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
114 changes: 57 additions & 57 deletions _examples/cache/otp_code_verification/internal/auth/handler.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
package auth

import (
"net/http"
"time"
"net/http"
"time"

"otp-backend/internal/middleware"
"otp-backend/internal/service"
"otp-backend/internal/utils"
"otp-backend/internal/middleware"
"otp-backend/internal/service"
"otp-backend/internal/utils"

"github.com/its-ernest/echox/store"
"github.com/labstack/echo/v5"
"github.com/golang-jwt/jwt/v5"
"github.com/golang-jwt/jwt/v5"
"github.com/its-ernest/echox/store"
"github.com/labstack/echo/v5"
)

type Handler struct {
service *service.AuthService
jwtSecret []byte
service *service.AuthService
jwtSecret []byte
}

func NewHandler(s *service.AuthService, secret []byte) *Handler {
return &Handler{
service: s,
jwtSecret: secret,
}
return &Handler{
service: s,
jwtSecret: secret,
}
}

type requestOTP struct {
Phone string `json:"phone"`
Phone string `json:"phone"`
}

type verifyOTP struct {
Phone string `json:"phone"`
Code string `json:"code"`
Phone string `json:"phone"`
Code string `json:"code"`
}

func (h *Handler) Register(g *echo.Group, store store.Store) {
Expand All @@ -40,55 +40,55 @@ func (h *Handler) Register(g *echo.Group, store store.Store) {
}

func (h *Handler) requestOTP(c *echo.Context) error {
req := new(requestOTP)
if err := c.Bind(req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid JSON")
}

// number validation
if req.Phone == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Phone number is required")
}
req := new(requestOTP)
if err := c.Bind(req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid JSON")
}

// number validation
if req.Phone == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Phone number is required")
}
// format for es64 format
phone := utils.FormatPhone(req.Phone)
phone := utils.FormatPhone(req.Phone)

// utilize auth_service.go's RequestOTP function
if err := h.service.RequestOTP(c.Request().Context(), phone); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "OTP failed")
}
if err := h.service.RequestOTP(c.Request().Context(), phone); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "OTP failed")
}

return c.JSON(http.StatusOK, map[string]string{"message": "OTP sent"})
return c.JSON(http.StatusOK, map[string]string{"message": "OTP sent"})
}

func (h *Handler) verifyOTP(c *echo.Context) error {
// validate JSON request
req := new(verifyOTP)
if err := c.Bind(req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid JSON")
}
req := new(verifyOTP)
if err := c.Bind(req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid JSON")
}

// verify OTP stored in echox cache
phone := utils.FormatPhone(req.Phone)
if err := h.service.VerifyOTP(c.Request().Context(), phone, req.Code); err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
}

// create and hand over a jwt afterwards
claims := &jwt.RegisteredClaims{
Subject: phone,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
}
phone := utils.FormatPhone(req.Phone)
if err := h.service.VerifyOTP(c.Request().Context(), phone, req.Code); err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
}

// create and hand over a jwt afterwards
claims := &jwt.RegisteredClaims{
Subject: phone,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
}
// sign jwt
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
t, err := token.SignedString(h.jwtSecret)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Could not generate token")
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
t, err := token.SignedString(h.jwtSecret)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Could not generate token")
}

// deliver jwt
return c.JSON(http.StatusOK, map[string]string{
"message": "Verified",
"token": t,
})
}
return c.JSON(http.StatusOK, map[string]string{
"message": "Verified",
"token": t,
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import (
// OTPCache uses my echox MemoryStore to prevent OTP spamming
func OTPCache(store store.Store) echo.MiddlewareFunc {
return cache.New(cache.Config{
Store: store,
TTL: 5 * time.Minute, // expire after 5 mins
MaxBodySize: 1024 * 10, // 10KB safety limit
Store: store,
TTL: 5 * time.Minute, // expire after 5 mins
MaxBodySize: 1024 * 10, // 10KB safety limit
// key-generator for cache values
// this one uses a generator specific to a phone number.
// this one uses a generator specific to a phone number.
//for e.g, 'otp_lock:+23324512XXXX', 'otp_lock:+23350348XXXX'
// each of the keys above is assigned a distinct generated values from other parts of the code
KeyGenerator: func(c *echo.Context) string {
Expand All @@ -24,4 +24,4 @@ func OTPCache(store store.Store) echo.MiddlewareFunc {
return "otp_lock:" + phone
},
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package service
import (
"context"
"errors"
"time"
"log"
"fmt"
"log"
"time"

"otp-backend/internal/utils"
"github.com/its-ernest/echox/store"
"otp-backend/internal/utils"
)

type AuthService struct {
Expand Down Expand Up @@ -38,12 +38,12 @@ func (s *AuthService) VerifyOTP(ctx context.Context, phone, code string) error {
}

if string(entry.Body) != code {
return errors.New("invalid otp: original: "+string(entry.Body))
return errors.New("invalid otp: original: " + string(entry.Body))
}

// delete otp after verification to free memory
_ = s.store.Delete(ctx, "otp:"+phone)

// simulate registration
return nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ func FormatPhone(phone string) string {
return "+" + phone
}
return phone
}
}
Loading