diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/SecurityCommands.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/SecurityCommands.cs
index b55865b4..50e2da93 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/SecurityCommands.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/SecurityCommands.cs
@@ -122,9 +122,10 @@ public static class SecurityCommands
///
/// Renders the create-key response, surfacing the one-time bearer token prominently —
/// it is the only moment the secret is available and cannot be retrieved afterwards.
+ /// The advisory line is written to stderr so that piping stdout captures only the token.
///
/// The JSON success body returned by the management API.
- private static int PrintCreatedKey(string json)
+ internal static int PrintCreatedKey(string json)
{
using var doc = System.Text.Json.JsonDocument.Parse(json);
var root = doc.RootElement;
@@ -133,7 +134,7 @@ public static class SecurityCommands
Console.WriteLine($"API key created. KeyId: {keyId}");
Console.WriteLine();
- Console.WriteLine("Save this token now — it will not be shown again:");
+ Console.Error.WriteLine("Save this token now — it will not be shown again:");
Console.WriteLine($" {token}");
return 0;
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
index a82b7326..dd1b7e3e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
@@ -1315,6 +1315,9 @@ public class ManagementActor : ReceiveActor
// peppered hash, and assembles the one-time bearer token (sbk__).
// The token is shown to the operator only here, in the create response; it cannot
// be retrieved later. No hash/secret is stored or returned by ScadaBridge.
+ if (cmd.Methods is null || cmd.Methods.Count == 0)
+ throw new ManagementCommandException("At least one method must be specified for an API key.");
+
var admin = sp.GetRequiredService();
var created = await admin.CreateAsync(cmd.Name, cmd.Methods);
@@ -1333,6 +1336,8 @@ public class ManagementActor : ReceiveActor
{
var admin = sp.GetRequiredService();
var deleted = await admin.DeleteAsync(cmd.KeyId);
+ if (!deleted)
+ throw new ManagementCommandException($"API key '{cmd.KeyId}' not found.");
await AuditAsync(sp, user, "Delete", "ApiKey", cmd.KeyId, cmd.KeyId, null);
return deleted;
}
@@ -1757,7 +1762,9 @@ public class ManagementActor : ReceiveActor
{
// Inbound-API key re-arch (C2): enable/disable via the shared seam (no secret change).
var admin = sp.GetRequiredService();
- await admin.SetEnabledAsync(cmd.KeyId, cmd.IsEnabled);
+ var updated = await admin.SetEnabledAsync(cmd.KeyId, cmd.IsEnabled);
+ if (!updated)
+ throw new ManagementCommandException($"API key '{cmd.KeyId}' not found.");
await AuditAsync(sp, user, "Update", "ApiKey", cmd.KeyId, cmd.KeyId,
new { cmd.KeyId, cmd.IsEnabled });
return new { cmd.KeyId, cmd.IsEnabled };
@@ -1767,11 +1774,16 @@ public class ManagementActor : ReceiveActor
{
// Inbound-API key re-arch (C2): replace a key's method-scope set via the shared seam
// (no secret change). The library is authoritative for the scope replacement.
+ if (cmd.Methods is null || cmd.Methods.Count == 0)
+ throw new ManagementCommandException("At least one method must be specified for an API key.");
+
var admin = sp.GetRequiredService();
var updated = await admin.SetMethodsAsync(cmd.KeyId, cmd.Methods);
+ if (!updated)
+ throw new ManagementCommandException($"API key '{cmd.KeyId}' not found.");
await AuditAsync(sp, user, "Update", "ApiKey", cmd.KeyId, cmd.KeyId,
new { cmd.KeyId, cmd.Methods });
- return updated;
+ return new { cmd.KeyId, Methods = cmd.Methods };
}
private static async Task