feat: Add SSH public key storage for users

- Add ssh_public_key column to User model for storing SSH keys
- Add database migration for ssh_public_key column
- Add SSH key section in user profile settings with validation
- Auto-inject user's SSH key when deploying VMs from templates
- Auto-inject user's SSH key when creating LXC containers
- Support for both regular and smart LXC creation endpoints
- Add i18n translations for SSH key UI (ru/en)
- Validate SSH key format (ssh-rsa, ssh-ed25519, ecdsa-sha2, ssh-dss)

Users can now save their SSH public key in Settings -> Account,
and it will be automatically added to any VM or LXC container
they create, enabling passwordless SSH access.
This commit is contained in:
2025-12-15 20:22:42 +05:00
parent 2b78d93710
commit 379afd0353
8 changed files with 162 additions and 4 deletions

View File

@@ -78,6 +78,22 @@
</button>
</form>
</div>
<div class="settings-section">
<h2><i class="fa-solid fa-key"></i> {{ t('ssh_public_key') }}</h2>
<form id="ssh-key-form" class="settings-form">
<div class="form-group">
<label for="ssh_public_key">{{ t('ssh_public_key_label') }}</label>
<textarea id="ssh_public_key" rows="4" placeholder="ssh-rsa AAAAB3NzaC1yc2EAAA... user@hostname" style="font-family: monospace; font-size: 0.85rem;"></textarea>
<small>{{ t('ssh_public_key_help') }}</small>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> {{ t('save_ssh_key') }}
</button>
</form>
</div>
</div>
<!-- Notifications Settings Tab -->
@@ -1139,6 +1155,41 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
// SSH Key form
document.getElementById('ssh-key-form').addEventListener('submit', async function(e) {
e.preventDefault();
const sshKey = document.getElementById('ssh_public_key').value.trim();
// Basic validation
if (sshKey && !sshKey.match(/^(ssh-rsa|ssh-ed25519|ecdsa-sha2|ssh-dss)\s+\S+/)) {
showNotification(t('invalid_ssh_key_format'), 'error');
return;
}
const data = {
ssh_public_key: sshKey || null
};
try {
const response = await fetchWithAuth('/settings/api/profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
showNotification(t('ssh_key_saved'), 'success');
} else {
const error = await response.json();
showNotification(error.detail || t('error_saving_ssh_key'), 'error');
}
} catch (error) {
console.error('Error:', error);
showNotification(t('error_saving_ssh_key'), 'error');
}
});
// Panel form
document.getElementById('panel-form').addEventListener('submit', async function(e) {
e.preventDefault();
@@ -1294,6 +1345,7 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('username').value = user.username;
document.getElementById('full_name').value = user.full_name || '';
document.getElementById('email').value = user.email || '';
document.getElementById('ssh_public_key').value = user.ssh_public_key || '';
document.getElementById('profile-form').style.display = 'block';
}