Skip to content

Commit

Permalink
Merge pull request #102 from smswithoutborders/feature/grpc_api
Browse files Browse the repository at this point in the history
feat: add device_id field and compute_device_id integration
  • Loading branch information
PromiseFru authored Jun 12, 2024
2 parents d1023fe + 25ea11d commit 05017d8
Show file tree
Hide file tree
Showing 9 changed files with 30 additions and 16 deletions.
1 change: 1 addition & 0 deletions src/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Entity(Model):
phone_number_hash = CharField()
password_hash = CharField()
country_code = CharField()
device_id = CharField(null=True)
client_publish_pub_key = TextField(null=True)
client_device_id_pub_key = TextField(null=True)
server_crypto_metadata = TextField(null=True)
Expand Down
4 changes: 2 additions & 2 deletions src/device_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ def compute_device_id(secret_key, phone_number, public_key) -> str:
Compute a device ID using HMAC and SHA-256.
Args:
secret_key (str): The secret key used for HMAC.
secret_key (bytes): The secret key used for HMAC.
phone_number (str): The phone number to be included in the HMAC input.
public_key (str): The public key to be included in the HMAC input.
Returns:
str: The hexadecimal representation of the HMAC digest.
"""
combined_input = phone_number + public_key
hmac_object = hmac.new(secret_key.encode(), combined_input.encode(), hashlib.sha256)
hmac_object = hmac.new(secret_key, combined_input.encode(), hashlib.sha256)
return hmac_object.hexdigest()
9 changes: 9 additions & 0 deletions src/grpc_entity_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
decrypt_and_decode,
)
from src.long_lived_token import generate_llt, verify_llt
from src.device_id import compute_device_id

HASHING_KEY = load_key(get_configs("HASHING_SALT"), 32)
KEYSTORE_PATH = get_configs("KEYSTORE_PATH")
Expand Down Expand Up @@ -216,6 +217,9 @@ def complete_creation(request):
"phone_number_hash": phone_number_hash,
"password_hash": password_hash,
"country_code": country_code_ciphertext_b64,
"device_id": compute_device_id(
shared_key, request.phone_number, request.client_device_id_pub_key
),
"client_publish_pub_key": request.client_publish_pub_key,
"client_device_id_pub_key": request.client_device_id_pub_key,
"server_crypto_metadata": crypto_metadata_ciphertext_b64,
Expand Down Expand Up @@ -346,6 +350,8 @@ def initiate_authentication(request, entity_obj):
return pow_response

message, expires = pow_response
entity_obj.device_id = None
entity_obj.save()

return response(
requires_ownership_proof=True,
Expand Down Expand Up @@ -407,6 +413,9 @@ def complete_authentication(request, entity_obj):

long_lived_token = generate_llt(eid, shared_key)

entity_obj.device_id = compute_device_id(
shared_key, request.phone_number, request.client_device_id_pub_key
)
entity_obj.client_publish_pub_key = request.client_publish_pub_key
entity_obj.client_device_id_pub_key = request.client_device_id_pub_key
entity_obj.server_crypto_metadata = crypto_metadata_ciphertext_b64
Expand Down
2 changes: 1 addition & 1 deletion src/otp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
TWILIO_AUTH_TOKEN = get_configs("TWILIO_AUTH_TOKEN")
TWILIO_SERVICE_SID = get_configs("TWILIO_SERVICE_SID")
MOCK_OTP = get_configs("MOCK_OTP")
MOCK_OTP = True if MOCK_OTP and MOCK_OTP.lower() == "true" else False
MOCK_OTP = MOCK_OTP.lower() == "true" if MOCK_OTP is not None else False

RATE_LIMIT_WINDOWS = [
{"duration": 2, "count": 1}, # 2 minute window
Expand Down
1 change: 0 additions & 1 deletion src/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ def remove_none_values(values):
{"a": 1, "b": 2, "c": None}
]
filtered_values = remove_none_values(values)
print(filtered_values)
# Output: [{'a': 1, 'c': 3}, {'b': 2, 'c': 3}, {'a': 1, 'b': 2}]
"""
return [{k: v for k, v in value.items() if v is not None} for value in values]
15 changes: 8 additions & 7 deletions src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,27 +162,28 @@ def get_configs(config_name: str, strict: bool = False) -> str:
raise


def set_configs(config_name: str, config_value: str) -> None:
def set_configs(config_name, config_value) -> None:
"""
Sets the value of a configuration in the environment variables.
Args:
config_name (str): The name of the configuration to set.
config_value (str): The value of the configuration to set.
config_value (str or bool): The value of the configuration to set.
Raises:
ValueError: If config_name or config_value is empty.
ValueError: If config_name is empty.
"""
if not config_name or not config_value:
if not config_name:
error_message = (
f"Cannot set configuration. Invalid config_name '{config_name}' ",
"or config_value '{config_value}'.",
f"Cannot set configuration. Invalid config_name '{config_name}'."
)
logger.error(error_message)
raise ValueError(error_message)

try:
os.environ[config_name] = config_value
if isinstance(config_value, bool):
config_value = str(config_value).lower()
os.environ[config_name] = str(config_value)
except Exception as error:
logger.error("Failed to set configuration '%s': %s", config_name, error)
raise
Expand Down
12 changes: 7 additions & 5 deletions tests/test_entity.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Test module for entity controller functions."""

import base64
import pytest
from peewee import SqliteDatabase
from src.utils import create_tables, set_configs, generate_eid
from src.device_id import compute_device_id


@pytest.fixture()
Expand Down Expand Up @@ -54,16 +56,15 @@ def test_create_entity_additional_fields():
eid = generate_eid(phone_number_hash)
password_hash = "password_hash2"
country_code = "CM"
publish_pub_key = "-----BEGIN PUBLIC KEY-----\n1234\n-----END PUBLIC KEY-----"
device_id_pub_key = (
"-----BEGIN DEVICE PUBLIC KEY-----\n1234\n-----END DEVICE PUBLIC KEY-----"
)

publish_pub_key = base64.b64encode(b"\x82" * 32).decode("utf-8")
device_id_pub_key = base64.b64encode(b"\x82" * 32).decode("utf-8")
device_id = compute_device_id(b"\x82" * 32, "+237123456789", publish_pub_key)
entity = create_entity(
eid,
phone_number_hash,
password_hash,
country_code,
device_id=device_id,
client_publish_pub_key=publish_pub_key,
client_device_id_pub_key=device_id_pub_key,
)
Expand All @@ -73,6 +74,7 @@ def test_create_entity_additional_fields():
assert entity.phone_number_hash == phone_number_hash
assert entity.password_hash == password_hash
assert entity.country_code == country_code
assert entity.device_id == device_id
assert entity.client_publish_pub_key == publish_pub_key
assert entity.client_device_id_pub_key == device_id_pub_key

Expand Down
1 change: 1 addition & 0 deletions tests/test_grpc_entity_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def configure_test_environment(session_temp_dir):
the application mode to testing.
"""
set_configs("MODE", "testing")
set_configs("MOCK_OTP", True)
set_configs("KEYSTORE_PATH", str(session_temp_dir))

hashing_key_path = session_temp_dir / "hash.key"
Expand Down
1 change: 1 addition & 0 deletions tests/test_otp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
def set_testing_mode():
"""Set the application mode to testing."""
set_configs("MODE", "testing")
set_configs("MOCK_OTP", True)


@pytest.fixture(autouse=True)
Expand Down

0 comments on commit 05017d8

Please sign in to comment.