Skip to content

Commit 215ace5

Browse files
author
lai0xn
committed
email verification
1 parent 5a66dd6 commit 215ace5

File tree

11 files changed

+129
-20
lines changed

11 files changed

+129
-20
lines changed

.env.example

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ FACEBOOK_REDIRECT_URL=
1212

1313
REDIS_ADDR=
1414
REDIS_PASSWORD=
15-
REDIS_USERNAME=
15+
REDIS_USERNAME=
16+
17+
EMAIL=
18+
EMAIL_PASSWORD=

config/config.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import (
1212
)
1313

1414
var JWT_SECRET string
15-
15+
var EMAIL string
16+
var EMAIL_PASSWORD string
1617
func Load() {
1718
// OAuth configuration
1819
godotenv.Load()
@@ -39,6 +40,8 @@ func Load() {
3940

4041
// JWT Secret
4142
JWT_SECRET = os.Getenv("JWT_SECRET")
43+
EMAIL = os.Getenv("EMAIL")
44+
EMAIL_PASSWORD = os.Getenv("EMAIL_PASSWORD")
4245

4346
// Initialize the logger
4447
logger.NewLogger()

internal/handlers/auth.go

+34-4
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@ import (
66
"github.com/go-playground/validator/v10"
77
"github.com/labstack/echo/v4"
88
"github.com/lai0xn/squid-tech/internal/services"
9+
"github.com/lai0xn/squid-tech/pkg/mail"
910
"github.com/lai0xn/squid-tech/pkg/types"
1011
"github.com/lai0xn/squid-tech/pkg/utils"
1112
)
1213

1314
type authHandler struct {
1415
srv *services.AuthService
16+
verifier *mail.EmailVerifier
1517
}
1618

1719
func NewAuthHandler() *authHandler {
1820
return &authHandler{
1921
srv: services.NewAuthService(),
22+
verifier: mail.NewVerifier(),
2023
}
2124
}
2225

@@ -71,10 +74,37 @@ func (h *authHandler) Register(c echo.Context) error {
7174
return c.JSON(http.StatusBadRequest, utils.NewValidationError(e))
7275
}
7376
//TODO: fix gender
74-
if err := h.srv.CreateUser(payload.Name, payload.Email, payload.Password, false); err != nil {
77+
user,err := h.srv.CreateUser(payload.Name, payload.Email, payload.Password, false)
78+
if err != nil {
7579
return echo.NewHTTPError(http.StatusBadRequest, err)
7680
}
77-
return c.JSON(http.StatusOK, types.Response{
78-
"message": "user created successfully",
79-
})
81+
err = h.verifier.SendVerfication(user.ID,[]string{user.Email})
82+
if err != nil {
83+
return echo.NewHTTPError(http.StatusBadRequest,err)
84+
}
85+
return c.JSON(http.StatusOK,"user created and verification sent")
86+
}
87+
88+
89+
90+
91+
// Email verification example
92+
//
93+
// @Summary Verification endpoint
94+
// @Tags auth
95+
// @Accept json
96+
// @Produce json
97+
// @Param id query string true "userid"
98+
// @Param id otp string true "otp"
99+
// @Router /auth/verify [post]
100+
func (h *authHandler) VerifyUser(c echo.Context) error {
101+
id := c.QueryParam("id")
102+
otp := c.QueryParam("otp")
103+
if err := h.verifier.Verify(id,otp);err!= nil {
104+
return echo.NewHTTPError(http.StatusBadRequest,err)
105+
}
106+
if err := h.srv.ActivateUser(id);err!=nil{
107+
return echo.NewHTTPError(http.StatusBadRequest,err)
108+
}
109+
return c.JSON(http.StatusOK,"verification successfull")
80110
}

internal/handlers/oauth.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (h *oauthHandler) handleCallback(c echo.Context, provider string) error {
8383

8484
// If user doesn't exist, create a new user
8585
if existingUser == nil {
86-
if err := h.srv.CreateUser(user.Name, user.Email, "", false); err != nil {
86+
if _,err := h.srv.CreateUser(user.Name, user.Email, "", false); err != nil {
8787
return echo.NewHTTPError(http.StatusInternalServerError, "failed to create user: "+err.Error())
8888
}
8989
}

internal/router/auth.go

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ func AuthRoutes(e *echo.Group) {
99
h := handlers.NewAuthHandler()
1010
auth := e.Group("/auth")
1111
auth.POST("/register", h.Register)
12+
auth.GET("/verify", h.VerifyUser)
1213
auth.POST("/login", h.Login)
1314

1415
}

internal/router/events.go

-1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,4 @@ func eventRoutes(e *echo.Group) {
1919
events.POST("/event/:id/upload",h.AddImage)
2020
events.DELETE("/event/acheivment/:id/delete",h.DeleteAcheivment)
2121

22-
2322
}

internal/services/auth.go

+20-10
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,14 @@ func NewAuthService() *AuthService {
1616
return &AuthService{}
1717
}
1818

19-
func (s *AuthService) CreateUser(name string, email string, password string, gender bool) error {
20-
ctx := context.Background()
21-
client := db.NewClient()
22-
if err := client.Prisma.Connect(); err != nil {
23-
return err
24-
}
19+
func (s *AuthService) CreateUser(name string, email string, password string, gender bool) (*db.UserModel,error){
20+
ctx := context.Background()
2521

2622
encrypted_password, err := utils.Encrypt(password)
2723
if err != nil {
28-
return err
24+
return nil,err
2925
}
30-
_, err = client.User.CreateOne(
26+
result, err := prisma.Client.User.CreateOne(
3127
db.User.Email.Set(email),
3228
db.User.Name.Set(name),
3329
db.User.Bio.Set(""),
@@ -37,9 +33,9 @@ func (s *AuthService) CreateUser(name string, email string, password string, gen
3733
db.User.BgImg.Set("uploads/bgs/default.jpg"),
3834
).Exec(ctx)
3935
if err != nil {
40-
return err
36+
return nil,err
4137
}
42-
return nil
38+
return result,nil
4339

4440
}
4541

@@ -85,3 +81,17 @@ func (s *AuthService) GetUserByEmail(email string) (*db.UserModel, error) {
8581

8682
return user, nil // User found
8783
}
84+
85+
func (s *AuthService)ActivateUser(userID string) error {
86+
ctx := context.Background()
87+
88+
_,err := prisma.Client.User.FindUnique(
89+
db.User.ID.Equals(userID),
90+
).Update(
91+
db.User.Active.Set(true),
92+
).Exec(ctx)
93+
if err != nil {
94+
return err
95+
}
96+
return nil
97+
}

pkg/mail/mail.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package mail
2+
3+
import (
4+
"crypto/rand"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"net/smtp"
9+
"time"
10+
11+
"github.com/lai0xn/squid-tech/config"
12+
"github.com/lai0xn/squid-tech/pkg/redis"
13+
r "github.com/redis/go-redis/v9"
14+
)
15+
16+
var table = [...]byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
17+
18+
type EmailVerifier struct {
19+
client *r.Client
20+
}
21+
22+
func NewVerifier()*EmailVerifier{
23+
return &EmailVerifier{
24+
client: redis.GetClient(),
25+
}
26+
}
27+
28+
func (v *EmailVerifier)GenerateOTP() string {
29+
b := make([]byte, 6)
30+
n, err := io.ReadAtLeast(rand.Reader, b, 6)
31+
if n != 6 {
32+
panic(err)
33+
}
34+
for i := 0; i < len(b); i++ {
35+
b[i] = table[int(b[i])%len(table)]
36+
}
37+
return string(b)
38+
}
39+
40+
41+
func (v *EmailVerifier)SendVerfication(userID string,to []string)error{
42+
smtpHost := "smtp.gmail.com"
43+
smtpPort := "587"
44+
otp := v.GenerateOTP()
45+
message := []byte(fmt.Sprintf("Verification code is %s",otp))
46+
v.client.Set(redis.Ctx,"userOTP:"+userID,otp,time.Hour * 1)
47+
auth := smtp.PlainAuth("", config.EMAIL, config.EMAIL_PASSWORD, smtpHost)
48+
49+
err := smtp.SendMail(smtpHost+":"+smtpPort, auth, config.EMAIL, to, message)
50+
if err != nil {
51+
return err
52+
}
53+
return nil
54+
}
55+
56+
func (v *EmailVerifier)Verify(userID string,otp string)error{
57+
userOTP := v.client.Get(redis.Ctx,"userOTP:"+userID).Val()
58+
if userOTP != otp{
59+
return errors.New("verification failed")
60+
}
61+
return nil
62+
}

pkg/redis/redis.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"github.com/redis/go-redis/v9"
99
)
1010

11-
var ctx = context.Background()
11+
var Ctx = context.TODO()
1212
var client *redis.Client
1313

1414
func Connect() {
@@ -19,7 +19,7 @@ func Connect() {
1919

2020
})
2121

22-
_, err := client.Ping(ctx).Result()
22+
_, err := client.Ping(Ctx).Result()
2323
if err != nil {
2424
log.Fatalf("Could not connect to Redis: %v", err)
2525
}

prisma/dev.db

-24 KB
Binary file not shown.

prisma/schema/user.prisma

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ model User {
1414
eventComments EventComment[]
1515
postsComments PostComment[]
1616
posts Post[]
17+
active Boolean @default(false)
1718
badges Badge[]
1819
following Organization[] @relation("orgfollowers",fields: [followingIds], references: [id])
1920
followingIds String[] @db.ObjectId

0 commit comments

Comments
 (0)