Skip to content

Commit

Permalink
feat: Wallet auth (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
valiafetisov authored Jun 29, 2023
1 parent 77cd773 commit 89d40a1
Show file tree
Hide file tree
Showing 44 changed files with 3,423 additions and 1,398 deletions.
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

0 comments on commit 89d40a1

Please sign in to comment.