diff --git a/cmd/verstak-server/server.go b/cmd/verstak-server/server.go
index cb47ea6..26c4088 100644
--- a/cmd/verstak-server/server.go
+++ b/cmd/verstak-server/server.go
@@ -320,7 +320,9 @@ func (s *Server) handleDeviceRegister(w http.ResponseWriter, r *http.Request) {
return
}
var req struct {
- Name string `json:"name"`
+ Name string `json:"name"`
+ Username string `json:"username"`
+ Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
jsonErr(w, 400, "invalid JSON")
@@ -330,6 +332,14 @@ func (s *Server) handleDeviceRegister(w http.ResponseWriter, r *http.Request) {
jsonErr(w, 400, "name required")
return
}
+ if req.Username == "" || req.Password == "" {
+ jsonErr(w, 401, "admin username and password required")
+ return
+ }
+ if !s.cfg.CheckAdmin(req.Username, req.Password) {
+ jsonErr(w, 401, "invalid admin credentials")
+ return
+ }
b := make([]byte, 20)
rand.Read(b)
@@ -337,7 +347,7 @@ func (s *Server) handleDeviceRegister(w http.ResponseWriter, r *http.Request) {
now := time.Now().UTC().Format(time.RFC3339)
- result, err := s.db.Exec(
+ _, err := s.db.Exec(
"INSERT INTO server_devices (id, name, api_key, last_seen, created_at) VALUES (?, ?, ?, ?, ?)",
apiKey[:12], req.Name, apiKey, now, now,
)
@@ -345,8 +355,6 @@ func (s *Server) handleDeviceRegister(w http.ResponseWriter, r *http.Request) {
jsonErr(w, 500, err.Error())
return
}
- id, _ := result.LastInsertId()
- _ = id
jsonOK(w, map[string]interface{}{
"device_id": apiKey[:12],
@@ -587,7 +595,29 @@ func (s *Server) handleAdminDashboard(w http.ResponseWriter, r *http.Request) {
Verstak Sync — Admin
-
+
Verstak Sync Server
@@ -600,16 +630,23 @@ func (s *Server) handleAdminDashboard(w http.ResponseWriter, r *http.Request) {
fetch('/admin/api/keys').then(r=>r.json()).then(keys=>{
const div=document.getElementById('keys')
if(!keys.length){div.innerHTML='
Нет ключей
';return}
- div.innerHTML='
| Устройство | Ключ | |
'+
- keys.map(k=>'| '+k.name+' | '+k.api_key.slice(0,16)+'… | '+
- ' |
').join('')+'
'
+ div.innerHTML='
| Устройство | API-ключ | | |
'+
+ keys.map(k=>'| '+k.name+' | '+k.api_key+' | '+
+ ' | '+
+ ' |
').join('')+'
'
})
+function copyKey(key,btn){
+ navigator.clipboard.writeText(key).then(()=>{
+ var old=btn.textContent;btn.textContent='Скопировано';btn.style.color='#4ade80'
+ setTimeout(function(){btn.textContent=old;btn.style.color=''},1500)
+ })
+}
function delKey(id){if(confirm('Удалить ключ?'))fetch('/admin/api/keys/'+id,{method:'DELETE'}).then(()=>location.reload())}
Новый ключ
Health check
`, deviceCount, opsCount)
diff --git a/docs/10_Sync_Server_Guide.md b/docs/10_Sync_Server_Guide.md
index 46f26b7..7f8efbf 100644
--- a/docs/10_Sync_Server_Guide.md
+++ b/docs/10_Sync_Server_Guide.md
@@ -82,15 +82,15 @@ API-ключ — это токен, который клиент (Верстак
2. В разделе "API Keys" ввести имя устройства и нажать "Create".
3. Скопировать сгенерированный ключ.
-Через API (без авторизации):
+Через API (требует логин и пароль администратора):
```bash
curl -X POST http://localhost:47732/api/v1/device/register \
-H "Content-Type: application/json" \
- -d '{"name":"мой-ноутбук"}'
+ -d '{"name":"мой-ноутбук","username":"admin","password":"пароль-админа"}'
```
Ответ: `{"device_id":"...","api_key":"..."}`
-**Важно:** endpoint регистрации открыт без аутентификации. Не выставляйте сервер в интернет без дополнительной защиты (фаервол, VPN, reverse proxy с базовой аутентификацией).
+**Важно:** не выставляйте сервер в интернет без HTTPS (через reverse proxy). До создания полноценной системы пользователей регистрация устройств требует учётных данных администратора.
### Как использовать
@@ -158,7 +158,6 @@ sync:
| Метод | Путь | Описание |
|---|---|---|
| GET | `/api/v1/health` | Проверка здоровья сервера |
-| POST | `/api/v1/device/register` | Регистрация устройства (без аутентификации) |
### Требуют API-ключ (Authorization: Bearer)
@@ -169,6 +168,12 @@ sync:
| POST | `/api/v1/blobs/` | Загрузить blob (multipart) |
| GET | `/api/v1/blobs/{sha256}` | Скачать blob |
+### Требуют логин+пароль администратора
+
+| Метод | Путь | Описание |
+|---|---|---|
+| POST | `/api/v1/device/register` | Регистрация устройства (body: name + username + password) |
+
### Требуют сессию админа (cookie)
| Метод | Путь | Описание |
diff --git a/test_smoke_sync.sh b/test_smoke_sync.sh
index ef91af9..888acde 100755
--- a/test_smoke_sync.sh
+++ b/test_smoke_sync.sh
@@ -23,7 +23,7 @@ curl -sf "http://localhost:$SERVER_PORT/api/v1/health" | grep -q '"ok"' && echo
echo ":: Register device"
REG=$(curl -sf -X POST "http://localhost:$SERVER_PORT/api/v1/device/register" \
- -H "Content-Type: application/json" -d '{"name":"smoke"}')
+ -H "Content-Type: application/json" -d '{"name":"smoke","username":"admin","password":"pass"}')
DID=$(echo "$REG" | python3 -c "import sys,json;print(json.load(sys.stdin)['device_id'])")
AKEY=$(echo "$REG" | python3 -c "import sys,json;print(json.load(sys.stdin)['api_key'])")
echo " device=$DID"