diff --git a/.gitignore b/.gitignore
index 76b5cf8..d34b3e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,4 +18,4 @@ TODO.md
logs.txt
.idea/
secret.md
-app.env
\ No newline at end of file
+tmp/
\ No newline at end of file
diff --git a/Golang_GORM.postman_collection.json b/Golang_GORM.postman_collection.json
new file mode 100644
index 0000000..7fa4fb1
--- /dev/null
+++ b/Golang_GORM.postman_collection.json
@@ -0,0 +1,365 @@
+{
+ "info": {
+ "_postman_id": "d8c74037-1a1e-41f0-975d-d927d4d02821",
+ "name": "Golang_GORM",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
+ "_exporter_id": "14791724"
+ },
+ "item": [
+ {
+ "name": "Auth",
+ "item": [
+ {
+ "name": "Register",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"email\": \"admin@admin.com\",\r\n \"name\": \"Admin\",\r\n \"password\": \"password123\",\r\n \"passwordConfirm\": \"password123\",\r\n \"photo\": \"default.png\"\r\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://localhost:8000/api/auth/register",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "auth",
+ "register"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Refresh Token",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:8000/api/auth/refresh",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "auth",
+ "refresh"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Forgot Password",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"email\": \"janeDoe@gmail.com\"\r\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://localhost:8000/api/auth/forgotpassword",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "auth",
+ "forgotpassword"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Reset Password",
+ "request": {
+ "method": "PATCH",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"password\": \"password123\",\r\n \"passwordConfirm\": \"password123\"\r\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://localhost:8000/api/auth/resetpassword/CJ6a7SPThfBv2o132NQF",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "auth",
+ "resetpassword",
+ "CJ6a7SPThfBv2o132NQF"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Logout",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:8000/api/auth/logout",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "auth",
+ "logout"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Login",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"email\": \"admin@admin.com\",\r\n \"password\": \"password123\"\r\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://localhost:8000/api/auth/login",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "auth",
+ "login"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Verify Email Address",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:8000/api/auth/verifyemail/TR1KnnPr2PvkYi2xvvAG",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "auth",
+ "verifyemail",
+ "TR1KnnPr2PvkYi2xvvAG"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "User",
+ "item": [
+ {
+ "name": "Get Me",
+ "request": {
+ "auth": {
+ "type": "noauth"
+ },
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:8000/api/users/me",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "users",
+ "me"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Post",
+ "item": [
+ {
+ "name": "Create Post",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"title\": \"My first 6 demo post always\",\r\n \"content\": \"My content haha My content haha\",\r\n \"image\": \"default.png\"\r\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://localhost:8000/api/posts",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "posts"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Get Post",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:8000/api/posts/571e4146-c8d9-4507-81f4-4b1a3a01cd03",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "posts",
+ "571e4146-c8d9-4507-81f4-4b1a3a01cd03"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Update Post",
+ "request": {
+ "method": "PUT",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"title\": \"Update post 6 with new tile\",\r\n \"content\": \"My content haha My content haha\",\r\n \"image\": \"default.png\"\r\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://localhost:8000/api/posts/571e4146-c8d9-4507-81f4-4b1a3a01cd03",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "posts",
+ "571e4146-c8d9-4507-81f4-4b1a3a01cd03"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Post",
+ "request": {
+ "method": "DELETE",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:8000/api/posts/571e4146-c8d9-4507-81f4-4b1a3a01cd03",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "posts",
+ "571e4146-c8d9-4507-81f4-4b1a3a01cd03"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Get All Posts",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:8000/api/posts?page=1&limit=10",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8000",
+ "path": [
+ "api",
+ "posts"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "1"
+ },
+ {
+ "key": "limit",
+ "value": "10"
+ }
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app.env b/app.env
new file mode 100644
index 0000000..1f93d6e
--- /dev/null
+++ b/app.env
@@ -0,0 +1,19 @@
+POSTGRES_HOST=127.0.0.1
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=password123
+POSTGRES_DB=golang-gorm
+POSTGRES_PORT=6500
+
+PORT=8000
+CLIENT_ORIGIN=http://localhost:3000
+
+EMAIL_FROM=admin@admin.com
+SMTP_HOST=smtp.mailtrap.io
+SMTP_USER=
+SMTP_PASS=
+SMTP_PORT=587
+
+TOKEN_EXPIRED_IN=60m
+TOKEN_MAXAGE=60
+
+TOKEN_SECRET=my-ultra-secure-json-web-token-string
\ No newline at end of file
diff --git a/controllers/auth.controller.go b/controllers/auth.controller.go
index df7ee56..6cba672 100644
--- a/controllers/auth.controller.go
+++ b/controllers/auth.controller.go
@@ -1,12 +1,12 @@
package controllers
import (
- "fmt"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
+ "github.com/thanhpk/randstr"
"github.com/wpcodevo/golang-gorm-postgres/initializers"
"github.com/wpcodevo/golang-gorm-postgres/models"
"github.com/wpcodevo/golang-gorm-postgres/utils"
@@ -21,7 +21,7 @@ func NewAuthController(DB *gorm.DB) AuthController {
return AuthController{DB}
}
-// SignUp User
+// [...] SignUp User
func (ac *AuthController) SignUpUser(ctx *gin.Context) {
var payload *models.SignUpInput
@@ -47,7 +47,7 @@ func (ac *AuthController) SignUpUser(ctx *gin.Context) {
Email: strings.ToLower(payload.Email),
Password: hashedPassword,
Role: "user",
- Verified: true,
+ Verified: false,
Photo: payload.Photo,
Provider: "local",
CreatedAt: now,
@@ -56,24 +56,45 @@ func (ac *AuthController) SignUpUser(ctx *gin.Context) {
result := ac.DB.Create(&newUser)
- if result.Error != nil {
+ if result.Error != nil && strings.Contains(result.Error.Error(), "duplicate key value violates unique") {
+ ctx.JSON(http.StatusConflict, gin.H{"status": "fail", "message": "User with that email already exists"})
+ return
+ } else if result.Error != nil {
ctx.JSON(http.StatusBadGateway, gin.H{"status": "error", "message": "Something bad happened"})
return
}
- userResponse := &models.UserResponse{
- ID: newUser.ID,
- Name: newUser.Name,
- Email: newUser.Email,
- Photo: newUser.Photo,
- Role: newUser.Role,
- Provider: newUser.Provider,
- CreatedAt: newUser.CreatedAt,
- UpdatedAt: newUser.UpdatedAt,
+ config, _ := initializers.LoadConfig(".")
+
+ // Generate Verification Code
+ code := randstr.String(20)
+
+ verification_code := utils.Encode(code)
+
+ // Update User in Database
+ newUser.VerificationCode = verification_code
+ ac.DB.Save(newUser)
+
+ var firstName = newUser.Name
+
+ if strings.Contains(firstName, " ") {
+ firstName = strings.Split(firstName, " ")[1]
+ }
+
+ // 👇 Send Email
+ emailData := utils.EmailData{
+ URL: config.ClientOrigin + "/verifyemail/" + code,
+ FirstName: firstName,
+ Subject: "Your account verification code",
}
- ctx.JSON(http.StatusCreated, gin.H{"status": "success", "data": gin.H{"user": userResponse}})
+
+ utils.SendEmail(&newUser, &emailData)
+
+ message := "We sent an email with a verification code to " + newUser.Email
+ ctx.JSON(http.StatusCreated, gin.H{"status": "success", "message": message})
}
+// [...] SignIn User
func (ac *AuthController) SignInUser(ctx *gin.Context) {
var payload *models.SignInInput
@@ -89,6 +110,11 @@ func (ac *AuthController) SignInUser(ctx *gin.Context) {
return
}
+ if !user.Verified {
+ ctx.JSON(http.StatusForbidden, gin.H{"status": "fail", "message": "Please verify your email"})
+ return
+ }
+
if err := utils.VerifyPassword(user.Password, payload.Password); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": "Invalid email or Password"})
return
@@ -96,68 +122,45 @@ func (ac *AuthController) SignInUser(ctx *gin.Context) {
config, _ := initializers.LoadConfig(".")
- // Generate Tokens
- access_token, err := utils.CreateToken(config.AccessTokenExpiresIn, user.ID, config.AccessTokenPrivateKey)
- if err != nil {
- ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": err.Error()})
- return
- }
-
- refresh_token, err := utils.CreateToken(config.RefreshTokenExpiresIn, user.ID, config.RefreshTokenPrivateKey)
+ // Generate Token
+ token, err := utils.GenerateToken(config.TokenExpiresIn, user.ID, config.TokenSecret)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": err.Error()})
return
}
- ctx.SetCookie("access_token", access_token, config.AccessTokenMaxAge*60, "/", "localhost", false, true)
- ctx.SetCookie("refresh_token", refresh_token, config.RefreshTokenMaxAge*60, "/", "localhost", false, true)
- ctx.SetCookie("logged_in", "true", config.AccessTokenMaxAge*60, "/", "localhost", false, false)
+ ctx.SetCookie("token", token, config.TokenMaxAge*60, "/", "localhost", false, true)
- ctx.JSON(http.StatusOK, gin.H{"status": "success", "access_token": access_token})
+ ctx.JSON(http.StatusOK, gin.H{"status": "success", "token": token})
}
-// Refresh Access Token
-func (ac *AuthController) RefreshAccessToken(ctx *gin.Context) {
- message := "could not refresh access token"
-
- cookie, err := ctx.Cookie("refresh_token")
+// [...] SignOut User
+func (ac *AuthController) LogoutUser(ctx *gin.Context) {
+ ctx.SetCookie("token", "", -1, "/", "localhost", false, true)
+ ctx.JSON(http.StatusOK, gin.H{"status": "success"})
+}
- if err != nil {
- ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"status": "fail", "message": message})
- return
- }
+// [...] Verify Email
+func (ac *AuthController) VerifyEmail(ctx *gin.Context) {
- config, _ := initializers.LoadConfig(".")
+ code := ctx.Params.ByName("verificationCode")
+ verification_code := utils.Encode(code)
- sub, err := utils.ValidateToken(cookie, config.RefreshTokenPublicKey)
- if err != nil {
- ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"status": "fail", "message": err.Error()})
- return
- }
-
- var user models.User
- result := ac.DB.First(&user, "id = ?", fmt.Sprint(sub))
+ var updatedUser models.User
+ result := ac.DB.First(&updatedUser, "verification_code = ?", verification_code)
if result.Error != nil {
- ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"status": "fail", "message": "the user belonging to this token no logger exists"})
+ ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": "Invalid verification code or user doesn't exists"})
return
}
- access_token, err := utils.CreateToken(config.AccessTokenExpiresIn, user.ID, config.AccessTokenPrivateKey)
- if err != nil {
- ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"status": "fail", "message": err.Error()})
+ if updatedUser.Verified {
+ ctx.JSON(http.StatusConflict, gin.H{"status": "fail", "message": "User already verified"})
return
}
- ctx.SetCookie("access_token", access_token, config.AccessTokenMaxAge*60, "/", "localhost", false, true)
- ctx.SetCookie("logged_in", "true", config.AccessTokenMaxAge*60, "/", "localhost", false, false)
+ updatedUser.VerificationCode = ""
+ updatedUser.Verified = true
+ ac.DB.Save(&updatedUser)
- ctx.JSON(http.StatusOK, gin.H{"status": "success", "access_token": access_token})
-}
-
-func (ac *AuthController) LogoutUser(ctx *gin.Context) {
- ctx.SetCookie("access_token", "", -1, "/", "localhost", false, true)
- ctx.SetCookie("refresh_token", "", -1, "/", "localhost", false, true)
- ctx.SetCookie("logged_in", "", -1, "/", "localhost", false, false)
-
- ctx.JSON(http.StatusOK, gin.H{"status": "success"})
+ ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": "Email verified successfully"})
}
diff --git a/example.env b/example.env
index cb1c622..1f93d6e 100644
--- a/example.env
+++ b/example.env
@@ -7,13 +7,13 @@ POSTGRES_PORT=6500
PORT=8000
CLIENT_ORIGIN=http://localhost:3000
-ACCESS_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCUEFJQkFBSkJBTzVIKytVM0xrWC91SlRvRHhWN01CUURXSTdGU0l0VXNjbGFFKzlaUUg5Q2VpOGIxcUVmCnJxR0hSVDVWUis4c3UxVWtCUVpZTER3MnN3RTVWbjg5c0ZVQ0F3RUFBUUpCQUw4ZjRBMUlDSWEvQ2ZmdWR3TGMKNzRCdCtwOXg0TEZaZXMwdHdtV3Vha3hub3NaV0w4eVpSTUJpRmI4a25VL0hwb3piTnNxMmN1ZU9wKzVWdGRXNApiTlVDSVFENm9JdWxqcHdrZTFGY1VPaldnaXRQSjNnbFBma3NHVFBhdFYwYnJJVVI5d0loQVBOanJ1enB4ckhsCkUxRmJxeGtUNFZ5bWhCOU1HazU0Wk1jWnVjSmZOcjBUQWlFQWhML3UxOVZPdlVBWVd6Wjc3Y3JxMTdWSFBTcXoKUlhsZjd2TnJpdEg1ZGdjQ0lRRHR5QmFPdUxuNDlIOFIvZ2ZEZ1V1cjg3YWl5UHZ1YStxeEpXMzQrb0tFNXdJZwpQbG1KYXZsbW9jUG4rTkVRdGhLcTZuZFVYRGpXTTlTbktQQTVlUDZSUEs0PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQ==
-ACCESS_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTzVIKytVM0xrWC91SlRvRHhWN01CUURXSTdGU0l0VQpzY2xhRSs5WlFIOUNlaThiMXFFZnJxR0hSVDVWUis4c3UxVWtCUVpZTER3MnN3RTVWbjg5c0ZVQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==
-ACCESS_TOKEN_EXPIRED_IN=15m
-ACCESS_TOKEN_MAXAGE=15
+EMAIL_FROM=admin@admin.com
+SMTP_HOST=smtp.mailtrap.io
+SMTP_USER=
+SMTP_PASS=
+SMTP_PORT=587
+TOKEN_EXPIRED_IN=60m
+TOKEN_MAXAGE=60
-REFRESH_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT1FJQkFBSkJBSWFJcXZXeldCSndnYjR1SEhFQ01RdHFZMTI5b2F5RzVZMGlGcG51a0J1VHpRZVlQWkE4Cmx4OC9lTUh3Rys1MlJGR3VxMmE2N084d2s3TDR5dnY5dVY4Q0F3RUFBUUpBRUZ6aEJqOUk3LzAxR285N01CZUgKSlk5TUJLUEMzVHdQQVdwcSswL3p3UmE2ZkZtbXQ5NXNrN21qT3czRzNEZ3M5T2RTeWdsbTlVdndNWXh6SXFERAplUUloQVA5UStrMTBQbGxNd2ZJbDZtdjdTMFRYOGJDUlRaZVI1ZFZZb3FTeW40YmpBaUVBaHVUa2JtZ1NobFlZCnRyclNWZjN0QWZJcWNVUjZ3aDdMOXR5MVlvalZVRlVDSUhzOENlVHkwOWxrbkVTV0dvV09ZUEZVemhyc3Q2Z08KU3dKa2F2VFdKdndEQWlBdWhnVU8yeEFBaXZNdEdwUHVtb3hDam8zNjBMNXg4d012bWdGcEFYNW9uUUlnQzEvSwpNWG1heWtsaFRDeWtXRnpHMHBMWVdkNGRGdTI5M1M2ZUxJUlNIS009Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t
-REFRESH_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBSWFJcXZXeldCSndnYjR1SEhFQ01RdHFZMTI5b2F5Rwo1WTBpRnBudWtCdVR6UWVZUFpBOGx4OC9lTUh3Rys1MlJGR3VxMmE2N084d2s3TDR5dnY5dVY4Q0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==
-REFRESH_TOKEN_EXPIRED_IN=60m
-REFRESH_TOKEN_MAXAGE=60
\ No newline at end of file
+TOKEN_SECRET=my-ultra-secure-json-web-token-string
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 10ab9ec..2fa3182 100644
--- a/go.mod
+++ b/go.mod
@@ -7,8 +7,11 @@ require (
github.com/gin-gonic/gin v1.8.1
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.1.2
+ github.com/k3a/html2text v1.0.8
github.com/spf13/viper v1.12.0
+ github.com/thanhpk/randstr v1.0.4
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
+ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gorm.io/driver/postgres v1.3.8
gorm.io/gorm v1.23.8
)
@@ -50,6 +53,7 @@ require (
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
+ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index c66c66c..5921745 100644
--- a/go.sum
+++ b/go.sum
@@ -153,6 +153,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@@ -214,6 +216,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/k3a/html2text v1.0.8 h1:rVanLhKilpnJUJs/CNKWzMC4YaQINGxK0rSG8ssmnV0=
+github.com/k3a/html2text v1.0.8/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIBmgUqA=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -276,6 +282,10 @@ github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXY
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
@@ -302,6 +312,8 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=
github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
+github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
+github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
@@ -497,6 +509,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@@ -641,11 +654,15 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
diff --git a/initializers/loadEnv.go b/initializers/loadEnv.go
index 9bd68fe..0e0f6f7 100644
--- a/initializers/loadEnv.go
+++ b/initializers/loadEnv.go
@@ -16,14 +16,15 @@ type Config struct {
ClientOrigin string `mapstructure:"CLIENT_ORIGIN"`
- AccessTokenPrivateKey string `mapstructure:"ACCESS_TOKEN_PRIVATE_KEY"`
- AccessTokenPublicKey string `mapstructure:"ACCESS_TOKEN_PUBLIC_KEY"`
- RefreshTokenPrivateKey string `mapstructure:"REFRESH_TOKEN_PRIVATE_KEY"`
- RefreshTokenPublicKey string `mapstructure:"REFRESH_TOKEN_PUBLIC_KEY"`
- AccessTokenExpiresIn time.Duration `mapstructure:"ACCESS_TOKEN_EXPIRED_IN"`
- RefreshTokenExpiresIn time.Duration `mapstructure:"REFRESH_TOKEN_EXPIRED_IN"`
- AccessTokenMaxAge int `mapstructure:"ACCESS_TOKEN_MAXAGE"`
- RefreshTokenMaxAge int `mapstructure:"REFRESH_TOKEN_MAXAGE"`
+ TokenSecret string `mapstructure:"TOKEN_SECRET"`
+ TokenExpiresIn time.Duration `mapstructure:"TOKEN_EXPIRED_IN"`
+ TokenMaxAge int `mapstructure:"TOKEN_MAXAGE"`
+
+ EmailFrom string `mapstructure:"EMAIL_FROM"`
+ SMTPHost string `mapstructure:"SMTP_HOST"`
+ SMTPPass string `mapstructure:"SMTP_PASS"`
+ SMTPPort int `mapstructure:"SMTP_PORT"`
+ SMTPUser string `mapstructure:"SMTP_USER"`
}
func LoadConfig(path string) (config Config, err error) {
diff --git a/middleware/deserialize-user.go b/middleware/deserialize-user.go
index 9e108f2..5ff6f79 100644
--- a/middleware/deserialize-user.go
+++ b/middleware/deserialize-user.go
@@ -13,25 +13,25 @@ import (
func DeserializeUser() gin.HandlerFunc {
return func(ctx *gin.Context) {
- var access_token string
- cookie, err := ctx.Cookie("access_token")
+ var token string
+ cookie, err := ctx.Cookie("token")
authorizationHeader := ctx.Request.Header.Get("Authorization")
fields := strings.Fields(authorizationHeader)
if len(fields) != 0 && fields[0] == "Bearer" {
- access_token = fields[1]
+ token = fields[1]
} else if err == nil {
- access_token = cookie
+ token = cookie
}
- if access_token == "" {
+ if token == "" {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"status": "fail", "message": "You are not logged in"})
return
}
config, _ := initializers.LoadConfig(".")
- sub, err := utils.ValidateToken(access_token, config.AccessTokenPublicKey)
+ sub, err := utils.ValidateToken(token, config.TokenSecret)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"status": "fail", "message": err.Error()})
return
diff --git a/migrate/migrate.go b/migrate/migrate.go
index 86e4c3e..72081ce 100644
--- a/migrate/migrate.go
+++ b/migrate/migrate.go
@@ -18,6 +18,7 @@ func init() {
}
func main() {
+ initializers.DB.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"")
initializers.DB.AutoMigrate(&models.User{})
fmt.Println("👍 Migration complete")
}
diff --git a/models/user.model.go b/models/user.model.go
index 00665a5..5bc4c62 100644
--- a/models/user.model.go
+++ b/models/user.model.go
@@ -7,20 +7,17 @@ import (
)
type User struct {
- ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primary_key"`
- Name string `gorm:"type:varchar(255)"`
- Email string `gorm:"uniqueIndex"`
- Password string
- Role string `gorm:"type:varchar(255)"`
- Provider string
- Photo string
- Verified bool
- CreatedAt time.Time
- UpdatedAt time.Time
-}
-
-func (User) UsersTable() string {
- return "users"
+ ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primary_key"`
+ Name string `gorm:"type:varchar(255);not null"`
+ Email string `gorm:"uniqueIndex;not null"`
+ Password string `gorm:"not null"`
+ Role string `gorm:"type:varchar(255);not null"`
+ Provider string `gorm:"not null"`
+ Photo string `gorm:"not null;default:'default.png'"`
+ VerificationCode string
+ Verified bool `gorm:"not null"`
+ CreatedAt time.Time `gorm:"not null"`
+ UpdatedAt time.Time `gorm:"not null"`
}
type SignUpInput struct {
@@ -28,7 +25,7 @@ type SignUpInput struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required,min=8"`
PasswordConfirm string `json:"passwordConfirm" binding:"required"`
- Photo string `json:"photo" binding:"required"`
+ Photo string `json:"photo"`
}
type SignInInput struct {
diff --git a/readMe.md b/readMe.md
index c1c43d0..ad327ea 100644
--- a/readMe.md
+++ b/readMe.md
@@ -1,5 +1,59 @@
-# Build Golang RESTful API with Gorm, Gin and Postgres
+# Golang and GORM - User Registration and Email Verification
+
+In this comprehensive guide, you will learn how to secure a Golang RESTful API with JSON Web Tokens and Email verification. We will start by registering the user, verifying the user's email address, logging in the registered user, and logging out the authenticated user.
+
+
+
+## Topics Covered
+
+- Golang and GORM JWT Authentication Overview
+- Create the Database Models with GORM
+- Database Migration with GORM
+- Generate and Verify the Password with Bcrypt
+- Sign and Verify the JWT (JSON Web Tokens)
+ - Update the Environment Variables File
+ - Validate the Variables with Viper
+ - Generate the JSON Web Tokens
+ - Verify the JSON Web Tokens
+- Create the SMTP Credentials
+- Setup the HTML Templates
+ - Add the HTML Email Base Template
+ - Add the HTML Email CSS Styles
+ - Add the Email Verification Template
+ - Create the Email Utility Function
+- Create the Controller Functions
+ - Function to Generate the Verification Code
+ - User Registration Controller
+ - Verify Email Controller
+ - Login User Controller
+ - Logout User Controller
+ - Get User Profile Controller
+- Create the Authentication Guard
+- Create Routes for the Controllers
+ - Auth Routes
+ - User Routes
+- Register the Routes and Start the Golang Server
+
+Read the entire article here: [https://codevoweb.com/golang-and-gorm-user-registration-email-verification](https://codevoweb.com/golang-and-gorm-user-registration-email-verification)
+
+Articles in this series:
### 1. How to Setup Golang GORM RESTful API Project with Postgres
[How to Setup Golang GORM RESTful API Project with Postgres](https://codevoweb.com/setup-golang-gorm-restful-api-project-with-postgres/)
+
+### 2. API with Golang + GORM + PostgreSQL: Access & Refresh Tokens
+
+[API with Golang + GORM + PostgreSQL: Access & Refresh Tokens](https://codevoweb.com/golang-gorm-postgresql-user-registration-with-refresh-tokens)
+
+### 3. Golang and GORM - User Registration and Email Verification
+
+[Golang and GORM - User Registration and Email Verification](https://codevoweb.com/golang-and-gorm-user-registration-email-verification)
+
+### 4. Forgot/Reset Passwords in Golang with SMTP HTML Email
+
+[Forgot/Reset Passwords in Golang with SMTP HTML Email](https://codevoweb.com/forgot-reset-passwords-in-golang-with-html-email)
+
+### 5. Build a RESTful CRUD API with Golang
+
+[Build a RESTful CRUD API with Golang](https://codevoweb.com/build-restful-crud-api-with-golang)
diff --git a/routes/auth.routes.go b/routes/auth.routes.go
index 5c90cdc..db31c4e 100644
--- a/routes/auth.routes.go
+++ b/routes/auth.routes.go
@@ -19,6 +19,6 @@ func (rc *AuthRouteController) AuthRoute(rg *gin.RouterGroup) {
router.POST("/register", rc.authController.SignUpUser)
router.POST("/login", rc.authController.SignInUser)
- router.GET("/refresh", rc.authController.RefreshAccessToken)
router.GET("/logout", middleware.DeserializeUser(), rc.authController.LogoutUser)
+ router.GET("/verifyemail/:verificationCode", rc.authController.VerifyEmail)
}
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 0000000..314c963
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,32 @@
+{{define "base"}}
+
+
+
+
+
+ {{template "styles" .}}
+ {{ .Subject}}
+
+
+
+
+ |
+
+
+
+ {{block "content" .}}{{end}}
+
+
+ |
+ |
+
+
+
+
+{{end}}
diff --git a/templates/styles.html b/templates/styles.html
new file mode 100644
index 0000000..3a51224
--- /dev/null
+++ b/templates/styles.html
@@ -0,0 +1,331 @@
+{{define "styles"}}
+
+{{end}}
diff --git a/templates/verificationCode.html b/templates/verificationCode.html
new file mode 100644
index 0000000..093b541
--- /dev/null
+++ b/templates/verificationCode.html
@@ -0,0 +1,50 @@
+{{template "base" .}} {{define "content"}}
+
+
+
+
+
+
+
+ Hi {{ .FirstName}},
+ Please verify your account to be able to login
+
+ Good luck! Codevo CEO.
+ |
+
+
+ |
+
+
+
+
+{{end}}
diff --git a/utils/email.go b/utils/email.go
new file mode 100644
index 0000000..bc0b38e
--- /dev/null
+++ b/utils/email.go
@@ -0,0 +1,84 @@
+package utils
+
+import (
+ "bytes"
+ "crypto/tls"
+ "html/template"
+ "log"
+ "os"
+ "path/filepath"
+
+ "github.com/k3a/html2text"
+ "github.com/wpcodevo/golang-gorm-postgres/initializers"
+ "github.com/wpcodevo/golang-gorm-postgres/models"
+ "gopkg.in/gomail.v2"
+)
+
+type EmailData struct {
+ URL string
+ FirstName string
+ Subject string
+}
+
+// 👇 Email template parser
+
+func ParseTemplateDir(dir string) (*template.Template, error) {
+ var paths []string
+ err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() {
+ paths = append(paths, path)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ return template.ParseFiles(paths...)
+}
+
+func SendEmail(user *models.User, data *EmailData) {
+ config, err := initializers.LoadConfig(".")
+
+ if err != nil {
+ log.Fatal("could not load config", err)
+ }
+
+ // Sender data.
+ from := config.EmailFrom
+ smtpPass := config.SMTPPass
+ smtpUser := config.SMTPUser
+ to := user.Email
+ smtpHost := config.SMTPHost
+ smtpPort := config.SMTPPort
+
+ var body bytes.Buffer
+
+ template, err := ParseTemplateDir("templates")
+ if err != nil {
+ log.Fatal("Could not parse template", err)
+ }
+
+ template.ExecuteTemplate(&body, "verificationCode.html", &data)
+
+ m := gomail.NewMessage()
+
+ m.SetHeader("From", from)
+ m.SetHeader("To", to)
+ m.SetHeader("Subject", data.Subject)
+ m.SetBody("text/html", body.String())
+ m.AddAlternative("text/plain", html2text.HTML2Text(body.String()))
+
+ d := gomail.NewDialer(smtpHost, smtpPort, smtpUser, smtpPass)
+ d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
+
+ // Send Email
+ if err := d.DialAndSend(m); err != nil {
+ log.Fatal("Could not send email: ", err)
+ }
+
+}
diff --git a/utils/encode.go b/utils/encode.go
new file mode 100644
index 0000000..22a2cd7
--- /dev/null
+++ b/utils/encode.go
@@ -0,0 +1,17 @@
+package utils
+
+import "encoding/base64"
+
+func Encode(s string) string {
+ data := base64.StdEncoding.EncodeToString([]byte(s))
+ return string(data)
+}
+
+func Decode(s string) (string, error) {
+ data, err := base64.StdEncoding.DecodeString(s)
+ if err != nil {
+ return "", err
+ }
+
+ return string(data), nil
+}
diff --git a/utils/token.go b/utils/token.go
index 20c9756..6b24bf0 100644
--- a/utils/token.go
+++ b/utils/token.go
@@ -1,67 +1,47 @@
package utils
import (
- "encoding/base64"
"fmt"
"time"
"github.com/golang-jwt/jwt"
)
-func CreateToken(ttl time.Duration, payload interface{}, privateKey string) (string, error) {
- decodedPrivateKey, err := base64.StdEncoding.DecodeString(privateKey)
- if err != nil {
- return "", fmt.Errorf("could not decode key: %w", err)
- }
- key, err := jwt.ParseRSAPrivateKeyFromPEM(decodedPrivateKey)
-
- if err != nil {
- return "", fmt.Errorf("create: parse key: %w", err)
- }
+func GenerateToken(ttl time.Duration, payload interface{}, secretJWTKey string) (string, error) {
+ token := jwt.New(jwt.SigningMethodHS256)
now := time.Now().UTC()
+ claims := token.Claims.(jwt.MapClaims)
- claims := make(jwt.MapClaims)
claims["sub"] = payload
claims["exp"] = now.Add(ttl).Unix()
claims["iat"] = now.Unix()
claims["nbf"] = now.Unix()
- token, err := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
+ tokenString, err := token.SignedString([]byte(secretJWTKey))
if err != nil {
- return "", fmt.Errorf("create: sign token: %w", err)
+ return "", fmt.Errorf("generating JWT Token failed: %w", err)
}
- return token, nil
+ return tokenString, nil
}
-func ValidateToken(token string, publicKey string) (interface{}, error) {
- decodedPublicKey, err := base64.StdEncoding.DecodeString(publicKey)
- if err != nil {
- return nil, fmt.Errorf("could not decode: %w", err)
- }
-
- key, err := jwt.ParseRSAPublicKeyFromPEM(decodedPublicKey)
-
- if err != nil {
- return "", fmt.Errorf("validate: parse key: %w", err)
- }
-
- parsedToken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
- if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
- return nil, fmt.Errorf("unexpected method: %s", t.Header["alg"])
+func ValidateToken(token string, signedJWTKey string) (interface{}, error) {
+ tok, err := jwt.Parse(token, func(jwtToken *jwt.Token) (interface{}, error) {
+ if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("unexpected method: %s", jwtToken.Header["alg"])
}
- return key, nil
- })
+ return []byte(signedJWTKey), nil
+ })
if err != nil {
- return nil, fmt.Errorf("validate: %w", err)
+ return nil, fmt.Errorf("invalidate token: %w", err)
}
- claims, ok := parsedToken.Claims.(jwt.MapClaims)
- if !ok || !parsedToken.Valid {
- return nil, fmt.Errorf("validate: invalid token")
+ claims, ok := tok.Claims.(jwt.MapClaims)
+ if !ok || !tok.Valid {
+ return nil, fmt.Errorf("invalid token claim")
}
return claims["sub"], nil