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"
"github.com/mattn/go-sqlite3"
"log"
"orblivion/lbry-id/auth"
2022-06-07 19:25:14 +02:00
"orblivion/lbry-id/wallet"
2021-12-25 02:16:58 +01:00
"time"
)
var (
ErrDuplicateToken = fmt . Errorf ( "Token already exists for this user and device" )
ErrNoToken = fmt . Errorf ( "Token does not exist for this user and device" )
2022-06-09 23:04:49 +02:00
ErrDuplicateWallet = fmt . Errorf ( "Wallet already exists for this user" )
ErrNoWallet = fmt . Errorf ( "Wallet does not exist for this user at this sequence" )
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-06-07 19:25:14 +02:00
ErrNoUId = fmt . Errorf ( "User Id not found with these credentials" )
2021-12-25 02:16:58 +01:00
)
// For test stubs
type StoreInterface interface {
SaveToken ( * auth . AuthToken ) error
2022-06-09 23:04:49 +02:00
GetToken ( auth . TokenString ) ( * auth . AuthToken , error )
SetWallet ( auth . UserId , wallet . EncryptedWallet , wallet . Sequence , wallet . WalletHmac ) ( wallet . EncryptedWallet , wallet . Sequence , wallet . WalletHmac , bool , error )
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 )
CreateAccount ( auth . Email , auth . Password ) ( err error )
2021-12-25 02:16:58 +01:00
}
type Store struct {
db * sql . DB
}
func ( s * Store ) Init ( fileName string ) {
db , err := sql . Open ( "sqlite3" , fileName )
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
// TODO does it actually fail with empty "NOT NULL" fields?
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-07 19:25:14 +02:00
PRIMARY KEY ( user_id , device_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 )
2021-12-25 02:16:58 +01:00
) ;
CREATE TABLE IF NOT EXISTS accounts (
email TEXT NOT NULL UNIQUE ,
2022-06-07 19:25:14 +02:00
user_id INTEGER PRIMARY KEY AUTOINCREMENT ,
password TEXT NOT NULL
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-06-09 23:04:49 +02:00
func ( s * Store ) GetToken ( token auth . TokenString ) ( * auth . AuthToken , error ) {
2021-12-25 02:16:58 +01:00
expirationCutoff := time . Now ( ) . UTC ( )
rows , err := s . db . Query (
2022-06-07 19:25:14 +02:00
"SELECT * FROM auth_tokens WHERE token=? AND expiration>?" , token , expirationCutoff ,
2021-12-25 02:16:58 +01:00
)
if err != nil {
return nil , err
}
defer rows . Close ( )
var authToken auth . AuthToken
for rows . Next ( ) {
err := rows . Scan (
& authToken . Token ,
2022-06-07 19:25:14 +02:00
& authToken . UserId ,
& authToken . DeviceId ,
2021-12-25 02:16:58 +01:00
& authToken . Scope ,
& authToken . Expiration ,
)
if err != nil {
return nil , err
}
return & authToken , nil
}
return nil , ErrNoToken // TODO - will need to test
}
func ( s * Store ) insertToken ( authToken * auth . AuthToken , expiration time . Time ) ( err error ) {
_ , err = s . db . Exec (
2022-06-07 19:25:14 +02:00
"INSERT INTO auth_tokens (token, user_id, device_id, scope, expiration) values(?,?,?,?,?)" ,
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 {
err = ErrNoToken
}
return
}
func ( s * Store ) SaveToken ( token * auth . AuthToken ) ( err error ) {
// TODO: For psql, do upsert here instead of separate insertToken and updateToken functions
// TODO - Should we auto-delete expired tokens?
expiration := time . Now ( ) . UTC ( ) . Add ( time . Hour * 24 * 14 )
// 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 )
if err == ErrNoToken {
// 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-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 ) {
2021-12-25 02:16:58 +01:00
rows , err := s . db . Query (
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 ,
2021-12-25 02:16:58 +01:00
)
if err != nil {
return
}
defer rows . Close ( )
for rows . Next ( ) {
err = rows . Scan (
2022-06-09 23:04:49 +02:00
& encryptedWallet ,
& sequence ,
2022-06-07 19:25:14 +02:00
& hmac ,
2021-12-25 02:16:58 +01:00
)
return
}
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
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-06-09 23:04:49 +02:00
"INSERT INTO wallets (user_id, encrypted_wallet, sequence, hmac) values(?,?,?,?)" ,
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-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-06-09 23:04:49 +02:00
// ErrNoWallet 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-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)
func ( s * Store ) SetWallet (
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 ,
// TODO `sequenceCorrect` should probably be replaced with `status`, that can
// equal `Updated` or `SequenceMismatch`. Maybe with a message for the API.
// Like an error, but not, because the function still returns a value.
// Right now, we have:
// `sequenceCorrect==true` and `err==nil` implies it updated.
// We could also have:
// `sequenceMismatch==true` or `err!=nil` implying it didn't update.
// Or:
// `updated==false` and `err=nil` implying the sequence mismatched.
// I don't like this implication stuff, the "status" should be explicit so
// we don't make bugs.
) ( latestEncryptedWallet wallet . EncryptedWallet , latestSequence wallet . Sequence , latestHmac wallet . WalletHmac , sequenceCorrect bool , 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 )
2021-12-25 02:16:58 +01:00
if err == nil {
// Successful update
2022-06-09 23:04:49 +02:00
latestEncryptedWallet = encryptedWallet
latestSequence = sequence
2022-06-07 19:25:14 +02:00
latestHmac = hmac
2022-06-09 23:04:49 +02:00
sequenceCorrect = true
2021-12-25 02:16:58 +01:00
return
2022-06-09 23:04:49 +02:00
} else if err != ErrDuplicateWallet {
2021-12-25 02:16:58 +01:00
// Unsuccessful update for reasons other than sequence conflict
return
}
} 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 )
2021-12-25 02:16:58 +01:00
if err == nil {
2022-06-09 23:04:49 +02:00
latestEncryptedWallet = encryptedWallet
latestSequence = sequence
2022-06-07 19:25:14 +02:00
latestHmac = hmac
2022-06-09 23:04:49 +02:00
sequenceCorrect = true
2021-12-25 02:16:58 +01:00
return
2022-06-09 23:04:49 +02:00
} else if err != ErrNoWallet {
2021-12-25 02:16:58 +01:00
return
}
}
// We failed to update above due to a sequence conflict. Perhaps the client
// was unaware of an update done by another client. Let's send back the latest
// version right away so the requesting client can take care of it.
2022-06-07 19:25:14 +02:00
//
2021-12-25 02:16:58 +01:00
// Note that this means that `err` will not be `nil` at this point, but we
2022-06-09 23:04:49 +02:00
// already accounted for it with `sequenceCorrect=false`. Instead, we'll pass
// on any errors from calling `GetWallet`.
latestEncryptedWallet , latestSequence , latestHmac , err = s . GetWallet ( userId )
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 ) {
2021-12-25 02:16:58 +01:00
rows , err := s . db . Query (
2022-06-07 19:25:14 +02:00
` SELECT user_id from accounts WHERE email=? AND password=? ` ,
email , password . Obfuscate ( ) ,
2021-12-25 02:16:58 +01:00
)
if err != nil {
return
}
defer rows . Close ( )
for rows . Next ( ) {
2022-06-07 19:25:14 +02:00
err = rows . Scan ( & userId )
2021-12-25 02:16:58 +01:00
return
}
2022-06-07 19:25:14 +02:00
err = ErrNoUId
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-06-07 19:25:14 +02:00
func ( s * Store ) CreateAccount ( email auth . Email , password auth . Password ) ( err error ) {
// userId auto-increments
2021-12-25 02:16:58 +01:00
_ , err = s . db . Exec (
2022-06-07 19:25:14 +02:00
"INSERT INTO accounts (email, password) values(?,?)" ,
email , password . Obfuscate ( ) ,
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?
2022-06-07 19:25:14 +02:00
// TODO - is this right? Does the above comment explain that it's backwards
// from what I would have expected? Or did I do this backwards?
2022-06-19 23:49:05 +02:00
// Or is this a holdover from when an account was attached to a walletstate?
2021-12-25 02:16:58 +01:00
if errors . Is ( sqliteErr . ExtendedCode , sqlite3 . ErrConstraintPrimaryKey ) {
err = ErrDuplicateEmail
}
if errors . Is ( sqliteErr . ExtendedCode , sqlite3 . ErrConstraintUnique ) {
err = ErrDuplicateAccount
}
}
return
}