-
-
Notifications
You must be signed in to change notification settings - Fork 467
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2797 from SadikSunbul/master
Added Email Verification
- Loading branch information
Showing
13 changed files
with
455 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
--- | ||
title: Email Verification Service | ||
keywords: [email, verification, smtp, golang, fiber] | ||
description: Email verification service with code generation and validation | ||
--- | ||
|
||
# Email Verification Service with Fiber | ||
|
||
[![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/email-verification) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/email-verification) | ||
|
||
A clean architecture based email verification service that generates and validates verification codes. | ||
|
||
## Features | ||
|
||
- Clean Architecture implementation | ||
- In-memory verification code storage | ||
- SMTP email service integration | ||
- Code generation and hashing | ||
- Configurable code expiration | ||
- Thread-safe operations | ||
|
||
## Project Structure | ||
|
||
``` | ||
email-verification/ | ||
├── api/ | ||
│ └── handlers/ # HTTP handlers | ||
├── application/ # Application business logic | ||
├── domain/ # Domain models and interfaces | ||
├── infrastructure/ # External implementations | ||
│ ├── code/ # Code generation | ||
│ ├── email/ # SMTP service | ||
│ └── repository/ # Data storage | ||
└── config/ # Configuration | ||
``` | ||
|
||
## Configuration | ||
|
||
Update `config/config.go` with your SMTP settings: | ||
|
||
```go | ||
func GetConfig() *Config { | ||
return &Config{ | ||
SMTPHost: "smtp.gmail.com", | ||
SMTPPort: 587, | ||
SMTPUser: "[email protected]", | ||
SMTPPassword: "your-app-password", | ||
CodeExpiration: time.Minute * 1, | ||
} | ||
} | ||
``` | ||
|
||
## API Endpoints | ||
|
||
| Method | URL | Description | | ||
|--------|----------------------------|--------------------------------| | ||
| POST | /verify/send/:email | Send verification code | | ||
| POST | /verify/check/:email/:code | Verify the received code | | ||
|
||
## Example Usage | ||
|
||
1. Send verification code: | ||
```bash | ||
curl -X POST http://localhost:3000/verify/send/[email protected] | ||
``` | ||
|
||
2. Verify code: | ||
```bash | ||
curl -X POST http://localhost:3000/verify/check/[email protected]/123456 | ||
``` | ||
|
||
## Response Examples | ||
|
||
Success: | ||
```json | ||
{ | ||
"message": "Code verified successfully" | ||
} | ||
``` | ||
|
||
Error: | ||
```json | ||
{ | ||
"error": "invalid code" | ||
} | ||
``` | ||
|
||
## How to Run | ||
|
||
1. Configure SMTP settings in `config/config.go` | ||
2. Run the application: | ||
```bash | ||
go run main.go | ||
``` | ||
|
||
## Dependencies | ||
|
||
- [Fiber v2](https://github.com/gofiber/fiber) | ||
- Go 1.21+ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package handlers | ||
|
||
import ( | ||
"email-verification/application" | ||
|
||
"github.com/gofiber/fiber/v2" | ||
) | ||
|
||
type VerificationHandler struct { | ||
verificationService *application.VerificationService | ||
} | ||
|
||
func NewVerificationHandler(service *application.VerificationService) *VerificationHandler { | ||
return &VerificationHandler{verificationService: service} | ||
} | ||
|
||
func (h *VerificationHandler) SendVerification(c *fiber.Ctx) error { | ||
email := c.Params("email") | ||
if err := h.verificationService.SendVerification(email); err != nil { | ||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||
"error": err.Error(), | ||
}) | ||
} | ||
return c.JSON(fiber.Map{"message": "Verification code sent"}) | ||
} | ||
|
||
func (h *VerificationHandler) CheckVerification(c *fiber.Ctx) error { | ||
email := c.Params("email") | ||
code := c.Params("code") | ||
|
||
if err := h.verificationService.VerifyCode(email, code); err != nil { | ||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||
"error": err.Error(), | ||
}) | ||
} | ||
|
||
return c.JSON(fiber.Map{"message": "Code verified successfully"}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package application | ||
|
||
import ( | ||
"email-verification/config" | ||
"email-verification/domain" | ||
"fmt" | ||
"time" | ||
) | ||
|
||
type VerificationService struct { | ||
repo domain.VerificationRepository | ||
emailService domain.EmailService | ||
codeGen domain.CodeGenerator | ||
codeExpiration time.Duration | ||
} | ||
|
||
func NewVerificationService( | ||
repo domain.VerificationRepository, | ||
emailService domain.EmailService, | ||
codeGen domain.CodeGenerator, | ||
config *config.Config, | ||
) *VerificationService { | ||
return &VerificationService{ | ||
repo: repo, | ||
emailService: emailService, | ||
codeGen: codeGen, | ||
codeExpiration: config.CodeExpiration, | ||
} | ||
} | ||
|
||
func (s *VerificationService) SendVerification(email string) error { | ||
if email == "" { | ||
return fmt.Errorf("email cannot be empty") | ||
} | ||
|
||
if _, err := s.repo.Get(email); err == nil { | ||
return fmt.Errorf("verification already pending") | ||
} | ||
|
||
code, err := s.codeGen.Generate() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := s.emailService.SendVerificationCode(email, code); err != nil { | ||
return err | ||
} | ||
|
||
verification := domain.Verification{ | ||
Code: s.codeGen.Hash(code), | ||
Exp: time.Now().Add(s.codeExpiration), | ||
} | ||
|
||
return s.repo.Store(email, verification) | ||
} | ||
|
||
func (s *VerificationService) VerifyCode(email, code string) error { | ||
if email == "" || code == "" { | ||
return fmt.Errorf("email and code cannot be empty") | ||
} | ||
|
||
verification, err := s.repo.Get(email) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
hashedCode := s.codeGen.Hash(code) | ||
if verification.Code != hashedCode { | ||
return fmt.Errorf("invalid code") | ||
} | ||
|
||
if time.Now().After(verification.Exp) { | ||
if err := s.repo.Delete(email); err != nil { | ||
return fmt.Errorf("failed to delete expired code: %w", err) | ||
} | ||
return fmt.Errorf("code expired") | ||
} | ||
|
||
return s.repo.Delete(email) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package config | ||
|
||
import ( | ||
"time" | ||
) | ||
|
||
type Config struct { | ||
SMTPHost string | ||
SMTPPort int | ||
SMTPUser string | ||
SMTPPassword string | ||
CodeExpiration time.Duration | ||
} | ||
|
||
func GetConfig() *Config { | ||
return &Config{ | ||
SMTPHost: "smtp.gmail.com", | ||
SMTPPort: 587, | ||
SMTPUser: "[email protected]", | ||
SMTPPassword: "bakkcmkakpfxwuef", | ||
CodeExpiration: time.Minute * 1, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package domain | ||
|
||
type CodeGenerator interface { | ||
Generate() (string, error) | ||
Hash(code string) string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package domain | ||
|
||
import "time" | ||
|
||
type Verification struct { | ||
Code string | ||
Exp time.Time | ||
} | ||
|
||
type VerificationRepository interface { | ||
Store(email string, verification Verification) error | ||
Get(email string) (Verification, error) | ||
Delete(email string) error | ||
} | ||
|
||
type EmailService interface { | ||
SendVerificationCode(to string, code string) error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
module email-verification | ||
|
||
go 1.21 | ||
|
||
require ( | ||
github.com/gofiber/fiber/v2 v2.52.6 | ||
golang.org/x/crypto v0.32.0 | ||
) | ||
|
||
require ( | ||
github.com/andybalholm/brotli v1.1.0 // indirect | ||
github.com/google/uuid v1.6.0 // indirect | ||
github.com/klauspost/compress v1.17.9 // indirect | ||
github.com/mattn/go-colorable v0.1.13 // indirect | ||
github.com/mattn/go-isatty v0.0.20 // indirect | ||
github.com/mattn/go-runewidth v0.0.16 // indirect | ||
github.com/rivo/uniseg v0.2.0 // indirect | ||
github.com/valyala/bytebufferpool v1.0.0 // indirect | ||
github.com/valyala/fasthttp v1.51.0 // indirect | ||
github.com/valyala/tcplisten v1.0.0 // indirect | ||
golang.org/x/sys v0.29.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= | ||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= | ||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= | ||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= | ||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | ||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= | ||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= | ||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | ||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | ||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | ||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= | ||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= | ||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= | ||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= | ||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= | ||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= | ||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= | ||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= | ||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package code | ||
|
||
import ( | ||
"crypto/rand" | ||
"encoding/hex" | ||
"fmt" | ||
|
||
"golang.org/x/crypto/sha3" | ||
) | ||
|
||
type DefaultCodeGenerator struct{} | ||
|
||
func NewCodeGenerator() *DefaultCodeGenerator { | ||
return &DefaultCodeGenerator{} | ||
} | ||
|
||
func (g *DefaultCodeGenerator) Generate() (string, error) { | ||
b := make([]byte, 3) | ||
if _, err := rand.Read(b); err != nil { | ||
return "", err | ||
} | ||
return fmt.Sprintf("%06x", b), nil | ||
} | ||
|
||
func (g *DefaultCodeGenerator) Hash(code string) string { | ||
hash := sha3.New256() | ||
hash.Write([]byte(code)) | ||
return hex.EncodeToString(hash.Sum(nil)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package email | ||
|
||
import ( | ||
"email-verification/config" | ||
"fmt" | ||
"net/smtp" | ||
) | ||
|
||
type SMTPService struct { | ||
config *config.Config | ||
} | ||
|
||
func NewSMTPService(config *config.Config) *SMTPService { | ||
return &SMTPService{config: config} | ||
} | ||
|
||
func (s *SMTPService) SendVerificationCode(to string, code string) error { | ||
subject := "Subject: Email Verification Code \n" | ||
body := fmt.Sprintf("Your verification code is %s", code) | ||
message := []byte(subject + "\n" + body) | ||
|
||
auth := smtp.PlainAuth("", s.config.SMTPUser, s.config.SMTPPassword, s.config.SMTPHost) | ||
err := smtp.SendMail( | ||
fmt.Sprintf("%s:%d", s.config.SMTPHost, s.config.SMTPPort), | ||
auth, | ||
s.config.SMTPUser, | ||
[]string{to}, | ||
message, | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.