diff --git a/.golangci.yaml b/.golangci.yaml
index b76c410..628e316 100755
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -34,6 +34,7 @@ linters:
- dogsled
- dupl
- errcheck
+ - errorlint
- exportloopref
- forbidigo
- funlen
diff --git a/cmd/auth_service/internal/services/user_gorm_service.go b/cmd/auth_service/internal/services/user_gorm_service.go
index 821b1e4..8a2c394 100755
--- a/cmd/auth_service/internal/services/user_gorm_service.go
+++ b/cmd/auth_service/internal/services/user_gorm_service.go
@@ -13,17 +13,17 @@ type (
Load(ctx context.Context, id string) (*models.UserDBModel, error)
}
userService struct {
- repository repository.UserRepository
+ repo repository.UserRepository
}
)
-func NewORMUserService(repository repository.UserRepository) UserService {
- return &userService{repository: repository}
+func NewORMUserService(repo repository.UserRepository) UserService {
+ return &userService{repo: repo}
}
func (s *userService) Load(ctx context.Context, id string) (*models.UserDBModel, error) {
uid, _ := strconv.ParseInt(id, 10, 64)
- res, err := s.repository.GetOrCreate(ctx, uid)
+ res, err := s.repo.GetOrCreate(ctx, uid)
if err != nil {
return nil, err
}
diff --git a/cmd/gateway/internal/handler/auth.go b/cmd/gateway/internal/handler/auth.go
index 9763e4c..08563e6 100644
--- a/cmd/gateway/internal/handler/auth.go
+++ b/cmd/gateway/internal/handler/auth.go
@@ -29,31 +29,33 @@ type (
GetVerificationCode() http.HandlerFunc
}
authHandler struct {
- cqrs *cqrs.Application
- logger *logger.Logger
+ application *cqrs.Application
+ logger *logger.Logger
}
)
-func (a authHandler) RegisterRoutes(r *mux.Router, cfg interface{}) {
+const verificationCodeParam = "code"
+
+func (h authHandler) RegisterRoutes(r *mux.Router, cfg interface{}) {
params := cfg.(auth.Auth)
authorizer, _ := auth.NewAuthorizer(¶ms)
sr := r.PathPrefix("/auth/").Subrouter()
- sr.Methods(http.MethodPost).Path("/signup").HandlerFunc(authorizer.Middleware(a.SignUpUser()))
- sr.Methods(http.MethodPost).Path("/signin/{auth_code}").HandlerFunc(authorizer.Middleware(a.SignInUserByCode()))
- sr.Methods(http.MethodPost).Path("/signin").HandlerFunc(authorizer.Middleware(a.SignInUser()))
+ sr.Methods(http.MethodPost).Path("/signup").HandlerFunc(authorizer.Middleware(h.SignUpUser()))
+ sr.Methods(http.MethodPost).Path("/signin/{auth_code}").HandlerFunc(authorizer.Middleware(h.SignInUserByCode()))
+ sr.Methods(http.MethodPost).Path("/signin").HandlerFunc(authorizer.Middleware(h.SignInUser()))
- sr.Methods(http.MethodGet).Path("/verify/{code}").HandlerFunc(authorizer.Middleware(a.Verify()))
- sr.Methods(http.MethodPost).Path("/code").HandlerFunc(authorizer.Middleware(a.GetVerificationCode()))
- sr.Methods(http.MethodGet).Path("/code/{code}").HandlerFunc(authorizer.Middleware(a.GetUserByCode()))
+ sr.Methods(http.MethodGet).Path("/verify/{code}").HandlerFunc(authorizer.Middleware(h.Verify()))
+ sr.Methods(http.MethodPost).Path("/code").HandlerFunc(authorizer.Middleware(h.GetVerificationCode()))
+ sr.Methods(http.MethodGet).Path("/code/{code}").HandlerFunc(authorizer.Middleware(h.GetUserByCode()))
}
-func NewAuthHandler(cqrs *cqrs.Application, l *logger.Logger) AuthHandler {
- return authHandler{cqrs, l}
+func NewAuthHandler(application *cqrs.Application, l *logger.Logger) AuthHandler {
+ return authHandler{application, l}
}
-func (a authHandler) SignInUser() http.HandlerFunc {
+func (h authHandler) SignInUser() http.HandlerFunc {
reqUser := models.SignInUserRequest{}
res := &models.UserResponse{}
@@ -62,17 +64,17 @@ func (a authHandler) SignInUser() http.HandlerFunc {
defer span.End()
if err := validate.UserInput(r, &reqUser); err != nil {
- a.logger.Error().Err(err).Msg("SignInUser: validate")
+ h.logger.Error().Err(err).Msg("SignInUser: validate")
responses.RespondBadRequest(w, err.Error())
return
}
var errQuery error
- res, errQuery = a.cqrs.SigninCommand(ctx, reqUser)
+ res, errQuery = h.application.SigninCommand(ctx, reqUser)
if errQuery != nil {
- a.logger.Error().Err(errQuery).Msg("SignInUser: grpc signIn")
+ h.logger.Error().Err(errQuery).Msg("SignInUser: grpc signIn")
if e, ok := status.FromError(errQuery); ok {
responses.FromGRPCError(e, w)
@@ -85,7 +87,7 @@ func (a authHandler) SignInUser() http.HandlerFunc {
}
}
-func (a authHandler) SignInUserByCode() http.HandlerFunc {
+func (h authHandler) SignInUserByCode() http.HandlerFunc {
var authCode string
reqSignIn := models.VerificationCodeRequest{}
res := &models.UserResponse{}
@@ -103,17 +105,17 @@ func (a authHandler) SignInUserByCode() http.HandlerFunc {
}
if err := validate.UserInput(r, &reqSignIn); err != nil {
tracing.RecordError(span, err)
- a.logger.Error().Err(err).Msg("SignInUserByCode: decode")
+ h.logger.Error().Err(err).Msg("SignInUserByCode: decode")
responses.RespondBadRequest(w, err.Error())
return
}
var errQuery error
- res, errQuery = a.cqrs.SigninByCodeCommand(ctx, reqSignIn.Email, authCode)
+ res, errQuery = h.application.SigninByCodeCommand(ctx, reqSignIn.Email, authCode)
if errQuery != nil {
- a.logger.Error().Err(errQuery).Msg("SignInUser: grpc signIn")
+ h.logger.Error().Err(errQuery).Msg("SignInUser: grpc signIn")
if e, ok := status.FromError(errQuery); ok {
responses.FromGRPCError(e, w)
@@ -126,27 +128,26 @@ func (a authHandler) SignInUserByCode() http.HandlerFunc {
}
}
-func (a authHandler) SignUpUser() http.HandlerFunc {
+func (h authHandler) SignUpUser() http.HandlerFunc {
var reqUser models.SignUpUserRequest
return func(w http.ResponseWriter, r *http.Request) {
- ctx, span := otel.GetTracerProvider().Tracer("Handler").Start(r.Context(), "Handler/SignUpUser")
+ ctx, span := otel.GetTracerProvider().Tracer("Handler").Start(r.Context(), "AuthHandler/SignUpUser")
defer span.End()
if err := validate.UserInput(r, &reqUser); err != nil {
tracing.RecordError(span, err)
- a.logger.Error().Err(err).Msg("SignUpUser: decode")
+ h.logger.Error().Err(err).Msg("SignUpUser: validate")
responses.RespondBadRequest(w, err.Error())
return
}
- err := a.cqrs.SignupUserCommand(ctx, reqUser)
+ err := h.application.SignupUserCommand(ctx, reqUser)
if err != nil {
- span.RecordError(err, trace.WithStackTrace(true))
- span.SetStatus(codes.Error, err.Error())
- a.logger.Error().Err(err).Msg("SignUpUser:create")
+ tracing.RecordError(span, err)
+ h.logger.Error().Err(err).Msg("SignUpUser:create")
if e, ok := status.FromError(err); ok {
responses.FromGRPCError(e, w)
@@ -159,7 +160,7 @@ func (a authHandler) SignUpUser() http.HandlerFunc {
}
}
-func (a authHandler) GetVerificationCode() http.HandlerFunc {
+func (h authHandler) GetVerificationCode() http.HandlerFunc {
reqSignIn := models.VerificationCodeRequest{}
resp := models.UserResponse{}
@@ -169,17 +170,17 @@ func (a authHandler) GetVerificationCode() http.HandlerFunc {
if err := validate.UserInput(r, &reqSignIn); err != nil {
tracing.RecordError(span, err)
- a.logger.Error().Err(err).Msg("GetVerificationCode: validate")
+ h.logger.Error().Err(err).Msg("GetVerificationCode: validate")
responses.RespondBadRequest(w, err.Error())
return
}
- _, err := a.cqrs.GetUser(ctx, models.UserRequest{Email: reqSignIn.Email})
+ _, err := h.application.GetUser(ctx, models.UserRequest{Email: reqSignIn.Email})
if err != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, err.Error())
- a.logger.Error().Err(err).Msg("GetVerificationCode: fetchUser")
+ h.logger.Error().Err(err).Msg("GetVerificationCode: fetchUser")
if e, ok := status.FromError(err); ok {
responses.FromGRPCError(e, w)
@@ -189,11 +190,11 @@ func (a authHandler) GetVerificationCode() http.HandlerFunc {
return
}
- resp, err = a.cqrs.GetVerificationCode(ctx, reqSignIn.Email)
+ resp, err = h.application.GetVerificationCode(ctx, reqSignIn.Email)
if err != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, err.Error())
- a.logger.Error().Err(err).Msg("GetVerificationCode: GetVerificationCode")
+ h.logger.Error().Err(err).Msg("GetVerificationCode: GetVerificationCode")
if e, ok := status.FromError(err); ok {
responses.FromGRPCError(e, w)
@@ -206,27 +207,27 @@ func (a authHandler) GetVerificationCode() http.HandlerFunc {
}
}
-func (a authHandler) GetUserByCode() http.HandlerFunc {
+func (h authHandler) GetUserByCode() http.HandlerFunc {
var vCode string
return func(w http.ResponseWriter, r *http.Request) {
ctx, span := otel.GetTracerProvider().Tracer("user-handler").Start(r.Context(), "GetUserByCode")
defer span.End()
- vCode = mux.Vars(r)["code"]
+ vCode = mux.Vars(r)[verificationCodeParam]
if vCode == "" {
- vCode = r.URL.Query().Get("code")
+ vCode = r.URL.Query().Get(verificationCodeParam)
if vCode == "" {
responses.RespondBadRequest(w, "code param is missing")
return
}
}
- user, err := a.cqrs.GetUserByCode(ctx, vCode)
+ user, err := h.application.GetUserByCode(ctx, vCode)
if err != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, err.Error())
- a.logger.Error().Err(err).Msg("GetUserByID:header:getId")
+ h.logger.Error().Err(err).Msg("GetUserByID:header:getId")
responses.RespondBadRequest(w, err.Error())
return
}
@@ -234,7 +235,7 @@ func (a authHandler) GetUserByCode() http.HandlerFunc {
if err != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, err.Error())
- a.logger.Error().Err(err).Msg("GetUserByID:grpc:getUser")
+ h.logger.Error().Err(err).Msg("GetUserByID:grpc:getUser")
if e, ok := status.FromError(err); ok {
responses.FromGRPCError(e, w)
@@ -247,29 +248,29 @@ func (a authHandler) GetUserByCode() http.HandlerFunc {
}
}
-func (a authHandler) Verify() http.HandlerFunc {
+func (h authHandler) Verify() http.HandlerFunc {
var vCode string
return func(w http.ResponseWriter, r *http.Request) {
ctx, span := otel.GetTracerProvider().Tracer("auth-handler").Start(r.Context(), "Handler SignUpUser")
defer span.End()
- vCode = mux.Vars(r)["code"]
+ vCode = mux.Vars(r)[verificationCodeParam]
if vCode == "" {
- vCode = r.URL.Query().Get("code")
+ vCode = r.URL.Query().Get(verificationCodeParam)
if vCode == "" {
responses.RespondBadRequest(w, "code param is missing")
return
}
}
- err := a.cqrs.Commands.VerifyUserByCode.Handle(ctx, command.VerifyCode{VerificationCode: vCode})
+ err := h.application.Commands.VerifyUserByCode.Handle(ctx, command.VerifyCode{VerificationCode: vCode})
if err != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, err.Error())
- a.logger.Error().Err(err).Msg("Verify")
+ h.logger.Error().Err(err).Msg("Verify")
if e, ok := status.FromError(err); ok {
responses.FromGRPCError(e, w)
diff --git a/cmd/gateway/internal/handler/crypton.go b/cmd/gateway/internal/handler/crypton.go
index 4134396..0712e30 100644
--- a/cmd/gateway/internal/handler/crypton.go
+++ b/cmd/gateway/internal/handler/crypton.go
@@ -4,12 +4,10 @@ import (
"net/http"
"github.com/RafalSalwa/auth-api/pkg/encdec"
+ "github.com/RafalSalwa/auth-api/pkg/logger"
"github.com/RafalSalwa/auth-api/pkg/responses"
- "go.opentelemetry.io/otel"
-
"github.com/gorilla/mux"
-
- "github.com/RafalSalwa/auth-api/pkg/logger"
+ "go.opentelemetry.io/otel"
)
type (
@@ -24,12 +22,16 @@ type (
}
)
-func (c cryptonHandler) RegisterRoutes(r *mux.Router, cfg interface{}) {
- r.Methods(http.MethodGet).Path("/encrypt/{message}").HandlerFunc(c.Encrypt())
- r.Methods(http.MethodGet).Path("/decrypt/{message}").HandlerFunc(c.Decrypt())
+func NewCryptonHandler(l *logger.Logger) CryptonHandler {
+ return cryptonHandler{l}
+}
+
+func (h cryptonHandler) RegisterRoutes(r *mux.Router, cfg interface{}) {
+ r.Methods(http.MethodGet).Path("/encrypt/{message}").HandlerFunc(h.Encrypt())
+ r.Methods(http.MethodGet).Path("/decrypt/{message}").HandlerFunc(h.Decrypt())
}
-func (c cryptonHandler) Encrypt() http.HandlerFunc {
+func (h cryptonHandler) Encrypt() http.HandlerFunc {
var message string
return func(w http.ResponseWriter, r *http.Request) {
_, span := otel.GetTracerProvider().Tracer("Encrypt").Start(r.Context(), "Encrypt Handler")
@@ -41,7 +43,7 @@ func (c cryptonHandler) Encrypt() http.HandlerFunc {
}
}
-func (c cryptonHandler) Decrypt() http.HandlerFunc {
+func (h cryptonHandler) Decrypt() http.HandlerFunc {
var message string
return func(w http.ResponseWriter, r *http.Request) {
_, span := otel.GetTracerProvider().Tracer("Encrypt").Start(r.Context(), "Encrypt Handler")
@@ -52,7 +54,3 @@ func (c cryptonHandler) Decrypt() http.HandlerFunc {
responses.RespondString(w, encrypted)
}
}
-
-func NewCryptonHandler(l *logger.Logger) CryptonHandler {
- return cryptonHandler{l}
-}
diff --git a/cmd/gateway/internal/handler/user.go b/cmd/gateway/internal/handler/user.go
index 665ca8b..8801b7a 100755
--- a/cmd/gateway/internal/handler/user.go
+++ b/cmd/gateway/internal/handler/user.go
@@ -27,25 +27,25 @@ type (
PasswordChange() HandlerFunc
}
userHandler struct {
- cqrs *cqrs.Application
- logger *logger.Logger
+ application *cqrs.Application
+ logger *logger.Logger
}
)
-func NewUserHandler(cqrs *cqrs.Application, l *logger.Logger) UserHandler {
- return userHandler{cqrs, l}
+func NewUserHandler(application *cqrs.Application, l *logger.Logger) UserHandler {
+ return userHandler{application, l}
}
-func (uh userHandler) RegisterRoutes(r *mux.Router, cfg interface{}) {
+func (h userHandler) RegisterRoutes(r *mux.Router, cfg interface{}) {
params := cfg.(auth.JWTConfig)
s := r.PathPrefix("/user").Subrouter()
s.Use(middlewares.ValidateJWTAccessToken(¶ms))
- s.Methods(http.MethodGet).Path("").HandlerFunc(uh.GetUserByID())
- s.Methods(http.MethodPost).Path("/change_password").HandlerFunc(uh.PasswordChange())
+ s.Methods(http.MethodGet).Path("").HandlerFunc(h.GetUserByID())
+ s.Methods(http.MethodPost).Path("/change_password").HandlerFunc(h.PasswordChange())
}
-func (uh userHandler) GetUserByID() HandlerFunc {
+func (h userHandler) GetUserByID() HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, span := otel.GetTracerProvider().Tracer("user-handler").Start(r.Context(), "GetUserByID")
defer span.End()
@@ -55,16 +55,16 @@ func (uh userHandler) GetUserByID() HandlerFunc {
if err != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, err.Error())
- uh.logger.Error().Err(err).Msg("GetUserByID:header:getId")
+ h.logger.Error().Err(err).Msg("GetUserByID:header:getId")
responses.RespondBadRequest(w, err.Error())
return
}
- user, err := uh.cqrs.GetUser(ctx, models.UserRequest{Id: userID})
+ user, err := h.application.GetUser(ctx, models.UserRequest{Id: userID})
if err != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, err.Error())
- uh.logger.Error().Err(err).Msg("GetUserByID:grpc:getUser")
+ h.logger.Error().Err(err).Msg("GetUserByID:grpc:getUser")
if e, ok := status.FromError(err); ok {
responses.FromGRPCError(e, w)
@@ -77,7 +77,7 @@ func (uh userHandler) GetUserByID() HandlerFunc {
}
}
-func (uh userHandler) PasswordChange() HandlerFunc {
+func (h userHandler) PasswordChange() HandlerFunc {
req := &models.ChangePasswordRequest{}
return func(w http.ResponseWriter, r *http.Request) {
@@ -87,38 +87,38 @@ func (uh userHandler) PasswordChange() HandlerFunc {
userID, err := strconv.ParseInt(r.Header.Get("x-user-id"), 10, 64)
if err != nil {
tracing.RecordError(span, err)
- uh.logger.Error().Err(err).Msg("GetUserByID:header:getId")
+ h.logger.Error().Err(err).Msg("GetUserByID:header:getId")
responses.RespondBadRequest(w, err.Error())
return
}
if err = validate.UserInput(r, &req); err != nil {
tracing.RecordError(span, err)
- uh.logger.Error().Err(err).Msg("PasswordChange: decode")
+ h.logger.Error().Err(err).Msg("PasswordChange: decode")
responses.RespondBadRequest(w, err.Error())
return
}
if err = hashing.Validate(req.Password, req.PasswordConfirm); err != nil {
tracing.RecordError(span, err)
- uh.logger.Error().Err(err).Msg("PasswordChange:validateInputPasswords")
+ h.logger.Error().Err(err).Msg("PasswordChange:validateInputPasswords")
responses.RespondBadRequest(w, err.Error())
return
}
- _, err = uh.cqrs.GetUser(ctx, models.UserRequest{Id: userID})
+ _, err = h.application.GetUser(ctx, models.UserRequest{Id: userID})
if err != nil {
tracing.RecordError(span, err)
- uh.logger.Error().Err(err).Msg("PasswordChange:grpc:GetUserByID")
+ h.logger.Error().Err(err).Msg("PasswordChange:grpc:GetUserByID")
responses.RespondBadRequest(w, err.Error())
return
}
- err = uh.cqrs.ChangePassword(ctx, req)
+ err = h.application.ChangePassword(ctx, req)
if err != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, err.Error())
- uh.logger.Error().Err(err).Msg("PasswordChange:grpc:ChangePassword")
+ h.logger.Error().Err(err).Msg("PasswordChange:grpc:ChangePassword")
if e, ok := status.FromError(err); ok {
responses.FromGRPCError(e, w)
diff --git a/cmd/gateway/internal/rpc_client/connection.go b/cmd/gateway/internal/rpc_client/connection.go
index d954eca..5da6138 100755
--- a/cmd/gateway/internal/rpc_client/connection.go
+++ b/cmd/gateway/internal/rpc_client/connection.go
@@ -10,7 +10,7 @@ import (
)
func newConnection(addr string) (*grpc.ClientConn, error) {
- conn, err := grpc.Dial(addr,
+ conn, err := grpc.NewClient(addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
diff --git a/docs/swagger.json b/docs/swagger.json
index dcaa7f1..c21a673 100755
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -4,7 +4,7 @@
"description": "Interview API with usage of REST,gRPC,MySQL,Redis,mongo,AMQP.
Current authorize settings is: Basic Auth with interview/interview parameters",
"title": "Go Interview REST API",
"contact": {
- "name": "RafalSalwa",
+ "name": "RafalSalwa Repo",
"url": "https://github.com/RafalSalwa"
},
"version": "1.0"
@@ -12,7 +12,7 @@
"paths": {
"/auth/signup": {
"post": {
- "description": "Create account",
+ "description": "Create account with given credentials, in next step we need to obtain verification code for DOI process",
"tags": [
"Auth"
],
@@ -60,13 +60,16 @@
}
}
}
+ },
+ "500": {
+ "description": "For every other action, we do not want to explicitly show any other flow details"
}
}
}
},
- "/auth/signin": {
+ "/auth/code": {
"post": {
- "description": "Log In account",
+ "description": "get code for user created in previous step",
"tags": [
"Auth"
],
@@ -75,7 +78,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/SignInRequest"
+ "$ref": "#/components/schemas/LogInRequest"
}
}
}
@@ -86,33 +89,13 @@
"content": {
"application/json": {
"example": {
- "user": {
- "username": "user1",
- "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTA0NjMxNDMsImlhdCI6MTY5MDQ1OTU0MywibmJmIjoxNjkwNDU5NTQzLCJzdWIiOnsiSUQiOjEsIlVzZXJuYW1lIjoiIn19.Ly1E6KnOmRyCeRd1VhctkNUZs882rR7buG37XHPMqaGIERmYsN2y2nF5QQNyUtkTtV9Agfc10onhX8dSw1eSRg",
- "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTA0ODExNDMsImlhdCI6MTY5MDQ1OTU0MywibmJmIjoxNjkwNDU5NTQzLCJzdWIiOnsiSUQiOjEsIlVzZXJuYW1lIjoiIn19.D3UfrFazNMV6al1Jgz6WGyq9g_NGZpGijTH2YrBMhUHyellXBQgBmt5GtHfDJlcuPdM2cajyhPRJ7pdYf_0Z8Q",
- "created_at": "0001-01-01T00:00:00Z",
- "updated_at": "0001-01-01T00:00:00Z",
- "last_login": "0001-01-01T00:00:00Z",
- "deleted_at": "0001-01-01T00:00:00Z"
- }
- }
- }
- }
- },
- "400": {
- "description": "Bad request",
- "content": {
- "application/json": {
- "example": {
- "code": 400,
- "reason": "bad request",
- "message": "Key: 'LoginUserRequest.Password' Error:Field validation for 'Password' failed on the 'required' tag"
+ "status": "ok"
}
}
}
},
"404": {
- "description": "User not found or activated",
+ "description": "OK",
"content": {
"application/json": {
"example": {
@@ -121,13 +104,16 @@
}
}
}
+ },
+ "500": {
+ "description": "For every other action, we do not want to explicitly show any other flow details"
}
}
}
},
"/auth/verify/{code}": {
"get": {
- "description": "Verification for account used in DOI process. This code can be obtained from email. \n
psst... check mailhog at this server and port :8025 :)",
+ "description": "Verification for account used in DOI process. This code can be obtained from email or auth/verify endpoint.",
"tags": [
"Auth"
],
@@ -175,13 +161,16 @@
}
}
}
+ },
+ "500": {
+ "description": "For every other action, we do not want to explicitly show any other flow details"
}
}
}
},
- "/auth/code": {
+ "/auth/signin": {
"post": {
- "description": "get code for created user
(workaround in case mailhog wont be working)",
+ "description": "Log In account, remember to activate account (get verification code and authorize this code) in previous endpoints.",
"tags": [
"Auth"
],
@@ -190,7 +179,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/LogInRequest"
+ "$ref": "#/components/schemas/SignInRequest"
}
}
}
@@ -201,13 +190,33 @@
"content": {
"application/json": {
"example": {
- "status": "ok"
+ "user": {
+ "username": "user1",
+ "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTA0NjMxNDMsImlhdCI6MTY5MDQ1OTU0MywibmJmIjoxNjkwNDU5NTQzLCJzdWIiOnsiSUQiOjEsIlVzZXJuYW1lIjoiIn19.Ly1E6KnOmRyCeRd1VhctkNUZs882rR7buG37XHPMqaGIERmYsN2y2nF5QQNyUtkTtV9Agfc10onhX8dSw1eSRg",
+ "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTA0ODExNDMsImlhdCI6MTY5MDQ1OTU0MywibmJmIjoxNjkwNDU5NTQzLCJzdWIiOnsiSUQiOjEsIlVzZXJuYW1lIjoiIn19.D3UfrFazNMV6al1Jgz6WGyq9g_NGZpGijTH2YrBMhUHyellXBQgBmt5GtHfDJlcuPdM2cajyhPRJ7pdYf_0Z8Q",
+ "created_at": "0001-01-01T00:00:00Z",
+ "updated_at": "0001-01-01T00:00:00Z",
+ "last_login": "0001-01-01T00:00:00Z",
+ "deleted_at": "0001-01-01T00:00:00Z"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad request",
+ "content": {
+ "application/json": {
+ "example": {
+ "code": 400,
+ "reason": "bad request",
+ "message": "Key: 'LoginUserRequest.Password' Error:Field validation for 'Password' failed on the 'required' tag"
}
}
}
},
"404": {
- "description": "OK",
+ "description": "User not found or activated",
"content": {
"application/json": {
"example": {
@@ -216,19 +225,23 @@
}
}
}
+ },
+ "500": {
+ "description": "For every other action, we do not want to explicitly show any other flow details"
}
}
}
},
"/user": {
"get": {
- "description": "Log In account via jwt token from previous endpoints",
+ "description": "Log In account via jwt token from SignIn endpoint",
"tags": [
"User"
],
"security": [
{
- "BearerAuth":[]
+ "basicAuth": [],
+ "BearerAuth": []
}
],
"responses": {
@@ -240,10 +253,24 @@
"user": {
"id": 256,
"username": "rafal@interview.com"
- }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "When token header or auth method is missing",
+ "content": {
+ "application/json": {
+ "example": {
+ "code": 400,
+ "reason": "bad request"
}
}
}
+ },
+ "500": {
+ "description": "For every other action, we do not want to explicitly show any other flow details"
}
}
}
@@ -293,7 +320,7 @@
},
"SignInRequest": {
"properties": {
- "username": {
+ "email": {
"type": "string",
"example": "rafal@interview.com"
},
diff --git a/pkg/http/auth/api_key_test.go b/pkg/http/auth/api_key_test.go
index c52e716..11a9b9b 100644
--- a/pkg/http/auth/api_key_test.go
+++ b/pkg/http/auth/api_key_test.go
@@ -1,6 +1,7 @@
package auth
import (
+ "context"
"net/http"
"net/http/httptest"
"testing"
@@ -15,13 +16,14 @@ func TestAPIKeyMiddleware(t *testing.T) {
// Create a test server with the middleware applied
apiKey := "my-api-key"
+ ctx := context.Background()
middleware := newAPIKeyMiddleware(apiKey)
handler := middleware.Middleware(mockHandler)
server := httptest.NewServer(handler)
defer server.Close()
// Test case 1: Valid API key
- req, err := http.NewRequest("GET", server.URL, nil)
+ req, err := http.NewRequestWithContext(ctx, "GET", server.URL, nil)
if err != nil {
t.Fatal(err)
}
@@ -36,7 +38,7 @@ func TestAPIKeyMiddleware(t *testing.T) {
}
// Test case 2: Missing API key
- req, err = http.NewRequest("GET", server.URL, nil)
+ req, err = http.NewRequestWithContext(ctx, "GET", server.URL, nil)
if err != nil {
t.Fatal(err)
}
@@ -50,7 +52,7 @@ func TestAPIKeyMiddleware(t *testing.T) {
}
// Test case 3: Wrong API key
- req, err = http.NewRequest("GET", server.URL, nil)
+ req, err = http.NewRequestWithContext(ctx, "GET", server.URL, nil)
if err != nil {
t.Fatal(err)
}
diff --git a/pkg/http/auth/basic_auth.go b/pkg/http/auth/basic_auth.go
old mode 100755
new mode 100644
index 9738c11..22440e9
--- a/pkg/http/auth/basic_auth.go
+++ b/pkg/http/auth/basic_auth.go
@@ -4,6 +4,8 @@ import (
"crypto/sha256"
"crypto/subtle"
"net/http"
+
+ "github.com/RafalSalwa/auth-api/pkg/responses"
)
type basicAuth struct {
@@ -30,6 +32,7 @@ func (a *basicAuth) Middleware(h http.HandlerFunc) http.HandlerFunc {
return
}
}
+ responses.RespondNotAuthorized(w, "Missing or invalid credentials")
}
}
diff --git a/pkg/http/auth/bearer_token.go b/pkg/http/auth/bearer_token.go
old mode 100755
new mode 100644
diff --git a/pkg/http/middlewares/content_type_json.go b/pkg/http/middlewares/content_type_json.go
old mode 100755
new mode 100644
index 23b1e8c..4a89622
--- a/pkg/http/middlewares/content_type_json.go
+++ b/pkg/http/middlewares/content_type_json.go
@@ -3,20 +3,18 @@ package middlewares
import (
"net/http"
+ "github.com/RafalSalwa/auth-api/pkg/responses"
"github.com/gorilla/mux"
)
func ContentTypeJSON() mux.MiddlewareFunc {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // if r.Header.Get("Content-type") != "application/json" {
- // w.WriteHeader(http.StatusUnsupportedMediaType)
- // _, err := w.Write([]byte("415 - Unsupported Media Type. Only JSON files are allowed"))
- // if err != nil {
- // return
- // }
- // return
- //}
+ if r.Header.Get("Content-type") != "application/json" &&
+ r.Method == http.MethodPost {
+ responses.RespondInternalServerError(w)
+ return
+ }
w.Header().Set("Content-Type", "application/json;charset=utf8")
diff --git a/pkg/http/middlewares/requestlog.go b/pkg/http/middlewares/requestlog.go
index ed5bc05..f8c27c3 100755
--- a/pkg/http/middlewares/requestlog.go
+++ b/pkg/http/middlewares/requestlog.go
@@ -17,7 +17,7 @@ func RequestLog(logger *logger.Logger) mux.MiddlewareFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
- le := &logEntry{
+ entry := &logEntry{
ReceivedTime: start,
RequestMethod: r.Method,
RequestURL: r.URL.String(),
@@ -29,7 +29,7 @@ func RequestLog(logger *logger.Logger) mux.MiddlewareFunc {
}
if addr, ok := r.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
- le.ServerIP = ipFromHostPort(addr.String())
+ entry.ServerIP = ipFromHostPort(addr.String())
}
body, _ := io.ReadAll(r.Body)
err := r.Body.Close()
@@ -44,35 +44,35 @@ func RequestLog(logger *logger.Logger) mux.MiddlewareFunc {
w2 := &responseStats{w: w}
r2.Body = io.NopCloser(bytes.NewBuffer(body))
- le.Latency = time.Since(start)
+ entry.Latency = time.Since(start)
if rcc.err == nil && rcc.r != nil {
_, err := io.Copy(io.Discard, rcc)
if err != nil {
return
}
}
- le.RequestBodySize = rcc.n
- le.Status = w2.code
- if le.Status == 0 {
- le.Status = http.StatusOK
+ entry.RequestBodySize = rcc.n
+ entry.Status = w2.code
+ if entry.Status == 0 {
+ entry.Status = http.StatusOK
}
- le.ResponseHeaderSize, le.ResponseBodySize = w2.size()
- if le.RequestURL != "/metrics" {
+ entry.ResponseHeaderSize, entry.ResponseBodySize = w2.size()
+ if entry.RequestURL != "/metrics" {
logger.Info().
- Time("received_time", le.ReceivedTime).
- Str("method", le.RequestMethod).
- Str("url", le.RequestURL).
- Int64("header_size", le.RequestHeaderSize).
- Int64("body_size", le.RequestBodySize).
- Str("agent", le.UserAgent).
- Str("referer", le.Referer).
- Str("proto", le.Proto).
- Str("remote_ip", le.RemoteIP).
- Str("server_ip", le.ServerIP).
- Int("status", le.Status).
- Int64("resp_header_size", le.ResponseHeaderSize).
- Int64("resp_body_size", le.ResponseBodySize).
- Dur("latency", le.Latency).
+ Time("received_time", entry.ReceivedTime).
+ Str("method", entry.RequestMethod).
+ Str("url", entry.RequestURL).
+ Int64("header_size", entry.RequestHeaderSize).
+ Int64("body_size", entry.RequestBodySize).
+ Str("agent", entry.UserAgent).
+ Str("referer", entry.Referer).
+ Str("proto", entry.Proto).
+ Str("remote_ip", entry.RemoteIP).
+ Str("server_ip", entry.ServerIP).
+ Int("status", entry.Status).
+ Int64("resp_header_size", entry.ResponseHeaderSize).
+ Int64("resp_body_size", entry.ResponseBodySize).
+ Dur("latency", entry.Latency).
Msg("Request")
}
h.ServeHTTP(w2, r2)
diff --git a/pkg/models/user.go b/pkg/models/user.go
index a1d885c..b0194f5 100644
--- a/pkg/models/user.go
+++ b/pkg/models/user.go
@@ -70,7 +70,7 @@ type (
SignUpUserRequest struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8,max=32"`
- PasswordConfirm string `json:"passwordConfirm" validate:"required,min=8,max=32"`
+ PasswordConfirm string `json:"passwordConfirm" validate:"required,min=8,max=32,eqfield=Password"`
}
SignInUserRequest struct {
Username string `json:"username" validate:"required_without=Email"`
diff --git a/pkg/responses/response.go b/pkg/responses/response.go
old mode 100755
new mode 100644
index b48879a..fa50ce3
--- a/pkg/responses/response.go
+++ b/pkg/responses/response.go
@@ -59,6 +59,9 @@ func RespondBadRequest(w http.ResponseWriter, msg string) {
responseBody := marshalErrorResponse(errorResponse)
Respond(w, http.StatusBadRequest, responseBody)
}
+func RespondInternalServerError(w http.ResponseWriter) {
+ Respond(w, http.StatusInternalServerError, []byte(""))
+}
func RespondString(w http.ResponseWriter, msg string) {
Respond(w, http.StatusOK, []byte(msg))
diff --git a/pkg/validate/json_input.go b/pkg/validate/json_input.go
new file mode 100644
index 0000000..c96347c
--- /dev/null
+++ b/pkg/validate/json_input.go
@@ -0,0 +1,46 @@
+package validate
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+
+ "github.com/go-playground/validator/v10"
+)
+
+type ErrEQField struct {
+}
+
+func (e ErrEQField) Error() string {
+ panic("implement me")
+}
+
+func UserInput(r *http.Request, req interface{}) error {
+ reqValidator := validator.New()
+
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ return fmt.Errorf("cannot decode request data")
+ }
+
+ if err := reqValidator.Struct(req); err != nil {
+ var validationErrors validator.ValidationErrors
+ if errors.As(err, &validationErrors) {
+ for _, fieldErr := range validationErrors {
+ return handleFieldError(fieldErr)
+ }
+ }
+ }
+ return nil
+}
+
+func handleFieldError(fieldErr validator.FieldError) error {
+ switch fieldErr.Tag() {
+ case "required":
+ return fmt.Errorf("field '%s' is required", fieldErr.Field())
+ case "eqfield":
+ return fmt.Errorf("different values for '%s', but should be same", fieldErr.Param())
+ default:
+ return fmt.Errorf("field '%s' failed validation for '%s'", fieldErr.Field(), fieldErr.Tag())
+ }
+}
diff --git a/pkg/validate/json_request.go b/pkg/validate/json_request.go
new file mode 100644
index 0000000..b1a14f3
--- /dev/null
+++ b/pkg/validate/json_request.go
@@ -0,0 +1,79 @@
+package validate
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+)
+
+type malformedRequest struct {
+ status int
+ msg string
+}
+
+func (mr *malformedRequest) Error() string {
+ return mr.msg
+}
+
+func JSONBody(w http.ResponseWriter, r *http.Request, dst any) error {
+ ct := r.Header.Get("Content-Type")
+ if ct != "" {
+ mediaType := strings.ToLower(strings.TrimSpace(strings.Split(ct, ";")[0]))
+ if mediaType != "application/json" {
+ msg := "Content-Type header is not application/json"
+ return &malformedRequest{status: http.StatusUnsupportedMediaType, msg: msg}
+ }
+ }
+
+ r.Body = http.MaxBytesReader(w, r.Body, 1048576)
+
+ dec := json.NewDecoder(r.Body)
+ dec.DisallowUnknownFields()
+
+ err := dec.Decode(&dst)
+ if err != nil {
+ var syntaxError *json.SyntaxError
+ var unmarshalTypeError *json.UnmarshalTypeError
+
+ switch {
+ case errors.As(err, &syntaxError):
+ msg := fmt.Sprintf("Request body contains badly-formed JSON (at position %d)", syntaxError.Offset)
+ return &malformedRequest{status: http.StatusBadRequest, msg: msg}
+
+ case errors.Is(err, io.ErrUnexpectedEOF):
+ msg := "Request body contains badly-formed JSON"
+ return &malformedRequest{status: http.StatusBadRequest, msg: msg}
+
+ case errors.As(err, &unmarshalTypeError):
+ msg := fmt.Sprintf("Request body contains an invalid value for the %q field (at position %d)", unmarshalTypeError.Field, unmarshalTypeError.Offset)
+ return &malformedRequest{status: http.StatusBadRequest, msg: msg}
+
+ case strings.HasPrefix(err.Error(), "json: unknown field "):
+ fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
+ msg := fmt.Sprintf("Request body contains unknown field %s", fieldName)
+ return &malformedRequest{status: http.StatusBadRequest, msg: msg}
+
+ case errors.Is(err, io.EOF):
+ msg := "Request body must not be empty"
+ return &malformedRequest{status: http.StatusBadRequest, msg: msg}
+
+ case err.Error() == "http: request body too large":
+ msg := "Request body must not be larger than 1MB"
+ return &malformedRequest{status: http.StatusRequestEntityTooLarge, msg: msg}
+
+ default:
+ return err
+ }
+ }
+
+ err = dec.Decode(&struct{}{})
+ if !errors.Is(err, io.EOF) {
+ msg := "Request body must only contain a single JSON object"
+ return &malformedRequest{status: http.StatusBadRequest, msg: msg}
+ }
+
+ return nil
+}
diff --git a/pkg/validate/request_user_sign_up.go b/pkg/validate/request_user_sign_up.go
deleted file mode 100755
index 0b912d0..0000000
--- a/pkg/validate/request_user_sign_up.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package validate
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
-
- "github.com/go-playground/validator/v10"
-)
-
-func UserInput(r *http.Request, req interface{}) error {
- reqValidator := validator.New()
-
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return fmt.Errorf("cannot decode request data")
- }
-
- if err := reqValidator.Struct(req); err != nil {
- return fmt.Errorf("data validation failed with reason: %s", err.Error())
- }
- return nil
-}