From f6c4f946233c7959904a8c68ae1ae710bcf998c9 Mon Sep 17 00:00:00 2001 From: Edem Ziddah Date: Mon, 8 Aug 2022 18:11:35 +0000 Subject: [PATCH 01/11] updated --- models/user.model.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/models/user.model.go b/models/user.model.go index 00665a5..40cbe45 100644 --- a/models/user.model.go +++ b/models/user.model.go @@ -8,15 +8,15 @@ 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 + 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"` + Verified bool `gorm:"not null"` + CreatedAt time.Time `gorm:"not null"` + UpdatedAt time.Time `gorm:"not null"` } func (User) UsersTable() string { From adb875a7cee0bd031fd65d69be2c41dcbc41a19f Mon Sep 17 00:00:00 2001 From: Edem Ziddah Date: Mon, 8 Aug 2022 18:14:22 +0000 Subject: [PATCH 02/11] updated --- models/user.model.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/models/user.model.go b/models/user.model.go index 40cbe45..b771f93 100644 --- a/models/user.model.go +++ b/models/user.model.go @@ -19,10 +19,6 @@ type User struct { UpdatedAt time.Time `gorm:"not null"` } -func (User) UsersTable() string { - return "users" -} - type SignUpInput struct { Name string `json:"name" binding:"required"` Email string `json:"email" binding:"required"` From e011847267714e4134efe1e2722009b4eca6b3ec Mon Sep 17 00:00:00 2001 From: Edem Ziddah Date: Sat, 13 Aug 2022 20:03:44 +0000 Subject: [PATCH 03/11] updated --- controllers/auth.controller.go | 106 +++++----- example.env | 16 +- go.mod | 4 + go.sum | 17 ++ initializers/loadEnv.go | 17 +- middleware/deserialize-user.go | 12 +- models/user.model.go | 21 +- routes/auth.routes.go | 2 +- templates/base.html | 32 +++ templates/styles.html | 331 ++++++++++++++++++++++++++++++++ templates/verificationCode.html | 50 +++++ utils/email.go | 84 ++++++++ utils/encode.go | 17 ++ utils/token.go | 52 ++--- 14 files changed, 635 insertions(+), 126 deletions(-) create mode 100644 templates/base.html create mode 100644 templates/styles.html create mode 100644 templates/verificationCode.html create mode 100644 utils/email.go create mode 100644 utils/encode.go diff --git a/controllers/auth.controller.go b/controllers/auth.controller.go index df7ee56..0093fd7 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" @@ -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, @@ -61,17 +61,34 @@ func (ac *AuthController) SignUpUser(ctx *gin.Context) { 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}) } func (ac *AuthController) SignInUser(ctx *gin.Context) { @@ -96,68 +113,43 @@ 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") - - if err != nil { - ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"status": "fail", "message": message}) - return - } +func (ac *AuthController) LogoutUser(ctx *gin.Context) { + ctx.SetCookie("token", "", -1, "/", "localhost", false, true) + ctx.JSON(http.StatusOK, gin.H{"status": "success"}) +} - config, _ := initializers.LoadConfig(".") +func (ac *AuthController) VerifyEmail(ctx *gin.Context) { - sub, err := utils.ValidateToken(cookie, config.RefreshTokenPublicKey) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"status": "fail", "message": err.Error()}) - return - } + code := ctx.Params.ByName("verificationCode") + verification_code := utils.Encode(code) - 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) - - 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) + updatedUser.VerificationCode = "" + updatedUser.Verified = true + ac.DB.Save(&updatedUser) - 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/models/user.model.go b/models/user.model.go index b771f93..46538db 100644 --- a/models/user.model.go +++ b/models/user.model.go @@ -7,16 +7,17 @@ import ( ) type User struct { - 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"` - Verified bool `gorm:"not null"` - CreatedAt time.Time `gorm:"not null"` - UpdatedAt time.Time `gorm:"not null"` + 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"` + VerificationCode string + Verified bool `gorm:"not null"` + CreatedAt time.Time `gorm:"not null"` + UpdatedAt time.Time `gorm:"not null"` } type SignUpInput struct { 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}} + + + + + + + + + + + +{{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"}} + + + + + + + + +{{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 From ed3e66a4d1656f72ff640e47b1b6e0f79ed4cb19 Mon Sep 17 00:00:00 2001 From: Edem Date: Fri, 19 Aug 2022 16:24:51 +0000 Subject: [PATCH 04/11] updated --- controllers/auth.controller.go | 10 +++++++++- readMe.md | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/controllers/auth.controller.go b/controllers/auth.controller.go index 0093fd7..d322711 100644 --- a/controllers/auth.controller.go +++ b/controllers/auth.controller.go @@ -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 @@ -91,6 +91,7 @@ func (ac *AuthController) SignUpUser(ctx *gin.Context) { ctx.JSON(http.StatusCreated, gin.H{"status": "success", "message": message}) } +// [...] SignIn User func (ac *AuthController) SignInUser(ctx *gin.Context) { var payload *models.SignInInput @@ -106,6 +107,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 @@ -125,11 +131,13 @@ func (ac *AuthController) SignInUser(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"status": "success", "token": 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"}) } +// [...] Verify Email func (ac *AuthController) VerifyEmail(ctx *gin.Context) { code := ctx.Params.ByName("verificationCode") diff --git a/readMe.md b/readMe.md index c1c43d0..d7f9828 100644 --- a/readMe.md +++ b/readMe.md @@ -3,3 +3,11 @@ ### 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) From b0196407b4cbc3fdf3d6bc33a07b6297541e55e3 Mon Sep 17 00:00:00 2001 From: Edem Ziddah Date: Sun, 21 Aug 2022 15:02:59 +0000 Subject: [PATCH 05/11] updated --- readMe.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readMe.md b/readMe.md index d7f9828..abf485c 100644 --- a/readMe.md +++ b/readMe.md @@ -11,3 +11,7 @@ ### 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) From 2ee1aab6e53a7c2310e99b094a9b7a97ed757d48 Mon Sep 17 00:00:00 2001 From: Edem Ziddah Date: Mon, 22 Aug 2022 18:03:44 +0000 Subject: [PATCH 06/11] updated --- readMe.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readMe.md b/readMe.md index abf485c..6bfb0bd 100644 --- a/readMe.md +++ b/readMe.md @@ -15,3 +15,7 @@ ### 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) From f69443acf3a1eeb9a4669aac86423a2ef43bd7c2 Mon Sep 17 00:00:00 2001 From: Edem Date: Sun, 28 Aug 2022 15:55:10 +0000 Subject: [PATCH 07/11] updated --- controllers/auth.controller.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/controllers/auth.controller.go b/controllers/auth.controller.go index d322711..6cba672 100644 --- a/controllers/auth.controller.go +++ b/controllers/auth.controller.go @@ -56,7 +56,10 @@ 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 } From 872e5b8c9e48781ab556cd083eacc9b7f054ec0a Mon Sep 17 00:00:00 2001 From: CODEVO Date: Sun, 2 Oct 2022 13:42:00 +0000 Subject: [PATCH 08/11] Update readMe.md --- readMe.md | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/readMe.md b/readMe.md index 6bfb0bd..a7ce095 100644 --- a/readMe.md +++ b/readMe.md @@ -1,4 +1,42 @@ -# Build Golang RESTful API with Gorm, Gin and Postgres +# Golang and GORM - User Registration and Email Verification + +![Golang and GORM - User Registration and Email Verification](https://codevoweb.com/wp-content/uploads/2022/08/Golang-and-GORM-User-Registration-and-Email-Verification.webp) + +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 From 349ea608772ecf1654b27a5b3436f5eecc4df786 Mon Sep 17 00:00:00 2001 From: CODEVO Date: Sun, 2 Oct 2022 15:27:23 +0000 Subject: [PATCH 09/11] Update readMe.md --- readMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readMe.md b/readMe.md index a7ce095..ad327ea 100644 --- a/readMe.md +++ b/readMe.md @@ -1,9 +1,9 @@ # Golang and GORM - User Registration and Email Verification -![Golang and GORM - User Registration and Email Verification](https://codevoweb.com/wp-content/uploads/2022/08/Golang-and-GORM-User-Registration-and-Email-Verification.webp) - 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. +![Golang and GORM - User Registration and Email Verification](https://codevoweb.com/wp-content/uploads/2022/08/Golang-and-GORM-User-Registration-and-Email-Verification.webp) + ## Topics Covered - Golang and GORM JWT Authentication Overview From 4e2d0061b7a1564b83dfcdf8c6bdd6e1b30a10df Mon Sep 17 00:00:00 2001 From: wpcodevo Date: Sat, 25 Mar 2023 21:06:21 +0000 Subject: [PATCH 10/11] updated --- .gitignore | 2 +- app.env | 19 +++++++++++++++++++ migrate/migrate.go | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 app.env 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/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/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") } From 900452fd719fe3ac8f777880824082dc18e90d55 Mon Sep 17 00:00:00 2001 From: wpcodevo Date: Wed, 17 May 2023 06:06:28 +0000 Subject: [PATCH 11/11] updated --- Golang_GORM.postman_collection.json | 365 ++++++++++++++++++++++++++++ models/user.model.go | 4 +- 2 files changed, 367 insertions(+), 2 deletions(-) create mode 100644 Golang_GORM.postman_collection.json 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/models/user.model.go b/models/user.model.go index 46538db..5bc4c62 100644 --- a/models/user.model.go +++ b/models/user.model.go @@ -13,7 +13,7 @@ type User struct { Password string `gorm:"not null"` Role string `gorm:"type:varchar(255);not null"` Provider string `gorm:"not null"` - Photo 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"` @@ -25,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 {