-
Notifications
You must be signed in to change notification settings - Fork 4
/
profile.go
251 lines (219 loc) · 6.56 KB
/
profile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
// go-coronanet - Coronavirus social distancing network
// Copyright (c) 2020 Péter Szilágyi. All rights reserved.
package coronanet
import (
"encoding/json"
"errors"
"github.com/coronanet/go-coronanet/protocols/corona"
"github.com/coronanet/go-coronanet/tornet"
"github.com/syndtr/goleveldb/leveldb/util"
)
var (
// dbProfileKey is the database key for storing the local user's profile.
dbProfileKey = []byte("profile")
// ErrProfileNotFound is returned if the profile is attempted to be read from
// the database but it does not exist.
ErrProfileNotFound = errors.New("profile not found")
// ErrProfileExists is returned if a new profile is attempted to be created
// but an old one already exists.
ErrProfileExists = errors.New("profile already exists")
)
// profile represents a local user's profile information, both public and private.
type profile struct {
KeyRing *tornet.SecretKeyRing `json:"keyring"`
Name string `json:"name`
Avatar [32]byte `json:"avatar"`
}
// CreateProfile generates a new cryptographic identity for the local user and
// injects it into the system.
func (b *Backend) CreateProfile() error {
b.logger.Debug("Profile creation requested")
b.lock.Lock()
defer b.lock.Unlock()
// Make sure there's no already existing user
if _, err := b.Profile(); err == nil {
return ErrProfileExists
}
// Generate a new profile and upload it
b.logger.Info("Creating new local profile")
keyring, err := tornet.GenerateKeyRing()
if err != nil {
return err
}
blob, err := json.Marshal(&profile{KeyRing: &keyring})
if err != nil {
return err
}
if err := b.database.Put(dbProfileKey, blob, nil); err != nil {
return err
}
return b.initOverlay(keyring)
}
// DeleteProfile wipes the entire database of everything. It's unforgiving, no
// backups, no restore, the data is gone!
func (b *Backend) DeleteProfile() error {
b.logger.Info("Deleting profile")
b.lock.Lock()
defer b.lock.Unlock()
// If the overlay is initialized by any chance, tear it down
if err := b.nukeOverlay(); err != nil {
return err
}
// Independent of what's in the database, nuke everything
it := b.database.NewIterator(&util.Range{nil, nil}, nil)
for it.Next() {
b.database.Delete(it.Key(), nil)
}
it.Release()
return b.database.CompactRange(util.Range{nil, nil})
}
// Profile retrieves the current user's profile infos.
func (b *Backend) Profile() (*profile, error) {
blob, err := b.database.Get(dbProfileKey, nil)
if err != nil {
return nil, ErrProfileNotFound
}
prof := new(profile)
if err := json.Unmarshal(blob, prof); err != nil {
return nil, err
}
return prof, nil
}
// updateKeyring is a callback method for the tornet Node to notify us whenever
// the cryptographic keyring was modified to serialize it to disk.
//
// This method internally will panic on any error. The reason is that there is
// nothing that could fail, yet if something does, game over.
func (b *Backend) updateKeyring(keyring tornet.SecretKeyRing) {
// This goroutine is super funky. The reason we have it is because the keyring
// update can be triggered both async from tornet.Node, as well as sync from a
// contact addition/removal.The latter already holds the write lock whereas the
// former does not. TODO(karalabe): Would be nice to fix this.
go func() {
b.logger.Info("Updating tornet keyring", "addresses", len(keyring.Addresses), "contacts", len(keyring.Trusted))
b.lock.Lock()
defer b.lock.Unlock()
prof, err := b.Profile()
if err != nil {
panic("keyring update without profile")
}
prof.KeyRing = &keyring
blob, err := json.Marshal(prof)
if err != nil {
panic(err)
}
if err := b.database.Put(dbProfileKey, blob, nil); err != nil {
panic(err)
}
// The keyring was updated, ping the scheduler to dial accordingly
b.dialer.reinit(keyring)
}()
}
// UpdateProfile changes the profile information of an existing local user.
func (b *Backend) UpdateProfile(name string) error {
b.logger.Debug("Profile update requested", "name", name)
b.lock.Lock()
defer b.lock.Unlock()
// Retrieve the current profile and abort if the update is a noop
prof, err := b.Profile()
if err != nil {
return err
}
if prof.Name == name {
b.logger.Debug("Skipping noop profile update")
return nil
}
// Name changed, update and serialize back to disk
b.logger.Info("Updating local profile name", "old", prof.Name, "new", name)
prof.Name = name
blob, err := json.Marshal(prof)
if err != nil {
return err
}
if err := b.database.Put(dbProfileKey, blob, nil); err != nil {
return err
}
// Propagate the update to all our contacts
b.broadcast(&corona.Envelope{
Profile: &corona.Profile{
Name: prof.Name,
Avatar: prof.Avatar,
},
}, schedulerProfileUpdate)
return nil
}
// UploadProfilePicture uploads a new profile picture for the user.
func (b *Backend) UploadProfilePicture(data []byte) error {
b.logger.Info("Uploading profile picture")
b.lock.Lock()
defer b.lock.Unlock()
// Retrieve the current profile to ensure the user exists
prof, err := b.Profile()
if err != nil {
return err
}
// Upload the image into the CDN and delete the old one
hash, err := b.uploadCDNImage(data)
if err != nil {
return err
}
if prof.Avatar != ([32]byte{}) {
if err := b.deleteCDNImage(prof.Avatar); err != nil {
return err
}
}
// If the hash changed, update the profile
if prof.Avatar == hash {
return nil
}
prof.Avatar = hash
blob, err := json.Marshal(prof)
if err != nil {
return err
}
if err := b.database.Put(dbProfileKey, blob, nil); err != nil {
return err
}
// Propagate the update to all our contacts
b.broadcast(&corona.Envelope{
Profile: &corona.Profile{
Name: prof.Name,
Avatar: prof.Avatar,
},
}, schedulerProfileUpdate)
return nil
}
// DeleteProfilePicture deletes the existing profile picture of the user.
func (b *Backend) DeleteProfilePicture() error {
b.logger.Info("Deleting profile picture")
b.lock.Lock()
defer b.lock.Unlock()
// Retrieve the current profile to ensure the user exists
prof, err := b.Profile()
if err != nil {
return err
}
if prof.Avatar == [32]byte{} {
return nil
}
// Profile picture exists, delete it from the CDN and update the profile
if err := b.deleteCDNImage(prof.Avatar); err != nil {
return err
}
prof.Avatar = [32]byte{}
blob, err := json.Marshal(prof)
if err != nil {
return err
}
if err := b.database.Put(dbProfileKey, blob, nil); err != nil {
return err
}
// Propagate the update to all our contacts
b.broadcast(&corona.Envelope{
Profile: &corona.Profile{
Name: prof.Name,
Avatar: prof.Avatar,
},
}, schedulerProfileUpdate)
return nil
}