Cambiatus Web based frontend using Elm
Portuguese version of guide.elm-lang.org 🇧🇷 🇵🇹 🇦🇴 🇲🇿 🇬🇼 🇨🇻 🇸🇹
Cambiatus public GraphQL API
Cambiatus EOSIO Smart Contracts
☕️⛓ Compre café usando Blockchain! Repo para o workshop de blockchain da CampusParty 2018
💰Simple cryptocurrency implementation using Elixir
A Semantic UI powered Rails app with some of my favorite gems
Simple Forum Elixir Exercise
🔥 Phoenix ashes for new projects
Pull request review commentcambiatus/backend
defmodule Cambiatus.Auth.Ecdsa do """ def verify_signature(account, signature, phrase) do- {"app", :publicKeyPoints}- |> NodeJS.call([signature, phrase])- |> case do- {:ok, %{"publicKey" => pub_points, "signature" => signature_points}} ->- public_key = %EllipticCurve.PublicKey.Data{- curve: %EllipticCurve.Curve.Data{- A: 0,- B: 7,- G: %EllipticCurve.Utils.Point{- x:- 55_066_263_022_277_343_669_578_718_895_168_534_326_250_603_453_777_594_175_500_187_360_389_116_729_240,- y:- 32_670_510_020_758_816_978_083_085_130_507_043_184_471_273_380_659_243_275_938_904_335_757_337_482_424,- z: 1- },- N:- 115_792_089_237_316_195_423_570_985_008_687_907_852_837_564_279_074_904_382_605_163_141_518_161_494_337,- P:- 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_908_834_671_663,- name: :secp256k1,- oid: [1, 3, 132, 0, 10]- },- point: %EllipticCurve.Utils.Point{- x: String.to_integer(pub_points["x"]),- y: String.to_integer(pub_points["y"]),- z: 1- }- }+ pub_key =+ account+ |> EosjsAuthWrapper.get_account_info()+ |> get_pub_key() - signature_elixir = %EllipticCurve.Signature.Data{- r: String.to_integer(signature_points["r"]),- s: String.to_integer(signature_points["s"])- }+ EosjsAuthWrapper.verify(signature, phrase, pub_key)+ |> case do+ {:ok, result} -> result+ {:error, _} -> false+ end+ end - EllipticCurve.Ecdsa.verify?(phrase, signature_elixir, public_key) &&- compare_public_keys(pub_points["string"], account)+ def sign(signature, priv_key) do+ EosjsAuthWrapper.sign(signature, priv_key)+ end - {:error, _} ->- false- end+ def sign_with_random(phrase) do+ EosjsAuthWrapper.gen_rand_signature(phrase) end - @doc """- Verify public key associated to the signature private key and the public key for the account- """- defp compare_public_keys(public_key_a, account) do- {"app", :accountToPublicKey}- |> NodeJS.call([account])+ defp get_pub_key(account) do
In my opinion private functions help hide the implementation details for the main functions of the module. In this case get_pub_key is a helper functions and it shouldn't be called outside off this module.
comment created time in an hour
Pull request review commentcambiatus/backend
defmodule Cambiatus.Auth.Ecdsa do """ def verify_signature(account, signature, phrase) do- {"app", :publicKeyPoints}- |> NodeJS.call([signature, phrase])- |> case do- {:ok, %{"publicKey" => pub_points, "signature" => signature_points}} ->- public_key = %EllipticCurve.PublicKey.Data{- curve: %EllipticCurve.Curve.Data{- A: 0,- B: 7,- G: %EllipticCurve.Utils.Point{- x:- 55_066_263_022_277_343_669_578_718_895_168_534_326_250_603_453_777_594_175_500_187_360_389_116_729_240,- y:- 32_670_510_020_758_816_978_083_085_130_507_043_184_471_273_380_659_243_275_938_904_335_757_337_482_424,- z: 1- },- N:- 115_792_089_237_316_195_423_570_985_008_687_907_852_837_564_279_074_904_382_605_163_141_518_161_494_337,- P:- 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_908_834_671_663,- name: :secp256k1,- oid: [1, 3, 132, 0, 10]- },- point: %EllipticCurve.Utils.Point{- x: String.to_integer(pub_points["x"]),- y: String.to_integer(pub_points["y"]),- z: 1- }- }+ pub_key =+ account+ |> EosjsAuthWrapper.get_account_info()+ |> get_pub_key() - signature_elixir = %EllipticCurve.Signature.Data{- r: String.to_integer(signature_points["r"]),- s: String.to_integer(signature_points["s"])- }+ EosjsAuthWrapper.verify(signature, phrase, pub_key)+ |> case do+ {:ok, result} -> result+ {:error, _} -> false+ end+ end - EllipticCurve.Ecdsa.verify?(phrase, signature_elixir, public_key) &&- compare_public_keys(pub_points["string"], account)+ def sign(signature, priv_key) do+ EosjsAuthWrapper.sign(signature, priv_key)+ end - {:error, _} ->- false- end+ def sign_with_random(phrase) do+ EosjsAuthWrapper.gen_rand_signature(phrase)
Agreed, I made the necessary update.
comment created time in 2 hours
Pull request review commentcambiatus/backend
defmodule Cambiatus.Auth.Ecdsa do """ def verify_signature(account, signature, phrase) do- {"app", :publicKeyPoints}- |> NodeJS.call([signature, phrase])- |> case do- {:ok, %{"publicKey" => pub_points, "signature" => signature_points}} ->- public_key = %EllipticCurve.PublicKey.Data{- curve: %EllipticCurve.Curve.Data{- A: 0,- B: 7,- G: %EllipticCurve.Utils.Point{- x:- 55_066_263_022_277_343_669_578_718_895_168_534_326_250_603_453_777_594_175_500_187_360_389_116_729_240,- y:- 32_670_510_020_758_816_978_083_085_130_507_043_184_471_273_380_659_243_275_938_904_335_757_337_482_424,- z: 1- },- N:- 115_792_089_237_316_195_423_570_985_008_687_907_852_837_564_279_074_904_382_605_163_141_518_161_494_337,- P:- 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_908_834_671_663,- name: :secp256k1,- oid: [1, 3, 132, 0, 10]- },- point: %EllipticCurve.Utils.Point{- x: String.to_integer(pub_points["x"]),- y: String.to_integer(pub_points["y"]),- z: 1- }- }+ pub_key =+ account+ |> EosjsAuthWrapper.get_account_info()+ |> get_pub_key() - signature_elixir = %EllipticCurve.Signature.Data{- r: String.to_integer(signature_points["r"]),- s: String.to_integer(signature_points["s"])- }+ EosjsAuthWrapper.verify(signature, phrase, pub_key)+ |> case do+ {:ok, result} -> result+ {:error, _} -> false+ end+ end - EllipticCurve.Ecdsa.verify?(phrase, signature_elixir, public_key) &&- compare_public_keys(pub_points["string"], account)+ def sign(signature, priv_key) do+ EosjsAuthWrapper.sign(signature, priv_key)+ end
I have made the update
comment created time in 2 hours
Pull request review commentcambiatus/frontend
encodeCreateCommunityData c = , ( "description", Encode.string c.description ) , ( "inviter_reward", Eos.encodeAsset c.inviterReward ) , ( "invited_reward", Eos.encodeAsset c.invitedReward )+ , ( "min_balance", Eos.encodeAsset c.minBalance ) , ( "has_objectives", Eos.encodeEosBool c.hasObjectives ) , ( "has_shop", Eos.encodeEosBool c.hasShop ) , ( "has_kyc", Eos.encodeEosBool c.hasKyc ) ] +createCommunityDataDecoder : Decoder CreateCommunityData+createCommunityDataDecoder =+ Decode.succeed CreateCommunityData+ |> required "cmm_asset" Eos.decodeAsset+ |> required "creator" Eos.nameDecoder+ |> required "logo" Decode.string+ |> required "name" Decode.string+ |> required "description" Decode.string+ |> required "inviter_reward" Eos.decodeAsset+ |> required "invited_reward" Eos.decodeAsset+ |> required "min_balance" Eos.decodeAsset+ |> required "has_objectives" Eos.eosBoolDecoder+ |> required "has_shop" Eos.eosBoolDecoder+ |> required "has_kyc" Eos.eosBoolDecoder++
The old flow for creating a community was like this:
- User sees empty form
- User fills in form
- User clicks "Save"
- If the symbol field is actually a valid symbol, we subscribe to GraphQL's
newCommunity(otherwise, literally do nothing) - We wait until the subscription port sends us something back.
- a. If it sends "responded", just redirect to the community page (and the flow is over) b. If it sends "starting", we ask the user for authentication
- Only now we validate the form
- a. If the form is not valid, we show the errors to the user (and the flow starts from 2.) b. If the form is valid, we send the Eos transaction
- a. If the Eos response was valid, redirect to community (we should remove this, so we only redirect when the subscription tells us to) b. Otherwise, show errors and start from 2.
The new flow is like this:
- User sees empty form
- User fills in form
- User clicks "Save"
- a. If the form is not valid, we show errors (and the flow starts from 2.) b. If the form is valid, we ask the user for authentication
- We subscribe to
newCommunity - We wait until the subscription port sends us something back
- a. If it sends "responded", redirect to the community (and the flow is over) b. If it sends "starting" we send the Eos transaction
- a. If the Eos response was valid, redirect to community (we should remove this, so we only redirect when the subscription tells us to) b. Otherwise, show errors and start from 2.
So the old flow validates the model very late (after subscribing to newCommunity and asking the user for authentication - IMO it should be before those), while the new one validates it as soon as the user clicks "Save". Because of that, we have the validated information on the SubmittedForm msg, not only just before sending the Eos transaction. So we didn't need the decoder before because we only validated the data when sending the Eos transaction - which means it would take longer to show errors to the user.
The result ends up being the same, but the process is a little different, and better for the user. By the way, minBalance already was in CreateCommunityData, it just wasn't included in the encoder. I'm still convinced minBalance should be in CreateCommunityData - that's an abstraction we make throughout the app (we don't have a Community and a Token, the token information is already included in Community). Having min_balance in the encoder doesn't seem to break anything, but we could make it so the encoder actually returns two encoders (in a tuple), one for the community and one for the token - I think this makes sense, because when we're creating a new community, we're also creating a new token anyway
comment created time in 5 hours
startedOneGraph/graphiql-explorer
started time in 6 hours
Pull request review commentcambiatus/frontend
+module Page.Community.Settings.Currency exposing+ ( Model+ , Msg+ , init+ , jsAddressToMsg+ , msgToString+ , receiveBroadcast+ , update+ , view+ )++import Community+import Eos+import Eos.Account as Eos+import Html exposing (Html, button, div, form, span, text)+import Html.Attributes exposing (class, disabled)+import Html.Events exposing (onSubmit)+import Json.Decode as Decode+import Json.Encode as Encode+import Page+import Ports+import RemoteData+import Route+import Session.LoggedIn as LoggedIn+import Session.Shared exposing (Translators)+import UpdateResult as UR+import View.Feedback as Feedback+import View.Form.Input as Input++++-- MODEL+++type alias Model =+ { inviterReward : String+ , invitedReward : String+ , minimumBalance : String+ , isLoading : Bool+ , errors : List ( Field, String )+ }+++type alias ValidModel =+ { inviterReward : Float+ , invitedReward : Float+ , minimumBalance : Float+ }+++init : LoggedIn.Model -> ( Model, Cmd Msg )+init loggedIn =+ ( { inviterReward = ""+ , invitedReward = ""+ , minimumBalance = ""+ , isLoading = True+ , errors = []+ }+ , LoggedIn.maybeInitWith CompletedLoadCommunity .selectedCommunity loggedIn+ )++++-- UPDATE+++type Msg+ = Ignored+ | EnteredInviterReward String+ | EnteredInvitedReward String+ | EnteredMinimumBalance String+ | ClickedSubmit+ | GotSubmitResponse (Result Encode.Value ValidModel)+ | CompletedLoadCommunity Community.Model+++type alias UpdateResult =+ UR.UpdateResult Model Msg (LoggedIn.External Msg)+++update : Msg -> Model -> LoggedIn.Model -> UpdateResult+update msg model ({ shared } as loggedIn) =+ case msg of+ Ignored ->+ UR.init model++ EnteredInviterReward inviterReward ->+ { model | inviterReward = inviterReward }+ |> withSymbolValidation validateInviterReward InviterReward loggedIn+ |> UR.init++ EnteredInvitedReward invitedReward ->+ { model | invitedReward = invitedReward }+ |> withSymbolValidation validateInvitedReward InvitedReward loggedIn+ |> UR.init++ EnteredMinimumBalance minimumBalance ->+ { model | minimumBalance = minimumBalance }+ |> withSymbolValidation validateMinimumBalance MinimumBalance loggedIn+ |> UR.init++ ClickedSubmit ->+ case loggedIn.selectedCommunity of+ RemoteData.Success community ->+ case validateModel community.symbol model of+ Ok validatedModel ->+ if LoggedIn.hasPrivateKey loggedIn then+ { model | isLoading = True }+ |> UR.init+ |> UR.addPort (savePort validatedModel loggedIn community)++ else+ UR.init model+ |> UR.addExt+ (Just ClickedSubmit+ |> LoggedIn.RequiredAuthentication+ )++ Err withError ->+ UR.init withError++ _ ->+ UR.init model+ |> UR.logImpossible msg [ "CommunityNotLoaded" ]++ GotSubmitResponse (Ok validModel) ->+ { model | isLoading = False }+ |> UR.init+ |> (case loggedIn.selectedCommunity of+ RemoteData.Success community ->+ UR.addExt+ ({ community+ | inviterReward = validModel.inviterReward+ , invitedReward = validModel.invitedReward+ , minBalance = Just validModel.minimumBalance+ }+ |> LoggedIn.CommunityLoaded+ |> LoggedIn.ExternalBroadcast+ )+ >> UR.addExt+ (LoggedIn.ShowFeedback Feedback.Success+ (shared.translators.t "community.create.success")+ )+ >> UR.addCmd (Route.pushUrl shared.navKey Route.CommunitySettings)++ _ ->+ UR.logImpossible msg [ "WithoutCommunity" ]+ )++ GotSubmitResponse (Err val) ->+ { model | isLoading = False }+ |> UR.init+ |> UR.addExt (LoggedIn.ShowFeedback Feedback.Failure (shared.translators.t "community.error_saving"))+ |> UR.logDebugValue msg val++ CompletedLoadCommunity community ->+ { model+ | inviterReward = String.fromFloat community.inviterReward+ , invitedReward = String.fromFloat community.invitedReward+ , minimumBalance =+ Maybe.map String.fromFloat community.minBalance+ |> Maybe.withDefault "0"+ , isLoading = False+ }+ |> UR.init+++withSymbolValidation : (Eos.Symbol -> Model -> Result String a) -> Field -> LoggedIn.Model -> Model -> Model+withSymbolValidation fn field loggedIn_ model_ =+ case loggedIn_.selectedCommunity of+ RemoteData.Success community ->+ fn community.symbol model_+ |> (\result -> setErrors field result model_)++ _ ->+ model_+++savePort : ValidModel -> LoggedIn.Model -> Community.Model -> Ports.JavascriptOutModel Msg+savePort validModel loggedIn community =+ let+ authorization =+ { actor = loggedIn.accountName+ , permissionName = Eos.samplePermission+ }++ asset amount =+ { amount = amount+ , symbol = community.symbol+ }+ in+ { responseAddress = ClickedSubmit+ , responseData = encodeValidModel validModel+ , data =+ Eos.encodeTransaction+ [ { accountName = loggedIn.shared.contracts.community+ , name = "update"+ , authorization = authorization+ , data =+ { asset = asset 0+ , logo = community.logo+ , name = community.name+ , description = community.description+ , inviterReward = asset validModel.inviterReward+ , invitedReward = asset validModel.invitedReward+ , hasObjectives = Eos.boolToEosBool community.hasObjectives+ , hasShop = Eos.boolToEosBool community.hasShop+ }+ |> Community.encodeUpdateData+ }+ , { accountName = loggedIn.shared.contracts.token+ , name = "update"+ , authorization = authorization+ , data =+ { maxSupply = asset 21000000.0+ , minBalance = asset validModel.minimumBalance+ }+ |> Community.encodeUpdateTokenData+ }
What about updating inviterReward and invitedReward though?
comment created time in 6 hours
pull request commentcambiatus/frontend
looks that for some browsers it will not work at all. Do you think its possible to have a flag to disable it somehow? I mean, I can use chrome, but without some mechanism to disable it, how can we test stuff that is safari only? Somehow safari is the new IE :facepalm:
I guess we could have a flag passed in on yarn start (or an env variable?). We'd need to store that in Shared, and then instead of redirecting when the user selects a community, we just load the correct one.
Also, I've noticed that the redirect is happening using the community name, and it fails on certain communities that have spaces or special characters on them like ' or 水. Are you needing some type of modification on the backend right?
Yep, right now I'm just using the community's name, I'm just waiting until subdomain is a thing in the backend
comment created time in 7 hours
Pull request review commentcambiatus/frontend
encodeCreateCommunityData c = , ( "description", Encode.string c.description ) , ( "inviter_reward", Eos.encodeAsset c.inviterReward ) , ( "invited_reward", Eos.encodeAsset c.invitedReward )+ , ( "min_balance", Eos.encodeAsset c.minBalance ) , ( "has_objectives", Eos.encodeEosBool c.hasObjectives ) , ( "has_shop", Eos.encodeEosBool c.hasShop ) , ( "has_kyc", Eos.encodeEosBool c.hasKyc ) ] +createCommunityDataDecoder : Decoder CreateCommunityData+createCommunityDataDecoder =+ Decode.succeed CreateCommunityData+ |> required "cmm_asset" Eos.decodeAsset+ |> required "creator" Eos.nameDecoder+ |> required "logo" Decode.string+ |> required "name" Decode.string+ |> required "description" Decode.string+ |> required "inviter_reward" Eos.decodeAsset+ |> required "invited_reward" Eos.decodeAsset+ |> required "min_balance" Eos.decodeAsset+ |> required "has_objectives" Eos.eosBoolDecoder+ |> required "has_shop" Eos.eosBoolDecoder+ |> required "has_kyc" Eos.eosBoolDecoder++
To create a new community, we use the newCommunity GraphQL subscription. So, if you look at Community.Editor.Msg, we fire a Msg when the user has SubmittedForm, which parses the model into a CreateCommunityData and, if it's valid, subscribes to the GraphQL newCommunity subscription, which then fires the StartedCreatingCommunity Msg, which takes that CreateCommunityData. To pass in CreateCommunityData to StartedCreatingCommunity, I'm using the port's responseData field, so we need to encode the CreateCommunityData when adding the port, and decode it on jsAddressToMsg to pass it to StartedCreatingCommunity.
I see two approaches we can take on this scenario:
- Do as I described above. This is nice because we don't have the impossible state where the user has submitted the form, and we started creating the community, but the form data isn't valid (e.g.
invitedRewardisn't a validFloat). The bad thing is we need to encode and then decode the data (which is why we needmin_balancewhen encoding, and why we need this decoder). - Use the
Modeldata. This is nice because we don't have to encode and then decodeCreateCommunityData, but we'd have to parse the form data again, which leads to impossible states.
I still prefer passing data through responseData in the port, so we could pass in an object like so
{
createCommunityData : { /* Regular CreateCommunityData, as it was before this PR, without `min_balance` */ },
minBalance: 0
}
That would mean we can keep the encoder without min_balance, and avoid those impossible states
comment created time in 7 hours
issue commentcambiatus/backend
As discussed on the call I will address in my second PR to improve-auth. Which will solve the above case and allow a single user to create sessions across multiple devices.
comment created time in 8 hours
Pull request review commentcambiatus/backend
defmodule Cambiatus.Commune do |> join(:left, [c, a], v in Validator, on: v.action_id == c.action_id) |> where( [c, a, o, v],- o.community_id == ^community_id and v.validator_id == ^account and a.is_completed == false and+ o.community_id == ^community_id and+ v.validator_id == ^account and+ a.is_completed == false and+ not (a.usages > 0 and a.usages_left == 0) and+ (is_nil(a.deadline) or fragment("NOW()") < a.deadline) and
As discussed on our call this branch will update the main feature branch improve-auth.
comment created time in 8 hours
Pull request review commentcambiatus/backend
defmodule Cambiatus.Auth.Ecdsa do """ def verify_signature(account, signature, phrase) do- {"app", :publicKeyPoints}- |> NodeJS.call([signature, phrase])- |> case do- {:ok, %{"publicKey" => pub_points, "signature" => signature_points}} ->- public_key = %EllipticCurve.PublicKey.Data{- curve: %EllipticCurve.Curve.Data{- A: 0,- B: 7,- G: %EllipticCurve.Utils.Point{- x:- 55_066_263_022_277_343_669_578_718_895_168_534_326_250_603_453_777_594_175_500_187_360_389_116_729_240,- y:- 32_670_510_020_758_816_978_083_085_130_507_043_184_471_273_380_659_243_275_938_904_335_757_337_482_424,- z: 1- },- N:- 115_792_089_237_316_195_423_570_985_008_687_907_852_837_564_279_074_904_382_605_163_141_518_161_494_337,- P:- 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_908_834_671_663,- name: :secp256k1,- oid: [1, 3, 132, 0, 10]- },- point: %EllipticCurve.Utils.Point{- x: String.to_integer(pub_points["x"]),- y: String.to_integer(pub_points["y"]),- z: 1- }- }+ pub_key =+ account+ |> EosjsAuthWrapper.get_account_info()+ |> get_pub_key() - signature_elixir = %EllipticCurve.Signature.Data{- r: String.to_integer(signature_points["r"]),- s: String.to_integer(signature_points["s"])- }+ EosjsAuthWrapper.verify(signature, phrase, pub_key)+ |> case do+ {:ok, result} -> result+ {:error, _} -> false+ end+ end - EllipticCurve.Ecdsa.verify?(phrase, signature_elixir, public_key) &&- compare_public_keys(pub_points["string"], account)+ def sign(signature, priv_key) do+ EosjsAuthWrapper.sign(signature, priv_key)+ end - {:error, _} ->- false- end+ def sign_with_random(phrase) do+ EosjsAuthWrapper.gen_rand_signature(phrase) end - @doc """- Verify public key associated to the signature private key and the public key for the account- """- defp compare_public_keys(public_key_a, account) do- {"app", :accountToPublicKey}- |> NodeJS.call([account])+ defp get_pub_key(account) do
not sure about this function returning nil bro, do you think we can improve it somehow?
I have made the fix for this
the main reason why I don't like to use private functions is that you loose stack trace if you run into an error on them
I agree but the question I have is, how do we utilize helper functions in modules without polluting the api?
comment created time in 8 hours
Pull request review commentcambiatus/backend
defmodule Cambiatus.Auth.Ecdsa do """ def verify_signature(account, signature, phrase) do- {"app", :publicKeyPoints}- |> NodeJS.call([signature, phrase])- |> case do- {:ok, %{"publicKey" => pub_points, "signature" => signature_points}} ->- public_key = %EllipticCurve.PublicKey.Data{- curve: %EllipticCurve.Curve.Data{- A: 0,- B: 7,- G: %EllipticCurve.Utils.Point{- x:- 55_066_263_022_277_343_669_578_718_895_168_534_326_250_603_453_777_594_175_500_187_360_389_116_729_240,- y:- 32_670_510_020_758_816_978_083_085_130_507_043_184_471_273_380_659_243_275_938_904_335_757_337_482_424,- z: 1- },- N:- 115_792_089_237_316_195_423_570_985_008_687_907_852_837_564_279_074_904_382_605_163_141_518_161_494_337,- P:- 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_908_834_671_663,- name: :secp256k1,- oid: [1, 3, 132, 0, 10]- },- point: %EllipticCurve.Utils.Point{- x: String.to_integer(pub_points["x"]),- y: String.to_integer(pub_points["y"]),- z: 1- }- }+ pub_key =+ account+ |> EosjsAuthWrapper.get_account_info()+ |> get_pub_key() - signature_elixir = %EllipticCurve.Signature.Data{- r: String.to_integer(signature_points["r"]),- s: String.to_integer(signature_points["s"])- }+ EosjsAuthWrapper.verify(signature, phrase, pub_key)+ |> case do+ {:ok, result} -> result+ {:error, _} -> false+ end+ end - EllipticCurve.Ecdsa.verify?(phrase, signature_elixir, public_key) &&- compare_public_keys(pub_points["string"], account)+ def sign(signature, priv_key) do+ EosjsAuthWrapper.sign(signature, priv_key)+ end - {:error, _} ->- false- end+ def sign_with_random(phrase) do+ EosjsAuthWrapper.gen_rand_signature(phrase)
same here, if the objetive is to have less lengthy words you can use aliases Fixed this by utilizing alias
also can you send a little explanation on the motivation behind random? This function is utilized to test invalid signatures
comment created time in 8 hours
Pull request review commentcambiatus/backend
defmodule Cambiatus.Auth.Ecdsa do """ def verify_signature(account, signature, phrase) do- {"app", :publicKeyPoints}- |> NodeJS.call([signature, phrase])- |> case do- {:ok, %{"publicKey" => pub_points, "signature" => signature_points}} ->- public_key = %EllipticCurve.PublicKey.Data{- curve: %EllipticCurve.Curve.Data{- A: 0,- B: 7,- G: %EllipticCurve.Utils.Point{- x:- 55_066_263_022_277_343_669_578_718_895_168_534_326_250_603_453_777_594_175_500_187_360_389_116_729_240,- y:- 32_670_510_020_758_816_978_083_085_130_507_043_184_471_273_380_659_243_275_938_904_335_757_337_482_424,- z: 1- },- N:- 115_792_089_237_316_195_423_570_985_008_687_907_852_837_564_279_074_904_382_605_163_141_518_161_494_337,- P:- 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_908_834_671_663,- name: :secp256k1,- oid: [1, 3, 132, 0, 10]- },- point: %EllipticCurve.Utils.Point{- x: String.to_integer(pub_points["x"]),- y: String.to_integer(pub_points["y"]),- z: 1- }- }+ pub_key =+ account+ |> EosjsAuthWrapper.get_account_info()+ |> get_pub_key() - signature_elixir = %EllipticCurve.Signature.Data{- r: String.to_integer(signature_points["r"]),- s: String.to_integer(signature_points["s"])- }+ EosjsAuthWrapper.verify(signature, phrase, pub_key)+ |> case do+ {:ok, result} -> result+ {:error, _} -> false+ end+ end - EllipticCurve.Ecdsa.verify?(phrase, signature_elixir, public_key) &&- compare_public_keys(pub_points["string"], account)+ def sign(signature, priv_key) do+ EosjsAuthWrapper.sign(signature, priv_key)+ end
This used to for testing the auth process
comment created time in 8 hours
Pull request review commentcambiatus/backend
defmodule Cambiatus.Auth.Ecdsa do """ def verify_signature(account, signature, phrase) do- {"app", :publicKeyPoints}- |> NodeJS.call([signature, phrase])- |> case do- {:ok, %{"publicKey" => pub_points, "signature" => signature_points}} ->- public_key = %EllipticCurve.PublicKey.Data{- curve: %EllipticCurve.Curve.Data{- A: 0,- B: 7,- G: %EllipticCurve.Utils.Point{- x:- 55_066_263_022_277_343_669_578_718_895_168_534_326_250_603_453_777_594_175_500_187_360_389_116_729_240,- y:- 32_670_510_020_758_816_978_083_085_130_507_043_184_471_273_380_659_243_275_938_904_335_757_337_482_424,- z: 1- },- N:- 115_792_089_237_316_195_423_570_985_008_687_907_852_837_564_279_074_904_382_605_163_141_518_161_494_337,- P:- 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_908_834_671_663,- name: :secp256k1,- oid: [1, 3, 132, 0, 10]- },- point: %EllipticCurve.Utils.Point{- x: String.to_integer(pub_points["x"]),- y: String.to_integer(pub_points["y"]),- z: 1- }- }+ pub_key =+ account+ |> EosjsAuthWrapper.get_account_info()+ |> get_pub_key() - signature_elixir = %EllipticCurve.Signature.Data{- r: String.to_integer(signature_points["r"]),- s: String.to_integer(signature_points["s"])- }+ EosjsAuthWrapper.verify(signature, phrase, pub_key)
I have made the update
comment created time in 8 hours
Pull request review commentcambiatus/backend
defmodule CambiatusWeb.Resolvers.Commune do context: %{current_user: current_user} }) do query = Commune.claim_analysis_query(community_id, current_user.account)++ query =+ if Map.has_key?(args, :filter) do+ args+ |> Map.get(:filter)+ |> Enum.reduce(query, fn+ {:direction, direction}, query ->+ Claim.ordered(query, direction)+ end)+ else+ query+ |> Claim.ordered(:desc)+ end
As discussed on our call this branch will update the main feature branch improve-auth.
comment created time in 8 hours
Pull request review commentcambiatus/backend
defmodule Cambiatus.Auth do |> netlink(invitation_id) end - def sign_in_v2(account, signature, phrase) do- account- |> Ecdsa.verify_signature(signature, phrase)+ def gen_auth_phrase(user) do+ user+ |> create_phrase()+ |> create_session()+ end++ def create_phrase(user) do+ phrase = Phrase.generate()+ token = AuthToken.gen_token(%{phrase: phrase})++ %UserToken{}+ |> UserToken.changeset(%{phrase: phrase, token: token, context: "auth"}, user)+ |> Repo.insert() |> case do- true ->- account- |> Accounts.get_user()- |> case do- nil ->- {:error, :not_found}+ {:ok, _} -> {:ok, {user, phrase}}+ {:error, _err} -> {:error, "Phrase exists already"}+ end+ end - user ->- {:ok, user}- end+ def create_session({:error, _} = err), do: err++ def create_session({:ok, {user, phrase}}) do+ token = AuthToken.gen_token(%{account: user.account})++ %UserToken{}+ |> UserToken.changeset(%{token: token, context: "session"}, user)+ |> Repo.insert()+ |> case do+ {:ok, _} -> {:ok, {phrase, token}}+ {:error, _err} -> {:error, "Session exists already"}+ end+ end++ def verify(token) do+ token+ |> verify_session_token()+ |> get_account_from_token()+ end++ def verify_session_token(token) do+ get_user_token(%{token: token, filter: :session})+ |> case do+ user_token when is_map(user_token) ->+ {AuthToken.verify(user_token.token, 2 * 24 * 3600), user_token}++ nil ->+ {:error, "Session not found"}+ end+ end++ def get_account_from_token({{:ok, _}, user_token}) do+ Cambiatus.Accounts.get_user(user_token.user_id)+ end++ def get_account_from_token({{:error, _}, _}) do+ {:error, "Invalid session"}+ end++ def verify_session_token(account, token) do+ %{account: account, filter: :session}+ |> get_user_token()+ |> Map.values()+ |> Enum.member?(token)+ |> case do+ true ->+ AuthToken.verify(token, 2 * 24 * 3600) false ->- {:error, "Invalid signature"}+ delete_user_token(%{account: account, filter: :auth})+ {:error, "No session found"} end end + def verify_signature(account, signature) do+ result =+ case get_user_token(%{account: account, filter: :auth}) do+ %{phrase: phrase} ->+ if Ecdsa.verify_signature(account, signature, phrase) do+ {:ok, Accounts.get_user(account)}+ else+ {:error, "Invalid signature"}+ end++ nil ->+ {:error, "No phrase found"}+ end++ delete_user_token(%{account: account, filter: :auth})+ result+ end++ def get_user_token(%{account: account, filter: :session}) do+ from("user_tokens",+ where: [context: "session", user_id: ^account],+ select: [:context, :user_id, :token]
I have extracted this into it's own module Auth.Session
comment created time in 9 hours
pull request commentcambiatus/frontend
Is there anything I need to change on my hosts configuration or something similar? the app gets redirected to app.localhost:3000 and my browser is unable to load anything
It works out of the box for me. It might be something related to the browser you're using (it works for me on Firefox and Chromium), but you can try editing your hosts file. What kind of errors are you getting? Is it a browser thing or an app thing? If it's from the app, is there something on the console?
The only thing I haven't figured out is accessing the app from another device (I used to do this with the IP address yarn start gives you) - and I guess it's at least not straightforward
comment created time in 9 hours
push eventcambiatus/frontend
commit sha 6a11d6ca7b3ac720b06794d4e3217d059b04b7cb
Fix disabled logic on image upload
push time in 10 hours
issue commentcambiatus/backend
@lucca65 I need your advise with this edge case here, when the client doesn't save their session token after sign in.
- This prevents the client from creating a new phrase because the session stills exists from sign in.
- And they can't sign unless they have their session token
One solutions is to allow phrase creation even if a session already exists.

comment created time in 12 hours
PR opened cambiatus/backend
What issue does this PR close
Updated main feature branch with master
Changes Proposed ( a list of new changes introduced by this PR)
How to test ( a list of instructions on how to test this PR)
pr created time in 13 hours
Pull request review commentcambiatus/backend
+defmodule Cambiatus.Auth.UserToken do+ @moduledoc false++ use Ecto.Schema+ import Ecto.Changeset++ alias Cambiatus.Accounts.User++ schema "user_tokens" do+ field(:phrase, :string)+ field(:token, :binary)+ field(:context, :string)+ belongs_to(:user, User, references: :account, type: :string)++ timestamps(updated_at: false)+ end++ @required_fields ~w(token context)a+ @optional_fields ~w(phrase)a
Made the necessary format
comment created time in 13 hours
Pull request review commentcambiatus/backend
defmodule CambiatusWeb.Resolvers.Accounts do end def sign_in(_, %{account: account, signature: signature}, %{context: context}) do- %{phrase: phrase, token: token} = context+ %{token: token} = context - case Auth.sign_in_v2(account, signature, phrase) do- {:error, reason} ->- CambiatusWeb.AuthToken.invalidate(token)- {:error, message: "Sign In failed", details: Cambiatus.Error.from(reason)}-- {:ok, user} ->- {:ok, %{user: user, token: CambiatusWeb.AuthToken.sign(user)}}+ with {:ok, _token_data} <- Auth.verify_session_token(account, token),+ {:ok, user} <- Auth.verify_signature(account, signature) do+ {:ok, %{user: user, token: token}}+ else+ error -> error
Made the update
comment created time in 13 hours
startedhail2u/html-best-practices
started time in 21 hours
starteddmjio/miso
started time in a day
starteddvf/blockchain
started time in 2 days
startedamericanexpress/react-albus
started time in 2 days
startedgcanti/fp-ts
started time in 2 days
startedrevelcw/react-hooks-helper
started time in 2 days
pull request commentcambiatus/frontend
@lucca65 as this is a pretty big pull request, and to have better organization, I've created a feature branch for this, and I'll develop the other features needed for this in other PRs, let me know what you think
comment created time in 2 days
create barnchcambiatus/frontend
branch : feature/support-subdomains-umbrella
created branch time in 2 days