|
| 1 | +# Specification: gRPC & REST API with gRPC-Gateway |
| 2 | + |
| 3 | +This document specifies the architecture and mapping for exposing Authorizer's public APIs via gRPC and a generated RESTful layer using `grpc-gateway`. |
| 4 | + |
| 5 | +## 1. Architecture Overview |
| 6 | + |
| 7 | +Authorizer uses a single-source-of-truth Protobuf definition to generate: |
| 8 | +1. **gRPC Service**: For high-performance, typed backend-to-backend communication. |
| 9 | +2. **RESTful API**: Via `grpc-gateway`, maintaining backward compatibility and ease of use for web/mobile clients. |
| 10 | +3. **TypeScript Clients**: Via `ts-proto`, providing type-safe API consumption out of the box. |
| 11 | + |
| 12 | +### Package & Versioning |
| 13 | +- **Package**: `authorizer.v1` |
| 14 | +- **Directory Structure**: `proto/authorizer/v1/` |
| 15 | +- **API Versioning**: Hardcoded in HTTP paths as `/api/v1/...` and tracked via Protobuf package versioning. |
| 16 | + |
| 17 | +### Ecosystem Tooling |
| 18 | +- **`buf`**: Managed build system for Protobuf. |
| 19 | +- **`protoc-gen-grpc-gateway`**: Generates the reverse proxy from gRPC to REST. |
| 20 | +- **`protoc-gen-openapiv2`**: Generates Swagger/OpenAPI documentation. |
| 21 | +- **`protoc-gen-ts_proto`**: Generates the TypeScript client. |
| 22 | +- **`protovalidate`**: High-performance validation rules using Common Expression Language (CEL). |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +## 2. API Mapping (Public Surface) |
| 27 | + |
| 28 | +All public GraphQL queries and mutations are mapped to RPC methods. Terminologies are preserved from the GraphQL schema. |
| 29 | + |
| 30 | +### 2.1. Authentication Service (`authorizer.v1.AuthorizerService`) |
| 31 | + |
| 32 | +| RPC Method | GraphQL Equivalent | HTTP Path | Permissions | |
| 33 | +| :--- | :--- | :--- | :--- | |
| 34 | +| `Signup` | `signup` | `POST /api/v1/signup` | Public | |
| 35 | +| `Login` | `login` | `POST /api/v1/login` | Public | |
| 36 | +| `MagicLinkLogin` | `magic_link_login` | `POST /api/v1/magic-link-login` | Public | |
| 37 | +| `Logout` | `logout` | `POST /api/v1/logout` | Authenticated | |
| 38 | +| `VerifyEmail` | `verify_email` | `POST /api/v1/verify-email` | Public | |
| 39 | +| `ResendVerifyEmail` | `resend_verify_email` | `POST /api/v1/resend-verify-email` | Public | |
| 40 | +| `ForgotPassword` | `forgot_password` | `POST /api/v1/forgot-password` | Public | |
| 41 | +| `ResetPassword` | `reset_password` | `POST /api/v1/reset-password` | Public | |
| 42 | +| `VerifyOtp` | `verify_otp` | `POST /api/v1/verify-otp` | Public | |
| 43 | +| `ResendOtp` | `resend_otp` | `POST /api/v1/resend-otp` | Public | |
| 44 | +| `Revoke` | `revoke` | `POST /api/v1/revoke` | Authenticated | |
| 45 | +| `DeactivateAccount` | `deactivate_account` | `DELETE /api/v1/account` | Authenticated | |
| 46 | +| `GetMeta` | `meta` | `GET /api/v1/meta` | Public | |
| 47 | +| `GetSession` | `session` | `POST /api/v1/session` | Authenticated | |
| 48 | +| `GetProfile` | `profile` | `GET /api/v1/profile` | Authenticated | |
| 49 | +| `ValidateJwtToken` | `validate_jwt_token` | `POST /api/v1/validate-jwt` | Public/Service | |
| 50 | +| `ValidateSession` | `validate_session` | `POST /api/v1/validate-session` | Public/Service | |
| 51 | +| `GetPermissions` | `permissions` | `GET /api/v1/permissions` | Authenticated | |
| 52 | + |
| 53 | +### 2.2. OIDC & OAuth2 REST Endpoints |
| 54 | +The following endpoints remain as pure HTTP handlers to comply with strict OIDC/OAuth2 protocol requirements (redirects, form-encoding): |
| 55 | +- `GET /.well-known/openid-configuration` |
| 56 | +- `GET /.well-known/jwks.json` |
| 57 | +- `GET /authorize` |
| 58 | +- `GET /userinfo` |
| 59 | +- `POST /oauth/token` |
| 60 | +- `POST /oauth/revoke` |
| 61 | +- `POST /oauth/introspect` |
| 62 | +- `GET /oauth_login/:oauth_provider` |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +## 3. Documentation & Commenting Standards |
| 67 | + |
| 68 | +To ensure the generated API documentation (Swagger/OpenAPI) and TypeScript clients are well-documented, the following standards MUST be followed in all `.proto` files: |
| 69 | + |
| 70 | +### 3.1. General Principles |
| 71 | +- Use `//` for all descriptions. |
| 72 | +- Every RPC, Message, and Field must have a description. |
| 73 | +- Start with a clear summary line, followed by details on constraints or behavior. |
| 74 | + |
| 75 | +### 3.2. Field Metadata Labels |
| 76 | +Include explicit labels in field comments to denote behavior: |
| 77 | +- `// Required.` - For fields that must be provided. |
| 78 | +- `// Optional.` - For fields that can be omitted. |
| 79 | +- `// Read-only.` - For fields that are only populated by the server in responses. |
| 80 | +- `// Output only.` - Similar to read-only, specifically for create/update requests where the field is ignored. |
| 81 | + |
| 82 | +### 3.3. Permission Blocks |
| 83 | +Every RPC method comment must include a standardized permission block: |
| 84 | +```protobuf |
| 85 | +// [Description of the RPC] |
| 86 | +// |
| 87 | +// Required permissions: |
| 88 | +// - [scope_name] (or "Public" for open endpoints) |
| 89 | +``` |
| 90 | + |
| 91 | +--- |
| 92 | + |
| 93 | +## 4. Protocol Buffer Definition Samples |
| 94 | + |
| 95 | +### Permissions, Validations & Documentation |
| 96 | +Using the Qdrant pattern for permissions, `protovalidate` for field rules, and the documentation standards defined above. |
| 97 | + |
| 98 | +```protobuf |
| 99 | +syntax = "proto3"; |
| 100 | +
|
| 101 | +package authorizer.v1; |
| 102 | +
|
| 103 | +import "google/api/annotations.proto"; |
| 104 | +import "buf/validate/validate.proto"; |
| 105 | +
|
| 106 | +// Custom option for granular permission checks |
| 107 | +extend google.protobuf.MethodOptions { |
| 108 | + string permissions = 50001; |
| 109 | +} |
| 110 | +
|
| 111 | +service AuthorizerService { |
| 112 | + // Signup registers a new user in the system. |
| 113 | + // |
| 114 | + // Required permissions: |
| 115 | + // - Public |
| 116 | + rpc Signup(SignUpRequest) returns (AuthResponse) { |
| 117 | + option (google.api.http) = { |
| 118 | + post: "/api/v1/signup" |
| 119 | + body: "*" |
| 120 | + }; |
| 121 | + option (authorizer.v1.permissions) = ""; |
| 122 | + } |
| 123 | +
|
| 124 | + // GetProfile returns the profile of the currently authenticated user. |
| 125 | + // |
| 126 | + // Required permissions: |
| 127 | + // - read:profile |
| 128 | + rpc GetProfile(GetProfileRequest) returns (User) { |
| 129 | + option (google.api.http) = { |
| 130 | + get: "/api/v1/profile" |
| 131 | + }; |
| 132 | + option (authorizer.v1.permissions) = "read:profile"; |
| 133 | + } |
| 134 | +} |
| 135 | +
|
| 136 | +// SignUpRequest defines the parameters for user registration. |
| 137 | +// It supports both email-based and phone-based signup. |
| 138 | +message SignUpRequest { |
| 139 | + // The unique email address for the user. |
| 140 | + // Optional. If provided, must be a valid email format. |
| 141 | + optional string email = 1 [(buf.validate.field).string.email = true]; |
| 142 | + |
| 143 | + // The password for the account. Must be at least 8 characters. |
| 144 | + // Required. |
| 145 | + string password = 2 [(buf.validate.field).string.min_len = 8]; |
| 146 | + |
| 147 | + // Confirmation of the password. Must match the 'password' field. |
| 148 | + // Required. |
| 149 | + string confirm_password = 3 [(buf.validate.field).string.min_len = 8]; |
| 150 | + |
| 151 | + // The user's phone number in E.164 format. |
| 152 | + // Optional. Example: +1234567890. |
| 153 | + optional string phone_number = 4 [(buf.validate.field).string.pattern = "^\\+[1-9]\\d{1,14}$"]; |
| 154 | +
|
| 155 | + // The first name of the user. |
| 156 | + // Optional. |
| 157 | + optional string given_name = 5; |
| 158 | +
|
| 159 | + // The last name of the user. |
| 160 | + // Optional. |
| 161 | + optional string family_name = 6; |
| 162 | +
|
| 163 | + // List of roles to be assigned to the user. |
| 164 | + // Optional. Defaults to project's default roles if empty. |
| 165 | + repeated string roles = 7; |
| 166 | +
|
| 167 | + // Arbitrary JSON data associated with the user. |
| 168 | + // Optional. |
| 169 | + map<string, string> app_data = 8; |
| 170 | +} |
| 171 | +``` |
| 172 | + |
| 173 | +--- |
| 174 | + |
| 175 | +## 5. Migration Strategy: Interface Pattern |
| 176 | + |
| 177 | +To avoid duplicating business logic between GraphQL and gRPC, all logic is moved to a unified `service.Provider` interface. |
| 178 | + |
| 179 | +### 1. Define the Interface |
| 180 | +```go |
| 181 | +// internal/service/provider.go |
| 182 | +package service |
| 183 | + |
| 184 | +type Provider interface { |
| 185 | + // Authentication & Profile |
| 186 | + Signup(ctx context.Context, params *SignUpRequest) (*AuthResponse, error) |
| 187 | + Login(ctx context.Context, params *LoginRequest) (*AuthResponse, error) |
| 188 | + MagicLinkLogin(ctx context.Context, params *MagicLinkLoginRequest) (*Response, error) |
| 189 | + Logout(ctx context.Context) (*Response, error) |
| 190 | + UpdateProfile(ctx context.Context, params *UpdateProfileRequest) (*Response, error) |
| 191 | + VerifyEmail(ctx context.Context, params *VerifyEmailRequest) (*AuthResponse, error) |
| 192 | + ResendVerifyEmail(ctx context.Context, params *ResendVerifyEmailRequest) (*Response, error) |
| 193 | + ForgotPassword(ctx context.Context, params *ForgotPasswordRequest) (*ForgotPasswordResponse, error) |
| 194 | + ResetPassword(ctx context.Context, params *ResetPasswordRequest) (*Response, error) |
| 195 | + Revoke(ctx context.Context, params *OAuthRevokeRequest) (*Response, error) |
| 196 | + VerifyOtp(ctx context.Context, params *VerifyOTPRequest) (*AuthResponse, error) |
| 197 | + ResendOtp(ctx context.Context, params *ResendOTPRequest) (*Response, error) |
| 198 | + DeactivateAccount(ctx context.Context) (*Response, error) |
| 199 | + |
| 200 | + // Metadata & Validation |
| 201 | + GetMeta(ctx context.Context) (*Meta, error) |
| 202 | + GetSession(ctx context.Context, params *SessionQueryRequest) (*AuthResponse, error) |
| 203 | + GetProfile(ctx context.Context) (*User, error) |
| 204 | + ValidateJwtToken(ctx context.Context, params *ValidateJWTTokenRequest) (*ValidateJWTTokenResponse, error) |
| 205 | + ValidateSession(ctx context.Context, params *ValidateSessionRequest) (*ValidateSessionResponse, error) |
| 206 | + GetPermissions(ctx context.Context) ([]*Permission, error) |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | +### 2. Refactor Resolvers |
| 211 | +Existing GraphQL resolvers will be thin wrappers around this service, using encapsulated mapping methods: |
| 212 | +```go |
| 213 | +// internal/graph/schema.resolvers.go |
| 214 | +func (r *mutationResolver) Signup(ctx context.Context, params model.SignUpRequest) (*model.AuthResponse, error) { |
| 215 | + // Convert GraphQL model to Service request |
| 216 | + req := service.SignUpRequestFromGQL(params) |
| 217 | + |
| 218 | + // Call business logic |
| 219 | + res, err := r.ServiceProvider.Signup(ctx, req) |
| 220 | + if err != nil { |
| 221 | + return nil, err |
| 222 | + } |
| 223 | + |
| 224 | + // Convert Service response back to GraphQL model |
| 225 | + return res.AsGQL(), nil |
| 226 | +} |
| 227 | +``` |
| 228 | + |
| 229 | +### 3. Implement gRPC Handler |
| 230 | +The gRPC server will similarly use the encapsulated mapping logic: |
| 231 | +```go |
| 232 | +// internal/grpc/handler.go |
| 233 | +func (s *Server) Signup(ctx context.Context, req *pb.SignUpRequest) (*pb.AuthResponse, error) { |
| 234 | + // Convert gRPC proto to Service request |
| 235 | + serviceReq := service.SignUpRequestFromPb(req) |
| 236 | + |
| 237 | + // Call business logic |
| 238 | + res, err := s.ServiceProvider.Signup(ctx, serviceReq) |
| 239 | + if err != nil { |
| 240 | + return nil, err |
| 241 | + } |
| 242 | + |
| 243 | + // Convert Service response back to gRPC proto |
| 244 | + return res.AsPb(), nil |
| 245 | +} |
| 246 | +``` |
| 247 | + |
| 248 | +--- |
| 249 | + |
| 250 | +## 6. Detailed Mapping Table |
| 251 | + |
| 252 | +| gRPC Method | GraphQL Field | REST Gateway Path | Perms | Logic Method | |
| 253 | +| :--- | :--- | :--- | :--- | :--- | |
| 254 | +| `Signup` | `signup` | `POST /api/v1/signup` | - | `Signup` | |
| 255 | +| `Login` | `login` | `POST /api/v1/login` | - | `Login` | |
| 256 | +| `MagicLinkLogin` | `magic_link_login` | `POST /api/v1/magic-link` | - | `MagicLinkLogin` | |
| 257 | +| `Logout` | `logout` | `POST /api/v1/logout` | `auth` | `Logout` | |
| 258 | +| `UpdateProfile` | `update_profile` | `PUT /api/v1/profile` | `auth` | `UpdateProfile` | |
| 259 | +| `VerifyEmail` | `verify_email` | `POST /api/v1/verify-email` | - | `VerifyEmail` | |
| 260 | +| `ResendVerifyEmail` | `resend_verify_email` | `POST /api/v1/resend-verify` | - | `ResendVerifyEmail` | |
| 261 | +| `ForgotPassword` | `forgot_password` | `POST /api/v1/forgot-password` | - | `ForgotPassword` | |
| 262 | +| `ResetPassword` | `reset_password` | `POST /api/v1/reset-password` | - | `ResetPassword` | |
| 263 | +| `Revoke` | `revoke` | `POST /api/v1/revoke` | `auth` | `Revoke` | |
| 264 | +| `VerifyOtp` | `verify_otp` | `POST /api/v1/verify-otp` | - | `VerifyOtp` | |
| 265 | +| `ResendOtp` | `resend_otp` | `POST /api/v1/resend-otp` | - | `ResendOtp` | |
| 266 | +| `DeactivateAccount`| `deactivate_account` | `DELETE /api/v1/account` | `auth` | `DeactivateAccount`| |
| 267 | +| `GetMeta` | `meta` | `GET /api/v1/meta` | - | `GetMeta` | |
| 268 | +| `GetSession` | `session` | `POST /api/v1/session` | `auth` | `GetSession` | |
| 269 | +| `GetProfile` | `profile` | `GET /api/v1/profile` | `auth` | `GetProfile` | |
| 270 | +| `ValidateJwtToken` | `validate_jwt_token` | `POST /api/v1/validate-jwt` | - | `ValidateJwtToken` | |
| 271 | +| `ValidateSession` | `validate_session` | `POST /api/v1/validate-session` | - | `ValidateSession` | |
| 272 | +| `GetPermissions` | `permissions` | `GET /api/v1/permissions` | `auth` | `GetPermissions` | |
| 273 | + |
| 274 | +--- |
| 275 | + |
| 276 | +## 7. Testing Strategy |
| 277 | + |
| 278 | +### 1. Service Logic Tests |
| 279 | +Unit tests for the `internal/service` implementation using mock storage and memory providers. These tests ensure business logic correctness regardless of the transport layer. |
| 280 | + |
| 281 | +### 2. Integration Tests (End-to-End) |
| 282 | +- **gRPC Integration**: Using `buf` generated client to call a test gRPC server. |
| 283 | +- **REST Gateway Integration**: Using standard HTTP clients to call the `/api/v1/...` endpoints. |
| 284 | +- **GraphQL Regression**: Ensuring existing GraphQL tests still pass after the refactor. |
| 285 | + |
| 286 | +### 3. Validation Tests |
| 287 | +Assert that `protovalidate` rules (e.g., email format) correctly reject invalid requests before they reach the business logic. |
| 288 | + |
| 289 | +--- |
| 290 | + |
| 291 | +## 8. Development Workflow |
| 292 | + |
| 293 | +1. **Modify Proto**: Edit files in `proto/authorizer/v1/`. |
| 294 | +2. **Generate Code**: |
| 295 | + ```bash |
| 296 | + buf generate |
| 297 | + ``` |
| 298 | +3. **Implement Service Logic**: Update `internal/service` if new fields or logic are added. |
| 299 | +4. **Update Handlers**: Wire the new proto RPC to the service method. |
0 commit comments