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

Wallet auth #55

Merged
merged 31 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c62db9f
WIP: server-side generated challenge
valiafetisov Jun 20, 2023
26a8e7c
fix max-len eslint rule
valiafetisov Jun 20, 2023
e7aec95
working resolvers (:
valiafetisov Jun 20, 2023
ae03db2
add Authentication section to the API readme
valiafetisov Jun 21, 2023
529f0a8
Challenge cleanup
valiafetisov Jun 21, 2023
fdbeed6
end-to-end api auth flow
valiafetisov Jun 21, 2023
7641457
update readme
valiafetisov Jun 21, 2023
d56b8f9
fix linter and add some logging
valiafetisov Jun 21, 2023
2bb6d66
frontend: replace signIn and signUp mutatons with createChallenge and…
valiafetisov Jun 26, 2023
f754939
frontend: sign in working with metamask
valiafetisov Jun 26, 2023
a42ec5e
frontend: add user address to the header
valiafetisov Jun 26, 2023
ea0a936
replace deprecated substr
valiafetisov Jun 26, 2023
23a4374
add icon to the button
valiafetisov Jun 26, 2023
5272ab9
always refetch me
valiafetisov Jun 26, 2023
598ce1b
WIP: half-fixed tests
valiafetisov Jun 27, 2023
002f13d
challange module 100% coverage
valiafetisov Jun 27, 2023
fc97e32
WIP: all tests are fixed, coverage fails
valiafetisov Jun 27, 2023
2c463d5
100% test coverage for models
valiafetisov Jun 27, 2023
a281433
refactor src/helpers into relevant module
valiafetisov Jun 27, 2023
ffc6ed1
fix types
valiafetisov Jun 27, 2023
865aad5
add more tests
valiafetisov Jun 27, 2023
3d3b48e
remove requirement of rpc url
valiafetisov Jun 27, 2023
5704fa6
update readme
valiafetisov Jun 27, 2023
88d2208
update readme + small improvements
valiafetisov Jun 27, 2023
e333af3
better user type
valiafetisov Jun 27, 2023
306e24c
typos
valiafetisov Jun 28, 2023
f0f6535
use matching ethers version on frontend
valiafetisov Jun 28, 2023
f48a24f
fixed types and typos
valiafetisov Jun 28, 2023
234bfe0
fix docker-compose
valiafetisov Jun 28, 2023
5d07ba0
frontend: throw correct error message if no extension is present
valiafetisov Jun 29, 2023
6393e72
update application screenshot
valiafetisov Jun 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .github/app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion api/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"import/prefer-default-export": "off",
"no-console": [
"error"
]
],
"max-len": ["error", { "code": 120 }]
}
}

2 changes: 1 addition & 1 deletion api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ COPY . .

# Run in production mode
ENV NODE_ENV=production
RUN sed --in-place 's/sqlite/postgresql/g' ./prisma/schema.prisma
RUN sed --in-place 's/sqlite/postgresql/g' ./prisma/schema.prisma

# initialize db and start the app
CMD npx prisma db push && npm run start
96 changes: 92 additions & 4 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ Some environment variables are pre-configured for the development. You can copy

- `DATABASE_URL` (required): path to the database file
- `JWT_SECRET` (required): server's jwt secret
- `PORT` (optional, default: 3000): port on which the server will run
- `AUTH_SIGNUP_ENABLED` (optional, default: `false`): if signing up mutation is allowed (i.e. user creation via endpoint is enabled)
- `JWT_EXPIRATION_PERIOD` (optional, default: `'7d'`): how soon the signed jwt token will expire
- `DEBUG` (optional): if set, enables the different more explicit logging mode where debug levels are set to `debug` for the app's logger and `query` for db logger
- `PORT` (optional, default `3000`): port on which the server will run
- `API_ORIGIN` (optional, default `http://0.0.0.0:${PORT}`): the URL at which the API is running. it's important to provide this variable in production since it influences the message signed during authorization
- `AUTH_SIGNUP_ENABLED` (optional, default: `false`): if signing up is allowed. In case it's not set, new users _cannot_ be created, but old users _can_ still sign in
- `JWT_EXPIRATION_PERIOD` (optional, default: `'7d'`): how soon JWT token will expire
- `DEBUG` (optional, default not set): if set, enables more explicit logging mode where debug levels are set to `debug` for the app's logger and `query` for db logger

### Database

Expand All @@ -54,6 +55,93 @@ npx prisma studio

The configuration is received from the `logger.config.ts` file at the root of the project. Adjust the file parameters to control the logger behaviour.

## Authentication

The API authentication is implemented using "Sign-In with Ethereum" standard described in [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361). Basically, in order to get a usual JWT token, one have to sign a message provided by the API using their etherium wallet. In practice it means:

1. Create challenge via executing `createChallenge` graphql mutation
- Example request (that have to contain your public etherium address)
```gql
mutation {
createChallenge(
address: "paste_your_ethereum_address"
) {
nonce
message
hex
}
}
```

- Example response (that contains hex-encoded message)
```json
{
"data": {
"createChallenge": {
"nonce": "6f4c7f7cd61a499290e68a2740957407",
"message": "example.com wants you to sign in with your Ethereum account...",
"hex": "0x302e302e302e302077616e74732029..."
}
}
}
```
Where `hex` is just hex-encoded `message` that actually needs to be signed

2. Sign provided message
- Either [using your metamask wallet](https://docs.metamask.io/wallet/how-to/use-siwe/)
```js
// those commands should be executed in the browser console of the graphql playground
const addresses = await provider.send('eth_requestAccounts', [])
await ethereum.request({
method: 'personal_sign',
params: [
'paste_hex_from_the_above',
addresses[0]
]
});
```

- Or using foundry command line tool called `cast` (note: you will be asked for your private key; for other auth methods, [read the cli docs](https://book.getfoundry.sh/reference/cast/cast-wallet-sign))
```sh
$ cast wallet sign -i "hex_from_the_above"
```

3. Provide signature back to the API to get usual JWT token back
- Example request
```gql
mutation {
solveChallenge(
nonce: "paste_nonce_from_step_1"
signature: "paste_signature_from_step_2"
) {
token
}
}
```

- Example response
```json
{
"data": {
"solveChallenge": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiI5ZGM1NjI3Mi1hMjBjLTRmM2YtYjM5MC1kZDc2NjE1NTA0YTYiLCJpYXQiOjE2ODczMzc2MDEsImV4cCI6MTY4Nzk0MjQwMX0.z1lJlKXnCbcex59JkU9j7hfRGhR2EBrnUE8phwPN7C0"
}
}
}
```

4. Use provided JWT token to make subsequent API requests
- Either sent as `Authorization: Bearer paste_token_from_step_3`
- Or set as `Authorization` cookie
- Example request
```gql
query {
me {
address
}
}
```

## Health endpoint

Endpoint available at `/healthz` path. Provides response if api is currently running and prisma (orm) is able to execute queries.
Loading