@@ -3,15 +3,13 @@ module Main
5import Prelude
5import Prelude
6−import BW (ApiService, CryptoService, Services)
6+import BW (ApiService, CryptoService, Services, CryptoFunctions)
7import BW as WB
7import BW as WB
8import BW.Logic (decodeCipher, decrypt, hashPassword, liftPromise)
8import BW.Logic (decodeCipher, decrypt, hashPassword, liftPromise)
9import BW.Logic as Logic
9import BW.Logic as Logic
10import BW.Types (CipherResponse, Email(..), Password(..), Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote)
10import BW.Types (CipherResponse, Email(..), Password(..), Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote)
11import Bridge as Bridge
11import Bridge as Bridge
12−import Control.El (class HasL, Al, L(..), l)
13import Control.Monad.Error.Class (catchError, throwError)
12import Control.Monad.Error.Class (catchError, throwError)
14−import Control.Monad.Reader (runReaderT)
15import Data.Argonaut (class DecodeJson)
13import Data.Argonaut (class DecodeJson)
16import Data.Array as Array
14import Data.Array as Array
17import Data.Clipboard as Clipboard
15import Data.Clipboard as Clipboard
@@ -37,7 +35,11 @@ import FFI (Elm)
37import FFI as FFI
35import FFI as FFI
38import Localstorage (class StorageKey)
36import Localstorage (class StorageKey)
39import Localstorage as Storage
37import Localstorage as Storage
38+import Run (AFF, EFFECT, Run, liftAff, runBaseAff')
39+import Run.Reader (Reader, askAt, runReaderAt)
40import Storage (MasterPasswordHashKey(..), PreloginResponseKey(..), SyncKey(..), TokenKey(..), UrlsKey(..))
40import Storage (MasterPasswordHashKey(..), PreloginResponseKey(..), SyncKey(..), TokenKey(..), UrlsKey(..))
41+import Type.Prelude (Proxy(..))
42+import Type.Row (type (+))
41import Untagged.Union (toEither1)
43import Untagged.Union (toEither1)
42import Web.HTML as Html
44import Web.HTML as Html
43import Web.HTML.Window as Window
45import Web.HTML.Window as Window
@@ -55,21 +57,50 @@ main = do
55 Ref.write (Just captchaToken) hCaptchaTokenRef
57 Ref.write (Just captchaToken) hCaptchaTokenRef
56 Elm.send app Bridge.CaptchaDone
58 Elm.send app Bridge.CaptchaDone
60+ runAff act = runElmAff app $ run $ act
68+ , crypto :: Reader CryptoService
69+ , storage :: Reader Storage
70+ , services :: Reader Services
71+ , cryptoFunctions :: Reader CryptoFunctions
74+ Run (EFFECT + AFF + ()) a
58− run act = do
75+ run act =
76+ runReaderAt (Proxy :: _ "app") app
77+ $ runReaderAt (Proxy :: _ "crypto") services.crypto
78+ $ runReaderAt (Proxy :: _ "storage") storage
79+ $ runReaderAt (Proxy :: _ "services") services
80+ $ runReaderAt (Proxy :: _ "cryptoFunctions") services.cryptoFunctions
83+ runWithDecryptionKey ::
88+ , key :: Reader SymmetricCryptoKey
89+ , crypto :: Reader CryptoService
90+ , storage :: Reader Storage
91+ , services :: Reader Services
92+ , cryptoFunctions :: Reader CryptoFunctions
96+ runWithDecryptionKey act = do
59 maybeKey <- Ref.read masterKeyRef
97 maybeKey <- Ref.read masterKeyRef
60− runElmAff app $ flip runReaderT { app: app, crypto: services.crypto, storage: storage }
98+ runAff
61 $ case maybeKey of
99 $ case maybeKey of
62 Just masterKey -> do
100 Just masterKey -> do
63 token <- getOrReset TokenKey
101 token <- getOrReset TokenKey
64 key <- Logic.makeDecryptionKey masterKey token.key
102 key <- Logic.makeDecryptionKey masterKey token.key
65− liftEffect $ runElmAff app
103+ runReaderAt (Proxy :: _ "key") key $ act
70− , crypto: services.crypto
73 Nothing -> requestMasterPassword
104 Nothing -> requestMasterPassword
74 Elm.subscribe app \cmd -> case cmd of
105 Elm.subscribe app \cmd -> case cmd of
75 Bridge.Login (Bridge.Cmd_Login { email, server, password }) -> do
106 Bridge.Login (Bridge.Cmd_Login { email, server, password }) -> do
@@ -77,22 +108,17 @@ main = do
77 Ref.write Nothing hCaptchaTokenRef
108 Ref.write Nothing hCaptchaTokenRef
79 urls = baseUrl server
110 urls = baseUrl server
80− runElmAff app do
111+ runAff do
81 unauthedApi <- liftPromise $ services.getApi urls jnull
112 unauthedApi <- liftPromise $ services.getApi urls jnull
85− , crypto: services.crypto
87− , cryptoFunctions: services.cryptoFunctions
90− flip runReaderT env do
91− prelogin <- liftPromise $ unauthedApi.postPrelogin { email }
113+ prelogin <- liftPromise $ unauthedApi.postPrelogin { email }
114+ runReaderAt (Proxy :: _ "api") unauthedApi do
92 loginResponse <-
115 loginResponse <-
93− catchError
117+ $ catchError
118+ ( runBaseAff' $ run $ runReaderAt (Proxy :: _ "api") unauthedApi
94− (Logic.getLogInRequestToken prelogin (Email email) (Password password) captchaToken)
119+ $ Logic.getLogInRequestToken prelogin (Email email) (Password password) captchaToken
95− (const $ liftEffect $ Exc.throw "Could not log in. Please check your data and try again.")
121+ (const $ liftEffect $ Exc.throw "Could not log in. Please check your data and try again.")
96 case toEither1 loginResponse of
122 case toEither1 loginResponse of
97 Left { siteKey } -> send $ Bridge.NeedsCaptcha siteKey
123 Left { siteKey } -> send $ Bridge.NeedsCaptcha siteKey
98 Right token -> do
124 Right token -> do
@@ -110,7 +136,7 @@ main = do
110 Storage.store storage TokenKey token
136 Storage.store storage TokenKey token
111 send Bridge.LoginSuccessful
137 send Bridge.LoginSuccessful
112 Bridge.NeedCiphersList ->
138 Bridge.NeedCiphersList ->
113− run do
139+ runWithDecryptionKey do
114 sync <- getOrReset SyncKey
140 sync <- getOrReset SyncKey
115 ciphers <- traverse processCipher sync.ciphers
141 ciphers <- traverse processCipher sync.ciphers
@@ -121,21 +147,18 @@ main = do
121 WebStorage.clear storage
147 WebStorage.clear storage
122 Elm.send app Bridge.Reset
148 Elm.send app Bridge.Reset
123 Bridge.SendMasterPassword masterPassword ->
149 Bridge.SendMasterPassword masterPassword ->
126− flip runReaderT { app: app, storage: storage, crypto: services.crypto, cryptoFunctions: services.cryptoFunctions }
128− hash <- getOrReset MasterPasswordHashKey
151+ hash <- getOrReset MasterPasswordHashKey
129− newHash <- hashPassword (Password masterPassword)
152+ newHash <- hashPassword (Password masterPassword)
130− if hash == newHash then do
153+ if hash == newHash then do
131− sync <- getOrReset SyncKey
154+ sync <- getOrReset SyncKey
132− prelogin <- getOrReset PreloginResponseKey
155+ prelogin <- getOrReset PreloginResponseKey
133− masterKey <- Logic.makePreloginKey prelogin (Email sync.profile.email) (Password masterPassword)
156+ masterKey <- Logic.makePreloginKey prelogin (Email sync.profile.email) (Password masterPassword)
134− liftEffect $ Ref.write (Just masterKey) masterKeyRef
157+ liftEffect $ Ref.write (Just masterKey) masterKeyRef
135− send Bridge.LoginSuccessful
158+ send Bridge.LoginSuccessful
137− send $ Bridge.Error "The password is wrong. Please try again."
160+ send $ Bridge.Error "The password is wrong. Please try again."
138− requestMasterPassword
161+ requestMasterPassword
139 Bridge.Init -> do
162 Bridge.Init -> do
140 liftEffect $ Storage.get storage TokenKey
163 liftEffect $ Storage.get storage TokenKey
141 >>= \x ->
164 >>= \x ->
@@ -143,7 +166,7 @@ main = do
143 Just _ -> Bridge.LoginSuccessful
166 Just _ -> Bridge.LoginSuccessful
144 Nothing -> Bridge.NeedsLogin
167 Nothing -> Bridge.NeedsLogin
145 Bridge.RequestCipher id ->
168 Bridge.RequestCipher id ->
146− run do
169+ runWithDecryptionKey do
147 sync <- getOrReset SyncKey
170 sync <- getOrReset SyncKey
148 case Array.find (\c -> c.id == id) sync.ciphers of
171 case Array.find (\c -> c.id == id) sync.ciphers of
149 Nothing -> pure unit
172 Nothing -> pure unit
@@ -153,20 +176,26 @@ main = do
153 pure unit
176 pure unit
154 pure unit
177 pure unit
155 Bridge.NeedEmail ->
178 Bridge.NeedEmail ->
156− run do
179+ runWithDecryptionKey do
157 sync <- getOrReset SyncKey
180 sync <- getOrReset SyncKey
158 send $ Bridge.RecieveEmail sync.profile.email
181 send $ Bridge.RecieveEmail sync.profile.email
159 pure unit
182 pure unit
160 Bridge.Copy text ->
183 Bridge.Copy text ->
161− run do
184+ runWithDecryptionKey do
162 liftPromise $ Clipboard.clipboard text
185 liftPromise $ Clipboard.clipboard text
163 Bridge.Open uri -> openURL uri
186 Bridge.Open uri -> openURL uri
165processCipher ::
188processCipher ::
166 forall r.
189 forall r.
167− HasL "crypto" CryptoService r =>
192+ ( crypto :: Reader CryptoService
168− HasL "key" SymmetricCryptoKey r =>
193+ , key :: Reader SymmetricCryptoKey
169− CipherResponse -> Al r { cipher :: Bridge.Sub_LoadCiphers, date :: DateTime }
198+ { cipher :: Bridge.Sub_LoadCiphers, date :: DateTime }
170processCipher cipher = do
199processCipher cipher = do
171 name <- decrypt cipher.name
200 name <- decrypt cipher.name
172 cipherType <- case cipher.type of
201 cipherType <- case cipher.type of
@@ -178,7 +207,7 @@ processCipher cipher = do
178 | n == cipherTypeCard -> pure Bridge.CardType
207 | n == cipherTypeCard -> pure Bridge.CardType
180 | n == cipherTypeIdentity -> pure Bridge.IdentityType
209 | n == cipherTypeIdentity -> pure Bridge.IdentityType
181− n -> throwError $ error $ "Unsupported cipher type: " <> show n
210+ n -> liftEffect $ throwError $ error $ "Unsupported cipher type: " <> show n
182 date <- liftEffect $ Timestamp.toDateTime cipher.revisionDate
211 date <- liftEffect $ Timestamp.toDateTime cipher.revisionDate
184 $ { cipher:
213 $ { cipher:
@@ -191,15 +220,17 @@ processCipher cipher = do
194−runElmAff :: FFI.Elm -> Aff Unit -> Effect Unit
223+runElmAff :: FFI.Elm -> Run (AFF + EFFECT + ()) Unit -> Effect Unit
195runElmAff app =
224runElmAff app =
197− $ \res -> do
226+ ( \res -> do
198 case res of
227 case res of
199 Left e -> do
228 Left e -> do
200 log $ show e
229 log $ show e
201 Elm.send app (Bridge.Error $ Exc.message e)
230 Elm.send app (Bridge.Error $ Exc.message e)
202 Right unit -> pure unit
231 Right unit -> pure unit
204baseUrl :: String -> Urls
235baseUrl :: String -> Urls
205baseUrl server =
236baseUrl server =
@@ -215,46 +246,72 @@ baseUrl server =
216getOrReset ::
247getOrReset ::
217 forall k t r.
248 forall k t r.
219− HasL "storage" Storage r =>
220 StorageKey k t =>
249 StorageKey k t =>
221 DecodeJson t =>
250 DecodeJson t =>
222− k -> Al r t
251+ k ->
253+ ( storage :: Reader Storage
223getOrReset key = do
259getOrReset key = do
224− storage <- l (L :: L "storage")
260+ storage <- askAt (Proxy :: _ "storage")
261+ app <- askAt (Proxy :: _ "app")
226 handleError = do
263 handleError = do
227− app <- l (L :: L "app")
228− liftEffect $ WebStorage.clear storage
264+ WebStorage.clear storage
229− liftEffect $ Elm.send app Bridge.Reset
265+ Elm.send app Bridge.Reset
230− liftEffect $ Exc.throw "Encountered an internal error. Resetting the app."
266+ Exc.throw "Encountered an internal error. Resetting the app."
231− try (liftEffect $ Storage.get storage key)
267+ liftEffect $ try (Storage.get storage key)
232 >>= \x -> case x of
268 >>= \x -> case x of
233 Left err -> do
269 Left err -> do
234− liftEffect $ log $ show err
270+ log $ show err
235 handleError
271 handleError
236 Right Nothing -> handleError
272 Right Nothing -> handleError
237 Right (Just y) -> pure y
273 Right (Just y) -> pure y
239getAuthedApi ::
275getAuthedApi ::
240 forall r.
276 forall r.
241− HasL "services" Services r =>
277+ Run
242− HasL "storage" Storage r =>
278+ ( storage :: Reader Storage
243− HasL "app" Elm r =>
279+ , app :: Reader Elm
282+ , services :: Reader Services
244− Al r ApiService
285+ ApiService
245getAuthedApi = do
286getAuthedApi = do
246− services <- l (L :: L "services")
287+ services <- askAt (Proxy :: _ "services")
247 token <- getOrReset TokenKey
288 token <- getOrReset TokenKey
248 url <- getOrReset UrlsKey
289 url <- getOrReset UrlsKey
249 liftPromise $ services.getApi (baseUrl url) (nullify token)
290 liftPromise $ services.getApi (baseUrl url) (nullify token)
251−requestMasterPassword :: forall r. HasL "storage" Storage r => HasL "app" Elm r => Al r Unit
292+requestMasterPassword ::
295+ ( storage :: Reader Storage
252requestMasterPassword = do
301requestMasterPassword = do
253 url <- getOrReset UrlsKey
302 url <- getOrReset UrlsKey
254 sync <- getOrReset SyncKey
303 sync <- getOrReset SyncKey
255 send $ Bridge.NeedsMasterPassword $ Bridge.Sub_NeedsMasterPassword { server: url, login: sync.profile.email }
304 send $ Bridge.NeedsMasterPassword $ Bridge.Sub_NeedsMasterPassword { server: url, login: sync.profile.email }
257−send :: forall r. HasL "app" Elm r => Bridge.Sub -> Al r Unit
306+send ::
258send sub = do
315send sub = do
259− app <- l (L :: L "app")
316+ app <- askAt (Proxy :: _ "app")
260 liftEffect $ Elm.send app sub
317 liftEffect $ Elm.send app sub