Files
IoT/internal/service/store/bridge.go
T
Repinoid 3b7c8a0a63 feat(iot): bcrypt-хэш паролей устройств и бриджа + EMQX на bcrypt (P0-3 ч.1)
- handler CreateIoTDevice: пароль устройства хранится bcrypt-хэшем (hashPassword),
  открытый пароль отдаётся один раз в 201.
- store.EnsureBridgeDevice: пароль бриджа тоже bcrypt-хэшем.
- emqx/emqx.conf: password_hash_algorithm name=bcrypt, salt_rounds=10 (подтверждено
  по emqx/emqx: HOCON-ключ salt_rounds; Go bcrypt.DefaultCost=10).
- Добавлена зависимость golang.org/x/crypto v0.42.0.
2026-10-10 19:21:10 +03:00

41 lines
1.7 KiB
Go

package store
import (
"context"
"fmt"
"golang.org/x/crypto/bcrypt"
)
// bridgeNamespace — служебный namespace строки бриджа в iot_devices.
// EMQX (postgres auth/ACL) узнаёт бридж по условию
// namespace = '__bridge' AND username = device_id.
const bridgeNamespace = "__bridge"
// EnsureBridgeDevice создаёт (или обновляет) служебную строку бриджа в
// iot_devices. EMQX авторизует MQTT-подключение бриджа по этой строке,
// поэтому device_id = MQTT username (iot-bridge), пароль = MQTT_PASSWORD.
//
// Нужно для PG-auth EMQX (хвост 3): EMQX зависит только от PostgreSQL,
// монолит при старте гарантирует наличие строки бриджа.
func (s *DeviceStore) EnsureBridgeDevice(ctx context.Context, username, password string) error {
// Пароль бриджа храним bcrypt-хэшем (EMQX проверяет bcrypt), как и пароли
// устройств.
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("store: hash bridge password: %w", err)
}
_, err = s.db.ExecContext(ctx, `
INSERT INTO iot_devices (namespace, name, device_id, enabled, mqtt_password, metadata, phase)
VALUES ($1, $2, $3, true, $4, '{}'::jsonb, 'Active')
ON CONFLICT (namespace, name) DO UPDATE
SET device_id = EXCLUDED.device_id,
enabled = true,
mqtt_password = EXCLUDED.mqtt_password`,
bridgeNamespace, "iot-bridge", username, string(hash))
if err != nil {
return fmt.Errorf("store: ensure bridge device: %w", err)
}
return nil
}