on master help sign in
.github/FUNDING.ymlnew file+1
since splitsnapshots
unifiedsplit
@@ -0,0 +1 @@
1+custom: ["https://iko.soy/donate"]
.github/workflows/build.yamlnew file+97
@@ -0,0 +1,97 @@
1+name: CI
2+
3+on:
4+ push:
5+ branches: ["master"]
6+ pull_request:
7+ branches: ["master"]
8+ release:
9+ types: [prereleased]
10+
11+jobs:
12+ build:
13+ runs-on: ubuntu-latest
14+
15+ steps:
16+ - uses: cachix/install-nix-action@v19
17+
18+ - name: Cachix
19+ uses: cachix/cachix-action@v12
20+ with:
21+ name: iko
22+ authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
23+
24+ - uses: actions/checkout@v3
25+ with:
26+ submodules: recursive
27+
28+ - name: Install clickable
29+ run: |
30+ docker --version
31+ sudo apt install adb git python3 python3-pip python3-setuptools
32+ pip3 install --user clickable-ut==7.10.0 --upgrade
33+ echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc
34+
35+ - name: Build artifacts
36+ run: ./dev/build.sh
37+
38+ - name: Build Warden
39+ run: clickable build --output click
40+
41+ - uses: actions/upload-artifact@v3
42+ with:
43+ name: click
44+ path: click/*
45+
46+ - uses: actions/upload-artifact@v3
47+ with:
48+ name: dist
49+ path: dist
50+
51+ # Tests
52+
53+ - uses: actions/setup-node@v3
54+ with:
55+ node-version: 16
56+
57+ - name: Install test dependencies
58+ working-directory: tests
59+ run: |
60+ npm ci
61+ npx playwright install --with-deps
62+
63+ - name: Run tests
64+ working-directory: tests
65+ run: npx playwright test
66+
67+ - uses: actions/upload-artifact@v3
68+ if: always()
69+ with:
70+ name: playwright-report
71+ path: tests/playwright-report/
72+
73+ # Upload release
74+
75+ - name: Zip Release Assets
76+ if: github.event_name == 'release'
77+ shell: bash
78+ run: |
79+ cd dist/
80+ zip -r ../../../../dist.zip .
81+
82+ - name: Upload dist to release
83+ if: github.event_name == 'release'
84+ uses: svenstaro/upload-release-action@2.4.1
85+ with:
86+ repo_token: ${{ secrets.GITHUB_TOKEN }}
87+ file: dist.zip
88+ tag: ${{ github.ref }}
89+
90+ - name: Upload click to release
91+ if: github.event_name == 'release'
92+ uses: svenstaro/upload-release-action@2.4.1
93+ with:
94+ repo_token: ${{ secrets.GITHUB_TOKEN }}
95+ file: build/all/app/*.click
96+ tag: ${{ github.ref }}
97+ file_glob: true
.gitignore+12−1
@@ -7,5 +7,16 @@ static/backend.js
7.spago
7.spago
8output
8output
9.psc-ide-port
9.psc-ide-port
10−backend.js
11elm.js
10elm.js
11+output-es
12+elm-stuff
13+node_modules/
14+/tests/test-results/
15+/tests/playwright-report/
16+/tests/playwright/.cache/
17+generated-docs
18+backend.js.map
19+.spago2nix
20+backend.js
21+vanilla
22+dist
.gitmodulesdeleted−6
@@ -1,6 +0,0 @@
1−[submodule "deps/bw"]
2− path = deps/bw
3− url = https://github.com/bitwarden/clients.git
4−[submodule "vanilla"]
5− path = vanilla
6− url = https://github.com/ilyakooo0/vanilla-compiled.git
.helix/languages.tomlnew file+3
@@ -0,0 +1,3 @@
1+[[language]]
2+name = "nix"
3+auto-format = true
.vscode/tasks.jsonnew file+31
@@ -0,0 +1,31 @@
1+{
2+ // See https://go.microsoft.com/fwlink/?LinkId=733558
3+ // for the documentation about the tasks.json format
4+ "version": "2.0.0",
5+ "tasks": [
6+ {
7+ "label": "Generate bridge",
8+ "type": "process",
9+ "command": "${workspaceFolder}/dev/generate-bridge.sh",
10+ "problemMatcher": []
11+ },
12+ {
13+ "label": "Build backend",
14+ "type": "process",
15+ "command": "${workspaceFolder}/dev/build-backend.sh",
16+ "problemMatcher": []
17+ },
18+ {
19+ "label": "Start Chrome",
20+ "type": "process",
21+ "command": "${workspaceFolder}/dev/start-chrome.sh",
22+ "problemMatcher": []
23+ },
24+ {
25+ "label": "Start Elm-live",
26+ "type": "process",
27+ "command": "${workspaceFolder}/dev/elm-live.sh",
28+ "problemMatcher": []
29+ }
30+ ]
31+}
README.md+32−8
@@ -1,14 +1,38 @@
1−# App Title
1+<p align="center">
2+ <img src="logo.png" style="width: 100px;" />
3+</p>
2
4
3−A short description of your app
5+<h1 align="center">Warden</h1>
4
6
5−## License
7+<p align="center">
8+ <a href="https://open-store.io/app/soy.iko.warden"><img src="https://open-store.io/badges/en_US.svg" alt="OpenStore" style="width: 150px;" /></a>
9+</p>
6
10
7−Copyright (C) 2022 Your FullName
8
11
9−This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published
12+<p align="center">
10−by the Free Software Foundation.
13+ <img src="screenshots/1.png" style="width: 150px;" />
14+ <img src="screenshots/2.png" style="width: 150px;" />
15+ <img src="screenshots/3.png" style="width: 150px;" />
16+ <img src="screenshots/4.png" style="width: 150px;" />
17+ <img src="screenshots/5.png" style="width: 150px;" />
18+</p>
11
19
12−This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
20+Warden is a [Bitwarden](https://bitwarden.com)/[Vaultwarden](https://github.com/dani-garcia/vaultwarden) client for [Ubuntu Touch](https://ubuntu-touch.io).
13
21
14−You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
22+## Features
23+
24+1. View and edit: passwords, contact, secure notes and credit cards
25+2. Generate passwords
26+
27+## Support the project
28+
29+I fund the development of Warden out of my own pocket. If you find the project useful and don't want it to die, please consider donating what you think the app is worth to you.
30+
31+<p align="center">
32+ <a href="https://iko.soy/donate"><img src="img/donate.png" alt="Donate" style="width: 150px;" /></a>
33+</p>
34+
35+
36+## Porting
37+
38+Although Warden is intended for ubuntu touch it is written completely in web technologies. This means that, if there is demand, the app can be ported to virtually any platform. The biggest problem will be [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).
backend-main.jsnew file+3
@@ -0,0 +1,3 @@
1+import * as Main from "./backend/Main/index.js"
2+
3+Main.main()
backend/BW.js+82−65
@@ -1,21 +1,29 @@
1−import { StateFactory } from "../../deps/bw/libs/shared/dist/src/factories/stateFactory";
1+import { StateFactory } from "../../bw/factories/stateFactory";
2−import { GlobalState } from "../../deps/bw/libs/shared/dist/src/models/domain/globalState";
2+import { GlobalState } from "../../bw/models/domain/global-state";
3−import { ApiService } from "../../deps/bw/libs/shared/dist/src/services/api.service";
3+import { ApiService } from "../../bw/services/api.service";
4−import { AppIdService } from "../../deps/bw/libs/shared/dist/src/services/appId.service";
4+import { AppIdService } from "../../bw/services/appId.service";
5−import { ConsoleLogService } from "../../deps/bw/libs/shared/dist/src/services/consoleLog.service";
5+import { ConsoleLogService } from "../../bw/services/consoleLog.service";
6−import { EnvironmentService } from "../../deps/bw/libs/shared/dist/src/services/environment.service";
6+import { EnvironmentService } from "../../bw/services/environment.service";
7−import { StateMigrationService } from "../../deps/bw/libs/shared/dist/src/services/stateMigration.service";
7+import { StateMigrationService } from "../../bw/services/stateMigration.service";
8−import { TokenService } from "../../deps/bw/libs/shared/dist/src/services/token.service";
8+import { TokenService } from "../../bw/services/token.service";
9−import { WebCryptoFunctionService } from "../../deps/bw/libs/shared/dist/src/services/webCryptoFunction.service";
9+import { WebCryptoFunctionService } from "../../bw/services/webCryptoFunction.service";
10−import { Account, AccountProfile, AccountTokens } from "../../deps/bw/libs/shared/dist/src/models/domain/account";
10+import { Account, AccountProfile, AccountTokens } from "../../bw/models/domain/account";
11−
11+import {MemoryStorageService} from "../../bw/services/memoryStorage.service"
12+
12−// import BrowserStorageService from "../../../../deps/bw/libs/shared/dist/src/services/browserStorage.service";
13+// import BrowserStorageService from "../../../../bw/services/browserStorage.service";
13−import { CryptoService } from "../../deps/bw/libs/shared/dist/src/services/crypto.service";
14+import { CryptoService } from "../../bw/services/crypto.service";
14−
15+
15−import { I18nService } from "../../deps/bw/libs/shared/dist/src/services/i18n.service";
16+import { I18nService } from "../../bw/services/i18n.service";
16−import { StateService as BaseStateService } from "../../deps/bw/libs/shared/dist/src/services/state.service";
17+import { StateService as BaseStateService } from "../../bw/services/state.service";
17−import { PasswordTokenRequest } from "../../deps/bw/libs/shared/dist/src/models/request/identityToken/passwordTokenRequest";
18+import { PasswordTokenRequest } from "../../bw/models/request/identity-token/password-token.request";
18−import { TokenRequestTwoFactor } from "../../deps/bw/libs/common/src/models/request/identityToken/tokenRequestTwoFactor.ts";
19+import { TokenTwoFactorRequest} from "../../bw/models/request/identity-token/token-two-factor.request";
20+import { CipherRequest } from "../../bw/vault/models/request/cipher.request";
21+import { CipherCreateRequest } from "../../bw/vault/models/request/cipher-create.request";
22+import { Cipher } from "../../bw/vault/models/domain/cipher";
23+import { CipherData } from "../../bw/vault/models/data/cipher.data";
24+import { PasswordGenerationService } from "../../bw/services/passwordGeneration.service";
25+import { TotpService } from "../../bw/services/totp.service"
26+import { EncryptServiceImplementation } from "../../bw/services/cryptography/encrypt.service.implementation"
19
27
20function sanitize(obj) {
28function sanitize(obj) {
21 return JSON.parse(JSON.stringify(obj));
29 return JSON.parse(JSON.stringify(obj));
@@ -30,7 +38,7 @@ export function getServices() {
30
38
31 await bg.bootstrap()
39 await bg.bootstrap()
32
40
33− bg.environmentService.setUrls(urls)
41+ await bg.environmentService.setUrls(urls)
34
42
35 if (tokenResponse != null) {
43 if (tokenResponse != null) {
36 const accountInformation = await bg.tokenService.decodeToken(tokenResponse.accessToken);
44 const accountInformation = await bg.tokenService.decodeToken(tokenResponse.accessToken);
@@ -64,36 +72,54 @@ export function getServices() {
64 getProfile: (req) => api.getProfile(req),
72 getProfile: (req) => api.getProfile(req),
65 postIdentityToken: (req) => api.postIdentityToken(
73 postIdentityToken: (req) => api.postIdentityToken(
66 new PasswordTokenRequest(
74 new PasswordTokenRequest(
67− email = req.email,
75+ req.email,
68− masterPasswordHash = req.masterPasswordHash,
76+ req.masterPasswordHash,
69− captchaResponse = req.captchaResponse,
77+ req.captchaResponse,
70− twoFactor = new TokenRequestTwoFactor(req.twoFactor),
78+ new TokenTwoFactorRequest(
79+ req.twoFactor.provider,
80+ req.twoFactor.token,
81+ req.twoFactor.remember
82+ ),
71− device = req.device
83+ req.device
72 )
84 )
73 ),
85 ),
74 getSync: () => api.getSync().then(sanitize),
86 getSync: () => api.getSync().then(sanitize),
87+ postCipherCreate: (cipher) => api.postCipherCreate(makeCipherCreateRequest(cipher)),
88+ putCipher: (cipher) => api.putCipher(cipher.id, makeCipherRequest(cipher)),
89+ deleteCipher: (id) => api.deleteCipher(id),
90+ postTwoFactorEmail: (req) => api.postTwoFactorEmail(req),
75 }
91 }
76 },
92 },
77 crypto: bg.cryptoService,
93 crypto: bg.cryptoService,
78− cryptoFunctions: bg.cryptoFunctionService
94+ cryptoFunctions: bg.cryptoFunctionService,
95+ passwordGeneration: bg.passwordGenerationService,
96+ totpService: bg.totpService,
79 }
97 }
80}
98}
81
99
100+function makeCipherRequest(cipher) {
101+ return new CipherRequest(new Cipher(new CipherData(cipher)))
102+}
103+
104+function makeCipherCreateRequest(cipher) {
105+ return new CipherCreateRequest(new Cipher(new CipherData(cipher)))
106+}
107+
82class MainBackground {
108class MainBackground {
83− messagingService; //: MessagingServiceAbstraction;
109+ // messagingService; //: MessagingServiceAbstraction;
84− storageService; //: StorageServiceAbstraction;
110+ // storageService; //: StorageServiceAbstraction;
85− i18nService; //: I18nServiceAbstraction;
111+ // i18nService; //: I18nServiceAbstraction;
86− platformUtilsService; //: PlatformUtilsServiceAbstraction;
112+ // platformUtilsService; //: PlatformUtilsServiceAbstraction;
87− logService; //: LogServiceAbstraction;
113+ // logService; //: LogServiceAbstraction;
88− cryptoService; //: CryptoServiceAbstraction;
114+ // cryptoService; //: CryptoServiceAbstraction;
89− cryptoFunctionService; //: CryptoFunctionServiceAbstraction;
115+ // cryptoFunctionService; //: CryptoFunctionServiceAbstraction;
90− tokenService; //: TokenServiceAbstraction;
116+ // tokenService; //: TokenServiceAbstraction;
91− appIdService; //: AppIdServiceAbstraction;
117+ // appIdService; //: AppIdServiceAbstraction;
92− apiService; //: ApiServiceAbstraction;
118+ // apiService; //: ApiServiceAbstraction;
93− environmentService; //: EnvironmentServiceAbstraction;
119+ // environmentService; //: EnvironmentServiceAbstraction;
94− authService; //: AuthServiceAbstraction;
120+ // authService; //: AuthServiceAbstraction;
95− stateService; //: StateServiceAbstraction;
121+ // stateService; //: StateServiceAbstraction;
96− stateMigrationService; //: StateMigrationService;
122+ // stateMigrationService; //: StateMigrationService;
97
123
98
124
99 constructor() {
125 constructor() {
@@ -107,6 +133,7 @@ class MainBackground {
107 new StateFactory(GlobalState, Account)
133 new StateFactory(GlobalState, Account)
108 );
134 );
109 this.stateService = new StateService(
135 this.stateService = new StateService(
136+ this.storageService,
110 this.storageService,
137 this.storageService,
111 this.storageService,
138 this.storageService,
112 this.logService,
139 this.logService,
@@ -137,8 +164,13 @@ class MainBackground {
137 );
164 );
138 this.i18nService = new I18nService("en");
165 this.i18nService = new I18nService("en");
139 this.cryptoFunctionService = new WebCryptoFunctionService(window);
166 this.cryptoFunctionService = new WebCryptoFunctionService(window);
167+ this.encryptService = new EncryptServiceImplementation(
168+ this.cryptoFunctionService,
169+ this.logService
170+ );
140 this.cryptoService = new BrowserCryptoService(
171 this.cryptoService = new BrowserCryptoService(
141 this.cryptoFunctionService,
172 this.cryptoFunctionService,
173+ this.encryptService,
142 this.platformUtilsService,
174 this.platformUtilsService,
143 this.logService,
175 this.logService,
144 this.stateService
176 this.stateService
@@ -153,6 +185,16 @@ class MainBackground {
153 this.appIdService,
185 this.appIdService,
154 (expired) => this.logout(expired)
186 (expired) => this.logout(expired)
155 );
187 );
188+ this.passwordGenerationService = new PasswordGenerationService(
189+ this.cryptoService,
190+ this.policyService,
191+ this.stateService
192+ );
193+ this.totpService = new TotpService(
194+ this.cryptoFunctionService,
195+ this.logService,
196+ this.stateService
197+ );
156 }
198 }
157
199
158 async bootstrap() {
200 async bootstrap() {
@@ -172,31 +214,6 @@ class MainBackground {
172 }
214 }
173}
215}
174
216
175−class MemoryStorageService {
176− storage = {};
177−
178− async get(key) {
179− return this.storage[key]
180− }
181−
182− async has(key) {
183− return key in this.storage;
184− }
185−
186− async save(key, obj) {
187− if (obj == null) {
188− this.remove(key)
189− return
190− }
191−
192− this.storage[key] = obj
193− }
194−
195− async remove(key) {
196− delete this.storage[key]
197− }
198−}
199−
200class BrowserCryptoService extends CryptoService {
217class BrowserCryptoService extends CryptoService {
201 async retrieveKeyFromStorage(keySuffix) {
218 async retrieveKeyFromStorage(keySuffix) {
202 if (keySuffix === "biometric") {
219 if (keySuffix === "biometric") {
@@ -277,7 +294,7 @@ class BrowserPlatformUtilsService {
277 saveFile(win, blobData, blobOptions, fileName) {
294 saveFile(win, blobData, blobOptions, fileName) {
278 // BrowserApi.downloadFile(win, blobData, blobOptions, fileName);
295 // BrowserApi.downloadFile(win, blobData, blobOptions, fileName);
279 }
296 }
280− getApplicationVersion() {
297+ getApplicationVersionNumber() {
281 // return Promise.resolve(BrowserApi.getApplicationVersion());
298 // return Promise.resolve(BrowserApi.getApplicationVersion());
282 }
299 }
283 supportsWebAuthn(win) {
300 supportsWebAuthn(win) {
backend/BW.purs+45−28
@@ -1,8 +1,10 @@
1module BW where
1module BW where
2
2
3import BW.Types
3import BW.Types
4+import Bridge
4import Prelude
5import Prelude
5
6
7+import Bridge as Bridge
6import Control.Promise (Promise)
8import Control.Promise (Promise)
7import Data.Argonaut (class DecodeJson, class EncodeJson, decodeJson, encodeJson)
9import Data.Argonaut (class DecodeJson, class EncodeJson, decodeJson, encodeJson)
8import Data.ArrayBuffer.Typed as AB
10import Data.ArrayBuffer.Typed as AB
@@ -13,22 +15,37 @@ import Data.JNullable (JNullable)
13import Data.SymmetricCryptoKey (SymmetricCryptoKey)
15import Data.SymmetricCryptoKey (SymmetricCryptoKey)
14import Effect (Effect)
16import Effect (Effect)
15import Effect.Unsafe (unsafePerformEffect)
17import Effect.Unsafe (unsafePerformEffect)
16−
18+import Untagged.Union (type (|+|))
19+
20+type TotpService =
21+ { getCode :: String -> Promise String
22+ , getTimeInterval :: String -> Int
23+ }
24+
17−type CryptoService
25+type CryptoService =
18− = { makeKey :: Fn4 Password String KDF Int (Promise SymmetricCryptoKey)
26+ { makeKey :: Fn4 Password String KDF KdfConfig (Promise SymmetricCryptoKey)
19− , hashPassword :: Fn3 Password SymmetricCryptoKey (JNullable HashPurpose) (Promise StringHash)
27+ , hashPassword :: Fn3 Password SymmetricCryptoKey (JNullable HashPurpose) (Promise StringHash)
20− , decryptToUtf8 :: Fn2 EncString SymmetricCryptoKey (Promise String)
28+ , decryptToUtf8 :: Fn2 EncString SymmetricCryptoKey (Promise String)
21− , decryptToBytes :: Fn2 EncString SymmetricCryptoKey (Promise ArrayBuffer)
29+ , decryptToBytes :: Fn2 EncString SymmetricCryptoKey (Promise ArrayBuffer)
30+ , encrypt :: Fn2 String SymmetricCryptoKey (Promise EncString)
22− , stretchKey :: SymmetricCryptoKey -> Promise SymmetricCryptoKey
31+ , stretchKey :: SymmetricCryptoKey -> Promise SymmetricCryptoKey
23− }
32+ }
24−
33+
25−type ApiService
34+type ApiService =
26− = { postPrelogin :: PreloginRequest -> Promise PreloginResponse
35+ { postPrelogin :: PreloginRequest -> Promise PreloginResponse
27− , getProfile :: Unit -> Promise ProfileResponse
36+ , getProfile :: Unit -> Promise ProfileResponse
28− -- TODO: There can really be more request and response types. Maybe handle this later.
37+ -- TODO: There can really be more request and response types. Maybe handle this later.
29− , postIdentityToken :: PasswordTokenRequest -> Promise IdentityTokenResponse
38+ , postIdentityToken :: PasswordTokenRequest -> Promise (IdentityCaptchaResponse |+| IdentityTwoFactorResponse |+| IdentityTokenResponse)
30− , getSync :: Unit -> Promise SyncResponse
39+ , getSync :: Unit -> Promise SyncResponse
40+ , postCipherCreate :: CipherResponse -> Promise CipherResponse
41+ , putCipher :: CipherResponse -> Promise CipherResponse
42+ , deleteCipher :: String -> Promise Unit
43+ , postTwoFactorEmail :: TwoFactorEmailRequest -> Promise Unit
44+ }
45+
46+type PasswordGeneration =
47+ { generatePassword :: Bridge.PasswordGeneratorConfig -> Promise String
31− }
48+ }
32
49
33cryptoFunctionsTypeSha1 = "sha1" :: CryptoFunctionsType
50cryptoFunctionsTypeSha1 = "sha1" :: CryptoFunctionsType
34
51
@@ -38,15 +55,13 @@ cryptoFunctionsTypeSha512 = "sha512" :: CryptoFunctionsType
38
55
39cryptoFunctionsTypeMd5 = "md5" :: CryptoFunctionsType
56cryptoFunctionsTypeMd5 = "md5" :: CryptoFunctionsType
40
57
41−type CryptoFunctionsType
58+type CryptoFunctionsType = String
42− = String
43
59
44−type CryptoFunctions
60+type CryptoFunctions =
45− = { hash :: Fn2 String CryptoFunctionsType (Promise Hash)
61+ { hash :: Fn2 String CryptoFunctionsType (Promise Hash)
46− }
62+ }
47
63
48−newtype Hash
49− = Hash (ArrayBuffer)
64+newtype Hash = Hash (ArrayBuffer)
50
65
51hashToArray :: Hash -> Array Int
66hashToArray :: Hash -> Array Int
52hashToArray (Hash buf) =
67hashToArray (Hash buf) =
@@ -69,10 +84,12 @@ instance EncodeJson Hash where
69instance DecodeJson Hash where
84instance DecodeJson Hash where
70 decodeJson = decodeJson >>> map arrayToHash
85 decodeJson = decodeJson >>> map arrayToHash
71
86
72−type Services
87+type Services =
73− = { crypto :: CryptoService
88+ { crypto :: CryptoService
74− , getApi :: Urls -> JNullable IdentityTokenResponse -> Promise ApiService
89+ , getApi :: Urls -> JNullable IdentityTokenResponse -> Promise ApiService
75− , cryptoFunctions :: CryptoFunctions
90+ , cryptoFunctions :: CryptoFunctions
91+ , passwordGeneration :: PasswordGeneration
92+ , totpService :: TotpService
76− }
93+ }
77
94
78foreign import getServices :: Effect Services
95foreign import getServices :: Effect Services
backend/BW/Logic.purs+454−41
@@ -2,38 +2,78 @@ module BW.Logic where
2
2
3import BW
3import BW
4import BW.Types
4import BW.Types
5−import Control.El
6import Prelude
5import Prelude
6+
7+import Bridge as Bridge
8+import Control.Monad.Error.Class (throwError)
7import Control.Promise (Promise)
9import Control.Promise (Promise)
8import Control.Promise as Promise
10import Control.Promise as Promise
9import Data.EncString as EncString
11import Data.EncString as EncString
10import Data.Function.Uncurried (runFn2, runFn3, runFn4)
12import Data.Function.Uncurried (runFn2, runFn3, runFn4)
11−import Data.JNullable (jnull, nullify)
13+import Data.JNullable (JNullable, fromJNullable, jnull, nullify)
14+import Data.JNullable as JNullable
15+import Data.JOpt (JOpt(..), fromJOpt)
16+import Data.Maybe (Maybe(..), fromMaybe, maybe)
17+import Data.Newtype (class Newtype, unwrap, wrap)
18+import Data.Number.Format as Number
12import Data.String as String
19import Data.String as String
13import Data.SymmetricCryptoKey (SymmetricCryptoKey)
20import Data.SymmetricCryptoKey (SymmetricCryptoKey)
14import Data.SymmetricCryptoKey as SymmetricCryptoKey
21import Data.SymmetricCryptoKey as SymmetricCryptoKey
22+import Data.Traversable (traverse)
23+import Data.Undefined.NoProblem (opt, undefined)
24+import Effect (Effect)
25+import Effect.Aff (Aff)
15import Effect.Aff.Class (class MonadAff, liftAff)
26import Effect.Aff.Class (class MonadAff, liftAff)
16import Effect.Class (liftEffect)
27import Effect.Class (liftEffect)
28+import Effect.Exception (error)
17import Effect.Exception as Exc
29import Effect.Exception as Exc
30+import Effect.Random (random)
31+import Localstorage as Storage
32+import Run (Run)
33+import Run.Reader (Reader, askAt)
34+import Storage (DeviceIDKey(..))
35+import Type.Prelude (Proxy(..))
36+import Untagged.Union (type (|+|))
37+import Web.Storage.Storage (Storage)
18
38
19-- | Creates the master key
39-- | Creates the master key
20−makePreloginKey ::
40+makePreloginKey
21− forall r.
41+ :: forall r
22− HasL "crypto" CryptoService r =>
42+ . PreloginResponse
43+ -> Email
44+ -> Password
45+ -> Run
46+ ( crypto :: Reader CryptoService
47+ , effect :: Effect
48+ , aff :: Aff
49+ | r
50+ )
23− PreloginResponse -> Email -> Password -> Al r SymmetricCryptoKey
51+ SymmetricCryptoKey
24−makePreloginKey { kdf, kdfIterations } (Email email') password = do
52+makePreloginKey { kdf, kdfIterations, kdfMemory, kdfParallelism } (Email email') password = do
25− crypto <- l (L :: L "crypto")
53+ crypto <- askAt (Proxy :: _ "crypto")
26 let
54 let
27 email = (String.trim >>> String.toLower) email'
55 email = (String.trim >>> String.toLower) email'
28− liftPromise $ runFn4 crypto.makeKey password email kdf kdfIterations
56+ liftPromise $ runFn4 crypto.makeKey password email kdf
57+ { iterations: kdfIterations
58+ , memory: kdfMemory
59+ , parallelism: kdfParallelism
60+ }
29
61
30−makeDecryptionKey ::
62+makeDecryptionKey
31− forall r.
63+ :: forall r
32− HasL "crypto" CryptoService r =>
64+ .
33 -- | Matser key (prelogin key)
65 -- | Matser key (prelogin key)
34− SymmetricCryptoKey -> EncryptedString -> Al r SymmetricCryptoKey
66+ SymmetricCryptoKey
67+ -> EncryptedString
68+ -> Run
69+ ( crypto :: Reader CryptoService
70+ , effect :: Effect
71+ , aff :: Aff
72+ | r
73+ )
74+ SymmetricCryptoKey
35makeDecryptionKey masterKey str = do
75makeDecryptionKey masterKey str = do
36− crypto <- l (L :: L "crypto")
76+ crypto <- askAt (Proxy :: _ "crypto")
37 let
77 let
38 encryptedKeyString = EncString.fromString str
78 encryptedKeyString = EncString.fromString str
39
79
@@ -48,52 +88,425 @@ makeDecryptionKey masterKey str = do
48 liftEffect $ Exc.throw $ "Unsupported key encryption type: " <> show encType
88 liftEffect $ Exc.throw $ "Unsupported key encryption type: " <> show encType
49 pure $ SymmetricCryptoKey.fromArrayBuffer key
89 pure $ SymmetricCryptoKey.fromArrayBuffer key
50
90
51−getLogInRequestToken ::
91+getLogInRequestToken
52− forall r.
92+ :: forall r
93+ . { prelogin :: PreloginResponse
94+ , email :: Email
95+ , password :: Password
96+ , captchaResponse :: Maybe String
97+ , secondFactor :: Bridge.Cmd_Login_secondFactor_Maybe
98+ }
99+ -> Run
53− HasL "api" ApiService r =>
100+ ( api :: Reader ApiService
54− HasL "crypto" CryptoService r =>
101+ , crypto :: Reader CryptoService
55− PreloginResponse -> Email -> Password -> Al r IdentityTokenResponse
102+ , effect :: Effect
103+ , aff :: Aff
104+ , storage :: Reader Storage
105+ | r
106+ )
107+ (IdentityCaptchaResponse |+| IdentityTwoFactorResponse |+| IdentityTokenResponse)
56−getLogInRequestToken prelogin email password = do
108+getLogInRequestToken
109+ { prelogin
110+ , email
111+ , password
112+ , captchaResponse
113+ , secondFactor: Bridge.Cmd_Login_secondFactor_Maybe secondFactor
114+ } = do
57 key <- makePreloginKey prelogin email password
115 key <- makePreloginKey prelogin email password
58− crypto <- l (L :: L "crypto")
116+ crypto <- askAt (Proxy :: _ "crypto")
59− api :: ApiService <- l (L :: L "api")
117+ let
118+ twoFactor =
119+ maybe
120+ { provider: twoFactorProviderTypeEmail
121+ , token: ""
122+ , remember: false
123+ }
124+ ( \(Bridge.Cmd_Login_secondFactor x@{ provider }) ->
125+ x { provider = bridgeToSecondFactorType provider }
126+ )
127+ secondFactor
60 _localHashedPassword <-
128 _localHashedPassword <-
61 liftPromise
129 liftPromise
62 $ runFn3 crypto.hashPassword password key (nullify hashPurposeLocalAuthorization)
130 $ runFn3 crypto.hashPassword password key (nullify hashPurposeLocalAuthorization)
63 StringHash hashedPassword <- liftPromise $ runFn3 crypto.hashPassword password key jnull
131 StringHash hashedPassword <- liftPromise $ runFn3 crypto.hashPassword password key jnull
132+ deviceId <- getDeviceID
133+ api :: ApiService <- askAt (Proxy :: _ "api")
64 liftPromise
134 liftPromise
65 $ api.postIdentityToken
135 $ api.postIdentityToken
66 { email: email
136 { email: email
67 , masterPasswordHash: hashedPassword
137 , masterPasswordHash: hashedPassword
68− , captchaResponse: ""
138+ , captchaResponse: fromMaybe "" captchaResponse
69− , twoFactor:
139+ , twoFactor
70− { provider: twoFactorProviderTypeEmail
71− , token: ""
72− , remember: false
73− }
74 , device:
140 , device:
75 { type: deviceTypeUnknownBrowser
141 { type: deviceTypeUnknownBrowser
76− , name: "Temporary device name"
142+ , name: "Ubuntu Touch"
77− , identifier: "42"
143+ , identifier: deviceId
78 , pushToken: jnull
144 , pushToken: jnull
79 }
145 }
80 }
146 }
81
147
148+bwPasswordStringHash
149+ :: forall r
150+ . PreloginResponse
151+ -> Email
152+ -> Password
153+ -> Run
154+ ( crypto :: Reader CryptoService
155+ , effect :: Effect
156+ , aff :: Aff
157+ | r
158+ )
159+ StringHash
160+bwPasswordStringHash prelogin email password = do
161+ key <- makePreloginKey prelogin email password
162+ crypto <- askAt (Proxy :: _ "crypto")
163+ _localHashedPassword <-
164+ liftPromise
165+ $ runFn3 crypto.hashPassword password key (nullify hashPurposeLocalAuthorization)
166+ liftPromise $ runFn3 crypto.hashPassword password key jnull
167+
168+hashPassword
169+ :: forall r
170+ . Password
171+ -> Run
82−hashPassword :: forall r. HasL "cryptoFunctions" CryptoFunctions r => Password -> Al r Hash
172+ ( cryptoFunctions :: Reader CryptoFunctions
173+ , aff :: Aff
174+ , effect :: Effect
175+ | r
176+ )
177+ Hash
83hashPassword (Password password) = do
178hashPassword (Password password) = do
84− cryptoFunctions <- l (L :: L "cryptoFunctions")
179+ cryptoFunctions <- askAt (Proxy :: _ "cryptoFunctions")
85 liftPromise $ runFn2 cryptoFunctions.hash password cryptoFunctionsTypeSha512
180 liftPromise $ runFn2 cryptoFunctions.hash password cryptoFunctionsTypeSha512
86
181
87−decrypt ::
182+decrypt
88− forall r.
183+ :: forall r
89− HasL "crypto" CryptoService r =>
90− HasL "key" SymmetricCryptoKey r =>
91− EncryptedString ->
184+ . EncryptedString
185+ -> Run
186+ ( key :: Reader SymmetricCryptoKey
187+ , crypto :: Reader CryptoService
188+ , aff :: Aff
189+ , effect :: Effect
190+ | r
191+ )
92− Al r String
192+ String
93decrypt input = do
193decrypt input = do
94− crypto <- l (L :: L "crypto")
194+ crypto <- askAt (Proxy :: _ "crypto")
95− key <- l (L :: L "key")
195+ key <- askAt (Proxy :: _ "key")
96 liftPromise $ runFn2 crypto.decryptToUtf8 (EncString.fromString input) key
196 liftPromise $ runFn2 crypto.decryptToUtf8 (EncString.fromString input) key
97
197
198+encrypt
199+ :: forall r
200+ . String
201+ -> Run
202+ ( key :: Reader SymmetricCryptoKey
203+ , crypto :: Reader CryptoService
204+ , aff :: Aff
205+ , effect :: Effect
206+ | r
207+ )
208+ EncryptedString
209+encrypt input = do
210+ crypto <- askAt (Proxy :: _ "crypto")
211+ key <- askAt (Proxy :: _ "key")
212+ map EncString.toString $ liftPromise $ runFn2 crypto.encrypt input key
213+
98−liftPromise ∷ forall m a. MonadAff m ⇒ Promise a → m a
214+liftPromise :: forall m a. MonadAff m => Promise a -> m a
99liftPromise = liftAff <<< Promise.toAff
215liftPromise = liftAff <<< Promise.toAff
216+
217+encodeCipher
218+ :: forall r
219+ . Bridge.FullCipher
220+ -> Run
221+ ( key :: Reader SymmetricCryptoKey
222+ , crypto :: Reader CryptoService
223+ , aff :: Aff
224+ , effect :: Effect
225+ | r
226+ )
227+ CipherResponse
228+encodeCipher
229+ (Bridge.FullCipher { name, cipher, id, favorite, reprompt, collectionIds }) = do
230+ let
231+ f :: CipherResponse -> Run _ CipherResponse
232+ f x = case cipher of
233+ Bridge.CardCipher (Bridge.Cipher_CardCipher c) -> do
234+ cardholderName <- encryptNullable c.cardholderName
235+ brand <- encryptNullable c.brand
236+ number <- encryptNullable c.number
237+ expMonth <- encryptNullable c.expMonth
238+ expYear <- encryptNullable c.expYear
239+ code <- encryptNullable c.code
240+ pure
241+ x
242+ { card =
243+ nullify
244+ { cardholderName
245+ , brand
246+ , number
247+ , expMonth
248+ , expYear
249+ , code
250+ }
251+ , type = cipherTypeCard
252+ }
253+ Bridge.IdentityCipher (Bridge.Cipher_IdentityCipher identity) -> do
254+ address1 <- encryptNullable identity.address1
255+ address2 <- encryptNullable identity.address2
256+ address3 <- encryptNullable identity.address3
257+ city <- encryptNullable identity.city
258+ company <- encryptNullable identity.company
259+ country <- encryptNullable identity.country
260+ email <- encryptNullable identity.email
261+ firstName <- encryptNullable identity.firstName
262+ lastName <- encryptNullable identity.lastName
263+ licenseNumber <- encryptNullable identity.licenseNumber
264+ middleName <- encryptNullable identity.middleName
265+ passportNumber <- encryptNullable identity.passportNumber
266+ phone <- encryptNullable identity.phone
267+ postalCode <- encryptNullable identity.postalCode
268+ ssn <- encryptNullable identity.ssn
269+ state <- encryptNullable identity.state
270+ title <- encryptNullable identity.title
271+ username <- encryptNullable identity.username
272+ pure
273+ x
274+ { identity =
275+ nullify
276+ { address1
277+ , address2
278+ , address3
279+ , city
280+ , company
281+ , country
282+ , email
283+ , firstName
284+ , lastName
285+ , licenseNumber
286+ , middleName
287+ , passportNumber
288+ , phone
289+ , postalCode
290+ , ssn
291+ , state
292+ , title
293+ , username
294+ }
295+ , type = cipherTypeIdentity
296+ }
297+ Bridge.LoginCipher (Bridge.Cipher_LoginCipher login) -> do
298+ password <- encryptNullable login.password
299+ uris <-
300+ traverse
301+ ( \uri -> do
302+ uriEnc <- nullify <$> encrypt uri
303+ pure { uri: uriEnc, match: jnull }
304+ )
305+ (unwrap login.uris)
306+ username <- encryptNullable login.username
307+ totp <- encryptNullable login.totp
308+ pure
309+ x
310+ { login =
311+ nullify
312+ { password
313+ , uris: JOpt $ opt uris
314+ , username
315+ , passwordRevisionDate: jnull
316+ , totp: totp
317+ , autofillOnPageLoad: JOpt undefined
318+ }
319+ , type = cipherTypeLogin
320+ }
321+ Bridge.NoteCipher note -> do
322+ encNote <- encrypt note
323+ pure
324+ x
325+ { notes = nullify encNote
326+ , type = cipherTypeSecureNote
327+ }
328+ encName <- encrypt name
329+ f
330+ { id
331+ , organizationId: jnull
332+ , folderId: jnull
333+ , type: 0
334+ , name: encName
335+ , notes: jnull
336+ , fields: jnull
337+ , login: jnull
338+ , card: jnull
339+ , identity: jnull
340+ , secureNote: jnull
341+ , favorite: favorite
342+ , edit: jnull
343+ , viewPassword: jnull
344+ , organizationUseTotp: jnull
345+ , revisionDate: jnull
346+ , attachments: jnull
347+ , passwordHistory: jnull
348+ , collectionIds: nullify $ unwrap collectionIds
349+ , deletedDate: jnull
350+ , reprompt
351+ }
352+
353+-- | Check if the cipher is supported by Warden.
354+supportedCipher :: CipherResponse -> Boolean
355+-- We do not support organizations since that requires managing a separate set of
356+-- encryption keys
357+supportedCipher { organizationId } | Just _ <- JNullable.toMaybe organizationId = false
358+supportedCipher _ = true
359+
360+decodeCipher
361+ :: forall r
362+ . CipherResponse
363+ -> Run
364+ ( key :: Reader SymmetricCryptoKey
365+ , crypto :: Reader CryptoService
366+ , aff :: Aff
367+ , effect :: Effect
368+ | r
369+ )
370+ (Maybe Bridge.FullCipher)
371+decodeCipher cipher | not (supportedCipher cipher) =
372+ pure $ Nothing
373+decodeCipher cipher = do
374+ name <- decrypt cipher.name
375+ cipherType <- case cipher.type of
376+ n
377+ | cipherTypeSecureNote == n -> do
378+ note <- fromJNullable "" <$> (traverse decrypt cipher.notes)
379+ pure $ Bridge.NoteCipher note
380+ n
381+ | cipherTypeLogin == n -> do
382+ case JNullable.toMaybe cipher.login of
383+ Nothing -> liftEffect $ throwError $ error "Login data is missing"
384+ Just login -> do
385+ username <- decryptNullable login.username
386+ password <- decryptNullable login.password
387+ totp <- decryptNullable login.totp
388+ uris <- fromJOpt [] <$>
389+ ((traverse >>> traverse) (_.uri >>> JNullable.toMaybe >>> maybe (pure "") decrypt) login.uris)
390+ pure $ Bridge.LoginCipher
391+ $ Bridge.Cipher_LoginCipher
392+ { username
393+ , password
394+ , uris: wrap uris
395+ , totp
396+ }
397+ n
398+ | cipherTypeCard == n -> do
399+ case JNullable.toMaybe cipher.card of
400+ Nothing -> liftEffect $ throwError $ error "Card data is missing"
401+ Just card -> do
402+ cardholderName <- decryptNullable card.cardholderName
403+ number <- decryptNullable card.number
404+ code <- decryptNullable card.code
405+ brand <- decryptNullable card.brand
406+ expMonth <- decryptNullable card.expMonth
407+ expYear <- decryptNullable card.expYear
408+ pure $ Bridge.CardCipher
409+ $ Bridge.Cipher_CardCipher
410+ { brand, cardholderName, code, expMonth, expYear, number }
411+ n
412+ | cipherTypeIdentity == n -> do
413+ case JNullable.toMaybe cipher.identity of
414+ Nothing -> liftEffect $ throwError $ error "Identity data is missing"
415+ Just identity -> do
416+ firstName <- decryptNullable identity.firstName
417+ middleName <- decryptNullable identity.middleName
418+ lastName <- decryptNullable identity.lastName
419+ address1 <- decryptNullable identity.address1
420+ address2 <- decryptNullable identity.address2
421+ address3 <- decryptNullable identity.address3
422+ city <- decryptNullable identity.city
423+ company <- decryptNullable identity.company
424+ country <- decryptNullable identity.country
425+ email <- decryptNullable identity.email
426+ licenseNumber <- decryptNullable identity.licenseNumber
427+ passportNumber <- decryptNullable identity.passportNumber
428+ phone <- decryptNullable identity.phone
429+ postalCode <- decryptNullable identity.postalCode
430+ ssn <- decryptNullable identity.ssn
431+ state <- decryptNullable identity.state
432+ title <- decryptNullable identity.title
433+ username <- decryptNullable identity.username
434+ pure $ Bridge.IdentityCipher
435+ $ wrap
436+ { firstName
437+ , middleName
438+ , lastName
439+ , address1
440+ , address2
441+ , address3
442+ , city
443+ , company
444+ , country
445+ , email
446+ , licenseNumber
447+ , passportNumber
448+ , phone
449+ , postalCode
450+ , ssn
451+ , state
452+ , title
453+ , username
454+ }
455+ n -> liftEffect $ throwError $ error $ "Unsupported cipher type: " <> show n
456+ pure $ Just
457+ $ wrap
458+ { cipher: cipherType
459+ , name
460+ , id: cipher.id
461+ , favorite: cipher.favorite
462+ , reprompt: cipher.reprompt
463+ , collectionIds: wrap <<< fromJNullable [] $ cipher.collectionIds
464+ }
465+
466+decryptNullable
467+ :: forall x r
468+ . Newtype x (Maybe String)
469+ => JNullable EncryptedString
470+ -> Run
471+ ( key :: Reader SymmetricCryptoKey
472+ , crypto :: Reader CryptoService
473+ , aff :: Aff
474+ , effect :: Effect
475+ | r
476+ )
477+ x
478+decryptNullable x = wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt x)
479+
480+encryptNullable
481+ :: forall x r
482+ . Newtype x (Maybe String)
483+ => x
484+ -> Run
485+ ( key :: Reader SymmetricCryptoKey
486+ , crypto :: Reader CryptoService
487+ , aff :: Aff
488+ , effect :: Effect
489+ | r
490+ )
491+ (JNullable EncryptedString)
492+encryptNullable x = case unwrap x of
493+ Nothing -> pure jnull
494+ Just y -> nullify <$> encrypt y
495+
496+getDeviceID ::
497+ forall r.
498+ Run
499+ ( effect :: Effect
500+ , storage :: Reader Storage
501+ | r
502+ )
503+ String
504+getDeviceID = do
505+ storage <- askAt (Proxy :: _ "storage")
506+ maybeId <- liftEffect $ Storage.get storage DeviceIDKey
507+ case maybeId of
508+ Just id -> pure id
509+ Nothing -> do
510+ id <- map Number.toString $ liftEffect $ random
511+ liftEffect $ Storage.store storage DeviceIDKey id
512+ pure id
backend/BW/Types.purs+454−393
@@ -2,14 +2,27 @@ module BW.Types where
2
2
3import Prelude
3import Prelude
4
4
5+import Bridge as Bridge
5import Data.Argonaut (class DecodeJson, class EncodeJson)
6import Data.Argonaut (class DecodeJson, class EncodeJson)
7+import Data.Either (Either)
8+import Data.Either as Either
9+import Data.Int as Int
6import Data.JNullable (JNullable)
10import Data.JNullable (JNullable)
7import Data.JOpt (JOpt)
11import Data.JOpt (JOpt)
12+import Data.Maybe (Maybe(..))
13+import Data.Nullable (Nullable)
8import Data.ShowableJson (ShowableJson)
14import Data.ShowableJson (ShowableJson)
9import Data.Timestamp (Timestamp)
15import Data.Timestamp (Timestamp)
16+import Data.Traversable (for)
17+import Data.Tuple.Nested ((/\))
18+import Foreign as Foreign
19+import Literals.Undefined (Undefined)
20+import Type.Prelude (Proxy(..))
21+import Untagged.TypeCheck (class HasRuntimeType)
22+import Untagged.Union (type (|+|))
23+import Untagged.Union as Union
10
24
11−newtype EncryptedString
25+newtype EncryptedString = EncryptedString String
12− = EncryptedString String
13
26
14derive newtype instance Show EncryptedString
27derive newtype instance Show EncryptedString
15derive newtype instance Ord EncryptedString
28derive newtype instance Ord EncryptedString
@@ -18,78 +31,86 @@ derive newtype instance EncodeJson EncryptedString
18derive newtype instance DecodeJson EncryptedString
31derive newtype instance DecodeJson EncryptedString
19
32
20kdfPBKDF2_SHA256 = 0 :: KDF
33kdfPBKDF2_SHA256 = 0 :: KDF
21−
34+kdfArgon2id = 1 :: KDF
35+
22−type KDF
36+type KDF = Int
23− = Int
37+
24−
25−type PreloginResponse
38+type PreloginResponse =
26− = { kdf :: KDF
39+ { kdf :: KDF
27− , kdfIterations :: Int
40+ , kdfIterations :: Int
41+ , kdfMemory :: JNullable Int
42+ , kdfParallelism :: JNullable Int
43+ }
44+
45+type KdfConfig =
46+ { iterations :: Int
47+ , memory :: JNullable Int
48+ , parallelism :: JNullable Int
28− }
49+ }
29−
50+
30−type PreloginRequest
51+type PreloginRequest =
31− = { email :: String
52+ { email :: Email
32− }
53+ }
33−
54+
34−type ProfileResponse
55+type ProfileResponse =
35− = { id :: String
56+ { id :: String
36− , name :: String
57+ , name :: JNullable String
37− , email :: String
58+ , email :: String
38− , emailVerified :: Boolean
59+ , emailVerified :: Boolean
39− , masterPasswordHint :: String
60+ , masterPasswordHint :: JNullable String
40− , premiumPersonally :: Boolean
61+ -- , premiumPersonally :: Boolean
41− -- , premiumFromOrganization :: Boolean
62+ -- , premiumFromOrganization :: Boolean
42− , culture :: String
63+ , culture :: JNullable String
43− , twoFactorEnabled :: Boolean
64+ , twoFactorEnabled :: Boolean
44− , key :: EncryptedString
65+ , key :: EncryptedString
45− , privateKey :: EncryptedString
66+ , privateKey :: EncryptedString
46− , securityStamp :: String
67+ , securityStamp :: JNullable String
47− , forcePasswordReset :: Boolean
68+ , forcePasswordReset :: Boolean
48− , usesKeyConnector :: Boolean
69+ , usesKeyConnector :: Boolean
49− , organizations :: Array ProfileOrganizationResponse
70+ -- , organizations :: Array ProfileOrganizationResponse
50− , providers :: Array ProfileProviderResponse
71+ , providers :: Array ProfileProviderResponse
51− , providerOrganizations :: Array ProfileProviderOrganizationResponse
72+ , providerOrganizations :: Array ProfileProviderOrganizationResponse
52− }
73+ }
53−
74+
54−type ProfileOrganizationResponse
75+type ProfileOrganizationResponse =
55− = { id :: String
76+ { id :: String
56− , name :: String
77+ , name :: JNullable String
57− , usePolicies :: Boolean
78+ , usePolicies :: JNullable Boolean
58− , useGroups :: Boolean
79+ , useGroups :: JNullable Boolean
59− , useDirectory :: Boolean
80+ , useDirectory :: JNullable Boolean
60− , useEvents :: Boolean
81+ , useEvents :: JNullable Boolean
61− , useTotp :: Boolean
82+ , useTotp :: JNullable Boolean
62− , use2fa :: Boolean
83+ , use2fa :: JNullable Boolean
63− , useApi :: Boolean
84+ , useApi :: JNullable Boolean
64− , useSso :: Boolean
85+ , useSso :: JNullable Boolean
65− , useKeyConnector :: Boolean
86+ , useKeyConnector :: JNullable Boolean
66− , useResetPassword :: Boolean
87+ , useResetPassword :: JNullable Boolean
67− , selfHost :: Boolean
88+ , selfHost :: JNullable Boolean
68− , usersGetPremium :: Boolean
89+ , usersGetPremium :: JNullable Boolean
69− , seats :: Int
90+ , seats :: JNullable Int
70− , maxCollections :: Int
91+ , maxCollections :: JNullable Int
71− , maxStorageGb :: JNullable Int
92+ , maxStorageGb :: JNullable Int
72− , key :: String
93+ , key :: JNullable String
73− , hasPublicAndPrivateKeys :: Boolean
94+ , hasPublicAndPrivateKeys :: JNullable Boolean
74− , status :: OrganizationUserStatusType
95+ , status :: JNullable OrganizationUserStatusType
75− , type :: OrganizationUserType
96+ , type :: JNullable OrganizationUserType
76− , enabled :: Boolean
97+ , enabled :: JNullable Boolean
77− , ssoBound :: Boolean
98+ , ssoBound :: JNullable Boolean
78− , identifier :: String
99+ , identifier :: JNullable String
79− , permissions :: PermissionsApi
100+ , permissions :: JNullable PermissionsApi
80− , resetPasswordEnrolled :: Boolean
101+ , resetPasswordEnrolled :: JNullable Boolean
81− , userId :: String
102+ , userId :: JNullable String
82− , providerId :: String
103+ , providerId :: JNullable String
83− , providerName :: String
104+ , providerName :: JNullable String
84− , familySponsorshipFriendlyName :: String
105+ , familySponsorshipFriendlyName :: JNullable String
85− , familySponsorshipAvailable :: Boolean
106+ , familySponsorshipAvailable :: JNullable Boolean
86− , planProductType :: ProductType
107+ , planProductType :: JNullable ProductType
87− , keyConnectorEnabled :: Boolean
108+ , keyConnectorEnabled :: JNullable Boolean
88− , keyConnectorUrl :: String
109+ , keyConnectorUrl :: JNullable String
89− , familySponsorshipLastSyncDate :: JNullable Timestamp
110+ , familySponsorshipLastSyncDate :: JNullable Timestamp
90− , familySponsorshipValidUntil :: JNullable Timestamp
111+ , familySponsorshipValidUntil :: JNullable Timestamp
91− , familySponsorshipToDelete :: JNullable Boolean
112+ , familySponsorshipToDelete :: JNullable Boolean
92− }
113+ }
93
114
94organizationUserStatusTypeInvited = 0 :: OrganizationUserStatusType
115organizationUserStatusTypeInvited = 0 :: OrganizationUserStatusType
95
116
@@ -97,8 +118,7 @@ organizationUserStatusTypeAccepted = 0 :: OrganizationUserStatusType
97
118
98organizationUserStatusTypeConfirmed = 0 :: OrganizationUserStatusType
119organizationUserStatusTypeConfirmed = 0 :: OrganizationUserStatusType
99
120
100−type OrganizationUserStatusType
121+type OrganizationUserStatusType = Int
101− = Int
102
122
103organizationUserTypeOwner = 0 :: OrganizationUserType
123organizationUserTypeOwner = 0 :: OrganizationUserType
104
124
@@ -110,27 +130,26 @@ organizationUserTypeManager = 3 :: OrganizationUserType
110
130
111organizationUserTypeCustom = 4 :: OrganizationUserType
131organizationUserTypeCustom = 4 :: OrganizationUserType
112
132
113−type OrganizationUserType
133+type OrganizationUserType = Int
114− = Int
134+
115−
116−type PermissionsApi
135+type PermissionsApi =
117− = { accessEventLogs :: Boolean
136+ { accessEventLogs :: Boolean
118− , accessImportExport :: Boolean
137+ , accessImportExport :: Boolean
119− , accessReports :: Boolean
138+ , accessReports :: Boolean
120− , manageAllCollections :: Boolean
139+ , manageAllCollections :: Boolean
121− , createNewCollections :: Boolean
140+ , createNewCollections :: Boolean
122− , editAnyCollection :: Boolean
141+ , editAnyCollection :: Boolean
123− , deleteAnyCollection :: Boolean
142+ , deleteAnyCollection :: Boolean
124− , manageAssignedCollections :: Boolean
143+ , manageAssignedCollections :: Boolean
125− , editAssignedCollections :: Boolean
144+ , editAssignedCollections :: Boolean
126− , deleteAssignedCollections :: Boolean
145+ , deleteAssignedCollections :: Boolean
127− , manageCiphers :: Boolean
146+ , manageCiphers :: Boolean
128− , manageGroups :: Boolean
147+ , manageGroups :: Boolean
129− , manageSso :: Boolean
148+ , manageSso :: Boolean
130− , managePolicies :: Boolean
149+ , managePolicies :: Boolean
131− , manageUsers :: Boolean
150+ , manageUsers :: Boolean
132− , manageResetPassword :: Boolean
151+ , manageResetPassword :: Boolean
133− }
152+ }
134
153
135productTypeFree = 0 :: ProductType
154productTypeFree = 0 :: ProductType
136
155
@@ -140,20 +159,19 @@ productTypeTeams = 2 :: ProductType
140
159
141productTypeEnterprise = 3 :: ProductType
160productTypeEnterprise = 3 :: ProductType
142
161
143−type ProductType
162+type ProductType = Int
144− = Int
163+
145−
146−type ProfileProviderResponse
164+type ProfileProviderResponse =
147− = { id :: String
165+ { id :: String
148− , name :: String
166+ , name :: JNullable String
149− , key :: String
167+ , key :: JNullable String
150− , status :: ProviderUserStatusType
168+ , status :: JNullable ProviderUserStatusType
151− , type :: ProviderUserType
169+ , type :: JNullable ProviderUserType
152− , enabled :: Boolean
170+ , enabled :: Boolean
153− , permissions :: PermissionsApi
171+ , permissions :: PermissionsApi
154− , userId :: String
172+ , userId :: JNullable String
155− , useEvents :: Boolean
173+ , useEvents :: Boolean
156− }
174+ }
157
175
158providerUserStatusTypeInvited = 0 :: ProviderUserStatusType
176providerUserStatusTypeInvited = 0 :: ProviderUserStatusType
159
177
@@ -161,81 +179,79 @@ providerUserStatusTypeAccepted = 1 :: ProviderUserStatusType
161
179
162providerUserStatusTypeConfirmed = 2 :: ProviderUserStatusType
180providerUserStatusTypeConfirmed = 2 :: ProviderUserStatusType
163
181
164−type ProviderUserStatusType
182+type ProviderUserStatusType = Int
165− = Int
166
183
167providerUserTypeProviderAdmin = 0 :: ProviderUserType
184providerUserTypeProviderAdmin = 0 :: ProviderUserType
168
185
169providerUserTypeServiceUser = 1 :: ProviderUserType
186providerUserTypeServiceUser = 1 :: ProviderUserType
170
187
171−type ProviderUserType
188+type ProviderUserType = Int
172− = Int
189+
173−
174−type ProfileProviderOrganizationResponse
190+type ProfileProviderOrganizationResponse =
175− = { id :: String
191+ { id :: String
176− , name :: String
192+ , name :: JNullable String
177− , usePolicies :: Boolean
193+ , usePolicies :: Boolean
178− , useGroups :: Boolean
194+ , useGroups :: Boolean
179− , useDirectory :: Boolean
195+ , useDirectory :: Boolean
180− , useEvents :: Boolean
196+ , useEvents :: Boolean
181− , useTotp :: Boolean
197+ , useTotp :: Boolean
182− , use2fa :: Boolean
198+ , use2fa :: Boolean
183− , useApi :: Boolean
199+ , useApi :: Boolean
184− , useSso :: Boolean
200+ , useSso :: Boolean
185− , useKeyConnector :: Boolean
201+ , useKeyConnector :: Boolean
186− , useResetPassword :: Boolean
202+ , useResetPassword :: Boolean
187− , selfHost :: Boolean
203+ , selfHost :: Boolean
188− , usersGetPremium :: Boolean
204+ , usersGetPremium :: Boolean
189− , seats :: Int
205+ , seats :: Int
190− , maxCollections :: Int
206+ , maxCollections :: Int
191− , maxStorageGb :: JNullable Int
207+ , maxStorageGb :: JNullable Int
192− , key :: String
208+ , key :: JNullable String
193− , hasPublicAndPrivateKeys :: Boolean
209+ , hasPublicAndPrivateKeys :: Boolean
194− , status :: OrganizationUserStatusType
210+ , status :: OrganizationUserStatusType
195− , type :: OrganizationUserType
211+ , type :: OrganizationUserType
196− , enabled :: Boolean
212+ , enabled :: Boolean
197− , ssoBound :: Boolean
213+ , ssoBound :: Boolean
198− , identifier :: String
214+ , identifier :: JNullable String
199− , permissions :: PermissionsApi
215+ , permissions :: PermissionsApi
200− , resetPasswordEnrolled :: Boolean
216+ , resetPasswordEnrolled :: Boolean
201− , userId :: String
217+ , userId :: JNullable String
202− , providerId :: String
218+ , providerId :: JNullable String
203− , providerName :: String
219+ , providerName :: JNullable String
204− , familySponsorshipFriendlyName :: String
220+ , familySponsorshipFriendlyName :: JNullable String
205− , familySponsorshipAvailable :: Boolean
221+ , familySponsorshipAvailable :: Boolean
206− , planProductType :: ProductType
222+ , planProductType :: ProductType
207− , keyConnectorEnabled :: Boolean
223+ , keyConnectorEnabled :: Boolean
208− , keyConnectorUrl :: String
224+ , keyConnectorUrl :: JNullable String
209− , familySponsorshipLastSyncDate :: JNullable Timestamp
225+ , familySponsorshipLastSyncDate :: JNullable Timestamp
210− , familySponsorshipValidUntil :: JNullable Timestamp
226+ , familySponsorshipValidUntil :: JNullable Timestamp
211− , familySponsorshipToDelete :: JNullable Boolean
227+ , familySponsorshipToDelete :: JNullable Boolean
212− }
228+ }
213−
229+
214−type Urls
230+type Urls =
215− = { base :: JNullable String
231+ { base :: JNullable String
216− , webVault :: JNullable String
232+ , webVault :: JNullable String
217− , api :: JNullable String
233+ , api :: JNullable String
218− , identity :: JNullable String
234+ , identity :: JNullable String
219− , icons :: JNullable String
235+ , icons :: JNullable String
220− , notifications :: JNullable String
236+ , notifications :: JNullable String
221− , events :: JNullable String
237+ , events :: JNullable String
222− , keyConnector :: JNullable String
238+ , keyConnector :: JNullable String
223− }
239+ }
224−
240+
225−type PasswordTokenRequest
241+type PasswordTokenRequest =
226− = { email :: Email
242+ { email :: Email
227− , masterPasswordHash :: String
243+ , masterPasswordHash :: String
228− , captchaResponse :: String
244+ , captchaResponse :: String
229− , twoFactor :: TokenRequestTwoFactor
245+ , twoFactor :: TokenRequestTwoFactor
230− , device :: DeviceRequest
246+ , device :: DeviceRequest
231− }
247+ }
232−
248+
233−type DeviceRequest
249+type DeviceRequest =
234− = { type :: DeviceType
250+ { type :: DeviceType
235− , name :: String
251+ , name :: String
236− , identifier :: String
252+ , identifier :: String
237− , pushToken :: JNullable String
253+ , pushToken :: JNullable String
238− }
254+ }
239
255
240deviceTypeAndroid = 0 :: DeviceType
256deviceTypeAndroid = 0 :: DeviceType
241
257
@@ -279,14 +295,13 @@ deviceTypeVivaldiExtension = 19 :: DeviceType
279
295
280deviceTypeSafariExtension = 20 :: DeviceType
296deviceTypeSafariExtension = 20 :: DeviceType
281
297
282−type DeviceType
298+type DeviceType = Int
283− = Int
284
299
285−type TokenRequestTwoFactor
300+type TokenRequestTwoFactor =
286− = { provider :: TwoFactorProviderType
301+ { provider :: TwoFactorProviderType
287− , token :: String
302+ , token :: String
288− , remember :: Boolean
303+ , remember :: Boolean
289− }
304+ }
290
305
291twoFactorProviderTypeAuthenticator = 0 :: TwoFactorProviderType
306twoFactorProviderTypeAuthenticator = 0 :: TwoFactorProviderType
292
307
@@ -304,24 +319,51 @@ twoFactorProviderTypeOrganizationDuo = 6 :: TwoFactorProviderType
304
319
305twoFactorProviderTypeWebAuthn = 7 :: TwoFactorProviderType
320twoFactorProviderTypeWebAuthn = 7 :: TwoFactorProviderType
306
321
307−type TwoFactorProviderType
322+type TwoFactorProviderType = Int
308− = Int
323+
309−
324+secondFactorTypeToBridge
325+ :: TwoFactorProviderType
326+ -> Maybe Bridge.TwoFactorProviderType
327+secondFactorTypeToBridge x = case x of
328+ n
329+ | n == twoFactorProviderTypeAuthenticator -> Just Bridge.Authenticator
330+ n
331+ | n == twoFactorProviderTypeEmail -> Just Bridge.Email
332+ n
333+ | n == twoFactorProviderTypeDuo -> Just Bridge.Duo
334+ n
335+ | n == twoFactorProviderTypeYubikey -> Just Bridge.Yubikey
336+ n
337+ | n == twoFactorProviderTypeU2f -> Just Bridge.U2f
338+ n
339+ | n == twoFactorProviderTypeRemember -> Just Bridge.Remember
340+ n
341+ | n == twoFactorProviderTypeOrganizationDuo -> Just Bridge.OrganizationDuo
342+ n
343+ | n == twoFactorProviderTypeWebAuthn -> Just Bridge.WebAuthn
344+ _ -> Nothing
345+
346+bridgeToSecondFactorType :: Bridge.TwoFactorProviderType -> TwoFactorProviderType
347+bridgeToSecondFactorType Bridge.Authenticator = twoFactorProviderTypeAuthenticator
348+bridgeToSecondFactorType Bridge.Email = twoFactorProviderTypeEmail
349+bridgeToSecondFactorType Bridge.Duo = twoFactorProviderTypeDuo
350+bridgeToSecondFactorType Bridge.Yubikey = twoFactorProviderTypeYubikey
351+bridgeToSecondFactorType Bridge.U2f = twoFactorProviderTypeU2f
352+bridgeToSecondFactorType Bridge.Remember = twoFactorProviderTypeRemember
353+bridgeToSecondFactorType Bridge.OrganizationDuo = twoFactorProviderTypeOrganizationDuo
354+bridgeToSecondFactorType Bridge.WebAuthn = twoFactorProviderTypeWebAuthn
355+
310−newtype Password
356+newtype Password = Password String
311− = Password String
312
357
313hashPurposeServerAuthorization = 1 :: HashPurpose
358hashPurposeServerAuthorization = 1 :: HashPurpose
314
359
315hashPurposeLocalAuthorization = 2 :: HashPurpose
360hashPurposeLocalAuthorization = 2 :: HashPurpose
316
361
317−type HashPurpose
362+type HashPurpose = Int
318− = Int
319
363
320−newtype StringHash
364+newtype StringHash = StringHash String
321− = StringHash String
322
365
323−newtype AccessToken
366+newtype AccessToken = AccessToken String
324− = AccessToken String
325
367
326derive newtype instance Show AccessToken
368derive newtype instance Show AccessToken
327derive newtype instance Eq AccessToken
369derive newtype instance Eq AccessToken
@@ -329,8 +371,7 @@ derive newtype instance Ord AccessToken
329derive newtype instance EncodeJson AccessToken
371derive newtype instance EncodeJson AccessToken
330derive newtype instance DecodeJson AccessToken
372derive newtype instance DecodeJson AccessToken
331
373
332−newtype RefreshToken
374+newtype RefreshToken = RefreshToken String
333− = RefreshToken String
334
375
335derive newtype instance Show RefreshToken
376derive newtype instance Show RefreshToken
336derive newtype instance Eq RefreshToken
377derive newtype instance Eq RefreshToken
@@ -338,73 +379,95 @@ derive newtype instance Ord RefreshToken
338derive newtype instance EncodeJson RefreshToken
379derive newtype instance EncodeJson RefreshToken
339derive newtype instance DecodeJson RefreshToken
380derive newtype instance DecodeJson RefreshToken
340
381
341−
382+type IdentityCaptchaResponse =
383+ { siteKey :: String
384+ }
385+
342−type IdentityTokenResponse
386+type IdentityTokenResponse =
343− = { accessToken :: AccessToken
387+ { accessToken :: AccessToken
344− , expiresIn :: Int
388+ , expiresIn :: Int
345− , refreshToken :: RefreshToken
389+ , refreshToken :: RefreshToken
346− , tokenType :: String
390+ , tokenType :: String
347− , resetMasterPassword :: Boolean
391+ , resetMasterPassword :: Boolean
348− , privateKey :: EncryptedString
392+ , privateKey :: EncryptedString
349− , key :: EncryptedString
393+ , key :: EncryptedString
350− , twoFactorToken :: JNullable String
394+ , twoFactorToken :: JNullable String
351− , kdf :: KDF
395+ , kdf :: KDF
352− , kdfIterations :: Int
396+ , kdfIterations :: Int
353− , forcePasswordReset :: JNullable Boolean
397+ , forcePasswordReset :: JNullable Boolean
354− , apiUseKeyConnector :: JNullable Boolean
398+ , apiUseKeyConnector :: JNullable Boolean
355− , keyConnectorUrl :: JNullable String
399+ , keyConnectorUrl :: JNullable String
356− }
400+ }
357−
401+
358−newtype Email
402+newtype TwoFactorProviderTypes = TwoFactorProviderTypes (Array (Int |+| String))
403+
404+instance HasRuntimeType TwoFactorProviderTypes where
405+ hasRuntimeType Proxy = Foreign.isArray
406+
407+fromTwoFactorProviderType :: TwoFactorProviderTypes -> Either String (Array Bridge.TwoFactorProviderType)
408+fromTwoFactorProviderType (TwoFactorProviderTypes xs) =
409+ for xs $ \rawProvider -> do
410+ providerInt <- Union.reduce (Either.Right /\ (\x -> Either.note x $ Int.fromString x)) rawProvider
411+ Either.note (show providerInt) $ secondFactorTypeToBridge providerInt
412+
413+type IdentityTwoFactorResponse =
414+ { twoFactorProviders :: TwoFactorProviderTypes
415+ -- it is a Map type and untaged unions gets confused
416+ -- , twoFactorProviders2 :: Object (Object String)
417+ , captchaToken :: String |+| Undefined
418+ }
419+
359− = Email String
420+newtype Email = Email String
360−
421+
361−type SyncResponse
422+type SyncResponse =
362− = { profile :: ProfileResponse
423+ { profile :: ProfileResponse
363− , folders :: Array FolderResponse
424+ , folders :: Array FolderResponse
364− , collections :: Array CollectionDetailsResponse
425+ , collections :: Array CollectionDetailsResponse
365− , ciphers :: Array CipherResponse
426+ , ciphers :: Array CipherResponse
366− , domains :: JNullable DomainsResponse
427+ , domains :: JNullable DomainsResponse
367− , policies :: JNullable (Array PolicyResponse)
428+ , policies :: JNullable (Array PolicyResponse)
368− , sends :: Array SendResponse
429+ , sends :: Array SendResponse
369− }
430+ }
370−
431+
371−type FolderResponse
432+type FolderResponse =
372− = { id :: String
433+ { id :: String
373− , name :: String
434+ , name :: JNullable String
374− , revisionDate :: String
435+ , revisionDate :: JNullable String
375− }
436+ }
376−
437+
377−type CollectionDetailsResponse
438+type CollectionDetailsResponse =
378− = { id :: String
439+ { id :: String
379− , organizationId :: JNullable String
440+ , organizationId :: JNullable String
380− , name :: String
441+ , name :: JNullable String
381− , externalId :: JNullable String
442+ , externalId :: JNullable String
382− , readOnly :: Boolean
443+ , readOnly :: Boolean
383− }
444+ }
384−
445+
446+type CipherId = String
447+
385−type CipherResponse
448+type CipherResponse =
386− = { id :: String
449+ { id :: CipherId
387− , organizationId :: JNullable String
450+ , organizationId :: JNullable String
388− , folderId :: JNullable String
451+ , folderId :: JNullable String
389− , type :: CipherType
452+ , type :: CipherType
390− , name :: EncryptedString
453+ , name :: EncryptedString
391− , notes :: JNullable EncryptedString
454+ , notes :: JNullable EncryptedString
392− , fields :: JNullable (Array FieldApi)
455+ , fields :: JNullable (Array FieldApi)
393− , login :: JNullable LoginApi
456+ , login :: JNullable LoginApi
394− , card :: JNullable CardApi
457+ , card :: JNullable CardApi
395− , identity :: JNullable IdentityApi
458+ , identity :: JNullable IdentityApi
396− , secureNote :: JNullable SecureNoteApi
459+ , secureNote :: JNullable SecureNoteApi
397− , favorite :: Boolean
460+ , favorite :: Boolean
398− , edit :: Boolean
461+ , edit :: JNullable Boolean
399− , viewPassword :: Boolean
462+ , viewPassword :: JNullable Boolean
400− , organizationUseTotp :: Boolean
463+ , organizationUseTotp :: JNullable Boolean
401− , revisionDate :: Timestamp
464+ , revisionDate :: JNullable Timestamp
402− , attachments :: JNullable (Array AttachmentResponse)
465+ , attachments :: JNullable (Array AttachmentResponse)
403− , passwordHistory :: JNullable (Array PasswordHistoryResponse)
466+ , passwordHistory :: JNullable (Array PasswordHistoryResponse)
404− , collectionIds :: JNullable (Array String)
467+ , collectionIds :: JNullable (Array String)
405− , deletedDate :: JNullable String
468+ , deletedDate :: JNullable String
406− , reprompt :: CipherRepromptType
469+ , reprompt :: CipherRepromptType
407− }
470+ }
408
471
409cipherTypeLogin = 1 :: CipherType
472cipherTypeLogin = 1 :: CipherType
410
473
@@ -414,15 +477,14 @@ cipherTypeCard = 3 :: CipherType
414
477
415cipherTypeIdentity = 4 :: CipherType
478cipherTypeIdentity = 4 :: CipherType
416
479
417−type CipherType
480+type CipherType = Int
418− = Int
419
481
420−type FieldApi
482+type FieldApi =
421− = { name :: String
483+ { name :: JNullable String
422− , value :: String
484+ , value :: JNullable String
423− , type :: FieldType
485+ , type :: FieldType
424− , linkedId :: JNullable LinkedIdType
486+ , linkedId :: JNullable LinkedIdType
425− }
487+ }
426
488
427fieldTypeText = 0 :: FieldType
489fieldTypeText = 0 :: FieldType
428
490
@@ -432,8 +494,7 @@ fieldTypeBoolean = 2 :: FieldType
432
494
433fieldTypeLinked = 3 :: FieldType
495fieldTypeLinked = 3 :: FieldType
434
496
435−type FieldType
497+type FieldType = Int
436− = Int
437
498
438linkedIdTypeLoginLinkedIdUsername = 100 :: LinkedIdType
499linkedIdTypeLoginLinkedIdUsername = 100 :: LinkedIdType
439
500
@@ -489,23 +550,22 @@ linkedIdTypeIdentityLinkedIdLastName = 417 :: LinkedIdType
489
550
490linkedIdTypeIdentityLinkedIdFullName = 418 :: LinkedIdType
551linkedIdTypeIdentityLinkedIdFullName = 418 :: LinkedIdType
491
552
492−type LinkedIdType
553+type LinkedIdType = Int
493− = Int
494
554
495−type LoginApi
555+type LoginApi =
496− = { uris :: JOpt (Array LoginUriApi)
556+ { uris :: JOpt (Array LoginUriApi)
497− , username :: JNullable EncryptedString
557+ , username :: JNullable EncryptedString
498− , password :: JNullable EncryptedString
558+ , password :: JNullable EncryptedString
499− , passwordRevisionDate :: JNullable String
559+ , passwordRevisionDate :: JNullable String
500− , totp :: JNullable EncryptedString
560+ , totp :: JNullable EncryptedString
501− -- This thing is undefined in some cases for some reson. Just ignoring it
561+ -- This thing is undefined in some cases for some reson. Just ignoring it
502− , autofillOnPageLoad :: JOpt (JNullable Boolean)
562+ , autofillOnPageLoad :: JOpt (JNullable Boolean)
503− }
563+ }
504
564
505−type LoginUriApi
565+type LoginUriApi =
506− = { uri :: EncryptedString
566+ { uri :: JNullable EncryptedString
507− , match :: JNullable UriMatchType
567+ , match :: JNullable UriMatchType
508− }
568+ }
509
569
510uriMatchTypeDomain = 0 :: UriMatchType
570uriMatchTypeDomain = 0 :: UriMatchType
511
571
@@ -519,87 +579,85 @@ uriMatchTypeRegularExpression = 4 :: UriMatchType
519
579
520uriMatchTypeNever = 5 :: UriMatchType
580uriMatchTypeNever = 5 :: UriMatchType
521
581
522−type UriMatchType
582+type UriMatchType = Int
523− = Int
583+
524−
525−type CardApi
584+type CardApi =
526− = { cardholderName :: JNullable EncryptedString
585+ { cardholderName :: JNullable EncryptedString
527− , brand :: JNullable EncryptedString
586+ , brand :: JNullable EncryptedString
528− , number :: JNullable EncryptedString
587+ , number :: JNullable EncryptedString
529− , expMonth :: JNullable EncryptedString
588+ , expMonth :: JNullable EncryptedString
530− , expYear :: JNullable EncryptedString
589+ , expYear :: JNullable EncryptedString
531− , code :: JNullable EncryptedString
590+ , code :: JNullable EncryptedString
532− }
591+ }
533−
592+
534−type IdentityApi
593+type IdentityApi =
535− = { title :: JNullable EncryptedString
594+ { title :: JNullable EncryptedString
536− , firstName :: JNullable EncryptedString
595+ , firstName :: JNullable EncryptedString
537− , middleName :: JNullable EncryptedString
596+ , middleName :: JNullable EncryptedString
538− , lastName :: JNullable EncryptedString
597+ , lastName :: JNullable EncryptedString
539− , address1 :: JNullable EncryptedString
598+ , address1 :: JNullable EncryptedString
540− , address2 :: JNullable EncryptedString
599+ , address2 :: JNullable EncryptedString
541− , address3 :: JNullable EncryptedString
600+ , address3 :: JNullable EncryptedString
542− , city :: JNullable EncryptedString
601+ , city :: JNullable EncryptedString
543− , state :: JNullable EncryptedString
602+ , state :: JNullable EncryptedString
544− , postalCode :: JNullable EncryptedString
603+ , postalCode :: JNullable EncryptedString
545− , country :: JNullable EncryptedString
604+ , country :: JNullable EncryptedString
546− , company :: JNullable EncryptedString
605+ , company :: JNullable EncryptedString
547− , email :: JNullable EncryptedString
606+ , email :: JNullable EncryptedString
548− , phone :: JNullable EncryptedString
607+ , phone :: JNullable EncryptedString
549− , ssn :: JNullable EncryptedString
608+ , ssn :: JNullable EncryptedString
550− , username :: JNullable EncryptedString
609+ , username :: JNullable EncryptedString
551− , passportNumber :: JNullable EncryptedString
610+ , passportNumber :: JNullable EncryptedString
552− , licenseNumber :: JNullable EncryptedString
611+ , licenseNumber :: JNullable EncryptedString
553− }
612+ }
554
613
555secureNoteTypeGeneric = 0 :: SecureNoteType
614secureNoteTypeGeneric = 0 :: SecureNoteType
556
615
557−type SecureNoteType
616+type SecureNoteType = Int
558− = Int
559
617
560−type SecureNoteApi = {
618+type SecureNoteApi =
619+ {
561− type :: SecureNoteType
620+ -- type :: SecureNoteType
562−}
621+ }
563
622
564−type AttachmentResponse
623+type AttachmentResponse =
565− = { id :: String
624+ { id :: String
566− , url :: String
625+ , url :: JNullable String
567− , fileName :: String
626+ , fileName :: JNullable String
568− , key :: String
627+ , key :: JNullable String
569− , size :: String
628+ , size :: JNullable String
570− , sizeName :: String
629+ , sizeName :: JNullable String
571− }
630+ }
572
631
573−type PasswordHistoryResponse
632+type PasswordHistoryResponse =
574− = { password :: String
633+ { password :: String
575− , lastUsedDate :: String
634+ , lastUsedDate :: JNullable String
576− }
635+ }
577
636
578cipherRepromptTypeNone = 0 :: CipherRepromptType
637cipherRepromptTypeNone = 0 :: CipherRepromptType
579
638
580cipherRepromptTypePassword = 1 :: CipherRepromptType
639cipherRepromptTypePassword = 1 :: CipherRepromptType
581
640
582−type CipherRepromptType
641+type CipherRepromptType = Int
583− = Int
584
642
585−type DomainsResponse
643+type DomainsResponse =
586− = { equivalentDomains :: Array (Array String)
644+ { equivalentDomains :: Array (Array String)
587− , globalEquivalentDomains :: Array GlobalDomainResponse
645+ , globalEquivalentDomains :: Array GlobalDomainResponse
588− }
646+ }
589
647
590−type GlobalDomainResponse
648+type GlobalDomainResponse =
591− = { type :: Int
649+ { type :: Int
592− , domains :: Array String
650+ , domains :: Array String
593− , excluded :: Boolean
651+ , excluded :: Boolean
594− }
652+ }
595
653
596−type PolicyResponse
654+type PolicyResponse =
597− = { id :: String
655+ { id :: String
598− , organizationId :: JNullable String
656+ , organizationId :: JNullable String
599− , type :: PolicyType
657+ , type :: PolicyType
600− , data :: ShowableJson
658+ , data :: ShowableJson
601− , enabled :: Boolean
659+ , enabled :: Boolean
602− }
660+ }
603
661
604policyTypeTwoFactorAuthentication = 0 :: PolicyType -- Requires users to have 2fa enabled
662policyTypeTwoFactorAuthentication = 0 :: PolicyType -- Requires users to have 2fa enabled
605
663
@@ -623,43 +681,46 @@ policyTypeMaximumVaultTimeout = 9 :: PolicyType -- Sets the maximum allowed vaul
623
681
624policyTypeDisablePersonalVaultExport = 10 :: PolicyType -- Disable personal vault export
682policyTypeDisablePersonalVaultExport = 10 :: PolicyType -- Disable personal vault export
625
683
626−type PolicyType
684+type PolicyType = Int
627− = Int
685+
628−
629−type SendResponse
686+type SendResponse =
630− = { id :: String
687+ { id :: String
631− , accessId :: String
688+ , accessId :: JNullable String
632− , type :: SendType
689+ , type :: SendType
633− , name :: String
690+ , name :: JNullable String
634− , notes :: JNullable String
691+ , notes :: JNullable String
635− , file :: JNullable SendFileApi
692+ , file :: JNullable SendFileApi
636− , text :: JNullable SendTextApi
693+ , text :: JNullable SendTextApi
637− , key :: String
694+ , key :: JNullable String
638− , maxAccessCount :: JNullable Int
695+ , maxAccessCount :: JNullable Int
639− , accessCount :: Int
696+ , accessCount :: JNullable Int
640− , revisionDate :: String
697+ , revisionDate :: JNullable String
641− , expirationDate :: String
698+ , expirationDate :: JNullable String
642− , deletionDate :: JNullable String
699+ , deletionDate :: JNullable String
643− , password :: String
700+ , password :: JNullable String
644− , disable :: Boolean
701+ , disable :: Boolean
645− , hideEmail :: Boolean
702+ , hideEmail :: Boolean
646− }
703+ }
647
704
648sendTypeText = 0 :: SendType
705sendTypeText = 0 :: SendType
649
706
650sendTypeFile = 1 :: SendType
707sendTypeFile = 1 :: SendType
651
708
652−type SendType
709+type SendType = Int
653− = Int
710+
711+type SendFileApi =
712+ { id :: String
713+ , fileName :: JNullable String
714+ , size :: JNullable String
715+ , sizeName :: JNullable String
716+ }
654
717
655−type SendFileApi
718+type SendTextApi =
656− = { id :: String
719+ { text :: JNullable String
657− , fileName :: String
720+ , hidden :: Boolean
658− , size :: String
659− , sizeName :: String
660− }
721+ }
661
722
662−type SendTextApi
723+type TwoFactorEmailRequest =
663− = { text :: String
724+ { email :: Email
664− , hidden :: Boolean
725+ , masterPasswordHash :: StringHash
665− }
726+ }
backend/Bridge.purs+496−294
@@ -12,448 +12,593 @@ import Data.Newtype (class Newtype)
12
12
13import Prelude
13import Prelude
14
14
15−newtype Cmd_Login =
15+newtype Cipher_CardCipher_brand_Maybe =
16− Cmd_Login {
16+ Cipher_CardCipher_brand_Maybe (Maybe String)
17− email :: String
18− , password :: String
19− , server :: String
20− }
21
17
22−instance encodeJsonCmd_Login :: EncodeJson Cmd_Login where
18+derive instance newtypeCipher_CardCipher_brand_Maybe :: Newtype Cipher_CardCipher_brand_Maybe _
19+instance encodeJsonCipher_CardCipher_brand_Maybe :: EncodeJson Cipher_CardCipher_brand_Maybe where
23 encodeJson = genericEncodeAeson Argonaut.defaultOptions
20 encodeJson = genericEncodeAeson Argonaut.defaultOptions
24−instance decodeJsonCmd_Login :: DecodeJson Cmd_Login where
21+instance decodeJsonCipher_CardCipher_brand_Maybe :: DecodeJson Cipher_CardCipher_brand_Maybe where
25 decodeJson = genericDecodeAeson Argonaut.defaultOptions
22 decodeJson = genericDecodeAeson Argonaut.defaultOptions
26−derive instance genericCmd_Login :: Generic Cmd_Login _
23+derive instance genericCipher_CardCipher_brand_Maybe :: Generic Cipher_CardCipher_brand_Maybe _
27−derive instance eqCmd_Login :: Eq Cmd_Login
24+derive instance eqCipher_CardCipher_brand_Maybe :: Eq Cipher_CardCipher_brand_Maybe
28−derive instance ordCmd_Login :: Ord Cmd_Login
25+derive instance ordCipher_CardCipher_brand_Maybe :: Ord Cipher_CardCipher_brand_Maybe
29
26
30−data Cmd =
27+newtype Cipher_CardCipher_cardholderName_Maybe =
31− Init
28+ Cipher_CardCipher_cardholderName_Maybe (Maybe String)
32− | Login Cmd_Login
33− | NeedCiphersList
34− | NeedsReset
35− | RequestCipher String
36− | SendMasterPassword String
37
29
38−instance encodeJsonCmd :: EncodeJson Cmd where
30+derive instance newtypeCipher_CardCipher_cardholderName_Maybe :: Newtype Cipher_CardCipher_cardholderName_Maybe _
31+instance encodeJsonCipher_CardCipher_cardholderName_Maybe :: EncodeJson Cipher_CardCipher_cardholderName_Maybe where
39 encodeJson = genericEncodeAeson Argonaut.defaultOptions
32 encodeJson = genericEncodeAeson Argonaut.defaultOptions
40−instance decodeJsonCmd :: DecodeJson Cmd where
33+instance decodeJsonCipher_CardCipher_cardholderName_Maybe :: DecodeJson Cipher_CardCipher_cardholderName_Maybe where
41 decodeJson = genericDecodeAeson Argonaut.defaultOptions
34 decodeJson = genericDecodeAeson Argonaut.defaultOptions
42−derive instance genericCmd :: Generic Cmd _
35+derive instance genericCipher_CardCipher_cardholderName_Maybe :: Generic Cipher_CardCipher_cardholderName_Maybe _
43−derive instance eqCmd :: Eq Cmd
36+derive instance eqCipher_CardCipher_cardholderName_Maybe :: Eq Cipher_CardCipher_cardholderName_Maybe
44−derive instance ordCmd :: Ord Cmd
37+derive instance ordCipher_CardCipher_cardholderName_Maybe :: Ord Cipher_CardCipher_cardholderName_Maybe
45
38
46−newtype Sub_LoadCipher_cipherType_CardCipher_brand_Maybe =
39+newtype Cipher_CardCipher_code_Maybe =
47− Sub_LoadCipher_cipherType_CardCipher_brand_Maybe (Maybe String)
40+ Cipher_CardCipher_code_Maybe (Maybe String)
48
41
49−instance encodeJsonSub_LoadCipher_cipherType_CardCipher_brand_Maybe :: EncodeJson Sub_LoadCipher_cipherType_CardCipher_brand_Maybe where
42+derive instance newtypeCipher_CardCipher_code_Maybe :: Newtype Cipher_CardCipher_code_Maybe _
43+instance encodeJsonCipher_CardCipher_code_Maybe :: EncodeJson Cipher_CardCipher_code_Maybe where
50 encodeJson = genericEncodeAeson Argonaut.defaultOptions
44 encodeJson = genericEncodeAeson Argonaut.defaultOptions
51−instance decodeJsonSub_LoadCipher_cipherType_CardCipher_brand_Maybe :: DecodeJson Sub_LoadCipher_cipherType_CardCipher_brand_Maybe where
45+instance decodeJsonCipher_CardCipher_code_Maybe :: DecodeJson Cipher_CardCipher_code_Maybe where
52 decodeJson = genericDecodeAeson Argonaut.defaultOptions
46 decodeJson = genericDecodeAeson Argonaut.defaultOptions
53−derive instance genericSub_LoadCipher_cipherType_CardCipher_brand_Maybe :: Generic Sub_LoadCipher_cipherType_CardCipher_brand_Maybe _
47+derive instance genericCipher_CardCipher_code_Maybe :: Generic Cipher_CardCipher_code_Maybe _
54−derive instance eqSub_LoadCipher_cipherType_CardCipher_brand_Maybe :: Eq Sub_LoadCipher_cipherType_CardCipher_brand_Maybe
48+derive instance eqCipher_CardCipher_code_Maybe :: Eq Cipher_CardCipher_code_Maybe
55−derive instance ordSub_LoadCipher_cipherType_CardCipher_brand_Maybe :: Ord Sub_LoadCipher_cipherType_CardCipher_brand_Maybe
49+derive instance ordCipher_CardCipher_code_Maybe :: Ord Cipher_CardCipher_code_Maybe
56
50
57−newtype Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe =
51+newtype Cipher_CardCipher_expMonth_Maybe =
58− Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe (Maybe String)
52+ Cipher_CardCipher_expMonth_Maybe (Maybe String)
59
53
60−instance encodeJsonSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe :: EncodeJson Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe where
54+derive instance newtypeCipher_CardCipher_expMonth_Maybe :: Newtype Cipher_CardCipher_expMonth_Maybe _
55+instance encodeJsonCipher_CardCipher_expMonth_Maybe :: EncodeJson Cipher_CardCipher_expMonth_Maybe where
61 encodeJson = genericEncodeAeson Argonaut.defaultOptions
56 encodeJson = genericEncodeAeson Argonaut.defaultOptions
62−instance decodeJsonSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe :: DecodeJson Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe where
57+instance decodeJsonCipher_CardCipher_expMonth_Maybe :: DecodeJson Cipher_CardCipher_expMonth_Maybe where
63 decodeJson = genericDecodeAeson Argonaut.defaultOptions
58 decodeJson = genericDecodeAeson Argonaut.defaultOptions
64−derive instance genericSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe :: Generic Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe _
59+derive instance genericCipher_CardCipher_expMonth_Maybe :: Generic Cipher_CardCipher_expMonth_Maybe _
65−derive instance eqSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe :: Eq Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe
60+derive instance eqCipher_CardCipher_expMonth_Maybe :: Eq Cipher_CardCipher_expMonth_Maybe
66−derive instance ordSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe :: Ord Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe
61+derive instance ordCipher_CardCipher_expMonth_Maybe :: Ord Cipher_CardCipher_expMonth_Maybe
67
62
68−newtype Sub_LoadCipher_cipherType_CardCipher_code_Maybe =
63+newtype Cipher_CardCipher_expYear_Maybe =
69− Sub_LoadCipher_cipherType_CardCipher_code_Maybe (Maybe String)
64+ Cipher_CardCipher_expYear_Maybe (Maybe String)
70
65
71−instance encodeJsonSub_LoadCipher_cipherType_CardCipher_code_Maybe :: EncodeJson Sub_LoadCipher_cipherType_CardCipher_code_Maybe where
66+derive instance newtypeCipher_CardCipher_expYear_Maybe :: Newtype Cipher_CardCipher_expYear_Maybe _
67+instance encodeJsonCipher_CardCipher_expYear_Maybe :: EncodeJson Cipher_CardCipher_expYear_Maybe where
72 encodeJson = genericEncodeAeson Argonaut.defaultOptions
68 encodeJson = genericEncodeAeson Argonaut.defaultOptions
73−instance decodeJsonSub_LoadCipher_cipherType_CardCipher_code_Maybe :: DecodeJson Sub_LoadCipher_cipherType_CardCipher_code_Maybe where
69+instance decodeJsonCipher_CardCipher_expYear_Maybe :: DecodeJson Cipher_CardCipher_expYear_Maybe where
74 decodeJson = genericDecodeAeson Argonaut.defaultOptions
70 decodeJson = genericDecodeAeson Argonaut.defaultOptions
75−derive instance genericSub_LoadCipher_cipherType_CardCipher_code_Maybe :: Generic Sub_LoadCipher_cipherType_CardCipher_code_Maybe _
71+derive instance genericCipher_CardCipher_expYear_Maybe :: Generic Cipher_CardCipher_expYear_Maybe _
76−derive instance eqSub_LoadCipher_cipherType_CardCipher_code_Maybe :: Eq Sub_LoadCipher_cipherType_CardCipher_code_Maybe
72+derive instance eqCipher_CardCipher_expYear_Maybe :: Eq Cipher_CardCipher_expYear_Maybe
77−derive instance ordSub_LoadCipher_cipherType_CardCipher_code_Maybe :: Ord Sub_LoadCipher_cipherType_CardCipher_code_Maybe
73+derive instance ordCipher_CardCipher_expYear_Maybe :: Ord Cipher_CardCipher_expYear_Maybe
78
74
79−newtype Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe =
75+newtype Cipher_CardCipher_number_Maybe =
80− Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe (Maybe String)
76+ Cipher_CardCipher_number_Maybe (Maybe String)
81
77
82−instance encodeJsonSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe :: EncodeJson Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe where
78+derive instance newtypeCipher_CardCipher_number_Maybe :: Newtype Cipher_CardCipher_number_Maybe _
79+instance encodeJsonCipher_CardCipher_number_Maybe :: EncodeJson Cipher_CardCipher_number_Maybe where
83 encodeJson = genericEncodeAeson Argonaut.defaultOptions
80 encodeJson = genericEncodeAeson Argonaut.defaultOptions
84−instance decodeJsonSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe :: DecodeJson Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe where
81+instance decodeJsonCipher_CardCipher_number_Maybe :: DecodeJson Cipher_CardCipher_number_Maybe where
85 decodeJson = genericDecodeAeson Argonaut.defaultOptions
82 decodeJson = genericDecodeAeson Argonaut.defaultOptions
86−derive instance genericSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe :: Generic Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe _
83+derive instance genericCipher_CardCipher_number_Maybe :: Generic Cipher_CardCipher_number_Maybe _
87−derive instance eqSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe :: Eq Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe
84+derive instance eqCipher_CardCipher_number_Maybe :: Eq Cipher_CardCipher_number_Maybe
88−derive instance ordSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe :: Ord Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe
85+derive instance ordCipher_CardCipher_number_Maybe :: Ord Cipher_CardCipher_number_Maybe
89
86
90−newtype Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe =
87+newtype Cipher_CardCipher =
91− Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe (Maybe String)
88+ Cipher_CardCipher {
89+ brand :: Cipher_CardCipher_brand_Maybe
90+ , cardholderName :: Cipher_CardCipher_cardholderName_Maybe
91+ , code :: Cipher_CardCipher_code_Maybe
92+ , expMonth :: Cipher_CardCipher_expMonth_Maybe
93+ , expYear :: Cipher_CardCipher_expYear_Maybe
94+ , number :: Cipher_CardCipher_number_Maybe
95+ }
92
96
93−instance encodeJsonSub_LoadCipher_cipherType_CardCipher_expYear_Maybe :: EncodeJson Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe where
97+derive instance newtypeCipher_CardCipher :: Newtype Cipher_CardCipher _
98+instance encodeJsonCipher_CardCipher :: EncodeJson Cipher_CardCipher where
94 encodeJson = genericEncodeAeson Argonaut.defaultOptions
99 encodeJson = genericEncodeAeson Argonaut.defaultOptions
95−instance decodeJsonSub_LoadCipher_cipherType_CardCipher_expYear_Maybe :: DecodeJson Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe where
100+instance decodeJsonCipher_CardCipher :: DecodeJson Cipher_CardCipher where
96 decodeJson = genericDecodeAeson Argonaut.defaultOptions
101 decodeJson = genericDecodeAeson Argonaut.defaultOptions
97−derive instance genericSub_LoadCipher_cipherType_CardCipher_expYear_Maybe :: Generic Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe _
102+derive instance genericCipher_CardCipher :: Generic Cipher_CardCipher _
98−derive instance eqSub_LoadCipher_cipherType_CardCipher_expYear_Maybe :: Eq Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe
103+derive instance eqCipher_CardCipher :: Eq Cipher_CardCipher
99−derive instance ordSub_LoadCipher_cipherType_CardCipher_expYear_Maybe :: Ord Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe
104+derive instance ordCipher_CardCipher :: Ord Cipher_CardCipher
100
105
101−newtype Sub_LoadCipher_cipherType_CardCipher_number_Maybe =
106+newtype Cipher_IdentityCipher_address1_Maybe =
102− Sub_LoadCipher_cipherType_CardCipher_number_Maybe (Maybe String)
107+ Cipher_IdentityCipher_address1_Maybe (Maybe String)
103
108
104−instance encodeJsonSub_LoadCipher_cipherType_CardCipher_number_Maybe :: EncodeJson Sub_LoadCipher_cipherType_CardCipher_number_Maybe where
109+derive instance newtypeCipher_IdentityCipher_address1_Maybe :: Newtype Cipher_IdentityCipher_address1_Maybe _
110+instance encodeJsonCipher_IdentityCipher_address1_Maybe :: EncodeJson Cipher_IdentityCipher_address1_Maybe where
105 encodeJson = genericEncodeAeson Argonaut.defaultOptions
111 encodeJson = genericEncodeAeson Argonaut.defaultOptions
106−instance decodeJsonSub_LoadCipher_cipherType_CardCipher_number_Maybe :: DecodeJson Sub_LoadCipher_cipherType_CardCipher_number_Maybe where
112+instance decodeJsonCipher_IdentityCipher_address1_Maybe :: DecodeJson Cipher_IdentityCipher_address1_Maybe where
107 decodeJson = genericDecodeAeson Argonaut.defaultOptions
113 decodeJson = genericDecodeAeson Argonaut.defaultOptions
108−derive instance genericSub_LoadCipher_cipherType_CardCipher_number_Maybe :: Generic Sub_LoadCipher_cipherType_CardCipher_number_Maybe _
114+derive instance genericCipher_IdentityCipher_address1_Maybe :: Generic Cipher_IdentityCipher_address1_Maybe _
109−derive instance eqSub_LoadCipher_cipherType_CardCipher_number_Maybe :: Eq Sub_LoadCipher_cipherType_CardCipher_number_Maybe
115+derive instance eqCipher_IdentityCipher_address1_Maybe :: Eq Cipher_IdentityCipher_address1_Maybe
110−derive instance ordSub_LoadCipher_cipherType_CardCipher_number_Maybe :: Ord Sub_LoadCipher_cipherType_CardCipher_number_Maybe
116+derive instance ordCipher_IdentityCipher_address1_Maybe :: Ord Cipher_IdentityCipher_address1_Maybe
111
117
112−newtype Sub_LoadCipher_cipherType_CardCipher =
118+newtype Cipher_IdentityCipher_address2_Maybe =
113− Sub_LoadCipher_cipherType_CardCipher {
119+ Cipher_IdentityCipher_address2_Maybe (Maybe String)
114− brand :: Sub_LoadCipher_cipherType_CardCipher_brand_Maybe
120+
115− , cardholderName :: Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe
121+derive instance newtypeCipher_IdentityCipher_address2_Maybe :: Newtype Cipher_IdentityCipher_address2_Maybe _
116− , code :: Sub_LoadCipher_cipherType_CardCipher_code_Maybe
122+instance encodeJsonCipher_IdentityCipher_address2_Maybe :: EncodeJson Cipher_IdentityCipher_address2_Maybe where
117− , expMonth :: Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe
123+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
118− , expYear :: Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe
124+instance decodeJsonCipher_IdentityCipher_address2_Maybe :: DecodeJson Cipher_IdentityCipher_address2_Maybe where
119− , number :: Sub_LoadCipher_cipherType_CardCipher_number_Maybe
125+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
120− }
126+derive instance genericCipher_IdentityCipher_address2_Maybe :: Generic Cipher_IdentityCipher_address2_Maybe _
127+derive instance eqCipher_IdentityCipher_address2_Maybe :: Eq Cipher_IdentityCipher_address2_Maybe
128+derive instance ordCipher_IdentityCipher_address2_Maybe :: Ord Cipher_IdentityCipher_address2_Maybe
121
129
122−instance encodeJsonSub_LoadCipher_cipherType_CardCipher :: EncodeJson Sub_LoadCipher_cipherType_CardCipher where
130+newtype Cipher_IdentityCipher_address3_Maybe =
131+ Cipher_IdentityCipher_address3_Maybe (Maybe String)
132+
133+derive instance newtypeCipher_IdentityCipher_address3_Maybe :: Newtype Cipher_IdentityCipher_address3_Maybe _
134+instance encodeJsonCipher_IdentityCipher_address3_Maybe :: EncodeJson Cipher_IdentityCipher_address3_Maybe where
123 encodeJson = genericEncodeAeson Argonaut.defaultOptions
135 encodeJson = genericEncodeAeson Argonaut.defaultOptions
124−instance decodeJsonSub_LoadCipher_cipherType_CardCipher :: DecodeJson Sub_LoadCipher_cipherType_CardCipher where
136+instance decodeJsonCipher_IdentityCipher_address3_Maybe :: DecodeJson Cipher_IdentityCipher_address3_Maybe where
125 decodeJson = genericDecodeAeson Argonaut.defaultOptions
137 decodeJson = genericDecodeAeson Argonaut.defaultOptions
126−derive instance genericSub_LoadCipher_cipherType_CardCipher :: Generic Sub_LoadCipher_cipherType_CardCipher _
138+derive instance genericCipher_IdentityCipher_address3_Maybe :: Generic Cipher_IdentityCipher_address3_Maybe _
127−derive instance eqSub_LoadCipher_cipherType_CardCipher :: Eq Sub_LoadCipher_cipherType_CardCipher
139+derive instance eqCipher_IdentityCipher_address3_Maybe :: Eq Cipher_IdentityCipher_address3_Maybe
128−derive instance ordSub_LoadCipher_cipherType_CardCipher :: Ord Sub_LoadCipher_cipherType_CardCipher
140+derive instance ordCipher_IdentityCipher_address3_Maybe :: Ord Cipher_IdentityCipher_address3_Maybe
129
141
130−newtype Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe =
142+newtype Cipher_IdentityCipher_city_Maybe =
131− Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe (Maybe String)
143+ Cipher_IdentityCipher_city_Maybe (Maybe String)
132
144
133−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe where
145+derive instance newtypeCipher_IdentityCipher_city_Maybe :: Newtype Cipher_IdentityCipher_city_Maybe _
146+instance encodeJsonCipher_IdentityCipher_city_Maybe :: EncodeJson Cipher_IdentityCipher_city_Maybe where
134 encodeJson = genericEncodeAeson Argonaut.defaultOptions
147 encodeJson = genericEncodeAeson Argonaut.defaultOptions
135−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe where
148+instance decodeJsonCipher_IdentityCipher_city_Maybe :: DecodeJson Cipher_IdentityCipher_city_Maybe where
136 decodeJson = genericDecodeAeson Argonaut.defaultOptions
149 decodeJson = genericDecodeAeson Argonaut.defaultOptions
137−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe _
150+derive instance genericCipher_IdentityCipher_city_Maybe :: Generic Cipher_IdentityCipher_city_Maybe _
138−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe
151+derive instance eqCipher_IdentityCipher_city_Maybe :: Eq Cipher_IdentityCipher_city_Maybe
139−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe
152+derive instance ordCipher_IdentityCipher_city_Maybe :: Ord Cipher_IdentityCipher_city_Maybe
140
153
141−newtype Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe =
154+newtype Cipher_IdentityCipher_company_Maybe =
142− Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe (Maybe String)
155+ Cipher_IdentityCipher_company_Maybe (Maybe String)
143
156
144−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe where
157+derive instance newtypeCipher_IdentityCipher_company_Maybe :: Newtype Cipher_IdentityCipher_company_Maybe _
158+instance encodeJsonCipher_IdentityCipher_company_Maybe :: EncodeJson Cipher_IdentityCipher_company_Maybe where
145 encodeJson = genericEncodeAeson Argonaut.defaultOptions
159 encodeJson = genericEncodeAeson Argonaut.defaultOptions
146−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe where
160+instance decodeJsonCipher_IdentityCipher_company_Maybe :: DecodeJson Cipher_IdentityCipher_company_Maybe where
147 decodeJson = genericDecodeAeson Argonaut.defaultOptions
161 decodeJson = genericDecodeAeson Argonaut.defaultOptions
148−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe _
162+derive instance genericCipher_IdentityCipher_company_Maybe :: Generic Cipher_IdentityCipher_company_Maybe _
149−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe
163+derive instance eqCipher_IdentityCipher_company_Maybe :: Eq Cipher_IdentityCipher_company_Maybe
150−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe
164+derive instance ordCipher_IdentityCipher_company_Maybe :: Ord Cipher_IdentityCipher_company_Maybe
151
165
152−newtype Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe =
166+newtype Cipher_IdentityCipher_country_Maybe =
153− Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe (Maybe String)
167+ Cipher_IdentityCipher_country_Maybe (Maybe String)
154
168
155−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe where
169+derive instance newtypeCipher_IdentityCipher_country_Maybe :: Newtype Cipher_IdentityCipher_country_Maybe _
170+instance encodeJsonCipher_IdentityCipher_country_Maybe :: EncodeJson Cipher_IdentityCipher_country_Maybe where
156 encodeJson = genericEncodeAeson Argonaut.defaultOptions
171 encodeJson = genericEncodeAeson Argonaut.defaultOptions
157−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe where
172+instance decodeJsonCipher_IdentityCipher_country_Maybe :: DecodeJson Cipher_IdentityCipher_country_Maybe where
158 decodeJson = genericDecodeAeson Argonaut.defaultOptions
173 decodeJson = genericDecodeAeson Argonaut.defaultOptions
159−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe _
174+derive instance genericCipher_IdentityCipher_country_Maybe :: Generic Cipher_IdentityCipher_country_Maybe _
160−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe
175+derive instance eqCipher_IdentityCipher_country_Maybe :: Eq Cipher_IdentityCipher_country_Maybe
161−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe
176+derive instance ordCipher_IdentityCipher_country_Maybe :: Ord Cipher_IdentityCipher_country_Maybe
162
177
163−newtype Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe =
178+newtype Cipher_IdentityCipher_email_Maybe =
164− Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe (Maybe String)
179+ Cipher_IdentityCipher_email_Maybe (Maybe String)
165
180
166−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_city_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe where
181+derive instance newtypeCipher_IdentityCipher_email_Maybe :: Newtype Cipher_IdentityCipher_email_Maybe _
182+instance encodeJsonCipher_IdentityCipher_email_Maybe :: EncodeJson Cipher_IdentityCipher_email_Maybe where
167 encodeJson = genericEncodeAeson Argonaut.defaultOptions
183 encodeJson = genericEncodeAeson Argonaut.defaultOptions
168−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_city_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe where
184+instance decodeJsonCipher_IdentityCipher_email_Maybe :: DecodeJson Cipher_IdentityCipher_email_Maybe where
169 decodeJson = genericDecodeAeson Argonaut.defaultOptions
185 decodeJson = genericDecodeAeson Argonaut.defaultOptions
170−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_city_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe _
186+derive instance genericCipher_IdentityCipher_email_Maybe :: Generic Cipher_IdentityCipher_email_Maybe _
171−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_city_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe
187+derive instance eqCipher_IdentityCipher_email_Maybe :: Eq Cipher_IdentityCipher_email_Maybe
172−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_city_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe
188+derive instance ordCipher_IdentityCipher_email_Maybe :: Ord Cipher_IdentityCipher_email_Maybe
173
189
174−newtype Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe =
190+newtype Cipher_IdentityCipher_firstName_Maybe =
175− Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe (Maybe String)
191+ Cipher_IdentityCipher_firstName_Maybe (Maybe String)
176
192
177−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_company_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe where
193+derive instance newtypeCipher_IdentityCipher_firstName_Maybe :: Newtype Cipher_IdentityCipher_firstName_Maybe _
194+instance encodeJsonCipher_IdentityCipher_firstName_Maybe :: EncodeJson Cipher_IdentityCipher_firstName_Maybe where
178 encodeJson = genericEncodeAeson Argonaut.defaultOptions
195 encodeJson = genericEncodeAeson Argonaut.defaultOptions
179−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_company_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe where
196+instance decodeJsonCipher_IdentityCipher_firstName_Maybe :: DecodeJson Cipher_IdentityCipher_firstName_Maybe where
180 decodeJson = genericDecodeAeson Argonaut.defaultOptions
197 decodeJson = genericDecodeAeson Argonaut.defaultOptions
181−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_company_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe _
198+derive instance genericCipher_IdentityCipher_firstName_Maybe :: Generic Cipher_IdentityCipher_firstName_Maybe _
182−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_company_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe
199+derive instance eqCipher_IdentityCipher_firstName_Maybe :: Eq Cipher_IdentityCipher_firstName_Maybe
183−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_company_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe
200+derive instance ordCipher_IdentityCipher_firstName_Maybe :: Ord Cipher_IdentityCipher_firstName_Maybe
184
201
185−newtype Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe =
202+newtype Cipher_IdentityCipher_lastName_Maybe =
186− Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe (Maybe String)
203+ Cipher_IdentityCipher_lastName_Maybe (Maybe String)
187
204
188−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_country_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe where
205+derive instance newtypeCipher_IdentityCipher_lastName_Maybe :: Newtype Cipher_IdentityCipher_lastName_Maybe _
206+instance encodeJsonCipher_IdentityCipher_lastName_Maybe :: EncodeJson Cipher_IdentityCipher_lastName_Maybe where
189 encodeJson = genericEncodeAeson Argonaut.defaultOptions
207 encodeJson = genericEncodeAeson Argonaut.defaultOptions
190−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_country_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe where
208+instance decodeJsonCipher_IdentityCipher_lastName_Maybe :: DecodeJson Cipher_IdentityCipher_lastName_Maybe where
191 decodeJson = genericDecodeAeson Argonaut.defaultOptions
209 decodeJson = genericDecodeAeson Argonaut.defaultOptions
192−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_country_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe _
210+derive instance genericCipher_IdentityCipher_lastName_Maybe :: Generic Cipher_IdentityCipher_lastName_Maybe _
193−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_country_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe
211+derive instance eqCipher_IdentityCipher_lastName_Maybe :: Eq Cipher_IdentityCipher_lastName_Maybe
194−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_country_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe
212+derive instance ordCipher_IdentityCipher_lastName_Maybe :: Ord Cipher_IdentityCipher_lastName_Maybe
195
213
196−newtype Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe =
214+newtype Cipher_IdentityCipher_licenseNumber_Maybe =
197− Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe (Maybe String)
215+ Cipher_IdentityCipher_licenseNumber_Maybe (Maybe String)
198
216
199−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_email_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe where
217+derive instance newtypeCipher_IdentityCipher_licenseNumber_Maybe :: Newtype Cipher_IdentityCipher_licenseNumber_Maybe _
218+instance encodeJsonCipher_IdentityCipher_licenseNumber_Maybe :: EncodeJson Cipher_IdentityCipher_licenseNumber_Maybe where
200 encodeJson = genericEncodeAeson Argonaut.defaultOptions
219 encodeJson = genericEncodeAeson Argonaut.defaultOptions
201−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_email_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe where
220+instance decodeJsonCipher_IdentityCipher_licenseNumber_Maybe :: DecodeJson Cipher_IdentityCipher_licenseNumber_Maybe where
202 decodeJson = genericDecodeAeson Argonaut.defaultOptions
221 decodeJson = genericDecodeAeson Argonaut.defaultOptions
203−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_email_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe _
222+derive instance genericCipher_IdentityCipher_licenseNumber_Maybe :: Generic Cipher_IdentityCipher_licenseNumber_Maybe _
204−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_email_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe
223+derive instance eqCipher_IdentityCipher_licenseNumber_Maybe :: Eq Cipher_IdentityCipher_licenseNumber_Maybe
205−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_email_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe
224+derive instance ordCipher_IdentityCipher_licenseNumber_Maybe :: Ord Cipher_IdentityCipher_licenseNumber_Maybe
206
225
207−newtype Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe =
226+newtype Cipher_IdentityCipher_middleName_Maybe =
208− Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe (Maybe String)
227+ Cipher_IdentityCipher_middleName_Maybe (Maybe String)
209
228
210−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe where
229+derive instance newtypeCipher_IdentityCipher_middleName_Maybe :: Newtype Cipher_IdentityCipher_middleName_Maybe _
230+instance encodeJsonCipher_IdentityCipher_middleName_Maybe :: EncodeJson Cipher_IdentityCipher_middleName_Maybe where
211 encodeJson = genericEncodeAeson Argonaut.defaultOptions
231 encodeJson = genericEncodeAeson Argonaut.defaultOptions
212−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe where
232+instance decodeJsonCipher_IdentityCipher_middleName_Maybe :: DecodeJson Cipher_IdentityCipher_middleName_Maybe where
213 decodeJson = genericDecodeAeson Argonaut.defaultOptions
233 decodeJson = genericDecodeAeson Argonaut.defaultOptions
214−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe _
234+derive instance genericCipher_IdentityCipher_middleName_Maybe :: Generic Cipher_IdentityCipher_middleName_Maybe _
215−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe
235+derive instance eqCipher_IdentityCipher_middleName_Maybe :: Eq Cipher_IdentityCipher_middleName_Maybe
216−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe
236+derive instance ordCipher_IdentityCipher_middleName_Maybe :: Ord Cipher_IdentityCipher_middleName_Maybe
217
237
218−newtype Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe =
238+newtype Cipher_IdentityCipher_passportNumber_Maybe =
219− Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe (Maybe String)
239+ Cipher_IdentityCipher_passportNumber_Maybe (Maybe String)
220
240
221−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe where
241+derive instance newtypeCipher_IdentityCipher_passportNumber_Maybe :: Newtype Cipher_IdentityCipher_passportNumber_Maybe _
242+instance encodeJsonCipher_IdentityCipher_passportNumber_Maybe :: EncodeJson Cipher_IdentityCipher_passportNumber_Maybe where
222 encodeJson = genericEncodeAeson Argonaut.defaultOptions
243 encodeJson = genericEncodeAeson Argonaut.defaultOptions
223−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe where
244+instance decodeJsonCipher_IdentityCipher_passportNumber_Maybe :: DecodeJson Cipher_IdentityCipher_passportNumber_Maybe where
224 decodeJson = genericDecodeAeson Argonaut.defaultOptions
245 decodeJson = genericDecodeAeson Argonaut.defaultOptions
225−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe _
246+derive instance genericCipher_IdentityCipher_passportNumber_Maybe :: Generic Cipher_IdentityCipher_passportNumber_Maybe _
226−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe
247+derive instance eqCipher_IdentityCipher_passportNumber_Maybe :: Eq Cipher_IdentityCipher_passportNumber_Maybe
227−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe
248+derive instance ordCipher_IdentityCipher_passportNumber_Maybe :: Ord Cipher_IdentityCipher_passportNumber_Maybe
228
249
229−newtype Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe =
250+newtype Cipher_IdentityCipher_phone_Maybe =
230− Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe (Maybe String)
251+ Cipher_IdentityCipher_phone_Maybe (Maybe String)
231
252
232−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe where
253+derive instance newtypeCipher_IdentityCipher_phone_Maybe :: Newtype Cipher_IdentityCipher_phone_Maybe _
254+instance encodeJsonCipher_IdentityCipher_phone_Maybe :: EncodeJson Cipher_IdentityCipher_phone_Maybe where
233 encodeJson = genericEncodeAeson Argonaut.defaultOptions
255 encodeJson = genericEncodeAeson Argonaut.defaultOptions
234−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe where
256+instance decodeJsonCipher_IdentityCipher_phone_Maybe :: DecodeJson Cipher_IdentityCipher_phone_Maybe where
235 decodeJson = genericDecodeAeson Argonaut.defaultOptions
257 decodeJson = genericDecodeAeson Argonaut.defaultOptions
236−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe _
258+derive instance genericCipher_IdentityCipher_phone_Maybe :: Generic Cipher_IdentityCipher_phone_Maybe _
237−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe
259+derive instance eqCipher_IdentityCipher_phone_Maybe :: Eq Cipher_IdentityCipher_phone_Maybe
238−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe
260+derive instance ordCipher_IdentityCipher_phone_Maybe :: Ord Cipher_IdentityCipher_phone_Maybe
239
261
240−newtype Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe =
262+newtype Cipher_IdentityCipher_postalCode_Maybe =
241− Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe (Maybe String)
263+ Cipher_IdentityCipher_postalCode_Maybe (Maybe String)
242
264
243−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe where
265+derive instance newtypeCipher_IdentityCipher_postalCode_Maybe :: Newtype Cipher_IdentityCipher_postalCode_Maybe _
266+instance encodeJsonCipher_IdentityCipher_postalCode_Maybe :: EncodeJson Cipher_IdentityCipher_postalCode_Maybe where
244 encodeJson = genericEncodeAeson Argonaut.defaultOptions
267 encodeJson = genericEncodeAeson Argonaut.defaultOptions
245−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe where
268+instance decodeJsonCipher_IdentityCipher_postalCode_Maybe :: DecodeJson Cipher_IdentityCipher_postalCode_Maybe where
246 decodeJson = genericDecodeAeson Argonaut.defaultOptions
269 decodeJson = genericDecodeAeson Argonaut.defaultOptions
247−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe _
270+derive instance genericCipher_IdentityCipher_postalCode_Maybe :: Generic Cipher_IdentityCipher_postalCode_Maybe _
248−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe
271+derive instance eqCipher_IdentityCipher_postalCode_Maybe :: Eq Cipher_IdentityCipher_postalCode_Maybe
249−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe
272+derive instance ordCipher_IdentityCipher_postalCode_Maybe :: Ord Cipher_IdentityCipher_postalCode_Maybe
250
273
251−newtype Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe =
274+newtype Cipher_IdentityCipher_ssn_Maybe =
252− Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe (Maybe String)
275+ Cipher_IdentityCipher_ssn_Maybe (Maybe String)
253
276
254−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe where
277+derive instance newtypeCipher_IdentityCipher_ssn_Maybe :: Newtype Cipher_IdentityCipher_ssn_Maybe _
278+instance encodeJsonCipher_IdentityCipher_ssn_Maybe :: EncodeJson Cipher_IdentityCipher_ssn_Maybe where
255 encodeJson = genericEncodeAeson Argonaut.defaultOptions
279 encodeJson = genericEncodeAeson Argonaut.defaultOptions
256−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe where
280+instance decodeJsonCipher_IdentityCipher_ssn_Maybe :: DecodeJson Cipher_IdentityCipher_ssn_Maybe where
257 decodeJson = genericDecodeAeson Argonaut.defaultOptions
281 decodeJson = genericDecodeAeson Argonaut.defaultOptions
258−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe _
282+derive instance genericCipher_IdentityCipher_ssn_Maybe :: Generic Cipher_IdentityCipher_ssn_Maybe _
259−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe
283+derive instance eqCipher_IdentityCipher_ssn_Maybe :: Eq Cipher_IdentityCipher_ssn_Maybe
260−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe
284+derive instance ordCipher_IdentityCipher_ssn_Maybe :: Ord Cipher_IdentityCipher_ssn_Maybe
261
285
262−newtype Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe =
286+newtype Cipher_IdentityCipher_state_Maybe =
263− Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe (Maybe String)
287+ Cipher_IdentityCipher_state_Maybe (Maybe String)
264
288
265−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe where
289+derive instance newtypeCipher_IdentityCipher_state_Maybe :: Newtype Cipher_IdentityCipher_state_Maybe _
290+instance encodeJsonCipher_IdentityCipher_state_Maybe :: EncodeJson Cipher_IdentityCipher_state_Maybe where
266 encodeJson = genericEncodeAeson Argonaut.defaultOptions
291 encodeJson = genericEncodeAeson Argonaut.defaultOptions
267−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe where
292+instance decodeJsonCipher_IdentityCipher_state_Maybe :: DecodeJson Cipher_IdentityCipher_state_Maybe where
268 decodeJson = genericDecodeAeson Argonaut.defaultOptions
293 decodeJson = genericDecodeAeson Argonaut.defaultOptions
269−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe _
294+derive instance genericCipher_IdentityCipher_state_Maybe :: Generic Cipher_IdentityCipher_state_Maybe _
270−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe
295+derive instance eqCipher_IdentityCipher_state_Maybe :: Eq Cipher_IdentityCipher_state_Maybe
271−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe
296+derive instance ordCipher_IdentityCipher_state_Maybe :: Ord Cipher_IdentityCipher_state_Maybe
272
297
273−newtype Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe =
298+newtype Cipher_IdentityCipher_title_Maybe =
274− Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe (Maybe String)
299+ Cipher_IdentityCipher_title_Maybe (Maybe String)
275
300
276−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe where
301+derive instance newtypeCipher_IdentityCipher_title_Maybe :: Newtype Cipher_IdentityCipher_title_Maybe _
302+instance encodeJsonCipher_IdentityCipher_title_Maybe :: EncodeJson Cipher_IdentityCipher_title_Maybe where
277 encodeJson = genericEncodeAeson Argonaut.defaultOptions
303 encodeJson = genericEncodeAeson Argonaut.defaultOptions
278−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe where
304+instance decodeJsonCipher_IdentityCipher_title_Maybe :: DecodeJson Cipher_IdentityCipher_title_Maybe where
279 decodeJson = genericDecodeAeson Argonaut.defaultOptions
305 decodeJson = genericDecodeAeson Argonaut.defaultOptions
280−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe _
306+derive instance genericCipher_IdentityCipher_title_Maybe :: Generic Cipher_IdentityCipher_title_Maybe _
281−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe
307+derive instance eqCipher_IdentityCipher_title_Maybe :: Eq Cipher_IdentityCipher_title_Maybe
282−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe
308+derive instance ordCipher_IdentityCipher_title_Maybe :: Ord Cipher_IdentityCipher_title_Maybe
283
309
284−newtype Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe =
310+newtype Cipher_IdentityCipher_username_Maybe =
285− Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe (Maybe String)
311+ Cipher_IdentityCipher_username_Maybe (Maybe String)
286
312
287−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe where
313+derive instance newtypeCipher_IdentityCipher_username_Maybe :: Newtype Cipher_IdentityCipher_username_Maybe _
314+instance encodeJsonCipher_IdentityCipher_username_Maybe :: EncodeJson Cipher_IdentityCipher_username_Maybe where
288 encodeJson = genericEncodeAeson Argonaut.defaultOptions
315 encodeJson = genericEncodeAeson Argonaut.defaultOptions
289−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe where
316+instance decodeJsonCipher_IdentityCipher_username_Maybe :: DecodeJson Cipher_IdentityCipher_username_Maybe where
290 decodeJson = genericDecodeAeson Argonaut.defaultOptions
317 decodeJson = genericDecodeAeson Argonaut.defaultOptions
291−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe _
318+derive instance genericCipher_IdentityCipher_username_Maybe :: Generic Cipher_IdentityCipher_username_Maybe _
292−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe
319+derive instance eqCipher_IdentityCipher_username_Maybe :: Eq Cipher_IdentityCipher_username_Maybe
293−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe
320+derive instance ordCipher_IdentityCipher_username_Maybe :: Ord Cipher_IdentityCipher_username_Maybe
294
321
295−newtype Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe =
322+newtype Cipher_IdentityCipher =
296− Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe (Maybe String)
323+ Cipher_IdentityCipher {
324+ address1 :: Cipher_IdentityCipher_address1_Maybe
325+ , address2 :: Cipher_IdentityCipher_address2_Maybe
326+ , address3 :: Cipher_IdentityCipher_address3_Maybe
327+ , city :: Cipher_IdentityCipher_city_Maybe
328+ , company :: Cipher_IdentityCipher_company_Maybe
329+ , country :: Cipher_IdentityCipher_country_Maybe
330+ , email :: Cipher_IdentityCipher_email_Maybe
331+ , firstName :: Cipher_IdentityCipher_firstName_Maybe
332+ , lastName :: Cipher_IdentityCipher_lastName_Maybe
333+ , licenseNumber :: Cipher_IdentityCipher_licenseNumber_Maybe
334+ , middleName :: Cipher_IdentityCipher_middleName_Maybe
335+ , passportNumber :: Cipher_IdentityCipher_passportNumber_Maybe
336+ , phone :: Cipher_IdentityCipher_phone_Maybe
337+ , postalCode :: Cipher_IdentityCipher_postalCode_Maybe
338+ , ssn :: Cipher_IdentityCipher_ssn_Maybe
339+ , state :: Cipher_IdentityCipher_state_Maybe
340+ , title :: Cipher_IdentityCipher_title_Maybe
341+ , username :: Cipher_IdentityCipher_username_Maybe
342+ }
297
343
298−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_state_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe where
344+derive instance newtypeCipher_IdentityCipher :: Newtype Cipher_IdentityCipher _
345+instance encodeJsonCipher_IdentityCipher :: EncodeJson Cipher_IdentityCipher where
299 encodeJson = genericEncodeAeson Argonaut.defaultOptions
346 encodeJson = genericEncodeAeson Argonaut.defaultOptions
300−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_state_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe where
347+instance decodeJsonCipher_IdentityCipher :: DecodeJson Cipher_IdentityCipher where
301 decodeJson = genericDecodeAeson Argonaut.defaultOptions
348 decodeJson = genericDecodeAeson Argonaut.defaultOptions
302−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_state_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe _
349+derive instance genericCipher_IdentityCipher :: Generic Cipher_IdentityCipher _
303−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_state_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe
350+derive instance eqCipher_IdentityCipher :: Eq Cipher_IdentityCipher
304−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_state_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe
351+derive instance ordCipher_IdentityCipher :: Ord Cipher_IdentityCipher
305
352
306−newtype Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe =
353+newtype Cipher_LoginCipher_password_Maybe =
307− Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe (Maybe String)
354+ Cipher_LoginCipher_password_Maybe (Maybe String)
308
355
309−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_title_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe where
356+derive instance newtypeCipher_LoginCipher_password_Maybe :: Newtype Cipher_LoginCipher_password_Maybe _
357+instance encodeJsonCipher_LoginCipher_password_Maybe :: EncodeJson Cipher_LoginCipher_password_Maybe where
310 encodeJson = genericEncodeAeson Argonaut.defaultOptions
358 encodeJson = genericEncodeAeson Argonaut.defaultOptions
311−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_title_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe where
359+instance decodeJsonCipher_LoginCipher_password_Maybe :: DecodeJson Cipher_LoginCipher_password_Maybe where
312 decodeJson = genericDecodeAeson Argonaut.defaultOptions
360 decodeJson = genericDecodeAeson Argonaut.defaultOptions
313−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_title_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe _
361+derive instance genericCipher_LoginCipher_password_Maybe :: Generic Cipher_LoginCipher_password_Maybe _
314−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_title_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe
362+derive instance eqCipher_LoginCipher_password_Maybe :: Eq Cipher_LoginCipher_password_Maybe
315−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_title_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe
363+derive instance ordCipher_LoginCipher_password_Maybe :: Ord Cipher_LoginCipher_password_Maybe
316
364
317−newtype Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe =
365+newtype Cipher_LoginCipher_totp_Maybe =
318− Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe (Maybe String)
366+ Cipher_LoginCipher_totp_Maybe (Maybe String)
319
367
320−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher_username_Maybe :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe where
368+derive instance newtypeCipher_LoginCipher_totp_Maybe :: Newtype Cipher_LoginCipher_totp_Maybe _
369+instance encodeJsonCipher_LoginCipher_totp_Maybe :: EncodeJson Cipher_LoginCipher_totp_Maybe where
321 encodeJson = genericEncodeAeson Argonaut.defaultOptions
370 encodeJson = genericEncodeAeson Argonaut.defaultOptions
322−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher_username_Maybe :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe where
371+instance decodeJsonCipher_LoginCipher_totp_Maybe :: DecodeJson Cipher_LoginCipher_totp_Maybe where
323 decodeJson = genericDecodeAeson Argonaut.defaultOptions
372 decodeJson = genericDecodeAeson Argonaut.defaultOptions
324−derive instance genericSub_LoadCipher_cipherType_IdentityCipher_username_Maybe :: Generic Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe _
373+derive instance genericCipher_LoginCipher_totp_Maybe :: Generic Cipher_LoginCipher_totp_Maybe _
325−derive instance eqSub_LoadCipher_cipherType_IdentityCipher_username_Maybe :: Eq Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe
374+derive instance eqCipher_LoginCipher_totp_Maybe :: Eq Cipher_LoginCipher_totp_Maybe
326−derive instance ordSub_LoadCipher_cipherType_IdentityCipher_username_Maybe :: Ord Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe
375+derive instance ordCipher_LoginCipher_totp_Maybe :: Ord Cipher_LoginCipher_totp_Maybe
327
376
328−newtype Sub_LoadCipher_cipherType_IdentityCipher =
377+newtype Cipher_LoginCipher_uris_List =
329− Sub_LoadCipher_cipherType_IdentityCipher {
378+ Cipher_LoginCipher_uris_List (Array String)
330− address1 :: Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe
379+
331− , address2 :: Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe
380+derive instance newtypeCipher_LoginCipher_uris_List :: Newtype Cipher_LoginCipher_uris_List _
332− , address3 :: Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe
381+instance encodeJsonCipher_LoginCipher_uris_List :: EncodeJson Cipher_LoginCipher_uris_List where
333− , city :: Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe
382+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
334− , company :: Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe
383+instance decodeJsonCipher_LoginCipher_uris_List :: DecodeJson Cipher_LoginCipher_uris_List where
335− , country :: Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe
384+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
336− , email :: Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe
385+derive instance genericCipher_LoginCipher_uris_List :: Generic Cipher_LoginCipher_uris_List _
337− , firstName :: Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe
386+derive instance eqCipher_LoginCipher_uris_List :: Eq Cipher_LoginCipher_uris_List
338− , lastName :: Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe
387+derive instance ordCipher_LoginCipher_uris_List :: Ord Cipher_LoginCipher_uris_List
339− , licenseNumber :: Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe
388+
340− , middleName :: Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe
389+newtype Cipher_LoginCipher_username_Maybe =
341− , passportNumber :: Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe
390+ Cipher_LoginCipher_username_Maybe (Maybe String)
342− , phone :: Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe
391+
343− , postalCode :: Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe
392+derive instance newtypeCipher_LoginCipher_username_Maybe :: Newtype Cipher_LoginCipher_username_Maybe _
344− , ssn :: Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe
393+instance encodeJsonCipher_LoginCipher_username_Maybe :: EncodeJson Cipher_LoginCipher_username_Maybe where
345− , state :: Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe
394+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
346− , title :: Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe
395+instance decodeJsonCipher_LoginCipher_username_Maybe :: DecodeJson Cipher_LoginCipher_username_Maybe where
347− , username :: Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe
396+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
397+derive instance genericCipher_LoginCipher_username_Maybe :: Generic Cipher_LoginCipher_username_Maybe _
398+derive instance eqCipher_LoginCipher_username_Maybe :: Eq Cipher_LoginCipher_username_Maybe
399+derive instance ordCipher_LoginCipher_username_Maybe :: Ord Cipher_LoginCipher_username_Maybe
400+
401+newtype Cipher_LoginCipher =
402+ Cipher_LoginCipher {
403+ password :: Cipher_LoginCipher_password_Maybe
404+ , totp :: Cipher_LoginCipher_totp_Maybe
405+ , uris :: Cipher_LoginCipher_uris_List
406+ , username :: Cipher_LoginCipher_username_Maybe
348 }
407 }
349
408
350−instance encodeJsonSub_LoadCipher_cipherType_IdentityCipher :: EncodeJson Sub_LoadCipher_cipherType_IdentityCipher where
409+derive instance newtypeCipher_LoginCipher :: Newtype Cipher_LoginCipher _
410+instance encodeJsonCipher_LoginCipher :: EncodeJson Cipher_LoginCipher where
351 encodeJson = genericEncodeAeson Argonaut.defaultOptions
411 encodeJson = genericEncodeAeson Argonaut.defaultOptions
352−instance decodeJsonSub_LoadCipher_cipherType_IdentityCipher :: DecodeJson Sub_LoadCipher_cipherType_IdentityCipher where
412+instance decodeJsonCipher_LoginCipher :: DecodeJson Cipher_LoginCipher where
353 decodeJson = genericDecodeAeson Argonaut.defaultOptions
413 decodeJson = genericDecodeAeson Argonaut.defaultOptions
354−derive instance genericSub_LoadCipher_cipherType_IdentityCipher :: Generic Sub_LoadCipher_cipherType_IdentityCipher _
414+derive instance genericCipher_LoginCipher :: Generic Cipher_LoginCipher _
355−derive instance eqSub_LoadCipher_cipherType_IdentityCipher :: Eq Sub_LoadCipher_cipherType_IdentityCipher
415+derive instance eqCipher_LoginCipher :: Eq Cipher_LoginCipher
356−derive instance ordSub_LoadCipher_cipherType_IdentityCipher :: Ord Sub_LoadCipher_cipherType_IdentityCipher
416+derive instance ordCipher_LoginCipher :: Ord Cipher_LoginCipher
357
417
358−newtype Sub_LoadCipher_cipherType_LoginCipher_password_Maybe =
418+data Cipher =
359− Sub_LoadCipher_cipherType_LoginCipher_password_Maybe (Maybe String)
419+ CardCipher Cipher_CardCipher
420+ | IdentityCipher Cipher_IdentityCipher
421+ | LoginCipher Cipher_LoginCipher
422+ | NoteCipher String
360
423
361−instance encodeJsonSub_LoadCipher_cipherType_LoginCipher_password_Maybe :: EncodeJson Sub_LoadCipher_cipherType_LoginCipher_password_Maybe where
424+instance encodeJsonCipher :: EncodeJson Cipher where
362 encodeJson = genericEncodeAeson Argonaut.defaultOptions
425 encodeJson = genericEncodeAeson Argonaut.defaultOptions
363−instance decodeJsonSub_LoadCipher_cipherType_LoginCipher_password_Maybe :: DecodeJson Sub_LoadCipher_cipherType_LoginCipher_password_Maybe where
426+instance decodeJsonCipher :: DecodeJson Cipher where
364 decodeJson = genericDecodeAeson Argonaut.defaultOptions
427 decodeJson = genericDecodeAeson Argonaut.defaultOptions
365−derive instance genericSub_LoadCipher_cipherType_LoginCipher_password_Maybe :: Generic Sub_LoadCipher_cipherType_LoginCipher_password_Maybe _
428+derive instance genericCipher :: Generic Cipher _
366−derive instance eqSub_LoadCipher_cipherType_LoginCipher_password_Maybe :: Eq Sub_LoadCipher_cipherType_LoginCipher_password_Maybe
429+derive instance eqCipher :: Eq Cipher
367−derive instance ordSub_LoadCipher_cipherType_LoginCipher_password_Maybe :: Ord Sub_LoadCipher_cipherType_LoginCipher_password_Maybe
430+derive instance ordCipher :: Ord Cipher
368
431
369−newtype Sub_LoadCipher_cipherType_LoginCipher_uris_List =
432+data CipherType =
370− Sub_LoadCipher_cipherType_LoginCipher_uris_List (Array String)
433+ CardType
434+ | IdentityType
435+ | LoginType
436+ | NoteType
371
437
372−instance encodeJsonSub_LoadCipher_cipherType_LoginCipher_uris_List :: EncodeJson Sub_LoadCipher_cipherType_LoginCipher_uris_List where
438+instance encodeJsonCipherType :: EncodeJson CipherType where
373 encodeJson = genericEncodeAeson Argonaut.defaultOptions
439 encodeJson = genericEncodeAeson Argonaut.defaultOptions
374−instance decodeJsonSub_LoadCipher_cipherType_LoginCipher_uris_List :: DecodeJson Sub_LoadCipher_cipherType_LoginCipher_uris_List where
440+instance decodeJsonCipherType :: DecodeJson CipherType where
375 decodeJson = genericDecodeAeson Argonaut.defaultOptions
441 decodeJson = genericDecodeAeson Argonaut.defaultOptions
376−derive instance genericSub_LoadCipher_cipherType_LoginCipher_uris_List :: Generic Sub_LoadCipher_cipherType_LoginCipher_uris_List _
442+derive instance genericCipherType :: Generic CipherType _
377−derive instance eqSub_LoadCipher_cipherType_LoginCipher_uris_List :: Eq Sub_LoadCipher_cipherType_LoginCipher_uris_List
443+derive instance eqCipherType :: Eq CipherType
378−derive instance ordSub_LoadCipher_cipherType_LoginCipher_uris_List :: Ord Sub_LoadCipher_cipherType_LoginCipher_uris_List
444+derive instance ordCipherType :: Ord CipherType
379
445
380−newtype Sub_LoadCipher_cipherType_LoginCipher_username_Maybe =
446+newtype Cmd_ChooseSecondFactor =
381− Sub_LoadCipher_cipherType_LoginCipher_username_Maybe (Maybe String)
447+ Cmd_ChooseSecondFactor {
448+ email :: String
449+ , factor :: TwoFactorProviderType
450+ , password :: String
451+ , requestFromServer :: Boolean
452+ , server :: String
453+ }
382
454
383−instance encodeJsonSub_LoadCipher_cipherType_LoginCipher_username_Maybe :: EncodeJson Sub_LoadCipher_cipherType_LoginCipher_username_Maybe where
455+derive instance newtypeCmd_ChooseSecondFactor :: Newtype Cmd_ChooseSecondFactor _
456+instance encodeJsonCmd_ChooseSecondFactor :: EncodeJson Cmd_ChooseSecondFactor where
384 encodeJson = genericEncodeAeson Argonaut.defaultOptions
457 encodeJson = genericEncodeAeson Argonaut.defaultOptions
385−instance decodeJsonSub_LoadCipher_cipherType_LoginCipher_username_Maybe :: DecodeJson Sub_LoadCipher_cipherType_LoginCipher_username_Maybe where
458+instance decodeJsonCmd_ChooseSecondFactor :: DecodeJson Cmd_ChooseSecondFactor where
386 decodeJson = genericDecodeAeson Argonaut.defaultOptions
459 decodeJson = genericDecodeAeson Argonaut.defaultOptions
387−derive instance genericSub_LoadCipher_cipherType_LoginCipher_username_Maybe :: Generic Sub_LoadCipher_cipherType_LoginCipher_username_Maybe _
460+derive instance genericCmd_ChooseSecondFactor :: Generic Cmd_ChooseSecondFactor _
388−derive instance eqSub_LoadCipher_cipherType_LoginCipher_username_Maybe :: Eq Sub_LoadCipher_cipherType_LoginCipher_username_Maybe
461+derive instance eqCmd_ChooseSecondFactor :: Eq Cmd_ChooseSecondFactor
389−derive instance ordSub_LoadCipher_cipherType_LoginCipher_username_Maybe :: Ord Sub_LoadCipher_cipherType_LoginCipher_username_Maybe
462+derive instance ordCmd_ChooseSecondFactor :: Ord Cmd_ChooseSecondFactor
390
463
391−newtype Sub_LoadCipher_cipherType_LoginCipher =
464+newtype Cmd_Login_secondFactor =
392− Sub_LoadCipher_cipherType_LoginCipher {
465+ Cmd_Login_secondFactor {
393− password :: Sub_LoadCipher_cipherType_LoginCipher_password_Maybe
466+ provider :: TwoFactorProviderType
394− , uris :: Sub_LoadCipher_cipherType_LoginCipher_uris_List
467+ , remember :: Boolean
395− , username :: Sub_LoadCipher_cipherType_LoginCipher_username_Maybe
468+ , token :: String
396 }
469 }
397
470
398−instance encodeJsonSub_LoadCipher_cipherType_LoginCipher :: EncodeJson Sub_LoadCipher_cipherType_LoginCipher where
471+derive instance newtypeCmd_Login_secondFactor :: Newtype Cmd_Login_secondFactor _
472+instance encodeJsonCmd_Login_secondFactor :: EncodeJson Cmd_Login_secondFactor where
399 encodeJson = genericEncodeAeson Argonaut.defaultOptions
473 encodeJson = genericEncodeAeson Argonaut.defaultOptions
400−instance decodeJsonSub_LoadCipher_cipherType_LoginCipher :: DecodeJson Sub_LoadCipher_cipherType_LoginCipher where
474+instance decodeJsonCmd_Login_secondFactor :: DecodeJson Cmd_Login_secondFactor where
401 decodeJson = genericDecodeAeson Argonaut.defaultOptions
475 decodeJson = genericDecodeAeson Argonaut.defaultOptions
402−derive instance genericSub_LoadCipher_cipherType_LoginCipher :: Generic Sub_LoadCipher_cipherType_LoginCipher _
476+derive instance genericCmd_Login_secondFactor :: Generic Cmd_Login_secondFactor _
403−derive instance eqSub_LoadCipher_cipherType_LoginCipher :: Eq Sub_LoadCipher_cipherType_LoginCipher
477+derive instance eqCmd_Login_secondFactor :: Eq Cmd_Login_secondFactor
404−derive instance ordSub_LoadCipher_cipherType_LoginCipher :: Ord Sub_LoadCipher_cipherType_LoginCipher
478+derive instance ordCmd_Login_secondFactor :: Ord Cmd_Login_secondFactor
405
479
406−data Sub_LoadCipher_cipherType =
480+newtype Cmd_Login_secondFactor_Maybe =
407− CardCipher Sub_LoadCipher_cipherType_CardCipher
481+ Cmd_Login_secondFactor_Maybe (Maybe Cmd_Login_secondFactor)
408− | IdentityCipher Sub_LoadCipher_cipherType_IdentityCipher
409− | LoginCipher Sub_LoadCipher_cipherType_LoginCipher
410− | NoteCipher String
411
482
412−instance encodeJsonSub_LoadCipher_cipherType :: EncodeJson Sub_LoadCipher_cipherType where
483+derive instance newtypeCmd_Login_secondFactor_Maybe :: Newtype Cmd_Login_secondFactor_Maybe _
484+instance encodeJsonCmd_Login_secondFactor_Maybe :: EncodeJson Cmd_Login_secondFactor_Maybe where
413 encodeJson = genericEncodeAeson Argonaut.defaultOptions
485 encodeJson = genericEncodeAeson Argonaut.defaultOptions
414−instance decodeJsonSub_LoadCipher_cipherType :: DecodeJson Sub_LoadCipher_cipherType where
486+instance decodeJsonCmd_Login_secondFactor_Maybe :: DecodeJson Cmd_Login_secondFactor_Maybe where
415 decodeJson = genericDecodeAeson Argonaut.defaultOptions
487 decodeJson = genericDecodeAeson Argonaut.defaultOptions
416−derive instance genericSub_LoadCipher_cipherType :: Generic Sub_LoadCipher_cipherType _
488+derive instance genericCmd_Login_secondFactor_Maybe :: Generic Cmd_Login_secondFactor_Maybe _
417−derive instance eqSub_LoadCipher_cipherType :: Eq Sub_LoadCipher_cipherType
489+derive instance eqCmd_Login_secondFactor_Maybe :: Eq Cmd_Login_secondFactor_Maybe
418−derive instance ordSub_LoadCipher_cipherType :: Ord Sub_LoadCipher_cipherType
490+derive instance ordCmd_Login_secondFactor_Maybe :: Ord Cmd_Login_secondFactor_Maybe
419
491
420−newtype Sub_LoadCipher =
492+newtype Cmd_Login =
421− Sub_LoadCipher {
493+ Cmd_Login {
422− cipherType :: Sub_LoadCipher_cipherType
494+ email :: String
423− , id :: String
495+ , password :: String
496+ , secondFactor :: Cmd_Login_secondFactor_Maybe
424− , name :: String
497+ , server :: String
425 }
498 }
426
499
500+derive instance newtypeCmd_Login :: Newtype Cmd_Login _
427−instance encodeJsonSub_LoadCipher :: EncodeJson Sub_LoadCipher where
501+instance encodeJsonCmd_Login :: EncodeJson Cmd_Login where
428 encodeJson = genericEncodeAeson Argonaut.defaultOptions
502 encodeJson = genericEncodeAeson Argonaut.defaultOptions
429−instance decodeJsonSub_LoadCipher :: DecodeJson Sub_LoadCipher where
503+instance decodeJsonCmd_Login :: DecodeJson Cmd_Login where
430 decodeJson = genericDecodeAeson Argonaut.defaultOptions
504 decodeJson = genericDecodeAeson Argonaut.defaultOptions
431−derive instance genericSub_LoadCipher :: Generic Sub_LoadCipher _
505+derive instance genericCmd_Login :: Generic Cmd_Login _
432−derive instance eqSub_LoadCipher :: Eq Sub_LoadCipher
506+derive instance eqCmd_Login :: Eq Cmd_Login
433−derive instance ordSub_LoadCipher :: Ord Sub_LoadCipher
507+derive instance ordCmd_Login :: Ord Cmd_Login
434
508
435−data Sub_LoadCiphers_cipherType =
509+data Cmd =
436− CardType
510+ ChooseSecondFactor Cmd_ChooseSecondFactor
437− | IdentityType
511+ | Copy String
438− | LoginType
512+ | CreateCipher FullCipher
439− | NoteType
513+ | DeleteCipher FullCipher
514+ | GeneratePassword PasswordGeneratorConfig
515+ | Init
516+ | Login Cmd_Login
517+ | NeedCiphersList
518+ | NeedEmail
519+ | NeedsReset
520+ | Open String
521+ | RequestCipher String
522+ | RequestTotp String
523+ | SendMasterPassword String
524+ | UpdateCipher FullCipher
525+
526+instance encodeJsonCmd :: EncodeJson Cmd where
527+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
528+instance decodeJsonCmd :: DecodeJson Cmd where
529+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
530+derive instance genericCmd :: Generic Cmd _
531+derive instance eqCmd :: Eq Cmd
532+derive instance ordCmd :: Ord Cmd
440
533
441−instance encodeJsonSub_LoadCiphers_cipherType :: EncodeJson Sub_LoadCiphers_cipherType where
534+newtype FullCipher_collectionIds_List =
535+ FullCipher_collectionIds_List (Array String)
536+
537+derive instance newtypeFullCipher_collectionIds_List :: Newtype FullCipher_collectionIds_List _
538+instance encodeJsonFullCipher_collectionIds_List :: EncodeJson FullCipher_collectionIds_List where
442 encodeJson = genericEncodeAeson Argonaut.defaultOptions
539 encodeJson = genericEncodeAeson Argonaut.defaultOptions
443−instance decodeJsonSub_LoadCiphers_cipherType :: DecodeJson Sub_LoadCiphers_cipherType where
540+instance decodeJsonFullCipher_collectionIds_List :: DecodeJson FullCipher_collectionIds_List where
444 decodeJson = genericDecodeAeson Argonaut.defaultOptions
541 decodeJson = genericDecodeAeson Argonaut.defaultOptions
445−derive instance genericSub_LoadCiphers_cipherType :: Generic Sub_LoadCiphers_cipherType _
542+derive instance genericFullCipher_collectionIds_List :: Generic FullCipher_collectionIds_List _
446−derive instance eqSub_LoadCiphers_cipherType :: Eq Sub_LoadCiphers_cipherType
543+derive instance eqFullCipher_collectionIds_List :: Eq FullCipher_collectionIds_List
447−derive instance ordSub_LoadCiphers_cipherType :: Ord Sub_LoadCiphers_cipherType
544+derive instance ordFullCipher_collectionIds_List :: Ord FullCipher_collectionIds_List
545+
546+newtype FullCipher =
547+ FullCipher {
548+ cipher :: Cipher
549+ , collectionIds :: FullCipher_collectionIds_List
550+ , favorite :: Boolean
551+ , id :: String
552+ , name :: String
553+ , reprompt :: Int
554+ }
555+
556+derive instance newtypeFullCipher :: Newtype FullCipher _
557+instance encodeJsonFullCipher :: EncodeJson FullCipher where
558+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
559+instance decodeJsonFullCipher :: DecodeJson FullCipher where
560+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
561+derive instance genericFullCipher :: Generic FullCipher _
562+derive instance eqFullCipher :: Eq FullCipher
563+derive instance ordFullCipher :: Ord FullCipher
564+
565+newtype PasswordGeneratorConfig =
566+ PasswordGeneratorConfig {
567+ ambiguous :: Boolean
568+ , capitalize :: Boolean
569+ , includeNumber :: Boolean
570+ , length :: Int
571+ , lowercase :: Boolean
572+ , minLowercase :: Int
573+ , minNumber :: Int
574+ , minSpecial :: Int
575+ , minUppercase :: Int
576+ , numWords :: Int
577+ , number :: Boolean
578+ , special :: Boolean
579+ , type :: String
580+ , uppercase :: Boolean
581+ , wordSeparator :: String
582+ }
583+
584+derive instance newtypePasswordGeneratorConfig :: Newtype PasswordGeneratorConfig _
585+instance encodeJsonPasswordGeneratorConfig :: EncodeJson PasswordGeneratorConfig where
586+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
587+instance decodeJsonPasswordGeneratorConfig :: DecodeJson PasswordGeneratorConfig where
588+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
589+derive instance genericPasswordGeneratorConfig :: Generic PasswordGeneratorConfig _
590+derive instance eqPasswordGeneratorConfig :: Eq PasswordGeneratorConfig
591+derive instance ordPasswordGeneratorConfig :: Ord PasswordGeneratorConfig
448
592
449newtype Sub_LoadCiphers =
593newtype Sub_LoadCiphers =
450 Sub_LoadCiphers {
594 Sub_LoadCiphers {
451− cipherType :: Sub_LoadCiphers_cipherType
595+ cipherType :: CipherType
452 , date :: String
596 , date :: String
453 , id :: String
597 , id :: String
454 , name :: String
598 , name :: String
455 }
599 }
456
600
601+derive instance newtypeSub_LoadCiphers :: Newtype Sub_LoadCiphers _
457instance encodeJsonSub_LoadCiphers :: EncodeJson Sub_LoadCiphers where
602instance encodeJsonSub_LoadCiphers :: EncodeJson Sub_LoadCiphers where
458 encodeJson = genericEncodeAeson Argonaut.defaultOptions
603 encodeJson = genericEncodeAeson Argonaut.defaultOptions
459instance decodeJsonSub_LoadCiphers :: DecodeJson Sub_LoadCiphers where
604instance decodeJsonSub_LoadCiphers :: DecodeJson Sub_LoadCiphers where
@@ -465,6 +610,7 @@ derive instance ordSub_LoadCiphers :: Ord Sub_LoadCiphers
465newtype Sub_LoadCiphers_List =
610newtype Sub_LoadCiphers_List =
466 Sub_LoadCiphers_List (Array Sub_LoadCiphers)
611 Sub_LoadCiphers_List (Array Sub_LoadCiphers)
467
612
613+derive instance newtypeSub_LoadCiphers_List :: Newtype Sub_LoadCiphers_List _
468instance encodeJsonSub_LoadCiphers_List :: EncodeJson Sub_LoadCiphers_List where
614instance encodeJsonSub_LoadCiphers_List :: EncodeJson Sub_LoadCiphers_List where
469 encodeJson = genericEncodeAeson Argonaut.defaultOptions
615 encodeJson = genericEncodeAeson Argonaut.defaultOptions
470instance decodeJsonSub_LoadCiphers_List :: DecodeJson Sub_LoadCiphers_List where
616instance decodeJsonSub_LoadCiphers_List :: DecodeJson Sub_LoadCiphers_List where
@@ -479,6 +625,7 @@ newtype Sub_NeedsMasterPassword =
479 , server :: String
625 , server :: String
480 }
626 }
481
627
628+derive instance newtypeSub_NeedsMasterPassword :: Newtype Sub_NeedsMasterPassword _
482instance encodeJsonSub_NeedsMasterPassword :: EncodeJson Sub_NeedsMasterPassword where
629instance encodeJsonSub_NeedsMasterPassword :: EncodeJson Sub_NeedsMasterPassword where
483 encodeJson = genericEncodeAeson Argonaut.defaultOptions
630 encodeJson = genericEncodeAeson Argonaut.defaultOptions
484instance decodeJsonSub_NeedsMasterPassword :: DecodeJson Sub_NeedsMasterPassword where
631instance decodeJsonSub_NeedsMasterPassword :: DecodeJson Sub_NeedsMasterPassword where
@@ -487,14 +634,51 @@ derive instance genericSub_NeedsMasterPassword :: Generic Sub_NeedsMasterPasswor
487derive instance eqSub_NeedsMasterPassword :: Eq Sub_NeedsMasterPassword
634derive instance eqSub_NeedsMasterPassword :: Eq Sub_NeedsMasterPassword
488derive instance ordSub_NeedsMasterPassword :: Ord Sub_NeedsMasterPassword
635derive instance ordSub_NeedsMasterPassword :: Ord Sub_NeedsMasterPassword
489
636
637+newtype Sub_NeedsSecondFactor_List =
638+ Sub_NeedsSecondFactor_List (Array TwoFactorProviderType)
639+
640+derive instance newtypeSub_NeedsSecondFactor_List :: Newtype Sub_NeedsSecondFactor_List _
641+instance encodeJsonSub_NeedsSecondFactor_List :: EncodeJson Sub_NeedsSecondFactor_List where
642+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
643+instance decodeJsonSub_NeedsSecondFactor_List :: DecodeJson Sub_NeedsSecondFactor_List where
644+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
645+derive instance genericSub_NeedsSecondFactor_List :: Generic Sub_NeedsSecondFactor_List _
646+derive instance eqSub_NeedsSecondFactor_List :: Eq Sub_NeedsSecondFactor_List
647+derive instance ordSub_NeedsSecondFactor_List :: Ord Sub_NeedsSecondFactor_List
648+
649+newtype Sub_Totp =
650+ Sub_Totp {
651+ code :: String
652+ , interval :: Int
653+ , source :: String
654+ }
655+
656+derive instance newtypeSub_Totp :: Newtype Sub_Totp _
657+instance encodeJsonSub_Totp :: EncodeJson Sub_Totp where
658+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
659+instance decodeJsonSub_Totp :: DecodeJson Sub_Totp where
660+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
661+derive instance genericSub_Totp :: Generic Sub_Totp _
662+derive instance eqSub_Totp :: Eq Sub_Totp
663+derive instance ordSub_Totp :: Ord Sub_Totp
664+
490data Sub =
665data Sub =
666+ CaptchaDone
667+ | CipherChanged FullCipher
668+ | CipherDeleted FullCipher
491− Error String
669+ | Error String
670+ | GeneratedPassword String
492− | LoadCipher Sub_LoadCipher
671+ | LoadCipher FullCipher
493 | LoadCiphers Sub_LoadCiphers_List
672 | LoadCiphers Sub_LoadCiphers_List
494 | LoginSuccessful
673 | LoginSuccessful
674+ | NeedsCaptcha String
495 | NeedsLogin
675 | NeedsLogin
496 | NeedsMasterPassword Sub_NeedsMasterPassword
676 | NeedsMasterPassword Sub_NeedsMasterPassword
677+ | NeedsSecondFactor Sub_NeedsSecondFactor_List
678+ | RecieveEmail String
497 | Reset
679 | Reset
680+ | Totp Sub_Totp
681+ | WrongPassword
498
682
499instance encodeJsonSub :: EncodeJson Sub where
683instance encodeJsonSub :: EncodeJson Sub where
500 encodeJson = genericEncodeAeson Argonaut.defaultOptions
684 encodeJson = genericEncodeAeson Argonaut.defaultOptions
@@ -504,3 +688,21 @@ derive instance genericSub :: Generic Sub _
504derive instance eqSub :: Eq Sub
688derive instance eqSub :: Eq Sub
505derive instance ordSub :: Ord Sub
689derive instance ordSub :: Ord Sub
506
690
691+data TwoFactorProviderType =
692+ Authenticator
693+ | Duo
694+ | Email
695+ | OrganizationDuo
696+ | Remember
697+ | U2f
698+ | WebAuthn
699+ | Yubikey
700+
701+instance encodeJsonTwoFactorProviderType :: EncodeJson TwoFactorProviderType where
702+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
703+instance decodeJsonTwoFactorProviderType :: DecodeJson TwoFactorProviderType where
704+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
705+derive instance genericTwoFactorProviderType :: Generic TwoFactorProviderType _
706+derive instance eqTwoFactorProviderType :: Eq TwoFactorProviderType
707+derive instance ordTwoFactorProviderType :: Ord TwoFactorProviderType
708+
backend/Data/Clipboard.jsnew file+4
@@ -0,0 +1,4 @@
1+
2+export function clipboard(text) {
3+ return navigator.clipboard.writeText(text)
4+}
backend/Data/Clipboard.pursnew file+6
@@ -0,0 +1,6 @@
1+module Data.Clipboard where
2+
3+import Prelude
4+import Control.Promise (Promise)
5+
6+foreign import clipboard :: String -> Promise Unit
backend/Data/EncString.js+5−2
@@ -1,11 +1,14 @@
1−import { EncString } from "../../deps/bw/libs/shared/dist/src/models/domain/encString"
1+import { EncString } from "../../bw/models/domain/enc-string"
2
2
3export function fromString(str) {
3export function fromString(str) {
4− console.log("fromString " + str)
5 const x = new EncString(str)
4 const x = new EncString(str)
6 return x
5 return x
7}
6}
8
7
8+export function toString(encStr) {
9+ return encStr.encryptedString
10+}
11+
9export function encryptionType(encStr) {
12export function encryptionType(encStr) {
10 return encStr.encryptionType
13 return encStr.encryptionType
11}
14}
backend/Data/EncString.purs+2
@@ -25,3 +25,5 @@ type EncryptionType
25 = Int
25 = Int
26
26
27foreign import fromString :: EncryptedString -> EncString
27foreign import fromString :: EncryptedString -> EncString
28+
29+foreign import toString :: EncString -> EncryptedString
backend/Data/HCaptcha.jsnew file+9
@@ -0,0 +1,9 @@
1+export function bindHCaptchToken(callback) {
2+ return () => {
3+ window.addEventListener("message", (e) => {
4+ if (typeof e.data === 'object' && "hCaptchaToken" in e.data) {
5+ callback(e.data.hCaptchaToken)();
6+ }
7+ })
8+ }
9+}
backend/Data/HCaptcha.pursnew file+6
@@ -0,0 +1,6 @@
1+module Data.HCaptcha where
2+
3+import Prelude
4+import Effect (Effect)
5+
6+foreign import bindHCaptchToken :: (String -> Effect Unit) -> Effect Unit
backend/Data/OpenURL.jsnew file+3
@@ -0,0 +1,3 @@
1+export function openURL(url) {
2+ return () => window.open(url, '_blank')
3+}
backend/Data/OpenURL.pursnew file+6
@@ -0,0 +1,6 @@
1+module Data.OpenURL where
2+
3+import Prelude
4+import Effect (Effect)
5+
6+foreign import openURL :: String -> Effect Unit
backend/Data/SymmetricCryptoKey.js+1−2
@@ -1,6 +1,5 @@
1−import { SymmetricCryptoKey } from '../../deps/bw/libs/shared/dist/src/models/domain/symmetricCryptoKey.js';
1+import { SymmetricCryptoKey } from '../../bw/models/domain/symmetric-crypto-key.js'
2
2
3export function fromArrayBuffer(arr) {
3export function fromArrayBuffer(arr) {
4− console.log("fromArrayBuffer " + arr)
5 return new SymmetricCryptoKey(arr)
4 return new SymmetricCryptoKey(arr)
6}
5}
backend/FFI.js+16−1
@@ -1,6 +1,21 @@
1+function searchForInit(obj) {
2+ if (obj.init) {
3+ return obj.init
4+ } else {
5+ for (let key in obj) {
6+ if (obj.hasOwnProperty(key)) {
7+ const result = searchForInit(obj[key])
8+ if (result) {
9+ return result
10+ }
11+ }
12+ }
13+ }
14+}
15+
1export function startElm(element) {
16export function startElm(element) {
2 return function () {
17 return function () {
3− return Elm.Main.init({
18+ return searchForInit(Elm)({
4 node: document.getElementById(element)
19 node: document.getElementById(element)
5 })
20 })
6 }
21 }
backend/Main.purs+368−235
@@ -4,24 +4,25 @@ module Main
4
4
5import Prelude
5import Prelude
6
6
7−import BW (ApiService, CryptoService, Hash, Services)
7+import BW (ApiService, CryptoFunctions, Services, CryptoService)
8import BW as WB
8import BW as WB
9−import BW.Logic (decrypt, hashPassword, liftPromise)
9+import BW.Logic (bwPasswordStringHash, decodeCipher, decrypt, encodeCipher, hashPassword, liftPromise, supportedCipher)
10import BW.Logic as Logic
10import BW.Logic as Logic
11−import BW.Types (CipherResponse, Email(..), IdentityTokenResponse, Password(..), PreloginResponse, SyncResponse, Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote)
11+import BW.Types (CipherResponse, Email(..), Password(..), Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote, fromTwoFactorProviderType)
12−import Bridge (Sub_LoadCipher(..))
13import Bridge as Bridge
12import Bridge as Bridge
14−import Control.El (class HasL, Al, L(..), l)
13+import Control.Monad.Error.Class (catchError)
15−import Control.Monad.Error.Class (throwError)
16−import Control.Monad.List.Trans as List
17−import Control.Monad.Reader (runReaderT)
18import Data.Argonaut (class DecodeJson)
14import Data.Argonaut (class DecodeJson)
19import Data.Array as Array
15import Data.Array as Array
16+import Data.Clipboard as Clipboard
17+import Data.DateTime (DateTime)
20import Data.Either (Either(..))
18import Data.Either (Either(..))
19+import Data.Foldable (for_)
20+import Data.HCaptcha (bindHCaptchToken)
21−import Data.JNullable (fromJNullable, jnull, nullify)
21+import Data.JNullable (jnull, nullify)
22import Data.JNullable as JNullable
22import Data.JNullable as JNullable
23−import Data.JOpt (fromJOpt)
23+import Data.Maybe (Maybe(..), maybe)
24−import Data.Maybe (Maybe(..))
24+import Data.OpenURL (openURL)
25+import Data.Ord.Down (Down(..))
25import Data.SymmetricCryptoKey (SymmetricCryptoKey)
26import Data.SymmetricCryptoKey (SymmetricCryptoKey)
26import Data.Timestamp as Timestamp
27import Data.Timestamp as Timestamp
27import Data.Traversable (traverse)
28import Data.Traversable (traverse)
@@ -29,7 +30,7 @@ import Effect (Effect)
29import Effect.Aff (Aff, runAff_, try)
30import Effect.Aff (Aff, runAff_, try)
30import Effect.Class (liftEffect)
31import Effect.Class (liftEffect)
31import Effect.Class.Console (log)
32import Effect.Class.Console (log)
32−import Effect.Exception (error)
33+import Effect.Exception (throw)
33import Effect.Exception as Exc
34import Effect.Exception as Exc
34import Effect.Ref as Ref
35import Effect.Ref as Ref
35import Elm as Elm
36import Elm as Elm
@@ -37,6 +38,12 @@ import FFI (Elm)
37import FFI as FFI
38import FFI as FFI
38import Localstorage (class StorageKey)
39import Localstorage (class StorageKey)
39import Localstorage as Storage
40import Localstorage as Storage
41+import Run (AFF, EFFECT, Run, liftAff, runBaseAff')
42+import Run.Reader (Reader, askAt, runReaderAt)
43+import Storage (MasterPasswordHashKey(..), PreloginResponseKey(..), SyncKey(..), TokenKey(..), UrlsKey(..))
44+import Type.Prelude (Proxy(..))
45+import Type.Row (type (+))
46+import Untagged.Union (toEither1)
40import Web.HTML as Html
47import Web.HTML as Html
41import Web.HTML.Window as Window
48import Web.HTML.Window as Window
42import Web.Storage.Storage (Storage)
49import Web.Storage.Storage (Storage)
@@ -48,221 +55,273 @@ main = do
48 storage <- Html.window >>= Window.localStorage
55 storage <- Html.window >>= Window.localStorage
49 services <- WB.getServices
56 services <- WB.getServices
50 masterKeyRef <- Ref.new Nothing
57 masterKeyRef <- Ref.new Nothing
58+ hCaptchaTokenRef <- Ref.new Nothing
59+ bindHCaptchToken \captchaToken -> do
60+ Ref.write (Just captchaToken) hCaptchaTokenRef
61+ Elm.send app Bridge.CaptchaDone
62+ let
63+ runAff act = runElmAff app $ run $ act
64+
65+ run
66+ :: forall a
67+ . Run
68+ ( app :: Reader Elm
69+ , aff :: Aff
70+ , effect :: Effect
71+ , crypto :: Reader CryptoService
72+ , storage :: Reader Storage
73+ , services :: Reader Services
74+ , cryptoFunctions :: Reader CryptoFunctions
75+ )
76+ a
77+ -> Run (EFFECT + AFF + ()) a
51− let run act = do
78+ run act =
79+ runReaderAt (Proxy :: _ "app") app
80+ $ runReaderAt (Proxy :: _ "crypto") services.crypto
81+ $ runReaderAt (Proxy :: _ "storage") storage
82+ $ runReaderAt (Proxy :: _ "services") services
83+ $ runReaderAt (Proxy :: _ "cryptoFunctions") services.cryptoFunctions
84+ $ act
85+
86+ runWithDecryptionKey
87+ :: Run
88+ ( app :: Reader Elm
89+ , aff :: Aff
90+ , effect :: Effect
91+ , key :: Reader SymmetricCryptoKey
92+ , crypto :: Reader CryptoService
93+ , storage :: Reader Storage
94+ , services :: Reader Services
95+ , cryptoFunctions :: Reader CryptoFunctions
96+ )
97+ Unit
98+ -> Effect Unit
99+ runWithDecryptionKey act = do
52− maybeKey <- Ref.read masterKeyRef
100+ maybeKey <- Ref.read masterKeyRef
53− runElmAff app $ flip runReaderT {app: app, crypto : services.crypto, storage: storage} $
101+ runAff
54− case maybeKey of
102+ $ case maybeKey of
55− Just masterKey -> do
103+ Just masterKey -> do
56− token <- getOrReset storage TokenKey
104+ token <- getOrReset TokenKey
57− key <- Logic.makeDecryptionKey masterKey token.key
105+ key <- Logic.makeDecryptionKey masterKey token.key
58− liftEffect $ runElmAff app $ runReaderT act
106+ runReaderAt (Proxy :: _ "key") key $ act
59− {app : app , storage : storage, services : services, crypto : services.crypto
60− , key : key}
61− Nothing -> requestMasterPassword
107+ Nothing -> requestMasterPassword
62 Elm.subscribe app \cmd -> case cmd of
108 Elm.subscribe app \cmd -> case cmd of
63− Bridge.Login (Bridge.Cmd_Login { email, server, password }) -> do
109+ Bridge.Login (Bridge.Cmd_Login { email: email', server, password, secondFactor }) -> do
110+ let
111+ email = Email email'
112+ captchaResponse <- Ref.read hCaptchaTokenRef
113+ Ref.write Nothing hCaptchaTokenRef
64 let
114 let
65 urls = baseUrl server
115 urls = baseUrl server
66− runElmAff app do
116+ runAff do
67 unauthedApi <- liftPromise $ services.getApi urls jnull
117 unauthedApi <- liftPromise $ services.getApi urls jnull
68− flip runReaderT { api: unauthedApi, crypto: services.crypto, storage:
69− storage, cryptoFunctions: services.cryptoFunctions } do
70− {token, prelogin: (prelogin :: PreloginResponse)} <-
71− try (do
72− prelogin <- liftPromise $ unauthedApi.postPrelogin {email}
118+ prelogin <- liftPromise $ unauthedApi.postPrelogin { email }
119+ runReaderAt (Proxy :: _ "api") unauthedApi do
120+ loginResponse <-
121+ liftAff
122+ $ catchError
123+ ( do
124+ resp <-
125+ runBaseAff' $ run $ runReaderAt (Proxy :: _ "api") unauthedApi
73− token <- (Logic.getLogInRequestToken prelogin (Email email) (Password password))
126+ $ Logic.getLogInRequestToken
74− pure {token, prelogin}
127+ { prelogin
128+ , email
129+ , password: Password password
130+ , captchaResponse
131+ , secondFactor
132+ }
133+ pure $ Right resp
75− )
134+ )
76− >>= \et -> case et of
135+ ( \err -> do
77− Left _ -> liftEffect $ Exc.throw "Could not log in. Please check your data and try again."
136+ log $ show err
78− Right t -> pure t
137+ pure $ Left unit
138+ )
139+ case map (toEither1 >>> map toEither1) $ loginResponse of
140+ Left _ -> send Bridge.WrongPassword
141+ Right (Left { siteKey }) -> send $ Bridge.NeedsCaptcha siteKey
142+ Right (Right (Right token)) -> do
79− masterKey <- Logic.makePreloginKey prelogin (Email email) (Password password)
143+ masterKey <- Logic.makePreloginKey prelogin email (Password password)
80− api <- liftPromise $ services.getApi urls (nullify token)
144+ api <- liftPromise $ services.getApi urls (nullify token)
81− sync <- liftPromise $ api.getSync unit
145+ sync <- liftPromise $ api.getSync unit
82− hash <- hashPassword (Password password)
146+ hash <- hashPassword (Password password)
83− log $ show sync
147+ log $ show sync
148+ liftEffect do
84− liftEffect $ Ref.write (Just masterKey) masterKeyRef
149+ Ref.write (Just masterKey) masterKeyRef
85− liftEffect $ Storage.store storage UrlsKey server
150+ Storage.store storage UrlsKey server
86− liftEffect $ Storage.store storage MasterPasswordHashKey hash
151+ Storage.store storage MasterPasswordHashKey hash
87− liftEffect $ Storage.store storage PreloginResponseKey prelogin
152+ Storage.store storage PreloginResponseKey prelogin
88− liftEffect $ Storage.store storage SyncKey sync
153+ Storage.store storage SyncKey sync
89− liftEffect $ Storage.store storage TokenKey token
154+ Storage.store storage TokenKey token
90− liftEffect $ Elm.send app Bridge.LoginSuccessful
155+ send Bridge.LoginSuccessful
156+ Right (Right (Left { twoFactorProviders, captchaToken })) -> do
157+ case toEither1 captchaToken of
158+ Left captchaTokenString ->
159+ liftEffect
160+ $ Ref.write (Just captchaTokenString) hCaptchaTokenRef
91− pure unit
161+ Right _ -> pure unit
162+ providers <- case fromTwoFactorProviderType twoFactorProviders of
163+ Left x -> liftEffect $ throw $ "Unknown second factor provider: " <> x
92− pure unit
164+ Right x -> pure x
165+ send $ Bridge.NeedsSecondFactor $ Bridge.Sub_NeedsSecondFactor_List providers
93− Bridge.NeedCiphersList -> run do
166+ Bridge.NeedCiphersList ->
94− sync <- getOrReset storage SyncKey
167+ runWithDecryptionKey do
95− ciphers <- traverse processCipher sync.ciphers
168+ sendCiphers
96− liftEffect $ Elm.send app $ Bridge.LoadCiphers $ Bridge.Sub_LoadCiphers_List ciphers
169+ performSync
97− api <- getAuthedApi
98− sync <- liftPromise $ api.getSync unit
99− liftEffect $ Storage.store storage SyncKey sync
100− pure unit
101 Bridge.NeedsReset -> do
170 Bridge.NeedsReset -> do
102 WebStorage.clear storage
171 WebStorage.clear storage
103 Elm.send app Bridge.Reset
172 Elm.send app Bridge.Reset
104− pure unit
105− Bridge.SendMasterPassword masterPassword -> runElmAff app $ do
173+ Bridge.SendMasterPassword masterPassword ->
106− flip runReaderT {app: app, storage: storage, crypto: services.crypto, cryptoFunctions: services.cryptoFunctions} $ do
174+ runAff do
107− hash <- getOrReset storage MasterPasswordHashKey
175+ hash <- getOrReset MasterPasswordHashKey
108 newHash <- hashPassword (Password masterPassword)
176 newHash <- hashPassword (Password masterPassword)
109− if hash == newHash
177+ if hash == newHash then do
110− then do
111− sync <- getOrReset storage SyncKey
178+ sync <- getOrReset SyncKey
112− prelogin <- getOrReset storage PreloginResponseKey
179+ prelogin <- getOrReset PreloginResponseKey
113− masterKey <- Logic.makePreloginKey prelogin (Email sync.profile.email) (Password masterPassword)
180+ masterKey <- Logic.makePreloginKey prelogin (Email sync.profile.email) (Password masterPassword)
114− liftEffect $ Ref.write (Just masterKey) masterKeyRef
181+ liftEffect $ Ref.write (Just masterKey) masterKeyRef
115− liftEffect $ Elm.send app Bridge.LoginSuccessful
182+ send Bridge.LoginSuccessful
116− else do
183+ else do
117− liftEffect $ Elm.send app $ Bridge.Error "The password is wrong. Please try again."
184+ send $ Bridge.Error "The password is wrong. Please try again."
118− requestMasterPassword
185+ requestMasterPassword
119− pure unit
120 Bridge.Init -> do
186 Bridge.Init -> do
121− liftEffect $ Storage.get storage TokenKey >>= \x -> case x of
187+ Storage.get storage TokenKey
188+ >>= \x ->
189+ Elm.send app case x of
122− Just _ -> do liftEffect $ Elm.send app Bridge.LoginSuccessful
190+ Just _ -> Bridge.LoginSuccessful
123− Nothing -> Elm.send app Bridge.NeedsLogin
191+ Nothing -> Bridge.NeedsLogin
124− Bridge.RequestCipher id -> run do
192+ Bridge.RequestCipher id ->
193+ runWithDecryptionKey do
125− sync <- getOrReset storage SyncKey
194+ sync <- getOrReset SyncKey
126− case Array.find (\c -> c.id == id) sync.ciphers of
195+ case Array.find (\c -> c.id == id) sync.ciphers of
127− Nothing -> pure unit
196+ Nothing -> pure unit
128− Just cipher -> do
197+ Just cipherResponse -> do
129− name <- decrypt cipher.name
198+ cipher <- decodeCipher cipherResponse
130− cipherType <- do
199+ for_ cipher $ send <<< Bridge.LoadCipher
131− case cipher.type of
200+ pure unit
132− n | cipherTypeSecureNote == n -> do
201+ Bridge.NeedEmail ->
133− note <- fromJNullable "" <$> (traverse decrypt cipher.notes)
202+ runWithDecryptionKey do
134− pure $ Bridge.NoteCipher note
203+ sync <- getOrReset SyncKey
135− n | cipherTypeLogin == n -> do
204+ send $ Bridge.RecieveEmail sync.profile.email
136− case JNullable.toMaybe cipher.login of
205+ pure unit
137− Nothing -> throwError $ error "Login data is missing"
206+ Bridge.Copy text ->
138− Just login -> do
207+ runWithDecryptionKey do
139− username <- Bridge.Sub_LoadCipher_cipherType_LoginCipher_username_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt login.username)
208+ liftPromise $ Clipboard.clipboard text
140− password <- Bridge.Sub_LoadCipher_cipherType_LoginCipher_password_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt login.password)
209+ Bridge.Open uri -> openURL uri
141− uris <- fromJOpt [] <$> ((traverse >>> traverse) (_.uri >>> decrypt) login.uris)
210+ Bridge.UpdateCipher fullCipher ->
142− pure $ Bridge.LoginCipher $ Bridge.Sub_LoadCipher_cipherType_LoginCipher
211+ runWithDecryptionKey do
143− { username
212+ api <- getAuthedApi
144− , password
213+ cipher <- encodeCipher fullCipher
145− , uris: Bridge.Sub_LoadCipher_cipherType_LoginCipher_uris_List uris
214+ newCipher <- liftPromise (api.putCipher cipher) >>= decodeCipher
146− }
215+ for_ newCipher $ send <<< Bridge.CipherChanged
147− n | cipherTypeCard == n -> do
216+ performSync
148− case JNullable.toMaybe cipher.card of
217+ pure unit
149− Nothing -> throwError $ error "Card data is missing"
218+ Bridge.GeneratePassword cfg ->
150− Just card -> do
219+ runWithDecryptionKey do
151− cardholderName <- Bridge.Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.cardholderName)
220+ password <- liftPromise $ services.passwordGeneration.generatePassword cfg
152− number <- Bridge.Sub_LoadCipher_cipherType_CardCipher_number_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.number)
221+ send $ Bridge.GeneratedPassword password
153− code <- Bridge.Sub_LoadCipher_cipherType_CardCipher_code_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.code)
222+ pure unit
154− brand <- Bridge.Sub_LoadCipher_cipherType_CardCipher_brand_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.brand)
223+ Bridge.CreateCipher fullCipher ->
155− expMonth <- Bridge.Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.expMonth)
224+ runWithDecryptionKey do
156− expYear <- Bridge.Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt card.expYear)
225+ api <- getAuthedApi
157− pure $ Bridge.CardCipher $ Bridge.Sub_LoadCipher_cipherType_CardCipher
226+ cipher <- encodeCipher fullCipher
158− { brand, cardholderName, code, expMonth, expYear, number }
227+ newCipher <- liftPromise (api.postCipherCreate cipher) >>= decodeCipher
159− n | cipherTypeIdentity == n -> do
228+ for_ newCipher $ send <<< Bridge.CipherChanged
160− case JNullable.toMaybe cipher.identity of
229+ performSync
161− Nothing -> throwError $ error "Identity data is missing"
230+ pure unit
162− Just identity -> do
231+ Bridge.DeleteCipher c@(Bridge.FullCipher { id }) ->
163− firstName <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.firstName)
232+ runWithDecryptionKey do
164− middleName <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.middleName)
233+ api <- getAuthedApi
165− lastName <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.lastName)
234+ liftPromise $ api.deleteCipher id
166− address1 <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.address1)
235+ send $ Bridge.CipherDeleted c
167− address2 <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.address2)
236+ performSync
168− address3 <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.address3)
237+ pure unit
169− city <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.city)
238+ Bridge.RequestTotp totp ->
170− company <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.company)
239+ runWithDecryptionKey do
171− country <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.country)
240+ { totpService } <- askAt (Proxy :: _ "services")
172− email <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.email)
241+ code <- liftPromise $ totpService.getCode totp
173− licenseNumber <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.licenseNumber)
242+ let
174− passportNumber <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.passportNumber)
243+ interval = totpService.getTimeInterval totp
175− phone <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.phone)
244+ send $ Bridge.Totp
176− postalCode <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.postalCode)
245+ $ Bridge.Sub_Totp
177− ssn <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.ssn)
246+ { interval
178− state <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.state)
247+ , code
179− title <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.title)
248+ , source: totp
180− username <- Bridge.Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt identity.username)
249+ }
181− pure $ Bridge.IdentityCipher $ Bridge.Sub_LoadCipher_cipherType_IdentityCipher
182− { firstName,
183− middleName,
184− lastName,
185− address1,
186− address2,
187− address3,
188− city,
189− company,
190− country,
191− email,
192− licenseNumber,
193− passportNumber,
194− phone,
195− postalCode,
196− ssn,
197− state,
198− title,
199− username
200− }
201− n -> throwError $ error $ "Unsupported cipher type: " <> show n
202− send $ Bridge.LoadCipher $ Bridge.Sub_LoadCipher {cipherType, name, id}
203− pure unit
250+ pure unit
204− pure unit
251+ Bridge.ChooseSecondFactor
205−
252+ ( Bridge.Cmd_ChooseSecondFactor
206−data SyncKey = SyncKey
253+ { email: email'
207−
254+ , factor
208−instance StorageKey SyncKey SyncResponse where
255+ , server
209− storageKey SyncKey = "sync"
256+ , password
210−
257+ , requestFromServer
211−data TokenKey
258+ }
212− = TokenKey
259+ ) ->
213−
260+ when requestFromServer
214−instance StorageKey TokenKey IdentityTokenResponse where
261+ let
215− storageKey TokenKey = "token"
262+ email = Email email'
216−
263+ in
217−data UrlsKey
264+ case factor of
218− = UrlsKey
265+ Bridge.Email ->
219−
266+ runAff do
220−instance StorageKey UrlsKey String where
267+ let
221− storageKey UrlsKey = "urls"
268+ urls = baseUrl server
222−
269+ unauthedApi <- liftPromise $ services.getApi urls jnull
223−data PreloginResponseKey
270+ prelogin <- liftPromise $ unauthedApi.postPrelogin { email }
224− = PreloginResponseKey
271+ masterPasswordHash <- bwPasswordStringHash prelogin email (Password password)
225−
272+ liftPromise $ unauthedApi.postTwoFactorEmail { email, masterPasswordHash }
226−instance StorageKey PreloginResponseKey PreloginResponse where
273+ pure unit
227− storageKey PreloginResponseKey = "prelogin-reponse"
274+ _ -> pure unit
228−
229−data MasterPasswordHashKey
230− = MasterPasswordHashKey
231−
232−instance StorageKey MasterPasswordHashKey Hash where
233− storageKey MasterPasswordHashKey = "master-password-hash"
234−
235
275
236−processCipher ::
276+processCipher
237− forall r.
277+ :: forall r
278+ . CipherResponse
279+ -> Run
238− HasL "crypto" CryptoService r =>
280+ ( crypto :: Reader CryptoService
239− HasL "key" SymmetricCryptoKey r =>
281+ , key :: Reader SymmetricCryptoKey
282+ , aff :: Aff
283+ , effect :: Effect
284+ | r
285+ )
240− CipherResponse -> Al r Bridge.Sub_LoadCiphers
286+ (Maybe { cipher :: Bridge.Sub_LoadCiphers, date :: DateTime })
287+processCipher cipher | not (supportedCipher cipher) = pure Nothing
241processCipher cipher = do
288processCipher cipher = do
242 name <- decrypt cipher.name
289 name <- decrypt cipher.name
243 cipherType <- case cipher.type of
290 cipherType <- case cipher.type of
291+ n
244− n | n == cipherTypeLogin -> pure Bridge.LoginType
292+ | n == cipherTypeLogin -> pure Bridge.LoginType
293+ n
245− n | n == cipherTypeSecureNote -> pure Bridge.NoteType
294+ | n == cipherTypeSecureNote -> pure Bridge.NoteType
295+ n
246− n | n == cipherTypeCard -> pure Bridge.CardType
296+ | n == cipherTypeCard -> pure Bridge.CardType
297+ n
247− n | n == cipherTypeIdentity -> pure Bridge.IdentityType
298+ | n == cipherTypeIdentity -> pure Bridge.IdentityType
248− n -> throwError $ error $ "Unsupported cipher type: " <> show n
299+ n -> liftEffect $ throw $ "Unsupported cipher type: " <> show n
300+ date <- liftEffect $ maybe (pure bottom) (Timestamp.toDateTime) $ JNullable.toMaybe cipher.revisionDate
249 pure
301 pure
302+ $ Just
303+ $
304+ { cipher:
250− $ Bridge.Sub_LoadCiphers
305+ Bridge.Sub_LoadCiphers
251− { name: name
306+ { name: name
252− , date: Timestamp.toLocalDateTimeString cipher.revisionDate
307+ , date: maybe "" Timestamp.toLocalDateTimeString $ JNullable.toMaybe cipher.revisionDate
253− , id: cipher.id
308+ , id: cipher.id
254− , cipherType
309+ , cipherType
310+ }
311+ , date
255− }
312+ }
256
313
257−runElmAff :: FFI.Elm -> Aff Unit -> Effect Unit
314+runElmAff :: FFI.Elm -> Run (AFF + EFFECT + ()) Unit -> Effect Unit
258runElmAff app =
315runElmAff app =
259 runAff_
316 runAff_
260− $ \res -> do
317+ ( \res -> do
261 case res of
318 case res of
262 Left e -> do
319 Left e -> do
263 log $ show e
320 log $ show e
264 Elm.send app (Bridge.Error $ Exc.message e)
321 Elm.send app (Bridge.Error $ Exc.message e)
265 Right unit -> pure unit
322 Right unit -> pure unit
323+ )
324+ <<< runBaseAff'
266
325
267baseUrl :: String -> Urls
326baseUrl :: String -> Urls
268baseUrl server =
327baseUrl server =
@@ -276,41 +335,115 @@ baseUrl server =
276 , keyConnector: jnull
335 , keyConnector: jnull
277 }
336 }
278
337
279−getOrReset :: forall k t r. HasL "app" Elm r =>
338+getOrReset
339+ :: forall k t r
280− StorageKey k t => DecodeJson t => Storage -> k -> Al r t
340+ . StorageKey k t
341+ => DecodeJson t
342+ => k
343+ -> Run
344+ ( storage :: Reader Storage
345+ , app :: Reader Elm
346+ , effect :: Effect
347+ | r
348+ )
349+ t
281−getOrReset storage key = do
350+getOrReset key = do
351+ storage <- askAt (Proxy :: _ "storage")
352+ app <- askAt (Proxy :: _ "app")
353+ let
282− let handleError = do
354+ handleError = do
283− app <- l (L :: L "app")
284− liftEffect $ WebStorage.clear storage
355+ WebStorage.clear storage
285− liftEffect $ Elm.send app Bridge.Reset
356+ Elm.send app Bridge.Reset
286− liftEffect $ Exc.throw "Encountered an internal error. Resetting the app."
357+ Exc.throw "Encountered an internal error. Resetting the app."
287− try (liftEffect $ Storage.get storage key) >>= \x -> case x of
358+ liftEffect $ try (Storage.get storage key)
359+ >>= \x -> case x of
288− Left err -> do
360+ Left err -> do
289− liftEffect $ log $ show err
361+ log $ show err
290− handleError
362+ handleError
291− Right Nothing -> handleError
363+ Right Nothing -> handleError
292− Right (Just y) -> pure y
364+ Right (Just y) -> pure y
293
365
294−getAuthedApi :: forall r. HasL "services" Services r => HasL "storage" Storage r
366+getAuthedApi
367+ :: forall r
368+ . Run
369+ ( storage :: Reader Storage
295− => HasL "app" Elm r =>
370+ , app :: Reader Elm
371+ , aff :: Aff
372+ , effect :: Effect
373+ , services :: Reader Services
374+ | r
375+ )
296− Al r ApiService
376+ ApiService
297getAuthedApi = do
377getAuthedApi = do
298− storage <- l (L :: L "storage")
299− services <- l (L :: L "services")
378+ services <- askAt (Proxy :: _ "services")
300− token <- getOrReset storage TokenKey
379+ token <- getOrReset TokenKey
301− url <- getOrReset storage UrlsKey
380+ url <- getOrReset UrlsKey
302 liftPromise $ services.getApi (baseUrl url) (nullify token)
381 liftPromise $ services.getApi (baseUrl url) (nullify token)
303
382
304−requestMasterPassword :: forall r. HasL "storage" Storage r => HasL "app" Elm r => Al r Unit
383+requestMasterPassword
384+ :: forall r
385+ . Run
386+ ( storage :: Reader Storage
387+ , app :: Reader Elm
388+ , effect :: Effect
389+ | r
390+ )
391+ Unit
305requestMasterPassword = do
392requestMasterPassword = do
306− storage <- l (L :: L "storage")
307− app <- l (L :: L "app")
308− url <- getOrReset storage UrlsKey
393+ url <- getOrReset UrlsKey
309− sync <- getOrReset storage SyncKey
394+ sync <- getOrReset SyncKey
310− liftEffect $ Elm.send app $ Bridge.NeedsMasterPassword
311− $ Bridge.Sub_NeedsMasterPassword {server: url, login: sync.profile.email}
395+ send $ Bridge.NeedsMasterPassword $ Bridge.Sub_NeedsMasterPassword { server: url, login: sync.profile.email }
312
396
313−send :: forall r. HasL "app" Elm r => Bridge.Sub -> Al r Unit
397+send
398+ :: forall r
399+ . Bridge.Sub
400+ -> Run
401+ ( app :: Reader Elm
402+ , effect :: Effect
403+ | r
404+ )
405+ Unit
314send sub = do
406send sub = do
315− app <- l (L :: L "app")
407+ app <- askAt (Proxy :: _ "app")
316 liftEffect $ Elm.send app sub
408 liftEffect $ Elm.send app sub
409+
410+performSync
411+ :: forall r
412+ . Run
413+ ( storage :: Reader Storage
414+ , app :: Reader Elm
415+ , aff :: Aff
416+ , effect :: Effect
417+ , services :: Reader Services
418+ , crypto :: Reader CryptoService
419+ , key :: Reader SymmetricCryptoKey
420+ | r
421+ )
422+ Unit
423+performSync = do
424+ api <- getAuthedApi
425+ storage <- askAt (Proxy :: _ "storage")
426+ newSync <- liftPromise $ api.getSync unit
427+ liftEffect $ Storage.store storage SyncKey newSync
428+ sendCiphers
429+
430+sendCiphers
431+ :: forall r
432+ . Run
433+ ( storage :: Reader Storage
434+ , app :: Reader Elm
435+ , aff :: Aff
436+ , effect :: Effect
437+ , services :: Reader Services
438+ , crypto :: Reader CryptoService
439+ , key :: Reader SymmetricCryptoKey
440+ | r
441+ )
442+ Unit
443+sendCiphers = do
444+ sync <- getOrReset SyncKey
445+ ciphers <- Array.catMaybes <$> traverse processCipher sync.ciphers
446+ let
447+ sortedCiphers = Array.sortWith (_.date >>> Down) ciphers
448+ send $ Bridge.LoadCiphers $ Bridge.Sub_LoadCiphers_List $ map _.cipher sortedCiphers
449+ pure unit
backend/Storage.pursnew file+43
@@ -0,0 +1,43 @@
1+module Storage where
2+
3+import Prelude
4+
5+import BW (Hash)
6+import BW.Types (IdentityTokenResponse, SyncResponse, PreloginResponse)
7+import Localstorage (class StorageKey)
8+
9+data SyncKey
10+ = SyncKey
11+
12+instance StorageKey SyncKey SyncResponse where
13+ storageKey SyncKey = "sync"
14+
15+data TokenKey
16+ = TokenKey
17+
18+instance StorageKey TokenKey IdentityTokenResponse where
19+ storageKey TokenKey = "token"
20+
21+data UrlsKey
22+ = UrlsKey
23+
24+instance StorageKey UrlsKey String where
25+ storageKey UrlsKey = "urls"
26+
27+data PreloginResponseKey
28+ = PreloginResponseKey
29+
30+instance StorageKey PreloginResponseKey PreloginResponse where
31+ storageKey PreloginResponseKey = "prelogin-reponse"
32+
33+data MasterPasswordHashKey
34+ = MasterPasswordHashKey
35+
36+instance StorageKey MasterPasswordHashKey Hash where
37+ storageKey MasterPasswordHashKey = "master-password-hash"
38+
39+data DeviceIDKey
40+ = DeviceIDKey
41+
42+instance StorageKey DeviceIDKey String where
43+ storageKey DeviceIDKey = "device-uuid"
bridge.yaml+153−84
@@ -1,90 +1,159 @@
1Sub:
1Sub:
2−- NeedsLogin
2+ - NeedsLogin
3+ - NeedsCaptcha: String
4+ - NeedsSecondFactor: { List: TwoFactorProviderType }
5+ - CaptchaDone
3−- Error: String
6+ - Error: String
4−- LoadCiphers:
7+ - LoadCiphers:
5− List:
8+ List:
6 name: String
9 name: String
7 date: String
10 date: String
8 id: String
11 id: String
9− cipherType:
12+ cipherType: CipherType
10− - LoginType
11− - CardType
12− - NoteType
13− - IdentityType
14−- LoginSuccessful
13+ - LoginSuccessful
14+ - WrongPassword
15−- NeedsMasterPassword:
15+ - NeedsMasterPassword:
16− server: String
16+ server: String
17− login: String
17+ login: String
18−- Reset
18+ - Reset
19−- LoadCipher:
19+ - LoadCipher: FullCipher
20− id: String
21− name: String
22− cipherType:
23− - LoginCipher:
24− uris: {List: String}
25− username:
26− Maybe: String
20+ - RecieveEmail: String
27− password:
21+ - CipherChanged: FullCipher
28− Maybe: String
22+ - GeneratedPassword: String
29− - NoteCipher: String
23+ - CipherDeleted: FullCipher
30− - CardCipher:
24+ - Totp:
31− cardholderName:
32− Maybe: String
33− brand:
34− Maybe: String
35− number:
36− Maybe: String
37− expMonth:
38− Maybe: String
39− expYear:
40− Maybe: String
25+ source: String
41− code:
26+ code: String
42− Maybe: String
27+ interval: Int
43− - IdentityCipher:
44− title:
45− Maybe: String
46− firstName:
47− Maybe: String
48− middleName:
49− Maybe: String
50− lastName:
51− Maybe: String
52− address1:
53− Maybe: String
54− address2:
55− Maybe: String
56− address3:
57− Maybe: String
58− city:
59− Maybe: String
60− state:
61− Maybe: String
62− postalCode:
63− Maybe: String
64− country:
65− Maybe: String
66− company:
67− Maybe: String
68− email:
69− Maybe: String
70− phone:
71− Maybe: String
72− ssn:
73− Maybe: String
74− username:
75− Maybe: String
76− passportNumber:
77− Maybe: String
78− licenseNumber:
79− Maybe: String
80
28
81Cmd:
29Cmd:
82−- Init
30+ - Init
83−- Login:
31+ - Login:
84− email: String
32+ email: String
85− server: String
33+ server: String
86− password: String
34+ password: String
35+ secondFactor:
36+ Maybe:
37+ provider: TwoFactorProviderType
38+ token: String
39+ remember: Bool
40+ - ChooseSecondFactor:
41+ factor: TwoFactorProviderType
42+ email: String
43+ server: String
44+ password: String
45+ requestFromServer: Bool
87−- NeedCiphersList
46+ - NeedCiphersList
88−- SendMasterPassword: String
47+ - SendMasterPassword: String
89−- NeedsReset
48+ - NeedsReset
90−- RequestCipher: String
49+ - RequestCipher: String
50+ - NeedEmail
51+ - Copy: String
52+ - Open: String
53+ - UpdateCipher: FullCipher
54+ - CreateCipher: FullCipher
55+ - GeneratePassword: PasswordGeneratorConfig
56+ - DeleteCipher: FullCipher
57+ - RequestTotp: String
58+
59+FullCipher:
60+ reprompt: Int
61+ favorite: Bool
62+ id: String
63+ name: String
64+ cipher: Cipher
65+ collectionIds: { List: String }
66+
67+Cipher:
68+ - LoginCipher:
69+ uris: { List: String }
70+ username:
71+ Maybe: String
72+ password:
73+ Maybe: String
74+ totp:
75+ Maybe: String
76+ - NoteCipher: String
77+ - CardCipher:
78+ cardholderName:
79+ Maybe: String
80+ brand:
81+ Maybe: String
82+ number:
83+ Maybe: String
84+ expMonth:
85+ Maybe: String
86+ expYear:
87+ Maybe: String
88+ code:
89+ Maybe: String
90+ - IdentityCipher:
91+ title:
92+ Maybe: String
93+ firstName:
94+ Maybe: String
95+ middleName:
96+ Maybe: String
97+ lastName:
98+ Maybe: String
99+ address1:
100+ Maybe: String
101+ address2:
102+ Maybe: String
103+ address3:
104+ Maybe: String
105+ city:
106+ Maybe: String
107+ state:
108+ Maybe: String
109+ postalCode:
110+ Maybe: String
111+ country:
112+ Maybe: String
113+ company:
114+ Maybe: String
115+ email:
116+ Maybe: String
117+ phone:
118+ Maybe: String
119+ ssn:
120+ Maybe: String
121+ username:
122+ Maybe: String
123+ passportNumber:
124+ Maybe: String
125+ licenseNumber:
126+ Maybe: String
127+
128+CipherType:
129+ - LoginType
130+ - CardType
131+ - NoteType
132+ - IdentityType
133+
134+PasswordGeneratorConfig:
135+ length: Int
136+ ambiguous: Bool
137+ number: Bool
138+ minNumber: Int
139+ uppercase: Bool
140+ minUppercase: Int
141+ lowercase: Bool
142+ minLowercase: Int
143+ special: Bool
144+ minSpecial: Int
145+ type: String
146+ numWords: Int
147+ wordSeparator: String
148+ capitalize: Bool
149+ includeNumber: Bool
150+
151+TwoFactorProviderType:
152+ - Authenticator
153+ - Email
154+ - Duo
155+ - Yubikey
156+ - U2f
157+ - Remember
158+ - OrganizationDuo
159+ - WebAuthn
build.sh+2−6
@@ -1,9 +1,5 @@
1−#!/bin/bash
1+#!/bin/sh
2
2
3set -e
3set -e
4
4
5−cp -r "$SRC_DIR"/{appname.apparmor,appname.desktop,vanilla,index.html,style.css,manifest.json,logo.svg} "$INSTALL_DIR"
5+cp -r "$SRC_DIR/dist"/* "$INSTALL_DIR"
6−
7−elm make --debug "$SRC_DIR/frontend/Main.elm" --output="$INSTALL_DIR/elm.js"
8−
9−(cd "$SRC_DIR" && spago bundle-app --to "$INSTALL_DIR/backend.js")
clickable.yaml+2−11
@@ -1,17 +1,8 @@
1clickable_minimum_required: 7.1.2
1clickable_minimum_required: 7.1.2
2+framework: ubuntu-sdk-20.04
2builder: custom
3builder: custom
3build: $SRC_DIR/build.sh
4build: $SRC_DIR/build.sh
4kill: webapp-container*
5kill: webapp-container*
5−image_setup:
6+docker_image: clickable/amd64-20.04-amd64
6− env: {}
7− # DEB_URL: http://archive.ubuntu.com/ubuntu
8− # HOST_ARCH: amd64
9−
10− # PATH: /opt/someprogram/bin:$PATH
11− run:
12−
13− - (cd /tmp && curl -L -o elm.gz https://github.com/elm/compiler/releases/download/0.19.1/binary-for-linux-64-bit.gz && gunzip elm.gz && chmod +x elm && mv elm /usr/local/bin/)
14− - npm install -g spago esbuild purescript --unsafe-perm=true
15− - (cd / && curl https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.37.1.tar.xz >git.tar.gz && tar -xvf git.tar.gz && cd git-2.37.1 && ./configure --without-curl && make)
16
7
17restrict_arch: all
8restrict_arch: all
deps/bwdeleted−1

Submodule changed.

dev/build-backend.sh+7−1
@@ -1,3 +1,9 @@
1#!/bin/sh
1#!/bin/sh
2
2
3−spago bundle-app --to backend.js
3+set -e
4+
5+spago build
6+
7+export out="$(pwd)/backend.js"
8+
9+$(nix build .#backendBuildScript --print-out-paths) "$(pwd)/output"
dev/build-bw.sh+2
@@ -1,5 +1,7 @@
1#!/bin/sh
1#!/bin/sh
2
2
3+set -e
4+
3(cd deps/bw && npm install)
5(cd deps/bw && npm install)
4
6
5(cd deps/bw/libs/common/ && npm run clean && npm exec -c "tsc -m es2020 -t es2018")
7(cd deps/bw/libs/common/ && npm run clean && npm exec -c "tsc -m es2020 -t es2018")
dev/build.shnew file+10
@@ -0,0 +1,10 @@
1+#!/bin/sh
2+
3+
4+rm -r dist
5+
6+mkdir -p dist
7+
8+cp -r "$(nix build ".#warden-click-src" --print-out-paths)"/* dist
9+
10+chmod -R 777 dist
dev/elm-live.sh+5−1
@@ -1,3 +1,7 @@
1#!/bin/sh
1#!/bin/sh
2
2
3+set -e
4+
5+nix build .#vanilla -o vanilla
6+
3−elm-live frontend/Main.elm --start-page=index.html --proxy-prefix=/api --proxy-host=http://localhost:6009 --pushstate -- --debug --output=elm.js
7+nix run github:NixOS/nixpkgs?rev=d9a1414346059619d9e13ab93e749bbb82e5252a#elmPackages.elm-live -- $1 --start-page=index.html --proxy-prefix=/api --proxy-host=http://localhost:6009 -- --debug --output=elm.js
dev/elm2nix.shnew file+8
@@ -0,0 +1,8 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd "$(dirname "$0")/.."
6+
7+nix run github:NixOS/nixpkgs#elm2nix -- snapshot
8+nix run github:NixOS/nixpkgs#elm2nix -- convert > elm-srcs.nix
dev/generate-bridge.sh+1−1
@@ -1,3 +1,3 @@
1#!/bin/sh
1#!/bin/sh
2
2
3−elm-ps-bridge --input bridge.yaml --elmOutput=Frontend/Bridge.elm --psOutput=backend/Bridge.purs
3+nix run github:ilyakooo0/elm-ps-bridge -- --input bridge.yaml --elmOutput=frontend/Bridge.elm --psOutput=backend/Bridge.purs
dev/record-test.shnew file+11
@@ -0,0 +1,11 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd tests
6+
7+npm install
8+
9+export RECORD_TEST=true
10+
11+npx playwright test
dev/spago2nix.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+nix run "github:justinwoo/spago2nix#spago2nix" -- generate
dev/start-chrome.sh+7−1
@@ -1,3 +1,9 @@
1#!/bin/sh
1#!/bin/sh
2
2
3+if test -x "$(which chromium)"; then
4+ chromium --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security
3−/Applications/Chromium.app/Contents/MacOS/Chromium --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security
5+elif test -x /Applications/Chromium.app/Contents/MacOS/Chromium; then
6+ /Applications/Chromium.app/Contents/MacOS/Chromium --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security
7+else
8+ flatpak run com.github.Eloston.UngoogledChromium --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security
9+fi
dev/test.shnew file+9
@@ -0,0 +1,9 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd tests
6+
7+npm install
8+
9+npx playwright test
dev/update-tests.shnew file+9
@@ -0,0 +1,9 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd tests
6+
7+npm install
8+
9+npx playwright test --update-snapshots
el/spago.dhalldeleted−14
@@ -1,14 +0,0 @@
1−{ name = "bw"
2−, dependencies =
3− [ "aff"
4− , "effect"
5− , "newtype"
6− , "prelude"
7− , "record"
8− , "test-unit"
9− , "transformers"
10− , "unsafe-coerce"
11− ]
12−, packages = ../packages.dhall
13−, sources = [ "src/**/*.purs", "test/**/*.purs" ]
14−}
el/src/Control/El.pursdeleted−23
@@ -1,23 +0,0 @@
1−module Control.El (El, Al, l, class HasL, L(..)) where
2−
3−
4−import Control.Monad.Reader (class MonadAsk, ReaderT, asks)
5−import Data.Symbol (class IsSymbol, reflectSymbol)
6−import Effect (Effect)
7−import Effect.Aff (Aff)
8−import Prim.Row (class Cons, class Nub)
9−import Record.Unsafe (unsafeGet)
10−import Type.Proxy (Proxy(..))
11−
12−data L :: forall k. k -> Type
13−data L t = L
14−
15−type El r = ReaderT (Record r) Effect
16−type Al r = ReaderT (Record r) Aff
17−
18−l :: forall res r field m. MonadAsk (Record r) m => HasL field res r => IsSymbol field => L field -> m res
19−l L = asks (unsafeGet (reflectSymbol (Proxy :: Proxy field)))
20−
21−class HasL :: forall k1 k2 k3. k1 -> k2 -> k3 -> Constraint
22−class HasL field t r | field r -> t
23−instance (Cons field t r r', Nub r' r) => HasL field t r
el/test/Main.pursdeleted−27
@@ -1,27 +0,0 @@
1−module Test.Main
2− ( main
3− ) where
4−
5−import Prelude
6−import Test.Unit
7−import Control.El
8−import Control.Monad.Reader (runReaderT)
9−import Effect (Effect)
10−import Effect.Aff (launchAff_)
11−import Test.Unit.Assert as Assert
12−import Test.Unit.Output.Fancy (runTest)
13−import Type.Proxy (Proxy(..))
14−
15−main ∷ Effect Unit
16−main =
17− launchAff_ $ void
18− $ runTest do
19− suite "sync code" do
20− test "arithmetic" do
21− x <- runReaderT singleHandler { handle: 2, ignored: "Hello" }
22− Assert.equal x 2
23−
24−singleHandler :: forall r. HasL "handle" Int r => Al r Int
25−singleHandler = do
26− x <- l (L :: L "handle")
27− pure $ x + 1
elm-srcs.nixnew file+62
@@ -0,0 +1,62 @@
1+{
2+
3+ "FabienHenon/elm-infinite-scroll" = {
4+ sha256 = "0vhh7j3lna05l5hwjazag52lbkjv11s80jj62m9wb53qxkarw948";
5+ version = "3.0.3";
6+ };
7+
8+ "bartavelle/json-helpers" = {
9+ sha256 = "0k96qra2nq1j4j4ahfl98dkpkc6f2831mq5d5xxg27mp31qwq5nn";
10+ version = "2.0.2";
11+ };
12+
13+ "elm/browser" = {
14+ sha256 = "0nagb9ajacxbbg985r4k9h0jadqpp0gp84nm94kcgbr5sf8i9x13";
15+ version = "1.0.2";
16+ };
17+
18+ "elm/core" = {
19+ sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf";
20+ version = "1.0.5";
21+ };
22+
23+ "elm/html" = {
24+ sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k";
25+ version = "1.0.0";
26+ };
27+
28+ "elm/json" = {
29+ sha256 = "0kjwrz195z84kwywaxhhlnpl3p251qlbm5iz6byd6jky2crmyqyh";
30+ version = "1.1.3";
31+ };
32+
33+ "elm/time" = {
34+ sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1";
35+ version = "1.0.0";
36+ };
37+
38+ "mdgriffith/elm-ui" = {
39+ sha256 = "0ffcqv4a4ad400hwp824m3qq4jy82cqp5ghmhp1m0q7n004z6kgv";
40+ version = "1.1.8";
41+ };
42+
43+ "mgold/elm-nonempty-list" = {
44+ sha256 = "02d3xgasgy5knh02rgl403z9hwlbyrsnp9wavc2lhkxxa2misrgl";
45+ version = "4.2.0";
46+ };
47+
48+ "elm/random" = {
49+ sha256 = "138n2455wdjwa657w6sjq18wx2r0k60ibpc4frhbqr50sncxrfdl";
50+ version = "1.0.0";
51+ };
52+
53+ "elm/url" = {
54+ sha256 = "0av8x5syid40sgpl5vd7pry2rq0q4pga28b4yykn9gd9v12rs3l4";
55+ version = "1.0.0";
56+ };
57+
58+ "elm/virtual-dom" = {
59+ sha256 = "1yvb8px2z62xd578ag2q0r5hd1vkz9y7dfkx05355iiy1d7jwq4v";
60+ version = "1.0.3";
61+ };
62+}
elm.json+2
@@ -6,12 +6,14 @@
6 "elm-version": "0.19.1",
6 "elm-version": "0.19.1",
7 "dependencies": {
7 "dependencies": {
8 "direct": {
8 "direct": {
9+ "FabienHenon/elm-infinite-scroll": "3.0.3",
9 "bartavelle/json-helpers": "2.0.2",
10 "bartavelle/json-helpers": "2.0.2",
10 "elm/browser": "1.0.2",
11 "elm/browser": "1.0.2",
11 "elm/core": "1.0.5",
12 "elm/core": "1.0.5",
12 "elm/html": "1.0.0",
13 "elm/html": "1.0.0",
13 "elm/json": "1.1.3",
14 "elm/json": "1.1.3",
14 "elm/time": "1.0.0",
15 "elm/time": "1.0.0",
16+ "mdgriffith/elm-ui": "1.1.8",
15 "mgold/elm-nonempty-list": "4.2.0"
17 "mgold/elm-nonempty-list": "4.2.0"
16 },
18 },
17 "indirect": {
19 "indirect": {
flake.locknew file+177
@@ -0,0 +1,177 @@
1+{
2+ "nodes": {
3+ "bw": {
4+ "flake": false,
5+ "locked": {
6+ "lastModified": 1675446731,
7+ "narHash": "sha256-IbFjYNYZep6W9c+dztJ9CXrwQ0cep3EKb6qnEvv4pj4=",
8+ "owner": "bitwarden",
9+ "repo": "clients",
10+ "rev": "ff143760d4f527f2349333ee4b3004eeda68fac5",
11+ "type": "github"
12+ },
13+ "original": {
14+ "owner": "bitwarden",
15+ "repo": "clients",
16+ "rev": "ff143760d4f527f2349333ee4b3004eeda68fac5",
17+ "type": "github"
18+ }
19+ },
20+ "easy-ps": {
21+ "flake": false,
22+ "locked": {
23+ "lastModified": 1671011575,
24+ "narHash": "sha256-tESal32bcqqdZO+aKnBzc1GoL2mtnaDtj2y7ociCRGA=",
25+ "owner": "justinwoo",
26+ "repo": "easy-purescript-nix",
27+ "rev": "11d3bd58ce6e32703bf69cec04dc7c38eabe14ba",
28+ "type": "github"
29+ },
30+ "original": {
31+ "owner": "justinwoo",
32+ "repo": "easy-purescript-nix",
33+ "type": "github"
34+ }
35+ },
36+ "easy-purescript-nix": {
37+ "flake": false,
38+ "locked": {
39+ "lastModified": 1670198178,
40+ "narHash": "sha256-5KkyNpPakv4xIP2ba0S5GX+dcmd3AcO9kPhwa482BbA=",
41+ "owner": "justinwoo",
42+ "repo": "easy-purescript-nix",
43+ "rev": "7a4cb3cd6ca53566ea1675692eab0aa13907ff09",
44+ "type": "github"
45+ },
46+ "original": {
47+ "owner": "justinwoo",
48+ "repo": "easy-purescript-nix",
49+ "type": "github"
50+ }
51+ },
52+ "flake-utils": {
53+ "locked": {
54+ "lastModified": 1676283394,
55+ "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
56+ "owner": "numtide",
57+ "repo": "flake-utils",
58+ "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
59+ "type": "github"
60+ },
61+ "original": {
62+ "owner": "numtide",
63+ "repo": "flake-utils",
64+ "type": "github"
65+ }
66+ },
67+ "flake-utils_2": {
68+ "locked": {
69+ "lastModified": 1667395993,
70+ "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
71+ "owner": "numtide",
72+ "repo": "flake-utils",
73+ "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
74+ "type": "github"
75+ },
76+ "original": {
77+ "owner": "numtide",
78+ "repo": "flake-utils",
79+ "type": "github"
80+ }
81+ },
82+ "nix-filter": {
83+ "locked": {
84+ "lastModified": 1676294984,
85+ "narHash": "sha256-hdLUa/3RH1VJ+gMUysQE0JGM4F2Q/tIIFbtoxAOurJQ=",
86+ "owner": "numtide",
87+ "repo": "nix-filter",
88+ "rev": "fc282c5478e4141842f9644c239a41cfe9586732",
89+ "type": "github"
90+ },
91+ "original": {
92+ "owner": "numtide",
93+ "repo": "nix-filter",
94+ "type": "github"
95+ }
96+ },
97+ "nixpkgs": {
98+ "locked": {
99+ "lastModified": 1677080879,
100+ "narHash": "sha256-0SjW4/d3Rkw6C7hHZ5lxT4r6Pw9vzQb6Il6zYWwe2Bo=",
101+ "owner": "NixOS",
102+ "repo": "nixpkgs",
103+ "rev": "f5dad40450d272a1ea2413f4a67ac08760649e89",
104+ "type": "github"
105+ },
106+ "original": {
107+ "id": "nixpkgs",
108+ "type": "indirect"
109+ }
110+ },
111+ "nixpkgs_2": {
112+ "locked": {
113+ "lastModified": 1670507980,
114+ "narHash": "sha256-riNZa0xzM1it3pzxciwALeMs+0CsBMWIW2FqulzK8vM=",
115+ "owner": "NixOS",
116+ "repo": "nixpkgs",
117+ "rev": "2787fc7d1e51404678614bf0fe92fc296746eec0",
118+ "type": "github"
119+ },
120+ "original": {
121+ "owner": "NixOS",
122+ "ref": "nixos-unstable",
123+ "repo": "nixpkgs",
124+ "type": "github"
125+ }
126+ },
127+ "root": {
128+ "inputs": {
129+ "bw": "bw",
130+ "easy-ps": "easy-ps",
131+ "flake-utils": "flake-utils",
132+ "nix-filter": "nix-filter",
133+ "nixpkgs": "nixpkgs",
134+ "spago2nix": "spago2nix",
135+ "vanilla": "vanilla"
136+ }
137+ },
138+ "spago2nix": {
139+ "inputs": {
140+ "easy-purescript-nix": "easy-purescript-nix",
141+ "flake-utils": "flake-utils_2",
142+ "nixpkgs": "nixpkgs_2"
143+ },
144+ "locked": {
145+ "lastModified": 1671225584,
146+ "narHash": "sha256-h9r67pmvDuA3TV9299L4CN60XSm8RRtX1EwUoKu9Pyw=",
147+ "owner": "justinwoo",
148+ "repo": "spago2nix",
149+ "rev": "1b8ec352bc7eac077b934d6b9f6efa0129926e59",
150+ "type": "github"
151+ },
152+ "original": {
153+ "owner": "justinwoo",
154+ "repo": "spago2nix",
155+ "type": "github"
156+ }
157+ },
158+ "vanilla": {
159+ "flake": false,
160+ "locked": {
161+ "lastModified": 1663430347,
162+ "narHash": "sha256-J+KyCcCMdC8SYINKAtUsNfM0/lnbwOnsY8TKOP+yY0s=",
163+ "owner": "ilyakooo0",
164+ "repo": "vanilla-compiled",
165+ "rev": "7a0ee46ce9b958b51c57b37c46e86a0a1868bfa9",
166+ "type": "github"
167+ },
168+ "original": {
169+ "owner": "ilyakooo0",
170+ "repo": "vanilla-compiled",
171+ "type": "github"
172+ }
173+ }
174+ },
175+ "root": "root",
176+ "version": 7
177+}
flake.nixnew file+133
@@ -0,0 +1,133 @@
1+{
2+ inputs = {
3+ flake-utils.url = "github:numtide/flake-utils";
4+ bw = {
5+ url =
6+ # Afer this commit they started requiring path at the top level and I can't deal with this right now
7+ "github:bitwarden/clients?rev=ff143760d4f527f2349333ee4b3004eeda68fac5";
8+ flake = false;
9+ };
10+ spago2nix.url = "github:justinwoo/spago2nix";
11+ easy-ps = {
12+ url = "github:justinwoo/easy-purescript-nix";
13+ flake = false;
14+ };
15+ vanilla = {
16+ url = "github:ilyakooo0/vanilla-compiled";
17+ flake = false;
18+ };
19+ nix-filter.url = "github:numtide/nix-filter";
20+ };
21+
22+ outputs = inputs@{ self, nixpkgs, flake-utils, spago2nix, ... }:
23+ flake-utils.lib.eachDefaultSystem (system:
24+ let
25+ pkgs = import nixpkgs { inherit system; };
26+ spagoPkgs = pkgs.callPackage ./spago-packages.nix { };
27+ easy-ps = pkgs.callPackage inputs.easy-ps { };
28+ nix-filter = import inputs.nix-filter;
29+
30+ bw = pkgs.buildNpmPackage {
31+ pname = "bw";
32+ version = "latest";
33+ npmDepsHash = "sha256-jQvL7/SzXqVk03ncU6Mxq8lqFFy+lIrF2QheheXrQ0w=";
34+ makeCacheWritable = true;
35+ src = inputs.bw;
36+ nativeBuildInputs = [ pkgs.python3 ];
37+ buildPhase = ''
38+ (cd libs/common/ && rm -r spec && find . -name "*.spec.ts" -exec rm {} \; && npm exec -c "tsc -m es2020 -t es2018") '';
39+ installPhase = ''
40+ mkdir -p $out
41+ cp -r libs/shared/dist/* $out
42+ cp -r node_modules $out/node_modules
43+ cp package.json $out/package.json
44+ '';
45+ ELECTRON_SKIP_BINARY_DOWNLOAD = 1;
46+ };
47+
48+ backendBuildScript = pkgs.writeScript "build-backend.sh" ''
49+ workdir=$(mktemp -d)
50+ (
51+ cd $workdir
52+ cp -r $1 ./backend
53+ cp -r ${bw} ./bw
54+ cp ${./backend-main.js} backend.js
55+ ${pkgs.esbuild}/bin/esbuild backend.js --bundle --define:ENVIRONMENT_IS_WEB=true --define:ENVIRONMENT_IS_WORKER=false --define:ENVIRONMENT_IS_NODE=false --define:ENVIRONMENT_IS_SHELL=false --external:fs --external:path --loader:.wasm=base64 --outfile=$out
56+ )
57+
58+ '';
59+ backend-js = pkgs.stdenv.mkDerivation {
60+ name = "backend-js";
61+ buildInputs =
62+ [ spagoPkgs.installSpagoStyle spagoPkgs.buildSpagoStyle ];
63+ nativeBuildInputs = [ easy-ps.purs-0_15_7 easy-ps.spago ];
64+ src = nix-filter {
65+ root = ./.;
66+ include = [
67+ ./spago.dhall
68+ ./packages.dhall
69+ (nix-filter.inDirectory ./backend)
70+ ];
71+ };
72+ unpackPhase = ''
73+ cp $src/spago.dhall .
74+ cp $src/packages.dhall .
75+ cp -r $src/backend .
76+ install-spago-style
77+ '';
78+ buildPhase = ''
79+ build-spago-style "./backend/**/*.purs"
80+ '';
81+ installPhase = ''
82+ mkdir $out
83+ cp -r output/* $out/
84+ '';
85+ };
86+
87+ backend =
88+ pkgs.runCommand "backend.js" { buildInputs = [ pkgs.esbuild ]; } ''
89+ ${backendBuildScript} ${backend-js}
90+ '';
91+
92+ frontend = pkgs.callPackage ./frontend.nix { inherit nix-filter; };
93+
94+ warden-src = pkgs.runCommand "warden-src" { } ''
95+ mkdir -p $out
96+
97+ cp ${backend} $out/backend.js
98+ cp -r ${frontend}/Main.min.js $out/elm.js
99+ cp ${./index.html} $out/index.html
100+ cp ${./style.css} $out/style.css
101+ cp -r ${inputs.vanilla} $out/vanilla
102+ '';
103+
104+ warden-click-src = pkgs.runCommand "warden-click-src" { } ''
105+ mkdir -p $out
106+
107+ cp ${./warden.apparmor} $out/warden.apparmor
108+ cp ${./warden.desktop} $out/warden.desktop
109+ cp ${./manifest.json} $out/manifest.json
110+ cp ${./logo.png} $out/logo.png
111+ cp -r ${warden-src}/* $out/
112+ '';
113+
114+ warden = pkgs.writeScript "warden" ''
115+ ${pkgs.electron}/bin/electron ${warden-src}/index.html
116+ '';
117+
118+ in {
119+ packages = {
120+ inherit bw frontend warden-src backendBuildScript warden-click-src;
121+ vanilla = inputs.vanilla.outPath;
122+ };
123+ apps = {
124+ inherit (spago2nix.apps.${system}) spago2nix;
125+ warden = {
126+ type = "app";
127+ program = warden.outPath;
128+ };
129+ };
130+
131+ devShells.default = import ./shell.nix { inherit pkgs; };
132+ });
133+}
frontend.nixnew file+49
@@ -0,0 +1,49 @@
1+{ pkgs, nix-filter }:
2+with pkgs;
3+
4+let
5+ mkDerivation = { srcs ? ./elm-srcs.nix, src, name, srcdir ? "./frontend"
6+ , targets ? [ ], registryDat ? ./registry.dat, outputJavaScript ? true }:
7+ stdenv.mkDerivation {
8+ inherit name src;
9+
10+ buildInputs = [ elmPackages.elm ]
11+ ++ lib.optional outputJavaScript nodePackages.uglify-js;
12+
13+ buildPhase = pkgs.elmPackages.fetchElmDeps {
14+ elmPackages = import srcs;
15+ elmVersion = "0.19.1";
16+ inherit registryDat;
17+ };
18+
19+ installPhase = let
20+ elmfile = module:
21+ "${srcdir}/${builtins.replaceStrings [ "." ] [ "/" ] module}.elm";
22+ extension = if outputJavaScript then "js" else "html";
23+ in ''
24+ mkdir -p $out/share/doc
25+ ${lib.concatStrings (map (module: ''
26+ echo "compiling ${elmfile module}"
27+ elm make ${
28+ elmfile module
29+ } --output $out/${module}.${extension} --docs $out/share/doc/${module}.json
30+ ${lib.optionalString outputJavaScript ''
31+ echo "minifying ${elmfile module}"
32+ uglifyjs $out/${module}.${extension} --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \
33+ | uglifyjs --mangle --output $out/${module}.min.${extension}
34+ ''}
35+ '') targets)}
36+ '';
37+ };
38+in mkDerivation {
39+ name = "frontend";
40+ srcs = ./elm-srcs.nix;
41+ src = nix-filter {
42+ root = ./.;
43+ include = [ (nix-filter.inDirectory ./frontend) ./elm.json ];
44+ };
45+ targets = [ "Main" ];
46+ srcdir = "./frontend";
47+ outputJavaScript = true;
48+}
49+
frontend/Bridge.elm+621−345
@@ -8,557 +8,743 @@ import Dict exposing (Dict)
8import Set exposing (Set)
8import Set exposing (Set)
9
9
10
10
11−type alias Cmd_Login =
11+type alias Cipher_CardCipher_brand_Maybe = (Maybe String)
12− { email: String
13− , password: String
14− , server: String
15− }
16−
17−jsonDecCmd_Login : Json.Decode.Decoder ( Cmd_Login )
18−jsonDecCmd_Login =
19− Json.Decode.succeed (\pemail ppassword pserver -> {email = pemail, password = ppassword, server = pserver})
20− |> required "email" (Json.Decode.string)
21− |> required "password" (Json.Decode.string)
22− |> required "server" (Json.Decode.string)
23−
24−jsonEncCmd_Login : Cmd_Login -> Value
25−jsonEncCmd_Login val =
26− Json.Encode.object
27− [ ("email", Json.Encode.string val.email)
28− , ("password", Json.Encode.string val.password)
29− , ("server", Json.Encode.string val.server)
30− ]
31−
32−
33−
34−type Cmd =
35− Init
36− | Login Cmd_Login
37− | NeedCiphersList
38− | NeedsReset
39− | RequestCipher String
40− | SendMasterPassword String
41−
42−jsonDecCmd : Json.Decode.Decoder ( Cmd )
43−jsonDecCmd =
44− let jsonDecDictCmd = Dict.fromList
45− [ ("Init", Json.Decode.lazy (\_ -> Json.Decode.succeed Init))
46− , ("Login", Json.Decode.lazy (\_ -> Json.Decode.map Login (jsonDecCmd_Login)))
47− , ("NeedCiphersList", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedCiphersList))
48− , ("NeedsReset", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedsReset))
49− , ("RequestCipher", Json.Decode.lazy (\_ -> Json.Decode.map RequestCipher (Json.Decode.string)))
50− , ("SendMasterPassword", Json.Decode.lazy (\_ -> Json.Decode.map SendMasterPassword (Json.Decode.string)))
51− ]
52− jsonDecObjectSetCmd = Set.fromList []
53− in decodeSumTaggedObject "Cmd" "tag" "contents" jsonDecDictCmd jsonDecObjectSetCmd
54−
55−jsonEncCmd : Cmd -> Value
56−jsonEncCmd val =
57− let keyval v = case v of
58− Init -> ("Init", encodeValue (Json.Encode.list identity []))
59− Login v1 -> ("Login", encodeValue (jsonEncCmd_Login v1))
60− NeedCiphersList -> ("NeedCiphersList", encodeValue (Json.Encode.list identity []))
61− NeedsReset -> ("NeedsReset", encodeValue (Json.Encode.list identity []))
62− RequestCipher v1 -> ("RequestCipher", encodeValue (Json.Encode.string v1))
63− SendMasterPassword v1 -> ("SendMasterPassword", encodeValue (Json.Encode.string v1))
64− in encodeSumTaggedObject "tag" "contents" keyval val
65−
66−
67
12
68−type alias Sub_LoadCipher_cipherType_CardCipher_brand_Maybe = (Maybe String)
13+jsonDecCipher_CardCipher_brand_Maybe : Json.Decode.Decoder ( Cipher_CardCipher_brand_Maybe )
69−
14+jsonDecCipher_CardCipher_brand_Maybe =
70−jsonDecSub_LoadCipher_cipherType_CardCipher_brand_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_CardCipher_brand_Maybe )
71−jsonDecSub_LoadCipher_cipherType_CardCipher_brand_Maybe =
72 Json.Decode.maybe (Json.Decode.string)
15 Json.Decode.maybe (Json.Decode.string)
73
16
74−jsonEncSub_LoadCipher_cipherType_CardCipher_brand_Maybe : Sub_LoadCipher_cipherType_CardCipher_brand_Maybe -> Value
17+jsonEncCipher_CardCipher_brand_Maybe : Cipher_CardCipher_brand_Maybe -> Value
75−jsonEncSub_LoadCipher_cipherType_CardCipher_brand_Maybe val = (maybeEncode (Json.Encode.string)) val
18+jsonEncCipher_CardCipher_brand_Maybe val = (maybeEncode (Json.Encode.string)) val
76
19
77
20
78
21
79−type alias Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe = (Maybe String)
22+type alias Cipher_CardCipher_cardholderName_Maybe = (Maybe String)
80
23
81−jsonDecSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe )
24+jsonDecCipher_CardCipher_cardholderName_Maybe : Json.Decode.Decoder ( Cipher_CardCipher_cardholderName_Maybe )
82−jsonDecSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe =
25+jsonDecCipher_CardCipher_cardholderName_Maybe =
83 Json.Decode.maybe (Json.Decode.string)
26 Json.Decode.maybe (Json.Decode.string)
84
27
85−jsonEncSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe : Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe -> Value
28+jsonEncCipher_CardCipher_cardholderName_Maybe : Cipher_CardCipher_cardholderName_Maybe -> Value
86−jsonEncSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe val = (maybeEncode (Json.Encode.string)) val
29+jsonEncCipher_CardCipher_cardholderName_Maybe val = (maybeEncode (Json.Encode.string)) val
87
30
88
31
89
32
90−type alias Sub_LoadCipher_cipherType_CardCipher_code_Maybe = (Maybe String)
33+type alias Cipher_CardCipher_code_Maybe = (Maybe String)
91
34
92−jsonDecSub_LoadCipher_cipherType_CardCipher_code_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_CardCipher_code_Maybe )
35+jsonDecCipher_CardCipher_code_Maybe : Json.Decode.Decoder ( Cipher_CardCipher_code_Maybe )
93−jsonDecSub_LoadCipher_cipherType_CardCipher_code_Maybe =
36+jsonDecCipher_CardCipher_code_Maybe =
94 Json.Decode.maybe (Json.Decode.string)
37 Json.Decode.maybe (Json.Decode.string)
95
38
96−jsonEncSub_LoadCipher_cipherType_CardCipher_code_Maybe : Sub_LoadCipher_cipherType_CardCipher_code_Maybe -> Value
39+jsonEncCipher_CardCipher_code_Maybe : Cipher_CardCipher_code_Maybe -> Value
97−jsonEncSub_LoadCipher_cipherType_CardCipher_code_Maybe val = (maybeEncode (Json.Encode.string)) val
40+jsonEncCipher_CardCipher_code_Maybe val = (maybeEncode (Json.Encode.string)) val
98
41
99
42
100
43
101−type alias Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe = (Maybe String)
44+type alias Cipher_CardCipher_expMonth_Maybe = (Maybe String)
102
45
103−jsonDecSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe )
46+jsonDecCipher_CardCipher_expMonth_Maybe : Json.Decode.Decoder ( Cipher_CardCipher_expMonth_Maybe )
104−jsonDecSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe =
47+jsonDecCipher_CardCipher_expMonth_Maybe =
105 Json.Decode.maybe (Json.Decode.string)
48 Json.Decode.maybe (Json.Decode.string)
106
49
107−jsonEncSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe : Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe -> Value
50+jsonEncCipher_CardCipher_expMonth_Maybe : Cipher_CardCipher_expMonth_Maybe -> Value
108−jsonEncSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe val = (maybeEncode (Json.Encode.string)) val
51+jsonEncCipher_CardCipher_expMonth_Maybe val = (maybeEncode (Json.Encode.string)) val
109
52
110
53
111
54
112−type alias Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe = (Maybe String)
55+type alias Cipher_CardCipher_expYear_Maybe = (Maybe String)
113
56
114−jsonDecSub_LoadCipher_cipherType_CardCipher_expYear_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe )
57+jsonDecCipher_CardCipher_expYear_Maybe : Json.Decode.Decoder ( Cipher_CardCipher_expYear_Maybe )
115−jsonDecSub_LoadCipher_cipherType_CardCipher_expYear_Maybe =
58+jsonDecCipher_CardCipher_expYear_Maybe =
116 Json.Decode.maybe (Json.Decode.string)
59 Json.Decode.maybe (Json.Decode.string)
117
60
118−jsonEncSub_LoadCipher_cipherType_CardCipher_expYear_Maybe : Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe -> Value
61+jsonEncCipher_CardCipher_expYear_Maybe : Cipher_CardCipher_expYear_Maybe -> Value
119−jsonEncSub_LoadCipher_cipherType_CardCipher_expYear_Maybe val = (maybeEncode (Json.Encode.string)) val
62+jsonEncCipher_CardCipher_expYear_Maybe val = (maybeEncode (Json.Encode.string)) val
120
63
121
64
122
65
123−type alias Sub_LoadCipher_cipherType_CardCipher_number_Maybe = (Maybe String)
66+type alias Cipher_CardCipher_number_Maybe = (Maybe String)
124
67
125−jsonDecSub_LoadCipher_cipherType_CardCipher_number_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_CardCipher_number_Maybe )
68+jsonDecCipher_CardCipher_number_Maybe : Json.Decode.Decoder ( Cipher_CardCipher_number_Maybe )
126−jsonDecSub_LoadCipher_cipherType_CardCipher_number_Maybe =
69+jsonDecCipher_CardCipher_number_Maybe =
127 Json.Decode.maybe (Json.Decode.string)
70 Json.Decode.maybe (Json.Decode.string)
128
71
129−jsonEncSub_LoadCipher_cipherType_CardCipher_number_Maybe : Sub_LoadCipher_cipherType_CardCipher_number_Maybe -> Value
72+jsonEncCipher_CardCipher_number_Maybe : Cipher_CardCipher_number_Maybe -> Value
130−jsonEncSub_LoadCipher_cipherType_CardCipher_number_Maybe val = (maybeEncode (Json.Encode.string)) val
73+jsonEncCipher_CardCipher_number_Maybe val = (maybeEncode (Json.Encode.string)) val
131
74
132
75
133
76
134−type alias Sub_LoadCipher_cipherType_CardCipher =
77+type alias Cipher_CardCipher =
135− { brand: Sub_LoadCipher_cipherType_CardCipher_brand_Maybe
78+ { brand: Cipher_CardCipher_brand_Maybe
136− , cardholderName: Sub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe
79+ , cardholderName: Cipher_CardCipher_cardholderName_Maybe
137− , code: Sub_LoadCipher_cipherType_CardCipher_code_Maybe
80+ , code: Cipher_CardCipher_code_Maybe
138− , expMonth: Sub_LoadCipher_cipherType_CardCipher_expMonth_Maybe
81+ , expMonth: Cipher_CardCipher_expMonth_Maybe
139− , expYear: Sub_LoadCipher_cipherType_CardCipher_expYear_Maybe
82+ , expYear: Cipher_CardCipher_expYear_Maybe
140− , number: Sub_LoadCipher_cipherType_CardCipher_number_Maybe
83+ , number: Cipher_CardCipher_number_Maybe
141 }
84 }
142
85
143−jsonDecSub_LoadCipher_cipherType_CardCipher : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_CardCipher )
86+jsonDecCipher_CardCipher : Json.Decode.Decoder ( Cipher_CardCipher )
144−jsonDecSub_LoadCipher_cipherType_CardCipher =
87+jsonDecCipher_CardCipher =
145 Json.Decode.succeed (\pbrand pcardholderName pcode pexpMonth pexpYear pnumber -> {brand = pbrand, cardholderName = pcardholderName, code = pcode, expMonth = pexpMonth, expYear = pexpYear, number = pnumber})
88 Json.Decode.succeed (\pbrand pcardholderName pcode pexpMonth pexpYear pnumber -> {brand = pbrand, cardholderName = pcardholderName, code = pcode, expMonth = pexpMonth, expYear = pexpYear, number = pnumber})
146− |> required "brand" (jsonDecSub_LoadCipher_cipherType_CardCipher_brand_Maybe)
89+ |> required "brand" (jsonDecCipher_CardCipher_brand_Maybe)
147− |> required "cardholderName" (jsonDecSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe)
90+ |> required "cardholderName" (jsonDecCipher_CardCipher_cardholderName_Maybe)
148− |> required "code" (jsonDecSub_LoadCipher_cipherType_CardCipher_code_Maybe)
91+ |> required "code" (jsonDecCipher_CardCipher_code_Maybe)
149− |> required "expMonth" (jsonDecSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe)
92+ |> required "expMonth" (jsonDecCipher_CardCipher_expMonth_Maybe)
150− |> required "expYear" (jsonDecSub_LoadCipher_cipherType_CardCipher_expYear_Maybe)
93+ |> required "expYear" (jsonDecCipher_CardCipher_expYear_Maybe)
151− |> required "number" (jsonDecSub_LoadCipher_cipherType_CardCipher_number_Maybe)
94+ |> required "number" (jsonDecCipher_CardCipher_number_Maybe)
152−
95+
153−jsonEncSub_LoadCipher_cipherType_CardCipher : Sub_LoadCipher_cipherType_CardCipher -> Value
96+jsonEncCipher_CardCipher : Cipher_CardCipher -> Value
154−jsonEncSub_LoadCipher_cipherType_CardCipher val =
97+jsonEncCipher_CardCipher val =
155 Json.Encode.object
98 Json.Encode.object
156− [ ("brand", jsonEncSub_LoadCipher_cipherType_CardCipher_brand_Maybe val.brand)
99+ [ ("brand", jsonEncCipher_CardCipher_brand_Maybe val.brand)
157− , ("cardholderName", jsonEncSub_LoadCipher_cipherType_CardCipher_cardholderName_Maybe val.cardholderName)
100+ , ("cardholderName", jsonEncCipher_CardCipher_cardholderName_Maybe val.cardholderName)
158− , ("code", jsonEncSub_LoadCipher_cipherType_CardCipher_code_Maybe val.code)
101+ , ("code", jsonEncCipher_CardCipher_code_Maybe val.code)
159− , ("expMonth", jsonEncSub_LoadCipher_cipherType_CardCipher_expMonth_Maybe val.expMonth)
102+ , ("expMonth", jsonEncCipher_CardCipher_expMonth_Maybe val.expMonth)
160− , ("expYear", jsonEncSub_LoadCipher_cipherType_CardCipher_expYear_Maybe val.expYear)
103+ , ("expYear", jsonEncCipher_CardCipher_expYear_Maybe val.expYear)
161− , ("number", jsonEncSub_LoadCipher_cipherType_CardCipher_number_Maybe val.number)
104+ , ("number", jsonEncCipher_CardCipher_number_Maybe val.number)
162 ]
105 ]
163
106
164
107
165
108
166−type alias Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe = (Maybe String)
109+type alias Cipher_IdentityCipher_address1_Maybe = (Maybe String)
167
110
168−jsonDecSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe )
111+jsonDecCipher_IdentityCipher_address1_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_address1_Maybe )
169−jsonDecSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe =
112+jsonDecCipher_IdentityCipher_address1_Maybe =
170 Json.Decode.maybe (Json.Decode.string)
113 Json.Decode.maybe (Json.Decode.string)
171
114
172−jsonEncSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe -> Value
115+jsonEncCipher_IdentityCipher_address1_Maybe : Cipher_IdentityCipher_address1_Maybe -> Value
173−jsonEncSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe val = (maybeEncode (Json.Encode.string)) val
116+jsonEncCipher_IdentityCipher_address1_Maybe val = (maybeEncode (Json.Encode.string)) val
174
117
175
118
176
119
177−type alias Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe = (Maybe String)
120+type alias Cipher_IdentityCipher_address2_Maybe = (Maybe String)
178
121
179−jsonDecSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe )
122+jsonDecCipher_IdentityCipher_address2_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_address2_Maybe )
180−jsonDecSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe =
123+jsonDecCipher_IdentityCipher_address2_Maybe =
181 Json.Decode.maybe (Json.Decode.string)
124 Json.Decode.maybe (Json.Decode.string)
182
125
183−jsonEncSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe -> Value
126+jsonEncCipher_IdentityCipher_address2_Maybe : Cipher_IdentityCipher_address2_Maybe -> Value
184−jsonEncSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe val = (maybeEncode (Json.Encode.string)) val
127+jsonEncCipher_IdentityCipher_address2_Maybe val = (maybeEncode (Json.Encode.string)) val
185
128
186
129
187
130
188−type alias Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe = (Maybe String)
131+type alias Cipher_IdentityCipher_address3_Maybe = (Maybe String)
189
132
190−jsonDecSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe )
133+jsonDecCipher_IdentityCipher_address3_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_address3_Maybe )
191−jsonDecSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe =
134+jsonDecCipher_IdentityCipher_address3_Maybe =
192 Json.Decode.maybe (Json.Decode.string)
135 Json.Decode.maybe (Json.Decode.string)
193
136
194−jsonEncSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe -> Value
137+jsonEncCipher_IdentityCipher_address3_Maybe : Cipher_IdentityCipher_address3_Maybe -> Value
195−jsonEncSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe val = (maybeEncode (Json.Encode.string)) val
138+jsonEncCipher_IdentityCipher_address3_Maybe val = (maybeEncode (Json.Encode.string)) val
196
139
197
140
198
141
199−type alias Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe = (Maybe String)
142+type alias Cipher_IdentityCipher_city_Maybe = (Maybe String)
200
143
201−jsonDecSub_LoadCipher_cipherType_IdentityCipher_city_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe )
144+jsonDecCipher_IdentityCipher_city_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_city_Maybe )
202−jsonDecSub_LoadCipher_cipherType_IdentityCipher_city_Maybe =
145+jsonDecCipher_IdentityCipher_city_Maybe =
203 Json.Decode.maybe (Json.Decode.string)
146 Json.Decode.maybe (Json.Decode.string)
204
147
205−jsonEncSub_LoadCipher_cipherType_IdentityCipher_city_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe -> Value
148+jsonEncCipher_IdentityCipher_city_Maybe : Cipher_IdentityCipher_city_Maybe -> Value
206−jsonEncSub_LoadCipher_cipherType_IdentityCipher_city_Maybe val = (maybeEncode (Json.Encode.string)) val
149+jsonEncCipher_IdentityCipher_city_Maybe val = (maybeEncode (Json.Encode.string)) val
207
150
208
151
209
152
210−type alias Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe = (Maybe String)
153+type alias Cipher_IdentityCipher_company_Maybe = (Maybe String)
211
154
212−jsonDecSub_LoadCipher_cipherType_IdentityCipher_company_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe )
155+jsonDecCipher_IdentityCipher_company_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_company_Maybe )
213−jsonDecSub_LoadCipher_cipherType_IdentityCipher_company_Maybe =
156+jsonDecCipher_IdentityCipher_company_Maybe =
214 Json.Decode.maybe (Json.Decode.string)
157 Json.Decode.maybe (Json.Decode.string)
215
158
216−jsonEncSub_LoadCipher_cipherType_IdentityCipher_company_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe -> Value
159+jsonEncCipher_IdentityCipher_company_Maybe : Cipher_IdentityCipher_company_Maybe -> Value
217−jsonEncSub_LoadCipher_cipherType_IdentityCipher_company_Maybe val = (maybeEncode (Json.Encode.string)) val
160+jsonEncCipher_IdentityCipher_company_Maybe val = (maybeEncode (Json.Encode.string)) val
218
161
219
162
220
163
221−type alias Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe = (Maybe String)
164+type alias Cipher_IdentityCipher_country_Maybe = (Maybe String)
222
165
223−jsonDecSub_LoadCipher_cipherType_IdentityCipher_country_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe )
166+jsonDecCipher_IdentityCipher_country_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_country_Maybe )
224−jsonDecSub_LoadCipher_cipherType_IdentityCipher_country_Maybe =
167+jsonDecCipher_IdentityCipher_country_Maybe =
225 Json.Decode.maybe (Json.Decode.string)
168 Json.Decode.maybe (Json.Decode.string)
226
169
227−jsonEncSub_LoadCipher_cipherType_IdentityCipher_country_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe -> Value
170+jsonEncCipher_IdentityCipher_country_Maybe : Cipher_IdentityCipher_country_Maybe -> Value
228−jsonEncSub_LoadCipher_cipherType_IdentityCipher_country_Maybe val = (maybeEncode (Json.Encode.string)) val
171+jsonEncCipher_IdentityCipher_country_Maybe val = (maybeEncode (Json.Encode.string)) val
229
172
230
173
231
174
232−type alias Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe = (Maybe String)
175+type alias Cipher_IdentityCipher_email_Maybe = (Maybe String)
233
176
234−jsonDecSub_LoadCipher_cipherType_IdentityCipher_email_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe )
177+jsonDecCipher_IdentityCipher_email_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_email_Maybe )
235−jsonDecSub_LoadCipher_cipherType_IdentityCipher_email_Maybe =
178+jsonDecCipher_IdentityCipher_email_Maybe =
236 Json.Decode.maybe (Json.Decode.string)
179 Json.Decode.maybe (Json.Decode.string)
237
180
238−jsonEncSub_LoadCipher_cipherType_IdentityCipher_email_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe -> Value
181+jsonEncCipher_IdentityCipher_email_Maybe : Cipher_IdentityCipher_email_Maybe -> Value
239−jsonEncSub_LoadCipher_cipherType_IdentityCipher_email_Maybe val = (maybeEncode (Json.Encode.string)) val
182+jsonEncCipher_IdentityCipher_email_Maybe val = (maybeEncode (Json.Encode.string)) val
240
183
241
184
242
185
243−type alias Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe = (Maybe String)
186+type alias Cipher_IdentityCipher_firstName_Maybe = (Maybe String)
244
187
245−jsonDecSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe )
188+jsonDecCipher_IdentityCipher_firstName_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_firstName_Maybe )
246−jsonDecSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe =
189+jsonDecCipher_IdentityCipher_firstName_Maybe =
247 Json.Decode.maybe (Json.Decode.string)
190 Json.Decode.maybe (Json.Decode.string)
248
191
249−jsonEncSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe -> Value
192+jsonEncCipher_IdentityCipher_firstName_Maybe : Cipher_IdentityCipher_firstName_Maybe -> Value
250−jsonEncSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe val = (maybeEncode (Json.Encode.string)) val
193+jsonEncCipher_IdentityCipher_firstName_Maybe val = (maybeEncode (Json.Encode.string)) val
251
194
252
195
253
196
254−type alias Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe = (Maybe String)
197+type alias Cipher_IdentityCipher_lastName_Maybe = (Maybe String)
255
198
256−jsonDecSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe )
199+jsonDecCipher_IdentityCipher_lastName_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_lastName_Maybe )
257−jsonDecSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe =
200+jsonDecCipher_IdentityCipher_lastName_Maybe =
258 Json.Decode.maybe (Json.Decode.string)
201 Json.Decode.maybe (Json.Decode.string)
259
202
260−jsonEncSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe -> Value
203+jsonEncCipher_IdentityCipher_lastName_Maybe : Cipher_IdentityCipher_lastName_Maybe -> Value
261−jsonEncSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe val = (maybeEncode (Json.Encode.string)) val
204+jsonEncCipher_IdentityCipher_lastName_Maybe val = (maybeEncode (Json.Encode.string)) val
262
205
263
206
264
207
265−type alias Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe = (Maybe String)
208+type alias Cipher_IdentityCipher_licenseNumber_Maybe = (Maybe String)
266
209
267−jsonDecSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe )
210+jsonDecCipher_IdentityCipher_licenseNumber_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_licenseNumber_Maybe )
268−jsonDecSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe =
211+jsonDecCipher_IdentityCipher_licenseNumber_Maybe =
269 Json.Decode.maybe (Json.Decode.string)
212 Json.Decode.maybe (Json.Decode.string)
270
213
271−jsonEncSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe -> Value
214+jsonEncCipher_IdentityCipher_licenseNumber_Maybe : Cipher_IdentityCipher_licenseNumber_Maybe -> Value
272−jsonEncSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe val = (maybeEncode (Json.Encode.string)) val
215+jsonEncCipher_IdentityCipher_licenseNumber_Maybe val = (maybeEncode (Json.Encode.string)) val
273
216
274
217
275
218
276−type alias Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe = (Maybe String)
219+type alias Cipher_IdentityCipher_middleName_Maybe = (Maybe String)
277
220
278−jsonDecSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe )
221+jsonDecCipher_IdentityCipher_middleName_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_middleName_Maybe )
279−jsonDecSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe =
222+jsonDecCipher_IdentityCipher_middleName_Maybe =
280 Json.Decode.maybe (Json.Decode.string)
223 Json.Decode.maybe (Json.Decode.string)
281
224
282−jsonEncSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe -> Value
225+jsonEncCipher_IdentityCipher_middleName_Maybe : Cipher_IdentityCipher_middleName_Maybe -> Value
283−jsonEncSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe val = (maybeEncode (Json.Encode.string)) val
226+jsonEncCipher_IdentityCipher_middleName_Maybe val = (maybeEncode (Json.Encode.string)) val
284
227
285
228
286
229
287−type alias Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe = (Maybe String)
230+type alias Cipher_IdentityCipher_passportNumber_Maybe = (Maybe String)
288
231
289−jsonDecSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe )
232+jsonDecCipher_IdentityCipher_passportNumber_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_passportNumber_Maybe )
290−jsonDecSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe =
233+jsonDecCipher_IdentityCipher_passportNumber_Maybe =
291 Json.Decode.maybe (Json.Decode.string)
234 Json.Decode.maybe (Json.Decode.string)
292
235
293−jsonEncSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe -> Value
236+jsonEncCipher_IdentityCipher_passportNumber_Maybe : Cipher_IdentityCipher_passportNumber_Maybe -> Value
294−jsonEncSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe val = (maybeEncode (Json.Encode.string)) val
237+jsonEncCipher_IdentityCipher_passportNumber_Maybe val = (maybeEncode (Json.Encode.string)) val
295
238
296
239
297
240
298−type alias Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe = (Maybe String)
241+type alias Cipher_IdentityCipher_phone_Maybe = (Maybe String)
299
242
300−jsonDecSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe )
243+jsonDecCipher_IdentityCipher_phone_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_phone_Maybe )
301−jsonDecSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe =
244+jsonDecCipher_IdentityCipher_phone_Maybe =
302 Json.Decode.maybe (Json.Decode.string)
245 Json.Decode.maybe (Json.Decode.string)
303
246
304−jsonEncSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe -> Value
247+jsonEncCipher_IdentityCipher_phone_Maybe : Cipher_IdentityCipher_phone_Maybe -> Value
305−jsonEncSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe val = (maybeEncode (Json.Encode.string)) val
248+jsonEncCipher_IdentityCipher_phone_Maybe val = (maybeEncode (Json.Encode.string)) val
306
249
307
250
308
251
309−type alias Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe = (Maybe String)
252+type alias Cipher_IdentityCipher_postalCode_Maybe = (Maybe String)
310
253
311−jsonDecSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe )
254+jsonDecCipher_IdentityCipher_postalCode_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_postalCode_Maybe )
312−jsonDecSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe =
255+jsonDecCipher_IdentityCipher_postalCode_Maybe =
313 Json.Decode.maybe (Json.Decode.string)
256 Json.Decode.maybe (Json.Decode.string)
314
257
315−jsonEncSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe -> Value
258+jsonEncCipher_IdentityCipher_postalCode_Maybe : Cipher_IdentityCipher_postalCode_Maybe -> Value
316−jsonEncSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe val = (maybeEncode (Json.Encode.string)) val
259+jsonEncCipher_IdentityCipher_postalCode_Maybe val = (maybeEncode (Json.Encode.string)) val
317
260
318
261
319
262
320−type alias Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe = (Maybe String)
263+type alias Cipher_IdentityCipher_ssn_Maybe = (Maybe String)
321
264
322−jsonDecSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe )
265+jsonDecCipher_IdentityCipher_ssn_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_ssn_Maybe )
323−jsonDecSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe =
266+jsonDecCipher_IdentityCipher_ssn_Maybe =
324 Json.Decode.maybe (Json.Decode.string)
267 Json.Decode.maybe (Json.Decode.string)
325
268
326−jsonEncSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe -> Value
269+jsonEncCipher_IdentityCipher_ssn_Maybe : Cipher_IdentityCipher_ssn_Maybe -> Value
327−jsonEncSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe val = (maybeEncode (Json.Encode.string)) val
270+jsonEncCipher_IdentityCipher_ssn_Maybe val = (maybeEncode (Json.Encode.string)) val
328
271
329
272
330
273
331−type alias Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe = (Maybe String)
274+type alias Cipher_IdentityCipher_state_Maybe = (Maybe String)
332
275
333−jsonDecSub_LoadCipher_cipherType_IdentityCipher_state_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe )
276+jsonDecCipher_IdentityCipher_state_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_state_Maybe )
334−jsonDecSub_LoadCipher_cipherType_IdentityCipher_state_Maybe =
277+jsonDecCipher_IdentityCipher_state_Maybe =
335 Json.Decode.maybe (Json.Decode.string)
278 Json.Decode.maybe (Json.Decode.string)
336
279
337−jsonEncSub_LoadCipher_cipherType_IdentityCipher_state_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe -> Value
280+jsonEncCipher_IdentityCipher_state_Maybe : Cipher_IdentityCipher_state_Maybe -> Value
338−jsonEncSub_LoadCipher_cipherType_IdentityCipher_state_Maybe val = (maybeEncode (Json.Encode.string)) val
281+jsonEncCipher_IdentityCipher_state_Maybe val = (maybeEncode (Json.Encode.string)) val
339
282
340
283
341
284
342−type alias Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe = (Maybe String)
285+type alias Cipher_IdentityCipher_title_Maybe = (Maybe String)
343
286
344−jsonDecSub_LoadCipher_cipherType_IdentityCipher_title_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe )
287+jsonDecCipher_IdentityCipher_title_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_title_Maybe )
345−jsonDecSub_LoadCipher_cipherType_IdentityCipher_title_Maybe =
288+jsonDecCipher_IdentityCipher_title_Maybe =
346 Json.Decode.maybe (Json.Decode.string)
289 Json.Decode.maybe (Json.Decode.string)
347
290
348−jsonEncSub_LoadCipher_cipherType_IdentityCipher_title_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe -> Value
291+jsonEncCipher_IdentityCipher_title_Maybe : Cipher_IdentityCipher_title_Maybe -> Value
349−jsonEncSub_LoadCipher_cipherType_IdentityCipher_title_Maybe val = (maybeEncode (Json.Encode.string)) val
292+jsonEncCipher_IdentityCipher_title_Maybe val = (maybeEncode (Json.Encode.string)) val
350
293
351
294
352
295
353−type alias Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe = (Maybe String)
296+type alias Cipher_IdentityCipher_username_Maybe = (Maybe String)
354
297
355−jsonDecSub_LoadCipher_cipherType_IdentityCipher_username_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe )
298+jsonDecCipher_IdentityCipher_username_Maybe : Json.Decode.Decoder ( Cipher_IdentityCipher_username_Maybe )
356−jsonDecSub_LoadCipher_cipherType_IdentityCipher_username_Maybe =
299+jsonDecCipher_IdentityCipher_username_Maybe =
357 Json.Decode.maybe (Json.Decode.string)
300 Json.Decode.maybe (Json.Decode.string)
358
301
359−jsonEncSub_LoadCipher_cipherType_IdentityCipher_username_Maybe : Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe -> Value
302+jsonEncCipher_IdentityCipher_username_Maybe : Cipher_IdentityCipher_username_Maybe -> Value
360−jsonEncSub_LoadCipher_cipherType_IdentityCipher_username_Maybe val = (maybeEncode (Json.Encode.string)) val
303+jsonEncCipher_IdentityCipher_username_Maybe val = (maybeEncode (Json.Encode.string)) val
361−
304+
362−
305+
363−
306+
364−type alias Sub_LoadCipher_cipherType_IdentityCipher =
307+type alias Cipher_IdentityCipher =
365− { address1: Sub_LoadCipher_cipherType_IdentityCipher_address1_Maybe
308+ { address1: Cipher_IdentityCipher_address1_Maybe
366− , address2: Sub_LoadCipher_cipherType_IdentityCipher_address2_Maybe
309+ , address2: Cipher_IdentityCipher_address2_Maybe
367− , address3: Sub_LoadCipher_cipherType_IdentityCipher_address3_Maybe
310+ , address3: Cipher_IdentityCipher_address3_Maybe
368− , city: Sub_LoadCipher_cipherType_IdentityCipher_city_Maybe
311+ , city: Cipher_IdentityCipher_city_Maybe
369− , company: Sub_LoadCipher_cipherType_IdentityCipher_company_Maybe
312+ , company: Cipher_IdentityCipher_company_Maybe
370− , country: Sub_LoadCipher_cipherType_IdentityCipher_country_Maybe
313+ , country: Cipher_IdentityCipher_country_Maybe
371− , email: Sub_LoadCipher_cipherType_IdentityCipher_email_Maybe
314+ , email: Cipher_IdentityCipher_email_Maybe
372− , firstName: Sub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe
315+ , firstName: Cipher_IdentityCipher_firstName_Maybe
373− , lastName: Sub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe
316+ , lastName: Cipher_IdentityCipher_lastName_Maybe
374− , licenseNumber: Sub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe
317+ , licenseNumber: Cipher_IdentityCipher_licenseNumber_Maybe
375− , middleName: Sub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe
318+ , middleName: Cipher_IdentityCipher_middleName_Maybe
376− , passportNumber: Sub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe
319+ , passportNumber: Cipher_IdentityCipher_passportNumber_Maybe
377− , phone: Sub_LoadCipher_cipherType_IdentityCipher_phone_Maybe
320+ , phone: Cipher_IdentityCipher_phone_Maybe
378− , postalCode: Sub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe
321+ , postalCode: Cipher_IdentityCipher_postalCode_Maybe
379− , ssn: Sub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe
322+ , ssn: Cipher_IdentityCipher_ssn_Maybe
380− , state: Sub_LoadCipher_cipherType_IdentityCipher_state_Maybe
323+ , state: Cipher_IdentityCipher_state_Maybe
381− , title: Sub_LoadCipher_cipherType_IdentityCipher_title_Maybe
324+ , title: Cipher_IdentityCipher_title_Maybe
382− , username: Sub_LoadCipher_cipherType_IdentityCipher_username_Maybe
325+ , username: Cipher_IdentityCipher_username_Maybe
383 }
326 }
384
327
385−jsonDecSub_LoadCipher_cipherType_IdentityCipher : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_IdentityCipher )
328+jsonDecCipher_IdentityCipher : Json.Decode.Decoder ( Cipher_IdentityCipher )
386−jsonDecSub_LoadCipher_cipherType_IdentityCipher =
329+jsonDecCipher_IdentityCipher =
387 Json.Decode.succeed (\paddress1 paddress2 paddress3 pcity pcompany pcountry pemail pfirstName plastName plicenseNumber pmiddleName ppassportNumber pphone ppostalCode pssn pstate ptitle pusername -> {address1 = paddress1, address2 = paddress2, address3 = paddress3, city = pcity, company = pcompany, country = pcountry, email = pemail, firstName = pfirstName, lastName = plastName, licenseNumber = plicenseNumber, middleName = pmiddleName, passportNumber = ppassportNumber, phone = pphone, postalCode = ppostalCode, ssn = pssn, state = pstate, title = ptitle, username = pusername})
330 Json.Decode.succeed (\paddress1 paddress2 paddress3 pcity pcompany pcountry pemail pfirstName plastName plicenseNumber pmiddleName ppassportNumber pphone ppostalCode pssn pstate ptitle pusername -> {address1 = paddress1, address2 = paddress2, address3 = paddress3, city = pcity, company = pcompany, country = pcountry, email = pemail, firstName = pfirstName, lastName = plastName, licenseNumber = plicenseNumber, middleName = pmiddleName, passportNumber = ppassportNumber, phone = pphone, postalCode = ppostalCode, ssn = pssn, state = pstate, title = ptitle, username = pusername})
388− |> required "address1" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe)
331+ |> required "address1" (jsonDecCipher_IdentityCipher_address1_Maybe)
389− |> required "address2" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe)
332+ |> required "address2" (jsonDecCipher_IdentityCipher_address2_Maybe)
390− |> required "address3" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe)
333+ |> required "address3" (jsonDecCipher_IdentityCipher_address3_Maybe)
391− |> required "city" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_city_Maybe)
334+ |> required "city" (jsonDecCipher_IdentityCipher_city_Maybe)
392− |> required "company" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_company_Maybe)
335+ |> required "company" (jsonDecCipher_IdentityCipher_company_Maybe)
393− |> required "country" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_country_Maybe)
336+ |> required "country" (jsonDecCipher_IdentityCipher_country_Maybe)
394− |> required "email" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_email_Maybe)
337+ |> required "email" (jsonDecCipher_IdentityCipher_email_Maybe)
395− |> required "firstName" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe)
338+ |> required "firstName" (jsonDecCipher_IdentityCipher_firstName_Maybe)
396− |> required "lastName" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe)
339+ |> required "lastName" (jsonDecCipher_IdentityCipher_lastName_Maybe)
397− |> required "licenseNumber" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe)
340+ |> required "licenseNumber" (jsonDecCipher_IdentityCipher_licenseNumber_Maybe)
398− |> required "middleName" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe)
341+ |> required "middleName" (jsonDecCipher_IdentityCipher_middleName_Maybe)
399− |> required "passportNumber" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe)
342+ |> required "passportNumber" (jsonDecCipher_IdentityCipher_passportNumber_Maybe)
400− |> required "phone" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe)
343+ |> required "phone" (jsonDecCipher_IdentityCipher_phone_Maybe)
401− |> required "postalCode" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe)
344+ |> required "postalCode" (jsonDecCipher_IdentityCipher_postalCode_Maybe)
402− |> required "ssn" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe)
345+ |> required "ssn" (jsonDecCipher_IdentityCipher_ssn_Maybe)
403− |> required "state" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_state_Maybe)
346+ |> required "state" (jsonDecCipher_IdentityCipher_state_Maybe)
404− |> required "title" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_title_Maybe)
347+ |> required "title" (jsonDecCipher_IdentityCipher_title_Maybe)
405− |> required "username" (jsonDecSub_LoadCipher_cipherType_IdentityCipher_username_Maybe)
348+ |> required "username" (jsonDecCipher_IdentityCipher_username_Maybe)
406−
349+
407−jsonEncSub_LoadCipher_cipherType_IdentityCipher : Sub_LoadCipher_cipherType_IdentityCipher -> Value
350+jsonEncCipher_IdentityCipher : Cipher_IdentityCipher -> Value
408−jsonEncSub_LoadCipher_cipherType_IdentityCipher val =
351+jsonEncCipher_IdentityCipher val =
409 Json.Encode.object
352 Json.Encode.object
410− [ ("address1", jsonEncSub_LoadCipher_cipherType_IdentityCipher_address1_Maybe val.address1)
353+ [ ("address1", jsonEncCipher_IdentityCipher_address1_Maybe val.address1)
411− , ("address2", jsonEncSub_LoadCipher_cipherType_IdentityCipher_address2_Maybe val.address2)
354+ , ("address2", jsonEncCipher_IdentityCipher_address2_Maybe val.address2)
412− , ("address3", jsonEncSub_LoadCipher_cipherType_IdentityCipher_address3_Maybe val.address3)
355+ , ("address3", jsonEncCipher_IdentityCipher_address3_Maybe val.address3)
413− , ("city", jsonEncSub_LoadCipher_cipherType_IdentityCipher_city_Maybe val.city)
356+ , ("city", jsonEncCipher_IdentityCipher_city_Maybe val.city)
414− , ("company", jsonEncSub_LoadCipher_cipherType_IdentityCipher_company_Maybe val.company)
357+ , ("company", jsonEncCipher_IdentityCipher_company_Maybe val.company)
415− , ("country", jsonEncSub_LoadCipher_cipherType_IdentityCipher_country_Maybe val.country)
358+ , ("country", jsonEncCipher_IdentityCipher_country_Maybe val.country)
416− , ("email", jsonEncSub_LoadCipher_cipherType_IdentityCipher_email_Maybe val.email)
359+ , ("email", jsonEncCipher_IdentityCipher_email_Maybe val.email)
417− , ("firstName", jsonEncSub_LoadCipher_cipherType_IdentityCipher_firstName_Maybe val.firstName)
360+ , ("firstName", jsonEncCipher_IdentityCipher_firstName_Maybe val.firstName)
418− , ("lastName", jsonEncSub_LoadCipher_cipherType_IdentityCipher_lastName_Maybe val.lastName)
361+ , ("lastName", jsonEncCipher_IdentityCipher_lastName_Maybe val.lastName)
419− , ("licenseNumber", jsonEncSub_LoadCipher_cipherType_IdentityCipher_licenseNumber_Maybe val.licenseNumber)
362+ , ("licenseNumber", jsonEncCipher_IdentityCipher_licenseNumber_Maybe val.licenseNumber)
420− , ("middleName", jsonEncSub_LoadCipher_cipherType_IdentityCipher_middleName_Maybe val.middleName)
363+ , ("middleName", jsonEncCipher_IdentityCipher_middleName_Maybe val.middleName)
421− , ("passportNumber", jsonEncSub_LoadCipher_cipherType_IdentityCipher_passportNumber_Maybe val.passportNumber)
364+ , ("passportNumber", jsonEncCipher_IdentityCipher_passportNumber_Maybe val.passportNumber)
422− , ("phone", jsonEncSub_LoadCipher_cipherType_IdentityCipher_phone_Maybe val.phone)
365+ , ("phone", jsonEncCipher_IdentityCipher_phone_Maybe val.phone)
423− , ("postalCode", jsonEncSub_LoadCipher_cipherType_IdentityCipher_postalCode_Maybe val.postalCode)
366+ , ("postalCode", jsonEncCipher_IdentityCipher_postalCode_Maybe val.postalCode)
424− , ("ssn", jsonEncSub_LoadCipher_cipherType_IdentityCipher_ssn_Maybe val.ssn)
367+ , ("ssn", jsonEncCipher_IdentityCipher_ssn_Maybe val.ssn)
425− , ("state", jsonEncSub_LoadCipher_cipherType_IdentityCipher_state_Maybe val.state)
368+ , ("state", jsonEncCipher_IdentityCipher_state_Maybe val.state)
426− , ("title", jsonEncSub_LoadCipher_cipherType_IdentityCipher_title_Maybe val.title)
369+ , ("title", jsonEncCipher_IdentityCipher_title_Maybe val.title)
427− , ("username", jsonEncSub_LoadCipher_cipherType_IdentityCipher_username_Maybe val.username)
370+ , ("username", jsonEncCipher_IdentityCipher_username_Maybe val.username)
428 ]
371 ]
429
372
430
373
431
374
432−type alias Sub_LoadCipher_cipherType_LoginCipher_password_Maybe = (Maybe String)
375+type alias Cipher_LoginCipher_password_Maybe = (Maybe String)
433
376
434−jsonDecSub_LoadCipher_cipherType_LoginCipher_password_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_LoginCipher_password_Maybe )
377+jsonDecCipher_LoginCipher_password_Maybe : Json.Decode.Decoder ( Cipher_LoginCipher_password_Maybe )
435−jsonDecSub_LoadCipher_cipherType_LoginCipher_password_Maybe =
378+jsonDecCipher_LoginCipher_password_Maybe =
436 Json.Decode.maybe (Json.Decode.string)
379 Json.Decode.maybe (Json.Decode.string)
437
380
438−jsonEncSub_LoadCipher_cipherType_LoginCipher_password_Maybe : Sub_LoadCipher_cipherType_LoginCipher_password_Maybe -> Value
381+jsonEncCipher_LoginCipher_password_Maybe : Cipher_LoginCipher_password_Maybe -> Value
439−jsonEncSub_LoadCipher_cipherType_LoginCipher_password_Maybe val = (maybeEncode (Json.Encode.string)) val
382+jsonEncCipher_LoginCipher_password_Maybe val = (maybeEncode (Json.Encode.string)) val
383+
440
384
441
385
386+type alias Cipher_LoginCipher_totp_Maybe = (Maybe String)
442
387
443−type alias Sub_LoadCipher_cipherType_LoginCipher_uris_List = (List String)
388+jsonDecCipher_LoginCipher_totp_Maybe : Json.Decode.Decoder ( Cipher_LoginCipher_totp_Maybe )
389+jsonDecCipher_LoginCipher_totp_Maybe =
390+ Json.Decode.maybe (Json.Decode.string)
444
391
445−jsonDecSub_LoadCipher_cipherType_LoginCipher_uris_List : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_LoginCipher_uris_List )
392+jsonEncCipher_LoginCipher_totp_Maybe : Cipher_LoginCipher_totp_Maybe -> Value
446−jsonDecSub_LoadCipher_cipherType_LoginCipher_uris_List =
393+jsonEncCipher_LoginCipher_totp_Maybe val = (maybeEncode (Json.Encode.string)) val
394+
395+
396+
397+type alias Cipher_LoginCipher_uris_List = (List String)
398+
399+jsonDecCipher_LoginCipher_uris_List : Json.Decode.Decoder ( Cipher_LoginCipher_uris_List )
400+jsonDecCipher_LoginCipher_uris_List =
447 Json.Decode.list (Json.Decode.string)
401 Json.Decode.list (Json.Decode.string)
448
402
449−jsonEncSub_LoadCipher_cipherType_LoginCipher_uris_List : Sub_LoadCipher_cipherType_LoginCipher_uris_List -> Value
403+jsonEncCipher_LoginCipher_uris_List : Cipher_LoginCipher_uris_List -> Value
450−jsonEncSub_LoadCipher_cipherType_LoginCipher_uris_List val = (Json.Encode.list Json.Encode.string) val
404+jsonEncCipher_LoginCipher_uris_List val = (Json.Encode.list Json.Encode.string) val
451
405
452
406
453
407
454−type alias Sub_LoadCipher_cipherType_LoginCipher_username_Maybe = (Maybe String)
408+type alias Cipher_LoginCipher_username_Maybe = (Maybe String)
455
409
456−jsonDecSub_LoadCipher_cipherType_LoginCipher_username_Maybe : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_LoginCipher_username_Maybe )
410+jsonDecCipher_LoginCipher_username_Maybe : Json.Decode.Decoder ( Cipher_LoginCipher_username_Maybe )
457−jsonDecSub_LoadCipher_cipherType_LoginCipher_username_Maybe =
411+jsonDecCipher_LoginCipher_username_Maybe =
458 Json.Decode.maybe (Json.Decode.string)
412 Json.Decode.maybe (Json.Decode.string)
459
413
460−jsonEncSub_LoadCipher_cipherType_LoginCipher_username_Maybe : Sub_LoadCipher_cipherType_LoginCipher_username_Maybe -> Value
414+jsonEncCipher_LoginCipher_username_Maybe : Cipher_LoginCipher_username_Maybe -> Value
461−jsonEncSub_LoadCipher_cipherType_LoginCipher_username_Maybe val = (maybeEncode (Json.Encode.string)) val
415+jsonEncCipher_LoginCipher_username_Maybe val = (maybeEncode (Json.Encode.string)) val
462
416
463
417
464
418
465−type alias Sub_LoadCipher_cipherType_LoginCipher =
419+type alias Cipher_LoginCipher =
466− { password: Sub_LoadCipher_cipherType_LoginCipher_password_Maybe
420+ { password: Cipher_LoginCipher_password_Maybe
467− , uris: Sub_LoadCipher_cipherType_LoginCipher_uris_List
421+ , totp: Cipher_LoginCipher_totp_Maybe
468− , username: Sub_LoadCipher_cipherType_LoginCipher_username_Maybe
422+ , uris: Cipher_LoginCipher_uris_List
423+ , username: Cipher_LoginCipher_username_Maybe
469 }
424 }
470
425
471−jsonDecSub_LoadCipher_cipherType_LoginCipher : Json.Decode.Decoder ( Sub_LoadCipher_cipherType_LoginCipher )
426+jsonDecCipher_LoginCipher : Json.Decode.Decoder ( Cipher_LoginCipher )
472−jsonDecSub_LoadCipher_cipherType_LoginCipher =
427+jsonDecCipher_LoginCipher =
473− Json.Decode.succeed (\ppassword puris pusername -> {password = ppassword, uris = puris, username = pusername})
428+ Json.Decode.succeed (\ppassword ptotp puris pusername -> {password = ppassword, totp = ptotp, uris = puris, username = pusername})
474− |> required "password" (jsonDecSub_LoadCipher_cipherType_LoginCipher_password_Maybe)
429+ |> required "password" (jsonDecCipher_LoginCipher_password_Maybe)
475− |> required "uris" (jsonDecSub_LoadCipher_cipherType_LoginCipher_uris_List)
430+ |> required "totp" (jsonDecCipher_LoginCipher_totp_Maybe)
476− |> required "username" (jsonDecSub_LoadCipher_cipherType_LoginCipher_username_Maybe)
431+ |> required "uris" (jsonDecCipher_LoginCipher_uris_List)
432+ |> required "username" (jsonDecCipher_LoginCipher_username_Maybe)
477
433
478−jsonEncSub_LoadCipher_cipherType_LoginCipher : Sub_LoadCipher_cipherType_LoginCipher -> Value
434+jsonEncCipher_LoginCipher : Cipher_LoginCipher -> Value
479−jsonEncSub_LoadCipher_cipherType_LoginCipher val =
435+jsonEncCipher_LoginCipher val =
480 Json.Encode.object
436 Json.Encode.object
481− [ ("password", jsonEncSub_LoadCipher_cipherType_LoginCipher_password_Maybe val.password)
437+ [ ("password", jsonEncCipher_LoginCipher_password_Maybe val.password)
482− , ("uris", jsonEncSub_LoadCipher_cipherType_LoginCipher_uris_List val.uris)
438+ , ("totp", jsonEncCipher_LoginCipher_totp_Maybe val.totp)
483− , ("username", jsonEncSub_LoadCipher_cipherType_LoginCipher_username_Maybe val.username)
439+ , ("uris", jsonEncCipher_LoginCipher_uris_List val.uris)
440+ , ("username", jsonEncCipher_LoginCipher_username_Maybe val.username)
484 ]
441 ]
485
442
486
443
487
444
488−type Sub_LoadCipher_cipherType =
445+type Cipher =
489− CardCipher Sub_LoadCipher_cipherType_CardCipher
446+ CardCipher Cipher_CardCipher
490− | IdentityCipher Sub_LoadCipher_cipherType_IdentityCipher
447+ | IdentityCipher Cipher_IdentityCipher
491− | LoginCipher Sub_LoadCipher_cipherType_LoginCipher
448+ | LoginCipher Cipher_LoginCipher
492 | NoteCipher String
449 | NoteCipher String
493
450
494−jsonDecSub_LoadCipher_cipherType : Json.Decode.Decoder ( Sub_LoadCipher_cipherType )
451+jsonDecCipher : Json.Decode.Decoder ( Cipher )
495−jsonDecSub_LoadCipher_cipherType =
452+jsonDecCipher =
496− let jsonDecDictSub_LoadCipher_cipherType = Dict.fromList
453+ let jsonDecDictCipher = Dict.fromList
497− [ ("CardCipher", Json.Decode.lazy (\_ -> Json.Decode.map CardCipher (jsonDecSub_LoadCipher_cipherType_CardCipher)))
454+ [ ("CardCipher", Json.Decode.lazy (\_ -> Json.Decode.map CardCipher (jsonDecCipher_CardCipher)))
498− , ("IdentityCipher", Json.Decode.lazy (\_ -> Json.Decode.map IdentityCipher (jsonDecSub_LoadCipher_cipherType_IdentityCipher)))
455+ , ("IdentityCipher", Json.Decode.lazy (\_ -> Json.Decode.map IdentityCipher (jsonDecCipher_IdentityCipher)))
499− , ("LoginCipher", Json.Decode.lazy (\_ -> Json.Decode.map LoginCipher (jsonDecSub_LoadCipher_cipherType_LoginCipher)))
456+ , ("LoginCipher", Json.Decode.lazy (\_ -> Json.Decode.map LoginCipher (jsonDecCipher_LoginCipher)))
500 , ("NoteCipher", Json.Decode.lazy (\_ -> Json.Decode.map NoteCipher (Json.Decode.string)))
457 , ("NoteCipher", Json.Decode.lazy (\_ -> Json.Decode.map NoteCipher (Json.Decode.string)))
501 ]
458 ]
502− jsonDecObjectSetSub_LoadCipher_cipherType = Set.fromList []
459+ jsonDecObjectSetCipher = Set.fromList []
503− in decodeSumTaggedObject "Sub_LoadCipher_cipherType" "tag" "contents" jsonDecDictSub_LoadCipher_cipherType jsonDecObjectSetSub_LoadCipher_cipherType
460+ in decodeSumTaggedObject "Cipher" "tag" "contents" jsonDecDictCipher jsonDecObjectSetCipher
504
461
505−jsonEncSub_LoadCipher_cipherType : Sub_LoadCipher_cipherType -> Value
462+jsonEncCipher : Cipher -> Value
506−jsonEncSub_LoadCipher_cipherType val =
463+jsonEncCipher val =
507 let keyval v = case v of
464 let keyval v = case v of
508− CardCipher v1 -> ("CardCipher", encodeValue (jsonEncSub_LoadCipher_cipherType_CardCipher v1))
465+ CardCipher v1 -> ("CardCipher", encodeValue (jsonEncCipher_CardCipher v1))
509− IdentityCipher v1 -> ("IdentityCipher", encodeValue (jsonEncSub_LoadCipher_cipherType_IdentityCipher v1))
466+ IdentityCipher v1 -> ("IdentityCipher", encodeValue (jsonEncCipher_IdentityCipher v1))
510− LoginCipher v1 -> ("LoginCipher", encodeValue (jsonEncSub_LoadCipher_cipherType_LoginCipher v1))
467+ LoginCipher v1 -> ("LoginCipher", encodeValue (jsonEncCipher_LoginCipher v1))
511 NoteCipher v1 -> ("NoteCipher", encodeValue (Json.Encode.string v1))
468 NoteCipher v1 -> ("NoteCipher", encodeValue (Json.Encode.string v1))
512 in encodeSumTaggedObject "tag" "contents" keyval val
469 in encodeSumTaggedObject "tag" "contents" keyval val
513
470
514
471
515
472
516−type alias Sub_LoadCipher =
473+type CipherType =
517− { cipherType: Sub_LoadCipher_cipherType
474+ CardType
475+ | IdentityType
476+ | LoginType
477+ | NoteType
478+
479+jsonDecCipherType : Json.Decode.Decoder ( CipherType )
480+jsonDecCipherType =
481+ let jsonDecDictCipherType = Dict.fromList [("CardType", CardType), ("IdentityType", IdentityType), ("LoginType", LoginType), ("NoteType", NoteType)]
482+ in decodeSumUnaries "CipherType" jsonDecDictCipherType
483+
484+jsonEncCipherType : CipherType -> Value
485+jsonEncCipherType val =
486+ case val of
487+ CardType -> Json.Encode.string "CardType"
488+ IdentityType -> Json.Encode.string "IdentityType"
489+ LoginType -> Json.Encode.string "LoginType"
490+ NoteType -> Json.Encode.string "NoteType"
491+
492+
493+
494+type alias Cmd_ChooseSecondFactor =
495+ { email: String
496+ , factor: TwoFactorProviderType
497+ , password: String
498+ , requestFromServer: Bool
499+ , server: String
500+ }
501+
502+jsonDecCmd_ChooseSecondFactor : Json.Decode.Decoder ( Cmd_ChooseSecondFactor )
503+jsonDecCmd_ChooseSecondFactor =
504+ Json.Decode.succeed (\pemail pfactor ppassword prequestFromServer pserver -> {email = pemail, factor = pfactor, password = ppassword, requestFromServer = prequestFromServer, server = pserver})
505+ |> required "email" (Json.Decode.string)
506+ |> required "factor" (jsonDecTwoFactorProviderType)
507+ |> required "password" (Json.Decode.string)
508+ |> required "requestFromServer" (Json.Decode.bool)
509+ |> required "server" (Json.Decode.string)
510+
511+jsonEncCmd_ChooseSecondFactor : Cmd_ChooseSecondFactor -> Value
512+jsonEncCmd_ChooseSecondFactor val =
513+ Json.Encode.object
514+ [ ("email", Json.Encode.string val.email)
515+ , ("factor", jsonEncTwoFactorProviderType val.factor)
516+ , ("password", Json.Encode.string val.password)
517+ , ("requestFromServer", Json.Encode.bool val.requestFromServer)
518+ , ("server", Json.Encode.string val.server)
519+ ]
520+
521+
522+
523+type alias Cmd_Login_secondFactor =
524+ { provider: TwoFactorProviderType
525+ , remember: Bool
526+ , token: String
527+ }
528+
529+jsonDecCmd_Login_secondFactor : Json.Decode.Decoder ( Cmd_Login_secondFactor )
530+jsonDecCmd_Login_secondFactor =
531+ Json.Decode.succeed (\pprovider premember ptoken -> {provider = pprovider, remember = premember, token = ptoken})
532+ |> required "provider" (jsonDecTwoFactorProviderType)
533+ |> required "remember" (Json.Decode.bool)
534+ |> required "token" (Json.Decode.string)
535+
536+jsonEncCmd_Login_secondFactor : Cmd_Login_secondFactor -> Value
537+jsonEncCmd_Login_secondFactor val =
538+ Json.Encode.object
539+ [ ("provider", jsonEncTwoFactorProviderType val.provider)
540+ , ("remember", Json.Encode.bool val.remember)
541+ , ("token", Json.Encode.string val.token)
542+ ]
543+
544+
545+
546+type alias Cmd_Login_secondFactor_Maybe = (Maybe Cmd_Login_secondFactor)
547+
548+jsonDecCmd_Login_secondFactor_Maybe : Json.Decode.Decoder ( Cmd_Login_secondFactor_Maybe )
549+jsonDecCmd_Login_secondFactor_Maybe =
550+ Json.Decode.maybe (jsonDecCmd_Login_secondFactor)
551+
552+jsonEncCmd_Login_secondFactor_Maybe : Cmd_Login_secondFactor_Maybe -> Value
553+jsonEncCmd_Login_secondFactor_Maybe val = (maybeEncode (jsonEncCmd_Login_secondFactor)) val
554+
555+
556+
557+type alias Cmd_Login =
558+ { email: String
559+ , password: String
560+ , secondFactor: Cmd_Login_secondFactor_Maybe
561+ , server: String
562+ }
563+
564+jsonDecCmd_Login : Json.Decode.Decoder ( Cmd_Login )
565+jsonDecCmd_Login =
566+ Json.Decode.succeed (\pemail ppassword psecondFactor pserver -> {email = pemail, password = ppassword, secondFactor = psecondFactor, server = pserver})
567+ |> required "email" (Json.Decode.string)
568+ |> required "password" (Json.Decode.string)
569+ |> required "secondFactor" (jsonDecCmd_Login_secondFactor_Maybe)
570+ |> required "server" (Json.Decode.string)
571+
572+jsonEncCmd_Login : Cmd_Login -> Value
573+jsonEncCmd_Login val =
574+ Json.Encode.object
575+ [ ("email", Json.Encode.string val.email)
576+ , ("password", Json.Encode.string val.password)
577+ , ("secondFactor", jsonEncCmd_Login_secondFactor_Maybe val.secondFactor)
578+ , ("server", Json.Encode.string val.server)
579+ ]
580+
581+
582+
583+type Cmd =
584+ ChooseSecondFactor Cmd_ChooseSecondFactor
585+ | Copy String
586+ | CreateCipher FullCipher
587+ | DeleteCipher FullCipher
588+ | GeneratePassword PasswordGeneratorConfig
589+ | Init
590+ | Login Cmd_Login
591+ | NeedCiphersList
592+ | NeedEmail
593+ | NeedsReset
594+ | Open String
595+ | RequestCipher String
596+ | RequestTotp String
597+ | SendMasterPassword String
598+ | UpdateCipher FullCipher
599+
600+jsonDecCmd : Json.Decode.Decoder ( Cmd )
601+jsonDecCmd =
602+ let jsonDecDictCmd = Dict.fromList
603+ [ ("ChooseSecondFactor", Json.Decode.lazy (\_ -> Json.Decode.map ChooseSecondFactor (jsonDecCmd_ChooseSecondFactor)))
604+ , ("Copy", Json.Decode.lazy (\_ -> Json.Decode.map Copy (Json.Decode.string)))
605+ , ("CreateCipher", Json.Decode.lazy (\_ -> Json.Decode.map CreateCipher (jsonDecFullCipher)))
606+ , ("DeleteCipher", Json.Decode.lazy (\_ -> Json.Decode.map DeleteCipher (jsonDecFullCipher)))
607+ , ("GeneratePassword", Json.Decode.lazy (\_ -> Json.Decode.map GeneratePassword (jsonDecPasswordGeneratorConfig)))
608+ , ("Init", Json.Decode.lazy (\_ -> Json.Decode.succeed Init))
609+ , ("Login", Json.Decode.lazy (\_ -> Json.Decode.map Login (jsonDecCmd_Login)))
610+ , ("NeedCiphersList", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedCiphersList))
611+ , ("NeedEmail", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedEmail))
612+ , ("NeedsReset", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedsReset))
613+ , ("Open", Json.Decode.lazy (\_ -> Json.Decode.map Open (Json.Decode.string)))
614+ , ("RequestCipher", Json.Decode.lazy (\_ -> Json.Decode.map RequestCipher (Json.Decode.string)))
615+ , ("RequestTotp", Json.Decode.lazy (\_ -> Json.Decode.map RequestTotp (Json.Decode.string)))
616+ , ("SendMasterPassword", Json.Decode.lazy (\_ -> Json.Decode.map SendMasterPassword (Json.Decode.string)))
617+ , ("UpdateCipher", Json.Decode.lazy (\_ -> Json.Decode.map UpdateCipher (jsonDecFullCipher)))
618+ ]
619+ jsonDecObjectSetCmd = Set.fromList []
620+ in decodeSumTaggedObject "Cmd" "tag" "contents" jsonDecDictCmd jsonDecObjectSetCmd
621+
622+jsonEncCmd : Cmd -> Value
623+jsonEncCmd val =
624+ let keyval v = case v of
625+ ChooseSecondFactor v1 -> ("ChooseSecondFactor", encodeValue (jsonEncCmd_ChooseSecondFactor v1))
626+ Copy v1 -> ("Copy", encodeValue (Json.Encode.string v1))
627+ CreateCipher v1 -> ("CreateCipher", encodeValue (jsonEncFullCipher v1))
628+ DeleteCipher v1 -> ("DeleteCipher", encodeValue (jsonEncFullCipher v1))
629+ GeneratePassword v1 -> ("GeneratePassword", encodeValue (jsonEncPasswordGeneratorConfig v1))
630+ Init -> ("Init", encodeValue (Json.Encode.list identity []))
631+ Login v1 -> ("Login", encodeValue (jsonEncCmd_Login v1))
632+ NeedCiphersList -> ("NeedCiphersList", encodeValue (Json.Encode.list identity []))
633+ NeedEmail -> ("NeedEmail", encodeValue (Json.Encode.list identity []))
634+ NeedsReset -> ("NeedsReset", encodeValue (Json.Encode.list identity []))
635+ Open v1 -> ("Open", encodeValue (Json.Encode.string v1))
636+ RequestCipher v1 -> ("RequestCipher", encodeValue (Json.Encode.string v1))
637+ RequestTotp v1 -> ("RequestTotp", encodeValue (Json.Encode.string v1))
638+ SendMasterPassword v1 -> ("SendMasterPassword", encodeValue (Json.Encode.string v1))
639+ UpdateCipher v1 -> ("UpdateCipher", encodeValue (jsonEncFullCipher v1))
640+ in encodeSumTaggedObject "tag" "contents" keyval val
641+
642+
643+
644+type alias FullCipher_collectionIds_List = (List String)
645+
646+jsonDecFullCipher_collectionIds_List : Json.Decode.Decoder ( FullCipher_collectionIds_List )
647+jsonDecFullCipher_collectionIds_List =
648+ Json.Decode.list (Json.Decode.string)
649+
650+jsonEncFullCipher_collectionIds_List : FullCipher_collectionIds_List -> Value
651+jsonEncFullCipher_collectionIds_List val = (Json.Encode.list Json.Encode.string) val
652+
653+
654+
655+type alias FullCipher =
656+ { cipher: Cipher
657+ , collectionIds: FullCipher_collectionIds_List
658+ , favorite: Bool
518 , id: String
659 , id: String
519 , name: String
660 , name: String
661+ , reprompt: Int
520 }
662 }
521
663
522−jsonDecSub_LoadCipher : Json.Decode.Decoder ( Sub_LoadCipher )
664+jsonDecFullCipher : Json.Decode.Decoder ( FullCipher )
523−jsonDecSub_LoadCipher =
665+jsonDecFullCipher =
524− Json.Decode.succeed (\pcipherType pid pname -> {cipherType = pcipherType, id = pid, name = pname})
666+ Json.Decode.succeed (\pcipher pcollectionIds pfavorite pid pname preprompt -> {cipher = pcipher, collectionIds = pcollectionIds, favorite = pfavorite, id = pid, name = pname, reprompt = preprompt})
525− |> required "cipherType" (jsonDecSub_LoadCipher_cipherType)
667+ |> required "cipher" (jsonDecCipher)
668+ |> required "collectionIds" (jsonDecFullCipher_collectionIds_List)
669+ |> required "favorite" (Json.Decode.bool)
526 |> required "id" (Json.Decode.string)
670 |> required "id" (Json.Decode.string)
527 |> required "name" (Json.Decode.string)
671 |> required "name" (Json.Decode.string)
672+ |> required "reprompt" (Json.Decode.int)
528
673
529−jsonEncSub_LoadCipher : Sub_LoadCipher -> Value
674+jsonEncFullCipher : FullCipher -> Value
530−jsonEncSub_LoadCipher val =
675+jsonEncFullCipher val =
531 Json.Encode.object
676 Json.Encode.object
532− [ ("cipherType", jsonEncSub_LoadCipher_cipherType val.cipherType)
677+ [ ("cipher", jsonEncCipher val.cipher)
678+ , ("collectionIds", jsonEncFullCipher_collectionIds_List val.collectionIds)
679+ , ("favorite", Json.Encode.bool val.favorite)
533 , ("id", Json.Encode.string val.id)
680 , ("id", Json.Encode.string val.id)
534 , ("name", Json.Encode.string val.name)
681 , ("name", Json.Encode.string val.name)
682+ , ("reprompt", Json.Encode.int val.reprompt)
535 ]
683 ]
536
684
537
685
538
686
539−type Sub_LoadCiphers_cipherType =
687+type alias PasswordGeneratorConfig =
540− CardType
688+ { ambiguous: Bool
541− | IdentityType
689+ , capitalize: Bool
542− | LoginType
690+ , includeNumber: Bool
543− | NoteType
691+ , length: Int
544−
692+ , lowercase: Bool
545−jsonDecSub_LoadCiphers_cipherType : Json.Decode.Decoder ( Sub_LoadCiphers_cipherType )
693+ , minLowercase: Int
546−jsonDecSub_LoadCiphers_cipherType =
694+ , minNumber: Int
547− let jsonDecDictSub_LoadCiphers_cipherType = Dict.fromList [("CardType", CardType), ("IdentityType", IdentityType), ("LoginType", LoginType), ("NoteType", NoteType)]
695+ , minSpecial: Int
548− in decodeSumUnaries "Sub_LoadCiphers_cipherType" jsonDecDictSub_LoadCiphers_cipherType
696+ , minUppercase: Int
697+ , numWords: Int
698+ , number: Bool
699+ , special: Bool
700+ , type_: String
701+ , uppercase: Bool
702+ , wordSeparator: String
703+ }
549
704
550−jsonEncSub_LoadCiphers_cipherType : Sub_LoadCiphers_cipherType -> Value
705+jsonDecPasswordGeneratorConfig : Json.Decode.Decoder ( PasswordGeneratorConfig )
551−jsonEncSub_LoadCiphers_cipherType val =
706+jsonDecPasswordGeneratorConfig =
552− case val of
707+ Json.Decode.succeed (\pambiguous pcapitalize pincludeNumber plength plowercase pminLowercase pminNumber pminSpecial pminUppercase pnumWords pnumber pspecial ptype puppercase pwordSeparator -> {ambiguous = pambiguous, capitalize = pcapitalize, includeNumber = pincludeNumber, length = plength, lowercase = plowercase, minLowercase = pminLowercase, minNumber = pminNumber, minSpecial = pminSpecial, minUppercase = pminUppercase, numWords = pnumWords, number = pnumber, special = pspecial, type_ = ptype, uppercase = puppercase, wordSeparator = pwordSeparator})
553− CardType -> Json.Encode.string "CardType"
708+ |> required "ambiguous" (Json.Decode.bool)
554− IdentityType -> Json.Encode.string "IdentityType"
709+ |> required "capitalize" (Json.Decode.bool)
555− LoginType -> Json.Encode.string "LoginType"
710+ |> required "includeNumber" (Json.Decode.bool)
556− NoteType -> Json.Encode.string "NoteType"
711+ |> required "length" (Json.Decode.int)
712+ |> required "lowercase" (Json.Decode.bool)
713+ |> required "minLowercase" (Json.Decode.int)
714+ |> required "minNumber" (Json.Decode.int)
715+ |> required "minSpecial" (Json.Decode.int)
716+ |> required "minUppercase" (Json.Decode.int)
717+ |> required "numWords" (Json.Decode.int)
718+ |> required "number" (Json.Decode.bool)
719+ |> required "special" (Json.Decode.bool)
720+ |> required "type" (Json.Decode.string)
721+ |> required "uppercase" (Json.Decode.bool)
722+ |> required "wordSeparator" (Json.Decode.string)
723+
724+jsonEncPasswordGeneratorConfig : PasswordGeneratorConfig -> Value
725+jsonEncPasswordGeneratorConfig val =
726+ Json.Encode.object
727+ [ ("ambiguous", Json.Encode.bool val.ambiguous)
728+ , ("capitalize", Json.Encode.bool val.capitalize)
729+ , ("includeNumber", Json.Encode.bool val.includeNumber)
730+ , ("length", Json.Encode.int val.length)
731+ , ("lowercase", Json.Encode.bool val.lowercase)
732+ , ("minLowercase", Json.Encode.int val.minLowercase)
733+ , ("minNumber", Json.Encode.int val.minNumber)
734+ , ("minSpecial", Json.Encode.int val.minSpecial)
735+ , ("minUppercase", Json.Encode.int val.minUppercase)
736+ , ("numWords", Json.Encode.int val.numWords)
737+ , ("number", Json.Encode.bool val.number)
738+ , ("special", Json.Encode.bool val.special)
739+ , ("type", Json.Encode.string val.type_)
740+ , ("uppercase", Json.Encode.bool val.uppercase)
741+ , ("wordSeparator", Json.Encode.string val.wordSeparator)
742+ ]
557
743
558
744
559
745
560type alias Sub_LoadCiphers =
746type alias Sub_LoadCiphers =
561− { cipherType: Sub_LoadCiphers_cipherType
747+ { cipherType: CipherType
562 , date: String
748 , date: String
563 , id: String
749 , id: String
564 , name: String
750 , name: String
@@ -567,7 +753,7 @@ type alias Sub_LoadCiphers =
567jsonDecSub_LoadCiphers : Json.Decode.Decoder ( Sub_LoadCiphers )
753jsonDecSub_LoadCiphers : Json.Decode.Decoder ( Sub_LoadCiphers )
568jsonDecSub_LoadCiphers =
754jsonDecSub_LoadCiphers =
569 Json.Decode.succeed (\pcipherType pdate pid pname -> {cipherType = pcipherType, date = pdate, id = pid, name = pname})
755 Json.Decode.succeed (\pcipherType pdate pid pname -> {cipherType = pcipherType, date = pdate, id = pid, name = pname})
570− |> required "cipherType" (jsonDecSub_LoadCiphers_cipherType)
756+ |> required "cipherType" (jsonDecCipherType)
571 |> required "date" (Json.Decode.string)
757 |> required "date" (Json.Decode.string)
572 |> required "id" (Json.Decode.string)
758 |> required "id" (Json.Decode.string)
573 |> required "name" (Json.Decode.string)
759 |> required "name" (Json.Decode.string)
@@ -575,7 +761,7 @@ jsonDecSub_LoadCiphers =
575jsonEncSub_LoadCiphers : Sub_LoadCiphers -> Value
761jsonEncSub_LoadCiphers : Sub_LoadCiphers -> Value
576jsonEncSub_LoadCiphers val =
762jsonEncSub_LoadCiphers val =
577 Json.Encode.object
763 Json.Encode.object
578− [ ("cipherType", jsonEncSub_LoadCiphers_cipherType val.cipherType)
764+ [ ("cipherType", jsonEncCipherType val.cipherType)
579 , ("date", Json.Encode.string val.date)
765 , ("date", Json.Encode.string val.date)
580 , ("id", Json.Encode.string val.id)
766 , ("id", Json.Encode.string val.id)
581 , ("name", Json.Encode.string val.name)
767 , ("name", Json.Encode.string val.name)
@@ -614,25 +800,77 @@ jsonEncSub_NeedsMasterPassword val =
614
800
615
801
616
802
803+type alias Sub_NeedsSecondFactor_List = (List TwoFactorProviderType)
804+
805+jsonDecSub_NeedsSecondFactor_List : Json.Decode.Decoder ( Sub_NeedsSecondFactor_List )
806+jsonDecSub_NeedsSecondFactor_List =
807+ Json.Decode.list (jsonDecTwoFactorProviderType)
808+
809+jsonEncSub_NeedsSecondFactor_List : Sub_NeedsSecondFactor_List -> Value
810+jsonEncSub_NeedsSecondFactor_List val = (Json.Encode.list jsonEncTwoFactorProviderType) val
811+
812+
813+
814+type alias Sub_Totp =
815+ { code: String
816+ , interval: Int
817+ , source: String
818+ }
819+
820+jsonDecSub_Totp : Json.Decode.Decoder ( Sub_Totp )
821+jsonDecSub_Totp =
822+ Json.Decode.succeed (\pcode pinterval psource -> {code = pcode, interval = pinterval, source = psource})
823+ |> required "code" (Json.Decode.string)
824+ |> required "interval" (Json.Decode.int)
825+ |> required "source" (Json.Decode.string)
826+
827+jsonEncSub_Totp : Sub_Totp -> Value
828+jsonEncSub_Totp val =
829+ Json.Encode.object
830+ [ ("code", Json.Encode.string val.code)
831+ , ("interval", Json.Encode.int val.interval)
832+ , ("source", Json.Encode.string val.source)
833+ ]
834+
835+
836+
617type Sub =
837type Sub =
838+ CaptchaDone
839+ | CipherChanged FullCipher
840+ | CipherDeleted FullCipher
618− Error String
841+ | Error String
842+ | GeneratedPassword String
619− | LoadCipher Sub_LoadCipher
843+ | LoadCipher FullCipher
620 | LoadCiphers Sub_LoadCiphers_List
844 | LoadCiphers Sub_LoadCiphers_List
621 | LoginSuccessful
845 | LoginSuccessful
846+ | NeedsCaptcha String
622 | NeedsLogin
847 | NeedsLogin
623 | NeedsMasterPassword Sub_NeedsMasterPassword
848 | NeedsMasterPassword Sub_NeedsMasterPassword
849+ | NeedsSecondFactor Sub_NeedsSecondFactor_List
850+ | RecieveEmail String
624 | Reset
851 | Reset
852+ | Totp Sub_Totp
853+ | WrongPassword
625
854
626jsonDecSub : Json.Decode.Decoder ( Sub )
855jsonDecSub : Json.Decode.Decoder ( Sub )
627jsonDecSub =
856jsonDecSub =
628 let jsonDecDictSub = Dict.fromList
857 let jsonDecDictSub = Dict.fromList
858+ [ ("CaptchaDone", Json.Decode.lazy (\_ -> Json.Decode.succeed CaptchaDone))
859+ , ("CipherChanged", Json.Decode.lazy (\_ -> Json.Decode.map CipherChanged (jsonDecFullCipher)))
860+ , ("CipherDeleted", Json.Decode.lazy (\_ -> Json.Decode.map CipherDeleted (jsonDecFullCipher)))
629− [ ("Error", Json.Decode.lazy (\_ -> Json.Decode.map Error (Json.Decode.string)))
861+ , ("Error", Json.Decode.lazy (\_ -> Json.Decode.map Error (Json.Decode.string)))
862+ , ("GeneratedPassword", Json.Decode.lazy (\_ -> Json.Decode.map GeneratedPassword (Json.Decode.string)))
630− , ("LoadCipher", Json.Decode.lazy (\_ -> Json.Decode.map LoadCipher (jsonDecSub_LoadCipher)))
863+ , ("LoadCipher", Json.Decode.lazy (\_ -> Json.Decode.map LoadCipher (jsonDecFullCipher)))
631 , ("LoadCiphers", Json.Decode.lazy (\_ -> Json.Decode.map LoadCiphers (jsonDecSub_LoadCiphers_List)))
864 , ("LoadCiphers", Json.Decode.lazy (\_ -> Json.Decode.map LoadCiphers (jsonDecSub_LoadCiphers_List)))
632 , ("LoginSuccessful", Json.Decode.lazy (\_ -> Json.Decode.succeed LoginSuccessful))
865 , ("LoginSuccessful", Json.Decode.lazy (\_ -> Json.Decode.succeed LoginSuccessful))
866+ , ("NeedsCaptcha", Json.Decode.lazy (\_ -> Json.Decode.map NeedsCaptcha (Json.Decode.string)))
633 , ("NeedsLogin", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedsLogin))
867 , ("NeedsLogin", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedsLogin))
634 , ("NeedsMasterPassword", Json.Decode.lazy (\_ -> Json.Decode.map NeedsMasterPassword (jsonDecSub_NeedsMasterPassword)))
868 , ("NeedsMasterPassword", Json.Decode.lazy (\_ -> Json.Decode.map NeedsMasterPassword (jsonDecSub_NeedsMasterPassword)))
869+ , ("NeedsSecondFactor", Json.Decode.lazy (\_ -> Json.Decode.map NeedsSecondFactor (jsonDecSub_NeedsSecondFactor_List)))
870+ , ("RecieveEmail", Json.Decode.lazy (\_ -> Json.Decode.map RecieveEmail (Json.Decode.string)))
635 , ("Reset", Json.Decode.lazy (\_ -> Json.Decode.succeed Reset))
871 , ("Reset", Json.Decode.lazy (\_ -> Json.Decode.succeed Reset))
872+ , ("Totp", Json.Decode.lazy (\_ -> Json.Decode.map Totp (jsonDecSub_Totp)))
873+ , ("WrongPassword", Json.Decode.lazy (\_ -> Json.Decode.succeed WrongPassword))
636 ]
874 ]
637 jsonDecObjectSetSub = Set.fromList []
875 jsonDecObjectSetSub = Set.fromList []
638 in decodeSumTaggedObject "Sub" "tag" "contents" jsonDecDictSub jsonDecObjectSetSub
876 in decodeSumTaggedObject "Sub" "tag" "contents" jsonDecDictSub jsonDecObjectSetSub
@@ -640,12 +878,50 @@ jsonDecSub =
640jsonEncSub : Sub -> Value
878jsonEncSub : Sub -> Value
641jsonEncSub val =
879jsonEncSub val =
642 let keyval v = case v of
880 let keyval v = case v of
881+ CaptchaDone -> ("CaptchaDone", encodeValue (Json.Encode.list identity []))
882+ CipherChanged v1 -> ("CipherChanged", encodeValue (jsonEncFullCipher v1))
883+ CipherDeleted v1 -> ("CipherDeleted", encodeValue (jsonEncFullCipher v1))
643 Error v1 -> ("Error", encodeValue (Json.Encode.string v1))
884 Error v1 -> ("Error", encodeValue (Json.Encode.string v1))
885+ GeneratedPassword v1 -> ("GeneratedPassword", encodeValue (Json.Encode.string v1))
644− LoadCipher v1 -> ("LoadCipher", encodeValue (jsonEncSub_LoadCipher v1))
886+ LoadCipher v1 -> ("LoadCipher", encodeValue (jsonEncFullCipher v1))
645 LoadCiphers v1 -> ("LoadCiphers", encodeValue (jsonEncSub_LoadCiphers_List v1))
887 LoadCiphers v1 -> ("LoadCiphers", encodeValue (jsonEncSub_LoadCiphers_List v1))
646 LoginSuccessful -> ("LoginSuccessful", encodeValue (Json.Encode.list identity []))
888 LoginSuccessful -> ("LoginSuccessful", encodeValue (Json.Encode.list identity []))
889+ NeedsCaptcha v1 -> ("NeedsCaptcha", encodeValue (Json.Encode.string v1))
647 NeedsLogin -> ("NeedsLogin", encodeValue (Json.Encode.list identity []))
890 NeedsLogin -> ("NeedsLogin", encodeValue (Json.Encode.list identity []))
648 NeedsMasterPassword v1 -> ("NeedsMasterPassword", encodeValue (jsonEncSub_NeedsMasterPassword v1))
891 NeedsMasterPassword v1 -> ("NeedsMasterPassword", encodeValue (jsonEncSub_NeedsMasterPassword v1))
892+ NeedsSecondFactor v1 -> ("NeedsSecondFactor", encodeValue (jsonEncSub_NeedsSecondFactor_List v1))
893+ RecieveEmail v1 -> ("RecieveEmail", encodeValue (Json.Encode.string v1))
649 Reset -> ("Reset", encodeValue (Json.Encode.list identity []))
894 Reset -> ("Reset", encodeValue (Json.Encode.list identity []))
895+ Totp v1 -> ("Totp", encodeValue (jsonEncSub_Totp v1))
896+ WrongPassword -> ("WrongPassword", encodeValue (Json.Encode.list identity []))
650 in encodeSumTaggedObject "tag" "contents" keyval val
897 in encodeSumTaggedObject "tag" "contents" keyval val
651
898
899+
900+
901+type TwoFactorProviderType =
902+ Authenticator
903+ | Duo
904+ | Email
905+ | OrganizationDuo
906+ | Remember
907+ | U2f
908+ | WebAuthn
909+ | Yubikey
910+
911+jsonDecTwoFactorProviderType : Json.Decode.Decoder ( TwoFactorProviderType )
912+jsonDecTwoFactorProviderType =
913+ let jsonDecDictTwoFactorProviderType = Dict.fromList [("Authenticator", Authenticator), ("Duo", Duo), ("Email", Email), ("OrganizationDuo", OrganizationDuo), ("Remember", Remember), ("U2f", U2f), ("WebAuthn", WebAuthn), ("Yubikey", Yubikey)]
914+ in decodeSumUnaries "TwoFactorProviderType" jsonDecDictTwoFactorProviderType
915+
916+jsonEncTwoFactorProviderType : TwoFactorProviderType -> Value
917+jsonEncTwoFactorProviderType val =
918+ case val of
919+ Authenticator -> Json.Encode.string "Authenticator"
920+ Duo -> Json.Encode.string "Duo"
921+ Email -> Json.Encode.string "Email"
922+ OrganizationDuo -> Json.Encode.string "OrganizationDuo"
923+ Remember -> Json.Encode.string "Remember"
924+ U2f -> Json.Encode.string "U2f"
925+ WebAuthn -> Json.Encode.string "WebAuthn"
926+ Yubikey -> Json.Encode.string "Yubikey"
927+
frontend/FFI.elm+1−3
@@ -2,7 +2,6 @@ port module FFI exposing (getBridge, sendBridge)
2
2
3import Bridge
3import Bridge
4import Json.Decode as D
4import Json.Decode as D
5−import Json.Encode as E
6
5
7
6
8port bridgeSub : (D.Value -> msg) -> Sub msg
7port bridgeSub : (D.Value -> msg) -> Sub msg
@@ -14,8 +13,7 @@ port bridgeCmd : D.Value -> Cmd msg
14getBridge : (String -> msg) -> (Bridge.Sub -> msg) -> Sub msg
13getBridge : (String -> msg) -> (Bridge.Sub -> msg) -> Sub msg
15getBridge err f =
14getBridge err f =
16 bridgeSub
15 bridgeSub
17− ((\v -> Debug.log (E.encode 1 v) v)
18− >> D.decodeValue Bridge.jsonDecSub
16+ (D.decodeValue Bridge.jsonDecSub
19 >> (\res ->
17 >> (\res ->
20 case res of
18 case res of
21 Ok a ->
19 Ok a ->
frontend/GlobalEvents.elmnew file+9
@@ -0,0 +1,9 @@
1+module GlobalEvents exposing (..)
2+
3+import Bridge
4+
5+
6+type Event
7+ = UpdateCipher Bridge.FullCipher
8+ | GeneratedPassword String
9+ | DecodedTotp Bridge.Sub_Totp
frontend/Logic/Cipher.elmnew file+78
@@ -0,0 +1,78 @@
1+module Logic.Cipher exposing (normalizeCipher)
2+
3+import Bridge
4+
5+
6+normalizeCipher : Bridge.Cipher -> Bridge.Cipher
7+normalizeCipher x =
8+ case x of
9+ Bridge.CardCipher c ->
10+ Bridge.CardCipher (normalizeCardCipher c)
11+
12+ Bridge.IdentityCipher c ->
13+ Bridge.IdentityCipher (normalizeIdentityCipher c)
14+
15+ Bridge.LoginCipher c ->
16+ Bridge.LoginCipher (normalizeLoginCipher c)
17+
18+ Bridge.NoteCipher c ->
19+ Bridge.NoteCipher c
20+
21+
22+normalizeCardCipher : Bridge.Cipher_CardCipher -> Bridge.Cipher_CardCipher
23+normalizeCardCipher { brand, cardholderName, code, expMonth, expYear, number } =
24+ { brand = normalizeStringMaybe brand
25+ , cardholderName = normalizeStringMaybe cardholderName
26+ , code = normalizeStringMaybe code
27+ , expMonth = normalizeStringMaybe expMonth
28+ , expYear = normalizeStringMaybe expYear
29+ , number = normalizeStringMaybe number
30+ }
31+
32+
33+normalizeIdentityCipher : Bridge.Cipher_IdentityCipher -> Bridge.Cipher_IdentityCipher
34+normalizeIdentityCipher { address1, address2, address3, city, company, country, email, firstName, lastName, licenseNumber, middleName, passportNumber, phone, postalCode, ssn, state, title, username } =
35+ { address1 = normalizeStringMaybe address1
36+ , address2 = normalizeStringMaybe address2
37+ , address3 = normalizeStringMaybe address3
38+ , city = normalizeStringMaybe city
39+ , company = normalizeStringMaybe company
40+ , country = normalizeStringMaybe country
41+ , email = normalizeStringMaybe email
42+ , firstName = normalizeStringMaybe firstName
43+ , lastName = normalizeStringMaybe lastName
44+ , licenseNumber = normalizeStringMaybe licenseNumber
45+ , middleName = normalizeStringMaybe middleName
46+ , passportNumber = normalizeStringMaybe passportNumber
47+ , phone = normalizeStringMaybe phone
48+ , postalCode = normalizeStringMaybe postalCode
49+ , ssn = normalizeStringMaybe ssn
50+ , state = normalizeStringMaybe state
51+ , title = normalizeStringMaybe title
52+ , username = normalizeStringMaybe username
53+ }
54+
55+
56+normalizeLoginCipher : Bridge.Cipher_LoginCipher -> Bridge.Cipher_LoginCipher
57+normalizeLoginCipher { password, totp, uris, username } =
58+ { password = password
59+ , uris = uris |> List.map String.trim |> List.filter String.isEmpty
60+ , totp = normalizeStringMaybe totp
61+ , username = username
62+ }
63+
64+
65+normalizeStringMaybe : Maybe String -> Maybe String
66+normalizeStringMaybe =
67+ Maybe.andThen
68+ (\x ->
69+ let
70+ y =
71+ String.trim x
72+ in
73+ if String.isEmpty y then
74+ Nothing
75+
76+ else
77+ Just y
78+ )
frontend/Logic/SecondFactor.elmnew file+31
@@ -0,0 +1,31 @@
1+module Logic.SecondFactor exposing (secondFactorName)
2+
3+import Bridge
4+
5+
6+secondFactorName : Bridge.TwoFactorProviderType -> String
7+secondFactorName factor =
8+ case factor of
9+ Bridge.Authenticator ->
10+ "Authenticator"
11+
12+ Bridge.Duo ->
13+ "Duo"
14+
15+ Bridge.Email ->
16+ "Email"
17+
18+ Bridge.OrganizationDuo ->
19+ "Organization Duo"
20+
21+ Bridge.Remember ->
22+ "Remember (IDK what this is)"
23+
24+ Bridge.U2f ->
25+ "U2f"
26+
27+ Bridge.WebAuthn ->
28+ "WebAuthn"
29+
30+ Bridge.Yubikey ->
31+ "Yubikey"
frontend/Main.elm+599−162
@@ -1,123 +1,228 @@
1−module Main exposing (..)
1+module Main exposing (main)
2
2
3import Bridge
3import Bridge
4import Browser
4import Browser
5import FFI exposing (getBridge, sendBridge)
5import FFI exposing (getBridge, sendBridge)
6+import GlobalEvents exposing (Event)
6import Html exposing (..)
7import Html exposing (..)
8+import Html.Lazy as Lazy
7import List.Nonempty as Nonempty exposing (Nonempty(..))
9import List.Nonempty as Nonempty exposing (Nonempty(..))
8import Notification
10import Notification
11+import Pages.Captcha as Captcha
9import Pages.Cipher as Cipher
12import Pages.Cipher as Cipher
10import Pages.Ciphers as Ciphers
13import Pages.Ciphers as Ciphers
14+import Pages.EditCipher as EditCipher exposing (Msg(..))
11import Pages.Loader exposing (loader)
15import Pages.Loader exposing (loader)
12import Pages.Login as Login
16import Pages.Login as Login
13import Pages.MasterPassword as MasterPassword
17import Pages.MasterPassword as MasterPassword
14import Pages.Navigation as Navigation exposing (TopButton(..))
18import Pages.Navigation as Navigation exposing (TopButton(..))
19+import Pages.SecondFactor as SecondFactor
20+import Pages.SecondFactorSelect as SecondFactorSelect
21+import Task
22+import Time
23+import Types exposing (CipherId)
15import Utils exposing (..)
24import Utils exposing (..)
16
25
17
26
27+notificationLingerSeconds : Int
28+notificationLingerSeconds =
29+ 15
30+
31+
18type Msg
32type Msg
19− = RecieveMessage String
20− | ShowLoginPage
33+ = ShowLoginPage
21− | ShowError String
34+ | ShowError String String
35+ | ShowInfo String String
22 | CloseNotification
36 | CloseNotification
23− | SubmitLogin { email : String, password : String, server : String }
37+ | SubmitLogin Bridge.Cmd_Login
24− | LoginMsg Login.Msg
25− | CiphersMsg Ciphers.Msg
26 | LoadCiphers Bridge.Sub_LoadCiphers_List
38 | LoadCiphers Bridge.Sub_LoadCiphers_List
27− | OpenCiphersScreen
39+ | LoginSuccessful
28 | Reset
40 | Reset
29 | NeedsReset
41 | NeedsReset
30− | MasterPasswordMsg MasterPassword.Msg
31 | SendMasterPassword { password : String }
42 | SendMasterPassword { password : String }
32 | ShowMasterPasswordPage { server : String, login : String }
43 | ShowMasterPasswordPage { server : String, login : String }
33− | CipherMsg Cipher.Msg
34− | ShowCipherPage Bridge.Sub_LoadCipher
44+ | ShowCipherPage Bridge.FullCipher
35 | RequestCipher CipherId
45 | RequestCipher CipherId
36 | PopView
46 | PopView
47+ | RecieveEmail String
48+ | Copy String
49+ | Open String
50+ | ShowCaptcha Captcha.HCaptchSiteKey
51+ | ClearNotification { currentTime : Time.Posix }
52+ | UpdateLastNotificatioTime Time.Posix
53+ | EditCipher Bridge.FullCipher
54+ | UpdateCipher Bridge.FullCipher
55+ | CreateCipher Bridge.FullCipher
56+ | FireGlobalEvent Event
57+ | GeneratePassword Bridge.PasswordGeneratorConfig
58+ | OpenNewCipherEditPage Bridge.CipherType
59+ | WrongPassword
60+ | DeleteCipher Bridge.FullCipher
61+ | RequestTotp String
62+ | ShowSecondFactorSelect (List Bridge.TwoFactorProviderType)
63+ | SelectSecondFactor
64+ { provider : Bridge.TwoFactorProviderType
65+ , requestFromServer : Bool
66+ }
67+ | PageMsg PageMsg
37
68
38
69
39−type alias CipherId =
70+type PageMsg
40− String
71+ = LoginMsg Login.Msg
72+ | CiphersMsg Ciphers.Msg
73+ | MasterPasswordMsg MasterPassword.Msg
74+ | CipherMsg Cipher.Msg
75+ | EditCipherMsg EditCipher.Msg
76+ | SecondFactorSelectMsg SecondFactorSelect.Msg
77+ | SecondFactorMsg SecondFactor.Msg
41
78
42
79
43type alias Model =
80type alias Model =
44 { notifications : List Notification.Config
81 { notifications : List Notification.Config
45 , pageStack : Navigation.PageStack PageModel
82 , pageStack : Navigation.PageStack PageModel
83+ , userEmail : Maybe String
84+ , lastNotificationTime : Time.Posix
46 }
85 }
47
86
48
87
88+findLoginDetails :
89+ List PageModel
90+ ->
91+ Maybe
92+ { server : String
93+ , email : String
94+ , password : String
95+ }
96+findLoginDetails xs =
97+ case xs of
98+ (LoginModel { server, email, password }) :: _ ->
99+ Just
100+ { server = server
101+ , email = email
102+ , password = password
103+ }
104+
105+ _ :: rest ->
106+ findLoginDetails rest
107+
108+ [] ->
109+ Nothing
110+
111+
49−appendNotification : Notification.Config -> Model -> Model
112+appendNotification : Notification.Config -> Model -> ( Model, Cmd Msg )
50appendNotification cfg model =
113appendNotification cfg model =
51− { model | notifications = cfg :: model.notifications }
114+ ( { model | notifications = cfg :: model.notifications }, Task.perform UpdateLastNotificatioTime Time.now )
52
115
53
116
54type PageModel
117type PageModel
55− = LoginModel Login.Model
118+ = LoginModel (Login.Model Msg)
56 | LoadingPage
119 | LoadingPage
57− | CiphersModel Ciphers.Model
120+ | CiphersModel (Ciphers.Model Msg)
121+ | EditCipherModel (EditCipher.Model Msg)
58− | MasterPasswordModel MasterPassword.Model
122+ | MasterPasswordModel (MasterPassword.Model Msg)
59− | CipherModel Cipher.Model
123+ | CipherModel (Cipher.Model Msg)
124+ | CaptchaModel Captcha.Model
125+ | SecondFactorSelectModel (SecondFactorSelect.Model Msg)
126+ | SecondFactorModel (SecondFactor.Model Msg)
60
127
61
128
62showPage :
129showPage :
130+ String
63− PageModel
131+ -> PageModel
64 ->
132 ->
65− { title : String
133+ { title : List (Html Msg)
66 , body : List (Html Msg)
134 , body : List (Html Msg)
67 , topButton : Maybe (Navigation.TopButton Msg)
135 , topButton : Maybe (Navigation.TopButton Msg)
68 }
136 }
69−showPage page =
137+showPage email page =
138+ let
139+ simpleBackButton =
140+ BackButton { action = PopView, icon = Nothing }
141+
142+ liftPageWith :
143+ (pageMsg -> PageMsg)
144+ ->
145+ { title : List (Html pageMsg)
146+ , body : List (Html pageMsg)
147+ , topButton : Maybe (Navigation.TopButton Msg)
148+ }
149+ ->
150+ { title : List (Html Msg)
151+ , body : List (Html Msg)
152+ , topButton : Maybe (Navigation.TopButton Msg)
153+ }
154+ liftPageWith lift { title, body, topButton } =
155+ { title = title |> List.map (Html.map (lift >> PageMsg))
156+ , body = body |> List.map (Html.map (lift >> PageMsg))
157+ , topButton = topButton
158+ }
159+ in
70 case page of
160 case page of
71 LoginModel model ->
161 LoginModel model ->
72− let
162+ liftPageWith LoginMsg
73− p =
74− Login.page loginCallbacks LoginMsg
75− in
76− { title = p.title model
163+ { title = [ text Login.title ]
77− , body = p.view model
164+ , body = Login.view model
78− , topButton = Nothing
165+ , topButton = Nothing
79− }
166+ }
80
167
81 CiphersModel model ->
168 CiphersModel model ->
82− let
169+ liftPageWith CiphersMsg
83− p =
84− Ciphers.page ciphersCallbacks CiphersMsg
85− in
86− { title = p.title model
170+ { title = Ciphers.title model
87− , body = p.view model
171+ , body = Ciphers.view model
88− , topButton =
172+ , topButton =
89− Ciphers.menuConfig model
173+ Ciphers.menuConfig email model
90− |> Navigation.mapMenuConfig CiphersMsg
174+ |> Navigation.mapMenuConfig (CiphersMsg >> PageMsg)
91− |> Navigation.MenuButton
175+ |> Navigation.MenuButton
92− |> Just
176+ |> Just
93− }
177+ }
94
178
95 LoadingPage ->
179 LoadingPage ->
96− { title = ""
180+ { title = []
97 , body = [ loader ]
181 , body = [ loader ]
98− , topButton = Just (BackButton PopView)
182+ , topButton = Just simpleBackButton
99 }
183 }
100
184
101 MasterPasswordModel model ->
185 MasterPasswordModel model ->
102− let
186+ liftPageWith MasterPasswordMsg
103− p =
104− MasterPassword.page masterPasswordCallbacks MasterPasswordMsg
105− in
106− { title = p.title model
187+ { title = [ text MasterPassword.title ]
107− , body = p.view model
188+ , body = MasterPassword.view model
108− , topButton = Nothing
189+ , topButton = Nothing
109− }
190+ }
110
191
111 CipherModel model ->
192 CipherModel model ->
112− let
193+ liftPageWith CipherMsg
113− p =
194+ { title = Cipher.title model
114− Cipher.page cipherCallbacks CipherMsg
195+ , body = Cipher.view model
115− in
196+ , topButton = Just simpleBackButton
197+ }
198+
199+ CaptchaModel model ->
116− { title = p.title model
200+ { title = [ text Captcha.title ]
117− , body = p.view model
201+ , body = Captcha.view model
118− , topButton = Just (BackButton PopView)
202+ , topButton = Just simpleBackButton
119 }
203 }
120
204
205+ EditCipherModel model ->
206+ liftPageWith EditCipherMsg
207+ { title = EditCipher.title model
208+ , body = EditCipher.view model
209+ , topButton = Just (BackButton { action = PopView, icon = Just "close" })
210+ }
211+
212+ SecondFactorSelectModel model ->
213+ liftPageWith SecondFactorSelectMsg
214+ { title = [ text SecondFactorSelect.title ]
215+ , body = SecondFactorSelect.view model
216+ , topButton = Just simpleBackButton
217+ }
218+
219+ SecondFactorModel model ->
220+ liftPageWith SecondFactorMsg
221+ { title = [ SecondFactor.title model |> text ]
222+ , body = [ SecondFactor.view model ]
223+ , topButton = Just simpleBackButton
224+ }
225+
121
226
122main : Program () Model Msg
227main : Program () Model Msg
123main =
228main =
@@ -130,30 +235,102 @@ main =
130
235
131
236
132subscriptions : Model -> Sub Msg
237subscriptions : Model -> Sub Msg
133−subscriptions _ =
238+subscriptions model =
239+ Sub.batch
134− getBridge ShowError
240+ ([ getBridge (ShowError "Something went wrong!")
135− (\msg ->
241+ (\msg ->
136− case msg of
242+ case msg of
137− Bridge.NeedsLogin ->
243+ Bridge.NeedsLogin ->
138− ShowLoginPage
244+ ShowLoginPage
245+
246+ Bridge.Error err ->
247+ ShowError "Something went wrong!" err
248+
249+ Bridge.LoadCiphers ciphers ->
250+ LoadCiphers ciphers
251+
252+ Bridge.LoginSuccessful ->
253+ LoginSuccessful
254+
255+ Bridge.Reset ->
256+ Reset
257+
258+ Bridge.NeedsMasterPassword { server, login } ->
259+ ShowMasterPasswordPage { server = server, login = login }
260+
261+ Bridge.LoadCipher cipher ->
262+ ShowCipherPage cipher
263+
264+ Bridge.RecieveEmail email ->
265+ RecieveEmail email
266+
267+ Bridge.NeedsCaptcha uri ->
268+ ShowCaptcha uri
269+
270+ Bridge.CaptchaDone ->
271+ PopView
139
272
140− Bridge.Error err ->
273+ Bridge.CipherChanged c ->
141− ShowError err
274+ ShowInfo "Entry updated" ("The entry “" ++ c.name ++ "” has been successfully updated.")
142
275
143− Bridge.LoadCiphers ciphers ->
276+ Bridge.GeneratedPassword password ->
144− LoadCiphers ciphers
277+ GlobalEvents.GeneratedPassword password |> FireGlobalEvent
145
278
146− Bridge.LoginSuccessful ->
279+ Bridge.WrongPassword ->
147− OpenCiphersScreen
280+ WrongPassword
148
281
149− Bridge.Reset ->
282+ Bridge.CipherDeleted c ->
150− Reset
283+ ShowInfo "Entry deleted" ("The entry “" ++ c.name ++ "” has been successfully deleted.")
151
284
152− Bridge.NeedsMasterPassword { server, login } ->
285+ Bridge.Totp totp ->
153− ShowMasterPasswordPage { server = server, login = login }
286+ GlobalEvents.DecodedTotp totp |> FireGlobalEvent
154
287
155− Bridge.LoadCipher cipher ->
288+ Bridge.NeedsSecondFactor secondFactors ->
156− ShowCipherPage cipher
289+ case secondFactors of
290+ [ provider ] ->
291+ SelectSecondFactor
292+ { provider = provider
293+ , requestFromServer = False
294+ }
295+
296+ _ ->
297+ ShowSecondFactorSelect secondFactors
298+ )
299+ ]
300+ ++ optional (List.isEmpty model.notifications |> not) (Time.every 1000 (\t -> ClearNotification { currentTime = t }))
301+ ++ (model.pageStack
302+ |> Nonempty.toList
303+ |> List.map
304+ (\x ->
305+ case x of
306+ LoginModel _ ->
307+ Sub.none
308+
309+ LoadingPage ->
310+ Sub.none
311+
312+ CiphersModel _ ->
313+ Sub.none
314+
315+ EditCipherModel _ ->
316+ Sub.none
317+
318+ MasterPasswordModel _ ->
319+ Sub.none
320+
321+ CipherModel m ->
322+ Cipher.subscriptions m
323+
324+ CaptchaModel _ ->
325+ Sub.none
326+
327+ SecondFactorSelectModel _ ->
328+ Sub.none
329+
330+ SecondFactorModel _ ->
331+ Sub.none
332+ )
333+ )
157 )
334 )
158
335
159
336
@@ -161,6 +338,8 @@ init : ( Model, Cmd Msg )
161init =
338init =
162 ( { notifications = []
339 ( { notifications = []
163 , pageStack = Nonempty.singleton LoadingPage
340 , pageStack = Nonempty.singleton LoadingPage
341+ , userEmail = Nothing
342+ , lastNotificationTime = Time.millisToPosix 0
164 }
343 }
165 , FFI.sendBridge Bridge.Init
344 , FFI.sendBridge Bridge.Init
166 )
345 )
@@ -169,8 +348,11 @@ init =
169view : Model -> Html Msg
348view : Model -> Html Msg
170view model =
349view model =
171 div []
350 div []
172− (maybeList (List.head model.notifications) (Notification.notification CloseNotification)
351+ (maybeList (List.head model.notifications) (Lazy.lazy2 Notification.notification CloseNotification)
173− ++ [ Navigation.showNavigationView model.pageStack showPage ]
352+ ++ [ Lazy.lazy2 Navigation.showNavigationView
353+ model.pageStack
354+ (showPage (Maybe.withDefault "" model.userEmail))
355+ ]
174 )
356 )
175
357
176
358
@@ -182,12 +364,6 @@ mapHead f (Nonempty x xx) =
182doNotStoreInHistory : PageModel -> Bool
364doNotStoreInHistory : PageModel -> Bool
183doNotStoreInHistory page =
365doNotStoreInHistory page =
184 case page of
366 case page of
185− LoginModel _ ->
186− True
187−
188− MasterPasswordModel _ ->
189− True
190−
191 LoadingPage ->
367 LoadingPage ->
192 True
368 True
193
369
@@ -198,49 +374,34 @@ doNotStoreInHistory page =
198update : Msg -> Model -> ( Model, Cmd Msg )
374update : Msg -> Model -> ( Model, Cmd Msg )
199update msg model =
375update msg model =
200 let
376 let
201− processPage : (pageModel -> PageModel) -> ( Result String pageModel, Cmd Msg ) -> ( Model, Cmd Msg )
377+ updatePageStackHead : PageModel -> Model
202− processPage liftModel ( resultModel, cmd ) =
378+ updatePageStackHead mdl =
203− case resultModel of
204− Ok mdl ->
205− ( { model | pageStack = mapHead (always (liftModel mdl)) model.pageStack }, cmd )
379+ { model | pageStack = mapHead (always mdl) model.pageStack }
206−
207− Err err ->
208− ( appendNotification
209− { title = "An error had occured"
210− , message = err
211− , severity = Notification.Error
212− }
213− model
214− , cmd
215− )
216
380
217 appendPageStack : PageModel -> Model
381 appendPageStack : PageModel -> Model
218 appendPageStack mdl =
382 appendPageStack mdl =
219 { model | pageStack = model.pageStack |> Nonempty.toList |> List.filter (doNotStoreInHistory >> not) |> Nonempty mdl }
383 { model | pageStack = model.pageStack |> Nonempty.toList |> List.filter (doNotStoreInHistory >> not) |> Nonempty mdl }
220
384
221− currentPage =
385+ keepStackWith : PageModel -> Model
386+ keepStackWith mdl =
222− model.pageStack |> Nonempty.head
387+ { model | pageStack = Nonempty.cons mdl model.pageStack }
223 in
388 in
224 case msg of
389 case msg of
225− RecieveMessage x ->
390+ ShowError title err ->
226− ( appendNotification
391+ appendNotification
227− { title = "Got a message!"
392+ { title = title
228− , message = x
393+ , message = err
229− , severity = Notification.Info
394+ , severity = Notification.Error
230 }
395 }
231 model
396 model
232− , Cmd.none
233− )
234
397
235− ShowError err ->
398+ ShowInfo title inf ->
236− ( appendNotification
399+ appendNotification
237− { title = "Something went wrong"
400+ { title = title
238− , message = err
401+ , message = inf
239− , severity = Notification.Error
402+ , severity = Notification.Info
240 }
403 }
241 model
404 model
242− , Cmd.none
243− )
244
405
245 CloseNotification ->
406 CloseNotification ->
246 ( { model | notifications = tailEmpty model.notifications }
407 ( { model | notifications = tailEmpty model.notifications }
@@ -248,43 +409,64 @@ update msg model =
248 )
409 )
249
410
250 SubmitLogin data ->
411 SubmitLogin data ->
251− ( model
412+ ( keepStackWith <| LoadingPage
252 , FFI.sendBridge (Bridge.Login data)
413 , FFI.sendBridge (Bridge.Login data)
253 )
414 )
254
415
255− LoginMsg imsg ->
256− case currentPage of
257− LoginModel page ->
258− (Login.page loginCallbacks LoginMsg).update imsg page |> processPage LoginModel
259−
260− _ ->
261− ( model, Cmd.none )
262−
263− CiphersMsg imsg ->
264− case currentPage of
265− CiphersModel page ->
266− (Ciphers.page ciphersCallbacks CiphersMsg).update imsg page |> processPage CiphersModel
267−
268− _ ->
269− ( model, Cmd.none )
270−
271 ShowLoginPage ->
416 ShowLoginPage ->
272− (Login.page loginCallbacks LoginMsg).init ()
417+ ( Login.init loginCallbacks |> LoginModel |> appendPageStack
273− |> Tuple.mapFirst (\pageModel -> appendPageStack <| LoginModel pageModel)
418+ , Cmd.none
419+ )
274
420
275 LoadCiphers ciphers ->
421 LoadCiphers ciphers ->
276− (Ciphers.page ciphersCallbacks CiphersMsg).init ciphers
422+ let
423+ ciphersShown =
424+ model.pageStack
425+ |> Nonempty.toList
426+ |> List.any
427+ (\x ->
428+ case x of
429+ CiphersModel _ ->
430+ True
431+
432+ _ ->
433+ False
434+ )
435+ in
436+ ( if ciphersShown then
437+ { model
438+ | pageStack =
439+ model.pageStack
440+ |> Nonempty.map
441+ (\x ->
442+ case x of
443+ CiphersModel pageModel ->
444+ CiphersModel (Ciphers.updateCiphers ciphers pageModel)
445+
446+ other ->
447+ other
448+ )
449+ }
450+
451+ else
277− |> Tuple.mapFirst (\pageModel -> appendPageStack <| CiphersModel pageModel)
452+ appendPageStack <| CiphersModel (Ciphers.init ciphersCallbacks ciphers)
453+ , Cmd.none
454+ )
278
455
279− OpenCiphersScreen ->
456+ LoginSuccessful ->
457+ ( { model | pageStack = Nonempty.singleton LoadingPage }
280− ( appendPageStack <| LoadingPage, sendBridge Bridge.NeedCiphersList )
458+ , Cmd.batch [ sendBridge Bridge.NeedCiphersList, sendBridge Bridge.NeedEmail ]
459+ )
281
460
282 Reset ->
461 Reset ->
283 init
462 init
284
463
285 ShowMasterPasswordPage { server, login } ->
464 ShowMasterPasswordPage { server, login } ->
286− (MasterPassword.page masterPasswordCallbacks MasterPasswordMsg).init { server = server, login = login }
465+ ( MasterPassword.init masterPasswordCallbacks { server = server, login = login }
287− |> Tuple.mapFirst (\pageModel -> appendPageStack <| MasterPasswordModel pageModel)
466+ |> MasterPasswordModel
467+ |> appendPageStack
468+ , Cmd.none
469+ )
288
470
289 NeedsReset ->
471 NeedsReset ->
290 ( appendPageStack <| LoadingPage, sendBridge Bridge.NeedsReset )
472 ( appendPageStack <| LoadingPage, sendBridge Bridge.NeedsReset )
@@ -292,24 +474,8 @@ update msg model =
292 SendMasterPassword { password } ->
474 SendMasterPassword { password } ->
293 ( appendPageStack <| LoadingPage, sendBridge (Bridge.SendMasterPassword password) )
475 ( appendPageStack <| LoadingPage, sendBridge (Bridge.SendMasterPassword password) )
294
476
295− MasterPasswordMsg imsg ->
296− case currentPage of
297− MasterPasswordModel page ->
298− (MasterPassword.page masterPasswordCallbacks MasterPasswordMsg).update imsg page |> processPage MasterPasswordModel
299−
300− _ ->
301− ( model, Cmd.none )
302−
303− CipherMsg imsg ->
304− case currentPage of
305− CipherModel page ->
306− (Cipher.page cipherCallbacks CipherMsg).update imsg page |> processPage CipherModel
307−
308− _ ->
309− ( model, Cmd.none )
310−
311 ShowCipherPage cipher ->
477 ShowCipherPage cipher ->
312− (Cipher.page cipherCallbacks CipherMsg).init cipher
478+ Cipher.init cipherCallbacks cipher
313 |> Tuple.mapFirst (\pageModel -> appendPageStack <| CipherModel pageModel)
479 |> Tuple.mapFirst (\pageModel -> appendPageStack <| CipherModel pageModel)
314
480
315 RequestCipher id ->
481 RequestCipher id ->
@@ -320,20 +486,283 @@ update msg model =
320 , Cmd.none
486 , Cmd.none
321 )
487 )
322
488
489+ RecieveEmail email ->
490+ ( { model | userEmail = Just email }, Cmd.none )
491+
492+ Copy text ->
493+ ( model, FFI.sendBridge (Bridge.Copy text) )
494+
495+ Open uri ->
496+ ( model, FFI.sendBridge (Bridge.Open uri) )
497+
498+ ShowCaptcha uri ->
499+ ( Captcha.init uri |> CaptchaModel |> appendPageStack, Cmd.none )
500+
501+ ClearNotification { currentTime } ->
502+ if Time.posixToMillis model.lastNotificationTime + notificationLingerSeconds * 1000 > Time.posixToMillis currentTime then
503+ ( model, Cmd.none )
504+
505+ else
506+ ( { model | notifications = List.tail model.notifications |> Maybe.withDefault [] }
507+ , Task.perform UpdateLastNotificatioTime Time.now
508+ )
509+
510+ UpdateLastNotificatioTime time ->
511+ ( { model | lastNotificationTime = time }, Cmd.none )
512+
513+ EditCipher cipher ->
514+ EditCipher.init { fullCipher = cipher, callbacks = editCipherCallbacks }
515+ |> Tuple.mapFirst (\pageModel -> keepStackWith <| EditCipherModel pageModel)
516+
517+ UpdateCipher cipher ->
518+ ( model
519+ , Cmd.batch
520+ [ Bridge.UpdateCipher cipher |> sendBridge
521+ , cipher |> GlobalEvents.UpdateCipher |> FireGlobalEvent |> pureCmd
522+ , PopView |> pureCmd
523+ ]
524+ )
525+
526+ CreateCipher cipher ->
527+ ( model
528+ , Cmd.batch
529+ [ Bridge.CreateCipher cipher |> sendBridge
530+ , PopView |> pureCmd
531+ ]
532+ )
533+
534+ FireGlobalEvent ev ->
535+ ( { model
536+ | pageStack =
537+ model.pageStack
538+ |> Nonempty.map
539+ (\page ->
540+ case page of
541+ LoadingPage ->
542+ LoadingPage
543+
544+ LoginModel m ->
545+ LoginModel m
546+
547+ CiphersModel m ->
548+ Ciphers.event m ev |> CiphersModel
549+
550+ MasterPasswordModel m ->
551+ MasterPasswordModel m
552+
553+ CipherModel m ->
554+ Cipher.event m ev |> CipherModel
555+
556+ EditCipherModel m ->
557+ EditCipher.event m ev |> EditCipherModel
558+
559+ CaptchaModel m ->
560+ CaptchaModel m
561+
562+ SecondFactorSelectModel m ->
563+ SecondFactorSelectModel m
564+
565+ SecondFactorModel m ->
566+ SecondFactorModel m
567+ )
568+ }
569+ , Cmd.none
570+ )
571+
572+ GeneratePassword cfg ->
573+ ( model, FFI.sendBridge (Bridge.GeneratePassword cfg) )
574+
575+ OpenNewCipherEditPage t ->
576+ EditCipher.init
577+ { callbacks = createCipherCallbacks
578+ , fullCipher =
579+ { reprompt = 0
580+ , favorite = False
581+ , id = ""
582+ , name = ""
583+ , collectionIds = []
584+ , cipher =
585+ case t of
586+ Bridge.LoginType ->
587+ Bridge.LoginCipher
588+ { uris = []
589+ , username = Nothing
590+ , password = Nothing
591+ , totp = Nothing
592+ }
593+
594+ Bridge.NoteType ->
595+ Bridge.NoteCipher ""
596+
597+ Bridge.CardType ->
598+ Bridge.CardCipher
599+ { cardholderName = Nothing
600+ , brand = Nothing
601+ , number = Nothing
602+ , expMonth = Nothing
603+ , expYear = Nothing
604+ , code = Nothing
605+ }
606+
607+ Bridge.IdentityType ->
608+ Bridge.IdentityCipher
609+ { title = Nothing
610+ , firstName = Nothing
611+ , middleName = Nothing
612+ , lastName = Nothing
613+ , address1 = Nothing
614+ , address2 = Nothing
615+ , address3 = Nothing
616+ , city = Nothing
617+ , state = Nothing
618+ , postalCode = Nothing
619+ , country = Nothing
620+ , company = Nothing
621+ , email = Nothing
622+ , phone = Nothing
623+ , ssn = Nothing
624+ , username = Nothing
625+ , passportNumber = Nothing
626+ , licenseNumber = Nothing
627+ }
628+ }
629+ }
630+ |> Tuple.mapFirst (\pageModel -> keepStackWith <| EditCipherModel pageModel)
631+
632+ WrongPassword ->
633+ ( model
634+ , Cmd.batch
635+ [ pureCmd PopView
636+ , pureCmd (ShowError "Login failed" "The login details you provided are not valid. Please try again.")
637+ ]
638+ )
639+
640+ DeleteCipher cipher ->
641+ ( model
642+ , Cmd.batch
643+ [ pureCmd PopView
644+ , pureCmd PopView
645+ , FFI.sendBridge (Bridge.DeleteCipher cipher)
646+ ]
647+ )
648+
649+ RequestTotp totp ->
650+ ( model, FFI.sendBridge (Bridge.RequestTotp totp) )
651+
652+ ShowSecondFactorSelect secondFactors ->
653+ ( SecondFactorSelect.init secondFactorSelectCallbacks secondFactors
654+ |> SecondFactorSelectModel
655+ |> appendPageStack
656+ , Cmd.none
657+ )
658+
659+ SelectSecondFactor { provider, requestFromServer } ->
660+ case Nonempty.toList model.pageStack |> findLoginDetails of
661+ Just { email, password, server } ->
662+ ( SecondFactor.init
663+ { provider = provider
664+ , callbacks =
665+ { submit =
666+ \{ token, remember } ->
667+ SubmitLogin
668+ { email = email
669+ , password = password
670+ , server = server
671+ , secondFactor =
672+ Just
673+ { provider = provider
674+ , token = token
675+ , remember = remember
676+ }
677+ }
678+ }
679+ }
680+ |> SecondFactorModel
681+ |> appendPageStack
682+ , Bridge.ChooseSecondFactor
683+ { factor = provider
684+ , email = email
685+ , password = password
686+ , server = server
687+ , requestFromServer = requestFromServer
688+ }
689+ |> FFI.sendBridge
690+ )
691+
692+ Nothing ->
693+ ( model, Cmd.none )
694+
695+ PageMsg pageMsg ->
696+ case ( pageMsg, model.pageStack |> Nonempty.head ) of
697+ ( SecondFactorMsg imsg, SecondFactorModel m ) ->
698+ SecondFactor.update m imsg
699+ |> Tuple.mapBoth
700+ (SecondFactorModel >> updatePageStackHead)
701+ (Maybe.map pureCmd >> Maybe.withDefault Cmd.none)
702+
703+ ( LoginMsg imsg, LoginModel page ) ->
704+ Login.update imsg page
705+ |> Tuple.mapFirst (LoginModel >> updatePageStackHead)
706+
707+ ( CiphersMsg imsg, CiphersModel page ) ->
708+ Ciphers.update imsg page
709+ |> Tuple.mapFirst (CiphersModel >> updatePageStackHead)
710+
711+ ( MasterPasswordMsg imsg, MasterPasswordModel page ) ->
712+ MasterPassword.update imsg page
713+ |> Tuple.mapFirst (MasterPasswordModel >> updatePageStackHead)
714+
715+ ( CipherMsg imsg, CipherModel page ) ->
716+ Cipher.update imsg page
717+ |> Tuple.mapFirst (CipherModel >> updatePageStackHead)
718+
719+ ( EditCipherMsg imsg, EditCipherModel page ) ->
720+ EditCipher.update imsg page
721+ |> Tuple.mapFirst (EditCipherModel >> updatePageStackHead)
722+
723+ ( SecondFactorSelectMsg imsg, SecondFactorSelectModel m ) ->
724+ SecondFactorSelect.update imsg m
725+ |> Tuple.mapFirst (SecondFactorSelectModel >> updatePageStackHead)
726+
727+ _ ->
728+ ( model, Cmd.none )
729+
323
730
324loginCallbacks : Login.Callbacks Msg
731loginCallbacks : Login.Callbacks Msg
325loginCallbacks =
732loginCallbacks =
733+ { submit =
734+ \{ email, server, password } ->
326− { submit = SubmitLogin }
735+ SubmitLogin
736+ { email = email
737+ , password = password
738+ , server = server
739+ , secondFactor = Nothing
740+ }
741+ }
327
742
328
743
329ciphersCallbacks : Ciphers.Callbacks Msg
744ciphersCallbacks : Ciphers.Callbacks Msg
330ciphersCallbacks =
745ciphersCallbacks =
331− { selected = RequestCipher }
746+ { selected = RequestCipher
747+ , logOut = NeedsReset
748+ , createNewCipher = OpenNewCipherEditPage
749+ , lift = CiphersMsg >> PageMsg
750+ }
332
751
333
752
334−cipherCallbacks : Cipher.Callbacks
753+cipherCallbacks : Cipher.Callbacks Msg
335cipherCallbacks =
754cipherCallbacks =
336− {}
755+ { copy = Copy, open = Open, edit = EditCipher, needTotp = RequestTotp }
756+
757+
758+editCipherCallbacks : EditCipher.Callbacks Msg
759+editCipherCallbacks =
760+ { save = UpdateCipher, generatePassword = GeneratePassword, delete = Just DeleteCipher }
761+
762+
763+createCipherCallbacks : EditCipher.Callbacks Msg
764+createCipherCallbacks =
765+ { save = CreateCipher, generatePassword = GeneratePassword, delete = Nothing }
337
766
338
767
339masterPasswordCallbacks : MasterPassword.Callbacks Msg
768masterPasswordCallbacks : MasterPassword.Callbacks Msg
@@ -341,3 +770,11 @@ masterPasswordCallbacks =
341 { submit = SendMasterPassword
770 { submit = SendMasterPassword
342 , reset = NeedsReset
771 , reset = NeedsReset
343 }
772 }
773+
774+
775+secondFactorSelectCallbacks : SecondFactorSelect.Callbacks Msg
776+secondFactorSelectCallbacks =
777+ { selectFactor =
778+ \provider ->
779+ SelectSecondFactor { provider = provider, requestFromServer = True }
780+ }
frontend/Modal.elmnew file+33
@@ -0,0 +1,33 @@
1+module Modal exposing (..)
2+
3+import Html exposing (..)
4+import Html.Attributes as Attr
5+import Html.Events as Ev
6+
7+
8+type alias ModelConfig msg =
9+ { title : String
10+ , body : List (Html msg)
11+ , close : msg
12+ , footer : List (Html msg)
13+ }
14+
15+
16+modal : ModelConfig msg -> Html msg
17+modal cfg =
18+ div
19+ [ Attr.class "p-modal" ]
20+ [ section
21+ [ Attr.class "p-modal__dialog"
22+ ]
23+ (header
24+ [ Attr.class "p-modal__header"
25+ ]
26+ [ h2 [ Attr.class "p-modal__title" ] [ text cfg.title ]
27+ , button [ Attr.class "p-modal__close", Ev.onClick cfg.close ] [ text "Close" ]
28+ ]
29+ :: cfg.body
30+ ++ [ footer [ Attr.class "p-modal__footer" ] cfg.footer
31+ ]
32+ )
33+ ]
frontend/Page.elm+23−13
@@ -1,22 +1,32 @@
1module Page exposing (..)
1module Page exposing (..)
2
2
3+import Browser
3import Html exposing (..)
4import Html exposing (..)
4import Html.Events exposing (..)
5import Html.Events exposing (..)
5
6
6
7
7−type alias Env =
8+page :
8− {}
9+ { init : model
9−
10+ , view : model -> List (Html msg)
11+ , update : msg -> model -> ( model, Cmd (Maybe msg) )
12+ }
13+ -> Program () model (Maybe msg)
14+page { init, view, update } =
15+ Browser.element
16+ { subscriptions = always Sub.none
17+ , init = \_ -> ( init, Cmd.none )
18+ , view = view >> Html.div [] >> Html.map Just
19+ , update =
20+ \mmsg model ->
21+ case mmsg of
22+ Nothing ->
23+ ( model, Cmd.none )
10
24
11−type alias Page init model imsg emsg =
25+ Just msg ->
12− (imsg -> emsg)
26+ update msg model
13− -> InitializedPage init model imsg emsg
27+ }
14
28
15
29
16−type alias InitializedPage init model imsg emsg =
30+noUpdate : a -> model -> ( model, Cmd msg )
31+noUpdate _ model =
17− { init : init -> ( model, Cmd emsg )
32+ ( model, Cmd.none )
18− , view : model -> List (Html emsg)
19− , update : imsg -> model -> ( Result String model, Cmd emsg )
20− , subscriptions : model -> Sub emsg
21− , title : model -> String
22− }
frontend/Pages/Captcha.elmnew file+45
@@ -0,0 +1,45 @@
1+module Pages.Captcha exposing (HCaptchSiteKey, Model, init, main, title, view)
2+
3+import Html exposing (..)
4+import Html.Attributes as Attr
5+import Page exposing (..)
6+import Utils exposing (..)
7+
8+
9+main : Program () Model (Maybe (Maybe ()))
10+main =
11+ page
12+ { init = init "10000000-ffff-ffff-ffff-000000000001"
13+ , view = view
14+ , update = noUpdate
15+ }
16+
17+
18+type alias HCaptchSiteKey =
19+ String
20+
21+
22+type alias Model =
23+ { siteKey : HCaptchSiteKey
24+ }
25+
26+
27+title : String
28+title =
29+ "Captcha"
30+
31+
32+init : HCaptchSiteKey -> Model
33+init siteKey =
34+ { siteKey = siteKey }
35+
36+
37+view : Model -> List (Html msg)
38+view { siteKey } =
39+ [ iframe
40+ [ Attr.src ("https://iko.soy/warden/hcaptcha.html?" ++ siteKey)
41+ , Attr.style "width" "100%"
42+ , Attr.style "height" "80vh"
43+ ]
44+ []
45+ ]
frontend/Pages/Cipher.elm+177−63
@@ -1,66 +1,140 @@
1−module Pages.Cipher exposing (..)
1+module Pages.Cipher exposing
2+ ( Callbacks
3+ , Model
4+ , Msg
5+ , event
6+ , init
7+ , subscriptions
8+ , title
9+ , update
10+ , view
11+ )
2
12
3import Bridge
13import Bridge
14+import GlobalEvents
4import Html exposing (..)
15import Html exposing (..)
5import Html.Attributes as Attr
16import Html.Attributes as Attr
6import Html.Events as Ev
17import Html.Events as Ev
7import Page exposing (..)
18import Page exposing (..)
19+import Time
8import Utils exposing (..)
20import Utils exposing (..)
9
21
10
22
11−type alias Model =
23+type alias Model emsg =
12− { cipher : Bridge.Sub_LoadCipher
24+ { cipher : Bridge.FullCipher
13 , passwordHidden : Bool
25 , passwordHidden : Bool
14 , cvvHidden : Bool
26 , cvvHidden : Bool
27+ , decodedTotp : Maybe { code : String, interval : Int }
28+ , callbacks : Callbacks emsg
15 }
29 }
16
30
17
31
18type Msg
32type Msg
19− = Noop
20− | TogglePasswordVisiblity
33+ = TogglePasswordVisiblity
21 | ToggleCVVVisiblity
34 | ToggleCVVVisiblity
35+ | Copy String
36+ | Open String
37+ | Edit Bridge.FullCipher
22
38
23
39
24−type alias Callbacks =
40+type alias Callbacks msg =
41+ { copy : String -> msg
42+ , open : String -> msg
43+ , edit : Bridge.FullCipher -> msg
44+ , needTotp : String -> msg
25− {}
45+ }
26
46
27
47
28−page : Callbacks -> Page Bridge.Sub_LoadCipher Model Msg emsg
48+event : Model emsg -> GlobalEvents.Event -> Model emsg
29−page callbacks liftMsg =
49+event model ev =
30− { init = \cipher -> Tuple.mapSecond (Cmd.map liftMsg) (init cipher)
50+ case ev of
31− , view = \model -> view model |> List.map (Html.map liftMsg)
51+ GlobalEvents.UpdateCipher c ->
32− , update = \msg model -> update callbacks liftMsg msg model
52+ { model
33− , subscriptions = \model -> subscriptions model |> Sub.map liftMsg
53+ | cipher =
34− , title = \{ cipher } -> cipher.name
54+ if c.id == model.cipher.id then
35− }
55+ c
56+
57+ else
58+ model.cipher
59+ }
60+
61+ GlobalEvents.DecodedTotp { source, code, interval } ->
62+ case model.cipher.cipher of
63+ Bridge.LoginCipher { totp } ->
64+ if totp == Just source then
65+ { model | decodedTotp = Just { code = code, interval = interval } }
66+
67+ else
68+ model
36
69
70+ _ ->
71+ model
37
72
73+ _ ->
74+ model
75+
76+
77+title : Model emsg -> List (Html Msg)
78+title { cipher } =
79+ [ text cipher.name
80+ , span [ Attr.class "u-float-right" ] [ iconButton "edit" (Edit cipher) ]
81+ ]
82+
83+
38−init : Bridge.Sub_LoadCipher -> ( Model, Cmd Msg )
84+init : Callbacks emsg -> Bridge.FullCipher -> ( Model emsg, Cmd emsg )
39−init cipher =
85+init callbacks cipher =
40 ( { cipher = cipher
86 ( { cipher = cipher
41 , passwordHidden = True
87 , passwordHidden = True
42 , cvvHidden = True
88 , cvvHidden = True
89+ , decodedTotp = Nothing
90+ , callbacks = callbacks
43 }
91 }
92+ , case cipher.cipher of
93+ Bridge.LoginCipher { totp } ->
94+ case totp of
95+ Just x ->
96+ callbacks.needTotp x |> pureCmd
97+
98+ Nothing ->
99+ Cmd.none
100+
101+ _ ->
44− , Cmd.none
102+ Cmd.none
45 )
103 )
46
104
47
105
48−update : Callbacks -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
106+update : Msg -> Model emsg -> ( Model emsg, Cmd emsg )
49−update {} _ msg model =
107+update msg model =
50 case msg of
108 case msg of
51− Noop ->
52− ( Ok model, Cmd.none )
53−
54 TogglePasswordVisiblity ->
109 TogglePasswordVisiblity ->
55− ( Ok { model | passwordHidden = not model.passwordHidden }, Cmd.none )
110+ ( { model | passwordHidden = not model.passwordHidden }, Cmd.none )
56
111
57 ToggleCVVVisiblity ->
112 ToggleCVVVisiblity ->
58− ( Ok { model | cvvHidden = not model.cvvHidden }, Cmd.none )
113+ ( { model | cvvHidden = not model.cvvHidden }, Cmd.none )
114+
115+ Copy text ->
116+ ( model, model.callbacks.copy text |> pureCmd )
117+
118+ Open uri ->
119+ ( model, model.callbacks.open uri |> pureCmd )
120+
121+ Edit cipher ->
122+ ( model, model.callbacks.edit cipher |> pureCmd )
123+
124+
125+subscriptions : Model msg -> Sub msg
126+subscriptions { cipher, callbacks } =
127+ case cipher.cipher of
128+ Bridge.LoginCipher { totp } ->
129+ case totp of
130+ Just x ->
131+ Time.every 1000 (always (callbacks.needTotp x))
59
132
133+ Nothing ->
134+ Sub.none
60
135
61−subscriptions : Model -> Sub Msg
136+ _ ->
62−subscriptions _ =
63− Sub.none
137+ Sub.none
64
138
65
139
66row : { name : String, value : String, nameIcon : String, icons : List ( String, msg ) } -> Html msg
140row : { name : String, value : String, nameIcon : String, icons : List ( String, msg ) } -> Html msg
@@ -73,14 +147,14 @@ row { name, value, nameIcon, icons } =
73 ]
147 ]
74
148
75
149
76−view : Model -> List (Html Msg)
150+view : Model emsg -> List (Html Msg)
77−view { passwordHidden, cipher, cvvHidden } =
151+view { passwordHidden, cipher, cvvHidden, decodedTotp } =
78− (case cipher.cipherType of
152+ (case cipher.cipher of
79− Bridge.LoginCipher { username, password, uris } ->
153+ Bridge.LoginCipher { username, password, uris, totp } ->
80 maybeList username
154 maybeList username
81 (\x ->
155 (\x ->
82 row
156 row
83− { name = "Username", value = x, nameIcon = "user", icons = [ ( "copy", Noop ) ] }
157+ { name = "Username", value = x, nameIcon = "user", icons = [ ( "copy", Copy x ) ] }
84 )
158 )
85 ++ maybeList password
159 ++ maybeList password
86 (\x ->
160 (\x ->
@@ -93,30 +167,60 @@ view { passwordHidden, cipher, cvvHidden } =
93 else
167 else
94 x
168 x
95 , nameIcon = "security"
169 , nameIcon = "security"
96− , icons = [ ( hiddenButtonIcon passwordHidden, TogglePasswordVisiblity ), ( "copy", Noop ) ]
170+ , icons = [ ( hiddenButtonIcon passwordHidden, TogglePasswordVisiblity ), ( "copy", Copy x ) ]
171+ }
172+ )
173+ ++ maybeList totp
174+ (\_ ->
175+ row
176+ { name = "One-time password"
177+ , value =
178+ case decodedTotp of
179+ Nothing ->
180+ "Loading..."
181+
182+ Just { code } ->
183+ code
184+ , nameIcon = "revisions"
185+ , icons =
186+ case decodedTotp of
187+ Nothing ->
188+ []
189+
190+ Just { code } ->
191+ [ ( "copy", Copy code ) ]
97 }
192 }
98 )
193 )
99 ++ (uris
194 ++ (uris
100 |> List.map
195 |> List.map
101 (\uri ->
196 (\uri ->
102 row
197 row
198+ { name = "URL"
199+ , value = uri
200+ , nameIcon = "get-link"
103− { name = "URL", value = uri, nameIcon = "get-link", icons = [ ( "external-link", Noop ), ( "copy", Noop ) ] }
201+ , icons = [ ( "external-link", Open uri ), ( "copy", Copy uri ) ]
202+ }
104 )
203 )
105 )
204 )
106
205
107 Bridge.NoteCipher contents ->
206 Bridge.NoteCipher contents ->
108− [ row { name = "Note", value = contents, nameIcon = "edit", icons = [] }
207+ [ row { name = "Note", value = contents, nameIcon = "switcher-environments", icons = [] }
109 ]
208 ]
110
209
111 Bridge.CardCipher { number, code, cardholderName, expMonth, expYear } ->
210 Bridge.CardCipher { number, code, cardholderName, expMonth, expYear } ->
112− maybeList number (\x -> row { name = "Card number", value = x, nameIcon = "containers", icons = [ ( "copy", Noop ) ] })
211+ maybeList number (\x -> row { name = "Card number", value = x, nameIcon = "containers", icons = [ ( "copy", Copy x ) ] })
113 ++ optional (expMonth /= Nothing || expYear /= Nothing)
212 ++ optional (expMonth /= Nothing || expYear /= Nothing)
114 (row
213 (row
115− { name = "Expiration date"
214+ (let
215+ x =
116− , value = [ expMonth, expYear ] |> List.concatMap (flip maybeList identity) |> String.join "/"
216+ [ expMonth, expYear ] |> List.concatMap (flip maybeList identity) |> String.join "/"
217+ in
218+ { name = "Expiration date"
219+ , value = x
117− , nameIcon = "timed-out"
220+ , nameIcon = "timed-out"
118− , icons = [ ( "copy", Noop ) ]
221+ , icons = [ ( "copy", Copy x ) ]
119− }
222+ }
223+ )
120 )
224 )
121 ++ maybeList code
225 ++ maybeList code
122 (\x ->
226 (\x ->
@@ -129,20 +233,25 @@ view { passwordHidden, cipher, cvvHidden } =
129 else
233 else
130 x
234 x
131 , nameIcon = "security"
235 , nameIcon = "security"
132− , icons = [ ( hiddenButtonIcon cvvHidden, ToggleCVVVisiblity ), ( "copy", Noop ) ]
236+ , icons = [ ( hiddenButtonIcon cvvHidden, ToggleCVVVisiblity ), ( "copy", Copy x ) ]
133 }
237 }
134 )
238 )
135 ++ maybeList cardholderName
239 ++ maybeList cardholderName
136− (\x -> row { name = "Cardholder name", value = x, nameIcon = "user", icons = [ ( "copy", Noop ) ] })
240+ (\x -> row { name = "Cardholder name", value = x, nameIcon = "user", icons = [ ( "copy", Copy x ) ] })
137
241
138− Bridge.IdentityCipher { address1, address2, address3, city, company, country, email, firstName, lastName, licenseNumber, middleName, passportNumber, phone, postalCode, ssn, state, title, username } ->
242+ Bridge.IdentityCipher { address1, address2, address3, company, email, firstName, lastName, licenseNumber, middleName, passportNumber, phone, ssn, username } ->
139 optional (firstName /= Nothing || middleName /= Nothing || lastName /= Nothing)
243 optional (firstName /= Nothing || middleName /= Nothing || lastName /= Nothing)
140 (row
244 (row
141− { name = "Name"
245+ (let
246+ x =
142− , value = [ firstName, middleName, lastName ] |> List.concatMap (flip maybeList identity) |> unwords
247+ [ firstName, middleName, lastName ] |> List.concatMap (flip maybeList identity) |> unwords
248+ in
249+ { name = "Name"
250+ , value = x
143− , nameIcon = ""
251+ , nameIcon = ""
144− , icons = [ ( "copy", Noop ) ]
252+ , icons = [ ( "copy", Copy x ) ]
145− }
253+ }
254+ )
146 )
255 )
147 ++ maybeList username
256 ++ maybeList username
148 (\x ->
257 (\x ->
@@ -150,7 +259,7 @@ view { passwordHidden, cipher, cvvHidden } =
150 { name = "Username"
259 { name = "Username"
151 , value = x
260 , value = x
152 , nameIcon = ""
261 , nameIcon = ""
153− , icons = [ ( "copy", Noop ) ]
262+ , icons = [ ( "copy", Copy x ) ]
154 }
263 }
155 )
264 )
156 ++ maybeList company
265 ++ maybeList company
@@ -159,16 +268,16 @@ view { passwordHidden, cipher, cvvHidden } =
159 { name = "Company"
268 { name = "Company"
160 , value = x
269 , value = x
161 , nameIcon = ""
270 , nameIcon = ""
162− , icons = [ ( "copy", Noop ) ]
271+ , icons = [ ( "copy", Copy x ) ]
163 }
272 }
164 )
273 )
165 ++ maybeList ssn
274 ++ maybeList ssn
166 (\x ->
275 (\x ->
167 row
276 row
168− { name = "Socia security number"
277+ { name = "Social security number"
169 , value = x
278 , value = x
170 , nameIcon = ""
279 , nameIcon = ""
171− , icons = [ ( "copy", Noop ) ]
280+ , icons = [ ( "copy", Copy x ) ]
172 }
281 }
173 )
282 )
174 ++ maybeList passportNumber
283 ++ maybeList passportNumber
@@ -177,7 +286,7 @@ view { passwordHidden, cipher, cvvHidden } =
177 { name = "Passport number"
286 { name = "Passport number"
178 , value = x
287 , value = x
179 , nameIcon = ""
288 , nameIcon = ""
180− , icons = [ ( "copy", Noop ) ]
289+ , icons = [ ( "copy", Copy x ) ]
181 }
290 }
182 )
291 )
183 ++ maybeList licenseNumber
292 ++ maybeList licenseNumber
@@ -186,7 +295,7 @@ view { passwordHidden, cipher, cvvHidden } =
186 { name = "License Number"
295 { name = "License Number"
187 , value = x
296 , value = x
188 , nameIcon = ""
297 , nameIcon = ""
189− , icons = [ ( "copy", Noop ) ]
298+ , icons = [ ( "copy", Copy x ) ]
190 }
299 }
191 )
300 )
192 ++ maybeList email
301 ++ maybeList email
@@ -195,7 +304,7 @@ view { passwordHidden, cipher, cvvHidden } =
195 { name = "Email"
304 { name = "Email"
196 , value = x
305 , value = x
197 , nameIcon = ""
306 , nameIcon = ""
198− , icons = [ ( "copy", Noop ) ]
307+ , icons = [ ( "copy", Copy x ) ]
199 }
308 }
200 )
309 )
201 ++ maybeList phone
310 ++ maybeList phone
@@ -204,16 +313,21 @@ view { passwordHidden, cipher, cvvHidden } =
204 { name = "Phone number"
313 { name = "Phone number"
205 , value = x
314 , value = x
206 , nameIcon = ""
315 , nameIcon = ""
207− , icons = [ ( "copy", Noop ) ]
316+ , icons = [ ( "copy", Copy x ) ]
208 }
317 }
209 )
318 )
210 ++ optional (address1 /= Nothing || address2 /= Nothing || address3 /= Nothing)
319 ++ optional (address1 /= Nothing || address2 /= Nothing || address3 /= Nothing)
211 (row
320 (row
212− { name = "Address"
321+ (let
322+ x =
213− , value = [ address1, address2, address3 ] |> List.concatMap (flip maybeList identity) |> unwords
323+ [ address1, address2, address3 ] |> List.concatMap (flip maybeList identity) |> unwords
324+ in
325+ { name = "Address"
326+ , value = x
214− , nameIcon = ""
327+ , nameIcon = ""
215− , icons = [ ( "copy", Noop ) ]
328+ , icons = [ ( "copy", Copy x ) ]
216− }
329+ }
330+ )
217 )
331 )
218 )
332 )
219 |> List.intersperse (hr [] [])
333 |> List.intersperse (hr [] [])
frontend/Pages/Ciphers.elm+187−63
@@ -1,26 +1,50 @@
1−module Pages.Ciphers exposing (Callbacks, Model, Msg, menuConfig, page)
1+module Pages.Ciphers exposing
2+ ( Callbacks
3+ , Model
4+ , Msg
5+ , event
6+ , init
7+ , menuConfig
8+ , title
9+ , update
10+ , updateCiphers
11+ , view
12+ )
2
13
3import Bridge
14import Bridge
15+import GlobalEvents
4import Html exposing (..)
16import Html exposing (..)
5import Html.Attributes as Attr
17import Html.Attributes as Attr
6import Html.Events as Ev
18import Html.Events as Ev
19+import Html.Keyed as Keyed
20+import Html.Lazy as Lazy
21+import InfiniteScroll
7import Page exposing (..)
22import Page exposing (..)
8import Pages.Navigation as Navigation
23import Pages.Navigation as Navigation
9import Search
24import Search
10import Utils exposing (..)
25import Utils exposing (..)
11
26
12
27
13−type alias Model =
28+type alias Model emsg =
14 { ciphers : Bridge.Sub_LoadCiphers_List
29 { ciphers : Bridge.Sub_LoadCiphers_List
15 , search : String
30 , search : String
16 , menuVisible : Bool
31 , menuVisible : Bool
17 , ciphersListFilter : CiphersFilter
32 , ciphersListFilter : CiphersFilter
33+ , scroll : InfiniteScroll.Model Msg
34+ , sublist : { end : Int }
35+ , cipherSelectDropdownVisivble : Bool
36+ , callbacks : Callbacks emsg
18 }
37 }
19
38
20
39
40+updateCiphers : Bridge.Sub_LoadCiphers_List -> Model emsg -> Model emsg
41+updateCiphers ciphers model =
42+ { model | ciphers = ciphers }
43+
44+
21type CiphersFilter
45type CiphersFilter
22 = AllCiphers
46 = AllCiphers
23− | SpecificCiphers Bridge.Sub_LoadCiphers_cipherType
47+ | SpecificCiphers Bridge.CipherType
24
48
25
49
26applyCipherFilter : CiphersFilter -> Bridge.Sub_LoadCiphers_List -> Bridge.Sub_LoadCiphers_List
50applyCipherFilter : CiphersFilter -> Bridge.Sub_LoadCiphers_List -> Bridge.Sub_LoadCiphers_List
@@ -33,9 +57,9 @@ applyCipherFilter filter list =
33 list |> List.filter (.cipherType >> (==) t)
57 list |> List.filter (.cipherType >> (==) t)
34
58
35
59
36−menuConfig : Model -> Navigation.MenuConfig Msg
60+menuConfig : String -> Model emsg -> Navigation.MenuConfig Msg
37−menuConfig { ciphersListFilter, menuVisible } =
61+menuConfig email { ciphersListFilter, menuVisible } =
38− { title = "TODO"
62+ { title = email
39 , items =
63 , items =
40 [ { icon = "units"
64 [ { icon = "units"
41 , name = "All items"
65 , name = "All items"
@@ -58,13 +82,14 @@ menuConfig { ciphersListFilter, menuVisible } =
58 , current = ciphersListFilter == SpecificCiphers Bridge.CardType
82 , current = ciphersListFilter == SpecificCiphers Bridge.CardType
59 }
83 }
60 , { icon = "user"
84 , { icon = "user"
61− , name = "Identities"
85+ , name = "Contacts"
62 , trigger = UpdateFilter (SpecificCiphers Bridge.IdentityType)
86 , trigger = UpdateFilter (SpecificCiphers Bridge.IdentityType)
63 , current = ciphersListFilter == SpecificCiphers Bridge.IdentityType
87 , current = ciphersListFilter == SpecificCiphers Bridge.IdentityType
64 }
88 }
65 ]
89 ]
66 , toggle = ToggleMenu
90 , toggle = ToggleMenu
67 , visible = menuVisible
91 , visible = menuVisible
92+ , logOut = LogOut
68 }
93 }
69
94
70
95
@@ -73,102 +98,201 @@ type Msg
73 | UpdateSearch String
98 | UpdateSearch String
74 | ToggleMenu
99 | ToggleMenu
75 | UpdateFilter CiphersFilter
100 | UpdateFilter CiphersFilter
101+ | LogOut
102+ | LoadMore InfiniteScroll.Direction
103+ | InfiniteScrollMsg InfiniteScroll.Msg
104+ | CreateNewCipher Bridge.CipherType
105+ | ToggleSelectCipherTypeVisible
106+ | SelectCipher String
76
107
77
108
78type alias Callbacks msg =
109type alias Callbacks msg =
79 { selected : String -> msg
110 { selected : String -> msg
111+ , logOut : msg
112+ , createNewCipher : Bridge.CipherType -> msg
113+ , lift : Msg -> msg
80 }
114 }
81
115
82
116
83−page : Callbacks emsg -> Page Bridge.Sub_LoadCiphers_List Model Msg emsg
117+event : Model emsg -> GlobalEvents.Event -> Model emsg
84−page callbacks liftMsg =
118+event model ev =
85− { init = \ciphers -> Tuple.mapSecond (Cmd.map liftMsg) (init ciphers)
119+ case ev of
86− , view = view callbacks liftMsg
120+ GlobalEvents.UpdateCipher c ->
87− , update = \msg model -> update callbacks liftMsg msg model
121+ { model
88− , subscriptions = \model -> subscriptions model |> Sub.map liftMsg
122+ | ciphers =
89− , title =
123+ model.ciphers
90− \{ ciphersListFilter } ->
124+ |> List.map
91− case ciphersListFilter of
125+ (\cipher ->
92− AllCiphers ->
126+ if cipher.id == c.id then
93− "All items"
127+ { cipher | name = c.name }
94
128
95− SpecificCiphers cipherType ->
129+ else
96− case cipherType of
130+ cipher
97− Bridge.CardType ->
131+ )
98− "Cards"
132+ }
99
133
100− Bridge.IdentityType ->
134+ _ ->
101− "Identities"
135+ model
102
136
103− Bridge.LoginType ->
104− "Passwords"
105
137
106− Bridge.NoteType ->
138+title : Model emsg -> List (Html Msg)
107− "Notes"
139+title { ciphersListFilter, cipherSelectDropdownVisivble } =
108− }
140+ [ text
141+ (case ciphersListFilter of
142+ AllCiphers ->
143+ "All items"
109
144
145+ SpecificCiphers cipherType ->
146+ case cipherType of
147+ Bridge.CardType ->
148+ "Cards"
110
149
111−init : Bridge.Sub_LoadCiphers_List -> ( Model, Cmd Msg )
150+ Bridge.IdentityType ->
112−init ciphers =
151+ "Contacts"
113− ( { ciphers = ciphers
152+
114− , search = ""
153+ Bridge.LoginType ->
115− , menuVisible = False
154+ "Passwords"
116− , ciphersListFilter = AllCiphers
155+
117− }
156+ Bridge.NoteType ->
118− , Cmd.none
157+ "Notes"
158+ )
159+ , span [ Attr.class "u-float-right" ]
160+ [ span [ Attr.class "p-contextual-menu" ]
161+ ([ span [] [ iconButton "plus" ToggleSelectCipherTypeVisible ]
162+ ]
163+ ++ optionals cipherSelectDropdownVisivble
164+ [ div
165+ [ Attr.class "overlay"
166+ , Ev.onClick ToggleSelectCipherTypeVisible
167+ ]
168+ []
169+ , span
170+ [ Attr.class "p-contextual-menu__dropdown"
171+ , Attr.attribute "aria-hidden" "false"
172+ ]
173+ (let
174+ create t =
175+ CreateNewCipher t
176+ in
177+ [ button
178+ [ Attr.class "p-contextual-menu__link"
179+ , Ev.onClick (create Bridge.LoginType)
180+ ]
181+ [ text "Login" ]
182+ , button
183+ [ Attr.class "p-contextual-menu__link"
184+ , Ev.onClick (create Bridge.CardType)
185+ ]
186+ [ text "Card" ]
187+ , button
188+ [ Attr.class "p-contextual-menu__link"
189+ , Ev.onClick (create Bridge.NoteType)
190+ ]
191+ [ text "Note" ]
192+ , button
193+ [ Attr.class "p-contextual-menu__link"
194+ , Ev.onClick (create Bridge.IdentityType)
195+ ]
196+ [ text "Contact" ]
197+ ]
119− )
198+ )
199+ ]
200+ )
201+ ]
202+ ]
120
203
121
204
122−update : Callbacks emsg -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
205+init : Callbacks emsg -> Bridge.Sub_LoadCiphers_List -> Model emsg
206+init callbacks ciphers =
207+ { ciphers = ciphers
208+ , search = ""
209+ , menuVisible = False
210+ , ciphersListFilter = AllCiphers
211+ , scroll = InfiniteScroll.init (LoadMore >> pureCmd)
212+ , sublist = { end = 20 }
213+ , cipherSelectDropdownVisivble = False
214+ , callbacks = callbacks
215+ }
216+
217+
218+update : Msg -> Model emsg -> ( Model emsg, Cmd emsg )
123−update {} liftMsg msg model =
219+update msg model =
124 case msg of
220 case msg of
125 Noop ->
221 Noop ->
126− ( Ok model, Cmd.none )
222+ ( model, Cmd.none )
127
223
128 UpdateSearch s ->
224 UpdateSearch s ->
129− ( Ok { model | search = s }, Cmd.none )
225+ ( { model | search = s }, Cmd.none )
130
226
131 ToggleMenu ->
227 ToggleMenu ->
132− ( Ok { model | menuVisible = not model.menuVisible }, Cmd.none )
228+ ( { model | menuVisible = not model.menuVisible }, Cmd.none )
133
229
134 UpdateFilter filter ->
230 UpdateFilter filter ->
135− ( Ok { model | ciphersListFilter = filter, menuVisible = False }, Cmd.none )
231+ ( { model | ciphersListFilter = filter, menuVisible = False }, Cmd.none )
232+
233+ LogOut ->
234+ ( model, pureCmd model.callbacks.logOut )
235+
236+ LoadMore direction ->
237+ let
238+ newSublist =
239+ case direction of
240+ InfiniteScroll.Top ->
241+ { end = model.sublist.end }
242+
243+ InfiniteScroll.Bottom ->
244+ { end = min (model.sublist.end + 10) (List.length model.ciphers) }
245+ in
246+ ( { model | sublist = newSublist, scroll = InfiniteScroll.stopLoading model.scroll }, Cmd.none )
247+
248+ InfiniteScrollMsg msg_ ->
249+ let
250+ ( scroll, cmd ) =
251+ InfiniteScroll.update InfiniteScrollMsg msg_ model.scroll
252+ in
253+ ( { model | scroll = scroll }, Cmd.map model.callbacks.lift cmd )
254+
255+ CreateNewCipher t ->
256+ ( { model | cipherSelectDropdownVisivble = False }, pureCmd (model.callbacks.createNewCipher t) )
136
257
258+ ToggleSelectCipherTypeVisible ->
259+ ( { model | cipherSelectDropdownVisivble = not model.cipherSelectDropdownVisivble }
260+ , Cmd.none
261+ )
137
262
138−subscriptions : Model -> Sub Msg
263+ SelectCipher id ->
139−subscriptions model =
264+ ( model, model.callbacks.selected id |> pureCmd )
140− Sub.none
141
265
142
266
143−view : Callbacks emsg -> (Msg -> emsg) -> Model -> List (Html emsg)
267+view : Model emsg -> List (Html Msg)
144−view { selected } liftMsg { ciphers, search, ciphersListFilter } =
268+view { ciphers, search, ciphersListFilter, sublist } =
145 [ input
269 [ input
146 [ Attr.type_ "search"
270 [ Attr.type_ "search"
147 , Attr.value search
271 , Attr.value search
148− , Ev.onInput (liftMsg << UpdateSearch)
272+ , Ev.onInput UpdateSearch
149 , Attr.placeholder "Search"
273 , Attr.placeholder "Search"
150 , Attr.attribute "autocomplete" "off"
274 , Attr.attribute "autocomplete" "off"
151 ]
275 ]
152 []
276 []
153− , ul [ Attr.class "p-list--divided" ]
277+ , ciphers
278+ |> applyCipherFilter ciphersListFilter
279+ |> Search.searchList search .name (List.take sublist.end >> Lazy.lazy showCiphers)
280+ ]
281+
282+
283+showCiphers : Bridge.Sub_LoadCiphers_List -> Html Msg
284+showCiphers ciphers =
285+ Keyed.ul [ Attr.class "p-list--divided", InfiniteScroll.infiniteScroll InfiniteScrollMsg, Attr.class "ciphers-list no-scroll-bar" ]
154 (ciphers
286 (ciphers
155− |> applyCipherFilter ciphersListFilter
156− |> Search.searchList (cipherSearch search)
157 |> List.map
287 |> List.map
158 (\{ name, date, id } ->
288 (\{ name, date, id } ->
289+ ( id
159− li [ Attr.class "p-list__item cipher-row", Ev.onClick (selected id) ]
290+ , li [ Attr.class "p-list__item cipher-row", Ev.onClick (SelectCipher id) ]
160 [ div [ Attr.class "cipher-row-container" ]
291 [ div [ Attr.class "cipher-row-container" ]
161 [ p [] [ text name ]
292 [ p [] [ text name ]
162− , p [ Attr.class "p-text--small u-align-text--right u-text--muted" ] [ text date ]
293+ , p [ Attr.class "p-text--small u-align-text--right u-text--muted hide-in-test" ] [ text date ]
163 ]
294 ]
164 ]
295 ]
296+ )
165 )
297 )
166 )
298 )
167− ]
168−
169−
170−cipherSearch : String -> Search.Search Bridge.Sub_LoadCiphers
171−cipherSearch query =
172− Search.Search
173− [ ( query, \q { name } -> Search.search q name )
174− ]
frontend/Pages/EditCipher.elmnew file+475
@@ -0,0 +1,475 @@
1+module Pages.EditCipher exposing (Callbacks, Model, Msg, event, init, title, update, view)
2+
3+import Bridge
4+import GlobalEvents
5+import Html exposing (..)
6+import Html.Attributes as Attr
7+import Html.Events as Ev
8+import Logic.Cipher exposing (normalizeCipher)
9+import Modal exposing (modal)
10+import Page exposing (..)
11+import Pages.MasterPassword exposing (Msg)
12+import Pages.PasswordGenerator as PasswordGenerator
13+import Utils exposing (..)
14+
15+
16+type alias Model msg =
17+ { fullCipher : Bridge.FullCipher
18+ , passwordGenerator : Maybe PasswordGenerator.Model
19+ , callbacks : Callbacks msg
20+ }
21+
22+
23+type Msg
24+ = EditCipher Bridge.FullCipher
25+ | PasswordGeneratorMsg PasswordGenerator.Msg
26+ | OpenGeneratePasword
27+ | CloseGeneratePassword
28+ | GeneratePassword Bridge.PasswordGeneratorConfig
29+ | Delete
30+ | Save
31+
32+
33+type alias Callbacks msg =
34+ { save : Bridge.FullCipher -> msg
35+ , generatePassword : Bridge.PasswordGeneratorConfig -> msg
36+ , delete : Maybe (Bridge.FullCipher -> msg)
37+ }
38+
39+
40+title : Model msg -> List (Html Msg)
41+title { fullCipher } =
42+ [ text fullCipher.name
43+ , span [ Attr.class "u-float-right" ]
44+ [ iconButton "task-outstanding" Save
45+ ]
46+ ]
47+
48+
49+event : Model msg -> GlobalEvents.Event -> Model msg
50+event model ev =
51+ case ev of
52+ GlobalEvents.UpdateCipher c ->
53+ { model
54+ | fullCipher =
55+ if c.id == model.fullCipher.id then
56+ c
57+
58+ else
59+ model.fullCipher
60+ }
61+
62+ GlobalEvents.GeneratedPassword password ->
63+ let
64+ fullCipher =
65+ model.fullCipher
66+ in
67+ { model
68+ | passwordGenerator = Nothing
69+ , fullCipher =
70+ { fullCipher
71+ | cipher =
72+ case fullCipher.cipher of
73+ Bridge.LoginCipher c ->
74+ Bridge.LoginCipher
75+ { c | password = Just password }
76+
77+ c ->
78+ c
79+ }
80+ }
81+
82+ _ ->
83+ model
84+
85+
86+init : { fullCipher : Bridge.FullCipher, callbacks : Callbacks emsg } -> ( Model emsg, Cmd emsg )
87+init { fullCipher, callbacks } =
88+ ( { fullCipher = fullCipher
89+ , passwordGenerator = Nothing
90+ , callbacks = callbacks
91+ }
92+ , Cmd.none
93+ )
94+
95+
96+update : Msg -> Model emsg -> ( Model emsg, Cmd emsg )
97+update msg model =
98+ case msg of
99+ EditCipher fullCipher ->
100+ ( { model | fullCipher = fullCipher }, Cmd.none )
101+
102+ PasswordGeneratorMsg m ->
103+ ( { model | passwordGenerator = Maybe.map (PasswordGenerator.update m) model.passwordGenerator }
104+ , Cmd.none
105+ )
106+
107+ OpenGeneratePasword ->
108+ ( { model | passwordGenerator = Just PasswordGenerator.init }
109+ , Cmd.none
110+ )
111+
112+ CloseGeneratePassword ->
113+ ( { model | passwordGenerator = Nothing }, Cmd.none )
114+
115+ GeneratePassword cfg ->
116+ ( model, pureCmd (model.callbacks.generatePassword cfg) )
117+
118+ Delete ->
119+ ( model
120+ , Maybe.withDefault Cmd.none
121+ (Maybe.map (\f -> f model.fullCipher |> pureCmd) model.callbacks.delete)
122+ )
123+
124+ Save ->
125+ let
126+ { fullCipher } =
127+ model
128+ in
129+ ( model
130+ , model.callbacks.save { fullCipher | cipher = normalizeCipher fullCipher.cipher } |> pureCmd
131+ )
132+
133+
134+heading : String -> String -> Html msg
135+heading icons name =
136+ span [ Attr.class "u-text--muted" ] [ i [ Attr.class ("p-icon--" ++ icons) ] [], text (" " ++ name) ]
137+
138+
139+row : { name : String, nameIcon : String, attrs : List (Html.Attribute msg) } -> Html msg
140+row { name, nameIcon, attrs } =
141+ p []
142+ [ heading nameIcon name
143+ , br [] []
144+ , input attrs []
145+ ]
146+
147+
148+view : Model emsg -> List (Html Msg)
149+view { fullCipher, passwordGenerator, callbacks } =
150+ maybeList passwordGenerator
151+ (\x ->
152+ modal
153+ { title = "Password generator"
154+ , close = CloseGeneratePassword
155+ , footer =
156+ [ button
157+ [ Attr.class "p-button--positive u-no-margin"
158+ , Ev.onClick <| GeneratePassword x.passwordConfig
159+ ]
160+ [ text "Generate password" ]
161+ ]
162+ , body =
163+ PasswordGenerator.view x |> List.map (Html.map PasswordGeneratorMsg)
164+ }
165+ )
166+ ++ [ row
167+ { name = "Entry name"
168+ , nameIcon = ""
169+ , attrs =
170+ [ Attr.type_ "text"
171+ , Attr.value fullCipher.name
172+ , Ev.onInput (\x -> { fullCipher | name = x } |> EditCipher)
173+ , Attr.attribute "autocomplete" "off"
174+ ]
175+ }
176+ ]
177+ ++ (case fullCipher.cipher of
178+ Bridge.LoginCipher cipher ->
179+ let
180+ { username, password, uris, totp } =
181+ cipher
182+
183+ edit c =
184+ { fullCipher | cipher = Bridge.LoginCipher c } |> EditCipher
185+ in
186+ [ row
187+ { name = "Username"
188+ , nameIcon = "user"
189+ , attrs =
190+ [ Attr.type_ "text"
191+ , Attr.value (Maybe.withDefault "" username)
192+ , Ev.onInput (\x -> edit { cipher | username = Just x })
193+ , Attr.attribute "autocomplete" "username"
194+ ]
195+ }
196+ , p []
197+ [ heading "security" "Password"
198+ , br [] []
199+ , input
200+ [ Attr.type_ "text"
201+ , Attr.value (Maybe.withDefault "" password)
202+ , Ev.onInput (\x -> edit { cipher | password = Just x })
203+ , Attr.attribute "autocomplete" "new-password"
204+ , Attr.style "width" "calc(100% - 3.5rem)"
205+ ]
206+ []
207+ , iconButton "restart" OpenGeneratePasword
208+ ]
209+ , row
210+ { name = "One-time password"
211+ , nameIcon = "revisions"
212+ , attrs =
213+ [ Attr.type_ "text"
214+ , Attr.value (Maybe.withDefault "" totp)
215+ , Ev.onInput (\x -> edit { cipher | totp = Just x })
216+ , Attr.attribute "autocomplete" "off"
217+ ]
218+ }
219+ ]
220+ ++ (uris
221+ |> List.indexedMap
222+ (\i uri ->
223+ row
224+ { name = "URL"
225+ , nameIcon = "get-link"
226+ , attrs =
227+ [ Attr.type_ "url"
228+ , Attr.value uri
229+ , Ev.onInput (\x -> edit { cipher | uris = replace i x uris })
230+ ]
231+ }
232+ )
233+ )
234+ ++ [ row
235+ { name = "URL"
236+ , nameIcon = "get-link"
237+ , attrs =
238+ [ Attr.type_ "url"
239+ , Ev.onInput (\x -> edit { cipher | uris = uris ++ [ x ] })
240+ ]
241+ }
242+ ]
243+
244+ Bridge.NoteCipher contents ->
245+ [ p []
246+ [ heading "switcher-environments" "Note"
247+ , br [] []
248+ , textarea
249+ [ Attr.spellcheck True
250+ , Attr.style "height" "65vh"
251+ , Attr.value contents
252+ , Ev.onInput (\x -> EditCipher { fullCipher | cipher = Bridge.NoteCipher x })
253+ ]
254+ []
255+ ]
256+ ]
257+
258+ Bridge.CardCipher cipher ->
259+ let
260+ { number, code, cardholderName, expMonth, expYear } =
261+ cipher
262+
263+ edit c =
264+ { fullCipher | cipher = Bridge.CardCipher c } |> EditCipher
265+ in
266+ [ row
267+ { name = "Card number"
268+ , nameIcon = "containers"
269+ , attrs =
270+ [ Attr.attribute "autocomplete" "cc-number"
271+ , Attr.type_ "text"
272+ , Attr.value (Maybe.withDefault "" number)
273+ , Ev.onInput (\x -> edit { cipher | number = Just x })
274+ ]
275+ }
276+ , p []
277+ [ heading "timed-out" "Expiration date"
278+ , br [] []
279+ , input
280+ [ Attr.attribute "autocomplete" "cc-exp-month"
281+ , Attr.type_ "text"
282+ , Attr.value (Maybe.withDefault "" expMonth)
283+ , Ev.onInput (\x -> edit { cipher | expMonth = Just x })
284+ , Attr.style "width" "30%"
285+ , Attr.style "min-width" "initial"
286+ , Attr.class "u-align--right"
287+ ]
288+ []
289+ , text " / "
290+ , input
291+ [ Attr.attribute "autocomplete" "cc-exp-year"
292+ , Attr.type_ "text"
293+ , Attr.value (Maybe.withDefault "" expYear)
294+ , Ev.onInput (\x -> edit { cipher | expYear = Just x })
295+ , Attr.style "width" "30%"
296+ , Attr.style "min-width" "initial"
297+ ]
298+ []
299+ ]
300+ , row
301+ { name = "CVV"
302+ , nameIcon = "security"
303+ , attrs =
304+ [ Attr.attribute "autocomplete" "cc-csc"
305+ , Attr.type_ "text"
306+ , Attr.value (Maybe.withDefault "" code)
307+ , Ev.onInput (\x -> edit { cipher | code = Just x })
308+ ]
309+ }
310+ , row
311+ { name = "Cardholder name"
312+ , nameIcon = "user"
313+ , attrs =
314+ [ Attr.attribute "autocomplete" "cc-name"
315+ , Attr.type_ "text"
316+ , Attr.value (Maybe.withDefault "" cardholderName)
317+ , Ev.onInput (\x -> edit { cipher | cardholderName = Just x })
318+ ]
319+ }
320+ ]
321+
322+ Bridge.IdentityCipher cipher ->
323+ let
324+ { address1, address2, address3, company, email, firstName, lastName, licenseNumber, middleName, passportNumber, phone, ssn, username } =
325+ cipher
326+
327+ edit c =
328+ { fullCipher | cipher = Bridge.IdentityCipher c } |> EditCipher
329+ in
330+ [ row
331+ { name = "First name"
332+ , nameIcon = ""
333+ , attrs =
334+ [ Attr.attribute "autocomplete" "given-name"
335+ , Attr.type_ "text"
336+ , Attr.value (Maybe.withDefault "" firstName)
337+ , Ev.onInput (\x -> edit { cipher | firstName = Just x })
338+ ]
339+ }
340+ , row
341+ { name = "Middle name"
342+ , nameIcon = ""
343+ , attrs =
344+ [ Attr.attribute "autocomplete" "additional-name"
345+ , Attr.type_ "text"
346+ , Attr.value (Maybe.withDefault "" middleName)
347+ , Ev.onInput (\x -> edit { cipher | middleName = Just x })
348+ ]
349+ }
350+ , row
351+ { name = "Last name"
352+ , nameIcon = ""
353+ , attrs =
354+ [ Attr.attribute "autocomplete" "family-name"
355+ , Attr.type_ "text"
356+ , Attr.value (Maybe.withDefault "" lastName)
357+ , Ev.onInput (\x -> edit { cipher | lastName = Just x })
358+ ]
359+ }
360+ , row
361+ { name = "Username"
362+ , nameIcon = ""
363+ , attrs =
364+ [ Attr.attribute "autocomplete" "username"
365+ , Attr.type_ "text"
366+ , Attr.value (Maybe.withDefault "" username)
367+ , Ev.onInput (\x -> edit { cipher | username = Just x })
368+ ]
369+ }
370+ , row
371+ { name = "Company"
372+ , nameIcon = ""
373+ , attrs =
374+ [ Attr.attribute "autocomplete" "organization"
375+ , Attr.type_ "text"
376+ , Attr.value (Maybe.withDefault "" company)
377+ , Ev.onInput (\x -> edit { cipher | company = Just x })
378+ ]
379+ }
380+ , row
381+ { name = "Social security number"
382+ , nameIcon = ""
383+ , attrs =
384+ [ Attr.attribute "autocomplete" "off"
385+ , Attr.type_ "number"
386+ , Attr.value (Maybe.withDefault "" ssn)
387+ , Ev.onInput (\x -> edit { cipher | ssn = Just x })
388+ ]
389+ }
390+ , row
391+ { name = "Passport number"
392+ , nameIcon = ""
393+ , attrs =
394+ [ Attr.attribute "autocomplete" "off"
395+ , Attr.type_ "number"
396+ , Attr.value (Maybe.withDefault "" passportNumber)
397+ , Ev.onInput (\x -> edit { cipher | passportNumber = Just x })
398+ ]
399+ }
400+ , row
401+ { name = "License Number"
402+ , nameIcon = ""
403+ , attrs =
404+ [ Attr.attribute "autocomplete" "off"
405+ , Attr.type_ "number"
406+ , Attr.value (Maybe.withDefault "" licenseNumber)
407+ , Ev.onInput (\x -> edit { cipher | licenseNumber = Just x })
408+ ]
409+ }
410+ , row
411+ { name = "Email"
412+ , nameIcon = ""
413+ , attrs =
414+ [ Attr.attribute "autocomplete" "email"
415+ , Attr.type_ "email"
416+ , Attr.value (Maybe.withDefault "" email)
417+ , Ev.onInput (\x -> edit { cipher | email = Just x })
418+ ]
419+ }
420+ , row
421+ { name = "Phone number"
422+ , nameIcon = ""
423+ , attrs =
424+ [ Attr.attribute "autocomplete" "tel"
425+ , Attr.type_ "tel"
426+ , Attr.value (Maybe.withDefault "" phone)
427+ , Ev.onInput (\x -> edit { cipher | phone = Just x })
428+ ]
429+ }
430+ , row
431+ { name = "Address line 1"
432+ , nameIcon = ""
433+ , attrs =
434+ [ Attr.attribute "autocomplete" "address-line1"
435+ , Attr.type_ "text"
436+ , Attr.value (Maybe.withDefault "" address1)
437+ , Ev.onInput (\x -> edit { cipher | address1 = Just x })
438+ ]
439+ }
440+ , row
441+ { name = "Address line 2"
442+ , nameIcon = ""
443+ , attrs =
444+ [ Attr.attribute "autocomplete" "address-line2"
445+ , Attr.type_ "text"
446+ , Attr.value (Maybe.withDefault "" address2)
447+ , Ev.onInput (\x -> edit { cipher | address2 = Just x })
448+ ]
449+ }
450+ , row
451+ { name = "Address line 3"
452+ , nameIcon = ""
453+ , attrs =
454+ [ Attr.attribute "autocomplete" "address-line3"
455+ , Attr.type_ "text"
456+ , Attr.value (Maybe.withDefault "" address3)
457+ , Ev.onInput (\x -> edit { cipher | address3 = Just x })
458+ ]
459+ }
460+ ]
461+ )
462+ ++ optional (callbacks.delete /= Nothing)
463+ (button
464+ [ Attr.style "width" "100%"
465+ , Attr.class "p-button--negative"
466+ , Ev.onClick Delete
467+ ]
468+ [ text "Delete" ]
469+ )
470+
471+
472+iconButton : String -> msg -> Html msg
473+iconButton name msg =
474+ button [ Attr.class "p-button is-inline is-dense has-icon", Ev.onClick msg ]
475+ [ i [ Attr.class ("p-icon--" ++ name) ] [] ]
frontend/Pages/Login.elm+35−34
@@ -1,4 +1,4 @@
1−module Pages.Login exposing (Callbacks, Model, Msg, page)
1+module Pages.Login exposing (Callbacks, Model, Msg, init, title, update, view)
2
2
3import Html exposing (..)
3import Html exposing (..)
4import Html.Attributes as Attr
4import Html.Attributes as Attr
@@ -7,10 +7,11 @@ import Page exposing (..)
7import Utils exposing (..)
7import Utils exposing (..)
8
8
9
9
10−type alias Model =
10+type alias Model emsg =
11 { server : String
11 { server : String
12 , email : String
12 , email : String
13 , password : String
13 , password : String
14+ , callbacks : Callbacks emsg
14 }
15 }
15
16
16
17
@@ -26,56 +27,54 @@ type alias Callbacks emsg =
26 }
27 }
27
28
28
29
29−page : Callbacks emsg -> Page () Model Msg emsg
30+title : String
30−page callbacks liftMsg =
31+title =
31− { init = \() -> Tuple.mapSecond (Cmd.map liftMsg) init
32− , view = \model -> view model |> List.map (Html.map liftMsg)
33− , update = \msg model -> update callbacks liftMsg msg model
34− , subscriptions = \model -> subscriptions model |> Sub.map liftMsg
35− , title = always "Log into bitwarden"
32+ "Log into bitwarden"
36− }
37
33
38
34
39−init : ( Model, Cmd Msg )
35+init : Callbacks emsg -> Model emsg
40−init =
36+init callbacks =
41− ( { server = "https://bitwarden.iko.soy"
37+ { server = "https://vault.bitwarden.com"
42− , email = "mail@iko.soy"
38+ , email = ""
43− , password = ""
39+ , password = ""
40+ , callbacks = callbacks
44− }
41+ }
45− , Cmd.none
46− )
47
42
48
43
49−update : Callbacks emsg -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
44+update : Msg -> Model emsg -> ( Model emsg, Cmd emsg )
50−update { submit } liftMsg msg model =
45+update msg model =
51 case msg of
46 case msg of
52 UpdateEmail email ->
47 UpdateEmail email ->
53− ( Ok { model | email = email }, Cmd.none )
48+ ( { model | email = email }, Cmd.none )
54
49
55 UpdateServer server ->
50 UpdateServer server ->
56− ( Ok { model | server = server }, Cmd.none )
51+ ( { model | server = server }, Cmd.none )
57
52
58 UpdatePassword password ->
53 UpdatePassword password ->
59− ( Ok { model | password = password }, Cmd.none )
54+ ( { model | password = password }, Cmd.none )
60
55
61 Submit ->
56 Submit ->
57+ ( model
62− ( Ok model, pureCmd (submit model) )
58+ , pureCmd
63−
59+ (model.callbacks.submit
64−
60+ { email = model.email
65−subscriptions : Model -> Sub Msg
61+ , password = model.password
66−subscriptions model =
62+ , server = model.server
67− Sub.none
63+ }
68−
64+ )
69−
65+ )
66+
67+
70−view : Model -> List (Html Msg)
68+view : Model emsg -> List (Html Msg)
71view model =
69view model =
72− [ form []
70+ [ form [ Ev.onSubmit Submit ]
73 [ label [] [ text "Server" ]
71 [ label [] [ text "Server" ]
74 , input
72 , input
75 [ Attr.type_ "url"
73 [ Attr.type_ "url"
76 , Attr.value model.server
74 , Attr.value model.server
77 , Ev.onInput UpdateServer
75 , Ev.onInput UpdateServer
78 , Attr.attribute "autocomplete" "url"
76 , Attr.attribute "autocomplete" "url"
77+ , Attr.required True
79 ]
78 ]
80 []
79 []
81 , label [] [ text "Email" ]
80 , label [] [ text "Email" ]
@@ -84,6 +83,7 @@ view model =
84 , Attr.value model.email
83 , Attr.value model.email
85 , Ev.onInput UpdateEmail
84 , Ev.onInput UpdateEmail
86 , Attr.attribute "autocomplete" "email"
85 , Attr.attribute "autocomplete" "email"
86+ , Attr.required True
87 ]
87 ]
88 []
88 []
89 , label [] [ text "Password" ]
89 , label [] [ text "Password" ]
@@ -92,8 +92,9 @@ view model =
92 , Attr.value model.password
92 , Attr.value model.password
93 , Ev.onInput UpdatePassword
93 , Ev.onInput UpdatePassword
94 , Attr.attribute "autocomplete" "current-password"
94 , Attr.attribute "autocomplete" "current-password"
95+ , Attr.required True
95 ]
96 ]
96 []
97 []
98+ , alignRight [ button [ Attr.type_ "submit" ] [ text "Log in" ] ]
97 ]
99 ]
98− , alignRight [ button [ Ev.onClick Submit ] [ text "Log in" ] ]
99 ]
100 ]
frontend/Pages/MasterPassword.elm+24−33
@@ -1,4 +1,4 @@
1−module Pages.MasterPassword exposing (..)
1+module Pages.MasterPassword exposing (Callbacks, Model, Msg, init, title, update, view)
2
2
3import Html exposing (..)
3import Html exposing (..)
4import Html.Attributes as Attr
4import Html.Attributes as Attr
@@ -7,10 +7,11 @@ import Page exposing (..)
7import Utils exposing (..)
7import Utils exposing (..)
8
8
9
9
10−type alias Model =
10+type alias Model emsg =
11 { server : String
11 { server : String
12 , email : String
12 , email : String
13 , password : String
13 , password : String
14+ , callbacks : Callbacks emsg
14 }
15 }
15
16
16
17
@@ -26,48 +27,37 @@ type alias Callbacks emsg =
26 }
27 }
27
28
28
29
29−page : Callbacks emsg -> Page { server : String, login : String } Model Msg emsg
30+title : String
30−page callbacks liftMsg =
31+title =
31− { init = \cfg -> Tuple.mapSecond (Cmd.map liftMsg) (init cfg)
32− , view = \model -> view model |> List.map (Html.map liftMsg)
33− , update = \msg model -> update callbacks liftMsg msg model
34− , subscriptions = \model -> subscriptions model |> Sub.map liftMsg
35− , title = always "Enter master password"
32+ "Enter master password"
36− }
37
33
38
34
39−init : { server : String, login : String } -> ( Model, Cmd Msg )
35+init : Callbacks emsg -> { server : String, login : String } -> Model emsg
40−init { server, login } =
36+init callbacks { server, login } =
41− ( { server = server
37+ { server = server
42− , email = login
38+ , email = login
43− , password = ""
39+ , password = ""
44− }
40+ , callbacks = callbacks
45− , Cmd.none
41+ }
46− )
47
42
48
43
49−update : Callbacks emsg -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
44+update : Msg -> Model emsg -> ( Model emsg, Cmd emsg )
50−update { submit, reset } _ msg model =
45+update msg model =
51 case msg of
46 case msg of
52 UpdatePassword password ->
47 UpdatePassword password ->
53− ( Ok { model | password = password }, Cmd.none )
48+ ( { model | password = password }, Cmd.none )
54
49
55 Submit ->
50 Submit ->
56− ( Ok model, pureCmd (submit { password = model.password }) )
51+ ( model, pureCmd (model.callbacks.submit { password = model.password }) )
57
52
58 Reset ->
53 Reset ->
59− ( Ok model, pureCmd reset )
54+ ( model, pureCmd model.callbacks.reset )
60−
61−
62−subscriptions : Model -> Sub Msg
63−subscriptions _ =
64− Sub.none
65
55
66
56
67−view : Model -> List (Html Msg)
57+view : Model emsg -> List (Html Msg)
68view model =
58view model =
69 [ alignRight [ button [ Attr.class "p-button--negative", Ev.onClick Reset ] [ text "Log out" ] ]
59 [ alignRight [ button [ Attr.class "p-button--negative", Ev.onClick Reset ] [ text "Log out" ] ]
70− , table [ Attr.class "p-table--mobile-card" ]
60+ , table [ Attr.class "p-table--mobile-card u-no-margin" ]
71 [ thead [] [ tr [] [ th [] [ text "Server" ], th [] [ text "Email" ] ] ]
61 [ thead [] [ tr [] [ th [] [ text "Server" ], th [] [ text "Email" ] ] ]
72 , tbody
62 , tbody
73 []
63 []
@@ -77,15 +67,16 @@ view model =
77 ]
67 ]
78 ]
68 ]
79 ]
69 ]
80− , form []
70+ , form [ Ev.onSubmit Submit ]
81 [ label [] [ text "Password" ]
71 [ label [] [ text "Password" ]
82 , input
72 , input
83 [ Attr.type_ "password"
73 [ Attr.type_ "password"
84 , Attr.value model.password
74 , Attr.value model.password
85 , Ev.onInput UpdatePassword
75 , Ev.onInput UpdatePassword
86− , Attr.attribute "autocomplete" "current-password"
76+ , Attr.attribute "autocomplete" "new-password"
77+ , Attr.required True
87 ]
78 ]
88 []
79 []
80+ , alignRight [ button [ Attr.type_ "submit" ] [ text "Log in" ] ]
89 ]
81 ]
90− , alignRight [ button [ Ev.onClick Submit ] [ text "Log in" ] ]
91 ]
82 ]
frontend/Pages/Navigation.elm+57−36
@@ -12,51 +12,61 @@ module Pages.Navigation exposing
12import Html exposing (..)
12import Html exposing (..)
13import Html.Attributes as Attr
13import Html.Attributes as Attr
14import Html.Events as Ev
14import Html.Events as Ev
15+import Html.Lazy as Lazy
15import List.Nonempty as Nonempty exposing (Nonempty)
16import List.Nonempty as Nonempty exposing (Nonempty)
16import Utils exposing (..)
17import Utils exposing (..)
17
18
18
19
19type TopButton msg
20type TopButton msg
20− = BackButton msg
21+ = BackButton { action : msg, icon : Maybe String }
21 | MenuButton (MenuConfig msg)
22 | MenuButton (MenuConfig msg)
22
23
23
24
24type alias Config msg =
25type alias Config msg =
25 { topButton : Maybe (TopButton msg)
26 { topButton : Maybe (TopButton msg)
26− , title : String
27+ , title : List (Html msg)
27 }
28 }
28
29
29
30
30−navigation : Config msg -> List (Html msg)
31+navigation : Config msg -> Html msg
31navigation { topButton, title } =
32navigation { topButton, title } =
32− (case topButton of
33+ div []
34+ ((case topButton of
33− Just (MenuButton cfg) ->
35+ Just (MenuButton cfg) ->
34− [ menu cfg ]
36+ [ Lazy.lazy menu cfg ]
37+
38+ _ ->
39+ []
40+ )
41+ ++ [ nav [ Attr.class "p-tabs p-tabs__list no-scroll-bar" ]
42+ [ h3 []
43+ ([ case topButton of
44+ Nothing ->
45+ button
46+ [ Attr.class "p-button--base is-inline u-no-margin", Attr.style "visibility" "hidden" ]
47+ []
48+
49+ Just (BackButton { action, icon }) ->
50+ button
51+ [ Attr.class "p-button--base is-inline u-no-margin", Ev.onClick action ]
52+ [ case icon of
53+ Nothing ->
54+ i [ Attr.class "p-icon--chevron-up ninety-counter" ] []
55+
56+ Just iconName ->
57+ i [ Attr.class ("p-icon--" ++ iconName) ] []
58+ ]
35
59
36− _ ->
37− []
38− )
39− ++ [ nav [ Attr.class "p-tabs p-tabs__list" ]
40− [ h3 [ Attr.class "" ]
41− [ case topButton of
42− Nothing ->
43− button
44− [ Attr.class "p-button--base is-inline u-no-margin", Attr.style "visibility" "hidden" ]
45− []
46−
47− Just (BackButton msg) ->
48− button
49− [ Attr.class "p-button--base is-inline u-no-margin", Ev.onClick msg ]
50− [ i [ Attr.class "p-icon--chevron-up ninety-counter" ] [] ]
51−
52− Just (MenuButton cfg) ->
60+ Just (MenuButton cfg) ->
53− button
61+ button
54− [ Attr.class "p-button--base is-inline u-no-margin", Ev.onClick cfg.toggle ]
62+ [ Attr.class "p-button--base is-inline u-no-margin", Ev.onClick cfg.toggle ]
55− [ i [ Attr.class "p-icon--menu" ] [] ]
63+ [ i [ Attr.class "p-icon--menu" ] [] ]
64+ ]
56− , text title
65+ ++ title
66+ )
57 ]
67 ]
58− ]
68+ ]
59− ]
69+ )
60
70
61
71
62type alias MenuConfig msg =
72type alias MenuConfig msg =
@@ -64,11 +74,12 @@ type alias MenuConfig msg =
64 , items : List { icon : String, name : String, trigger : msg, current : Bool }
74 , items : List { icon : String, name : String, trigger : msg, current : Bool }
65 , toggle : msg
75 , toggle : msg
66 , visible : Bool
76 , visible : Bool
77+ , logOut : msg
67 }
78 }
68
79
69
80
70mapMenuConfig : (a -> b) -> MenuConfig a -> MenuConfig b
81mapMenuConfig : (a -> b) -> MenuConfig a -> MenuConfig b
71−mapMenuConfig f { title, items, toggle, visible } =
82+mapMenuConfig f { title, items, toggle, visible, logOut } =
72 { items =
83 { items =
73 List.map
84 List.map
74 (\{ icon, name, trigger, current } ->
85 (\{ icon, name, trigger, current } ->
@@ -78,16 +89,23 @@ mapMenuConfig f { title, items, toggle, visible } =
78 , title = title
89 , title = title
79 , toggle = f toggle
90 , toggle = f toggle
80 , visible = visible
91 , visible = visible
92+ , logOut = f logOut
81 }
93 }
82
94
83
95
84menu : MenuConfig msg -> Html msg
96menu : MenuConfig msg -> Html msg
85−menu { title, items, toggle, visible } =
97+menu { title, items, toggle, visible, logOut } =
86 div [ Attr.class "p-side-navigation--icons", Attr.classList [ ( "is-drawer-expanded", visible ) ] ]
98 div [ Attr.class "p-side-navigation--icons", Attr.classList [ ( "is-drawer-expanded", visible ) ] ]
87 [ div [ Attr.class "p-side-navigation__overlay" ] []
99 [ div [ Attr.class "p-side-navigation__overlay" ] []
88 , nav [ Attr.class "p-side-navigation__drawer" ]
100 , nav [ Attr.class "p-side-navigation__drawer" ]
89 [ div [ Attr.class "p-side-navigation__drawer-header" ]
101 [ div [ Attr.class "p-side-navigation__drawer-header" ]
90 [ a [ Attr.class "p-side-navigation__toggle--in-drawer", Ev.onClick toggle ] [ text title ]
102 [ a [ Attr.class "p-side-navigation__toggle--in-drawer", Ev.onClick toggle ] [ text title ]
103+ , button
104+ [ Attr.class "p-button--negative is-inline u-float-right"
105+ , Attr.style "margin-right" "1rem"
106+ , Ev.onClick logOut
107+ ]
108+ [ text "Log out" ]
91 ]
109 ]
92 , ul [ Attr.class "p-side-navigation__list" ]
110 , ul [ Attr.class "p-side-navigation__list" ]
93 (items
111 (items
@@ -124,7 +142,10 @@ pushView model stack =
124
142
125popView : PageStack model -> PageStack model
143popView : PageStack model -> PageStack model
126popView stack =
144popView stack =
145+ stack
146+ |> Nonempty.tail
147+ |> Nonempty.fromList
127− Nonempty.tail stack |> Nonempty.fromList |> Maybe.withDefault stack
148+ |> Maybe.withDefault stack
128
149
129
150
130showNavigationView :
151showNavigationView :
@@ -132,7 +153,7 @@ showNavigationView :
132 ->
153 ->
133 (model
154 (model
134 ->
155 ->
135− { title : String
156+ { title : List (Html msg)
136 , body : List (Html msg)
157 , body : List (Html msg)
137 , topButton : Maybe (TopButton msg)
158 , topButton : Maybe (TopButton msg)
138 }
159 }
@@ -144,9 +165,9 @@ showNavigationView stack render =
144 render (Nonempty.head stack)
165 render (Nonempty.head stack)
145 in
166 in
146 div []
167 div []
147− (navigation
168+ [ Lazy.lazy navigation
148 { topButton = topButton
169 { topButton = topButton
149 , title = title
170 , title = title
150 }
171 }
151− ++ [ main_ [] body ]
172+ , main_ [] body
152− )
173+ ]
frontend/Pages/PasswordGenerator.elmnew file+158
@@ -0,0 +1,158 @@
1+module Pages.PasswordGenerator exposing (Model, Msg, init, update, view)
2+
3+import Bridge
4+import Html exposing (..)
5+import Html.Attributes as Attr
6+import Html.Events as Ev
7+import Switch exposing (switch)
8+
9+
10+type Msg
11+ = Noop
12+ | UpdateNumberCount Int
13+ | ToggleNumber Bool
14+ | ToggleAmbiguousCharacters Bool
15+ | ToggleCapitals Bool
16+ | UpdateNumberOfCapitals Int
17+ | UpdateLength Int
18+ | ToggleSpecialCharacters Bool
19+ | UpdateSpecialCharactersCount Int
20+
21+
22+type alias Model =
23+ { passwordConfig : Bridge.PasswordGeneratorConfig
24+ }
25+
26+
27+update : Msg -> Model -> Model
28+update msg { passwordConfig } =
29+ case msg of
30+ Noop ->
31+ { passwordConfig = passwordConfig }
32+
33+ UpdateNumberCount count ->
34+ { passwordConfig = { passwordConfig | minNumber = count } }
35+
36+ ToggleNumber enabled ->
37+ { passwordConfig = { passwordConfig | includeNumber = enabled } }
38+
39+ ToggleAmbiguousCharacters enabled ->
40+ { passwordConfig = { passwordConfig | ambiguous = enabled } }
41+
42+ ToggleCapitals enabled ->
43+ { passwordConfig = { passwordConfig | capitalize = enabled } }
44+
45+ UpdateNumberOfCapitals count ->
46+ { passwordConfig = { passwordConfig | minUppercase = count } }
47+
48+ UpdateLength count ->
49+ { passwordConfig = { passwordConfig | length = count } }
50+
51+ ToggleSpecialCharacters enabled ->
52+ { passwordConfig = { passwordConfig | special = enabled } }
53+
54+ UpdateSpecialCharactersCount count ->
55+ { passwordConfig = { passwordConfig | minSpecial = count } }
56+
57+
58+init : Model
59+init =
60+ { passwordConfig =
61+ { ambiguous = True
62+ , capitalize = True
63+ , includeNumber = True
64+ , length = 16
65+ , lowercase = True
66+ , minLowercase = 1
67+ , minNumber = 1
68+ , minSpecial = 1
69+ , minUppercase = 1
70+ , numWords = 1
71+ , number = True
72+ , special = True
73+ , type_ = "password"
74+ , uppercase = True
75+ , wordSeparator = "-"
76+ }
77+ }
78+
79+
80+view : Model -> List (Html Msg)
81+view { passwordConfig } =
82+ [ passwordGeneratorGroup
83+ [ switch
84+ { trigger = always Noop
85+ , label = "Length"
86+ , value = True
87+ , hidden = True
88+ }
89+ , input
90+ [ Attr.type_ "number"
91+ , Attr.class "is-dense"
92+ , Attr.value (String.fromInt passwordConfig.length)
93+ , Ev.onInput (String.toInt >> Maybe.withDefault 1 >> UpdateLength)
94+ , Attr.min "6"
95+ ]
96+ []
97+ ]
98+ , switchRow
99+ { label = "Numbers"
100+ , enabled = passwordConfig.includeNumber
101+ , toggle = ToggleNumber
102+ , trigger = UpdateNumberCount
103+ , value = passwordConfig.minNumber
104+ }
105+ , switchRow
106+ { label = "Uppercase Characters"
107+ , enabled = passwordConfig.capitalize
108+ , toggle = ToggleCapitals
109+ , trigger = UpdateNumberOfCapitals
110+ , value = passwordConfig.minUppercase
111+ }
112+ , switchRow
113+ { label = "Special Characters"
114+ , enabled = passwordConfig.special
115+ , toggle = ToggleSpecialCharacters
116+ , trigger = UpdateSpecialCharactersCount
117+ , value = passwordConfig.minSpecial
118+ }
119+ , switch
120+ { trigger = ToggleAmbiguousCharacters
121+ , label = "Ambiguous Characters"
122+ , value = passwordConfig.ambiguous
123+ , hidden = False
124+ }
125+ ]
126+
127+
128+switchRow :
129+ { toggle : Bool -> Msg
130+ , label : String
131+ , enabled : Bool
132+ , value : Int
133+ , trigger : Int -> Msg
134+ }
135+ -> Html Msg
136+switchRow { toggle, label, enabled, value, trigger } =
137+ passwordGeneratorGroup
138+ [ switch
139+ { trigger = toggle
140+ , label = label
141+ , value = enabled
142+ , hidden = False
143+ }
144+ , input
145+ [ Attr.type_ "number"
146+ , Attr.class "is-dense"
147+ , Attr.value (String.fromInt value)
148+ , Ev.onInput (String.toInt >> Maybe.withDefault 1 >> trigger)
149+ , Attr.min "1"
150+ , Attr.disabled (not enabled)
151+ ]
152+ []
153+ ]
154+
155+
156+passwordGeneratorGroup : List (Html msg) -> Html msg
157+passwordGeneratorGroup =
158+ div [ Attr.class "password-generator-group" ]
frontend/Pages/SecondFactor.elmnew file+91
@@ -0,0 +1,91 @@
1+module Pages.SecondFactor exposing
2+ ( Callbacks
3+ , Model
4+ , Msg
5+ , init
6+ , title
7+ , update
8+ , view
9+ )
10+
11+import Bridge
12+import Html exposing (..)
13+import Html.Attributes as Attr
14+import Html.Events as Ev
15+import Logic.SecondFactor exposing (secondFactorName)
16+import Utils exposing (..)
17+
18+
19+type alias Callbacks msg =
20+ { submit : { token : String, remember : Bool } -> msg
21+ }
22+
23+
24+type Msg
25+ = Submit
26+ | UpdateValue String
27+ | UpdateRemember Bool
28+
29+
30+type alias Model msg =
31+ { token : String
32+ , remember : Bool
33+ , factorType : Bridge.TwoFactorProviderType
34+ , callbacks : Callbacks msg
35+ }
36+
37+
38+init :
39+ { provider : Bridge.TwoFactorProviderType
40+ , callbacks : Callbacks msg
41+ }
42+ -> Model msg
43+init { provider, callbacks } =
44+ { token = ""
45+ , remember = True
46+ , factorType = provider
47+ , callbacks = callbacks
48+ }
49+
50+
51+title : Model msg -> String
52+title { factorType } =
53+ secondFactorName factorType
54+
55+
56+update : Model emsg -> Msg -> ( Model emsg, Maybe emsg )
57+update model msg =
58+ case msg of
59+ Submit ->
60+ ( model, model.callbacks.submit { token = model.token, remember = model.remember } |> Just )
61+
62+ UpdateValue token ->
63+ ( { model | token = token }, Nothing )
64+
65+ UpdateRemember remember ->
66+ ( { model | remember = remember }, Nothing )
67+
68+
69+view : Model emsg -> Html Msg
70+view { factorType, token, remember } =
71+ form [ Ev.onSubmit Submit ]
72+ [ label [] [ secondFactorName factorType ++ " code" |> text ]
73+ , input
74+ [ Attr.type_ "number"
75+ , Attr.value token
76+ , Attr.required True
77+ , Ev.onInput UpdateValue
78+ , Attr.attribute "autocomplete" "one-time-code"
79+ ]
80+ []
81+ , label []
82+ [ input
83+ [ Attr.type_ "checkbox"
84+ , Attr.checked remember
85+ , Ev.onCheck UpdateRemember
86+ ]
87+ []
88+ , text "Remember this device"
89+ ]
90+ , alignRight [ button [ Attr.type_ "submit" ] [ text "Submit" ] ]
91+ ]
frontend/Pages/SecondFactorSelect.elmnew file+164
@@ -0,0 +1,164 @@
1+module Pages.SecondFactorSelect exposing
2+ ( Callbacks
3+ , Model
4+ , Msg
5+ , init
6+ , main
7+ , title
8+ , update
9+ , view
10+ )
11+
12+import Bridge
13+import Element as El
14+import Element.Font as Font
15+import Element.Input as Input
16+import Html
17+import Html.Attributes as Attr
18+import Logic.SecondFactor exposing (secondFactorName)
19+import Page exposing (..)
20+import Utils exposing (..)
21+
22+
23+main : Program () (Model (Maybe Msg)) (Maybe Msg)
24+main =
25+ Page.page
26+ { init =
27+ init { selectFactor = always Nothing }
28+ [ Bridge.Duo
29+ , Bridge.Email
30+ ]
31+ , view = view
32+ , update = update
33+ }
34+
35+
36+type alias Model emsg =
37+ { availableFactors : List Bridge.TwoFactorProviderType
38+ , callbacks : Callbacks emsg
39+ }
40+
41+
42+type Msg
43+ = SelectFactor Bridge.TwoFactorProviderType
44+
45+
46+type alias Callbacks emsg =
47+ { selectFactor : Bridge.TwoFactorProviderType -> emsg
48+ }
49+
50+
51+title : String
52+title =
53+ "Second factor"
54+
55+
56+init : Callbacks emsg -> List Bridge.TwoFactorProviderType -> Model emsg
57+init callbacks providers =
58+ { availableFactors = providers
59+ , callbacks = callbacks
60+ }
61+
62+
63+update : Msg -> Model emsg -> ( Model emsg, Cmd emsg )
64+update msg model =
65+ case msg of
66+ SelectFactor factor ->
67+ ( model, model.callbacks.selectFactor factor |> pureCmd )
68+
69+
70+view : Model emsg -> List (Html.Html Msg)
71+view { availableFactors } =
72+ let
73+ ( supportedProviders, unsupportedProviders ) =
74+ List.partition providerIsSupported availableFactors
75+ in
76+ [ El.layout [ El.padding 8 ]
77+ (El.column
78+ [ El.width El.fill
79+ , El.spacing 8
80+ ]
81+ ([ if List.isEmpty supportedProviders then
82+ El.column []
83+ [ El.paragraph []
84+ [ El.text "Unfortunately, your account has no compatible 2FA providers."
85+ ]
86+ , El.el
87+ [ El.height (El.px 16)
88+ ]
89+ El.none
90+ ]
91+
92+ else
93+ El.column [ El.centerX ]
94+ (supportedProviders
95+ |> List.map
96+ (\factor ->
97+ Input.button [ El.centerX ]
98+ { label =
99+ Html.button [ Attr.class "p-button" ] [ secondFactorName factor |> Html.text ]
100+ |> El.html
101+ , onPress = SelectFactor factor |> Just
102+ }
103+ )
104+ )
105+ ]
106+ ++ (if List.isEmpty unsupportedProviders |> not then
107+ [ Html.div
108+ [ Attr.class "p-notification--information"
109+ ]
110+ [ Html.div [ Attr.class "p-notification__content" ]
111+ [ Html.h5 [ Attr.class "p-notification__title" ]
112+ [ Html.text "The following 2FA providers are not supported"
113+ ]
114+ , Html.p [ Attr.class "notification__message" ]
115+ [ Html.ul []
116+ (List.map
117+ (secondFactorName >> Html.text >> List.singleton >> Html.li [])
118+ unsupportedProviders
119+ )
120+ ]
121+ ]
122+ ]
123+ |> El.html
124+ |> List.singleton
125+ |> El.paragraph
126+ [ Font.size 16
127+ , El.width El.fill
128+ ]
129+ ]
130+
131+ else
132+ []
133+ )
134+ )
135+ )
136+ ]
137+
138+
139+providerIsSupported : Bridge.TwoFactorProviderType -> Bool
140+providerIsSupported provider =
141+ case provider of
142+ Bridge.Authenticator ->
143+ True
144+
145+ Bridge.Duo ->
146+ False
147+
148+ Bridge.Email ->
149+ True
150+
151+ Bridge.OrganizationDuo ->
152+ False
153+
154+ Bridge.Remember ->
155+ False
156+
157+ Bridge.U2f ->
158+ False
159+
160+ Bridge.WebAuthn ->
161+ False
162+
163+ Bridge.Yubikey ->
164+ False
frontend/Search.elm+24−166
@@ -1,182 +1,40 @@
1−module Search exposing
1+module Search exposing (searchList)
2− ( Query
3− , Search(..)
4− , SearchResult
5− , combine
6− , contramap
7− , search
8− , searchList
9− )
10
2
11−import Set exposing (Set)
3+import Html exposing (..)
12−
4+import Html.Lazy as Lazy
13−
14−ignoredCharacters : Set Char
15−ignoredCharacters =
16− Set.fromList [ '_', '-', '.' ]
17−
18−
19−prepareString : String -> String
20−prepareString =
21− String.filter (\c -> Set.member c ignoredCharacters |> not)
22− >> String.toLower
23−
24−
25−type SearchResult
26− = Match Int
27− | NoMatch {- | A search result for an empty query. It is for the most part just ignored, hance the name. -}
28− | Ignore
29−
30−
31−{-| Used to combine different search results within one search entry.
32−-}
33−combine : List SearchResult -> SearchResult
34−combine ress =
35− case ress of
36− [] ->
37− NoMatch
38−
39− _ ->
40− if List.all ((==) Ignore) ress then
41− Ignore
42−
43− else
44− let
45− go : List SearchResult -> SearchResult
46− go rr =
47− case rr of
48− [] ->
49− NoMatch
50−
51− NoMatch :: xs ->
52− go xs
53−
54− (Match score) :: xs ->
55− case go xs of
56− NoMatch ->
57− Match score
58−
59− Match score_ ->
60− Match (score + score_)
61−
62− Ignore ->
63− Match score
64−
65− Ignore :: xs ->
66− go xs
67− in
68− go ress
69−
70−
71−type alias Haystack =
72− String
73
5
74
6
75type alias Needle =
7type alias Needle =
76 String
8 String
77
9
78
10
79−type Query
11+type alias Haystack =
80− = Query Needle
12+ String
81−
82−
83−{-| Constructs queries from an input string. (Splits on spaces and requires all words to match)
84−-}
85−constructQueries : String -> List Query
86−constructQueries =
87− prepareString >> String.words >> List.filter (String.isEmpty >> not) >> List.map Query
88−
89−
90−{-| Search a search term within a search entry.
91−-}
92−search : Query -> Haystack -> SearchResult
93−search (Query needle) haystack =
94− if String.contains needle (prepareString haystack) then
95− Match 1
96−
97− else
98− NoMatch
99
13
100
14
101−{-| Requires all search results to be matched
15+search : List Needle -> Haystack -> Bool
102−-}
16+search needles haystack =
103−searchAll : List SearchResult -> SearchResult
104−searchAll =
105 let
17 let
106− combineQueries : SearchResult -> SearchResult -> SearchResult
18+ haystack_ =
107− combineQueries x y =
19+ haystack |> String.toLower
108− case ( x, y ) of
109− ( NoMatch, _ ) ->
110− NoMatch
111−
112− ( _, NoMatch ) ->
113− NoMatch
114−
115− ( Ignore, a ) ->
116− a
117−
118− ( a, Ignore ) ->
119− a
120−
121− ( Match a, Match b ) ->
122− Match (a + b)
123 in
20 in
124− List.foldl combineQueries Ignore
21+ List.all (\needle -> String.contains needle haystack_) needles
125−
126−
127−{-| A list of search inputs with the definition of what it means to search for that query.
128−-}
129−type Search a
130− = Search (List ( String, Query -> a -> SearchResult ))
131−
132−
133−contramap : (b -> a) -> Search a -> Search b
134−contramap f (Search s) =
135− List.map (\( q, g ) -> ( q, \query b -> g query (f b) )) s
136− |> Search
137
22
138
23
139−{-| The main function of the module. Search a list with the given `Search`.
24+prepareNeedles : String -> List Needle
140−-}
25+prepareNeedles =
141−searchList : Search a -> List a -> List a
26+ String.toLower
142−searchList (Search searches) aa =
27+ >> String.words
143− let
144− searchByTerm =
145− searches
146− |> List.concatMap
147− (\( s, f ) ->
148− let
149− qq =
150− constructQueries s
151− in
152− List.map (\q a -> f q a) qq
28+ >> List.map String.trim
153− )
29+ >> List.filter (String.isEmpty >> not)
154−
30+ >> List.filter (String.length >> (<) 1)
155− searchEntry a =
156− List.map (\f -> f a) searchByTerm |> searchAll
157− in
158− aa
159− |> List.map (\a -> ( searchEntry a, a ))
160− |> sortResult Tuple.first
161− |> List.map Tuple.second
162
31
163
32
164−sortResult : (a -> SearchResult) -> List a -> List a
33+searchList : Needle -> (a -> Haystack) -> (List a -> Html msg) -> List a -> Html msg
165−sortResult f =
34+searchList needle getHaystack show aa =
166− List.filterMap
35+ case prepareNeedles needle of
167− (\a ->
36+ [] ->
168− case f a of
37+ Lazy.lazy show aa
169− Match score ->
170− Just ( score, a )
171−
172− NoMatch ->
173− Nothing
174
38
175− Ignore ->
39+ needles ->
176− Just ( 0, a )
40+ aa |> List.filter (\a -> search needles (getHaystack a)) |> show
177− )
178− -- Here we assume that the sort is stable which is not true for old
179− -- versions of ECMAScript which would result in undeterministic
180− -- ordering for empty queries.
181− >> List.sortBy Tuple.first
182− >> List.map Tuple.second
frontend/Switch.elmnew file+35
@@ -0,0 +1,35 @@
1+module Switch exposing (..)
2+
3+import Html exposing (..)
4+import Html.Attributes as Attr
5+import Html.Events as Ev
6+import Utils exposing (..)
7+
8+
9+type alias SwitchConfig msg =
10+ { trigger : Bool -> msg
11+ , label : String
12+ , value : Bool
13+ , hidden : Bool
14+ }
15+
16+
17+switch : SwitchConfig msg -> Html msg
18+switch cfg =
19+ label
20+ [ Attr.class "p-switch"
21+ ]
22+ [ input
23+ [ Attr.type_ "checkbox"
24+ , Attr.class "p-switch__input"
25+ , Ev.onCheck cfg.trigger
26+ , Attr.checked cfg.value
27+ ]
28+ []
29+ , span
30+ (Attr.class "p-switch__slider"
31+ :: optional cfg.hidden (Attr.style "visibility" "hidden")
32+ )
33+ []
34+ , span [ Attr.class "p-switch__label" ] [ text cfg.label ]
35+ ]
frontend/Types.elmnew file+5
@@ -0,0 +1,5 @@
1+module Types exposing (..)
2+
3+
4+type alias CipherId =
5+ String
frontend/Utils.elm+65
@@ -2,6 +2,7 @@ module Utils exposing (..)
2
2
3import Html exposing (..)
3import Html exposing (..)
4import Html.Attributes as Attr
4import Html.Attributes as Attr
5+import Html.Events as Ev
5import Task
6import Task
6
7
7
8
@@ -86,3 +87,67 @@ tailEmpty x =
86unwords : List String -> String
87unwords : List String -> String
87unwords =
88unwords =
88 String.join " "
89 String.join " "
90+
91+
92+zip : List a -> List b -> List ( a, b )
93+zip =
94+ List.map2 (\a b -> ( a, b ))
95+
96+
97+mapIndex : (a -> a) -> Int -> List a -> List a
98+mapIndex f i xs =
99+ case ( i, xs ) of
100+ ( _, [] ) ->
101+ []
102+
103+ ( 0, x :: xx ) ->
104+ f x :: xx
105+
106+ ( j, x :: xx ) ->
107+ x :: mapIndex f (j - 1) xx
108+
109+
110+replace : Int -> a -> List a -> List a
111+replace i a xs =
112+ case ( i, xs ) of
113+ ( 0, _ :: xx ) ->
114+ a :: xx
115+
116+ ( j, x :: xx ) ->
117+ x :: replace (j - 1) a xx
118+
119+ ( _, [] ) ->
120+ []
121+
122+
123+index : Int -> List a -> Maybe a
124+index i xs =
125+ case ( i, xs ) of
126+ ( 0, x :: _ ) ->
127+ Just x
128+
129+ ( j, _ :: xx ) ->
130+ index (j - 1) xx
131+
132+ ( _, [] ) ->
133+ Nothing
134+
135+
136+spanList : (a -> Bool) -> List a -> ( List a, List a )
137+spanList f aa =
138+ case aa of
139+ [] ->
140+ ( aa, aa )
141+
142+ a :: rest ->
143+ if f a then
144+ spanList f rest |> Tuple.mapFirst ((::) a)
145+
146+ else
147+ ( [], aa )
148+
149+
150+iconButton : String -> msg -> Html msg
151+iconButton name msg =
152+ button [ Attr.class "p-button is-inline is-dense has-icon", Ev.onClick msg ]
153+ [ i [ Attr.class ("p-icon--" ++ name) ] [] ]
hcaptcha.htmlnew file+33
@@ -0,0 +1,33 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+
4+<head>
5+ <meta charset="UTF-8">
6+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8+ <title>hCaptcha</title>
9+ <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
10+</head>
11+
12+<body>
13+
14+ <script>
15+ function processSuccess(token) {
16+ console.log(token);
17+ window.parent.postMessage({ "hCaptchaToken": token }, "*");
18+ }
19+
20+ const sitekey = location.search.replace("?", "")
21+ console.log(sitekey)
22+
23+ const div = document.createElement("div");
24+ div.setAttribute("class", "h-captcha");
25+ div.setAttribute("data-sitekey", sitekey);
26+ div.setAttribute("data-callback", "processSuccess");
27+
28+ document.body.append(div)
29+
30+ </script>
31+</body>
32+
33+</html>
img/donate.pngnew file

Binary file not shown.

index.html+2−2
@@ -5,9 +5,9 @@
5 <meta charset="UTF-8">
5 <meta charset="UTF-8">
6 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta http-equiv="X-UA-Compatible" content="IE=edge">
7 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
7 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
8− <link rel="stylesheet" href="vanilla/style.css">
8+ <link rel="stylesheet" href="vanilla/dist/style.css">
9 <link rel="stylesheet" href="style.css">
9 <link rel="stylesheet" href="style.css">
10− <title>Test.</title>
10+ <title>Warden</title>
11 <script src="elm.js"></script>
11 <script src="elm.js"></script>
12</head>
12</head>
13
13
logo.pngnew file

Binary file not shown.

logo.svgdeleted−24
@@ -1,24 +0,0 @@
1−<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2−<svg style="enable-background:new" xmlns="http://www.w3.org/2000/svg" height="512" viewBox="0 0 512.40002 512" width="512.4" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
3− <defs>
4− <linearGradient id="a" y2="494" gradientUnits="userSpaceOnUse" x2="256" gradientTransform="matrix(1 0 0 1.076 0 -19.36)" y1="18" x1="256">
5− <stop style="stop-color:#eaeaea" offset="0"/>
6− <stop style="stop-color:#f4f4f4" offset="1"/>
7− </linearGradient>
8− </defs>
9− <rect style="color:#000000;enable-background:accumulate;fill:url(#a)" height="512" width="512" y="0" x="0"/>
10− <path style="opacity:.1" d="m256 112c-79.5 0-144 64.5-144 144s64.5 144 144 144 144-64.5 144-144-64.5-144-144-144zm49.47 39.98c3.098 0.0797 6.222 0.9104 9.096 2.568 9.197 5.312 12.34 17.07 7.029 26.26-5.305 9.196-17.06 12.34-26.25 7.029-9.19-5.305-12.34-17.06-7.039-26.25 3.653-6.322 10.35-9.787 17.17-9.611zm-49.48 20.5c7.731 0 15.21 1.074 22.31 3.035 1.253 7.726 5.837 14.84 13.15 19.06 7.298 4.207 15.74 4.629 23.05 1.867 14.22 13.98 23.47 32.99 24.81 54.17l-27.39 0.4043c-2.5-28.7-26.6-51.2-55.9-51.2-8.451 0-16.47 1.88-23.65 5.225l-13.4-23.9c11.16-5.522 23.72-8.645 37.01-8.645zm-46.33 14.03 14.06 23.54c-14.44 10.16-23.88 26.95-23.88 45.95s9.437 35.78 23.88 45.94l-14.06 23.55c-16.83-11.24-29.34-28.43-34.54-48.55 6.069-4.952 9.953-12.49 9.953-20.94 0-8.451-3.884-15.99-9.953-20.94 5.201-20.12 17.72-37.3 34.54-48.55zm-51.58 50.26c10.62 0 19.22 8.608 19.22 19.23s-8.605 19.22-19.22 19.22-19.23-8.605-19.23-19.22 8.607-19.23 19.23-19.23zm153.8 24.21 27.4 0.4023c-1.348 21.18-10.6 40.19-24.81 54.17-7.31-2.762-15.75-2.347-23.05 1.867-7.309 4.227-11.89 11.34-13.15 19.06-7.104 1.967-14.58 3.041-22.31 3.041-13.29 0-25.86-3.124-37.01-8.652l13.36-23.94c7.186 3.344 15.2 5.225 23.65 5.225 29.34 0 53.41-22.49 55.93-51.18zm-7.482 60.6c6.816-0.1758 13.51 3.289 17.16 9.607 5.311 9.197 2.16 20.95-7.035 26.25-9.196 5.311-20.95 2.159-26.25-7.037-5.312-9.191-2.159-20.94 7.037-26.25 2.872-1.658 5.994-2.49 9.092-2.57z"/>
11− <g transform="translate(0,18)">
12− <path style="opacity:.3;color:#000000;enable-background:accumulate;fill:#fff" d="m256 256l-147.8 256h403.8v-256h-122.7-133.3z" transform="translate(0,-18)"/>
13− <path style="opacity:.15;color:#000000;enable-background:accumulate;fill:#fff" d="m0 0v512h108.2l76.4-132.3 71.4-123.7-68.9-120-0.2 0.3-78.7-136.3h-108.2z" transform="translate(0,-18)"/>
14− </g>
15− <path d="m-208.3 605.6c-7.842 0-14.55 5.029-17.32 12.19-0.0612-0.00067-0.1208 0-0.1823 0-7.088 0-12.83 6.002-12.83 13.41 0 7.404 5.746 13.41 12.83 13.41h17.5 16.33c5.155 0 9.333-4.365 9.333-9.75 0-4.543-2.974-8.363-7-9.445 0.001-0.1029 0-0.2015 0-0.3047 0-10.77-8.357-19.5-18.67-19.5z" style="stroke-width:4;color:#000000;stroke:#ccc;enable-background:accumulate;display:none;fill:#fff"/>
16− <path style="stroke-width:4;color:#000000;stroke:#ccc;enable-background:accumulate;display:none;fill:none" d="m288.9 828.1c-6.349 0-11.78 4.072-14.02 9.867-0.0496-0.00055-0.0978 0-0.1476 0-5.738 0-10.39 4.86-10.39 10.85 0 5.995 4.652 10.85 10.39 10.85h14.17 13.22c4.173 0 7.557-3.534 7.557-7.894 0-3.678-2.408-6.771-5.667-7.647 0.001-0.0833 0-0.1632 0-0.2467 0-8.719-6.766-15.79-15.11-15.79z"/>
17− <g style="display:none" transform="translate(0 -558.4)">
18− <path d="m166.1 576.4h179.9c145.3 0 166.1 20.74 166.1 165.9v144.2c0 145.2-20.76 165.9-166.1 165.9h-179.9c-145.3 0-166.1-20-166.1-165.5v-144.2c0-145.2 20.76-165.9 166.1-165.9z" style="stroke:#00f;fill:#00f;display:inline;fill-opacity:.08118"/>
19− <circle style="stroke-width:.8333;fill-opacity:.08118;color:#000000;stroke:#00f;enable-background:accumulate;display:inline;fill:#00f" cx="256" transform="matrix(1.2 0 0 1.2 -51.2 528.8)" cy="238" r="120"/>
20− <rect style="stroke-linejoin:round;fill-opacity:.08118;color:#000000;stroke:#00f;stroke-linecap:round;enable-background:accumulate;display:inline;fill:#00f" height="272" width="272" y="678.4" x="120"/>
21− <rect style="stroke-linejoin:round;fill-opacity:.08118;color:#000000;stroke:#00f;stroke-linecap:round;enable-background:accumulate;display:inline;fill:#00f" height="240" width="304" y="694.4" x="104"/>
22− <rect style="stroke-linejoin:round;fill-opacity:.08118;color:#000000;stroke:#00f;stroke-linecap:round;enable-background:accumulate;display:inline;fill:#00f" height="304" width="240" y="662.4" x="136"/>
23− </g>
24−</svg>
logo.xcfnew file

Binary file not shown.

manifest.json+8−8
@@ -1,15 +1,15 @@
1{
1{
2− "name": "appname.iko",
2+ "name": "soy.iko.warden",
3− "description": "A short description of your app",
3+ "description": "A simple Bitwarden client",
4 "architecture": "@CLICK_ARCH@",
4 "architecture": "@CLICK_ARCH@",
5− "title": "App Title",
5+ "title": "Warden",
6 "hooks": {
6 "hooks": {
7− "appname": {
7+ "warden": {
8− "apparmor": "appname.apparmor",
8+ "apparmor": "warden.apparmor",
9− "desktop": "appname.desktop"
9+ "desktop": "warden.desktop"
10 }
10 }
11 },
11 },
12− "version": "1.0.0",
12+ "version": "1.2.4",
13− "maintainer": "Your FullName <email@domain.org>",
13+ "maintainer": "Ilia Kostiuchenko <mail@iko.soy>",
14 "framework": "@CLICK_FRAMEWORK@"
14 "framework": "@CLICK_FRAMEWORK@"
15}
15}
package-lock.jsonnew file+959
@@ -0,0 +1,959 @@
1+{
2+ "name": "warden",
3+ "lockfileVersion": 3,
4+ "requires": true,
5+ "packages": {
6+ "": {
7+ "devDependencies": {
8+ "cssnano": "^5.1.15"
9+ }
10+ },
11+ "node_modules/@trysound/sax": {
12+ "version": "0.2.0",
13+ "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
14+ "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
15+ "dev": true,
16+ "engines": {
17+ "node": ">=10.13.0"
18+ }
19+ },
20+ "node_modules/boolbase": {
21+ "version": "1.0.0",
22+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
23+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
24+ "dev": true
25+ },
26+ "node_modules/browserslist": {
27+ "version": "4.21.5",
28+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
29+ "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
30+ "dev": true,
31+ "funding": [
32+ {
33+ "type": "opencollective",
34+ "url": "https://opencollective.com/browserslist"
35+ },
36+ {
37+ "type": "tidelift",
38+ "url": "https://tidelift.com/funding/github/npm/browserslist"
39+ }
40+ ],
41+ "dependencies": {
42+ "caniuse-lite": "^1.0.30001449",
43+ "electron-to-chromium": "^1.4.284",
44+ "node-releases": "^2.0.8",
45+ "update-browserslist-db": "^1.0.10"
46+ },
47+ "bin": {
48+ "browserslist": "cli.js"
49+ },
50+ "engines": {
51+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
52+ }
53+ },
54+ "node_modules/caniuse-api": {
55+ "version": "3.0.0",
56+ "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
57+ "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==",
58+ "dev": true,
59+ "dependencies": {
60+ "browserslist": "^4.0.0",
61+ "caniuse-lite": "^1.0.0",
62+ "lodash.memoize": "^4.1.2",
63+ "lodash.uniq": "^4.5.0"
64+ }
65+ },
66+ "node_modules/caniuse-lite": {
67+ "version": "1.0.30001457",
68+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz",
69+ "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==",
70+ "dev": true,
71+ "funding": [
72+ {
73+ "type": "opencollective",
74+ "url": "https://opencollective.com/browserslist"
75+ },
76+ {
77+ "type": "tidelift",
78+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
79+ }
80+ ]
81+ },
82+ "node_modules/colord": {
83+ "version": "2.9.3",
84+ "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
85+ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
86+ "dev": true
87+ },
88+ "node_modules/commander": {
89+ "version": "7.2.0",
90+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
91+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
92+ "dev": true,
93+ "engines": {
94+ "node": ">= 10"
95+ }
96+ },
97+ "node_modules/css-declaration-sorter": {
98+ "version": "6.3.1",
99+ "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz",
100+ "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==",
101+ "dev": true,
102+ "engines": {
103+ "node": "^10 || ^12 || >=14"
104+ },
105+ "peerDependencies": {
106+ "postcss": "^8.0.9"
107+ }
108+ },
109+ "node_modules/css-select": {
110+ "version": "4.3.0",
111+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
112+ "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
113+ "dev": true,
114+ "dependencies": {
115+ "boolbase": "^1.0.0",
116+ "css-what": "^6.0.1",
117+ "domhandler": "^4.3.1",
118+ "domutils": "^2.8.0",
119+ "nth-check": "^2.0.1"
120+ },
121+ "funding": {
122+ "url": "https://github.com/sponsors/fb55"
123+ }
124+ },
125+ "node_modules/css-tree": {
126+ "version": "1.1.3",
127+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
128+ "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
129+ "dev": true,
130+ "dependencies": {
131+ "mdn-data": "2.0.14",
132+ "source-map": "^0.6.1"
133+ },
134+ "engines": {
135+ "node": ">=8.0.0"
136+ }
137+ },
138+ "node_modules/css-what": {
139+ "version": "6.1.0",
140+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
141+ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
142+ "dev": true,
143+ "engines": {
144+ "node": ">= 6"
145+ },
146+ "funding": {
147+ "url": "https://github.com/sponsors/fb55"
148+ }
149+ },
150+ "node_modules/cssesc": {
151+ "version": "3.0.0",
152+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
153+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
154+ "dev": true,
155+ "bin": {
156+ "cssesc": "bin/cssesc"
157+ },
158+ "engines": {
159+ "node": ">=4"
160+ }
161+ },
162+ "node_modules/cssnano": {
163+ "version": "5.1.15",
164+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz",
165+ "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==",
166+ "dev": true,
167+ "dependencies": {
168+ "cssnano-preset-default": "^5.2.14",
169+ "lilconfig": "^2.0.3",
170+ "yaml": "^1.10.2"
171+ },
172+ "engines": {
173+ "node": "^10 || ^12 || >=14.0"
174+ },
175+ "funding": {
176+ "type": "opencollective",
177+ "url": "https://opencollective.com/cssnano"
178+ },
179+ "peerDependencies": {
180+ "postcss": "^8.2.15"
181+ }
182+ },
183+ "node_modules/cssnano-preset-default": {
184+ "version": "5.2.14",
185+ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz",
186+ "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==",
187+ "dev": true,
188+ "dependencies": {
189+ "css-declaration-sorter": "^6.3.1",
190+ "cssnano-utils": "^3.1.0",
191+ "postcss-calc": "^8.2.3",
192+ "postcss-colormin": "^5.3.1",
193+ "postcss-convert-values": "^5.1.3",
194+ "postcss-discard-comments": "^5.1.2",
195+ "postcss-discard-duplicates": "^5.1.0",
196+ "postcss-discard-empty": "^5.1.1",
197+ "postcss-discard-overridden": "^5.1.0",
198+ "postcss-merge-longhand": "^5.1.7",
199+ "postcss-merge-rules": "^5.1.4",
200+ "postcss-minify-font-values": "^5.1.0",
201+ "postcss-minify-gradients": "^5.1.1",
202+ "postcss-minify-params": "^5.1.4",
203+ "postcss-minify-selectors": "^5.2.1",
204+ "postcss-normalize-charset": "^5.1.0",
205+ "postcss-normalize-display-values": "^5.1.0",
206+ "postcss-normalize-positions": "^5.1.1",
207+ "postcss-normalize-repeat-style": "^5.1.1",
208+ "postcss-normalize-string": "^5.1.0",
209+ "postcss-normalize-timing-functions": "^5.1.0",
210+ "postcss-normalize-unicode": "^5.1.1",
211+ "postcss-normalize-url": "^5.1.0",
212+ "postcss-normalize-whitespace": "^5.1.1",
213+ "postcss-ordered-values": "^5.1.3",
214+ "postcss-reduce-initial": "^5.1.2",
215+ "postcss-reduce-transforms": "^5.1.0",
216+ "postcss-svgo": "^5.1.0",
217+ "postcss-unique-selectors": "^5.1.1"
218+ },
219+ "engines": {
220+ "node": "^10 || ^12 || >=14.0"
221+ },
222+ "peerDependencies": {
223+ "postcss": "^8.2.15"
224+ }
225+ },
226+ "node_modules/cssnano-utils": {
227+ "version": "3.1.0",
228+ "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz",
229+ "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==",
230+ "dev": true,
231+ "engines": {
232+ "node": "^10 || ^12 || >=14.0"
233+ },
234+ "peerDependencies": {
235+ "postcss": "^8.2.15"
236+ }
237+ },
238+ "node_modules/csso": {
239+ "version": "4.2.0",
240+ "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz",
241+ "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==",
242+ "dev": true,
243+ "dependencies": {
244+ "css-tree": "^1.1.2"
245+ },
246+ "engines": {
247+ "node": ">=8.0.0"
248+ }
249+ },
250+ "node_modules/dom-serializer": {
251+ "version": "1.4.1",
252+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
253+ "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
254+ "dev": true,
255+ "dependencies": {
256+ "domelementtype": "^2.0.1",
257+ "domhandler": "^4.2.0",
258+ "entities": "^2.0.0"
259+ },
260+ "funding": {
261+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
262+ }
263+ },
264+ "node_modules/domelementtype": {
265+ "version": "2.3.0",
266+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
267+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
268+ "dev": true,
269+ "funding": [
270+ {
271+ "type": "github",
272+ "url": "https://github.com/sponsors/fb55"
273+ }
274+ ]
275+ },
276+ "node_modules/domhandler": {
277+ "version": "4.3.1",
278+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
279+ "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
280+ "dev": true,
281+ "dependencies": {
282+ "domelementtype": "^2.2.0"
283+ },
284+ "engines": {
285+ "node": ">= 4"
286+ },
287+ "funding": {
288+ "url": "https://github.com/fb55/domhandler?sponsor=1"
289+ }
290+ },
291+ "node_modules/domutils": {
292+ "version": "2.8.0",
293+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
294+ "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
295+ "dev": true,
296+ "dependencies": {
297+ "dom-serializer": "^1.0.1",
298+ "domelementtype": "^2.2.0",
299+ "domhandler": "^4.2.0"
300+ },
301+ "funding": {
302+ "url": "https://github.com/fb55/domutils?sponsor=1"
303+ }
304+ },
305+ "node_modules/electron-to-chromium": {
306+ "version": "1.4.302",
307+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.302.tgz",
308+ "integrity": "sha512-Uk7C+7aPBryUR1Fwvk9VmipBcN9fVsqBO57jV2ZjTm+IZ6BMNqu7EDVEg2HxCNufk6QcWlFsBkhQyQroB2VWKw==",
309+ "dev": true
310+ },
311+ "node_modules/entities": {
312+ "version": "2.2.0",
313+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
314+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
315+ "dev": true,
316+ "funding": {
317+ "url": "https://github.com/fb55/entities?sponsor=1"
318+ }
319+ },
320+ "node_modules/escalade": {
321+ "version": "3.1.1",
322+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
323+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
324+ "dev": true,
325+ "engines": {
326+ "node": ">=6"
327+ }
328+ },
329+ "node_modules/lilconfig": {
330+ "version": "2.0.6",
331+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz",
332+ "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
333+ "dev": true,
334+ "engines": {
335+ "node": ">=10"
336+ }
337+ },
338+ "node_modules/lodash.memoize": {
339+ "version": "4.1.2",
340+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
341+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
342+ "dev": true
343+ },
344+ "node_modules/lodash.uniq": {
345+ "version": "4.5.0",
346+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
347+ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
348+ "dev": true
349+ },
350+ "node_modules/mdn-data": {
351+ "version": "2.0.14",
352+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
353+ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
354+ "dev": true
355+ },
356+ "node_modules/nanoid": {
357+ "version": "3.3.4",
358+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
359+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
360+ "dev": true,
361+ "peer": true,
362+ "bin": {
363+ "nanoid": "bin/nanoid.cjs"
364+ },
365+ "engines": {
366+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
367+ }
368+ },
369+ "node_modules/node-releases": {
370+ "version": "2.0.10",
371+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
372+ "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
373+ "dev": true
374+ },
375+ "node_modules/normalize-url": {
376+ "version": "6.1.0",
377+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
378+ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
379+ "dev": true,
380+ "engines": {
381+ "node": ">=10"
382+ },
383+ "funding": {
384+ "url": "https://github.com/sponsors/sindresorhus"
385+ }
386+ },
387+ "node_modules/nth-check": {
388+ "version": "2.1.1",
389+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
390+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
391+ "dev": true,
392+ "dependencies": {
393+ "boolbase": "^1.0.0"
394+ },
395+ "funding": {
396+ "url": "https://github.com/fb55/nth-check?sponsor=1"
397+ }
398+ },
399+ "node_modules/picocolors": {
400+ "version": "1.0.0",
401+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
402+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
403+ "dev": true
404+ },
405+ "node_modules/postcss": {
406+ "version": "8.4.21",
407+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
408+ "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
409+ "dev": true,
410+ "funding": [
411+ {
412+ "type": "opencollective",
413+ "url": "https://opencollective.com/postcss/"
414+ },
415+ {
416+ "type": "tidelift",
417+ "url": "https://tidelift.com/funding/github/npm/postcss"
418+ }
419+ ],
420+ "peer": true,
421+ "dependencies": {
422+ "nanoid": "^3.3.4",
423+ "picocolors": "^1.0.0",
424+ "source-map-js": "^1.0.2"
425+ },
426+ "engines": {
427+ "node": "^10 || ^12 || >=14"
428+ }
429+ },
430+ "node_modules/postcss-calc": {
431+ "version": "8.2.4",
432+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz",
433+ "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==",
434+ "dev": true,
435+ "dependencies": {
436+ "postcss-selector-parser": "^6.0.9",
437+ "postcss-value-parser": "^4.2.0"
438+ },
439+ "peerDependencies": {
440+ "postcss": "^8.2.2"
441+ }
442+ },
443+ "node_modules/postcss-colormin": {
444+ "version": "5.3.1",
445+ "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz",
446+ "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==",
447+ "dev": true,
448+ "dependencies": {
449+ "browserslist": "^4.21.4",
450+ "caniuse-api": "^3.0.0",
451+ "colord": "^2.9.1",
452+ "postcss-value-parser": "^4.2.0"
453+ },
454+ "engines": {
455+ "node": "^10 || ^12 || >=14.0"
456+ },
457+ "peerDependencies": {
458+ "postcss": "^8.2.15"
459+ }
460+ },
461+ "node_modules/postcss-convert-values": {
462+ "version": "5.1.3",
463+ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz",
464+ "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==",
465+ "dev": true,
466+ "dependencies": {
467+ "browserslist": "^4.21.4",
468+ "postcss-value-parser": "^4.2.0"
469+ },
470+ "engines": {
471+ "node": "^10 || ^12 || >=14.0"
472+ },
473+ "peerDependencies": {
474+ "postcss": "^8.2.15"
475+ }
476+ },
477+ "node_modules/postcss-discard-comments": {
478+ "version": "5.1.2",
479+ "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz",
480+ "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==",
481+ "dev": true,
482+ "engines": {
483+ "node": "^10 || ^12 || >=14.0"
484+ },
485+ "peerDependencies": {
486+ "postcss": "^8.2.15"
487+ }
488+ },
489+ "node_modules/postcss-discard-duplicates": {
490+ "version": "5.1.0",
491+ "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz",
492+ "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==",
493+ "dev": true,
494+ "engines": {
495+ "node": "^10 || ^12 || >=14.0"
496+ },
497+ "peerDependencies": {
498+ "postcss": "^8.2.15"
499+ }
500+ },
501+ "node_modules/postcss-discard-empty": {
502+ "version": "5.1.1",
503+ "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz",
504+ "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==",
505+ "dev": true,
506+ "engines": {
507+ "node": "^10 || ^12 || >=14.0"
508+ },
509+ "peerDependencies": {
510+ "postcss": "^8.2.15"
511+ }
512+ },
513+ "node_modules/postcss-discard-overridden": {
514+ "version": "5.1.0",
515+ "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz",
516+ "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==",
517+ "dev": true,
518+ "engines": {
519+ "node": "^10 || ^12 || >=14.0"
520+ },
521+ "peerDependencies": {
522+ "postcss": "^8.2.15"
523+ }
524+ },
525+ "node_modules/postcss-merge-longhand": {
526+ "version": "5.1.7",
527+ "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz",
528+ "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==",
529+ "dev": true,
530+ "dependencies": {
531+ "postcss-value-parser": "^4.2.0",
532+ "stylehacks": "^5.1.1"
533+ },
534+ "engines": {
535+ "node": "^10 || ^12 || >=14.0"
536+ },
537+ "peerDependencies": {
538+ "postcss": "^8.2.15"
539+ }
540+ },
541+ "node_modules/postcss-merge-rules": {
542+ "version": "5.1.4",
543+ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz",
544+ "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==",
545+ "dev": true,
546+ "dependencies": {
547+ "browserslist": "^4.21.4",
548+ "caniuse-api": "^3.0.0",
549+ "cssnano-utils": "^3.1.0",
550+ "postcss-selector-parser": "^6.0.5"
551+ },
552+ "engines": {
553+ "node": "^10 || ^12 || >=14.0"
554+ },
555+ "peerDependencies": {
556+ "postcss": "^8.2.15"
557+ }
558+ },
559+ "node_modules/postcss-minify-font-values": {
560+ "version": "5.1.0",
561+ "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz",
562+ "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==",
563+ "dev": true,
564+ "dependencies": {
565+ "postcss-value-parser": "^4.2.0"
566+ },
567+ "engines": {
568+ "node": "^10 || ^12 || >=14.0"
569+ },
570+ "peerDependencies": {
571+ "postcss": "^8.2.15"
572+ }
573+ },
574+ "node_modules/postcss-minify-gradients": {
575+ "version": "5.1.1",
576+ "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz",
577+ "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==",
578+ "dev": true,
579+ "dependencies": {
580+ "colord": "^2.9.1",
581+ "cssnano-utils": "^3.1.0",
582+ "postcss-value-parser": "^4.2.0"
583+ },
584+ "engines": {
585+ "node": "^10 || ^12 || >=14.0"
586+ },
587+ "peerDependencies": {
588+ "postcss": "^8.2.15"
589+ }
590+ },
591+ "node_modules/postcss-minify-params": {
592+ "version": "5.1.4",
593+ "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz",
594+ "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==",
595+ "dev": true,
596+ "dependencies": {
597+ "browserslist": "^4.21.4",
598+ "cssnano-utils": "^3.1.0",
599+ "postcss-value-parser": "^4.2.0"
600+ },
601+ "engines": {
602+ "node": "^10 || ^12 || >=14.0"
603+ },
604+ "peerDependencies": {
605+ "postcss": "^8.2.15"
606+ }
607+ },
608+ "node_modules/postcss-minify-selectors": {
609+ "version": "5.2.1",
610+ "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz",
611+ "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==",
612+ "dev": true,
613+ "dependencies": {
614+ "postcss-selector-parser": "^6.0.5"
615+ },
616+ "engines": {
617+ "node": "^10 || ^12 || >=14.0"
618+ },
619+ "peerDependencies": {
620+ "postcss": "^8.2.15"
621+ }
622+ },
623+ "node_modules/postcss-normalize-charset": {
624+ "version": "5.1.0",
625+ "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz",
626+ "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==",
627+ "dev": true,
628+ "engines": {
629+ "node": "^10 || ^12 || >=14.0"
630+ },
631+ "peerDependencies": {
632+ "postcss": "^8.2.15"
633+ }
634+ },
635+ "node_modules/postcss-normalize-display-values": {
636+ "version": "5.1.0",
637+ "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz",
638+ "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==",
639+ "dev": true,
640+ "dependencies": {
641+ "postcss-value-parser": "^4.2.0"
642+ },
643+ "engines": {
644+ "node": "^10 || ^12 || >=14.0"
645+ },
646+ "peerDependencies": {
647+ "postcss": "^8.2.15"
648+ }
649+ },
650+ "node_modules/postcss-normalize-positions": {
651+ "version": "5.1.1",
652+ "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz",
653+ "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==",
654+ "dev": true,
655+ "dependencies": {
656+ "postcss-value-parser": "^4.2.0"
657+ },
658+ "engines": {
659+ "node": "^10 || ^12 || >=14.0"
660+ },
661+ "peerDependencies": {
662+ "postcss": "^8.2.15"
663+ }
664+ },
665+ "node_modules/postcss-normalize-repeat-style": {
666+ "version": "5.1.1",
667+ "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz",
668+ "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==",
669+ "dev": true,
670+ "dependencies": {
671+ "postcss-value-parser": "^4.2.0"
672+ },
673+ "engines": {
674+ "node": "^10 || ^12 || >=14.0"
675+ },
676+ "peerDependencies": {
677+ "postcss": "^8.2.15"
678+ }
679+ },
680+ "node_modules/postcss-normalize-string": {
681+ "version": "5.1.0",
682+ "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz",
683+ "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==",
684+ "dev": true,
685+ "dependencies": {
686+ "postcss-value-parser": "^4.2.0"
687+ },
688+ "engines": {
689+ "node": "^10 || ^12 || >=14.0"
690+ },
691+ "peerDependencies": {
692+ "postcss": "^8.2.15"
693+ }
694+ },
695+ "node_modules/postcss-normalize-timing-functions": {
696+ "version": "5.1.0",
697+ "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz",
698+ "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==",
699+ "dev": true,
700+ "dependencies": {
701+ "postcss-value-parser": "^4.2.0"
702+ },
703+ "engines": {
704+ "node": "^10 || ^12 || >=14.0"
705+ },
706+ "peerDependencies": {
707+ "postcss": "^8.2.15"
708+ }
709+ },
710+ "node_modules/postcss-normalize-unicode": {
711+ "version": "5.1.1",
712+ "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz",
713+ "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==",
714+ "dev": true,
715+ "dependencies": {
716+ "browserslist": "^4.21.4",
717+ "postcss-value-parser": "^4.2.0"
718+ },
719+ "engines": {
720+ "node": "^10 || ^12 || >=14.0"
721+ },
722+ "peerDependencies": {
723+ "postcss": "^8.2.15"
724+ }
725+ },
726+ "node_modules/postcss-normalize-url": {
727+ "version": "5.1.0",
728+ "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz",
729+ "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==",
730+ "dev": true,
731+ "dependencies": {
732+ "normalize-url": "^6.0.1",
733+ "postcss-value-parser": "^4.2.0"
734+ },
735+ "engines": {
736+ "node": "^10 || ^12 || >=14.0"
737+ },
738+ "peerDependencies": {
739+ "postcss": "^8.2.15"
740+ }
741+ },
742+ "node_modules/postcss-normalize-whitespace": {
743+ "version": "5.1.1",
744+ "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz",
745+ "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==",
746+ "dev": true,
747+ "dependencies": {
748+ "postcss-value-parser": "^4.2.0"
749+ },
750+ "engines": {
751+ "node": "^10 || ^12 || >=14.0"
752+ },
753+ "peerDependencies": {
754+ "postcss": "^8.2.15"
755+ }
756+ },
757+ "node_modules/postcss-ordered-values": {
758+ "version": "5.1.3",
759+ "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz",
760+ "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==",
761+ "dev": true,
762+ "dependencies": {
763+ "cssnano-utils": "^3.1.0",
764+ "postcss-value-parser": "^4.2.0"
765+ },
766+ "engines": {
767+ "node": "^10 || ^12 || >=14.0"
768+ },
769+ "peerDependencies": {
770+ "postcss": "^8.2.15"
771+ }
772+ },
773+ "node_modules/postcss-reduce-initial": {
774+ "version": "5.1.2",
775+ "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz",
776+ "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==",
777+ "dev": true,
778+ "dependencies": {
779+ "browserslist": "^4.21.4",
780+ "caniuse-api": "^3.0.0"
781+ },
782+ "engines": {
783+ "node": "^10 || ^12 || >=14.0"
784+ },
785+ "peerDependencies": {
786+ "postcss": "^8.2.15"
787+ }
788+ },
789+ "node_modules/postcss-reduce-transforms": {
790+ "version": "5.1.0",
791+ "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz",
792+ "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==",
793+ "dev": true,
794+ "dependencies": {
795+ "postcss-value-parser": "^4.2.0"
796+ },
797+ "engines": {
798+ "node": "^10 || ^12 || >=14.0"
799+ },
800+ "peerDependencies": {
801+ "postcss": "^8.2.15"
802+ }
803+ },
804+ "node_modules/postcss-selector-parser": {
805+ "version": "6.0.11",
806+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
807+ "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
808+ "dev": true,
809+ "dependencies": {
810+ "cssesc": "^3.0.0",
811+ "util-deprecate": "^1.0.2"
812+ },
813+ "engines": {
814+ "node": ">=4"
815+ }
816+ },
817+ "node_modules/postcss-svgo": {
818+ "version": "5.1.0",
819+ "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz",
820+ "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==",
821+ "dev": true,
822+ "dependencies": {
823+ "postcss-value-parser": "^4.2.0",
824+ "svgo": "^2.7.0"
825+ },
826+ "engines": {
827+ "node": "^10 || ^12 || >=14.0"
828+ },
829+ "peerDependencies": {
830+ "postcss": "^8.2.15"
831+ }
832+ },
833+ "node_modules/postcss-unique-selectors": {
834+ "version": "5.1.1",
835+ "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz",
836+ "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==",
837+ "dev": true,
838+ "dependencies": {
839+ "postcss-selector-parser": "^6.0.5"
840+ },
841+ "engines": {
842+ "node": "^10 || ^12 || >=14.0"
843+ },
844+ "peerDependencies": {
845+ "postcss": "^8.2.15"
846+ }
847+ },
848+ "node_modules/postcss-value-parser": {
849+ "version": "4.2.0",
850+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
851+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
852+ "dev": true
853+ },
854+ "node_modules/source-map": {
855+ "version": "0.6.1",
856+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
857+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
858+ "dev": true,
859+ "engines": {
860+ "node": ">=0.10.0"
861+ }
862+ },
863+ "node_modules/source-map-js": {
864+ "version": "1.0.2",
865+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
866+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
867+ "dev": true,
868+ "peer": true,
869+ "engines": {
870+ "node": ">=0.10.0"
871+ }
872+ },
873+ "node_modules/stable": {
874+ "version": "0.1.8",
875+ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
876+ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==",
877+ "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility",
878+ "dev": true
879+ },
880+ "node_modules/stylehacks": {
881+ "version": "5.1.1",
882+ "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
883+ "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==",
884+ "dev": true,
885+ "dependencies": {
886+ "browserslist": "^4.21.4",
887+ "postcss-selector-parser": "^6.0.4"
888+ },
889+ "engines": {
890+ "node": "^10 || ^12 || >=14.0"
891+ },
892+ "peerDependencies": {
893+ "postcss": "^8.2.15"
894+ }
895+ },
896+ "node_modules/svgo": {
897+ "version": "2.8.0",
898+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz",
899+ "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==",
900+ "dev": true,
901+ "dependencies": {
902+ "@trysound/sax": "0.2.0",
903+ "commander": "^7.2.0",
904+ "css-select": "^4.1.3",
905+ "css-tree": "^1.1.3",
906+ "csso": "^4.2.0",
907+ "picocolors": "^1.0.0",
908+ "stable": "^0.1.8"
909+ },
910+ "bin": {
911+ "svgo": "bin/svgo"
912+ },
913+ "engines": {
914+ "node": ">=10.13.0"
915+ }
916+ },
917+ "node_modules/update-browserslist-db": {
918+ "version": "1.0.10",
919+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
920+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
921+ "dev": true,
922+ "funding": [
923+ {
924+ "type": "opencollective",
925+ "url": "https://opencollective.com/browserslist"
926+ },
927+ {
928+ "type": "tidelift",
929+ "url": "https://tidelift.com/funding/github/npm/browserslist"
930+ }
931+ ],
932+ "dependencies": {
933+ "escalade": "^3.1.1",
934+ "picocolors": "^1.0.0"
935+ },
936+ "bin": {
937+ "browserslist-lint": "cli.js"
938+ },
939+ "peerDependencies": {
940+ "browserslist": ">= 4.21.0"
941+ }
942+ },
943+ "node_modules/util-deprecate": {
944+ "version": "1.0.2",
945+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
946+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
947+ "dev": true
948+ },
949+ "node_modules/yaml": {
950+ "version": "1.10.2",
951+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
952+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
953+ "dev": true,
954+ "engines": {
955+ "node": ">= 6"
956+ }
957+ }
958+ }
959+}
packages.dhall+25−13
@@ -99,11 +99,10 @@ in upstream
99-------------------------------
99-------------------------------
100-}
100-}
101let upstream =
101let upstream =
102− https://github.com/purescript/package-sets/releases/download/psc-0.15.2-20220706/packages.dhall
102+ https://github.com/purescript/package-sets/releases/download/psc-0.15.4-20220808/packages.dhall
103− sha256:7a24ebdbacb2bfa27b2fc6ce3da96f048093d64e54369965a2a7b5d9892b6031
103+ sha256:60eee64b04ca0013fae3e02a69fc3b176105c6baa2f31865c67cd5f881a412fd
104
104
105in upstream
105in upstream
106− with el = ./el/spago.dhall as Location
107 with argonaut-aeson-generic =
106 with argonaut-aeson-generic =
108 { dependencies =
107 { dependencies =
109 [ "argonaut-codecs"
108 [ "argonaut-codecs"
@@ -126,25 +125,38 @@ in upstream
126 ]
125 ]
127 , repo =
126 , repo =
128 "https://github.com/ilyakooo0/purescript-argonaut-aeson-generic.git"
127 "https://github.com/ilyakooo0/purescript-argonaut-aeson-generic.git"
129− , version = "purescript-0.14"
128+ , version = "3e388500811ef4b6718fc776789f3be050ad7b57"
130 }
129 }
131− with subtlecrypto =
130+ with untagged-union =
132 { dependencies =
131 { dependencies =
133− [ "aff"
132+ [ "assert"
134− , "aff-promise"
135− , "arraybuffer-types"
136 , "console"
133 , "console"
137 , "effect"
134 , "effect"
138 , "either"
135 , "either"
139− , "exceptions"
140 , "foreign"
136 , "foreign"
141− , "functions"
137+ , "foreign-object"
138+ , "literals"
142 , "maybe"
139 , "maybe"
140+ , "newtype"
143 , "prelude"
141 , "prelude"
144− , "transformers"
145 , "tuples"
142 , "tuples"
146 , "unsafe-coerce"
143 , "unsafe-coerce"
147 ]
144 ]
148− , repo = "https://github.com/xc-jp/purescript-subtlecrypto.git"
145+ , repo = "https://github.com/rowtype-yoga/purescript-untagged-union.git"
149− , version = "7999dc92b94d1b7285375cfaad4a0ae6644b71b6"
146+ , version = "6c5bccef38696a16e86ba9a65cf778b4462c4a66"
147+ }
148+ with literals =
149+ { repo = "https://github.com/ilyakooo0/purescript-literals.git"
150+ , version = "6875fb28026595cfb780318305a77e79b098bb01"
151+ , dependencies =
152+ [ "assert"
153+ , "effect"
154+ , "integers"
155+ , "maybe"
156+ , "numbers"
157+ , "partial"
158+ , "prelude"
159+ , "typelevel-prelude"
160+ , "unsafe-coerce"
161+ ]
150 }
162 }
registry.datnew file

Binary file not shown.

scratchpad/password_generator.htmlnew file+74
@@ -0,0 +1,74 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+
4+<head>
5+ <meta charset="UTF-8">
6+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8+ <link rel="stylesheet" href="../vanilla/dist/style.css">
9+ <link rel="stylesheet" href="../style.css">
10+ <title>Password generator</title>
11+
12+ <style>
13+ body {
14+ margin: 10px;
15+ }
16+
17+ </style>
18+
19+</head>
20+
21+<body>
22+
23+ <div class="select-second-factor-container">
24+ <button class="p-button">Hello</button>
25+ <button class="p-button">Bar</button>
26+ <button class="p-button">Bar</button>
27+ <button class="p-button">Bardi</button>
28+ <button class="p-button">Wolrd</button>
29+ </div>
30+
31+ <div class="password-generator-group">
32+ <p>Password length</p>
33+ <input type="number" class="p-slider__input">
34+ </div>
35+
36+
37+ <div class="password-generator-group">
38+ <label class="p-switch">
39+ <input type="checkbox" class="p-switch__input">
40+ <span class="p-switch__slider"></span>
41+ <span class="p-switch__label">Numbers</span>
42+ </label>
43+ <input type="number" class="p-slider__input">
44+ </div>
45+
46+ <form action="">
47+ <label><input type="checkbox" name="" id="">Hahahah</label>
48+ <input type="checkbox">
49+
50+ </form>
51+
52+
53+
54+ <input type="range" min="0" max="100" value="50" step="1">
55+ <input type="range" min="0" max="100" value="50" step="1"
56+ style="background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(0.5, rgb(0, 102, 204)), color-stop(0.5, rgb(217, 217, 217)));">
57+ <h1>h</h1>
58+ <div class="p-slider__wrapper">
59+ <input type="range" min="0" max="100" value="50" step="1" id="slider4" disabled=""
60+ aria-label="Example of a disabled slider, range 0 to 100">
61+ <input class="p-slider__input" type="text" maxlength="3" id="slider4-input" disabled="" tabindex="0">
62+ </div>
63+ <div class="p-slider__wrapper">
64+ <input type="range" min="0" max="100" value="50" step="1" id="slider3" aria-label="Example slider, range 0 to 100">
65+ <input class="p-slider__input" type="number" maxlength="3" id="slider3-input" tabindex="0">
66+ </div>
67+
68+ <form>
69+ <input type="range" min="0" max="100" value="50" step="1">
70+ </form>
71+
72+</body>
73+
74+</html>
screenshots/1.pngnew file

Binary file not shown.

screenshots/2.pngnew file

Binary file not shown.

screenshots/3.pngnew file

Binary file not shown.

screenshots/4.pngnew file

Binary file not shown.

screenshots/5.pngnew file

Binary file not shown.

spago-packages.nixnew file+1102
@@ -0,0 +1,1102 @@
1+# This file was generated by Spago2Nix
2+
3+{ pkgs ? import <nixpkgs> {} }:
4+
5+let
6+ inputs = {
7+
8+ "aff" = pkgs.stdenv.mkDerivation {
9+ name = "aff";
10+ version = "v7.0.0";
11+ src = pkgs.fetchgit {
12+ url = "https://github.com/purescript-contrib/purescript-aff.git";
13+ rev = "2d44d9f9d0d6a416a4103fba2fb39e5160f80e36";
14+ sha256 = "1d38l16s8hvfqq5w40w21j5089svdiggkraw3rwrl6ml7lijjqx4";
15+ };
16+ phases = "installPhase";
17+ installPhase = "ln -s $src $out";
18+ };
19+
20+ "aff-promise" = pkgs.stdenv.mkDerivation {
21+ name = "aff-promise";
22+ version = "v4.0.0";
23+ src = pkgs.fetchgit {
24+ url = "https://github.com/nwolverson/purescript-aff-promise.git";
25+ rev = "3aa74e68e3e4c3e38d821375703e0b2f49d831eb";
26+ sha256 = "10gv3b05dxnlmr9zmigqs1hz4xqrx7da30aqh12kqg3zrg49k9cc";
27+ };
28+ phases = "installPhase";
29+ installPhase = "ln -s $src $out";
30+ };
31+
32+ "argonaut" = pkgs.stdenv.mkDerivation {
33+ name = "argonaut";
34+ version = "v9.0.0";
35+ src = pkgs.fetchgit {
36+ url = "https://github.com/purescript-contrib/purescript-argonaut.git";
37+ rev = "7505a47f2edb0c9cacaac4f11dcedf4723a3e9c8";
38+ sha256 = "18yxhlrwri6q858krz1klyq29fx8nvfm16c04wm2rn91mgyasn6x";
39+ };
40+ phases = "installPhase";
41+ installPhase = "ln -s $src $out";
42+ };
43+
44+ "argonaut-aeson-generic" = pkgs.stdenv.mkDerivation {
45+ name = "argonaut-aeson-generic";
46+ version = "3e388500811ef4b6718fc776789f3be050ad7b57";
47+ src = pkgs.fetchgit {
48+ url = "https://github.com/ilyakooo0/purescript-argonaut-aeson-generic.git";
49+ rev = "3e388500811ef4b6718fc776789f3be050ad7b57";
50+ sha256 = "0yhcxxz5lpgwxxsx93kmr884rk5nrvgy7lmpaixazqps2a2q0fhc";
51+ };
52+ phases = "installPhase";
53+ installPhase = "ln -s $src $out";
54+ };
55+
56+ "argonaut-codecs" = pkgs.stdenv.mkDerivation {
57+ name = "argonaut-codecs";
58+ version = "v9.0.0";
59+ src = pkgs.fetchgit {
60+ url = "https://github.com/purescript-contrib/purescript-argonaut-codecs.git";
61+ rev = "176a5dddae28fda9a2ff31ed4bf1efcc148513a4";
62+ sha256 = "08ys445222syysxh24la8bc8l20lwwqi4vdfa3ph2a60l1lx3kp8";
63+ };
64+ phases = "installPhase";
65+ installPhase = "ln -s $src $out";
66+ };
67+
68+ "argonaut-core" = pkgs.stdenv.mkDerivation {
69+ name = "argonaut-core";
70+ version = "v7.0.0";
71+ src = pkgs.fetchgit {
72+ url = "https://github.com/purescript-contrib/purescript-argonaut-core.git";
73+ rev = "68da81dd80ec36d3b013eff46dc067a972c22e5d";
74+ sha256 = "0hxl17ddbflkk0hchjgk5xj6j9fwp3b182w4g073p0dwscdl4f08";
75+ };
76+ phases = "installPhase";
77+ installPhase = "ln -s $src $out";
78+ };
79+
80+ "argonaut-generic" = pkgs.stdenv.mkDerivation {
81+ name = "argonaut-generic";
82+ version = "v8.0.0";
83+ src = pkgs.fetchgit {
84+ url = "https://github.com/purescript-contrib/purescript-argonaut-generic.git";
85+ rev = "2df4080f036762df91a24b22842e389395ef0bdd";
86+ sha256 = "0f3bkmv37nk89vscvivdr971sjydqiw7lwk9hfdfms05id8jdjkw";
87+ };
88+ phases = "installPhase";
89+ installPhase = "ln -s $src $out";
90+ };
91+
92+ "argonaut-traversals" = pkgs.stdenv.mkDerivation {
93+ name = "argonaut-traversals";
94+ version = "v10.0.0";
95+ src = pkgs.fetchgit {
96+ url = "https://github.com/purescript-contrib/purescript-argonaut-traversals.git";
97+ rev = "8d2403d8d57afb568933dbb36063d5670ce770a0";
98+ sha256 = "0v965bcl1hdp1hacbzxcm35kxxclixj0kpsdm816gs7m548z6l43";
99+ };
100+ phases = "installPhase";
101+ installPhase = "ln -s $src $out";
102+ };
103+
104+ "arraybuffer" = pkgs.stdenv.mkDerivation {
105+ name = "arraybuffer";
106+ version = "v13.0.0";
107+ src = pkgs.fetchgit {
108+ url = "https://github.com/purescript-contrib/purescript-arraybuffer.git";
109+ rev = "3b5ba3040f060057e864805546a50f319853517d";
110+ sha256 = "0cfi15r95zd7ikvs8v3pf2i3rzxjqxk7209j96d8qs6jzqlypbwd";
111+ };
112+ phases = "installPhase";
113+ installPhase = "ln -s $src $out";
114+ };
115+
116+ "arraybuffer-types" = pkgs.stdenv.mkDerivation {
117+ name = "arraybuffer-types";
118+ version = "v3.0.2";
119+ src = pkgs.fetchgit {
120+ url = "https://github.com/purescript-contrib/purescript-arraybuffer-types.git";
121+ rev = "9b0b7a0f9ee034e039f3d3a2a9c3f74eb7c9264a";
122+ sha256 = "1q3111jk6jj64bxy86lc7ik6q5mgdz87bc086wj5rm57dmlivlxx";
123+ };
124+ phases = "installPhase";
125+ installPhase = "ln -s $src $out";
126+ };
127+
128+ "arrays" = pkgs.stdenv.mkDerivation {
129+ name = "arrays";
130+ version = "v7.0.0";
131+ src = pkgs.fetchgit {
132+ url = "https://github.com/purescript/purescript-arrays.git";
133+ rev = "d20bae2f76db6cf29b7b75e26e82b8a54c32295e";
134+ sha256 = "1ciz796yrdfnqiskjjhh6r3bn9mks1dm5zin7kwzh1nfy1i4w9pz";
135+ };
136+ phases = "installPhase";
137+ installPhase = "ln -s $src $out";
138+ };
139+
140+ "assert" = pkgs.stdenv.mkDerivation {
141+ name = "assert";
142+ version = "v6.0.0";
143+ src = pkgs.fetchgit {
144+ url = "https://github.com/purescript/purescript-assert.git";
145+ rev = "27c0edb57d2ee497eb5fab664f5601c35b613eda";
146+ sha256 = "02qpcsijil64qq4kcikv2ynxl85zkryvv0vmzjm6752956ymzi0j";
147+ };
148+ phases = "installPhase";
149+ installPhase = "ln -s $src $out";
150+ };
151+
152+ "avar" = pkgs.stdenv.mkDerivation {
153+ name = "avar";
154+ version = "v5.0.0";
155+ src = pkgs.fetchgit {
156+ url = "https://github.com/purescript-contrib/purescript-avar.git";
157+ rev = "d00f5784d9cc8f079babd62740f5c52b87e5caa5";
158+ sha256 = "132g0ccmjr8328xpaycww8b5wmx1vi3li4irxxqcjk5wi47i8c9h";
159+ };
160+ phases = "installPhase";
161+ installPhase = "ln -s $src $out";
162+ };
163+
164+ "bifunctors" = pkgs.stdenv.mkDerivation {
165+ name = "bifunctors";
166+ version = "v6.0.0";
167+ src = pkgs.fetchgit {
168+ url = "https://github.com/purescript/purescript-bifunctors.git";
169+ rev = "16ba2fb6dd7f05528ebd9e2f9ca3a068b325e5b3";
170+ sha256 = "1qz5aaiq5w4g1rhjb63z47h6nqhw8hw23x1zw5alam047pq4vfkg";
171+ };
172+ phases = "installPhase";
173+ installPhase = "ln -s $src $out";
174+ };
175+
176+ "catenable-lists" = pkgs.stdenv.mkDerivation {
177+ name = "catenable-lists";
178+ version = "v7.0.0";
179+ src = pkgs.fetchgit {
180+ url = "https://github.com/purescript/purescript-catenable-lists.git";
181+ rev = "09abe1f4888bc00841ad2b59e56a9e7ce7ebd4ab";
182+ sha256 = "0fn6caspdnb0nrlj31sy7d5hxrhndfz7a2b9wfkxf5bv5vpjyawb";
183+ };
184+ phases = "installPhase";
185+ installPhase = "ln -s $src $out";
186+ };
187+
188+ "console" = pkgs.stdenv.mkDerivation {
189+ name = "console";
190+ version = "v6.0.0";
191+ src = pkgs.fetchgit {
192+ url = "https://github.com/purescript/purescript-console.git";
193+ rev = "3b83d7b792d03872afeea5e62b4f686ab0f09842";
194+ sha256 = "0fr5l1myhscp910mybp04cg6g8f2hy3ikjfc8fkqlb2dm2cqzdfs";
195+ };
196+ phases = "installPhase";
197+ installPhase = "ln -s $src $out";
198+ };
199+
200+ "const" = pkgs.stdenv.mkDerivation {
201+ name = "const";
202+ version = "v6.0.0";
203+ src = pkgs.fetchgit {
204+ url = "https://github.com/purescript/purescript-const.git";
205+ rev = "ab9570cf2b6e67f7e441178211db1231cfd75c37";
206+ sha256 = "0mcpwqqf5bcwxlzk53smvsjjz1ymlnq0ypphg6sc83ibw70g64f6";
207+ };
208+ phases = "installPhase";
209+ installPhase = "ln -s $src $out";
210+ };
211+
212+ "contravariant" = pkgs.stdenv.mkDerivation {
213+ name = "contravariant";
214+ version = "v6.0.0";
215+ src = pkgs.fetchgit {
216+ url = "https://github.com/purescript/purescript-contravariant.git";
217+ rev = "9ad3e105b8855bcc25f4e0893c784789d05a58de";
218+ sha256 = "1xkd3rfs8v20w7mj6sj6dmhfb2vb1zpmjv245xj1rahk3g16qm7m";
219+ };
220+ phases = "installPhase";
221+ installPhase = "ln -s $src $out";
222+ };
223+
224+ "control" = pkgs.stdenv.mkDerivation {
225+ name = "control";
226+ version = "v6.0.0";
227+ src = pkgs.fetchgit {
228+ url = "https://github.com/purescript/purescript-control.git";
229+ rev = "a6033808790879a17b2729e73747a9ed3fb2264e";
230+ sha256 = "05sdgywprwpav62d2bvlb80yd39brxhnmhhl1f116mm9hw5clqia";
231+ };
232+ phases = "installPhase";
233+ installPhase = "ln -s $src $out";
234+ };
235+
236+ "datetime" = pkgs.stdenv.mkDerivation {
237+ name = "datetime";
238+ version = "v6.0.0";
239+ src = pkgs.fetchgit {
240+ url = "https://github.com/purescript/purescript-datetime.git";
241+ rev = "a6a0cf1b0324964ad1854bc3377ed8766ba90e6f";
242+ sha256 = "1idjljxy8rjlw3wiypk1c9cc1fc4g9kf7drzdy6a0yjdg7504gad";
243+ };
244+ phases = "installPhase";
245+ installPhase = "ln -s $src $out";
246+ };
247+
248+ "datetime-parsing" = pkgs.stdenv.mkDerivation {
249+ name = "datetime-parsing";
250+ version = "v0.2.0";
251+ src = pkgs.fetchgit {
252+ url = "https://github.com/flounders/purescript-datetime-parsing.git";
253+ rev = "aa7d336c2681501dbe343adcbbf8f2ee51b23932";
254+ sha256 = "1w0lfs64qz1hs7zijyzpvqlp2z06wmbz4jp8cjsnw9yj46jsy8nh";
255+ };
256+ phases = "installPhase";
257+ installPhase = "ln -s $src $out";
258+ };
259+
260+ "distributive" = pkgs.stdenv.mkDerivation {
261+ name = "distributive";
262+ version = "v6.0.0";
263+ src = pkgs.fetchgit {
264+ url = "https://github.com/purescript/purescript-distributive.git";
265+ rev = "6005e513642e855ebf6f884d24a35c2803ca252a";
266+ sha256 = "0x6cfsx9ff66kvw9l10pxd5vv16r91mmfxrfc68w1xfdfi81lhdx";
267+ };
268+ phases = "installPhase";
269+ installPhase = "ln -s $src $out";
270+ };
271+
272+ "effect" = pkgs.stdenv.mkDerivation {
273+ name = "effect";
274+ version = "v4.0.0";
275+ src = pkgs.fetchgit {
276+ url = "https://github.com/purescript/purescript-effect.git";
277+ rev = "a192ddb923027d426d6ea3d8deb030c9aa7c7dda";
278+ sha256 = "0aa10lc6h9mlf4xf3g3ziig7v6kxdqvbh20kma8ay59w0b1bhmj1";
279+ };
280+ phases = "installPhase";
281+ installPhase = "ln -s $src $out";
282+ };
283+
284+ "either" = pkgs.stdenv.mkDerivation {
285+ name = "either";
286+ version = "v6.1.0";
287+ src = pkgs.fetchgit {
288+ url = "https://github.com/purescript/purescript-either.git";
289+ rev = "af655a04ed2fd694b6688af39ee20d7907ad0763";
290+ sha256 = "05zps4klvgmvlm06f4hrycssm8q0pysbqnjsrk26lfvmid6mmg63";
291+ };
292+ phases = "installPhase";
293+ installPhase = "ln -s $src $out";
294+ };
295+
296+ "enums" = pkgs.stdenv.mkDerivation {
297+ name = "enums";
298+ version = "v6.0.0";
299+ src = pkgs.fetchgit {
300+ url = "https://github.com/purescript/purescript-enums.git";
301+ rev = "bae47961d401f9acf88da38f32e87403e5f0cf2f";
302+ sha256 = "08sl5q07hv6v2008dcpg4mxiqwk9z0fhq67k0bgkxvlbsmar7khg";
303+ };
304+ phases = "installPhase";
305+ installPhase = "ln -s $src $out";
306+ };
307+
308+ "exceptions" = pkgs.stdenv.mkDerivation {
309+ name = "exceptions";
310+ version = "v6.0.0";
311+ src = pkgs.fetchgit {
312+ url = "https://github.com/purescript/purescript-exceptions.git";
313+ rev = "afab3c07c820bb49b6c5be50049db46a964a6161";
314+ sha256 = "04xrbrcjwsv8gj465ygrlqhv1majxmr6m242iazg6apxlr89hgvg";
315+ };
316+ phases = "installPhase";
317+ installPhase = "ln -s $src $out";
318+ };
319+
320+ "exists" = pkgs.stdenv.mkDerivation {
321+ name = "exists";
322+ version = "v6.0.0";
323+ src = pkgs.fetchgit {
324+ url = "https://github.com/purescript/purescript-exists.git";
325+ rev = "f765b4ace7869c27b9c05949e18c843881f9173b";
326+ sha256 = "0xnxi8fsmi15wrpq5d99gg94glk8a45hpx7pf4pmxg5r6rn1vjvx";
327+ };
328+ phases = "installPhase";
329+ installPhase = "ln -s $src $out";
330+ };
331+
332+ "float32" = pkgs.stdenv.mkDerivation {
333+ name = "float32";
334+ version = "v2.0.0";
335+ src = pkgs.fetchgit {
336+ url = "https://github.com/purescript-contrib/purescript-float32.git";
337+ rev = "3b70984d823c12c1b84a45a9b3d45199a4fcc67d";
338+ sha256 = "0a1lvga6bbhbb8g3crp3fr0kmpkzjmffhfq71ig6flin13xijgys";
339+ };
340+ phases = "installPhase";
341+ installPhase = "ln -s $src $out";
342+ };
343+
344+ "foldable-traversable" = pkgs.stdenv.mkDerivation {
345+ name = "foldable-traversable";
346+ version = "v6.0.0";
347+ src = pkgs.fetchgit {
348+ url = "https://github.com/purescript/purescript-foldable-traversable.git";
349+ rev = "b3926f870532d287ea59e2d5cd3873b81ef2a93a";
350+ sha256 = "0xg8qvyc6r9wqgy7wnw1rjqljl4wpgdrkxsm5x9rsagj2k5brxip";
351+ };
352+ phases = "installPhase";
353+ installPhase = "ln -s $src $out";
354+ };
355+
356+ "foreign" = pkgs.stdenv.mkDerivation {
357+ name = "foreign";
358+ version = "v7.0.0";
359+ src = pkgs.fetchgit {
360+ url = "https://github.com/purescript/purescript-foreign.git";
361+ rev = "2dd222d1ec7363fa0a0a7adb0d8eaf81bb7006dd";
362+ sha256 = "0ycjaal8b2rsg3zl3b7acv166vjfdvziiza3fs8nhfw0rx0xmm1m";
363+ };
364+ phases = "installPhase";
365+ installPhase = "ln -s $src $out";
366+ };
367+
368+ "foreign-object" = pkgs.stdenv.mkDerivation {
369+ name = "foreign-object";
370+ version = "v4.0.0";
371+ src = pkgs.fetchgit {
372+ url = "https://github.com/purescript/purescript-foreign-object.git";
373+ rev = "28a635827a9a6c251df73f68874070d51fe9f756";
374+ sha256 = "0ql0vgndcb5dqaqz81mvrqkrl5c2rlgxpydgdiiaxmmfxvxwi439";
375+ };
376+ phases = "installPhase";
377+ installPhase = "ln -s $src $out";
378+ };
379+
380+ "free" = pkgs.stdenv.mkDerivation {
381+ name = "free";
382+ version = "v7.0.0";
383+ src = pkgs.fetchgit {
384+ url = "https://github.com/purescript/purescript-free.git";
385+ rev = "e2d8fa8023a864363857834e11393483bced5e38";
386+ sha256 = "0gyy0k297m26gw76w7kx8k1zjczlqbqb3nmh2h4gb3r84gl05g6r";
387+ };
388+ phases = "installPhase";
389+ installPhase = "ln -s $src $out";
390+ };
391+
392+ "functions" = pkgs.stdenv.mkDerivation {
393+ name = "functions";
394+ version = "v6.0.0";
395+ src = pkgs.fetchgit {
396+ url = "https://github.com/purescript/purescript-functions.git";
397+ rev = "f626f20580483977c5b27a01aac6471e28aff367";
398+ sha256 = "1bsf2y8hx103a92yrc6m05q2dv51ckag1srd7q5n9vaf2k9byrj9";
399+ };
400+ phases = "installPhase";
401+ installPhase = "ln -s $src $out";
402+ };
403+
404+ "functors" = pkgs.stdenv.mkDerivation {
405+ name = "functors";
406+ version = "v5.0.0";
407+ src = pkgs.fetchgit {
408+ url = "https://github.com/purescript/purescript-functors.git";
409+ rev = "022ffd7a2a7ec12080314f3d217b400674a247b4";
410+ sha256 = "1pnw3r1nk0rx8mrw3ajjml40zvx9v26qrqwj77pz7hpwxigxfbff";
411+ };
412+ phases = "installPhase";
413+ installPhase = "ln -s $src $out";
414+ };
415+
416+ "gen" = pkgs.stdenv.mkDerivation {
417+ name = "gen";
418+ version = "v4.0.0";
419+ src = pkgs.fetchgit {
420+ url = "https://github.com/purescript/purescript-gen.git";
421+ rev = "9fbcc2a1261c32e30d79c5418edef4d96fe76931";
422+ sha256 = "0kajzcp9a8dmqvb3ga27hlf18m2smbp98a0bmzzb9vv156ckw0b4";
423+ };
424+ phases = "installPhase";
425+ installPhase = "ln -s $src $out";
426+ };
427+
428+ "identity" = pkgs.stdenv.mkDerivation {
429+ name = "identity";
430+ version = "v6.0.0";
431+ src = pkgs.fetchgit {
432+ url = "https://github.com/purescript/purescript-identity.git";
433+ rev = "ef6768f8a52ab0bc943a85f5761ba07c257f639f";
434+ sha256 = "1lh0pkwwdyd8q9bgf4l1m96gdnardki4r2s359cnhrwp94b43fyr";
435+ };
436+ phases = "installPhase";
437+ installPhase = "ln -s $src $out";
438+ };
439+
440+ "integers" = pkgs.stdenv.mkDerivation {
441+ name = "integers";
442+ version = "v6.0.0";
443+ src = pkgs.fetchgit {
444+ url = "https://github.com/purescript/purescript-integers.git";
445+ rev = "54d712b25c594833083d15dc9ff2418eb9c52822";
446+ sha256 = "04j9sqvkgyxxvbm8jfc2118wzmgswn8jva9fysgbsnmsg9vcxfm6";
447+ };
448+ phases = "installPhase";
449+ installPhase = "ln -s $src $out";
450+ };
451+
452+ "invariant" = pkgs.stdenv.mkDerivation {
453+ name = "invariant";
454+ version = "v6.0.0";
455+ src = pkgs.fetchgit {
456+ url = "https://github.com/purescript/purescript-invariant.git";
457+ rev = "1d2a196d51e90623adb88496c2cfd759c6736894";
458+ sha256 = "07rqag47ykdiqc5yfbc1a64ijaqliljhcw1vgbv81m7ljzzd2xna";
459+ };
460+ phases = "installPhase";
461+ installPhase = "ln -s $src $out";
462+ };
463+
464+ "js-date" = pkgs.stdenv.mkDerivation {
465+ name = "js-date";
466+ version = "v8.0.0";
467+ src = pkgs.fetchgit {
468+ url = "https://github.com/purescript-contrib/purescript-js-date.git";
469+ rev = "1ea020316946cc4b87195bca9c54d0c16abaa490";
470+ sha256 = "03535g8hc4bzyzig25hf75dls9ypsgrdk2nc45h8zw4xyy4ha0gz";
471+ };
472+ phases = "installPhase";
473+ installPhase = "ln -s $src $out";
474+ };
475+
476+ "js-timers" = pkgs.stdenv.mkDerivation {
477+ name = "js-timers";
478+ version = "v6.1.0";
479+ src = pkgs.fetchgit {
480+ url = "https://github.com/purescript-contrib/purescript-js-timers.git";
481+ rev = "7cb728b3e6dc29f355143617e6e9ac770ecd9273";
482+ sha256 = "0dm933kqhwgmpkylgrlpjplidn8biic8jlzs4l4nny4a1z20mr61";
483+ };
484+ phases = "installPhase";
485+ installPhase = "ln -s $src $out";
486+ };
487+
488+ "lazy" = pkgs.stdenv.mkDerivation {
489+ name = "lazy";
490+ version = "v6.0.0";
491+ src = pkgs.fetchgit {
492+ url = "https://github.com/purescript/purescript-lazy.git";
493+ rev = "48347841226b27af5205a1a8ec71e27a93ce86fd";
494+ sha256 = "0dxlc3b2bdqsri45i7a9bvwvv0gg43cc1gq61zi41f290a29ny2q";
495+ };
496+ phases = "installPhase";
497+ installPhase = "ln -s $src $out";
498+ };
499+
500+ "lcg" = pkgs.stdenv.mkDerivation {
501+ name = "lcg";
502+ version = "v4.0.0";
503+ src = pkgs.fetchgit {
504+ url = "https://github.com/purescript/purescript-lcg.git";
505+ rev = "67c6c6483a563a59ae036d9dca0f1be2835326a5";
506+ sha256 = "1shzn6zvc1cxd7v0bvfsk7x3xf59vxby8c5lfjvd746r6396zn65";
507+ };
508+ phases = "installPhase";
509+ installPhase = "ln -s $src $out";
510+ };
511+
512+ "lists" = pkgs.stdenv.mkDerivation {
513+ name = "lists";
514+ version = "v7.0.0";
515+ src = pkgs.fetchgit {
516+ url = "https://github.com/purescript/purescript-lists.git";
517+ rev = "b113451e5b41cad87d669a3165f955c71cd863e2";
518+ sha256 = "1mg7vy44k8jlqkri7x4ikciixx92b17wi35887x09dwdsicjf0sx";
519+ };
520+ phases = "installPhase";
521+ installPhase = "ln -s $src $out";
522+ };
523+
524+ "literals" = pkgs.stdenv.mkDerivation {
525+ name = "literals";
526+ version = "6875fb28026595cfb780318305a77e79b098bb01";
527+ src = pkgs.fetchgit {
528+ url = "https://github.com/ilyakooo0/purescript-literals.git";
529+ rev = "6875fb28026595cfb780318305a77e79b098bb01";
530+ sha256 = "0s45cdgpdhni3rj7irr8pch46yxwmdcmj2nrg1vpkvygydkikdmb";
531+ };
532+ phases = "installPhase";
533+ installPhase = "ln -s $src $out";
534+ };
535+
536+ "maybe" = pkgs.stdenv.mkDerivation {
537+ name = "maybe";
538+ version = "v6.0.0";
539+ src = pkgs.fetchgit {
540+ url = "https://github.com/purescript/purescript-maybe.git";
541+ rev = "c6f98ac1088766287106c5d9c8e30e7648d36786";
542+ sha256 = "0n33g8c579vcs06ii0r7f14jjdwzjw7p583w4nmrv9h442q2cvlz";
543+ };
544+ phases = "installPhase";
545+ installPhase = "ln -s $src $out";
546+ };
547+
548+ "media-types" = pkgs.stdenv.mkDerivation {
549+ name = "media-types";
550+ version = "v6.0.0";
551+ src = pkgs.fetchgit {
552+ url = "https://github.com/purescript-contrib/purescript-media-types.git";
553+ rev = "af853de226592f319a953637069a943dd261cba3";
554+ sha256 = "0bxpsd2yw3zg58ay649v3xld7n17n7j7n9y8l841cjih9bc7ccxx";
555+ };
556+ phases = "installPhase";
557+ installPhase = "ln -s $src $out";
558+ };
559+
560+ "newtype" = pkgs.stdenv.mkDerivation {
561+ name = "newtype";
562+ version = "v5.0.0";
563+ src = pkgs.fetchgit {
564+ url = "https://github.com/purescript/purescript-newtype.git";
565+ rev = "29d8e6dd77aec2c975c948364ec3faf26e14ee7b";
566+ sha256 = "0kfc644zn2f9rpx8fy4gjvmz04k9mv1c2xrqis8m3s0ydnhbffd7";
567+ };
568+ phases = "installPhase";
569+ installPhase = "ln -s $src $out";
570+ };
571+
572+ "nonempty" = pkgs.stdenv.mkDerivation {
573+ name = "nonempty";
574+ version = "v7.0.0";
575+ src = pkgs.fetchgit {
576+ url = "https://github.com/purescript/purescript-nonempty.git";
577+ rev = "28150ecc7419238b187abd609a92a645273348bb";
578+ sha256 = "1mwdc2sny2ygp67kyk7h2493vq7syf7j18s3d9a5gwf1y7jlf7gh";
579+ };
580+ phases = "installPhase";
581+ installPhase = "ln -s $src $out";
582+ };
583+
584+ "now" = pkgs.stdenv.mkDerivation {
585+ name = "now";
586+ version = "v6.0.0";
587+ src = pkgs.fetchgit {
588+ url = "https://github.com/purescript-contrib/purescript-now.git";
589+ rev = "b5ffed2381e5fefc063f484e607e8499e79eaf32";
590+ sha256 = "01khmqs4iz7dhlvd43zxj19q0c0vvqf1m128vgvgxz5mv430zfgr";
591+ };
592+ phases = "installPhase";
593+ installPhase = "ln -s $src $out";
594+ };
595+
596+ "nullable" = pkgs.stdenv.mkDerivation {
597+ name = "nullable";
598+ version = "v6.0.0";
599+ src = pkgs.fetchgit {
600+ url = "https://github.com/purescript-contrib/purescript-nullable.git";
601+ rev = "3202744c6c65e8d1fbba7f4256a1c482078e7fb5";
602+ sha256 = "10s3b54pjmr2rxlyzvc8yc99k1dpn0qrijnc4y65yj5z6bxf2791";
603+ };
604+ phases = "installPhase";
605+ installPhase = "ln -s $src $out";
606+ };
607+
608+ "numbers" = pkgs.stdenv.mkDerivation {
609+ name = "numbers";
610+ version = "v9.0.0";
611+ src = pkgs.fetchgit {
612+ url = "https://github.com/purescript/purescript-numbers.git";
613+ rev = "2a53528f18f9415334bae28e7bb3cf3be86342c2";
614+ sha256 = "1h4v6ir1hq6gvkk0kla38vlcd6bv73xkc8pv71jygwqxrdwfxxgw";
615+ };
616+ phases = "installPhase";
617+ installPhase = "ln -s $src $out";
618+ };
619+
620+ "ordered-collections" = pkgs.stdenv.mkDerivation {
621+ name = "ordered-collections";
622+ version = "v3.0.0";
623+ src = pkgs.fetchgit {
624+ url = "https://github.com/purescript/purescript-ordered-collections.git";
625+ rev = "9826b7632d0d0a691173bde308a634195f42a419";
626+ sha256 = "1wk8mcn1zsxi0yk9ybas2v7m0drb492806gld1xxxic8x1i19ws0";
627+ };
628+ phases = "installPhase";
629+ installPhase = "ln -s $src $out";
630+ };
631+
632+ "orders" = pkgs.stdenv.mkDerivation {
633+ name = "orders";
634+ version = "v6.0.0";
635+ src = pkgs.fetchgit {
636+ url = "https://github.com/purescript/purescript-orders.git";
637+ rev = "f86db621ec5eef1274145f8b1fd8ebbfe0ed4a2c";
638+ sha256 = "14fjls3v14ia6fr2w4p0i5dqv10zk343wg28m4c77a17jmvbs9r4";
639+ };
640+ phases = "installPhase";
641+ installPhase = "ln -s $src $out";
642+ };
643+
644+ "parallel" = pkgs.stdenv.mkDerivation {
645+ name = "parallel";
646+ version = "v6.0.0";
647+ src = pkgs.fetchgit {
648+ url = "https://github.com/purescript/purescript-parallel.git";
649+ rev = "85290dca837771ac4870071008c933d315ef678f";
650+ sha256 = "0bpwvzq4dn7vx5mfj12w1zvvnamc6w2c45c2v54b9yw36snnwpcf";
651+ };
652+ phases = "installPhase";
653+ installPhase = "ln -s $src $out";
654+ };
655+
656+ "parsing" = pkgs.stdenv.mkDerivation {
657+ name = "parsing";
658+ version = "v9.1.0";
659+ src = pkgs.fetchgit {
660+ url = "https://github.com/purescript-contrib/purescript-parsing.git";
661+ rev = "44ef16b7cfb7cedc3d518c00ef10c6cb73ff4fc8";
662+ sha256 = "1wkk3p1w2k1cvby4airyxgavk1djfzvzc2ch6b8951b592rj8lkr";
663+ };
664+ phases = "installPhase";
665+ installPhase = "ln -s $src $out";
666+ };
667+
668+ "partial" = pkgs.stdenv.mkDerivation {
669+ name = "partial";
670+ version = "v4.0.0";
671+ src = pkgs.fetchgit {
672+ url = "https://github.com/purescript/purescript-partial.git";
673+ rev = "0fa0646f5ea1ec5f0c46dcbd770c705a6c9ad3ec";
674+ sha256 = "04s1h0r3slyd8kcamhqqrr6piksl9y76nmf2418j0ifhp16qwxmm";
675+ };
676+ phases = "installPhase";
677+ installPhase = "ln -s $src $out";
678+ };
679+
680+ "prelude" = pkgs.stdenv.mkDerivation {
681+ name = "prelude";
682+ version = "v6.0.0";
683+ src = pkgs.fetchgit {
684+ url = "https://github.com/purescript/purescript-prelude.git";
685+ rev = "32787f4399c92459c41602131a5858559eb868c5";
686+ sha256 = "0jkbckxw2640gmfi2f8bpf2zwnay2dxy8n24rvxl3w3dihcgd9qc";
687+ };
688+ phases = "installPhase";
689+ installPhase = "ln -s $src $out";
690+ };
691+
692+ "profunctor" = pkgs.stdenv.mkDerivation {
693+ name = "profunctor";
694+ version = "v6.0.0";
695+ src = pkgs.fetchgit {
696+ url = "https://github.com/purescript/purescript-profunctor.git";
697+ rev = "0a966a14e7b0c827d44657dc1710cdc712d2e034";
698+ sha256 = "07rhk8micbc74my6s53xbj2smvvwah2w1drqrcmcnncxdks3lqg3";
699+ };
700+ phases = "installPhase";
701+ installPhase = "ln -s $src $out";
702+ };
703+
704+ "profunctor-lenses" = pkgs.stdenv.mkDerivation {
705+ name = "profunctor-lenses";
706+ version = "v8.0.0";
707+ src = pkgs.fetchgit {
708+ url = "https://github.com/purescript-contrib/purescript-profunctor-lenses.git";
709+ rev = "973d567afe458fd802cf4f0d9725b6dc35ad9297";
710+ sha256 = "0axz7nznn12iqh6sn9qjg6i8qglpdsqgkp30ndwdj98czcnbnybv";
711+ };
712+ phases = "installPhase";
713+ installPhase = "ln -s $src $out";
714+ };
715+
716+ "psci-support" = pkgs.stdenv.mkDerivation {
717+ name = "psci-support";
718+ version = "v6.0.0";
719+ src = pkgs.fetchgit {
720+ url = "https://github.com/purescript/purescript-psci-support.git";
721+ rev = "897cdb543548cb6078d69b6413b54841404eda72";
722+ sha256 = "1ix53r8avkn3fw72mngwzw7v6c6mv7j4miw5mrgjrh9hb8p2ydl1";
723+ };
724+ phases = "installPhase";
725+ installPhase = "ln -s $src $out";
726+ };
727+
728+ "quickcheck" = pkgs.stdenv.mkDerivation {
729+ name = "quickcheck";
730+ version = "v8.0.1";
731+ src = pkgs.fetchgit {
732+ url = "https://github.com/purescript/purescript-quickcheck.git";
733+ rev = "bf5029f97e6c0d7552d3a08d2ab793a19e2c5e3d";
734+ sha256 = "142dvh57fl8b6i7mm37a38v7vkc1znbqz6l6wqa704m53hkikvyd";
735+ };
736+ phases = "installPhase";
737+ installPhase = "ln -s $src $out";
738+ };
739+
740+ "random" = pkgs.stdenv.mkDerivation {
741+ name = "random";
742+ version = "v6.0.0";
743+ src = pkgs.fetchgit {
744+ url = "https://github.com/purescript/purescript-random.git";
745+ rev = "9540bc965a9596da02fefd9949418bb19c92533a";
746+ sha256 = "0qz14qviz5053j9h1jwpk0gnn692hcdx6fp90wckzrnw31d53174";
747+ };
748+ phases = "installPhase";
749+ installPhase = "ln -s $src $out";
750+ };
751+
752+ "record" = pkgs.stdenv.mkDerivation {
753+ name = "record";
754+ version = "v4.0.0";
755+ src = pkgs.fetchgit {
756+ url = "https://github.com/purescript/purescript-record.git";
757+ rev = "c89cd1ada6b636692571fc374196b1c39c4c9f70";
758+ sha256 = "1g7s2h1as5cz824wpm0jhjprrh66shha5i4gq37q73yw0s5p2ahm";
759+ };
760+ phases = "installPhase";
761+ installPhase = "ln -s $src $out";
762+ };
763+
764+ "refs" = pkgs.stdenv.mkDerivation {
765+ name = "refs";
766+ version = "v6.0.0";
767+ src = pkgs.fetchgit {
768+ url = "https://github.com/purescript/purescript-refs.git";
769+ rev = "f8e6216da4cb9309fde1f20cd6f69ac3a3b7f9e8";
770+ sha256 = "09bvfxhjfwfwv55py45s71maazwrr68k0rk4v8ynfqv91h34319h";
771+ };
772+ phases = "installPhase";
773+ installPhase = "ln -s $src $out";
774+ };
775+
776+ "run" = pkgs.stdenv.mkDerivation {
777+ name = "run";
778+ version = "v5.0.0";
779+ src = pkgs.fetchgit {
780+ url = "https://github.com/natefaubion/purescript-run.git";
781+ rev = "abec7c343e92154d44b9dafd52b91ee82d32a870";
782+ sha256 = "059xhjxjfs1w2hmgld53kciyfws3pmjyz75w836ph99rqr4aczny";
783+ };
784+ phases = "installPhase";
785+ installPhase = "ln -s $src $out";
786+ };
787+
788+ "safe-coerce" = pkgs.stdenv.mkDerivation {
789+ name = "safe-coerce";
790+ version = "v2.0.0";
791+ src = pkgs.fetchgit {
792+ url = "https://github.com/purescript/purescript-safe-coerce.git";
793+ rev = "7fa799ae80a38b8d948efcb52608e58e198b3da7";
794+ sha256 = "00m4l733gpl0153cbl6n5kly7jr8ids399apza2rbczif40brp9g";
795+ };
796+ phases = "installPhase";
797+ installPhase = "ln -s $src $out";
798+ };
799+
800+ "st" = pkgs.stdenv.mkDerivation {
801+ name = "st";
802+ version = "v6.0.0";
803+ src = pkgs.fetchgit {
804+ url = "https://github.com/purescript/purescript-st.git";
805+ rev = "2cc7ae1c3318a303378c4a9f3fa0f10ee7cc3589";
806+ sha256 = "1ykk27zn03svfsv2s1mf88d6x3q6qhb9bwjzgals79krw8w0aiyl";
807+ };
808+ phases = "installPhase";
809+ installPhase = "ln -s $src $out";
810+ };
811+
812+ "strings" = pkgs.stdenv.mkDerivation {
813+ name = "strings";
814+ version = "v6.0.0";
815+ src = pkgs.fetchgit {
816+ url = "https://github.com/purescript/purescript-strings.git";
817+ rev = "4bc6954448d056f8aa7a659695a6ad60ad4fdf19";
818+ sha256 = "101va3rawcn5inp20krfg192bbjbvm27xfzwwc1inb2ya0m98amg";
819+ };
820+ phases = "installPhase";
821+ installPhase = "ln -s $src $out";
822+ };
823+
824+ "tailrec" = pkgs.stdenv.mkDerivation {
825+ name = "tailrec";
826+ version = "v6.0.0";
827+ src = pkgs.fetchgit {
828+ url = "https://github.com/purescript/purescript-tailrec.git";
829+ rev = "5e2104aa734b31a17074cc805bf087d72b65afd1";
830+ sha256 = "0vc3dpsd2xxz694zlm75nx1cn6c6f29nzkb8xlxkk6ydw3wpsk3w";
831+ };
832+ phases = "installPhase";
833+ installPhase = "ln -s $src $out";
834+ };
835+
836+ "test-unit" = pkgs.stdenv.mkDerivation {
837+ name = "test-unit";
838+ version = "v17.0.0";
839+ src = pkgs.fetchgit {
840+ url = "https://github.com/bodil/purescript-test-unit.git";
841+ rev = "3112d7ebe06d467238058a6384dc75ffd960d335";
842+ sha256 = "02gfb4dqbj5v7dwifm8jhr0lh7lfp5hari5795w1z48wy3apnvvx";
843+ };
844+ phases = "installPhase";
845+ installPhase = "ln -s $src $out";
846+ };
847+
848+ "transformers" = pkgs.stdenv.mkDerivation {
849+ name = "transformers";
850+ version = "v6.0.0";
851+ src = pkgs.fetchgit {
852+ url = "https://github.com/purescript/purescript-transformers.git";
853+ rev = "be72ab52357d9a665cbf93d73ba1c07c4b0957ee";
854+ sha256 = "0ijrdsppl8vx0rlbwc9p0vjbmvwlcy4ia7xymvk6y34zbxzjlzm6";
855+ };
856+ phases = "installPhase";
857+ installPhase = "ln -s $src $out";
858+ };
859+
860+ "tuples" = pkgs.stdenv.mkDerivation {
861+ name = "tuples";
862+ version = "v7.0.0";
863+ src = pkgs.fetchgit {
864+ url = "https://github.com/purescript/purescript-tuples.git";
865+ rev = "4f52da2729b448c8564369378f1232d8d2dc1d8b";
866+ sha256 = "1m1ng0xxicb73945jymcl1hn2y2hmynlnmhb2k0kkn1jrjwgcc3d";
867+ };
868+ phases = "installPhase";
869+ installPhase = "ln -s $src $out";
870+ };
871+
872+ "type-equality" = pkgs.stdenv.mkDerivation {
873+ name = "type-equality";
874+ version = "v4.0.1";
875+ src = pkgs.fetchgit {
876+ url = "https://github.com/purescript/purescript-type-equality.git";
877+ rev = "0525b7d39e0fbd81b4209518139fb8ab02695774";
878+ sha256 = "1ass38jdycsjisdimdc4drg2w8vkkwp6lkvz3kvy7q0h98vdmlbr";
879+ };
880+ phases = "installPhase";
881+ installPhase = "ln -s $src $out";
882+ };
883+
884+ "typelevel-prelude" = pkgs.stdenv.mkDerivation {
885+ name = "typelevel-prelude";
886+ version = "v7.0.0";
887+ src = pkgs.fetchgit {
888+ url = "https://github.com/purescript/purescript-typelevel-prelude.git";
889+ rev = "dca2fe3c8cfd5527d4fe70c4bedfda30148405bf";
890+ sha256 = "0x86mrg33kpnrnsfp4p3c92j5lpyqzy87bxdynwf7smk3inqr2jc";
891+ };
892+ phases = "installPhase";
893+ installPhase = "ln -s $src $out";
894+ };
895+
896+ "uint" = pkgs.stdenv.mkDerivation {
897+ name = "uint";
898+ version = "v7.0.0";
899+ src = pkgs.fetchgit {
900+ url = "https://github.com/purescript-contrib/purescript-uint.git";
901+ rev = "9e4f76ffd5192472f75583844172fe8ab3c0cb9f";
902+ sha256 = "173bhrd006q53s7agwyasxhfbr89x9jpz5b47vm2fr74l3jcw3lq";
903+ };
904+ phases = "installPhase";
905+ installPhase = "ln -s $src $out";
906+ };
907+
908+ "undefined-is-not-a-problem" = pkgs.stdenv.mkDerivation {
909+ name = "undefined-is-not-a-problem";
910+ version = "v1.1.0";
911+ src = pkgs.fetchgit {
912+ url = "https://github.com/paluh/purescript-undefined-is-not-a-problem.git";
913+ rev = "8ac77ac8525343f7319cd48bf2ceb736286f3b27";
914+ sha256 = "1qv7gdyslfj86ga0a7gxkbpvykm3s1x4kzcm8zi75lhlvkpyjh3w";
915+ };
916+ phases = "installPhase";
917+ installPhase = "ln -s $src $out";
918+ };
919+
920+ "unfoldable" = pkgs.stdenv.mkDerivation {
921+ name = "unfoldable";
922+ version = "v6.0.0";
923+ src = pkgs.fetchgit {
924+ url = "https://github.com/purescript/purescript-unfoldable.git";
925+ rev = "493dfe04ed590e20d8f69079df2f58486882748d";
926+ sha256 = "15z2k639ph8wdkrc2y838m5am1z7szw2vqymmv021skzisyn7zwf";
927+ };
928+ phases = "installPhase";
929+ installPhase = "ln -s $src $out";
930+ };
931+
932+ "unicode" = pkgs.stdenv.mkDerivation {
933+ name = "unicode";
934+ version = "v6.0.0";
935+ src = pkgs.fetchgit {
936+ url = "https://github.com/purescript-contrib/purescript-unicode.git";
937+ rev = "6454d59d9e1fd1bc5a72e80e985d8282022a567a";
938+ sha256 = "0w2gsmqcy7pj5vd22qzpig20bdgzmbagyqxrg9mm1njjfsmcm8cv";
939+ };
940+ phases = "installPhase";
941+ installPhase = "ln -s $src $out";
942+ };
943+
944+ "unsafe-coerce" = pkgs.stdenv.mkDerivation {
945+ name = "unsafe-coerce";
946+ version = "v6.0.0";
947+ src = pkgs.fetchgit {
948+ url = "https://github.com/purescript/purescript-unsafe-coerce.git";
949+ rev = "ab956f82e66e633f647fb3098e8ddd3ec58d689f";
950+ sha256 = "0r6d3dx8jalfzvrvkagz9v05yxwkkhgbzlpswg4w1cyl03zjcla4";
951+ };
952+ phases = "installPhase";
953+ installPhase = "ln -s $src $out";
954+ };
955+
956+ "untagged-union" = pkgs.stdenv.mkDerivation {
957+ name = "untagged-union";
958+ version = "6c5bccef38696a16e86ba9a65cf778b4462c4a66";
959+ src = pkgs.fetchgit {
960+ url = "https://github.com/rowtype-yoga/purescript-untagged-union.git";
961+ rev = "6c5bccef38696a16e86ba9a65cf778b4462c4a66";
962+ sha256 = "0s4pik6xcr7zqhlrjxjqgzp8hfg09kgd58zq3xjj0inw9ly35ywn";
963+ };
964+ phases = "installPhase";
965+ installPhase = "ln -s $src $out";
966+ };
967+
968+ "variant" = pkgs.stdenv.mkDerivation {
969+ name = "variant";
970+ version = "v8.0.0";
971+ src = pkgs.fetchgit {
972+ url = "https://github.com/natefaubion/purescript-variant.git";
973+ rev = "8b2bd73a4202257d80a955c4dd36f6055f149823";
974+ sha256 = "18nyxnaqqg1zzydwd2hh9hbzr31j509g5yb1lnd0m60bma4l9h5g";
975+ };
976+ phases = "installPhase";
977+ installPhase = "ln -s $src $out";
978+ };
979+
980+ "web-dom" = pkgs.stdenv.mkDerivation {
981+ name = "web-dom";
982+ version = "v6.0.0";
983+ src = pkgs.fetchgit {
984+ url = "https://github.com/purescript-web/purescript-web-dom.git";
985+ rev = "568a1ee158b29e6e739e7a9aaed3e35ca4c4305a";
986+ sha256 = "0angs86ld0h3ld889ag55lh62101dfn3fknyqrfv0imgn40vwm44";
987+ };
988+ phases = "installPhase";
989+ installPhase = "ln -s $src $out";
990+ };
991+
992+ "web-events" = pkgs.stdenv.mkDerivation {
993+ name = "web-events";
994+ version = "v4.0.0";
995+ src = pkgs.fetchgit {
996+ url = "https://github.com/purescript-web/purescript-web-events.git";
997+ rev = "2124356117be7b764a2f3948032255ac4dab7051";
998+ sha256 = "0l47gsgj71x0gjbhjqwsar2y44ljhq3xa0nkgr1qvqxmcm00llqf";
999+ };
1000+ phases = "installPhase";
1001+ installPhase = "ln -s $src $out";
1002+ };
1003+
1004+ "web-file" = pkgs.stdenv.mkDerivation {
1005+ name = "web-file";
1006+ version = "v4.0.0";
1007+ src = pkgs.fetchgit {
1008+ url = "https://github.com/purescript-web/purescript-web-file.git";
1009+ rev = "023786ae62bbb8bf58156dd7f02011fa38221ef1";
1010+ sha256 = "1wjf1m5y2qsba83is297icv9zyl7jilxdga0192mm1rp0j16jjln";
1011+ };
1012+ phases = "installPhase";
1013+ installPhase = "ln -s $src $out";
1014+ };
1015+
1016+ "web-html" = pkgs.stdenv.mkDerivation {
1017+ name = "web-html";
1018+ version = "v4.0.0";
1019+ src = pkgs.fetchgit {
1020+ url = "https://github.com/purescript-web/purescript-web-html.git";
1021+ rev = "be189cf91b9a19cf493637423522e2fe4a0088cc";
1022+ sha256 = "0yspv6sci3mkflvqwf5jvz3a8nclmhbvazx9n34acyj3aai7k6bs";
1023+ };
1024+ phases = "installPhase";
1025+ installPhase = "ln -s $src $out";
1026+ };
1027+
1028+ "web-storage" = pkgs.stdenv.mkDerivation {
1029+ name = "web-storage";
1030+ version = "v5.0.0";
1031+ src = pkgs.fetchgit {
1032+ url = "https://github.com/purescript-web/purescript-web-storage.git";
1033+ rev = "6b74461e136755db70c271dc898d51776363d7e2";
1034+ sha256 = "0q1grh8cp6h1szsqg3xmwlzrc1bbz8yzlwxlcl1iym5mjiyzgkcs";
1035+ };
1036+ phases = "installPhase";
1037+ installPhase = "ln -s $src $out";
1038+ };
1039+
1040+ };
1041+
1042+ cpPackage = pkg:
1043+ let
1044+ target = ".spago/${pkg.name}/${pkg.version}";
1045+ in ''
1046+ if [ ! -e ${target} ]; then
1047+ echo "Installing ${target}."
1048+ mkdir -p ${target}
1049+ cp --no-preserve=mode,ownership,timestamp -r ${toString pkg.outPath}/* ${target}
1050+ else
1051+ echo "${target} already exists. Skipping."
1052+ fi
1053+ '';
1054+
1055+ getGlob = pkg: ''".spago/${pkg.name}/${pkg.version}/src/**/*.purs"'';
1056+
1057+ getStoreGlob = pkg: ''"${pkg.outPath}/src/**/*.purs"'';
1058+
1059+in {
1060+ inherit inputs;
1061+
1062+ installSpagoStyle = pkgs.writeShellScriptBin "install-spago-style" ''
1063+ set -e
1064+ echo installing dependencies...
1065+ ${builtins.toString (builtins.map cpPackage (builtins.attrValues inputs))}
1066+ echo "echo done."
1067+ '';
1068+
1069+ buildSpagoStyle = pkgs.writeShellScriptBin "build-spago-style" ''
1070+ set -e
1071+ echo building project...
1072+ purs compile ${builtins.toString (builtins.map getGlob (builtins.attrValues inputs))} "$@"
1073+ echo done.
1074+ '';
1075+
1076+ buildFromNixStore = pkgs.writeShellScriptBin "build-from-store" ''
1077+ set -e
1078+ echo building project using sources from nix store...
1079+ purs compile ${builtins.toString (
1080+ builtins.map getStoreGlob (builtins.attrValues inputs))} "$@"
1081+ echo done.
1082+ '';
1083+
1084+ mkBuildProjectOutput =
1085+ { src, purs }:
1086+
1087+ pkgs.stdenv.mkDerivation {
1088+ name = "build-project-output";
1089+ src = src;
1090+
1091+ buildInputs = [ purs ];
1092+
1093+ installPhase = ''
1094+ mkdir -p $out
1095+ purs compile "$src/**/*.purs" ${builtins.toString
1096+ (builtins.map
1097+ (x: ''"${x.outPath}/src/**/*.purs"'')
1098+ (builtins.attrValues inputs))}
1099+ mv output $out
1100+ '';
1101+ };
1102+}
spago.dhall+7−4
@@ -26,24 +26,27 @@ to generate this file without the comments in this block.
26 , "datetime-parsing"
26 , "datetime-parsing"
27 , "effect"
27 , "effect"
28 , "either"
28 , "either"
29− , "el"
30 , "exceptions"
29 , "exceptions"
31 , "foldable-traversable"
30 , "foldable-traversable"
32 , "foreign"
31 , "foreign"
33 , "functions"
32 , "functions"
34− , "lists"
33+ , "integers"
34+ , "literals"
35 , "maybe"
35 , "maybe"
36 , "newtype"
36 , "newtype"
37 , "nullable"
37 , "nullable"
38+ , "numbers"
39+ , "orders"
38 , "prelude"
40 , "prelude"
41+ , "random"
39 , "refs"
42 , "refs"
43+ , "run"
40 , "strings"
44 , "strings"
41− , "subtlecrypto"
42 , "transformers"
45 , "transformers"
43 , "tuples"
46 , "tuples"
44 , "typelevel-prelude"
47 , "typelevel-prelude"
45 , "undefined-is-not-a-problem"
48 , "undefined-is-not-a-problem"
46− , "unsafe-coerce"
49+ , "untagged-union"
47 , "web-html"
50 , "web-html"
48 , "web-storage"
51 , "web-storage"
49 ]
52 ]
style.css+101−10
@@ -1,32 +1,49 @@
1−
2:root {
1:root {
3− --color-light: #F7F7F7;
2+ --color-light: #f7f7f7;
4}
3}
5
4
6main {
5main {
7 margin: 1rem;
6 margin: 1rem;
8}
7}
9
8
10−
11nav.p-tabs__list {
9nav.p-tabs__list {
12 top: 0;
10 top: 0;
13 position: sticky;
11 position: sticky;
14 background-color: white;
12 background-color: white;
15 padding-bottom: 1rem;
13 padding-bottom: 1rem;
14+ padding-right: 1rem;
16 padding-bottom: 0;
15 padding-bottom: 0;
17 z-index: 10;
16 z-index: 10;
18}
17}
19
18
19+nav h3 {
20+ width: 100%;
21+}
22+
20.ninety-counter {
23.ninety-counter {
21 transform: rotate(90deg);
24 transform: rotate(90deg);
22}
25}
23
26
27+@keyframes floating-notification-animation {
28+ from {
29+ transform: translateY(-60px);
30+ opacity: 0;
31+ }
32+
33+ to {
34+ transform: none;
35+ opacity: 1;
36+ }
37+}
38+
24.floating-notifications {
39.floating-notifications {
25 position: fixed;
40 position: fixed;
26 top: 1rem;
41 top: 1rem;
27 right: 1rem;
42 right: 1rem;
28 left: 1rem;
43 left: 1rem;
29 z-index: 9999;
44 z-index: 9999;
45+ animation-duration: 0.5s;
46+ animation-name: floating-notification-animation;
30}
47}
31
48
32.simple-flex-center {
49.simple-flex-center {
@@ -38,17 +55,17 @@ nav.p-tabs__list {
38 align-content: center;
55 align-content: center;
39}
56}
40
57
41−.spinner{
58+.spinner {
42 padding: 1rem;
59 padding: 1rem;
43}
60}
44
61
45.cipher-row-container {
62.cipher-row-container {
46− display: flex;
63+ display: flex;
47− width: 100%;
64+ width: 100%;
48− align-items: baseline;
65+ align-items: baseline;
49− justify-content: space-between;
66+ justify-content: space-between;
50− flex-direction: row;
67+ flex-direction: row;
51− flex-wrap: wrap;
68+ flex-wrap: wrap;
52}
69}
53
70
54.cipher-row p {
71.cipher-row p {
@@ -58,3 +75,77 @@ nav.p-tabs__list {
58.cipher-row .p-text--small {
75.cipher-row .p-text--small {
59 margin-bottom: 0.5rem;
76 margin-bottom: 0.5rem;
60}
77}
78+
79+.ciphers-list {
80+ height: calc(100vh - 9.5rem);
81+ overflow: auto;
82+ margin: 0;
83+}
84+
85+p {
86+ word-break: break-word;
87+}
88+
89+.no-scroll-bar::-webkit-scrollbar {
90+ display: none;
91+}
92+
93+textarea {
94+ resize: none;
95+}
96+
97+.password-generator-group {
98+ display: flex;
99+ flex-direction: row;
100+ align-items: baseline;
101+ justify-content: space-between;
102+}
103+
104+.password-generator-group > input[type="number"] {
105+ width: 5%;
106+ margin-bottom: 0px;
107+ min-width: 5rem;
108+ text-align: center;
109+}
110+
111+.p-side-navigation__drawer {
112+ max-width: initial;
113+}
114+
115+.select-cipher-type-container {
116+ display: flex;
117+ flex-direction: column;
118+ align-items: center;
119+ justify-content: center;
120+}
121+
122+.select-cipher-type-container > * {
123+ margin: 0px;
124+ margin-bottom: 1.5rem;
125+}
126+
127+.overlay {
128+ position: fixed;
129+ top: 0;
130+ left: 0;
131+ right: 0;
132+ bottom: 0;
133+}
134+
135+.select-second-factor-container {
136+ display: flex;
137+ flex-direction: column;
138+ align-items: center;
139+ justify-content: center;
140+}
141+
142+.p-tabs__list {
143+ overflow: initial;
144+}
145+
146+.p-modal {
147+ top: 0;
148+ left: 0;
149+ right: 0;
150+ bottom: 0;
151+}
tests/package-lock.jsonnew file+73
@@ -0,0 +1,73 @@
1+{
2+ "name": "warden-tests",
3+ "version": "1.0.0",
4+ "lockfileVersion": 2,
5+ "requires": true,
6+ "packages": {
7+ "": {
8+ "name": "warden-tests",
9+ "version": "1.0.0",
10+ "devDependencies": {
11+ "@playwright/test": "^1.27.1"
12+ }
13+ },
14+ "node_modules/@playwright/test": {
15+ "version": "1.27.1",
16+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.27.1.tgz",
17+ "integrity": "sha512-mrL2q0an/7tVqniQQF6RBL2saskjljXzqNcCOVMUjRIgE6Y38nCNaP+Dc2FBW06bcpD3tqIws/HT9qiMHbNU0A==",
18+ "dev": true,
19+ "dependencies": {
20+ "@types/node": "*",
21+ "playwright-core": "1.27.1"
22+ },
23+ "bin": {
24+ "playwright": "cli.js"
25+ },
26+ "engines": {
27+ "node": ">=14"
28+ }
29+ },
30+ "node_modules/@types/node": {
31+ "version": "18.11.9",
32+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
33+ "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
34+ "dev": true
35+ },
36+ "node_modules/playwright-core": {
37+ "version": "1.27.1",
38+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz",
39+ "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==",
40+ "dev": true,
41+ "bin": {
42+ "playwright": "cli.js"
43+ },
44+ "engines": {
45+ "node": ">=14"
46+ }
47+ }
48+ },
49+ "dependencies": {
50+ "@playwright/test": {
51+ "version": "1.27.1",
52+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.27.1.tgz",
53+ "integrity": "sha512-mrL2q0an/7tVqniQQF6RBL2saskjljXzqNcCOVMUjRIgE6Y38nCNaP+Dc2FBW06bcpD3tqIws/HT9qiMHbNU0A==",
54+ "dev": true,
55+ "requires": {
56+ "@types/node": "*",
57+ "playwright-core": "1.27.1"
58+ }
59+ },
60+ "@types/node": {
61+ "version": "18.11.9",
62+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
63+ "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
64+ "dev": true
65+ },
66+ "playwright-core": {
67+ "version": "1.27.1",
68+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz",
69+ "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==",
70+ "dev": true
71+ }
72+ }
73+}
tests/package.jsonnew file+9
@@ -0,0 +1,9 @@
1+{
2+ "name": "warden-tests",
3+ "version": "1.0.0",
4+ "scripts": {},
5+ "keywords": [],
6+ "devDependencies": {
7+ "@playwright/test": "^1.27.1"
8+ }
9+}
tests/playwright.config.tsnew file+65
@@ -0,0 +1,65 @@
1+import type { PlaywrightTestConfig } from '@playwright/test';
2+import { devices } from '@playwright/test';
3+
4+/**
5+ * See https://playwright.dev/docs/test-configuration.
6+ */
7+const config: PlaywrightTestConfig = {
8+ testDir: './src',
9+ /* Maximum time one test can run for. */
10+ timeout: 30 * 1000,
11+ expect: {
12+ /**
13+ * Maximum time expect() should wait for the condition to be met.
14+ * For example in `await expect(locator).toHaveText();`
15+ */
16+ timeout: 5000
17+ },
18+ /* Run tests in files in parallel */
19+ fullyParallel: false,
20+ /* Fail the build on CI if you accidentally left test.only in the source code. */
21+ forbidOnly: !!process.env.CI,
22+ /* Retry on CI only */
23+ retries: 0,
24+ /* Opt out of parallel tests on CI. */
25+ workers: 1,
26+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
27+ reporter: 'html',
28+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
29+ use: {
30+ /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
31+ actionTimeout: 0,
32+ /* Base URL to use in actions like `await page.goto('/')`. */
33+ // baseURL: 'http://localhost:3000',
34+
35+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
36+ trace: 'on-first-retry',
37+ },
38+
39+ /* Configure projects for major browsers */
40+ projects: [
41+ {
42+ name: 'Pixel 3',
43+ use: {
44+ ...devices['Pixel 3'],
45+ launchOptions: {
46+ args: ["--disable-web-security"],
47+ },
48+ headless: process.env.RECORD_TEST !== "true",
49+ }
50+ },
51+
52+ ],
53+
54+ /* Folder for test artifacts such as screenshots, videos, traces, etc. */
55+ outputDir: 'test-results/',
56+
57+ /* Run your local dev server before starting the tests */
58+ webServer: {
59+ command: './vaultwarden/run.sh',
60+ port: 8000,
61+ },
62+
63+};
64+
65+export default config;
tests/src/test.spec.tsnew file+257
@@ -0,0 +1,257 @@
1+import { test, expect, Page } from '@playwright/test';
2+
3+async function logIn(page: Page) {
4+ await page.locator('input[type="url"]').tap();
5+ await page.locator('input[type="url"]').press('Control+a');
6+ await page.locator('input[type="url"]').fill('http://localhost:8000');
7+ await page.locator('input[type="email"]').tap();
8+ await page.locator('input[type="email"]').fill('test@example.com');
9+ await page.locator('input[type="password"]').tap();
10+ await page.locator('input[type="password"]').fill('Password123');
11+ await page.getByRole('button', { name: 'Log in' }).tap();
12+ await page.locator('.p-icon--spinner.u-animation--spin.spinner').waitFor({ state: "visible" })
13+ await page.locator('.p-icon--spinner.u-animation--spin.spinner').waitFor({ state: "hidden" })
14+}
15+
16+async function fillInLoginCipher(page: Page) {
17+ await page.locator('p:has-text("Entry name") input[type="text"]').tap();
18+ await page.locator('p:has-text("Entry name") input[type="text"]').fill('Test login entry');
19+ await page.locator('p:has-text("Username") input[type="text"]').tap();
20+ await page.locator('p:has-text("Username") input[type="text"]').fill('login_entry_username');
21+ await page.locator('input[type="text"]').nth(2).tap();
22+ await page.locator('input[type="text"]').nth(2).fill('Password123');
23+ await page.locator('input[type="url"]').tap();
24+ await page.locator('input[type="url"]').fill('https://example.com');
25+}
26+
27+async function fillInCardCipher(page: Page) {
28+ await page.locator('p:has-text("Entry name") input[type="text"]').tap();
29+ await page.locator('p:has-text("Entry name") input[type="text"]').fill('Test card');
30+ await page.locator('p:has-text("Card number") input[type="text"]').tap();
31+ await page.locator('p:has-text("Card number") input[type="text"]').fill('1234567890123456');
32+ await page.locator('p:has-text("Expiration date /") input[type="text"]').first().tap();
33+ await page.locator('p:has-text("Expiration date /") input[type="text"]').first().fill('01');
34+ await page.locator('p:has-text("Expiration date /") input[type="text"]').nth(1).tap();
35+ await page.locator('p:has-text("Expiration date /") input[type="text"]').nth(1).fill('01');
36+ await page.locator('p:has-text("CVV") input[type="text"]').tap();
37+ await page.locator('p:has-text("CVV") input[type="text"]').fill('123');
38+ await page.locator('p:has-text("Cardholder name") input[type="text"]').tap();
39+ await page.locator('p:has-text("Cardholder name") input[type="text"]').fill('Sampel Palnet');
40+}
41+
42+async function fillInNoteCipher(page: Page) {
43+ await page.locator('input[type="text"]').tap();
44+ await page.locator('input[type="text"]').fill('Secret note');
45+ await page.locator('textarea').tap();
46+ await page.locator('textarea').fill('This is a very secret note');
47+}
48+
49+async function fillInContractCipher(page: Page) {
50+ await page.locator('p:has-text("Entry name") input[type="text"]').tap();
51+ await page.locator('p:has-text("Entry name") input[type="text"]').fill('John Appleseed');
52+ await page.locator('p:has-text("First name") input[type="text"]').tap();
53+ await page.locator('p:has-text("First name") input[type="text"]').fill('John');
54+ await page.locator('p:has-text("Middle name") input[type="text"]').tap();
55+ await page.locator('p:has-text("Middle name") input[type="text"]').fill('Mathews');
56+ await page.locator('p:has-text("Last name") input[type="text"]').tap();
57+ await page.locator('p:has-text("Last name") input[type="text"]').fill('Appleseed');
58+ await page.locator('p:has-text("Username") input[type="text"]').tap();
59+ await page.locator('p:has-text("Username") input[type="text"]').fill('jappleseed');
60+ await page.locator('p:has-text("Company") input[type="text"]').tap();
61+ await page.locator('p:has-text("Company") input[type="text"]').fill('Very good building company');
62+ await page.locator('p:has-text("Social security number") input[type="number"]').tap();
63+ await page.locator('p:has-text("Social security number") input[type="number"]').fill('123456789');
64+ await page.locator('p:has-text("Passport number") input[type="number"]').tap();
65+ await page.locator('p:has-text("Passport number") input[type="number"]').fill('123456798');
66+ await page.locator('p:has-text("License Number") input[type="number"]').tap();
67+ await page.locator('p:has-text("License Number") input[type="number"]').fill('12345');
68+ await page.locator('input[type="email"]').tap();
69+ await page.locator('input[type="email"]').fill('john@example.com');
70+ await page.locator('input[type="tel"]').tap();
71+ await page.locator('input[type="tel"]').fill('123456789');
72+ await page.locator('p:has-text("Address line 1") input[type="text"]').tap();
73+ await page.locator('p:has-text("Address line 1") input[type="text"]').fill('Abc def');
74+ await page.locator('p:has-text("Address line 2") input[type="text"]').tap();
75+ await page.locator('p:has-text("Address line 2") input[type="text"]').fill('Ghijk Lmn');
76+}
77+
78+async function screenshot(page: Page) {
79+ await page.waitForLoadState("networkidle")
80+
81+ if (await page.locator('div.floating-notifications').isVisible()) {
82+ await page.locator("button.p-notification__close").tap()
83+ await screenshot(page)
84+ } else if (await page.locator('.p-icon--spinner.u-animation--spin.spinner').isVisible()) {
85+ await page.locator('.p-icon--spinner.u-animation--spin.spinner').waitFor({ state: "hidden" })
86+ await screenshot(page)
87+ } else {
88+ await expect.soft(page).toHaveScreenshot({ animations: "disabled", fullPage: true, maxDiffPixelRatio: 0.01 })
89+ }
90+}
91+
92+async function waitForNotification(page: Page) {
93+ await page.locator('div.floating-notifications').waitFor({ state: "visible" })
94+}
95+
96+
97+/**
98+ * Screenshots the very first (last edited) entry.
99+ */
100+function cipherPage(name: string) {
101+ test(name + " cipher page", async ({ page }) => {
102+ await logIn(page)
103+
104+ await page.locator('.cipher-row').first().tap()
105+
106+ await screenshot(page)
107+ })
108+}
109+
110+test.beforeEach(async ({ page }) => {
111+ await page.goto(`file://${__dirname}/../../dist/index.html`);
112+ await page.addStyleTag({ content: '.hide-in-test {opacity: 0;}' })
113+})
114+
115+if (process.env.RECORD_TEST === "true") {
116+
117+ test('record', async ({ page }) => {
118+ await logIn(page)
119+ await page.pause()
120+ })
121+
122+} else {
123+ test('Login page', async ({ page }) => {
124+ await screenshot(page)
125+ });
126+
127+ test('Empty ciphers page', async ({ page }) => {
128+ await logIn(page)
129+
130+ await screenshot(page)
131+ })
132+
133+ test('Login entry page', async ({ page }) => {
134+ await logIn(page)
135+
136+ await page.getByRole('button').nth(2).tap();
137+ await page.getByRole('button', { name: 'Login' }).tap();
138+
139+ await screenshot(page)
140+
141+ await fillInLoginCipher(page)
142+
143+ await screenshot(page)
144+
145+ await page.getByRole('heading').locator('span').getByRole('button').tap();
146+
147+ await waitForNotification(page)
148+ });
149+
150+ cipherPage("Login")
151+
152+ test('Create Card entry page', async ({ page }) => {
153+ await logIn(page)
154+
155+ await page.getByRole('button').nth(2).tap();
156+ await page.getByRole('button', { name: 'Card' }).tap();
157+
158+ await screenshot(page)
159+
160+ await fillInCardCipher(page)
161+
162+ await screenshot(page)
163+
164+ await page.getByRole('heading').locator('span').getByRole('button').tap();
165+
166+ await waitForNotification(page)
167+ })
168+
169+ cipherPage("Card")
170+
171+ test('Create Note entry page', async ({ page }) => {
172+ await logIn(page)
173+
174+ await page.getByRole('button').nth(2).tap();
175+ await page.getByRole('button', { name: 'Note' }).tap();
176+
177+ await screenshot(page)
178+
179+ await fillInNoteCipher(page)
180+
181+ await screenshot(page)
182+
183+ await page.getByRole('heading').locator('span').getByRole('button').tap();
184+
185+ await waitForNotification(page)
186+ })
187+
188+ cipherPage("Note")
189+
190+ test('Create Contact entry page', async ({ page }) => {
191+ await logIn(page)
192+
193+ await page.getByRole('button').nth(2).tap();
194+ await page.getByRole('button', { name: 'Contact' }).tap();
195+
196+ await screenshot(page)
197+
198+ await fillInContractCipher(page)
199+
200+ await screenshot(page)
201+
202+ await page.getByRole('heading').locator('span').getByRole('button').tap();
203+
204+ await waitForNotification(page)
205+ })
206+
207+ cipherPage("Contact")
208+
209+ test('Filled ciphers page', async ({ page }) => {
210+ await logIn(page)
211+ await screenshot(page)
212+ })
213+
214+ test('Side bar', async ({ page }) => {
215+ await logIn(page)
216+
217+ await page.getByRole('button').nth(1).tap();
218+
219+ await screenshot(page)
220+ })
221+
222+ test('Passwords view', async ({ page }) => {
223+ await logIn(page)
224+
225+ await page.getByRole('button').nth(1).click();
226+ await page.getByText('Passwords').click();
227+
228+ await screenshot(page)
229+ })
230+
231+ test('Notes view', async ({ page }) => {
232+ await logIn(page)
233+
234+ await page.getByRole('button').nth(1).click();
235+ await page.getByText('Notes').click();
236+
237+ await screenshot(page)
238+ })
239+
240+ test('Cards view', async ({ page }) => {
241+ await logIn(page)
242+
243+ await page.getByRole('button').nth(1).click();
244+ await page.getByText('Cards').click();
245+
246+ await screenshot(page)
247+ })
248+
249+ test('Contacts view', async ({ page }) => {
250+ await logIn(page)
251+
252+ await page.getByRole('button').nth(1).click();
253+ await page.getByText('Contacts').click();
254+
255+ await screenshot(page)
256+ })
257+}
tests/src/test.spec.ts-snapshots/Card-cipher-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Cards-view-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Contact-cipher-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Contacts-view-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Create-Card-entry-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Create-Card-entry-page-2-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Create-Contact-entry-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Create-Contact-entry-page-2-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Create-Note-entry-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Create-Note-entry-page-2-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Empty-ciphers-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Filled-ciphers-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Login-cipher-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Login-entry-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Login-entry-page-2-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Login-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Note-cipher-page-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Notes-view-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Passwords-view-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/src/test.spec.ts-snapshots/Side-bar-1-Pixel-3-linux.pngnew file

Binary file not shown.

tests/vaultwarden/.envnew file+3
@@ -0,0 +1,3 @@
1+WEB_VAULT_ENABLED=false
2+LOGIN_RATELIMIT_SECONDS=1
3+LOGIN_RATELIMIT_MAX_BURST=100000000
tests/vaultwarden/README.mdnew file+4
@@ -0,0 +1,4 @@
1+# Test vaultwarden setup
2+
3+login: `test@example.com`
4+password: `Password123`
tests/vaultwarden/data/db.sqlite3new file

Binary file not shown.

tests/vaultwarden/data/db.sqlite3-shmnew file

Binary file not shown.

tests/vaultwarden/data/db.sqlite3-walnew file

No changes to the content.

tests/vaultwarden/data/rsa_key.pemnew file+27
@@ -0,0 +1,27 @@
1+-----BEGIN RSA PRIVATE KEY-----
2+MIIEowIBAAKCAQEArVVTdkrmMbDxyGxGY73Fse9Du7DplgSBPQ5uyOnAsqf6pa1U
3+jjWwTV7Xqzp0z7k32WC64P0eGdacTQGZiFUQxTVm6bJ4TZ8bh4fz51tB4npCAzpr
4+8EcgGRzZT+cA1GQOG7jDqv8w9edWWGdPr6mx1ENvnpXtv60tvnwfsioAUSnx3SC6
5+0oTVCxj1InyneUDXCk/UqBE1wmwl8aF5U3aOc42qcELAgr6i0aCsK1LQjH+SOREh
6+tRQgpDF17IdYAxdYUTyinqrYJnoo5kQXeC/4QoZIsDi1empas7pXwrDlVYtjNK2T
7+Oht6dWUnffc2oTUCNY+ATZ6Nsu4zItEM7yQo/QIDAQABAoIBABUX/q21JLlkXrA0
8+2QYXWKE0TGphd7O9fZrYEExcAgrb3WlfJBygVQG9aoKYfRGwaScHmgZ6VzjuwJuw
9+d6Y7L+zH+Gu1P+W2WKvQNDLyDJ0KotDwwaT+it3zgFC0fIncWUkvXYSIRMWQLxZY
10+ED8A6Da8NC+48DN02E3HnjXY3awCusn9fQkPLeparsdyrvz5s8lnYYQZ6u7FQeD9
11+gM5SUP8WVLtPl4KBidJvHl4qPDAxnu98cZEoffbWIAsg3wvKNZL9yE21ZGWdExxA
12+/czWYO4ckuGY7Tb0Y+MiRI5kBM4VbCzwVfZTLRPXnKddiL7QpLMNmYpHzEcMZut4
13+z3vcdnMCgYEA5ZIzaltIEfL8zmX76pIPlyBs5n98RaagCIxQyURNRSU4H6Sr6yW2
14+CNAszcn6iAxiteXkPUkWmPXE4BFt0ifKgecPQIDk0spaVu2khRPMwUMyR0PfwXfY
15+2fqT0zMZX0MB3VPxnDTY9sAvTi2FsskL9j+Pil7U8bYaixzdc2H+LcsCgYEAwUm2
16+5A1eyX8ojZM5xUaQ/ZAiW+9u0fI5UxJ9Q0SFUWn11LJyIoIvkw6puE10508e7UT1
17+rnYZc5v/Mr6E1oO2NGBHkOZdlfJMD346r2YAtm2Nt/HZuLZU38wyIZzg0FxS6lmZ
18+m1vTEoM6Ko5GVtjvLEst27ZZDgWRW7rJNyYEq1cCgYBm6ZJY9FCrkzUXRYqXziBl
19+DCklbzMDej21d0lj4FWPUK8HKGfVQxOGgeQD4K6JvJunlUYq3w/dRBecptOHaUUT
20+1MP2k5GAmSvEWkXtTkH0xS3N2rR84z/3/6ey/u450S0Nzeq6rsowW1kZf7i2u/+7
21+xSSQ0BA/xvXBNRBlACHU3wKBgQDAuYF0zqgOfY2v7HaT75KuYUMBLDHxgHYMEMl5
22+gx7zWJXXqL4JDonlNZ+gtwqzgL6zkijt9a/XCFpi/HhzdAz/GGkDX888rcWNRS26
23+qhhXzdCa8rlGZfcm1vG9djDrUMMUDJcOw3YoZVRgDlCwU8nW/ncRNjaBtqeDMTFk
24+nbHWLQKBgH0Xatwy6DVHDbU+GYcZBjyQP5zCz0JEjDE8hEhv/ioCfUK1rfjY58zU
25+oXDdn3GpFm6eFxcVxv9Ki4jViQDYAvAErBl0kMkVU6hvITPlMEyqBh89h65xUEnw
26+ttaX+Q2lKGwHsZT4RzA45XjO8kvBymY9siL2JQUMLB8wpu9RyTRj
27+-----END RSA PRIVATE KEY-----
tests/vaultwarden/data/rsa_key.pub.pemnew file+9
@@ -0,0 +1,9 @@
1+-----BEGIN PUBLIC KEY-----
2+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVVTdkrmMbDxyGxGY73F
3+se9Du7DplgSBPQ5uyOnAsqf6pa1UjjWwTV7Xqzp0z7k32WC64P0eGdacTQGZiFUQ
4+xTVm6bJ4TZ8bh4fz51tB4npCAzpr8EcgGRzZT+cA1GQOG7jDqv8w9edWWGdPr6mx
5+1ENvnpXtv60tvnwfsioAUSnx3SC60oTVCxj1InyneUDXCk/UqBE1wmwl8aF5U3aO
6+c42qcELAgr6i0aCsK1LQjH+SOREhtRQgpDF17IdYAxdYUTyinqrYJnoo5kQXeC/4
7+QoZIsDi1empas7pXwrDlVYtjNK2TOht6dWUnffc2oTUCNY+ATZ6Nsu4zItEM7yQo
8+/QIDAQAB
9+-----END PUBLIC KEY-----
tests/vaultwarden/run.shnew file+13
@@ -0,0 +1,13 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd "$(dirname "$0")"
6+
7+tmp="$(mktemp -d)"
8+
9+cp -r data .env "$tmp"
10+
11+cd "$tmp"
12+
13+nix run github:NixOS/nixpkgs?rev=d9a1414346059619d9e13ab93e749bbb82e5252a#vaultwarden
vanilladeleted−1

Submodule changed.

appname.apparmor → warden.apparmorrenamed+1−1
@@ -5,5 +5,5 @@
5 "audio",
5 "audio",
6 "networking"
6 "networking"
7 ],
7 ],
8− "policy_version": 16.04
8+ "policy_version": 20.04
9}
9}
appname.desktop → warden.desktoprenamed+3−3
@@ -1,7 +1,7 @@
1[Desktop Entry]
1[Desktop Entry]
2−Name=App Title
2+Name=Warden
3−Exec=webapp-container --local-content-can-access-remote-urls --app-id="appname.iko" $@ index.html
3+Exec=webapp-container --local-content-can-access-remote-urls --app-id="soy.iko.warden" $@ index.html
4−Icon=logo.svg
4+Icon=logo.png
5Terminal=false
5Terminal=false
6Type=Application
6Type=Application
7X-Ubuntu-Touch=true
7X-Ubuntu-Touch=true