package main import ( "context" "errors" "fmt" "net" "os" "strconv" "strings" "time" userpb "juwan-backend/app/users/rpc/pb" corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" codepb "google.golang.org/genproto/googleapis/rpc/code" statuspb "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) const ( headerAuthUserID = "x-auth-user-id" headerAuthRoleType = "x-auth-role-type" headerAuthIsAdmin = "x-auth-is-admin" headerCookie = "cookie" cookieJToken = "JToken" ) type authzServer struct { authv3.UnimplementedAuthorizationServer userRPC userpb.UsercenterClient } func (s *authzServer) Check(ctx context.Context, req *authv3.CheckRequest) (*authv3.CheckResponse, error) { httpReq := req.GetAttributes().GetRequest().GetHttp() if httpReq == nil { return deny(codepb.Code_INVALID_ARGUMENT, typev3.StatusCode_BadRequest, "missing http attributes"), nil } path := httpReq.GetPath() if isPublicPath(path) { return allow(nil), nil } token, ok := getCookieValue(httpReq.GetHeaders(), cookieJToken) if !ok || token == "" { return deny(codepb.Code_UNAUTHENTICATED, typev3.StatusCode_Unauthorized, "missing JToken cookie"), nil } userIDHeader := getHeader(httpReq.GetHeaders(), headerAuthUserID) if userIDHeader == "" { return deny(codepb.Code_UNAUTHENTICATED, typev3.StatusCode_Unauthorized, "missing x-auth-user-id header"), nil } userID, err := strconv.ParseInt(userIDHeader, 10, 64) if err != nil || userID <= 0 { return deny(codepb.Code_UNAUTHENTICATED, typev3.StatusCode_Unauthorized, "invalid x-auth-user-id"), nil } rpcCtx, cancel := context.WithTimeout(ctx, 1200*time.Millisecond) defer cancel() resp, err := s.userRPC.ValidateToken(rpcCtx, &userpb.ValidateTokenReq{ Token: token, UserId: userID, }) if err != nil { return deny(codepb.Code_UNAUTHENTICATED, typev3.StatusCode_Unauthorized, "validate token failed"), nil } if !resp.GetValid() { return deny(codepb.Code_PERMISSION_DENIED, typev3.StatusCode_Forbidden, "token invalid"), nil } outHeaders := []*corev3.HeaderValueOption{ {Header: &corev3.HeaderValue{Key: headerAuthUserID, Value: strconv.FormatInt(resp.GetUserId(), 10)}}, {Header: &corev3.HeaderValue{Key: headerAuthRoleType, Value: strconv.FormatInt(resp.GetRoleType(), 10)}}, } if getHeader(httpReq.GetHeaders(), headerAuthIsAdmin) != "" { outHeaders = append(outHeaders, &corev3.HeaderValueOption{Header: &corev3.HeaderValue{Key: headerAuthIsAdmin, Value: getHeader(httpReq.GetHeaders(), headerAuthIsAdmin)}}) } return allow(outHeaders), nil } func allow(headers []*corev3.HeaderValueOption) *authv3.CheckResponse { return &authv3.CheckResponse{ Status: &statuspb.Status{Code: int32(codepb.Code_OK)}, HttpResponse: &authv3.CheckResponse_OkResponse{ OkResponse: &authv3.OkHttpResponse{Headers: headers}, }, } } func deny(code codepb.Code, httpCode typev3.StatusCode, message string) *authv3.CheckResponse { return &authv3.CheckResponse{ Status: &statuspb.Status{Code: int32(code), Message: message}, HttpResponse: &authv3.CheckResponse_DeniedResponse{ DeniedResponse: &authv3.DeniedHttpResponse{ Status: &typev3.HttpStatus{Code: httpCode}, Body: fmt.Sprintf(`{"code":%d,"message":"%s"}`, httpCode, message), Headers: []*corev3.HeaderValueOption{ {Header: &corev3.HeaderValue{Key: "content-type", Value: "application/json"}}, }, }, }, } } func isPublicPath(path string) bool { if path == "/healthz" || path == "/api/users/login" || path == "/api/users/register" { return true } return false } func getHeader(headers map[string]string, key string) string { for k, v := range headers { if strings.EqualFold(k, key) { return v } } return "" } func getCookieValue(headers map[string]string, name string) (string, bool) { cookieHeader := getHeader(headers, headerCookie) if cookieHeader == "" { return "", false } parts := strings.Split(cookieHeader, ";") for _, part := range parts { kv := strings.SplitN(strings.TrimSpace(part), "=", 2) if len(kv) != 2 { continue } if kv[0] == name { return kv[1], true } } return "", false } func getEnvWithDefault(key, defaultValue string) string { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return defaultValue } return value } func run() error { listenOn := getEnvWithDefault("LISTEN_ON", "0.0.0.0:9002") userRPCTarget := getEnvWithDefault("USER_RPC_TARGET", "user-rpc-svc.juwan.svc.cluster.local:9001") conn, err := grpc.NewClient(userRPCTarget, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return fmt.Errorf("dial user rpc failed: %w", err) } defer conn.Close() lis, err := net.Listen("tcp", listenOn) if err != nil { return fmt.Errorf("listen failed: %w", err) } grpcServer := grpc.NewServer() authv3.RegisterAuthorizationServer(grpcServer, &authzServer{userRPC: userpb.NewUsercenterClient(conn)}) fmt.Printf("authz-adapter listening on %s, user-rpc target %s\n", listenOn, userRPCTarget) return grpcServer.Serve(lis) } func main() { if err := run(); err != nil { if !errors.Is(err, net.ErrClosed) { panic(err) } } }