From 066d700240748d5241ebe095c3e342f76ed47c78 Mon Sep 17 00:00:00 2001 From: InChul <49394875+inchori@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:57:11 +0900 Subject: [PATCH] feat: add jwt authorization and logging for error --- README.md | 2 +- handler/auth_handler.go | 3 +- handler/post_handler.go | 2 +- main.go | 1 + server/auth_grpc_server.go | 11 +++- server/gateway.go | 11 ++-- server/interceptor/auth.go | 41 ++++++++++--- server/post_grpc_server.go | 121 ++++++++++++++++++++++++++++++++++--- server/user_grpc_server.go | 84 ++++++++++++++++++------- 9 files changed, 225 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 8b76e05..b913e51 100644 --- a/README.md +++ b/README.md @@ -17,5 +17,5 @@ Build a Simple Blog Service with gRPC and goFiber ### ETC - [X] GitHub Actions CI -- [ ] Error Handler +- [X] Error Handle with logging - [ ] Swagger Docs diff --git a/handler/auth_handler.go b/handler/auth_handler.go index 66a2489..3e2fc25 100644 --- a/handler/auth_handler.go +++ b/handler/auth_handler.go @@ -2,12 +2,13 @@ package handler import ( "context" - "github.com/gofiber/fiber/v2" "grpc_identity/dto" "grpc_identity/middleware" "grpc_identity/service" "grpc_identity/utils" "strconv" + + "github.com/gofiber/fiber/v2" ) func NewLoginHandler(app fiber.Router, ctx context.Context, userService service.IUserService) { diff --git a/handler/post_handler.go b/handler/post_handler.go index 3814be8..b3c0e27 100644 --- a/handler/post_handler.go +++ b/handler/post_handler.go @@ -12,7 +12,7 @@ import ( func NewPostHandler(app fiber.Router, ctx context.Context, postService service.IPostService, userService service.IUserService, protected fiber.Handler) { - app.Post("", middleware.Protected(), CreatePost(ctx, postService, userService)) + app.Post("", protected, CreatePost(ctx, postService, userService)) app.Get("/:id", GetPostByID(ctx, postService)) app.Get("/user/:userId", protected, GetPostByUserID(ctx, postService, userService)) app.Delete("/:id", protected, DeleteByID(ctx, postService, userService)) diff --git a/main.go b/main.go index 5aebe1f..dda7eca 100644 --- a/main.go +++ b/main.go @@ -47,6 +47,7 @@ func main() { } methods := []string{ + "proto.v1beta1.post.Post/CreatePost", "proto.v1beta1.post.Post/UpdatePost", "proto.v1beta1.post.Post/DeletePost", "proto.v1beta1.user.Post/GetPostByUser", diff --git a/server/auth_grpc_server.go b/server/auth_grpc_server.go index 93107f0..003b4de 100644 --- a/server/auth_grpc_server.go +++ b/server/auth_grpc_server.go @@ -2,12 +2,14 @@ package server import ( "context" + "fmt" "grpc_identity/middleware" "grpc_identity/pb/v1beta1/auth" "grpc_identity/service" "grpc_identity/utils" "strconv" + "github.com/sirupsen/logrus" "google.golang.org/grpc" ) @@ -25,16 +27,19 @@ func RegisterAuthService(userService service.IUserService, svr *grpc.Server) { func (a *AuthGRPCServiceServer) Login(ctx context.Context, req *auth.LoginRequest) (*auth.LoginResponse, error) { userByEmail, err := a.userService.GetUserByEmail(ctx, req.Email) if err != nil { - return nil, err + logrus.Errorf("failed to get user by email: %v", err) + return nil, fmt.Errorf("failed to get user by email: %v", err) } if !utils.CheckPasswordHash(req.Password, userByEmail.Password) { - return nil, err + logrus.Errorf("invalid password") + return nil, fmt.Errorf("invalid password") } jwtToken, err := middleware.CreateAccessToken(strconv.Itoa(userByEmail.ID)) if err != nil { - return nil, err + logrus.Errorf("failed to create access jwt token: %v", err) + return nil, fmt.Errorf("failed to create access jwt token: %v", err) } return &auth.LoginResponse{Token: jwtToken}, nil diff --git a/server/gateway.go b/server/gateway.go index a5b7681..6cf7b02 100644 --- a/server/gateway.go +++ b/server/gateway.go @@ -2,10 +2,10 @@ package server import ( "context" + "fmt" "grpc_identity/pb/v1beta1/auth" "grpc_identity/pb/v1beta1/post" "grpc_identity/pb/v1beta1/user" - "log" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/sirupsen/logrus" @@ -23,24 +23,25 @@ func GatewayServer(grpcListenAddr string) (*runtime.ServeMux, error) { conn, err := grpc.DialContext(ctx, grpcListenAddr, opts...) if err != nil { logrus.Fatalf("failed to dial gRPC: %v", err) - return nil, err + return nil, fmt.Errorf("failed to dial gRPC: %v", err) } err = auth.RegisterAuthHandler(ctx, mux, conn) if err != nil { logrus.Fatalf("failed to register auth handler gateway: %v", err) - return nil, err + return nil, fmt.Errorf("failed to register auth handler gateway: %v\", err") } err = user.RegisterUserHandler(ctx, mux, conn) if err != nil { logrus.Fatalf("failed to register user handler gateway: %v", err) - return nil, err + return nil, fmt.Errorf("failed to register user handler gateway: %v", err) } err = post.RegisterPostHandler(ctx, mux, conn) if err != nil { - log.Fatal(err) + logrus.Fatalf("failed to register post handler gateway: %v", err) + return nil, fmt.Errorf("failed to register post handler gateway: %v", err) } return mux, nil diff --git a/server/interceptor/auth.go b/server/interceptor/auth.go index 0065e52..b0670a0 100644 --- a/server/interceptor/auth.go +++ b/server/interceptor/auth.go @@ -2,10 +2,12 @@ package interceptor import ( "context" + "fmt" + "github.com/golang-jwt/jwt/v5" grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" + "github.com/sirupsen/logrus" "google.golang.org/grpc" - "log" ) type JWTInterceptor struct { @@ -38,7 +40,7 @@ func (ic *JWTInterceptor) Interceptor(ctx context.Context, req any, info *grpc.U } jwtTokenStr, err := grpc_auth.AuthFromMD(ctx, "Bearer") if err != nil { - log.Fatalf("error jwt from header: %v", err) + logrus.Errorf("error jwt from header: %v", err) return ctx, nil } @@ -46,21 +48,44 @@ func (ic *JWTInterceptor) Interceptor(ctx context.Context, req any, info *grpc.U return []byte("secret"), nil }) if err != nil { - log.Fatalf("error jwt parsing: %v", err) - return nil, err + logrus.Errorf("error jwt parsing: %v", err) + return nil, fmt.Errorf("error jwt parsing: %v", err) } sub, err := token.Claims.GetSubject() if err != nil { - log.Fatalf("invalid sub: %v", err) - return nil, err + logrus.Errorf("invalid sub: %v", err) + return nil, fmt.Errorf("invalid sub: %v", err) } if !token.Valid { - log.Fatalf("invalid jwt: %v", err) - return nil, err + logrus.Errorf("invalid jwt token: %v", err) + return nil, fmt.Errorf("invalid jwt token: %v", err) } newCtx := context.WithValue(ctx, ContextKeyAuthenticated{}, sub) return newCtx, nil } + +func ExtractTokenFromMetadata(jwtToken string) (string, error) { + token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) { + return []byte("secret"), nil + }) + if err != nil { + logrus.Errorf("error jwt parsing: %v", err) + return "", fmt.Errorf("error jwt parsing: %v", err) + } + + sub, err := token.Claims.GetSubject() + if err != nil { + logrus.Errorf("invalid sub from claims: %v", err) + return "", fmt.Errorf("invalid sub from claims: %v", err) + } + + if !token.Valid { + logrus.Errorf("invalid jwt token: %v", err) + return "", fmt.Errorf("invalid jwt token: %v", err) + } + + return sub, nil +} diff --git a/server/post_grpc_server.go b/server/post_grpc_server.go index 8fb8513..99bd921 100644 --- a/server/post_grpc_server.go +++ b/server/post_grpc_server.go @@ -2,9 +2,15 @@ package server import ( "context" - "google.golang.org/grpc" + "fmt" "grpc_identity/pb/v1beta1/post" + "grpc_identity/server/interceptor" "grpc_identity/service" + "strconv" + + grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" + "github.com/sirupsen/logrus" + "google.golang.org/grpc" ) type PostGRPCServiceServer struct { @@ -21,14 +27,35 @@ func RegisterPostService(postService service.IPostService, userService service.I } func (p *PostGRPCServiceServer) CreatePost(ctx context.Context, req *post.CreatePostRequest) (*post.CreatePostResponse, error) { - userByID, err := p.userService.GetUserByID(ctx, int(req.UserId)) + jwtToken, err := grpc_auth.AuthFromMD(ctx, "Bearer") + if err != nil { + logrus.Errorf("failed to get jwt token: %v", err) + return nil, fmt.Errorf("failed to get jwt token: %v", err) + } + + tokenClaimsID, err := interceptor.ExtractTokenFromMetadata(jwtToken) if err != nil { - return nil, err + logrus.Errorf("failed to extract token from metadata: %v", err) + return nil, fmt.Errorf("failed to extract token from metadata: %v", err) + } + + tokenID, _ := strconv.Atoi(tokenClaimsID) + fmt.Println(tokenID) + userByID, err := p.userService.GetUserByID(ctx, tokenID) + if err != nil { + logrus.Errorf("failed to get user: %v", err) + return nil, fmt.Errorf("failed to get user: %v", err) + } + + if userByID.ID != tokenID { + logrus.Errorf("unauthorized") + return nil, fmt.Errorf("unauthorzied") } createPost, err := p.postService.CreatePost(ctx, req.Title, req.Content, userByID) if err != nil { - return nil, err + logrus.Errorf("failed to create post: %v", err) + return nil, fmt.Errorf("failed to create post: %v", err) } postRes := &post.PostMessage{ @@ -43,7 +70,8 @@ func (p *PostGRPCServiceServer) CreatePost(ctx context.Context, req *post.Create func (p *PostGRPCServiceServer) GetPost(ctx context.Context, req *post.GetPostByIDRequest) (*post.GetPostByIDResponse, error) { postByID, err := p.postService.GetPostByID(ctx, int(req.Id)) if err != nil { - return nil, err + logrus.Errorf("failed to get post by ID: %v", err) + return nil, fmt.Errorf("failed to get post by ID: %v", err) } postRes := &post.PostMessage{ @@ -56,9 +84,34 @@ func (p *PostGRPCServiceServer) GetPost(ctx context.Context, req *post.GetPostBy } func (p *PostGRPCServiceServer) GetPostByUser(ctx context.Context, req *post.GetPostByUserRequest) (*post.GetPostByUserResponse, error) { + jwtToken, err := grpc_auth.AuthFromMD(ctx, "Bearer") + if err != nil { + logrus.Errorf("failed to get jwt token: %v", err) + return nil, fmt.Errorf("failed to get jwt token: %v", err) + } + + tokenClaimsID, err := interceptor.ExtractTokenFromMetadata(jwtToken) + if err != nil { + logrus.Errorf("failed to extract token from metadata: %v", err) + return nil, fmt.Errorf("failed to extract token from metadata: %v", err) + } + + tokenID, _ := strconv.Atoi(tokenClaimsID) + userByID, err := p.userService.GetUserByID(ctx, tokenID) + if err != nil { + logrus.Errorf("failed to get user: %v", err) + return nil, fmt.Errorf("failed to get user: %v", err) + } + + if userByID.ID != tokenID { + logrus.Errorf("unauthorized") + return nil, fmt.Errorf("unauthorzied") + } + postsByUserID, err := p.postService.GetPostByUserID(ctx, int(req.UserId)) if err != nil { - return nil, err + logrus.Errorf("failed to get post by user: %v", err) + return nil, fmt.Errorf("failed to get post by user: %v", err) } var postResponses []*post.PostMessage @@ -75,18 +128,68 @@ func (p *PostGRPCServiceServer) GetPostByUser(ctx context.Context, req *post.Get } func (p *PostGRPCServiceServer) DeletePost(ctx context.Context, req *post.DeletePostRequest) (*post.DeletePostResponse, error) { - err := p.postService.DeleteByID(ctx, int(req.Id)) + jwtToken, err := grpc_auth.AuthFromMD(ctx, "Bearer") if err != nil { - return nil, err + logrus.Errorf("failed to get jwt token: %v", err) + return nil, fmt.Errorf("failed to get jwt token: %v", err) + } + + tokenClaimsID, err := interceptor.ExtractTokenFromMetadata(jwtToken) + if err != nil { + logrus.Errorf("failed to extract token from metadata: %v", err) + return nil, fmt.Errorf("failed to extract token from metadata: %v", err) + } + + tokenID, _ := strconv.Atoi(tokenClaimsID) + userByID, err := p.userService.GetUserByID(ctx, tokenID) + if err != nil { + logrus.Errorf("failed to get user by ID: %v", err) + return nil, fmt.Errorf("failed to get user by ID: %v", err) + } + + if userByID.ID != tokenID { + logrus.Errorf("unauthorized") + return nil, fmt.Errorf("unauthorzied") + } + + err = p.postService.DeleteByID(ctx, int(req.Id)) + if err != nil { + logrus.Errorf("failed to delete by ID: %v", err) + return nil, fmt.Errorf("failed to delete by ID: %v", err) } return &post.DeletePostResponse{}, nil } func (p *PostGRPCServiceServer) UpdatePost(ctx context.Context, req *post.UpdatePostRequest) (*post.UpdatePostResponse, error) { + jwtToken, err := grpc_auth.AuthFromMD(ctx, "Bearer") + if err != nil { + logrus.Errorf("failed to get jwt token: %v", err) + return nil, fmt.Errorf("failed to get jwt token: %v", err) + } + + tokenClaimsID, err := interceptor.ExtractTokenFromMetadata(jwtToken) + if err != nil { + logrus.Errorf("failed to extract token from metadata: %v", err) + return nil, fmt.Errorf("failed to extract token from metadata: %v", err) + } + + tokenID, _ := strconv.Atoi(tokenClaimsID) + userByID, err := p.userService.GetUserByID(ctx, tokenID) + if err != nil { + logrus.Errorf("failed to get user: %v", err) + return nil, fmt.Errorf("failed to get user: %v", err) + } + + if userByID.ID != tokenID { + logrus.Error("unauthorized") + return nil, fmt.Errorf("unauthorzied") + } + updatePost, err := p.postService.UpdatePost(ctx, req.Content, req.Title, int(req.Id)) if err != nil { - return nil, err + logrus.Errorf("failed to update post: %v", err) + return nil, fmt.Errorf("failed to update post: %v", err) } postMsg := &post.PostMessage{ diff --git a/server/user_grpc_server.go b/server/user_grpc_server.go index 85af199..9cfc4d3 100644 --- a/server/user_grpc_server.go +++ b/server/user_grpc_server.go @@ -2,13 +2,16 @@ package server import ( "context" - "errors" - "github.com/golang-jwt/jwt/v5" - grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" - "google.golang.org/grpc" + "fmt" "grpc_identity/pb/v1beta1/user" + "grpc_identity/server/interceptor" "grpc_identity/service" + "grpc_identity/utils" "strconv" + + grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" + "github.com/sirupsen/logrus" + "google.golang.org/grpc" ) type UserGRPCServiceServer struct { @@ -23,9 +26,16 @@ func RegisterUserService(userService service.IUserService, svr *grpc.Server) { } func (u *UserGRPCServiceServer) CreateUser(ctx context.Context, req *user.CreateUserRequest) (*user.CreateUserResponse, error) { - createUser, err := u.userService.CreateUser(ctx, req.Name, req.Email, req.Password) + hashPassword, err := utils.HashPassword(req.Password) if err != nil { - return nil, err + logrus.Errorf("failed to hash password: %v", err) + return nil, fmt.Errorf("failed to hash password: %v", err) + } + + createUser, err := u.userService.CreateUser(ctx, req.Name, req.Email, hashPassword) + if err != nil { + logrus.Errorf("failed to create user: %v", err) + return nil, fmt.Errorf("failed to create user: %v", err) } userResp := &user.CreateUserResponse{ @@ -40,7 +50,8 @@ func (u *UserGRPCServiceServer) CreateUser(ctx context.Context, req *user.Create func (u *UserGRPCServiceServer) GetUserByID(ctx context.Context, req *user.GetUserByIDRequest) (*user.GetUserByIDResponse, error) { userByID, err := u.userService.GetUserByID(ctx, int(req.Id)) if err != nil { - return nil, err + logrus.Errorf("failed to get user by ID: %v", err) + return nil, fmt.Errorf("failed to get user by ID: %v", err) } userMsg := &user.UserMessage{ @@ -55,7 +66,8 @@ func (u *UserGRPCServiceServer) GetUserByID(ctx context.Context, req *user.GetUs func (u *UserGRPCServiceServer) GetUserByName(ctx context.Context, req *user.GetUserByNameRequest) (*user.GetUserByNameResponse, error) { userByName, err := u.userService.GetUserByName(ctx, req.Name) if err != nil { - return nil, err + logrus.Errorf("failed to get user by name: %v", err) + return nil, fmt.Errorf("failed to get user by name: %v", err) } userMsg := &user.UserMessage{ @@ -70,7 +82,8 @@ func (u *UserGRPCServiceServer) GetUserByName(ctx context.Context, req *user.Get func (u *UserGRPCServiceServer) GetUserByEmail(ctx context.Context, req *user.GetUserByEmailRequest) (*user.GetUserByEmailResponse, error) { userByEmail, err := u.userService.GetUserByEmail(ctx, req.Email) if err != nil { - return nil, err + logrus.Errorf("failed to get user by email: %v", err) + return nil, fmt.Errorf("failed to get user by email: %v", err) } userMsg := &user.UserMessage{ @@ -85,40 +98,65 @@ func (u *UserGRPCServiceServer) GetUserByEmail(ctx context.Context, req *user.Ge func (u *UserGRPCServiceServer) DeleteUser(ctx context.Context, req *user.DeleteUserRequest) (*user.DeleteUserResponse, error) { jwtToken, err := grpc_auth.AuthFromMD(ctx, "Bearer") if err != nil { - return nil, err - } - if jwtToken == "" { - return nil, errors.New("empty token") + logrus.Errorf("failed to get jwt token: %v", err) + return nil, fmt.Errorf("failed to get jwt token: %v", err) } - token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) { - return []byte("secret"), nil - }) + tokenClaimsID, err := interceptor.ExtractTokenFromMetadata(jwtToken) if err != nil { - return nil, err + logrus.Errorf("failed to extract token from metadata: %v", err) + return nil, fmt.Errorf("failed to extract token from metadata: %v", err) } - subID, err := token.Claims.GetSubject() + tokenID, _ := strconv.Atoi(tokenClaimsID) + userByID, err := u.userService.GetUserByID(ctx, tokenID) if err != nil { - return nil, err + logrus.Errorf("failed to get user: %v", err) + return nil, fmt.Errorf("failed to get user: %v", err) } - if subID != strconv.Itoa(int(req.Id)) { - return nil, err + if userByID.ID != tokenID { + logrus.Errorf("unauthorized") + return nil, fmt.Errorf("unauthorzied") } err = u.userService.DeleteByID(ctx, int(req.Id)) if err != nil { - return nil, err + logrus.Errorf("failed to delete user: %v", err) + return nil, fmt.Errorf("failed to delete user: %v", err) } return &user.DeleteUserResponse{}, nil } func (u *UserGRPCServiceServer) UpdateUser(ctx context.Context, req *user.UpdateUserRequest) (*user.UpdateUserResponse, error) { + jwtToken, err := grpc_auth.AuthFromMD(ctx, "Bearer") + if err != nil { + logrus.Errorf("failed to get jwt token: %v", err) + return nil, fmt.Errorf("failed to get jwt token: %v", err) + } + + tokenClaimsID, err := interceptor.ExtractTokenFromMetadata(jwtToken) + if err != nil { + logrus.Errorf("failed to extract token from metadata: %v", err) + return nil, fmt.Errorf("failed to extract token from metadata: %v", err) + } + + tokenID, _ := strconv.Atoi(tokenClaimsID) + userByID, err := u.userService.GetUserByID(ctx, tokenID) + if err != nil { + logrus.Errorf("failed to get user by ID: %v", err) + return nil, fmt.Errorf("failed to get user by ID: %v", err) + } + + if userByID.ID != tokenID { + logrus.Errorf("unauthorized") + return nil, fmt.Errorf("unauthorzied") + } updateUser, err := u.userService.UpdateUser(ctx, req.Name, req.Password, int(req.Id)) if err != nil { - return nil, err + logrus.Errorf("failed to update user: %v", err) + return nil, fmt.Errorf("failed to update user: %v", err) } userResp := &user.UpdateUserResponse{