on master help sign in
Done with captcha (I think)
aa9755ciko
backend/BW.purs+2−1
@@ -13,6 +13,7 @@ import Data.JNullable (JNullable)
13import Data.SymmetricCryptoKey (SymmetricCryptoKey)
13import Data.SymmetricCryptoKey (SymmetricCryptoKey)
14import Effect (Effect)
14import Effect (Effect)
15import Effect.Unsafe (unsafePerformEffect)
15import Effect.Unsafe (unsafePerformEffect)
16+import Untagged.Union (type (|+|))
16
17
17type CryptoService
18type CryptoService
18 = { makeKey :: Fn4 Password String KDF Int (Promise SymmetricCryptoKey)
19 = { makeKey :: Fn4 Password String KDF Int (Promise SymmetricCryptoKey)
@@ -26,7 +27,7 @@ type ApiService
26 = { postPrelogin :: PreloginRequest -> Promise PreloginResponse
27 = { postPrelogin :: PreloginRequest -> Promise PreloginResponse
27 , getProfile :: Unit -> Promise ProfileResponse
28 , getProfile :: Unit -> Promise ProfileResponse
28 -- TODO: There can really be more request and response types. Maybe handle this later.
29 -- TODO: There can really be more request and response types. Maybe handle this later.
29− , postIdentityToken :: PasswordTokenRequest -> Promise IdentityTokenResponse
30+ , postIdentityToken :: PasswordTokenRequest -> Promise (IdentityCaptchaResponse |+| IdentityTokenResponse)
30 , getSync :: Unit -> Promise SyncResponse
31 , getSync :: Unit -> Promise SyncResponse
31 }
32 }
32
33
backend/BW/Logic.purs+6−3
@@ -15,6 +15,8 @@ import Data.JNullable as JNullable
15import Data.JOpt (fromJOpt)
15import Data.JOpt (fromJOpt)
16import Data.Maybe (Maybe(..))
16import Data.Maybe (Maybe(..))
17import Data.Newtype (class Newtype, wrap)
17import Data.Newtype (class Newtype, wrap)
18+import Data.JNullable (jnull, nullify)
19+import Data.Maybe (Maybe, fromMaybe)
18import Data.String as String
20import Data.String as String
19import Data.SymmetricCryptoKey (SymmetricCryptoKey)
21import Data.SymmetricCryptoKey (SymmetricCryptoKey)
20import Data.SymmetricCryptoKey as SymmetricCryptoKey
22import Data.SymmetricCryptoKey as SymmetricCryptoKey
@@ -23,6 +25,7 @@ import Effect.Aff.Class (class MonadAff, liftAff)
23import Effect.Class (liftEffect)
25import Effect.Class (liftEffect)
24import Effect.Exception (error)
26import Effect.Exception (error)
25import Effect.Exception as Exc
27import Effect.Exception as Exc
28+import Untagged.Union (type (|+|))
26
29
27-- | Creates the master key
30-- | Creates the master key
28makePreloginKey ::
31makePreloginKey ::
@@ -60,8 +63,8 @@ getLogInRequestToken ::
60 forall r.
63 forall r.
61 HasL "api" ApiService r =>
64 HasL "api" ApiService r =>
62 HasL "crypto" CryptoService r =>
65 HasL "crypto" CryptoService r =>
63− PreloginResponse -> Email -> Password -> Al r IdentityTokenResponse
66+ PreloginResponse -> Email -> Password -> Maybe String -> Al r (IdentityCaptchaResponse |+| IdentityTokenResponse)
64−getLogInRequestToken prelogin email password = do
67+getLogInRequestToken prelogin email password captchaResponse = do
65 key <- makePreloginKey prelogin email password
68 key <- makePreloginKey prelogin email password
66 crypto <- l (L :: L "crypto")
69 crypto <- l (L :: L "crypto")
67 api :: ApiService <- l (L :: L "api")
70 api :: ApiService <- l (L :: L "api")
@@ -73,7 +76,7 @@ getLogInRequestToken prelogin email password = do
73 $ api.postIdentityToken
76 $ api.postIdentityToken
74 { email: email
77 { email: email
75 , masterPasswordHash: hashedPassword
78 , masterPasswordHash: hashedPassword
76− , captchaResponse: ""
79+ , captchaResponse: fromMaybe "" captchaResponse
77 , twoFactor:
80 , twoFactor:
78 { provider: twoFactorProviderTypeEmail
81 { provider: twoFactorProviderTypeEmail
79 , token: ""
82 , token: ""
backend/BW/Types.purs+4−1
@@ -36,7 +36,7 @@ type ProfileResponse
36 , name :: String
36 , name :: String
37 , email :: String
37 , email :: String
38 , emailVerified :: Boolean
38 , emailVerified :: Boolean
39− , masterPasswordHint :: String
39+ , masterPasswordHint :: JNullable String
40 , premiumPersonally :: Boolean
40 , premiumPersonally :: Boolean
41 -- , premiumFromOrganization :: Boolean
41 -- , premiumFromOrganization :: Boolean
42 , culture :: String
42 , culture :: String
@@ -338,6 +338,9 @@ derive newtype instance Ord RefreshToken
338derive newtype instance EncodeJson RefreshToken
338derive newtype instance EncodeJson RefreshToken
339derive newtype instance DecodeJson RefreshToken
339derive newtype instance DecodeJson RefreshToken
340
340
341+type IdentityCaptchaResponse =
342+ { siteKey :: String
343+ }
341
344
342type IdentityTokenResponse
345type IdentityTokenResponse
343 = { accessToken :: AccessToken
346 = { accessToken :: AccessToken
backend/Bridge.purs+3−1
@@ -526,10 +526,12 @@ derive instance eqSub_NeedsMasterPassword :: Eq Sub_NeedsMasterPassword
526derive instance ordSub_NeedsMasterPassword :: Ord Sub_NeedsMasterPassword
526derive instance ordSub_NeedsMasterPassword :: Ord Sub_NeedsMasterPassword
527
527
528data Sub =
528data Sub =
529+ CaptchaDone
529− Error String
530+ | Error String
530 | LoadCipher Sub_LoadCipher
531 | LoadCipher Sub_LoadCipher
531 | LoadCiphers Sub_LoadCiphers_List
532 | LoadCiphers Sub_LoadCiphers_List
532 | LoginSuccessful
533 | LoginSuccessful
534+ | NeedsCaptcha String
533 | NeedsLogin
535 | NeedsLogin
534 | NeedsMasterPassword Sub_NeedsMasterPassword
536 | NeedsMasterPassword Sub_NeedsMasterPassword
535 | RecieveEmail String
537 | RecieveEmail String
backend/Data/HCaptcha.js−1
@@ -5,6 +5,5 @@ export function bindHCaptchToken(callback) {
5 callback(e.data.hCaptchaToken)();
5 callback(e.data.hCaptchaToken)();
6 }
6 }
7 })
7 })
8−
9 }
8 }
10}
9}
backend/Main.purs+28−16
@@ -20,6 +20,7 @@ import Data.Either (Either(..))
20import Data.HCaptcha (bindHCaptchToken)
20import Data.HCaptcha (bindHCaptchToken)
21import Data.JNullable (jnull, nullify)
21import Data.JNullable (jnull, nullify)
22import Data.Maybe (Maybe(..))
22import Data.Maybe (Maybe(..))
23+import Data.Newtype (wrap)
23import Data.OpenURL (openURL)
24import Data.OpenURL (openURL)
24import Data.Ord.Down (Down(..))
25import Data.Ord.Down (Down(..))
25import Data.SymmetricCryptoKey (SymmetricCryptoKey)
26import Data.SymmetricCryptoKey (SymmetricCryptoKey)
@@ -38,6 +39,7 @@ import FFI as FFI
38import Localstorage (class StorageKey)
39import Localstorage (class StorageKey)
39import Localstorage as Storage
40import Localstorage as Storage
40import Storage (MasterPasswordHashKey(..), PreloginResponseKey(..), SyncKey(..), TokenKey(..), UrlsKey(..))
41import Storage (MasterPasswordHashKey(..), PreloginResponseKey(..), SyncKey(..), TokenKey(..), UrlsKey(..))
42+import Untagged.Union (toEither1)
41import Web.HTML as Html
43import Web.HTML as Html
42import Web.HTML.Window as Window
44import Web.HTML.Window as Window
43import Web.Storage.Storage (Storage)
45import Web.Storage.Storage (Storage)
@@ -49,7 +51,10 @@ main = do
49 storage <- Html.window >>= Window.localStorage
51 storage <- Html.window >>= Window.localStorage
50 services <- WB.getServices
52 services <- WB.getServices
51 masterKeyRef <- Ref.new Nothing
53 masterKeyRef <- Ref.new Nothing
54+ hCaptchaTokenRef <- Ref.new Nothing
52− bindHCaptchToken log
55+ bindHCaptchToken \captchaToken -> do
56+ Ref.write (Just captchaToken) hCaptchaTokenRef
57+ Elm.send app Bridge.CaptchaDone
53 let
58 let
54 run act = do
59 run act = do
55 maybeKey <- Ref.read masterKeyRef
60 maybeKey <- Ref.read masterKeyRef
@@ -69,6 +74,8 @@ main = do
69 Nothing -> requestMasterPassword
74 Nothing -> requestMasterPassword
70 Elm.subscribe app \cmd -> case cmd of
75 Elm.subscribe app \cmd -> case cmd of
71 Bridge.Login (Bridge.Cmd_Login { email, server, password }) -> do
76 Bridge.Login (Bridge.Cmd_Login { email, server, password }) -> do
77+ captchaToken <- Ref.read hCaptchaTokenRef
78+ Ref.write Nothing hCaptchaTokenRef
72 let
79 let
73 urls = baseUrl server
80 urls = baseUrl server
74 runElmAff app do
81 runElmAff app do
@@ -82,23 +89,28 @@ main = do
82 }
89 }
83 flip runReaderT env do
90 flip runReaderT env do
84 prelogin <- liftPromise $ unauthedApi.postPrelogin { email }
91 prelogin <- liftPromise $ unauthedApi.postPrelogin { email }
85− token <-
92+ loginResponse <-
86 catchError
93 catchError
87− (Logic.getLogInRequestToken prelogin (Email email) (Password password))
94+ (Logic.getLogInRequestToken prelogin (Email email) (Password password) captchaToken)
88 (const $ liftEffect $ Exc.throw "Could not log in. Please check your data and try again.")
95 (const $ liftEffect $ Exc.throw "Could not log in. Please check your data and try again.")
96+ case toEither1 loginResponse of
97+ Left { siteKey } ->
98+ liftEffect do
99+ Elm.send app $ Bridge.NeedsCaptcha siteKey
100+ Right token -> do
89− masterKey <- Logic.makePreloginKey prelogin (Email email) (Password password)
101+ masterKey <- Logic.makePreloginKey prelogin (Email email) (Password password)
90− api <- liftPromise $ services.getApi urls (nullify token)
102+ api <- liftPromise $ services.getApi urls (nullify token)
91− sync <- liftPromise $ api.getSync unit
103+ sync <- liftPromise $ api.getSync unit
92− hash <- hashPassword (Password password)
104+ hash <- hashPassword (Password password)
93− log $ show sync
105+ log $ show sync
94− liftEffect do
106+ liftEffect do
95− Ref.write (Just masterKey) masterKeyRef
107+ Ref.write (Just masterKey) masterKeyRef
96− Storage.store storage UrlsKey server
108+ Storage.store storage UrlsKey server
97− Storage.store storage MasterPasswordHashKey hash
109+ Storage.store storage MasterPasswordHashKey hash
98− Storage.store storage PreloginResponseKey prelogin
110+ Storage.store storage PreloginResponseKey prelogin
99− Storage.store storage SyncKey sync
111+ Storage.store storage SyncKey sync
100− Storage.store storage TokenKey token
112+ Storage.store storage TokenKey token
101− Elm.send app Bridge.LoginSuccessful
113+ Elm.send app Bridge.LoginSuccessful
102 Bridge.NeedCiphersList ->
114 Bridge.NeedCiphersList ->
103 run do
115 run do
104 sync <- getOrReset SyncKey
116 sync <- getOrReset SyncKey
bridge.yaml+2
@@ -1,5 +1,7 @@
1Sub:
1Sub:
2- NeedsLogin
2- NeedsLogin
3+- NeedsCaptcha: String
4+- CaptchaDone
3- Error: String
5- Error: String
4- LoadCiphers:
6- LoadCiphers:
5 List:
7 List:
frontend/Bridge.elm+8−2
@@ -624,10 +624,12 @@ jsonEncSub_NeedsMasterPassword val =
624
624
625
625
626type Sub =
626type Sub =
627+ CaptchaDone
627− Error String
628+ | Error String
628 | LoadCipher Sub_LoadCipher
629 | LoadCipher Sub_LoadCipher
629 | LoadCiphers Sub_LoadCiphers_List
630 | LoadCiphers Sub_LoadCiphers_List
630 | LoginSuccessful
631 | LoginSuccessful
632+ | NeedsCaptcha String
631 | NeedsLogin
633 | NeedsLogin
632 | NeedsMasterPassword Sub_NeedsMasterPassword
634 | NeedsMasterPassword Sub_NeedsMasterPassword
633 | RecieveEmail String
635 | RecieveEmail String
@@ -636,10 +638,12 @@ type Sub =
636jsonDecSub : Json.Decode.Decoder ( Sub )
638jsonDecSub : Json.Decode.Decoder ( Sub )
637jsonDecSub =
639jsonDecSub =
638 let jsonDecDictSub = Dict.fromList
640 let jsonDecDictSub = Dict.fromList
641+ [ ("CaptchaDone", Json.Decode.lazy (\_ -> Json.Decode.succeed CaptchaDone))
639− [ ("Error", Json.Decode.lazy (\_ -> Json.Decode.map Error (Json.Decode.string)))
642+ , ("Error", Json.Decode.lazy (\_ -> Json.Decode.map Error (Json.Decode.string)))
640 , ("LoadCipher", Json.Decode.lazy (\_ -> Json.Decode.map LoadCipher (jsonDecSub_LoadCipher)))
643 , ("LoadCipher", Json.Decode.lazy (\_ -> Json.Decode.map LoadCipher (jsonDecSub_LoadCipher)))
641 , ("LoadCiphers", Json.Decode.lazy (\_ -> Json.Decode.map LoadCiphers (jsonDecSub_LoadCiphers_List)))
644 , ("LoadCiphers", Json.Decode.lazy (\_ -> Json.Decode.map LoadCiphers (jsonDecSub_LoadCiphers_List)))
642 , ("LoginSuccessful", Json.Decode.lazy (\_ -> Json.Decode.succeed LoginSuccessful))
645 , ("LoginSuccessful", Json.Decode.lazy (\_ -> Json.Decode.succeed LoginSuccessful))
646+ , ("NeedsCaptcha", Json.Decode.lazy (\_ -> Json.Decode.map NeedsCaptcha (Json.Decode.string)))
643 , ("NeedsLogin", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedsLogin))
647 , ("NeedsLogin", Json.Decode.lazy (\_ -> Json.Decode.succeed NeedsLogin))
644 , ("NeedsMasterPassword", Json.Decode.lazy (\_ -> Json.Decode.map NeedsMasterPassword (jsonDecSub_NeedsMasterPassword)))
648 , ("NeedsMasterPassword", Json.Decode.lazy (\_ -> Json.Decode.map NeedsMasterPassword (jsonDecSub_NeedsMasterPassword)))
645 , ("RecieveEmail", Json.Decode.lazy (\_ -> Json.Decode.map RecieveEmail (Json.Decode.string)))
649 , ("RecieveEmail", Json.Decode.lazy (\_ -> Json.Decode.map RecieveEmail (Json.Decode.string)))
@@ -651,10 +655,12 @@ jsonDecSub =
651jsonEncSub : Sub -> Value
655jsonEncSub : Sub -> Value
652jsonEncSub val =
656jsonEncSub val =
653 let keyval v = case v of
657 let keyval v = case v of
658+ CaptchaDone -> ("CaptchaDone", encodeValue (Json.Encode.list identity []))
654 Error v1 -> ("Error", encodeValue (Json.Encode.string v1))
659 Error v1 -> ("Error", encodeValue (Json.Encode.string v1))
655 LoadCipher v1 -> ("LoadCipher", encodeValue (jsonEncSub_LoadCipher v1))
660 LoadCipher v1 -> ("LoadCipher", encodeValue (jsonEncSub_LoadCipher v1))
656 LoadCiphers v1 -> ("LoadCiphers", encodeValue (jsonEncSub_LoadCiphers_List v1))
661 LoadCiphers v1 -> ("LoadCiphers", encodeValue (jsonEncSub_LoadCiphers_List v1))
657 LoginSuccessful -> ("LoginSuccessful", encodeValue (Json.Encode.list identity []))
662 LoginSuccessful -> ("LoginSuccessful", encodeValue (Json.Encode.list identity []))
663+ NeedsCaptcha v1 -> ("NeedsCaptcha", encodeValue (Json.Encode.string v1))
658 NeedsLogin -> ("NeedsLogin", encodeValue (Json.Encode.list identity []))
664 NeedsLogin -> ("NeedsLogin", encodeValue (Json.Encode.list identity []))
659 NeedsMasterPassword v1 -> ("NeedsMasterPassword", encodeValue (jsonEncSub_NeedsMasterPassword v1))
665 NeedsMasterPassword v1 -> ("NeedsMasterPassword", encodeValue (jsonEncSub_NeedsMasterPassword v1))
660 RecieveEmail v1 -> ("RecieveEmail", encodeValue (Json.Encode.string v1))
666 RecieveEmail v1 -> ("RecieveEmail", encodeValue (Json.Encode.string v1))
frontend/Main.elm+41
@@ -7,6 +7,7 @@ import Html exposing (..)
7import Html.Lazy as Lazy
7import Html.Lazy as Lazy
8import List.Nonempty as Nonempty exposing (Nonempty(..))
8import List.Nonempty as Nonempty exposing (Nonempty(..))
9import Notification
9import Notification
10+import Pages.Captcha as Captcha
10import Pages.Cipher as Cipher
11import Pages.Cipher as Cipher
11import Pages.Ciphers as Ciphers
12import Pages.Ciphers as Ciphers
12import Pages.Loader exposing (loader)
13import Pages.Loader exposing (loader)
@@ -38,6 +39,8 @@ type Msg
38 | RecieveEmail String
39 | RecieveEmail String
39 | Copy String
40 | Copy String
40 | Open String
41 | Open String
42+ | CaptchaMsg Captcha.Msg
43+ | ShowCaptcha Captcha.HCaptchSiteKey
41
44
42
45
43type alias CipherId =
46type alias CipherId =
@@ -62,6 +65,7 @@ type PageModel
62 | CiphersModel Ciphers.Model
65 | CiphersModel Ciphers.Model
63 | MasterPasswordModel MasterPassword.Model
66 | MasterPasswordModel MasterPassword.Model
64 | CipherModel Cipher.Model
67 | CipherModel Cipher.Model
68+ | CaptchaModel Captcha.Model
65
69
66
70
67showPage :
71showPage :
@@ -124,6 +128,16 @@ showPage email page =
124 , topButton = Just (BackButton PopView)
128 , topButton = Just (BackButton PopView)
125 }
129 }
126
130
131+ CaptchaModel model ->
132+ let
133+ p =
134+ Captcha.page captchaCallbacks CaptchaMsg
135+ in
136+ { title = p.title model
137+ , body = p.view model
138+ , topButton = Just (BackButton PopView)
139+ }
140+
127
141
128main : Program () Model Msg
142main : Program () Model Msg
129main =
143main =
@@ -163,6 +177,12 @@ subscriptions _ =
163
177
164 Bridge.RecieveEmail email ->
178 Bridge.RecieveEmail email ->
165 RecieveEmail email
179 RecieveEmail email
180+
181+ Bridge.NeedsCaptcha uri ->
182+ ShowCaptcha uri
183+
184+ Bridge.CaptchaDone ->
185+ PopView
166 )
186 )
167
187
168
188
@@ -228,6 +248,10 @@ update msg model =
228 appendPageStack mdl =
248 appendPageStack mdl =
229 { model | pageStack = model.pageStack |> Nonempty.toList |> List.filter (doNotStoreInHistory >> not) |> Nonempty mdl }
249 { model | pageStack = model.pageStack |> Nonempty.toList |> List.filter (doNotStoreInHistory >> not) |> Nonempty mdl }
230
250
251+ keepStackWith : PageModel -> Model
252+ keepStackWith mdl =
253+ { model | pageStack = Nonempty.cons mdl model.pageStack }
254+
231 currentPage =
255 currentPage =
232 model.pageStack |> Nonempty.head
256 model.pageStack |> Nonempty.head
233 in
257 in
@@ -339,6 +363,18 @@ update msg model =
339 Open uri ->
363 Open uri ->
340 ( model, FFI.sendBridge (Bridge.Open uri) )
364 ( model, FFI.sendBridge (Bridge.Open uri) )
341
365
366+ ShowCaptcha uri ->
367+ (Captcha.page captchaCallbacks CaptchaMsg).init uri
368+ |> Tuple.mapFirst (\pageModel -> keepStackWith <| CaptchaModel pageModel)
369+
370+ CaptchaMsg imsg ->
371+ case currentPage of
372+ CaptchaModel page ->
373+ (Captcha.page captchaCallbacks CaptchaMsg).update imsg page |> processPage CaptchaModel
374+
375+ _ ->
376+ ( model, Cmd.none )
377+
342
378
343loginCallbacks : Login.Callbacks Msg
379loginCallbacks : Login.Callbacks Msg
344loginCallbacks =
380loginCallbacks =
@@ -355,6 +391,11 @@ cipherCallbacks =
355 { copy = Copy, open = Open }
391 { copy = Copy, open = Open }
356
392
357
393
394+captchaCallbacks : Captcha.Callbacks
395+captchaCallbacks =
396+ {}
397+
398+
358masterPasswordCallbacks : MasterPassword.Callbacks Msg
399masterPasswordCallbacks : MasterPassword.Callbacks Msg
359masterPasswordCallbacks =
400masterPasswordCallbacks =
360 { submit = SendMasterPassword
401 { submit = SendMasterPassword
frontend/Pages/Captcha.elmnew file+57
@@ -0,0 +1,57 @@
1+module Pages.Captcha exposing (Callbacks, HCaptchSiteKey, Model, Msg, page)
2+
3+import Html exposing (..)
4+import Html.Attributes as Attr
5+import Page exposing (..)
6+import Utils exposing (..)
7+
8+
9+type alias HCaptchSiteKey =
10+ String
11+
12+
13+type alias Model =
14+ { siteKey : HCaptchSiteKey
15+ }
16+
17+
18+type Msg
19+ = Noop
20+
21+
22+type alias Callbacks =
23+ {}
24+
25+
26+page : Callbacks -> Page HCaptchSiteKey Model Msg emsg
27+page callbacks liftMsg =
28+ { init = \siteKey -> Tuple.mapSecond (Cmd.map liftMsg) (init siteKey)
29+ , view = \model -> view model |> List.map (Html.map liftMsg)
30+ , update = \msg model -> update callbacks liftMsg msg model
31+ , subscriptions = \model -> subscriptions model |> Sub.map liftMsg
32+ , title = always "Captcha"
33+ }
34+
35+
36+init : HCaptchSiteKey -> ( Model, Cmd Msg )
37+init siteKey =
38+ ( { siteKey = siteKey }, Cmd.none )
39+
40+
41+update : Callbacks -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
42+update _ _ msg model =
43+ case msg of
44+ Noop ->
45+ ( Ok model, Cmd.none )
46+
47+
48+subscriptions : Model -> Sub Msg
49+subscriptions model =
50+ Sub.none
51+
52+
53+view : Model -> List (Html Msg)
54+view { siteKey } =
55+ [ div [ Attr.class "u-embedded-media" ]
56+ [ iframe [ Attr.src ("https://iko.soy/warden/hcaptcha.html?" ++ siteKey) ] [] ]
57+ ]
frontend/Pages/Login.elm+12−11
@@ -38,8 +38,8 @@ page callbacks liftMsg =
38
38
39init : ( Model, Cmd Msg )
39init : ( Model, Cmd Msg )
40init =
40init =
41− ( { server = "https://bitwarden.iko.soy"
41+ ( { server = "https://vault.bitwarden.com"
42− , email = "mail@iko.soy"
42+ , email = "ilyakooo0@gmail.com"
43 , password = ""
43 , password = ""
44 }
44 }
45 , Cmd.none
45 , Cmd.none
@@ -70,31 +70,32 @@ subscriptions model =
70view : Model -> List (Html Msg)
70view : Model -> List (Html Msg)
71view model =
71view model =
72 [ form [ Ev.onSubmit Submit ]
72 [ form [ Ev.onSubmit Submit ]
73− [ label [] [ text "Server" ]
73+ ([ label [] [ text "Server" ]
74− , input
74+ , input
75 [ Attr.type_ "url"
75 [ Attr.type_ "url"
76 , Attr.value model.server
76 , Attr.value model.server
77 , Ev.onInput UpdateServer
77 , Ev.onInput UpdateServer
78 , Attr.attribute "autocomplete" "url"
78 , Attr.attribute "autocomplete" "url"
79 ]
79 ]
80 []
80 []
81− , label [] [ text "Email" ]
81+ , label [] [ text "Email" ]
82− , input
82+ , input
83 [ Attr.type_ "email"
83 [ Attr.type_ "email"
84 , Attr.value model.email
84 , Attr.value model.email
85 , Ev.onInput UpdateEmail
85 , Ev.onInput UpdateEmail
86 , Attr.attribute "autocomplete" "email"
86 , Attr.attribute "autocomplete" "email"
87 ]
87 ]
88 []
88 []
89− , label [] [ text "Password" ]
89+ , label [] [ text "Password" ]
90− , input
90+ , input
91 [ Attr.type_ "password"
91 [ Attr.type_ "password"
92 , Attr.value model.password
92 , Attr.value model.password
93 , Ev.onInput UpdatePassword
93 , Ev.onInput UpdatePassword
94 , Attr.attribute "autocomplete" "current-password"
94 , Attr.attribute "autocomplete" "current-password"
95 ]
95 ]
96 []
96 []
97+ ]
97− , iframe [ Attr.src "https://iko.soy/warden/hcaptcha.html" ] []
98+ -- ++ maybeList model.siteKey (\siteKey -> iframe [ Attr.src ("https://iko.soy/warden/hcaptcha.html?" ++ siteKey) ] [])
98− , alignRight [ button [ Attr.type_ "submit" ] [ text "Log in" ] ]
99+ ++ [ alignRight [ button [ Attr.type_ "submit" ] [ text "Log in" ] ] ]
99− ]
100+ )
100 ]
101 ]
packages.dhall+16−1
@@ -144,5 +144,20 @@ in upstream
144 , "unsafe-coerce"
144 , "unsafe-coerce"
145 ]
145 ]
146 , repo = "https://github.com/ilyakooo0/purescript-untagged-union.git"
146 , repo = "https://github.com/ilyakooo0/purescript-untagged-union.git"
147− , version = "ps-15"
147+ , version = "iko/ps-15"
148+ }
149+ with literals =
150+ { repo = "https://github.com/ilyakooo0/purescript-literals.git"
151+ , version = "iko/_null"
152+ , dependencies =
153+ [ "assert"
154+ , "effect"
155+ , "integers"
156+ , "maybe"
157+ , "numbers"
158+ , "partial"
159+ , "prelude"
160+ , "typelevel-prelude"
161+ , "unsafe-coerce"
162+ ]
148 }
163 }
spago.dhall+1
@@ -41,6 +41,7 @@ to generate this file without the comments in this block.
41 , "tuples"
41 , "tuples"
42 , "typelevel-prelude"
42 , "typelevel-prelude"
43 , "undefined-is-not-a-problem"
43 , "undefined-is-not-a-problem"
44+ , "untagged-union"
44 , "web-html"
45 , "web-html"
45 , "web-storage"
46 , "web-storage"
46 ]
47 ]