Coverage for mcpgateway/utils/services_auth.py: 100%

33 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-22 15:47 +0100

1# -*- coding: utf-8 -*- 

2""" 

3 

4Copyright 2025 

5SPDX-License-Identifier: Apache-2.0 

6Authors: Mihai Criveti 

7 

8""" 

9 

10import base64 

11import hashlib 

12import json 

13import os 

14 

15from cryptography.hazmat.primitives.ciphers.aead import AESGCM 

16 

17from mcpgateway.config import settings 

18 

19 

20def get_key() -> bytes: 

21 """ 

22 Generate a 32-byte AES encryption key derived from a passphrase. 

23 

24 Returns: 

25 bytes: A 32-byte encryption key. 

26 

27 Raises: 

28 ValueError: If the passphrase is not set or empty. 

29 """ 

30 passphrase = settings.auth_encryption_secret 

31 if not passphrase: 

32 raise ValueError("AUTH_ENCRPYPTION_SECRET not set in environment.") 

33 return hashlib.sha256(passphrase.encode()).digest() # 32-byte key 

34 

35 

36def encode_auth(auth_value: dict) -> str: 

37 """ 

38 Encrypt and encode an authentication dictionary into a compact base64-url string. 

39 

40 Args: 

41 auth_value (dict): The authentication dictionary to encrypt and encode. 

42 

43 Returns: 

44 str: A base64-url-safe encrypted string representing the dictionary, or None if input is None. 

45 """ 

46 if not auth_value: 

47 return None 

48 plaintext = json.dumps(auth_value) 

49 key = get_key() 

50 aesgcm = AESGCM(key) 

51 nonce = os.urandom(12) 

52 ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None) 

53 combined = nonce + ciphertext 

54 encoded = base64.urlsafe_b64encode(combined).rstrip(b"=") 

55 return encoded.decode() 

56 

57 

58def decode_auth(encoded_value: str) -> dict: 

59 """ 

60 Decode and decrypt a base64-url-safe encrypted string back into the authentication dictionary. 

61 

62 Args: 

63 encoded_value (str): The encrypted base64-url string to decode and decrypt. 

64 

65 Returns: 

66 dict: The decrypted authentication dictionary, or empty dict if input is None. 

67 """ 

68 if not encoded_value: 

69 return {} 

70 key = get_key() 

71 aesgcm = AESGCM(key) 

72 # Fix base64 padding 

73 padded = encoded_value + "=" * (-len(encoded_value) % 4) 

74 combined = base64.urlsafe_b64decode(padded) 

75 nonce = combined[:12] 

76 ciphertext = combined[12:] 

77 plaintext = aesgcm.decrypt(nonce, ciphertext, None) 

78 return json.loads(plaintext.decode())