@@ -3,15 +3,13 @@ module Main
33 ) where
44
55import Prelude
6−import BW (ApiService, CryptoService, Services)
6+import BW (ApiService, CryptoService, Services, CryptoFunctions)
77import BW as WB
88import BW.Logic (decodeCipher, decrypt, hashPassword, liftPromise)
99import BW.Logic as Logic
1010import BW.Types (CipherResponse, Email(..), Password(..), Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote)
1111import Bridge as Bridge
12−import Control.El (class HasL, Al, L(..), l)
1312import Control.Monad.Error.Class (catchError, throwError)
14−import Control.Monad.Reader (runReaderT)
1513import Data.Argonaut (class DecodeJson)
1614import Data.Array as Array
1715import Data.Clipboard as Clipboard
@@ -37,7 +35,11 @@ import FFI (Elm)
3735import FFI as FFI
3836import Localstorage (class StorageKey)
3937import Localstorage as Storage
38+import Run (AFF, EFFECT, Run, liftAff, runBaseAff')
39+import Run.Reader (Reader, askAt, runReaderAt)
4040import Storage (MasterPasswordHashKey(..), PreloginResponseKey(..), SyncKey(..), TokenKey(..), UrlsKey(..))
41+import Type.Prelude (Proxy(..))
42+import Type.Row (type (+))
4143import Untagged.Union (toEither1)
4244import Web.HTML as Html
4345import Web.HTML.Window as Window
@@ -55,21 +57,50 @@ main = do
5557 Ref.write (Just captchaToken) hCaptchaTokenRef
5658 Elm.send app Bridge.CaptchaDone
5759 let
58− run act = do
60+ runAff act = runElmAff app $ run $ act
61+
62+ run ::
63+ forall a.
64+ Run
65+ ( app :: Reader Elm
66+ , aff :: Aff
67+ , effect :: Effect
68+ , crypto :: Reader CryptoService
69+ , storage :: Reader Storage
70+ , services :: Reader Services
71+ , cryptoFunctions :: Reader CryptoFunctions
72+ )
73+ a ->
74+ Run (EFFECT + AFF + ()) a
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
81+ $ act
82+
83+ runWithDecryptionKey ::
84+ Run
85+ ( app :: Reader Elm
86+ , aff :: Aff
87+ , effect :: Effect
88+ , key :: Reader SymmetricCryptoKey
89+ , crypto :: Reader CryptoService
90+ , storage :: Reader Storage
91+ , services :: Reader Services
92+ , cryptoFunctions :: Reader CryptoFunctions
93+ )
94+ Unit ->
95+ Effect Unit
96+ runWithDecryptionKey act = do
5997 maybeKey <- Ref.read masterKeyRef
60− runElmAff app $ flip runReaderT { app: app, crypto: services.crypto, storage: storage }
98+ runAff
6199 $ case maybeKey of
62100 Just masterKey -> do
63101 token <- getOrReset TokenKey
64102 key <- Logic.makeDecryptionKey masterKey token.key
65− liftEffect $ runElmAff app
66− $ runReaderT act
67− { app
68− , storage
69− , services
70− , crypto: services.crypto
71− , key
72− }
103+ runReaderAt (Proxy :: _ "key") key $ act
73104 Nothing -> requestMasterPassword
74105 Elm.subscribe app \cmd -> case cmd of
75106 Bridge.Login (Bridge.Cmd_Login { email, server, password }) -> do
@@ -77,22 +108,17 @@ main = do
77108 Ref.write Nothing hCaptchaTokenRef
78109 let
79110 urls = baseUrl server
80− runElmAff app do
111+ runAff do
81112 unauthedApi <- liftPromise $ services.getApi urls jnull
82− let
83− env =
84− { api: unauthedApi
85− , crypto: services.crypto
86− , storage
87− , cryptoFunctions: services.cryptoFunctions
88− , app
89− }
90− flip runReaderT env do
91− prelogin <- liftPromise $ unauthedApi.postPrelogin { email }
113+ prelogin <- liftPromise $ unauthedApi.postPrelogin { email }
114+ runReaderAt (Proxy :: _ "api") unauthedApi do
92115 loginResponse <-
93− catchError
94− (Logic.getLogInRequestToken prelogin (Email email) (Password password) captchaToken)
95− (const $ liftEffect $ Exc.throw "Could not log in. Please check your data and try again.")
116+ liftAff
117+ $ catchError
118+ ( runBaseAff' $ run $ runReaderAt (Proxy :: _ "api") unauthedApi
119+ $ Logic.getLogInRequestToken prelogin (Email email) (Password password) captchaToken
120+ )
121+ (const $ liftEffect $ Exc.throw "Could not log in. Please check your data and try again.")
96122 case toEither1 loginResponse of
97123 Left { siteKey } -> send $ Bridge.NeedsCaptcha siteKey
98124 Right token -> do
@@ -110,7 +136,7 @@ main = do
110136 Storage.store storage TokenKey token
111137 send Bridge.LoginSuccessful
112138 Bridge.NeedCiphersList ->
113− run do
139+ runWithDecryptionKey do
114140 sync <- getOrReset SyncKey
115141 ciphers <- traverse processCipher sync.ciphers
116142 let
@@ -121,21 +147,18 @@ main = do
121147 WebStorage.clear storage
122148 Elm.send app Bridge.Reset
123149 Bridge.SendMasterPassword masterPassword ->
124− runElmAff app
125− $ do
126− flip runReaderT { app: app, storage: storage, crypto: services.crypto, cryptoFunctions: services.cryptoFunctions }
127− $ do
128− hash <- getOrReset MasterPasswordHashKey
129− newHash <- hashPassword (Password masterPassword)
130− if hash == newHash then do
131− sync <- getOrReset SyncKey
132− prelogin <- getOrReset PreloginResponseKey
133− masterKey <- Logic.makePreloginKey prelogin (Email sync.profile.email) (Password masterPassword)
134− liftEffect $ Ref.write (Just masterKey) masterKeyRef
135− send Bridge.LoginSuccessful
136− else do
137− send $ Bridge.Error "The password is wrong. Please try again."
138− requestMasterPassword
150+ runAff do
151+ hash <- getOrReset MasterPasswordHashKey
152+ newHash <- hashPassword (Password masterPassword)
153+ if hash == newHash then do
154+ sync <- getOrReset SyncKey
155+ prelogin <- getOrReset PreloginResponseKey
156+ masterKey <- Logic.makePreloginKey prelogin (Email sync.profile.email) (Password masterPassword)
157+ liftEffect $ Ref.write (Just masterKey) masterKeyRef
158+ send Bridge.LoginSuccessful
159+ else do
160+ send $ Bridge.Error "The password is wrong. Please try again."
161+ requestMasterPassword
139162 Bridge.Init -> do
140163 liftEffect $ Storage.get storage TokenKey
141164 >>= \x ->
@@ -143,7 +166,7 @@ main = do
143166 Just _ -> Bridge.LoginSuccessful
144167 Nothing -> Bridge.NeedsLogin
145168 Bridge.RequestCipher id ->
146− run do
169+ runWithDecryptionKey do
147170 sync <- getOrReset SyncKey
148171 case Array.find (\c -> c.id == id) sync.ciphers of
149172 Nothing -> pure unit
@@ -153,20 +176,26 @@ main = do
153176 pure unit
154177 pure unit
155178 Bridge.NeedEmail ->
156− run do
179+ runWithDecryptionKey do
157180 sync <- getOrReset SyncKey
158181 send $ Bridge.RecieveEmail sync.profile.email
159182 pure unit
160183 Bridge.Copy text ->
161− run do
184+ runWithDecryptionKey do
162185 liftPromise $ Clipboard.clipboard text
163186 Bridge.Open uri -> openURL uri
164187
165188processCipher ::
166189 forall r.
167− HasL "crypto" CryptoService r =>
168− HasL "key" SymmetricCryptoKey r =>
169− CipherResponse -> Al r { cipher :: Bridge.Sub_LoadCiphers, date :: DateTime }
190+ CipherResponse ->
191+ Run
192+ ( crypto :: Reader CryptoService
193+ , key :: Reader SymmetricCryptoKey
194+ , aff :: Aff
195+ , effect :: Effect
196+ | r
197+ )
198+ { cipher :: Bridge.Sub_LoadCiphers, date :: DateTime }
170199processCipher cipher = do
171200 name <- decrypt cipher.name
172201 cipherType <- case cipher.type of
@@ -178,7 +207,7 @@ processCipher cipher = do
178207 | n == cipherTypeCard -> pure Bridge.CardType
179208 n
180209 | n == cipherTypeIdentity -> pure Bridge.IdentityType
181− n -> throwError $ error $ "Unsupported cipher type: " <> show n
210+ n -> liftEffect $ throwError $ error $ "Unsupported cipher type: " <> show n
182211 date <- liftEffect $ Timestamp.toDateTime cipher.revisionDate
183212 pure
184213 $ { cipher:
@@ -191,15 +220,17 @@ processCipher cipher = do
191220 , date
192221 }
193222
194−runElmAff :: FFI.Elm -> Aff Unit -> Effect Unit
223+runElmAff :: FFI.Elm -> Run (AFF + EFFECT + ()) Unit -> Effect Unit
195224runElmAff app =
196225 runAff_
197− $ \res -> do
226+ ( \res -> do
198227 case res of
199228 Left e -> do
200229 log $ show e
201230 Elm.send app (Bridge.Error $ Exc.message e)
202231 Right unit -> pure unit
232+ )
233+ <<< runBaseAff'
203234
204235baseUrl :: String -> Urls
205236baseUrl server =
@@ -215,46 +246,72 @@ baseUrl server =
215246
216247getOrReset ::
217248 forall k t r.
218− HasL "app" Elm r =>
219− HasL "storage" Storage r =>
220249 StorageKey k t =>
221250 DecodeJson t =>
222− k -> Al r t
251+ k ->
252+ Run
253+ ( storage :: Reader Storage
254+ , app :: Reader Elm
255+ , effect :: Effect
256+ | r
257+ )
258+ t
223259getOrReset key = do
224− storage <- l (L :: L "storage")
260+ storage <- askAt (Proxy :: _ "storage")
261+ app <- askAt (Proxy :: _ "app")
225262 let
226263 handleError = do
227− app <- l (L :: L "app")
228− liftEffect $ WebStorage.clear storage
229− liftEffect $ Elm.send app Bridge.Reset
230− liftEffect $ Exc.throw "Encountered an internal error. Resetting the app."
231− try (liftEffect $ Storage.get storage key)
264+ WebStorage.clear storage
265+ Elm.send app Bridge.Reset
266+ Exc.throw "Encountered an internal error. Resetting the app."
267+ liftEffect $ try (Storage.get storage key)
232268 >>= \x -> case x of
233269 Left err -> do
234− liftEffect $ log $ show err
270+ log $ show err
235271 handleError
236272 Right Nothing -> handleError
237273 Right (Just y) -> pure y
238274
239275getAuthedApi ::
240276 forall r.
241− HasL "services" Services r =>
242− HasL "storage" Storage r =>
243− HasL "app" Elm r =>
244− Al r ApiService
277+ Run
278+ ( storage :: Reader Storage
279+ , app :: Reader Elm
280+ , aff :: Aff
281+ , effect :: Effect
282+ , services :: Reader Services
283+ | r
284+ )
285+ ApiService
245286getAuthedApi = do
246− services <- l (L :: L "services")
287+ services <- askAt (Proxy :: _ "services")
247288 token <- getOrReset TokenKey
248289 url <- getOrReset UrlsKey
249290 liftPromise $ services.getApi (baseUrl url) (nullify token)
250291
251−requestMasterPassword :: forall r. HasL "storage" Storage r => HasL "app" Elm r => Al r Unit
292+requestMasterPassword ::
293+ forall r.
294+ Run
295+ ( storage :: Reader Storage
296+ , app :: Reader Elm
297+ , effect :: Effect
298+ | r
299+ )
300+ Unit
252301requestMasterPassword = do
253302 url <- getOrReset UrlsKey
254303 sync <- getOrReset SyncKey
255304 send $ Bridge.NeedsMasterPassword $ Bridge.Sub_NeedsMasterPassword { server: url, login: sync.profile.email }
256305
257−send :: forall r. HasL "app" Elm r => Bridge.Sub -> Al r Unit
306+send ::
307+ forall r.
308+ Bridge.Sub ->
309+ Run
310+ ( app :: Reader Elm
311+ , effect :: Effect
312+ | r
313+ )
314+ Unit
258315send sub = do
259− app <- l (L :: L "app")
316+ app <- askAt (Proxy :: _ "app")
260317 liftEffect $ Elm.send app sub