### Create AuthenticationResult Example Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/types.md An example demonstrating how to instantiate an AuthenticationResult with context, principal, and attributes. ```go result := &connectpermify.AuthenticationResult{ Context: ctx, Principal: &connectpermify.Resource{ Type: "user", ID: "user-123", }, Attributes: connectpermify.Attributes{ "email": "alice@example.com", "role": "admin", }, } ``` -------------------------------- ### Install ConnectRPC Permify Source: https://github.com/nrf110/connectrpc-permify/blob/main/README.md Install the library using go get. ```bash go get github.com/nrf110/connectrpc-permify ``` -------------------------------- ### Complete Server Setup with Permify Interceptor Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/configuration.md Demonstrates a full server setup, including creating a Permify client, check client, authenticator, and the Permify interceptor, then registering it with a ConnectRPC service. ```go package main import ( "net/http" "os" "github.com/nrf110/connectrpc-permify" permifyclient "buf.build/gen/go/permifyco/permify/connectrpc/go/grpc/v1/grpcv1connect" "yourmodule/gen/yourmodule/v1/yourmodulev1connect" ) func setupServer() *http.ServeMux { // 1. Create Permify client permifyClient := permifyclient.NewPermissionClient( http.DefaultClient, os.Getenv("PERMIFY_URL"), // e.g., "http://localhost:3476" ) // 2. Create check client with options checkClient := connectpermify.NewCheckClient( permifyClient, connectpermify.WithDefaultDepth(10), connectpermify.WithSchemaVersion("latest"), ) // 3. Create authenticator (implement Authenticator interface) authenticator := &JWTAuthenticator{ issue: os.Getenv("JWT_ISSUER"), audience: os.Getenv("JWT_AUDIENCE"), } // 4. Create interceptor with enable function enabled := func() bool { return os.Getenv("AUTH_ENABLED") != "false" } interceptor := connectpermify.NewPermifyInterceptor( checkClient, authenticator, enabled, ) // 5. Register with services mux := http.NewServeMux() path, handler := yourmodulev1connect.NewYourServiceHandler( &yourServiceImpl{}, connect.WithInterceptors(interceptor), ) mux.Handle(path, handler) return mux } func main() { mux := setupServer() http.ListenAndServe(":8080", mux) } ``` -------------------------------- ### CheckConfig Examples Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/check.md Illustrates how to create CheckConfig instances for public endpoints, authenticated endpoints with permission checks, and authenticated-only endpoints. ```go // Public endpoint - no auth or checks required publicConfig := connectpermify.CheckConfig{ IsPublic: true, Checks: []connectpermify.Check{}, } // Authenticated endpoint with permission checks protectedConfig := connectpermify.CheckConfig{ IsPublic: false, Checks: []connectpermify.Check{ { Permission: "read", Entity: &connectpermify.Resource{ Type: "document", ID: "doc-123", }, }, { Permission: "write", Entity: &connectpermify.Resource{ Type: "organization", ID: "org-456", }, }, }, } // Authenticated only - no specific permission checks authOnlyConfig := connectpermify.CheckConfig{ IsPublic: false, Checks: []connectpermify.Check{}, } ``` -------------------------------- ### Create Attributes Example Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/types.md An example demonstrating how to create an Attributes map with string and slice values. ```go attrs := connectpermify.Attributes{ "department": "engineering", "seniority": "senior", "roles": []string{"editor", "reviewer"}, } ``` -------------------------------- ### Define Permissions for Document Service Methods Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/protobuf-extensions.md Examples of defining `create`, `read`, `write`, and `delete` permissions for various methods within a `DocumentService`. ```protobuf service DocumentService { rpc CreateDocument(CreateDocumentRequest) returns (Document) { option (nrf110.permify.v1.permission) = "create"; } rpc ReadDocument(GetDocumentRequest) returns (Document) { option (nrf110.permify.v1.permission) = "read"; } rpc UpdateDocument(UpdateDocumentRequest) returns (Document) { option (nrf110.permify.v1.permission) = "write"; } rpc DeleteDocument(DeleteDocumentRequest) returns (google.protobuf.Empty) { option (nrf110.permify.v1.permission) = "delete"; } } ``` -------------------------------- ### Full Permify Client Usage Example Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/client.md Demonstrates how to create and use the Permify check client. It shows setting up the client, defining resources and attributes, configuring checks with custom depths, and performing the permission check. Requires the Permify server to be running on http://localhost:3476. ```go package main import ( "context" "fmt" "net/http" "github.com/nrf110/connectrpc-permify" permifyclient "buf.build/gen/go/permifyco/permify/connectrpc/go/grpc/v1/grpcv1connect" ) func checkUserPermissions() { // Create Permify client httpClient := http.DefaultClient permifyClient := permifyclient.NewPermissionClient( httpClient, "http://localhost:3476", ) // Create check client with custom depth checkClient := connectpermify.NewCheckClient( permifyClient, connectpermify.WithDefaultDepth(12), connectpermify.WithSchemaVersion("latest"), ) // Define the user (principal) user := &connectpermify.Resource{ Type: "user", ID: "user123", } // Define global attributes attributes := connectpermify.Attributes{ "department": "engineering", } // Configure checks config := connectpermify.CheckConfig{ IsPublic: false, Checks: []connectpermify.Check{ { Permission: "read", Entity: &connectpermify.Resource{ Type: "document", ID: "doc456", }, Depth: nil, // Will use default depth (12) }, { Permission: "write", Entity: &connectpermify.Resource{ Type: "document", ID: "doc456", Attributes: connectpermify.Attributes{ "owner_id": "user123", }, }, }, }, } // Perform the check ctx := context.Background() allowed, err := checkClient.Check(ctx, user, attributes, config) if err != nil { fmt.Printf("Error checking permissions: %v\n", err) return } if allowed { fmt.Println("User has all required permissions") } else { fmt.Println("User denied: insufficient permissions") } } ``` -------------------------------- ### Define Resource Types for Multiple Messages Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/protobuf-extensions.md Examples demonstrating the `resource_type` extension for different message types like `Document` and `Organization`. ```protobuf message Document { option (nrf110.permify.v1.resource_type) = "document"; string id = 1; string title = 2; string owner_id = 3; } message Organization { option (nrf110.permify.v1.resource_type) = "organization"; string id = 1; string name = 2; } ``` -------------------------------- ### Permify Interceptor Enabled Function Examples Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/configuration.md Examples of the 'enabled' function for NewPermifyInterceptor, controlling whether authorization checks are performed. Use 'true' to enable, 'false' to disable. ```go // Always enabled func() bool { return true } ``` ```go // Environment-based func() bool { return os.Getenv("AUTH_ENABLED") != "false" } ``` ```go // Conditional on time/deployment func() bool { return time.Now().After(migrationDeadline) } ``` -------------------------------- ### Define Depths for Resource Service Methods Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/protobuf-extensions.md Examples setting different traversal depths for `GetConfig` (shallow) and `GetUserWithTeams` (deeper) methods in a `ResourceService`. ```protobuf service ResourceService { // Simple resource - shallow depth rpc GetConfig(ConfigRequest) returns (Config) { option (nrf110.permify.v1.permission) = "read"; option (nrf110.permify.v1.depth) = 3; } // Complex nested resource - deeper depth rpc GetUserWithTeams(UserRequest) returns (User) { option (nrf110.permify.v1.permission) = "read"; option (nrf110.permify.v1.depth) = 15; } } ``` -------------------------------- ### Define Public and Non-Public Methods in AuthService Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/protobuf-extensions.md Examples showing how to mark `HealthCheck` and `Status` methods as public, while `Login` requires authentication. ```protobuf service AuthService { rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse) { option (nrf110.permify.v1.public) = true; } rpc Status(StatusRequest) returns (StatusResponse) { option (nrf110.permify.v1.public) = true; } rpc Login(LoginRequest) returns (LoginResponse) { // Not public - authentication required } } ``` -------------------------------- ### Define Global Attributes Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/check.md Example of creating a global `Attributes` map for use in permission checks, including department, seniority, and region. ```go attributes := connectpermify.Attributes{ "department": "engineering", "seniority": "senior", "region": "us-east", } ``` -------------------------------- ### Define a User Principal Resource Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/check.md Example of creating a `Resource` object to represent a user principal with associated attributes like email and organization ID. ```go user := &connectpermify.Resource{ Type: "user", ID: "user-xyz", Attributes: connectpermify.Attributes{ "email": "alice@example.com", "org_id": "org-123", }, } ``` -------------------------------- ### Initialize ConnectRPC Permify Components Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/EXPORTS.md Instantiate Permify interceptors and clients using the imported library. These are common starting points for integrating Permify with ConnectRPC services. ```go connectpermify.NewPermifyInterceptor(...) connectpermify.NewCheckClient(...) ``` -------------------------------- ### Permify Check Example: Single Check Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/check.md Demonstrates how to create a `Check` configuration for a single permission, like checking if a user can read a document. Uses the client's default depth. ```go // Single check: Can user read this document? check := connectpermify.Check{ TenantID: "acme-corp", Permission: "read", Entity: &connectpermify.Resource{ Type: "document", ID: "doc-123", }, Depth: nil, // Uses client default } ``` -------------------------------- ### Exported Functions Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/MANIFEST.md Documentation for the 6 exported functions, including their signatures, parameters, return types, and usage examples. ```APIDOC ## Functions & Methods ### NewPermifyInterceptor **Description:** Main middleware for Permify integration. ### NewCheckClient **Description:** Client constructor for permission checking. ### WithDefaultDepth **Description:** Configuration option to set the default depth for checks. ### WithSchemaVersion **Description:** Configuration option to specify the schema version. ### GetSnapToken **Description:** Utility function for managing snap tokens (context). ### SetSnapToken **Description:** Utility function for managing snap tokens (context). ``` -------------------------------- ### Define a Document Entity Resource Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/check.md Example of creating a `Resource` object to represent a document entity, including attributes like owner, classification, and department. ```go document := &connectpermify.Resource{ Type: "document", ID: "doc-abc", Attributes: connectpermify.Attributes{ "owner_id": "user-xyz", "classification": "public", "department": "engineering", }, } ``` -------------------------------- ### Implement Checkable Interface for User Request Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/configuration.md Implement the `Checkable` interface for a `GetUserRequest` to define access control checks. This example specifies a 'read' permission for a specific user and tenant. ```go type GetUserRequest struct { UserID string TenantID string } func (r *GetUserRequest) GetChecks() connectpermify.CheckConfig { return connectpermify.CheckConfig{ IsPublic: false, Checks: []connectpermify.Check{ { TenantID: r.TenantID, Permission: "read", Entity: &connectpermify.Resource{ Type: "user", ID: r.UserID, }, }, }, } } ``` -------------------------------- ### Permify Check Example: Custom Depth Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/check.md Shows how to configure a `Check` with a specific maximum depth for permission graph traversal, preventing infinite loops in complex relationships. ```go // Check with custom depth depth := int32(5) check := connectpermify.Check{ Permission: "write", Entity: &connectpermify.Resource{ Type: "project", ID: "proj-456", }, Depth: &depth, } ``` -------------------------------- ### GetUserRequest Checkable Implementation Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/check.md Example implementation of the Checkable interface for a GetUserRequest. This typically auto-generates from protobuf annotations and specifies permission checks for a user request. ```go // Usually auto-generated from protobuf annotations type GetUserRequest struct { UserID string TenantID string } func (r *GetUserRequest) GetChecks() connectpermify.CheckConfig { return connectpermify.CheckConfig{ IsPublic: false, Checks: []connectpermify.Check{ { TenantID: r.TenantID, Permission: "read", Entity: &connectpermify.Resource{ Type: "user", ID: r.UserID, }, }, }, } } ``` -------------------------------- ### Debug CheckClient.Check() Errors in Go Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/errors.md Log and inspect errors returned by CheckClient.Check(). This example shows how to print the full error and convert it to a ConnectRPC error for detailed analysis. ```go result, err := checkClient.Check(ctx, principal, attributes, config) if err != nil { // Log the full error for debugging fmt.Printf("Check error: %v\n", err) // Convert to ConnectRPC error if needed if connectErr := (*connect.Error)(nil); errors.As(err, &connectErr) { fmt.Printf("Code: %s, Message: %s\n", connectErr.Code(), connectErr.Message()) } return err } ``` -------------------------------- ### Generate GetChecks() Implementation in Go Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/protobuf-extensions.md Example of generated Go code that implements the `GetChecks()` method. This method extracts extension values from Protobuf definitions to create a `connectpermify.CheckConfig` for access control. ```go func (r *CreateDocumentRequest) GetChecks() connectpermify.CheckConfig { return connectpermify.CheckConfig{ IsPublic: false, Checks: []connectpermify.Check{ { TenantID: r.TenantId, Permission: "create", Entity: &connectpermify.Resource{ Type: "project", ID: r.ProjectId, Attributes: connectpermify.Attributes{ "doc_classification": r.Classification, }, }, Depth: ptr(int32(5)), }, }, } } ``` -------------------------------- ### Require Multiple Permissions for a Request Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/configuration.md Define multiple checks within a single `CheckConfig` to enforce that a request satisfies all required permissions. This example requires 'create' permission for a project and 'use_feature' permission for an organization. ```go func (r *CreateDocumentRequest) GetChecks() connectpermify.CheckConfig { return connectpermify.CheckConfig{ IsPublic: false, Checks: []connectpermify.Check{ { Permission: "create", Entity: &connectpermify.Resource{ Type: "project", ID: r.ProjectID, }, }, { Permission: "use_feature", Entity: &connectpermify.Resource{ Type: "organization", ID: r.OrgID, }, }, }, } } ``` -------------------------------- ### Permify CheckClient Initialization Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/INDEX.md Shows how to initialize a new Permify CheckClient. Options for default depth and schema version can be provided. ```go // CheckClient func NewCheckClient(PermifyInterface, ...ClientOption) CheckClient func WithDefaultDepth(int32) ClientOption func WithSchemaVersion(string) ClientOption ``` -------------------------------- ### Build and Test with Make Commands Source: https://github.com/nrf110/connectrpc-permify/blob/main/README.md Standard make commands for development: 'make gen' to generate protobuf code, 'make test' to run tests, 'make clean' to remove generated files, and 'make update' to update dependencies. ```bash # Generate protobuf code make gen # Run tests make test # Clean generated files make clean # Update dependencies make update ``` -------------------------------- ### Create CheckClient with Schema Version Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/client.md Use `NewCheckClient` to create a client instance. Specify the schema version to be sent with permission check requests using `WithSchemaVersion`. ```go checkClient := connectpermify.NewCheckClient( permifyClient, connectpermify.WithSchemaVersion("latest"), ) ``` -------------------------------- ### Run Tests with Go and Testify Source: https://github.com/nrf110/connectrpc-permify/blob/main/README.md Execute all tests using 'make test' or target specific packages with 'go test -v ./pkg/'. The library utilizes Go's standard testing framework with testify for assertions and mockio for mocking. ```bash # Run all tests make test # Run tests for specific package go test -v ./pkg/ ``` -------------------------------- ### Typical Permify Integration Pattern Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/README.md Demonstrates the steps to integrate Permify with a Connect-RPC service. This includes creating clients, implementing an authenticator, setting up the interceptor, and registering it with the service. ```go // 1. Create Permify client permifyClient := permifyclient.NewPermissionClient( httpClient, "http://localhost:3476", ) // 2. Create check client checkClient := connectpermify.NewCheckClient( permifyClient, connectpermify.WithDefaultDepth(10), ) // 3. Implement Authenticator authenticator := &MyAuthenticator{...} // 4. Create interceptor interceptor := connectpermify.NewPermifyInterceptor( checkClient, authenticator, func() bool { return true }, ) // 5. Register with service path, handler := myService.NewHandler( &serviceImpl{}, connect.WithInterceptors(interceptor), ) ``` -------------------------------- ### HealthCheckRequest Checkable Implementation Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/check.md Example implementation of the Checkable interface for a HealthCheckRequest, marking the endpoint as public. This is used for requests that do not require permission checks. ```go // Public endpoint example type HealthCheckRequest struct{} func (r *HealthCheckRequest) GetChecks() connectpermify.CheckConfig { return connectpermify.CheckConfig{ IsPublic: true, } } ``` -------------------------------- ### Create CheckClient with Default Depth Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/client.md Use `NewCheckClient` to create a client instance. Configure the default depth for permission checks using `WithDefaultDepth` to prevent infinite loops in permission graphs. ```go checkClient := connectpermify.NewCheckClient( permifyClient, connectpermify.WithDefaultDepth(15), ) ``` -------------------------------- ### Configure Check Client with connectrpc-permify Source: https://github.com/nrf110/connectrpc-permify/blob/main/README.md Instantiate a new check client using connectpermify.NewCheckClient. Optional configurations like default depth and schema version can be provided. ```go checkClient := connectpermify.NewCheckClient( permifyClient, // Optional. Defaults to 10 connectpermify.WithDefaultDepth(10), // Optional. Defaults to "" connectpermify.WithSchemaVersion("latest"), ) ``` -------------------------------- ### Define Entity-Specific Attributes Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/check.md Example of embedding `Attributes` directly within a `Resource` object to provide context specific to that entity, such as owner ID and confidentiality. ```go doc := &connectpermify.Resource{ Type: "document", ID: "doc-123", Attributes: connectpermify.Attributes{ "owner_id": "user-456", "confidential": false, }, } ``` -------------------------------- ### Run Tests Source: https://github.com/nrf110/connectrpc-permify/blob/main/CLAUDE.md Execute all tests with 'make test', which includes regeneration. Alternatively, run tests directly for a specific package using 'go test ./pkg/'. ```bash make test # Run all tests (runs make gen first) go test ./pkg/ # Run tests directly without regeneration ``` -------------------------------- ### Set Up ConnectRPC Permify Interceptor Source: https://github.com/nrf110/connectrpc-permify/blob/main/README.md Configure the Permify client, an authenticator, and the interceptor itself. Then, apply the interceptor to your ConnectRPC server handler. ```go package main import ( "github.com/nrf110/connectrpc-permify" permifyclient "buf.build/gen/go/permifyco/permify/connectrpc/go/grpc/v1/grpcv1connect" ) func main() { // Configure Permify client permify := permifyclient.NewPermissionClient( http.DefaultClient, "http://localhost:3476", // Permify server URL ) // An Authenticator defines the logic for finding credentials in the request, verifying them, and returning the Principal // NewCustomAuthenticator isn't a real method. Separate libraries defining Authenticator implementations are available. authenticator := NewCustomAuthenticator() // Create the interceptor interceptor := connectpermify.NewPermifyInterceptor( connectpermify.NewCheckClient(permify), authenticator, func() bool { return true }, // Enable/disable authorization. Useful for testing locally without checks, or while migrating from another authorization solution. ) // Use with your ConnectRPC server mux := http.NewServeMux() path, handler := yourv1connect.NewUserServiceHandler( &userService{}, connect.WithInterceptors(interceptor), ) mux.Handle(path, handler) } ``` -------------------------------- ### Setting Snap Token in Context (Correct vs. Incorrect) Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/errors.md Demonstrates the correct way to set a string snap token in the context using `context.WithValue`. Incorrect usage with a non-string type will cause `GetSnapToken()` to return an empty string. ```go ctx = context.WithValue(ctx, connectpermify.PermifySnapToken, "snap_123") ctx = context.WithValue(ctx, connectpermify.PermifySnapToken, 12345) // integer ``` -------------------------------- ### Configure CheckClient with Multiple Options Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/configuration.md Combines multiple configuration options, such as WithDefaultDepth and WithSchemaVersion, when creating a CheckClient. ```go checkClient := connectpermify.NewCheckClient( permifyClient, connectpermify.WithDefaultDepth(12), connectpermify.WithSchemaVersion("latest"), ) ``` -------------------------------- ### Get Snap Token from Context Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/EXPORTS.md Retrieves a snap token from the request context. This function is useful for accessing snap tokens that have been previously set in the context. ```go func GetSnapToken(ctx context.Context) string ``` -------------------------------- ### Snap Token Usage Pattern in Go Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/snap-token.md Illustrates the typical flow for using snap tokens to ensure consistent permission checks. This involves creating a document, storing the snap token from the response in the context, and then performing permission checks against that consistent snapshot. ```go package main import ( "context" "github.com/nrf110/connectrpc-permify" ) // Typical flow: func createDocumentAndCheckAccess(ctx context.Context) { // 1. Create a document // Assume this returns a snap token from the permission system snapToken := "snap_token_from_create_response" // 2. Store snap token for consistency ctx = connectpermify.SetSnapToken(ctx, snapToken) // 3. Perform permission check using the same snapshot // This ensures we're checking against the state // that existed when the document was created result, err := checkClient.Check(ctx, principal, attributes, config) if err != nil { // Handle error } if !result { // Check failed - but we know it's against a consistent snapshot } } // In the authenticator: func (a *MyAuthenticator) Authenticate( ctx context.Context, req connect.AnyRequest, ) (*connectpermify.AuthenticationResult, error) { // ... validation logic ... // If the JWT contains a snap token claim, propagate it if claims["snap_token"] != nil { snapToken := claims["snap_token"].(string) ctx = connectpermify.SetSnapToken(ctx, snapToken) } return &connectpermify.AuthenticationResult{ Context: ctx, Principal: &connectpermify.Resource{Type: "user", ID: userID}, Attributes: connectpermify.Attributes{}, }, nil } // In the permission check client: // The Check method automatically includes the snap token // from the context when building the Permify request func (client *permifyCheckClient) check( ctx context.Context, principal *Resource, attributes Attributes, check Check, ) (bool, error) { request, err := check.toCheckRequest(ctx, principal, attributes, client.schemaVersion) // ... toCheckRequest calls GetSnapToken(ctx) and includes it in the request } ``` -------------------------------- ### Constructing a Valid Check Configuration Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/errors.md Illustrates the required fields (`Permission`, `Entity.Type`, `Entity.ID`) for a `connectpermify.Check` object. Optional fields like `Attributes` and `Depth` can also be set. ```go check := connectpermify.Check{ Permission: "read", // Required Entity: &connectpermify.Resource{ Type: "document", // Required ID: "doc-123", // Required Attributes: connectpermify.Attributes{ "owner": "user-456", // Optional }, }, Depth: nil, // Optional, uses default } ``` -------------------------------- ### Build and Generate Protobuf Code Source: https://github.com/nrf110/connectrpc-permify/blob/main/CLAUDE.md Use 'make gen' to clean, update dependencies, and generate protobuf code. 'make clean' removes generated files, and 'make update' runs go mod tidy. ```bash make gen # Clean, update deps, and generate protobuf code make clean # Remove generated files (gen/ and dist/) make update # Run go mod tidy ``` -------------------------------- ### Retrieve Snap Token from Context Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/snap-token.md Use this function to get a snap token from the context. It returns an empty string if no token is found or if the value is not a string. This is useful for ensuring permission checks use a consistent state. ```go package main import ( "context" "github.com/nrf110/connectrpc-permify" ) func processRequest(ctx context.Context) { // Retrieve snap token if present snapToken := connectpermify.GetSnapToken(ctx) if snapToken != "" { // Use snap token for consistent permission checks println("Using snap token:", snapToken) } else { // No snap token - Permify will use latest snapshot println("No snap token specified") } } ``` -------------------------------- ### Implement Authenticator Interface with JWT Validation Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/authenticator.md Example implementation of the Authenticator interface using JWT validation. It extracts and validates a Bearer token from the Authorization header, parses claims, and constructs an AuthenticationResult. Requires a JWTValidator and handles various unauthenticated error cases. ```go package main import ( "context" "errors" "strings" "connectrpc.com/connect" "github.com/nrf110/connectrpc-permify" ) // MyAuthenticator implements the Authenticator interface type MyAuthenticator struct { jwtValidator *JWTValidator // Your JWT validation logic } func (a *MyAuthenticator) Authenticate( ctx context.Context, req connect.AnyRequest, ) (*connectpermify.AuthenticationResult, error) { // Extract the Authorization header authHeader := req.Header().Get("Authorization") if authHeader == "" { return nil, connect.NewError( connect.CodeUnauthenticated, errors.New("missing authorization header"), ) } // Parse Bearer token parts := strings.Split(authHeader, " ") if len(parts) != 2 || parts[0] != "Bearer" { return nil, connect.NewError( connect.CodeUnauthenticated, errors.New("invalid authorization header format"), ) } token := parts[1] // Validate and parse the JWT claims, err := a.jwtValidator.ValidateAndParse(token) if err != nil { return nil, connect.NewError( connect.CodeUnauthenticated, errors.New("invalid token"), ) } // Extract principal from claims userID, ok := claims["sub"].(string) if !ok { return nil, connect.NewError( connect.CodeUnauthenticated, errors.New("missing subject in token"), ) } // Optionally, extract snap token for Permify consistency snapToken, _ := claims["snap_token"].(string) if snapToken != "" { ctx = connectpermify.SetSnapToken(ctx, snapToken) } // Build result result := &connectpermify.AuthenticationResult{ Context: ctx, Principal: &connectpermify.Resource{ Type: "user", ID: userID, }, Attributes: connectpermify.Attributes{ "email": claims["email"], "roles": claims["roles"], "org_id": claims["org_id"], }, } return result, nil } // Example setup func setupServiceWithAuth() { authenticator := &MyAuthenticator{ jwtValidator: createJWTValidator(), } checkClient := connectpermify.NewCheckClient(permifyClient) interceptor := connectpermify.NewPermifyInterceptor( checkClient, authenticator, func() bool { return true }, ) // Use interceptor in your service handler // ... } ``` -------------------------------- ### Import ConnectRPC Permify Library Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/EXPORTS.md Import the ConnectRPC Permify library to utilize its features. This is the standard import path for Go projects. ```go import "github.com/nrf110/connectrpc-permify" ``` -------------------------------- ### Protocol Buffer Development Commands Source: https://github.com/nrf110/connectrpc-permify/blob/main/CLAUDE.md Manage protobuf development with 'buf generate' for code generation, 'buf lint' for file linting, and 'buf breaking' to check for breaking changes. ```bash buf generate # Generate Go code from protobuf definitions buf lint # Lint protobuf files buf breaking # Check for breaking changes ``` -------------------------------- ### Protobuf Extensions Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/MANIFEST.md Documentation for the 7 custom protobuf extensions, including field and method extensions. ```APIDOC ## Protobuf Extensions ### Field Extensions - `resource_id` - `tenant_id` - `attribute_name` ### Message Extension - `resource_type` ### Method Extensions - `permission` - `public` - `depth` ``` -------------------------------- ### Direct Context Usage with Sentinel Key Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/snap-token.md Demonstrates how to directly use the PermifySnapToken sentinel value to set and retrieve a snap token from a context. ```go // Direct context usage with the sentinel key ctx := context.WithValue(baseCtx, connectpermify.PermifySnapToken, "snap_123") value := ctx.Value(connectpermify.PermifySnapToken) ``` -------------------------------- ### ConnectRPC Error Handling for Authentication Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/authenticator.md Demonstrates how to return authentication-related errors using ConnectRPC's `NewError` function with appropriate status codes and messages. ```go import "connectrpc.com/connect" // Missing credentials return nil, connect.NewError( connect.CodeUnauthenticated, errors.New("missing credentials"), ) // Invalid credentials return nil, connect.NewError( connect.CodeUnauthenticated, errors.New("invalid token"), ) // Other errors return nil, connect.NewError( connect.CodeInternal, errors.New("failed to validate credentials"), ) ``` -------------------------------- ### Gradual Rollout of Authorization Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/configuration.md Implement a gradual rollout strategy by enabling authorization for a percentage of users. The `authEnabled` function hashes the user ID to determine if authorization should be enforced. ```go // Roll out to percentage of traffic func authEnabled() bool { // Get user from context and hash it return hashUserID(userID) % 100 < rolloutPercentage } interceptor := connectpermify.NewPermifyInterceptor( checkClient, authenticator, authEnabled, ) ``` -------------------------------- ### Create Permify Interceptor Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/interceptor.md Initializes a ConnectRPC unary interceptor for permission checks. Configure the Permify client, an authenticator, and an enable function to control authorization. ```go package main import ( "context" "net/http" "connectrpc.com/connect" "github.com/nrf110/connectrpc-permify" permifyclient "buf.build/gen/go/permifyco/permify/connectrpc/go/grpc/v1/grpcv1connect" ) func setupService() { // Initialize Permify client permifyClient := permifyclient.NewPermissionClient( http.DefaultClient, "http://localhost:3476", ) // Create check client checkClient := connectpermify.NewCheckClient( permifyClient, connectpermify.WithDefaultDepth(10), ) // Create authenticator (implement Authenticator interface) authenticator := &myAuthenticator{} // Create interceptor interceptor := connectpermify.NewPermifyInterceptor( checkClient, authenticator, func() bool { return true }, // Enable checks in production ) // Register with service mux := http.NewServeMux() path, handler := yourServiceHandler( &myServiceImpl{}, connect.WithInterceptors(interceptor), ) mux.Handle(path, handler) } ``` -------------------------------- ### Build Commands for Permify Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/README.md Standard make commands for managing the Permify project, including code generation, testing, cleaning, and dependency updates. ```bash make gen # Generate protobuf code ``` ```bash make test # Run tests ``` ```bash make clean # Remove generated files ``` ```bash make update # Update dependencies ``` -------------------------------- ### CheckClient Functions and Options Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/INDEX.md Functions for creating and configuring the CheckClient, used for permission checks. ```APIDOC ## NewCheckClient ### Description Creates a new CheckClient instance. ### Signature func NewCheckClient(PermifyInterface, ...ClientOption) CheckClient ### Parameters - **PermifyInterface**: An instance of the PermifyInterface. - **...ClientOption**: Variadic options to configure the client. ``` ```APIDOC ## WithDefaultDepth ### Description Sets the default depth for permission checks. ### Signature func WithDefaultDepth(int32) ClientOption ### Parameters - **int32**: The default depth value. ``` ```APIDOC ## WithSchemaVersion ### Description Sets the schema version for permission checks. ### Signature func WithSchemaVersion(string) ClientOption ### Parameters - **string**: The schema version. ``` -------------------------------- ### Multi-Tenant Permission Check Configuration Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/README.md Configure a permission check for a multi-tenant environment by including the TenantID along with the permission and resource entity. The request is not public. ```go func (r *GetResourceRequest) GetChecks() connectpermify.CheckConfig { return connectpermify.CheckConfig{ IsPublic: false, Checks: []connectpermify.Check{ { TenantID: r.TenantID, // Multi-tenant ID Permission: "read", Entity: &connectpermify.Resource{ Type: "resource", ID: r.ResourceID, }, }, }, } } ``` -------------------------------- ### Implement Checkable Interface for Request Messages Source: https://github.com/nrf110/connectrpc-permify/blob/main/README.md Ensure your request messages implement the Checkable interface to provide authorization configuration, including permissions, entities, and depth. ```go // Usually auto-generated from protobuf annotations func (r *GetUserRequest) GetChecks() connectpermify.CheckConfig { return connectpermify.CheckConfig{ Checks: []connectpermify.Check{ { TenantID: r.GetTenantId(), // If using multi-tenancy Permission: "read", Entity: &connectpermify.Resource{ Type: "user", ID: r.GetUserId(), }, Depth: int32(10), }, }, } } ``` -------------------------------- ### Annotate Protobuf Services Source: https://github.com/nrf110/connectrpc-permify/blob/main/README.md Use custom extensions in your protobuf definitions to configure permissions, resource types, and public endpoints. ```protobuf import "nrf110/permify/v1/permify.proto"; message GetUserRequest { string user_id = 1 [(nrf110.permify.v1.resource_id) = true]; } message User { option (nrf110.permify.v1.resource_type) = "user"; string id = 1; string email = 2; } service UserService { rpc GetUser(GetUserRequest) returns (User) { option (nrf110.permify.v1.action) = "read"; option (nrf110.permify.v1.depth) = 2; // Optional } rpc PublicHealthCheck(HealthCheckRequest) returns (HealthCheckResponse) { option (nrf110.permify.v1.public) = true; // No auth required } } ``` -------------------------------- ### Validating Check Configuration Against Schema Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/errors.md Ensures that the `Permission` and `Entity.Type` used in `connectpermify.CheckConfig` are defined in the Permify schema. Uncomment the incorrect lines to see validation failures. ```go // In GetChecks() implementation func (r *MyRequest) GetChecks() connectpermify.CheckConfig { return connectpermify.CheckConfig{ IsPublic: false, Checks: []connectpermify.Check{ { // Permission must be defined in Permify schema Permission: "read", // ✓ Defined // Permission: "unknown_action", // ✗ Not defined // Entity type must be defined in Permify schema Entity: &connectpermify.Resource{ Type: "document", // ✓ Defined // Type: "unknown_type", // ✗ Not defined ID: r.DocumentID, }, }, }, } } ``` -------------------------------- ### Check Structure Definition Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/README.md Represents a single permission to be verified. It includes tenant ID, permission name, the entity it applies to, and optional graph traversal depth. ```go type Check struct { TenantID string // Multi-tenant ID Permission string // "read", "write", etc. Entity *Resource // What resource? Depth *int32 // Graph traversal depth } ``` -------------------------------- ### Protobuf Schema with Permify Extensions Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/protobuf-extensions.md This Protobuf definition includes extensions for tenant ID, resource ID, attribute name, resource type, permissions, and public endpoints. Use this to define your data structures and service endpoints with integrated access control metadata. ```protobuf syntax = "proto3"; package myapp.v1; import "google/protobuf/timestamp.proto"; import "nrf110/permify/v1/permify.proto"; // Request message with field extensions message CreateDocumentRequest { string tenant_id = 1 [(nrf110.permify.v1.tenant_id) = true]; string project_id = 2 [(nrf110.permify.v1.resource_id) = true]; string title = 3; string classification = 4 [(nrf110.permify.v1.attribute_name) = "doc_classification"]; } message GetDocumentRequest { string tenant_id = 1 [(nrf110.permify.v1.tenant_id) = true]; string document_id = 2 [(nrf110.permify.v1.resource_id) = true]; } message UpdateDocumentRequest { string tenant_id = 1 [(nrf110.permify.v1.tenant_id) = true]; string document_id = 2 [(nrf110.permify.v1.resource_id) = true]; string title = 3; string classification = 4 [(nrf110.permify.v1.attribute_name) = "doc_classification"]; } message DeleteDocumentRequest { string tenant_id = 1 [(nrf110.permify.v1.tenant_id) = true]; string document_id = 2 [(nrf110.permify.v1.resource_id) = true]; } message ListDocumentsRequest { string tenant_id = 1 [(nrf110.permify.v1.tenant_id) = true]; string project_id = 2 [(nrf110.permify.v1.resource_id) = true]; } message HealthCheckRequest {} // Response message with message extension message Document { option (nrf110.permify.v1.resource_type) = "document"; string id = 1; string title = 2; string project_id = 3; string owner_id = 4; string classification = 5; google.protobuf.Timestamp created_at = 6; } message Project { option (nrf110.permify.v1.resource_type) = "project"; string id = 1; string name = 2; string organization_id = 3; } // Service with method extensions service DocumentService { // Public endpoint rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse) { option (nrf110.permify.v1.public) = true; } // Create document - requires "create" permission on project rpc CreateDocument(CreateDocumentRequest) returns (Document) { option (nrf110.permify.v1.permission) = "create"; option (nrf110.permify.v1.depth) = 5; } // Read document - requires "read" permission on document rpc GetDocument(GetDocumentRequest) returns (Document) { option (nrf110.permify.v1.permission) = "read"; option (nrf110.permify.v1.depth) = 3; } // Update document - requires "write" permission on document rpc UpdateDocument(UpdateDocumentRequest) returns (Document) { option (nrf110.permify.v1.permission) = "write"; option (nrf110.permify.v1.depth) = 5; } // Delete document - requires "delete" permission on document rpc DeleteDocument(DeleteDocumentRequest) returns (google.protobuf.Empty) { option (nrf110.permify.v1.permission) = "delete"; option (nrf110.permify.v1.depth) = 3; } // List documents - requires "read" permission on project rpc ListDocuments(ListDocumentsRequest) returns (DocumentList) { option (nrf110.permify.v1.permission) = "read"; option (nrf110.permify.v1.depth) = 5; } } ``` -------------------------------- ### CheckClient Options Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/EXPORTS.md Provides options to configure the CheckClient for specific behaviors during permission checks. ```APIDOC ## CheckClient Options ### WithDefaultDepth #### Description Sets the default depth for permission graph traversal in checks. #### Signature ```go func WithDefaultDepth(depth int32) ClientOption ``` ### WithSchemaVersion #### Description Sets the schema version for permission checks. #### Signature ```go func WithSchemaVersion(version string) ClientOption ``` ``` -------------------------------- ### ClientOption Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/EXPORTS.md Functional option for configuring the CheckClient, allowing customization of default depth and schema version. ```APIDOC ## Type: ClientOption ### Description Functional option for configuring CheckClient. ### Implementations - `WithDefaultDepth(int32) ClientOption` - `WithSchemaVersion(string) ClientOption` ``` -------------------------------- ### Snap Token Usage Pattern Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/api-reference/snap-token.md Snap tokens ensure that multiple permission checks within a single operation view the same state of the permission database. This pattern demonstrates how to set and use snap tokens to maintain consistency. ```APIDOC ## Snap Token Usage Pattern Snap tokens ensure that multiple permission checks in a single operation view the same state of the permission database: ```go package main import ( "context" "github.com/nrf110/connectrpc-permify" ) // Typical flow: func createDocumentAndCheckAccess(ctx context.Context) { // 1. Create a document // Assume this returns a snap token from the permission system snapToken := "snap_token_from_create_response" // 2. Store snap token for consistency ctx = connectpermify.SetSnapToken(ctx, snapToken) // 3. Perform permission check using the same snapshot // This ensures we're checking against the state // that existed when the document was created result, err := checkClient.Check(ctx, principal, attributes, config) if err != nil { // Handle error } if !result { // Check failed - but we know it's against a consistent snapshot } } // In the authenticator: func (a *MyAuthenticator) Authenticate( ctx context.Context, req connect.AnyRequest, ) (*connectpermify.AuthenticationResult, error) { // ... validation logic ... // If the JWT contains a snap token claim, propagate it if claims["snap_token"] != nil { snapToken := claims["snap_token"].(string) ctx = connectpermify.SetSnapToken(ctx, snapToken) } return &connectpermify.AuthenticationResult{ Context: ctx, Principal: &connectpermify.Resource{Type: "user", ID: userID}, Attributes: connectpermify.Attributes{}, }, nil } // In the permission check client: // The Check method automatically includes the snap token // from the context when building the Permify request func (client *permifyCheckClient) check( ctx context.Context, principal *Resource, attributes Attributes, check Check, ) (bool, error) { request, err := check.toCheckRequest(ctx, principal, attributes, client.schemaVersion) // ... toCheckRequest calls GetSnapToken(ctx) and includes it in the request } ``` ``` -------------------------------- ### Permify Core Interfaces Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/INDEX.md Lists the core interfaces provided by the Permify ConnectRPC library, including Authenticator, CheckClient, PermifyInterface, and Checkable. ```go // Interfaces type Authenticator interface type CheckClient interface type PermifyInterface interface type Checkable interface ``` -------------------------------- ### Public Endpoint Check Configuration Source: https://github.com/nrf110/connectrpc-permify/blob/main/_autodocs/README.md Configure a request as a public endpoint by setting IsPublic to true. No permission checks are required. ```go func (r *HealthCheckRequest) GetChecks() connectpermify.CheckConfig { return connectpermify.CheckConfig{ IsPublic: true, } } ```