Skip to content

Commit 8e7bcf7

Browse files
committed
feat(pnba): add optional auth channel for code flows
1 parent 75dd090 commit 8e7bcf7

9 files changed

Lines changed: 51 additions & 10 deletions

File tree

docs/grpc.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ Requests a one-time passcode (OTP) for Phone Number-Based Authentication.
274274
| platform | string | Yes | Platform identifier |
275275
| phone_number | string | Yes | Phone number to receive OTP |
276276
| request_identifier | string | Optional | Request identifier for tracking |
277+
| channel | string | Optional | The medium used to send the code (e.g., "whatsapp", "telegram", "signal") |
277278

278279
**Response:** `GetPNBACodeResponse`
279280

@@ -315,6 +316,7 @@ Exchanges a one-time passcode (OTP) for a token, stores it, and returns encrypte
315316
| password | string | Optional | Password for two-step verification |
316317
| request_identifier | string | Optional | Request identifier for tracking |
317318
| client_ephemeral_public_keys | list[PublicKey] | Yes | Exactly 256 ephemeral public keys generated by the client |
319+
| channel | string | Optional | The medium used to receive the code (e.g., "whatsapp", "telegram", "signal") |
318320

319321
**Response:** `ExchangePNBACodeAndStoreResponse`
320322

grpc_services/v3/exchange_pnba_code.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def ExchangePNBACodeAndStore(self, request, context):
6060
"base_path": adapter.assets_path,
6161
"password": request.password or None,
6262
"request_identifier": request.request_identifier or None,
63+
"channel": request.channel or None,
6364
}
6465

6566
if params.get("password"):
@@ -103,7 +104,7 @@ def ExchangePNBACodeAndStore(self, request, context):
103104
proto_id=adapter.proto_id,
104105
token_data={
105106
"account_id": result["userinfo"]["account_identifier"],
106-
"token": result["userinfo"]["account_identifier"],
107+
"token": {"name": result["userinfo"].get("name")},
107108
},
108109
session=s,
109110
)

grpc_services/v3/get_pnba_code.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def GetPNBACode(self, request, context):
3737
"phone_number": request.phone_number,
3838
"base_path": adapter.assets_path,
3939
"request_identifier": request.request_identifier or None,
40+
"channel": request.channel or None,
4041
},
4142
)
4243

grpc_services/v3/sync_keys.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ def SyncKeys(self, request, context):
2828
if auth_error:
2929
return auth_error
3030

31+
if not payload_bin:
32+
logger.error("missing token ciphertext in request payload")
33+
return self.handle_create_grpc_error_response(
34+
context,
35+
response,
36+
"token ciphertext is required in the request payload",
37+
grpc.StatusCode.INVALID_ARGUMENT,
38+
)
39+
3140
invalid = self.handle_request_field_validation(
3241
context,
3342
request,

lib_relaysms_payload_specs

protos/v3/publisher.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ message GetPNBACodeRequest {
134134
string phone_number = 2;
135135
// Optional request identifier for tracking the request
136136
string request_identifier = 3;
137+
// The medium used to deliver the code (e.g., "whatsapp", "telegram", "signal").
138+
string channel = 4;
137139
}
138140

139141
// Response message for getting the PNBA code
@@ -165,6 +167,8 @@ message ExchangePNBACodeAndStoreRequest {
165167
string request_identifier = 5;
166168
// Client ephemeral public keys to associate with the stored token
167169
repeated PublicKey client_ephemeral_public_keys = 6;
170+
// The medium used to receive the code (e.g., "whatsapp", "telegram", "signal").
171+
string channel = 7;
168172
}
169173

170174
// Response message for exchanging the PNBA code for a token and storing it

rest_services/v1/services.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# SPDX-License-Identifier: GPL-3.0-only
22
"""v1 Services for the REST API."""
33

4+
import base64
5+
46
from sqlalchemy import delete
57
from sqlalchemy.orm import Session
68

@@ -33,6 +35,9 @@ def _get_adapter_params(
3335
) -> dict:
3436
cat_id = content.get_cat_id()
3537
extra_params = extras or {}
38+
attachment = content.get_attachment()
39+
if attachment:
40+
extra_params["attachments"] = [{"data": base64.b64encode(attachment).decode()}]
3641

3742
match cat_id:
3843
case rrs.V1ContentCategories.EMAIL:
@@ -41,6 +46,7 @@ def _get_adapter_params(
4146
"to_email": content.get_to().decode(),
4247
"subject": content.get_subject().decode(),
4348
"message": content.get_body().decode(),
49+
"recipient": content.get_to().decode(),
4450
**extra_params,
4551
}
4652
case rrs.V1ContentCategories.MESSAGE:
@@ -135,7 +141,7 @@ def publish_content(
135141
token_data=token.token_data,
136142
content=content,
137143
extras={
138-
"phone_number": token.token_data["token"],
144+
"phone_number": token.token_data["account_id"],
139145
"base_path": adapter.assets_path,
140146
},
141147
)

tests/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ Requests a passcode/OTP for Phone Number-Based Authentication (PNBA).
8282
python -m tests.client get-pnba-code --platform telegram --phone-number +123456789
8383
```
8484

85+
```sh
86+
python -m tests.client get-pnba-code --platform telegram --phone-number +123456789 --auth-channel signal
87+
```
88+
8589
### 6. Exchange PNBA Code
8690

8791
Exchanges the PNBA OTP code for a token, decrypts it, and stores the session data.
@@ -90,6 +94,10 @@ Exchanges the PNBA OTP code for a token, decrypts it, and stores the session dat
9094
python -m tests.client exchange-pnba-code --platform telegram --phone-number +123456789 --code <OTP_CODE>
9195
```
9296

97+
```sh
98+
python -m tests.client exchange-pnba-code --platform telegram --phone-number +123456789 --code <OTP_CODE> --auth-channel signal
99+
```
100+
93101
If the account has two-step verification enabled, re-run with `--password`:
94102

95103
```sh

tests/client.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,17 @@ def cmd_revoke_oauth2_token(host, port, tls, rest_api, token, **_):
371371

372372
@cli.command("get-pnba-code")
373373
@shared_options
374+
@click.option("--auth-channel", help="The medium used to receive the code.")
374375
def cmd_get_pnba_code(
375-
host, port, tls, rest_api, platform, phone_number, request_identifier, **_
376+
host,
377+
port,
378+
tls,
379+
rest_api,
380+
platform,
381+
phone_number,
382+
request_identifier,
383+
auth_channel,
384+
**_,
376385
):
377386
"""Request a PNBA code."""
378387
logger.info("platform=%s | phone_number=%s", platform, phone_number)
@@ -393,7 +402,8 @@ def cmd_get_pnba_code(
393402
publisher_pb2.GetPNBACodeRequest(
394403
platform=platform,
395404
phone_number=phone_number,
396-
request_identifier=request_identifier or "",
405+
request_identifier=request_identifier,
406+
channel=auth_channel,
397407
),
398408
metadata=metadata,
399409
)
@@ -409,6 +419,7 @@ def cmd_get_pnba_code(
409419
@shared_options
410420
@click.option("--code", required=True, help="Authorization code.")
411421
@click.option("--password", help="Two-step verification password.")
422+
@click.option("--auth-channel", help="The medium used to receive the code.")
412423
def cmd_exchange_pnba_code(
413424
host,
414425
port,
@@ -419,6 +430,7 @@ def cmd_exchange_pnba_code(
419430
request_identifier,
420431
code,
421432
password,
433+
auth_channel,
422434
**_,
423435
):
424436
"""Exchange a PNBA code, decrypt the token, and store session data."""
@@ -451,6 +463,7 @@ def cmd_exchange_pnba_code(
451463
)
452464
for i, kp in enumerate(client_keypairs)
453465
],
466+
channel=auth_channel,
454467
),
455468
metadata=metadata,
456469
)
@@ -929,7 +942,7 @@ def cmd_send(
929942
sys.exit(1)
930943

931944
len_att = len(attachment_bytes) if attachment_bytes else 0
932-
sess_id = secrets.randbelow(256) if has_attachment else None
945+
sess_id = secrets.randbelow(128) if has_attachment else None
933946

934947
try:
935948
payload = rrs.V1Payloads(
@@ -941,10 +954,7 @@ def cmd_send(
941954
)
942955
if has_attachment:
943956
segments = payload.split(rrs.Transports.SMS)
944-
segments_b64 = [
945-
seg.decode() if isinstance(seg, (bytes, bytearray)) else seg
946-
for seg in segments
947-
]
957+
segments_b64 = [seg.decode() for seg in segments]
948958
else:
949959
raw = payload.serialize_without_attachment()
950960
segments_b64 = [b64(raw, urlsafe=False)]

0 commit comments

Comments
 (0)