on master help sign in
78 files changed+4074−951
since splitsnapshots
unifiedsplit
.github/FUNDING.yml+1−1 .github/workflows/build.yaml+72−8 .gitignore+10−1 .gitmodules−6 .helix/languages.toml+3 backend-main.js+3 backend/BW.js+35−55 backend/BW.purs+37−39 backend/BW/Logic.purs+294−248 backend/BW/Types.purs+392−396 backend/Data/EncString.js+1−1 backend/Data/SymmetricCryptoKey.js+1−2 backend/Main.purs+145−138 backend/Storage.purs+7−1 build.sh+2−14 clickable.yaml+2−10 deps/bw−1 dev/build-backend.sh+7−1 dev/build-bw.sh+2 dev/build.sh+10 dev/elm-live.sh+5−1 dev/elm2nix.sh+8 dev/record-test.sh+11 dev/spago2nix.sh+3 dev/start-chrome.sh+3−1 dev/test.sh+9 dev/update-tests.sh+9 elm-srcs.nix+62 flake.lock+177 flake.nix+133 frontend.nix+49 frontend/Main.elm+4−7 frontend/Page.elm+5 frontend/Pages/Captcha.elm+16−3 frontend/Pages/Ciphers.elm+19−3 frontend/Pages/SecondFactorSelect.elm+2−1 index.html+1−1 manifest.json+5−5 package-lock.json+959 packages.dhall+4−4 registry.dat spago-packages.nix+1102 spago.dhall+3−1 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 appname.apparmor → warden.apparmor+1−1 appname.desktop → warden.desktop
.github/FUNDING.yml+1−1
@@ -1 +1 @@
1−custom: https://iko.soy/donate
1+custom: ["https://iko.soy/donate"]
.github/workflows/build.yaml+72−8
@@ -2,32 +2,96 @@ name: CI
22
33on:
44 push:
5− branches: [ "master" ]
5+ branches: ["master"]
66 pull_request:
7− branches: [ "master" ]
7+ branches: ["master"]
8+ release:
9+ types: [prereleased]
810
911jobs:
1012 build:
1113 runs-on: ubuntu-latest
1214
1315 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+
1424 - uses: actions/checkout@v3
1525 with:
1626 submodules: recursive
1727
1828 - name: Install clickable
1929 run: |
30+ docker --version
2031 sudo apt install adb git python3 python3-pip python3-setuptools
21− pip3 install --user clickable-ut --upgrade
32+ pip3 install --user clickable-ut==7.10.0 --upgrade
2233 echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc
2334
24− - name: Build bw libraries
25− run: ./dev/build-bw.sh
35+ - name: Build artifacts
36+ run: ./dev/build.sh
2637
2738 - name: Build Warden
28− run: clickable build --output dist
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
2966
3067 - 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
3193 with:
32− name: warden
33− path: dist/*
94+ repo_token: ${{ secrets.GITHUB_TOKEN }}
95+ file: build/all/app/*.click
96+ tag: ${{ github.ref }}
97+ file_glob: true
.gitignore+10−1
@@ -7,7 +7,16 @@ static/backend.js
77.spago
88output
99.psc-ide-port
10−backend.js
1110elm.js
1211output-es
1312elm-stuff
13+node_modules/
14+/tests/test-results/
15+/tests/playwright-report/
16+/tests/playwright/.cache/
17+generated-docs
18+backend.js.map
19+.spago2nix
20+backend.js
21+vanilla
22+dist
.gitmodulesdeleted−6
@@ -1,6 +0,0 @@
1−[submodule "deps/bw"]
2− path = deps/bw
3− url = https://github.com/bitwarden/clients.git
4−[submodule "vanilla"]
5− path = vanilla
6− url = https://github.com/ilyakooo0/vanilla-compiled.git
.helix/languages.tomlnew file+3
@@ -0,0 +1,3 @@
1+[[language]]
2+name = "nix"
3+auto-format = true
backend-main.jsnew file+3
@@ -0,0 +1,3 @@
1+import * as Main from "./backend/Main/index.js"
2+
3+Main.main()
backend/BW.js+35−55
@@ -1,27 +1,29 @@
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";
19−import { CipherRequest } from "../../deps/bw/libs/common/src/models/request/cipherRequest";
20−import { CipherCreateRequest } from "../../deps/bw/libs/common/src/models/request/cipherCreateRequest";
21−import { Cipher } from "../../deps/bw/libs/common/src/models/domain/cipher";
22−import { CipherData } from "../../deps/bw/libs/common/src/models/data/cipherData";
23−import { PasswordGenerationService } from "../../deps/bw/libs/common/src/services/passwordGeneration.service";
24−import { TotpService } from "../../deps/bw/libs/shared/dist/src/services/totp.service"
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"
2527
2628function sanitize(obj) {
2729 return JSON.parse(JSON.stringify(obj));
@@ -36,7 +38,7 @@ export function getServices() {
3638
3739 await bg.bootstrap()
3840
39− bg.environmentService.setUrls(urls)
41+ await bg.environmentService.setUrls(urls)
4042
4143 if (tokenResponse != null) {
4244 const accountInformation = await bg.tokenService.decodeToken(tokenResponse.accessToken);
@@ -73,7 +75,7 @@ export function getServices() {
7375 req.email,
7476 req.masterPasswordHash,
7577 req.captchaResponse,
76− new TokenRequestTwoFactor(
78+ new TokenTwoFactorRequest(
7779 req.twoFactor.provider,
7880 req.twoFactor.token,
7981 req.twoFactor.remember
@@ -131,6 +133,7 @@ class MainBackground {
131133 new StateFactory(GlobalState, Account)
132134 );
133135 this.stateService = new StateService(
136+ this.storageService,
134137 this.storageService,
135138 this.storageService,
136139 this.logService,
@@ -161,8 +164,13 @@ class MainBackground {
161164 );
162165 this.i18nService = new I18nService("en");
163166 this.cryptoFunctionService = new WebCryptoFunctionService(window);
167+ this.encryptService = new EncryptServiceImplementation(
168+ this.cryptoFunctionService,
169+ this.logService
170+ );
164171 this.cryptoService = new BrowserCryptoService(
165172 this.cryptoFunctionService,
173+ this.encryptService,
166174 this.platformUtilsService,
167175 this.logService,
168176 this.stateService
@@ -206,34 +214,6 @@ class MainBackground {
206214 }
207215}
208216
209−class MemoryStorageService {
210−
211− constructor() {
212− this.storage = {};
213− }
214−
215− async get(key) {
216− return this.storage[key]
217− }
218−
219− async has(key) {
220− return key in this.storage;
221− }
222−
223− async save(key, obj) {
224− if (obj == null) {
225− this.remove(key)
226− return
227− }
228−
229− this.storage[key] = obj
230− }
231−
232− async remove(key) {
233− delete this.storage[key]
234− }
235−}
236−
237217class BrowserCryptoService extends CryptoService {
238218 async retrieveKeyFromStorage(keySuffix) {
239219 if (keySuffix === "biometric") {
@@ -314,7 +294,7 @@ class BrowserPlatformUtilsService {
314294 saveFile(win, blobData, blobOptions, fileName) {
315295 // BrowserApi.downloadFile(win, blobData, blobOptions, fileName);
316296 }
317− getApplicationVersion() {
297+ getApplicationVersionNumber() {
318298 // return Promise.resolve(BrowserApi.getApplicationVersion());
319299 }
320300 supportsWebAuthn(win) {
backend/BW.purs+37−39
@@ -17,31 +17,31 @@ import Effect (Effect)
1717import Effect.Unsafe (unsafePerformEffect)
1818import Untagged.Union (type (|+|))
1919
20−type TotpService
21− = { getCode :: String -> Promise String
22− , getTimeInterval :: String -> Int
23− }
24−
25−type CryptoService
26− = { makeKey :: Fn4 Password String KDF Int (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− }
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+ }
4545
4646type PasswordGeneration =
4747 { generatePassword :: Bridge.PasswordGeneratorConfig -> Promise String
@@ -55,15 +55,13 @@ cryptoFunctionsTypeSha512 = "sha512" :: CryptoFunctionsType
5555
5656cryptoFunctionsTypeMd5 = "md5" :: CryptoFunctionsType
5757
58−type CryptoFunctionsType
59− = String
58+type CryptoFunctionsType = String
6059
61−type CryptoFunctions
62− = { hash :: Fn2 String CryptoFunctionsType (Promise Hash)
63− }
60+type CryptoFunctions =
61+ { hash :: Fn2 String CryptoFunctionsType (Promise Hash)
62+ }
6463
65−newtype Hash
66− = Hash (ArrayBuffer)
64+newtype Hash = Hash (ArrayBuffer)
6765
6866hashToArray :: Hash -> Array Int
6967hashToArray (Hash buf) =
@@ -86,12 +84,12 @@ instance EncodeJson Hash where
8684instance DecodeJson Hash where
8785 decodeJson = decodeJson >>> map arrayToHash
8886
89−type Services
90− = { crypto :: CryptoService
91− , getApi :: Urls -> JNullable IdentityTokenResponse -> Promise ApiService
92− , cryptoFunctions :: CryptoFunctions
93− , passwordGeneration :: PasswordGeneration
94− , totpService :: TotpService
95− }
87+type Services =
88+ { crypto :: CryptoService
89+ , getApi :: Urls -> JNullable IdentityTokenResponse -> Promise ApiService
90+ , cryptoFunctions :: CryptoFunctions
91+ , passwordGeneration :: PasswordGeneration
92+ , totpService :: TotpService
93+ }
9694
9795foreign import getServices :: Effect Services
backend/BW/Logic.purs+294−248
@@ -3,6 +3,7 @@ module BW.Logic where
33import BW
44import BW.Types
55import Prelude
6+
67import Bridge as Bridge
78import Control.Monad.Error.Class (throwError)
89import Control.Promise (Promise)
@@ -14,6 +15,7 @@ import Data.JNullable as JNullable
1415import Data.JOpt (JOpt(..), fromJOpt)
1516import Data.Maybe (Maybe(..), fromMaybe, maybe)
1617import Data.Newtype (class Newtype, unwrap, wrap)
18+import Data.Number.Format as Number
1719import Data.String as String
1820import Data.SymmetricCryptoKey (SymmetricCryptoKey)
1921import Data.SymmetricCryptoKey as SymmetricCryptoKey
@@ -25,43 +27,51 @@ import Effect.Aff.Class (class MonadAff, liftAff)
2527import Effect.Class (liftEffect)
2628import Effect.Exception (error)
2729import Effect.Exception as Exc
30+import Effect.Random (random)
31+import Localstorage as Storage
2832import Run (Run)
2933import Run.Reader (Reader, askAt)
34+import Storage (DeviceIDKey(..))
3035import Type.Prelude (Proxy(..))
3136import Untagged.Union (type (|+|))
37+import Web.Storage.Storage (Storage)
3238
3339-- | Creates the master key
34−makePreloginKey ::
35− forall r.
36− PreloginResponse ->
37− Email ->
38− Password ->
39− Run
40− ( crypto ::
41− Reader CryptoService
42− , effect :: Effect
43− , aff :: Aff
44− | r
45− )
46− SymmetricCryptoKey
47−makePreloginKey { kdf, kdfIterations } (Email email') password = do
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
4853 crypto <- askAt (Proxy :: _ "crypto")
4954 let
5055 email = (String.trim >>> String.toLower) email'
51− liftPromise $ runFn4 crypto.makeKey password email kdf kdfIterations
56+ liftPromise $ runFn4 crypto.makeKey password email kdf
57+ { iterations: kdfIterations
58+ , memory: kdfMemory
59+ , parallelism: kdfParallelism
60+ }
5261
53−makeDecryptionKey ::
54− forall r.
62+makeDecryptionKey
63+ :: forall r
64+ .
5565 -- | Matser key (prelogin key)
56− SymmetricCryptoKey ->
57− EncryptedString ->
58− Run
59− ( crypto :: Reader CryptoService
60− , effect :: Effect
61− , aff :: Aff
62− | r
63− )
64− SymmetricCryptoKey
66+ SymmetricCryptoKey
67+ -> EncryptedString
68+ -> Run
69+ ( crypto :: Reader CryptoService
70+ , effect :: Effect
71+ , aff :: Aff
72+ | r
73+ )
74+ SymmetricCryptoKey
6575makeDecryptionKey masterKey str = do
6676 crypto <- askAt (Proxy :: _ "crypto")
6777 let
@@ -78,28 +88,32 @@ makeDecryptionKey masterKey str = do
7888 liftEffect $ Exc.throw $ "Unsupported key encryption type: " <> show encType
7989 pure $ SymmetricCryptoKey.fromArrayBuffer key
8090
81−getLogInRequestToken ::
82− forall r.
83− { prelogin :: PreloginResponse
84− , email :: Email
85− , password :: Password
86− , captchaResponse :: Maybe String
87− , secondFactor :: Bridge.Cmd_Login_secondFactor_Maybe
88− } ->
89− Run
90− ( api :: Reader ApiService
91− , crypto :: Reader CryptoService
92− , effect :: Effect
93− , aff :: Aff
94− | r
95− )
96− (IdentityCaptchaResponse |+| IdentityTwoFactorResponse |+| IdentityTokenResponse)
97−getLogInRequestToken { prelogin
98−, email
99−, password
100−, captchaResponse
101−, secondFactor: Bridge.Cmd_Login_secondFactor_Maybe secondFactor
102−} = do
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
115+ key <- makePreloginKey prelogin email password
116+ crypto <- askAt (Proxy :: _ "crypto")
103117 let
104118 twoFactor =
105119 maybe
@@ -111,7 +125,11 @@ getLogInRequestToken { prelogin
111125 x { provider = bridgeToSecondFactorType provider }
112126 )
113127 secondFactor
114− StringHash hashedPassword <- bwPasswordStringHash prelogin email password
128+ _localHashedPassword <-
129+ liftPromise
130+ $ runFn3 crypto.hashPassword password key (nullify hashPurposeLocalAuthorization)
131+ StringHash hashedPassword <- liftPromise $ runFn3 crypto.hashPassword password key jnull
132+ deviceId <- getDeviceID
115133 api :: ApiService <- askAt (Proxy :: _ "api")
116134 liftPromise
117135 $ api.postIdentityToken
@@ -122,23 +140,23 @@ getLogInRequestToken { prelogin
122140 , device:
123141 { type: deviceTypeUnknownBrowser
124142 , name: "Ubuntu Touch"
125− , identifier: "42"
143+ , identifier: deviceId
126144 , pushToken: jnull
127145 }
128146 }
129147
130−bwPasswordStringHash ::
131− forall r.
132− PreloginResponse ->
133− Email ->
134− Password ->
135− Run
136− ( crypto :: Reader CryptoService
137− , effect :: Effect
138− , aff :: Aff
139− | r
140− )
141− StringHash
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
142160bwPasswordStringHash prelogin email password = do
143161 key <- makePreloginKey prelogin email password
144162 crypto <- askAt (Proxy :: _ "crypto")
@@ -147,47 +165,47 @@ bwPasswordStringHash prelogin email password = do
147165 $ runFn3 crypto.hashPassword password key (nullify hashPurposeLocalAuthorization)
148166 liftPromise $ runFn3 crypto.hashPassword password key jnull
149167
150−hashPassword ::
151− forall r.
152− Password ->
153− Run
154− ( cryptoFunctions :: Reader CryptoFunctions
155− , aff :: Aff
156− , effect :: Effect
157− | r
158− )
159− Hash
168+hashPassword
169+ :: forall r
170+ . Password
171+ -> Run
172+ ( cryptoFunctions :: Reader CryptoFunctions
173+ , aff :: Aff
174+ , effect :: Effect
175+ | r
176+ )
177+ Hash
160178hashPassword (Password password) = do
161179 cryptoFunctions <- askAt (Proxy :: _ "cryptoFunctions")
162180 liftPromise $ runFn2 cryptoFunctions.hash password cryptoFunctionsTypeSha512
163181
164−decrypt ::
165− forall r.
166− EncryptedString ->
167− Run
168− ( key :: Reader SymmetricCryptoKey
169− , crypto :: Reader CryptoService
170− , aff :: Aff
171− , effect :: Effect
172− | r
173− )
174− String
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
175193decrypt input = do
176194 crypto <- askAt (Proxy :: _ "crypto")
177195 key <- askAt (Proxy :: _ "key")
178196 liftPromise $ runFn2 crypto.decryptToUtf8 (EncString.fromString input) key
179197
180−encrypt ::
181− forall r.
182− String ->
183− Run
184− ( key :: Reader SymmetricCryptoKey
185− , crypto :: Reader CryptoService
186− , aff :: Aff
187− , effect :: Effect
188− | r
189− )
190− EncryptedString
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
191209encrypt input = do
192210 crypto <- askAt (Proxy :: _ "crypto")
193211 key <- askAt (Proxy :: _ "key")
@@ -196,17 +214,17 @@ encrypt input = do
196214liftPromise :: forall m a. MonadAff m => Promise a -> m a
197215liftPromise = liftAff <<< Promise.toAff
198216
199−encodeCipher ::
200− forall r.
201− Bridge.FullCipher ->
202− Run
203− ( key :: Reader SymmetricCryptoKey
204− , crypto :: Reader CryptoService
205− , aff :: Aff
206− , effect :: Effect
207− | r
208− )
209− CipherResponse
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
210228encodeCipher
211229 (Bridge.FullCipher { name, cipher, id, favorite, reprompt, collectionIds }) = do
212230 let
@@ -222,14 +240,14 @@ encodeCipher
222240 pure
223241 x
224242 { card =
225− nullify
226− { cardholderName
227− , brand
228− , number
229− , expMonth
230− , expYear
231− , code
232− }
243+ nullify
244+ { cardholderName
245+ , brand
246+ , number
247+ , expMonth
248+ , expYear
249+ , code
250+ }
233251 , type = cipherTypeCard
234252 }
235253 Bridge.IdentityCipher (Bridge.Cipher_IdentityCipher identity) -> do
@@ -254,26 +272,26 @@ encodeCipher
254272 pure
255273 x
256274 { identity =
257− nullify
258− { address1
259− , address2
260− , address3
261− , city
262− , company
263− , country
264− , email
265− , firstName
266− , lastName
267− , licenseNumber
268− , middleName
269− , passportNumber
270− , phone
271− , postalCode
272− , ssn
273− , state
274− , title
275− , username
276− }
275+ nullify
276+ { address1
277+ , address2
278+ , address3
279+ , city
280+ , company
281+ , country
282+ , email
283+ , firstName
284+ , lastName
285+ , licenseNumber
286+ , middleName
287+ , passportNumber
288+ , phone
289+ , postalCode
290+ , ssn
291+ , state
292+ , title
293+ , username
294+ }
277295 , type = cipherTypeIdentity
278296 }
279297 Bridge.LoginCipher (Bridge.Cipher_LoginCipher login) -> do
@@ -281,7 +299,7 @@ encodeCipher
281299 uris <-
282300 traverse
283301 ( \uri -> do
284− uriEnc <- encrypt uri
302+ uriEnc <- nullify <$> encrypt uri
285303 pure { uri: uriEnc, match: jnull }
286304 )
287305 (unwrap login.uris)
@@ -290,14 +308,14 @@ encodeCipher
290308 pure
291309 x
292310 { login =
293− nullify
294− { password
295− , uris: JOpt $ opt uris
296− , username
297− , passwordRevisionDate: jnull
298− , totp: totp
299− , autofillOnPageLoad: JOpt undefined
300− }
311+ nullify
312+ { password
313+ , uris: JOpt $ opt uris
314+ , username
315+ , passwordRevisionDate: jnull
316+ , totp: totp
317+ , autofillOnPageLoad: JOpt undefined
318+ }
301319 , type = cipherTypeLogin
302320 }
303321 Bridge.NoteCipher note -> do
@@ -332,100 +350,110 @@ encodeCipher
332350 , reprompt
333351 }
334352
335−decodeCipher ::
336− forall r.
337− CipherResponse ->
338− Run
339− ( key :: Reader SymmetricCryptoKey
340− , crypto :: Reader CryptoService
341− , aff :: Aff
342− , effect :: Effect
343− | r
344− )
345− Bridge.FullCipher
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
346373decodeCipher cipher = do
347374 name <- decrypt cipher.name
348375 cipherType <- case cipher.type of
349376 n
350377 | cipherTypeSecureNote == n -> do
351− note <- fromJNullable "" <$> (traverse decrypt cipher.notes)
352− pure $ Bridge.NoteCipher note
378+ note <- fromJNullable "" <$> (traverse decrypt cipher.notes)
379+ pure $ Bridge.NoteCipher note
353380 n
354381 | cipherTypeLogin == n -> do
355− case JNullable.toMaybe cipher.login of
356− Nothing -> liftEffect $ throwError $ error "Login data is missing"
357− Just login -> do
358− username <- decryptNullable login.username
359− password <- decryptNullable login.password
360− totp <- decryptNullable login.totp
361− uris <- fromJOpt [] <$> ((traverse >>> traverse) (_.uri >>> decrypt) login.uris)
362− pure $ Bridge.LoginCipher
363− $ Bridge.Cipher_LoginCipher
364− { username
365− , password
366− , uris: wrap uris
367− , totp
368− }
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+ }
369397 n
370398 | cipherTypeCard == n -> do
371− case JNullable.toMaybe cipher.card of
372− Nothing -> liftEffect $ throwError $ error "Card data is missing"
373− Just card -> do
374− cardholderName <- decryptNullable card.cardholderName
375− number <- decryptNullable card.number
376− code <- decryptNullable card.code
377− brand <- decryptNullable card.brand
378− expMonth <- decryptNullable card.expMonth
379− expYear <- decryptNullable card.expYear
380− pure $ Bridge.CardCipher
381− $ Bridge.Cipher_CardCipher
382− { brand, cardholderName, code, expMonth, expYear, number }
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 }
383411 n
384412 | cipherTypeIdentity == n -> do
385− case JNullable.toMaybe cipher.identity of
386− Nothing -> liftEffect $ throwError $ error "Identity data is missing"
387− Just identity -> do
388− firstName <- decryptNullable identity.firstName
389− middleName <- decryptNullable identity.middleName
390− lastName <- decryptNullable identity.lastName
391− address1 <- decryptNullable identity.address1
392− address2 <- decryptNullable identity.address2
393− address3 <- decryptNullable identity.address3
394− city <- decryptNullable identity.city
395− company <- decryptNullable identity.company
396− country <- decryptNullable identity.country
397− email <- decryptNullable identity.email
398− licenseNumber <- decryptNullable identity.licenseNumber
399− passportNumber <- decryptNullable identity.passportNumber
400− phone <- decryptNullable identity.phone
401− postalCode <- decryptNullable identity.postalCode
402− ssn <- decryptNullable identity.ssn
403− state <- decryptNullable identity.state
404− title <- decryptNullable identity.title
405− username <- decryptNullable identity.username
406− pure $ Bridge.IdentityCipher
407− $ wrap
408− { firstName
409− , middleName
410− , lastName
411− , address1
412− , address2
413− , address3
414− , city
415− , company
416− , country
417− , email
418− , licenseNumber
419− , passportNumber
420− , phone
421− , postalCode
422− , ssn
423− , state
424− , title
425− , username
426− }
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+ }
427455 n -> liftEffect $ throwError $ error $ "Unsupported cipher type: " <> show n
428− pure
456+ pure $ Just
429457 $ wrap
430458 { cipher: cipherType
431459 , name
@@ -435,32 +463,50 @@ decodeCipher cipher = do
435463 , collectionIds: wrap <<< fromJNullable [] $ cipher.collectionIds
436464 }
437465
438−decryptNullable ::
439− forall x r.
440− Newtype x (Maybe String) =>
441− JNullable EncryptedString ->
442− Run
443− ( key :: Reader SymmetricCryptoKey
444− , crypto :: Reader CryptoService
445− , aff :: Aff
446− , effect :: Effect
447− | r
448− )
449− x
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
450478decryptNullable x = wrap <<< fromJNullable Nothing <<< map Just <$> (traverse decrypt x)
451479
452−encryptNullable ::
453− forall x r.
454− Newtype x (Maybe String) =>
455− x ->
456− Run
457− ( key :: Reader SymmetricCryptoKey
458− , crypto :: Reader CryptoService
459− , aff :: Aff
460− , effect :: Effect
461− | r
462− )
463− (JNullable EncryptedString)
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)
464492encryptNullable x = case unwrap x of
465493 Nothing -> pure jnull
466494 Just y -> nullify <$> encrypt y
495+
496+getDeviceID ::
497+ forall r.
498+ Run
499+ ( effect :: Effect
500+ , storage :: Reader Storage
501+ | r
502+ )
503+ String
504+getDeviceID = do
505+ storage <- askAt (Proxy :: _ "storage")
506+ maybeId <- liftEffect $ Storage.get storage DeviceIDKey
507+ case maybeId of
508+ Just id -> pure id
509+ Nothing -> do
510+ id <- map Number.toString $ liftEffect $ random
511+ liftEffect $ Storage.store storage DeviceIDKey id
512+ pure id
backend/BW/Types.purs+392−396
@@ -4,22 +4,25 @@ import Prelude
44
55import Bridge as Bridge
66import Data.Argonaut (class DecodeJson, class EncodeJson)
7+import Data.Either (Either)
8+import Data.Either as Either
9+import Data.Int as Int
710import Data.JNullable (JNullable)
811import Data.JOpt (JOpt)
912import Data.Maybe (Maybe(..))
13+import Data.Nullable (Nullable)
1014import Data.ShowableJson (ShowableJson)
1115import Data.Timestamp (Timestamp)
12−import Data.Traversable (traverse)
13−import Foreign (Foreign)
16+import Data.Traversable (for)
17+import Data.Tuple.Nested ((/\))
1418import Foreign as Foreign
15−import Foreign.Object (Object)
1619import Literals.Undefined (Undefined)
1720import Type.Prelude (Proxy(..))
18−import Untagged.TypeCheck (class HasRuntimeType, hasRuntimeType)
21+import Untagged.TypeCheck (class HasRuntimeType)
1922import Untagged.Union (type (|+|))
23+import Untagged.Union as Union
2024
21−newtype EncryptedString
22− = EncryptedString String
25+newtype EncryptedString = EncryptedString String
2326
2427derive newtype instance Show EncryptedString
2528derive newtype instance Ord EncryptedString
@@ -28,78 +31,86 @@ derive newtype instance EncodeJson EncryptedString
2831derive newtype instance DecodeJson EncryptedString
2932
3033kdfPBKDF2_SHA256 = 0 :: KDF
34+kdfArgon2id = 1 :: KDF
3135
32−type KDF
33− = Int
34−
35−type PreloginResponse
36− = { kdf :: KDF
37− , kdfIterations :: Int
38− }
39−
40−type PreloginRequest
41− = { email :: Email
42− }
43−
44−type ProfileResponse
45− = { id :: String
46− , name :: JNullable String
47− , email :: String
48− , emailVerified :: Boolean
49− , masterPasswordHint :: JNullable String
50− , premiumPersonally :: Boolean
51− -- , premiumFromOrganization :: Boolean
52− , culture :: JNullable String
53− , twoFactorEnabled :: Boolean
54− , key :: EncryptedString
55− , privateKey :: EncryptedString
56− , securityStamp :: JNullable String
57− , forcePasswordReset :: Boolean
58− , usesKeyConnector :: Boolean
59− , organizations :: Array ProfileOrganizationResponse
60− , providers :: Array ProfileProviderResponse
61− , providerOrganizations :: Array ProfileProviderOrganizationResponse
62− }
63−
64−type ProfileOrganizationResponse
65− = { id :: String
66− , name :: JNullable String
67− , usePolicies :: JNullable Boolean
68− , useGroups :: JNullable Boolean
69− , useDirectory :: JNullable Boolean
70− , useEvents :: JNullable Boolean
71− , useTotp :: JNullable Boolean
72− , use2fa :: JNullable Boolean
73− , useApi :: JNullable Boolean
74− , useSso :: JNullable Boolean
75− , useKeyConnector :: JNullable Boolean
76− , useResetPassword :: JNullable Boolean
77− , selfHost :: JNullable Boolean
78− , usersGetPremium :: JNullable Boolean
79− , seats :: JNullable Int
80− , maxCollections :: JNullable Int
81− , maxStorageGb :: JNullable Int
82− , key :: JNullable String
83− , hasPublicAndPrivateKeys :: JNullable Boolean
84− , status :: JNullable OrganizationUserStatusType
85− , type :: JNullable OrganizationUserType
86− , enabled :: JNullable Boolean
87− , ssoBound :: JNullable Boolean
88− , identifier :: JNullable String
89− , permissions :: JNullable PermissionsApi
90− , resetPasswordEnrolled :: JNullable Boolean
91− , userId :: JNullable String
92− , providerId :: JNullable String
93− , providerName :: JNullable String
94− , familySponsorshipFriendlyName :: JNullable String
95− , familySponsorshipAvailable :: JNullable Boolean
96− , planProductType :: JNullable ProductType
97− , keyConnectorEnabled :: JNullable Boolean
98− , keyConnectorUrl :: JNullable String
99− , familySponsorshipLastSyncDate :: JNullable Timestamp
100− , familySponsorshipValidUntil :: JNullable Timestamp
101− , familySponsorshipToDelete :: JNullable Boolean
102− }
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+ }
103114
104115organizationUserStatusTypeInvited = 0 :: OrganizationUserStatusType
105116
@@ -107,8 +118,7 @@ organizationUserStatusTypeAccepted = 0 :: OrganizationUserStatusType
107118
108119organizationUserStatusTypeConfirmed = 0 :: OrganizationUserStatusType
109120
110−type OrganizationUserStatusType
111− = Int
121+type OrganizationUserStatusType = Int
112122
113123organizationUserTypeOwner = 0 :: OrganizationUserType
114124
@@ -120,27 +130,26 @@ organizationUserTypeManager = 3 :: OrganizationUserType
120130
121131organizationUserTypeCustom = 4 :: OrganizationUserType
122132
123−type OrganizationUserType
124− = Int
125−
126−type PermissionsApi
127− = { accessEventLogs :: Boolean
128− , accessImportExport :: Boolean
129− , accessReports :: Boolean
130− , manageAllCollections :: Boolean
131− , createNewCollections :: Boolean
132− , editAnyCollection :: Boolean
133− , deleteAnyCollection :: Boolean
134− , manageAssignedCollections :: Boolean
135− , editAssignedCollections :: Boolean
136− , deleteAssignedCollections :: Boolean
137− , manageCiphers :: Boolean
138− , manageGroups :: Boolean
139− , manageSso :: Boolean
140− , managePolicies :: Boolean
141− , manageUsers :: Boolean
142− , manageResetPassword :: Boolean
143− }
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+ }
144153
145154productTypeFree = 0 :: ProductType
146155
@@ -150,20 +159,19 @@ productTypeTeams = 2 :: ProductType
150159
151160productTypeEnterprise = 3 :: ProductType
152161
153−type ProductType
154− = Int
155−
156−type ProfileProviderResponse
157− = { id :: String
158− , name :: JNullable String
159− , key :: JNullable String
160− , status :: JNullable ProviderUserStatusType
161− , type :: JNullable ProviderUserType
162− , enabled :: Boolean
163− , permissions :: PermissionsApi
164− , userId :: JNullable String
165− , useEvents :: Boolean
166− }
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+ }
167175
168176providerUserStatusTypeInvited = 0 :: ProviderUserStatusType
169177
@@ -171,81 +179,79 @@ providerUserStatusTypeAccepted = 1 :: ProviderUserStatusType
171179
172180providerUserStatusTypeConfirmed = 2 :: ProviderUserStatusType
173181
174−type ProviderUserStatusType
175− = Int
182+type ProviderUserStatusType = Int
176183
177184providerUserTypeProviderAdmin = 0 :: ProviderUserType
178185
179186providerUserTypeServiceUser = 1 :: ProviderUserType
180187
181−type ProviderUserType
182− = Int
183−
184−type ProfileProviderOrganizationResponse
185− = { id :: String
186− , name :: JNullable String
187− , usePolicies :: Boolean
188− , useGroups :: Boolean
189− , useDirectory :: Boolean
190− , useEvents :: Boolean
191− , useTotp :: Boolean
192− , use2fa :: Boolean
193− , useApi :: Boolean
194− , useSso :: Boolean
195− , useKeyConnector :: Boolean
196− , useResetPassword :: Boolean
197− , selfHost :: Boolean
198− , usersGetPremium :: Boolean
199− , seats :: Int
200− , maxCollections :: Int
201− , maxStorageGb :: JNullable Int
202− , key :: JNullable String
203− , hasPublicAndPrivateKeys :: Boolean
204− , status :: OrganizationUserStatusType
205− , type :: OrganizationUserType
206− , enabled :: Boolean
207− , ssoBound :: Boolean
208− , identifier :: JNullable String
209− , permissions :: PermissionsApi
210− , resetPasswordEnrolled :: Boolean
211− , userId :: JNullable String
212− , providerId :: JNullable String
213− , providerName :: JNullable String
214− , familySponsorshipFriendlyName :: JNullable String
215− , familySponsorshipAvailable :: Boolean
216− , planProductType :: ProductType
217− , keyConnectorEnabled :: Boolean
218− , keyConnectorUrl :: JNullable String
219− , familySponsorshipLastSyncDate :: JNullable Timestamp
220− , familySponsorshipValidUntil :: JNullable Timestamp
221− , familySponsorshipToDelete :: JNullable Boolean
222− }
223−
224−type Urls
225− = { base :: JNullable String
226− , webVault :: JNullable String
227− , api :: JNullable String
228− , identity :: JNullable String
229− , icons :: JNullable String
230− , notifications :: JNullable String
231− , events :: JNullable String
232− , keyConnector :: JNullable String
233− }
234−
235−type PasswordTokenRequest
236− = { email :: Email
237− , masterPasswordHash :: String
238− , captchaResponse :: String
239− , twoFactor :: TokenRequestTwoFactor
240− , device :: DeviceRequest
241− }
242−
243−type DeviceRequest
244− = { type :: DeviceType
245− , name :: String
246− , identifier :: String
247− , pushToken :: JNullable String
248− }
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+ }
249255
250256deviceTypeAndroid = 0 :: DeviceType
251257
@@ -289,14 +295,13 @@ deviceTypeVivaldiExtension = 19 :: DeviceType
289295
290296deviceTypeSafariExtension = 20 :: DeviceType
291297
292−type DeviceType
293− = Int
298+type DeviceType = Int
294299
295−type TokenRequestTwoFactor
296− = { provider :: TwoFactorProviderType
297− , token :: String
298− , remember :: Boolean
299− }
300+type TokenRequestTwoFactor =
301+ { provider :: TwoFactorProviderType
302+ , token :: String
303+ , remember :: Boolean
304+ }
300305
301306twoFactorProviderTypeAuthenticator = 0 :: TwoFactorProviderType
302307
@@ -314,12 +319,11 @@ twoFactorProviderTypeOrganizationDuo = 6 :: TwoFactorProviderType
314319
315320twoFactorProviderTypeWebAuthn = 7 :: TwoFactorProviderType
316321
317−type TwoFactorProviderType
318− = Int
322+type TwoFactorProviderType = Int
319323
320−secondFactorTypeToBridge ::
321− TwoFactorProviderType ->
322− Maybe Bridge.TwoFactorProviderType
324+secondFactorTypeToBridge
325+ :: TwoFactorProviderType
326+ -> Maybe Bridge.TwoFactorProviderType
323327secondFactorTypeToBridge x = case x of
324328 n
325329 | n == twoFactorProviderTypeAuthenticator -> Just Bridge.Authenticator
@@ -349,21 +353,17 @@ bridgeToSecondFactorType Bridge.Remember = twoFactorProviderTypeRemember
349353bridgeToSecondFactorType Bridge.OrganizationDuo = twoFactorProviderTypeOrganizationDuo
350354bridgeToSecondFactorType Bridge.WebAuthn = twoFactorProviderTypeWebAuthn
351355
352−newtype Password
353− = Password String
356+newtype Password = Password String
354357
355358hashPurposeServerAuthorization = 1 :: HashPurpose
356359
357360hashPurposeLocalAuthorization = 2 :: HashPurpose
358361
359−type HashPurpose
360− = Int
362+type HashPurpose = Int
361363
362−newtype StringHash
363− = StringHash String
364+newtype StringHash = StringHash String
364365
365−newtype AccessToken
366− = AccessToken String
366+newtype AccessToken = AccessToken String
367367
368368derive newtype instance Show AccessToken
369369derive newtype instance Eq AccessToken
@@ -371,8 +371,7 @@ derive newtype instance Ord AccessToken
371371derive newtype instance EncodeJson AccessToken
372372derive newtype instance DecodeJson AccessToken
373373
374−newtype RefreshToken
375− = RefreshToken String
374+newtype RefreshToken = RefreshToken String
376375
377376derive newtype instance Show RefreshToken
378377derive newtype instance Eq RefreshToken
@@ -384,27 +383,33 @@ type IdentityCaptchaResponse =
384383 { siteKey :: String
385384 }
386385
387−type IdentityTokenResponse
388− = { accessToken :: AccessToken
389− , expiresIn :: Int
390− , refreshToken :: RefreshToken
391− , tokenType :: String
392− , resetMasterPassword :: Boolean
393− , privateKey :: EncryptedString
394− , key :: EncryptedString
395− , twoFactorToken :: JNullable String
396− , kdf :: KDF
397− , kdfIterations :: Int
398− , forcePasswordReset :: JNullable Boolean
399− , apiUseKeyConnector :: JNullable Boolean
400− , keyConnectorUrl :: JNullable String
401− }
402−
403−newtype TwoFactorProviderTypes = TwoFactorProviderTypes (Array TwoFactorProviderType)
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))
404403
405404instance HasRuntimeType TwoFactorProviderTypes where
406405 hasRuntimeType Proxy = Foreign.isArray
407406
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+
408413type IdentityTwoFactorResponse =
409414 { twoFactorProviders :: TwoFactorProviderTypes
410415 -- it is a Map type and untaged unions gets confused
@@ -412,59 +417,57 @@ type IdentityTwoFactorResponse =
412417 , captchaToken :: String |+| Undefined
413418 }
414419
415−newtype Email
416− = Email String
417−
418−type SyncResponse
419− = { profile :: ProfileResponse
420− , folders :: Array FolderResponse
421− , collections :: Array CollectionDetailsResponse
422− , ciphers :: Array CipherResponse
423− , domains :: JNullable DomainsResponse
424− , policies :: JNullable (Array PolicyResponse)
425− , sends :: Array SendResponse
426− }
420+newtype Email = Email String
427421
428−type FolderResponse
429− = { id :: String
430− , name :: JNullable String
431− , revisionDate :: JNullable String
432− }
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+ }
433431
434−type CollectionDetailsResponse
435− = { id :: String
436− , organizationId :: JNullable String
437− , name :: JNullable String
438− , externalId :: JNullable String
439− , readOnly :: Boolean
440− }
432+type FolderResponse =
433+ { id :: String
434+ , name :: JNullable String
435+ , revisionDate :: JNullable String
436+ }
441437
438+type CollectionDetailsResponse =
439+ { id :: String
440+ , organizationId :: JNullable String
441+ , name :: JNullable String
442+ , externalId :: JNullable String
443+ , readOnly :: Boolean
444+ }
442445
443446type CipherId = String
444447
445−type CipherResponse
446− = { id :: CipherId
447− , organizationId :: JNullable String
448− , folderId :: JNullable String
449− , type :: CipherType
450− , name :: EncryptedString
451− , notes :: JNullable EncryptedString
452− , fields :: JNullable (Array FieldApi)
453− , login :: JNullable LoginApi
454− , card :: JNullable CardApi
455− , identity :: JNullable IdentityApi
456− , secureNote :: JNullable SecureNoteApi
457− , favorite :: Boolean
458− , edit :: JNullable Boolean
459− , viewPassword :: JNullable Boolean
460− , organizationUseTotp :: JNullable Boolean
461− , revisionDate :: JNullable Timestamp
462− , attachments :: JNullable (Array AttachmentResponse)
463− , passwordHistory :: JNullable (Array PasswordHistoryResponse)
464− , collectionIds :: JNullable (Array String)
465− , deletedDate :: JNullable String
466− , reprompt :: CipherRepromptType
467− }
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+ }
468471
469472cipherTypeLogin = 1 :: CipherType
470473
@@ -474,15 +477,14 @@ cipherTypeCard = 3 :: CipherType
474477
475478cipherTypeIdentity = 4 :: CipherType
476479
477−type CipherType
478− = Int
480+type CipherType = Int
479481
480−type FieldApi
481− = { name :: JNullable String
482− , value :: JNullable String
483− , type :: FieldType
484− , linkedId :: JNullable LinkedIdType
485− }
482+type FieldApi =
483+ { name :: JNullable String
484+ , value :: JNullable String
485+ , type :: FieldType
486+ , linkedId :: JNullable LinkedIdType
487+ }
486488
487489fieldTypeText = 0 :: FieldType
488490
@@ -492,8 +494,7 @@ fieldTypeBoolean = 2 :: FieldType
492494
493495fieldTypeLinked = 3 :: FieldType
494496
495−type FieldType
496− = Int
497+type FieldType = Int
497498
498499linkedIdTypeLoginLinkedIdUsername = 100 :: LinkedIdType
499500
@@ -549,23 +550,22 @@ linkedIdTypeIdentityLinkedIdLastName = 417 :: LinkedIdType
549550
550551linkedIdTypeIdentityLinkedIdFullName = 418 :: LinkedIdType
551552
552−type LinkedIdType
553− = Int
553+type LinkedIdType = Int
554554
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− }
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+ }
564564
565−type LoginUriApi
566− = { uri :: EncryptedString
567− , match :: JNullable UriMatchType
568− }
565+type LoginUriApi =
566+ { uri :: JNullable EncryptedString
567+ , match :: JNullable UriMatchType
568+ }
569569
570570uriMatchTypeDomain = 0 :: UriMatchType
571571
@@ -579,87 +579,85 @@ uriMatchTypeRegularExpression = 4 :: UriMatchType
579579
580580uriMatchTypeNever = 5 :: UriMatchType
581581
582−type UriMatchType
583− = Int
584−
585−type CardApi
586− = { cardholderName :: JNullable EncryptedString
587− , brand :: JNullable EncryptedString
588− , number :: JNullable EncryptedString
589− , expMonth :: JNullable EncryptedString
590− , expYear :: JNullable EncryptedString
591− , code :: JNullable EncryptedString
592− }
593−
594−type IdentityApi
595− = { title :: JNullable EncryptedString
596− , firstName :: JNullable EncryptedString
597− , middleName :: JNullable EncryptedString
598− , lastName :: JNullable EncryptedString
599− , address1 :: JNullable EncryptedString
600− , address2 :: JNullable EncryptedString
601− , address3 :: JNullable EncryptedString
602− , city :: JNullable EncryptedString
603− , state :: JNullable EncryptedString
604− , postalCode :: JNullable EncryptedString
605− , country :: JNullable EncryptedString
606− , company :: JNullable EncryptedString
607− , email :: JNullable EncryptedString
608− , phone :: JNullable EncryptedString
609− , ssn :: JNullable EncryptedString
610− , username :: JNullable EncryptedString
611− , passportNumber :: JNullable EncryptedString
612− , licenseNumber :: JNullable EncryptedString
613− }
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+ }
614613
615614secureNoteTypeGeneric = 0 :: SecureNoteType
616615
617−type SecureNoteType
618− = Int
616+type SecureNoteType = Int
619617
620−type SecureNoteApi = {
618+type SecureNoteApi =
619+ {
621620 -- type :: SecureNoteType
622−}
623−
624−type AttachmentResponse
625− = { id :: String
626− , url :: JNullable String
627− , fileName :: JNullable String
628− , key :: JNullable String
629− , size :: JNullable String
630− , sizeName :: JNullable String
631− }
632−
633−type PasswordHistoryResponse
634− = { password :: String
635− , lastUsedDate :: JNullable String
636− }
621+ }
622+
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+ }
631+
632+type PasswordHistoryResponse =
633+ { password :: String
634+ , lastUsedDate :: JNullable String
635+ }
637636
638637cipherRepromptTypeNone = 0 :: CipherRepromptType
639638
640639cipherRepromptTypePassword = 1 :: CipherRepromptType
641640
642−type CipherRepromptType
643− = Int
641+type CipherRepromptType = Int
644642
645−type DomainsResponse
646− = { equivalentDomains :: Array (Array String)
647− , globalEquivalentDomains :: Array GlobalDomainResponse
648− }
643+type DomainsResponse =
644+ { equivalentDomains :: Array (Array String)
645+ , globalEquivalentDomains :: Array GlobalDomainResponse
646+ }
649647
650−type GlobalDomainResponse
651− = { type :: Int
652− , domains :: Array String
653− , excluded :: Boolean
654− }
648+type GlobalDomainResponse =
649+ { type :: Int
650+ , domains :: Array String
651+ , excluded :: Boolean
652+ }
655653
656−type PolicyResponse
657− = { id :: String
658− , organizationId :: JNullable String
659− , type :: PolicyType
660− , data :: ShowableJson
661− , enabled :: Boolean
662− }
654+type PolicyResponse =
655+ { id :: String
656+ , organizationId :: JNullable String
657+ , type :: PolicyType
658+ , data :: ShowableJson
659+ , enabled :: Boolean
660+ }
663661
664662policyTypeTwoFactorAuthentication = 0 :: PolicyType -- Requires users to have 2fa enabled
665663
@@ -683,46 +681,44 @@ policyTypeMaximumVaultTimeout = 9 :: PolicyType -- Sets the maximum allowed vaul
683681
684682policyTypeDisablePersonalVaultExport = 10 :: PolicyType -- Disable personal vault export
685683
686−type PolicyType
687− = Int
688−
689−type SendResponse
690− = { id :: String
691− , accessId :: JNullable String
692− , type :: SendType
693− , name :: JNullable String
694− , notes :: JNullable String
695− , file :: JNullable SendFileApi
696− , text :: JNullable SendTextApi
697− , key :: JNullable String
698− , maxAccessCount :: JNullable Int
699− , accessCount :: JNullable Int
700− , revisionDate :: JNullable String
701− , expirationDate :: JNullable String
702− , deletionDate :: JNullable String
703− , password :: JNullable String
704− , disable :: Boolean
705− , hideEmail :: Boolean
706− }
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+ }
707704
708705sendTypeText = 0 :: SendType
709706
710707sendTypeFile = 1 :: SendType
711708
712−type SendType
713− = Int
709+type SendType = Int
714710
715−type SendFileApi
716− = { id :: String
717− , fileName :: JNullable String
718− , size :: JNullable String
719− , sizeName :: JNullable String
720− }
711+type SendFileApi =
712+ { id :: String
713+ , fileName :: JNullable String
714+ , size :: JNullable String
715+ , sizeName :: JNullable String
716+ }
721717
722−type SendTextApi
723− = { text :: JNullable String
724− , hidden :: Boolean
725− }
718+type SendTextApi =
719+ { text :: JNullable String
720+ , hidden :: Boolean
721+ }
726722
727723type TwoFactorEmailRequest =
728724 { email :: Email
backend/Data/EncString.js+1−1
@@ -1,4 +1,4 @@
1−import { EncString } from "../../deps/bw/libs/shared/dist/src/models/domain/encString"
1+import { EncString } from "../../bw/models/domain/enc-string"
22
33export function fromString(str) {
44 const x = new EncString(str)
backend/Data/SymmetricCryptoKey.js+1−2
@@ -1,6 +1,5 @@
1−import { SymmetricCryptoKey } from '../../deps/bw/libs/shared/dist/src/models/domain/symmetricCryptoKey.js';
1+import { SymmetricCryptoKey } from '../../bw/models/domain/symmetric-crypto-key.js'
22
33export function fromArrayBuffer(arr) {
4− console.log("fromArrayBuffer " + arr)
54 return new SymmetricCryptoKey(arr)
65}
backend/Main.purs+145−138
@@ -3,19 +3,20 @@ module Main
33 ) where
44
55import Prelude
6+
67import BW (ApiService, CryptoFunctions, Services, CryptoService)
78import BW as WB
8−import BW.Logic (bwPasswordStringHash, decodeCipher, decrypt, encodeCipher, hashPassword, liftPromise)
9+import BW.Logic (bwPasswordStringHash, decodeCipher, decrypt, encodeCipher, hashPassword, liftPromise, supportedCipher)
910import BW.Logic as Logic
10−import BW.Types (CipherResponse, Email(..), Password(..), TwoFactorProviderTypes(..), Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote, secondFactorTypeToBridge)
11+import BW.Types (CipherResponse, Email(..), Password(..), Urls, cipherTypeCard, cipherTypeIdentity, cipherTypeLogin, cipherTypeSecureNote, fromTwoFactorProviderType)
1112import Bridge as Bridge
12−import Control.Monad.Error.Class (catchError, throwError)
13−import Control.Monad.Error.Class as Error
13+import Control.Monad.Error.Class (catchError)
1414import Data.Argonaut (class DecodeJson)
1515import Data.Array as Array
1616import Data.Clipboard as Clipboard
1717import Data.DateTime (DateTime)
1818import Data.Either (Either(..))
19+import Data.Foldable (for_)
1920import Data.HCaptcha (bindHCaptchToken)
2021import Data.JNullable (jnull, nullify)
2122import Data.JNullable as JNullable
@@ -29,7 +30,7 @@ import Effect (Effect)
2930import Effect.Aff (Aff, runAff_, try)
3031import Effect.Class (liftEffect)
3132import Effect.Class.Console (log)
32−import Effect.Exception (error)
33+import Effect.Exception (throw)
3334import Effect.Exception as Exc
3435import Effect.Ref as Ref
3536import Elm as Elm
@@ -61,19 +62,19 @@ main = do
6162 let
6263 runAff act = runElmAff app $ run $ act
6364
64− run ::
65− forall a.
66− Run
67− ( app :: Reader Elm
68− , aff :: Aff
69− , effect :: Effect
70− , crypto :: Reader CryptoService
71− , storage :: Reader Storage
72− , services :: Reader Services
73− , cryptoFunctions :: Reader CryptoFunctions
74− )
75− a ->
76− Run (EFFECT + AFF + ()) a
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
7778 run act =
7879 runReaderAt (Proxy :: _ "app") app
7980 $ runReaderAt (Proxy :: _ "crypto") services.crypto
@@ -82,19 +83,19 @@ main = do
8283 $ runReaderAt (Proxy :: _ "cryptoFunctions") services.cryptoFunctions
8384 $ act
8485
85− runWithDecryptionKey ::
86− Run
87− ( app :: Reader Elm
88− , aff :: Aff
89− , effect :: Effect
90− , key :: Reader SymmetricCryptoKey
91− , crypto :: Reader CryptoService
92− , storage :: Reader Storage
93− , services :: Reader Services
94− , cryptoFunctions :: Reader CryptoFunctions
95− )
96− Unit ->
97− Effect Unit
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
9899 runWithDecryptionKey act = do
99100 maybeKey <- Ref.read masterKeyRef
100101 runAff
@@ -131,7 +132,10 @@ main = do
131132 }
132133 pure $ Right resp
133134 )
134− (const $ pure $ Left unit)
135+ ( \err -> do
136+ log $ show err
137+ pure $ Left unit
138+ )
135139 case map (toEither1 >>> map toEither1) $ loginResponse of
136140 Left _ -> send Bridge.WrongPassword
137141 Right (Left { siteKey }) -> send $ Bridge.NeedsCaptcha siteKey
@@ -149,20 +153,15 @@ main = do
149153 Storage.store storage SyncKey sync
150154 Storage.store storage TokenKey token
151155 send Bridge.LoginSuccessful
152− Right (Right (Left { twoFactorProviders: TwoFactorProviderTypes rawProviders, captchaToken })) -> do
156+ Right (Right (Left { twoFactorProviders, captchaToken })) -> do
153157 case toEither1 captchaToken of
154158 Left captchaTokenString ->
155159 liftEffect
156160 $ Ref.write (Just captchaTokenString) hCaptchaTokenRef
157161 Right _ -> pure unit
158− providers <-
159− liftEffect
160− $ traverse
161− ( \x ->
162− secondFactorTypeToBridge x
163− # Error.liftMaybe (error $ "Unknown second factor provider: " <> show x)
164− )
165− rawProviders
162+ providers <- case fromTwoFactorProviderType twoFactorProviders of
163+ Left x -> liftEffect $ throw $ "Unknown second factor provider: " <> x
164+ Right x -> pure x
166165 send $ Bridge.NeedsSecondFactor $ Bridge.Sub_NeedsSecondFactor_List providers
167166 Bridge.NeedCiphersList ->
168167 runWithDecryptionKey do
@@ -187,9 +186,9 @@ main = do
187186 Bridge.Init -> do
188187 Storage.get storage TokenKey
189188 >>= \x ->
190− Elm.send app case x of
191− Just _ -> Bridge.LoginSuccessful
192− Nothing -> Bridge.NeedsLogin
189+ Elm.send app case x of
190+ Just _ -> Bridge.LoginSuccessful
191+ Nothing -> Bridge.NeedsLogin
193192 Bridge.RequestCipher id ->
194193 runWithDecryptionKey do
195194 sync <- getOrReset SyncKey
@@ -197,8 +196,7 @@ main = do
197196 Nothing -> pure unit
198197 Just cipherResponse -> do
199198 cipher <- decodeCipher cipherResponse
200− send $ Bridge.LoadCipher cipher
201− pure unit
199+ for_ cipher $ send <<< Bridge.LoadCipher
202200 pure unit
203201 Bridge.NeedEmail ->
204202 runWithDecryptionKey do
@@ -214,7 +212,7 @@ main = do
214212 api <- getAuthedApi
215213 cipher <- encodeCipher fullCipher
216214 newCipher <- liftPromise (api.putCipher cipher) >>= decodeCipher
217− send $ Bridge.CipherChanged newCipher
215+ for_ newCipher $ send <<< Bridge.CipherChanged
218216 performSync
219217 pure unit
220218 Bridge.GeneratePassword cfg ->
@@ -227,7 +225,7 @@ main = do
227225 api <- getAuthedApi
228226 cipher <- encodeCipher fullCipher
229227 newCipher <- liftPromise (api.postCipherCreate cipher) >>= decodeCipher
230− send $ Bridge.CipherChanged newCipher
228+ for_ newCipher $ send <<< Bridge.CipherChanged
231229 performSync
232230 pure unit
233231 Bridge.DeleteCipher c@(Bridge.FullCipher { id }) ->
@@ -245,14 +243,20 @@ main = do
245243 interval = totpService.getTimeInterval totp
246244 send $ Bridge.Totp
247245 $ Bridge.Sub_Totp
248− { interval, code, source: totp
246+ { interval
247+ , code
248+ , source: totp
249249 }
250250 pure unit
251251 Bridge.ChooseSecondFactor
252252 ( Bridge.Cmd_ChooseSecondFactor
253− { email: email', factor, server, password, requestFromServer
254− }
255− ) ->
253+ { email: email'
254+ , factor
255+ , server
256+ , password
257+ , requestFromServer
258+ }
259+ ) ->
256260 when requestFromServer
257261 let
258262 email = Email email'
@@ -269,17 +273,18 @@ main = do
269273 pure unit
270274 _ -> pure unit
271275
272−processCipher ::
273− forall r.
274− CipherResponse ->
275− Run
276− ( crypto :: Reader CryptoService
277− , key :: Reader SymmetricCryptoKey
278− , aff :: Aff
279− , effect :: Effect
280− | r
281− )
282− { cipher :: Bridge.Sub_LoadCiphers, date :: DateTime }
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
283288processCipher cipher = do
284289 name <- decrypt cipher.name
285290 cipherType <- case cipher.type of
@@ -291,10 +296,12 @@ processCipher cipher = do
291296 | n == cipherTypeCard -> pure Bridge.CardType
292297 n
293298 | n == cipherTypeIdentity -> pure Bridge.IdentityType
294− n -> liftEffect $ throwError $ error $ "Unsupported cipher type: " <> show n
299+ n -> liftEffect $ throw $ "Unsupported cipher type: " <> show n
295300 date <- liftEffect $ maybe (pure bottom) (Timestamp.toDateTime) $ JNullable.toMaybe cipher.revisionDate
296301 pure
297− $ { cipher:
302+ $ Just
303+ $
304+ { cipher:
298305 Bridge.Sub_LoadCiphers
299306 { name: name
300307 , date: maybe "" Timestamp.toLocalDateTimeString $ JNullable.toMaybe cipher.revisionDate
@@ -328,18 +335,18 @@ baseUrl server =
328335 , keyConnector: jnull
329336 }
330337
331−getOrReset ::
332− forall k t r.
333− StorageKey k t =>
334− DecodeJson t =>
335− k ->
336− Run
337− ( storage :: Reader Storage
338− , app :: Reader Elm
339− , effect :: Effect
340− | r
341− )
342− t
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
343350getOrReset key = do
344351 storage <- askAt (Proxy :: _ "storage")
345352 app <- askAt (Proxy :: _ "app")
@@ -350,69 +357,69 @@ getOrReset key = do
350357 Exc.throw "Encountered an internal error. Resetting the app."
351358 liftEffect $ try (Storage.get storage key)
352359 >>= \x -> case x of
353− Left err -> do
354− log $ show err
355− handleError
356− Right Nothing -> handleError
357− Right (Just y) -> pure y
360+ Left err -> do
361+ log $ show err
362+ handleError
363+ Right Nothing -> handleError
364+ Right (Just y) -> pure y
358365
359−getAuthedApi ::
360− forall r.
361− Run
362− ( storage :: Reader Storage
363− , app :: Reader Elm
364− , aff :: Aff
365− , effect :: Effect
366− , services :: Reader Services
367− | r
368− )
369− ApiService
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
370377getAuthedApi = do
371378 services <- askAt (Proxy :: _ "services")
372379 token <- getOrReset TokenKey
373380 url <- getOrReset UrlsKey
374381 liftPromise $ services.getApi (baseUrl url) (nullify token)
375382
376−requestMasterPassword ::
377− forall r.
378− Run
379− ( storage :: Reader Storage
380− , app :: Reader Elm
381− , effect :: Effect
382− | r
383− )
384− Unit
383+requestMasterPassword
384+ :: forall r
385+ . Run
386+ ( storage :: Reader Storage
387+ , app :: Reader Elm
388+ , effect :: Effect
389+ | r
390+ )
391+ Unit
385392requestMasterPassword = do
386393 url <- getOrReset UrlsKey
387394 sync <- getOrReset SyncKey
388395 send $ Bridge.NeedsMasterPassword $ Bridge.Sub_NeedsMasterPassword { server: url, login: sync.profile.email }
389396
390−send ::
391− forall r.
392− Bridge.Sub ->
393− Run
394− ( app :: Reader Elm
395− , effect :: Effect
396− | r
397− )
398− Unit
397+send
398+ :: forall r
399+ . Bridge.Sub
400+ -> Run
401+ ( app :: Reader Elm
402+ , effect :: Effect
403+ | r
404+ )
405+ Unit
399406send sub = do
400407 app <- askAt (Proxy :: _ "app")
401408 liftEffect $ Elm.send app sub
402409
403−performSync ::
404− forall r.
405− Run
406− ( storage :: Reader Storage
407− , app :: Reader Elm
408− , aff :: Aff
409− , effect :: Effect
410− , services :: Reader Services
411− , crypto :: Reader CryptoService
412− , key :: Reader SymmetricCryptoKey
413− | r
414− )
415− Unit
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
416423performSync = do
417424 api <- getAuthedApi
418425 storage <- askAt (Proxy :: _ "storage")
@@ -420,22 +427,22 @@ performSync = do
420427 liftEffect $ Storage.store storage SyncKey newSync
421428 sendCiphers
422429
423−sendCiphers ::
424− forall r.
425− Run
426− ( storage :: Reader Storage
427− , app :: Reader Elm
428− , aff :: Aff
429− , effect :: Effect
430− , services :: Reader Services
431− , crypto :: Reader CryptoService
432− , key :: Reader SymmetricCryptoKey
433− | r
434− )
435− Unit
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
436443sendCiphers = do
437444 sync <- getOrReset SyncKey
438− ciphers <- traverse processCipher sync.ciphers
445+ ciphers <- Array.catMaybes <$> traverse processCipher sync.ciphers
439446 let
440447 sortedCiphers = Array.sortWith (_.date >>> Down) ciphers
441448 send $ Bridge.LoadCiphers $ Bridge.Sub_LoadCiphers_List $ map _.cipher sortedCiphers
backend/Storage.purs+7−1
@@ -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,3 +35,9 @@ 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"
build.sh+2−14
@@ -1,17 +1,5 @@
1−#!/bin/bash
1+#!/bin/sh
22
33set -e
44
5−TEMP=$(mktemp -d)
6−
7−cp -r "$SRC_DIR"/{appname.apparmor,appname.desktop,manifest.json,logo.png,index.html,style.css} "$INSTALL_DIR"
8−mkdir -p "$INSTALL_DIR"/vanilla
9−cp -r "$SRC_DIR"/vanilla/dist "$INSTALL_DIR"/vanilla
10−
11−elm make "$SRC_DIR/frontend/Main.elm" --optimize --output="$TEMP/elm.js"
12−
13−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"
14−
15−(cd "$SRC_DIR" && spago bundle-app --to "$TEMP/backend.js")
16−
17−terser "$TEMP/backend.js" --ecma 2018 --mangle --compress 'drop_console=true' --comments=false --toplevel --output "$INSTALL_DIR/backend.js"
5+cp -r "$SRC_DIR/dist"/* "$INSTALL_DIR"
clickable.yaml+2−10
@@ -1,16 +1,8 @@
11clickable_minimum_required: 7.1.2
2+framework: ubuntu-sdk-20.04
23builder: custom
34build: $SRC_DIR/build.sh
45kill: webapp-container*
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
6+docker_image: clickable/amd64-20.04-amd64
157
168restrict_arch: all
deps/bwdeleted−1

Submodule changed.

dev/build-backend.sh+7−1
@@ -1,3 +1,9 @@
11#!/bin/sh
22
3−spago bundle-app --to backend.js
3+set -e
4+
5+spago build
6+
7+export out="$(pwd)/backend.js"
8+
9+$(nix build .#backendBuildScript --print-out-paths) "$(pwd)/output"
dev/build-bw.sh+2
@@ -1,5 +1,7 @@
11#!/bin/sh
22
3+set -e
4+
35(cd deps/bw && npm install)
46
57(cd deps/bw/libs/common/ && npm run clean && npm exec -c "tsc -m es2020 -t es2018")
dev/build.shnew file+10
@@ -0,0 +1,10 @@
1+#!/bin/sh
2+
3+
4+rm -r dist
5+
6+mkdir -p dist
7+
8+cp -r "$(nix build ".#warden-click-src" --print-out-paths)"/* dist
9+
10+chmod -R 777 dist
dev/elm-live.sh+5−1
@@ -1,3 +1,7 @@
11#!/bin/sh
22
3−nix run github:NixOS/nixpkgs?rev=d9a1414346059619d9e13ab93e749bbb82e5252a#elmPackages.elm-live -- $1 --start-page=index.html --proxy-prefix=/api --proxy-host=http://localhost:6009 --pushstate -- --debug --output=elm.js
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
dev/elm2nix.shnew file+8
@@ -0,0 +1,8 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd "$(dirname "$0")/.."
6+
7+nix run github:NixOS/nixpkgs#elm2nix -- snapshot
8+nix run github:NixOS/nixpkgs#elm2nix -- convert > elm-srcs.nix
dev/record-test.shnew file+11
@@ -0,0 +1,11 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd tests
6+
7+npm install
8+
9+export RECORD_TEST=true
10+
11+npx playwright test
dev/spago2nix.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+nix run "github:justinwoo/spago2nix#spago2nix" -- generate
dev/start-chrome.sh+3−1
@@ -1,6 +1,8 @@
11#!/bin/sh
22
3−if test -f /Applications/Chromium.app/Contents/MacOS/Chromium; then
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
46 /Applications/Chromium.app/Contents/MacOS/Chromium --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security
57else
68 flatpak run com.github.Eloston.UngoogledChromium --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security
dev/test.shnew file+9
@@ -0,0 +1,9 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd tests
6+
7+npm install
8+
9+npx playwright test
dev/update-tests.shnew file+9
@@ -0,0 +1,9 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd tests
6+
7+npm install
8+
9+npx playwright test --update-snapshots
elm-srcs.nixnew file+62
@@ -0,0 +1,62 @@
1+{
2+
3+ "FabienHenon/elm-infinite-scroll" = {
4+ sha256 = "0vhh7j3lna05l5hwjazag52lbkjv11s80jj62m9wb53qxkarw948";
5+ version = "3.0.3";
6+ };
7+
8+ "bartavelle/json-helpers" = {
9+ sha256 = "0k96qra2nq1j4j4ahfl98dkpkc6f2831mq5d5xxg27mp31qwq5nn";
10+ version = "2.0.2";
11+ };
12+
13+ "elm/browser" = {
14+ sha256 = "0nagb9ajacxbbg985r4k9h0jadqpp0gp84nm94kcgbr5sf8i9x13";
15+ version = "1.0.2";
16+ };
17+
18+ "elm/core" = {
19+ sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf";
20+ version = "1.0.5";
21+ };
22+
23+ "elm/html" = {
24+ sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k";
25+ version = "1.0.0";
26+ };
27+
28+ "elm/json" = {
29+ sha256 = "0kjwrz195z84kwywaxhhlnpl3p251qlbm5iz6byd6jky2crmyqyh";
30+ version = "1.1.3";
31+ };
32+
33+ "elm/time" = {
34+ sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1";
35+ version = "1.0.0";
36+ };
37+
38+ "mdgriffith/elm-ui" = {
39+ sha256 = "0ffcqv4a4ad400hwp824m3qq4jy82cqp5ghmhp1m0q7n004z6kgv";
40+ version = "1.1.8";
41+ };
42+
43+ "mgold/elm-nonempty-list" = {
44+ sha256 = "02d3xgasgy5knh02rgl403z9hwlbyrsnp9wavc2lhkxxa2misrgl";
45+ version = "4.2.0";
46+ };
47+
48+ "elm/random" = {
49+ sha256 = "138n2455wdjwa657w6sjq18wx2r0k60ibpc4frhbqr50sncxrfdl";
50+ version = "1.0.0";
51+ };
52+
53+ "elm/url" = {
54+ sha256 = "0av8x5syid40sgpl5vd7pry2rq0q4pga28b4yykn9gd9v12rs3l4";
55+ version = "1.0.0";
56+ };
57+
58+ "elm/virtual-dom" = {
59+ sha256 = "1yvb8px2z62xd578ag2q0r5hd1vkz9y7dfkx05355iiy1d7jwq4v";
60+ version = "1.0.3";
61+ };
62+}
flake.locknew file+177
@@ -0,0 +1,177 @@
1+{
2+ "nodes": {
3+ "bw": {
4+ "flake": false,
5+ "locked": {
6+ "lastModified": 1675446731,
7+ "narHash": "sha256-IbFjYNYZep6W9c+dztJ9CXrwQ0cep3EKb6qnEvv4pj4=",
8+ "owner": "bitwarden",
9+ "repo": "clients",
10+ "rev": "ff143760d4f527f2349333ee4b3004eeda68fac5",
11+ "type": "github"
12+ },
13+ "original": {
14+ "owner": "bitwarden",
15+ "repo": "clients",
16+ "rev": "ff143760d4f527f2349333ee4b3004eeda68fac5",
17+ "type": "github"
18+ }
19+ },
20+ "easy-ps": {
21+ "flake": false,
22+ "locked": {
23+ "lastModified": 1671011575,
24+ "narHash": "sha256-tESal32bcqqdZO+aKnBzc1GoL2mtnaDtj2y7ociCRGA=",
25+ "owner": "justinwoo",
26+ "repo": "easy-purescript-nix",
27+ "rev": "11d3bd58ce6e32703bf69cec04dc7c38eabe14ba",
28+ "type": "github"
29+ },
30+ "original": {
31+ "owner": "justinwoo",
32+ "repo": "easy-purescript-nix",
33+ "type": "github"
34+ }
35+ },
36+ "easy-purescript-nix": {
37+ "flake": false,
38+ "locked": {
39+ "lastModified": 1670198178,
40+ "narHash": "sha256-5KkyNpPakv4xIP2ba0S5GX+dcmd3AcO9kPhwa482BbA=",
41+ "owner": "justinwoo",
42+ "repo": "easy-purescript-nix",
43+ "rev": "7a4cb3cd6ca53566ea1675692eab0aa13907ff09",
44+ "type": "github"
45+ },
46+ "original": {
47+ "owner": "justinwoo",
48+ "repo": "easy-purescript-nix",
49+ "type": "github"
50+ }
51+ },
52+ "flake-utils": {
53+ "locked": {
54+ "lastModified": 1676283394,
55+ "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
56+ "owner": "numtide",
57+ "repo": "flake-utils",
58+ "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
59+ "type": "github"
60+ },
61+ "original": {
62+ "owner": "numtide",
63+ "repo": "flake-utils",
64+ "type": "github"
65+ }
66+ },
67+ "flake-utils_2": {
68+ "locked": {
69+ "lastModified": 1667395993,
70+ "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
71+ "owner": "numtide",
72+ "repo": "flake-utils",
73+ "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
74+ "type": "github"
75+ },
76+ "original": {
77+ "owner": "numtide",
78+ "repo": "flake-utils",
79+ "type": "github"
80+ }
81+ },
82+ "nix-filter": {
83+ "locked": {
84+ "lastModified": 1676294984,
85+ "narHash": "sha256-hdLUa/3RH1VJ+gMUysQE0JGM4F2Q/tIIFbtoxAOurJQ=",
86+ "owner": "numtide",
87+ "repo": "nix-filter",
88+ "rev": "fc282c5478e4141842f9644c239a41cfe9586732",
89+ "type": "github"
90+ },
91+ "original": {
92+ "owner": "numtide",
93+ "repo": "nix-filter",
94+ "type": "github"
95+ }
96+ },
97+ "nixpkgs": {
98+ "locked": {
99+ "lastModified": 1677080879,
100+ "narHash": "sha256-0SjW4/d3Rkw6C7hHZ5lxT4r6Pw9vzQb6Il6zYWwe2Bo=",
101+ "owner": "NixOS",
102+ "repo": "nixpkgs",
103+ "rev": "f5dad40450d272a1ea2413f4a67ac08760649e89",
104+ "type": "github"
105+ },
106+ "original": {
107+ "id": "nixpkgs",
108+ "type": "indirect"
109+ }
110+ },
111+ "nixpkgs_2": {
112+ "locked": {
113+ "lastModified": 1670507980,
114+ "narHash": "sha256-riNZa0xzM1it3pzxciwALeMs+0CsBMWIW2FqulzK8vM=",
115+ "owner": "NixOS",
116+ "repo": "nixpkgs",
117+ "rev": "2787fc7d1e51404678614bf0fe92fc296746eec0",
118+ "type": "github"
119+ },
120+ "original": {
121+ "owner": "NixOS",
122+ "ref": "nixos-unstable",
123+ "repo": "nixpkgs",
124+ "type": "github"
125+ }
126+ },
127+ "root": {
128+ "inputs": {
129+ "bw": "bw",
130+ "easy-ps": "easy-ps",
131+ "flake-utils": "flake-utils",
132+ "nix-filter": "nix-filter",
133+ "nixpkgs": "nixpkgs",
134+ "spago2nix": "spago2nix",
135+ "vanilla": "vanilla"
136+ }
137+ },
138+ "spago2nix": {
139+ "inputs": {
140+ "easy-purescript-nix": "easy-purescript-nix",
141+ "flake-utils": "flake-utils_2",
142+ "nixpkgs": "nixpkgs_2"
143+ },
144+ "locked": {
145+ "lastModified": 1671225584,
146+ "narHash": "sha256-h9r67pmvDuA3TV9299L4CN60XSm8RRtX1EwUoKu9Pyw=",
147+ "owner": "justinwoo",
148+ "repo": "spago2nix",
149+ "rev": "1b8ec352bc7eac077b934d6b9f6efa0129926e59",
150+ "type": "github"
151+ },
152+ "original": {
153+ "owner": "justinwoo",
154+ "repo": "spago2nix",
155+ "type": "github"
156+ }
157+ },
158+ "vanilla": {
159+ "flake": false,
160+ "locked": {
161+ "lastModified": 1663430347,
162+ "narHash": "sha256-J+KyCcCMdC8SYINKAtUsNfM0/lnbwOnsY8TKOP+yY0s=",
163+ "owner": "ilyakooo0",
164+ "repo": "vanilla-compiled",
165+ "rev": "7a0ee46ce9b958b51c57b37c46e86a0a1868bfa9",
166+ "type": "github"
167+ },
168+ "original": {
169+ "owner": "ilyakooo0",
170+ "repo": "vanilla-compiled",
171+ "type": "github"
172+ }
173+ }
174+ },
175+ "root": "root",
176+ "version": 7
177+}
flake.nixnew file+133
@@ -0,0 +1,133 @@
1+{
2+ inputs = {
3+ flake-utils.url = "github:numtide/flake-utils";
4+ bw = {
5+ url =
6+ # Afer this commit they started requiring path at the top level and I can't deal with this right now
7+ "github:bitwarden/clients?rev=ff143760d4f527f2349333ee4b3004eeda68fac5";
8+ flake = false;
9+ };
10+ spago2nix.url = "github:justinwoo/spago2nix";
11+ easy-ps = {
12+ url = "github:justinwoo/easy-purescript-nix";
13+ flake = false;
14+ };
15+ vanilla = {
16+ url = "github:ilyakooo0/vanilla-compiled";
17+ flake = false;
18+ };
19+ nix-filter.url = "github:numtide/nix-filter";
20+ };
21+
22+ outputs = inputs@{ self, nixpkgs, flake-utils, spago2nix, ... }:
23+ flake-utils.lib.eachDefaultSystem (system:
24+ let
25+ pkgs = import nixpkgs { inherit system; };
26+ spagoPkgs = pkgs.callPackage ./spago-packages.nix { };
27+ easy-ps = pkgs.callPackage inputs.easy-ps { };
28+ nix-filter = import inputs.nix-filter;
29+
30+ bw = pkgs.buildNpmPackage {
31+ pname = "bw";
32+ version = "latest";
33+ npmDepsHash = "sha256-jQvL7/SzXqVk03ncU6Mxq8lqFFy+lIrF2QheheXrQ0w=";
34+ makeCacheWritable = true;
35+ src = inputs.bw;
36+ nativeBuildInputs = [ pkgs.python3 ];
37+ buildPhase = ''
38+ (cd libs/common/ && rm -r spec && find . -name "*.spec.ts" -exec rm {} \; && npm exec -c "tsc -m es2020 -t es2018") '';
39+ installPhase = ''
40+ mkdir -p $out
41+ cp -r libs/shared/dist/* $out
42+ cp -r node_modules $out/node_modules
43+ cp package.json $out/package.json
44+ '';
45+ ELECTRON_SKIP_BINARY_DOWNLOAD = 1;
46+ };
47+
48+ backendBuildScript = pkgs.writeScript "build-backend.sh" ''
49+ workdir=$(mktemp -d)
50+ (
51+ cd $workdir
52+ cp -r $1 ./backend
53+ cp -r ${bw} ./bw
54+ cp ${./backend-main.js} backend.js
55+ ${pkgs.esbuild}/bin/esbuild backend.js --bundle --define:ENVIRONMENT_IS_WEB=true --define:ENVIRONMENT_IS_WORKER=false --define:ENVIRONMENT_IS_NODE=false --define:ENVIRONMENT_IS_SHELL=false --external:fs --external:path --loader:.wasm=base64 --outfile=$out
56+ )
57+
58+ '';
59+ backend-js = pkgs.stdenv.mkDerivation {
60+ name = "backend-js";
61+ buildInputs =
62+ [ spagoPkgs.installSpagoStyle spagoPkgs.buildSpagoStyle ];
63+ nativeBuildInputs = [ easy-ps.purs-0_15_7 easy-ps.spago ];
64+ src = nix-filter {
65+ root = ./.;
66+ include = [
67+ ./spago.dhall
68+ ./packages.dhall
69+ (nix-filter.inDirectory ./backend)
70+ ];
71+ };
72+ unpackPhase = ''
73+ cp $src/spago.dhall .
74+ cp $src/packages.dhall .
75+ cp -r $src/backend .
76+ install-spago-style
77+ '';
78+ buildPhase = ''
79+ build-spago-style "./backend/**/*.purs"
80+ '';
81+ installPhase = ''
82+ mkdir $out
83+ cp -r output/* $out/
84+ '';
85+ };
86+
87+ backend =
88+ pkgs.runCommand "backend.js" { buildInputs = [ pkgs.esbuild ]; } ''
89+ ${backendBuildScript} ${backend-js}
90+ '';
91+
92+ frontend = pkgs.callPackage ./frontend.nix { inherit nix-filter; };
93+
94+ warden-src = pkgs.runCommand "warden-src" { } ''
95+ mkdir -p $out
96+
97+ cp ${backend} $out/backend.js
98+ cp -r ${frontend}/Main.min.js $out/elm.js
99+ cp ${./index.html} $out/index.html
100+ cp ${./style.css} $out/style.css
101+ cp -r ${inputs.vanilla} $out/vanilla
102+ '';
103+
104+ warden-click-src = pkgs.runCommand "warden-click-src" { } ''
105+ mkdir -p $out
106+
107+ cp ${./warden.apparmor} $out/warden.apparmor
108+ cp ${./warden.desktop} $out/warden.desktop
109+ cp ${./manifest.json} $out/manifest.json
110+ cp ${./logo.png} $out/logo.png
111+ cp -r ${warden-src}/* $out/
112+ '';
113+
114+ warden = pkgs.writeScript "warden" ''
115+ ${pkgs.electron}/bin/electron ${warden-src}/index.html
116+ '';
117+
118+ in {
119+ packages = {
120+ inherit bw frontend warden-src backendBuildScript warden-click-src;
121+ vanilla = inputs.vanilla.outPath;
122+ };
123+ apps = {
124+ inherit (spago2nix.apps.${system}) spago2nix;
125+ warden = {
126+ type = "app";
127+ program = warden.outPath;
128+ };
129+ };
130+
131+ devShells.default = import ./shell.nix { inherit pkgs; };
132+ });
133+}
frontend.nixnew file+49
@@ -0,0 +1,49 @@
1+{ pkgs, nix-filter }:
2+with pkgs;
3+
4+let
5+ mkDerivation = { srcs ? ./elm-srcs.nix, src, name, srcdir ? "./frontend"
6+ , targets ? [ ], registryDat ? ./registry.dat, outputJavaScript ? true }:
7+ stdenv.mkDerivation {
8+ inherit name src;
9+
10+ buildInputs = [ elmPackages.elm ]
11+ ++ lib.optional outputJavaScript nodePackages.uglify-js;
12+
13+ buildPhase = pkgs.elmPackages.fetchElmDeps {
14+ elmPackages = import srcs;
15+ elmVersion = "0.19.1";
16+ inherit registryDat;
17+ };
18+
19+ installPhase = let
20+ elmfile = module:
21+ "${srcdir}/${builtins.replaceStrings [ "." ] [ "/" ] module}.elm";
22+ extension = if outputJavaScript then "js" else "html";
23+ in ''
24+ mkdir -p $out/share/doc
25+ ${lib.concatStrings (map (module: ''
26+ echo "compiling ${elmfile module}"
27+ elm make ${
28+ elmfile module
29+ } --output $out/${module}.${extension} --docs $out/share/doc/${module}.json
30+ ${lib.optionalString outputJavaScript ''
31+ echo "minifying ${elmfile module}"
32+ uglifyjs $out/${module}.${extension} --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \
33+ | uglifyjs --mangle --output $out/${module}.min.${extension}
34+ ''}
35+ '') targets)}
36+ '';
37+ };
38+in mkDerivation {
39+ name = "frontend";
40+ srcs = ./elm-srcs.nix;
41+ src = nix-filter {
42+ root = ./.;
43+ include = [ (nix-filter.inDirectory ./frontend) ./elm.json ];
44+ };
45+ targets = [ "Main" ];
46+ srcdir = "./frontend";
47+ outputJavaScript = true;
48+}
49+
frontend/Main.elm+4−7
@@ -432,9 +432,6 @@ update msg model =
432432 _ ->
433433 False
434434 )
435−
436− pageModel =
437− Ciphers.init ciphersCallbacks ciphers
438435 in
439436 ( if ciphersShown then
440437 { model
@@ -443,8 +440,8 @@ update msg model =
443440 |> Nonempty.map
444441 (\x ->
445442 case x of
446− CiphersModel _ ->
447− CiphersModel pageModel
443+ CiphersModel pageModel ->
444+ CiphersModel (Ciphers.updateCiphers ciphers pageModel)
448445
449446 other ->
450447 other
@@ -452,7 +449,7 @@ update msg model =
452449 }
453450
454451 else
455− appendPageStack <| CiphersModel pageModel
452+ appendPageStack <| CiphersModel (Ciphers.init ciphersCallbacks ciphers)
456453 , Cmd.none
457454 )
458455
@@ -499,7 +496,7 @@ update msg model =
499496 ( model, FFI.sendBridge (Bridge.Open uri) )
500497
501498 ShowCaptcha uri ->
502− ( Captcha.init uri |> CaptchaModel |> keepStackWith, Cmd.none )
499+ ( Captcha.init uri |> CaptchaModel |> appendPageStack, Cmd.none )
503500
504501 ClearNotification { currentTime } ->
505502 if Time.posixToMillis model.lastNotificationTime + notificationLingerSeconds * 1000 > Time.posixToMillis currentTime then
frontend/Page.elm+5
@@ -25,3 +25,8 @@ page { init, view, update } =
2525 Just msg ->
2626 update msg model
2727 }
28+
29+
30+noUpdate : a -> model -> ( model, Cmd msg )
31+noUpdate _ model =
32+ ( model, Cmd.none )
frontend/Pages/Captcha.elm+16−3
@@ -1,4 +1,4 @@
1−module Pages.Captcha exposing (HCaptchSiteKey, Model, init, title, view)
1+module Pages.Captcha exposing (HCaptchSiteKey, Model, init, main, title, view)
22
33import Html exposing (..)
44import Html.Attributes as Attr
@@ -6,6 +6,15 @@ 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+
918type alias HCaptchSiteKey =
1019 String
1120
@@ -27,6 +36,10 @@ init siteKey =
2736
2837view : Model -> List (Html msg)
2938view { siteKey } =
30− [ div [ Attr.class "u-embedded-media" ]
31− [ iframe [ Attr.src ("https://iko.soy/warden/hcaptcha.html?" ++ 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+ []
3245 ]
frontend/Pages/Ciphers.elm+19−3
@@ -1,4 +1,15 @@
1−module Pages.Ciphers exposing (Callbacks, Model, Msg, event, init, menuConfig, title, update, view)
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+ )
213
314import Bridge
415import GlobalEvents
@@ -26,6 +37,11 @@ type alias Model emsg =
2637 }
2738
2839
40+updateCiphers : Bridge.Sub_LoadCiphers_List -> Model emsg -> Model emsg
41+updateCiphers ciphers model =
42+ { model | ciphers = ciphers }
43+
44+
2945type CiphersFilter
3046 = AllCiphers
3147 | SpecificCiphers Bridge.CipherType
@@ -132,7 +148,7 @@ title { ciphersListFilter, cipherSelectDropdownVisivble } =
132148 "Cards"
133149
134150 Bridge.IdentityType ->
135− "Identities"
151+ "Contacts"
136152
137153 Bridge.LoginType ->
138154 "Passwords"
@@ -274,7 +290,7 @@ showCiphers ciphers =
274290 , li [ Attr.class "p-list__item cipher-row", Ev.onClick (SelectCipher id) ]
275291 [ div [ Attr.class "cipher-row-container" ]
276292 [ p [] [ text name ]
277− , p [ Attr.class "p-text--small u-align-text--right u-text--muted" ] [ text date ]
293+ , p [ Attr.class "p-text--small u-align-text--right u-text--muted hide-in-test" ] [ text date ]
278294 ]
279295 ]
280296 )
frontend/Pages/SecondFactorSelect.elm+2−1
@@ -121,7 +121,8 @@ view { availableFactors } =
121121 ]
122122 ]
123123 |> El.html
124− |> El.el
124+ |> List.singleton
125+ |> El.paragraph
125126 [ Font.size 16
126127 , El.width El.fill
127128 ]
index.html+1−1
@@ -7,7 +7,7 @@
77 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
88 <link rel="stylesheet" href="vanilla/dist/style.css">
99 <link rel="stylesheet" href="style.css">
10− <title>Test.</title>
10+ <title>Warden</title>
1111 <script src="elm.js"></script>
1212</head>
1313
manifest.json+5−5
@@ -4,12 +4,12 @@
44 "architecture": "@CLICK_ARCH@",
55 "title": "Warden",
66 "hooks": {
7− "appname": {
8− "apparmor": "appname.apparmor",
9− "desktop": "appname.desktop"
7+ "warden": {
8+ "apparmor": "warden.apparmor",
9+ "desktop": "warden.desktop"
1010 }
1111 },
12− "version": "1.1.0",
13− "maintainer": "Ilia Kostiuchenko <email@iko.soy>",
12+ "version": "1.2.4",
13+ "maintainer": "Ilia Kostiuchenko <mail@iko.soy>",
1414 "framework": "@CLICK_FRAMEWORK@"
1515}
package-lock.jsonnew file+959
@@ -0,0 +1,959 @@
1+{
2+ "name": "warden",
3+ "lockfileVersion": 3,
4+ "requires": true,
5+ "packages": {
6+ "": {
7+ "devDependencies": {
8+ "cssnano": "^5.1.15"
9+ }
10+ },
11+ "node_modules/@trysound/sax": {
12+ "version": "0.2.0",
13+ "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
14+ "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
15+ "dev": true,
16+ "engines": {
17+ "node": ">=10.13.0"
18+ }
19+ },
20+ "node_modules/boolbase": {
21+ "version": "1.0.0",
22+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
23+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
24+ "dev": true
25+ },
26+ "node_modules/browserslist": {
27+ "version": "4.21.5",
28+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
29+ "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
30+ "dev": true,
31+ "funding": [
32+ {
33+ "type": "opencollective",
34+ "url": "https://opencollective.com/browserslist"
35+ },
36+ {
37+ "type": "tidelift",
38+ "url": "https://tidelift.com/funding/github/npm/browserslist"
39+ }
40+ ],
41+ "dependencies": {
42+ "caniuse-lite": "^1.0.30001449",
43+ "electron-to-chromium": "^1.4.284",
44+ "node-releases": "^2.0.8",
45+ "update-browserslist-db": "^1.0.10"
46+ },
47+ "bin": {
48+ "browserslist": "cli.js"
49+ },
50+ "engines": {
51+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
52+ }
53+ },
54+ "node_modules/caniuse-api": {
55+ "version": "3.0.0",
56+ "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
57+ "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==",
58+ "dev": true,
59+ "dependencies": {
60+ "browserslist": "^4.0.0",
61+ "caniuse-lite": "^1.0.0",
62+ "lodash.memoize": "^4.1.2",
63+ "lodash.uniq": "^4.5.0"
64+ }
65+ },
66+ "node_modules/caniuse-lite": {
67+ "version": "1.0.30001457",
68+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz",
69+ "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==",
70+ "dev": true,
71+ "funding": [
72+ {
73+ "type": "opencollective",
74+ "url": "https://opencollective.com/browserslist"
75+ },
76+ {
77+ "type": "tidelift",
78+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
79+ }
80+ ]
81+ },
82+ "node_modules/colord": {
83+ "version": "2.9.3",
84+ "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
85+ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
86+ "dev": true
87+ },
88+ "node_modules/commander": {
89+ "version": "7.2.0",
90+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
91+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
92+ "dev": true,
93+ "engines": {
94+ "node": ">= 10"
95+ }
96+ },
97+ "node_modules/css-declaration-sorter": {
98+ "version": "6.3.1",
99+ "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz",
100+ "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==",
101+ "dev": true,
102+ "engines": {
103+ "node": "^10 || ^12 || >=14"
104+ },
105+ "peerDependencies": {
106+ "postcss": "^8.0.9"
107+ }
108+ },
109+ "node_modules/css-select": {
110+ "version": "4.3.0",
111+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
112+ "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
113+ "dev": true,
114+ "dependencies": {
115+ "boolbase": "^1.0.0",
116+ "css-what": "^6.0.1",
117+ "domhandler": "^4.3.1",
118+ "domutils": "^2.8.0",
119+ "nth-check": "^2.0.1"
120+ },
121+ "funding": {
122+ "url": "https://github.com/sponsors/fb55"
123+ }
124+ },
125+ "node_modules/css-tree": {
126+ "version": "1.1.3",
127+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
128+ "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
129+ "dev": true,
130+ "dependencies": {
131+ "mdn-data": "2.0.14",
132+ "source-map": "^0.6.1"
133+ },
134+ "engines": {
135+ "node": ">=8.0.0"
136+ }
137+ },
138+ "node_modules/css-what": {
139+ "version": "6.1.0",
140+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
141+ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
142+ "dev": true,
143+ "engines": {
144+ "node": ">= 6"
145+ },
146+ "funding": {
147+ "url": "https://github.com/sponsors/fb55"
148+ }
149+ },
150+ "node_modules/cssesc": {
151+ "version": "3.0.0",
152+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
153+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
154+ "dev": true,
155+ "bin": {
156+ "cssesc": "bin/cssesc"
157+ },
158+ "engines": {
159+ "node": ">=4"
160+ }
161+ },
162+ "node_modules/cssnano": {
163+ "version": "5.1.15",
164+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz",
165+ "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==",
166+ "dev": true,
167+ "dependencies": {
168+ "cssnano-preset-default": "^5.2.14",
169+ "lilconfig": "^2.0.3",
170+ "yaml": "^1.10.2"
171+ },
172+ "engines": {
173+ "node": "^10 || ^12 || >=14.0"
174+ },
175+ "funding": {
176+ "type": "opencollective",
177+ "url": "https://opencollective.com/cssnano"
178+ },
179+ "peerDependencies": {
180+ "postcss": "^8.2.15"
181+ }
182+ },
183+ "node_modules/cssnano-preset-default": {
184+ "version": "5.2.14",
185+ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz",
186+ "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==",
187+ "dev": true,
188+ "dependencies": {
189+ "css-declaration-sorter": "^6.3.1",
190+ "cssnano-utils": "^3.1.0",
191+ "postcss-calc": "^8.2.3",
192+ "postcss-colormin": "^5.3.1",
193+ "postcss-convert-values": "^5.1.3",
194+ "postcss-discard-comments": "^5.1.2",
195+ "postcss-discard-duplicates": "^5.1.0",
196+ "postcss-discard-empty": "^5.1.1",
197+ "postcss-discard-overridden": "^5.1.0",
198+ "postcss-merge-longhand": "^5.1.7",
199+ "postcss-merge-rules": "^5.1.4",
200+ "postcss-minify-font-values": "^5.1.0",
201+ "postcss-minify-gradients": "^5.1.1",
202+ "postcss-minify-params": "^5.1.4",
203+ "postcss-minify-selectors": "^5.2.1",
204+ "postcss-normalize-charset": "^5.1.0",
205+ "postcss-normalize-display-values": "^5.1.0",
206+ "postcss-normalize-positions": "^5.1.1",
207+ "postcss-normalize-repeat-style": "^5.1.1",
208+ "postcss-normalize-string": "^5.1.0",
209+ "postcss-normalize-timing-functions": "^5.1.0",
210+ "postcss-normalize-unicode": "^5.1.1",
211+ "postcss-normalize-url": "^5.1.0",
212+ "postcss-normalize-whitespace": "^5.1.1",
213+ "postcss-ordered-values": "^5.1.3",
214+ "postcss-reduce-initial": "^5.1.2",
215+ "postcss-reduce-transforms": "^5.1.0",
216+ "postcss-svgo": "^5.1.0",
217+ "postcss-unique-selectors": "^5.1.1"
218+ },
219+ "engines": {
220+ "node": "^10 || ^12 || >=14.0"
221+ },
222+ "peerDependencies": {
223+ "postcss": "^8.2.15"
224+ }
225+ },
226+ "node_modules/cssnano-utils": {
227+ "version": "3.1.0",
228+ "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz",
229+ "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==",
230+ "dev": true,
231+ "engines": {
232+ "node": "^10 || ^12 || >=14.0"
233+ },
234+ "peerDependencies": {
235+ "postcss": "^8.2.15"
236+ }
237+ },
238+ "node_modules/csso": {
239+ "version": "4.2.0",
240+ "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz",
241+ "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==",
242+ "dev": true,
243+ "dependencies": {
244+ "css-tree": "^1.1.2"
245+ },
246+ "engines": {
247+ "node": ">=8.0.0"
248+ }
249+ },
250+ "node_modules/dom-serializer": {
251+ "version": "1.4.1",
252+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
253+ "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
254+ "dev": true,
255+ "dependencies": {
256+ "domelementtype": "^2.0.1",
257+ "domhandler": "^4.2.0",
258+ "entities": "^2.0.0"
259+ },
260+ "funding": {
261+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
262+ }
263+ },
264+ "node_modules/domelementtype": {
265+ "version": "2.3.0",
266+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
267+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
268+ "dev": true,
269+ "funding": [
270+ {
271+ "type": "github",
272+ "url": "https://github.com/sponsors/fb55"
273+ }
274+ ]
275+ },
276+ "node_modules/domhandler": {
277+ "version": "4.3.1",
278+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
279+ "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
280+ "dev": true,
281+ "dependencies": {
282+ "domelementtype": "^2.2.0"
283+ },
284+ "engines": {
285+ "node": ">= 4"
286+ },
287+ "funding": {
288+ "url": "https://github.com/fb55/domhandler?sponsor=1"
289+ }
290+ },
291+ "node_modules/domutils": {
292+ "version": "2.8.0",
293+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
294+ "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
295+ "dev": true,
296+ "dependencies": {
297+ "dom-serializer": "^1.0.1",
298+ "domelementtype": "^2.2.0",
299+ "domhandler": "^4.2.0"
300+ },
301+ "funding": {
302+ "url": "https://github.com/fb55/domutils?sponsor=1"
303+ }
304+ },
305+ "node_modules/electron-to-chromium": {
306+ "version": "1.4.302",
307+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.302.tgz",
308+ "integrity": "sha512-Uk7C+7aPBryUR1Fwvk9VmipBcN9fVsqBO57jV2ZjTm+IZ6BMNqu7EDVEg2HxCNufk6QcWlFsBkhQyQroB2VWKw==",
309+ "dev": true
310+ },
311+ "node_modules/entities": {
312+ "version": "2.2.0",
313+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
314+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
315+ "dev": true,
316+ "funding": {
317+ "url": "https://github.com/fb55/entities?sponsor=1"
318+ }
319+ },
320+ "node_modules/escalade": {
321+ "version": "3.1.1",
322+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
323+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
324+ "dev": true,
325+ "engines": {
326+ "node": ">=6"
327+ }
328+ },
329+ "node_modules/lilconfig": {
330+ "version": "2.0.6",
331+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz",
332+ "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
333+ "dev": true,
334+ "engines": {
335+ "node": ">=10"
336+ }
337+ },
338+ "node_modules/lodash.memoize": {
339+ "version": "4.1.2",
340+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
341+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
342+ "dev": true
343+ },
344+ "node_modules/lodash.uniq": {
345+ "version": "4.5.0",
346+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
347+ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
348+ "dev": true
349+ },
350+ "node_modules/mdn-data": {
351+ "version": "2.0.14",
352+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
353+ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
354+ "dev": true
355+ },
356+ "node_modules/nanoid": {
357+ "version": "3.3.4",
358+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
359+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
360+ "dev": true,
361+ "peer": true,
362+ "bin": {
363+ "nanoid": "bin/nanoid.cjs"
364+ },
365+ "engines": {
366+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
367+ }
368+ },
369+ "node_modules/node-releases": {
370+ "version": "2.0.10",
371+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
372+ "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
373+ "dev": true
374+ },
375+ "node_modules/normalize-url": {
376+ "version": "6.1.0",
377+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
378+ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
379+ "dev": true,
380+ "engines": {
381+ "node": ">=10"
382+ },
383+ "funding": {
384+ "url": "https://github.com/sponsors/sindresorhus"
385+ }
386+ },
387+ "node_modules/nth-check": {
388+ "version": "2.1.1",
389+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
390+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
391+ "dev": true,
392+ "dependencies": {
393+ "boolbase": "^1.0.0"
394+ },
395+ "funding": {
396+ "url": "https://github.com/fb55/nth-check?sponsor=1"
397+ }
398+ },
399+ "node_modules/picocolors": {
400+ "version": "1.0.0",
401+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
402+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
403+ "dev": true
404+ },
405+ "node_modules/postcss": {
406+ "version": "8.4.21",
407+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
408+ "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
409+ "dev": true,
410+ "funding": [
411+ {
412+ "type": "opencollective",
413+ "url": "https://opencollective.com/postcss/"
414+ },
415+ {
416+ "type": "tidelift",
417+ "url": "https://tidelift.com/funding/github/npm/postcss"
418+ }
419+ ],
420+ "peer": true,
421+ "dependencies": {
422+ "nanoid": "^3.3.4",
423+ "picocolors": "^1.0.0",
424+ "source-map-js": "^1.0.2"
425+ },
426+ "engines": {
427+ "node": "^10 || ^12 || >=14"
428+ }
429+ },
430+ "node_modules/postcss-calc": {
431+ "version": "8.2.4",
432+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz",
433+ "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==",
434+ "dev": true,
435+ "dependencies": {
436+ "postcss-selector-parser": "^6.0.9",
437+ "postcss-value-parser": "^4.2.0"
438+ },
439+ "peerDependencies": {
440+ "postcss": "^8.2.2"
441+ }
442+ },
443+ "node_modules/postcss-colormin": {
444+ "version": "5.3.1",
445+ "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz",
446+ "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==",
447+ "dev": true,
448+ "dependencies": {
449+ "browserslist": "^4.21.4",
450+ "caniuse-api": "^3.0.0",
451+ "colord": "^2.9.1",
452+ "postcss-value-parser": "^4.2.0"
453+ },
454+ "engines": {
455+ "node": "^10 || ^12 || >=14.0"
456+ },
457+ "peerDependencies": {
458+ "postcss": "^8.2.15"
459+ }
460+ },
461+ "node_modules/postcss-convert-values": {
462+ "version": "5.1.3",
463+ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz",
464+ "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==",
465+ "dev": true,
466+ "dependencies": {
467+ "browserslist": "^4.21.4",
468+ "postcss-value-parser": "^4.2.0"
469+ },
470+ "engines": {
471+ "node": "^10 || ^12 || >=14.0"
472+ },
473+ "peerDependencies": {
474+ "postcss": "^8.2.15"
475+ }
476+ },
477+ "node_modules/postcss-discard-comments": {
478+ "version": "5.1.2",
479+ "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz",
480+ "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==",
481+ "dev": true,
482+ "engines": {
483+ "node": "^10 || ^12 || >=14.0"
484+ },
485+ "peerDependencies": {
486+ "postcss": "^8.2.15"
487+ }
488+ },
489+ "node_modules/postcss-discard-duplicates": {
490+ "version": "5.1.0",
491+ "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz",
492+ "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==",
493+ "dev": true,
494+ "engines": {
495+ "node": "^10 || ^12 || >=14.0"
496+ },
497+ "peerDependencies": {
498+ "postcss": "^8.2.15"
499+ }
500+ },
501+ "node_modules/postcss-discard-empty": {
502+ "version": "5.1.1",
503+ "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz",
504+ "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==",
505+ "dev": true,
506+ "engines": {
507+ "node": "^10 || ^12 || >=14.0"
508+ },
509+ "peerDependencies": {
510+ "postcss": "^8.2.15"
511+ }
512+ },
513+ "node_modules/postcss-discard-overridden": {
514+ "version": "5.1.0",
515+ "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz",
516+ "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==",
517+ "dev": true,
518+ "engines": {
519+ "node": "^10 || ^12 || >=14.0"
520+ },
521+ "peerDependencies": {
522+ "postcss": "^8.2.15"
523+ }
524+ },
525+ "node_modules/postcss-merge-longhand": {
526+ "version": "5.1.7",
527+ "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz",
528+ "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==",
529+ "dev": true,
530+ "dependencies": {
531+ "postcss-value-parser": "^4.2.0",
532+ "stylehacks": "^5.1.1"
533+ },
534+ "engines": {
535+ "node": "^10 || ^12 || >=14.0"
536+ },
537+ "peerDependencies": {
538+ "postcss": "^8.2.15"
539+ }
540+ },
541+ "node_modules/postcss-merge-rules": {
542+ "version": "5.1.4",
543+ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz",
544+ "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==",
545+ "dev": true,
546+ "dependencies": {
547+ "browserslist": "^4.21.4",
548+ "caniuse-api": "^3.0.0",
549+ "cssnano-utils": "^3.1.0",
550+ "postcss-selector-parser": "^6.0.5"
551+ },
552+ "engines": {
553+ "node": "^10 || ^12 || >=14.0"
554+ },
555+ "peerDependencies": {
556+ "postcss": "^8.2.15"
557+ }
558+ },
559+ "node_modules/postcss-minify-font-values": {
560+ "version": "5.1.0",
561+ "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz",
562+ "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==",
563+ "dev": true,
564+ "dependencies": {
565+ "postcss-value-parser": "^4.2.0"
566+ },
567+ "engines": {
568+ "node": "^10 || ^12 || >=14.0"
569+ },
570+ "peerDependencies": {
571+ "postcss": "^8.2.15"
572+ }
573+ },
574+ "node_modules/postcss-minify-gradients": {
575+ "version": "5.1.1",
576+ "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz",
577+ "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==",
578+ "dev": true,
579+ "dependencies": {
580+ "colord": "^2.9.1",
581+ "cssnano-utils": "^3.1.0",
582+ "postcss-value-parser": "^4.2.0"
583+ },
584+ "engines": {
585+ "node": "^10 || ^12 || >=14.0"
586+ },
587+ "peerDependencies": {
588+ "postcss": "^8.2.15"
589+ }
590+ },
591+ "node_modules/postcss-minify-params": {
592+ "version": "5.1.4",
593+ "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz",
594+ "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==",
595+ "dev": true,
596+ "dependencies": {
597+ "browserslist": "^4.21.4",
598+ "cssnano-utils": "^3.1.0",
599+ "postcss-value-parser": "^4.2.0"
600+ },
601+ "engines": {
602+ "node": "^10 || ^12 || >=14.0"
603+ },
604+ "peerDependencies": {
605+ "postcss": "^8.2.15"
606+ }
607+ },
608+ "node_modules/postcss-minify-selectors": {
609+ "version": "5.2.1",
610+ "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz",
611+ "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==",
612+ "dev": true,
613+ "dependencies": {
614+ "postcss-selector-parser": "^6.0.5"
615+ },
616+ "engines": {
617+ "node": "^10 || ^12 || >=14.0"
618+ },
619+ "peerDependencies": {
620+ "postcss": "^8.2.15"
621+ }
622+ },
623+ "node_modules/postcss-normalize-charset": {
624+ "version": "5.1.0",
625+ "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz",
626+ "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==",
627+ "dev": true,
628+ "engines": {
629+ "node": "^10 || ^12 || >=14.0"
630+ },
631+ "peerDependencies": {
632+ "postcss": "^8.2.15"
633+ }
634+ },
635+ "node_modules/postcss-normalize-display-values": {
636+ "version": "5.1.0",
637+ "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz",
638+ "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==",
639+ "dev": true,
640+ "dependencies": {
641+ "postcss-value-parser": "^4.2.0"
642+ },
643+ "engines": {
644+ "node": "^10 || ^12 || >=14.0"
645+ },
646+ "peerDependencies": {
647+ "postcss": "^8.2.15"
648+ }
649+ },
650+ "node_modules/postcss-normalize-positions": {
651+ "version": "5.1.1",
652+ "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz",
653+ "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==",
654+ "dev": true,
655+ "dependencies": {
656+ "postcss-value-parser": "^4.2.0"
657+ },
658+ "engines": {
659+ "node": "^10 || ^12 || >=14.0"
660+ },
661+ "peerDependencies": {
662+ "postcss": "^8.2.15"
663+ }
664+ },
665+ "node_modules/postcss-normalize-repeat-style": {
666+ "version": "5.1.1",
667+ "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz",
668+ "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==",
669+ "dev": true,
670+ "dependencies": {
671+ "postcss-value-parser": "^4.2.0"
672+ },
673+ "engines": {
674+ "node": "^10 || ^12 || >=14.0"
675+ },
676+ "peerDependencies": {
677+ "postcss": "^8.2.15"
678+ }
679+ },
680+ "node_modules/postcss-normalize-string": {
681+ "version": "5.1.0",
682+ "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz",
683+ "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==",
684+ "dev": true,
685+ "dependencies": {
686+ "postcss-value-parser": "^4.2.0"
687+ },
688+ "engines": {
689+ "node": "^10 || ^12 || >=14.0"
690+ },
691+ "peerDependencies": {
692+ "postcss": "^8.2.15"
693+ }
694+ },
695+ "node_modules/postcss-normalize-timing-functions": {
696+ "version": "5.1.0",
697+ "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz",
698+ "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==",
699+ "dev": true,
700+ "dependencies": {
701+ "postcss-value-parser": "^4.2.0"
702+ },
703+ "engines": {
704+ "node": "^10 || ^12 || >=14.0"
705+ },
706+ "peerDependencies": {
707+ "postcss": "^8.2.15"
708+ }
709+ },
710+ "node_modules/postcss-normalize-unicode": {
711+ "version": "5.1.1",
712+ "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz",
713+ "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==",
714+ "dev": true,
715+ "dependencies": {
716+ "browserslist": "^4.21.4",
717+ "postcss-value-parser": "^4.2.0"
718+ },
719+ "engines": {
720+ "node": "^10 || ^12 || >=14.0"
721+ },
722+ "peerDependencies": {
723+ "postcss": "^8.2.15"
724+ }
725+ },
726+ "node_modules/postcss-normalize-url": {
727+ "version": "5.1.0",
728+ "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz",
729+ "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==",
730+ "dev": true,
731+ "dependencies": {
732+ "normalize-url": "^6.0.1",
733+ "postcss-value-parser": "^4.2.0"
734+ },
735+ "engines": {
736+ "node": "^10 || ^12 || >=14.0"
737+ },
738+ "peerDependencies": {
739+ "postcss": "^8.2.15"
740+ }
741+ },
742+ "node_modules/postcss-normalize-whitespace": {
743+ "version": "5.1.1",
744+ "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz",
745+ "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==",
746+ "dev": true,
747+ "dependencies": {
748+ "postcss-value-parser": "^4.2.0"
749+ },
750+ "engines": {
751+ "node": "^10 || ^12 || >=14.0"
752+ },
753+ "peerDependencies": {
754+ "postcss": "^8.2.15"
755+ }
756+ },
757+ "node_modules/postcss-ordered-values": {
758+ "version": "5.1.3",
759+ "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz",
760+ "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==",
761+ "dev": true,
762+ "dependencies": {
763+ "cssnano-utils": "^3.1.0",
764+ "postcss-value-parser": "^4.2.0"
765+ },
766+ "engines": {
767+ "node": "^10 || ^12 || >=14.0"
768+ },
769+ "peerDependencies": {
770+ "postcss": "^8.2.15"
771+ }
772+ },
773+ "node_modules/postcss-reduce-initial": {
774+ "version": "5.1.2",
775+ "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz",
776+ "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==",
777+ "dev": true,
778+ "dependencies": {
779+ "browserslist": "^4.21.4",
780+ "caniuse-api": "^3.0.0"
781+ },
782+ "engines": {
783+ "node": "^10 || ^12 || >=14.0"
784+ },
785+ "peerDependencies": {
786+ "postcss": "^8.2.15"
787+ }
788+ },
789+ "node_modules/postcss-reduce-transforms": {
790+ "version": "5.1.0",
791+ "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz",
792+ "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==",
793+ "dev": true,
794+ "dependencies": {
795+ "postcss-value-parser": "^4.2.0"
796+ },
797+ "engines": {
798+ "node": "^10 || ^12 || >=14.0"
799+ },
800+ "peerDependencies": {
801+ "postcss": "^8.2.15"
802+ }
803+ },
804+ "node_modules/postcss-selector-parser": {
805+ "version": "6.0.11",
806+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
807+ "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
808+ "dev": true,
809+ "dependencies": {
810+ "cssesc": "^3.0.0",
811+ "util-deprecate": "^1.0.2"
812+ },
813+ "engines": {
814+ "node": ">=4"
815+ }
816+ },
817+ "node_modules/postcss-svgo": {
818+ "version": "5.1.0",
819+ "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz",
820+ "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==",
821+ "dev": true,
822+ "dependencies": {
823+ "postcss-value-parser": "^4.2.0",
824+ "svgo": "^2.7.0"
825+ },
826+ "engines": {
827+ "node": "^10 || ^12 || >=14.0"
828+ },
829+ "peerDependencies": {
830+ "postcss": "^8.2.15"
831+ }
832+ },
833+ "node_modules/postcss-unique-selectors": {
834+ "version": "5.1.1",
835+ "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz",
836+ "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==",
837+ "dev": true,
838+ "dependencies": {
839+ "postcss-selector-parser": "^6.0.5"
840+ },
841+ "engines": {
842+ "node": "^10 || ^12 || >=14.0"
843+ },
844+ "peerDependencies": {
845+ "postcss": "^8.2.15"
846+ }
847+ },
848+ "node_modules/postcss-value-parser": {
849+ "version": "4.2.0",
850+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
851+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
852+ "dev": true
853+ },
854+ "node_modules/source-map": {
855+ "version": "0.6.1",
856+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
857+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
858+ "dev": true,
859+ "engines": {
860+ "node": ">=0.10.0"
861+ }
862+ },
863+ "node_modules/source-map-js": {
864+ "version": "1.0.2",
865+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
866+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
867+ "dev": true,
868+ "peer": true,
869+ "engines": {
870+ "node": ">=0.10.0"
871+ }
872+ },
873+ "node_modules/stable": {
874+ "version": "0.1.8",
875+ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
876+ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==",
877+ "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility",
878+ "dev": true
879+ },
880+ "node_modules/stylehacks": {
881+ "version": "5.1.1",
882+ "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
883+ "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==",
884+ "dev": true,
885+ "dependencies": {
886+ "browserslist": "^4.21.4",
887+ "postcss-selector-parser": "^6.0.4"
888+ },
889+ "engines": {
890+ "node": "^10 || ^12 || >=14.0"
891+ },
892+ "peerDependencies": {
893+ "postcss": "^8.2.15"
894+ }
895+ },
896+ "node_modules/svgo": {
897+ "version": "2.8.0",
898+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz",
899+ "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==",
900+ "dev": true,
901+ "dependencies": {
902+ "@trysound/sax": "0.2.0",
903+ "commander": "^7.2.0",
904+ "css-select": "^4.1.3",
905+ "css-tree": "^1.1.3",
906+ "csso": "^4.2.0",
907+ "picocolors": "^1.0.0",
908+ "stable": "^0.1.8"
909+ },
910+ "bin": {
911+ "svgo": "bin/svgo"
912+ },
913+ "engines": {
914+ "node": ">=10.13.0"
915+ }
916+ },
917+ "node_modules/update-browserslist-db": {
918+ "version": "1.0.10",
919+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
920+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
921+ "dev": true,
922+ "funding": [
923+ {
924+ "type": "opencollective",
925+ "url": "https://opencollective.com/browserslist"
926+ },
927+ {
928+ "type": "tidelift",
929+ "url": "https://tidelift.com/funding/github/npm/browserslist"
930+ }
931+ ],
932+ "dependencies": {
933+ "escalade": "^3.1.1",
934+ "picocolors": "^1.0.0"
935+ },
936+ "bin": {
937+ "browserslist-lint": "cli.js"
938+ },
939+ "peerDependencies": {
940+ "browserslist": ">= 4.21.0"
941+ }
942+ },
943+ "node_modules/util-deprecate": {
944+ "version": "1.0.2",
945+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
946+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
947+ "dev": true
948+ },
949+ "node_modules/yaml": {
950+ "version": "1.10.2",
951+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
952+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
953+ "dev": true,
954+ "engines": {
955+ "node": ">= 6"
956+ }
957+ }
958+ }
959+}
packages.dhall+4−4
@@ -99,8 +99,8 @@ in upstream
9999-------------------------------
100100-}
101101let upstream =
102− https://github.com/purescript/package-sets/releases/download/psc-0.15.2-20220706/packages.dhall
103− sha256:7a24ebdbacb2bfa27b2fc6ce3da96f048093d64e54369965a2a7b5d9892b6031
102+ https://github.com/purescript/package-sets/releases/download/psc-0.15.4-20220808/packages.dhall
103+ sha256:60eee64b04ca0013fae3e02a69fc3b176105c6baa2f31865c67cd5f881a412fd
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 = "purescript-0.14"
128+ , version = "3e388500811ef4b6718fc776789f3be050ad7b57"
129129 }
130130 with untagged-union =
131131 { dependencies =
@@ -147,7 +147,7 @@ in upstream
147147 }
148148 with literals =
149149 { repo = "https://github.com/ilyakooo0/purescript-literals.git"
150− , version = "iko/_null"
150+ , version = "6875fb28026595cfb780318305a77e79b098bb01"
151151 , dependencies =
152152 [ "assert"
153153 , "effect"
registry.datnew file

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

No changes to the content.

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

Submodule changed.

appname.apparmor → warden.apparmorrenamed+1−1
@@ -5,5 +5,5 @@
55 "audio",
66 "networking"
77 ],
8− "policy_version": 16.04
8+ "policy_version": 20.04
99}
appname.desktop → warden.desktoprenamed

The content is the same.