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

33 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-09 11:03 +0100

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

2"""mcpgateway.utils.services_auth - Authentication utilities for MCP Gateway 

3 

4Copyright 2025 

5SPDX-License-Identifier: Apache-2.0 

6Authors: Mihai Criveti 

7 

8""" 

9 

10# Standard 

11import base64 

12import hashlib 

13import json 

14import os 

15 

16# Third-Party 

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

18 

19# First-Party 

20from mcpgateway.config import settings 

21 

22 

23def get_key() -> bytes: 

24 """ 

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

26 

27 Returns: 

28 bytes: A 32-byte encryption key. 

29 

30 Raises: 

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

32 """ 

33 passphrase = settings.auth_encryption_secret 

34 if not passphrase: 

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

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

37 

38 

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

40 """ 

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

42 

43 Args: 

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

45 

46 Returns: 

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

48 """ 

49 if not auth_value: 

50 return None 

51 plaintext = json.dumps(auth_value) 

52 key = get_key() 

53 aesgcm = AESGCM(key) 

54 nonce = os.urandom(12) 

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

56 combined = nonce + ciphertext 

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

58 return encoded.decode() 

59 

60 

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

62 """ 

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

64 

65 Args: 

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

67 

68 Returns: 

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

70 """ 

71 if not encoded_value: 

72 return {} 

73 key = get_key() 

74 aesgcm = AESGCM(key) 

75 # Fix base64 padding 

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

77 combined = base64.urlsafe_b64decode(padded) 

78 nonce = combined[:12] 

79 ciphertext = combined[12:] 

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

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