-
+
diff --git a/backend/package.json b/backend/package.json
index 15efc2c..be0c8f4 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "mikrotik-manager-backend",
- "version": "0.23.24-beta",
+ "version": "0.23.25-beta",
"description": "Mikrotik Network Management Platform - Backend API",
"main": "dist/index.js",
"scripts": {
diff --git a/backend/src/routes/devices.ts b/backend/src/routes/devices.ts
index 1f00f40..d8ca450 100644
--- a/backend/src/routes/devices.ts
+++ b/backend/src/routes/devices.ts
@@ -1663,7 +1663,19 @@ router.delete('/:id/ssh-key', requireWrite, async (req: Request, res: Response)
* ends by proving the key, and running sixty of those at once would be a
* thundering herd against the fleet for no wall-clock gain worth having.
*/
-router.post('/ssh-keys/deploy-all', requireWrite, async (_req: Request, res: Response) => {
+router.post('/ssh-keys/deploy-all', requireWrite, async (req: Request, res: Response) => {
+ // This is the most consequential call in the feature: it disables password SSH
+ // across an entire fleet in one request. The per-device path asks for an
+ // acknowledgement in the UI, and an endpoint that can do it sixty times over
+ // should not be easier to trigger than the one that does it once.
+ if ((req.body as { confirm?: boolean })?.confirm !== true) {
+ return res.status(400).json({
+ error: 'Refusing to deploy fleet-wide without confirmation. This disables password ' +
+ 'SSH for the configured user on every device it touches; the API and WinBox ' +
+ 'are unaffected. Repeat with {"confirm": true} once that is understood.',
+ });
+ }
+
const targets = await query(
`SELECT ${SSH_TARGET_COLS} FROM devices d
WHERE d.status != 'disabled' AND d.ssh_username IS NOT NULL
diff --git a/frontend/package.json b/frontend/package.json
index 0754e77..387bb92 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "mikrotik-manager-frontend",
- "version": "0.23.24-beta",
+ "version": "0.23.25-beta",
"private": true,
"type": "module",
"scripts": {
diff --git a/frontend/src/components/device-detail/SshKeyCard.tsx b/frontend/src/components/device-detail/SshKeyCard.tsx
index 7b0b253..a7ba98f 100644
--- a/frontend/src/components/device-detail/SshKeyCard.tsx
+++ b/frontend/src/components/device-detail/SshKeyCard.tsx
@@ -11,10 +11,16 @@ import { sshKeyApi } from '../../services/api';
* so installing a key is a real trade, not a pure addition, and the operator has
* to be told before they make it rather than after (#110).
*/
-export default function SshKeyCard({ deviceId }: { deviceId: number }) {
+export default function SshKeyCard(
+ { deviceId, sshUsername }: { deviceId: number; sshUsername?: string },
+) {
const queryClient = useQueryClient();
const [msg, setMsg] = useState<{ ok: boolean; text: string } | null>(null);
const [confirmRevoke, setConfirmRevoke] = useState(false);
+ // Installing a key changes how this device can be reached, and cannot be
+ // undone by retrying — it is undone by revoking. A single click was the wrong
+ // weight for that, especially when revoking already asked for confirmation.
+ const [acknowledged, setAcknowledged] = useState(false);
const { data, isLoading } = useQuery({
queryKey: ['ssh-key', deviceId],
@@ -43,6 +49,7 @@ export default function SshKeyCard({ deviceId }: { deviceId: number }) {
if (isLoading) return
@@ -90,24 +97,53 @@ export default function SshKeyCard({ deviceId }: { deviceId: number }) {
)}
- ) : (
-
-
-
- Installing a key disables password SSH for this user.{' '}
- RouterOS requires key authentication once a key is bound to an account. The binary API and
- WinBox are unaffected, and revoking the key restores password SSH.
+ ) : null}
+
+ {/* Shown before installing *and* after. Someone arriving at a keyed device
+ needs to know why their password no longer works over SSH just as much
+ as the person about to cause it. */}
+
+
+
+
+ {data
+ ? `Password SSH is disabled for "${user}" on this device.`
+ : `Installing a key disables password SSH for "${user}".`}
+
+
RouterOS requires key authentication once a key is bound to an account.
+
The binary API and WinBox are unaffected, and remain a way in.
+
Anyone who signs in to this device over SSH with a password — a colleague,
+ a script, another tool — will stop being able to.
+
+ If this server loses its database or encryption key,
+ the private key goes with it.{' '}
+ SSH would then need recovering through the API or WinBox, because the password
+ will already have been refused.
+