2021-12-25 02:16:58 +01:00
package store
2022-06-07 19:25:14 +02:00
// TODO - DeviceId - What about clients that lie about deviceId? Maybe require a certain format to make sure it gives a real value? Something it wouldn't come up with by accident.
2021-12-25 02:16:58 +01:00
import (
"database/sql"
"errors"
"fmt"
"log"
2022-06-27 21:59:56 +02:00
"time"
"github.com/mattn/go-sqlite3"
2022-07-12 04:10:19 +02:00
"lbryio/lbry-id/auth"
"lbryio/lbry-id/wallet"
2021-12-25 02:16:58 +01:00
)
var (
2022-07-26 22:36:57 +02:00
ErrDuplicateToken = fmt . Errorf ( "Token already exists for this user and device" )
ErrNoTokenForUserDevice = fmt . Errorf ( "Token does not exist for this user and device" )
ErrNoTokenForUser = fmt . Errorf ( "Token does not exist for this user" )
2021-12-25 02:16:58 +01:00
2022-06-09 23:04:49 +02:00
ErrDuplicateWallet = fmt . Errorf ( "Wallet already exists for this user" )
2022-07-01 00:44:59 +02:00
2022-07-11 16:41:18 +02:00
ErrNoWallet = fmt . Errorf ( "Wallet does not exist for this user" )
2021-12-25 02:16:58 +01:00
2022-07-05 23:12:14 +02:00
ErrUnexpectedWallet = fmt . Errorf ( "Wallet unexpectedly exist for this user" )
ErrWrongSequence = fmt . Errorf ( "Wallet could not be updated to this sequence" )
2022-06-23 21:22:31 +02:00
2021-12-25 02:16:58 +01:00
ErrDuplicateEmail = fmt . Errorf ( "Email already exists for this user" )
ErrDuplicateAccount = fmt . Errorf ( "User already has an account" )
2022-07-29 21:52:23 +02:00
ErrWrongCredentials = fmt . Errorf ( "No match for email and/or password" )
2022-07-26 17:18:43 +02:00
ErrNotVerified = fmt . Errorf ( "User account is not verified" )
2021-12-25 02:16:58 +01:00
)
2022-07-31 18:26:03 +02:00
const (
AuthTokenLifespan = time . Hour * 24 * 14
VerifyTokenLifespan = time . Hour * 24 * 2
)
2021-12-25 02:16:58 +01:00
// For test stubs
type StoreInterface interface {
SaveToken ( * auth . AuthToken ) error
2022-07-28 01:45:09 +02:00
GetToken ( auth . AuthTokenString ) ( * auth . AuthToken , error )
2022-06-23 21:22:31 +02:00
SetWallet ( auth . UserId , wallet . EncryptedWallet , wallet . Sequence , wallet . WalletHmac ) error
2022-06-09 23:04:49 +02:00
GetWallet ( auth . UserId ) ( wallet . EncryptedWallet , wallet . Sequence , wallet . WalletHmac , error )
2022-06-07 19:25:14 +02:00
GetUserId ( auth . Email , auth . Password ) ( auth . UserId , error )
2022-07-28 01:45:09 +02:00
CreateAccount ( auth . Email , auth . Password , auth . ClientSaltSeed , auth . VerifyTokenString ) error
2022-07-30 02:49:00 +02:00
UpdateVerifyTokenString ( auth . Email , auth . VerifyTokenString ) error
2022-07-26 22:36:57 +02:00
VerifyAccount ( auth . VerifyTokenString ) error
2022-07-15 21:36:11 +02:00
ChangePasswordWithWallet ( auth . Email , auth . Password , auth . Password , auth . ClientSaltSeed , wallet . EncryptedWallet , wallet . Sequence , wallet . WalletHmac ) error
ChangePasswordNoWallet ( auth . Email , auth . Password , auth . Password , auth . ClientSaltSeed ) error
GetClientSaltSeed ( auth . Email ) ( auth . ClientSaltSeed , error )
2021-12-25 02:16:58 +01:00
}
type Store struct {
db * sql . DB
}
func ( s * Store ) Init ( fileName string ) {
2022-06-29 06:06:43 +02:00
db , err := sql . Open ( "sqlite3" , "file:" + fileName + "?_foreign_keys=on" )
2021-12-25 02:16:58 +01:00
if err != nil {
log . Fatal ( err )
}
s . db = db
}
func ( s * Store ) Migrate ( ) error {
2022-06-09 23:04:49 +02:00
// We use the `sequence` field for transaction safety. For instance, let's
// say two different clients are trying to update the sequence from 5 to 6.
// The update command will specify "WHERE sequence=5". Only one of these
// commands will succeed, and the other will get back an error.
2021-12-25 02:16:58 +01:00
2022-06-07 19:25:14 +02:00
// We use AUTOINCREMENT against the protestations of people on the Internet
// who claim that INTEGER PRIMARY KEY automatically has autoincrment, and
// that using it when it's not "strictly needed" uses extra resources. But
// without AUTOINCREMENT, it might reuse primary keys if a row is deleted and
// re-added. Who wants that risk? Besides, we'll switch to Postgres when it's
// time to scale anyway.
// We use UNIQUE on auth_tokens.token so that we can retrieve it easily and
// identify the user (and I suppose the uniqueness provides a little extra
// security in case we screw up the random generator). However the primary
// key should still be (user_id, device_id) so that a device's row can be
// updated with a new token.
2021-12-25 02:16:58 +01:00
query := `
CREATE TABLE IF NOT EXISTS auth_tokens (
2022-06-07 19:25:14 +02:00
token TEXT NOT NULL UNIQUE ,
user_id INTEGER NOT NULL ,
2021-12-25 02:16:58 +01:00
device_id TEXT NOT NULL ,
scope TEXT NOT NULL ,
expiration DATETIME NOT NULL ,
2022-06-29 00:43:43 +02:00
CHECK (
-- should eventually fail for foreign key constraint instead
2022-06-29 06:06:43 +02:00
device_id < > ' ' AND
2022-06-29 00:43:43 +02:00
token < > ' ' AND
scope < > ' ' AND
-- Don ' t know when it uses either format to denote UTC
expiration < > "0001-01-01 00:00:00+00:00" AND
expiration < > "0001-01-01 00:00:00Z"
) ,
2022-06-07 19:25:14 +02:00
PRIMARY KEY ( user_id , device_id )
2022-06-29 06:06:43 +02:00
FOREIGN KEY ( user_id ) REFERENCES accounts ( user_id )
2021-12-25 02:16:58 +01:00
) ;
2022-06-09 23:04:49 +02:00
CREATE TABLE IF NOT EXISTS wallets (
2022-06-07 19:25:14 +02:00
user_id INTEGER NOT NULL ,
2022-06-09 23:04:49 +02:00
encrypted_wallet TEXT NOT NULL ,
2021-12-25 02:16:58 +01:00
sequence INTEGER NOT NULL ,
2022-06-07 19:25:14 +02:00
hmac TEXT NOT NULL ,
PRIMARY KEY ( user_id )
FOREIGN KEY ( user_id ) REFERENCES accounts ( user_id )
2022-06-29 06:06:43 +02:00
CHECK (
encrypted_wallet < > ' ' AND
hmac < > ' ' AND
sequence < > 0
)
2021-12-25 02:16:58 +01:00
) ;
CREATE TABLE IF NOT EXISTS accounts (
2022-07-22 03:19:24 +02:00
normalized_email TEXT NOT NULL UNIQUE ,
email TEXT NOT NULL ,
2022-07-13 18:32:48 +02:00
key TEXT NOT NULL ,
2022-07-15 21:36:11 +02:00
client_salt_seed TEXT NOT NULL ,
server_salt TEXT NOT NULL ,
2022-07-31 18:42:03 +02:00
verify_token TEXT NOT NULL UNIQUE , -- will query by token when verifying
2022-07-30 20:24:33 +02:00
verify_expiration DATETIME ,
2022-06-07 19:25:14 +02:00
user_id INTEGER PRIMARY KEY AUTOINCREMENT ,
2022-06-29 06:06:43 +02:00
CHECK (
email < > ' ' AND
2022-07-22 03:19:24 +02:00
normalized_email < > ' ' AND
2022-07-13 18:32:48 +02:00
key < > ' ' AND
2022-07-15 21:36:11 +02:00
client_salt_seed < > ' ' AND
server_salt < > ' '
2022-06-29 06:06:43 +02:00
)
2021-12-25 02:16:58 +01:00
) ;
`
_ , err := s . db . Exec ( query )
return err
}
////////////////
// Auth Token //
////////////////
2022-06-07 19:25:14 +02:00
// TODO - Is it safe to assume that the owner of the token is legit, and is
// coming from the legit device id? No need to query by userId and deviceId
// (which I did previously)?
//
// TODO Put the timestamp in the token to avoid duplicates over time. And/or just use a library! Someone solved this already.
2022-07-31 19:54:40 +02:00
// Assumption: User is verified (as it was necessary to call SaveToken to begin
// with)
2022-07-28 01:45:09 +02:00
func ( s * Store ) GetToken ( token auth . AuthTokenString ) ( authToken * auth . AuthToken , err error ) {
2021-12-25 02:16:58 +01:00
expirationCutoff := time . Now ( ) . UTC ( )
2022-07-06 21:15:17 +02:00
authToken = & ( auth . AuthToken { } )
err = s . db . QueryRow (
"SELECT token, user_id, device_id, scope, expiration FROM auth_tokens WHERE token=? AND expiration>?" , token , expirationCutoff ,
) . Scan (
& authToken . Token ,
& authToken . UserId ,
& authToken . DeviceId ,
& authToken . Scope ,
& authToken . Expiration ,
2021-12-25 02:16:58 +01:00
)
2022-07-06 21:15:17 +02:00
if err == sql . ErrNoRows {
2022-07-26 22:36:57 +02:00
err = ErrNoTokenForUserDevice
2021-12-25 02:16:58 +01:00
}
2022-07-06 21:15:17 +02:00
if err != nil {
authToken = nil
2021-12-25 02:16:58 +01:00
}
2022-07-06 21:15:17 +02:00
return
2021-12-25 02:16:58 +01:00
}
func ( s * Store ) insertToken ( authToken * auth . AuthToken , expiration time . Time ) ( err error ) {
_ , err = s . db . Exec (
2022-07-01 00:44:59 +02:00
"INSERT INTO auth_tokens (token, user_id, device_id, scope, expiration) VALUES(?,?,?,?,?)" ,
2022-06-07 19:25:14 +02:00
authToken . Token , authToken . UserId , authToken . DeviceId , authToken . Scope , expiration ,
2021-12-25 02:16:58 +01:00
)
var sqliteErr sqlite3 . Error
if errors . As ( err , & sqliteErr ) {
// I initially expected to need to check for ErrConstraintUnique.
// Maybe for psql it will be?
if errors . Is ( sqliteErr . ExtendedCode , sqlite3 . ErrConstraintPrimaryKey ) {
err = ErrDuplicateToken
}
}
return
}
func ( s * Store ) updateToken ( authToken * auth . AuthToken , experation time . Time ) ( err error ) {
res , err := s . db . Exec (
2022-06-07 19:25:14 +02:00
"UPDATE auth_tokens SET token=?, expiration=?, scope=? WHERE user_id=? AND device_id=?" ,
authToken . Token , experation , authToken . Scope , authToken . UserId , authToken . DeviceId ,
2021-12-25 02:16:58 +01:00
)
if err != nil {
return
}
numRows , err := res . RowsAffected ( )
if err != nil {
return
}
if numRows == 0 {
2022-07-26 22:36:57 +02:00
err = ErrNoTokenForUserDevice
2021-12-25 02:16:58 +01:00
}
return
}
2022-07-31 19:54:40 +02:00
// Assumption: User is verified (as they have been identified with GetUserId
// which requires users be verified)
2021-12-25 02:16:58 +01:00
func ( s * Store ) SaveToken ( token * auth . AuthToken ) ( err error ) {
// TODO: For psql, do upsert here instead of separate insertToken and updateToken functions
2022-07-01 00:44:59 +02:00
// Actually it may even be available for SQLite?
2022-07-31 18:26:03 +02:00
// But not for wallet, it probably makes sense to keep that separate because of the sequence variable
2021-12-25 02:16:58 +01:00
// TODO - Should we auto-delete expired tokens?
2022-07-31 18:26:03 +02:00
expiration := time . Now ( ) . UTC ( ) . Add ( AuthTokenLifespan )
2021-12-25 02:16:58 +01:00
// This is most likely not the first time calling this function for this
// device, so there's probably already a token in there.
err = s . updateToken ( token , expiration )
2022-07-26 22:36:57 +02:00
if err == ErrNoTokenForUserDevice {
2021-12-25 02:16:58 +01:00
// If we don't have a token already saved, insert a new one:
err = s . insertToken ( token , expiration )
if err == ErrDuplicateToken {
// By unlikely coincidence, a token was created between trying `updateToken`
// and trying `insertToken`. At this point we can safely `updateToken`.
// TODO - reconsider this - if one client has two concurrent requests
// that create this situation, maybe the second one should just fail?
err = s . updateToken ( token , expiration )
}
}
if err == nil {
token . Expiration = & expiration
}
return
}
2022-06-09 23:04:49 +02:00
////////////
// Wallet //
////////////
2021-12-25 02:16:58 +01:00
2022-07-31 19:54:40 +02:00
// Assumption: Auth token has been checked (thus account is verified)
2022-06-09 23:04:49 +02:00
func ( s * Store ) GetWallet ( userId auth . UserId ) ( encryptedWallet wallet . EncryptedWallet , sequence wallet . Sequence , hmac wallet . WalletHmac , err error ) {
2022-07-06 21:15:17 +02:00
err = s . db . QueryRow (
2022-06-09 23:04:49 +02:00
"SELECT encrypted_wallet, sequence, hmac FROM wallets WHERE user_id=?" ,
2022-06-07 19:25:14 +02:00
userId ,
2022-07-06 21:15:17 +02:00
) . Scan (
& encryptedWallet ,
& sequence ,
& hmac ,
2021-12-25 02:16:58 +01:00
)
2022-07-06 21:15:17 +02:00
if err == sql . ErrNoRows {
err = ErrNoWallet
2021-12-25 02:16:58 +01:00
}
return
}
2022-06-09 23:04:49 +02:00
func ( s * Store ) insertFirstWallet (
2022-06-07 19:25:14 +02:00
userId auth . UserId ,
2022-06-09 23:04:49 +02:00
encryptedWallet wallet . EncryptedWallet ,
hmac wallet . WalletHmac ,
2021-12-25 02:16:58 +01:00
) ( err error ) {
2022-06-09 23:04:49 +02:00
// This will only be used to attempt to insert the first wallet (sequence=1).
// The database will enforce that this will not be set if this user already
// has a wallet.
2021-12-25 02:16:58 +01:00
_ , err = s . db . Exec (
2022-07-01 00:44:59 +02:00
"INSERT INTO wallets (user_id, encrypted_wallet, sequence, hmac) VALUES(?,?,?,?)" ,
2022-06-09 23:04:49 +02:00
userId , encryptedWallet , 1 , hmac ,
2021-12-25 02:16:58 +01:00
)
var sqliteErr sqlite3 . Error
if errors . As ( err , & sqliteErr ) {
// I initially expected to need to check for ErrConstraintUnique.
// Maybe for psql it will be?
if errors . Is ( sqliteErr . ExtendedCode , sqlite3 . ErrConstraintPrimaryKey ) {
2022-07-11 16:41:18 +02:00
// NOTE While ErrDuplicateWallet makes sense in the context of trying to insert,
// SetWallet, which also handles update, translates this to ErrWrongSequence
2022-06-09 23:04:49 +02:00
err = ErrDuplicateWallet
2021-12-25 02:16:58 +01:00
}
}
return
}
2022-06-09 23:04:49 +02:00
func ( s * Store ) updateWalletToSequence (
2022-06-07 19:25:14 +02:00
userId auth . UserId ,
2022-06-09 23:04:49 +02:00
encryptedWallet wallet . EncryptedWallet ,
sequence wallet . Sequence ,
hmac wallet . WalletHmac ,
2021-12-25 02:16:58 +01:00
) ( err error ) {
2022-06-09 23:04:49 +02:00
// This will be used for wallets with sequence > 1.
2021-12-25 02:16:58 +01:00
// Use the database to enforce that we only update if we are incrementing the sequence.
// This way, if two clients attempt to update at the same time, it will return
2022-07-11 16:41:18 +02:00
// an error for the second one.
2021-12-25 02:16:58 +01:00
res , err := s . db . Exec (
2022-06-09 23:04:49 +02:00
"UPDATE wallets SET encrypted_wallet=?, sequence=?, hmac=? WHERE user_id=? AND sequence=?" ,
encryptedWallet , sequence , hmac , userId , sequence - 1 ,
2021-12-25 02:16:58 +01:00
)
if err != nil {
return
}
numRows , err := res . RowsAffected ( )
if err != nil {
return
}
if numRows == 0 {
2022-07-11 16:41:18 +02:00
// NOTE While ErrNoWallet makes sense in the context of trying to update,
// SetWallet, which also handles insert, translates this to ErrWrongSequence
2022-06-09 23:04:49 +02:00
err = ErrNoWallet
2021-12-25 02:16:58 +01:00
}
return
}
2022-06-09 23:04:49 +02:00
// Assumption: Sequence has been validated (>=1)
2022-07-31 19:54:40 +02:00
// Assumption: Auth token has been checked (thus account is verified)
2022-06-23 21:22:31 +02:00
func ( s * Store ) SetWallet ( userId auth . UserId , encryptedWallet wallet . EncryptedWallet , sequence wallet . Sequence , hmac wallet . WalletHmac ) ( err error ) {
2021-12-25 02:16:58 +01:00
if sequence == 1 {
// If sequence == 1, the client assumed that this is our first
2022-06-09 23:04:49 +02:00
// wallet. Try to insert. If we get a conflict, the client
2021-12-25 02:16:58 +01:00
// assumed incorrectly and we proceed below to return the latest
2022-06-09 23:04:49 +02:00
// wallet from the db.
err = s . insertFirstWallet ( userId , encryptedWallet , hmac )
2022-06-23 21:22:31 +02:00
if err == ErrDuplicateWallet {
// A wallet already exists. That means the input sequence should not be 1.
// To the caller, this means the sequence was wrong.
err = ErrWrongSequence
2021-12-25 02:16:58 +01:00
}
} else {
2022-06-09 23:04:49 +02:00
// If sequence > 1, the client assumed that it is replacing wallet
// with sequence - 1. Explicitly try to update the wallet with
2021-12-25 02:16:58 +01:00
// sequence - 1. If we updated no rows, the client assumed incorrectly
2022-06-09 23:04:49 +02:00
// and we proceed below to return the latest wallet from the db.
err = s . updateWalletToSequence ( userId , encryptedWallet , sequence , hmac )
2022-06-23 21:22:31 +02:00
if err == ErrNoWallet {
// No wallet found to replace at the `sequence - 1`. To the caller, this
// means the sequence they put in was wrong.
err = ErrWrongSequence
2021-12-25 02:16:58 +01:00
}
}
return
}
2022-06-07 19:25:14 +02:00
func ( s * Store ) GetUserId ( email auth . Email , password auth . Password ) ( userId auth . UserId , err error ) {
2022-07-13 18:32:48 +02:00
var key auth . KDFKey
2022-07-15 21:36:11 +02:00
var salt auth . ServerSalt
2022-07-30 21:06:27 +02:00
var verified bool
2022-07-06 21:15:17 +02:00
err = s . db . QueryRow (
2022-07-30 21:06:27 +02:00
` SELECT user_id, key, server_salt, verify_token="" from accounts WHERE normalized_email=? ` ,
2022-07-22 03:19:24 +02:00
email . Normalize ( ) ,
2022-07-30 21:06:27 +02:00
) . Scan ( & userId , & key , & salt , & verified )
2022-07-06 21:15:17 +02:00
if err == sql . ErrNoRows {
err = ErrWrongCredentials
2021-12-25 02:16:58 +01:00
}
2022-07-13 18:32:48 +02:00
if err != nil {
return
}
match , err := password . Check ( key , salt )
if err == nil && ! match {
err = ErrWrongCredentials
userId = auth . UserId ( 0 )
}
2022-07-30 21:06:27 +02:00
if err == nil && ! verified {
err = ErrNotVerified
userId = auth . UserId ( 0 )
}
2021-12-25 02:16:58 +01:00
return
}
2022-06-07 19:25:14 +02:00
/////////////
// Account //
/////////////
2021-12-25 02:16:58 +01:00
2022-07-28 01:45:09 +02:00
func ( s * Store ) CreateAccount ( email auth . Email , password auth . Password , seed auth . ClientSaltSeed , verifyToken auth . VerifyTokenString ) ( err error ) {
2022-07-13 18:32:48 +02:00
key , salt , err := password . Create ( )
if err != nil {
return
}
2022-07-22 03:19:24 +02:00
2022-07-30 20:24:33 +02:00
var verifyExpiration * time . Time
if len ( verifyToken ) > 0 {
verifyExpiration = new ( time . Time )
2022-07-31 18:26:03 +02:00
* verifyExpiration = time . Now ( ) . UTC ( ) . Add ( VerifyTokenLifespan )
2022-07-30 20:24:33 +02:00
}
2022-06-07 19:25:14 +02:00
// userId auto-increments
2021-12-25 02:16:58 +01:00
_ , err = s . db . Exec (
2022-07-30 20:24:33 +02:00
"INSERT INTO accounts (normalized_email, email, key, server_salt, client_salt_seed, verify_token, verify_expiration) VALUES(?,?,?,?,?,?,?)" ,
email . Normalize ( ) , email , key , salt , seed , verifyToken , verifyExpiration ,
2021-12-25 02:16:58 +01:00
)
var sqliteErr sqlite3 . Error
if errors . As ( err , & sqliteErr ) {
if errors . Is ( sqliteErr . ExtendedCode , sqlite3 . ErrConstraintUnique ) {
err = ErrDuplicateAccount
}
}
return
}
2022-07-01 00:44:59 +02:00
2022-07-31 20:13:30 +02:00
// In case the user needs a new verification email, generate a new verify token
// with a new deadline 2 days away.
//
// This function should only work if the account is not already verified.
// Otherwise we risk de-verifying accounts which would be confusing and
// annoying if it were to ever get triggered.
2022-07-31 18:26:03 +02:00
func ( s * Store ) UpdateVerifyTokenString ( email auth . Email , verifyTokenString auth . VerifyTokenString ) ( err error ) {
expiration := time . Now ( ) . UTC ( ) . Add ( VerifyTokenLifespan )
res , err := s . db . Exec (
2022-07-31 20:13:30 +02:00
` UPDATE accounts SET verify_token=?, verify_expiration=? WHERE normalized_email=? and verify_token!="" ` ,
2022-07-31 18:26:03 +02:00
verifyTokenString , expiration , email . Normalize ( ) ,
)
if err != nil {
return
}
numRows , err := res . RowsAffected ( )
if err != nil {
return
}
if numRows == 0 {
2022-07-31 20:13:30 +02:00
// Since we got a miss (presumably not very common), let's do another check
// to see which error to return: invalid email or invalid token
var dummy int
err = s . db . QueryRow (
` SELECT 1 from accounts WHERE normalized_email=? ` ,
email . Normalize ( ) ,
) . Scan ( & dummy )
if err == sql . ErrNoRows {
err = ErrWrongCredentials
}
if err == nil {
err = ErrNoTokenForUser
}
2022-07-31 18:26:03 +02:00
}
2022-07-30 02:49:00 +02:00
return
}
2022-07-31 18:42:03 +02:00
func ( s * Store ) VerifyAccount ( verifyTokenString auth . VerifyTokenString ) ( err error ) {
res , err := s . db . Exec (
"UPDATE accounts SET verify_token=?, verify_expiration=? WHERE verify_token=?" ,
"" , nil , verifyTokenString ,
)
if err != nil {
return
}
numRows , err := res . RowsAffected ( )
if err != nil {
return
}
if numRows == 0 {
err = ErrNoTokenForUser
}
2022-07-26 22:36:57 +02:00
return
}
2022-07-01 00:44:59 +02:00
// Change password. For the user, this requires changing their root password,
// which changes the encryption key for the wallet as well. Thus, we should
// update the wallet at the same time to avoid ever having a situation where
// these two don't match.
//
// Also delete all auth tokens to force clients to update their root password
// to get a new token. This prevents other clients from posting a wallet
// encrypted with the old key.
2022-07-05 23:12:14 +02:00
func ( s * Store ) ChangePasswordWithWallet (
2022-07-01 00:44:59 +02:00
email auth . Email ,
oldPassword auth . Password ,
newPassword auth . Password ,
2022-07-15 21:36:11 +02:00
clientSaltSeed auth . ClientSaltSeed ,
2022-07-01 00:44:59 +02:00
encryptedWallet wallet . EncryptedWallet ,
sequence wallet . Sequence ,
hmac wallet . WalletHmac ,
) ( err error ) {
2022-07-13 18:32:48 +02:00
return s . changePassword (
email ,
oldPassword ,
newPassword ,
2022-07-15 21:36:11 +02:00
clientSaltSeed ,
2022-07-13 18:32:48 +02:00
encryptedWallet ,
sequence ,
hmac ,
2022-07-01 00:44:59 +02:00
)
}
2022-07-05 23:12:14 +02:00
// Change password, but with no wallet currently saved. Since there's no
// wallet saved, there's no wallet to update. The encryption key is moot.
//
// Also delete all auth tokens to force clients to update their root password
// to get a new token. This prevents other clients from posting a wallet
// encrypted with the old key.
func ( s * Store ) ChangePasswordNoWallet (
email auth . Email ,
oldPassword auth . Password ,
newPassword auth . Password ,
2022-07-15 21:36:11 +02:00
clientSaltSeed auth . ClientSaltSeed ,
2022-07-13 18:32:48 +02:00
) ( err error ) {
return s . changePassword (
email ,
oldPassword ,
newPassword ,
2022-07-15 21:36:11 +02:00
clientSaltSeed ,
2022-07-13 18:32:48 +02:00
wallet . EncryptedWallet ( "" ) ,
wallet . Sequence ( 0 ) ,
wallet . WalletHmac ( "" ) ,
)
}
// Common code for for WithWallet and WithNoWallet password change functions
func ( s * Store ) changePassword (
email auth . Email ,
oldPassword auth . Password ,
newPassword auth . Password ,
2022-07-15 21:36:11 +02:00
clientSaltSeed auth . ClientSaltSeed ,
2022-07-13 18:32:48 +02:00
encryptedWallet wallet . EncryptedWallet ,
sequence wallet . Sequence ,
hmac wallet . WalletHmac ,
2022-07-05 23:12:14 +02:00
) ( err error ) {
var userId auth . UserId
tx , err := s . db . Begin ( )
if err != nil {
return
}
// Lots of error conditions. Just defer this. However, we need to make sure to
// make sure the variable `err` is set to the error before we return, instead
// of doing `return <error>`.
endTxn := func ( ) {
if err != nil {
tx . Rollback ( )
} else {
tx . Commit ( )
}
}
defer endTxn ( )
2022-07-13 18:32:48 +02:00
var oldKey auth . KDFKey
2022-07-15 21:36:11 +02:00
var oldSalt auth . ServerSalt
2022-07-31 05:09:33 +02:00
var verified bool
2022-07-13 18:32:48 +02:00
2022-07-05 23:12:14 +02:00
err = tx . QueryRow (
2022-07-31 05:09:33 +02:00
` SELECT user_id, key, server_salt, verify_token="" from accounts WHERE normalized_email=? ` ,
2022-07-22 03:19:24 +02:00
email . Normalize ( ) ,
2022-07-31 05:09:33 +02:00
) . Scan ( & userId , & oldKey , & oldSalt , & verified )
2022-07-05 23:12:14 +02:00
if err == sql . ErrNoRows {
err = ErrWrongCredentials
2022-07-13 18:32:48 +02:00
}
if err != nil {
return
}
match , err := oldPassword . Check ( oldKey , oldSalt )
if err == nil && ! match {
err = ErrWrongCredentials
}
2022-07-31 05:09:33 +02:00
if err == nil && ! verified {
err = ErrNotVerified
}
2022-07-13 18:32:48 +02:00
if err != nil {
2022-07-05 23:12:14 +02:00
return
}
2022-07-13 18:32:48 +02:00
newKey , newSalt , err := newPassword . Create ( )
2022-07-05 23:12:14 +02:00
if err != nil {
return
}
res , err := tx . Exec (
2022-07-15 21:36:11 +02:00
"UPDATE accounts SET key=?, server_salt=?, client_salt_seed=? WHERE user_id=?" ,
newKey , newSalt , clientSaltSeed , userId ,
2022-07-05 23:12:14 +02:00
)
if err != nil {
return
}
numRows , err := res . RowsAffected ( )
if err != nil {
return
}
2022-07-06 21:22:45 +02:00
if numRows == 0 {
2022-07-05 23:12:14 +02:00
// Very unexpected error!
err = fmt . Errorf ( "Password failed to update" )
return
}
2022-07-13 18:32:48 +02:00
if encryptedWallet != "" {
// With a wallet expected: update it.
res , err = tx . Exec (
` UPDATE wallets SET encrypted_wallet = ? , sequence = ? , hmac = ?
WHERE user_id = ? AND sequence = ? ` ,
encryptedWallet , sequence , hmac , userId , sequence - 1 ,
)
if err != nil {
return
}
numRows , err = res . RowsAffected ( )
if err != nil {
return
}
if numRows == 0 {
err = ErrWrongSequence
return
}
} else {
// With no wallet expected: assert we have no wallet.
var dummy string
err = tx . QueryRow ( "SELECT 1 FROM wallets WHERE user_id=?" , userId ) . Scan ( & dummy )
if err != sql . ErrNoRows {
if err == nil {
// We expected no rows
err = ErrUnexpectedWallet
return
}
// Some other error
2022-07-05 23:12:14 +02:00
return
}
}
// Don't care how many I delete here. Might even be zero. No login token while
// changing password seems plausible.
_ , err = tx . Exec ( "DELETE FROM auth_tokens WHERE user_id=?" , userId )
return
}
2022-07-15 21:36:11 +02:00
2022-07-31 19:54:40 +02:00
// It's a public endpoint, we don't really care if the user is verified
2022-07-15 21:36:11 +02:00
func ( s * Store ) GetClientSaltSeed ( email auth . Email ) ( seed auth . ClientSaltSeed , err error ) {
err = s . db . QueryRow (
2022-07-22 03:19:24 +02:00
` SELECT client_salt_seed from accounts WHERE normalized_email=? ` ,
email . Normalize ( ) ,
2022-07-15 21:36:11 +02:00
) . Scan ( & seed )
if err == sql . ErrNoRows {
err = ErrWrongCredentials
}
return
}