1+ import re
2+ import base64
3+
14from .base import BaseDatabaseModel
2- from pydantic import Field
5+ from pydantic import Field , field_validator
36from typing import Optional , List
47from ..graph_template_validation_status import GraphTemplateValidationStatus
58from ..node_template_model import NodeTemplate
69from pymongo import IndexModel
7-
10+ from typing import Dict
11+ from app .utils .encrypter import encrypter
812
913class GraphTemplate (BaseDatabaseModel ):
1014 name : str = Field (..., description = "Name of the graph" )
1115 namespace : str = Field (..., description = "Namespace of the graph" )
1216 nodes : List [NodeTemplate ] = Field (..., description = "Nodes of the graph" )
1317 validation_status : GraphTemplateValidationStatus = Field (..., description = "Validation status of the graph" )
1418 validation_errors : Optional [List [str ]] = Field (None , description = "Validation errors of the graph" )
19+ secrets : Dict [str , str ] = Field (default_factory = dict , description = "Secrets of the graph" )
1520
1621 class Settings :
1722 indexes = [
@@ -20,4 +25,58 @@ class Settings:
2025 unique = True ,
2126 name = "unique_name_namespace"
2227 )
23- ]
28+ ]
29+
30+ @field_validator ('secrets' )
31+ @classmethod
32+ def validate_secrets (cls , v : Dict [str , str ]) -> Dict [str , str ]:
33+ for secret_name , secret_value in v .items ():
34+ if not secret_name or not secret_value :
35+ raise ValueError ("Secrets cannot be empty" )
36+ if not isinstance (secret_name , str ):
37+ raise ValueError ("Secret name must be a string" )
38+ if not isinstance (secret_value , str ):
39+ raise ValueError ("Secret value must be a string" )
40+ cls ._validate_secret_value (secret_value )
41+
42+ return v
43+
44+ @classmethod
45+ def _validate_secret_value (cls , secret_value : str ) -> None :
46+ # Check minimum length for AES-GCM encrypted string
47+ # 12 bytes nonce + minimum ciphertext + base64 encoding
48+ if len (secret_value ) < 32 : # Minimum length for encrypted string
49+ raise ValueError ("Value appears to be too short for an encrypted string" )
50+
51+ # Check if the string contains only URL-safe base64 characters
52+ url_safe_base64_pattern = r'^[A-Za-z0-9_-]+$'
53+ if not re .match (url_safe_base64_pattern , secret_value ):
54+ raise ValueError ("Value must be URL-safe base64 encoded" )
55+
56+ # Check if the string length is valid for base64 encoding
57+ # Base64 encoding increases size by ~33%, and we need at least 12 bytes nonce
58+ if len (secret_value ) % 4 != 0 :
59+ raise ValueError ("Value length is not valid for base64 encoding" )
60+
61+ # Try to decode as base64 to ensure it's valid
62+ try :
63+ decoded = base64 .urlsafe_b64decode (secret_value )
64+ if len (decoded ) < 12 :
65+ raise ValueError ("Decoded value is too short to contain valid nonce" )
66+ except Exception :
67+ raise ValueError ("Value is not valid URL-safe base64 encoded" )
68+
69+
70+ def set_secrets (self , secrets : Dict [str , str ]) -> "GraphTemplate" :
71+ self .secrets = {secret_name : encrypter .encrypt (secret_value ) for secret_name , secret_value in secrets .items ()}
72+ return self
73+
74+ def get_secrets (self ) -> Dict [str , str ]:
75+ if not self .secrets :
76+ return {}
77+ return {secret_name : encrypter .decrypt (secret_value ) for secret_name , secret_value in self .secrets .items ()}
78+
79+ def get_secret (self , secret_name : str ) -> str :
80+ if not self .secrets :
81+ return ""
82+ return encrypter .decrypt (self .secrets [secret_name ])
0 commit comments