on 890ca92 help sign in
109 files changed+1290−7228
since splitsnapshots
unifiedsplit
.github/FUNDING.yml−1 .github/workflows/build.yaml−97 .gitignore+1−12 .gitmodules+6 .helix/languages.toml−3 .vscode/tasks.json−31 README.md+8−32 warden.apparmor → appname.apparmor+1−1 warden.desktop → appname.desktop backend-main.js−3 backend/BW.js+55−60 backend/BW.purs+29−43 backend/BW/Logic.purs+159−413 backend/BW/Types.purs+390−445 backend/Bridge.purs+11−172 backend/Data/EncString.js+2−5 backend/Data/EncString.purs−2 backend/Data/SymmetricCryptoKey.js+2−1 backend/FFI.js+1−16 backend/Main.purs+118−244 backend/Storage.purs+1−7 bridge.yaml+63−121 build.sh+10−2 clickable.yaml+10−2 deps/bw+1 dev/build-backend.sh+1−7 dev/build-bw.sh−2 dev/build.sh−10 dev/elm-live.sh+1−5 dev/elm2nix.sh−8 dev/generate-bridge.sh+1−1 dev/record-test.sh−11 dev/spago2nix.sh−3 dev/start-chrome.sh+1−7 dev/test.sh−9 dev/update-tests.sh−9 elm-srcs.nix−62 elm.json−1 flake.lock−177 flake.nix−133 frontend.nix−49 frontend/Bridge.elm+16−274 frontend/GlobalEvents.elm−9 frontend/Logic/Cipher.elm−78 frontend/Logic/SecondFactor.elm−31 frontend/Main.elm+156−512 frontend/Modal.elm−33 frontend/Page.elm+13−23 frontend/Pages/Captcha.elm+34−22 frontend/Pages/Cipher.elm+31−126 frontend/Pages/Ciphers.elm+58−148 frontend/Pages/EditCipher.elm−475 frontend/Pages/Login.elm+42−41 frontend/Pages/MasterPassword.elm+30−21 frontend/Pages/Navigation.elm+10−20 frontend/Pages/PasswordGenerator.elm−158 frontend/Pages/SecondFactor.elm−91 frontend/Pages/SecondFactorSelect.elm−164 frontend/Switch.elm−35 frontend/Types.elm−5 frontend/Utils.elm−65 img/donate.png index.html+2−2 manifest.json+6−6 package-lock.json−959 packages.dhall+6−6 registry.dat scratchpad/password_generator.html−74 screenshots/1.png screenshots/2.png screenshots/3.png screenshots/4.png screenshots/5.png spago-packages.nix−1102 spago.dhall−5 style.css+13−76 tests/package-lock.json−73 tests/package.json−9 tests/playwright.config.ts−65 tests/src/test.spec.ts−257 tests/src/test.spec.ts-snapshots/Card-cipher-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Cards-view-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Contact-cipher-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Contacts-view-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Create-Card-entry-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Create-Card-entry-page-2-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Create-Contact-entry-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Create-Contact-entry-page-2-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Create-Note-entry-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Create-Note-entry-page-2-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Empty-ciphers-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Filled-ciphers-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Login-cipher-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Login-entry-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Login-entry-page-2-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Login-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Note-cipher-page-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Notes-view-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Passwords-view-1-Pixel-3-linux.png tests/src/test.spec.ts-snapshots/Side-bar-1-Pixel-3-linux.png tests/vaultwarden/.env−3 tests/vaultwarden/README.md−4 tests/vaultwarden/data/db.sqlite3 tests/vaultwarden/data/db.sqlite3-shm tests/vaultwarden/data/db.sqlite3-wal tests/vaultwarden/data/rsa_key.pem−27 tests/vaultwarden/data/rsa_key.pub.pem−9 tests/vaultwarden/run.sh−13 vanilla+1
.github/FUNDING.ymldeleted−1
@@ -1 +0,0 @@
1−custom: ["https://iko.soy/donate"]
.github/workflows/build.yamldeleted−97
@@ -1,97 +0,0 @@
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+1−12
@@ -7,16 +7,5 @@ static/backend.js
77.spago
88output
99.psc-ide-port
10−elm.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
2010backend.js
21−vanilla
22−dist
11+elm.js
.gitmodulesnew file+6
@@ -0,0 +1,6 @@
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.tomldeleted−3
@@ -1,3 +0,0 @@
1−[[language]]
2−name = "nix"
3−auto-format = true
.vscode/tasks.jsondeleted−31
@@ -1,31 +0,0 @@
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+8−32
@@ -1,38 +1,14 @@
1−<p align="center">
2− <img src="logo.png" style="width: 100px;" />
3−</p>
1+# App Title
42
5−<h1 align="center">Warden</h1>
3+A short description of your app
64
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>
5+## License
106
7+Copyright (C) 2022 Your FullName
118
12−<p align="center">
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>
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
10+by the Free Software Foundation.
1911
20−Warden is a [Bitwarden](https://bitwarden.com)/[Vaultwarden](https://github.com/dani-garcia/vaultwarden) client for [Ubuntu Touch](https://ubuntu-touch.io).
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.
2113
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).
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/>.
warden.apparmor → appname.apparmorrenamed+1−1
@@ -5,5 +5,5 @@
55 "audio",
66 "networking"
77 ],
8− "policy_version": 20.04
8+ "policy_version": 16.04
99}
warden.desktop → appname.desktoprenamed

The content is the same.

backend-main.jsdeleted−3
@@ -1,3 +0,0 @@
1−import * as Main from "./backend/Main/index.js"
2−
3−Main.main()
backend/BW.js+55−60
@@ -1,29 +1,24 @@
1−import { StateFactory } from "../../bw/factories/stateFactory";
2−import { GlobalState } from "../../bw/models/domain/global-state";
3−import { ApiService } from "../../bw/services/api.service";
4−import { AppIdService } from "../../bw/services/appId.service";
5−import { ConsoleLogService } from "../../bw/services/consoleLog.service";
6−import { EnvironmentService } from "../../bw/services/environment.service";
7−import { StateMigrationService } from "../../bw/services/stateMigration.service";
8−import { TokenService } from "../../bw/services/token.service";
9−import { WebCryptoFunctionService } from "../../bw/services/webCryptoFunction.service";
10−import { Account, AccountProfile, AccountTokens } from "../../bw/models/domain/account";
11−import {MemoryStorageService} from "../../bw/services/memoryStorage.service"
12−
13−// import BrowserStorageService from "../../../../bw/services/browserStorage.service";
14−import { CryptoService } from "../../bw/services/crypto.service";
15−
16−import { I18nService } from "../../bw/services/i18n.service";
17−import { StateService as BaseStateService } from "../../bw/services/state.service";
18−import { PasswordTokenRequest } from "../../bw/models/request/identity-token/password-token.request";
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"
1+import { StateFactory } from "../../deps/bw/libs/shared/dist/src/factories/stateFactory";
2+import { GlobalState } from "../../deps/bw/libs/shared/dist/src/models/domain/globalState";
3+import { ApiService } from "../../deps/bw/libs/shared/dist/src/services/api.service";
4+import { AppIdService } from "../../deps/bw/libs/shared/dist/src/services/appId.service";
5+import { ConsoleLogService } from "../../deps/bw/libs/shared/dist/src/services/consoleLog.service";
6+import { EnvironmentService } from "../../deps/bw/libs/shared/dist/src/services/environment.service";
7+import { StateMigrationService } from "../../deps/bw/libs/shared/dist/src/services/stateMigration.service";
8+import { TokenService } from "../../deps/bw/libs/shared/dist/src/services/token.service";
9+import { WebCryptoFunctionService } from "../../deps/bw/libs/shared/dist/src/services/webCryptoFunction.service";
10+import { Account, AccountProfile, AccountTokens } from "../../deps/bw/libs/shared/dist/src/models/domain/account";
11+
12+// import BrowserStorageService from "../../../../deps/bw/libs/shared/dist/src/services/browserStorage.service";
13+import { CryptoService } from "../../deps/bw/libs/shared/dist/src/services/crypto.service";
14+
15+import { I18nService } from "../../deps/bw/libs/shared/dist/src/services/i18n.service";
16+import { StateService as BaseStateService } from "../../deps/bw/libs/shared/dist/src/services/state.service";
17+import { PasswordTokenRequest } from "../../deps/bw/libs/shared/dist/src/models/request/identityToken/passwordTokenRequest";
18+import { TokenRequestTwoFactor } from "../../deps/bw/libs/common/src/models/request/identityToken/tokenRequestTwoFactor.ts";
19+import { CipherRequest } from "../../deps/bw/libs/common/src/models/request/cipherRequest";
20+import { Cipher } from "../../deps/bw/libs/common/src/models/domain/cipher";
21+import { CipherData } from "../../deps/bw/libs/common/src/models/data/cipherData";
2722
2823function sanitize(obj) {
2924 return JSON.parse(JSON.stringify(obj));
@@ -38,7 +33,7 @@ export function getServices() {
3833
3934 await bg.bootstrap()
4035
41− await bg.environmentService.setUrls(urls)
36+ bg.environmentService.setUrls(urls)
4237
4338 if (tokenResponse != null) {
4439 const accountInformation = await bg.tokenService.decodeToken(tokenResponse.accessToken);
@@ -75,25 +70,17 @@ export function getServices() {
7570 req.email,
7671 req.masterPasswordHash,
7772 req.captchaResponse,
78− new TokenTwoFactorRequest(
79− req.twoFactor.provider,
80− req.twoFactor.token,
81− req.twoFactor.remember
82− ),
73+ new TokenRequestTwoFactor(req.twoFactor),
8374 req.device
8475 )
8576 ),
8677 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),
78+ postCipher: (cipher) => api.postCipher(makeCipherRequest(cipher)),
79+ putCipher: (id) => (cipher) => api.putCipher(id, makeCipherRequest(cipher)),
9180 }
9281 },
9382 crypto: bg.cryptoService,
94− cryptoFunctions: bg.cryptoFunctionService,
95− passwordGeneration: bg.passwordGenerationService,
96− totpService: bg.totpService,
83+ cryptoFunctions: bg.cryptoFunctionService
9784 }
9885}
9986
@@ -101,10 +88,6 @@ function makeCipherRequest(cipher) {
10188 return new CipherRequest(new Cipher(new CipherData(cipher)))
10289}
10390
104−function makeCipherCreateRequest(cipher) {
105− return new CipherCreateRequest(new Cipher(new CipherData(cipher)))
106−}
107−
10891class MainBackground {
10992 // messagingService; //: MessagingServiceAbstraction;
11093 // storageService; //: StorageServiceAbstraction;
@@ -133,7 +116,6 @@ class MainBackground {
133116 new StateFactory(GlobalState, Account)
134117 );
135118 this.stateService = new StateService(
136− this.storageService,
137119 this.storageService,
138120 this.storageService,
139121 this.logService,
@@ -164,13 +146,8 @@ class MainBackground {
164146 );
165147 this.i18nService = new I18nService("en");
166148 this.cryptoFunctionService = new WebCryptoFunctionService(window);
167− this.encryptService = new EncryptServiceImplementation(
168− this.cryptoFunctionService,
169− this.logService
170− );
171149 this.cryptoService = new BrowserCryptoService(
172150 this.cryptoFunctionService,
173− this.encryptService,
174151 this.platformUtilsService,
175152 this.logService,
176153 this.stateService
@@ -185,16 +162,6 @@ class MainBackground {
185162 this.appIdService,
186163 (expired) => this.logout(expired)
187164 );
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− );
198165 }
199166
200167 async bootstrap() {
@@ -214,6 +181,34 @@ class MainBackground {
214181 }
215182}
216183
184+class MemoryStorageService {
185+
186+ constructor() {
187+ this.storage = {};
188+ }
189+
190+ async get(key) {
191+ return this.storage[key]
192+ }
193+
194+ async has(key) {
195+ return key in this.storage;
196+ }
197+
198+ async save(key, obj) {
199+ if (obj == null) {
200+ this.remove(key)
201+ return
202+ }
203+
204+ this.storage[key] = obj
205+ }
206+
207+ async remove(key) {
208+ delete this.storage[key]
209+ }
210+}
211+
217212class BrowserCryptoService extends CryptoService {
218213 async retrieveKeyFromStorage(keySuffix) {
219214 if (keySuffix === "biometric") {
@@ -294,7 +289,7 @@ class BrowserPlatformUtilsService {
294289 saveFile(win, blobData, blobOptions, fileName) {
295290 // BrowserApi.downloadFile(win, blobData, blobOptions, fileName);
296291 }
297− getApplicationVersionNumber() {
292+ getApplicationVersion() {
298293 // return Promise.resolve(BrowserApi.getApplicationVersion());
299294 }
300295 supportsWebAuthn(win) {
backend/BW.purs+29−43
@@ -1,10 +1,8 @@
11module BW where
22
33import BW.Types
4−import Bridge
54import Prelude
65
7−import Bridge as Bridge
86import Control.Promise (Promise)
97import Data.Argonaut (class DecodeJson, class EncodeJson, decodeJson, encodeJson)
108import Data.ArrayBuffer.Typed as AB
@@ -17,35 +15,23 @@ import Effect (Effect)
1715import Effect.Unsafe (unsafePerformEffect)
1816import Untagged.Union (type (|+|))
1917
20−type TotpService =
21− { getCode :: String -> Promise String
22− , getTimeInterval :: String -> Int
23− }
24−
25−type CryptoService =
26− { makeKey :: Fn4 Password String KDF KdfConfig (Promise SymmetricCryptoKey)
27− , hashPassword :: Fn3 Password SymmetricCryptoKey (JNullable HashPurpose) (Promise StringHash)
28− , decryptToUtf8 :: Fn2 EncString SymmetricCryptoKey (Promise String)
29− , decryptToBytes :: Fn2 EncString SymmetricCryptoKey (Promise ArrayBuffer)
30− , encrypt :: Fn2 String SymmetricCryptoKey (Promise EncString)
31− , stretchKey :: SymmetricCryptoKey -> Promise SymmetricCryptoKey
32− }
33−
34−type ApiService =
35− { postPrelogin :: PreloginRequest -> Promise PreloginResponse
36− , getProfile :: Unit -> Promise ProfileResponse
37− -- TODO: There can really be more request and response types. Maybe handle this later.
38− , postIdentityToken :: PasswordTokenRequest -> Promise (IdentityCaptchaResponse |+| IdentityTwoFactorResponse |+| IdentityTokenResponse)
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
48− }
18+type CryptoService
19+ = { makeKey :: Fn4 Password String KDF Int (Promise SymmetricCryptoKey)
20+ , hashPassword :: Fn3 Password SymmetricCryptoKey (JNullable HashPurpose) (Promise StringHash)
21+ , decryptToUtf8 :: Fn2 EncString SymmetricCryptoKey (Promise String)
22+ , decryptToBytes :: Fn2 EncString SymmetricCryptoKey (Promise ArrayBuffer)
23+ , stretchKey :: SymmetricCryptoKey -> Promise SymmetricCryptoKey
24+ }
25+
26+type ApiService
27+ = { postPrelogin :: PreloginRequest -> Promise PreloginResponse
28+ , getProfile :: Unit -> Promise ProfileResponse
29+ -- TODO: There can really be more request and response types. Maybe handle this later.
30+ , postIdentityToken :: PasswordTokenRequest -> Promise (IdentityCaptchaResponse |+| IdentityTokenResponse)
31+ , getSync :: Unit -> Promise SyncResponse
32+ , postCipher :: CipherResponse -> Promise CipherResponse
33+ , putCipher :: CipherId -> CipherResponse -> Promise CipherResponse
34+ }
4935
5036cryptoFunctionsTypeSha1 = "sha1" :: CryptoFunctionsType
5137
@@ -55,13 +41,15 @@ cryptoFunctionsTypeSha512 = "sha512" :: CryptoFunctionsType
5541
5642cryptoFunctionsTypeMd5 = "md5" :: CryptoFunctionsType
5743
58−type CryptoFunctionsType = String
44+type CryptoFunctionsType
45+ = String
5946
60−type CryptoFunctions =
61− { hash :: Fn2 String CryptoFunctionsType (Promise Hash)
62− }
47+type CryptoFunctions
48+ = { hash :: Fn2 String CryptoFunctionsType (Promise Hash)
49+ }
6350
64−newtype Hash = Hash (ArrayBuffer)
51+newtype Hash
52+ = Hash (ArrayBuffer)
6553
6654hashToArray :: Hash -> Array Int
6755hashToArray (Hash buf) =
@@ -84,12 +72,10 @@ instance EncodeJson Hash where
8472instance DecodeJson Hash where
8573 decodeJson = decodeJson >>> map arrayToHash
8674
87−type Services =
88− { crypto :: CryptoService
89− , getApi :: Urls -> JNullable IdentityTokenResponse -> Promise ApiService
90− , cryptoFunctions :: CryptoFunctions
91− , passwordGeneration :: PasswordGeneration
92− , totpService :: TotpService
93− }
75+type Services
76+ = { crypto :: CryptoService
77+ , getApi :: Urls -> JNullable IdentityTokenResponse -> Promise ApiService
78+ , cryptoFunctions :: CryptoFunctions
79+ }
9480
9581foreign import getServices :: Effect Services
backend/BW/Logic.purs+159−413
@@ -3,7 +3,6 @@ module BW.Logic where
33import BW
44import BW.Types
55import Prelude
6−
76import Bridge as Bridge
87import Control.Monad.Error.Class (throwError)
98import Control.Promise (Promise)
@@ -12,66 +11,56 @@ import Data.EncString as EncString
1211import Data.Function.Uncurried (runFn2, runFn3, runFn4)
1312import Data.JNullable (JNullable, fromJNullable, jnull, nullify)
1413import 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
14+import Data.JOpt (fromJOpt)
15+import Data.Maybe (Maybe(..), fromMaybe)
16+import Data.Newtype (class Newtype, wrap)
1917import Data.String as String
2018import Data.SymmetricCryptoKey (SymmetricCryptoKey)
2119import Data.SymmetricCryptoKey as SymmetricCryptoKey
2220import Data.Traversable (traverse)
23−import Data.Undefined.NoProblem (opt, undefined)
2421import Effect (Effect)
2522import Effect.Aff (Aff)
2623import Effect.Aff.Class (class MonadAff, liftAff)
2724import Effect.Class (liftEffect)
2825import Effect.Exception (error)
2926import Effect.Exception as Exc
30−import Effect.Random (random)
31−import Localstorage as Storage
3227import Run (Run)
3328import Run.Reader (Reader, askAt)
34−import Storage (DeviceIDKey(..))
3529import Type.Prelude (Proxy(..))
3630import Untagged.Union (type (|+|))
37−import Web.Storage.Storage (Storage)
3831
3932-- | Creates the master key
40−makePreloginKey
41− :: forall r
42− . PreloginResponse
43− -> Email
44− -> Password
45− -> Run
46− ( crypto :: Reader CryptoService
47− , effect :: Effect
48− , aff :: Aff
49− | r
50− )
51− SymmetricCryptoKey
52−makePreloginKey { kdf, kdfIterations, kdfMemory, kdfParallelism } (Email email') password = do
33+makePreloginKey ::
34+ forall r.
35+ PreloginResponse ->
36+ Email ->
37+ Password ->
38+ Run
39+ ( crypto ::
40+ Reader CryptoService
41+ , effect :: Effect
42+ , aff :: Aff
43+ | r
44+ )
45+ SymmetricCryptoKey
46+makePreloginKey { kdf, kdfIterations } (Email email') password = do
5347 crypto <- askAt (Proxy :: _ "crypto")
5448 let
5549 email = (String.trim >>> String.toLower) email'
56− liftPromise $ runFn4 crypto.makeKey password email kdf
57− { iterations: kdfIterations
58− , memory: kdfMemory
59− , parallelism: kdfParallelism
60− }
50+ liftPromise $ runFn4 crypto.makeKey password email kdf kdfIterations
6151
62−makeDecryptionKey
63− :: forall r
64− .
52+makeDecryptionKey ::
53+ forall r.
6554 -- | Matser key (prelogin key)
66− SymmetricCryptoKey
67− -> EncryptedString
68− -> Run
69− ( crypto :: Reader CryptoService
70− , effect :: Effect
71− , aff :: Aff
72− | r
73− )
74− SymmetricCryptoKey
55+ SymmetricCryptoKey ->
56+ EncryptedString ->
57+ Run
58+ ( crypto :: Reader CryptoService
59+ , effect :: Effect
60+ , aff :: Aff
61+ | r
62+ )
63+ SymmetricCryptoKey
7564makeDecryptionKey masterKey str = do
7665 crypto <- askAt (Proxy :: _ "crypto")
7766 let
@@ -88,202 +77,161 @@ makeDecryptionKey masterKey str = do
8877 liftEffect $ Exc.throw $ "Unsupported key encryption type: " <> show encType
8978 pure $ SymmetricCryptoKey.fromArrayBuffer key
9079
91−getLogInRequestToken
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
100− ( api :: Reader ApiService
101− , crypto :: Reader CryptoService
102− , effect :: Effect
103− , aff :: Aff
104− , storage :: Reader Storage
105− | r
106− )
107− (IdentityCaptchaResponse |+| IdentityTwoFactorResponse |+| IdentityTokenResponse)
108−getLogInRequestToken
109− { prelogin
110− , email
111− , password
112− , captchaResponse
113− , secondFactor: Bridge.Cmd_Login_secondFactor_Maybe secondFactor
114− } = do
80+getLogInRequestToken ::
81+ forall r.
82+ PreloginResponse ->
83+ Email ->
84+ Password ->
85+ Maybe String ->
86+ Run
87+ ( api :: Reader ApiService
88+ , crypto :: Reader CryptoService
89+ , effect :: Effect
90+ , aff :: Aff
91+ | r
92+ )
93+ (IdentityCaptchaResponse |+| IdentityTokenResponse)
94+getLogInRequestToken prelogin email password captchaResponse = do
11595 key <- makePreloginKey prelogin email password
11696 crypto <- askAt (Proxy :: _ "crypto")
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
97+ api :: ApiService <- askAt (Proxy :: _ "api")
12898 _localHashedPassword <-
12999 liftPromise
130100 $ runFn3 crypto.hashPassword password key (nullify hashPurposeLocalAuthorization)
131101 StringHash hashedPassword <- liftPromise $ runFn3 crypto.hashPassword password key jnull
132− deviceId <- getDeviceID
133− api :: ApiService <- askAt (Proxy :: _ "api")
134102 liftPromise
135103 $ api.postIdentityToken
136104 { email: email
137105 , masterPasswordHash: hashedPassword
138106 , captchaResponse: fromMaybe "" captchaResponse
139− , twoFactor
107+ , twoFactor:
108+ { provider: twoFactorProviderTypeEmail
109+ , token: ""
110+ , remember: false
111+ }
140112 , device:
141113 { type: deviceTypeUnknownBrowser
142− , name: "Ubuntu Touch"
143− , identifier: deviceId
114+ , name: "Temporary device name"
115+ , identifier: "42"
144116 , pushToken: jnull
145117 }
146118 }
147119
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
172− ( cryptoFunctions :: Reader CryptoFunctions
173− , aff :: Aff
174− , effect :: Effect
175− | r
176− )
177− Hash
120+hashPassword ::
121+ forall r.
122+ Password ->
123+ Run
124+ ( cryptoFunctions :: Reader CryptoFunctions
125+ , aff :: Aff
126+ , effect :: Effect
127+ | r
128+ )
129+ Hash
178130hashPassword (Password password) = do
179131 cryptoFunctions <- askAt (Proxy :: _ "cryptoFunctions")
180132 liftPromise $ runFn2 cryptoFunctions.hash password cryptoFunctionsTypeSha512
181133
182−decrypt
183− :: forall r
184− . EncryptedString
185− -> Run
186− ( key :: Reader SymmetricCryptoKey
187− , crypto :: Reader CryptoService
188− , aff :: Aff
189− , effect :: Effect
190− | r
191− )
192− String
134+decrypt ::
135+ forall r.
136+ EncryptedString ->
137+ Run
138+ ( key :: Reader SymmetricCryptoKey
139+ , crypto :: Reader CryptoService
140+ , aff :: Aff
141+ , effect :: Effect
142+ | r
143+ )
144+ String
193145decrypt input = do
194146 crypto <- askAt (Proxy :: _ "crypto")
195147 key <- askAt (Proxy :: _ "key")
196148 liftPromise $ runFn2 crypto.decryptToUtf8 (EncString.fromString input) key
197149
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−
214−liftPromise :: forall m a. MonadAff m => Promise a -> m a
150+liftPromise ∷ forall m a. MonadAff m ⇒ Promise a → m a
215151liftPromise = liftAff <<< Promise.toAff
216152
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
153+decodeCipher ::
154+ forall r.
155+ CipherResponse ->
156+ Run
157+ ( key :: Reader SymmetricCryptoKey
158+ , crypto :: Reader CryptoService
159+ , aff :: Aff
160+ , effect :: Effect
161+ | r
162+ )
163+ Bridge.Sub_LoadCipher
164+decodeCipher cipher = do
165+ name <- decrypt cipher.name
166+ cipherType <- case cipher.type of
167+ n
168+ | cipherTypeSecureNote == n -> do
169+ note <- fromJNullable "" <$> (traverse decrypt cipher.notes)
170+ pure $ Bridge.NoteCipher note
171+ n
172+ | cipherTypeLogin == n -> do
173+ case JNullable.toMaybe cipher.login of
174+ Nothing -> liftEffect $ throwError $ error "Login data is missing"
175+ Just login -> do
176+ username <- decryptNullable login.username
177+ password <- decryptNullable login.password
178+ uris <- fromJOpt [] <$> ((traverse >>> traverse) (_.uri >>> decrypt) login.uris)
179+ pure $ Bridge.LoginCipher
180+ $ Bridge.Cipher_LoginCipher
181+ { username
182+ , password
183+ , uris: wrap uris
250184 }
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
185+ n
186+ | cipherTypeCard == n -> do
187+ case JNullable.toMaybe cipher.card of
188+ Nothing -> liftEffect $ throwError $ error "Card data is missing"
189+ Just card -> do
190+ cardholderName <- decryptNullable card.cardholderName
191+ number <- decryptNullable card.number
192+ code <- decryptNullable card.code
193+ brand <- decryptNullable card.brand
194+ expMonth <- decryptNullable card.expMonth
195+ expYear <- decryptNullable card.expYear
196+ pure $ Bridge.CardCipher
197+ $ Bridge.Cipher_CardCipher
198+ { brand, cardholderName, code, expMonth, expYear, number }
199+ n
200+ | cipherTypeIdentity == n -> do
201+ case JNullable.toMaybe cipher.identity of
202+ Nothing -> liftEffect $ throwError $ error "Identity data is missing"
203+ Just identity -> do
204+ firstName <- decryptNullable identity.firstName
205+ middleName <- decryptNullable identity.middleName
206+ lastName <- decryptNullable identity.lastName
207+ address1 <- decryptNullable identity.address1
208+ address2 <- decryptNullable identity.address2
209+ address3 <- decryptNullable identity.address3
210+ city <- decryptNullable identity.city
211+ company <- decryptNullable identity.company
212+ country <- decryptNullable identity.country
213+ email <- decryptNullable identity.email
214+ licenseNumber <- decryptNullable identity.licenseNumber
215+ passportNumber <- decryptNullable identity.passportNumber
216+ phone <- decryptNullable identity.phone
217+ postalCode <- decryptNullable identity.postalCode
218+ ssn <- decryptNullable identity.ssn
219+ state <- decryptNullable identity.state
220+ title <- decryptNullable identity.title
221+ username <- decryptNullable identity.username
222+ pure $ Bridge.IdentityCipher
223+ $ wrap
224+ { firstName
225+ , middleName
226+ , lastName
227+ , address1
277228 , address2
278229 , address3
279230 , city
280231 , company
281232 , country
282233 , email
283− , firstName
284− , lastName
285234 , licenseNumber
286− , middleName
287235 , passportNumber
288236 , phone
289237 , postalCode
@@ -292,221 +240,19 @@ encodeCipher
292240 , title
293241 , username
294242 }
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− }
455243 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
244+ pure $ Bridge.Sub_LoadCipher { cipherType, name, id: cipher.id }
495245
496−getDeviceID ::
497− forall r.
246+decryptNullable ::
247+ forall x r.
248+ Newtype x (Maybe String) =>
249+ JNullable EncryptedString ->
498250 Run
499− ( effect :: Effect
500− , storage :: Reader Storage
251+ ( key :: Reader SymmetricCryptoKey
252+ , crypto :: Reader CryptoService
253+ , aff :: Aff
254+ , effect :: Effect
501255 | r
502256 )
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
257+ x
258+decryptNullable x = wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt x)
backend/BW/Types.purs+390−445
@@ -2,27 +2,14 @@ module BW.Types where
22
33import Prelude
44
5−import Bridge as Bridge
65import Data.Argonaut (class DecodeJson, class EncodeJson)
7−import Data.Either (Either)
8−import Data.Either as Either
9−import Data.Int as Int
106import Data.JNullable (JNullable)
117import Data.JOpt (JOpt)
12−import Data.Maybe (Maybe(..))
13−import Data.Nullable (Nullable)
148import Data.ShowableJson (ShowableJson)
159import 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
2410
25−newtype EncryptedString = EncryptedString String
11+newtype EncryptedString
12+ = EncryptedString String
2613
2714derive newtype instance Show EncryptedString
2815derive newtype instance Ord EncryptedString
@@ -31,86 +18,78 @@ derive newtype instance EncodeJson EncryptedString
3118derive newtype instance DecodeJson EncryptedString
3219
3320kdfPBKDF2_SHA256 = 0 :: KDF
34−kdfArgon2id = 1 :: KDF
3521
36−type KDF = Int
37−
38−type PreloginResponse =
39− { kdf :: KDF
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
49− }
50−
51−type PreloginRequest =
52− { email :: Email
53− }
54−
55−type ProfileResponse =
56− { id :: String
57− , name :: JNullable String
58− , email :: String
59− , emailVerified :: Boolean
60− , masterPasswordHint :: JNullable String
61− -- , premiumPersonally :: Boolean
62− -- , premiumFromOrganization :: Boolean
63− , culture :: JNullable String
64− , twoFactorEnabled :: Boolean
65− , key :: EncryptedString
66− , privateKey :: EncryptedString
67− , securityStamp :: JNullable String
68− , forcePasswordReset :: Boolean
69− , usesKeyConnector :: Boolean
70− -- , organizations :: Array ProfileOrganizationResponse
71− , providers :: Array ProfileProviderResponse
72− , providerOrganizations :: Array ProfileProviderOrganizationResponse
73− }
74−
75−type ProfileOrganizationResponse =
76− { id :: String
77− , name :: JNullable String
78− , usePolicies :: JNullable Boolean
79− , useGroups :: JNullable Boolean
80− , useDirectory :: JNullable Boolean
81− , useEvents :: JNullable Boolean
82− , useTotp :: JNullable Boolean
83− , use2fa :: JNullable Boolean
84− , useApi :: JNullable Boolean
85− , useSso :: JNullable Boolean
86− , useKeyConnector :: JNullable Boolean
87− , useResetPassword :: JNullable Boolean
88− , selfHost :: JNullable Boolean
89− , usersGetPremium :: JNullable Boolean
90− , seats :: JNullable Int
91− , maxCollections :: JNullable Int
92− , maxStorageGb :: JNullable Int
93− , key :: JNullable String
94− , hasPublicAndPrivateKeys :: JNullable Boolean
95− , status :: JNullable OrganizationUserStatusType
96− , type :: JNullable OrganizationUserType
97− , enabled :: JNullable Boolean
98− , ssoBound :: JNullable Boolean
99− , identifier :: JNullable String
100− , permissions :: JNullable PermissionsApi
101− , resetPasswordEnrolled :: JNullable Boolean
102− , userId :: JNullable String
103− , providerId :: JNullable String
104− , providerName :: JNullable String
105− , familySponsorshipFriendlyName :: JNullable String
106− , familySponsorshipAvailable :: JNullable Boolean
107− , planProductType :: JNullable ProductType
108− , keyConnectorEnabled :: JNullable Boolean
109− , keyConnectorUrl :: JNullable String
110− , familySponsorshipLastSyncDate :: JNullable Timestamp
111− , familySponsorshipValidUntil :: JNullable Timestamp
112− , familySponsorshipToDelete :: JNullable Boolean
113− }
22+type KDF
23+ = Int
24+
25+type PreloginResponse
26+ = { kdf :: KDF
27+ , kdfIterations :: Int
28+ }
29+
30+type PreloginRequest
31+ = { email :: String
32+ }
33+
34+type ProfileResponse
35+ = { id :: String
36+ , name :: String
37+ , email :: String
38+ , emailVerified :: Boolean
39+ , masterPasswordHint :: JNullable String
40+ , premiumPersonally :: Boolean
41+ -- , premiumFromOrganization :: Boolean
42+ , culture :: String
43+ , twoFactorEnabled :: Boolean
44+ , key :: EncryptedString
45+ , privateKey :: EncryptedString
46+ , securityStamp :: String
47+ , forcePasswordReset :: Boolean
48+ , usesKeyConnector :: Boolean
49+ , organizations :: Array ProfileOrganizationResponse
50+ , providers :: Array ProfileProviderResponse
51+ , providerOrganizations :: Array ProfileProviderOrganizationResponse
52+ }
53+
54+type ProfileOrganizationResponse
55+ = { id :: String
56+ , name :: String
57+ , usePolicies :: Boolean
58+ , useGroups :: Boolean
59+ , useDirectory :: Boolean
60+ , useEvents :: Boolean
61+ , useTotp :: Boolean
62+ , use2fa :: Boolean
63+ , useApi :: Boolean
64+ , useSso :: Boolean
65+ , useKeyConnector :: Boolean
66+ , useResetPassword :: Boolean
67+ , selfHost :: Boolean
68+ , usersGetPremium :: Boolean
69+ , seats :: Int
70+ , maxCollections :: Int
71+ , maxStorageGb :: JNullable Int
72+ , key :: String
73+ , hasPublicAndPrivateKeys :: Boolean
74+ , status :: OrganizationUserStatusType
75+ , type :: OrganizationUserType
76+ , enabled :: Boolean
77+ , ssoBound :: Boolean
78+ , identifier :: String
79+ , permissions :: PermissionsApi
80+ , resetPasswordEnrolled :: Boolean
81+ , userId :: String
82+ , providerId :: String
83+ , providerName :: String
84+ , familySponsorshipFriendlyName :: String
85+ , familySponsorshipAvailable :: Boolean
86+ , planProductType :: ProductType
87+ , keyConnectorEnabled :: Boolean
88+ , keyConnectorUrl :: String
89+ , familySponsorshipLastSyncDate :: JNullable Timestamp
90+ , familySponsorshipValidUntil :: JNullable Timestamp
91+ , familySponsorshipToDelete :: JNullable Boolean
92+ }
11493
11594organizationUserStatusTypeInvited = 0 :: OrganizationUserStatusType
11695
@@ -118,7 +97,8 @@ organizationUserStatusTypeAccepted = 0 :: OrganizationUserStatusType
11897
11998organizationUserStatusTypeConfirmed = 0 :: OrganizationUserStatusType
12099
121−type OrganizationUserStatusType = Int
100+type OrganizationUserStatusType
101+ = Int
122102
123103organizationUserTypeOwner = 0 :: OrganizationUserType
124104
@@ -130,26 +110,27 @@ organizationUserTypeManager = 3 :: OrganizationUserType
130110
131111organizationUserTypeCustom = 4 :: OrganizationUserType
132112
133−type OrganizationUserType = Int
134−
135−type PermissionsApi =
136− { accessEventLogs :: Boolean
137− , accessImportExport :: Boolean
138− , accessReports :: Boolean
139− , manageAllCollections :: Boolean
140− , createNewCollections :: Boolean
141− , editAnyCollection :: Boolean
142− , deleteAnyCollection :: Boolean
143− , manageAssignedCollections :: Boolean
144− , editAssignedCollections :: Boolean
145− , deleteAssignedCollections :: Boolean
146− , manageCiphers :: Boolean
147− , manageGroups :: Boolean
148− , manageSso :: Boolean
149− , managePolicies :: Boolean
150− , manageUsers :: Boolean
151− , manageResetPassword :: Boolean
152− }
113+type OrganizationUserType
114+ = Int
115+
116+type PermissionsApi
117+ = { accessEventLogs :: Boolean
118+ , accessImportExport :: Boolean
119+ , accessReports :: Boolean
120+ , manageAllCollections :: Boolean
121+ , createNewCollections :: Boolean
122+ , editAnyCollection :: Boolean
123+ , deleteAnyCollection :: Boolean
124+ , manageAssignedCollections :: Boolean
125+ , editAssignedCollections :: Boolean
126+ , deleteAssignedCollections :: Boolean
127+ , manageCiphers :: Boolean
128+ , manageGroups :: Boolean
129+ , manageSso :: Boolean
130+ , managePolicies :: Boolean
131+ , manageUsers :: Boolean
132+ , manageResetPassword :: Boolean
133+ }
153134
154135productTypeFree = 0 :: ProductType
155136
@@ -159,19 +140,20 @@ productTypeTeams = 2 :: ProductType
159140
160141productTypeEnterprise = 3 :: ProductType
161142
162−type ProductType = Int
163−
164−type ProfileProviderResponse =
165− { id :: String
166− , name :: JNullable String
167− , key :: JNullable String
168− , status :: JNullable ProviderUserStatusType
169− , type :: JNullable ProviderUserType
170− , enabled :: Boolean
171− , permissions :: PermissionsApi
172− , userId :: JNullable String
173− , useEvents :: Boolean
174− }
143+type ProductType
144+ = Int
145+
146+type ProfileProviderResponse
147+ = { id :: String
148+ , name :: String
149+ , key :: String
150+ , status :: ProviderUserStatusType
151+ , type :: ProviderUserType
152+ , enabled :: Boolean
153+ , permissions :: PermissionsApi
154+ , userId :: String
155+ , useEvents :: Boolean
156+ }
175157
176158providerUserStatusTypeInvited = 0 :: ProviderUserStatusType
177159
@@ -179,79 +161,81 @@ providerUserStatusTypeAccepted = 1 :: ProviderUserStatusType
179161
180162providerUserStatusTypeConfirmed = 2 :: ProviderUserStatusType
181163
182−type ProviderUserStatusType = Int
164+type ProviderUserStatusType
165+ = Int
183166
184167providerUserTypeProviderAdmin = 0 :: ProviderUserType
185168
186169providerUserTypeServiceUser = 1 :: ProviderUserType
187170
188−type ProviderUserType = Int
189−
190−type ProfileProviderOrganizationResponse =
191− { id :: String
192− , name :: JNullable String
193− , usePolicies :: Boolean
194− , useGroups :: Boolean
195− , useDirectory :: Boolean
196− , useEvents :: Boolean
197− , useTotp :: Boolean
198− , use2fa :: Boolean
199− , useApi :: Boolean
200− , useSso :: Boolean
201− , useKeyConnector :: Boolean
202− , useResetPassword :: Boolean
203− , selfHost :: Boolean
204− , usersGetPremium :: Boolean
205− , seats :: Int
206− , maxCollections :: Int
207− , maxStorageGb :: JNullable Int
208− , key :: JNullable String
209− , hasPublicAndPrivateKeys :: Boolean
210− , status :: OrganizationUserStatusType
211− , type :: OrganizationUserType
212− , enabled :: Boolean
213− , ssoBound :: Boolean
214− , identifier :: JNullable String
215− , permissions :: PermissionsApi
216− , resetPasswordEnrolled :: Boolean
217− , userId :: JNullable String
218− , providerId :: JNullable String
219− , providerName :: JNullable String
220− , familySponsorshipFriendlyName :: JNullable String
221− , familySponsorshipAvailable :: Boolean
222− , planProductType :: ProductType
223− , keyConnectorEnabled :: Boolean
224− , keyConnectorUrl :: JNullable String
225− , familySponsorshipLastSyncDate :: JNullable Timestamp
226− , familySponsorshipValidUntil :: JNullable Timestamp
227− , familySponsorshipToDelete :: JNullable Boolean
228− }
229−
230−type Urls =
231− { base :: JNullable String
232− , webVault :: JNullable String
233− , api :: JNullable String
234− , identity :: JNullable String
235− , icons :: JNullable String
236− , notifications :: JNullable String
237− , events :: JNullable String
238− , keyConnector :: JNullable String
239− }
240−
241−type PasswordTokenRequest =
242− { email :: Email
243− , masterPasswordHash :: String
244− , captchaResponse :: String
245− , twoFactor :: TokenRequestTwoFactor
246− , device :: DeviceRequest
247− }
248−
249−type DeviceRequest =
250− { type :: DeviceType
251− , name :: String
252− , identifier :: String
253− , pushToken :: JNullable String
254− }
171+type ProviderUserType
172+ = Int
173+
174+type ProfileProviderOrganizationResponse
175+ = { id :: String
176+ , name :: String
177+ , usePolicies :: Boolean
178+ , useGroups :: Boolean
179+ , useDirectory :: Boolean
180+ , useEvents :: Boolean
181+ , useTotp :: Boolean
182+ , use2fa :: Boolean
183+ , useApi :: Boolean
184+ , useSso :: Boolean
185+ , useKeyConnector :: Boolean
186+ , useResetPassword :: Boolean
187+ , selfHost :: Boolean
188+ , usersGetPremium :: Boolean
189+ , seats :: Int
190+ , maxCollections :: Int
191+ , maxStorageGb :: JNullable Int
192+ , key :: String
193+ , hasPublicAndPrivateKeys :: Boolean
194+ , status :: OrganizationUserStatusType
195+ , type :: OrganizationUserType
196+ , enabled :: Boolean
197+ , ssoBound :: Boolean
198+ , identifier :: String
199+ , permissions :: PermissionsApi
200+ , resetPasswordEnrolled :: Boolean
201+ , userId :: String
202+ , providerId :: String
203+ , providerName :: String
204+ , familySponsorshipFriendlyName :: String
205+ , familySponsorshipAvailable :: Boolean
206+ , planProductType :: ProductType
207+ , keyConnectorEnabled :: Boolean
208+ , keyConnectorUrl :: String
209+ , familySponsorshipLastSyncDate :: JNullable Timestamp
210+ , familySponsorshipValidUntil :: JNullable Timestamp
211+ , familySponsorshipToDelete :: JNullable Boolean
212+ }
213+
214+type Urls
215+ = { base :: JNullable String
216+ , webVault :: JNullable String
217+ , api :: JNullable String
218+ , identity :: JNullable String
219+ , icons :: JNullable String
220+ , notifications :: JNullable String
221+ , events :: JNullable String
222+ , keyConnector :: JNullable String
223+ }
224+
225+type PasswordTokenRequest
226+ = { email :: Email
227+ , masterPasswordHash :: String
228+ , captchaResponse :: String
229+ , twoFactor :: TokenRequestTwoFactor
230+ , device :: DeviceRequest
231+ }
232+
233+type DeviceRequest
234+ = { type :: DeviceType
235+ , name :: String
236+ , identifier :: String
237+ , pushToken :: JNullable String
238+ }
255239
256240deviceTypeAndroid = 0 :: DeviceType
257241
@@ -295,13 +279,14 @@ deviceTypeVivaldiExtension = 19 :: DeviceType
295279
296280deviceTypeSafariExtension = 20 :: DeviceType
297281
298−type DeviceType = Int
282+type DeviceType
283+ = Int
299284
300−type TokenRequestTwoFactor =
301− { provider :: TwoFactorProviderType
302− , token :: String
303− , remember :: Boolean
304− }
285+type TokenRequestTwoFactor
286+ = { provider :: TwoFactorProviderType
287+ , token :: String
288+ , remember :: Boolean
289+ }
305290
306291twoFactorProviderTypeAuthenticator = 0 :: TwoFactorProviderType
307292
@@ -319,51 +304,24 @@ twoFactorProviderTypeOrganizationDuo = 6 :: TwoFactorProviderType
319304
320305twoFactorProviderTypeWebAuthn = 7 :: TwoFactorProviderType
321306
322−type TwoFactorProviderType = Int
323−
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−
356−newtype Password = Password String
307+type TwoFactorProviderType
308+ = Int
309+
310+newtype Password
311+ = Password String
357312
358313hashPurposeServerAuthorization = 1 :: HashPurpose
359314
360315hashPurposeLocalAuthorization = 2 :: HashPurpose
361316
362−type HashPurpose = Int
317+type HashPurpose
318+ = Int
363319
364−newtype StringHash = StringHash String
320+newtype StringHash
321+ = StringHash String
365322
366−newtype AccessToken = AccessToken String
323+newtype AccessToken
324+ = AccessToken String
367325
368326derive newtype instance Show AccessToken
369327derive newtype instance Eq AccessToken
@@ -371,7 +329,8 @@ derive newtype instance Ord AccessToken
371329derive newtype instance EncodeJson AccessToken
372330derive newtype instance DecodeJson AccessToken
373331
374−newtype RefreshToken = RefreshToken String
332+newtype RefreshToken
333+ = RefreshToken String
375334
376335derive newtype instance Show RefreshToken
377336derive newtype instance Eq RefreshToken
@@ -383,91 +342,75 @@ type IdentityCaptchaResponse =
383342 { siteKey :: String
384343 }
385344
386−type IdentityTokenResponse =
387− { accessToken :: AccessToken
388− , expiresIn :: Int
389− , refreshToken :: RefreshToken
390− , tokenType :: String
391− , resetMasterPassword :: Boolean
392− , privateKey :: EncryptedString
393− , key :: EncryptedString
394− , twoFactorToken :: JNullable String
395− , kdf :: KDF
396− , kdfIterations :: Int
397− , forcePasswordReset :: JNullable Boolean
398− , apiUseKeyConnector :: JNullable Boolean
399− , keyConnectorUrl :: JNullable String
400− }
401−
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−
420−newtype Email = Email String
421−
422−type SyncResponse =
423− { profile :: ProfileResponse
424− , folders :: Array FolderResponse
425− , collections :: Array CollectionDetailsResponse
426− , ciphers :: Array CipherResponse
427− , domains :: JNullable DomainsResponse
428− , policies :: JNullable (Array PolicyResponse)
429− , sends :: Array SendResponse
430− }
345+type IdentityTokenResponse
346+ = { accessToken :: AccessToken
347+ , expiresIn :: Int
348+ , refreshToken :: RefreshToken
349+ , tokenType :: String
350+ , resetMasterPassword :: Boolean
351+ , privateKey :: EncryptedString
352+ , key :: EncryptedString
353+ , twoFactorToken :: JNullable String
354+ , kdf :: KDF
355+ , kdfIterations :: Int
356+ , forcePasswordReset :: JNullable Boolean
357+ , apiUseKeyConnector :: JNullable Boolean
358+ , keyConnectorUrl :: JNullable String
359+ }
360+
361+newtype Email
362+ = Email String
363+
364+type SyncResponse
365+ = { profile :: ProfileResponse
366+ , folders :: Array FolderResponse
367+ , collections :: Array CollectionDetailsResponse
368+ , ciphers :: Array CipherResponse
369+ , domains :: JNullable DomainsResponse
370+ , policies :: JNullable (Array PolicyResponse)
371+ , sends :: Array SendResponse
372+ }
373+
374+type FolderResponse
375+ = { id :: String
376+ , name :: String
377+ , revisionDate :: String
378+ }
379+
380+type CollectionDetailsResponse
381+ = { id :: String
382+ , organizationId :: JNullable String
383+ , name :: String
384+ , externalId :: JNullable String
385+ , readOnly :: Boolean
386+ }
431387
432−type FolderResponse =
433− { id :: String
434− , name :: JNullable String
435− , revisionDate :: JNullable String
436− }
437−
438−type CollectionDetailsResponse =
439− { id :: String
440− , organizationId :: JNullable String
441− , name :: JNullable String
442− , externalId :: JNullable String
443− , readOnly :: Boolean
444− }
445388
446389type CipherId = String
447390
448−type CipherResponse =
449− { id :: CipherId
450− , organizationId :: JNullable String
451− , folderId :: JNullable String
452− , type :: CipherType
453− , name :: EncryptedString
454− , notes :: JNullable EncryptedString
455− , fields :: JNullable (Array FieldApi)
456− , login :: JNullable LoginApi
457− , card :: JNullable CardApi
458− , identity :: JNullable IdentityApi
459− , secureNote :: JNullable SecureNoteApi
460− , favorite :: Boolean
461− , edit :: JNullable Boolean
462− , viewPassword :: JNullable Boolean
463− , organizationUseTotp :: JNullable Boolean
464− , revisionDate :: JNullable Timestamp
465− , attachments :: JNullable (Array AttachmentResponse)
466− , passwordHistory :: JNullable (Array PasswordHistoryResponse)
467− , collectionIds :: JNullable (Array String)
468− , deletedDate :: JNullable String
469− , reprompt :: CipherRepromptType
470− }
391+type CipherResponse
392+ = { id :: CipherId
393+ , organizationId :: JNullable String
394+ , folderId :: JNullable String
395+ , type :: CipherType
396+ , name :: EncryptedString
397+ , notes :: JNullable EncryptedString
398+ , fields :: JNullable (Array FieldApi)
399+ , login :: JNullable LoginApi
400+ , card :: JNullable CardApi
401+ , identity :: JNullable IdentityApi
402+ , secureNote :: JNullable SecureNoteApi
403+ , favorite :: Boolean
404+ , edit :: Boolean
405+ , viewPassword :: Boolean
406+ , organizationUseTotp :: Boolean
407+ , revisionDate :: Timestamp
408+ , attachments :: JNullable (Array AttachmentResponse)
409+ , passwordHistory :: JNullable (Array PasswordHistoryResponse)
410+ , collectionIds :: JNullable (Array String)
411+ , deletedDate :: JNullable String
412+ , reprompt :: CipherRepromptType
413+ }
471414
472415cipherTypeLogin = 1 :: CipherType
473416
@@ -477,14 +420,15 @@ cipherTypeCard = 3 :: CipherType
477420
478421cipherTypeIdentity = 4 :: CipherType
479422
480−type CipherType = Int
423+type CipherType
424+ = Int
481425
482−type FieldApi =
483− { name :: JNullable String
484− , value :: JNullable String
485− , type :: FieldType
486− , linkedId :: JNullable LinkedIdType
487− }
426+type FieldApi
427+ = { name :: String
428+ , value :: String
429+ , type :: FieldType
430+ , linkedId :: JNullable LinkedIdType
431+ }
488432
489433fieldTypeText = 0 :: FieldType
490434
@@ -494,7 +438,8 @@ fieldTypeBoolean = 2 :: FieldType
494438
495439fieldTypeLinked = 3 :: FieldType
496440
497−type FieldType = Int
441+type FieldType
442+ = Int
498443
499444linkedIdTypeLoginLinkedIdUsername = 100 :: LinkedIdType
500445
@@ -550,22 +495,23 @@ linkedIdTypeIdentityLinkedIdLastName = 417 :: LinkedIdType
550495
551496linkedIdTypeIdentityLinkedIdFullName = 418 :: LinkedIdType
552497
553−type LinkedIdType = Int
498+type LinkedIdType
499+ = Int
554500
555−type LoginApi =
556− { uris :: JOpt (Array LoginUriApi)
557− , username :: JNullable EncryptedString
558− , password :: JNullable EncryptedString
559− , passwordRevisionDate :: JNullable String
560− , totp :: JNullable EncryptedString
561− -- This thing is undefined in some cases for some reson. Just ignoring it
562− , autofillOnPageLoad :: JOpt (JNullable Boolean)
563− }
501+type LoginApi
502+ = { uris :: JOpt (Array LoginUriApi)
503+ , username :: JNullable EncryptedString
504+ , password :: JNullable EncryptedString
505+ , passwordRevisionDate :: JNullable String
506+ , totp :: JNullable EncryptedString
507+ -- This thing is undefined in some cases for some reson. Just ignoring it
508+ , autofillOnPageLoad :: JOpt (JNullable Boolean)
509+ }
564510
565−type LoginUriApi =
566− { uri :: JNullable EncryptedString
567− , match :: JNullable UriMatchType
568− }
511+type LoginUriApi
512+ = { uri :: EncryptedString
513+ , match :: JNullable UriMatchType
514+ }
569515
570516uriMatchTypeDomain = 0 :: UriMatchType
571517
@@ -579,85 +525,87 @@ uriMatchTypeRegularExpression = 4 :: UriMatchType
579525
580526uriMatchTypeNever = 5 :: UriMatchType
581527
582−type UriMatchType = Int
583−
584−type CardApi =
585− { cardholderName :: JNullable EncryptedString
586− , brand :: JNullable EncryptedString
587− , number :: JNullable EncryptedString
588− , expMonth :: JNullable EncryptedString
589− , expYear :: JNullable EncryptedString
590− , code :: JNullable EncryptedString
591− }
592−
593−type IdentityApi =
594− { title :: JNullable EncryptedString
595− , firstName :: JNullable EncryptedString
596− , middleName :: JNullable EncryptedString
597− , lastName :: JNullable EncryptedString
598− , address1 :: JNullable EncryptedString
599− , address2 :: JNullable EncryptedString
600− , address3 :: JNullable EncryptedString
601− , city :: JNullable EncryptedString
602− , state :: JNullable EncryptedString
603− , postalCode :: JNullable EncryptedString
604− , country :: JNullable EncryptedString
605− , company :: JNullable EncryptedString
606− , email :: JNullable EncryptedString
607− , phone :: JNullable EncryptedString
608− , ssn :: JNullable EncryptedString
609− , username :: JNullable EncryptedString
610− , passportNumber :: JNullable EncryptedString
611− , licenseNumber :: JNullable EncryptedString
612− }
528+type UriMatchType
529+ = Int
530+
531+type CardApi
532+ = { cardholderName :: JNullable EncryptedString
533+ , brand :: JNullable EncryptedString
534+ , number :: JNullable EncryptedString
535+ , expMonth :: JNullable EncryptedString
536+ , expYear :: JNullable EncryptedString
537+ , code :: JNullable EncryptedString
538+ }
539+
540+type IdentityApi
541+ = { title :: JNullable EncryptedString
542+ , firstName :: JNullable EncryptedString
543+ , middleName :: JNullable EncryptedString
544+ , lastName :: JNullable EncryptedString
545+ , address1 :: JNullable EncryptedString
546+ , address2 :: JNullable EncryptedString
547+ , address3 :: JNullable EncryptedString
548+ , city :: JNullable EncryptedString
549+ , state :: JNullable EncryptedString
550+ , postalCode :: JNullable EncryptedString
551+ , country :: JNullable EncryptedString
552+ , company :: JNullable EncryptedString
553+ , email :: JNullable EncryptedString
554+ , phone :: JNullable EncryptedString
555+ , ssn :: JNullable EncryptedString
556+ , username :: JNullable EncryptedString
557+ , passportNumber :: JNullable EncryptedString
558+ , licenseNumber :: JNullable EncryptedString
559+ }
613560
614561secureNoteTypeGeneric = 0 :: SecureNoteType
615562
616−type SecureNoteType = Int
563+type SecureNoteType
564+ = Int
617565
618−type SecureNoteApi =
619− {
620− -- type :: SecureNoteType
621− }
566+type SecureNoteApi = {
567+ type :: SecureNoteType
568+}
622569
623−type AttachmentResponse =
624− { id :: String
625− , url :: JNullable String
626− , fileName :: JNullable String
627− , key :: JNullable String
628− , size :: JNullable String
629− , sizeName :: JNullable String
630− }
570+type AttachmentResponse
571+ = { id :: String
572+ , url :: String
573+ , fileName :: String
574+ , key :: String
575+ , size :: String
576+ , sizeName :: String
577+ }
631578
632−type PasswordHistoryResponse =
633− { password :: String
634− , lastUsedDate :: JNullable String
635− }
579+type PasswordHistoryResponse
580+ = { password :: String
581+ , lastUsedDate :: String
582+ }
636583
637584cipherRepromptTypeNone = 0 :: CipherRepromptType
638585
639586cipherRepromptTypePassword = 1 :: CipherRepromptType
640587
641−type CipherRepromptType = Int
588+type CipherRepromptType
589+ = Int
642590
643−type DomainsResponse =
644− { equivalentDomains :: Array (Array String)
645− , globalEquivalentDomains :: Array GlobalDomainResponse
646− }
591+type DomainsResponse
592+ = { equivalentDomains :: Array (Array String)
593+ , globalEquivalentDomains :: Array GlobalDomainResponse
594+ }
647595
648−type GlobalDomainResponse =
649− { type :: Int
650− , domains :: Array String
651− , excluded :: Boolean
652− }
596+type GlobalDomainResponse
597+ = { type :: Int
598+ , domains :: Array String
599+ , excluded :: Boolean
600+ }
653601
654−type PolicyResponse =
655− { id :: String
656− , organizationId :: JNullable String
657− , type :: PolicyType
658− , data :: ShowableJson
659− , enabled :: Boolean
660− }
602+type PolicyResponse
603+ = { id :: String
604+ , organizationId :: JNullable String
605+ , type :: PolicyType
606+ , data :: ShowableJson
607+ , enabled :: Boolean
608+ }
661609
662610policyTypeTwoFactorAuthentication = 0 :: PolicyType -- Requires users to have 2fa enabled
663611
@@ -681,46 +629,43 @@ policyTypeMaximumVaultTimeout = 9 :: PolicyType -- Sets the maximum allowed vaul
681629
682630policyTypeDisablePersonalVaultExport = 10 :: PolicyType -- Disable personal vault export
683631
684−type PolicyType = Int
685−
686−type SendResponse =
687− { id :: String
688− , accessId :: JNullable String
689− , type :: SendType
690− , name :: JNullable String
691− , notes :: JNullable String
692− , file :: JNullable SendFileApi
693− , text :: JNullable SendTextApi
694− , key :: JNullable String
695− , maxAccessCount :: JNullable Int
696− , accessCount :: JNullable Int
697− , revisionDate :: JNullable String
698− , expirationDate :: JNullable String
699− , deletionDate :: JNullable String
700− , password :: JNullable String
701− , disable :: Boolean
702− , hideEmail :: Boolean
703− }
632+type PolicyType
633+ = Int
634+
635+type SendResponse
636+ = { id :: String
637+ , accessId :: String
638+ , type :: SendType
639+ , name :: String
640+ , notes :: JNullable String
641+ , file :: JNullable SendFileApi
642+ , text :: JNullable SendTextApi
643+ , key :: String
644+ , maxAccessCount :: JNullable Int
645+ , accessCount :: Int
646+ , revisionDate :: String
647+ , expirationDate :: String
648+ , deletionDate :: JNullable String
649+ , password :: String
650+ , disable :: Boolean
651+ , hideEmail :: Boolean
652+ }
704653
705654sendTypeText = 0 :: SendType
706655
707656sendTypeFile = 1 :: SendType
708657
709−type SendType = Int
710−
711−type SendFileApi =
712− { id :: String
713− , fileName :: JNullable String
714− , size :: JNullable String
715− , sizeName :: JNullable String
716− }
658+type SendType
659+ = Int
717660
718−type SendTextApi =
719− { text :: JNullable String
720− , hidden :: Boolean
721− }
661+type SendFileApi
662+ = { id :: String
663+ , fileName :: String
664+ , size :: String
665+ , sizeName :: String
666+ }
722667
723−type TwoFactorEmailRequest =
724− { email :: Email
725− , masterPasswordHash :: StringHash
726− }
668+type SendTextApi
669+ = { text :: String
670+ , hidden :: Boolean
671+ }
backend/Bridge.purs+11−172
@@ -362,18 +362,6 @@ derive instance genericCipher_LoginCipher_password_Maybe :: Generic Cipher_Login
362362derive instance eqCipher_LoginCipher_password_Maybe :: Eq Cipher_LoginCipher_password_Maybe
363363derive instance ordCipher_LoginCipher_password_Maybe :: Ord Cipher_LoginCipher_password_Maybe
364364
365−newtype Cipher_LoginCipher_totp_Maybe =
366− Cipher_LoginCipher_totp_Maybe (Maybe String)
367−
368−derive instance newtypeCipher_LoginCipher_totp_Maybe :: Newtype Cipher_LoginCipher_totp_Maybe _
369−instance encodeJsonCipher_LoginCipher_totp_Maybe :: EncodeJson Cipher_LoginCipher_totp_Maybe where
370− encodeJson = genericEncodeAeson Argonaut.defaultOptions
371−instance decodeJsonCipher_LoginCipher_totp_Maybe :: DecodeJson Cipher_LoginCipher_totp_Maybe where
372− decodeJson = genericDecodeAeson Argonaut.defaultOptions
373−derive instance genericCipher_LoginCipher_totp_Maybe :: Generic Cipher_LoginCipher_totp_Maybe _
374−derive instance eqCipher_LoginCipher_totp_Maybe :: Eq Cipher_LoginCipher_totp_Maybe
375−derive instance ordCipher_LoginCipher_totp_Maybe :: Ord Cipher_LoginCipher_totp_Maybe
376−
377365newtype Cipher_LoginCipher_uris_List =
378366 Cipher_LoginCipher_uris_List (Array String)
379367
@@ -401,7 +389,6 @@ derive instance ordCipher_LoginCipher_username_Maybe :: Ord Cipher_LoginCipher_u
401389newtype Cipher_LoginCipher =
402390 Cipher_LoginCipher {
403391 password :: Cipher_LoginCipher_password_Maybe
404− , totp :: Cipher_LoginCipher_totp_Maybe
405392 , uris :: Cipher_LoginCipher_uris_List
406393 , username :: Cipher_LoginCipher_username_Maybe
407394 }
@@ -443,57 +430,10 @@ derive instance genericCipherType :: Generic CipherType _
443430derive instance eqCipherType :: Eq CipherType
444431derive instance ordCipherType :: Ord CipherType
445432
446−newtype Cmd_ChooseSecondFactor =
447− Cmd_ChooseSecondFactor {
448− email :: String
449− , factor :: TwoFactorProviderType
450− , password :: String
451− , requestFromServer :: Boolean
452− , server :: String
453− }
454−
455−derive instance newtypeCmd_ChooseSecondFactor :: Newtype Cmd_ChooseSecondFactor _
456−instance encodeJsonCmd_ChooseSecondFactor :: EncodeJson Cmd_ChooseSecondFactor where
457− encodeJson = genericEncodeAeson Argonaut.defaultOptions
458−instance decodeJsonCmd_ChooseSecondFactor :: DecodeJson Cmd_ChooseSecondFactor where
459− decodeJson = genericDecodeAeson Argonaut.defaultOptions
460−derive instance genericCmd_ChooseSecondFactor :: Generic Cmd_ChooseSecondFactor _
461−derive instance eqCmd_ChooseSecondFactor :: Eq Cmd_ChooseSecondFactor
462−derive instance ordCmd_ChooseSecondFactor :: Ord Cmd_ChooseSecondFactor
463−
464−newtype Cmd_Login_secondFactor =
465− Cmd_Login_secondFactor {
466− provider :: TwoFactorProviderType
467− , remember :: Boolean
468− , token :: String
469− }
470−
471−derive instance newtypeCmd_Login_secondFactor :: Newtype Cmd_Login_secondFactor _
472−instance encodeJsonCmd_Login_secondFactor :: EncodeJson Cmd_Login_secondFactor where
473− encodeJson = genericEncodeAeson Argonaut.defaultOptions
474−instance decodeJsonCmd_Login_secondFactor :: DecodeJson Cmd_Login_secondFactor where
475− decodeJson = genericDecodeAeson Argonaut.defaultOptions
476−derive instance genericCmd_Login_secondFactor :: Generic Cmd_Login_secondFactor _
477−derive instance eqCmd_Login_secondFactor :: Eq Cmd_Login_secondFactor
478−derive instance ordCmd_Login_secondFactor :: Ord Cmd_Login_secondFactor
479−
480−newtype Cmd_Login_secondFactor_Maybe =
481− Cmd_Login_secondFactor_Maybe (Maybe Cmd_Login_secondFactor)
482−
483−derive instance newtypeCmd_Login_secondFactor_Maybe :: Newtype Cmd_Login_secondFactor_Maybe _
484−instance encodeJsonCmd_Login_secondFactor_Maybe :: EncodeJson Cmd_Login_secondFactor_Maybe where
485− encodeJson = genericEncodeAeson Argonaut.defaultOptions
486−instance decodeJsonCmd_Login_secondFactor_Maybe :: DecodeJson Cmd_Login_secondFactor_Maybe where
487− decodeJson = genericDecodeAeson Argonaut.defaultOptions
488−derive instance genericCmd_Login_secondFactor_Maybe :: Generic Cmd_Login_secondFactor_Maybe _
489−derive instance eqCmd_Login_secondFactor_Maybe :: Eq Cmd_Login_secondFactor_Maybe
490−derive instance ordCmd_Login_secondFactor_Maybe :: Ord Cmd_Login_secondFactor_Maybe
491−
492433newtype Cmd_Login =
493434 Cmd_Login {
494435 email :: String
495436 , password :: String
496− , secondFactor :: Cmd_Login_secondFactor_Maybe
497437 , server :: String
498438 }
499439
@@ -507,11 +447,7 @@ derive instance eqCmd_Login :: Eq Cmd_Login
507447derive instance ordCmd_Login :: Ord Cmd_Login
508448
509449data Cmd =
510− ChooseSecondFactor Cmd_ChooseSecondFactor
511− | Copy String
512− | CreateCipher FullCipher
513− | DeleteCipher FullCipher
514− | GeneratePassword PasswordGeneratorConfig
450+ Copy String
515451 | Init
516452 | Login Cmd_Login
517453 | NeedCiphersList
@@ -519,9 +455,7 @@ data Cmd =
519455 | NeedsReset
520456 | Open String
521457 | RequestCipher String
522− | RequestTotp String
523458 | SendMasterPassword String
524− | UpdateCipher FullCipher
525459
526460instance encodeJsonCmd :: EncodeJson Cmd where
527461 encodeJson = genericEncodeAeson Argonaut.defaultOptions
@@ -531,64 +465,21 @@ derive instance genericCmd :: Generic Cmd _
531465derive instance eqCmd :: Eq Cmd
532466derive instance ordCmd :: Ord Cmd
533467
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
539− encodeJson = genericEncodeAeson Argonaut.defaultOptions
540−instance decodeJsonFullCipher_collectionIds_List :: DecodeJson FullCipher_collectionIds_List where
541− decodeJson = genericDecodeAeson Argonaut.defaultOptions
542−derive instance genericFullCipher_collectionIds_List :: Generic FullCipher_collectionIds_List _
543−derive instance eqFullCipher_collectionIds_List :: Eq FullCipher_collectionIds_List
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
468+newtype Sub_LoadCipher =
469+ Sub_LoadCipher {
470+ cipherType :: Cipher
551471 , id :: String
552472 , name :: String
553− , reprompt :: Int
554473 }
555474
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
475+derive instance newtypeSub_LoadCipher :: Newtype Sub_LoadCipher _
476+instance encodeJsonSub_LoadCipher :: EncodeJson Sub_LoadCipher where
586477 encodeJson = genericEncodeAeson Argonaut.defaultOptions
587−instance decodeJsonPasswordGeneratorConfig :: DecodeJson PasswordGeneratorConfig where
478+instance decodeJsonSub_LoadCipher :: DecodeJson Sub_LoadCipher where
588479 decodeJson = genericDecodeAeson Argonaut.defaultOptions
589−derive instance genericPasswordGeneratorConfig :: Generic PasswordGeneratorConfig _
590−derive instance eqPasswordGeneratorConfig :: Eq PasswordGeneratorConfig
591−derive instance ordPasswordGeneratorConfig :: Ord PasswordGeneratorConfig
480+derive instance genericSub_LoadCipher :: Generic Sub_LoadCipher _
481+derive instance eqSub_LoadCipher :: Eq Sub_LoadCipher
482+derive instance ordSub_LoadCipher :: Ord Sub_LoadCipher
592483
593484newtype Sub_LoadCiphers =
594485 Sub_LoadCiphers {
@@ -634,51 +525,17 @@ derive instance genericSub_NeedsMasterPassword :: Generic Sub_NeedsMasterPasswor
634525derive instance eqSub_NeedsMasterPassword :: Eq Sub_NeedsMasterPassword
635526derive instance ordSub_NeedsMasterPassword :: Ord Sub_NeedsMasterPassword
636527
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−
665528data Sub =
666529 CaptchaDone
667− | CipherChanged FullCipher
668− | CipherDeleted FullCipher
669530 | Error String
670− | GeneratedPassword String
671− | LoadCipher FullCipher
531+ | LoadCipher Sub_LoadCipher
672532 | LoadCiphers Sub_LoadCiphers_List
673533 | LoginSuccessful
674534 | NeedsCaptcha String
675535 | NeedsLogin
676536 | NeedsMasterPassword Sub_NeedsMasterPassword
677− | NeedsSecondFactor Sub_NeedsSecondFactor_List
678537 | RecieveEmail String
679538 | Reset
680− | Totp Sub_Totp
681− | WrongPassword
682539
683540instance encodeJsonSub :: EncodeJson Sub where
684541 encodeJson = genericEncodeAeson Argonaut.defaultOptions
@@ -688,21 +545,3 @@ derive instance genericSub :: Generic Sub _
688545derive instance eqSub :: Eq Sub
689546derive instance ordSub :: Ord Sub
690547
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/EncString.js+2−5
@@ -1,14 +1,11 @@
1−import { EncString } from "../../bw/models/domain/enc-string"
1+import { EncString } from "../../deps/bw/libs/shared/dist/src/models/domain/encString"
22
33export function fromString(str) {
4+ console.log("fromString " + str)
45 const x = new EncString(str)
56 return x
67}
78
8−export function toString(encStr) {
9− return encStr.encryptedString
10−}
11−
129export function encryptionType(encStr) {
1310 return encStr.encryptionType
1411}
backend/Data/EncString.purs−2
@@ -25,5 +25,3 @@ type EncryptionType
2525 = Int
2626
2727foreign import fromString :: EncryptedString -> EncString
28−
29−foreign import toString :: EncString -> EncryptedString
backend/Data/SymmetricCryptoKey.js+2−1
@@ -1,5 +1,6 @@
1−import { SymmetricCryptoKey } from '../../bw/models/domain/symmetric-crypto-key.js'
1+import { SymmetricCryptoKey } from '../../deps/bw/libs/shared/dist/src/models/domain/symmetricCryptoKey.js';
22
33export function fromArrayBuffer(arr) {
4+ console.log("fromArrayBuffer " + arr)
45 return new SymmetricCryptoKey(arr)
56}
backend/FFI.js+1−16
@@ -1,21 +1,6 @@
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−
161export function startElm(element) {
172 return function () {
18− return searchForInit(Elm)({
3+ return Elm.Main.init({
194 node: document.getElementById(element)
205 })
216 }
backend/Main.purs+118−244
@@ -3,24 +3,21 @@ module Main
33 ) where
44
55import Prelude
6−
7−import BW (ApiService, CryptoFunctions, Services, CryptoService)
6+import BW (ApiService, CryptoService, Services, CryptoFunctions)
87import BW as WB
9−import BW.Logic (bwPasswordStringHash, decodeCipher, decrypt, encodeCipher, hashPassword, liftPromise, supportedCipher)
8+import BW.Logic (decodeCipher, decrypt, hashPassword, liftPromise)
109import BW.Logic as Logic
11−import BW.Types (CipherResponse, Email(..), Password(..), Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote, fromTwoFactorProviderType)
10+import BW.Types (CipherResponse, Email(..), Password(..), Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote)
1211import Bridge as Bridge
13−import Control.Monad.Error.Class (catchError)
12+import Control.Monad.Error.Class (catchError, throwError)
1413import Data.Argonaut (class DecodeJson)
1514import Data.Array as Array
1615import Data.Clipboard as Clipboard
1716import Data.DateTime (DateTime)
1817import Data.Either (Either(..))
19−import Data.Foldable (for_)
2018import Data.HCaptcha (bindHCaptchToken)
2119import Data.JNullable (jnull, nullify)
22−import Data.JNullable as JNullable
23−import Data.Maybe (Maybe(..), maybe)
20+import Data.Maybe (Maybe(..))
2421import Data.OpenURL (openURL)
2522import Data.Ord.Down (Down(..))
2623import Data.SymmetricCryptoKey (SymmetricCryptoKey)
@@ -30,7 +27,7 @@ import Effect (Effect)
3027import Effect.Aff (Aff, runAff_, try)
3128import Effect.Class (liftEffect)
3229import Effect.Class.Console (log)
33−import Effect.Exception (throw)
30+import Effect.Exception (error)
3431import Effect.Exception as Exc
3532import Effect.Ref as Ref
3633import Elm as Elm
@@ -62,19 +59,19 @@ main = do
6259 let
6360 runAff act = runElmAff app $ run $ act
6461
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
62+ run ::
63+ forall a.
64+ Run
65+ ( app :: Reader Elm
66+ , aff :: Aff
67+ , effect :: Effect
68+ , crypto :: Reader CryptoService
69+ , storage :: Reader Storage
70+ , services :: Reader Services
71+ , cryptoFunctions :: Reader CryptoFunctions
72+ )
73+ a ->
74+ Run (EFFECT + AFF + ()) a
7875 run act =
7976 runReaderAt (Proxy :: _ "app") app
8077 $ runReaderAt (Proxy :: _ "crypto") services.crypto
@@ -83,19 +80,19 @@ main = do
8380 $ runReaderAt (Proxy :: _ "cryptoFunctions") services.cryptoFunctions
8481 $ act
8582
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
83+ runWithDecryptionKey ::
84+ Run
85+ ( app :: Reader Elm
86+ , aff :: Aff
87+ , effect :: Effect
88+ , key :: Reader SymmetricCryptoKey
89+ , crypto :: Reader CryptoService
90+ , storage :: Reader Storage
91+ , services :: Reader Services
92+ , cryptoFunctions :: Reader CryptoFunctions
93+ )
94+ Unit ->
95+ Effect Unit
9996 runWithDecryptionKey act = do
10097 maybeKey <- Ref.read masterKeyRef
10198 runAff
@@ -106,10 +103,8 @@ main = do
106103 runReaderAt (Proxy :: _ "key") key $ act
107104 Nothing -> requestMasterPassword
108105 Elm.subscribe app \cmd -> case cmd of
109− Bridge.Login (Bridge.Cmd_Login { email: email', server, password, secondFactor }) -> do
110− let
111− email = Email email'
112− captchaResponse <- Ref.read hCaptchaTokenRef
106+ Bridge.Login (Bridge.Cmd_Login { email, server, password }) -> do
107+ captchaToken <- Ref.read hCaptchaTokenRef
113108 Ref.write Nothing hCaptchaTokenRef
114109 let
115110 urls = baseUrl server
@@ -120,27 +115,18 @@ main = do
120115 loginResponse <-
121116 liftAff
122117 $ catchError
123− ( do
124− resp <-
125− runBaseAff' $ run $ runReaderAt (Proxy :: _ "api") unauthedApi
126− $ Logic.getLogInRequestToken
127− { prelogin
128− , email
129− , password: Password password
130− , captchaResponse
131− , secondFactor
132− }
133− pure $ Right resp
118+ ( runBaseAff' $ run $ runReaderAt (Proxy :: _ "api") unauthedApi
119+ $ Logic.getLogInRequestToken prelogin (Email email) (Password password) captchaToken
134120 )
135− ( \err -> do
136− log $ show err
137− pure $ Left unit
121+ ( const
122+ $ liftEffect do
123+ Elm.send app Bridge.NeedsLogin
124+ Exc.throw "Could not log in. Please check your data and try again."
138125 )
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
143− masterKey <- Logic.makePreloginKey prelogin email (Password password)
126+ case toEither1 loginResponse of
127+ Left { siteKey } -> send $ Bridge.NeedsCaptcha siteKey
128+ Right token -> do
129+ masterKey <- Logic.makePreloginKey prelogin (Email email) (Password password)
144130 api <- liftPromise $ services.getApi urls (nullify token)
145131 sync <- liftPromise $ api.getSync unit
146132 hash <- hashPassword (Password password)
@@ -153,20 +139,16 @@ main = do
153139 Storage.store storage SyncKey sync
154140 Storage.store storage TokenKey token
155141 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
161− Right _ -> pure unit
162− providers <- case fromTwoFactorProviderType twoFactorProviders of
163− Left x -> liftEffect $ throw $ "Unknown second factor provider: " <> x
164− Right x -> pure x
165− send $ Bridge.NeedsSecondFactor $ Bridge.Sub_NeedsSecondFactor_List providers
166142 Bridge.NeedCiphersList ->
167143 runWithDecryptionKey do
168− sendCiphers
169− performSync
144+ sync <- getOrReset SyncKey
145+ ciphers <- traverse processCipher sync.ciphers
146+ let
147+ sortedCiphers = Array.sortWith (_.date >>> Down) ciphers
148+ send $ Bridge.LoadCiphers $ Bridge.Sub_LoadCiphers_List $ map _.cipher sortedCiphers
149+ api <- getAuthedApi
150+ newSync <- liftPromise $ api.getSync unit
151+ liftEffect $ Storage.store storage SyncKey newSync
170152 Bridge.NeedsReset -> do
171153 WebStorage.clear storage
172154 Elm.send app Bridge.Reset
@@ -186,9 +168,9 @@ main = do
186168 Bridge.Init -> do
187169 Storage.get storage TokenKey
188170 >>= \x ->
189− Elm.send app case x of
190− Just _ -> Bridge.LoginSuccessful
191− Nothing -> Bridge.NeedsLogin
171+ Elm.send app case x of
172+ Just _ -> Bridge.LoginSuccessful
173+ Nothing -> Bridge.NeedsLogin
192174 Bridge.RequestCipher id ->
193175 runWithDecryptionKey do
194176 sync <- getOrReset SyncKey
@@ -196,7 +178,8 @@ main = do
196178 Nothing -> pure unit
197179 Just cipherResponse -> do
198180 cipher <- decodeCipher cipherResponse
199− for_ cipher $ send <<< Bridge.LoadCipher
181+ send $ Bridge.LoadCipher cipher
182+ pure unit
200183 pure unit
201184 Bridge.NeedEmail ->
202185 runWithDecryptionKey do
@@ -207,84 +190,18 @@ main = do
207190 runWithDecryptionKey do
208191 liftPromise $ Clipboard.clipboard text
209192 Bridge.Open uri -> openURL uri
210− Bridge.UpdateCipher fullCipher ->
211− runWithDecryptionKey do
212− api <- getAuthedApi
213− cipher <- encodeCipher fullCipher
214− newCipher <- liftPromise (api.putCipher cipher) >>= decodeCipher
215− for_ newCipher $ send <<< Bridge.CipherChanged
216− performSync
217− pure unit
218− Bridge.GeneratePassword cfg ->
219− runWithDecryptionKey do
220− password <- liftPromise $ services.passwordGeneration.generatePassword cfg
221− send $ Bridge.GeneratedPassword password
222− pure unit
223− Bridge.CreateCipher fullCipher ->
224− runWithDecryptionKey do
225− api <- getAuthedApi
226− cipher <- encodeCipher fullCipher
227− newCipher <- liftPromise (api.postCipherCreate cipher) >>= decodeCipher
228− for_ newCipher $ send <<< Bridge.CipherChanged
229− performSync
230− pure unit
231− Bridge.DeleteCipher c@(Bridge.FullCipher { id }) ->
232− runWithDecryptionKey do
233− api <- getAuthedApi
234− liftPromise $ api.deleteCipher id
235− send $ Bridge.CipherDeleted c
236− performSync
237− pure unit
238− Bridge.RequestTotp totp ->
239− runWithDecryptionKey do
240− { totpService } <- askAt (Proxy :: _ "services")
241− code <- liftPromise $ totpService.getCode totp
242− let
243− interval = totpService.getTimeInterval totp
244− send $ Bridge.Totp
245− $ Bridge.Sub_Totp
246− { interval
247− , code
248− , source: totp
249− }
250− pure unit
251− Bridge.ChooseSecondFactor
252− ( Bridge.Cmd_ChooseSecondFactor
253− { email: email'
254− , factor
255− , server
256− , password
257− , requestFromServer
258− }
259− ) ->
260− when requestFromServer
261− let
262− email = Email email'
263− in
264− case factor of
265− Bridge.Email ->
266− runAff do
267− let
268− urls = baseUrl server
269− unauthedApi <- liftPromise $ services.getApi urls jnull
270− prelogin <- liftPromise $ unauthedApi.postPrelogin { email }
271− masterPasswordHash <- bwPasswordStringHash prelogin email (Password password)
272− liftPromise $ unauthedApi.postTwoFactorEmail { email, masterPasswordHash }
273− pure unit
274− _ -> pure unit
275193
276−processCipher
277− :: forall r
278− . CipherResponse
279− -> Run
280− ( crypto :: Reader CryptoService
281− , key :: Reader SymmetricCryptoKey
282− , aff :: Aff
283− , effect :: Effect
284− | r
285− )
286− (Maybe { cipher :: Bridge.Sub_LoadCiphers, date :: DateTime })
287−processCipher cipher | not (supportedCipher cipher) = pure Nothing
194+processCipher ::
195+ forall r.
196+ CipherResponse ->
197+ Run
198+ ( crypto :: Reader CryptoService
199+ , key :: Reader SymmetricCryptoKey
200+ , aff :: Aff
201+ , effect :: Effect
202+ | r
203+ )
204+ { cipher :: Bridge.Sub_LoadCiphers, date :: DateTime }
288205processCipher cipher = do
289206 name <- decrypt cipher.name
290207 cipherType <- case cipher.type of
@@ -296,15 +213,13 @@ processCipher cipher = do
296213 | n == cipherTypeCard -> pure Bridge.CardType
297214 n
298215 | n == cipherTypeIdentity -> pure Bridge.IdentityType
299− n -> liftEffect $ throw $ "Unsupported cipher type: " <> show n
300− date <- liftEffect $ maybe (pure bottom) (Timestamp.toDateTime) $ JNullable.toMaybe cipher.revisionDate
216+ n -> liftEffect $ throwError $ error $ "Unsupported cipher type: " <> show n
217+ date <- liftEffect $ Timestamp.toDateTime cipher.revisionDate
301218 pure
302− $ Just
303− $
304− { cipher:
219+ $ { cipher:
305220 Bridge.Sub_LoadCiphers
306221 { name: name
307− , date: maybe "" Timestamp.toLocalDateTimeString $ JNullable.toMaybe cipher.revisionDate
222+ , date: Timestamp.toLocalDateTimeString cipher.revisionDate
308223 , id: cipher.id
309224 , cipherType
310225 }
@@ -335,18 +250,18 @@ baseUrl server =
335250 , keyConnector: jnull
336251 }
337252
338−getOrReset
339− :: forall k t r
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
253+getOrReset ::
254+ forall k t r.
255+ StorageKey k t =>
256+ DecodeJson t =>
257+ k ->
258+ Run
259+ ( storage :: Reader Storage
260+ , app :: Reader Elm
261+ , effect :: Effect
262+ | r
263+ )
264+ t
350265getOrReset key = do
351266 storage <- askAt (Proxy :: _ "storage")
352267 app <- askAt (Proxy :: _ "app")
@@ -357,93 +272,52 @@ getOrReset key = do
357272 Exc.throw "Encountered an internal error. Resetting the app."
358273 liftEffect $ try (Storage.get storage key)
359274 >>= \x -> case x of
360− Left err -> do
361− log $ show err
362− handleError
363− Right Nothing -> handleError
364− Right (Just y) -> pure y
275+ Left err -> do
276+ log $ show err
277+ handleError
278+ Right Nothing -> handleError
279+ Right (Just y) -> pure y
365280
366−getAuthedApi
367− :: forall r
368− . Run
369− ( storage :: Reader Storage
370− , app :: Reader Elm
371− , aff :: Aff
372− , effect :: Effect
373− , services :: Reader Services
374− | r
375− )
376− ApiService
281+getAuthedApi ::
282+ forall r.
283+ Run
284+ ( storage :: Reader Storage
285+ , app :: Reader Elm
286+ , aff :: Aff
287+ , effect :: Effect
288+ , services :: Reader Services
289+ | r
290+ )
291+ ApiService
377292getAuthedApi = do
378293 services <- askAt (Proxy :: _ "services")
379294 token <- getOrReset TokenKey
380295 url <- getOrReset UrlsKey
381296 liftPromise $ services.getApi (baseUrl url) (nullify token)
382297
383−requestMasterPassword
384− :: forall r
385− . Run
386− ( storage :: Reader Storage
387− , app :: Reader Elm
388− , effect :: Effect
389− | r
390− )
391− Unit
298+requestMasterPassword ::
299+ forall r.
300+ Run
301+ ( storage :: Reader Storage
302+ , app :: Reader Elm
303+ , effect :: Effect
304+ | r
305+ )
306+ Unit
392307requestMasterPassword = do
393308 url <- getOrReset UrlsKey
394309 sync <- getOrReset SyncKey
395310 send $ Bridge.NeedsMasterPassword $ Bridge.Sub_NeedsMasterPassword { server: url, login: sync.profile.email }
396311
397−send
398− :: forall r
399− . Bridge.Sub
400− -> Run
401− ( app :: Reader Elm
402− , effect :: Effect
403− | r
404− )
405− Unit
312+send ::
313+ forall r.
314+ Bridge.Sub ->
315+ Run
316+ ( app :: Reader Elm
317+ , effect :: Effect
318+ | r
319+ )
320+ Unit
406321send sub = do
407322 app <- askAt (Proxy :: _ "app")
408323 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.purs+1−7
@@ -2,7 +2,7 @@ module Storage where
22
33import Prelude
44
5−import BW (Hash)
5+import BW (Hash(..))
66import BW.Types (IdentityTokenResponse, SyncResponse, PreloginResponse)
77import Localstorage (class StorageKey)
88
@@ -35,9 +35,3 @@ data MasterPasswordHashKey
3535
3636instance StorageKey MasterPasswordHashKey Hash where
3737 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+63−121
@@ -1,159 +1,101 @@
11Sub:
2− - NeedsLogin
3− - NeedsCaptcha: String
4− - NeedsSecondFactor: { List: TwoFactorProviderType }
5− - CaptchaDone
6− - Error: String
7− - LoadCiphers:
8− List:
2+- NeedsLogin
3+- NeedsCaptcha: String
4+- CaptchaDone
5+- Error: String
6+- LoadCiphers:
7+ List:
98 name: String
109 date: String
1110 id: String
1211 cipherType: CipherType
13− - LoginSuccessful
14− - WrongPassword
15− - NeedsMasterPassword:
16− server: String
17− login: String
18− - Reset
19− - LoadCipher: FullCipher
20− - RecieveEmail: String
21− - CipherChanged: FullCipher
22− - GeneratedPassword: String
23− - CipherDeleted: FullCipher
24− - Totp:
25− source: String
26− code: String
27− interval: Int
12+- LoginSuccessful
13+- NeedsMasterPassword:
14+ server: String
15+ login: String
16+- Reset
17+- LoadCipher:
18+ id: String
19+ name: String
20+ cipherType: Cipher
21+- RecieveEmail: String
2822
2923Cmd:
30− - Init
31− - Login:
32− email: String
33− server: 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
46− - NeedCiphersList
47− - SendMasterPassword: String
48− - NeedsReset
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
24+- Init
25+- Login:
26+ email: String
27+ server: String
28+ password: String
29+- NeedCiphersList
30+- SendMasterPassword: String
31+- NeedsReset
32+- RequestCipher: String
33+- NeedEmail
34+- Copy: String
35+- Open: String
5836
59−FullCipher:
60− reprompt: Int
61− favorite: Bool
62− id: String
63− name: String
64− cipher: Cipher
65− collectionIds: { List: String }
6637
6738Cipher:
68− - LoginCipher:
69− uris: { List: String }
70− username:
39+- LoginCipher:
40+ uris: {List: String}
41+ username:
7142 Maybe: String
72− password:
43+ password:
7344 Maybe: String
74− totp:
45+- NoteCipher: String
46+- CardCipher:
47+ cardholderName:
7548 Maybe: String
76− - NoteCipher: String
77− - CardCipher:
78− cardholderName:
49+ brand:
7950 Maybe: String
80− brand:
51+ number:
8152 Maybe: String
82− number:
53+ expMonth:
8354 Maybe: String
84− expMonth:
55+ expYear:
8556 Maybe: String
86− expYear:
57+ code:
8758 Maybe: String
88− code:
59+- IdentityCipher:
60+ title:
8961 Maybe: String
90− - IdentityCipher:
91− title:
62+ firstName:
9263 Maybe: String
93− firstName:
64+ middleName:
9465 Maybe: String
95− middleName:
66+ lastName:
9667 Maybe: String
97− lastName:
68+ address1:
9869 Maybe: String
99− address1:
70+ address2:
10071 Maybe: String
101− address2:
72+ address3:
10273 Maybe: String
103− address3:
74+ city:
10475 Maybe: String
105− city:
76+ state:
10677 Maybe: String
107− state:
78+ postalCode:
10879 Maybe: String
109− postalCode:
80+ country:
11081 Maybe: String
111− country:
82+ company:
11283 Maybe: String
113− company:
84+ email:
11485 Maybe: String
115− email:
86+ phone:
11687 Maybe: String
117− phone:
88+ ssn:
11889 Maybe: String
119− ssn:
90+ username:
12091 Maybe: String
121− username:
92+ passportNumber:
12293 Maybe: String
123− passportNumber:
124− Maybe: String
125− licenseNumber:
94+ licenseNumber:
12695 Maybe: String
12796
12897CipherType:
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
98+- LoginType
99+- CardType
100+- NoteType
101+- IdentityType
build.sh+10−2
@@ -1,5 +1,13 @@
1−#!/bin/sh
1+#!/bin/bash
22
33set -e
44
5−cp -r "$SRC_DIR/dist"/* "$INSTALL_DIR"
5+TEMP=$(mktemp -d)
6+
7+cp -r "$SRC_DIR"/{vanilla,appname.apparmor,appname.desktop,manifest.json,logo.png,index.html,style.css} "$INSTALL_DIR"
8+
9+elm make "$SRC_DIR/frontend/Main.elm" --optimize --output="$TEMP/elm.js"
10+
11+terser "$TEMP/elm.js" --ecma 2018 --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' | terser --ecma 2018 --mangle --compress --output "$INSTALL_DIR/elm.js"
12+
13+(cd "$SRC_DIR" && spago bundle-app --minify --to "$INSTALL_DIR/backend.js")
clickable.yaml+10−2
@@ -1,8 +1,16 @@
11clickable_minimum_required: 7.1.2
2−framework: ubuntu-sdk-20.04
32builder: custom
43build: $SRC_DIR/build.sh
54kill: webapp-container*
6−docker_image: clickable/amd64-20.04-amd64
5+image_setup:
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 terser --unsafe-perm=true
715
816restrict_arch: all
deps/bwnew file+1

Submodule changed.

dev/build-backend.sh+1−7
@@ -1,9 +1,3 @@
11#!/bin/sh
22
3−set -e
4−
5−spago build
6−
7−export out="$(pwd)/backend.js"
8−
9−$(nix build .#backendBuildScript --print-out-paths) "$(pwd)/output"
3+spago bundle-app --to backend.js
dev/build-bw.sh−2
@@ -1,7 +1,5 @@
11#!/bin/sh
22
3−set -e
4−
53(cd deps/bw && npm install)
64
75(cd deps/bw/libs/common/ && npm run clean && npm exec -c "tsc -m es2020 -t es2018")
dev/build.shdeleted−10
@@ -1,10 +0,0 @@
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+1−5
@@ -1,7 +1,3 @@
11#!/bin/sh
22
3−set -e
4−
5−nix build .#vanilla -o vanilla
6−
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
3+elm-live frontend/Main.elm --start-page=index.html --proxy-prefix=/api --proxy-host=http://localhost:6009 --pushstate -- --debug --output=elm.js
dev/elm2nix.shdeleted−8
@@ -1,8 +0,0 @@
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 @@
11#!/bin/sh
22
3−nix run github:ilyakooo0/elm-ps-bridge -- --input bridge.yaml --elmOutput=frontend/Bridge.elm --psOutput=backend/Bridge.purs
3+elm-ps-bridge --input bridge.yaml --elmOutput=Frontend/Bridge.elm --psOutput=backend/Bridge.purs
dev/record-test.shdeleted−11
@@ -1,11 +0,0 @@
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.shdeleted−3
@@ -1,3 +0,0 @@
1−#!/bin/sh
2−
3−nix run "github:justinwoo/spago2nix#spago2nix" -- generate
dev/start-chrome.sh+1−7
@@ -1,9 +1,3 @@
11#!/bin/sh
22
3−if test -x "$(which chromium)"; then
4− 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
3+/Applications/Chromium.app/Contents/MacOS/Chromium --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security
dev/test.shdeleted−9
@@ -1,9 +0,0 @@
1−#!/bin/sh
2−
3−set -e
4−
5−cd tests
6−
7−npm install
8−
9−npx playwright test
dev/update-tests.shdeleted−9
@@ -1,9 +0,0 @@
1−#!/bin/sh
2−
3−set -e
4−
5−cd tests
6−
7−npm install
8−
9−npx playwright test --update-snapshots
elm-srcs.nixdeleted−62
@@ -1,62 +0,0 @@
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−1
@@ -13,7 +13,6 @@
1313 "elm/html": "1.0.0",
1414 "elm/json": "1.1.3",
1515 "elm/time": "1.0.0",
16− "mdgriffith/elm-ui": "1.1.8",
1716 "mgold/elm-nonempty-list": "4.2.0"
1817 },
1918 "indirect": {
flake.lockdeleted−177
@@ -1,177 +0,0 @@
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.nixdeleted−133
@@ -1,133 +0,0 @@
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.nixdeleted−49
@@ -1,49 +0,0 @@
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+16−274
@@ -383,17 +383,6 @@ jsonEncCipher_LoginCipher_password_Maybe val = (maybeEncode (Json.Encode.string
383383
384384
385385
386−type alias Cipher_LoginCipher_totp_Maybe = (Maybe String)
387−
388−jsonDecCipher_LoginCipher_totp_Maybe : Json.Decode.Decoder ( Cipher_LoginCipher_totp_Maybe )
389−jsonDecCipher_LoginCipher_totp_Maybe =
390− Json.Decode.maybe (Json.Decode.string)
391−
392−jsonEncCipher_LoginCipher_totp_Maybe : Cipher_LoginCipher_totp_Maybe -> Value
393−jsonEncCipher_LoginCipher_totp_Maybe val = (maybeEncode (Json.Encode.string)) val
394−
395−
396−
397386type alias Cipher_LoginCipher_uris_List = (List String)
398387
399388jsonDecCipher_LoginCipher_uris_List : Json.Decode.Decoder ( Cipher_LoginCipher_uris_List )
@@ -418,16 +407,14 @@ jsonEncCipher_LoginCipher_username_Maybe val = (maybeEncode (Json.Encode.string
418407
419408type alias Cipher_LoginCipher =
420409 { password: Cipher_LoginCipher_password_Maybe
421− , totp: Cipher_LoginCipher_totp_Maybe
422410 , uris: Cipher_LoginCipher_uris_List
423411 , username: Cipher_LoginCipher_username_Maybe
424412 }
425413
426414jsonDecCipher_LoginCipher : Json.Decode.Decoder ( Cipher_LoginCipher )
427415jsonDecCipher_LoginCipher =
428− Json.Decode.succeed (\ppassword ptotp puris pusername -> {password = ppassword, totp = ptotp, uris = puris, username = pusername})
416+ Json.Decode.succeed (\ppassword puris pusername -> {password = ppassword, uris = puris, username = pusername})
429417 |> required "password" (jsonDecCipher_LoginCipher_password_Maybe)
430− |> required "totp" (jsonDecCipher_LoginCipher_totp_Maybe)
431418 |> required "uris" (jsonDecCipher_LoginCipher_uris_List)
432419 |> required "username" (jsonDecCipher_LoginCipher_username_Maybe)
433420
@@ -435,7 +422,6 @@ jsonEncCipher_LoginCipher : Cipher_LoginCipher -> Value
435422jsonEncCipher_LoginCipher val =
436423 Json.Encode.object
437424 [ ("password", jsonEncCipher_LoginCipher_password_Maybe val.password)
438− , ("totp", jsonEncCipher_LoginCipher_totp_Maybe val.totp)
439425 , ("uris", jsonEncCipher_LoginCipher_uris_List val.uris)
440426 , ("username", jsonEncCipher_LoginCipher_username_Maybe val.username)
441427 ]
@@ -491,82 +477,17 @@ jsonEncCipherType val =
491477
492478
493479
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−
557480type alias Cmd_Login =
558481 { email: String
559482 , password: String
560− , secondFactor: Cmd_Login_secondFactor_Maybe
561483 , server: String
562484 }
563485
564486jsonDecCmd_Login : Json.Decode.Decoder ( Cmd_Login )
565487jsonDecCmd_Login =
566− Json.Decode.succeed (\pemail ppassword psecondFactor pserver -> {email = pemail, password = ppassword, secondFactor = psecondFactor, server = pserver})
488+ Json.Decode.succeed (\pemail ppassword pserver -> {email = pemail, password = ppassword, server = pserver})
567489 |> required "email" (Json.Decode.string)
568490 |> required "password" (Json.Decode.string)
569− |> required "secondFactor" (jsonDecCmd_Login_secondFactor_Maybe)
570491 |> required "server" (Json.Decode.string)
571492
572493jsonEncCmd_Login : Cmd_Login -> Value
@@ -574,18 +495,13 @@ jsonEncCmd_Login val =
574495 Json.Encode.object
575496 [ ("email", Json.Encode.string val.email)
576497 , ("password", Json.Encode.string val.password)
577− , ("secondFactor", jsonEncCmd_Login_secondFactor_Maybe val.secondFactor)
578498 , ("server", Json.Encode.string val.server)
579499 ]
580500
581501
582502
583503type Cmd =
584− ChooseSecondFactor Cmd_ChooseSecondFactor
585− | Copy String
586− | CreateCipher FullCipher
587− | DeleteCipher FullCipher
588− | GeneratePassword PasswordGeneratorConfig
504+ Copy String
589505 | Init
590506 | Login Cmd_Login
591507 | NeedCiphersList
@@ -593,18 +509,12 @@ type Cmd =
593509 | NeedsReset
594510 | Open String
595511 | RequestCipher String
596− | RequestTotp String
597512 | SendMasterPassword String
598− | UpdateCipher FullCipher
599513
600514jsonDecCmd : Json.Decode.Decoder ( Cmd )
601515jsonDecCmd =
602516 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)))
517+ [ ("Copy", Json.Decode.lazy (\_ -> Json.Decode.map Copy (Json.Decode.string)))
608518 , ("Init", Json.Decode.lazy (\_ -> Json.Decode.succeed Init))
609519 , ("Login", Json.Decode.lazy (\_ -> Json.Decode.map Login (jsonDecCmd_Login)))
610520 , ("NeedCiphersList", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedCiphersList))
@@ -612,9 +522,7 @@ jsonDecCmd =
612522 , ("NeedsReset", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedsReset))
613523 , ("Open", Json.Decode.lazy (\_ -> Json.Decode.map Open (Json.Decode.string)))
614524 , ("RequestCipher", Json.Decode.lazy (\_ -> Json.Decode.map RequestCipher (Json.Decode.string)))
615− , ("RequestTotp", Json.Decode.lazy (\_ -> Json.Decode.map RequestTotp (Json.Decode.string)))
616525 , ("SendMasterPassword", Json.Decode.lazy (\_ -> Json.Decode.map SendMasterPassword (Json.Decode.string)))
617− , ("UpdateCipher", Json.Decode.lazy (\_ -> Json.Decode.map UpdateCipher (jsonDecFullCipher)))
618526 ]
619527 jsonDecObjectSetCmd = Set.fromList []
620528 in decodeSumTaggedObject "Cmd" "tag" "contents" jsonDecDictCmd jsonDecObjectSetCmd
@@ -622,11 +530,7 @@ jsonDecCmd =
622530jsonEncCmd : Cmd -> Value
623531jsonEncCmd val =
624532 let keyval v = case v of
625− ChooseSecondFactor v1 -> ("ChooseSecondFactor", encodeValue (jsonEncCmd_ChooseSecondFactor v1))
626533 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))
630534 Init -> ("Init", encodeValue (Json.Encode.list identity []))
631535 Login v1 -> ("Login", encodeValue (jsonEncCmd_Login v1))
632536 NeedCiphersList -> ("NeedCiphersList", encodeValue (Json.Encode.list identity []))
@@ -634,111 +538,30 @@ jsonEncCmd val =
634538 NeedsReset -> ("NeedsReset", encodeValue (Json.Encode.list identity []))
635539 Open v1 -> ("Open", encodeValue (Json.Encode.string v1))
636540 RequestCipher v1 -> ("RequestCipher", encodeValue (Json.Encode.string v1))
637− RequestTotp v1 -> ("RequestTotp", encodeValue (Json.Encode.string v1))
638541 SendMasterPassword v1 -> ("SendMasterPassword", encodeValue (Json.Encode.string v1))
639− UpdateCipher v1 -> ("UpdateCipher", encodeValue (jsonEncFullCipher v1))
640542 in encodeSumTaggedObject "tag" "contents" keyval val
641543
642544
643545
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
546+type alias Sub_LoadCipher =
547+ { cipherType: Cipher
659548 , id: String
660549 , name: String
661− , reprompt: Int
662550 }
663551
664−jsonDecFullCipher : Json.Decode.Decoder ( FullCipher )
665−jsonDecFullCipher =
666− Json.Decode.succeed (\pcipher pcollectionIds pfavorite pid pname preprompt -> {cipher = pcipher, collectionIds = pcollectionIds, favorite = pfavorite, id = pid, name = pname, reprompt = preprompt})
667− |> required "cipher" (jsonDecCipher)
668− |> required "collectionIds" (jsonDecFullCipher_collectionIds_List)
669− |> required "favorite" (Json.Decode.bool)
552+jsonDecSub_LoadCipher : Json.Decode.Decoder ( Sub_LoadCipher )
553+jsonDecSub_LoadCipher =
554+ Json.Decode.succeed (\pcipherType pid pname -> {cipherType = pcipherType, id = pid, name = pname})
555+ |> required "cipherType" (jsonDecCipher)
670556 |> required "id" (Json.Decode.string)
671557 |> required "name" (Json.Decode.string)
672− |> required "reprompt" (Json.Decode.int)
673558
674−jsonEncFullCipher : FullCipher -> Value
675−jsonEncFullCipher val =
559+jsonEncSub_LoadCipher : Sub_LoadCipher -> Value
560+jsonEncSub_LoadCipher val =
676561 Json.Encode.object
677− [ ("cipher", jsonEncCipher val.cipher)
678− , ("collectionIds", jsonEncFullCipher_collectionIds_List val.collectionIds)
679− , ("favorite", Json.Encode.bool val.favorite)
562+ [ ("cipherType", jsonEncCipher val.cipherType)
680563 , ("id", Json.Encode.string val.id)
681564 , ("name", Json.Encode.string val.name)
682− , ("reprompt", Json.Encode.int val.reprompt)
683− ]
684−
685−
686−
687−type alias PasswordGeneratorConfig =
688− { ambiguous: Bool
689− , capitalize: Bool
690− , includeNumber: Bool
691− , length: Int
692− , lowercase: Bool
693− , minLowercase: Int
694− , minNumber: Int
695− , minSpecial: Int
696− , minUppercase: Int
697− , numWords: Int
698− , number: Bool
699− , special: Bool
700− , type_: String
701− , uppercase: Bool
702− , wordSeparator: String
703− }
704−
705−jsonDecPasswordGeneratorConfig : Json.Decode.Decoder ( PasswordGeneratorConfig )
706−jsonDecPasswordGeneratorConfig =
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})
708− |> required "ambiguous" (Json.Decode.bool)
709− |> required "capitalize" (Json.Decode.bool)
710− |> required "includeNumber" (Json.Decode.bool)
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)
742565 ]
743566
744567
@@ -800,77 +623,31 @@ jsonEncSub_NeedsMasterPassword val =
800623
801624
802625
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−
837626type Sub =
838627 CaptchaDone
839− | CipherChanged FullCipher
840− | CipherDeleted FullCipher
841628 | Error String
842− | GeneratedPassword String
843− | LoadCipher FullCipher
629+ | LoadCipher Sub_LoadCipher
844630 | LoadCiphers Sub_LoadCiphers_List
845631 | LoginSuccessful
846632 | NeedsCaptcha String
847633 | NeedsLogin
848634 | NeedsMasterPassword Sub_NeedsMasterPassword
849− | NeedsSecondFactor Sub_NeedsSecondFactor_List
850635 | RecieveEmail String
851636 | Reset
852− | Totp Sub_Totp
853− | WrongPassword
854637
855638jsonDecSub : Json.Decode.Decoder ( Sub )
856639jsonDecSub =
857640 let jsonDecDictSub = Dict.fromList
858641 [ ("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)))
861642 , ("Error", Json.Decode.lazy (\_ -> Json.Decode.map Error (Json.Decode.string)))
862− , ("GeneratedPassword", Json.Decode.lazy (\_ -> Json.Decode.map GeneratedPassword (Json.Decode.string)))
863− , ("LoadCipher", Json.Decode.lazy (\_ -> Json.Decode.map LoadCipher (jsonDecFullCipher)))
643+ , ("LoadCipher", Json.Decode.lazy (\_ -> Json.Decode.map LoadCipher (jsonDecSub_LoadCipher)))
864644 , ("LoadCiphers", Json.Decode.lazy (\_ -> Json.Decode.map LoadCiphers (jsonDecSub_LoadCiphers_List)))
865645 , ("LoginSuccessful", Json.Decode.lazy (\_ -> Json.Decode.succeed LoginSuccessful))
866646 , ("NeedsCaptcha", Json.Decode.lazy (\_ -> Json.Decode.map NeedsCaptcha (Json.Decode.string)))
867647 , ("NeedsLogin", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedsLogin))
868648 , ("NeedsMasterPassword", Json.Decode.lazy (\_ -> Json.Decode.map NeedsMasterPassword (jsonDecSub_NeedsMasterPassword)))
869− , ("NeedsSecondFactor", Json.Decode.lazy (\_ -> Json.Decode.map NeedsSecondFactor (jsonDecSub_NeedsSecondFactor_List)))
870649 , ("RecieveEmail", Json.Decode.lazy (\_ -> Json.Decode.map RecieveEmail (Json.Decode.string)))
871650 , ("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))
874651 ]
875652 jsonDecObjectSetSub = Set.fromList []
876653 in decodeSumTaggedObject "Sub" "tag" "contents" jsonDecDictSub jsonDecObjectSetSub
@@ -879,49 +656,14 @@ jsonEncSub : Sub -> Value
879656jsonEncSub val =
880657 let keyval v = case v of
881658 CaptchaDone -> ("CaptchaDone", encodeValue (Json.Encode.list identity []))
882− CipherChanged v1 -> ("CipherChanged", encodeValue (jsonEncFullCipher v1))
883− CipherDeleted v1 -> ("CipherDeleted", encodeValue (jsonEncFullCipher v1))
884659 Error v1 -> ("Error", encodeValue (Json.Encode.string v1))
885− GeneratedPassword v1 -> ("GeneratedPassword", encodeValue (Json.Encode.string v1))
886− LoadCipher v1 -> ("LoadCipher", encodeValue (jsonEncFullCipher v1))
660+ LoadCipher v1 -> ("LoadCipher", encodeValue (jsonEncSub_LoadCipher v1))
887661 LoadCiphers v1 -> ("LoadCiphers", encodeValue (jsonEncSub_LoadCiphers_List v1))
888662 LoginSuccessful -> ("LoginSuccessful", encodeValue (Json.Encode.list identity []))
889663 NeedsCaptcha v1 -> ("NeedsCaptcha", encodeValue (Json.Encode.string v1))
890664 NeedsLogin -> ("NeedsLogin", encodeValue (Json.Encode.list identity []))
891665 NeedsMasterPassword v1 -> ("NeedsMasterPassword", encodeValue (jsonEncSub_NeedsMasterPassword v1))
892− NeedsSecondFactor v1 -> ("NeedsSecondFactor", encodeValue (jsonEncSub_NeedsSecondFactor_List v1))
893666 RecieveEmail v1 -> ("RecieveEmail", encodeValue (Json.Encode.string v1))
894667 Reset -> ("Reset", encodeValue (Json.Encode.list identity []))
895− Totp v1 -> ("Totp", encodeValue (jsonEncSub_Totp v1))
896− WrongPassword -> ("WrongPassword", encodeValue (Json.Encode.list identity []))
897668 in encodeSumTaggedObject "tag" "contents" keyval val
898669
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/GlobalEvents.elmdeleted−9
@@ -1,9 +0,0 @@
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.elmdeleted−78
@@ -1,78 +0,0 @@
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.elmdeleted−31
@@ -1,31 +0,0 @@
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+156−512
@@ -1,9 +1,8 @@
1−module Main exposing (main)
1+module Main exposing (..)
22
33import Bridge
44import Browser
55import FFI exposing (getBridge, sendBridge)
6−import GlobalEvents exposing (Event)
76import Html exposing (..)
87import Html.Lazy as Lazy
98import List.Nonempty as Nonempty exposing (Nonempty(..))
@@ -11,16 +10,12 @@ import Notification
1110import Pages.Captcha as Captcha
1211import Pages.Cipher as Cipher
1312import Pages.Ciphers as Ciphers
14−import Pages.EditCipher as EditCipher exposing (Msg(..))
1513import Pages.Loader exposing (loader)
1614import Pages.Login as Login
1715import Pages.MasterPassword as MasterPassword
1816import Pages.Navigation as Navigation exposing (TopButton(..))
19−import Pages.SecondFactor as SecondFactor
20−import Pages.SecondFactorSelect as SecondFactorSelect
2117import Task
2218import Time
23−import Types exposing (CipherId)
2419import Utils exposing (..)
2520
2621
@@ -30,51 +25,35 @@ notificationLingerSeconds =
3025
3126
3227type Msg
33− = ShowLoginPage
34− | ShowError String String
35− | ShowInfo String String
28+ = RecieveMessage String
29+ | ShowLoginPage
30+ | ShowError String
3631 | CloseNotification
37− | SubmitLogin Bridge.Cmd_Login
32+ | SubmitLogin { email : String, password : String, server : String }
33+ | LoginMsg Login.Msg
34+ | CiphersMsg Ciphers.Msg
3835 | LoadCiphers Bridge.Sub_LoadCiphers_List
39− | LoginSuccessful
36+ | OpenCiphersScreen
4037 | Reset
4138 | NeedsReset
39+ | MasterPasswordMsg MasterPassword.Msg
4240 | SendMasterPassword { password : String }
4341 | ShowMasterPasswordPage { server : String, login : String }
44− | ShowCipherPage Bridge.FullCipher
42+ | CipherMsg Cipher.Msg
43+ | ShowCipherPage Bridge.Sub_LoadCipher
4544 | RequestCipher CipherId
4645 | PopView
4746 | RecieveEmail String
4847 | Copy String
4948 | Open String
49+ | CaptchaMsg Captcha.Msg
5050 | ShowCaptcha Captcha.HCaptchSiteKey
5151 | ClearNotification { currentTime : Time.Posix }
5252 | 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
6853
6954
70−type PageMsg
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
55+type alias CipherId =
56+ String
7857
7958
8059type alias Model =
@@ -85,144 +64,90 @@ type alias Model =
8564 }
8665
8766
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−
11267appendNotification : Notification.Config -> Model -> ( Model, Cmd Msg )
11368appendNotification cfg model =
11469 ( { model | notifications = cfg :: model.notifications }, Task.perform UpdateLastNotificatioTime Time.now )
11570
11671
11772type PageModel
118− = LoginModel (Login.Model Msg)
73+ = LoginModel Login.Model
11974 | LoadingPage
120− | CiphersModel (Ciphers.Model Msg)
121− | EditCipherModel (EditCipher.Model Msg)
122− | MasterPasswordModel (MasterPassword.Model Msg)
123− | CipherModel (Cipher.Model Msg)
75+ | CiphersModel Ciphers.Model
76+ | MasterPasswordModel MasterPassword.Model
77+ | CipherModel Cipher.Model
12478 | CaptchaModel Captcha.Model
125− | SecondFactorSelectModel (SecondFactorSelect.Model Msg)
126− | SecondFactorModel (SecondFactor.Model Msg)
12779
12880
12981showPage :
13082 String
13183 -> PageModel
13284 ->
133− { title : List (Html Msg)
85+ { title : String
13486 , body : List (Html Msg)
13587 , topButton : Maybe (Navigation.TopButton Msg)
13688 }
13789showPage 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
16090 case page of
16191 LoginModel model ->
162− liftPageWith LoginMsg
163− { title = [ text Login.title ]
164− , body = Login.view model
165− , topButton = Nothing
166− }
92+ let
93+ p =
94+ Login.page loginCallbacks LoginMsg
95+ in
96+ { title = p.title model
97+ , body = p.view model
98+ , topButton = Nothing
99+ }
167100
168101 CiphersModel model ->
169− liftPageWith CiphersMsg
170− { title = Ciphers.title model
171− , body = Ciphers.view model
172− , topButton =
173− Ciphers.menuConfig email model
174− |> Navigation.mapMenuConfig (CiphersMsg >> PageMsg)
175− |> Navigation.MenuButton
176− |> Just
177− }
102+ let
103+ p =
104+ Ciphers.page ciphersCallbacks CiphersMsg
105+ in
106+ { title = p.title model
107+ , body = p.view model
108+ , topButton =
109+ Ciphers.menuConfig email model
110+ |> Navigation.mapMenuConfig CiphersMsg
111+ |> Navigation.MenuButton
112+ |> Just
113+ }
178114
179115 LoadingPage ->
180− { title = []
116+ { title = ""
181117 , body = [ loader ]
182− , topButton = Just simpleBackButton
118+ , topButton = Just (BackButton PopView)
183119 }
184120
185121 MasterPasswordModel model ->
186− liftPageWith MasterPasswordMsg
187− { title = [ text MasterPassword.title ]
188− , body = MasterPassword.view model
189− , topButton = Nothing
190− }
122+ let
123+ p =
124+ MasterPassword.page masterPasswordCallbacks MasterPasswordMsg
125+ in
126+ { title = p.title model
127+ , body = p.view model
128+ , topButton = Nothing
129+ }
191130
192131 CipherModel model ->
193− liftPageWith CipherMsg
194− { title = Cipher.title model
195− , body = Cipher.view model
196− , topButton = Just simpleBackButton
197− }
132+ let
133+ p =
134+ Cipher.page cipherCallbacks CipherMsg
135+ in
136+ { title = p.title model
137+ , body = p.view model
138+ , topButton = Just (BackButton PopView)
139+ }
198140
199141 CaptchaModel model ->
200− { title = [ text Captcha.title ]
201− , body = Captcha.view model
202− , topButton = Just simpleBackButton
142+ let
143+ p =
144+ Captcha.page captchaCallbacks CaptchaMsg
145+ in
146+ { title = p.title model
147+ , body = p.view model
148+ , topButton = Just (BackButton PopView)
203149 }
204150
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−
226151
227152main : Program () Model Msg
228153main =
@@ -237,20 +162,20 @@ main =
237162subscriptions : Model -> Sub Msg
238163subscriptions model =
239164 Sub.batch
240− ([ getBridge (ShowError "Something went wrong!")
165+ ([ getBridge ShowError
241166 (\msg ->
242167 case msg of
243168 Bridge.NeedsLogin ->
244169 ShowLoginPage
245170
246171 Bridge.Error err ->
247− ShowError "Something went wrong!" err
172+ ShowError err
248173
249174 Bridge.LoadCiphers ciphers ->
250175 LoadCiphers ciphers
251176
252177 Bridge.LoginSuccessful ->
253− LoginSuccessful
178+ OpenCiphersScreen
254179
255180 Bridge.Reset ->
256181 Reset
@@ -269,68 +194,9 @@ subscriptions model =
269194
270195 Bridge.CaptchaDone ->
271196 PopView
272−
273− Bridge.CipherChanged c ->
274− ShowInfo "Entry updated" ("The entry “" ++ c.name ++ "” has been successfully updated.")
275−
276− Bridge.GeneratedPassword password ->
277− GlobalEvents.GeneratedPassword password |> FireGlobalEvent
278−
279− Bridge.WrongPassword ->
280− WrongPassword
281−
282− Bridge.CipherDeleted c ->
283− ShowInfo "Entry deleted" ("The entry “" ++ c.name ++ "” has been successfully deleted.")
284−
285− Bridge.Totp totp ->
286− GlobalEvents.DecodedTotp totp |> FireGlobalEvent
287−
288− Bridge.NeedsSecondFactor secondFactors ->
289− case secondFactors of
290− [ provider ] ->
291− SelectSecondFactor
292− { provider = provider
293− , requestFromServer = False
294− }
295−
296− _ ->
297− ShowSecondFactorSelect secondFactors
298197 )
299198 ]
300199 ++ 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− )
334200 )
335201
336202
@@ -349,10 +215,7 @@ view : Model -> Html Msg
349215view model =
350216 div []
351217 (maybeList (List.head model.notifications) (Lazy.lazy2 Notification.notification CloseNotification)
352− ++ [ Lazy.lazy2 Navigation.showNavigationView
353− model.pageStack
354− (showPage (Maybe.withDefault "" model.userEmail))
355− ]
218+ ++ [ Lazy.lazy2 Navigation.showNavigationView model.pageStack (showPage (Maybe.withDefault "" model.userEmail)) ]
356219 )
357220
358221
@@ -364,6 +227,12 @@ mapHead f (Nonempty x xx) =
364227doNotStoreInHistory : PageModel -> Bool
365228doNotStoreInHistory page =
366229 case page of
230+ LoginModel _ ->
231+ True
232+
233+ MasterPasswordModel _ ->
234+ True
235+
367236 LoadingPage ->
368237 True
369238
@@ -374,9 +243,19 @@ doNotStoreInHistory page =
374243update : Msg -> Model -> ( Model, Cmd Msg )
375244update msg model =
376245 let
377− updatePageStackHead : PageModel -> Model
378− updatePageStackHead mdl =
379− { model | pageStack = mapHead (always mdl) model.pageStack }
246+ processPage : (pageModel -> PageModel) -> ( Result String pageModel, Cmd Msg ) -> ( Model, Cmd Msg )
247+ processPage liftModel ( resultModel, cmd ) =
248+ case resultModel of
249+ Ok mdl ->
250+ ( { model | pageStack = mapHead (always (liftModel mdl)) model.pageStack }, cmd )
251+
252+ Err err ->
253+ appendNotification
254+ { title = "An error had occured"
255+ , message = err
256+ , severity = Notification.Error
257+ }
258+ model
380259
381260 appendPageStack : PageModel -> Model
382261 appendPageStack mdl =
@@ -385,21 +264,24 @@ update msg model =
385264 keepStackWith : PageModel -> Model
386265 keepStackWith mdl =
387266 { model | pageStack = Nonempty.cons mdl model.pageStack }
267+
268+ currentPage =
269+ model.pageStack |> Nonempty.head
388270 in
389271 case msg of
390− ShowError title err ->
272+ RecieveMessage x ->
391273 appendNotification
392− { title = title
393− , message = err
394− , severity = Notification.Error
274+ { title = "Got a message!"
275+ , message = x
276+ , severity = Notification.Info
395277 }
396278 model
397279
398− ShowInfo title inf ->
280+ ShowError err ->
399281 appendNotification
400− { title = title
401− , message = inf
402− , severity = Notification.Info
282+ { title = "Something went wrong"
283+ , message = err
284+ , severity = Notification.Error
403285 }
404286 model
405287
@@ -409,64 +291,43 @@ update msg model =
409291 )
410292
411293 SubmitLogin data ->
412− ( keepStackWith <| LoadingPage
294+ ( appendPageStack <| LoadingPage
413295 , FFI.sendBridge (Bridge.Login data)
414296 )
415297
298+ LoginMsg imsg ->
299+ case currentPage of
300+ LoginModel page ->
301+ (Login.page loginCallbacks LoginMsg).update imsg page |> processPage LoginModel
302+
303+ _ ->
304+ ( model, Cmd.none )
305+
306+ CiphersMsg imsg ->
307+ case currentPage of
308+ CiphersModel page ->
309+ (Ciphers.page ciphersCallbacks CiphersMsg).update imsg page |> processPage CiphersModel
310+
311+ _ ->
312+ ( model, Cmd.none )
313+
416314 ShowLoginPage ->
417− ( Login.init loginCallbacks |> LoginModel |> appendPageStack
418− , Cmd.none
419− )
315+ (Login.page loginCallbacks LoginMsg).init ()
316+ |> Tuple.mapFirst (\pageModel -> appendPageStack <| LoginModel pageModel)
420317
421318 LoadCiphers 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− }
319+ (Ciphers.page ciphersCallbacks CiphersMsg).init ciphers
320+ |> Tuple.mapFirst (\pageModel -> appendPageStack <| CiphersModel pageModel)
450321
451− else
452− appendPageStack <| CiphersModel (Ciphers.init ciphersCallbacks ciphers)
453− , Cmd.none
454− )
455−
456− LoginSuccessful ->
457− ( { model | pageStack = Nonempty.singleton LoadingPage }
458− , Cmd.batch [ sendBridge Bridge.NeedCiphersList, sendBridge Bridge.NeedEmail ]
459− )
322+ OpenCiphersScreen ->
323+ ( appendPageStack <| LoadingPage, Cmd.batch [ sendBridge Bridge.NeedCiphersList, sendBridge Bridge.NeedEmail ] )
460324
461325 Reset ->
462326 init
463327
464328 ShowMasterPasswordPage { server, login } ->
465− ( MasterPassword.init masterPasswordCallbacks { server = server, login = login }
466− |> MasterPasswordModel
467− |> appendPageStack
468− , Cmd.none
469− )
329+ (MasterPassword.page masterPasswordCallbacks MasterPasswordMsg).init { server = server, login = login }
330+ |> Tuple.mapFirst (\pageModel -> appendPageStack <| MasterPasswordModel pageModel)
470331
471332 NeedsReset ->
472333 ( appendPageStack <| LoadingPage, sendBridge Bridge.NeedsReset )
@@ -474,8 +335,24 @@ update msg model =
474335 SendMasterPassword { password } ->
475336 ( appendPageStack <| LoadingPage, sendBridge (Bridge.SendMasterPassword password) )
476337
338+ MasterPasswordMsg imsg ->
339+ case currentPage of
340+ MasterPasswordModel page ->
341+ (MasterPassword.page masterPasswordCallbacks MasterPasswordMsg).update imsg page |> processPage MasterPasswordModel
342+
343+ _ ->
344+ ( model, Cmd.none )
345+
346+ CipherMsg imsg ->
347+ case currentPage of
348+ CipherModel page ->
349+ (Cipher.page cipherCallbacks CipherMsg).update imsg page |> processPage CipherModel
350+
351+ _ ->
352+ ( model, Cmd.none )
353+
477354 ShowCipherPage cipher ->
478− Cipher.init cipherCallbacks cipher
355+ (Cipher.page cipherCallbacks CipherMsg).init cipher
479356 |> Tuple.mapFirst (\pageModel -> appendPageStack <| CipherModel pageModel)
480357
481358 RequestCipher id ->
@@ -496,7 +373,16 @@ update msg model =
496373 ( model, FFI.sendBridge (Bridge.Open uri) )
497374
498375 ShowCaptcha uri ->
499− ( Captcha.init uri |> CaptchaModel |> appendPageStack, Cmd.none )
376+ (Captcha.page captchaCallbacks CaptchaMsg).init uri
377+ |> Tuple.mapFirst (\pageModel -> keepStackWith <| CaptchaModel pageModel)
378+
379+ CaptchaMsg imsg ->
380+ case currentPage of
381+ CaptchaModel page ->
382+ (Captcha.page captchaCallbacks CaptchaMsg).update imsg page |> processPage CaptchaModel
383+
384+ _ ->
385+ ( model, Cmd.none )
500386
501387 ClearNotification { currentTime } ->
502388 if Time.posixToMillis model.lastNotificationTime + notificationLingerSeconds * 1000 > Time.posixToMillis currentTime then
@@ -510,259 +396,25 @@ update msg model =
510396 UpdateLastNotificatioTime time ->
511397 ( { model | lastNotificationTime = time }, Cmd.none )
512398
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−
730399
731400loginCallbacks : Login.Callbacks Msg
732401loginCallbacks =
733− { submit =
734− \{ email, server, password } ->
735− SubmitLogin
736− { email = email
737− , password = password
738− , server = server
739− , secondFactor = Nothing
740− }
741− }
402+ { submit = SubmitLogin }
742403
743404
744405ciphersCallbacks : Ciphers.Callbacks Msg
745406ciphersCallbacks =
746− { selected = RequestCipher
747− , logOut = NeedsReset
748− , createNewCipher = OpenNewCipherEditPage
749− , lift = CiphersMsg >> PageMsg
750− }
407+ { selected = RequestCipher, logOut = NeedsReset }
751408
752409
753410cipherCallbacks : Cipher.Callbacks Msg
754411cipherCallbacks =
755− { copy = Copy, open = Open, edit = EditCipher, needTotp = RequestTotp }
756−
412+ { copy = Copy, open = Open }
757413
758−editCipherCallbacks : EditCipher.Callbacks Msg
759−editCipherCallbacks =
760− { save = UpdateCipher, generatePassword = GeneratePassword, delete = Just DeleteCipher }
761414
762−
763−createCipherCallbacks : EditCipher.Callbacks Msg
764−createCipherCallbacks =
765− { save = CreateCipher, generatePassword = GeneratePassword, delete = Nothing }
415+captchaCallbacks : Captcha.Callbacks
416+captchaCallbacks =
417+ {}
766418
767419
768420masterPasswordCallbacks : MasterPassword.Callbacks Msg
@@ -770,11 +422,3 @@ masterPasswordCallbacks =
770422 { submit = SendMasterPassword
771423 , reset = NeedsReset
772424 }
773−
774−
775−secondFactorSelectCallbacks : SecondFactorSelect.Callbacks Msg
776−secondFactorSelectCallbacks =
777− { selectFactor =
778− \provider ->
779− SelectSecondFactor { provider = provider, requestFromServer = True }
780− }
frontend/Modal.elmdeleted−33
@@ -1,33 +0,0 @@
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+13−23
@@ -1,32 +1,22 @@
11module Page exposing (..)
22
3−import Browser
43import Html exposing (..)
54import Html.Events exposing (..)
65
76
8−page :
9− { init : model
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 )
7+type alias Env =
8+ {}
9+
2410
25− Just msg ->
26− update msg model
27− }
11+type alias Page init model imsg emsg =
12+ (imsg -> emsg)
13+ -> InitializedPage init model imsg emsg
2814
2915
30−noUpdate : a -> model -> ( model, Cmd msg )
31−noUpdate _ model =
32− ( model, Cmd.none )
16+type alias InitializedPage init model imsg emsg =
17+ { init : init -> ( model, Cmd emsg )
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.elm+34−22
@@ -1,4 +1,4 @@
1−module Pages.Captcha exposing (HCaptchSiteKey, Model, init, main, title, view)
1+module Pages.Captcha exposing (Callbacks, HCaptchSiteKey, Model, Msg, page)
22
33import Html exposing (..)
44import Html.Attributes as Attr
@@ -6,15 +6,6 @@ import Page exposing (..)
66import Utils exposing (..)
77
88
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−
189type alias HCaptchSiteKey =
1910 String
2011
@@ -24,22 +15,43 @@ type alias Model =
2415 }
2516
2617
27−title : String
28−title =
29− "Captcha"
18+type Msg
19+ = Noop
20+
3021
22+type alias Callbacks =
23+ {}
24+
25+
26+page : Callbacks -> Page HCaptchSiteKey Model Msg emsg
27+page callbacks liftMsg =
28+ { init = \siteKey -> Tuple.mapSecond (Cmd.map liftMsg) (init siteKey)
29+ , view = \model -> view model |> List.map (Html.map liftMsg)
30+ , update = \msg model -> update callbacks liftMsg msg model
31+ , subscriptions = \model -> subscriptions model |> Sub.map liftMsg
32+ , title = always "Captcha"
33+ }
3134
32−init : HCaptchSiteKey -> Model
35+
36+init : HCaptchSiteKey -> ( Model, Cmd Msg )
3337init siteKey =
34− { siteKey = siteKey }
38+ ( { siteKey = siteKey }, Cmd.none )
39+
40+
41+update : Callbacks -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
42+update _ _ msg model =
43+ case msg of
44+ Noop ->
45+ ( Ok model, Cmd.none )
46+
47+
48+subscriptions : Model -> Sub Msg
49+subscriptions model =
50+ Sub.none
3551
3652
37−view : Model -> List (Html msg)
53+view : Model -> List (Html Msg)
3854view { 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− []
55+ [ div [ Attr.class "u-embedded-media" ]
56+ [ iframe [ Attr.src ("https://iko.soy/warden/hcaptcha.html?" ++ siteKey) ] [] ]
4557 ]
frontend/Pages/Cipher.elm+31−126
@@ -1,31 +1,17 @@
1−module Pages.Cipher exposing
2− ( Callbacks
3− , Model
4− , Msg
5− , event
6− , init
7− , subscriptions
8− , title
9− , update
10− , view
11− )
1+module Pages.Cipher exposing (..)
122
133import Bridge
14−import GlobalEvents
154import Html exposing (..)
165import Html.Attributes as Attr
176import Html.Events as Ev
187import Page exposing (..)
19−import Time
208import Utils exposing (..)
219
2210
23−type alias Model emsg =
24− { cipher : Bridge.FullCipher
11+type alias Model =
12+ { cipher : Bridge.Sub_LoadCipher
2513 , passwordHidden : Bool
2614 , cvvHidden : Bool
27− , decodedTotp : Maybe { code : String, interval : Int }
28− , callbacks : Callbacks emsg
2915 }
3016
3117
@@ -34,107 +20,51 @@ type Msg
3420 | ToggleCVVVisiblity
3521 | Copy String
3622 | Open String
37− | Edit Bridge.FullCipher
3823
3924
4025type alias Callbacks msg =
41− { copy : String -> msg
42− , open : String -> msg
43− , edit : Bridge.FullCipher -> msg
44− , needTotp : String -> msg
45− }
46−
47−
48−event : Model emsg -> GlobalEvents.Event -> Model emsg
49−event model ev =
50− case ev of
51− GlobalEvents.UpdateCipher c ->
52− { model
53− | cipher =
54− if c.id == model.cipher.id then
55− c
56−
57− else
58− model.cipher
59− }
26+ { copy : String -> msg, open : String -> msg }
6027
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 } }
6628
67− else
68− model
69−
70− _ ->
71− model
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− ]
29+page : Callbacks emsg -> Page Bridge.Sub_LoadCipher Model Msg emsg
30+page callbacks liftMsg =
31+ { init = \cipher -> Tuple.mapSecond (Cmd.map liftMsg) (init cipher)
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 = \{ cipher } -> cipher.name
36+ }
8237
8338
84−init : Callbacks emsg -> Bridge.FullCipher -> ( Model emsg, Cmd emsg )
85−init callbacks cipher =
39+init : Bridge.Sub_LoadCipher -> ( Model, Cmd Msg )
40+init cipher =
8641 ( { cipher = cipher
8742 , passwordHidden = True
8843 , cvvHidden = True
89− , decodedTotp = Nothing
90− , callbacks = callbacks
9144 }
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− _ ->
102− Cmd.none
45+ , Cmd.none
10346 )
10447
10548
106−update : Msg -> Model emsg -> ( Model emsg, Cmd emsg )
107−update msg model =
49+update : Callbacks emsg -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
50+update { copy, open } _ msg model =
10851 case msg of
10952 TogglePasswordVisiblity ->
110− ( { model | passwordHidden = not model.passwordHidden }, Cmd.none )
53+ ( Ok { model | passwordHidden = not model.passwordHidden }, Cmd.none )
11154
11255 ToggleCVVVisiblity ->
113− ( { model | cvvHidden = not model.cvvHidden }, Cmd.none )
56+ ( Ok { model | cvvHidden = not model.cvvHidden }, Cmd.none )
11457
11558 Copy text ->
116− ( model, model.callbacks.copy text |> pureCmd )
59+ ( Ok model, copy text |> pureCmd )
11760
11861 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))
62+ ( Ok model, open uri |> pureCmd )
13263
133− Nothing ->
134− Sub.none
13564
136− _ ->
137− Sub.none
65+subscriptions : Model -> Sub Msg
66+subscriptions _ =
67+ Sub.none
13868
13969
14070row : { name : String, value : String, nameIcon : String, icons : List ( String, msg ) } -> Html msg
@@ -147,10 +77,10 @@ row { name, value, nameIcon, icons } =
14777 ]
14878
14979
150−view : Model emsg -> List (Html Msg)
151−view { passwordHidden, cipher, cvvHidden, decodedTotp } =
152− (case cipher.cipher of
153− Bridge.LoginCipher { username, password, uris, totp } ->
80+view : Model -> List (Html Msg)
81+view { passwordHidden, cipher, cvvHidden } =
82+ (case cipher.cipherType of
83+ Bridge.LoginCipher { username, password, uris } ->
15484 maybeList username
15585 (\x ->
15686 row
@@ -170,41 +100,16 @@ view { passwordHidden, cipher, cvvHidden, decodedTotp } =
170100 , icons = [ ( hiddenButtonIcon passwordHidden, TogglePasswordVisiblity ), ( "copy", Copy x ) ]
171101 }
172102 )
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 ) ]
192− }
193− )
194103 ++ (uris
195104 |> List.map
196105 (\uri ->
197106 row
198− { name = "URL"
199− , value = uri
200− , nameIcon = "get-link"
201− , icons = [ ( "external-link", Open uri ), ( "copy", Copy uri ) ]
202− }
107+ { name = "URL", value = uri, nameIcon = "get-link", icons = [ ( "external-link", Open uri ), ( "copy", Copy uri ) ] }
203108 )
204109 )
205110
206111 Bridge.NoteCipher contents ->
207− [ row { name = "Note", value = contents, nameIcon = "switcher-environments", icons = [] }
112+ [ row { name = "Note", value = contents, nameIcon = "edit", icons = [] }
208113 ]
209114
210115 Bridge.CardCipher { number, code, cardholderName, expMonth, expYear } ->
@@ -274,7 +179,7 @@ view { passwordHidden, cipher, cvvHidden, decodedTotp } =
274179 ++ maybeList ssn
275180 (\x ->
276181 row
277− { name = "Social security number"
182+ { name = "Socia security number"
278183 , value = x
279184 , nameIcon = ""
280185 , icons = [ ( "copy", Copy x ) ]
frontend/Pages/Ciphers.elm+58−148
@@ -1,18 +1,6 @@
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− )
1+module Pages.Ciphers exposing (Callbacks, Model, Msg, menuConfig, page)
132
143import Bridge
15−import GlobalEvents
164import Html exposing (..)
175import Html.Attributes as Attr
186import Html.Events as Ev
@@ -25,23 +13,16 @@ import Search
2513import Utils exposing (..)
2614
2715
28−type alias Model emsg =
16+type alias Model =
2917 { ciphers : Bridge.Sub_LoadCiphers_List
3018 , search : String
3119 , menuVisible : Bool
3220 , ciphersListFilter : CiphersFilter
3321 , scroll : InfiniteScroll.Model Msg
3422 , sublist : { end : Int }
35− , cipherSelectDropdownVisivble : Bool
36− , callbacks : Callbacks emsg
3723 }
3824
3925
40−updateCiphers : Bridge.Sub_LoadCiphers_List -> Model emsg -> Model emsg
41−updateCiphers ciphers model =
42− { model | ciphers = ciphers }
43−
44−
4526type CiphersFilter
4627 = AllCiphers
4728 | SpecificCiphers Bridge.CipherType
@@ -57,7 +38,7 @@ applyCipherFilter filter list =
5738 list |> List.filter (.cipherType >> (==) t)
5839
5940
60−menuConfig : String -> Model emsg -> Navigation.MenuConfig Msg
41+menuConfig : String -> Model -> Navigation.MenuConfig Msg
6142menuConfig email { ciphersListFilter, menuVisible } =
6243 { title = email
6344 , items =
@@ -82,7 +63,7 @@ menuConfig email { ciphersListFilter, menuVisible } =
8263 , current = ciphersListFilter == SpecificCiphers Bridge.CardType
8364 }
8465 , { icon = "user"
85− , name = "Contacts"
66+ , name = "Identities"
8667 , trigger = UpdateFilter (SpecificCiphers Bridge.IdentityType)
8768 , current = ciphersListFilter == SpecificCiphers Bridge.IdentityType
8869 }
@@ -101,137 +82,72 @@ type Msg
10182 | LogOut
10283 | LoadMore InfiniteScroll.Direction
10384 | InfiniteScrollMsg InfiniteScroll.Msg
104− | CreateNewCipher Bridge.CipherType
105− | ToggleSelectCipherTypeVisible
106− | SelectCipher String
10785
10886
10987type alias Callbacks msg =
11088 { selected : String -> msg
11189 , logOut : msg
112− , createNewCipher : Bridge.CipherType -> msg
113− , lift : Msg -> msg
11490 }
11591
11692
117−event : Model emsg -> GlobalEvents.Event -> Model emsg
118−event model ev =
119− case ev of
120− GlobalEvents.UpdateCipher c ->
121− { model
122− | ciphers =
123− model.ciphers
124− |> List.map
125− (\cipher ->
126− if cipher.id == c.id then
127− { cipher | name = c.name }
128−
129− else
130− cipher
131− )
132− }
133−
134− _ ->
135− model
93+page : Callbacks emsg -> Page Bridge.Sub_LoadCiphers_List Model Msg emsg
94+page callbacks liftMsg =
95+ { init = \ciphers -> Tuple.mapSecond (Cmd.map liftMsg) (init ciphers)
96+ , view = view callbacks liftMsg
97+ , update = \msg model -> update callbacks liftMsg msg model
98+ , subscriptions = \model -> subscriptions model |> Sub.map liftMsg
99+ , title =
100+ \{ ciphersListFilter } ->
101+ case ciphersListFilter of
102+ AllCiphers ->
103+ "All items"
136104
105+ SpecificCiphers cipherType ->
106+ case cipherType of
107+ Bridge.CardType ->
108+ "Cards"
137109
138−title : Model emsg -> List (Html Msg)
139−title { ciphersListFilter, cipherSelectDropdownVisivble } =
140− [ text
141− (case ciphersListFilter of
142− AllCiphers ->
143− "All items"
110+ Bridge.IdentityType ->
111+ "Identities"
144112
145− SpecificCiphers cipherType ->
146− case cipherType of
147− Bridge.CardType ->
148− "Cards"
113+ Bridge.LoginType ->
114+ "Passwords"
149115
150− Bridge.IdentityType ->
151− "Contacts"
152−
153− Bridge.LoginType ->
154− "Passwords"
155−
156− Bridge.NoteType ->
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− ]
198− )
199− ]
200− )
201− ]
202− ]
116+ Bridge.NoteType ->
117+ "Notes"
118+ }
203119
204120
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− }
121+init : Bridge.Sub_LoadCiphers_List -> ( Model, Cmd Msg )
122+init ciphers =
123+ ( { ciphers = ciphers
124+ , search = ""
125+ , menuVisible = False
126+ , ciphersListFilter = AllCiphers
127+ , scroll = InfiniteScroll.init (LoadMore >> pureCmd)
128+ , sublist = { end = 20 }
129+ }
130+ , Cmd.none
131+ )
216132
217133
218−update : Msg -> Model emsg -> ( Model emsg, Cmd emsg )
219−update msg model =
134+update : Callbacks emsg -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
135+update { logOut } liftMsg msg model =
220136 case msg of
221137 Noop ->
222− ( model, Cmd.none )
138+ ( Ok model, Cmd.none )
223139
224140 UpdateSearch s ->
225− ( { model | search = s }, Cmd.none )
141+ ( Ok { model | search = s }, Cmd.none )
226142
227143 ToggleMenu ->
228− ( { model | menuVisible = not model.menuVisible }, Cmd.none )
144+ ( Ok { model | menuVisible = not model.menuVisible }, Cmd.none )
229145
230146 UpdateFilter filter ->
231− ( { model | ciphersListFilter = filter, menuVisible = False }, Cmd.none )
147+ ( Ok { model | ciphersListFilter = filter, menuVisible = False }, Cmd.none )
232148
233149 LogOut ->
234− ( model, pureCmd model.callbacks.logOut )
150+ ( Ok model, pureCmd logOut )
235151
236152 LoadMore direction ->
237153 let
@@ -243,54 +159,48 @@ update msg model =
243159 InfiniteScroll.Bottom ->
244160 { end = min (model.sublist.end + 10) (List.length model.ciphers) }
245161 in
246− ( { model | sublist = newSublist, scroll = InfiniteScroll.stopLoading model.scroll }, Cmd.none )
162+ ( Ok { model | sublist = newSublist, scroll = InfiniteScroll.stopLoading model.scroll }, Cmd.none )
247163
248164 InfiniteScrollMsg msg_ ->
249165 let
250166 ( scroll, cmd ) =
251167 InfiniteScroll.update InfiniteScrollMsg msg_ model.scroll
252168 in
253− ( { model | scroll = scroll }, Cmd.map model.callbacks.lift cmd )
254−
255− CreateNewCipher t ->
256− ( { model | cipherSelectDropdownVisivble = False }, pureCmd (model.callbacks.createNewCipher t) )
169+ ( Ok { model | scroll = scroll }, Cmd.map liftMsg cmd )
257170
258− ToggleSelectCipherTypeVisible ->
259− ( { model | cipherSelectDropdownVisivble = not model.cipherSelectDropdownVisivble }
260− , Cmd.none
261− )
262171
263− SelectCipher id ->
264− ( model, model.callbacks.selected id |> pureCmd )
172+subscriptions : Model -> Sub Msg
173+subscriptions _ =
174+ Sub.none
265175
266176
267−view : Model emsg -> List (Html Msg)
268−view { ciphers, search, ciphersListFilter, sublist } =
177+view : Callbacks emsg -> (Msg -> emsg) -> Model -> List (Html emsg)
178+view { selected } liftMsg { ciphers, search, ciphersListFilter, sublist } =
269179 [ input
270180 [ Attr.type_ "search"
271181 , Attr.value search
272− , Ev.onInput UpdateSearch
182+ , Ev.onInput (liftMsg << UpdateSearch)
273183 , Attr.placeholder "Search"
274184 , Attr.attribute "autocomplete" "off"
275185 ]
276186 []
277187 , ciphers
278188 |> applyCipherFilter ciphersListFilter
279− |> Search.searchList search .name (List.take sublist.end >> Lazy.lazy showCiphers)
189+ |> Search.searchList search .name (List.take sublist.end >> Lazy.lazy3 showCiphers liftMsg selected)
280190 ]
281191
282192
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" ]
193+showCiphers : (Msg -> msg) -> (String -> msg) -> Bridge.Sub_LoadCiphers_List -> Html msg
194+showCiphers liftMsg selected ciphers =
195+ Keyed.ul [ Attr.class "p-list--divided", InfiniteScroll.infiniteScroll (InfiniteScrollMsg >> liftMsg), Attr.class "ciphers-list no-scroll-bar" ]
286196 (ciphers
287197 |> List.map
288198 (\{ name, date, id } ->
289199 ( id
290− , li [ Attr.class "p-list__item cipher-row", Ev.onClick (SelectCipher id) ]
200+ , li [ Attr.class "p-list__item cipher-row", Ev.onClick (selected id) ]
291201 [ div [ Attr.class "cipher-row-container" ]
292202 [ p [] [ text name ]
293− , p [ Attr.class "p-text--small u-align-text--right u-text--muted hide-in-test" ] [ text date ]
203+ , p [ Attr.class "p-text--small u-align-text--right u-text--muted" ] [ text date ]
294204 ]
295205 ]
296206 )
frontend/Pages/EditCipher.elmdeleted−475
@@ -1,475 +0,0 @@
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+42−41
@@ -1,4 +1,4 @@
1−module Pages.Login exposing (Callbacks, Model, Msg, init, title, update, view)
1+module Pages.Login exposing (Callbacks, Model, Msg, page)
22
33import Html exposing (..)
44import Html.Attributes as Attr
@@ -7,11 +7,10 @@ import Page exposing (..)
77import Utils exposing (..)
88
99
10−type alias Model emsg =
10+type alias Model =
1111 { server : String
1212 , email : String
1313 , password : String
14− , callbacks : Callbacks emsg
1514 }
1615
1716
@@ -27,74 +26,76 @@ type alias Callbacks emsg =
2726 }
2827
2928
30−title : String
31−title =
32− "Log into bitwarden"
29+page : Callbacks emsg -> Page () Model Msg emsg
30+page callbacks liftMsg =
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"
36+ }
3337
3438
35−init : Callbacks emsg -> Model emsg
36−init callbacks =
37− { server = "https://vault.bitwarden.com"
38− , email = ""
39− , password = ""
40− , callbacks = callbacks
41− }
39+init : ( Model, Cmd Msg )
40+init =
41+ ( { server = "https://vault.bitwarden.com"
42+ , email = "ilyakooo0@gmail.com"
43+ , password = ""
44+ }
45+ , Cmd.none
46+ )
4247
4348
44−update : Msg -> Model emsg -> ( Model emsg, Cmd emsg )
45−update msg model =
49+update : Callbacks emsg -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
50+update { submit } liftMsg msg model =
4651 case msg of
4752 UpdateEmail email ->
48− ( { model | email = email }, Cmd.none )
53+ ( Ok { model | email = email }, Cmd.none )
4954
5055 UpdateServer server ->
51− ( { model | server = server }, Cmd.none )
56+ ( Ok { model | server = server }, Cmd.none )
5257
5358 UpdatePassword password ->
54− ( { model | password = password }, Cmd.none )
59+ ( Ok { model | password = password }, Cmd.none )
5560
5661 Submit ->
57− ( model
58− , pureCmd
59− (model.callbacks.submit
60− { email = model.email
61− , password = model.password
62− , server = model.server
63− }
64− )
65− )
66−
67−
68−view : Model emsg -> List (Html Msg)
62+ ( Ok model, pureCmd (submit model) )
63+
64+
65+subscriptions : Model -> Sub Msg
66+subscriptions model =
67+ Sub.none
68+
69+
70+view : Model -> List (Html Msg)
6971view model =
7072 [ form [ Ev.onSubmit Submit ]
71− [ label [] [ text "Server" ]
72− , input
73+ ([ label [] [ text "Server" ]
74+ , input
7375 [ Attr.type_ "url"
7476 , Attr.value model.server
7577 , Ev.onInput UpdateServer
7678 , Attr.attribute "autocomplete" "url"
77− , Attr.required True
7879 ]
7980 []
80− , label [] [ text "Email" ]
81− , input
81+ , label [] [ text "Email" ]
82+ , input
8283 [ Attr.type_ "email"
8384 , Attr.value model.email
8485 , Ev.onInput UpdateEmail
8586 , Attr.attribute "autocomplete" "email"
86− , Attr.required True
8787 ]
8888 []
89− , label [] [ text "Password" ]
90− , input
89+ , label [] [ text "Password" ]
90+ , input
9191 [ Attr.type_ "password"
9292 , Attr.value model.password
9393 , Ev.onInput UpdatePassword
9494 , Attr.attribute "autocomplete" "current-password"
95− , Attr.required True
9695 ]
9796 []
98− , alignRight [ button [ Attr.type_ "submit" ] [ text "Log in" ] ]
99− ]
97+ ]
98+ -- ++ maybeList model.siteKey (\siteKey -> iframe [ Attr.src ("https://iko.soy/warden/hcaptcha.html?" ++ siteKey) ] [])
99+ ++ [ alignRight [ button [ Attr.type_ "submit" ] [ text "Log in" ] ] ]
100+ )
100101 ]
frontend/Pages/MasterPassword.elm+30−21
@@ -1,4 +1,4 @@
1−module Pages.MasterPassword exposing (Callbacks, Model, Msg, init, title, update, view)
1+module Pages.MasterPassword exposing (..)
22
33import Html exposing (..)
44import Html.Attributes as Attr
@@ -7,11 +7,10 @@ import Page exposing (..)
77import Utils exposing (..)
88
99
10−type alias Model emsg =
10+type alias Model =
1111 { server : String
1212 , email : String
1313 , password : String
14− , callbacks : Callbacks emsg
1514 }
1615
1716
@@ -27,34 +26,45 @@ type alias Callbacks emsg =
2726 }
2827
2928
30−title : String
31−title =
32− "Enter master password"
29+page : Callbacks emsg -> Page { server : String, login : String } Model Msg emsg
30+page callbacks liftMsg =
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"
36+ }
3337
3438
35−init : Callbacks emsg -> { server : String, login : String } -> Model emsg
36−init callbacks { server, login } =
37− { server = server
38− , email = login
39− , password = ""
40− , callbacks = callbacks
41− }
39+init : { server : String, login : String } -> ( Model, Cmd Msg )
40+init { server, login } =
41+ ( { server = server
42+ , email = login
43+ , password = ""
44+ }
45+ , Cmd.none
46+ )
4247
4348
44−update : Msg -> Model emsg -> ( Model emsg, Cmd emsg )
45−update msg model =
49+update : Callbacks emsg -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
50+update { submit, reset } _ msg model =
4651 case msg of
4752 UpdatePassword password ->
48− ( { model | password = password }, Cmd.none )
53+ ( Ok { model | password = password }, Cmd.none )
4954
5055 Submit ->
51− ( model, pureCmd (model.callbacks.submit { password = model.password }) )
56+ ( Ok model, pureCmd (submit { password = model.password }) )
5257
5358 Reset ->
54− ( model, pureCmd model.callbacks.reset )
59+ ( Ok model, pureCmd reset )
60+
61+
62+subscriptions : Model -> Sub Msg
63+subscriptions _ =
64+ Sub.none
5565
5666
57−view : Model emsg -> List (Html Msg)
67+view : Model -> List (Html Msg)
5868view model =
5969 [ alignRight [ button [ Attr.class "p-button--negative", Ev.onClick Reset ] [ text "Log out" ] ]
6070 , table [ Attr.class "p-table--mobile-card u-no-margin" ]
@@ -73,8 +83,7 @@ view model =
7383 [ Attr.type_ "password"
7484 , Attr.value model.password
7585 , Ev.onInput UpdatePassword
76− , Attr.attribute "autocomplete" "new-password"
77− , Attr.required True
86+ , Attr.attribute "autocomplete" "current-password"
7887 ]
7988 []
8089 , alignRight [ button [ Attr.type_ "submit" ] [ text "Log in" ] ]
frontend/Pages/Navigation.elm+10−20
@@ -18,13 +18,13 @@ import Utils exposing (..)
1818
1919
2020type TopButton msg
21− = BackButton { action : msg, icon : Maybe String }
21+ = BackButton msg
2222 | MenuButton (MenuConfig msg)
2323
2424
2525type alias Config msg =
2626 { topButton : Maybe (TopButton msg)
27− , title : List (Html msg)
27+ , title : String
2828 }
2929
3030
@@ -40,30 +40,23 @@ navigation { topButton, title } =
4040 )
4141 ++ [ nav [ Attr.class "p-tabs p-tabs__list no-scroll-bar" ]
4242 [ h3 []
43− ([ case topButton of
43+ [ case topButton of
4444 Nothing ->
4545 button
4646 [ Attr.class "p-button--base is-inline u-no-margin", Attr.style "visibility" "hidden" ]
4747 []
4848
49− Just (BackButton { action, icon }) ->
49+ Just (BackButton msg) ->
5050 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− ]
51+ [ Attr.class "p-button--base is-inline u-no-margin", Ev.onClick msg ]
52+ [ i [ Attr.class "p-icon--chevron-up ninety-counter" ] [] ]
5953
6054 Just (MenuButton cfg) ->
6155 button
6256 [ Attr.class "p-button--base is-inline u-no-margin", Ev.onClick cfg.toggle ]
6357 [ i [ Attr.class "p-icon--menu" ] [] ]
64− ]
65− ++ title
66− )
58+ , text title
59+ ]
6760 ]
6861 ]
6962 )
@@ -142,10 +135,7 @@ pushView model stack =
142135
143136popView : PageStack model -> PageStack model
144137popView stack =
145− stack
146− |> Nonempty.tail
147− |> Nonempty.fromList
148− |> Maybe.withDefault stack
138+ Nonempty.tail stack |> Nonempty.fromList |> Maybe.withDefault stack
149139
150140
151141showNavigationView :
@@ -153,7 +143,7 @@ showNavigationView :
153143 ->
154144 (model
155145 ->
156− { title : List (Html msg)
146+ { title : String
157147 , body : List (Html msg)
158148 , topButton : Maybe (TopButton msg)
159149 }
frontend/Pages/PasswordGenerator.elmdeleted−158
@@ -1,158 +0,0 @@
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.elmdeleted−91
@@ -1,91 +0,0 @@
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.elmdeleted−164
@@ -1,164 +0,0 @@
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/Switch.elmdeleted−35
@@ -1,35 +0,0 @@
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.elmdeleted−5
@@ -1,5 +0,0 @@
1−module Types exposing (..)
2−
3−
4−type alias CipherId =
5− String
frontend/Utils.elm−65
@@ -2,7 +2,6 @@ module Utils exposing (..)
22
33import Html exposing (..)
44import Html.Attributes as Attr
5−import Html.Events as Ev
65import Task
76
87
@@ -87,67 +86,3 @@ tailEmpty x =
8786unwords : List String -> String
8887unwords =
8988 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) ] [] ]
img/donate.pngdeleted

Binary file not shown.

index.html+2−2
@@ -5,9 +5,9 @@
55 <meta charset="UTF-8">
66 <meta http-equiv="X-UA-Compatible" content="IE=edge">
77 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
8− <link rel="stylesheet" href="vanilla/dist/style.css">
8+ <link rel="stylesheet" href="vanilla/style.css">
99 <link rel="stylesheet" href="style.css">
10− <title>Warden</title>
10+ <title>Test.</title>
1111 <script src="elm.js"></script>
1212</head>
1313
manifest.json+6−6
@@ -1,15 +1,15 @@
11{
22 "name": "soy.iko.warden",
3− "description": "A simple Bitwarden client",
3+ "description": "A simple bitwarden client",
44 "architecture": "@CLICK_ARCH@",
55 "title": "Warden",
66 "hooks": {
7− "warden": {
8− "apparmor": "warden.apparmor",
9− "desktop": "warden.desktop"
7+ "appname": {
8+ "apparmor": "appname.apparmor",
9+ "desktop": "appname.desktop"
1010 }
1111 },
12− "version": "1.2.4",
13− "maintainer": "Ilia Kostiuchenko <mail@iko.soy>",
12+ "version": "1.0.0",
13+ "maintainer": "Ilia Kostiuchenko <email@iko.soy>",
1414 "framework": "@CLICK_FRAMEWORK@"
1515}
package-lock.jsondeleted−959
@@ -1,959 +0,0 @@
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+6−6
@@ -99,8 +99,8 @@ in upstream
9999-------------------------------
100100-}
101101let upstream =
102− https://github.com/purescript/package-sets/releases/download/psc-0.15.4-20220808/packages.dhall
103− sha256:60eee64b04ca0013fae3e02a69fc3b176105c6baa2f31865c67cd5f881a412fd
102+ https://github.com/purescript/package-sets/releases/download/psc-0.15.2-20220706/packages.dhall
103+ sha256:7a24ebdbacb2bfa27b2fc6ce3da96f048093d64e54369965a2a7b5d9892b6031
104104
105105in upstream
106106 with argonaut-aeson-generic =
@@ -125,7 +125,7 @@ in upstream
125125 ]
126126 , repo =
127127 "https://github.com/ilyakooo0/purescript-argonaut-aeson-generic.git"
128− , version = "3e388500811ef4b6718fc776789f3be050ad7b57"
128+ , version = "purescript-0.14"
129129 }
130130 with untagged-union =
131131 { dependencies =
@@ -142,12 +142,12 @@ in upstream
142142 , "tuples"
143143 , "unsafe-coerce"
144144 ]
145− , repo = "https://github.com/rowtype-yoga/purescript-untagged-union.git"
146− , version = "6c5bccef38696a16e86ba9a65cf778b4462c4a66"
145+ , repo = "https://github.com/ilyakooo0/purescript-untagged-union.git"
146+ , version = "iko/ps-15"
147147 }
148148 with literals =
149149 { repo = "https://github.com/ilyakooo0/purescript-literals.git"
150− , version = "6875fb28026595cfb780318305a77e79b098bb01"
150+ , version = "iko/_null"
151151 , dependencies =
152152 [ "assert"
153153 , "effect"
registry.datdeleted

Binary file not shown.

scratchpad/password_generator.htmldeleted−74
@@ -1,74 +0,0 @@
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.pngdeleted

Binary file not shown.

screenshots/2.pngdeleted

Binary file not shown.

screenshots/3.pngdeleted

Binary file not shown.

screenshots/4.pngdeleted

Binary file not shown.

screenshots/5.pngdeleted

Binary file not shown.

spago-packages.nixdeleted−1102
@@ -1,1102 +0,0 @@
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−5
@@ -28,17 +28,12 @@ to generate this file without the comments in this block.
2828 , "either"
2929 , "exceptions"
3030 , "foldable-traversable"
31− , "foreign"
3231 , "functions"
33− , "integers"
34− , "literals"
3532 , "maybe"
3633 , "newtype"
3734 , "nullable"
38− , "numbers"
3935 , "orders"
4036 , "prelude"
41− , "random"
4237 , "refs"
4338 , "run"
4439 , "strings"
style.css+13−76
@@ -1,38 +1,35 @@
1+
12:root {
2− --color-light: #f7f7f7;
3+ --color-light: #F7F7F7;
34}
45
56main {
67 margin: 1rem;
78}
89
10+
911nav.p-tabs__list {
1012 top: 0;
1113 position: sticky;
1214 background-color: white;
1315 padding-bottom: 1rem;
14− padding-right: 1rem;
1516 padding-bottom: 0;
1617 z-index: 10;
1718}
1819
19−nav h3 {
20− width: 100%;
21−}
22−
2320.ninety-counter {
2421 transform: rotate(90deg);
2522}
2623
2724@keyframes floating-notification-animation {
2825 from {
29− transform: translateY(-60px);
30− opacity: 0;
26+ transform: translateY(-60px) ;
27+ opacity: 0
3128 }
3229
3330 to {
3431 transform: none;
35− opacity: 1;
32+ opacity: 1
3633 }
3734}
3835
@@ -55,17 +52,17 @@ nav h3 {
5552 align-content: center;
5653}
5754
58−.spinner {
55+.spinner{
5956 padding: 1rem;
6057}
6158
6259.cipher-row-container {
63− display: flex;
64− width: 100%;
65− align-items: baseline;
66− justify-content: space-between;
67− flex-direction: row;
68− flex-wrap: wrap;
60+ display: flex;
61+ width: 100%;
62+ align-items: baseline;
63+ justify-content: space-between;
64+ flex-direction: row;
65+ flex-wrap: wrap;
6966}
7067
7168.cipher-row p {
@@ -89,63 +86,3 @@ p {
8986.no-scroll-bar::-webkit-scrollbar {
9087 display: none;
9188}
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.jsondeleted−73
@@ -1,73 +0,0 @@
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.jsondeleted−9
@@ -1,9 +0,0 @@
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.tsdeleted−65
@@ -1,65 +0,0 @@
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.tsdeleted−257
@@ -1,257 +0,0 @@
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.pngdeleted

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

tests/vaultwarden/data/db.sqlite3-shmdeleted

Binary file not shown.

tests/vaultwarden/data/db.sqlite3-waldeleted

No changes to the content.

tests/vaultwarden/data/rsa_key.pemdeleted−27
@@ -1,27 +0,0 @@
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.pemdeleted−9
@@ -1,9 +0,0 @@
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.shdeleted−13
@@ -1,13 +0,0 @@
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
vanillanew file+1

Submodule changed.