Skip to content

Commit

Permalink
Merge pull request #104 from smswithoutborders/feature/grpc_api
Browse files Browse the repository at this point in the history
refactor: update the StoreEntityToken function
  • Loading branch information
PromiseFru authored Jun 14, 2024
2 parents 729ec97 + 450756a commit 8ddd27e
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 267 deletions.
8 changes: 0 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
python=python3
PROTO_URL=https://raw.githubusercontent.com/smswithoutborders/SMSWithoutBorders-Publisher/feature/grpc-api/protos/v1/publisher.proto
PROTO_DIR=protos/v1
PROTO_FILE=$(PROTO_DIR)/publisher.proto

define log_message
@echo "[$(shell date +'%Y-%m-%d %H:%M:%S')] - $1"
Expand Down Expand Up @@ -65,8 +62,3 @@ grpc-compile:
./protos/v1/*.proto
$(call log_message,INFO - gRPC Compilation complete!)

download-publisher-proto:
$(call log_message,INFO - Downloading publisher.proto ...)
@mkdir -p $(PROTO_DIR)
@curl -o $(PROTO_FILE) -L $(PROTO_URL)
$(call log_message,INFO - Publisher.proto downloaded successfully!)
27 changes: 2 additions & 25 deletions docs/grpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -517,29 +517,6 @@ This step involves storing tokens securely for the authenticated entity.

---

> [!NOTE]
>
> Ensure you have generated your authorization URL before using this function.
> For Gmail and Twitter offline access, use the following recommended
> parameters:
>
> **Gmail:**
>
> - **scope:** >
> `openid https://www.googleapis.com/auth/gmail.send https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email`
> - **access_type:** `offline`
> - **prompt:** `consent`
>
> **Twitter:**
>
> - **scope:** `tweet.read tweet.write users.read offline.access`
> - **prompt:** `consent`
>
> You can use the publisher's [Get Authorization URL](#) function to help
> generate the URL for you, or use other tools that can construct the URL.
---

> `request` **StoreEntityTokenRequest**
> [!IMPORTANT]
Expand All @@ -550,9 +527,9 @@ This step involves storing tokens securely for the authenticated entity.
| Field | Type | Description |
| ------------------ | ------ | ------------------------------------------------------------------- |
| long_lived_token | string | The long-lived token for the authenticated session. |
| authorization_code | string | The authorization code obtained from the OAuth2 flow. |
| token | string | The token to be stored. |
| platform | string | The platform from which the token is being issued. (e.g., "gmail"). |
| protocol | string | The protocol used for authentication (e.g., "oauth2"). |
| account_identifier | string | The identifier of the account associated with the token. |

Optional fields:

Expand Down
12 changes: 5 additions & 7 deletions protos/v1/vault.proto
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,12 @@ message Token {
message StoreEntityTokenRequest {
// The long-lived token of the authenticated entity.
string long_lived_token = 1;
// The authorization code for the token.
string authorization_code = 2;
// The code verifier for the token.
string code_verifier = 3;
// The OAuth2 token to be stored.
string token = 2;
// The platform associated with the token.
string platform = 4;
// The protocol used for the request.
string protocol = 5;
string platform = 3;
// The identifier of the account associated with the token.
string account_identifier = 4;
}

// Response message for storing an entity's token.
Expand Down
102 changes: 51 additions & 51 deletions src/grpc_entity_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
)
from src.long_lived_token import generate_llt, verify_llt
from src.device_id import compute_device_id
from src.grpc_publisher_client import exchange_oauth2_code

HASHING_KEY = load_key(get_configs("HASHING_SALT"), 32)
KEYSTORE_PATH = get_configs("KEYSTORE_PATH")
SUPPORTED_PLATFORMS = ("gmail",)

logging.basicConfig(
level=logging.INFO, format=("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
Expand Down Expand Up @@ -173,20 +173,29 @@ def verify_long_lived_token(request, context, response):
Returns:
tuple: Tuple containing entity object, and error response.
"""
eid, llt = request.long_lived_token.split(":", 1)

entity_obj = find_entity(eid=eid)
if not entity_obj:
return None, error_response(
def create_error_response(error_msg, error_detail=None):
return error_response(
context,
response,
f"Possible token tampering detected. Entity not found with eid: {eid}",
error_msg,
grpc.StatusCode.UNAUTHENTICATED,
user_msg=(
"Your session has expired or the token is invalid. "
"Please log in again to generate a new token."
),
_type="UNKNOWN",
_type=error_detail,
)

try:
eid, llt = request.long_lived_token.split(":", 1)
except ValueError as err:
return None, create_error_response(err, "UNKNOWN")

entity_obj = find_entity(eid=eid)
if not entity_obj:
return None, create_error_response(
f"Possible token tampering detected. Entity not found with eid: {eid}"
)

entity_crypto_metadata = load_crypto_metadata(
Expand All @@ -203,28 +212,11 @@ def verify_long_lived_token(request, context, response):
llt_payload, llt_error = verify_llt(llt, entity_device_id_shared_key)

if not llt_payload:
return None, error_response(
context,
response,
llt_error,
grpc.StatusCode.UNAUTHENTICATED,
user_msg=(
"Your session has expired or the token is invalid. "
"Please log in again to generate a new token."
),
)
return None, create_error_response(llt_error, "UNKNOWN")

if llt_payload["eid"] != eid:
return None, error_response(
context,
response,
f"Possible token tampering detected. EID mismatch: {eid}",
grpc.StatusCode.UNAUTHENTICATED,
user_msg=(
"Your session has expired or the token is invalid. "
"Please log in again to generate a new token."
),
_type="UNKNOWN",
if llt_payload.get("eid") != eid:
return None, create_error_response(
f"Possible token tampering detected. EID mismatch: {eid}"
)

return entity_obj, None
Expand Down Expand Up @@ -577,7 +569,7 @@ def ListEntityStoredTokens(self, request, context):
)

def StoreEntityToken(self, request, context):
"""Handles storing tokens for an entiry"""
"""Handles storing tokens for an entity"""

response = vault_pb2.StoreEntityTokenResponse

Expand All @@ -586,7 +578,7 @@ def StoreEntityToken(self, request, context):
context,
request,
response,
["long_lived_token", "authorization_code", "platform", "protocol"],
["long_lived_token", "token", "platform", "account_identifier"],
)
if invalid_fields_response:
return invalid_fields_response
Expand All @@ -597,30 +589,38 @@ def StoreEntityToken(self, request, context):
if llt_error_response:
return llt_error_response

if request.protocol.lower() == "oauth2":
oauth2_response, oauth2_error = exchange_oauth2_code(
request.platform,
request.authorization_code,
getattr(request, "code_verifier"),
if request.platform.lower() not in SUPPORTED_PLATFORMS:
raise NotImplementedError(
f"The protocol '{request.platform}' is currently not supported. "
"Please contact the developers for more information on when "
"this platform will be implemented."
)

if oauth2_error:
return response(message=oauth2_error, success=False)

profile_data = json.loads(oauth2_response.profile)
account_identifier = profile_data.get("email") or profile_data.get(
"username"
if fetch_entity_tokens(
entity=entity_obj,
account_identifier_hash=generate_hmac(
HASHING_KEY, request.account_identifier
),
platform=request.platform,
):
return error_response(
context,
response,
"Entity already has a token associated with account "
f"identifier {request.account_identifier} for {request.platform}",
grpc.StatusCode.ALREADY_EXISTS,
)
new_token = {
"entity": entity_obj,
"platform": request.platform,
"account_identifier_hash": generate_hmac(
HASHING_KEY, account_identifier
),
"account_identifier": encrypt_and_encode(account_identifier),
"account_tokens": encrypt_and_encode(oauth2_response.token),
}
create_entity_token(**new_token)

new_token = {
"entity": entity_obj,
"platform": request.platform,
"account_identifier_hash": generate_hmac(
HASHING_KEY, request.account_identifier
),
"account_identifier": encrypt_and_encode(request.account_identifier),
"account_tokens": encrypt_and_encode(request.token),
}
create_entity_token(**new_token)

logger.info("Successfully stored tokens for %s", entity_obj.eid)

Expand Down
Loading

0 comments on commit 8ddd27e

Please sign in to comment.