@@ -3,12 +3,11 @@ module Main
5import Prelude
5import Prelude
7−import BW (ApiService, CryptoService, Hash, Services)
6+import BW (ApiService, CryptoService, Services)
8import BW as WB
7import BW as WB
9−import BW.Logic (decrypt, hashPassword, liftPromise)
8+import BW.Logic (decodeCipher, decrypt, hashPassword, liftPromise)
10import BW.Logic as Logic
9import BW.Logic as Logic
11−import BW.Types (CipherResponse, Email(..), IdentityTokenResponse, Password(..), PreloginResponse, SyncResponse, Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote)
10+import BW.Types (CipherResponse, Email(..), Password(..), PreloginResponse, Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote)
12import Bridge as Bridge
11import Bridge as Bridge
13import Control.El (class HasL, Al, L(..), l)
12import Control.El (class HasL, Al, L(..), l)
14import Control.Monad.Error.Class (throwError)
13import Control.Monad.Error.Class (throwError)
@@ -19,11 +18,8 @@ import Data.Clipboard as Clipboard
19import Data.DateTime (DateTime)
18import Data.DateTime (DateTime)
20import Data.Either (Either(..))
19import Data.Either (Either(..))
21import Data.HCaptcha (bindHCaptchToken)
20import Data.HCaptcha (bindHCaptchToken)
22−import Data.JNullable (fromJNullable, jnull, nullify)
21+import Data.JNullable (jnull, nullify)
23−import Data.JNullable as JNullable
24−import Data.JOpt (fromJOpt)
25import Data.Maybe (Maybe(..))
22import Data.Maybe (Maybe(..))
26−import Data.Newtype (wrap)
27import Data.OpenURL (openURL)
23import Data.OpenURL (openURL)
28import Data.Ord.Down (Down(..))
24import Data.Ord.Down (Down(..))
29import Data.SymmetricCryptoKey (SymmetricCryptoKey)
25import Data.SymmetricCryptoKey (SymmetricCryptoKey)
@@ -41,6 +37,7 @@ import FFI (Elm)
41import FFI as FFI
37import FFI as FFI
42import Localstorage (class StorageKey)
38import Localstorage (class StorageKey)
43import Localstorage as Storage
39import Localstorage as Storage
40+import Storage (MasterPasswordHashKey(..), PreloginResponseKey(..), SyncKey(..), TokenKey(..), UrlsKey(..))
44import Web.HTML as Html
41import Web.HTML as Html
45import Web.HTML.Window as Window
42import Web.HTML.Window as Window
46import Web.Storage.Storage (Storage)
43import Web.Storage.Storage (Storage)
@@ -53,218 +50,141 @@ main = do
53 services <- WB.getServices
50 services <- WB.getServices
54 masterKeyRef <- Ref.new Nothing
51 masterKeyRef <- Ref.new Nothing
55 bindHCaptchToken log
52 bindHCaptchToken log
56− let run act = do
54+ run act = do
57− maybeKey <- Ref.read masterKeyRef
55+ maybeKey <- Ref.read masterKeyRef
58− runElmAff app $ flip runReaderT {app: app, crypto : services.crypto, storage: storage} $
56+ runElmAff app $ flip runReaderT { app: app, crypto: services.crypto, storage: storage }
59− case maybeKey of
57+ $ case maybeKey of
60− Just masterKey -> do
58+ Just masterKey -> do
61− token <- getOrReset storage TokenKey
59+ token <- getOrReset storage TokenKey
62− key <- Logic.makeDecryptionKey masterKey token.key
60+ key <- Logic.makeDecryptionKey masterKey token.key
63− liftEffect $ runElmAff app $ runReaderT act
61+ liftEffect $ runElmAff app
64− {app : app , storage : storage, services : services, crypto : services.crypto
66+ , crypto: services.crypto
65− , key : key}
67+ , key: key
66− Nothing -> requestMasterPassword
69+ Nothing -> requestMasterPassword
67 Elm.subscribe app \cmd -> case cmd of
70 Elm.subscribe app \cmd -> case cmd of
68 Bridge.Login (Bridge.Cmd_Login { email, server, password }) -> do
71 Bridge.Login (Bridge.Cmd_Login { email, server, password }) -> do
70 urls = baseUrl server
73 urls = baseUrl server
71 runElmAff app do
74 runElmAff app do
72 unauthedApi <- liftPromise $ services.getApi urls jnull
75 unauthedApi <- liftPromise $ services.getApi urls jnull
73− flip runReaderT { api: unauthedApi, crypto: services.crypto, storage:
78+ , crypto: services.crypto
74− storage, cryptoFunctions: services.cryptoFunctions } do
81+ , cryptoFunctions: services.cryptoFunctions
75− {token, prelogin: (prelogin :: PreloginResponse)} <-
83+ { token, prelogin: (prelogin :: PreloginResponse) } <-
77− prelogin <- liftPromise $ unauthedApi.postPrelogin {email}
86+ prelogin <- liftPromise $ unauthedApi.postPrelogin { email }
78− token <- (Logic.getLogInRequestToken prelogin (Email email) (Password password))
87+ token <- (Logic.getLogInRequestToken prelogin (Email email) (Password password))
79− pure {token, prelogin}
88+ pure { token, prelogin }
81− >>= \et -> case et of
90+ >>= \et -> case et of
82− Left _ -> liftEffect $ Exc.throw "Could not log in. Please check your data and try again."
91+ Left _ -> liftEffect $ Exc.throw "Could not log in. Please check your data and try again."
83− Right t -> pure t
92+ Right t -> pure t
84− masterKey <- Logic.makePreloginKey prelogin (Email email) (Password password)
93+ masterKey <- Logic.makePreloginKey prelogin (Email email) (Password password)
85− api <- liftPromise $ services.getApi urls (nullify token)
94+ api <- liftPromise $ services.getApi urls (nullify token)
86− sync <- liftPromise $ api.getSync unit
95+ sync <- liftPromise $ api.getSync unit
87− hash <- hashPassword (Password password)
96+ hash <- hashPassword (Password password)
88− log $ show sync
97+ log $ show sync
89− liftEffect $ Ref.write (Just masterKey) masterKeyRef
98+ liftEffect $ Ref.write (Just masterKey) masterKeyRef
90− liftEffect $ Storage.store storage UrlsKey server
99+ liftEffect $ Storage.store storage UrlsKey server
91− liftEffect $ Storage.store storage MasterPasswordHashKey hash
100+ liftEffect $ Storage.store storage MasterPasswordHashKey hash
92− liftEffect $ Storage.store storage PreloginResponseKey prelogin
101+ liftEffect $ Storage.store storage PreloginResponseKey prelogin
93− liftEffect $ Storage.store storage SyncKey sync
102+ liftEffect $ Storage.store storage SyncKey sync
94− liftEffect $ Storage.store storage TokenKey token
103+ liftEffect $ Storage.store storage TokenKey token
95− liftEffect $ Elm.send app Bridge.LoginSuccessful
104+ liftEffect $ Elm.send app Bridge.LoginSuccessful
96− pure unit
106+ pure unit
107+ Bridge.NeedCiphersList ->
109+ sync <- getOrReset storage SyncKey
110+ ciphers <- traverse processCipher sync.ciphers
112+ sortedCiphers = Array.sortWith (_.date >>> Down) ciphers
113+ liftEffect $ Elm.send app $ Bridge.LoadCiphers $ Bridge.Sub_LoadCiphers_List $ map _.cipher sortedCiphers
114+ liftEffect $ Storage.store storage SyncKey sync
97 pure unit
115 pure unit
98− Bridge.NeedCiphersList -> run do
99− sync <- getOrReset storage SyncKey
100− ciphers <- traverse processCipher sync.ciphers
101− let sortedCiphers = Array.sortWith (_.date >>> Down) ciphers
102− liftEffect $ Elm.send app $ Bridge.LoadCiphers $ Bridge.Sub_LoadCiphers_List $ map _.cipher sortedCiphers
103− liftEffect $ Storage.store storage SyncKey sync
105 Bridge.NeedsReset -> do
116 Bridge.NeedsReset -> do
106 WebStorage.clear storage
117 WebStorage.clear storage
107 Elm.send app Bridge.Reset
118 Elm.send app Bridge.Reset
108 pure unit
119 pure unit
109− Bridge.SendMasterPassword masterPassword -> runElmAff app $ do
120+ Bridge.SendMasterPassword masterPassword ->
110− flip runReaderT {app: app, storage: storage, crypto: services.crypto, cryptoFunctions: services.cryptoFunctions} $ do
123+ flip runReaderT { app: app, storage: storage, crypto: services.crypto, cryptoFunctions: services.cryptoFunctions }
111− hash <- getOrReset storage MasterPasswordHashKey
125+ hash <- getOrReset storage MasterPasswordHashKey
112− newHash <- hashPassword (Password masterPassword)
126+ newHash <- hashPassword (Password masterPassword)
113− if hash == newHash
127+ if hash == newHash then do
115− sync <- getOrReset storage SyncKey
128+ sync <- getOrReset storage SyncKey
116− prelogin <- getOrReset storage PreloginResponseKey
129+ prelogin <- getOrReset storage PreloginResponseKey
117− masterKey <- Logic.makePreloginKey prelogin (Email sync.profile.email) (Password masterPassword)
130+ masterKey <- Logic.makePreloginKey prelogin (Email sync.profile.email) (Password masterPassword)
118− liftEffect $ Ref.write (Just masterKey) masterKeyRef
131+ liftEffect $ Ref.write (Just masterKey) masterKeyRef
119− liftEffect $ Elm.send app Bridge.LoginSuccessful
132+ liftEffect $ Elm.send app Bridge.LoginSuccessful
121− liftEffect $ Elm.send app $ Bridge.Error "The password is wrong. Please try again."
134+ liftEffect $ Elm.send app $ Bridge.Error "The password is wrong. Please try again."
122− requestMasterPassword
135+ requestMasterPassword
123− pure unit
136+ pure unit
124 Bridge.Init -> do
137 Bridge.Init -> do
125− liftEffect $ Storage.get storage TokenKey >>= \x -> case x of
138+ liftEffect $ Storage.get storage TokenKey
126− Just _ -> do liftEffect $ Elm.send app Bridge.LoginSuccessful
140+ Just _ -> do liftEffect $ Elm.send app Bridge.LoginSuccessful
127− Nothing -> Elm.send app Bridge.NeedsLogin
141+ Nothing -> Elm.send app Bridge.NeedsLogin
128− Bridge.RequestCipher id -> run do
142+ Bridge.RequestCipher id ->
129− sync <- getOrReset storage SyncKey
144+ sync <- getOrReset storage SyncKey
130− case Array.find (\c -> c.id == id) sync.ciphers of
145+ case Array.find (\c -> c.id == id) sync.ciphers of
131− Nothing -> pure unit
146+ Nothing -> pure unit
132− Just cipher -> do
147+ Just cipherResponse -> do
133− name <- decrypt cipher.name
148+ cipher <- decodeCipher cipherResponse
136− n | cipherTypeSecureNote == n -> do
137− note <- fromJNullable "" <$> (traverse decrypt cipher.notes)
138− pure $ Bridge.NoteCipher note
139− n | cipherTypeLogin == n -> do
140− case JNullable.toMaybe cipher.login of
141− Nothing -> throwError $ error "Login data is missing"
143− username <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt login.username)
144− password <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt login.password)
145− uris <- fromJOpt [] <$> ((traverse >>> traverse) (_.uri >>> decrypt) login.uris)
146− pure $ Bridge.LoginCipher $ Bridge.Cipher_LoginCipher
151− n | cipherTypeCard == n -> do
152− case JNullable.toMaybe cipher.card of
153− Nothing -> throwError $ error "Card data is missing"
155− cardholderName <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.cardholderName)
156− number <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.number)
157− code <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.code)
158− brand <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.brand)
159− expMonth <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.expMonth)
160− expYear <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.expYear)
161− pure $ Bridge.CardCipher $ Bridge.Cipher_CardCipher
162− { brand, cardholderName, code, expMonth, expYear, number }
163− n | cipherTypeIdentity == n -> do
164− case JNullable.toMaybe cipher.identity of
165− Nothing -> throwError $ error "Identity data is missing"
167− firstName <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.firstName)
168− middleName <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.middleName)
169− lastName <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.lastName)
170− address1 <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.address1)
171− address2 <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.address2)
172− address3 <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.address3)
173− city <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.city)
174− company <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.company)
175− country <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.country)
176− email <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.email)
177− licenseNumber <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.licenseNumber)
178− passportNumber <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.passportNumber)
179− phone <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.phone)
180− postalCode <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.postalCode)
181− ssn <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.ssn)
182− state <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.state)
183− title <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.title)
184− username <- wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.username)
185− pure $ Bridge.IdentityCipher $ wrap
205− n -> throwError $ error $ "Unsupported cipher type: " <> show n
206− send $ Bridge.LoadCipher $ Bridge.Sub_LoadCipher {cipherType, name, id}
149+ send $ Bridge.LoadCipher cipher
207− pure unit
150+ pure unit
208− pure unit
151+ pure unit
209− Bridge.NeedEmail -> run do
152+ Bridge.NeedEmail ->
210− sync <- getOrReset storage SyncKey
154+ sync <- getOrReset storage SyncKey
211− send $ Bridge.RecieveEmail sync.profile.email
155+ send $ Bridge.RecieveEmail sync.profile.email
212− pure unit
156+ pure unit
213− Bridge.Copy text -> run do
157+ Bridge.Copy text ->
214− liftPromise $ Clipboard.clipboard text
159+ liftPromise $ Clipboard.clipboard text
215 Bridge.Open uri -> openURL uri
160 Bridge.Open uri -> openURL uri
217−data SyncKey = SyncKey
219−instance StorageKey SyncKey SyncResponse where
220− storageKey SyncKey = "sync"
225−instance StorageKey TokenKey IdentityTokenResponse where
226− storageKey TokenKey = "token"
231−instance StorageKey UrlsKey String where
232− storageKey UrlsKey = "urls"
234−data PreloginResponseKey
235− = PreloginResponseKey
237−instance StorageKey PreloginResponseKey PreloginResponse where
238− storageKey PreloginResponseKey = "prelogin-reponse"
240−data MasterPasswordHashKey
241− = MasterPasswordHashKey
243−instance StorageKey MasterPasswordHashKey Hash where
244− storageKey MasterPasswordHashKey = "master-password-hash"
247processCipher ::
162processCipher ::
248 forall r.
163 forall r.
249 HasL "crypto" CryptoService r =>
164 HasL "crypto" CryptoService r =>
250 HasL "key" SymmetricCryptoKey r =>
165 HasL "key" SymmetricCryptoKey r =>
251− CipherResponse -> Al r {cipher :: Bridge.Sub_LoadCiphers, date :: DateTime}
166+ CipherResponse -> Al r { cipher :: Bridge.Sub_LoadCiphers, date :: DateTime }
252processCipher cipher = do
167processCipher cipher = do
253 name <- decrypt cipher.name
168 name <- decrypt cipher.name
254 cipherType <- case cipher.type of
169 cipherType <- case cipher.type of
255− n | n == cipherTypeLogin -> pure Bridge.LoginType
171+ | n == cipherTypeLogin -> pure Bridge.LoginType
256− n | n == cipherTypeSecureNote -> pure Bridge.NoteType
173+ | n == cipherTypeSecureNote -> pure Bridge.NoteType
257− n | n == cipherTypeCard -> pure Bridge.CardType
175+ | n == cipherTypeCard -> pure Bridge.CardType
258− n | n == cipherTypeIdentity -> pure Bridge.IdentityType
177+ | n == cipherTypeIdentity -> pure Bridge.IdentityType
259− n -> throwError $ error $ "Unsupported cipher type: " <> show n
178+ n -> throwError $ error $ "Unsupported cipher type: " <> show n
260 date <- liftEffect $ Timestamp.toDateTime cipher.revisionDate
179 date <- liftEffect $ Timestamp.toDateTime cipher.revisionDate
262− $ {cipher: Bridge.Sub_LoadCiphers
182+ Bridge.Sub_LoadCiphers
263− { name: name
183+ { name: name
264− , date: Timestamp.toLocalDateTimeString cipher.revisionDate
184+ , date: Timestamp.toLocalDateTimeString cipher.revisionDate
265− , id: cipher.id
185+ , id: cipher.id
266− , cipherType
186+ , cipherType
@@ -290,23 +210,30 @@ baseUrl server =
290 , keyConnector: jnull
210 , keyConnector: jnull
293−getOrReset :: forall k t r. HasL "app" Elm r =>
215+ HasL "app" Elm r =>
294 StorageKey k t => DecodeJson t => Storage -> k -> Al r t
216 StorageKey k t => DecodeJson t => Storage -> k -> Al r t
295getOrReset storage key = do
217getOrReset storage key = do
296− let handleError = do
219+ handleError = do
297− app <- l (L :: L "app")
220+ app <- l (L :: L "app")
298− liftEffect $ WebStorage.clear storage
221+ liftEffect $ WebStorage.clear storage
299− liftEffect $ Elm.send app Bridge.Reset
222+ liftEffect $ Elm.send app Bridge.Reset
300− liftEffect $ Exc.throw "Encountered an internal error. Resetting the app."
223+ liftEffect $ Exc.throw "Encountered an internal error. Resetting the app."
301− try (liftEffect $ Storage.get storage key) >>= \x -> case x of
224+ try (liftEffect $ Storage.get storage key)
302− Left err -> do
226+ Left err -> do
303− liftEffect $ log $ show err
227+ liftEffect $ log $ show err
304− handleError
228+ handleError
305− Right Nothing -> handleError
229+ Right Nothing -> handleError
306− Right (Just y) -> pure y
230+ Right (Just y) -> pure y
308−getAuthedApi :: forall r. HasL "services" Services r => HasL "storage" Storage r
234+ HasL "services" Services r =>
235+ HasL "storage" Storage r =>
309− => HasL "app" Elm r =>
236+ HasL "app" Elm r =>
310 Al r ApiService
237 Al r ApiService
311getAuthedApi = do
238getAuthedApi = do
312 storage <- l (L :: L "storage")
239 storage <- l (L :: L "storage")
@@ -322,7 +249,7 @@ requestMasterPassword = do
322 url <- getOrReset storage UrlsKey
249 url <- getOrReset storage UrlsKey
323 sync <- getOrReset storage SyncKey
250 sync <- getOrReset storage SyncKey
324 liftEffect $ Elm.send app $ Bridge.NeedsMasterPassword
251 liftEffect $ Elm.send app $ Bridge.NeedsMasterPassword
325− $ Bridge.Sub_NeedsMasterPassword {server: url, login: sync.profile.email}
252+ $ Bridge.Sub_NeedsMasterPassword { server: url, login: sync.profile.email }
327send :: forall r. HasL "app" Elm r => Bridge.Sub -> Al r Unit
254send :: forall r. HasL "app" Elm r => Bridge.Sub -> Al r Unit
328send sub = do
255send sub = do