Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Eth wallet support #282

Closed
wants to merge 60 commits into from
Closed

Conversation

HarryET
Copy link
Contributor

@HarryET HarryET commented Nov 19, 2021

What kind of change does this PR introduce?

  • Support for eth authentication

Adds new endpoints:

  • POST /nonce
  • GET /nonce/{id}
  • POST /eth

What is the current behavior?

  • No support for eth login

What is the new behavior?

  • Adds eth login support

Additional context

* This is the first attempt and has flaws will most likely be reverted to try again!
Use crypto secure token function
Also adds them to the example.env file
@Rahat-ch
Copy link

Looking great so far!

A couple things to keep in mind:

Nonce should regenerate on every auth request
For the user in this case we should look at saving either the wallet address or ens address if it is available. That info will come from the client side implementation but I think is something we should keep in mind

Initial work on getting nonces stored in the database, also did some changes to the api route with a todo to implement saving to the database.
* Validating Signature, Nonce & Wallet Address
* Generating Nonce
* Starting to save nonce to database
* new Audit log type
* New deps
* Update SQL
* Update nonce struct
* New migration for nonce
* User signup with Wallet Address
* Get nonce by ID
* Remove consumed_at
@HarryET
Copy link
Contributor Author

HarryET commented Nov 20, 2021

I've now done as much as I can for the API without starting to make changes to https://github.com/Supabase/gotrue-js, I know @Rahat-ch said they wanted to try this. However, I think it could be done faster and with less friction If I was to start the PR and @Rahat-ch can take over and optimise it later on as its inevitable I'll make a few mistakes

@HarryET
Copy link
Contributor Author

HarryET commented Nov 20, 2021

Looking great so far!

A couple things to keep in mind:

Nonce should regenerate on every auth request For the user in this case we should look at saving either the wallet address or ens address if it is available. That info will come from the client side implementation but I think is something we should keep in mind

That is how everything is working! It should be even more clear now

@HarryET
Copy link
Contributor Author

HarryET commented Nov 20, 2021

The initial JS lib changes are being worked on supabase/auth-js#185, afaik @Rahat-ch was going to take over so here was the start of a testing file I made. Also that PR will needs tons of changed reach out if you need perms to my branch!

import { GoTrueClient } from ".";

const client = new GoTrueClient({
    url: "http://localhost:9999"
});

(async () => {
    const nonce = await client.getNonce();
    // TODO: Sign Nonce
    const {session, error} = await client.signIn({
        web3: {
            wallet_address: "",
            nonce: nonce.data!.nonce,
            signature: ""
        }
    });
    console.log(JSON.stringify(nonce))
})()

Create migration to add the new wallet_address on users table
@HarryET
Copy link
Contributor Author

HarryET commented Nov 20, 2021

Unsure as to why tests are failing currently, will review this in more detail when it's time to think about moving the PR out of the draft stage

* Migrate Web3 -> Eth
* Change nonce to support ETH standards
* Misc Cleanup
* Fixes validation, sends not finished message when logging in as rest of auth needs to be fixed
@HarryET HarryET changed the title feat: web3/eth wallet support feat: Eth wallet support Nov 27, 2021
@HarryET HarryET marked this pull request as ready for review November 27, 2021 19:29
@HarryET
Copy link
Contributor Author

HarryET commented Nov 28, 2021

Ready for review now 🎉. There is a demo in this tweet: https://twitter.com/i/status/1464678182372655110

@gwendall
Copy link

Awesome job! Do you think we could also customize the message to sign there?

@HarryET
Copy link
Contributor Author

HarryET commented Nov 28, 2021

Awesome job! Do you think we could also customize the message to sign there?

No, as the nonce conforms to standards and it would be hard to track if you allowed custom "nonces"

@gwendall
Copy link

Yup, the server has to know this message too. Would be awesome if it could be somehow integrated in the supabase UI somewhere - ie if we could define the message we want on the app with a placeholder for the nonce like "hey, please sign this message. Nonce: {nonce}".

@HarryET
Copy link
Contributor Author

HarryET commented Nov 28, 2021

Yup, the server has to know this message too. Would be awesome if it could be somehow integrated in the supabase UI somewhere - ie if we could define the message we want on the app with a placeholder for the nonce like "hey, please sign this message. Nonce: {nonce}".

Can you elaborate on what you mean? The client dose receive the nonce btw!

Also this was made with help from the wallet connect team. Originally the nonce was just a random value but after recommendation from them it was moved to how it is now and it follows this: https://github.com/ethereum/EIPs/blob/75e2bd7f6afc6b6ed3f5a064e3ddf8c60a562e00/EIPS/eip-4361.md

You can see the reference implementation here: https://github.com/HarryET/walletconnect-supabase-example
The specific auth logic is here: https://github.com/HarryET/walletconnect-supabase-example/blob/master/src/App.tsx#L46-L86

@HarryET
Copy link
Contributor Author

HarryET commented Mar 12, 2022

This can now be tested on the demo, the only thing missing in this PR are tests. Any other comments are appreciated in regards to parts of the PR that are done incorrectly or are missing.

Flagged as issue by SpruceID as not conformant to spec
@Xanewok
Copy link
Contributor

Xanewok commented Apr 26, 2022

Should this be marked as ready for review? Thanks for your work btw!

@HarryET
Copy link
Contributor Author

HarryET commented Apr 26, 2022

Should this be marked as ready for review? Thanks for your work btw!

No @Xanewok, this will stay as draft till the Supabase team are happy with the security side. There open internal channels of communication to keep this PR moving. Refer too @kiwicopple's reason for closing this issue here for an explanation from the Supabase team: supabase/supabase#6474 (comment)

@ghost
Copy link

ghost commented May 2, 2022

Really need this. What can we do to merge this? @kiwicopple

Even though the eip is still in draft, it is already widely used.

I need the magic.link wallet though this also means that I cannot use magic.link to authenticate with Supabase while having access to the wallet. This pr would allow me to use the magic.link wallet while using this wallet to sign the user into Supabase.

@HarryET
Copy link
Contributor Author

HarryET commented May 3, 2022

Hello @byterose, I see you have also commented on the demo repo. This PR changes the core of GoTrue by adding a brand new method of auth that could have added vulnerabilities to GoTrue; as such this PR may take some time. Having people keep replying here with some version of "I need this" will not help. Also pinging the Supabase team will not help speed this up. As I have previously started, there are internal communication tracks between myself, Supabase and other parties. And updates will be posted here as it is applicable for people to know.

@supabase supabase locked and limited conversation to collaborators May 3, 2022
@kiwicopple
Copy link
Member

Hey all, dropping a note here so that everyone knows where we're at. At this stage we are putting this on hold for the following reasons:

  1. Prioritisation: we are prioritising SAML/SSO before web3. There are more users asking for this. We also plan to make this server multi-tenant, which is a big task and ideally happens before we introduce more complexity.
  2. Backwards compatibility: this PR is a significant change to GoTrue, and we will need to do a full security audit to ensure that it doesn't introduce security concerns for any existing auth methods.
  3. Forwards compatibility - there are a few things that came up from an initial investigation:
  • EIP-4361 is still in draft (specifically the EIP calls out "This EIP is not recommended for general use or implementation as it is likely to change.")
  • siwe-go (used in this PR) hasn't had a formal security review yet (see Disclaimer), and perhaps doesn't follow the EIP spec, which would make it hard to add other providers

We'll revisit this once we have completed 1!

Copy link
Contributor

@hf hf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @HarryET, here's my feedback (I'm back from holiday!). Let me know on Slack or here if you want to discuss more about it.

"bytes"
"encoding/json"
"github.com/gofrs/uuid"
"github.com/netlify/gotrue/api/crypto_provider"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This style of naming packages is not common in Go code. Can you rename it to crypto-provider or something else?

Comment on lines +31 to +35
body, err := ioutil.ReadAll(r.Body)
jsonDecoder := json.NewDecoder(bytes.NewReader(body))
if err = jsonDecoder.Decode(params); err != nil {
return badRequestError("Could not read verification params: %v", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the getBody function instead.


func (a *API) Crypto(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
config := a.getConfig(ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK this method is no longer there.

Comment on lines +85 to +111
user, uerr = a.signupNewUser(ctx, tx, &SignupParams{
CryptoAddress: accountInfo.Address,
Provider: "crypto",
CryptoProvider: params.Provider,
Aud: aud,
})
didUserExist = false

identity, terr := a.createNewIdentity(tx, user, params.Provider, map[string]interface{}{"sub": accountInfo.Address, "address": accountInfo.Address})
if terr != nil {
return terr
}

user.Identities = []models.Identity{*identity}

if uerr = user.Confirm(tx); uerr != nil {
return uerr
}

if uerr = models.NewAuditLogEntry(r, tx, instanceID, user, models.UserSignedUpAction, "", nil); uerr != nil {
return uerr
}
if uerr = triggerEventHooks(ctx, tx, SignupEvent, user, instanceID, config); uerr != nil {
return uerr
}

return uerr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This account creation logic does not take many edge cases into account which we're trying to resolve systematically in GoTrue. Accounts may need to be linked -- assigned a linking domain and linking rules -- or be identified separately (which today is only possible with SSO accounts).

}

func (e *EthProvider) GenerateNonce(_ *http.Request, instanceId uuid.UUID, options CryptoNonceOptions) (*models.Nonce, error) {
uri, err := url.Parse(options.Url)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably ParseRequestURI is required here?

Comment on lines +288 to +290
case "crypto":
user, err = models.NewUser(instanceID, "", "", params.Password, params.Aud, params.Data)
cryptoAddress, err = models.NewCryptoAddress(instanceID, user.ID, params.CryptoProvider, params.CryptoAddress)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not put crypto "sign up" in the sign up method as it's getting complex. Can you pull this out in another API that can be easily turned on / off instead? Does that make sense?

Are you sure we want users with no email and phone address to be added here? Sounds like a dangerous thing if someone can just pile data in the database without any identifying information?

Comment on lines -3 to 15
CREATE TABLE IF NOT EXISTS auth.identities (
id text NOT NULL,
user_id uuid NOT NULL,
identity_data JSONB NOT NULL,
provider text NOT NULL,
CREATE TABLE IF NOT EXISTS auth.identities
(
id text NOT NULL,
user_id uuid NOT NULL,
identity_data JSONB NOT NULL,
provider text NOT NULL,
last_sign_in_at timestamptz NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
CONSTRAINT identities_pkey PRIMARY KEY (provider, id),
CONSTRAINT identities_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE
CONSTRAINT identities_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE
);
COMMENT ON TABLE auth.identities is 'Auth: Stores identities associated to a user.';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing migration files must not have any changes.

Comment on lines +1 to +14
CREATE TABLE auth.crypto_addresses
(
instance_id uuid NULL,
id uuid NOT NULL,

account_id uuid NOT NULL REFERENCES auth.users (id),
address varchar(255) NOT NULL UNIQUE,
provider varchar(255) NOT NULL,

created_at timestamptz NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS crypto_addresses_instance_id_idx ON auth.crypto_addresses USING btree (instance_id);
comment on table auth.crypto_addresses is 'Auth: Stored Cryptocurrency addresses for web3 authentication.';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, these seem to belong better inside auth.identities as a (id, provider) pair. For example provider could be web3:<chain> and ID could be the 0x... address.

Comment on lines +13 to +22
type CryptoAddress struct {
InstanceID uuid.UUID `json:"-" db:"instance_id"`
ID uuid.UUID `json:"id" db:"id"`

AccountId uuid.UUID `json:"account_id" db:"account_id"`
Address string `json:"address" db:"address"`
Provider string `json:"provider" db:"provider"`

CreatedAt time.Time `json:"created_at" db:"created_at"`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto. This whole logic seems to be a specialization of the auth.identities table.

Comment on lines +15 to +31
type Nonce struct {
InstanceID uuid.UUID `json:"-" db:"instance_id"`
ID uuid.UUID `json:"id" db:"id"`
Provider string `json:"provider" db:"provider"`

Url string `json:"url" db:"uri"`
Hostname string `json:"hostname" db:"hostname"`

ChainId int `json:"chain_id" db:"chain_id"`
Address string `json:"address" db:"address"`
Namespace string `json:"namespace" db:"namespace"`
Nonce string `json:"nonce" db:"nonce"`

CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are nonces stored in the database? Can't they be cryptographically signed and verified without storing anything in the database?

@kiwicopple kiwicopple closed this Mar 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
For discussion To discuss during next Auth catchup
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Ethereum login