on master help sign in
Got sync working
commitfbd470b9087a4436b3bd5cf39bbed6a982299782 authoriko <ilyakooo0@gmail.com> dateWed 27 Jul 2022, 13:36 (4 years ago) parent0b9dcd7
19 files changed+814−529
TODO.md+1 backend/Bridge.purs+6−2 backend/Elm.purs+18−8 backend/FFI.purs+1−3 backend/Main.purs+69−55 bridge.yaml+3−1 bw/src/BW.purs+19−19 bw/src/BW/Logic.purs+63−25 bw/src/BW/Types.purs+515−397 bw/src/Data/EncString.js+8−2 bw/src/Data/EncString.purs+21−1 bw/src/Data/SymmetricCryptoKey.js+6 bw/src/Data/SymmetricCryptoKey.purs+34 dev/start-chrome.sh+3 frontend/Bridge.elm+19−5 frontend/Main.elm+21−8 frontend/Pages/Login.elm+2−2 frontend/Pages/Navigation.elm+1−1 spago.dhall+4
TODO.mdnew file+1
@@ -0,0 +1 @@
1+- alternarive login paths
backend/Bridge.purs+6−2
@@ -27,8 +27,9 @@ derive instance genericCmd_Login :: Generic Cmd_Login _
2727derive instance eqCmd_Login :: Eq Cmd_Login
2828derive instance ordCmd_Login :: Ord Cmd_Login
2929
30−newtype Cmd =
31− Login Cmd_Login
30+data Cmd =
31+ Init
32+ | Login Cmd_Login
3233
3334instance encodeJsonCmd :: EncodeJson Cmd where
3435 encodeJson = genericEncodeAeson Argonaut.defaultOptions
@@ -54,8 +55,10 @@ derive instance ordSub_Hello :: Ord Sub_Hello
5455
5556data Sub =
5657 Empty
58+ | Error String
5759 | GotEverything String
5860 | Hello Sub_Hello
61+ | NeedsLogin
5962
6063instance encodeJsonSub :: EncodeJson Sub where
6164 encodeJson = genericEncodeAeson Argonaut.defaultOptions
@@ -64,3 +67,4 @@ instance decodeJsonSub :: DecodeJson Sub where
6467derive instance genericSub :: Generic Sub _
6568derive instance eqSub :: Eq Sub
6669derive instance ordSub :: Ord Sub
70+
backend/Elm.purs+18−8
@@ -1,24 +1,34 @@
11module Elm
22 ( send
33 , subscribe
4− )
5− where
4+ ) where
65
76import Data.Either
87import Prelude
9−
108import Bridge as Bridge
119import Data.Argonaut (decodeJson, encodeJson, stringify)
1210import Effect (Effect)
1311import Effect.Class.Console (log)
1412import FFI as FFI
13+import Effect.Exception (try)
1514
1615send ∷ FFI.Elm → Bridge.Sub → Effect Unit
1716send elm a = FFI.sendElmEvent elm "bridgeSub" (encodeJson a)
1817
1918subscribe :: FFI.Elm → (Bridge.Cmd → Effect Unit) -> Effect Unit
20−subscribe elm f = FFI.createElmSubscription elm "bridgeCmd" $ \a → do
21− log $ stringify a
22− case decodeJson a of
23− Right (cmd :: Bridge.Cmd) -> f cmd
24− Left err -> log $ show err
19+subscribe elm f =
20+ FFI.createElmSubscription elm "bridgeCmd"
21+ $ \a → do
22+ log $ stringify a
23+ result <- case decodeJson a of
24+ Right (cmd :: Bridge.Cmd) -> do
25+ result <- try $ f cmd
26+ case result of
27+ Left err -> pure $ Left $ show err
28+ Right _ -> pure $ Right unit
29+ Left err -> pure $ Left $ show err
30+ case result of
31+ Left err -> do
32+ log err
33+ send elm $ Bridge.Error err
34+ Right _ -> pure unit
backend/FFI.purs+1−3
@@ -3,11 +3,9 @@ module FFI
33 , createElmSubscription
44 , sendElmEvent
55 , startElm
6− )
7− where
6+ ) where
87
98import Prelude
10−
119import Data.Argonaut (Json)
1210import Effect (Effect)
1311
backend/Main.purs+69−55
@@ -1,77 +1,91 @@
1−module Main where
1+module Main
2+ ( main
3+ ) where
24
35import Prelude
4−
56import BW as WB
7+import BW.Logic (decrypt)
68import BW.Logic as Logic
7−import BW.Types (Email(..), Password(..))
8−import Bridge (Sub(..))
9+import BW.Types (Email(..), EncryptedString(..), Password(..), Urls)
910import Bridge as Bridge
1011import Control.Monad.Reader (runReaderT)
12+import Control.Promise (Promise)
1113import Control.Promise as Promise
14+import Data.Argonaut (class EncodeJson, encodeJson)
15+import Data.Argonaut as Json
1216import Data.Either (Either(..))
13−import Data.Interval (second)
1417import Data.Maybe (Maybe(..))
15−import Data.Nullable (null, toNullable)
18+import Data.Nullable (Nullable, null, toNullable)
19+import Data.Nullable as Nullable
20+import Data.Traversable (traverse)
1621import Effect (Effect)
17−import Effect.Aff (runAff_)
22+import Effect.Aff (Aff, runAff_, try)
23+import Effect.Aff as Aff
24+import Effect.Aff.Class (class MonadAff, liftAff)
1825import Effect.Aff.Class as Aff
1926import Effect.Class (liftEffect)
20−import Effect.Console (log)
27+import Effect.Class.Console (log)
28+import Effect.Exception as Exc
2129import Elm as Elm
2230import FFI as FFI
23−
31+import Prelude as Array
32+import Unsafe.Coerce (unsafeCoerce)
2433
2534main :: Effect Unit
2635main = do
2736 app <- FFI.startElm "elm"
28− let
29− processResult res = do
30− log res
31− Elm.send app (GotEverything res)
32− Elm.subscribe app \(Bridge.Login (Bridge.Cmd_Login {email, server, password})) -> do
33− log $ "Got cmd"
34− services <- WB.getServices
35− runAff_ (\res -> case res of
36− Left e -> processResult $ show e
37− Right x -> processResult $ show x
38− ) do
39− unauthedApi <- Aff.liftAff $ Promise.toAff $ services.getApi
40− { base : toNullable $ Just server,
41− webVault : null,
42− api : null,
43− identity : null,
44− icons : null,
45− notifications : null,
46− events : null,
47− keyConnector : null
48− }
49− null
50−
51− runReaderT (do
52− tokens <- Logic.getLogInRequestToken (Email email) (Password password)
53−
54− api <- Aff.liftAff $ Promise.toAff $ services.getApi
55− { base : toNullable $ Just server,
56− webVault : null,
57− api : null,
58− identity : null,
59− icons : null,
60− notifications : null,
61− events : null,
62− keyConnector : null
63− }
64− (toNullable $ Just tokens)
65−
37+ services <- WB.getServices
38+ Elm.subscribe app \cmd -> case cmd of
39+ Bridge.Login (Bridge.Cmd_Login { email, server, password }) -> do
40+ let
41+ urls = baseUrl server
42+ runElmAff app do
43+ unauthedApi <- liftPromise $ services.getApi urls null
44+ { masterKey, token } <-
45+ flip runReaderT { api: unauthedApi, crypto: services.crypto } do
46+ token <-
47+ try (Logic.getLogInRequestToken (Email email) (Password password))
48+ >>= \et -> case et of
49+ Left _ -> liftEffect $ Exc.throw "Could not log in. Please check your data and try again."
50+ Right t -> pure t
51+ masterKey <- Logic.makePreloginKey (Email email) (Password password)
52+ pure { token, masterKey }
53+ api <- liftPromise $ services.getApi urls (nullify token)
54+ _ <-
55+ flip runReaderT { api: api, crypto: services.crypto } do
56+ sync <- liftPromise $ api.getSync unit
57+ key <- Logic.makeDecryptionKey masterKey sync.profile.key
58+ log $ show key.keyB64
59+ ciphersNames <- traverse (decrypt key) $ map _.name sync.ciphers
60+ liftEffect $ Elm.send app $ Bridge.Hello $ Bridge.Sub_Hello { first: "hello", second: show ciphersNames }
61+ pure unit
62+ pure unit
63+ Bridge.Init -> Elm.send app (Bridge.NeedsLogin)
6664
67− Aff.liftAff $ Promise.toAff $ api.getSync unit
68− ) {
69− api: unauthedApi,
70− crypto : services.crypto
71− }
65+liftPromise ∷ forall m a. MonadAff m ⇒ Promise a → m a
66+liftPromise = Aff.liftAff <<< Promise.toAff
7267
73− Elm.send app (Bridge.Hello (Bridge.Sub_Hello {first: "f", second: "u"}))
68+runElmAff :: FFI.Elm -> Aff Unit -> Effect Unit
69+runElmAff app =
70+ runAff_
71+ $ \res -> do
72+ case res of
73+ Left e -> do
74+ log $ show e
75+ Elm.send app (Bridge.Error $ Exc.message e)
76+ Right unit -> pure unit
7477
75− Elm.send app (Bridge.Empty)
78+baseUrl :: String -> Urls
79+baseUrl server =
80+ { base: nullify server
81+ , webVault: null
82+ , api: null
83+ , identity: null
84+ , icons: null
85+ , notifications: null
86+ , events: null
87+ , keyConnector: null
88+ }
7689
77− log "🍝"
90+nullify :: forall a. a -> Nullable a
91+nullify = Just >>> toNullable
bridge.yaml+3−1
@@ -2,11 +2,13 @@ Sub:
22- Hello:
33 first: String
44 second: String
5−
65- GotEverything: String
76- Empty
7+- NeedsLogin
8+- Error: String
89
910Cmd:
11+- Init
1012- Login:
1113 email: String
1214 server: String
bw/src/BW.purs+19−19
@@ -2,33 +2,33 @@ module BW where
22
33import BW.Types
44import Prelude
5−
65import Control.Promise (Promise)
76import Data.ArrayBuffer.Types (ArrayBuffer)
87import Data.EncString (EncString)
98import Data.Function.Uncurried (Fn2, Fn3, Fn4)
109import Data.Nullable (Nullable)
10+import Data.SymmetricCryptoKey (SymmetricCryptoKey)
1111import Effect (Effect)
1212
13−type CryptoService = {
14− makeKey :: Fn4 Password String KDF Int (Promise SymmetricCryptoKey),
15− hashPassword :: Fn3 Password SymmetricCryptoKey (Nullable HashPurpose) (Promise Hash),
16− decryptToUtf8 :: Fn2 EncString SymmetricCryptoKey (Promise String),
17− decryptToBytes :: Fn2 EncString SymmetricCryptoKey (Promise ArrayBuffer)
18−}
19−
20−type ApiService = {
21− postPrelogin :: PreloginRequest -> Promise PreloginResponse,
22− getProfile :: Unit -> Promise ProfileResponse,
13+type CryptoService
14+ = { makeKey :: Fn4 Password String KDF Int (Promise SymmetricCryptoKey)
15+ , hashPassword :: Fn3 Password SymmetricCryptoKey (Nullable HashPurpose) (Promise Hash)
16+ , decryptToUtf8 :: Fn2 EncString SymmetricCryptoKey (Promise String)
17+ , decryptToBytes :: Fn2 EncString SymmetricCryptoKey (Promise ArrayBuffer)
18+ , stretchKey :: SymmetricCryptoKey -> Promise SymmetricCryptoKey
19+ }
2320
24− -- TODO: There can really be more request and response types. Maybe handle this later.
25− postIdentityToken :: PasswordTokenRequest -> Promise IdentityTokenResponse,
26− getSync :: Unit -> Promise SyncResponse
27−}
21+type ApiService
22+ = { postPrelogin :: PreloginRequest -> Promise PreloginResponse
23+ , getProfile :: Unit -> Promise ProfileResponse
24+ -- TODO: There can really be more request and response types. Maybe handle this later.
25+ , postIdentityToken :: PasswordTokenRequest -> Promise IdentityTokenResponse
26+ , getSync :: Unit -> Promise SyncResponse
27+ }
2828
29−type Services = {
30− crypto :: CryptoService,
31− getApi :: Urls -> Nullable IdentityTokenResponse -> Promise ApiService
32−}
29+type Services
30+ = { crypto :: CryptoService
31+ , getApi :: Urls -> Nullable IdentityTokenResponse -> Promise ApiService
32+ }
3333
3434foreign import getServices :: Effect Services
bw/src/BW/Logic.purs+63−25
@@ -4,13 +4,20 @@ import BW
44import BW.Types
55import Control.El
66import Prelude
7−
7+import Control.Monad.Error.Class (throwError)
88import Control.Promise as Promise
9−import Data.Function.Uncurried (runFn3, runFn4)
9+import Data.EncString as EncString
10+import Data.Function.Uncurried (runFn2, runFn3, runFn4)
1011import Data.Maybe (Maybe(..))
1112import Data.Nullable (null, toNullable)
1213import Data.String as String
14+import Data.SymmetricCryptoKey (SymmetricCryptoKey)
15+import Data.SymmetricCryptoKey as SymmetricCryptoKey
1316import Effect.Aff.Class (liftAff)
17+import Effect.Class (liftEffect)
18+import Effect.Class.Console (log)
19+import Effect.Exception (error)
20+import Effect.Exception as Exc
1421
1522makePreloginKey ::
1623 forall r.
@@ -20,11 +27,31 @@ makePreloginKey ::
2027makePreloginKey (Email email') (Password password) = do
2128 api :: ApiService <- l (L :: L "api")
2229 crypto <- l (L :: L "crypto")
30+ let
31+ email = (String.trim >>> String.toLower) email'
32+ { kdf, kdfIterations } <- liftPromise $ api.postPrelogin { email }
33+ liftPromise $ runFn4 crypto.makeKey (Password password) email kdf kdfIterations
2334
24− let email = (String.trim >>> String.toLower) email'
35+makeDecryptionKey ::
36+ forall r.
37+ HasL "crypto" CryptoService r =>
38+ -- | Matser key (prelogin key)
39+ SymmetricCryptoKey -> EncryptedString -> Al r SymmetricCryptoKey
40+makeDecryptionKey masterKey str = do
41+ crypto <- l (L :: L "crypto")
42+ let
43+ encryptedKeyString = EncString.fromString str
2544
26− {kdf, kdfIterations} <- liftAff $ Promise.toAff $ api.postPrelogin {email}
27− liftAff $ Promise.toAff $ runFn4 crypto.makeKey (Password password) email kdf kdfIterations
45+ encType = EncString.encryptionType encryptedKeyString
46+ key <-
47+ if encType == EncString.encryptionTypeAesCbc256_B64 then
48+ liftPromise $ runFn2 crypto.decryptToBytes (EncString.fromString str) masterKey
49+ else if encType == EncString.encryptionTypeAesCbc256_HmacSha256_B64 then do
50+ stretchedMasterKey <- liftPromise $ crypto.stretchKey masterKey
51+ liftPromise $ runFn2 crypto.decryptToBytes (EncString.fromString str) stretchedMasterKey
52+ else
53+ liftEffect $ Exc.throw $ "Unsupported key encryption type: " <> show encType
54+ pure $ SymmetricCryptoKey.fromArrayBuffer key
2855
2956getLogInRequestToken ::
3057 forall r.
@@ -35,25 +62,36 @@ getLogInRequestToken email password = do
3562 key <- makePreloginKey email password
3663 crypto <- l (L :: L "crypto")
3764 api :: ApiService <- l (L :: L "api")
65+ _localHashedPassword <-
66+ liftPromise
67+ $ runFn3 crypto.hashPassword password key (toNullable $ Just hashPurposeLocalAuthorization)
68+ Hash hashedPassword <- liftPromise $ runFn3 crypto.hashPassword password key null
69+ liftPromise
70+ $ api.postIdentityToken
71+ { email: email
72+ , masterPasswordHash: hashedPassword
73+ , captchaResponse: ""
74+ , twoFactor:
75+ { provider: twoFactorProviderTypeEmail
76+ , token: ""
77+ , remember: false
78+ }
79+ , device:
80+ { type: deviceTypeUnknownBrowser
81+ , name: "Temporary device name"
82+ , identifier: "42"
83+ , pushToken: null
84+ }
85+ }
3886
39− _localHashedPassword <- liftAff $ Promise.toAff $
40− runFn3 crypto.hashPassword password key (toNullable $ Just hashPurposeLocalAuthorization)
41−
42− Hash hashedPassword <- liftAff $ Promise.toAff $ runFn3 crypto.hashPassword password key null
87+decrypt ::
88+ forall r.
89+ HasL "crypto" CryptoService r =>
90+ SymmetricCryptoKey ->
91+ EncryptedString ->
92+ Al r String
93+decrypt key input = do
94+ crypto <- l (L :: L "crypto")
95+ liftPromise $ runFn2 crypto.decryptToUtf8 (EncString.fromString input) key
4396
44− liftAff $ Promise.toAff $ api.postIdentityToken {
45− email: email,
46− masterPasswordHash: hashedPassword,
47− captchaResponse: "",
48− twoFactor: {
49− provider: twoFactorProviderTypeEmail,
50− token: "",
51− remember: false
52− },
53− device: {
54− type: deviceTypeUnknownBrowser,
55− name: "Temporary device name",
56− identifier: "42",
57− pushToken: null
58− }
59− }
97+liftPromise = liftAff <<< Promise.toAff
bw/src/BW/Types.purs+515−397
@@ -1,530 +1,648 @@
11module BW.Types where
22
33import Prelude
4−
54import Data.ArrayBuffer.Types (ArrayBuffer)
65import Data.Date (Date)
76import Data.Nullable (Nullable)
87import Data.ShowableJson (ShowableJson)
98import Data.Undefined.NoProblem (Opt)
109
10+newtype EncryptedString
11+ = EncryptedString String
12+
13+derive newtype instance Show EncryptedString
14+
1115kdfPBKDF2_SHA256 = 0 :: KDF
12−type KDF = Int
13−
14−type PreloginResponse = {
15− kdf :: KDF,
16− kdfIterations :: Int
17−}
18−
19−type PreloginRequest = {
20− email :: String
21−}
22−
23−type ProfileResponse = {
24− id :: String,
25− name :: String,
26− email :: String,
27− emailVerified :: Boolean,
28− masterPasswordHint :: String,
29− premiumPersonally :: Boolean,
30− premiumFromOrganization :: Boolean,
31− culture :: String,
32− twoFactorEnabled :: Boolean,
33− key :: String,
34− privateKey :: String,
35− securityStamp :: String,
36− forcePasswordReset :: Boolean,
37− usesKeyConnector :: Boolean,
38− organizations :: Array ProfileOrganizationResponse,
39− providers :: Array ProfileProviderResponse,
40− providerOrganizations :: Array ProfileProviderOrganizationResponse
41−}
42−
43−type ProfileOrganizationResponse = {
44− id :: String,
45− name :: String,
46− usePolicies :: Boolean,
47− useGroups :: Boolean,
48− useDirectory :: Boolean,
49− useEvents :: Boolean,
50− useTotp :: Boolean,
51− use2fa :: Boolean,
52− useApi :: Boolean,
53− useSso :: Boolean,
54− useKeyConnector :: Boolean,
55− useResetPassword :: Boolean,
56− selfHost :: Boolean,
57− usersGetPremium :: Boolean,
58− seats :: Int,
59− maxCollections :: Int,
60− maxStorageGb :: Nullable Int,
61− key :: String,
62− hasPublicAndPrivateKeys :: Boolean,
63− status :: OrganizationUserStatusType,
64− type :: OrganizationUserType,
65− enabled :: Boolean,
66− ssoBound :: Boolean,
67− identifier :: String,
68− permissions :: PermissionsApi,
69− resetPasswordEnrolled :: Boolean,
70− userId :: String,
71− providerId :: String,
72− providerName :: String,
73− familySponsorshipFriendlyName :: String,
74− familySponsorshipAvailable :: Boolean,
75− planProductType :: ProductType,
76− keyConnectorEnabled :: Boolean,
77− keyConnectorUrl :: String,
78− familySponsorshipLastSyncDate :: Nullable Date,
79− familySponsorshipValidUntil :: Nullable Date,
80− familySponsorshipToDelete :: Nullable Boolean
81− }
16+
17+type KDF
18+ = Int
19+
20+type PreloginResponse
21+ = { kdf :: KDF
22+ , kdfIterations :: Int
23+ }
24+
25+type PreloginRequest
26+ = { email :: String
27+ }
28+
29+type ProfileResponse
30+ = { id :: String
31+ , name :: String
32+ , email :: String
33+ , emailVerified :: Boolean
34+ , masterPasswordHint :: String
35+ , premiumPersonally :: Boolean
36+ , premiumFromOrganization :: Boolean
37+ , culture :: String
38+ , twoFactorEnabled :: Boolean
39+ , key :: EncryptedString
40+ , privateKey :: EncryptedString
41+ , securityStamp :: String
42+ , forcePasswordReset :: Boolean
43+ , usesKeyConnector :: Boolean
44+ , organizations :: Array ProfileOrganizationResponse
45+ , providers :: Array ProfileProviderResponse
46+ , providerOrganizations :: Array ProfileProviderOrganizationResponse
47+ }
48+
49+type ProfileOrganizationResponse
50+ = { id :: String
51+ , name :: String
52+ , usePolicies :: Boolean
53+ , useGroups :: Boolean
54+ , useDirectory :: Boolean
55+ , useEvents :: Boolean
56+ , useTotp :: Boolean
57+ , use2fa :: Boolean
58+ , useApi :: Boolean
59+ , useSso :: Boolean
60+ , useKeyConnector :: Boolean
61+ , useResetPassword :: Boolean
62+ , selfHost :: Boolean
63+ , usersGetPremium :: Boolean
64+ , seats :: Int
65+ , maxCollections :: Int
66+ , maxStorageGb :: Nullable Int
67+ , key :: String
68+ , hasPublicAndPrivateKeys :: Boolean
69+ , status :: OrganizationUserStatusType
70+ , type :: OrganizationUserType
71+ , enabled :: Boolean
72+ , ssoBound :: Boolean
73+ , identifier :: String
74+ , permissions :: PermissionsApi
75+ , resetPasswordEnrolled :: Boolean
76+ , userId :: String
77+ , providerId :: String
78+ , providerName :: String
79+ , familySponsorshipFriendlyName :: String
80+ , familySponsorshipAvailable :: Boolean
81+ , planProductType :: ProductType
82+ , keyConnectorEnabled :: Boolean
83+ , keyConnectorUrl :: String
84+ , familySponsorshipLastSyncDate :: Nullable Date
85+ , familySponsorshipValidUntil :: Nullable Date
86+ , familySponsorshipToDelete :: Nullable Boolean
87+ }
8288
8389organizationUserStatusTypeInvited = 0 :: OrganizationUserStatusType
90+
8491organizationUserStatusTypeAccepted = 0 :: OrganizationUserStatusType
92+
8593organizationUserStatusTypeConfirmed = 0 :: OrganizationUserStatusType
86−type OrganizationUserStatusType = Int
94+
95+type OrganizationUserStatusType
96+ = Int
8797
8898organizationUserTypeOwner = 0 :: OrganizationUserType
99+
89100organizationUserTypeAdmin = 1 :: OrganizationUserType
101+
90102organizationUserTypeUser = 2 :: OrganizationUserType
103+
91104organizationUserTypeManager = 3 :: OrganizationUserType
105+
92106organizationUserTypeCustom = 4 :: OrganizationUserType
93−type OrganizationUserType = Int
94−
95−type PermissionsApi = {
96− accessEventLogs :: Boolean,
97− accessImportExport :: Boolean,
98− accessReports :: Boolean,
99− manageAllCollections :: Boolean,
100− createNewCollections :: Boolean,
101− editAnyCollection :: Boolean,
102− deleteAnyCollection :: Boolean,
103− manageAssignedCollections :: Boolean,
104− editAssignedCollections :: Boolean,
105− deleteAssignedCollections :: Boolean,
106− manageCiphers :: Boolean,
107− manageGroups :: Boolean,
108− manageSso :: Boolean,
109− managePolicies :: Boolean,
110− manageUsers :: Boolean,
111− manageResetPassword :: Boolean
112−}
107+
108+type OrganizationUserType
109+ = Int
110+
111+type PermissionsApi
112+ = { accessEventLogs :: Boolean
113+ , accessImportExport :: Boolean
114+ , accessReports :: Boolean
115+ , manageAllCollections :: Boolean
116+ , createNewCollections :: Boolean
117+ , editAnyCollection :: Boolean
118+ , deleteAnyCollection :: Boolean
119+ , manageAssignedCollections :: Boolean
120+ , editAssignedCollections :: Boolean
121+ , deleteAssignedCollections :: Boolean
122+ , manageCiphers :: Boolean
123+ , manageGroups :: Boolean
124+ , manageSso :: Boolean
125+ , managePolicies :: Boolean
126+ , manageUsers :: Boolean
127+ , manageResetPassword :: Boolean
128+ }
113129
114130productTypeFree = 0 :: ProductType
131+
115132productTypeFamilies = 1 :: ProductType
133+
116134productTypeTeams = 2 :: ProductType
135+
117136productTypeEnterprise = 3 :: ProductType
118−type ProductType = Int
119−
120−type ProfileProviderResponse = {
121− id :: String,
122− name :: String,
123− key :: String,
124− status :: ProviderUserStatusType,
125− type :: ProviderUserType,
126− enabled :: Boolean,
127− permissions :: PermissionsApi,
128− userId :: String,
129− useEvents :: Boolean
130−}
137+
138+type ProductType
139+ = Int
140+
141+type ProfileProviderResponse
142+ = { id :: String
143+ , name :: String
144+ , key :: String
145+ , status :: ProviderUserStatusType
146+ , type :: ProviderUserType
147+ , enabled :: Boolean
148+ , permissions :: PermissionsApi
149+ , userId :: String
150+ , useEvents :: Boolean
151+ }
131152
132153providerUserStatusTypeInvited = 0 :: ProviderUserStatusType
154+
133155providerUserStatusTypeAccepted = 1 :: ProviderUserStatusType
156+
134157providerUserStatusTypeConfirmed = 2 :: ProviderUserStatusType
135−type ProviderUserStatusType = Int
158+
159+type ProviderUserStatusType
160+ = Int
136161
137162providerUserTypeProviderAdmin = 0 :: ProviderUserType
163+
138164providerUserTypeServiceUser = 1 :: ProviderUserType
139−type ProviderUserType = Int
140−
141−type ProfileProviderOrganizationResponse = {
142− id :: String,
143− name :: String,
144− usePolicies :: Boolean,
145− useGroups :: Boolean,
146− useDirectory :: Boolean,
147− useEvents :: Boolean,
148− useTotp :: Boolean,
149− use2fa :: Boolean,
150− useApi :: Boolean,
151− useSso :: Boolean,
152− useKeyConnector :: Boolean,
153− useResetPassword :: Boolean,
154− selfHost :: Boolean,
155− usersGetPremium :: Boolean,
156− seats :: Int,
157− maxCollections :: Int,
158− maxStorageGb :: Nullable Int,
159− key :: String,
160− hasPublicAndPrivateKeys :: Boolean,
161− status :: OrganizationUserStatusType,
162− type :: OrganizationUserType,
163− enabled :: Boolean,
164− ssoBound :: Boolean,
165− identifier :: String,
166− permissions :: PermissionsApi,
167− resetPasswordEnrolled :: Boolean,
168− userId :: String,
169− providerId :: String,
170− providerName :: String,
171− familySponsorshipFriendlyName :: String,
172− familySponsorshipAvailable :: Boolean,
173− planProductType :: ProductType,
174− keyConnectorEnabled :: Boolean,
175− keyConnectorUrl :: String,
176− familySponsorshipLastSyncDate :: Nullable Date,
177− familySponsorshipValidUntil :: Nullable Date,
178− familySponsorshipToDelete :: Nullable Boolean
179−}
180−
181−type Urls = {
182− base :: Nullable String,
183− webVault :: Nullable String,
184− api :: Nullable String,
185− identity :: Nullable String,
186− icons :: Nullable String,
187− notifications :: Nullable String,
188− events :: Nullable String,
189− keyConnector :: Nullable String
190−}
191−
192−type PasswordTokenRequest = {
193− email :: Email,
194− masterPasswordHash :: String,
195− captchaResponse :: String,
196− twoFactor :: TokenRequestTwoFactor,
197− device :: DeviceRequest
198−}
199−
200−type DeviceRequest = {
201− type :: DeviceType,
202− name :: String,
203− identifier :: String,
204− pushToken :: Nullable String
205−}
165+
166+type ProviderUserType
167+ = Int
168+
169+type ProfileProviderOrganizationResponse
170+ = { id :: String
171+ , name :: String
172+ , usePolicies :: Boolean
173+ , useGroups :: Boolean
174+ , useDirectory :: Boolean
175+ , useEvents :: Boolean
176+ , useTotp :: Boolean
177+ , use2fa :: Boolean
178+ , useApi :: Boolean
179+ , useSso :: Boolean
180+ , useKeyConnector :: Boolean
181+ , useResetPassword :: Boolean
182+ , selfHost :: Boolean
183+ , usersGetPremium :: Boolean
184+ , seats :: Int
185+ , maxCollections :: Int
186+ , maxStorageGb :: Nullable Int
187+ , key :: String
188+ , hasPublicAndPrivateKeys :: Boolean
189+ , status :: OrganizationUserStatusType
190+ , type :: OrganizationUserType
191+ , enabled :: Boolean
192+ , ssoBound :: Boolean
193+ , identifier :: String
194+ , permissions :: PermissionsApi
195+ , resetPasswordEnrolled :: Boolean
196+ , userId :: String
197+ , providerId :: String
198+ , providerName :: String
199+ , familySponsorshipFriendlyName :: String
200+ , familySponsorshipAvailable :: Boolean
201+ , planProductType :: ProductType
202+ , keyConnectorEnabled :: Boolean
203+ , keyConnectorUrl :: String
204+ , familySponsorshipLastSyncDate :: Nullable Date
205+ , familySponsorshipValidUntil :: Nullable Date
206+ , familySponsorshipToDelete :: Nullable Boolean
207+ }
208+
209+type Urls
210+ = { base :: Nullable String
211+ , webVault :: Nullable String
212+ , api :: Nullable String
213+ , identity :: Nullable String
214+ , icons :: Nullable String
215+ , notifications :: Nullable String
216+ , events :: Nullable String
217+ , keyConnector :: Nullable String
218+ }
219+
220+type PasswordTokenRequest
221+ = { email :: Email
222+ , masterPasswordHash :: String
223+ , captchaResponse :: String
224+ , twoFactor :: TokenRequestTwoFactor
225+ , device :: DeviceRequest
226+ }
227+
228+type DeviceRequest
229+ = { type :: DeviceType
230+ , name :: String
231+ , identifier :: String
232+ , pushToken :: Nullable String
233+ }
206234
207235deviceTypeAndroid = 0 :: DeviceType
236+
208237deviceTypeiOS = 1 :: DeviceType
238+
209239deviceTypeChromeExtension = 2 :: DeviceType
240+
210241deviceTypeFirefoxExtension = 3 :: DeviceType
242+
211243deviceTypeOperaExtension = 4 :: DeviceType
244+
212245deviceTypeEdgeExtension = 5 :: DeviceType
246+
213247deviceTypeWindowsDesktop = 6 :: DeviceType
248+
214249deviceTypeMacOsDesktop = 7 :: DeviceType
250+
215251deviceTypeLinuxDesktop = 8 :: DeviceType
252+
216253deviceTypeChromeBrowser = 9 :: DeviceType
254+
217255deviceTypeFirefoxBrowser = 10 :: DeviceType
256+
218257deviceTypeOperaBrowser = 11 :: DeviceType
258+
219259deviceTypeEdgeBrowser = 12 :: DeviceType
260+
220261deviceTypeIEBrowser = 13 :: DeviceType
262+
221263deviceTypeUnknownBrowser = 14 :: DeviceType
264+
222265deviceTypeAndroidAmazon = 15 :: DeviceType
266+
223267deviceTypeUWP = 16 :: DeviceType
268+
224269deviceTypeSafariBrowser = 17 :: DeviceType
270+
225271deviceTypeVivaldiBrowser = 18 :: DeviceType
272+
226273deviceTypeVivaldiExtension = 19 :: DeviceType
274+
227275deviceTypeSafariExtension = 20 :: DeviceType
228−type DeviceType = Int
229276
230−type TokenRequestTwoFactor = {
231− provider :: TwoFactorProviderType,
232− token :: String,
233− remember :: Boolean
234−}
277+type DeviceType
278+ = Int
279+
280+type TokenRequestTwoFactor
281+ = { provider :: TwoFactorProviderType
282+ , token :: String
283+ , remember :: Boolean
284+ }
235285
236286twoFactorProviderTypeAuthenticator = 0 :: TwoFactorProviderType
287+
237288twoFactorProviderTypeEmail = 1 :: TwoFactorProviderType
289+
238290twoFactorProviderTypeDuo = 2 :: TwoFactorProviderType
291+
239292twoFactorProviderTypeYubikey = 3 :: TwoFactorProviderType
293+
240294twoFactorProviderTypeU2f = 4 :: TwoFactorProviderType
295+
241296twoFactorProviderTypeRemember = 5 :: TwoFactorProviderType
297+
242298twoFactorProviderTypeOrganizationDuo = 6 :: TwoFactorProviderType
299+
243300twoFactorProviderTypeWebAuthn = 7 :: TwoFactorProviderType
244−type TwoFactorProviderType = Int
245−
246−
247−type SymmetricCryptoKey = {
248− key :: ArrayBuffer,
249− encKey :: Nullable ArrayBuffer,
250− macKey :: Nullable ArrayBuffer,
251− encType :: EncryptionType,
252− keyB64 :: String,
253− encKeyB64 :: String,
254− macKeyB64 :: String
255−}
256−
257−encryptionTypeAesCbc256_B64 = 0 :: EncryptionType
258−encryptionTypeAesCbc128_HmacSha256_B64 = 1 :: EncryptionType
259−encryptionTypeAesCbc256_HmacSha256_B64 = 2 :: EncryptionType
260−encryptionTypeRsa2048_OaepSha256_B64 = 3 :: EncryptionType
261−encryptionTypeRsa2048_OaepSha1_B64 = 4 :: EncryptionType
262−encryptionTypeRsa2048_OaepSha256_HmacSha256_B64 = 5 :: EncryptionType
263−encryptionTypeRsa2048_OaepSha1_HmacSha256_B64 = 6 :: EncryptionType
264−type EncryptionType = Int
265−
266−newtype Password = Password String
301+
302+type TwoFactorProviderType
303+ = Int
304+
305+newtype Password
306+ = Password String
267307
268308hashPurposeServerAuthorization = 1 :: HashPurpose
309+
269310hashPurposeLocalAuthorization = 2 :: HashPurpose
270−type HashPurpose = Int
271−
272−newtype Hash = Hash String
273−
274−newtype AccessToken = AccessToken String
275−newtype RefreshToken = RefreshToken String
276−
277−type IdentityTokenResponse = {
278− accessToken :: AccessToken,
279− expiresIn :: Int,
280− refreshToken :: RefreshToken,
281− tokenType :: String,
282− resetMasterPassword :: Boolean,
283− privateKey :: String,
284− key :: String,
285− twoFactorToken :: Nullable String,
286− kdf :: KDF,
287− kdfIterations :: Int,
288− forcePasswordReset :: Nullable Boolean,
289− apiUseKeyConnector :: Nullable Boolean,
290− keyConnectorUrl :: Nullable String
291−}
292−
293−newtype Email = Email String
294−
295−type SyncResponse = {
296− profile :: Nullable ProfileResponse,
297− folders :: Array FolderResponse,
298− collections :: Array CollectionDetailsResponse,
299− ciphers :: Array CipherResponse,
300− domains :: Nullable DomainsResponse,
301− policies :: Nullable (Array PolicyResponse),
302− sends :: Array SendResponse
303−}
304−
305−type FolderResponse = {
306− id :: String,
307− name :: String,
308− revisionDate :: String
309−}
310−
311−type CollectionDetailsResponse = {
312− id :: String,
313− organizationId :: Nullable String,
314− name :: String,
315− externalId :: Nullable String,
316− readOnly :: Boolean
317−}
318−
319−type CipherResponse = {
320− id :: String,
321− organizationId :: Nullable String,
322− folderId :: Nullable String,
323− type :: CipherType,
324− name :: String,
325− notes :: Nullable String,
326− fields :: Nullable (Array FieldApi),
327− login :: Nullable LoginApi,
328− card :: Nullable CardApi,
329− identity :: Nullable IdentityApi,
330− secureNote :: Nullable SecureNoteApi,
331− favorite :: Boolean,
332− edit :: Boolean,
333− viewPassword :: Boolean,
334− organizationUseTotp :: Boolean,
335− revisionDate :: String,
336− attachments :: Nullable (Array AttachmentResponse),
337− passwordHistory :: Nullable (Array PasswordHistoryResponse),
338− collectionIds :: Nullable (Array String),
339− deletedDate :: Nullable String,
340− reprompt :: CipherRepromptType
341−}
311+
312+type HashPurpose
313+ = Int
314+
315+newtype Hash
316+ = Hash String
317+
318+newtype AccessToken
319+ = AccessToken String
320+
321+derive newtype instance Show AccessToken
322+
323+newtype RefreshToken
324+ = RefreshToken String
325+
326+derive newtype instance Show RefreshToken
327+
328+
329+type IdentityTokenResponse
330+ = { accessToken :: AccessToken
331+ , expiresIn :: Int
332+ , refreshToken :: RefreshToken
333+ , tokenType :: String
334+ , resetMasterPassword :: Boolean
335+ , privateKey :: EncryptedString
336+ , key :: EncryptedString
337+ , twoFactorToken :: Nullable String
338+ , kdf :: KDF
339+ , kdfIterations :: Int
340+ , forcePasswordReset :: Nullable Boolean
341+ , apiUseKeyConnector :: Nullable Boolean
342+ , keyConnectorUrl :: Nullable String
343+ }
344+
345+newtype Email
346+ = Email String
347+
348+type SyncResponse
349+ = { profile :: ProfileResponse
350+ , folders :: Array FolderResponse
351+ , collections :: Array CollectionDetailsResponse
352+ , ciphers :: Array CipherResponse
353+ , domains :: Nullable DomainsResponse
354+ , policies :: Nullable (Array PolicyResponse)
355+ , sends :: Array SendResponse
356+ }
357+
358+type FolderResponse
359+ = { id :: String
360+ , name :: String
361+ , revisionDate :: String
362+ }
363+
364+type CollectionDetailsResponse
365+ = { id :: String
366+ , organizationId :: Nullable String
367+ , name :: String
368+ , externalId :: Nullable String
369+ , readOnly :: Boolean
370+ }
371+
372+type CipherResponse
373+ = { id :: String
374+ , organizationId :: Nullable String
375+ , folderId :: Nullable String
376+ , type :: CipherType
377+ , name :: EncryptedString
378+ , notes :: Nullable EncryptedString
379+ , fields :: Nullable (Array FieldApi)
380+ , login :: Nullable LoginApi
381+ , card :: Nullable CardApi
382+ , identity :: Nullable IdentityApi
383+ , secureNote :: Nullable SecureNoteApi
384+ , favorite :: Boolean
385+ , edit :: Boolean
386+ , viewPassword :: Boolean
387+ , organizationUseTotp :: Boolean
388+ , revisionDate :: String
389+ , attachments :: Nullable (Array AttachmentResponse)
390+ , passwordHistory :: Nullable (Array PasswordHistoryResponse)
391+ , collectionIds :: Nullable (Array String)
392+ , deletedDate :: Nullable String
393+ , reprompt :: CipherRepromptType
394+ }
342395
343396cipherTypeLogin = 1 :: CipherType
397+
344398cipherTypeSecureNote = 2 :: CipherType
399+
345400cipherTypeCard = 3 :: CipherType
401+
346402cipherTypeIdentity = 4 :: CipherType
347−type CipherType = Int
348403
349−type FieldApi = {
350− name :: String,
351− value :: String,
352− type :: FieldType,
353− linkedId :: Nullable LinkedIdType
354−}
404+type CipherType
405+ = Int
406+
407+type FieldApi
408+ = { name :: String
409+ , value :: String
410+ , type :: FieldType
411+ , linkedId :: Nullable LinkedIdType
412+ }
355413
356414fieldTypeText = 0 :: FieldType
415+
357416fieldTypeHidden = 1 :: FieldType
417+
358418fieldTypeBoolean = 2 :: FieldType
419+
359420fieldTypeLinked = 3 :: FieldType
360−type FieldType = Int
421+
422+type FieldType
423+ = Int
361424
362425linkedIdTypeLoginLinkedIdUsername = 100 :: LinkedIdType
426+
363427linkedIdTypeLoginLinkedIdPassword = 101 :: LinkedIdType
428+
364429linkedIdTypeCardLinkedIdCardholderName = 300 :: LinkedIdType
430+
365431linkedIdTypeCardLinkedIdExpMonth = 301 :: LinkedIdType
432+
366433linkedIdTypeCardLinkedIdExpYear = 302 :: LinkedIdType
434+
367435linkedIdTypeCardLinkedIdCode = 303 :: LinkedIdType
436+
368437linkedIdTypeCardLinkedIdBrand = 304 :: LinkedIdType
438+
369439linkedIdTypeCardLinkedIdNumber = 305 :: LinkedIdType
440+
370441linkedIdTypeIdentityLinkedIdTitle = 400 :: LinkedIdType
442+
371443linkedIdTypeIdentityLinkedIdMiddleName = 401 :: LinkedIdType
444+
372445linkedIdTypeIdentityLinkedIdAddress1 = 402 :: LinkedIdType
446+
373447linkedIdTypeIdentityLinkedIdAddress2 = 403 :: LinkedIdType
448+
374449linkedIdTypeIdentityLinkedIdAddress3 = 404 :: LinkedIdType
450+
375451linkedIdTypeIdentityLinkedIdCity = 405 :: LinkedIdType
452+
376453linkedIdTypeIdentityLinkedIdState = 406 :: LinkedIdType
454+
377455linkedIdTypeIdentityLinkedIdPostalCode = 407 :: LinkedIdType
456+
378457linkedIdTypeIdentityLinkedIdCountry = 408 :: LinkedIdType
458+
379459linkedIdTypeIdentityLinkedIdCompany = 409 :: LinkedIdType
460+
380461linkedIdTypeIdentityLinkedIdEmail = 410 :: LinkedIdType
462+
381463linkedIdTypeIdentityLinkedIdPhone = 411 :: LinkedIdType
464+
382465linkedIdTypeIdentityLinkedIdSsn = 412 :: LinkedIdType
466+
383467linkedIdTypeIdentityLinkedIdUsername = 413 :: LinkedIdType
468+
384469linkedIdTypeIdentityLinkedIdPassportNumber = 414 :: LinkedIdType
470+
385471linkedIdTypeIdentityLinkedIdLicenseNumber = 415 :: LinkedIdType
472+
386473linkedIdTypeIdentityLinkedIdFirstName = 416 :: LinkedIdType
474+
387475linkedIdTypeIdentityLinkedIdLastName = 417 :: LinkedIdType
476+
388477linkedIdTypeIdentityLinkedIdFullName = 418 :: LinkedIdType
389−type LinkedIdType = Int
390−
391−type LoginApi = {
392− uris :: Opt (Array LoginUriApi),
393− username :: Nullable String,
394− password :: Nullable String,
395− passwordRevisionDate :: Nullable String,
396− totp :: Nullable String,
397− -- This thig is undefined in some cases for some reson. Just ignoring it
398− autofillOnPageLoad :: Opt (Nullable Boolean)
399−}
400−
401−type LoginUriApi = {
402− uri :: String,
403− match :: Nullable UriMatchType
404−}
478+
479+type LinkedIdType
480+ = Int
481+
482+type LoginApi
483+ = { uris :: Opt (Array LoginUriApi)
484+ , username :: Nullable EncryptedString
485+ , password :: Nullable EncryptedString
486+ , passwordRevisionDate :: Nullable String
487+ , totp :: Nullable EncryptedString
488+ -- This thing is undefined in some cases for some reson. Just ignoring it
489+ , autofillOnPageLoad :: Opt (Nullable Boolean)
490+ }
491+
492+type LoginUriApi
493+ = { uri :: String
494+ , match :: Nullable UriMatchType
495+ }
405496
406497uriMatchTypeDomain = 0 :: UriMatchType
498+
407499uriMatchTypeHost = 1 :: UriMatchType
500+
408501uriMatchTypeStartsWith = 2 :: UriMatchType
502+
409503uriMatchTypeExact = 3 :: UriMatchType
504+
410505uriMatchTypeRegularExpression = 4 :: UriMatchType
506+
411507uriMatchTypeNever = 5 :: UriMatchType
412−type UriMatchType = Int
413−
414−type CardApi = {
415− cardholderName :: Nullable String,
416− brand :: Nullable String,
417− number :: Nullable String,
418− expMonth :: Nullable String,
419− expYear :: Nullable String,
420− code :: Nullable String
421−}
422−
423−type IdentityApi = {
424− title :: String,
425− firstName :: String,
426− middleName :: String,
427− lastName :: String,
428− address1 :: String,
429− address2 :: String,
430− address3 :: String,
431− city :: String,
432− state :: String,
433− postalCode :: String,
434− country :: String,
435− company :: String,
436− email :: String,
437− phone :: String,
438− ssn :: String,
439− username :: String,
440− passportNumber :: String,
441− licenseNumber :: String
442−}
508+
509+type UriMatchType
510+ = Int
511+
512+type CardApi
513+ = { cardholderName :: Nullable String
514+ , brand :: Nullable String
515+ , number :: Nullable String
516+ , expMonth :: Nullable String
517+ , expYear :: Nullable String
518+ , code :: Nullable String
519+ }
520+
521+type IdentityApi
522+ = { title :: String
523+ , firstName :: String
524+ , middleName :: String
525+ , lastName :: String
526+ , address1 :: String
527+ , address2 :: String
528+ , address3 :: String
529+ , city :: String
530+ , state :: String
531+ , postalCode :: String
532+ , country :: String
533+ , company :: String
534+ , email :: String
535+ , phone :: String
536+ , ssn :: String
537+ , username :: String
538+ , passportNumber :: String
539+ , licenseNumber :: String
540+ }
443541
444542secureNoteApiGeneric = 0 :: SecureNoteApi
445−type SecureNoteApi = Int
446−
447−type AttachmentResponse = {
448− id :: String,
449− url :: String,
450− fileName :: String,
451− key :: String,
452− size :: String,
453− sizeName :: String
454−}
455−
456−type PasswordHistoryResponse = {
457− password :: String,
458− lastUsedDate :: String
459−}
543+
544+type SecureNoteApi
545+ = Int
546+
547+type AttachmentResponse
548+ = { id :: String
549+ , url :: String
550+ , fileName :: String
551+ , key :: String
552+ , size :: String
553+ , sizeName :: String
554+ }
555+
556+type PasswordHistoryResponse
557+ = { password :: String
558+ , lastUsedDate :: String
559+ }
460560
461561cipherRepromptTypeNone = 0 :: CipherRepromptType
562+
462563cipherRepromptTypePassword = 1 :: CipherRepromptType
463−type CipherRepromptType = Int
464−
465−type DomainsResponse = {
466− equivalentDomains :: Array (Array String),
467− globalEquivalentDomains :: Array GlobalDomainResponse
468−}
469−
470−type GlobalDomainResponse = {
471− type :: Int,
472− domains :: Array String,
473− excluded :: Boolean
474−}
475−
476−type PolicyResponse = {
477− id :: String,
478− organizationId :: Nullable String,
479− type :: PolicyType,
480− data :: ShowableJson,
481− enabled :: Boolean
482−}
564+
565+type CipherRepromptType
566+ = Int
567+
568+type DomainsResponse
569+ = { equivalentDomains :: Array (Array String)
570+ , globalEquivalentDomains :: Array GlobalDomainResponse
571+ }
572+
573+type GlobalDomainResponse
574+ = { type :: Int
575+ , domains :: Array String
576+ , excluded :: Boolean
577+ }
578+
579+type PolicyResponse
580+ = { id :: String
581+ , organizationId :: Nullable String
582+ , type :: PolicyType
583+ , data :: ShowableJson
584+ , enabled :: Boolean
585+ }
483586
484587policyTypeTwoFactorAuthentication = 0 :: PolicyType -- Requires users to have 2fa enabled
588+
485589policyTypeMasterPassword = 1 :: PolicyType -- Sets minimum requirements for master password complexity
590+
486591policyTypePasswordGenerator = 2 :: PolicyType -- Sets minimum requirements/default type for generated passwords/passphrases
592+
487593policyTypeSingleOrg = 3 :: PolicyType -- Allows users to only be apart of one organization
594+
488595policyTypeRequireSso = 4 :: PolicyType -- Requires users to authenticate with SSO
596+
489597policyTypePersonalOwnership = 5 :: PolicyType -- Disables personal vault ownership for adding/cloning items
598+
490599policyTypeDisableSend = 6 :: PolicyType -- Disables the ability to create and edit Bitwarden Sends
600+
491601policyTypeSendOptions = 7 :: PolicyType -- Sets restrictions or defaults for Bitwarden Sends
602+
492603policyTypeResetPassword = 8 :: PolicyType -- Allows orgs to use reset password : also can enable auto-enrollment during invite flow
604+
493605policyTypeMaximumVaultTimeout = 9 :: PolicyType -- Sets the maximum allowed vault timeout
606+
494607policyTypeDisablePersonalVaultExport = 10 :: PolicyType -- Disable personal vault export
495−type PolicyType = Int
496−
497−type SendResponse = {
498− id :: String,
499− accessId :: String,
500− type :: SendType,
501− name :: String,
502− notes :: Nullable String,
503− file :: Nullable SendFileApi,
504− text :: Nullable SendTextApi,
505− key :: String,
506− maxAccessCount :: Nullable Int,
507− accessCount :: Int,
508− revisionDate :: String,
509− expirationDate :: String,
510− deletionDate :: Nullable String,
511− password :: String,
512− disable :: Boolean,
513− hideEmail :: Boolean
514−}
608+
609+type PolicyType
610+ = Int
611+
612+type SendResponse
613+ = { id :: String
614+ , accessId :: String
615+ , type :: SendType
616+ , name :: String
617+ , notes :: Nullable String
618+ , file :: Nullable SendFileApi
619+ , text :: Nullable SendTextApi
620+ , key :: String
621+ , maxAccessCount :: Nullable Int
622+ , accessCount :: Int
623+ , revisionDate :: String
624+ , expirationDate :: String
625+ , deletionDate :: Nullable String
626+ , password :: String
627+ , disable :: Boolean
628+ , hideEmail :: Boolean
629+ }
515630
516631sendTypeText = 0 :: SendType
632+
517633sendTypeFile = 1 :: SendType
518−type SendType = Int
519−
520−type SendFileApi = {
521− id :: String,
522− fileName :: String,
523− size :: String,
524− sizeName :: String
525−}
526−
527−type SendTextApi = {
528− text :: String,
529− hidden :: Boolean
530−}
634+
635+type SendType
636+ = Int
637+
638+type SendFileApi
639+ = { id :: String
640+ , fileName :: String
641+ , size :: String
642+ , sizeName :: String
643+ }
644+
645+type SendTextApi
646+ = { text :: String
647+ , hidden :: Boolean
648+ }
bw/src/Data/EncString.js+8−2
@@ -1,5 +1,11 @@
1−import { EncString } from "../../../deps/bw/libs/shared/dist/src/models/domain/encString"
1+import { EncString } from "../../deps/bw/libs/shared/dist/src/models/domain/encString"
22
33export function fromString(str) {
4− return new EncString(str)
4+ console.log("fromString " + str)
5+ const x = new EncString(str)
6+ return x
7+}
8+
9+export function encryptionType(encStr) {
10+ return encStr.encryptionType
511}
bw/src/Data/EncString.purs+21−1
@@ -1,7 +1,27 @@
11module Data.EncString where
22
33import Prelude
4+import BW.Types (EncryptedString(..))
45
56foreign import data EncString :: Type
67
7−foreign import fromString :: String -> EncString
8+foreign import encryptionType :: EncString -> EncryptionType
9+
10+encryptionTypeAesCbc256_B64 = 0 :: EncryptionType
11+
12+encryptionTypeAesCbc128_HmacSha256_B64 = 1 :: EncryptionType
13+
14+encryptionTypeAesCbc256_HmacSha256_B64 = 2 :: EncryptionType
15+
16+encryptionTypeRsa2048_OaepSha256_B64 = 3 :: EncryptionType
17+
18+encryptionTypeRsa2048_OaepSha1_B64 = 4 :: EncryptionType
19+
20+encryptionTypeRsa2048_OaepSha256_HmacSha256_B64 = 5 :: EncryptionType
21+
22+encryptionTypeRsa2048_OaepSha1_HmacSha256_B64 = 6 :: EncryptionType
23+
24+type EncryptionType
25+ = Int
26+
27+foreign import fromString :: EncryptedString -> EncString
bw/src/Data/SymmetricCryptoKey.jsnew file+6
@@ -0,0 +1,6 @@
1+import { SymmetricCryptoKey } from '../../deps/bw/libs/shared/dist/src/models/domain/symmetricCryptoKey.js';
2+
3+export function fromArrayBuffer(arr) {
4+ console.log("fromArrayBuffer " + arr)
5+ return new SymmetricCryptoKey(arr)
6+}
bw/src/Data/SymmetricCryptoKey.pursnew file+34
@@ -0,0 +1,34 @@
1+module Data.SymmetricCryptoKey where
2+
3+import Prelude
4+import Data.ArrayBuffer.Types (ArrayBuffer)
5+import Data.Nullable (Nullable)
6+
7+type SymmetricCryptoKey
8+ = { key :: ArrayBuffer
9+ , encKey :: Nullable ArrayBuffer
10+ , macKey :: Nullable ArrayBuffer
11+ , encType :: EncryptionType
12+ , keyB64 :: String
13+ , encKeyB64 :: String
14+ , macKeyB64 :: String
15+ }
16+
17+encryptionTypeAesCbc256_B64 = 0 :: EncryptionType
18+
19+encryptionTypeAesCbc128_HmacSha256_B64 = 1 :: EncryptionType
20+
21+encryptionTypeAesCbc256_HmacSha256_B64 = 2 :: EncryptionType
22+
23+encryptionTypeRsa2048_OaepSha256_B64 = 3 :: EncryptionType
24+
25+encryptionTypeRsa2048_OaepSha1_B64 = 4 :: EncryptionType
26+
27+encryptionTypeRsa2048_OaepSha256_HmacSha256_B64 = 5 :: EncryptionType
28+
29+encryptionTypeRsa2048_OaepSha1_HmacSha256_B64 = 6 :: EncryptionType
30+
31+type EncryptionType
32+ = Int
33+
34+foreign import fromArrayBuffer :: ArrayBuffer -> SymmetricCryptoKey
dev/start-chrome.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+/Applications/Chromium.app/Contents/MacOS/Chromium --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security
frontend/Bridge.elm+19−5
@@ -32,16 +32,24 @@ jsonEncCmd_Login val =
3232
3333
3434type Cmd =
35− Login Cmd_Login
35+ Init
36+ | Login Cmd_Login
3637
3738jsonDecCmd : Json.Decode.Decoder ( Cmd )
3839jsonDecCmd =
39− Json.Decode.lazy (\_ -> Json.Decode.map Login (jsonDecCmd_Login))
40−
40+ let jsonDecDictCmd = Dict.fromList
41+ [ ("Init", Json.Decode.lazy (\_ -> Json.Decode.succeed Init))
42+ , ("Login", Json.Decode.lazy (\_ -> Json.Decode.map Login (jsonDecCmd_Login)))
43+ ]
44+ jsonDecObjectSetCmd = Set.fromList []
45+ in decodeSumTaggedObject "Cmd" "tag" "contents" jsonDecDictCmd jsonDecObjectSetCmd
4146
4247jsonEncCmd : Cmd -> Value
43−jsonEncCmd (Login v1) =
44− jsonEncCmd_Login v1
48+jsonEncCmd val =
49+ let keyval v = case v of
50+ Init -> ("Init", encodeValue (Json.Encode.list identity []))
51+ Login v1 -> ("Login", encodeValue (jsonEncCmd_Login v1))
52+ in encodeSumTaggedObject "tag" "contents" keyval val
4553
4654
4755
@@ -67,15 +75,19 @@ jsonEncSub_Hello val =
6775
6876type Sub =
6977 Empty
78+ | Error String
7079 | GotEverything String
7180 | Hello Sub_Hello
81+ | NeedsLogin
7282
7383jsonDecSub : Json.Decode.Decoder ( Sub )
7484jsonDecSub =
7585 let jsonDecDictSub = Dict.fromList
7686 [ ("Empty", Json.Decode.lazy (\_ -> Json.Decode.succeed Empty))
87+ , ("Error", Json.Decode.lazy (\_ -> Json.Decode.map Error (Json.Decode.string)))
7788 , ("GotEverything", Json.Decode.lazy (\_ -> Json.Decode.map GotEverything (Json.Decode.string)))
7889 , ("Hello", Json.Decode.lazy (\_ -> Json.Decode.map Hello (jsonDecSub_Hello)))
90+ , ("NeedsLogin", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedsLogin))
7991 ]
8092 jsonDecObjectSetSub = Set.fromList []
8193 in decodeSumTaggedObject "Sub" "tag" "contents" jsonDecDictSub jsonDecObjectSetSub
@@ -84,7 +96,9 @@ jsonEncSub : Sub -> Value
8496jsonEncSub val =
8597 let keyval v = case v of
8698 Empty -> ("Empty", encodeValue (Json.Encode.list identity []))
99+ Error v1 -> ("Error", encodeValue (Json.Encode.string v1))
87100 GotEverything v1 -> ("GotEverything", encodeValue (Json.Encode.string v1))
88101 Hello v1 -> ("Hello", encodeValue (jsonEncSub_Hello v1))
102+ NeedsLogin -> ("NeedsLogin", encodeValue (Json.Encode.list identity []))
89103 in encodeSumTaggedObject "tag" "contents" keyval val
90104
frontend/Main.elm+21−8
@@ -13,9 +13,10 @@ import Utils exposing (..)
1313
1414type Msg
1515 = RecieveMessage String
16+ | ShowLoginPage
1617 | ShowError String
1718 | CloseNotification
18− | Submit { email : String, password : String, server : String }
19+ | SubmitLogin { email : String, password : String, server : String }
1920 | LoginMsg Login.Msg
2021
2122
@@ -52,7 +53,7 @@ showPage page =
5253main : Program () Model Msg
5354main =
5455 Browser.element
55− { init = \_ -> ( init, Cmd.none )
56+ { init = \_ -> init
5657 , view = view
5758 , update = update
5859 , subscriptions =
@@ -68,15 +69,23 @@ main =
6869
6970 Bridge.Empty ->
7071 RecieveMessage "Empty"
72+
73+ Bridge.NeedsLogin ->
74+ ShowLoginPage
75+
76+ Bridge.Error err ->
77+ ShowError err
7178 )
7279 }
7380
7481
75−init : Model
82+init : ( Model, Cmd Msg )
7683init =
77− { notifications = []
78− , page = LoadingPage
79− }
84+ ( { notifications = []
85+ , page = LoadingPage
86+ }
87+ , FFI.sendBridge Bridge.Init
88+ )
8089
8190
8291view : Model -> Html Msg
@@ -139,7 +148,7 @@ update msg model =
139148 , Cmd.none
140149 )
141150
142− Submit data ->
151+ SubmitLogin data ->
143152 ( model
144153 , FFI.sendBridge (Bridge.Login data)
145154 )
@@ -152,7 +161,11 @@ update msg model =
152161 _ ->
153162 ( model, Cmd.none )
154163
164+ ShowLoginPage ->
165+ (Login.page loginCallbacks LoginMsg).init ()
166+ |> Tuple.mapFirst (\pageModel -> { model | page = LoginModel pageModel })
167+
155168
156169loginCallbacks : Login.Callbacks Msg
157170loginCallbacks =
158− { submit = Submit }
171+ { submit = SubmitLogin }
frontend/Pages/Login.elm+2−2
@@ -32,7 +32,7 @@ page callbacks liftMsg =
3232 , view = \model -> view model |> List.map (Html.map liftMsg)
3333 , update = \msg model -> update callbacks liftMsg msg model
3434 , subscriptions = \model -> subscriptions model |> Sub.map liftMsg
35− , title = always "Log into your bitwarden account"
35+ , title = always "Log into bitwarden"
3636 }
3737
3838
@@ -76,6 +76,6 @@ view model =
7676 , input [ Attr.type_ "email", Attr.value model.email, Ev.onInput UpdateEmail ] []
7777 , label [] [ text "Password" ]
7878 , input [ Attr.type_ "password", Attr.value model.password, Ev.onInput UpdatePassword ] []
79− , alignRight [ button [ Ev.onClick Submit ] [ text "Log in" ] ]
8079 ]
80+ , alignRight [ button [ Ev.onClick Submit ] [ text "Log in" ] ]
8181 ]
frontend/Pages/Navigation.elm+1−1
@@ -14,7 +14,7 @@ type alias Config =
1414navigation : Config -> Html msg
1515navigation { back, title } =
1616 nav [ Attr.class "p-tabs p-tabs__list" ]
17− [ h3 []
17+ [ h3 [ Attr.class "u-no-margin u-no-padding" ]
1818 [ button
1919 (Attr.class "p-button--base is-inline u-no-margin" :: optional (not back) (Attr.style "visibility" "hidden"))
2020 [ i [ Attr.class "p-icon--chevron-up ninety-counter" ] []
spago.dhall+4
@@ -22,12 +22,16 @@ to generate this file without the comments in this block.
2222 , "datetime"
2323 , "effect"
2424 , "either"
25+ , "exceptions"
26+ , "foldable-traversable"
2527 , "foreign"
28+ , "lists"
2629 , "maybe"
2730 , "newtype"
2831 , "nullable"
2932 , "prelude"
3033 , "transformers"
34+ , "unsafe-coerce"
3135 ]
3236, packages = ./packages.dhall
3337, sources = [ "backend/**/*.purs" ]