@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"data-terminal-cloud/internal/audit"
|
||||
"data-terminal-cloud/internal/database"
|
||||
"data-terminal-cloud/internal/deviceactivation"
|
||||
"data-terminal-cloud/internal/identity"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
@@ -43,15 +44,15 @@ func (s *Service) ClaimDevice(ctx context.Context, principal identity.Principal,
|
||||
return fmt.Errorf("resolve claim subject: %w", err)
|
||||
}
|
||||
|
||||
var claimID, deviceID, devicePublicID string
|
||||
var claimID, deviceID, devicePublicID, activationState string
|
||||
var expiresAt time.Time
|
||||
var revokedAt, usedAt *time.Time
|
||||
var usedBindingID *string
|
||||
if err := tx.QueryRow(ctx, `
|
||||
SELECT dc.id::text,dc.device_id::text,d.public_id::text,dc.expires_at,dc.revoked_at,dc.used_at,dc.used_binding_id::text
|
||||
SELECT dc.id::text,dc.device_id::text,d.public_id::text,d.activation_state,dc.expires_at,dc.revoked_at,dc.used_at,dc.used_binding_id::text
|
||||
FROM device_claim dc JOIN device d ON d.id=dc.device_id AND d.tenant_id=dc.tenant_id
|
||||
WHERE dc.token_digest=$1 AND dc.tenant_id=$2::uuid AND d.status='registered'
|
||||
FOR UPDATE OF dc`, digest, access.tenantID).Scan(&claimID, &deviceID, &devicePublicID, &expiresAt, &revokedAt, &usedAt, &usedBindingID); errors.Is(err, pgx.ErrNoRows) {
|
||||
FOR UPDATE OF d,dc`, digest, access.tenantID).Scan(&claimID, &deviceID, &devicePublicID, &activationState, &expiresAt, &revokedAt, &usedAt, &usedBindingID); errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrDeviceClaimInvalid
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("resolve device claim: %w", err)
|
||||
@@ -78,6 +79,9 @@ func (s *Service) ClaimDevice(ctx context.Context, principal identity.Principal,
|
||||
replayed = true
|
||||
return nil
|
||||
}
|
||||
if activationState != string(deviceactivation.Claimable) {
|
||||
return ErrDeviceClaimInvalid
|
||||
}
|
||||
var activeBinding bool
|
||||
if err := tx.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM device_binding WHERE device_id=$1::uuid AND unbound_at IS NULL)`, deviceID).Scan(&activeBinding); err != nil {
|
||||
return fmt.Errorf("check active device binding: %w", err)
|
||||
@@ -97,6 +101,27 @@ func (s *Service) ClaimDevice(ctx context.Context, principal identity.Principal,
|
||||
if _, err := tx.Exec(ctx, `UPDATE device_claim SET used_at=now(),used_binding_id=$1::uuid WHERE id=$2::uuid`, bindingInternalID, claimID); err != nil {
|
||||
return fmt.Errorf("mark device claim used: %w", err)
|
||||
}
|
||||
if _, err := deviceactivation.Apply(ctx, tx, deviceactivation.Input{DeviceID: deviceID, TenantID: access.tenantID,
|
||||
Target: deviceactivation.Bound, Reason: "用户通过一次性绑定码认领设备", Source: "device-claim", ActorType: "user",
|
||||
ActorPublicID: strPtr(access.userPublicID), RequestID: requestID}); err != nil {
|
||||
return fmt.Errorf("activate claimed device binding: %w", err)
|
||||
}
|
||||
var runtime string
|
||||
if err := tx.QueryRow(ctx, `SELECT COALESCE(desired_config->>'voice_runtime','legacy_voicechat') FROM device_twin WHERE device_id=$1::uuid`, deviceID).Scan(&runtime); err != nil {
|
||||
return fmt.Errorf("read claimed device runtime: %w", err)
|
||||
}
|
||||
if runtime == "legacy_voicechat" {
|
||||
if _, err := deviceactivation.Apply(ctx, tx, deviceactivation.Input{DeviceID: deviceID, TenantID: access.tenantID,
|
||||
Target: deviceactivation.Provisioning, Reason: "旧版语音运行时平台凭据预配", Source: "platform-provisioning", ActorType: "user",
|
||||
ActorPublicID: strPtr(access.userPublicID), RequestID: requestID}); err != nil {
|
||||
return fmt.Errorf("provision claimed legacy device: %w", err)
|
||||
}
|
||||
if _, err := deviceactivation.Apply(ctx, tx, deviceactivation.Input{DeviceID: deviceID, TenantID: access.tenantID,
|
||||
Target: deviceactivation.Active, Reason: "旧版语音运行时已具备绑定与平台凭据", Source: "platform-provisioning", ActorType: "user",
|
||||
ActorPublicID: strPtr(access.userPublicID), RequestID: requestID}); err != nil {
|
||||
return fmt.Errorf("activate claimed legacy device: %w", err)
|
||||
}
|
||||
}
|
||||
return s.audit.Record(ctx, tx, audit.Event{
|
||||
TenantID: strPtr(access.tenantID), ActorType: "user", ActorPublicID: strPtr(access.userPublicID),
|
||||
Action: "device.claim", ResourceType: "device_binding", ResourceID: strPtr(result.BindingID),
|
||||
|
||||
Reference in New Issue
Block a user