broadfield-dev commited on
Commit
7191e6a
Β·
verified Β·
1 Parent(s): cc34811

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +139 -94
app.py CHANGED
@@ -3,178 +3,223 @@ from gradio_client import Client
3
  from PIL import Image
4
  import base64
5
  import io
 
6
  import logging
 
 
7
 
8
  # --- Configure Logging ---
9
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
10
  logger = logging.getLogger(__name__)
11
 
12
  # ==============================================================================
13
- # CONFIGURATION: IDs of the remote Gradio Spaces
14
  # ==============================================================================
15
  CREATOR_SPACE_ID = "broadfield-dev/KeyLock-Auth-Creator"
16
  SERVER_SPACE_ID = "broadfield-dev/KeyLock-Auth-Server"
17
- CLIENT_SPACE_LINK = "https://huggingface.co/spaces/broadfield-dev/KeyLock-Auth-Client"
18
 
 
 
 
 
 
 
19
 
20
  # ==============================================================================
21
  # API CALL WRAPPER FUNCTIONS
22
  # ==============================================================================
23
 
24
- def create_image_via_api(secret_data: str, public_key: str):
25
- """
26
- Calls the Creator Space API to generate an encrypted image.
27
- This function uses 'yield' to provide real-time status updates to the UI.
28
- """
29
- if not all([secret_data, public_key]):
30
- raise gr.Error("Secret Data and Public Key are both required.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- status = f"Initializing client for Creator: {CREATOR_SPACE_ID}..."
33
- yield None, None, status # Yield initial status update
34
 
 
 
 
 
 
 
 
 
35
  try:
36
  client = Client(src=CREATOR_SPACE_ID)
37
- status = f"Calling API '/create_image' on {CREATOR_SPACE_ID}..."
38
- yield None, None, status
39
 
40
- # The 'predict' method calls the API endpoint.
41
- # The result from an Image output in the remote API is a filepath to a temporary file.
42
- temp_filepath = client.predict(
43
- secret_data_str=secret_data,
44
- public_key_pem=public_key,
45
- api_name="/create_image" # The API name defined in the Creator space
46
- )
47
 
48
- if not temp_filepath:
49
- raise gr.Error("Creator API did not return a valid image file path.")
50
-
51
- # Load the image from the temporary file to display it in our dashboard's UI
52
  created_image = Image.open(temp_filepath)
53
-
54
- status = "βœ… Success! Image created by the Creator service."
55
  yield created_image, temp_filepath, status
56
-
57
  except Exception as e:
58
  logger.error(f"Creator API call failed: {e}", exc_info=True)
59
- # Yield the error message back to the UI
60
  yield None, None, f"❌ Error calling Creator API: {e}"
61
 
62
-
63
  def decrypt_image_via_api(image: Image.Image):
64
- """
65
- Calls the Server Space API to decrypt an image.
66
- Uses 'yield' for status updates.
67
- """
68
- if image is None:
69
- raise gr.Error("Please upload an image to decrypt.")
70
-
71
- status = f"Initializing client for Server: {SERVER_SPACE_ID}..."
72
  yield None, status
73
-
74
  try:
75
  client = Client(src=SERVER_SPACE_ID)
76
-
77
- # Convert the user's uploaded PIL image to a base64 string for the server's API
78
  with io.BytesIO() as buffer:
79
  image.save(buffer, format="PNG")
80
  b64_string = base64.b64encode(buffer.getvalue()).decode("utf-8")
81
 
82
- status = f"Calling API '/keylock-auth-decoder' on {SERVER_SPACE_ID}..."
83
  yield None, status
84
-
85
- # Call the server's API endpoint. The result will be a dictionary.
86
- decrypted_json = client.predict(
87
- image_base64_string=b64_string,
88
- api_name="/keylock-auth-decoder" # The API name defined in the Server space
89
- )
90
-
91
  status = "βœ… Success! Data decrypted by the Server."
92
  yield decrypted_json, status
93
-
94
  except Exception as e:
95
  logger.error(f"Server API call failed: {e}", exc_info=True)
96
  yield None, f"❌ Error calling Server API: {e}"
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
  # ==============================================================================
100
  # GRADIO DASHBOARD INTERFACE
101
  # ==============================================================================
102
  theme = gr.themes.Base(
103
- primary_hue="blue",
104
- secondary_hue="sky",
105
- neutral_hue="slate",
106
  font=(gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"),
107
  ).set(
108
- body_background_fill="#F8FAFC",
 
109
  block_background_fill="white",
110
  block_border_width="1px",
111
  block_shadow="*shadow_drop_lg",
112
  button_primary_background_fill="*primary_600",
113
  button_primary_background_fill_hover="*primary_700",
114
- button_primary_text_color="white",
115
  )
116
 
117
  with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
 
 
 
118
  gr.Markdown("# πŸ”‘ KeyLock Operations Dashboard")
119
- gr.Markdown("A centralized dashboard demonstrating the entire KeyLock ecosystem by making live API calls to the deployed Creator and Server spaces.")
120
 
121
- with gr.Tabs():
122
- with gr.TabItem("🏭 Image Creator", id=0):
123
- gr.Markdown("## Create an Encrypted Image via API Call")
124
- gr.Markdown(f"This interface calls the **`{CREATOR_SPACE_ID}`** Space to generate a new encrypted image.")
 
 
 
 
 
 
 
 
 
125
  with gr.Row(variant="panel"):
126
  with gr.Column(scale=2):
127
- creator_secret_input = gr.Textbox(lines=5, label="Secret Data", placeholder="API_KEY: sk-123...\nUSER: demo-user")
128
- creator_pubkey_input = gr.Textbox(lines=7, label="Public Key of the Target Server", placeholder="Paste the Server's public key here...")
129
- creator_button = gr.Button("Create Image via Creator API", variant="primary", icon="✨")
 
 
130
  with gr.Column(scale=1):
131
  creator_status = gr.Textbox(label="Status", interactive=False, lines=2)
132
  creator_image_output = gr.Image(label="Image from Creator Service", type="pil", show_download_button=True)
133
- creator_download_output = gr.File(label="Download Image File")
134
 
135
- with gr.TabItem("πŸ’» Client / Decoder", id=1):
136
- gr.Markdown("## Decrypt an Image via API Call")
137
- gr.Markdown(f"This interface acts as a client, calling the **`{SERVER_SPACE_ID}`** Space to decrypt an image.")
138
  with gr.Row(variant="panel"):
139
  with gr.Column(scale=1):
140
- client_image_input = gr.Image(type="pil", label="Upload Encrypted Image", sources=["upload", "clipboard"])
141
  client_button = gr.Button("Decrypt Image via Server API", variant="primary", icon="πŸ”“")
142
  with gr.Column(scale=1):
143
  client_status = gr.Textbox(label="Status", interactive=False, lines=2)
144
  client_json_output = gr.JSON(label="Decrypted Data from Server")
145
 
146
- with gr.TabItem("ℹ️ Service Information", id=2):
147
- gr.Markdown("## Ecosystem Overview")
148
  gr.Markdown(
149
- f"""
150
- This dashboard coordinates three separate Hugging Face Spaces to demonstrate a complete workflow:
151
-
152
- 1. **Creator Service:**
153
- - **Space:** [{CREATOR_SPACE_ID}](https://huggingface.co/spaces/{CREATOR_SPACE_ID})
154
- - **Role:** Provides a UI and an API (`/run/create_image`) to encrypt data into PNG images.
155
-
156
- 2. **Server (Decoder) Service:**
157
- - **Space:** [{SERVER_SPACE_ID}](https://huggingface.co/spaces/{SERVER_SPACE_ID})
158
- - **Role:** Holds a secret private key and provides a secure API (`/run/keylock-auth-decoder`) to decrypt images.
159
-
160
- 3. **This Dashboard:**
161
- - **Role:** Acts as a master client and control panel, making live API calls to the other services to showcase the end-to-end process.
162
-
163
- *Note: The original `{CLIENT_SPACE_LINK.split('/')[-1]}` is now superseded by the 'Client / Decoder' tab in this dashboard.*
164
  """
165
  )
166
-
167
- creator_button.click(
168
- fn=create_image_via_api,
169
- inputs=[creator_secret_input, creator_pubkey_input],
170
- outputs=[creator_image_output, creator_download_output, creator_status]
171
- )
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
- client_button.click(
174
- fn=decrypt_image_via_api,
175
- inputs=[client_image_input],
176
- outputs=[client_json_output, client_status]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  )
178
 
 
 
 
 
179
  if __name__ == "__main__":
180
  demo.launch()
 
3
  from PIL import Image
4
  import base64
5
  import io
6
+ import json
7
  import logging
8
+ from cryptography.hazmat.primitives import serialization
9
+ from cryptography.hazmat.primitives.asymmetric import rsa
10
 
11
  # --- Configure Logging ---
12
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
13
  logger = logging.getLogger(__name__)
14
 
15
  # ==============================================================================
16
+ # CONFIGURATION: IDs AND URLs OF THE REMOTE SERVICES
17
  # ==============================================================================
18
  CREATOR_SPACE_ID = "broadfield-dev/KeyLock-Auth-Creator"
19
  SERVER_SPACE_ID = "broadfield-dev/KeyLock-Auth-Server"
 
20
 
21
+ # Construct URLs for linking in documentation
22
+ BASE_HF_URL = "https://huggingface.co/spaces/"
23
+ CREATOR_URL = f"{BASE_HF_URL}{CREATOR_SPACE_ID}"
24
+ SERVER_URL = f"{BASE_HF_URL}{SERVER_SPACE_ID}"
25
+ CREATOR_APP_PY_URL = f"{CREATOR_URL}/blob/main/app.py"
26
+ SERVER_APP_PY_URL = f"{SERVER_URL}/blob/main/app.py"
27
 
28
  # ==============================================================================
29
  # API CALL WRAPPER FUNCTIONS
30
  # ==============================================================================
31
 
32
+ def get_creator_endpoints():
33
+ """Calls the Creator space to get its list of supported endpoints."""
34
+ status = f"Fetching endpoint list from {CREATOR_SPACE_ID}..."
35
+ yield gr.Dropdown(choices=[], value=None, label="⏳ Fetching..."), status
36
+ try:
37
+ client = Client(src=CREATOR_SPACE_ID)
38
+ json_string = client.predict(api_name="/get_endpoints")
39
+
40
+ endpoints = json.loads(json_string)
41
+ endpoint_names = [e['name'] for e in endpoints]
42
+
43
+ status = f"βœ… Success! Found {len(endpoint_names)} endpoints."
44
+ # Return the full list to the state, and the updated dropdown
45
+ yield gr.Dropdown(choices=endpoint_names, value=endpoint_names[0] if endpoint_names else None, label="Target Service"), status
46
+ except Exception as e:
47
+ logger.error(f"Failed to get endpoints from creator: {e}", exc_info=True)
48
+ status = f"❌ Error: Could not fetch endpoints from Creator space. Check if it's running. Details: {e}"
49
+ yield gr.Dropdown(choices=[], value=None, label="Error fetching services"), status
50
+
51
+ def create_image_via_api(service_name: str, secret_data: str, available_endpoints: list):
52
+ """Calls the Creator Space API to generate an encrypted image for a selected service."""
53
+ if not all([service_name, secret_data]):
54
+ raise gr.Error("Please select a service and provide secret data.")
55
 
56
+ status = f"Looking up public key for '{service_name}'..."
57
+ yield None, None, status
58
 
59
+ # Find the public key from the list we fetched earlier
60
+ public_key = next((e['public_key'] for e in available_endpoints if e['name'] == service_name), None)
61
+ if not public_key:
62
+ raise gr.Error(f"Could not find public key for '{service_name}' in the fetched configuration.")
63
+
64
+ status = f"Connecting to Creator: {CREATOR_SPACE_ID}..."
65
+ yield None, None, status
66
+
67
  try:
68
  client = Client(src=CREATOR_SPACE_ID)
69
+ temp_filepath = client.predict(secret_data, public_key, api_name="/create_image")
 
70
 
71
+ if not temp_filepath: raise gr.Error("Creator API did not return an image.")
 
 
 
 
 
 
72
 
 
 
 
 
73
  created_image = Image.open(temp_filepath)
74
+ status = f"βœ… Success! Image created for '{service_name}'."
 
75
  yield created_image, temp_filepath, status
 
76
  except Exception as e:
77
  logger.error(f"Creator API call failed: {e}", exc_info=True)
 
78
  yield None, None, f"❌ Error calling Creator API: {e}"
79
 
 
80
  def decrypt_image_via_api(image: Image.Image):
81
+ """Calls the Server Space API to decrypt an image."""
82
+ if image is None: raise gr.Error("Please upload an image to decrypt.")
83
+ status = f"Connecting to Server: {SERVER_SPACE_ID}..."
 
 
 
 
 
84
  yield None, status
 
85
  try:
86
  client = Client(src=SERVER_SPACE_ID)
 
 
87
  with io.BytesIO() as buffer:
88
  image.save(buffer, format="PNG")
89
  b64_string = base64.b64encode(buffer.getvalue()).decode("utf-8")
90
 
91
+ status = f"Calling API on {SERVER_SPACE_ID}..."
92
  yield None, status
93
+ decrypted_json = client.predict(b64_string, api_name="/keylock-auth-decoder")
 
 
 
 
 
 
94
  status = "βœ… Success! Data decrypted by the Server."
95
  yield decrypted_json, status
 
96
  except Exception as e:
97
  logger.error(f"Server API call failed: {e}", exc_info=True)
98
  yield None, f"❌ Error calling Server API: {e}"
99
 
100
+ def generate_rsa_keys():
101
+ """Generates a new RSA key pair."""
102
+ private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
103
+ private_pem = private_key.private_bytes(
104
+ encoding=serialization.Encoding.PEM,
105
+ format=serialization.PrivateFormat.PKCS8,
106
+ encryption_algorithm=serialization.NoEncryption()
107
+ ).decode('utf-8')
108
+ public_pem = private_key.public_key().public_bytes(
109
+ encoding=serialization.Encoding.PEM,
110
+ format=serialization.PublicFormat.SubjectPublicKeyInfo
111
+ ).decode('utf-8')
112
+ return private_pem, public_pem
113
 
114
  # ==============================================================================
115
  # GRADIO DASHBOARD INTERFACE
116
  # ==============================================================================
117
  theme = gr.themes.Base(
118
+ primary_hue=gr.themes.colors.blue,
119
+ secondary_hue=gr.themes.colors.sky,
120
+ neutral_hue=gr.themes.colors.slate,
121
  font=(gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"),
122
  ).set(
123
+ body_background_fill="#F1F5F9", # Light grey
124
+ panel_background_fill="white",
125
  block_background_fill="white",
126
  block_border_width="1px",
127
  block_shadow="*shadow_drop_lg",
128
  button_primary_background_fill="*primary_600",
129
  button_primary_background_fill_hover="*primary_700",
 
130
  )
131
 
132
  with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
133
+ # This State component will hold the list of dicts fetched from the Creator
134
+ endpoints_state = gr.State([])
135
+
136
  gr.Markdown("# πŸ”‘ KeyLock Operations Dashboard")
137
+ gr.Markdown("A centralized dashboard to manage and demonstrate the entire KeyLock ecosystem, powered by live API calls to dedicated services.")
138
 
139
+ with gr.Tabs() as tabs:
140
+ with gr.TabItem("β‘  Generate Keys", id=0):
141
+ gr.Markdown("## RSA Key Pair Generator")
142
+ gr.Markdown("Create a new public/private key pair. The public key can be added to a service's configuration to allow it to be a target for the Auth Creator.")
143
+ with gr.Row(variant="panel"):
144
+ with gr.Column():
145
+ gen_keys_button = gr.Button("Generate New 2048-bit Key Pair", icon="πŸ”‘", variant="secondary")
146
+ output_private_key = gr.Textbox(lines=10, label="Generated Private Key (Keep Secret!)", interactive=False, show_copy_button=True)
147
+ output_public_key = gr.Textbox(lines=10, label="Generated Public Key (Share This)", interactive=False, show_copy_button=True)
148
+
149
+ with gr.TabItem("β‘‘ Auth Creator", id=1):
150
+ gr.Markdown("## Create an Encrypted Authentication Image")
151
+ gr.Markdown(f"This tool calls the **[{CREATOR_SPACE_ID}]({CREATOR_URL})** service to encrypt data for a chosen target. The list of targets is fetched live from the Creator's configuration.")
152
  with gr.Row(variant="panel"):
153
  with gr.Column(scale=2):
154
+ with gr.Row():
155
+ creator_service_dropdown = gr.Dropdown(label="Target Service", interactive=True, info="Select the API server you want to encrypt data for.")
156
+ refresh_button = gr.Button("Refresh List", icon="πŸ”„", scale=0)
157
+ creator_secret_input = gr.Textbox(lines=8, label="Secret Data to Encrypt", placeholder="API_KEY: sk-123...\nUSER: demo-user")
158
+ creator_button = gr.Button("Create Auth Image via API", variant="primary", icon="✨")
159
  with gr.Column(scale=1):
160
  creator_status = gr.Textbox(label="Status", interactive=False, lines=2)
161
  creator_image_output = gr.Image(label="Image from Creator Service", type="pil", show_download_button=True)
 
162
 
163
+ with gr.TabItem("β‘’ Client / Decoder", id=2):
164
+ gr.Markdown("## Decrypt an Authentication Image")
165
+ gr.Markdown(f"This tool acts as a client, calling the **[{SERVER_SPACE_ID}]({SERVER_URL})** service to decrypt an image using its securely stored private key.")
166
  with gr.Row(variant="panel"):
167
  with gr.Column(scale=1):
168
+ client_image_input = gr.Image(type="pil", label="Upload Encrypted Auth Image", sources=["upload", "clipboard"])
169
  client_button = gr.Button("Decrypt Image via Server API", variant="primary", icon="πŸ”“")
170
  with gr.Column(scale=1):
171
  client_status = gr.Textbox(label="Status", interactive=False, lines=2)
172
  client_json_output = gr.JSON(label="Decrypted Data from Server")
173
 
174
+ with gr.TabItem("ℹ️ Service Information", id=3):
175
+ gr.Markdown("## Ecosystem Architecture")
176
  gr.Markdown(
177
+ """
178
+ This dashboard coordinates separate Hugging Face Spaces to demonstrate a secure, decoupled workflow. Each service has a specific role.
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  """
180
  )
181
+ with gr.Row():
182
+ with gr.Column():
183
+ gr.Markdown(f"""
184
+ ### 🏭 Auth Creator Service
185
+ - **Space:** [{CREATOR_SPACE_ID}]({CREATOR_URL})
186
+ - **Role:** Provides a UI and an API to encrypt data into PNG images for various targets. It reads a public configuration file (`endpoints.json`) to know which public keys it can use.
187
+ - **Source Code:** [app.py]({CREATOR_APP_PY_URL})
188
+ """)
189
+ with gr.Column():
190
+ gr.Markdown(f"""
191
+ ### πŸ“‘ Decoder Server
192
+ - **Space:** [{SERVER_SPACE_ID}]({SERVER_URL})
193
+ - **Role:** The trusted authority. It holds a **secret private key** and provides a secure API to decrypt images. This is the only component that can read the secret data.
194
+ - **Source Code:** [app.py]({SERVER_APP_PY_URL})
195
+ """)
196
+
197
+ # --- Wire up the component logic ---
198
+ gen_keys_button.click(fn=generate_rsa_keys, inputs=None, outputs=[output_private_key, output_public_key])
199
 
200
+ # Logic for fetching and populating the dropdown
201
+ load_event = demo.load(
202
+ fn=get_creator_endpoints,
203
+ inputs=None,
204
+ outputs=[creator_service_dropdown, creator_status]
205
+ ).then(
206
+ lambda data: data, # Pass the full data to the state
207
+ _js="(data) => data.choices",
208
+ outputs=[endpoints_state]
209
+ )
210
+ refresh_button.click(
211
+ fn=get_creator_endpoints,
212
+ inputs=None,
213
+ outputs=[creator_service_dropdown, creator_status]
214
+ ).then(
215
+ lambda data: data,
216
+ _js="(data) => data.choices",
217
+ outputs=[endpoints_state]
218
  )
219
 
220
+ # Logic for the Creator and Client tabs
221
+ creator_button.click(fn=create_image_via_api, inputs=[creator_service_dropdown, creator_secret_input, endpoints_state], outputs=[creator_image_output, gr.File(visible=False), creator_status])
222
+ client_button.click(fn=decrypt_image_via_api, inputs=[client_image_input], outputs=[client_json_output, client_status])
223
+
224
  if __name__ == "__main__":
225
  demo.launch()