-
Notifications
You must be signed in to change notification settings - Fork 394
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
Implement EIP-4361 support with SIWS message handling and verification #1918
base: master
Are you sure you want to change the base?
Conversation
@@ -10,7 +10,7 @@ GOTRUE_JWT_ADMIN_ROLES="supabase_admin,service_role" | |||
# Database & API connection details | |||
GOTRUE_DB_DRIVER="postgres" | |||
DB_NAMESPACE="auth" | |||
DATABASE_URL="postgres://supabase_auth_admin:root@localhost:5432/postgres" | |||
DATABASE_URL="postgres://supabase_auth_admin:root@localhost:5433/postgres" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was there a reason for this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my bad, I changed it to use the docker postgres in the repo instead of my local pg, will revert this.
internal/utilities/siws/helpers.go
Outdated
// GenerateNonce creates a random 16-byte nonce, returning a hex-encoded string. | ||
func GenerateNonce() (string, error) { | ||
b := make([]byte, 16) | ||
_, err := rand.Read(b) | ||
if err != nil { | ||
return "", err | ||
} | ||
return hex.EncodeToString(b), nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this to crypto
package / re-use something in it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the siws folder was supposed to be a separate package that could be deployed separately, I just switched it to use nonce := crypto.SecureToken()
would that work?
internal/utilities/siws/helpers.go
Outdated
validNetworks := map[string]bool{ | ||
"mainnet": true, | ||
"devnet": true, | ||
"testnet": true, | ||
} | ||
return validNetworks[strings.ToLower(network)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a switch instead, it's a lot more efficient than maps.
"github.com/ethereum/go-ethereum/crypto" | ||
) | ||
|
||
func VerifySignature(message string, signature string, address string) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, 100% coverage needed here.
internal/conf/configuration.go
Outdated
type EIP4361Configuration struct { | ||
Enabled bool `json:"enabled" default:"false" split_words:"true"` | ||
Domain string `json:"domain" required:"true" split_words:"true"` | ||
Statement string `json:"statement" split_words:"true"` | ||
Version string `json:"version" default:"1" split_words:"true"` | ||
Timeout time.Duration `json:"timeout" default:"300s" split_words:"true"` | ||
|
||
// Comma-separated list of supported chains (e.g. "ethereum:1,ethereum:137,solana:mainnet") | ||
SupportedChains string `json:"supported_chains" split_words:"true"` | ||
DefaultChain string `json:"default_chain" split_words:"true"` | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So is EIP4361 fully chain independent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So is EIP4361 fully chain independent?
Supposedly so, ETH implemented it first then SOL followed after, with very minimal deviation.
However, methods to check addresses/verify them are different per chain, I'm also expecting a future requirement to get account data so that's why i structured it this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really good! Great direction!
A few general notes:
- We need to think a bit about how the config is encoded. I get that
EIP<numbers>
makes a lot of sense for people deep in this matter, but it's not very descriptive for regular programmers that are self-hosting Supabase Auth. I'd say let's just doGOTRUE_EXTERNAL_WEB3_<chain>
or something. If there's a new EIP later on, we'll think about its config options then. - Similarly
POST /token?grant_type=<this should be readable by regular programmers>
.siws
,web3
,siwe
, things of this sort are better thaneip<numbers>
. This value will also be mapped into auth-js as well. POST /token?grant_type=<web3 type>
should only take in JSON, not URL forms. All APIs currently only use JSON, so this is a weird exception. Is there a particular reason we can't use JSON here?utilities/siws
is a weird name for a package that doeseip<numbers>
. Maybe just move this under/internal/web3
and it will house this and any future additions.
What is the best way to test this? Any simple test app you can host somewhere?
This makes sense, originally i thought of this heirarchy: Web3 > EIP4361 > Sign in with solana /sign in with ethereum, as there's maybe other web3 modes of auth that aren't eip4361 compliant, eth/sol go under eip4361, but what you said makes sense, I think web3 is friendly here.
I'm using JSON, is there something i missed? func (a *API) EIP4361Grant(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
db := a.db.WithContext(ctx)
params := &Web3GrantParams{}
if err := retrieveRequestParams(r, params); err != nil {
return err
}
...
I'll look into it and get back to you. |
I saw this which caught my eye. https://github.com/supabase/auth/pull/1918/files#diff-db4fba83e08f520d74d66c9283e5dcd938e1746de1c4e7c481cce8aff142b5a1R71-R73
No need to go into |
…kage and remove legacy code
…legacy web3 references
What kind of change does this PR introduce?
EIP4361 Auth on the backend via SIWS That can easily be extended further for any other EIP4361 compliant sign-in method.
What is the current behavior?
No onchain auth available.
What is the new behavior?
I've added a new grant type ?grant_type=eip4361, which takes the siws message and uses that to create/authenticate users using the existing methods, I've avoided modifying existing structures as much as possible unless necessary.
In the root folder, there is a external_eip4361_siws_example.go
uncomment the last 3 lines and run it using go run and it will provide an example SIWS message you can test.
built it, spun up the server:
Response:
Additional context
To support this, and for it to be easily extendable, I've added this to the .env.example
since siws/siwe use eip4361, i added the grant type eip4361, which can extend eth/sol and any compatible network, which can be specified in the .env
then based on the chosen network e.g solana:mainnet the appropriate validation will be used.
I've implemented a siws package inside internal/utilities/siws that implement many of the necessary siws functions (need to review them to double check the validations).
for solana, I tried a no-dependency validation however i added the btc base58 package
as for ethereum, I'm, using the ethereum-go package, which is widely supported, but perhaps we can omit and try to implement a native validation without the dep.
I haven't worked much with GO specifically, but I'm the author of https://deauth.vercel.app/, which has won 4th place in the Infra track of the Solana Hyperdrive Hackathon before.
Further considerations
Willing to hear your opinions, and revise the conventions. 🙏