@in-human-resources/backend-proto 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,63 +1,84 @@
1
- # `@in-human-resources/backend-proto`
2
-
3
- Published npm package: **Protobuf-ES** message types and **Connect-ES** service stubs for `api.v1`, `auth.v1`, and `common.v1`.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install @in-human-resources/backend-proto
9
- ```
10
-
11
- Peer dependencies (install in your app):
12
-
13
- ```bash
14
- npm install @bufbuild/protobuf @connectrpc/connect
15
- ```
16
-
17
- For browsers you may also use `@connectrpc/connect-web`.
18
-
19
- ## Usage
20
-
21
- Import Connect service definitions and create a client (example: gateway `api.v1`):
22
-
23
- ```typescript
24
- import { createClient } from '@connectrpc/connect';
25
- import { createGrpcTransport } from '@connectrpc/connect-node';
26
- import { AuthService } from '@in-human-resources/backend-proto/gen/ts/api/v1/auth_connect.js';
27
-
28
- const transport = createGrpcTransport({ baseUrl: 'http://localhost:8082', httpVersion: '2' });
29
- export const authClient = createClient(AuthService, transport);
30
- ```
31
-
32
- Use the same pattern for `company_connect.js`, `candidate_connect.js`, or internal `auth/v1/service_connect.js` as needed.
33
-
34
- ## Regenerate (maintainers)
35
-
36
- From this directory:
37
-
38
- ```bash
39
- npm install
40
- npx buf generate
41
- ```
42
-
43
- This refreshes **Go** output under `gen/` and **TypeScript** under `gen/ts/`. Commit both when APIs change.
44
-
45
- ## Publish
46
-
47
- 1. Bump `"version"` in `package.json`.
48
- 2. `npm login` (npmjs.com account with permission to publish the `@in-human-resources` scope).
49
- 3. **npm requires 2FA to publish** (or a [granular access token](https://docs.npmjs.com/about-access-tokens) with publish rights). Enable 2FA on [npm account settings](https://www.npmjs.com/settings/~/auth) (Authorization and publishing), or create a token with “Bypass two-factor authentication” if your org allows it.
50
- 4. From `proto/`:
51
-
52
- ```bash
53
- npm publish
54
- ```
55
-
56
- `publishConfig.access` is set to `public` in `package.json` for this scoped package.
57
-
58
- ## Layout
59
-
60
- | Path | Contents |
61
- |------------|-----------------------------------------------|
62
- | `gen/` | Go protobuf + gRPC (`paths=source_relative`) |
63
- | `gen/ts/` | `*_pb.ts` (messages) and `*_connect.ts` (Connect services) |
1
+ # `@in-human-resources/backend-proto`
2
+
3
+ Published npm package: **Protobuf-ES** message types and **Connect-ES** service stubs for `api.v1`, `auth.v1`, and `common.v1`.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @in-human-resources/backend-proto
9
+ ```
10
+
11
+ Peer dependencies (install in your app):
12
+
13
+ ```bash
14
+ npm install @bufbuild/protobuf @connectrpc/connect
15
+ ```
16
+
17
+ For browsers you may also use `@connectrpc/connect-web`.
18
+
19
+ ## Usage
20
+
21
+ Import Connect service definitions and create a client (example: gateway `api.v1`):
22
+
23
+ ```typescript
24
+ import { createClient } from '@connectrpc/connect';
25
+ import { createGrpcTransport } from '@connectrpc/connect-node';
26
+ import { AuthService } from '@in-human-resources/backend-proto/gen/ts/api/v1/auth_connect';
27
+
28
+ const transport = createGrpcTransport({ baseUrl: 'http://localhost:8082', httpVersion: '2' });
29
+ export const authClient = createClient(AuthService, transport);
30
+ ```
31
+
32
+ Use the same pattern for `company_connect`, `candidate_connect`, or internal `auth/v1/service_connect` (extensionless so Next/Turbopack can resolve the published `.ts` files).
33
+
34
+ > **Note:** Code is generated with `import_extension=none` so local imports inside `gen/ts` omit `.js`. The package only publishes TypeScript under `gen/ts` (not precompiled `.js`); a `.js` suffix in import specifiers is incorrect for that layout.
35
+
36
+ ## Source `.proto` files (contract reference)
37
+
38
+ The package ships the **original Protobuf schemas** next to generated code so you can read RPCs and messages without leaving `node_modules`:
39
+
40
+ | Location in the package | Contents |
41
+ |-------------------------|----------|
42
+ | `api/v1/*.proto` | Public gateway API (`AuthService`, `CompanyService`, `CandidateService`, shared types) |
43
+ | `auth/v1/service.proto` | Internal auth service (`auth.v1.AuthService`) |
44
+ | `common/v1/common.proto` | Shared `common.v1` messages |
45
+
46
+ Example path after install:
47
+
48
+ `node_modules/@in-human-resources/backend-proto/api/v1/auth.proto`
49
+
50
+ `buf.yaml` / `buf.gen.yaml` are included for Buf-based tooling if you point at this folder as a module.
51
+
52
+ ## Regenerate (maintainers)
53
+
54
+ From this directory:
55
+
56
+ ```bash
57
+ npm install
58
+ npx buf generate
59
+ ```
60
+
61
+ This refreshes **Go** output under `gen/` and **TypeScript** under `gen/ts/`. Commit both when APIs change.
62
+
63
+ ## Publish
64
+
65
+ 1. Bump `"version"` in `package.json`.
66
+ 2. `npm login` (npmjs.com account with permission to publish the `@in-human-resources` scope).
67
+ 3. **npm requires 2FA to publish** (or a [granular access token](https://docs.npmjs.com/about-access-tokens) with publish rights). Enable 2FA on [npm account settings](https://www.npmjs.com/settings/~/auth) (Authorization and publishing), or create a token with “Bypass two-factor authentication” if your org allows it.
68
+ 4. From `proto/`:
69
+
70
+ ```bash
71
+ npm publish
72
+ ```
73
+
74
+ `publishConfig.access` is set to `public` in `package.json` for this scoped package.
75
+
76
+ ## What ships on npm
77
+
78
+ | Path | Contents |
79
+ |------|----------|
80
+ | `gen/ts/` | Generated TypeScript (`*_pb.ts`, `*_connect.ts`) |
81
+ | `api/`, `auth/`, `common/` | **Source `.proto` files** (API contract) |
82
+ | `buf.yaml`, `buf.gen.yaml` | Buf module + codegen config |
83
+
84
+ Go stubs under `gen/*.go` are **not** published (they stay in the git repo for the backend workspace only).
@@ -0,0 +1,282 @@
1
+ # API Gateway - Client-Facing gRPC Services
2
+
3
+ This directory contains the **client-facing gRPC service definitions** that mobile and web applications use to communicate with the backend.
4
+
5
+ ## Architecture Overview
6
+
7
+ The API gateway follows a **two-layer proto pattern**:
8
+
9
+ ```
10
+ ┌─────────────────────────────────────────────────────────┐
11
+ │ Mobile/Web Clients (gRPC) │
12
+ │ - Type-safe generated stubs │
13
+ │ - Kotlin, Swift, TypeScript │
14
+ └───────────────────────┬─────────────────────────────────┘
15
+
16
+ │ gRPC (proto/api/v1/*.proto)
17
+ │ Port: 8082
18
+
19
+ ┌─────────────────────────────────────────────────────────┐
20
+ │ API Gateway (BFF) │
21
+ │ - Authentication/validation │
22
+ │ - Rate limiting │
23
+ │ - Request logging │
24
+ │ - Metrics collection │
25
+ └───────────────────────┬─────────────────────────────────┘
26
+
27
+ │ gRPC (proto/auth/v1/service.proto)
28
+
29
+
30
+ ┌─────────────────────────────────────────────────────────┐
31
+ │ Backend Services │
32
+ │ - Auth Service (port 9091) │
33
+ │ - Future: Jobs, Workflows, etc. │
34
+ └─────────────────────────────────────────────────────────┘
35
+ ```
36
+
37
+ ## Why Two Proto Layers?
38
+
39
+ ### Layer 1: Client-Facing API (`proto/api/v1/`)
40
+ - **Purpose**: Public contract for mobile/web clients
41
+ - **Location**: This directory
42
+ - **Features**:
43
+ - Simplified, client-friendly message formats
44
+ - Versioned (`/api/v1`, `/api/v2`)
45
+ - Can aggregate multiple backend calls
46
+ - Stable interface that doesn't change with backend refactors
47
+
48
+ ### Layer 2: Internal Backend (`proto/auth/v1/service.proto`)
49
+ - **Purpose**: Internal service-to-service communication
50
+ - **Location**: `proto/<service>/v1/`
51
+ - **Features**:
52
+ - Domain-specific logic
53
+ - Can evolve independently
54
+ - Optimized for backend needs
55
+
56
+ ## Services
57
+
58
+ ### AuthService (`auth.proto`)
59
+ Handles all authentication and user management operations:
60
+ - Registration & Login
61
+ - Token refresh & logout
62
+ - Email verification
63
+ - Password management
64
+ - Profile management
65
+ - Session management
66
+ - OAuth integration
67
+
68
+ **Public methods** (no auth required):
69
+ - `Register`, `Login`, `RefreshToken`
70
+ - `VerifyEmail`, `ResendVerificationEmail`
71
+ - `RequestPasswordReset`, `ResetPassword`
72
+ - `OAuthLogin`, `OAuthCallback`
73
+
74
+ **Protected methods** (require auth token in metadata):
75
+ - All other methods
76
+
77
+ ### CompanyService (`company.proto`)
78
+ Manages company profiles and team members:
79
+ - Company CRUD operations
80
+ - Team member management
81
+ - Company onboarding flow
82
+
83
+ ### CandidateService (`candidate.proto`)
84
+ Manages candidate profiles and job applications:
85
+ - Profile management
86
+ - Skills & experience
87
+ - Job preferences
88
+ - Application readiness checks
89
+ - Candidate onboarding flow
90
+
91
+ ## Client Integration
92
+
93
+ ### Mobile (Kotlin)
94
+
95
+ ```kotlin
96
+ import io.grpc.ManagedChannelBuilder
97
+ import com.inhuman.backend.apiv1.AuthServiceGrpc
98
+ import com.inhuman.backend.apiv1.RegisterRequest
99
+
100
+ // Create channel
101
+ val channel = ManagedChannelBuilder
102
+ .forAddress("api.example.com", 8082)
103
+ .useTransportSecurity()
104
+ .build()
105
+
106
+ // Create client stub
107
+ val authClient = AuthServiceGrpc.newBlockingStub(channel)
108
+
109
+ // Make request
110
+ val response = authClient.register(
111
+ RegisterRequest.newBuilder()
112
+ .setEmail("user@example.com")
113
+ .setPassword("secret")
114
+ .setName("John Doe")
115
+ .setUserType("candidate")
116
+ .build()
117
+ )
118
+
119
+ println("Access token: ${response.accessToken}")
120
+ ```
121
+
122
+ ### Web (TypeScript with grpc-web)
123
+
124
+ ```typescript
125
+ import { AuthServiceClient } from './proto/api/v1/auth_grpc_web_pb';
126
+ import { RegisterRequest } from './proto/api/v1/auth_pb';
127
+
128
+ // Create client
129
+ const client = new AuthServiceClient('https://api.example.com:8082');
130
+
131
+ // Make request
132
+ const request = new RegisterRequest();
133
+ request.setEmail('user@example.com');
134
+ request.setPassword('secret');
135
+ request.setName('John Doe');
136
+ request.setUserType('candidate');
137
+
138
+ client.register(request, {}, (err, response) => {
139
+ if (err) {
140
+ console.error(err);
141
+ } else {
142
+ console.log('Access token:', response.getAccessToken());
143
+ }
144
+ });
145
+ ```
146
+
147
+ ## Authentication
148
+
149
+ Protected RPCs require a bearer token in the gRPC metadata:
150
+
151
+ ```kotlin
152
+ // Kotlin
153
+ val metadata = Metadata()
154
+ metadata.put(
155
+ Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER),
156
+ "Bearer $accessToken"
157
+ )
158
+ val stub = authClient.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata))
159
+ ```
160
+
161
+ ```typescript
162
+ // TypeScript
163
+ const metadata = {
164
+ 'authorization': `Bearer ${accessToken}`
165
+ };
166
+ client.getProfile(request, metadata, callback);
167
+ ```
168
+
169
+ ## Error Handling
170
+
171
+ The gateway returns standard gRPC status codes:
172
+
173
+ | Code | Meaning | Common Causes |
174
+ |------|---------|---------------|
175
+ | `OK` (0) | Success | Request completed successfully |
176
+ | `INVALID_ARGUMENT` (3) | Bad request | Missing required fields, invalid format |
177
+ | `UNAUTHENTICATED` (16) | Auth required | Missing or invalid token |
178
+ | `PERMISSION_DENIED` (7) | Forbidden | User lacks permission |
179
+ | `NOT_FOUND` (5) | Not found | Resource doesn't exist |
180
+ | `ALREADY_EXISTS` (6) | Conflict | Duplicate email, etc. |
181
+ | `RESOURCE_EXHAUSTED` (8) | Rate limited | Too many requests |
182
+ | `INTERNAL` (13) | Server error | Unexpected backend error |
183
+
184
+ ## Rate Limiting
185
+
186
+ The gateway enforces rate limits per client IP:
187
+ - **Default**: 100 requests/second per IP
188
+ - **Burst**: 20 additional requests allowed
189
+ - **Response**: `RESOURCE_EXHAUSTED` (code 8) when exceeded
190
+
191
+ ## Monitoring
192
+
193
+ ### Health Checks (HTTP)
194
+ - `GET http://api.example.com:8083/health` - Liveness probe
195
+ - `GET http://api.example.com:8083/readyz` - Readiness probe (checks Redis)
196
+
197
+ ### Metrics (Prometheus)
198
+ - `GET http://api.example.com:8083/metrics`
199
+
200
+ Metrics exposed:
201
+ - `grpc_requests_total{method, code}` - Total requests
202
+ - `grpc_request_duration_seconds{method}` - Request latency
203
+ - `grpc_requests_in_flight` - Current concurrent requests
204
+
205
+ ## Development
206
+
207
+ ### Generating Client Code
208
+
209
+ **Go** (server):
210
+ ```bash
211
+ make proto-gen
212
+ ```
213
+
214
+ **Kotlin** (Android):
215
+ ```bash
216
+ protoc --kotlin_out=app/src/main/java \
217
+ --grpc-kotlin_out=app/src/main/java \
218
+ proto/api/v1/*.proto
219
+ ```
220
+
221
+ **Swift** (iOS):
222
+ ```bash
223
+ protoc --swift_out=Sources \
224
+ --grpc-swift_out=Sources \
225
+ proto/api/v1/*.proto
226
+ ```
227
+
228
+ **TypeScript** (Web):
229
+ ```bash
230
+ protoc --js_out=import_style=commonjs:src \
231
+ --grpc-web_out=import_style=typescript,mode=grpcwebtext:src \
232
+ proto/api/v1/*.proto
233
+ ```
234
+
235
+ ### Testing
236
+
237
+ Use `grpcurl` to test endpoints:
238
+
239
+ ```bash
240
+ # Public endpoint (no auth)
241
+ grpcurl -plaintext \
242
+ -d '{"email":"test@example.com","password":"secret","name":"Test","user_type":"candidate"}' \
243
+ localhost:8082 \
244
+ api.v1.AuthService/Register
245
+
246
+ # Protected endpoint (with auth)
247
+ grpcurl -plaintext \
248
+ -H "authorization: Bearer $TOKEN" \
249
+ localhost:8082 \
250
+ api.v1.AuthService/GetProfile
251
+ ```
252
+
253
+ ## Versioning Strategy
254
+
255
+ - **Current**: `api.v1` (stable)
256
+ - **Future**: `api.v2` (when breaking changes needed)
257
+
258
+ Breaking changes require a new version. Non-breaking changes can be added to v1:
259
+ - ✅ Adding new RPCs
260
+ - ✅ Adding optional fields
261
+ - ✅ Adding new enum values (with default handling)
262
+ - ❌ Removing RPCs
263
+ - ❌ Removing fields
264
+ - ❌ Changing field types
265
+
266
+ ## Benefits of gRPC
267
+
268
+ Compared to REST/JSON:
269
+
270
+ 1. **Type Safety**: Generated stubs catch errors at compile time
271
+ 2. **Performance**: Binary protobuf is 3-10x smaller than JSON
272
+ 3. **Streaming**: Supports bidirectional streaming (future use)
273
+ 4. **Auto-completion**: IDEs provide full method/field completion
274
+ 5. **Consistency**: Same types across all platforms
275
+ 6. **Versioning**: Explicit version in package name
276
+
277
+ ## Next Steps
278
+
279
+ - [ ] Add streaming support for real-time features
280
+ - [ ] Implement gRPC reflection for dynamic discovery
281
+ - [ ] Add OpenTelemetry tracing
282
+ - [ ] Create client SDKs for each platform
@@ -0,0 +1,228 @@
1
+ syntax = "proto3";
2
+
3
+ package api.v1;
4
+
5
+ option go_package = "github.com/InHuman-Resources/Backend/Go/proto/gen/api/v1;apiv1";
6
+
7
+ import "api/v1/common.proto";
8
+
9
+ // AuthService is the client-facing authentication service.
10
+ // Mobile and web clients call these RPCs directly.
11
+ service AuthService {
12
+ // Core authentication
13
+ rpc Register(RegisterRequest) returns (RegisterResponse);
14
+ rpc Login(LoginRequest) returns (LoginResponse);
15
+ rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
16
+ rpc Logout(LogoutRequest) returns (Empty);
17
+
18
+ // Email verification
19
+ rpc VerifyEmail(VerifyEmailRequest) returns (Empty);
20
+ rpc ResendVerificationEmail(ResendVerificationEmailRequest) returns (Empty);
21
+ rpc SendEmailVerificationOtp(SendEmailVerificationOtpRequest) returns (Empty);
22
+ rpc VerifyEmailWithOtp(VerifyEmailWithOtpRequest) returns (Empty);
23
+
24
+ // Password management
25
+ rpc RequestPasswordReset(RequestPasswordResetRequest) returns (Empty);
26
+ rpc ResetPassword(ResetPasswordRequest) returns (Empty);
27
+ rpc ChangePassword(ChangePasswordRequest) returns (Empty);
28
+
29
+ // Profile
30
+ rpc GetProfile(GetProfileRequest) returns (GetProfileResponse);
31
+ rpc UpdateProfile(UpdateProfileRequest) returns (Empty);
32
+
33
+ // Sessions
34
+ rpc ListSessions(ListSessionsRequest) returns (ListSessionsResponse);
35
+ rpc RevokeSession(RevokeSessionRequest) returns (Empty);
36
+ rpc RevokeAllSessions(RevokeAllSessionsRequest) returns (Empty);
37
+
38
+ // OAuth
39
+ rpc OAuthLogin(OAuthLoginRequest) returns (OAuthLoginResponse);
40
+ rpc OAuthCallback(OAuthCallbackRequest) returns (OAuthCallbackResponse);
41
+ rpc LinkOAuthAccount(LinkOAuthAccountRequest) returns (Empty);
42
+ rpc UnlinkOAuthAccount(UnlinkOAuthAccountRequest) returns (Empty);
43
+ }
44
+
45
+ // ==================== Core Authentication ====================
46
+
47
+ message RegisterRequest {
48
+ string email = 1;
49
+ string password = 2;
50
+ string name = 3;
51
+ string user_type = 4; // "company" or "candidate"
52
+ string professional_headline = 5; // Required for candidates
53
+ string company_invite_token = 6; // optional; company invites when user_type=company
54
+ RequestContext context = 10;
55
+ }
56
+
57
+ message RegisterResponse {
58
+ string user_id = 1;
59
+ string access_token = 2;
60
+ string refresh_token = 3;
61
+ string user_type = 4;
62
+ bool email_verified = 5;
63
+ ProfileCompletion profile_completion = 6;
64
+ string onboarding_step = 7;
65
+ bool onboarding_complete = 8;
66
+ }
67
+
68
+ message LoginRequest {
69
+ string email = 1;
70
+ string password = 2;
71
+ RequestContext context = 10;
72
+ }
73
+
74
+ message LoginResponse {
75
+ string user_id = 1;
76
+ string access_token = 2;
77
+ string refresh_token = 3;
78
+ string user_type = 4;
79
+ string company_id = 5;
80
+ string hierarchy_level = 6;
81
+ string department = 7;
82
+ bool email_verified = 8;
83
+ string onboarding_status = 9;
84
+ string onboarding_step = 10;
85
+ bool onboarding_complete = 11;
86
+ }
87
+
88
+ message RefreshTokenRequest {
89
+ string refresh_token = 1;
90
+ }
91
+
92
+ message RefreshTokenResponse {
93
+ string user_id = 1;
94
+ string access_token = 2;
95
+ string refresh_token = 3;
96
+ string user_type = 4;
97
+ string company_id = 5;
98
+ string hierarchy_level = 6;
99
+ string department = 7;
100
+ bool email_verified = 8;
101
+ string onboarding_status = 9;
102
+ string onboarding_step = 10;
103
+ bool onboarding_complete = 11;
104
+ }
105
+
106
+ message LogoutRequest {
107
+ // Session ID is extracted from auth token in interceptor
108
+ }
109
+
110
+ // ==================== Email Verification ====================
111
+
112
+ message VerifyEmailRequest {
113
+ string token = 1;
114
+ }
115
+
116
+ message ResendVerificationEmailRequest {
117
+ string email = 1;
118
+ }
119
+
120
+ message SendEmailVerificationOtpRequest {
121
+ string email = 1;
122
+ }
123
+
124
+ message VerifyEmailWithOtpRequest {
125
+ string email = 1;
126
+ string code = 2;
127
+ }
128
+
129
+ // ==================== Password Management ====================
130
+
131
+ message RequestPasswordResetRequest {
132
+ string email = 1;
133
+ }
134
+
135
+ message ResetPasswordRequest {
136
+ string token = 1;
137
+ string new_password = 2;
138
+ }
139
+
140
+ message ChangePasswordRequest {
141
+ string old_password = 1;
142
+ string new_password = 2;
143
+ // User ID is extracted from auth token in interceptor
144
+ }
145
+
146
+ // ==================== Profile ====================
147
+
148
+ message GetProfileRequest {
149
+ // User ID is extracted from auth token in interceptor
150
+ }
151
+
152
+ message GetProfileResponse {
153
+ string user_id = 1;
154
+ string email = 2;
155
+ string name = 3;
156
+ string user_type = 4;
157
+ string company_id = 5;
158
+ string company_name = 6;
159
+ string hierarchy_level = 7;
160
+ string department = 8;
161
+ bool email_verified = 9;
162
+ string created_at = 10;
163
+ }
164
+
165
+ message UpdateProfileRequest {
166
+ string name = 1;
167
+ // User ID is extracted from auth token in interceptor
168
+ }
169
+
170
+ // ==================== Sessions ====================
171
+
172
+ message ListSessionsRequest {
173
+ // User ID is extracted from auth token in interceptor
174
+ }
175
+
176
+ message ListSessionsResponse {
177
+ repeated SessionInfo sessions = 1;
178
+ }
179
+
180
+ message RevokeSessionRequest {
181
+ string session_id = 1;
182
+ // User ID is extracted from auth token in interceptor
183
+ }
184
+
185
+ message RevokeAllSessionsRequest {
186
+ // User ID is extracted from auth token in interceptor
187
+ }
188
+
189
+ // ==================== OAuth ====================
190
+
191
+ message OAuthLoginRequest {
192
+ string provider = 1; // "google", "github", "linkedin", etc.
193
+ }
194
+
195
+ message OAuthLoginResponse {
196
+ string redirect_url = 1;
197
+ }
198
+
199
+ message OAuthCallbackRequest {
200
+ string provider = 1;
201
+ string code = 2;
202
+ string state = 3;
203
+ }
204
+
205
+ message OAuthCallbackResponse {
206
+ string user_id = 1;
207
+ string access_token = 2;
208
+ string refresh_token = 3;
209
+ string user_type = 4;
210
+ string company_id = 5;
211
+ string hierarchy_level = 6;
212
+ string department = 7;
213
+ bool email_verified = 8;
214
+ string onboarding_status = 9;
215
+ string onboarding_step = 10;
216
+ bool onboarding_complete = 11;
217
+ }
218
+
219
+ message LinkOAuthAccountRequest {
220
+ string provider = 1;
221
+ string code = 2;
222
+ // User ID is extracted from auth token in interceptor
223
+ }
224
+
225
+ message UnlinkOAuthAccountRequest {
226
+ string provider = 1;
227
+ // User ID is extracted from auth token in interceptor
228
+ }