diff --git a/.gitignore b/.gitignore
index b8ca426..c423937 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
-sendgrid.env
-sendgrid.env
+# Environment
+*.env
+*.pem
+*.log
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index d35a7e0..0000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [
- {
- "type": "go",
- "request": "launch",
- "name": "Launch Chrome against localhost",
- // "url": "http://localhost:8080",
- // "webRoot": "${workspaceFolder}"
- "program": "${workspaceRoot}"
- }
- ]
-}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index fe2d0af..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,24 +0,0 @@
-FROM golang:alpine as builder
-
-RUN apk update && apk upgrade && \
- apk add --no-cache git
-
-RUN mkdir /build
-WORKDIR /build
-
-COPY go.mod .
-COPY go.sum .
-RUN go mod download
-
-COPY . .
-
-RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o user-auth *.go
-
-FROM alpine:latest
-
-RUN mkdir /app
-WORKDIR /app
-
-COPY --from=builder /build/user-auth .
-
-CMD ["./user-auth"]
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..24b1b2d
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 caseyrwebb
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Procfile b/Procfile
deleted file mode 100644
index 5a1e536..0000000
--- a/Procfile
+++ /dev/null
@@ -1 +0,0 @@
-web: bin/go-jwt-auth
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..695f7e2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+# Go Auth Microservice
+
+## Inspired by the work of:
+
+## d-vignesh's go-jwt-auth -> [d-vignesh/go-jwt-auth](https://github.com/d-vignesh/go-jwt-auth)
+
+## and
+
+## obe711's node-server-boilerplate -> [obe711/node-server-boilerplate](https://github.com/obe711/node-server-boilerplate)
diff --git a/app/app.go b/app/app.go
new file mode 100644
index 0000000..300fe8c
--- /dev/null
+++ b/app/app.go
@@ -0,0 +1,25 @@
+package app
+
+import (
+ "github.com/caseyrwebb/go-jwt-auth/app/data"
+ "github.com/caseyrwebb/go-jwt-auth/app/router"
+ "github.com/caseyrwebb/go-jwt-auth/app/utils"
+ "github.com/gorilla/mux"
+ "go.uber.org/zap"
+)
+
+type App struct {
+ Router *mux.Router
+ DB data.GoDB
+}
+
+func New(logger zap.Logger, configs *utils.Configurations) *App {
+ a := &App{
+ Router: mux.NewRouter(),
+ DB: &data.DB{},
+ }
+
+ router.InitRoutes(a.Router, a.DB, logger, configs)
+
+ return a
+}
diff --git a/app/data/db.go b/app/data/db.go
new file mode 100644
index 0000000..aac1ee5
--- /dev/null
+++ b/app/data/db.go
@@ -0,0 +1,73 @@
+package data
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/caseyrwebb/go-jwt-auth/app/models"
+ "github.com/caseyrwebb/go-jwt-auth/app/utils"
+ "github.com/jmoiron/sqlx"
+ _ "github.com/lib/pq"
+ "go.uber.org/zap"
+)
+
+type AuthDB interface {
+ Create(ctx context.Context, user *models.User) error
+ GetUserByEmail(ctx context.Context, email string) (*models.User, error)
+ GetUserByID(ctx context.Context, userID string) (*models.User, error)
+ UpdateUsername(ctx context.Context, user *models.User) error
+ UpdateUserVerificationStatus(ctx context.Context, email string, status bool) error
+ StoreVerificationData(ctx context.Context, verificationData *models.VerificationData) error
+ GetVerificationData(ctx context.Context, email string, verificationDataType models.VerificationDataType) (*models.VerificationData, error)
+ DeleteVerificationData(ctx context.Context, email string, verificationDataType models.VerificationDataType) error
+ UpdatePassword(ctx context.Context, userID string, password string, tokenHash string) error
+}
+
+type GoDB interface {
+ AuthDB
+ Open(config *utils.Configurations) error
+ Close() error
+ SetDBLogger(logger *zap.Logger)
+}
+
+type DB struct {
+ db *sqlx.DB
+ logger *zap.Logger
+}
+
+func (d *DB) SetDBLogger(logger *zap.Logger) {
+ d.logger = logger
+}
+
+func (d *DB) Open(config *utils.Configurations) error {
+ var conn string
+
+ if config.DBConn != "" {
+ conn = config.DBConn
+ } else {
+ host := config.DBHost
+ port := config.DBPort
+ user := config.DBUser
+ dbName := config.DBName
+ password := config.DBPass
+ conn = fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=disable", host, port, user, dbName, password)
+ }
+ d.logger.Debug(fmt.Sprintf("%s %s", "connection string", conn))
+
+ pg, err := sqlx.Open("postgres", conn)
+ if err != nil {
+ return err
+ }
+ d.logger.Debug("Connected to Database!")
+
+ pg.MustExec(createUserTableSchema)
+ pg.MustExec(createVerificationSchema)
+
+ d.db = pg
+
+ return nil
+}
+
+func (d *DB) Close() error {
+ return d.db.Close()
+}
diff --git a/app/data/methods.go b/app/data/methods.go
new file mode 100644
index 0000000..1740501
--- /dev/null
+++ b/app/data/methods.go
@@ -0,0 +1,102 @@
+package data
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/caseyrwebb/go-jwt-auth/app/models"
+ uuid "github.com/google/uuid"
+)
+
+// Create inserts the given user into the database
+func (d *DB) Create(ctx context.Context, user *models.User) error {
+ user.ID = uuid.New().String()
+ user.CreatedAt = time.Now()
+ user.UpdatedAt = time.Now()
+
+ d.logger.Info(fmt.Sprintf("%s %v", "creating user", user))
+ query := "insert into users (id, email, username, password, token, created_at, updated_at) values ($1, $2, $3, $4, $5, $6, $7)"
+ _, err := d.db.ExecContext(ctx, query, user.ID, user.Email, user.Username, user.Password, user.Token, user.CreatedAt, user.UpdatedAt)
+ return err
+}
+
+// GetUserByEmail retrieves the user object having the given email, else returns error
+func (d *DB) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
+ d.logger.Debug(fmt.Sprintf("%s %v", "querying for user with email", email))
+ query := "select * from users where email = $1"
+ var user models.User
+ if err := d.db.GetContext(ctx, &user, query, email); err != nil {
+ return nil, err
+ }
+ d.logger.Debug(fmt.Sprintf("%s %v", "read users", user))
+ return &user, nil
+}
+
+// GetUserByID retrieves the user object having the given ID, else returns error
+func (d *DB) GetUserByID(ctx context.Context, userID string) (*models.User, error) {
+ d.logger.Debug(fmt.Sprintf("%s %v", "querying for user with id", userID))
+ query := "select * from users where id = $1"
+ var user models.User
+ if err := d.db.GetContext(ctx, &user, query, userID); err != nil {
+ return nil, err
+ }
+ return &user, nil
+}
+
+// UpdateUsername updates the username of the given user
+func (d *DB) UpdateUsername(ctx context.Context, user *models.User) error {
+ user.UpdatedAt = time.Now()
+
+ query := "update users set username = $1, updatedat = $2 where id = $3"
+ if _, err := d.db.ExecContext(ctx, query, user.Username, user.UpdatedAt, user.ID); err != nil {
+ return err
+ }
+ return nil
+}
+
+// UpdateUserVerificationStatus updates user verification status to true
+func (d *DB) UpdateUserVerificationStatus(ctx context.Context, email string, status bool) error {
+
+ query := "update users set isverified = $1 where email = $2"
+ if _, err := d.db.ExecContext(ctx, query, status, email); err != nil {
+ return err
+ }
+ return nil
+}
+
+// StoreMailVerificationData adds a mail verification data to db
+func (d *DB) StoreVerificationData(ctx context.Context, verificationData *models.VerificationData) error {
+
+ query := "insert into verifications(email, code, expiresat, type) values($1, $2, $3, $4)"
+ _, err := d.db.ExecContext(ctx, query, verificationData.Email, verificationData.Code, verificationData.ExpiresAt, verificationData.Type)
+ return err
+}
+
+// GetMailVerificationCode retrieves the stored verification code.
+func (d *DB) GetVerificationData(ctx context.Context, email string, verificationDataType models.VerificationDataType) (*models.VerificationData, error) {
+
+ query := "select * from verifications where email = $1 and type = $2"
+
+ var verificationData models.VerificationData
+ if err := d.db.GetContext(ctx, &verificationData, query, email, verificationDataType); err != nil {
+ return nil, err
+ }
+ return &verificationData, nil
+}
+
+// DeleteMailVerificationData deletes a used verification data
+func (d *DB) DeleteVerificationData(ctx context.Context, email string, verificationDataType models.VerificationDataType) error {
+
+ query := "delete from verifications where email = $1 and type = $2"
+ _, err := d.db.ExecContext(ctx, query, email, verificationDataType)
+ return err
+}
+
+// UpdatePassword updates the user password
+func (d *DB) UpdatePassword(ctx context.Context, userID string, password string, tokenHash string) error {
+
+ query := "update users set password = $1, token = $2 where id = $3"
+ _, err := d.db.ExecContext(ctx, query, password, tokenHash, userID)
+ return err
+}
diff --git a/app/data/schemas.go b/app/data/schemas.go
new file mode 100644
index 0000000..0b1d687
--- /dev/null
+++ b/app/data/schemas.go
@@ -0,0 +1,28 @@
+package data
+
+// User table schema
+var createUserTableSchema = `
+create table if not exists users (
+ id varchar(36) not null,
+ email varchar(225) not null unique,
+ username varchar(225),
+ password varchar(225) not null,
+ token varchar(15) not null,
+ is_verified boolean default false,
+ created_at timestamp not null,
+ updated_at timestamp not null,
+ primary key (id)
+);
+`
+
+var createVerificationSchema = `
+create table if not exists verifications (
+ email Varchar(100) not null,
+ code Varchar(10) not null,
+ expiresat Timestamp not null,
+ type Varchar(10) not null,
+ Primary Key (email),
+ Constraint fk_user_email Foreign Key(email) References users(email)
+ On Delete Cascade On Update Cascade
+)
+`
diff --git a/data/serialization.go b/app/data/serializers.go
similarity index 99%
rename from data/serialization.go
rename to app/data/serializers.go
index a5d7f39..f8b37ba 100644
--- a/data/serialization.go
+++ b/app/data/serializers.go
@@ -16,4 +16,4 @@ func ToJSON(i interface{}, w io.Writer) error {
func FromJSON(i interface{}, r io.Reader) error {
d := json.NewDecoder(r)
return d.Decode(i)
-}
\ No newline at end of file
+}
diff --git a/data/validation.go b/app/data/validation.go
similarity index 96%
rename from data/validation.go
rename to app/data/validation.go
index 01b4ea2..b1a5731 100644
--- a/data/validation.go
+++ b/app/data/validation.go
@@ -59,7 +59,7 @@ func (v *Validation) Validate(i interface{}) ValidationErrors {
var returnErrs ValidationErrors
for _, err := range errs.(validator.ValidationErrors) {
// cast the FieldError into our ValidationError and append to the slice
- ve := ValidationError{err.(validator.FieldError)}
+ ve := ValidationError{err}
returnErrs = append(returnErrs, ve)
}
return returnErrs
diff --git a/handlers/auth.go b/app/handlers/auth.go
similarity index 62%
rename from handlers/auth.go
rename to app/handlers/auth.go
index fcd8d6d..daf3e5c 100644
--- a/handlers/auth.go
+++ b/app/handlers/auth.go
@@ -1,12 +1,10 @@
package handlers
import (
- "fmt"
-
- "github.com/d-vignesh/go-jwt-auth/data"
- "github.com/d-vignesh/go-jwt-auth/service"
- "github.com/d-vignesh/go-jwt-auth/utils"
- "github.com/hashicorp/go-hclog"
+ "github.com/caseyrwebb/go-jwt-auth/app/data"
+ "github.com/caseyrwebb/go-jwt-auth/app/services"
+ "github.com/caseyrwebb/go-jwt-auth/app/utils"
+ "go.uber.org/zap"
)
// UserKey is used as a key for storing the User object in context at middleware
@@ -20,23 +18,23 @@ type VerificationDataKey struct{}
// UserHandler wraps instances needed to perform operations on user object
type AuthHandler struct {
- logger hclog.Logger
+ logger zap.Logger
configs *utils.Configurations
validator *data.Validation
- repo data.Repository
- authService service.Authentication
- mailService service.MailService
+ authService services.Authentication
+ mailService services.MailService
+ db data.GoDB
}
// NewUserHandler returns a new UserHandler instance
-func NewAuthHandler(l hclog.Logger, c *utils.Configurations, v *data.Validation, r data.Repository, auth service.Authentication, mail service.MailService) *AuthHandler {
+func NewAuthHandler(d data.GoDB, l zap.Logger, c *utils.Configurations, v *data.Validation, auth services.Authentication, mail services.MailService) *AuthHandler {
return &AuthHandler{
- logger: l,
- configs: c,
- validator: v,
- repo: r,
+ logger: l,
+ configs: c,
+ validator: v,
authService: auth,
mailService: mail,
+ db: d,
}
}
@@ -69,19 +67,19 @@ type UsernameUpdate struct {
}
type CodeVerificationReq struct {
- Code string `json: "code"`
- Type string `json" "type"`
+ Code string `json:"code"`
+ Type string `json:"type"`
}
type PasswordResetReq struct {
- Password string `json: "password"`
- PasswordRe string `json: "password_re"`
- Code string `json: "code"`
+ Password string `json:"password"`
+ PasswordRe string `json:"password_re"`
+ Code string `json:"code"`
}
-var ErrUserAlreadyExists = fmt.Sprintf("User already exists with the given email")
-var ErrUserNotFound = fmt.Sprintf("No user account exists with given email. Please sign in first")
-var UserCreationFailed = fmt.Sprintf("Unable to create user.Please try again later")
+var ErrUserAlreadyExists = "User already exists with the given email"
+var ErrUserNotFound = "No user account exists with given email. Please sign in first"
+var UserCreationFailed = "Unable to create user.Please try again later"
var PgDuplicateKeyMsg = "duplicate key value violates unique constraint"
var PgNoRowsMsg = "no rows in result set"
diff --git a/handlers/get.go b/app/handlers/get.go
similarity index 70%
rename from handlers/get.go
rename to app/handlers/get.go
index a670dd6..48dd663 100644
--- a/handlers/get.go
+++ b/app/handlers/get.go
@@ -1,13 +1,15 @@
package handlers
import (
- "net/http"
"context"
+ "fmt"
+ "net/http"
"time"
- "github.com/d-vignesh/go-jwt-auth/data"
- "github.com/d-vignesh/go-jwt-auth/service"
- "github.com/d-vignesh/go-jwt-auth/utils"
+ "github.com/caseyrwebb/go-jwt-auth/app/data"
+ "github.com/caseyrwebb/go-jwt-auth/app/models"
+ "github.com/caseyrwebb/go-jwt-auth/app/services"
+ "github.com/caseyrwebb/go-jwt-auth/app/utils"
)
// RefreshToken handles refresh token request
@@ -15,10 +17,10 @@ func (ah *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
- user := r.Context().Value(UserKey{}).(data.User)
+ user := r.Context().Value(UserKey{}).(models.User)
accessToken, err := ah.authService.GenerateAccessToken(&user)
if err != nil {
- ah.logger.Error("unable to generate access token", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to generate access token", "error", err))
w.WriteHeader(http.StatusInternalServerError)
// data.ToJSON(&GenericError{Error: err.Error()}, w)
data.ToJSON(&GenericResponse{Status: false, Message: "Unable to generate access token.Please try again later"}, w)
@@ -55,44 +57,44 @@ func (ah *AuthHandler) GeneratePassResetCode(w http.ResponseWriter, r *http.Requ
userID := r.Context().Value(UserIDKey{}).(string)
- user, err := ah.repo.GetUserByID(context.Background(), userID)
+ user, err := ah.db.GetUserByID(context.Background(), userID)
if err != nil {
- ah.logger.Error("unable to get user to generate secret code for password reset", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to get user to generate secret code for password reset", "error", err))
w.WriteHeader(http.StatusInternalServerError)
data.ToJSON(&GenericResponse{Status: false, Message: "Unable to send password reset code. Please try again later"}, w)
return
}
// Send verification mail
- from := "vikisquarez@gmail.com"
+ from := "dummy@email.com"
to := []string{user.Email}
- subject := "Password Reset for Bookite"
- mailType := service.PassReset
- mailData := &service.MailData{
+ subject := "Password Reset for go-jwt-auth"
+ mailType := services.PassReset
+ mailData := &services.MailData{
Username: user.Username,
- Code: utils.GenerateRandomString(8),
+ Code: utils.GenerateRandomString(8),
}
mailReq := ah.mailService.NewMail(from, to, subject, mailType, mailData)
err = ah.mailService.SendMail(mailReq)
if err != nil {
- ah.logger.Error("unable to send mail", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to send mail", "error", err))
w.WriteHeader(http.StatusInternalServerError)
data.ToJSON(&GenericResponse{Status: false, Message: "Unable to send password reset code. Please try again later"}, w)
return
}
// store the password reset code to db
- verificationData := &data.VerificationData{
- Email: user.Email,
- Code: mailData.Code,
- Type: data.PassReset,
+ verificationData := &models.VerificationData{
+ Email: user.Email,
+ Code: mailData.Code,
+ Type: models.PassReset,
ExpiresAt: time.Now().Add(time.Minute * time.Duration(ah.configs.PassResetCodeExpiration)),
}
- err = ah.repo.StoreVerificationData(context.Background(), verificationData)
+ err = ah.db.StoreVerificationData(context.Background(), verificationData)
if err != nil {
- ah.logger.Error("unable to store password reset verification data", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to store password reset verification data", "error", err))
w.WriteHeader(http.StatusInternalServerError)
data.ToJSON(&GenericResponse{Status: false, Message: "Unable to send password reset code. Please try again later"}, w)
return
diff --git a/handlers/middleware.go b/app/handlers/middleware.go
similarity index 80%
rename from handlers/middleware.go
rename to app/handlers/middleware.go
index 837a6c8..1b50c62 100644
--- a/handlers/middleware.go
+++ b/app/handlers/middleware.go
@@ -3,10 +3,12 @@ package handlers
import (
"context"
"errors"
+ "fmt"
"net/http"
"strings"
- "github.com/d-vignesh/go-jwt-auth/data"
+ "github.com/caseyrwebb/go-jwt-auth/app/data"
+ "github.com/caseyrwebb/go-jwt-auth/app/models"
)
// MiddlewareValidateUser validates the user in the request
@@ -15,12 +17,12 @@ func (ah *AuthHandler) MiddlewareValidateUser(next http.Handler) http.Handler {
w.Header().Set("Content-Type", "application/json")
- ah.logger.Debug("user json", r.Body)
- user := &data.User{}
+ ah.logger.Debug(fmt.Sprintf("%s %s", "user json", r.Body))
+ user := &models.User{}
err := data.FromJSON(user, r.Body)
if err != nil {
- ah.logger.Error("deserialization of user json failed", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "deserialization of user json failed", "error", err))
w.WriteHeader(http.StatusBadRequest)
// data.ToJSON(&GenericError{Error: err.Error()}, w)
data.ToJSON(&GenericResponse{Status: false, Message: err.Error()}, w)
@@ -30,7 +32,7 @@ func (ah *AuthHandler) MiddlewareValidateUser(next http.Handler) http.Handler {
// validate the user
errs := ah.validator.Validate(user)
if len(errs) != 0 {
- ah.logger.Error("validation of user json failed", "error", errs)
+ ah.logger.Error(fmt.Sprint("validation of user json failed", "error", errs))
w.WriteHeader(http.StatusBadRequest)
// data.ToJSON(&ValidationError{Errors: errs.Errors()}, w)
data.ToJSON(&GenericResponse{Status: false, Message: strings.Join(errs.Errors(), ",")}, w)
@@ -63,11 +65,11 @@ func (ah *AuthHandler) MiddlewareValidateAccessToken(next http.Handler) http.Han
data.ToJSON(&GenericResponse{Status: false, Message: "Authentication failed. Token not provided or malformed"}, w)
return
}
- ah.logger.Debug("token present in header", token)
+ ah.logger.Debug(fmt.Sprintf("%s %s", "token present in header", token))
userID, err := ah.authService.ValidateAccessToken(token)
if err != nil {
- ah.logger.Error("token validation failed", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "token validation failed", "error", err))
w.WriteHeader(http.StatusBadRequest)
// data.ToJSON(&GenericError{Error: err.Error()}, w)
data.ToJSON(&GenericResponse{Status: false, Message: "Authentication failed. Invalid token"}, w)
@@ -90,7 +92,7 @@ func (ah *AuthHandler) MiddlewareValidateRefreshToken(next http.Handler) http.Ha
w.Header().Set("Content-Type", "application/json")
ah.logger.Debug("validating refresh token")
- ah.logger.Debug("auth header", r.Header.Get("Authorization"))
+ ah.logger.Debug(fmt.Sprintf("%s %s", "auth header", r.Header.Get("Authorization")))
token, err := extractToken(r)
if err != nil {
ah.logger.Error("token not provided or malformed")
@@ -98,27 +100,27 @@ func (ah *AuthHandler) MiddlewareValidateRefreshToken(next http.Handler) http.Ha
data.ToJSON(&GenericResponse{Status: false, Message: "Authentication failed. Token not provided or malformed"}, w)
return
}
- ah.logger.Debug("token present in header", token)
+ ah.logger.Debug(fmt.Sprint("token present in header", token))
userID, customKey, err := ah.authService.ValidateRefreshToken(token)
if err != nil {
- ah.logger.Error("token validation failed", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "token validation failed", "error", err))
w.WriteHeader(http.StatusBadRequest)
data.ToJSON(&GenericResponse{Status: false, Message: "Authentication failed. Invalid token"}, w)
return
}
ah.logger.Debug("refresh token validated")
- user, err := ah.repo.GetUserByID(context.Background(), userID)
+ user, err := ah.db.GetUserByID(context.Background(), userID)
if err != nil {
- ah.logger.Error("invalid token: wrong userID while parsing", err)
+ ah.logger.Error(fmt.Sprintf("%s %d", "invalid token: wrong userID while parsing", err))
w.WriteHeader(http.StatusBadRequest)
// data.ToJSON(&GenericError{Error: "invalid token: authentication failed"}, w)
data.ToJSON(&GenericResponse{Status: false, Message: "Unable to fetch corresponding user"}, w)
return
}
- actualCustomKey := ah.authService.GenerateCustomKey(user.ID, user.TokenHash)
+ actualCustomKey := ah.authService.GenerateCustomKey(user.ID, user.Token)
if customKey != actualCustomKey {
ah.logger.Debug("wrong token: authetincation failed")
w.WriteHeader(http.StatusBadRequest)
@@ -133,19 +135,19 @@ func (ah *AuthHandler) MiddlewareValidateRefreshToken(next http.Handler) http.Ha
})
}
-// MiddlerwareValidateVerificationData validates whether the request contains the email
+// MiddlerwareValidateVerificationData validates whether the request contains the email
// and confirmation code that are required for the verification
func (ah *AuthHandler) MiddlewareValidateVerificationData(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
+
w.Header().Set("Content-Type", "application/json")
ah.logger.Debug("validating verification data")
- verificationData := &data.VerificationData{}
+ verificationData := &models.VerificationData{}
err := data.FromJSON(verificationData, r.Body)
if err != nil {
- ah.logger.Error("deserialization of verification data failed", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "deserialization of verification data failed", "error", err))
w.WriteHeader(http.StatusBadRequest)
data.ToJSON(&GenericResponse{Status: false, Message: err.Error()}, w)
return
@@ -153,7 +155,7 @@ func (ah *AuthHandler) MiddlewareValidateVerificationData(next http.Handler) htt
errs := ah.validator.Validate(verificationData)
if len(errs) != 0 {
- ah.logger.Error("validation of verification data json failed", "error", errs)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "validation of verification data json failed", "error", errs))
w.WriteHeader(http.StatusBadRequest)
data.ToJSON(&GenericResponse{Status: false, Message: strings.Join(errs.Errors(), ",")}, w)
return
@@ -171,7 +173,7 @@ func extractToken(r *http.Request) (string, error) {
authHeader := r.Header.Get("Authorization")
authHeaderContent := strings.Split(authHeader, " ")
if len(authHeaderContent) != 2 {
- return "", errors.New("Token not provided or malformed")
+ return "", errors.New("token not provided or malformed")
}
return authHeaderContent[1], nil
}
diff --git a/handlers/post.go b/app/handlers/post.go
similarity index 64%
rename from handlers/post.go
rename to app/handlers/post.go
index e6db6ad..a426fc4 100644
--- a/handlers/post.go
+++ b/app/handlers/post.go
@@ -2,14 +2,15 @@ package handlers
import (
"context"
+ "errors"
+ "fmt"
"net/http"
"strings"
"time"
- "errors"
- "github.com/d-vignesh/go-jwt-auth/data"
- "github.com/d-vignesh/go-jwt-auth/utils"
- "github.com/d-vignesh/go-jwt-auth/service"
+ "github.com/caseyrwebb/go-jwt-auth/app/data"
+ "github.com/caseyrwebb/go-jwt-auth/app/models"
+ "github.com/caseyrwebb/go-jwt-auth/app/utils"
"golang.org/x/crypto/bcrypt"
)
@@ -18,7 +19,7 @@ func (ah *AuthHandler) Signup(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
- user := r.Context().Value(UserKey{}).(data.User)
+ user := r.Context().Value(UserKey{}).(models.User)
// hashedPass, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
// if err != nil {
@@ -36,11 +37,11 @@ func (ah *AuthHandler) Signup(w http.ResponseWriter, r *http.Request) {
return
}
user.Password = hashedPass
- user.TokenHash = utils.GenerateRandomString(15)
+ user.Token = utils.GenerateRandomString(15)
- err = ah.repo.Create(context.Background(), &user)
+ err = ah.db.Create(context.Background(), &user)
if err != nil {
- ah.logger.Error("unable to insert user to database", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to insert user to database", "error", err))
errMsg := err.Error()
if strings.Contains(errMsg, PgDuplicateKeyMsg) {
w.WriteHeader(http.StatusBadRequest)
@@ -55,34 +56,35 @@ func (ah *AuthHandler) Signup(w http.ResponseWriter, r *http.Request) {
}
// Send verification mail
- from := "vikisquarez@gmail.com"
- to := []string{user.Email}
- subject := "Email Verification for Bookite"
- mailType := service.MailConfirmation
- mailData := &service.MailData{
- Username: user.Username,
- Code: utils.GenerateRandomString(8),
- }
+ // from := "dummy@email.com"
+ // to := []string{user.Email}
+ // subject := "Email Verification for Bookite"
+ // mailType := services.MailConfirmation
+ // mailData := &services.MailData{
+ // Username: user.Username,
+ // Code: utils.GenerateRandomString(8),
+ // }
- mailReq := ah.mailService.NewMail(from, to, subject, mailType, mailData)
- err = ah.mailService.SendMail(mailReq)
- if err != nil {
- ah.logger.Error("unable to send mail", "error", err)
- w.WriteHeader(http.StatusInternalServerError)
- data.ToJSON(&GenericResponse{Status: false, Message: UserCreationFailed}, w)
- return
- }
+ // mailReq := ah.mailService.NewMail(from, to, subject, mailType, mailData)
+ // err = ah.mailService.SendMail(mailReq)
+ // if err != nil {
+ // ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to send mail", "error", err))
+ // w.WriteHeader(http.StatusInternalServerError)
+ // data.ToJSON(&GenericResponse{Status: false, Message: UserCreationFailed}, w)
+ // return
+ // }
- verificationData := &data.VerificationData{
+ verificationData := &models.VerificationData{
Email: user.Email,
- Code : mailData.Code,
- Type : data.MailConfirmation,
+ // Code: mailData.Code,
+ Code: "12345678",
+ Type: models.MailConfirmation,
ExpiresAt: time.Now().Add(time.Hour * time.Duration(ah.configs.MailVerifCodeExpiration)),
}
- err = ah.repo.StoreVerificationData(context.Background(), verificationData)
+ err = ah.db.StoreVerificationData(context.Background(), verificationData)
if err != nil {
- ah.logger.Error("unable to store mail verification data", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to store mail verification data", "error", err))
w.WriteHeader(http.StatusInternalServerError)
data.ToJSON(&GenericResponse{Status: false, Message: UserCreationFailed}, w)
return
@@ -98,7 +100,7 @@ func (ah *AuthHandler) hashPassword(password string) (string, error) {
hashedPass, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
- ah.logger.Error("unable to hash password", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to hash password", "error", err))
return "", err
}
@@ -110,11 +112,11 @@ func (ah *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
- reqUser := r.Context().Value(UserKey{}).(data.User)
+ reqUser := r.Context().Value(UserKey{}).(models.User)
- user, err := ah.repo.GetUserByEmail(context.Background(), reqUser.Email)
+ user, err := ah.db.GetUserByEmail(context.Background(), reqUser.Email)
if err != nil {
- ah.logger.Error("error fetching the user", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "error fetching the user", "error", err))
errMsg := err.Error()
if strings.Contains(errMsg, PgNoRowsMsg) {
w.WriteHeader(http.StatusBadRequest)
@@ -128,12 +130,12 @@ func (ah *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
return
}
- if !user.IsVerified {
- ah.logger.Error("unverified user")
- w.WriteHeader(http.StatusUnauthorized)
- data.ToJSON(&GenericResponse{Status: false, Message: "Please verify your mail address before login"}, w)
- return
- }
+ // if !user.IsVerified {
+ // ah.logger.Error("unverified user")
+ // w.WriteHeader(http.StatusUnauthorized)
+ // data.ToJSON(&GenericResponse{Status: false, Message: "Please verify your mail address before login"}, w)
+ // return
+ // }
if valid := ah.authService.Authenticate(&reqUser, user); !valid {
ah.logger.Debug("Authetication of user failed")
@@ -145,7 +147,7 @@ func (ah *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
accessToken, err := ah.authService.GenerateAccessToken(user)
if err != nil {
- ah.logger.Error("unable to generate access token", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to generate access token", "error", err))
w.WriteHeader(http.StatusInternalServerError)
// data.ToJSON(&GenericError{Error: err.Error()}, w)
data.ToJSON(&GenericResponse{Status: false, Message: "Unable to login the user. Please try again later"}, w)
@@ -153,14 +155,14 @@ func (ah *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
}
refreshToken, err := ah.authService.GenerateRefreshToken(user)
if err != nil {
- ah.logger.Error("unable to generate refresh token", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to generate refresh token", "error", err))
w.WriteHeader(http.StatusInternalServerError)
// data.ToJSON(&GenericError{Error: err.Error()}, w)
data.ToJSON(&GenericResponse{Status: false, Message: "Unable to login the user. Please try again later"}, w)
return
}
- ah.logger.Debug("successfully generated token", "accesstoken", accessToken, "refreshtoken", refreshToken)
+ ah.logger.Debug(fmt.Sprintf("%s %s %s %s %s", "successfully generated token", "accesstoken", accessToken, "refreshtoken", refreshToken))
w.WriteHeader(http.StatusOK)
// data.ToJSON(&AuthResponse{AccessToken: accessToken, RefreshToken: refreshToken, Username: user.Username}, w)
data.ToJSON(&GenericResponse{
@@ -176,12 +178,12 @@ func (ah *AuthHandler) VerifyMail(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
ah.logger.Debug("verifying the confimation code")
- verificationData := r.Context().Value(VerificationDataKey{}).(data.VerificationData)
- verificationData.Type = data.MailConfirmation
+ verificationData := r.Context().Value(VerificationDataKey{}).(models.VerificationData)
+ verificationData.Type = models.MailConfirmation
- actualVerificationData, err := ah.repo.GetVerificationData(context.Background(), verificationData.Email, verificationData.Type)
+ actualVerificationData, err := ah.db.GetVerificationData(context.Background(), verificationData.Email, verificationData.Type)
if err != nil {
- ah.logger.Error("unable to fetch verification data", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to fetch verification data", "error", err))
if strings.Contains(err.Error(), PgNoRowsMsg) {
w.WriteHeader(http.StatusNotAcceptable)
@@ -202,7 +204,7 @@ func (ah *AuthHandler) VerifyMail(w http.ResponseWriter, r *http.Request) {
}
// correct code, update user status to verified.
- err = ah.repo.UpdateUserVerificationStatus(context.Background(), verificationData.Email, true)
+ err = ah.db.UpdateUserVerificationStatus(context.Background(), verificationData.Email, true)
if err != nil {
ah.logger.Error("unable to set user verification status to true")
w.WriteHeader(http.StatusInternalServerError)
@@ -211,9 +213,9 @@ func (ah *AuthHandler) VerifyMail(w http.ResponseWriter, r *http.Request) {
}
// delete the VerificationData from db
- err = ah.repo.DeleteVerificationData(context.Background(), verificationData.Email, verificationData.Type)
+ err = ah.db.DeleteVerificationData(context.Background(), verificationData.Email, verificationData.Type)
if err != nil {
- ah.logger.Error("unable to delete the verification data", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to delete the verification data", "error", err))
}
ah.logger.Debug("user mail verification succeeded")
@@ -228,12 +230,12 @@ func (ah *AuthHandler) VerifyPasswordReset(w http.ResponseWriter, r *http.Reques
w.Header().Set("Content-Type", "application/json")
ah.logger.Debug("verifing password reset code")
- verificationData := r.Context().Value(VerificationDataKey{}).(data.VerificationData)
- verificationData.Type = data.PassReset
+ verificationData := r.Context().Value(VerificationDataKey{}).(models.VerificationData)
+ verificationData.Type = models.PassReset
- actualVerificationData, err := ah.repo.GetVerificationData(context.Background(), verificationData.Email, verificationData.Type)
+ actualVerificationData, err := ah.db.GetVerificationData(context.Background(), verificationData.Email, verificationData.Type)
if err != nil {
- ah.logger.Error("unable to fetch verification data", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to fetch verification data", "error", err))
if strings.Contains(err.Error(), PgNoRowsMsg) {
w.WriteHeader(http.StatusNotAcceptable)
data.ToJSON(&GenericResponse{Status: false, Message: ErrUserNotFound}, w)
@@ -251,7 +253,7 @@ func (ah *AuthHandler) VerifyPasswordReset(w http.ResponseWriter, r *http.Reques
return
}
- respData := struct{
+ respData := struct {
Code string
}{
Code: verificationData.Code,
@@ -259,23 +261,23 @@ func (ah *AuthHandler) VerifyPasswordReset(w http.ResponseWriter, r *http.Reques
ah.logger.Debug("password reset code verification succeeded")
w.WriteHeader(http.StatusAccepted)
- data.ToJSON(&GenericResponse{Status: true, Message: "Password Reset code verification succeeded", Data: respData,}, w)
+ data.ToJSON(&GenericResponse{Status: true, Message: "Password Reset code verification succeeded", Data: respData}, w)
}
-func (ah *AuthHandler) verify(actualVerificationData *data.VerificationData, verificationData *data.VerificationData) (bool, error) {
+func (ah *AuthHandler) verify(actualVerificationData *models.VerificationData, verificationData *models.VerificationData) (bool, error) {
// check for expiration
if actualVerificationData.ExpiresAt.Before(time.Now()) {
ah.logger.Error("verification data provided is expired")
- err := ah.repo.DeleteVerificationData(context.Background(), actualVerificationData.Email, actualVerificationData.Type)
- ah.logger.Error("unable to delete verification data from db", "error", err)
- return false, errors.New("Confirmation code has expired. Please try generating a new code")
+ err := ah.db.DeleteVerificationData(context.Background(), actualVerificationData.Email, actualVerificationData.Type)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to delete verification data from db", "error", err))
+ return false, errors.New("confirmation code has expired. please try generating a new code")
}
if actualVerificationData.Code != verificationData.Code {
ah.logger.Error("verification of mail failed. Invalid verification code provided")
- return false, errors.New("Verification code provided is Invalid. Please look in your mail for the code")
+ return false, errors.New("verification code provided is Invalid. please look in your mail for the code")
}
return true, nil
-}
\ No newline at end of file
+}
diff --git a/handlers/put.go b/app/handlers/put.go
similarity index 68%
rename from handlers/put.go
rename to app/handlers/put.go
index 9df3be2..b3daebb 100644
--- a/handlers/put.go
+++ b/app/handlers/put.go
@@ -2,10 +2,12 @@ package handlers
import (
"context"
+ "fmt"
"net/http"
- "github.com/d-vignesh/go-jwt-auth/data"
- "github.com/d-vignesh/go-jwt-auth/utils"
+ "github.com/caseyrwebb/go-jwt-auth/app/data"
+ "github.com/caseyrwebb/go-jwt-auth/app/models"
+ "github.com/caseyrwebb/go-jwt-auth/app/utils"
)
// UpdateUsername handles username update request
@@ -13,22 +15,22 @@ func (ah *AuthHandler) UpdateUsername(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
- user := &data.User{}
+ user := &models.User{}
err := data.FromJSON(user, r.Body)
if err != nil {
- ah.logger.Error("unable to decode user json", "error", err.Error())
+ ah.logger.Error(fmt.Sprintf("%s %s %v", "unable to decode user json", "error", err.Error()))
w.WriteHeader(http.StatusBadRequest)
- // data.ToJSON(&GenericError{Error: err.Error()}, w)
+ // data.ToJSON(&GenericError{Error: err.Error()}, w)d
data.ToJSON(&GenericResponse{Status: false, Message: err.Error()}, w)
return
}
user.ID = r.Context().Value(UserIDKey{}).(string)
- ah.logger.Debug("udpating username for user : ", user)
+ ah.logger.Debug(fmt.Sprintf("%s %v", "udpating username for user : ", user))
- err = ah.repo.UpdateUsername(context.Background(), user)
+ err = ah.db.UpdateUsername(context.Background(), user)
if err != nil {
- ah.logger.Error("unable to update username", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to update username", "error", err))
w.WriteHeader(http.StatusInternalServerError)
// data.ToJSON(&GenericError{Error: err.Error()}, w)
data.ToJSON(&GenericResponse{Status: false, Message: "Unable to update username. Please try again later"}, w)
@@ -52,24 +54,24 @@ func (ah *AuthHandler) ResetPassword(w http.ResponseWriter, r *http.Request) {
passResetReq := &PasswordResetReq{}
err := data.FromJSON(passResetReq, r.Body)
if err != nil {
- ah.logger.Error("unable to decode password reset request json", "error", err.Error())
+ ah.logger.Error(fmt.Sprintf("%s %s %v", "unable to decode password reset request json", "error", err.Error()))
w.WriteHeader(http.StatusBadRequest)
data.ToJSON(&GenericResponse{Status: false, Message: err.Error()}, w)
return
}
userID := r.Context().Value(UserIDKey{}).(string)
- user, err := ah.repo.GetUserByID(context.Background(), userID)
+ user, err := ah.db.GetUserByID(context.Background(), userID)
if err != nil {
- ah.logger.Error("unable to retrieve the user from db", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to retrieve the user from db", "error", err))
w.WriteHeader(http.StatusInternalServerError)
data.ToJSON(&GenericResponse{Status: false, Message: "Unable to reset password. Please try again later"}, w)
return
}
- verificationData, err := ah.repo.GetVerificationData(context.Background(), user.Email, data.PassReset)
+ verificationData, err := ah.db.GetVerificationData(context.Background(), user.Email, models.PassReset)
if err != nil {
- ah.logger.Error("unable to retrieve the verification data from db", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to retrieve the verification data from db", "error", err))
w.WriteHeader(http.StatusInternalServerError)
data.ToJSON(&GenericResponse{Status: false, Message: "Unable to reset password. Please try again later"}, w)
return
@@ -98,19 +100,18 @@ func (ah *AuthHandler) ResetPassword(w http.ResponseWriter, r *http.Request) {
}
tokenHash := utils.GenerateRandomString(15)
- err = ah.repo.UpdatePassword(context.Background(), userID, hashedPass, tokenHash)
+ err = ah.db.UpdatePassword(context.Background(), userID, hashedPass, tokenHash)
if err != nil {
- ah.logger.Error("unable to update user password in db", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to update user password in db", "error", err))
w.WriteHeader(http.StatusInternalServerError)
data.ToJSON(&GenericResponse{Status: false, Message: "Password and re-entered Password are not same"}, w)
return
}
-
// delete the VerificationData from db
- err = ah.repo.DeleteVerificationData(context.Background(), verificationData.Email, verificationData.Type)
+ err = ah.db.DeleteVerificationData(context.Background(), verificationData.Email, verificationData.Type)
if err != nil {
- ah.logger.Error("unable to delete the verification data", "error", err)
+ ah.logger.Error(fmt.Sprintf("%s %s %d", "unable to delete the verification data", "error", err))
}
w.WriteHeader(http.StatusOK)
diff --git a/app/models/users.go b/app/models/users.go
new file mode 100644
index 0000000..229d90d
--- /dev/null
+++ b/app/models/users.go
@@ -0,0 +1,14 @@
+package models
+
+import "time"
+
+type User struct {
+ ID string `json:"id" db:"id"`
+ Email string `json:"email" validate:"required" db:"email"`
+ Password string `json:"password" validate:"required" db:"password"`
+ Username string `json:"username" db:"username"`
+ Token string `json:"token" db:"token"`
+ IsVerified bool `json:"isVerified" db:"is_verified"`
+ CreatedAt time.Time `json:"createdAt" db:"created_at"`
+ UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
+}
diff --git a/app/models/verifications.go b/app/models/verifications.go
new file mode 100644
index 0000000..7c2451b
--- /dev/null
+++ b/app/models/verifications.go
@@ -0,0 +1,18 @@
+package models
+
+import "time"
+
+type VerificationDataType int
+
+const (
+ MailConfirmation VerificationDataType = iota + 1
+ PassReset
+)
+
+// VerificationData represents the type for the data stored for verification.
+type VerificationData struct {
+ Email string `json:"email" validate:"required" db:"email"`
+ Code string `json:"code" validate:"required" db:"code"`
+ ExpiresAt time.Time `json:"expiresat" db:"expiresat"`
+ Type VerificationDataType `json:"type" db:"type"`
+}
diff --git a/app/router/router.go b/app/router/router.go
new file mode 100644
index 0000000..b37d957
--- /dev/null
+++ b/app/router/router.go
@@ -0,0 +1,55 @@
+package router
+
+import (
+ "net/http"
+
+ "github.com/caseyrwebb/go-jwt-auth/app/data"
+ "github.com/caseyrwebb/go-jwt-auth/app/handlers"
+ "github.com/caseyrwebb/go-jwt-auth/app/services"
+ "github.com/caseyrwebb/go-jwt-auth/app/utils"
+ "github.com/gorilla/mux"
+ "go.uber.org/zap"
+)
+
+// create a function to return the routes of app router
+func InitRoutes(r *mux.Router, db data.GoDB, logger zap.Logger, configs *utils.Configurations) {
+ // validator contains all the methods that are need to validate the user json in request
+ validator := data.NewValidation()
+ // authService contains all methods that help in authorizing a user request
+ authService := services.NewAuthService(logger, configs)
+
+ // mailService contains the utility methods to send an email
+ mailService := services.NewSGMailService(logger, configs)
+
+ // UserHandler encapsulates all the services related to user
+ uh := handlers.NewAuthHandler(db, logger, configs, validator, authService, mailService)
+
+ // register handlers
+ postR := r.Methods(http.MethodPost).Subrouter()
+
+ mailR := r.PathPrefix("/verify").Methods(http.MethodPost).Subrouter()
+ mailR.HandleFunc("/mail", uh.VerifyMail)
+ mailR.HandleFunc("/password-reset", uh.VerifyPasswordReset)
+ mailR.Use(uh.MiddlewareValidateVerificationData)
+
+ postR.HandleFunc("/signup", uh.Signup)
+ postR.HandleFunc("/login", uh.Login)
+ postR.Use(uh.MiddlewareValidateUser)
+
+ // used the PathPrefix as workaround for scenarios where all the
+ // get requests must use the ValidateAccessToken middleware except
+ // the /refresh-token request which has to use ValidateRefreshToken middleware
+ refToken := r.PathPrefix("/refresh-token").Subrouter()
+ refToken.HandleFunc("", uh.RefreshToken)
+ refToken.Use(uh.MiddlewareValidateRefreshToken)
+
+ getR := r.Methods(http.MethodGet).Subrouter()
+ getR.HandleFunc("/greet", uh.Greet)
+ getR.HandleFunc("/get-password-reset-code", uh.GeneratePassResetCode)
+ getR.Use(uh.MiddlewareValidateAccessToken)
+
+ putR := r.Methods(http.MethodPut).Subrouter()
+ putR.HandleFunc("/update-username", uh.UpdateUsername)
+ putR.HandleFunc("/reset-password", uh.ResetPassword)
+ putR.Use(uh.MiddlewareValidateAccessToken)
+}
diff --git a/service/auth.go b/app/services/auth.go
similarity index 73%
rename from service/auth.go
rename to app/services/auth.go
index 81926f7..4df5b23 100644
--- a/service/auth.go
+++ b/app/services/auth.go
@@ -1,25 +1,26 @@
-package service
+package services
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
+ "fmt"
"io/ioutil"
"time"
- "github.com/d-vignesh/go-jwt-auth/data"
- "github.com/d-vignesh/go-jwt-auth/utils"
- "github.com/dgrijalva/jwt-go"
- "github.com/hashicorp/go-hclog"
+ "github.com/caseyrwebb/go-jwt-auth/app/models"
+ "github.com/caseyrwebb/go-jwt-auth/app/utils"
+ "github.com/golang-jwt/jwt"
+ "go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
// Authentication interface lists the methods that our authentication service should implement
type Authentication interface {
- Authenticate(reqUser *data.User, user *data.User) bool
- GenerateAccessToken(user *data.User) (string, error)
- GenerateRefreshToken(user *data.User) (string, error)
+ Authenticate(reqUser *models.User, user *models.User) bool
+ GenerateAccessToken(user *models.User) (string, error)
+ GenerateRefreshToken(user *models.User) (string, error)
GenerateCustomKey(userID string, password string) string
ValidateAccessToken(token string) (string, error)
ValidateRefreshToken(token string) (string, string, error)
@@ -42,17 +43,17 @@ type AccessTokenCustomClaims struct {
// AuthService is the implementation of our Authentication
type AuthService struct {
- logger hclog.Logger
+ logger zap.Logger
configs *utils.Configurations
}
// NewAuthService returns a new instance of the auth service
-func NewAuthService(logger hclog.Logger, configs *utils.Configurations) *AuthService {
+func NewAuthService(logger zap.Logger, configs *utils.Configurations) *AuthService {
return &AuthService{logger, configs}
}
// Authenticate checks the user credentials in request against the db and authenticates the request
-func (auth *AuthService) Authenticate(reqUser *data.User, user *data.User) bool {
+func (auth *AuthService) Authenticate(reqUser *models.User, user *models.User) bool {
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(reqUser.Password)); err != nil {
auth.logger.Debug("password hashes are not same")
@@ -62,9 +63,9 @@ func (auth *AuthService) Authenticate(reqUser *data.User, user *data.User) bool
}
// GenerateRefreshToken generate a new refresh token for the given user
-func (auth *AuthService) GenerateRefreshToken(user *data.User) (string, error) {
+func (auth *AuthService) GenerateRefreshToken(user *models.User) (string, error) {
- cusKey := auth.GenerateCustomKey(user.ID, user.TokenHash)
+ cusKey := auth.GenerateCustomKey(user.ID, user.Token)
tokenType := "refresh"
claims := RefreshTokenCustomClaims{
@@ -72,19 +73,19 @@ func (auth *AuthService) GenerateRefreshToken(user *data.User) (string, error) {
cusKey,
tokenType,
jwt.StandardClaims{
- Issuer: "bookite.auth.service",
+ Issuer: "auth.microservice",
},
}
signBytes, err := ioutil.ReadFile(auth.configs.RefreshTokenPrivateKeyPath)
if err != nil {
- auth.logger.Error("unable to read private key", "error", err)
+ auth.logger.Error(fmt.Sprintf("%s %s %d", "unable to read private key", "error", err))
return "", errors.New("could not generate refresh token. please try again later")
}
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(signBytes)
if err != nil {
- auth.logger.Error("unable to parse private key", "error", err)
+ auth.logger.Error(fmt.Sprintf("%s %s %d", "unable to parse private key", "error", err))
return "", errors.New("could not generate refresh token. please try again later")
}
@@ -94,7 +95,7 @@ func (auth *AuthService) GenerateRefreshToken(user *data.User) (string, error) {
}
// GenerateAccessToken generates a new access token for the given user
-func (auth *AuthService) GenerateAccessToken(user *data.User) (string, error) {
+func (auth *AuthService) GenerateAccessToken(user *models.User) (string, error) {
userID := user.ID
tokenType := "access"
@@ -104,19 +105,19 @@ func (auth *AuthService) GenerateAccessToken(user *data.User) (string, error) {
tokenType,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * time.Duration(auth.configs.JwtExpiration)).Unix(),
- Issuer: "bookite.auth.service",
+ Issuer: "auth.microservice",
},
}
signBytes, err := ioutil.ReadFile(auth.configs.AccessTokenPrivateKeyPath)
if err != nil {
- auth.logger.Error("unable to read private key", "error", err)
+ auth.logger.Error(fmt.Sprintf("%s %s %d", "unable to read private key", "error", err))
return "", errors.New("could not generate access token. please try again later")
}
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(signBytes)
if err != nil {
- auth.logger.Error("unable to parse private key", "error", err)
+ auth.logger.Error(fmt.Sprintf("%s %s %d", "unable to parse private key", "error", err))
return "", errors.New("could not generate access token. please try again later")
}
@@ -143,17 +144,17 @@ func (auth *AuthService) ValidateAccessToken(tokenString string) (string, error)
token, err := jwt.ParseWithClaims(tokenString, &AccessTokenCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
auth.logger.Error("Unexpected signing method in auth token")
- return nil, errors.New("Unexpected signing method in auth token")
+ return nil, errors.New("unexpected signing method in auth token")
}
verifyBytes, err := ioutil.ReadFile(auth.configs.AccessTokenPublicKeyPath)
if err != nil {
- auth.logger.Error("unable to read public key", "error", err)
+ auth.logger.Error(fmt.Sprintf("%s %s %d", "unable to read public key", "error", err))
return nil, err
}
verifyKey, err := jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
if err != nil {
- auth.logger.Error("unable to parse public key", "error", err)
+ auth.logger.Error(fmt.Sprintf("%s %s %d", "unable to parse public key", "error", err))
return nil, err
}
@@ -161,7 +162,7 @@ func (auth *AuthService) ValidateAccessToken(tokenString string) (string, error)
})
if err != nil {
- auth.logger.Error("unable to parse claims", "error", err)
+ auth.logger.Error(fmt.Sprintf("%s %s %d", "unable to parse claims", "error", err))
return "", err
}
@@ -179,17 +180,17 @@ func (auth *AuthService) ValidateRefreshToken(tokenString string) (string, strin
token, err := jwt.ParseWithClaims(tokenString, &RefreshTokenCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
auth.logger.Error("Unexpected signing method in auth token")
- return nil, errors.New("Unexpected signing method in auth token")
+ return nil, errors.New("unexpected signing method in auth token")
}
verifyBytes, err := ioutil.ReadFile(auth.configs.RefreshTokenPublicKeyPath)
if err != nil {
- auth.logger.Error("unable to read public key", "error", err)
+ auth.logger.Error(fmt.Sprintf("%s %s %d", "unable to read public key", "error", err))
return nil, err
}
verifyKey, err := jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
if err != nil {
- auth.logger.Error("unable to parse public key", "error", err)
+ auth.logger.Error(fmt.Sprintf("%s %s %d", "unable to parse public key", "error", err))
return nil, err
}
@@ -197,12 +198,12 @@ func (auth *AuthService) ValidateRefreshToken(tokenString string) (string, strin
})
if err != nil {
- auth.logger.Error("unable to parse claims", "error", err)
+ auth.logger.Error(fmt.Sprintf("%s %s %d", "unable to parse claims", "error", err))
return "", "", err
}
claims, ok := token.Claims.(*RefreshTokenCustomClaims)
- auth.logger.Debug("ok", ok)
+ auth.logger.Debug(fmt.Sprintf("%s %v", "ok", ok))
if !ok || !token.Valid || claims.UserID == "" || claims.KeyType != "refresh" {
auth.logger.Debug("could not extract claims from token")
return "", "", errors.New("invalid token: authentication failed")
diff --git a/service/mail.go b/app/services/mail.go
similarity index 78%
rename from service/mail.go
rename to app/services/mail.go
index 6c9c13b..789b8df 100644
--- a/service/mail.go
+++ b/app/services/mail.go
@@ -1,8 +1,10 @@
-package service
+package services
import (
- "github.com/d-vignesh/go-jwt-auth/utils"
- "github.com/hashicorp/go-hclog"
+ "fmt"
+
+ "github.com/caseyrwebb/go-jwt-auth/app/utils"
+ "go.uber.org/zap"
"github.com/sendgrid/sendgrid-go"
"github.com/sendgrid/sendgrid-go/helpers/mail"
@@ -26,37 +28,36 @@ const (
// MailData represents the data to be sent to the template of the mail.
type MailData struct {
Username string
- Code string
+ Code string
}
// Mail represents a email request
type Mail struct {
- from string
- to []string
+ from string
+ to []string
subject string
- body string
- mtype MailType
- data *MailData
+ body string
+ mtype MailType
+ data *MailData
}
// SGMailService is the sendgrid implementation of our MailService.
type SGMailService struct {
- logger hclog.Logger
+ logger zap.Logger
configs *utils.Configurations
}
// NewSGMailService returns a new instance of SGMailService
-func NewSGMailService(logger hclog.Logger, configs *utils.Configurations) *SGMailService {
+func NewSGMailService(logger zap.Logger, configs *utils.Configurations) *SGMailService {
return &SGMailService{logger, configs}
}
-
// CreateMail takes in a mail request and constructs a sendgrid mail type.
func (ms *SGMailService) CreateMail(mailReq *Mail) []byte {
m := mail.NewV3Mail()
- from := mail.NewEmail("bookite", mailReq.from)
+ from := mail.NewEmail("go-jwt-auth", mailReq.from)
m.SetFrom(from)
if mailReq.mtype == MailConfirmation {
@@ -90,21 +91,20 @@ func (ms *SGMailService) SendMail(mailReq *Mail) error {
request.Body = Body
response, err := sendgrid.API(request)
if err != nil {
- ms.logger.Error("unable to send mail", "error", err)
+ ms.logger.Error(fmt.Sprintf("%s %s %d", "unable to send mail", "error", err))
return err
- }
- ms.logger.Info("mail sent successfully", "sent status code", response.StatusCode)
+ }
+ ms.logger.Info(fmt.Sprintf("%s %s %d", "mail sent successfully", "sent status code", response.StatusCode))
return nil
}
// NewMail returns a new mail request.
func (ms *SGMailService) NewMail(from string, to []string, subject string, mailType MailType, data *MailData) *Mail {
return &Mail{
- from: from,
- to: to,
+ from: from,
+ to: to,
subject: subject,
- mtype: mailType,
- data: data,
+ mtype: mailType,
+ data: data,
}
}
-
diff --git a/app/utils/config.go b/app/utils/config.go
new file mode 100644
index 0000000..3fadf44
--- /dev/null
+++ b/app/utils/config.go
@@ -0,0 +1,100 @@
+package utils
+
+import (
+ "fmt"
+
+ "github.com/lib/pq"
+ "github.com/spf13/viper"
+ "go.uber.org/zap"
+)
+
+// Configurations wraps all the config variables required by the auth service
+type Configurations struct {
+ ServerAddress string
+ DBHost string
+ DBName string
+ DBUser string
+ DBPass string
+ DBPort string
+ DBConn string
+ AccessTokenPrivateKeyPath string
+ AccessTokenPublicKeyPath string
+ RefreshTokenPrivateKeyPath string
+ RefreshTokenPublicKeyPath string
+ JwtExpiration int // in minutes
+ SendGridApiKey string
+ MailVerifCodeExpiration int // in hours
+ PassResetCodeExpiration int // in minutes
+ MailVerifTemplateID string
+ PassResetTemplateID string
+}
+
+// NewConfigurations returns a new Configuration object
+func NewConfigurations(logger *zap.Logger) *Configurations {
+ viper.AddConfigPath(".")
+ viper.SetConfigName("app")
+ viper.SetConfigType("env")
+
+ viper.AutomaticEnv()
+
+ err := viper.ReadInConfig()
+ if err != nil {
+ logger.Error(fmt.Sprintf("%s %v", "cannot load config:", err))
+ }
+
+ dbURL := viper.GetString("DATABASE_URL")
+ conn, _ := pq.ParseURL(dbURL)
+ logger.Debug("Parsing db URL connection string")
+ logger.Debug(fmt.Sprintf("%s %s", "DB connection string: ", conn))
+
+ viper.SetDefault("SERVER_ADDRESS", "0.0.0.0:9000")
+ viper.SetDefault("DB_HOST", "localhost")
+ viper.SetDefault("DB_NAME", "test")
+ viper.SetDefault("DB_USER", "postgres")
+ viper.SetDefault("DB_PASSWORD", "postgres")
+ viper.SetDefault("DB_PORT", "5432")
+ viper.SetDefault("ACCESS_TOKEN_PRIVATE_KEY_PATH", "./access-private.pem")
+ viper.SetDefault("ACCESS_TOKEN_PUBLIC_KEY_PATH", "./access-public.pem")
+ viper.SetDefault("REFRESH_TOKEN_PRIVATE_KEY_PATH", "./refresh-private.pem")
+ viper.SetDefault("REFRESH_TOKEN_PUBLIC_KEY_PATH", "./refresh-public.pem")
+ viper.SetDefault("JWT_EXPIRATION", 30)
+ viper.SetDefault("MAIL_VERIFICATION_CODE_EXPIRATION", 24)
+ viper.SetDefault("PASSWORD_RESET_CODE_EXPIRATION", 15)
+ viper.SetDefault("MAIL_VERIFICATION_TEMPLATE_ID", "")
+ viper.SetDefault("PASSWORD_RESET_TEMPLATE_ID", "")
+
+ configs := &Configurations{
+ ServerAddress: viper.GetString("SERVER_ADDRESS"),
+ DBHost: viper.GetString("DB_HOST"),
+ DBName: viper.GetString("DB_NAME"),
+ DBUser: viper.GetString("DB_USER"),
+ DBPass: viper.GetString("DB_PASSWORD"),
+ DBPort: viper.GetString("DB_PORT"),
+ DBConn: conn,
+ JwtExpiration: viper.GetInt("JWT_EXPIRATION"),
+ AccessTokenPrivateKeyPath: viper.GetString("ACCESS_TOKEN_PRIVATE_KEY_PATH"),
+ AccessTokenPublicKeyPath: viper.GetString("ACCESS_TOKEN_PUBLIC_KEY_PATH"),
+ RefreshTokenPrivateKeyPath: viper.GetString("REFRESH_TOKEN_PRIVATE_KEY_PATH"),
+ RefreshTokenPublicKeyPath: viper.GetString("REFRESH_TOKEN_PUBLIC_KEY_PATH"),
+ SendGridApiKey: viper.GetString("SENDGRID_API_KEY"),
+ MailVerifCodeExpiration: viper.GetInt("MAIL_VERIFICATION_CODE_EXPIRATION"),
+ PassResetCodeExpiration: viper.GetInt("PASSWORD_RESET_CODE_EXPIRATION"),
+ MailVerifTemplateID: viper.GetString("MAIL_VERIFICATION_TEMPLATE_ID"),
+ PassResetTemplateID: viper.GetString("PASSWORD_RESET_TEMPLATE_ID"),
+ }
+
+ // Used for cloud deployment
+ port := viper.GetString("PORT")
+ if port != "" {
+ logger.Debug(fmt.Sprintf("%s %s", "using the port allocated by cloud provider", port))
+ configs.ServerAddress = "0.0.0.0:" + port
+ }
+
+ logger.Debug(fmt.Sprintf("%s %s", "serve port", configs.ServerAddress))
+ logger.Debug(fmt.Sprintf("%s %s", "db host", configs.DBHost))
+ logger.Debug(fmt.Sprintf("%s %s", "db name", configs.DBName))
+ logger.Debug(fmt.Sprintf("%s %s", "db port", configs.DBPort))
+ logger.Debug(fmt.Sprintf("%s %d", "jwt expiration", configs.JwtExpiration))
+
+ return configs
+}
diff --git a/app/utils/logger.go b/app/utils/logger.go
new file mode 100644
index 0000000..4f11c11
--- /dev/null
+++ b/app/utils/logger.go
@@ -0,0 +1,26 @@
+package utils
+
+import (
+ "os"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+func CustomLogger(filename string) *zap.Logger {
+ config := zap.NewProductionEncoderConfig()
+ config.EncodeTime = zapcore.ISO8601TimeEncoder
+ fileEncoder := zapcore.NewJSONEncoder(config)
+ consoleEncoder := zapcore.NewConsoleEncoder(config)
+ logFile, _ := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ writer := zapcore.AddSync(logFile)
+ defaultLogLevel := zapcore.DebugLevel
+ core := zapcore.NewTee(
+ zapcore.NewCore(fileEncoder, writer, defaultLogLevel),
+ zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), defaultLogLevel),
+ )
+
+ logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
+
+ return logger
+}
diff --git a/utils/util.go b/app/utils/util.go
similarity index 99%
rename from utils/util.go
rename to app/utils/util.go
index 6dcf6d0..36b62d0 100644
--- a/utils/util.go
+++ b/app/utils/util.go
@@ -17,4 +17,3 @@ func GenerateRandomString(n int) string {
}
return sb.String()
}
-
diff --git a/bin/go-jwt-auth b/bin/go-jwt-auth
deleted file mode 100755
index cc6c078..0000000
Binary files a/bin/go-jwt-auth and /dev/null differ
diff --git a/data/database.go b/data/database.go
deleted file mode 100644
index 09c0e4c..0000000
--- a/data/database.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package data
-
-import (
- "fmt"
-
- "github.com/jmoiron/sqlx"
- _ "github.com/lib/pq"
- "github.com/d-vignesh/go-jwt-auth/utils"
- "github.com/hashicorp/go-hclog"
-)
-
-// NewConnection creates the connection to the database
-func NewConnection(config *utils.Configurations, logger hclog.Logger) (*sqlx.DB, error) {
-
- var conn string
-
- if config.DBConn != "" {
- conn = config.DBConn
- } else {
- host := config.DBHost
- port := config.DBPort
- user := config.DBUser
- dbName := config.DBName
- password := config.DBPass
- conn = fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=disable", host, port, user, dbName, password)
- }
- logger.Debug("connection string", conn)
-
- db, err := sqlx.Connect("postgres", conn)
- if err != nil {
- return nil, err
- }
- return db, nil
-}
\ No newline at end of file
diff --git a/data/models.go b/data/models.go
deleted file mode 100644
index 1ef0271..0000000
--- a/data/models.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package data
-
-import (
- "time"
-)
-
-// User is the data type for user object
-type User struct {
- ID string `json:"id" sql:"id"`
- Email string `json:"email" validate:"required" sql:"email"`
- Password string `json:"password" validate:"required" sql:"password"`
- Username string `json:"username" sql:"username"`
- TokenHash string `json:"tokenhash" sql:"tokenhash"`
- IsVerified bool `json:"isverified" sql:"isverified"`
- CreatedAt time.Time `json:"createdat" sql:"createdat"`
- UpdatedAt time.Time `json:"updatedat" sql:"updatedat"`
-}
-
-type VerificationDataType int
-
-const (
- MailConfirmation VerificationDataType = iota + 1
- PassReset
-)
-
-// VerificationData represents the type for the data stored for verification.
-type VerificationData struct {
- Email string `json:"email" validate:"required" sql:"email"`
- Code string `json:"code" validate:"required" sql:"code"`
- ExpiresAt time.Time `json:"expiresat" sql:"expiresat"`
- Type VerificationDataType `json:"type" sql:"type"`
-}
-
-
diff --git a/data/postgresrepo.go b/data/postgresrepo.go
deleted file mode 100644
index a00d1a3..0000000
--- a/data/postgresrepo.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package data
-
-import (
- "context"
- "time"
-
- "github.com/hashicorp/go-hclog"
- "github.com/jmoiron/sqlx"
- uuid "github.com/satori/go.uuid"
-)
-
-// PostgresRepository has the implementation of the db methods.
-type PostgresRepository struct {
- db *sqlx.DB
- logger hclog.Logger
-}
-
-// NewPostgresRepository returns a new PostgresRepository instance
-func NewPostgresRepository(db *sqlx.DB, logger hclog.Logger) *PostgresRepository {
- return &PostgresRepository{db, logger}
-}
-
-// Create inserts the given user into the database
-func (repo *PostgresRepository) Create(ctx context.Context, user *User) error {
- user.ID = uuid.NewV4().String()
- user.CreatedAt = time.Now()
- user.UpdatedAt = time.Now()
-
- repo.logger.Info("creating user", hclog.Fmt("%#v", user))
- query := "insert into users (id, email, username, password, tokenhash, createdat, updatedat) values ($1, $2, $3, $4, $5, $6, $7)"
- _, err := repo.db.ExecContext(ctx, query, user.ID, user.Email, user.Username, user.Password, user.TokenHash, user.CreatedAt, user.UpdatedAt)
- return err
-}
-
-// GetUserByEmail retrieves the user object having the given email, else returns error
-func (repo *PostgresRepository) GetUserByEmail(ctx context.Context, email string) (*User, error) {
- repo.logger.Debug("querying for user with email", email)
- query := "select * from users where email = $1"
- var user User
- if err := repo.db.GetContext(ctx, &user, query, email); err != nil {
- return nil, err
- }
- repo.logger.Debug("read users", hclog.Fmt("%#v", user))
- return &user, nil
-}
-
-// GetUserByID retrieves the user object having the given ID, else returns error
-func (repo *PostgresRepository) GetUserByID(ctx context.Context, userID string) (*User, error) {
- repo.logger.Debug("querying for user with id", userID)
- query := "select * from users where id = $1"
- var user User
- if err := repo.db.GetContext(ctx, &user, query, userID); err != nil {
- return nil, err
- }
- return &user, nil
-}
-
-// UpdateUsername updates the username of the given user
-func (repo *PostgresRepository) UpdateUsername(ctx context.Context, user *User) error {
- user.UpdatedAt = time.Now()
-
- query := "update users set username = $1, updatedat = $2 where id = $3"
- if _, err := repo.db.ExecContext(ctx, query, user.Username, user.UpdatedAt, user.ID); err != nil {
- return err
- }
- return nil
-}
-
-// UpdateUserVerificationStatus updates user verification status to true
-func (repo *PostgresRepository) UpdateUserVerificationStatus(ctx context.Context, email string, status bool) error {
-
- query := "update users set isverified = $1 where email = $2"
- if _, err := repo.db.ExecContext(ctx, query, status, email); err != nil {
- return err
- }
- return nil
-}
-
-// StoreMailVerificationData adds a mail verification data to db
-func (repo *PostgresRepository) StoreVerificationData(ctx context.Context, verificationData *VerificationData) error {
-
- query := "insert into verifications(email, code, expiresat, type) values($1, $2, $3, $4)"
- _, err := repo.db.ExecContext(ctx, query, verificationData.Email, verificationData.Code, verificationData.ExpiresAt, verificationData.Type)
- return err
-}
-
-// GetMailVerificationCode retrieves the stored verification code.
-func (repo *PostgresRepository) GetVerificationData(ctx context.Context, email string, verificationDataType VerificationDataType) (*VerificationData, error) {
-
- query := "select * from verifications where email = $1 and type = $2"
-
- var verificationData VerificationData
- if err := repo.db.GetContext(ctx, &verificationData, query, email, verificationDataType); err != nil {
- return nil, err
- }
- return &verificationData, nil
-}
-
-// DeleteMailVerificationData deletes a used verification data
-func (repo *PostgresRepository) DeleteVerificationData(ctx context.Context, email string, verificationDataType VerificationDataType) error {
-
- query := "delete from verifications where email = $1 and type = $2"
- _, err := repo.db.ExecContext(ctx, query, email, verificationDataType)
- return err
-}
-
-
-// UpdatePassword updates the user password
-func (repo *PostgresRepository) UpdatePassword(ctx context.Context, userID string, password string, tokenHash string) error {
-
- query := "update users set password = $1, tokenhash = $2 where id = $3"
- _, err := repo.db.ExecContext(ctx, query, password, tokenHash, userID)
- return err
-}
\ No newline at end of file
diff --git a/data/repository.go b/data/repository.go
deleted file mode 100644
index 3af21ea..0000000
--- a/data/repository.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package data
-
-import (
- "context"
-)
-
-// Repository is an interface for the storage implementation of the auth service
-type Repository interface {
- Create(ctx context.Context, user *User) error
- GetUserByEmail(ctx context.Context, email string) (*User, error)
- GetUserByID(ctx context.Context, userID string) (*User, error)
- UpdateUsername(ctx context.Context, user *User) error
- StoreVerificationData(ctx context.Context, verificationData *VerificationData) error
- GetVerificationData(ctx context.Context, email string, verificationDataType VerificationDataType) (*VerificationData, error)
- UpdateUserVerificationStatus(ctx context.Context, email string, status bool) error
- DeleteVerificationData(ctx context.Context, email string, verificationDataType VerificationDataType) error
- UpdatePassword(ctx context.Context, userID string, password string, tokenHash string) error
-}
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 991cc2b..0000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-version: '3.1'
-
-services:
-
- auth:
- restart: always
- build: ./auth
- depends_on:
- - database
- links:
- - database
- ports:
- - 9090:9090
- environment:
- USER_AUTH_DB_HOST: database
- USER_AUTH_DB_USER: "admin"
- USER_AUTH_DB_PASSWORD: "password"
- USER_AUTH_DB_NAME: "postgres"
- USER_AUTH_SERVER_PORT: "0.0.0.0:9090"
- USER_AUTH_DB_PORT: "5432"
- USER_AUTH_ACCESS_JWT_SECRETE_KEY: "superSecreteAccessToken"
- USER_AUTH_REFRESH_JWT_SECRETE_KEY: "superSecreteRefreshToken"
- USER_AUTH_JWT_EXPIRATION: 30
- USER_AUTH_CUSTOM_KEY_SECRETE: "superSecreteCustomKey"
-
- database:
- image: postgres:alpine
- container_name: database
- environment:
- POSTGRES_PASSWORD: "password"
- POSTGRES_USER: "admin"
- ports:
- - 5432:5432
diff --git a/go.mod b/go.mod
index e043d95..8abf1e2 100644
--- a/go.mod
+++ b/go.mod
@@ -1,21 +1,41 @@
-module github.com/d-vignesh/go-jwt-auth
+module github.com/caseyrwebb/go-jwt-auth
-// +heroku goVersion go1.15
-go 1.15
+go 1.19
require (
- github.com/dgrijalva/jwt-go v3.2.0+incompatible
- github.com/go-playground/universal-translator v0.17.0 // indirect
- github.com/go-playground/validator v9.31.0+incompatible
github.com/gorilla/mux v1.8.0
- github.com/hashicorp/go-hclog v0.15.0
- github.com/jmoiron/sqlx v1.2.0
- github.com/leodido/go-urn v1.2.0 // indirect
- github.com/lib/pq v1.8.0
- github.com/satori/go.uuid v1.2.0
- github.com/sendgrid/rest v2.6.2+incompatible // indirect
- github.com/sendgrid/sendgrid-go v3.7.2+incompatible
- github.com/spf13/viper v1.7.1
- golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
- gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
+ github.com/lib/pq v1.10.7
+ github.com/spf13/viper v1.14.0
+ go.uber.org/zap v1.21.0
+)
+
+require (
+ github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/go-playground/locales v0.14.0 // indirect
+ github.com/go-playground/universal-translator v0.18.0 // indirect
+ github.com/go-playground/validator v9.31.0+incompatible // indirect
+ github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
+ github.com/jmoiron/sqlx v1.3.5 // indirect
+ github.com/leodido/go-urn v1.2.1 // indirect
+ github.com/magiconair/properties v1.8.6 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/pelletier/go-toml v1.9.5 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.5 // indirect
+ github.com/sendgrid/rest v2.6.9+incompatible // indirect
+ github.com/sendgrid/sendgrid-go v3.12.0+incompatible // indirect
+ github.com/spf13/afero v1.9.2 // indirect
+ github.com/spf13/cast v1.5.0 // indirect
+ github.com/spf13/jwalterweatherman v1.1.0 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/subosito/gotenv v1.4.1 // indirect
+ go.uber.org/atomic v1.9.0 // indirect
+ go.uber.org/multierr v1.8.0 // indirect
+ golang.org/x/crypto v0.4.0 // indirect
+ golang.org/x/sys v0.3.0 // indirect
+ golang.org/x/text v0.5.0 // indirect
+ gopkg.in/ini.v1 v1.67.0 // 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 41b9e0a..3da9918 100644
--- a/go.sum
+++ b/go.sum
@@ -3,230 +3,241 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
-github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
-github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
-github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
-github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
+github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
+github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
+github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA=
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
-github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
-github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
-github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-hclog v0.15.0 h1:qMuK0wxsoW4D0ddCCYwPSTm4KQv1X1ke3WmPWZ0Mvsk=
-github.com/hashicorp/go-hclog v0.15.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
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=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
-github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
-github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
+github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
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/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
-github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
-github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
-github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
-github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
-github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
-github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
-github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
-github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
+github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
+github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
+github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
+github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
-github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/sendgrid/rest v1.0.2 h1:xdfALkR1m9eqf41/zEnUmV0fw4b31ZzGZ4Dj5f2/w04=
-github.com/sendgrid/rest v2.6.2+incompatible h1:zGMNhccsPkIc8SvU9x+qdDz2qhFoGUPGGC4mMvTondA=
-github.com/sendgrid/rest v2.6.2+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
-github.com/sendgrid/sendgrid-go v1.2.0 h1:2K3teZdhaPe12ftFyFL4AWDH4QmNPc+sCi6mWFx5+oo=
-github.com/sendgrid/sendgrid-go v3.7.2+incompatible h1:ePQr9ns8so+28whk+gLKRYiyI5IiCESkDIqy7cjiwLg=
-github.com/sendgrid/sendgrid-go v3.7.2+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-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/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
-github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
+github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
+github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
+github.com/sendgrid/sendgrid-go v3.12.0+incompatible h1:/N2vx18Fg1KmQOh6zESc5FJB8pYwt5QFBDflYPh1KVg=
+github.com/sendgrid/sendgrid-go v3.12.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
+github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
+github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
+github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
+github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
+github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
+github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
+github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
-golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
+golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -236,16 +247,23 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -254,64 +272,181 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
+golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
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-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -321,26 +456,80 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
-gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
-gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
-gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/main.go b/main.go
index 076db1f..f088221 100644
--- a/main.go
+++ b/main.go
@@ -2,141 +2,63 @@ package main
import (
"context"
+ "fmt"
"net/http"
"os"
"os/signal"
+ "syscall"
"time"
- "github.com/d-vignesh/go-jwt-auth/data"
- "github.com/d-vignesh/go-jwt-auth/handlers"
- "github.com/d-vignesh/go-jwt-auth/service"
- "github.com/d-vignesh/go-jwt-auth/utils"
- "github.com/gorilla/mux"
- "github.com/hashicorp/go-hclog"
+ "github.com/caseyrwebb/go-jwt-auth/app"
+ "github.com/caseyrwebb/go-jwt-auth/app/utils"
+ "go.uber.org/zap"
)
-// schema for user table
-const userSchema = `
- create table if not exists users (
- id Varchar(36) not null,
- email Varchar(100) not null unique,
- username Varchar(225),
- password Varchar(225) not null,
- tokenhash Varchar(15) not null,
- isverified Boolean default false,
- createdat Timestamp not null,
- updatedat Timestamp not null,
- Primary Key (id)
- );
-`
-
-const verificationSchema = `
- create table if not exists verifications (
- email Varchar(100) not null,
- code Varchar(10) not null,
- expiresat Timestamp not null,
- type Varchar(10) not null,
- Primary Key (email),
- Constraint fk_user_email Foreign Key(email) References users(email)
- On Delete Cascade On Update Cascade
- )
-`
-
func main() {
- logger := utils.NewLogger()
+ filename := "logs.log"
+ logger := utils.CustomLogger(filename)
configs := utils.NewConfigurations(logger)
- // validator contains all the methods that are need to validate the user json in request
- validator := data.NewValidation()
-
- // create a new connection to the postgres db store
- db, err := data.NewConnection(configs, logger)
+ app := app.New(*logger, configs)
+ app.DB.SetDBLogger(logger)
+ err := app.DB.Open(configs)
if err != nil {
- logger.Error("unable to connect to db", "error", err)
- panic(err)
+ logger.Error(fmt.Sprintf("%s %s %s", "could not connect to the database", "error", err))
+ os.Exit(1)
}
- defer db.Close()
-
- // creation of user table.
- db.MustExec(userSchema)
- db.MustExec(verificationSchema)
-
- // repository contains all the methods that interact with DB to perform CURD operations for user.
- repository := data.NewPostgresRepository(db, logger)
-
- // authService contains all methods that help in authorizing a user request
- authService := service.NewAuthService(logger, configs)
-
- // mailService contains the utility methods to send an email
- mailService := service.NewSGMailService(logger, configs)
-
- // UserHandler encapsulates all the services related to user
- uh := handlers.NewAuthHandler(logger, configs, validator, repository, authService, mailService)
- // create a serve mux
- sm := mux.NewRouter()
+ defer app.DB.Close()
- // register handlers
- postR := sm.Methods(http.MethodPost).Subrouter()
-
- mailR := sm.PathPrefix("/verify").Methods(http.MethodPost).Subrouter()
- mailR.HandleFunc("/mail", uh.VerifyMail)
- mailR.HandleFunc("/password-reset", uh.VerifyPasswordReset)
- mailR.Use(uh.MiddlewareValidateVerificationData)
-
- postR.HandleFunc("/signup", uh.Signup)
- postR.HandleFunc("/login", uh.Login)
- postR.Use(uh.MiddlewareValidateUser)
-
- // used the PathPrefix as workaround for scenarios where all the
- // get requests must use the ValidateAccessToken middleware except
- // the /refresh-token request which has to use ValidateRefreshToken middleware
- refToken := sm.PathPrefix("/refresh-token").Subrouter()
- refToken.HandleFunc("", uh.RefreshToken)
- refToken.Use(uh.MiddlewareValidateRefreshToken)
-
- getR := sm.Methods(http.MethodGet).Subrouter()
- getR.HandleFunc("/greet", uh.Greet)
- getR.HandleFunc("/get-password-reset-code", uh.GeneratePassResetCode)
- getR.Use(uh.MiddlewareValidateAccessToken)
-
- putR := sm.Methods(http.MethodPut).Subrouter()
- putR.HandleFunc("/update-username", uh.UpdateUsername)
- putR.HandleFunc("/reset-password", uh.ResetPassword)
- putR.Use(uh.MiddlewareValidateAccessToken)
-
- // create a server
svr := http.Server{
Addr: configs.ServerAddress,
- Handler: sm,
- ErrorLog: logger.StandardLogger(&hclog.StandardLoggerOptions{}),
+ Handler: app.Router,
+ ErrorLog: zap.NewStdLog(logger),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
- // start the server
go func() {
- logger.Info("starting the server at port", configs.ServerAddress)
+ logger.Info(fmt.Sprintf("%s %s", "starting the server at port", configs.ServerAddress))
err := svr.ListenAndServe()
if err != nil {
- logger.Error("could not start the server", "error", err)
+ logger.Error(fmt.Sprintf("%s %s %s", "could not start the server", "error", err))
os.Exit(1)
}
}()
- // look for interrupts for graceful shutdown
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
- signal.Notify(c, os.Kill)
+ signal.Notify(c, syscall.SIGTERM)
sig := <-c
- logger.Info("shutting down the server", "received signal", sig)
+ logger.Info(fmt.Sprintf("%s %s %s", "shutting down the server", "received signal", sig))
- //gracefully shutdown the server, waiting max 30 seconds for current operations to complete
- ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
svr.Shutdown(ctx)
+
}
diff --git a/notes.txt b/notes.txt
deleted file mode 100644
index 0ca1352..0000000
--- a/notes.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-Generate rsa private key :
- openssl genrsa -out access-private.pem 2048
- openssl genrsa -out refresh-private.pem 2048
-
-Export rsa public key :
- openssl rsa -in access-private.pem -outform PEM -pubout -out access-public.pem
- openssl rsa -in refresh-private.pem -outform PEM -pubout -out refresh-public.pem
\ No newline at end of file
diff --git a/templates/confirm_mail.html b/templates/confirm_mail.html
deleted file mode 100644
index 507171f..0000000
--- a/templates/confirm_mail.html
+++ /dev/null
@@ -1,156 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- We're thrilled to have you here! Get ready to dive into your new account.
-
-
-
-
-
- |
-
-
-
-
-
-
- Welcome to bookite
-
- |
-
-
- |
-
-
-
-
-
-
- Hey Vignesh, thanks for signing up and we are super delighted about having you here. Please verify your email to start exploring great reads.
- Use the below confirmation code to complete the process.
- ABCEDFGH
- |
-
-
-
- If you have any questions, just reply to this email—we're always happy to help out.
- |
-
-
-
- Cheers, Bookite Team
- |
-
-
- |
-
-
-
-
-
\ No newline at end of file
diff --git a/templates/password_reset.html b/templates/password_reset.html
deleted file mode 100644
index bf2683f..0000000
--- a/templates/password_reset.html
+++ /dev/null
@@ -1,153 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- We're thrilled to have you here! Get ready to dive into your new account.
-
-
-
-
-
- |
-
-
-
-
-
-
- Reset your bookite password?
-
- |
-
-
- |
-
-
-
-
-
-
- Hi {{ Username }},
If you requested a password reset, use the confirmation code below to complete the process. If you didn't make this request, ignore this email.
- {{ Code }}
- |
-
-
-
- If you have any questions, just reply to this email—we're always happy to help out.
- |
-
-
-
- Cheers, Bookite Team
- |
-
-
- |
-
-
-
-
-
\ No newline at end of file
diff --git a/utils/config.go b/utils/config.go
deleted file mode 100644
index dc31f19..0000000
--- a/utils/config.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package utils
-
-import (
- "github.com/hashicorp/go-hclog"
- "github.com/lib/pq"
- "github.com/spf13/viper"
-)
-
-// Configurations wraps all the config variables required by the auth service
-type Configurations struct {
- ServerAddress string
- DBHost string
- DBName string
- DBUser string
- DBPass string
- DBPort string
- DBConn string
- AccessTokenPrivateKeyPath string
- AccessTokenPublicKeyPath string
- RefreshTokenPrivateKeyPath string
- RefreshTokenPublicKeyPath string
- JwtExpiration int // in minutes
- SendGridApiKey string
- MailVerifCodeExpiration int // in hours
- PassResetCodeExpiration int // in minutes
- MailVerifTemplateID string
- PassResetTemplateID string
-}
-
-// NewConfigurations returns a new Configuration object
-func NewConfigurations(logger hclog.Logger) *Configurations {
-
- viper.AutomaticEnv()
-
- dbURL := viper.GetString("DATABASE_URL")
- conn, _ := pq.ParseURL(dbURL)
- logger.Debug("found database url in env, connection string is formed by parsing it")
- logger.Debug("db connection string", conn)
-
- viper.SetDefault("SERVER_ADDRESS", "0.0.0.0:9090")
- viper.SetDefault("DB_HOST", "localhost")
- viper.SetDefault("DB_NAME", "bookite")
- viper.SetDefault("DB_USER", "vignesh")
- viper.SetDefault("DB_PASSWORD", "password")
- viper.SetDefault("DB_PORT", "5432")
- viper.SetDefault("ACCESS_TOKEN_PRIVATE_KEY_PATH", "./access-private.pem")
- viper.SetDefault("ACCESS_TOKEN_PUBLIC_KEY_PATH", "./access-public.pem")
- viper.SetDefault("REFRESH_TOKEN_PRIVATE_KEY_PATH", "./refresh-private.pem")
- viper.SetDefault("REFRESH_TOKEN_PUBLIC_KEY_PATH", "./refresh-public.pem")
- viper.SetDefault("JWT_EXPIRATION", 30)
- viper.SetDefault("MAIL_VERIFICATION_CODE_EXPIRATION", 24)
- viper.SetDefault("PASSWORD_RESET_CODE_EXPIRATION", 15)
- viper.SetDefault("MAIL_VERIFICATION_TEMPLATE_ID", "d-5ecbea6e38764af3b703daf03f139b48")
- viper.SetDefault("PASSWORD_RESET_TEMPLATE_ID", "d-3fc222d11809441abaa8ed459bb44319")
-
- configs := &Configurations{
- ServerAddress: viper.GetString("SERVER_ADDRESS"),
- DBHost: viper.GetString("DB_HOST"),
- DBName: viper.GetString("DB_NAME"),
- DBUser: viper.GetString("DB_USER"),
- DBPass: viper.GetString("DB_PASSWORD"),
- DBPort: viper.GetString("DB_PORT"),
- DBConn: conn,
- JwtExpiration: viper.GetInt("JWT_EXPIRATION"),
- AccessTokenPrivateKeyPath: viper.GetString("ACCESS_TOKEN_PRIVATE_KEY_PATH"),
- AccessTokenPublicKeyPath: viper.GetString("ACCESS_TOKEN_PUBLIC_KEY_PATH"),
- RefreshTokenPrivateKeyPath: viper.GetString("REFRESH_TOKEN_PRIVATE_KEY_PATH"),
- RefreshTokenPublicKeyPath: viper.GetString("REFRESH_TOKEN_PUBLIC_KEY_PATH"),
- SendGridApiKey: viper.GetString("SENDGRID_API_KEY"),
- MailVerifCodeExpiration: viper.GetInt("MAIL_VERIFICATION_CODE_EXPIRATION"),
- PassResetCodeExpiration: viper.GetInt("PASSWORD_RESET_CODE_EXPIRATION"),
- MailVerifTemplateID: viper.GetString("MAIL_VERIFICATION_TEMPLATE_ID"),
- PassResetTemplateID: viper.GetString("PASSWORD_RESET_TEMPLATE_ID"),
- }
-
- // reading heroku provided port to handle deployment with heroku
- port := viper.GetString("PORT")
- if port != "" {
- logger.Debug("using the port allocated by heroku", port)
- configs.ServerAddress = "0.0.0.0:" + port
- }
-
- logger.Debug("serve port", configs.ServerAddress)
- logger.Debug("db host", configs.DBHost)
- logger.Debug("db name", configs.DBName)
- logger.Debug("db port", configs.DBPort)
- logger.Debug("jwt expiration", configs.JwtExpiration)
-
- return configs
-}
diff --git a/utils/logger.go b/utils/logger.go
deleted file mode 100644
index b9526da..0000000
--- a/utils/logger.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package utils
-
-import (
- "github.com/hashicorp/go-hclog"
-)
-
-// NewLogger returns a new logger instance
-func NewLogger() hclog.Logger {
- logger := hclog.New(&hclog.LoggerOptions{
- Name: "user-auth-service",
- Level: hclog.LevelFromString("DEBUG"),
- })
-
- return logger
-}