This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"data-terminal-cloud/internal/audit"
|
||||
"data-terminal-cloud/internal/deviceactivation"
|
||||
"data-terminal-cloud/internal/deviceauth"
|
||||
"data-terminal-cloud/internal/devicebinding"
|
||||
"data-terminal-cloud/internal/idempotency"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -48,6 +49,7 @@ type Claim struct {
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
BindingGeneration int64 `json:"binding_generation,omitempty"`
|
||||
ResetID string `json:"reset_id"`
|
||||
DeviceID string `json:"device_id"`
|
||||
ActivationState string `json:"activation_state"`
|
||||
@@ -59,7 +61,7 @@ type Response struct {
|
||||
EndedConversationCount int `json:"ended_conversation_count"`
|
||||
CancelledCommandCount int64 `json:"cancelled_command_count"`
|
||||
ConfigRevision int64 `json:"config_revision"`
|
||||
Claim Claim `json:"claim"`
|
||||
Claim *Claim `json:"claim,omitempty"`
|
||||
LocalActions []string `json:"local_actions"`
|
||||
ResetAt time.Time `json:"reset_at"`
|
||||
}
|
||||
@@ -102,11 +104,40 @@ func (s *Service) Reset(ctx context.Context, identity deviceauth.Identity, idemp
|
||||
return Response{}, false, ErrPendingTranscripts
|
||||
}
|
||||
tenantID := identity.TenantID
|
||||
result, err := s.executor.Execute(ctx, idempotency.Request{
|
||||
if err := devicebinding.RequireUnboundForReset(ctx, s.pool, identity.ID); err != nil {
|
||||
return Response{}, false, err
|
||||
}
|
||||
guard := func(ctx context.Context, tx pgx.Tx, cached *idempotency.Result) error {
|
||||
var mode string
|
||||
var generation, counter int64
|
||||
if err := tx.QueryRow(ctx, `SELECT enrollment_mode,binding_generation,factory_reset_counter FROM device WHERE id=$1::uuid FOR UPDATE`, identity.ID).Scan(&mode, &generation, &counter); err != nil {
|
||||
return err
|
||||
}
|
||||
if mode != "physical_confirm" {
|
||||
return nil
|
||||
}
|
||||
if generation != identity.BindingGeneration {
|
||||
return ErrStateConflict
|
||||
}
|
||||
if err := devicebinding.RequireUnboundForReset(ctx, tx, identity.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if cached != nil {
|
||||
var previous Response
|
||||
if err := json.Unmarshal(cached.Body, &previous); err != nil {
|
||||
return err
|
||||
}
|
||||
if counter != input.ResetCounter || generation != previous.BindingGeneration {
|
||||
return ErrResetCounterConflict
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
result, err := s.executor.ExecuteGuarded(ctx, idempotency.Request{
|
||||
TenantID: &tenantID, ActorKey: "device:" + identity.PublicID,
|
||||
Scope: "POST /device/v3/factory-reset", Key: idempotencyKey,
|
||||
RequestHash: idempotency.Hash(rawRequest), ExpiresAfter: 7 * 24 * time.Hour,
|
||||
}, func(operationContext context.Context, tx pgx.Tx) (idempotency.Result, error) {
|
||||
}, guard, func(operationContext context.Context, tx pgx.Tx) (idempotency.Result, error) {
|
||||
response, resetErr := s.reset(operationContext, tx, identity, requestID, input)
|
||||
if resetErr != nil {
|
||||
return idempotency.Result{}, resetErr
|
||||
@@ -145,6 +176,9 @@ func (s *Service) reset(ctx context.Context, tx pgx.Tx, identity deviceauth.Iden
|
||||
if input.ResetCounter <= currentCounter {
|
||||
return Response{}, ErrResetCounterConflict
|
||||
}
|
||||
if err := devicebinding.RequireUnboundForReset(ctx, tx, identity.ID); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
|
||||
var activeFirmware int
|
||||
if err := tx.QueryRow(ctx, `
|
||||
@@ -233,10 +267,35 @@ func (s *Service) reset(ctx context.Context, tx pgx.Tx, identity deviceauth.Iden
|
||||
return Response{}, fmt.Errorf("record factory reset counter: %w", err)
|
||||
}
|
||||
|
||||
claim, err := issueClaim(ctx, tx, identity, now)
|
||||
if err != nil {
|
||||
var claim *Claim
|
||||
var mode string
|
||||
if err = tx.QueryRow(ctx, `SELECT enrollment_mode FROM device WHERE id=$1::uuid`, identity.ID).Scan(&mode); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
if mode == "legacy" {
|
||||
value, e := issueClaim(ctx, tx, identity, now)
|
||||
if e != nil {
|
||||
return Response{}, e
|
||||
}
|
||||
claim = &value
|
||||
} else {
|
||||
_, err = tx.Exec(ctx, `UPDATE device SET binding_generation=binding_generation+1,onboarding_revision=onboarding_revision+1,onboarding_boot_id=NULL WHERE id=$1::uuid`, identity.ID)
|
||||
if err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
_, err = tx.Exec(ctx, `UPDATE device_setup_session SET closed_at=COALESCE(closed_at,now()),close_reason='FACTORY_RESET' WHERE device_id=$1::uuid`, identity.ID)
|
||||
if err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
_, err = tx.Exec(ctx, `UPDATE device_binding_claim SET status='CANCELLED',completed_at=now() WHERE device_id=$1::uuid AND status='PENDING'`, identity.ID)
|
||||
if err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
_, err = tx.Exec(ctx, `UPDATE onboarding_web_session SET revoked_at=COALESCE(revoked_at,now()) WHERE setup_id IN (SELECT id FROM device_setup_session WHERE device_id=$1::uuid)`, identity.ID)
|
||||
if err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
}
|
||||
response := Response{
|
||||
ResetID: input.ResetID, DeviceID: identity.PublicID, ActivationState: string(deviceactivation.Claimable),
|
||||
ResetCounter: input.ResetCounter, BindingRemoved: bindingRemoved, PreviousBindingID: previousBindingID,
|
||||
@@ -245,6 +304,11 @@ func (s *Service) reset(ctx context.Context, tx pgx.Tx, identity deviceauth.Iden
|
||||
ConfigRevision: configRevision, Claim: claim, ResetAt: now,
|
||||
LocalActions: []string{"clear_volc_credential", "clear_user_context", "clear_session_state", "clear_network_credentials", "reboot_to_claimable"},
|
||||
}
|
||||
if mode == "physical_confirm" {
|
||||
if err = tx.QueryRow(ctx, `SELECT binding_generation FROM device WHERE id=$1::uuid`, identity.ID).Scan(&response.BindingGeneration); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
}
|
||||
actorID := identity.PublicID
|
||||
if err := s.audit.Record(ctx, tx, audit.Event{
|
||||
TenantID: &identity.TenantID, ActorType: "device", ActorPublicID: &actorID,
|
||||
|
||||
Reference in New Issue
Block a user