github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/device_clone_state.go (about) 1 // Copyright 2018 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package libkb 5 6 import ( 7 "fmt" 8 ) 9 10 const ( 11 DefaultCloneTokenValue string = "00000000000000000000000000000000" 12 ) 13 14 type DeviceCloneState struct { 15 Prior string 16 Stage string 17 Clones int 18 } 19 20 type DeviceCloneStateJSONFile struct { 21 *JSONFile 22 } 23 24 type cloneDetectionResponse struct { 25 AppStatusEmbed 26 Token string `json:"token"` 27 Clones int `json:"clones"` 28 } 29 30 func (d DeviceCloneState) IsClone() bool { 31 return d.Clones > 1 32 } 33 34 func UpdateDeviceCloneState(m MetaContext) (before, after int, err error) { 35 d, err := GetDeviceCloneState(m) 36 if err != nil { 37 return 0, 0, err 38 } 39 before = d.Clones 40 41 prior, stage := d.Prior, d.Stage 42 if prior == "" { 43 // first run 44 prior = DefaultCloneTokenValue 45 } 46 if stage == "" { 47 stage, err = RandHexString("", 16) 48 if err != nil { 49 return 0, 0, err 50 } 51 if err = SetDeviceCloneState(m, DeviceCloneState{Prior: prior, Stage: stage, Clones: d.Clones}); err != nil { 52 return 0, 0, err 53 } 54 } 55 56 // POST these tokens to the server 57 arg := APIArg{ 58 Endpoint: "device/clone_detection_token", 59 SessionType: APISessionTypeREQUIRED, 60 Args: HTTPArgs{ 61 "device_id": m.G().ActiveDevice.DeviceID(), 62 "prior": S{Val: prior}, 63 "stage": S{Val: stage}, 64 }, 65 } 66 var res cloneDetectionResponse 67 if err = m.G().API.PostDecode(m, arg, &res); err != nil { 68 return 0, 0, err 69 } 70 71 after = res.Clones 72 err = SetDeviceCloneState(m, DeviceCloneState{Prior: res.Token, Stage: "", Clones: after}) 73 return before, after, err 74 } 75 76 func deviceCloneJSONPaths(m MetaContext) (string, string, string) { 77 deviceID := m.G().ActiveDevice.DeviceID() 78 p := fmt.Sprintf("%s.prior", deviceID) 79 s := fmt.Sprintf("%s.stage", deviceID) 80 c := fmt.Sprintf("%s.clones", deviceID) 81 return p, s, c 82 } 83 84 func GetDeviceCloneState(m MetaContext) (state DeviceCloneState, err error) { 85 reader, err := newDeviceCloneStateReader(m) 86 if err != nil { 87 return state, err 88 } 89 pPath, sPath, cPath := deviceCloneJSONPaths(m) 90 p, _ := reader.GetStringAtPath(pPath) 91 s, _ := reader.GetStringAtPath(sPath) 92 c, _ := reader.GetIntAtPath(cPath) 93 if c < 1 { 94 c = 1 95 } 96 state = DeviceCloneState{Prior: p, Stage: s, Clones: c} 97 m.Debug("GetDeviceCloneState: %+v", state) 98 return state, nil 99 } 100 101 func SetDeviceCloneState(m MetaContext, d DeviceCloneState) error { 102 m.Debug("SetDeviceCloneState: %+v", d) 103 writer, err := newDeviceCloneStateWriter(m) 104 if err != nil { 105 return err 106 } 107 tx, err := writer.BeginTransaction() 108 if err != nil { 109 return err 110 } 111 // From this point on, if there's an error, we abort the 112 // transaction. 113 defer func() { 114 if tx != nil { 115 err := tx.Rollback() 116 if err != nil { 117 m.Debug("SetDeviceCloneState: rollback error: %+v", err) 118 } 119 err = tx.Abort() 120 if err != nil { 121 m.Debug("SetDeviceCloneState: abort error: %+v", err) 122 } 123 } 124 }() 125 126 pPath, sPath, cPath := deviceCloneJSONPaths(m) 127 if err = writer.SetStringAtPath(pPath, d.Prior); err != nil { 128 return err 129 } 130 if err = writer.SetStringAtPath(sPath, d.Stage); err != nil { 131 return err 132 } 133 if err = writer.SetIntAtPath(cPath, d.Clones); err != nil { 134 return err 135 } 136 if err = tx.Commit(); err != nil { 137 return err 138 } 139 140 // Zero out the TX so that we don't abort it in the defer() 141 tx = nil 142 return nil 143 } 144 145 func newDeviceCloneStateReader(m MetaContext) (*DeviceCloneStateJSONFile, error) { 146 f := DeviceCloneStateJSONFile{NewJSONFile(m.G(), m.G().Env.GetDeviceCloneStateFilename(), "device clone state")} 147 err := f.Load(false) 148 return &f, err 149 } 150 151 func newDeviceCloneStateWriter(m MetaContext) (*DeviceCloneStateJSONFile, error) { 152 f := DeviceCloneStateJSONFile{NewJSONFile(m.G(), m.G().Env.GetDeviceCloneStateFilename(), "device clone state")} 153 err := f.Load(false) 154 return &f, err 155 }