126 lines
3.6 KiB
Go
126 lines
3.6 KiB
Go
|
|
package interceptor
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"github.com/cloudprint/cloudprint-backend/pkg/jwt"
|
||
|
|
"google.golang.org/grpc"
|
||
|
|
"google.golang.org/grpc/codes"
|
||
|
|
"google.golang.org/grpc/metadata"
|
||
|
|
"google.golang.org/grpc/status"
|
||
|
|
)
|
||
|
|
|
||
|
|
const (
|
||
|
|
AuthorizationHeader = "authorization"
|
||
|
|
BearerPrefix = "Bearer "
|
||
|
|
UserIDKey = "user_id"
|
||
|
|
UserTypeKey = "user_type"
|
||
|
|
)
|
||
|
|
|
||
|
|
type AuthInterceptor struct {
|
||
|
|
jwtManager *jwt.JWTManager
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewAuthInterceptor(jwtManager *jwt.JWTManager) *AuthInterceptor {
|
||
|
|
return &AuthInterceptor{jwtManager: jwtManager}
|
||
|
|
}
|
||
|
|
|
||
|
|
// UnaryServerInterceptor returns a unary server interceptor that handles authentication
|
||
|
|
func (i *AuthInterceptor) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
|
||
|
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||
|
|
// Skip auth for certain methods
|
||
|
|
if isPublicMethod(info.FullMethod) {
|
||
|
|
return handler(ctx, req)
|
||
|
|
}
|
||
|
|
|
||
|
|
claims, err := i.extractAndValidateToken(ctx)
|
||
|
|
if err != nil {
|
||
|
|
return nil, status.Errorf(codes.Unauthenticated, "invalid token: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add user info to context
|
||
|
|
ctx = context.WithValue(ctx, UserIDKey, claims.UserID)
|
||
|
|
ctx = context.WithValue(ctx, UserTypeKey, claims.UserType)
|
||
|
|
|
||
|
|
return handler(ctx, req)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// StreamServerInterceptor returns a streaming server interceptor that handles authentication
|
||
|
|
func (i *AuthInterceptor) StreamServerInterceptor() grpc.StreamServerInterceptor {
|
||
|
|
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||
|
|
// Skip auth for certain methods
|
||
|
|
if isPublicMethod(info.FullMethod) {
|
||
|
|
return handler(srv, ss)
|
||
|
|
}
|
||
|
|
|
||
|
|
claims, err := i.extractAndValidateToken(ss.Context())
|
||
|
|
if err != nil {
|
||
|
|
return status.Errorf(codes.Unauthenticated, "invalid token: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add user info to context
|
||
|
|
ctx := context.WithValue(ss.Context(), UserIDKey, claims.UserID)
|
||
|
|
ctx = context.WithValue(ctx, UserTypeKey, claims.UserType)
|
||
|
|
|
||
|
|
wrapped := &serverStreamWrapper{
|
||
|
|
ServerStream: ss,
|
||
|
|
ctx: ctx,
|
||
|
|
}
|
||
|
|
|
||
|
|
return handler(srv, wrapped)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (i *AuthInterceptor) extractAndValidateToken(ctx context.Context) (*jwt.Claims, error) {
|
||
|
|
md, ok := metadata.FromIncomingContext(ctx)
|
||
|
|
if !ok {
|
||
|
|
return nil, status.Error(codes.Unauthenticated, "missing metadata")
|
||
|
|
}
|
||
|
|
|
||
|
|
authHeader := md.Get(AuthorizationHeader)
|
||
|
|
if len(authHeader) == 0 {
|
||
|
|
return nil, status.Error(codes.Unauthenticated, "missing authorization header")
|
||
|
|
}
|
||
|
|
|
||
|
|
tokenString := strings.TrimPrefix(authHeader[0], BearerPrefix)
|
||
|
|
if tokenString == authHeader[0] {
|
||
|
|
return nil, status.Error(codes.Unauthenticated, "invalid authorization format")
|
||
|
|
}
|
||
|
|
|
||
|
|
return i.jwtManager.ValidateToken(tokenString)
|
||
|
|
}
|
||
|
|
|
||
|
|
func isPublicMethod(method string) bool {
|
||
|
|
publicMethods := map[string]bool{
|
||
|
|
"/cloudprint.AuthService/Login": true,
|
||
|
|
"/cloudprint.AuthService/RefreshToken": true,
|
||
|
|
"/cloudprint.AdminService/Login": true,
|
||
|
|
"/cloudprint.PayService/HandlePayCallback": true,
|
||
|
|
}
|
||
|
|
return publicMethods[method]
|
||
|
|
}
|
||
|
|
|
||
|
|
// serverStreamWrapper wraps grpc.ServerStream to override Context method
|
||
|
|
type serverStreamWrapper struct {
|
||
|
|
grpc.ServerStream
|
||
|
|
ctx context.Context
|
||
|
|
}
|
||
|
|
|
||
|
|
func (w *serverStreamWrapper) Context() context.Context {
|
||
|
|
return w.ctx
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetUserIDFromContext extracts user ID from context
|
||
|
|
func GetUserIDFromContext(ctx context.Context) (int32, bool) {
|
||
|
|
userID, ok := ctx.Value(UserIDKey).(int32)
|
||
|
|
return userID, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetUserTypeFromContext extracts user type from context
|
||
|
|
func GetUserTypeFromContext(ctx context.Context) (string, bool) {
|
||
|
|
userType, ok := ctx.Value(UserTypeKey).(string)
|
||
|
|
return userType, ok
|
||
|
|
}
|