@aepstore-dev/contracts 1.1.0 → 1.3.0

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/gen/auth.ts CHANGED
@@ -34,32 +34,16 @@ export interface RegisterRequest {
34
34
  ip: string;
35
35
  }
36
36
 
37
- /** Standard tokens-plus-user payload returned by Login / Register / RefreshToken. */
38
- export interface AuthResponse {
39
- accessToken: string;
40
- refreshToken: string;
41
- user: AuthUser | undefined;
42
- }
43
-
44
- export interface AuthUser {
45
- id: string;
46
- email: string;
47
- username: string;
48
- /** primary role for backwards compat ("User" / "Administrator" / "Moderator") */
49
- role: string;
50
- }
51
-
52
- /** Code types: '2fa', 'reset', 'confirm', 'totp' (legacy strings preserved as-is). */
37
+ /** type { twofa, reset, totp_generate, totp_activate, confirm } (legacy strings) */
53
38
  export interface CreateCodeRequest {
54
39
  email: string;
55
40
  type: string;
56
41
  }
57
42
 
58
- export interface CreateCodeResponse {
59
- ok: boolean;
60
- }
61
-
62
- /** `new_password` is only used by the 'reset' flow. */
43
+ /**
44
+ * type ∈ { twofa, twofa_deactivate, reset, totp, totp_deactivate, backup, confirm }
45
+ * new_password is only used by the 'reset' flow.
46
+ */
63
47
  export interface VerifyCodeRequest {
64
48
  email: string;
65
49
  code: string;
@@ -68,29 +52,10 @@ export interface VerifyCodeRequest {
68
52
  newPassword: string;
69
53
  }
70
54
 
71
- /**
72
- * Some verification flows (e.g. confirm-on-register) issue tokens immediately;
73
- * others just return ok=true.
74
- */
75
- export interface VerifyCodeResponse {
76
- ok: boolean;
77
- /** optional */
78
- accessToken: string;
79
- /** optional */
80
- refreshToken: string;
81
- }
82
-
83
55
  export interface CheckTokenRequest {
84
56
  token: string;
85
57
  }
86
58
 
87
- export interface CheckTokenResponse {
88
- valid: boolean;
89
- userId: string;
90
- /** populated when valid=false */
91
- reason: string;
92
- }
93
-
94
59
  export interface RefreshTokenRequest {
95
60
  refreshToken: string;
96
61
  /** optional */
@@ -103,24 +68,54 @@ export interface Get2faStatusRequest {
103
68
  username: string;
104
69
  }
105
70
 
106
- export interface Get2faStatusResponse {
107
- enabled: boolean;
108
- type: TwoFactorType;
109
- }
110
-
111
71
  export interface GetProfileRequest {
112
72
  userId: string;
113
73
  }
114
74
 
115
- export interface UserProfileResponse {
75
+ export interface AuthUser {
116
76
  id: string;
117
- email: string;
118
77
  username: string;
119
- fullName: string;
78
+ email: string;
79
+ avatarUrl: string;
80
+ bannerUrl: string;
81
+ roles: string[];
82
+ /** primary role (roles[0]) */
83
+ role: string;
120
84
  isVerified: boolean;
85
+ twoFactorType: TwoFactorType;
121
86
  twoFactorEnabled: boolean;
87
+ /** Decimal as string (D7) */
88
+ balance: string;
89
+ }
90
+
91
+ /**
92
+ * Unified result for login/register/createCode/verifyCode/refresh. Which fields
93
+ * are populated depends on the flow:
94
+ * - fully authenticated → access_token + refresh_token + expires_in + user
95
+ * - 2FA required / pending confirm → message + requires_2fa + two_factor_type + user (no tokens)
96
+ * - totp_generate → secret + otpauth + qr (+ user)
97
+ * - totp_activate → backup_codes (+ tokens/user)
98
+ */
99
+ export interface AuthResult {
100
+ accessToken: string;
101
+ refreshToken: string;
102
+ expiresIn: number;
103
+ user: AuthUser | undefined;
104
+ message: string;
105
+ requires2fa: boolean;
122
106
  twoFactorType: TwoFactorType;
123
- roles: string[];
107
+ /** TOTP setup extras */
108
+ secret: string;
109
+ otpauth: string;
110
+ /** data URL (PNG) */
111
+ qr: string;
112
+ backupCodes: string[];
113
+ }
114
+
115
+ export interface TwoFactorStatusResponse {
116
+ twoFactorType: TwoFactorType;
117
+ has2fa: boolean;
118
+ isVerified: boolean;
124
119
  }
125
120
 
126
121
  export interface Empty {
@@ -130,64 +125,60 @@ export const AUTH_V1_PACKAGE_NAME = "auth.v1";
130
125
 
131
126
  /**
132
127
  * Ported from legacy auth-service (@MessagePattern → @GrpcMethod).
133
- * All tokens are HMAC primitives minted by @aepstore-dev/passport;
134
- * the access token only carries `userId`. Username / roles / 2FA state
135
- * must be looked up via Prisma or RbacService on the consumer side.
128
+ * Tokens are HMAC primitives from @aepstore-dev/passport:
129
+ * - access token: short TTL, carries userId; the gateway validates it
130
+ * locally via PassportAuthGuard (no round-trip here).
131
+ * - refresh token: long TTL HMAC + a RefreshToken DB row (device list +
132
+ * revocation). Refresh verifies the HMAC AND that the row still exists.
136
133
  */
137
134
 
138
135
  export interface AuthServiceClient {
139
- login(request: LoginRequest): Observable<AuthResponse>;
136
+ login(request: LoginRequest): Observable<AuthResult>;
140
137
 
141
- register(request: RegisterRequest): Observable<AuthResponse>;
138
+ register(request: RegisterRequest): Observable<AuthResult>;
142
139
 
143
- createCode(request: CreateCodeRequest): Observable<CreateCodeResponse>;
140
+ createCode(request: CreateCodeRequest): Observable<AuthResult>;
144
141
 
145
- verifyCode(request: VerifyCodeRequest): Observable<VerifyCodeResponse>;
142
+ verifyCode(request: VerifyCodeRequest): Observable<AuthResult>;
146
143
 
147
- checkToken(request: CheckTokenRequest): Observable<CheckTokenResponse>;
144
+ checkToken(request: CheckTokenRequest): Observable<AuthUser>;
148
145
 
149
- refreshToken(request: RefreshTokenRequest): Observable<AuthResponse>;
146
+ refreshToken(request: RefreshTokenRequest): Observable<AuthResult>;
150
147
 
151
- get2FaStatus(request: Get2faStatusRequest): Observable<Get2faStatusResponse>;
148
+ get2FaStatus(request: Get2faStatusRequest): Observable<TwoFactorStatusResponse>;
152
149
 
153
- getProfile(request: GetProfileRequest): Observable<UserProfileResponse>;
150
+ getProfile(request: GetProfileRequest): Observable<AuthUser>;
154
151
 
155
152
  cleanupExpiredTotpSetups(request: Empty): Observable<Empty>;
156
153
  }
157
154
 
158
155
  /**
159
156
  * Ported from legacy auth-service (@MessagePattern → @GrpcMethod).
160
- * All tokens are HMAC primitives minted by @aepstore-dev/passport;
161
- * the access token only carries `userId`. Username / roles / 2FA state
162
- * must be looked up via Prisma or RbacService on the consumer side.
157
+ * Tokens are HMAC primitives from @aepstore-dev/passport:
158
+ * - access token: short TTL, carries userId; the gateway validates it
159
+ * locally via PassportAuthGuard (no round-trip here).
160
+ * - refresh token: long TTL HMAC + a RefreshToken DB row (device list +
161
+ * revocation). Refresh verifies the HMAC AND that the row still exists.
163
162
  */
164
163
 
165
164
  export interface AuthServiceController {
166
- login(request: LoginRequest): Promise<AuthResponse> | Observable<AuthResponse> | AuthResponse;
165
+ login(request: LoginRequest): Promise<AuthResult> | Observable<AuthResult> | AuthResult;
167
166
 
168
- register(request: RegisterRequest): Promise<AuthResponse> | Observable<AuthResponse> | AuthResponse;
167
+ register(request: RegisterRequest): Promise<AuthResult> | Observable<AuthResult> | AuthResult;
169
168
 
170
- createCode(
171
- request: CreateCodeRequest,
172
- ): Promise<CreateCodeResponse> | Observable<CreateCodeResponse> | CreateCodeResponse;
169
+ createCode(request: CreateCodeRequest): Promise<AuthResult> | Observable<AuthResult> | AuthResult;
173
170
 
174
- verifyCode(
175
- request: VerifyCodeRequest,
176
- ): Promise<VerifyCodeResponse> | Observable<VerifyCodeResponse> | VerifyCodeResponse;
171
+ verifyCode(request: VerifyCodeRequest): Promise<AuthResult> | Observable<AuthResult> | AuthResult;
177
172
 
178
- checkToken(
179
- request: CheckTokenRequest,
180
- ): Promise<CheckTokenResponse> | Observable<CheckTokenResponse> | CheckTokenResponse;
173
+ checkToken(request: CheckTokenRequest): Promise<AuthUser> | Observable<AuthUser> | AuthUser;
181
174
 
182
- refreshToken(request: RefreshTokenRequest): Promise<AuthResponse> | Observable<AuthResponse> | AuthResponse;
175
+ refreshToken(request: RefreshTokenRequest): Promise<AuthResult> | Observable<AuthResult> | AuthResult;
183
176
 
184
177
  get2FaStatus(
185
178
  request: Get2faStatusRequest,
186
- ): Promise<Get2faStatusResponse> | Observable<Get2faStatusResponse> | Get2faStatusResponse;
179
+ ): Promise<TwoFactorStatusResponse> | Observable<TwoFactorStatusResponse> | TwoFactorStatusResponse;
187
180
 
188
- getProfile(
189
- request: GetProfileRequest,
190
- ): Promise<UserProfileResponse> | Observable<UserProfileResponse> | UserProfileResponse;
181
+ getProfile(request: GetProfileRequest): Promise<AuthUser> | Observable<AuthUser> | AuthUser;
191
182
 
192
183
  cleanupExpiredTotpSetups(request: Empty): Promise<Empty> | Observable<Empty> | Empty;
193
184
  }
package/gen/upload.ts CHANGED
@@ -10,84 +10,203 @@ import { Observable } from "rxjs";
10
10
 
11
11
  export const protobufPackage = "upload.v1";
12
12
 
13
- export interface FileChunk {
14
- filename: string;
15
- data: Uint8Array;
13
+ /**
14
+ * Determines the object key prefix (and future ACL/privacy policy).
15
+ * ATTACHMENT = paid product deliverable (private; presigned GET only after purchase).
16
+ * MEDIA / AVATAR / BANNER = preview assets.
17
+ */
18
+ export enum UploadKind {
19
+ UPLOAD_KIND_UNSPECIFIED = 0,
20
+ UPLOAD_KIND_ATTACHMENT = 1,
21
+ UPLOAD_KIND_MEDIA = 2,
22
+ UPLOAD_KIND_AVATAR = 3,
23
+ UPLOAD_KIND_BANNER = 4,
24
+ UNRECOGNIZED = -1,
25
+ }
26
+
27
+ export interface Ok {
28
+ ok: boolean;
29
+ }
30
+
31
+ export interface CreateMultipartUploadRequest {
32
+ fileName: string;
16
33
  mimeType: string;
17
- isLast: boolean;
18
- chunkIndex: number;
34
+ fileSize: number;
35
+ kind: UploadKind;
36
+ /** user initiating, for key namespacing + audit */
37
+ ownerId: string;
38
+ }
39
+
40
+ export interface CreateMultipartUploadResponse {
41
+ /** S3 multipart UploadId */
19
42
  uploadId: string;
43
+ /** object key this service generated */
44
+ fileKey: string;
45
+ /** recommended part size (bytes) the client should slice with */
46
+ partSize: number;
47
+ /** number of parts for file_size at part_size */
48
+ partCount: number;
20
49
  }
21
50
 
22
- export interface UploadResponse {
23
- fileUrl: string;
24
- success: boolean;
25
- message: string;
51
+ export interface SignPartRequest {
52
+ fileKey: string;
26
53
  uploadId: string;
54
+ /** 1-based */
55
+ partNumber: number;
56
+ }
57
+
58
+ export interface SignPartResponse {
59
+ /** presigned PUT URL for this single part */
60
+ url: string;
61
+ expiresIn: number;
62
+ }
63
+
64
+ export interface CompletedPart {
65
+ partNumber: number;
66
+ /** ETag the client received in the S3 PUT response header for this part */
67
+ etag: string;
68
+ }
69
+
70
+ export interface CompleteMultipartUploadRequest {
71
+ fileKey: string;
72
+ uploadId: string;
73
+ parts: CompletedPart[];
74
+ }
75
+
76
+ export interface CompleteMultipartUploadResponse {
77
+ fileKey: string;
78
+ /** final object location (internal reference) */
79
+ location: string;
27
80
  fileSize: number;
28
81
  }
29
82
 
30
- export interface UploadStatusRequest {
83
+ export interface AbortMultipartUploadRequest {
84
+ fileKey: string;
31
85
  uploadId: string;
32
86
  }
33
87
 
34
- export interface UploadStatusResponse {
35
- status: string;
36
- progressPercent: number;
37
- message: string;
88
+ export interface CreateUploadUrlRequest {
89
+ fileName: string;
90
+ mimeType: string;
91
+ kind: UploadKind;
92
+ ownerId: string;
93
+ }
94
+
95
+ export interface CreateUploadUrlResponse {
96
+ /** presigned PUT URL (client PUTs the whole file) */
97
+ url: string;
98
+ fileKey: string;
99
+ expiresIn: number;
38
100
  }
39
101
 
40
102
  export interface SignedUrlRequest {
41
103
  fileKey: string;
104
+ /** optional, default 3600 */
42
105
  expiresIn: number;
106
+ /** optional: forces Content-Disposition attachment filename */
107
+ downloadFileName: string;
43
108
  }
44
109
 
45
110
  export interface SignedUrlResponse {
46
111
  signedUrl: string;
47
- success: boolean;
48
- message: string;
112
+ expiresIn: number;
113
+ }
114
+
115
+ export interface DeleteObjectRequest {
116
+ fileKey: string;
49
117
  }
50
118
 
51
119
  export const UPLOAD_V1_PACKAGE_NAME = "upload.v1";
52
120
 
53
121
  /**
54
- * Ported from legacy libs/shared/src/proto/upload.proto.
55
- * Bumped package to upload.v1 for consistency with the rest of the new stack.
122
+ * Thin signing service. File bytes NEVER pass through this service — clients
123
+ * upload/download directly to/from S3 (Yandex Object Storage) using presigned
124
+ * URLs this service mints. Memory/bandwidth flat regardless of file size.
125
+ *
126
+ * Large files (product deliverables, up to ~1GB) use the multipart flow:
127
+ * CreateMultipartUpload → (SignPart × N, client PUTs each part to S3) →
128
+ * CompleteMultipartUpload (or AbortMultipartUpload on failure)
129
+ *
130
+ * Small files (preview images, avatars) use the single-PUT flow: CreateUploadUrl.
131
+ *
132
+ * Downloads are gated elsewhere (products-service verifies purchase, then calls
133
+ * GenerateSignedUrl). This service does not know about purchases.
56
134
  */
57
135
 
58
136
  export interface UploadServiceClient {
59
- uploadFile(request: Observable<FileChunk>): Observable<UploadResponse>;
137
+ createMultipartUpload(request: CreateMultipartUploadRequest): Observable<CreateMultipartUploadResponse>;
138
+
139
+ signPart(request: SignPartRequest): Observable<SignPartResponse>;
140
+
141
+ completeMultipartUpload(request: CompleteMultipartUploadRequest): Observable<CompleteMultipartUploadResponse>;
142
+
143
+ abortMultipartUpload(request: AbortMultipartUploadRequest): Observable<Ok>;
60
144
 
61
- getUploadStatus(request: UploadStatusRequest): Observable<UploadStatusResponse>;
145
+ createUploadUrl(request: CreateUploadUrlRequest): Observable<CreateUploadUrlResponse>;
62
146
 
63
147
  generateSignedUrl(request: SignedUrlRequest): Observable<SignedUrlResponse>;
148
+
149
+ deleteObject(request: DeleteObjectRequest): Observable<Ok>;
64
150
  }
65
151
 
66
152
  /**
67
- * Ported from legacy libs/shared/src/proto/upload.proto.
68
- * Bumped package to upload.v1 for consistency with the rest of the new stack.
153
+ * Thin signing service. File bytes NEVER pass through this service — clients
154
+ * upload/download directly to/from S3 (Yandex Object Storage) using presigned
155
+ * URLs this service mints. Memory/bandwidth flat regardless of file size.
156
+ *
157
+ * Large files (product deliverables, up to ~1GB) use the multipart flow:
158
+ * CreateMultipartUpload → (SignPart × N, client PUTs each part to S3) →
159
+ * CompleteMultipartUpload (or AbortMultipartUpload on failure)
160
+ *
161
+ * Small files (preview images, avatars) use the single-PUT flow: CreateUploadUrl.
162
+ *
163
+ * Downloads are gated elsewhere (products-service verifies purchase, then calls
164
+ * GenerateSignedUrl). This service does not know about purchases.
69
165
  */
70
166
 
71
167
  export interface UploadServiceController {
72
- uploadFile(request: Observable<FileChunk>): Promise<UploadResponse> | Observable<UploadResponse> | UploadResponse;
168
+ createMultipartUpload(
169
+ request: CreateMultipartUploadRequest,
170
+ ): Promise<CreateMultipartUploadResponse> | Observable<CreateMultipartUploadResponse> | CreateMultipartUploadResponse;
171
+
172
+ signPart(request: SignPartRequest): Promise<SignPartResponse> | Observable<SignPartResponse> | SignPartResponse;
173
+
174
+ completeMultipartUpload(
175
+ request: CompleteMultipartUploadRequest,
176
+ ):
177
+ | Promise<CompleteMultipartUploadResponse>
178
+ | Observable<CompleteMultipartUploadResponse>
179
+ | CompleteMultipartUploadResponse;
73
180
 
74
- getUploadStatus(
75
- request: UploadStatusRequest,
76
- ): Promise<UploadStatusResponse> | Observable<UploadStatusResponse> | UploadStatusResponse;
181
+ abortMultipartUpload(request: AbortMultipartUploadRequest): Promise<Ok> | Observable<Ok> | Ok;
182
+
183
+ createUploadUrl(
184
+ request: CreateUploadUrlRequest,
185
+ ): Promise<CreateUploadUrlResponse> | Observable<CreateUploadUrlResponse> | CreateUploadUrlResponse;
77
186
 
78
187
  generateSignedUrl(
79
188
  request: SignedUrlRequest,
80
189
  ): Promise<SignedUrlResponse> | Observable<SignedUrlResponse> | SignedUrlResponse;
190
+
191
+ deleteObject(request: DeleteObjectRequest): Promise<Ok> | Observable<Ok> | Ok;
81
192
  }
82
193
 
83
194
  export function UploadServiceControllerMethods() {
84
195
  return function (constructor: Function) {
85
- const grpcMethods: string[] = ["getUploadStatus", "generateSignedUrl"];
196
+ const grpcMethods: string[] = [
197
+ "createMultipartUpload",
198
+ "signPart",
199
+ "completeMultipartUpload",
200
+ "abortMultipartUpload",
201
+ "createUploadUrl",
202
+ "generateSignedUrl",
203
+ "deleteObject",
204
+ ];
86
205
  for (const method of grpcMethods) {
87
206
  const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
88
207
  GrpcMethod("UploadService", method)(constructor.prototype[method], method, descriptor);
89
208
  }
90
- const grpcStreamMethods: string[] = ["uploadFile"];
209
+ const grpcStreamMethods: string[] = [];
91
210
  for (const method of grpcStreamMethods) {
92
211
  const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
93
212
  GrpcStreamMethod("UploadService", method)(constructor.prototype[method], method, descriptor);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aepstore-dev/contracts",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Protobuf definitions for aepstore microservices",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/proto/auth.proto CHANGED
@@ -3,21 +3,33 @@ syntax = "proto3";
3
3
  package auth.v1;
4
4
 
5
5
  // Ported from legacy auth-service (@MessagePattern → @GrpcMethod).
6
- // All tokens are HMAC primitives minted by @aepstore-dev/passport;
7
- // the access token only carries `userId`. Username / roles / 2FA state
8
- // must be looked up via Prisma or RbacService on the consumer side.
6
+ // Tokens are HMAC primitives from @aepstore-dev/passport:
7
+ // - access token: short TTL, carries userId; the gateway validates it
8
+ // locally via PassportAuthGuard (no round-trip here).
9
+ // - refresh token: long TTL HMAC + a RefreshToken DB row (device list +
10
+ // revocation). Refresh verifies the HMAC AND that the row still exists.
9
11
  service AuthService {
10
- rpc Login(LoginRequest) returns (AuthResponse);
11
- rpc Register(RegisterRequest) returns (AuthResponse);
12
- rpc CreateCode(CreateCodeRequest) returns (CreateCodeResponse);
13
- rpc VerifyCode(VerifyCodeRequest) returns (VerifyCodeResponse);
14
- rpc CheckToken(CheckTokenRequest) returns (CheckTokenResponse);
15
- rpc RefreshToken(RefreshTokenRequest) returns (AuthResponse);
16
- rpc Get2faStatus(Get2faStatusRequest) returns (Get2faStatusResponse);
17
- rpc GetProfile(GetProfileRequest) returns (UserProfileResponse);
12
+ rpc Login(LoginRequest) returns (AuthResult);
13
+ rpc Register(RegisterRequest) returns (AuthResult);
14
+ rpc CreateCode(CreateCodeRequest) returns (AuthResult);
15
+ rpc VerifyCode(VerifyCodeRequest) returns (AuthResult);
16
+ rpc CheckToken(CheckTokenRequest) returns (AuthUser);
17
+ rpc RefreshToken(RefreshTokenRequest) returns (AuthResult);
18
+ rpc Get2faStatus(Get2faStatusRequest) returns (TwoFactorStatusResponse);
19
+ rpc GetProfile(GetProfileRequest) returns (AuthUser);
18
20
  rpc CleanupExpiredTotpSetups(Empty) returns (Empty);
19
21
  }
20
22
 
23
+ enum TwoFactorType {
24
+ NONE = 0;
25
+ APP = 1;
26
+ EMAIL = 2;
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Requests
31
+ // ---------------------------------------------------------------------------
32
+
21
33
  // Login by username OR email (one of the two must be set).
22
34
  message LoginRequest {
23
35
  string password = 1;
@@ -32,31 +44,14 @@ message RegisterRequest {
32
44
  string ip = 4; // optional, attached by gateway from req
33
45
  }
34
46
 
35
- // Standard tokens-plus-user payload returned by Login / Register / RefreshToken.
36
- message AuthResponse {
37
- string access_token = 1;
38
- string refresh_token = 2;
39
- AuthUser user = 3;
40
- }
41
-
42
- message AuthUser {
43
- string id = 1;
44
- string email = 2;
45
- string username = 3;
46
- string role = 4; // primary role for backwards compat ("User" / "Administrator" / "Moderator")
47
- }
48
-
49
- // Code types: '2fa', 'reset', 'confirm', 'totp' (legacy strings preserved as-is).
47
+ // type { twofa, reset, totp_generate, totp_activate, confirm } (legacy strings)
50
48
  message CreateCodeRequest {
51
49
  string email = 1;
52
50
  string type = 2;
53
51
  }
54
52
 
55
- message CreateCodeResponse {
56
- bool ok = 1;
57
- }
58
-
59
- // `new_password` is only used by the 'reset' flow.
53
+ // type { twofa, twofa_deactivate, reset, totp, totp_deactivate, backup, confirm }
54
+ // new_password is only used by the 'reset' flow.
60
55
  message VerifyCodeRequest {
61
56
  string email = 1;
62
57
  string code = 2;
@@ -64,24 +59,10 @@ message VerifyCodeRequest {
64
59
  string new_password = 4; // optional
65
60
  }
66
61
 
67
- // Some verification flows (e.g. confirm-on-register) issue tokens immediately;
68
- // others just return ok=true.
69
- message VerifyCodeResponse {
70
- bool ok = 1;
71
- string access_token = 2; // optional
72
- string refresh_token = 3; // optional
73
- }
74
-
75
62
  message CheckTokenRequest {
76
63
  string token = 1;
77
64
  }
78
65
 
79
- message CheckTokenResponse {
80
- bool valid = 1;
81
- string user_id = 2;
82
- string reason = 3; // populated when valid=false
83
- }
84
-
85
66
  message RefreshTokenRequest {
86
67
  string refresh_token = 1;
87
68
  string user_agent = 2; // optional
@@ -92,30 +73,55 @@ message Get2faStatusRequest {
92
73
  string username = 1;
93
74
  }
94
75
 
95
- message Get2faStatusResponse {
96
- bool enabled = 1;
97
- TwoFactorType type = 2;
98
- }
99
-
100
- enum TwoFactorType {
101
- NONE = 0;
102
- APP = 1;
103
- EMAIL = 2;
104
- }
105
-
106
76
  message GetProfileRequest {
107
77
  string user_id = 1;
108
78
  }
109
79
 
110
- message UserProfileResponse {
80
+ // ---------------------------------------------------------------------------
81
+ // Responses
82
+ // ---------------------------------------------------------------------------
83
+
84
+ message AuthUser {
111
85
  string id = 1;
112
- string email = 2;
113
- string username = 3;
114
- string full_name = 4;
115
- bool is_verified = 5;
116
- bool two_factor_enabled = 6;
86
+ string username = 2;
87
+ string email = 3;
88
+ string avatar_url = 4;
89
+ string banner_url = 5;
90
+ repeated string roles = 6;
91
+ string role = 7; // primary role (roles[0])
92
+ bool is_verified = 8;
93
+ TwoFactorType two_factor_type = 9;
94
+ bool two_factor_enabled = 10;
95
+ string balance = 11; // Decimal as string (D7)
96
+ }
97
+
98
+ // Unified result for login/register/createCode/verifyCode/refresh. Which fields
99
+ // are populated depends on the flow:
100
+ // - fully authenticated → access_token + refresh_token + expires_in + user
101
+ // - 2FA required / pending confirm → message + requires_2fa + two_factor_type + user (no tokens)
102
+ // - totp_generate → secret + otpauth + qr (+ user)
103
+ // - totp_activate → backup_codes (+ tokens/user)
104
+ message AuthResult {
105
+ string access_token = 1;
106
+ string refresh_token = 2;
107
+ int32 expires_in = 3;
108
+ AuthUser user = 4;
109
+
110
+ string message = 5;
111
+ bool requires_2fa = 6;
117
112
  TwoFactorType two_factor_type = 7;
118
- repeated string roles = 8;
113
+
114
+ // TOTP setup extras
115
+ string secret = 8;
116
+ string otpauth = 9;
117
+ string qr = 10; // data URL (PNG)
118
+ repeated string backup_codes = 11;
119
+ }
120
+
121
+ message TwoFactorStatusResponse {
122
+ TwoFactorType two_factor_type = 1;
123
+ bool has_2fa = 2;
124
+ bool is_verified = 3;
119
125
  }
120
126
 
121
127
  message Empty {}
@@ -2,48 +2,134 @@ syntax = "proto3";
2
2
 
3
3
  package upload.v1;
4
4
 
5
- // Ported from legacy libs/shared/src/proto/upload.proto.
6
- // Bumped package to upload.v1 for consistency with the rest of the new stack.
5
+ // Thin signing service. File bytes NEVER pass through this service — clients
6
+ // upload/download directly to/from S3 (Yandex Object Storage) using presigned
7
+ // URLs this service mints. Memory/bandwidth flat regardless of file size.
8
+ //
9
+ // Large files (product deliverables, up to ~1GB) use the multipart flow:
10
+ // CreateMultipartUpload → (SignPart × N, client PUTs each part to S3) →
11
+ // CompleteMultipartUpload (or AbortMultipartUpload on failure)
12
+ //
13
+ // Small files (preview images, avatars) use the single-PUT flow: CreateUploadUrl.
14
+ //
15
+ // Downloads are gated elsewhere (products-service verifies purchase, then calls
16
+ // GenerateSignedUrl). This service does not know about purchases.
7
17
  service UploadService {
8
- rpc UploadFile(stream FileChunk) returns (UploadResponse);
9
- rpc GetUploadStatus(UploadStatusRequest) returns (UploadStatusResponse);
18
+ rpc CreateMultipartUpload(CreateMultipartUploadRequest) returns (CreateMultipartUploadResponse);
19
+ rpc SignPart(SignPartRequest) returns (SignPartResponse);
20
+ rpc CompleteMultipartUpload(CompleteMultipartUploadRequest) returns (CompleteMultipartUploadResponse);
21
+ rpc AbortMultipartUpload(AbortMultipartUploadRequest) returns (Ok);
22
+
23
+ rpc CreateUploadUrl(CreateUploadUrlRequest) returns (CreateUploadUrlResponse);
24
+
10
25
  rpc GenerateSignedUrl(SignedUrlRequest) returns (SignedUrlResponse);
26
+
27
+ rpc DeleteObject(DeleteObjectRequest) returns (Ok);
11
28
  }
12
29
 
13
- message FileChunk {
14
- string filename = 1;
15
- bytes data = 2;
16
- string mime_type = 3;
17
- bool is_last = 4;
18
- int32 chunk_index = 5;
19
- string upload_id = 6;
30
+ // Determines the object key prefix (and future ACL/privacy policy).
31
+ // ATTACHMENT = paid product deliverable (private; presigned GET only after purchase).
32
+ // MEDIA / AVATAR / BANNER = preview assets.
33
+ enum UploadKind {
34
+ UPLOAD_KIND_UNSPECIFIED = 0;
35
+ UPLOAD_KIND_ATTACHMENT = 1;
36
+ UPLOAD_KIND_MEDIA = 2;
37
+ UPLOAD_KIND_AVATAR = 3;
38
+ UPLOAD_KIND_BANNER = 4;
20
39
  }
21
40
 
22
- message UploadResponse {
23
- string file_url = 1;
24
- bool success = 2;
25
- string message = 3;
26
- string upload_id = 4;
27
- int64 file_size = 5;
41
+ message Ok {
42
+ bool ok = 1;
28
43
  }
29
44
 
30
- message UploadStatusRequest {
31
- string upload_id = 1;
45
+ // ---------------------------------------------------------------------------
46
+ // Multipart upload (large files)
47
+ // ---------------------------------------------------------------------------
48
+
49
+ message CreateMultipartUploadRequest {
50
+ string file_name = 1;
51
+ string mime_type = 2;
52
+ int64 file_size = 3;
53
+ UploadKind kind = 4;
54
+ string owner_id = 5; // user initiating, for key namespacing + audit
32
55
  }
33
56
 
34
- message UploadStatusResponse {
35
- string status = 1;
36
- int32 progress_percent = 2;
37
- string message = 3;
57
+ message CreateMultipartUploadResponse {
58
+ string upload_id = 1; // S3 multipart UploadId
59
+ string file_key = 2; // object key this service generated
60
+ int64 part_size = 3; // recommended part size (bytes) the client should slice with
61
+ int32 part_count = 4; // number of parts for file_size at part_size
38
62
  }
39
63
 
40
- message SignedUrlRequest {
64
+ message SignPartRequest {
41
65
  string file_key = 1;
66
+ string upload_id = 2;
67
+ int32 part_number = 3; // 1-based
68
+ }
69
+
70
+ message SignPartResponse {
71
+ string url = 1; // presigned PUT URL for this single part
42
72
  int32 expires_in = 2;
43
73
  }
44
74
 
75
+ message CompletedPart {
76
+ int32 part_number = 1;
77
+ string etag = 2; // ETag the client received in the S3 PUT response header for this part
78
+ }
79
+
80
+ message CompleteMultipartUploadRequest {
81
+ string file_key = 1;
82
+ string upload_id = 2;
83
+ repeated CompletedPart parts = 3;
84
+ }
85
+
86
+ message CompleteMultipartUploadResponse {
87
+ string file_key = 1;
88
+ string location = 2; // final object location (internal reference)
89
+ int64 file_size = 3;
90
+ }
91
+
92
+ message AbortMultipartUploadRequest {
93
+ string file_key = 1;
94
+ string upload_id = 2;
95
+ }
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // Single-PUT upload (small files)
99
+ // ---------------------------------------------------------------------------
100
+
101
+ message CreateUploadUrlRequest {
102
+ string file_name = 1;
103
+ string mime_type = 2;
104
+ UploadKind kind = 3;
105
+ string owner_id = 4;
106
+ }
107
+
108
+ message CreateUploadUrlResponse {
109
+ string url = 1; // presigned PUT URL (client PUTs the whole file)
110
+ string file_key = 2;
111
+ int32 expires_in = 3;
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Download
116
+ // ---------------------------------------------------------------------------
117
+
118
+ message SignedUrlRequest {
119
+ string file_key = 1;
120
+ int32 expires_in = 2; // optional, default 3600
121
+ string download_file_name = 3; // optional: forces Content-Disposition attachment filename
122
+ }
123
+
45
124
  message SignedUrlResponse {
46
125
  string signed_url = 1;
47
- bool success = 2;
48
- string message = 3;
126
+ int32 expires_in = 2;
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // Cleanup
131
+ // ---------------------------------------------------------------------------
132
+
133
+ message DeleteObjectRequest {
134
+ string file_key = 1;
49
135
  }