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
« 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
4Copyright 2025
5SPDX-License-Identifier: Apache-2.0
6Authors: Mihai Criveti
8"""
10# Standard
11import base64
12import hashlib
13import json
14import os
16# Third-Party
17from cryptography.hazmat.primitives.ciphers.aead import AESGCM
19# First-Party
20from mcpgateway.config import settings
23def get_key() -> bytes:
24 """
25 Generate a 32-byte AES encryption key derived from a passphrase.
27 Returns:
28 bytes: A 32-byte encryption key.
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
39def encode_auth(auth_value: dict) -> str:
40 """
41 Encrypt and encode an authentication dictionary into a compact base64-url string.
43 Args:
44 auth_value (dict): The authentication dictionary to encrypt and encode.
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()
61def decode_auth(encoded_value: str) -> dict:
62 """
63 Decode and decrypt a base64-url-safe encrypted string back into the authentication dictionary.
65 Args:
66 encoded_value (str): The encrypted base64-url string to decode and decrypt.
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())