-
Notifications
You must be signed in to change notification settings - Fork 1k
[CLI] Ethereum auth setup updates #3337
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
Changes from all commits
5f029de
70df416
e450fb4
65bb004
f8f32a2
8178671
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| export const schema = gql` | ||
| type AuthChallengeResult { | ||
| message: String! | ||
| } | ||
|
|
||
| type AuthVerifyResult { | ||
| token: String! | ||
| } | ||
|
|
||
| input AuthChallengeInput { | ||
| address: String! | ||
| options: JSON | ||
| } | ||
|
|
||
| input AuthVerifyInput { | ||
| signature: String! | ||
| address: String! | ||
| options: JSON | ||
| } | ||
|
|
||
| type Mutation { | ||
| authChallenge(input: AuthChallengeInput!): AuthChallengeResult | ||
| authVerify(input: AuthVerifyInput!): AuthVerifyResult | ||
| } | ||
| ` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| import { AuthenticationError } from '@redwoodjs/api' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you considered instead of having this be a service with SDL, it is just a serverless function? Does it need to be exposed to as GraphQL?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah you bring up a good point. Now that I've learned how to use dbAuth, I'm thinking that I should completely re-haul the ETH Auth to piggyback on dbAuth. This is a lot, so I may need to move this to draft and come back to it
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. We can move to draft and definitely review when you've had some time to rethink if need. |
||
|
|
||
| import { bufferToHex } from 'ethereumjs-util' | ||
| import { recoverPersonalSignature } from 'eth-sig-util' | ||
| import jwt from 'jsonwebtoken' | ||
|
|
||
| import { db } from 'src/lib/db' | ||
|
|
||
| const NONCE_MESSAGE = | ||
| 'Please prove you control this wallet by signing this random text: ' | ||
|
|
||
| const getNonceMessage = (nonce, options) => { | ||
| let optionsText = '' | ||
| if (options) | ||
| optionsText = | ||
| '&' + | ||
| Object.keys(options) | ||
| .map( | ||
| (key) => | ||
| encodeURIComponent(key) + '=' + encodeURIComponent(options[key]) | ||
| ) | ||
| .join('&') | ||
| return NONCE_MESSAGE + nonce + optionsText | ||
| } | ||
|
|
||
| export const beforeResolver = (rules) => { | ||
| rules.skip({ only: ['authChallenge', 'authVerify'] }) | ||
| } | ||
|
|
||
| export const authChallenge = async ({ | ||
| input: { address: addressRaw, options }, | ||
| }) => { | ||
| const nonce = Math.floor(Math.random() * 1000000).toString() | ||
| const address = addressRaw.toLowerCase() | ||
| await db.user.upsert({ | ||
| where: { address }, | ||
| update: { | ||
| authDetail: { | ||
| update: { | ||
| nonce, | ||
| timestamp: new Date(), | ||
| }, | ||
| }, | ||
| }, | ||
| create: { | ||
| address, | ||
| authDetail: { | ||
| create: { | ||
| nonce, | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| return { message: getNonceMessage(nonce, options) } | ||
| } | ||
|
|
||
| export const authVerify = async ({ | ||
| input: { signature, address: addressRaw, options }, | ||
| }) => { | ||
| try { | ||
| const address = addressRaw.toLowerCase() | ||
| const user = await db.user.findUnique({ | ||
| where: { address }, | ||
| }) | ||
| if (!user) throw new Error('No authentication started') | ||
| const { nonce, timestamp } = await db.user | ||
| .findUnique({ | ||
| where: { address }, | ||
| }) | ||
| .authDetail() | ||
|
|
||
| const startTime = new Date(timestamp) | ||
| if (new Date() - startTime > 5 * 60 * 1000) | ||
| throw new Error( | ||
| 'The challenge must have been generated within the last 5 minutes' | ||
| ) | ||
| const signerAddress = recoverPersonalSignature({ | ||
| data: bufferToHex(Buffer.from(getNonceMessage(nonce, options), 'utf8')), | ||
| sig: signature, | ||
| }) | ||
| if (address !== signerAddress.toLowerCase()) | ||
| throw new Error('invalid signature') | ||
|
|
||
| const token = jwt.sign( | ||
| { address, id: user.id }, | ||
| process.env.ETHEREUM_JWT_SECRET, | ||
| { | ||
| expiresIn: '5h', | ||
| } | ||
| ) | ||
| return { token } | ||
| } catch (e) { | ||
| throw new Error(e) | ||
| } | ||
| } | ||
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.
Hmm, this feels out of place here. Understood you'll need service and graphql path included, but hardcoding these for Ethereum Auth here probably isn't the right approach. Maybe modify files() so you can pass config from providers/etherum.js that effectively adds to the returned
files? Something like this would be more extensible for any/all auth providers.Any other (better) thoughts about this @dthyresson?
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.
Oh you weren't supposed to find that 🙃
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, the need here is to have the setup command:
?