github.com/decred/politeia@v1.4.0/politeiawww/legacy/user/user.go (about) 1 // Copyright (c) 2017 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package user 6 7 import ( 8 "bytes" 9 "encoding/hex" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "time" 14 15 "github.com/decred/politeia/politeiad/api/v1/identity" 16 "github.com/google/uuid" 17 ) 18 19 var ( 20 // ErrSessionNotFound indicates that a user session was not found 21 // in the database. 22 ErrSessionNotFound = errors.New("no user session found") 23 24 // ErrUserNotFound indicates that a user name was not found in the 25 // database. 26 ErrUserNotFound = errors.New("user not found") 27 28 // ErrUserExists indicates that a user already exists in the 29 // database. 30 ErrUserExists = errors.New("user already exists") 31 32 // ErrShutdown is emitted when the database is shutting down. 33 ErrShutdown = errors.New("database is shutting down") 34 35 // ErrInvalidPlugin is emitted when a invalid plugin is used. 36 ErrInvalidPlugin = errors.New("invalid plugin") 37 38 // ErrInvalidPluginCmd is emitted when an invalid plugin command 39 // is used. 40 ErrInvalidPluginCmd = errors.New("invalid plugin command") 41 42 // ErrCodeStatsNotFound indicates that an requested code stats entry wasn't 43 // found. 44 ErrCodeStatsNotFound = errors.New("code stats not found") 45 ) 46 47 // Identity wraps an ed25519 public key and timestamps to indicate if it is 48 // active. An identity can be in one of three states: inactive, active, or 49 // deactivated. 50 // 51 // inactive: Activated == 0 && Deactivated == 0 52 // The identity has been created, but has not yet been activated. 53 // 54 // active: Activated != 0 && Deactivated == 0 55 // The identity has been created and has been activated. 56 // 57 // deactivated: Deactivated != 0 58 // The identity in no longer active and the key is no longer valid. Both 59 // inactive and active identities can be marked as deactivated. An inactive 60 // identity being deactivated means that the identity was never verified before 61 // a newer identity was created. An active identity being deactivated means 62 // that a newer identity was created to replace the active identity. 63 type Identity struct { 64 Key [identity.PublicKeySize]byte `json:"key"` // ed25519 public key 65 Activated int64 `json:"activated"` // Time key as activated for use 66 Deactivated int64 `json:"deactivated"` // Time key was deactivated 67 } 68 69 // Activate activates the identity by setting the activated timestamp. 70 func (i *Identity) Activate() { 71 i.Activated = time.Now().Unix() 72 } 73 74 // Deactivate deactivates the identity by setting the deactivated timestamp. 75 func (i *Identity) Deactivate() { 76 i.Deactivated = time.Now().Unix() 77 } 78 79 // IsInactive returns whether the identity is inactive. See the Identity 80 // definition for more info on inactive identities. 81 func (i *Identity) IsInactive() bool { 82 return i.Activated == 0 && i.Deactivated == 0 83 } 84 85 // IsActive returns whether the identity is active. See the Identity definition 86 // for more info on active identities. 87 func (i *Identity) IsActive() bool { 88 return i.Activated != 0 && i.Deactivated == 0 89 } 90 91 // IsDeactivated returns whether the identity has been deactivated. See the 92 // Identity definition for more info on deactivated identities. 93 func (i *Identity) IsDeactivated() bool { 94 return i.Deactivated != 0 95 } 96 97 // Status returns whether the identity is inactive, active, or deactivated. 98 func (i *Identity) Status() string { 99 switch { 100 case i.IsInactive(): 101 return "inactive" 102 case i.IsActive(): 103 return "active" 104 case i.IsDeactivated(): 105 return "deactivated" 106 } 107 return "invalid" 108 } 109 110 // String returns a hex encoded string of the identity key. 111 func (i *Identity) String() string { 112 return hex.EncodeToString(i.Key[:]) 113 } 114 115 // NewIdentity returns a new inactive identity that was created using the 116 // provided public key. 117 func NewIdentity(publicKey string) (*Identity, error) { 118 b, err := hex.DecodeString(publicKey) 119 if err != nil { 120 return nil, err 121 } 122 123 var empty [identity.PublicKeySize]byte 124 switch { 125 case len(b) != len(empty): 126 return nil, fmt.Errorf("invalid length") 127 case bytes.Equal(b, empty[:]): 128 return nil, fmt.Errorf("empty bytes") 129 } 130 131 id := Identity{} 132 copy(id.Key[:], b) 133 return &id, nil 134 } 135 136 // A proposal paywall allows the user to purchase proposal credits. Proposal 137 // paywalls are only valid for one tx. The number of proposal credits created 138 // is determined by dividing the tx amount by the credit price. Proposal 139 // paywalls expire after a set duration. politeiawww polls the paywall address 140 // for a payment tx until the paywall is either paid or it expires. 141 type ProposalPaywall struct { 142 ID uint64 `json:"id"` // Paywall ID 143 CreditPrice uint64 `json:"creditprice"` // Cost per proposal credit in atoms 144 Address string `json:"address"` // Paywall address 145 TxNotBefore int64 `json:"txnotbefore"` // Unix timestamp of minimum timestamp for paywall tx 146 PollExpiry int64 `json:"pollexpiry"` // Unix timestamp of expiration time of paywall polling 147 TxID string `json:"txid"` // Payment transaction ID 148 TxAmount uint64 `json:"txamount"` // Amount sent to paywall address in atoms 149 NumCredits uint64 `json:"numcredits"` // Number of proposal credits created by payment tx 150 } 151 152 // A proposal credit allows the user to submit a new proposal. Credits are 153 // created when a user sends a payment to a proposal paywall. A credit is 154 // automatically spent when a user submits a new proposal. When a credit is 155 // spent, it is updated with the proposal's censorship token and moved to the 156 // user's spent proposal credits list. 157 type ProposalCredit struct { 158 PaywallID uint64 `json:"paywallid"` // Proposal paywall ID of associated paywall 159 Price uint64 `json:"price"` // Credit price in atoms 160 DatePurchased int64 `json:"datepurchased"` // Unix timestamp of credit purchase 161 TxID string `json:"txid"` // Payment transaction ID 162 CensorshipToken string `json:"censorshiptoken"` // Token of proposal that spent this credit 163 } 164 165 // VersionUser is the version of the User struct. 166 const VersionUser uint32 = 1 167 168 // User represents a politeiawww user. 169 type User struct { 170 ID uuid.UUID `json:"id"` // Unique user uuid 171 Email string `json:"email"` // Email address 172 Username string `json:"username"` // Unique username 173 HashedPassword []byte `json:"hashedpassword"` // Blowfish hash 174 Admin bool `json:"admin"` // Is user an admin 175 EmailNotifications uint64 `json:"emailnotifications"` // Email notification setting 176 LastLoginTime int64 `json:"lastlogintime"` // Unix timestamp of last login 177 FailedLoginAttempts uint64 `json:"failedloginattempts"` // Sequential failed login attempts 178 Deactivated bool `json:"deactivated"` // Is account deactivated 179 180 // Verification tokens and their expirations 181 NewUserVerificationToken []byte `json:"newuserverificationtoken"` 182 NewUserVerificationExpiry int64 `json:"newuserverificationtokenexiry"` 183 ResendNewUserVerificationExpiry int64 `json:"resendnewuserverificationtoken"` 184 UpdateKeyVerificationToken []byte `json:"updatekeyverificationtoken"` 185 UpdateKeyVerificationExpiry int64 `json:"updatekeyverificationexpiry"` 186 ResetPasswordVerificationToken []byte `json:"resetpasswordverificationtoken"` 187 ResetPasswordVerificationExpiry int64 `json:"resetpasswordverificationexpiry"` 188 189 // PaywallAddressIndex is the index that is used to generate the 190 // paywall address for the user. The same paywall address is used 191 // for the user registration paywall and for proposal credit 192 // paywalls. The index is set during the new user record creation 193 // and is sequential. 194 // XXX why is this an uint64 when hdkeychain requires a uint32? 195 PaywallAddressIndex uint64 `json:"paywalladdressindex"` 196 197 // User registration paywall info 198 NewUserPaywallAddress string `json:"newuserpaywalladdress"` 199 NewUserPaywallAmount uint64 `json:"newuserpaywallamount"` 200 NewUserPaywallTx string `json:"newuserpaywalltx"` 201 202 // NewUserPaywallTxNotBeore is the minimum UNIX time (in seconds) 203 // required for the block containing the transaction sent to 204 // NewUserPaywallAddress. If the user has already paid, this field 205 // will be empty. 206 NewUserPaywallTxNotBefore int64 `json:"newuserpaywalltxnotbefore"` 207 208 // The UNIX time (in seconds) for when the server will stop polling 209 // the server for transactions at NewUserPaywallAddress. If the 210 // user has already paid, this field will be empty. 211 NewUserPaywallPollExpiry int64 `json:"newuserpaywallpollexpiry"` 212 213 // User access times for proposal comments. The access time is a 214 // Unix timestamp of the last time the user accessed a proposal's 215 // comments. 216 // [token]accessTime 217 ProposalCommentsAccessTimes map[string]int64 `json:"proposalcommentsaccesstime"` 218 219 // All identities the user has ever used. We allow the user to change 220 // identities to deal with key loss. An identity can be in one of three 221 // states: inactive, active, or deactivated. 222 // 223 // Inactive identities 224 // An identity is consider inactive until it has been verified. 225 // An unverified user will have an inactive identity. 226 // A user will only ever have one inactive identity at a time. 227 // 228 // Active identities 229 // A verified user will always have one active identity. 230 // A verified user may have both an active and inactive identity if 231 // they have requested a new identity but have not yet verified it. 232 // 233 // Deactivated identities 234 // An identity is deactivated when it is replaced by a new identity. 235 // The key of a deactivated identity is no longer valid. 236 // An identity cannot be re-activated once it has been deactivated. 237 Identities []Identity `json:"identities"` 238 239 // All proposal paywalls that have been issued to the user in chronological 240 // order. 241 ProposalPaywalls []ProposalPaywall `json:"proposalpaywalls"` 242 243 // All proposal credits that have been purchased by the user, but have not 244 // yet been used to submit a proposal. Once a credit is used to submit a 245 // proposal, it is updated with the proposal's censorship token and moved to 246 // the user's spent proposal credits list. The price that the proposal 247 // credit was purchased at is in atoms. 248 UnspentProposalCredits []ProposalCredit `json:"unspentproposalcredits"` 249 250 // All credits that have been purchased by the user and have already been 251 // used to submit proposals. Spent credits have a proposal censorship token 252 // associated with them to signify that they have been spent. The price that 253 // the proposal credit was purchased at is in atoms. 254 SpentProposalCredits []ProposalCredit `json:"spentproposalcredits"` 255 256 // TOTP Secret Key and type of TOTP being used. 257 TOTPSecret string `json:"totpsecret"` 258 TOTPType int `json:"totptype"` 259 TOTPVerified bool `json:"totpverified"` // whether current totp secret has been verified with 260 TOTPLastUpdated []int64 `json:"totplastupdated"` 261 TOTPLastFailedCodeTime []int64 `json:"totplastfailedcodetime"` 262 } 263 264 // ActiveIdentity returns the active identity for the user if one exists. 265 func (u *User) ActiveIdentity() *Identity { 266 for k, v := range u.Identities { 267 if v.IsActive() { 268 return &u.Identities[k] 269 } 270 } 271 return nil 272 } 273 274 // InactiveIdentity returns the inactive identity for the user if one exists. 275 func (u *User) InactiveIdentity() *Identity { 276 for k, v := range u.Identities { 277 if v.IsInactive() { 278 return &u.Identities[k] 279 } 280 } 281 return nil 282 } 283 284 // PublicKey returns a hex encoded string of the user's active identity. 285 func (u *User) PublicKey() string { 286 if u.ActiveIdentity() != nil { 287 return u.ActiveIdentity().String() 288 } 289 return "" 290 } 291 292 // AddIdentity adds the provided inactive identity to the identities array for 293 // the user. Any existing inactive identities are deactivated. A user should 294 // only ever have one inactive identity at a time, but due to a prior bug, this 295 // may not always be the case. 296 func (u *User) AddIdentity(id Identity) error { 297 if u.Identities == nil { 298 u.Identities = make([]Identity, 0) 299 } 300 301 // Validate provided identity 302 for _, v := range u.Identities { 303 if bytes.Equal(v.Key[:], id.Key[:]) { 304 if v.IsInactive() { 305 // Inactive identity has already been 306 // added. This is ok. 307 return nil 308 } 309 return fmt.Errorf("duplicate key") 310 } 311 } 312 switch { 313 case id.Deactivated != 0: 314 return fmt.Errorf("identity is deactivated") 315 case id.Activated != 0: 316 return fmt.Errorf("identity is activated") 317 } 318 319 // Deactivate any existing inactive identities 320 for k, v := range u.Identities { 321 if v.IsInactive() { 322 u.Identities[k].Deactivate() 323 } 324 } 325 326 // Add new inactive identity 327 u.Identities = append(u.Identities, id) 328 329 return nil 330 } 331 332 // ActivateIdentity sets the identity associated with the provided key as the 333 // active identity for the user. The provided key must correspond to an 334 // inactive identity. If there is an existing active identity, it wil be 335 // deactivated. 336 func (u *User) ActivateIdentity(key []byte) error { 337 if u.Identities == nil { 338 return fmt.Errorf("identity not found") 339 } 340 341 // Ensure provided key exists and is inactive 342 var inactive *Identity 343 for k, v := range u.Identities { 344 if bytes.Equal(v.Key[:], key[:]) { 345 inactive = &u.Identities[k] 346 break 347 } 348 } 349 switch { 350 case inactive == nil: 351 return fmt.Errorf("identity not found") 352 case inactive.Deactivated != 0: 353 return fmt.Errorf("identity is deactivated") 354 case inactive.Activated != 0: 355 return fmt.Errorf("identity is activated") 356 } 357 358 // Deactivate any active identities. There should only ever be a 359 // single active identity at a time, but due to a prior bug in the 360 // early version of politeia, this may not hold true. Check all 361 // identities just to be sure. 362 for k, v := range u.Identities { 363 // Skip the inactive identity that is going 364 // to be the new active identity. 365 if inactive.String() == v.String() { 366 continue 367 } 368 369 if v.Deactivated == 0 { 370 u.Identities[k].Deactivate() 371 } 372 } 373 374 // Update the inactive identity to be active. 375 inactive.Activate() 376 377 return nil 378 } 379 380 // NotificationIsEnabled returns whether the user has the provided notification 381 // bit enabled. This function will always return false if the user has been 382 // deactivated. 383 func (u *User) NotificationIsEnabled(ntfnBit uint64) bool { 384 if u.Deactivated { 385 return false 386 } 387 return u.EmailNotifications&ntfnBit != 0 388 } 389 390 // EncodeUser encodes User into a JSON byte slice. 391 func EncodeUser(u User) ([]byte, error) { 392 b, err := json.Marshal(u) 393 if err != nil { 394 return nil, err 395 } 396 397 return b, nil 398 } 399 400 // DecodeUser decodes a JSON byte slice into a User. 401 func DecodeUser(payload []byte) (*User, error) { 402 var u User 403 404 err := json.Unmarshal(payload, &u) 405 if err != nil { 406 return nil, err 407 } 408 409 return &u, nil 410 } 411 412 // PluginCommand is used to execute a plugin command. 413 type PluginCommand struct { 414 ID string // Plugin identifier 415 Command string // Command identifier 416 Payload string // Command payload 417 } 418 419 // PluginCommandReply is used to reply to a PluginCommand. 420 type PluginCommandReply struct { 421 ID string // Plugin identifier 422 Command string // Command identifier 423 Payload string // Command reply payload 424 } 425 426 // PluginSetting holds the key/value pair of a plugin setting. 427 type PluginSetting struct { 428 Key string // Name of setting 429 Value string // Value of setting 430 } 431 432 // Plugin describes a plugin and its settings. 433 type Plugin struct { 434 ID string 435 Version string 436 Settings []PluginSetting 437 } 438 439 // Session represents a user session. 440 // 441 // ID is the decoded session ID. The ID present in the session cookie is the 442 // encoded session ID. The encoding/decoding is handled by the session Store. 443 // 444 // Values are politeiawww specific encoded session values. The encoding is 445 // handled by the session Store. 446 // 447 // UserID and CreatedAt are included in the encoded Values but have also been 448 // broken out into their own fields so that they can be queryable. UserID 449 // allows for lookups by UserID and CreatedAt allows for periodically cleaning 450 // up expired sessions in the database. 451 type Session struct { 452 ID string `json:"id"` // Unique session ID 453 UserID uuid.UUID `json:"userid"` // User UUID 454 CreatedAt int64 `json:"createdat"` // Created at UNIX timestamp 455 Values string `json:"values"` // Encoded session values 456 } 457 458 // VersionSession is the version of the Session struct. 459 const VersionSession uint32 = 1 460 461 // EncodeSession encodes Session into a JSON byte slice. 462 func EncodeSession(s Session) ([]byte, error) { 463 b, err := json.Marshal(s) 464 if err != nil { 465 return nil, err 466 } 467 468 return b, nil 469 } 470 471 // DecodeSession decodes a JSON byte slice into a Session. 472 func DecodeSession(payload []byte) (*Session, error) { 473 var s Session 474 475 err := json.Unmarshal(payload, &s) 476 if err != nil { 477 return nil, err 478 } 479 480 return &s, nil 481 } 482 483 // Database describes the interface used for interacting with the user 484 // database. 485 type Database interface { 486 // Add a new user 487 UserNew(User) error 488 489 // Update an existing user 490 UserUpdate(User) error 491 492 // Return user record given the username 493 UserGetByUsername(string) (*User, error) 494 495 // Return user record given its id 496 UserGetById(uuid.UUID) (*User, error) 497 498 // Return user record given a public key 499 UserGetByPubKey(string) (*User, error) 500 501 // Return a map of public key to user record 502 UsersGetByPubKey(pubKeys []string) (map[string]User, error) 503 504 // Insert a user to the database. 505 // Intended to be used for migration between databases. 506 InsertUser(User) error 507 508 // Iterate over all users 509 AllUsers(callbackFn func(u *User)) error 510 511 // Create or update a user session 512 SessionSave(Session) error 513 514 // Return a user session given its id 515 SessionGetByID(sessionID string) (*Session, error) 516 517 // Delete a user session given its id 518 SessionDeleteByID(sessionID string) error 519 520 // Delete all sessions for a user except for the given session IDs 521 SessionsDeleteByUserID(id uuid.UUID, exemptSessionIDs []string) error 522 523 // SetPaywallAddressIndex updates the paywall address index. 524 SetPaywallAddressIndex(index uint64) error 525 526 // Rotate encryption keys 527 RotateKeys(newKeyPath string) error 528 529 // Register a plugin 530 RegisterPlugin(Plugin) error 531 532 // Execute a plugin command 533 PluginExec(PluginCommand) (*PluginCommandReply, error) 534 535 // Close performs cleanup of the backend. 536 Close() error 537 }