Skip to content

Commit fdc8d5d

Browse files
committed
feat(grpc): add v3 GetPNBACode, ExchangePNBACodeAndStore, RevokePNBAToken
1 parent a7f8561 commit fdc8d5d

11 files changed

Lines changed: 858 additions & 9 deletions

docs/grpc.md

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
- [v3: Get OAuth2 Authorization URL](#v3-get-oauth2-authorization-url)
99
- [v3: Exchange OAuth2 Code and Store Token](#v3-exchange-oauth2-code-and-store-token)
1010
- [v3: Revoke and Delete OAuth2 Token](#v3-revoke-and-delete-oauth2-token)
11+
- [v3: Get PNBA Code](#v3-get-pnba-code)
12+
- [v3: Exchange PNBA Code and Store Token](#v3-exchange-pnba-code-and-store-token)
13+
- [v3: Revoke and Delete PNBA Token](#v3-revoke-and-delete-pnba-token)
1114
- [Version 2 API](#version-2-api)
1215
- [v2: Get OAuth2 Authorization URL](#v2-get-oauth2-authorization-url)
1316
- [v2: Exchange OAuth2 Code and Store Token](#v2-exchange-oauth2-code-and-store-token)
@@ -147,7 +150,6 @@ Generates an OAuth2 authorization URL for initiating the OAuth2 flow.
147150
**Example:**
148151

149152
```bash
150-
# Note: Headers must be correctly prepared with encryption
151153
grpcurl -plaintext \
152154
-H 'x-payload-bin: <encrypted_payload_binary>' \
153155
-H 'x-public-key-bin: <client_pk_binary>' \
@@ -260,6 +262,136 @@ grpcurl -plaintext \
260262

261263
---
262264

265+
### v3: Get PNBA Code
266+
267+
Requests a one-time passcode (OTP) for Phone Number-Based Authentication.
268+
269+
**Request:** `GetPNBACodeRequest`
270+
271+
| Field | Type | Required | Description |
272+
| :--- | :--- | :--- | :--- |
273+
| platform | string | Yes | Platform identifier |
274+
| phone_number | string | Yes | Phone number to receive OTP |
275+
| request_identifier | string | Optional | Request identifier for tracking |
276+
277+
**Response:** `GetPNBACodeResponse`
278+
279+
| Field | Type | Description |
280+
| :--- | :--- | :--- |
281+
| success | bool | Operation success |
282+
| message | string | Response message |
283+
284+
**Example:**
285+
286+
```bash
287+
grpcurl -plaintext \
288+
-H 'x-payload-bin: <encrypted_payload_binary>' \
289+
-H 'x-public-key-bin: <client_pk_binary>' \
290+
-H 'x-key-id: <key_id>' \
291+
-H 'x-nonce-bin: <nonce_binary>' \
292+
-H 'x-timestamp: <timestamp>' \
293+
-d '{
294+
"platform": "telegram",
295+
"phone_number": "+1234567890"
296+
}' \
297+
-proto protos/v3/publisher.proto \
298+
<your_host>:<your_port> publisher.v3.Publisher/GetPNBACode
299+
```
300+
301+
---
302+
303+
### v3: Exchange PNBA Code and Store Token
304+
305+
Exchanges a one-time passcode (OTP) for a token, stores it, and returns encrypted token data.
306+
307+
**Request:** `ExchangePNBACodeAndStoreRequest`
308+
309+
| Field | Type | Required | Description |
310+
| :--- | :--- | :--- | :--- |
311+
| platform | string | Yes | Platform identifier |
312+
| phone_number | string | Yes | Phone number that received OTP |
313+
| authorization_code | string | Yes | PNBA authorization code |
314+
| password | string | Optional | Password for two-step verification |
315+
| request_identifier | string | Optional | Request identifier for tracking |
316+
| client_ephemeral_public_keys | list[PublicKey] | Yes | Exactly 256 ephemeral public keys generated by the client |
317+
318+
**Response:** `ExchangePNBACodeAndStoreResponse`
319+
320+
| Field | Type | Description |
321+
| :--- | :--- | :--- |
322+
| success | bool | Operation success |
323+
| message | string | Response message |
324+
| two_step_verification_enabled | bool | Two-step verification status |
325+
| account_identifier | string | Account identifier |
326+
| token_ciphertext | bytes | Encrypted token ciphertext |
327+
| token_id | bytes | Unique identifier for the stored token |
328+
| server_ephemeral_public_keys | list[PublicKey] | Exactly 256 ephemeral public keys generated by the server |
329+
| key_id | uint32 | Key ID used for encrypting token |
330+
331+
**Example:**
332+
333+
```bash
334+
grpcurl -plaintext \
335+
-H 'x-payload-bin: <encrypted_payload_binary>' \
336+
-H 'x-public-key-bin: <client_pk_binary>' \
337+
-H 'x-key-id: <key_id>' \
338+
-H 'x-nonce-bin: <nonce_binary>' \
339+
-H 'x-timestamp: <timestamp>' \
340+
-d @ \
341+
-proto protos/v3/publisher.proto \
342+
<your_host>:<your_port> publisher.v3.Publisher/ExchangePNBACodeAndStore <<EOF
343+
{
344+
"platform": "telegram",
345+
"phone_number": "+1234567890",
346+
"authorization_code": "auth_code",
347+
"client_ephemeral_public_keys": [
348+
{"key_id": 0, "public_key": "..."},
349+
... 254 more keys ...
350+
{"key_id": 255, "public_key": "..."}
351+
]
352+
}
353+
EOF
354+
```
355+
356+
---
357+
358+
### v3: Revoke and Delete PNBA Token
359+
360+
Revokes and deletes a stored PNBA token.
361+
362+
**Request:** `RevokePNBATokenRequest`
363+
364+
| Field | Type | Required | Description |
365+
| :--- | :--- | :--- | :--- |
366+
| token_id | bytes | Yes | Unique identifier for the stored token |
367+
| key_id | uint32 | Yes | Key index used for encrypting the token |
368+
369+
**Response:** `RevokePNBATokenResponse`
370+
371+
| Field | Type | Description |
372+
| :--- | :--- | :--- |
373+
| success | bool | Operation success |
374+
| message | string | Response message |
375+
376+
**Example:**
377+
378+
```bash
379+
grpcurl -plaintext \
380+
-H 'x-payload-bin: <encrypted_payload_binary>' \
381+
-H 'x-public-key-bin: <client_pk_binary>' \
382+
-H 'x-key-id: <key_id>' \
383+
-H 'x-nonce-bin: <nonce_binary>' \
384+
-H 'x-timestamp: <timestamp>' \
385+
-d '{
386+
"token_id": "<token_id_binary>",
387+
"key_id": 123
388+
}' \
389+
-proto protos/v3/publisher.proto \
390+
<your_host>:<your_port> publisher.v3.Publisher/RevokePNBAToken
391+
```
392+
393+
---
394+
263395
## Version 2 API
264396

265397
**Package:** `publisher.v2`

grpc_services/v3/exchange_oauth2_code.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919

2020
def ExchangeOAuth2CodeAndStore(self, request, context):
21-
"""Exchange an OAuth2 authorization code for a token."""
21+
"""Handles ExchangeOAuth2CodeAndStore."""
2222

2323
response = publisher_pb2.ExchangeOAuth2CodeAndStoreResponse
2424

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# SPDX-License-Identifier: GPL-3.0-only
2+
"""ExchangePNBACodeAndStore gRPC service handler."""
3+
4+
import grpc
5+
6+
from db import get_session
7+
from grpc_services.v3.utils import (
8+
create_token_pools_and_encrypt,
9+
get_pnba_adapter,
10+
validate_client_ephemeral_public_keys,
11+
)
12+
from logutils import get_logger
13+
from models.token import create as create_token
14+
from platforms.adapter_ipc_handler import AdapterIPCHandler
15+
from protos.v3 import publisher_pb2
16+
17+
logger = get_logger(__name__)
18+
19+
20+
def ExchangePNBACodeAndStore(self, request, context):
21+
"""Handles ExchangePNBACodeAndStore."""
22+
23+
response = publisher_pb2.ExchangePNBACodeAndStoreResponse
24+
25+
_, auth_error = self.handle_v1_request_auth(context, response)
26+
if auth_error:
27+
return auth_error
28+
29+
invalid = self.handle_request_field_validation(
30+
context,
31+
request,
32+
response,
33+
[
34+
"platform",
35+
"phone_number",
36+
"authorization_code",
37+
"client_ephemeral_public_keys",
38+
],
39+
)
40+
if invalid:
41+
return invalid
42+
43+
try:
44+
validation_error = validate_client_ephemeral_public_keys(
45+
request.client_ephemeral_public_keys
46+
)
47+
if validation_error:
48+
return self.handle_create_grpc_error_response(
49+
context,
50+
response,
51+
validation_error,
52+
grpc.StatusCode.INVALID_ARGUMENT,
53+
)
54+
55+
adapter = get_pnba_adapter(request.platform)
56+
57+
params = {
58+
"code": request.authorization_code,
59+
"phone_number": request.phone_number,
60+
"base_path": adapter["assets_path"],
61+
"password": request.password or None,
62+
"request_identifier": request.request_identifier or None,
63+
}
64+
65+
if params.get("password"):
66+
pipe = AdapterIPCHandler.invoke(
67+
adapter_path=adapter["path"],
68+
venv_path=adapter["venv_path"],
69+
method="validate_password_and_fetch_user_info",
70+
params=params,
71+
)
72+
else:
73+
pipe = AdapterIPCHandler.invoke(
74+
adapter_path=adapter["path"],
75+
venv_path=adapter["venv_path"],
76+
method="validate_code_and_fetch_user_info",
77+
params=params,
78+
)
79+
80+
if pipe.get("error"):
81+
return self.handle_create_grpc_error_response(
82+
context,
83+
response,
84+
pipe["error"],
85+
grpc.StatusCode.INVALID_ARGUMENT,
86+
error_type="UNKNOWN",
87+
)
88+
89+
result = pipe["result"]
90+
if result.get("two_step_verification_enabled"):
91+
return response(
92+
success=True,
93+
two_step_verification_enabled=True,
94+
message="two-steps verification is enabled and a password is required",
95+
)
96+
97+
with get_session() as s:
98+
token = create_token(
99+
platform=request.platform.lower(),
100+
token_data={
101+
"account_id": result["userinfo"]["account_identifier"],
102+
"token": result["userinfo"]["account_identifier"],
103+
},
104+
session=s,
105+
)
106+
107+
token_ciphertext, kid_index, server_public_keys = (
108+
create_token_pools_and_encrypt(
109+
token_id=token.id,
110+
client_ephemeral_public_keys=request.client_ephemeral_public_keys,
111+
session=s,
112+
)
113+
)
114+
115+
return response(
116+
success=True,
117+
message="Successfully fetched and stored token",
118+
account_identifier=result["userinfo"]["account_identifier"],
119+
token_ciphertext=token_ciphertext,
120+
token_id=token.token_id,
121+
server_ephemeral_public_keys=server_public_keys,
122+
key_id=kid_index,
123+
)
124+
125+
except NotImplementedError as exc:
126+
return self.handle_create_grpc_error_response(
127+
context,
128+
response,
129+
exc,
130+
grpc.StatusCode.UNIMPLEMENTED,
131+
)
132+
133+
except ValueError as exc:
134+
logger.error("%s", exc)
135+
return self.handle_create_grpc_error_response(
136+
context,
137+
response,
138+
exc,
139+
grpc.StatusCode.INTERNAL,
140+
user_msg="Oops! Something went wrong. Please try again later.",
141+
)
142+
143+
except Exception as exc:
144+
return self.handle_create_grpc_error_response(
145+
context,
146+
response,
147+
exc,
148+
grpc.StatusCode.INTERNAL,
149+
user_msg="Oops! Something went wrong. Please try again later.",
150+
error_type="UNKNOWN",
151+
)

grpc_services/v3/get_oauth2_auth_url.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313

1414
def GetOAuth2AuthorizationUrl(self, request, context):
15-
"""Generate and return an encrypted OAuth2 authorization URL."""
15+
"""Handles GetOAuth2AuthorizationUrl."""
1616

1717
response = publisher_pb2.GetOAuth2AuthorizationUrlResponse
1818

0 commit comments

Comments
 (0)