Skip to content

Commit

Permalink
Merge pull request #12 from Delavalom/ft/add-grpc-gateway
Browse files Browse the repository at this point in the history
Ft/add grpc gateway
  • Loading branch information
Delavalom authored Oct 18, 2023
2 parents 260460b + fa6a3f0 commit 50d6baa
Show file tree
Hide file tree
Showing 28 changed files with 2,375 additions and 6 deletions.
15 changes: 14 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,17 @@ server:
mock:
mockgen -package mockdb -destination db/mock/store.go github.com/Delavalom/RBD/db/sqlc Store

.PHONY: postgres createdb dropdb migrateup migratedown sqlc test server mock migrateup1 migratedown1
proto:
rm -f pb/*.go
rm -f public/docs/swagger/*.swagger.json
protoc --proto_path=proto --go_out=pb --go_opt=paths=source_relative \
--go-grpc_out=pb --go-grpc_opt=paths=source_relative \
--grpc-gateway_out=pb --grpc-gateway_opt=paths=source_relative \
--openapiv2_out=public/docs/swagger --openapiv2_opt=allow_merge=true,merge_file_name=RBD \
proto/*.proto
statik -src=./public/docs/swagger -dest=./public/docs

evans:
evans --host localhost --port 9090 -r repl

.PHONY: postgres createdb dropdb migrateup migratedown sqlc test server mock migrateup1 migratedown1 proto evans
2 changes: 2 additions & 0 deletions app.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
DB_SOURCE=postgresql://root:secret@localhost:5432/rdb?sslmode=disable
HTTP_SERVER_ADDRESS=0.0.0.0:8080
GRPC_SERVER_ADDRESS=0.0.0.0:9090
TOKEN_SYMMETRIC_KEY=12345678901234567890123456789012
ACCESS_TOKEN_DURATION=15m
REFRESH_TOKEN_DURATION=24h
17 changes: 17 additions & 0 deletions gapi/converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package gapi

import (
db "github.com/Delavalom/RBD/db/sqlc"
"github.com/Delavalom/RBD/pb"
"google.golang.org/protobuf/types/known/timestamppb"
)

func convertUser(user db.User) *pb.User {
return &pb.User{
Username: user.Username,
FullName: user.FullName,
Email: user.Email,
PasswordChangeAt: timestamppb.New(user.PasswordChangedAt),
CreatedAt: timestamppb.New(user.CreatedAt),
}
}
25 changes: 25 additions & 0 deletions gapi/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package gapi

import (
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func fieldViolation(field string, err error) *errdetails.BadRequest_FieldViolation {
return &errdetails.BadRequest_FieldViolation{
Field: field,
Description: err.Error(),
}
}

func invalidArgumentError(violations []*errdetails.BadRequest_FieldViolation) error {
badRequest := &errdetails.BadRequest{FieldViolations: violations}
statusInvalid := status.New(codes.InvalidArgument, "invalid parameters")

statusDetails, err := statusInvalid.WithDetails(badRequest)
if err != nil {
return statusInvalid.Err()
}
return statusDetails.Err()
}
41 changes: 41 additions & 0 deletions gapi/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gapi

import (
"context"

"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)

const (
grpcGatewayUserAgentHeader = "grpcgateway-user-agent"
userAgentHeader = "user-agent"
xForwardedForHeader = "x-forwarded-for"
)

type Metadata struct {
UserAgent string
ClientIP string
}

func (server *Server) extractMetaData(ctx context.Context) *Metadata {
mtdt := &Metadata{}

if data, ok := metadata.FromIncomingContext(ctx); ok {
if userAgents := data.Get(grpcGatewayUserAgentHeader); len(userAgents) > 0 {
mtdt.UserAgent = userAgents[0]
}
if userAgents := data.Get(userAgentHeader); len(userAgents) > 0 {
mtdt.UserAgent = userAgents[0]
}
if clientIPs := data.Get(xForwardedForHeader); len(clientIPs) > 0 {
mtdt.ClientIP = clientIPs[0]
}
}

if peers, ok := peer.FromContext(ctx); ok {
mtdt.ClientIP = peers.Addr.String()
}

return mtdt
}
61 changes: 61 additions & 0 deletions gapi/rpc_create_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package gapi

import (
"context"

db "github.com/Delavalom/RBD/db/sqlc"
"github.com/Delavalom/RBD/pb"
"github.com/Delavalom/RBD/util"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func (server *Server) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
violations := validateCreateUserRequest(req)
if violations != nil {
return nil, invalidArgumentError(violations)
}
hashedPassword, err := util.HashPassword(req.Password)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to hash password: %s", err)
}

arg := db.CreateUserParams{
Username: req.GetUsername(),
HashedPassword: hashedPassword,
FullName: req.GetFullName(),
Email: req.GetEmail(),
}

user, err := server.store.CreateUser(ctx, arg)
if err != nil {
errCode := db.ErrorCode(err)
if errCode == db.UniqueViolation {
return nil, status.Errorf(codes.AlreadyExists, "username already exists: %s", err)
}
return nil, status.Errorf(codes.Internal, "failed to create user: %s", err)
}

rsp := &pb.CreateUserResponse{
User: convertUser(user),
}
return rsp, nil
}

func validateCreateUserRequest(req *pb.CreateUserRequest) (violations []*errdetails.BadRequest_FieldViolation) {
validator := util.NewValidator()
if err := validator.ValidateUsername(req.GetUsername()); err != nil {
violations = append(violations, fieldViolation("username", err))
}
if err := validator.ValidateFullName(req.GetFullName()); err != nil {
violations = append(violations, fieldViolation("full_name", err))
}
if err := validator.ValidateEmail(req.GetEmail()); err != nil {
violations = append(violations, fieldViolation("email", err))
}
if err := validator.ValidatePassword(req.GetPassword()); err != nil {
violations = append(violations, fieldViolation("password", err))
}
return
}
88 changes: 88 additions & 0 deletions gapi/rpc_login_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package gapi

import (
"context"
"errors"

db "github.com/Delavalom/RBD/db/sqlc"
"github.com/Delavalom/RBD/pb"
"github.com/Delavalom/RBD/util"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)

func (server *Server) LoginUser(ctx context.Context, req *pb.LoginUserRequest) (*pb.LoginUserResponse, error) {
violations := validateLoginUserRequest(req)
if violations != nil {
return nil, invalidArgumentError(violations)
}
user, err := server.store.GetUser(ctx, req.GetUsername())
if err != nil {
if errors.Is(err, db.ErrRecordNotFound) {
return nil, status.Errorf(codes.NotFound, "user not found: %s", err)
}
return nil, status.Errorf(codes.Internal, "failed to find user: %s", err)
}

err = util.CheckPassword(req.Password, user.HashedPassword)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "incorrect password: %s", err)
}

accessToken, accessPayload, err := server.tokenMaker.CreateToken(
user.Username,
server.config.AccessTokenDuration,
)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create access token: %s", err)
}

refreshToken, refreshPayload, err := server.tokenMaker.CreateToken(
user.Username,
server.config.RefreshTokenDuration,
)

if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create refresh token: %s", err)
}

extractMetaData := server.extractMetaData(ctx)

session, err := server.store.CreateSession(ctx, db.CreateSessionParams{
ID: refreshPayload.ID,
Username: user.Username,
RefreshToken: refreshToken,
UserAgent: extractMetaData.UserAgent,
ClientIp: extractMetaData.ClientIP,
IsBlocked: false,
ExpiresAt: refreshPayload.ExpiredAt,
})

if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create session: %s", err)
}

rsp := &pb.LoginUserResponse{
User: convertUser(user),
SessionId: session.ID.String(),
AccessToken: accessToken,
RefreshToken: refreshToken,
AccessTokenExpiresAt: timestamppb.New(accessPayload.ExpiredAt),
RefreshTokenExpiresAt: timestamppb.New(refreshPayload.ExpiredAt),
}

return rsp, nil
}

func validateLoginUserRequest(req *pb.LoginUserRequest) (violations []*errdetails.BadRequest_FieldViolation) {
validator := util.NewValidator()
if err := validator.ValidateUsername(req.GetUsername()); err != nil {
violations = append(violations, fieldViolation("username", err))
}
if err := validator.ValidatePassword(req.GetPassword()); err != nil {
violations = append(violations, fieldViolation("password", err))
}
return
}
33 changes: 33 additions & 0 deletions gapi/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package gapi

import (
"fmt"

db "github.com/Delavalom/RBD/db/sqlc"
"github.com/Delavalom/RBD/pb"
"github.com/Delavalom/RBD/token"
"github.com/Delavalom/RBD/util"
)

// Server serves gRPC requests for our banking service.
type Server struct {
pb.UnimplementedRBDServer
config util.Config
store db.Store
tokenMaker token.Maker
}

// NewServer creates a new gRPC server.
func NewServer(config util.Config, store db.Store) (*Server, error) {
tokenMaker, err := token.NewPasetoMaker(config.TokenSymmetricKey)
if err != nil {
return nil, fmt.Errorf("cannot create token maker: %w", err)
}
server := &Server{
config: config,
store: store,
tokenMaker: tokenMaker,
}

return server, nil
}
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@ go 1.21.1

require (
github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29
github.com/golang/protobuf v1.5.3
github.com/google/uuid v1.3.1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0
github.com/jackc/pgx/v5 v5.4.3
github.com/o1egl/paseto v1.0.0
github.com/spf13/viper v1.16.0
github.com/stretchr/testify v1.8.4
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d
google.golang.org/grpc v1.58.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
)

require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
)

require (
Expand Down Expand Up @@ -55,10 +63,10 @@ require (
golang.org/x/arch v0.5.0 // indirect
golang.org/x/crypto v0.14.0
golang.org/x/net v0.15.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
google.golang.org/protobuf v1.31.0
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 50d6baa

Please sign in to comment.