Skip to content

Commit f5ac8de

Browse files
committed
Added Email Verification
1 parent ea6dec0 commit f5ac8de

File tree

13 files changed

+472
-0
lines changed

13 files changed

+472
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Here you can find the most **delicious** recipes to cook delicious meals using o
3131
- [Docker + MariaDB](./docker-mariadb-clean-arch/README.md) - Dockerized MariaDB with Clean Architecture.
3232
- [Docker + Nginx](./docker-nginx-loadbalancer/README.md) - Load balancing with Docker and Nginx.
3333
- [Dummy JSON Proxy](./dummyjson/README.md) - Proxying dummy JSON data.
34+
- [Email Verification](./email-verification/README.md) - Email verification service with code generation and validation.
3435
- [Entgo ORM (MySQL)](./ent-mysql/README.md) - Using Entgo ORM with MySQL
3536
- [Entgo Sveltekit](./entgo-sveltekit/README.md) - A full-stack Todo application built using Sveltekit, Tailwind CSS, Entgo, and SQLite.
3637
- [Envoy External Authorization](./envoy-extauthz/README.md) - External authorization with Envoy.

email-verification/README.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
title: Email Verification Service
3+
keywords: [email, verification, smtp, golang, fiber]
4+
description: Email verification service with code generation and validation
5+
---
6+
7+
# Email Verification Service with Fiber
8+
9+
[![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)
10+
11+
A clean architecture based email verification service that generates and validates verification codes.
12+
13+
## Features
14+
15+
- Clean Architecture implementation
16+
- In-memory verification code storage
17+
- SMTP email service integration
18+
- Code generation and hashing
19+
- Configurable code expiration
20+
- Thread-safe operations
21+
22+
## Project Structure
23+
24+
```
25+
email-verification/
26+
├── api/
27+
│ └── handlers/ # HTTP handlers
28+
├── application/ # Application business logic
29+
├── domain/ # Domain models and interfaces
30+
├── infrastructure/ # External implementations
31+
│ ├── code/ # Code generation
32+
│ ├── email/ # SMTP service
33+
│ └── repository/ # Data storage
34+
└── config/ # Configuration
35+
```
36+
37+
## Configuration
38+
39+
Update `config/config.go` with your SMTP settings:
40+
41+
```go
42+
func GetConfig() *Config {
43+
return &Config{
44+
SMTPHost: "smtp.gmail.com",
45+
SMTPPort: 587,
46+
SMTPUser: "[email protected]",
47+
SMTPPassword: "your-app-password",
48+
CodeExpiration: time.Minute * 1,
49+
}
50+
}
51+
```
52+
53+
## API Endpoints
54+
55+
| Method | URL | Description |
56+
|--------|----------------------------|--------------------------------|
57+
| POST | /verify/send/:email | Send verification code |
58+
| POST | /verify/check/:email/:code | Verify the received code |
59+
60+
## Example Usage
61+
62+
1. Send verification code:
63+
```bash
64+
curl -X POST http://localhost:3000/verify/send/[email protected]
65+
```
66+
67+
2. Verify code:
68+
```bash
69+
curl -X POST http://localhost:3000/verify/check/[email protected]/123456
70+
```
71+
72+
## Response Examples
73+
74+
Success:
75+
```json
76+
{
77+
"message": "Code verified successfully"
78+
}
79+
```
80+
81+
Error:
82+
```json
83+
{
84+
"error": "invalid code"
85+
}
86+
```
87+
88+
## How to Run
89+
90+
1. Configure SMTP settings in `config/config.go`
91+
2. Run the application:
92+
```bash
93+
go run main.go
94+
```
95+
96+
## Dependencies
97+
98+
- [Fiber v2](https://github.com/gofiber/fiber)
99+
- Go 1.21+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package handlers
2+
3+
import (
4+
"email-verification/application"
5+
6+
"github.com/gofiber/fiber/v2"
7+
)
8+
9+
type VerificationHandler struct {
10+
verificationService *application.VerificationService
11+
}
12+
13+
func NewVerificationHandler(service *application.VerificationService) *VerificationHandler {
14+
return &VerificationHandler{verificationService: service}
15+
}
16+
17+
func (h *VerificationHandler) SendVerification(c *fiber.Ctx) error {
18+
email := c.Params("email")
19+
if err := h.verificationService.SendVerification(email); err != nil {
20+
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
21+
"error": err.Error(),
22+
})
23+
}
24+
return c.JSON(fiber.Map{"message": "Verification code sent"})
25+
}
26+
27+
func (h *VerificationHandler) CheckVerification(c *fiber.Ctx) error {
28+
email := c.Params("email")
29+
code := c.Params("code")
30+
31+
if err := h.verificationService.VerifyCode(email, code); err != nil {
32+
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
33+
"error": err.Error(),
34+
})
35+
}
36+
37+
return c.JSON(fiber.Map{"message": "Code verified successfully"})
38+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package application
2+
3+
import (
4+
"email-verification/config"
5+
"email-verification/domain"
6+
"fmt"
7+
"time"
8+
)
9+
10+
type VerificationService struct {
11+
repo domain.VerificationRepository
12+
emailService domain.EmailService
13+
codeGen domain.CodeGenerator
14+
codeExpiration time.Duration
15+
}
16+
17+
func NewVerificationService(
18+
repo domain.VerificationRepository,
19+
emailService domain.EmailService,
20+
codeGen domain.CodeGenerator,
21+
config *config.Config,
22+
) *VerificationService {
23+
return &VerificationService{
24+
repo: repo,
25+
emailService: emailService,
26+
codeGen: codeGen,
27+
codeExpiration: config.CodeExpiration,
28+
}
29+
}
30+
31+
func (s *VerificationService) SendVerification(email string) error {
32+
code, err := s.codeGen.Generate()
33+
if err != nil {
34+
return err
35+
}
36+
37+
if err := s.emailService.SendVerificationCode(email, code); err != nil {
38+
return err
39+
}
40+
41+
verification := domain.Verification{
42+
Code: s.codeGen.Hash(code),
43+
Exp: time.Now().Add(s.codeExpiration),
44+
}
45+
46+
return s.repo.Store(email, verification)
47+
}
48+
49+
func (s *VerificationService) VerifyCode(email, code string) error {
50+
verification, err := s.repo.Get(email)
51+
if err != nil {
52+
return err
53+
}
54+
55+
hashedCode := s.codeGen.Hash(code)
56+
if verification.Code != hashedCode {
57+
return fmt.Errorf("invalid code")
58+
}
59+
60+
if time.Now().After(verification.Exp) {
61+
s.repo.Delete(email)
62+
return fmt.Errorf("code expired")
63+
}
64+
65+
return s.repo.Delete(email)
66+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package config
2+
3+
import (
4+
"time"
5+
)
6+
7+
type Config struct {
8+
SMTPHost string
9+
SMTPPort int
10+
SMTPUser string
11+
SMTPPassword string
12+
CodeExpiration time.Duration
13+
}
14+
15+
func GetConfig() *Config {
16+
return &Config{
17+
SMTPHost: "smtp.gmail.com",
18+
SMTPPort: 587,
19+
SMTPUser: "[email protected]",
20+
SMTPPassword: "your-app-password",
21+
CodeExpiration: time.Minute * 1,
22+
}
23+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package domain
2+
3+
type CodeGenerator interface {
4+
Generate() (string, error)
5+
Hash(code string) string
6+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package domain
2+
3+
import "time"
4+
5+
type Verification struct {
6+
Code string
7+
Exp time.Time
8+
}
9+
10+
type VerificationRepository interface {
11+
Store(email string, verification Verification) error
12+
Get(email string) (Verification, error)
13+
Delete(email string) error
14+
}
15+
16+
type EmailService interface {
17+
SendVerificationCode(to string, code string) error
18+
}

email-verification/go.mod

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module email-verification
2+
3+
go 1.21
4+
5+
require (
6+
github.com/gofiber/fiber/v2 v2.52.6
7+
golang.org/x/crypto v0.32.0
8+
)
9+
10+
require (
11+
github.com/andybalholm/brotli v1.1.0 // indirect
12+
github.com/gofiber/fiber v1.14.6 // indirect
13+
github.com/gofiber/utils v0.0.10 // indirect
14+
github.com/google/uuid v1.6.0 // indirect
15+
github.com/gorilla/schema v1.1.0 // indirect
16+
github.com/klauspost/compress v1.17.9 // indirect
17+
github.com/mattn/go-colorable v0.1.13 // indirect
18+
github.com/mattn/go-isatty v0.0.20 // indirect
19+
github.com/mattn/go-runewidth v0.0.16 // indirect
20+
github.com/rivo/uniseg v0.2.0 // indirect
21+
github.com/valyala/bytebufferpool v1.0.0 // indirect
22+
github.com/valyala/fasthttp v1.51.0 // indirect
23+
github.com/valyala/tcplisten v1.0.0 // indirect
24+
golang.org/x/sys v0.29.0 // indirect
25+
)

email-verification/go.sum

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
2+
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
3+
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
4+
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
5+
github.com/gofiber/fiber v1.14.6 h1:QRUPvPmr8ijQuGo1MgupHBn8E+wW0IKqiOvIZPtV70o=
6+
github.com/gofiber/fiber v1.14.6/go.mod h1:Yw2ekF1YDPreO9V6TMYjynu94xRxZBdaa8X5HhHsjCM=
7+
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
8+
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
9+
github.com/gofiber/utils v0.0.10 h1:3Mr7X7JdCUo7CWf/i5sajSaDmArEDtti8bM1JUVso2U=
10+
github.com/gofiber/utils v0.0.10/go.mod h1:9J5aHFUIjq0XfknT4+hdSMG6/jzfaAgCu4HEbWDeBlo=
11+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
12+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
13+
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
14+
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
15+
github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg=
16+
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
17+
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
18+
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
19+
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
20+
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
21+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
22+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
23+
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
24+
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
25+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
26+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
27+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
28+
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
29+
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
30+
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
31+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
32+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
33+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
34+
github.com/valyala/fasthttp v1.16.0 h1:9zAqOYLl8Tuy3E5R6ckzGDJ1g8+pw15oQp2iL9Jl6gQ=
35+
github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
36+
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
37+
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
38+
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
39+
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
40+
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
41+
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
42+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
43+
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
44+
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
45+
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
46+
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
47+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
48+
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
49+
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
50+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
51+
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
52+
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
53+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
54+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
55+
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
56+
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
57+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package code
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/hex"
6+
"fmt"
7+
8+
"golang.org/x/crypto/sha3"
9+
)
10+
11+
type DefaultCodeGenerator struct{}
12+
13+
func NewCodeGenerator() *DefaultCodeGenerator {
14+
return &DefaultCodeGenerator{}
15+
}
16+
17+
func (g *DefaultCodeGenerator) Generate() (string, error) {
18+
b := make([]byte, 3)
19+
if _, err := rand.Read(b); err != nil {
20+
return "", err
21+
}
22+
return fmt.Sprintf("%06x", b), nil
23+
}
24+
25+
func (g *DefaultCodeGenerator) Hash(code string) string {
26+
hash := sha3.New256()
27+
hash.Write([]byte(code))
28+
return hex.EncodeToString(hash.Sum(nil))
29+
}

0 commit comments

Comments
 (0)