2023-09-13 02:50:15 +00:00
package pkg
2023-09-13 08:51:05 +00:00
import (
"context"
"encoding/json"
2023-09-21 06:25:40 +00:00
"fmt"
2023-10-21 09:26:13 +00:00
"github.com/ente-io/cli/internal"
"github.com/ente-io/cli/internal/api"
"github.com/ente-io/cli/pkg/model"
"github.com/ente-io/cli/utils/encoding"
2023-09-14 04:20:32 +00:00
"log"
2023-09-13 08:51:05 +00:00
bolt "go.etcd.io/bbolt"
)
const AccBucket = "accounts"
2023-09-14 04:20:32 +00:00
func ( c * ClICtrl ) AddAccount ( cxt context . Context ) {
var flowErr error
defer func ( ) {
if flowErr != nil {
log . Fatal ( flowErr )
}
} ( )
2023-09-23 10:45:10 +00:00
app := internal . GetAppType ( )
2023-09-21 09:35:52 +00:00
cxt = context . WithValue ( cxt , "app" , string ( app ) )
2023-10-16 18:18:00 +00:00
dir := internal . GetExportDir ( )
if dir == "" {
flowErr = fmt . Errorf ( "export directory not set" )
return
}
2023-09-23 10:45:10 +00:00
email , flowErr := internal . GetUserInput ( "Enter email address" )
2023-09-14 04:20:32 +00:00
if flowErr != nil {
return
}
var verifyEmail bool
2023-09-21 06:25:40 +00:00
2023-09-14 04:20:32 +00:00
srpAttr , flowErr := c . Client . GetSRPAttributes ( cxt , email )
if flowErr != nil {
2023-09-21 03:16:26 +00:00
// if flowErr type is ApiError and status code is 404, then set verifyEmail to true and continue
// else return
if apiErr , ok := flowErr . ( * api . ApiError ) ; ok && apiErr . StatusCode == 404 {
verifyEmail = true
} else {
2023-09-14 04:20:32 +00:00
return
}
2023-09-21 03:16:26 +00:00
}
2023-09-21 06:25:40 +00:00
var authResponse * api . AuthorizationResponse
var keyEncKey [ ] byte
2023-09-21 03:16:26 +00:00
if verifyEmail || srpAttr . IsEmailMFAEnabled {
authResponse , flowErr = c . validateEmail ( cxt , email )
2023-09-14 04:20:32 +00:00
} else {
2023-09-27 05:32:36 +00:00
authResponse , keyEncKey , flowErr = c . signInViaPassword ( cxt , srpAttr )
2023-09-14 04:20:32 +00:00
}
if flowErr != nil {
return
}
if authResponse . IsMFARequired ( ) {
2023-09-15 10:47:38 +00:00
authResponse , flowErr = c . validateTOTP ( cxt , authResponse )
2023-09-14 04:20:32 +00:00
}
2023-09-21 03:16:26 +00:00
if authResponse . EncryptedToken == "" || authResponse . KeyAttributes == nil {
2024-05-06 10:21:15 +00:00
log . Fatalf ( "missing key attributes or token.\nNote: Please use the mobile,web or desktop app to create a new account.\nIf you are trying to login to an existing account, report a bug." )
2023-09-21 03:16:26 +00:00
}
2023-09-22 16:15:01 +00:00
secretInfo , decErr := c . decryptAccSecretInfo ( cxt , authResponse , keyEncKey )
2023-09-21 06:25:40 +00:00
if decErr != nil {
flowErr = decErr
return
2023-09-14 04:20:32 +00:00
}
2023-10-16 18:18:00 +00:00
err := c . storeAccount ( cxt , email , authResponse . ID , app , secretInfo , dir )
2023-09-21 12:07:18 +00:00
if err != nil {
flowErr = err
return
} else {
fmt . Println ( "Account added successfully" )
2023-10-25 11:52:22 +00:00
fmt . Println ( "run `ente export` to initiate export of your account data" )
2023-09-21 12:07:18 +00:00
}
}
2023-10-16 18:18:00 +00:00
func ( c * ClICtrl ) storeAccount ( _ context . Context , email string , userID int64 , app api . App , secretInfo * model . AccSecretInfo , exportDir string ) error {
2023-09-21 12:07:18 +00:00
// get password
err := c . DB . Update ( func ( tx * bolt . Tx ) error {
b , err := tx . CreateBucketIfNotExists ( [ ] byte ( AccBucket ) )
if err != nil {
return err
}
2023-09-22 05:29:02 +00:00
accInfo := model . Account {
2023-09-21 12:07:18 +00:00
Email : email ,
UserID : userID ,
2023-09-27 08:39:44 +00:00
MasterKey : * model . MakeEncString ( secretInfo . MasterKey , c . KeyHolder . DeviceKey ) ,
SecretKey : * model . MakeEncString ( secretInfo . SecretKey , c . KeyHolder . DeviceKey ) ,
Token : * model . MakeEncString ( secretInfo . Token , c . KeyHolder . DeviceKey ) ,
2023-09-21 12:07:18 +00:00
App : app ,
2023-09-23 04:10:54 +00:00
PublicKey : encoding . EncodeBase64 ( secretInfo . PublicKey ) ,
2023-10-16 18:18:00 +00:00
ExportDir : exportDir ,
2023-09-21 12:07:18 +00:00
}
accInfoBytes , err := json . Marshal ( accInfo )
if err != nil {
return err
}
2023-09-22 05:29:02 +00:00
accountKey := accInfo . AccountKey ( )
2023-09-21 12:07:18 +00:00
return b . Put ( [ ] byte ( accountKey ) , accInfoBytes )
} )
return err
2023-09-14 04:20:32 +00:00
}
2023-09-22 05:29:02 +00:00
func ( c * ClICtrl ) GetAccounts ( cxt context . Context ) ( [ ] model . Account , error ) {
var accounts [ ] model . Account
2023-09-14 04:20:32 +00:00
err := c . DB . View ( func ( tx * bolt . Tx ) error {
b := tx . Bucket ( [ ] byte ( AccBucket ) )
err := b . ForEach ( func ( k , v [ ] byte ) error {
2023-09-22 05:29:02 +00:00
var info model . Account
2023-09-14 04:20:32 +00:00
err := json . Unmarshal ( v , & info )
if err != nil {
return err
}
2023-09-22 04:35:25 +00:00
accounts = append ( accounts , info )
2023-09-14 04:20:32 +00:00
return nil
} )
2023-09-13 08:51:05 +00:00
if err != nil {
return err
}
2023-09-14 04:20:32 +00:00
return nil
2023-09-13 08:51:05 +00:00
} )
2023-09-22 04:35:25 +00:00
return accounts , err
}
func ( c * ClICtrl ) ListAccounts ( cxt context . Context ) error {
accounts , err := c . GetAccounts ( cxt )
2023-09-14 04:20:32 +00:00
if err != nil {
return err
}
2023-09-22 04:35:25 +00:00
fmt . Printf ( "Configured accounts: %d\n" , len ( accounts ) )
for _ , acc := range accounts {
fmt . Println ( "====================================" )
2023-10-16 17:34:12 +00:00
fmt . Println ( "Email: " , acc . Email )
fmt . Println ( "ID: " , acc . UserID )
fmt . Println ( "App: " , acc . App )
fmt . Println ( "ExportDir:" , acc . ExportDir )
2023-09-22 04:35:25 +00:00
fmt . Println ( "====================================" )
}
return nil
2023-09-13 08:51:05 +00:00
}
2023-10-16 17:34:12 +00:00
2024-03-08 12:05:16 +00:00
func ( c * ClICtrl ) UpdateAccount ( ctx context . Context , params model . AccountCommandParams ) error {
2023-10-16 17:34:12 +00:00
accounts , err := c . GetAccounts ( ctx )
if err != nil {
return err
}
var acc * model . Account
for _ , a := range accounts {
if a . Email == params . Email && a . App == params . App {
acc = & a
break
}
}
if acc == nil {
2023-10-18 07:11:20 +00:00
return fmt . Errorf ( "account not found, use `account list` to list accounts" )
2023-10-16 17:34:12 +00:00
}
if params . ExportDir != nil && * params . ExportDir != "" {
2023-10-16 18:18:00 +00:00
_ , err := internal . ValidateDirForWrite ( * params . ExportDir )
2023-10-16 17:34:12 +00:00
if err != nil {
return err
}
acc . ExportDir = * params . ExportDir
}
err = c . DB . Update ( func ( tx * bolt . Tx ) error {
b , err := tx . CreateBucketIfNotExists ( [ ] byte ( AccBucket ) )
if err != nil {
return err
}
accInfoBytes , err := json . Marshal ( acc )
if err != nil {
return err
}
accountKey := acc . AccountKey ( )
return b . Put ( [ ] byte ( accountKey ) , accInfoBytes )
} )
return err
2024-03-08 12:05:16 +00:00
}
2023-10-16 17:34:12 +00:00
2024-03-08 12:05:16 +00:00
func ( c * ClICtrl ) GetToken ( ctx context . Context , params model . AccountCommandParams ) error {
accounts , err := c . GetAccounts ( ctx )
if err != nil {
return err
}
var acc * model . Account
for _ , a := range accounts {
if a . Email == params . Email && a . App == params . App {
acc = & a
break
}
}
if acc == nil {
return fmt . Errorf ( "account not found, use `account list` to list accounts" )
}
secretInfo , err := c . KeyHolder . LoadSecrets ( * acc )
if err != nil {
return err
}
fmt . Println ( secretInfo . TokenStr ( ) )
return nil
2023-10-16 17:34:12 +00:00
}