Skip to content

Commit

Permalink
Merge branch 'purposeinplay:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgepurposeinplay authored Jan 16, 2025
2 parents e16261b + a751dd8 commit dd1f08e
Show file tree
Hide file tree
Showing 41 changed files with 1,046 additions and 527 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codeql_httpserver.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/lint-test_httpserver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:

env:
PACKAGE: httpserver
GO_VERSION: 1.19
GO_VERSION: stable

name: test-httpserver
jobs:
Expand All @@ -18,15 +18,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}

- name: Lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
version: latest
args: --timeout=5m -c=$GITHUB_WORKSPACE/.golangci.yml
Expand All @@ -37,10 +37,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}

Expand Down
4 changes: 4 additions & 0 deletions auth/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
test:
go test -race -timeout=10s ./...
lint:
golangci-lint run --fix -c=../.golangci.yml
28 changes: 15 additions & 13 deletions auth/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,40 @@ import (
"log"

"go.uber.org/zap"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"

"google.golang.org/grpc/status"

"google.golang.org/grpc/metadata"

"google.golang.org/grpc"
"google.golang.org/grpc/status"
)

type AuthInterceptor struct {
// Interceptor is a server interceptor to authenticate and authorize requests.
type Interceptor struct {
logger *zap.Logger
jwtManager *JWTManager
authRoles map[string][]string
}

func NewAuthInterceptor(logger *zap.Logger, jwtManager *JWTManager, authRoles map[string][]string) *AuthInterceptor {
return &AuthInterceptor{
// NewAuthInterceptor creates a new authorizer interceptor.
func NewAuthInterceptor(
logger *zap.Logger,
jwtManager *JWTManager,
authRoles map[string][]string,
) *Interceptor {
return &Interceptor{
logger: logger,
jwtManager: jwtManager,
authRoles: authRoles,
}
}

// Unary returns a server interceptor function to authenticate and authorize unary RPC.
func (i *AuthInterceptor) Unary() grpc.UnaryServerInterceptor {
func (i *Interceptor) Unary() grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req interface{},
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
) (any, error) {
log.Println("--> unary interceptor: ", info.FullMethod)

ctxAuth, err := i.authorize(ctx, info.FullMethod)
Expand All @@ -48,7 +50,7 @@ func (i *AuthInterceptor) Unary() grpc.UnaryServerInterceptor {
}
}

func (i *AuthInterceptor) authorize(ctx context.Context, method string) (context.Context, error) {
func (i *Interceptor) authorize(ctx context.Context, method string) (context.Context, error) {
authRoles, ok := i.authRoles[method]
if !ok {
// public route
Expand Down
33 changes: 18 additions & 15 deletions auth/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,39 @@ import (
"context"

"github.com/dgrijalva/jwt-go"

"go.uber.org/zap"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"

"google.golang.org/grpc/status"

"google.golang.org/grpc/metadata"

"google.golang.org/grpc"
"google.golang.org/grpc/status"
)

// AuthorizerConfig is the configuration for the authorizer interceptor.
type AuthorizerConfig struct {
Secret string
}

// AuthorizerInterceptor is a server interceptor to authenticate and authorize requests.
type AuthorizerInterceptor struct {
logger *zap.Logger
jwtManager *JWTManager
config *AuthorizerConfig
}

// ProviderClaims is a custom JWT claims that contains some provider's information.
type ProviderClaims struct {
jwt.StandardClaims
}

// CtxProviderClaimsKey is a context key for provider claims.
type CtxProviderClaimsKey struct{}

func NewAuthorizerInterceptor(logger *zap.Logger, jwtManager *JWTManager, config *AuthorizerConfig) *AuthorizerInterceptor {
// NewAuthorizerInterceptor creates a new authorizer interceptor.
func NewAuthorizerInterceptor(
logger *zap.Logger,
jwtManager *JWTManager,
config *AuthorizerConfig,
) *AuthorizerInterceptor {
return &AuthorizerInterceptor{
logger: logger,
jwtManager: jwtManager,
Expand All @@ -44,10 +48,10 @@ func NewAuthorizerInterceptor(logger *zap.Logger, jwtManager *JWTManager, config
func (i *AuthorizerInterceptor) Unary() grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req interface{},
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
) (any, error) {
authorize, err := i.authorize(ctx, info.FullMethod)
if err != nil {
return nil, err
Expand All @@ -57,7 +61,7 @@ func (i *AuthorizerInterceptor) Unary() grpc.UnaryServerInterceptor {
}
}

func (i *AuthorizerInterceptor) authorize(ctx context.Context, method string) (context.Context, error) {
func (i *AuthorizerInterceptor) authorize(ctx context.Context, _ string) (context.Context, error) {
md, ok := metadata.FromIncomingContext(ctx)

if !ok {
Expand All @@ -74,14 +78,13 @@ func (i *AuthorizerInterceptor) authorize(ctx context.Context, method string) (c

p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}

_, err := p.ParseWithClaims(signature[0], &claims, func(token *jwt.Token) (interface{}, error) {
_, err := p.ParseWithClaims(signature[0], &claims, func(*jwt.Token) (any, error) {
return []byte(i.config.Secret), nil
})
if err != nil {
// nolint: revive
return nil, status.Errorf(codes.Unauthenticated, "you are not authorized to access this resource")
}

ctx = context.WithValue(ctx, CtxProviderClaimsKey{}, claims)

return ctx, nil
return context.WithValue(ctx, CtxProviderClaimsKey{}, claims), nil
}
23 changes: 14 additions & 9 deletions auth/go.mod
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
module github.com/purposeinplay/go-commons/auth

go 1.18
go 1.22

toolchain go1.23.4

require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
go.uber.org/zap v1.26.0
google.golang.org/grpc v1.60.1
github.com/stretchr/testify v1.8.1
go.uber.org/zap v1.27.0
google.golang.org/grpc v1.68.1
)

require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect
google.golang.org/protobuf v1.32.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect
google.golang.org/protobuf v1.35.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
56 changes: 33 additions & 23 deletions auth/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions auth/jwt_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package auth

import (
"context"
"errors"
"fmt"
"time"

Expand Down Expand Up @@ -40,15 +41,21 @@ func NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager {
return &JWTManager{secretKey, tokenDuration}
}

// JWT parsing errors.
var (
ErrInvalidTokenClaims = errors.New("invalid token claims")
ErrUnexpectedTokenSigningMethod = errors.New("unexpected token signing method")
)

// Verify verifies the access token string and return a user claim if the token is valid.
func (m *JWTManager) Verify(accessToken string) (*UserClaims, error) {
token, err := jwt.ParseWithClaims(
accessToken,
&UserClaims{},
func(token *jwt.Token) (interface{}, error) {
func(token *jwt.Token) (any, error) {
_, ok := token.Method.(*jwt.SigningMethodHMAC)
if !ok {
return nil, fmt.Errorf("unexpected token signing method")
return nil, ErrUnexpectedTokenSigningMethod
}

return []byte(m.secretKey), nil
Expand All @@ -60,7 +67,7 @@ func (m *JWTManager) Verify(accessToken string) (*UserClaims, error) {

claims, ok := token.Claims.(*UserClaims)
if !ok {
return nil, fmt.Errorf("invalid token claims")
return nil, ErrInvalidTokenClaims
}

return claims, nil
Expand Down
13 changes: 10 additions & 3 deletions auth/metadata.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
package auth

import (
"fmt"
"errors"
"strings"

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

// GRPC Metadata auth errors.
var (
ErrAuthTokenMissing = errors.New("auth token is missing")
ErrBadAuthString = errors.New("bad authorization string")
)

// ExtractTokenFromMetadata extracts the token from the grpc metadata.
func ExtractTokenFromMetadata(md metadata.MD) (string, error) {
meta, ok := md["authorization"]
if !ok {
return "", fmt.Errorf("auth token is missing")
return "", ErrAuthTokenMissing
}

auth := meta[0]

const prefix = "Bearer "

if !strings.HasPrefix(auth, prefix) {
return "", fmt.Errorf("bad authorization string")
return "", ErrBadAuthString
}

return auth[len(prefix):], nil
Expand Down
Loading

0 comments on commit dd1f08e

Please sign in to comment.