fix(devices): move device creation from CheckRegistration to ApproveDevice
Previously device creation happened in CheckRegistrationStatus (polling endpoint), which was racy. Now the admin's ApproveDevice handler creates the device record and stores auth token + device ID on the registration entry. CheckRegistrationStatus just returns the pre-created credentials. Also adds approved/authToken/deviceID/syncEndpoints fields to PendingRegistration struct.
This commit is contained in:
@@ -100,6 +100,10 @@ type PendingRegistration struct {
|
||||
UserID uuid.UUID
|
||||
ExpiresAt time.Time
|
||||
CreatedAt time.Time
|
||||
Approved bool
|
||||
AuthToken string
|
||||
DeviceID [16]byte
|
||||
SyncEndpoints map[string]string
|
||||
}
|
||||
|
||||
var pendingRegistrations = make(map[string]*PendingRegistration)
|
||||
@@ -173,60 +177,21 @@ func (h *DeviceHandler) CheckRegistrationStatus(c *echo.Context) error {
|
||||
return c.JSON(http.StatusGone, map[string]string{"error": "registration expired"})
|
||||
}
|
||||
|
||||
if registration.UserID == (uuid.UUID{}) {
|
||||
if registration.Approved {
|
||||
delete(pendingRegistrations, req.RegistrationID)
|
||||
|
||||
return c.JSON(http.StatusOK, DeviceAuthStatusResponse{
|
||||
Status: "pending",
|
||||
Message: "awaiting user approval",
|
||||
ExpiresIn: int(time.Until(registration.ExpiresAt).Seconds()),
|
||||
Status: "approved",
|
||||
AuthToken: registration.AuthToken,
|
||||
DeviceID: registration.DeviceID,
|
||||
SyncEndpoints: registration.SyncEndpoints,
|
||||
})
|
||||
}
|
||||
|
||||
authToken, err := generateDeviceToken()
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate auth token"})
|
||||
}
|
||||
|
||||
userUUID := registration.UserID
|
||||
pgUserID := pgtype.UUID{Bytes: [16]byte(userUUID), Valid: true}
|
||||
|
||||
syncEnabled := pgtype.Bool{Bool: true, Valid: true}
|
||||
autoSync := pgtype.Bool{Bool: true, Valid: true}
|
||||
syncFreq := pgtype.Int4{Int32: 5, Valid: true}
|
||||
|
||||
device, err := h.db.CreateDevice(c.Request().Context(), database.CreateDeviceParams{
|
||||
UserID: pgUserID,
|
||||
DeviceName: registration.DeviceName,
|
||||
DeviceType: registration.DeviceType,
|
||||
DeviceIdentifier: registration.DeviceIdentifier,
|
||||
AuthToken: authToken,
|
||||
SyncEnabled: syncEnabled,
|
||||
AutoSync: autoSync,
|
||||
SyncFrequencyMinutes: syncFreq,
|
||||
DeviceMetadata: []byte("{}"),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to create device"})
|
||||
}
|
||||
|
||||
delete(pendingRegistrations, req.RegistrationID)
|
||||
|
||||
syncEndpoints := map[string]string{}
|
||||
switch registration.DeviceType {
|
||||
case "koreader":
|
||||
syncEndpoints["progress"] = fmt.Sprintf("%s/api/sync/koreader/progress", h.cfg.BaseURL)
|
||||
syncEndpoints["metadata"] = fmt.Sprintf("%s/api/sync/koreader/metadata", h.cfg.BaseURL)
|
||||
syncEndpoints["bookmarks"] = fmt.Sprintf("%s/api/sync/koreader/bookmarks", h.cfg.BaseURL)
|
||||
case "kobo":
|
||||
syncEndpoints["markup"] = fmt.Sprintf("%s/api/sync/kobo/markup", h.cfg.BaseURL)
|
||||
syncEndpoints["library"] = fmt.Sprintf("%s/api/sync/kobo/library", h.cfg.BaseURL)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, DeviceAuthStatusResponse{
|
||||
Status: "approved",
|
||||
AuthToken: authToken,
|
||||
DeviceID: device.ID.Bytes,
|
||||
SyncEndpoints: syncEndpoints,
|
||||
Status: "pending",
|
||||
Message: "awaiting user approval",
|
||||
ExpiresIn: int(time.Until(registration.ExpiresAt).Seconds()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -597,14 +562,63 @@ func (h *DeviceHandler) ApproveDevice(c *echo.Context) error {
|
||||
return c.JSON(http.StatusGone, map[string]string{"error": "registration expired"})
|
||||
}
|
||||
|
||||
if registration.Approved {
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"message": "device already approved",
|
||||
"device_name": registration.DeviceName,
|
||||
"device_type": registration.DeviceType,
|
||||
"approved": true,
|
||||
})
|
||||
}
|
||||
|
||||
authToken, err := generateDeviceToken()
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate auth token"})
|
||||
}
|
||||
|
||||
pgUserID := pgtype.UUID{Bytes: [16]byte(userUUID), Valid: true}
|
||||
syncEnabled := pgtype.Bool{Bool: true, Valid: true}
|
||||
autoSync := pgtype.Bool{Bool: true, Valid: true}
|
||||
syncFreq := pgtype.Int4{Int32: 5, Valid: true}
|
||||
|
||||
device, err := h.db.CreateDevice(c.Request().Context(), database.CreateDeviceParams{
|
||||
UserID: pgUserID,
|
||||
DeviceName: registration.DeviceName,
|
||||
DeviceType: registration.DeviceType,
|
||||
DeviceIdentifier: registration.DeviceIdentifier,
|
||||
AuthToken: authToken,
|
||||
SyncEnabled: syncEnabled,
|
||||
AutoSync: autoSync,
|
||||
SyncFrequencyMinutes: syncFreq,
|
||||
DeviceMetadata: []byte("{}"),
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to create device"})
|
||||
}
|
||||
|
||||
syncEndpoints := map[string]string{}
|
||||
switch registration.DeviceType {
|
||||
case "koreader":
|
||||
syncEndpoints["progress"] = fmt.Sprintf("%s/api/sync/koreader/progress", h.cfg.BaseURL)
|
||||
syncEndpoints["metadata"] = fmt.Sprintf("%s/api/sync/koreader/metadata", h.cfg.BaseURL)
|
||||
syncEndpoints["bookmarks"] = fmt.Sprintf("%s/api/sync/koreader/bookmarks", h.cfg.BaseURL)
|
||||
case "kobo":
|
||||
syncEndpoints["markup"] = fmt.Sprintf("%s/api/sync/kobo/markup", h.cfg.BaseURL)
|
||||
syncEndpoints["library"] = fmt.Sprintf("%s/api/sync/kobo/library", h.cfg.BaseURL)
|
||||
}
|
||||
|
||||
registration.UserID = userUUID
|
||||
registration.Approved = true
|
||||
registration.AuthToken = authToken
|
||||
registration.DeviceID = device.ID.Bytes
|
||||
registration.SyncEndpoints = syncEndpoints
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"message": "device approved successfully",
|
||||
"device_name": registration.DeviceName,
|
||||
"device_type": registration.DeviceType,
|
||||
"registration_id": registrationID,
|
||||
"approved": true, // Fixed: Add confirmation field for test compatibility
|
||||
"approved": true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -624,15 +638,9 @@ func (h *DeviceHandler) RejectDevice(c *echo.Context) error {
|
||||
}
|
||||
|
||||
func (h *DeviceHandler) GetPendingRegistrationsData(c *echo.Context) ([]map[string]interface{}, error) {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registrations := []map[string]interface{}{}
|
||||
for _, reg := range pendingRegistrations {
|
||||
if reg.UserID == userUUID || reg.UserID == (uuid.UUID{}) {
|
||||
if reg.UserID == (uuid.UUID{}) {
|
||||
registrations = append(registrations, map[string]interface{}{
|
||||
"registration_id": reg.RegistrationID,
|
||||
"device_name": reg.DeviceName,
|
||||
@@ -640,7 +648,7 @@ func (h *DeviceHandler) GetPendingRegistrationsData(c *echo.Context) ([]map[stri
|
||||
"device_identifier": reg.DeviceIdentifier,
|
||||
"expires_at": reg.ExpiresAt,
|
||||
"created_at": reg.CreatedAt,
|
||||
"is_approved": reg.UserID != (uuid.UUID{}),
|
||||
"is_approved": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user