diff --git a/backend/grant/app.py b/backend/grant/app.py index 696beca3..cf17fdd3 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -10,6 +10,7 @@ from grant.extensions import bcrypt, migrate, db, ma, mail def create_app(config_object="grant.settings"): app = Flask(__name__.split(".")[0]) app.config.from_object(config_object) + app.url_map.strict_slashes = False register_extensions(app) register_blueprints(app) register_shellcontext(app) diff --git a/backend/grant/user/views.py b/backend/grant/user/views.py index d7766f96..e81a68c7 100644 --- a/backend/grant/user/views.py +++ b/backend/grant/user/views.py @@ -4,7 +4,7 @@ from flask_yoloapi import endpoint, parameter from .models import User, SocialMedia, Avatar, users_schema, user_schema, db from grant.proposal.models import Proposal, proposal_team -from grant.utils.auth import requires_sm +from grant.utils.auth import requires_sm, verify_signed_auth, BadSignatureException blueprint = Blueprint('user', __name__, url_prefix='/api/v1/users') @@ -50,12 +50,34 @@ def get_user(user_identity): parameter('emailAddress', type=str, required=True), parameter('displayName', type=str, required=True), parameter('title', type=str, required=True), + parameter('signedMessage', type=str, required=True), + parameter('rawTypedData', type=str, required=True) ) -def create_user(account_address, email_address, display_name, title): +def create_user( + account_address, + email_address, + display_name, + title, + signed_message, + raw_typed_data +): existing_user = User.get_by_email_or_account_address(email_address=email_address, account_address=account_address) if existing_user: return {"message": "User with that address or email already exists"}, 409 + # Handle signature + try: + sig_address = verify_signed_auth(signed_message, raw_typed_data) + if sig_address.lower() != account_address.lower(): + return { + "message": "Message signature address ({sig_address}) doesn't match account_address ({account_address})".format( + sig_address=sig_address, + account_address=account_address + ) + }, 400 + except BadSignatureException: + return {"message": "Invalid message signature"}, 400 + # TODO: Handle avatar & social stuff too user = User.create( account_address=account_address, @@ -66,6 +88,30 @@ def create_user(account_address, email_address, display_name, title): result = user_schema.dump(user) return result +@blueprint.route("/auth", methods=["POST"]) +@endpoint.api( + parameter('accountAddress', type=str, required=True), + parameter('signedMessage', type=str, required=True), + parameter('rawTypedData', type=str, required=True) +) +def auth_user(account_address, signed_message, raw_typed_data): + existing_user = User.get_by_email_or_account_address(account_address=account_address) + if not existing_user: + return {"message": "No user exists with that address"}, 400 + + try: + sig_address = verify_signed_auth(signed_message, raw_typed_data) + if sig_address.lower() != account_address.lower(): + return { + "message": "Message signature address ({sig_address}) doesn't match account_address ({account_address})".format( + sig_address=sig_address, + account_address=account_address + ) + }, 400 + except BadSignatureException: + return {"message": "Invalid message signature"}, 400 + + return user_schema.dump(existing_user) @blueprint.route("/", methods=["PUT"]) @endpoint.api( diff --git a/backend/grant/utils/auth.py b/backend/grant/utils/auth.py index 55f13f45..7cf47de3 100644 --- a/backend/grant/utils/auth.py +++ b/backend/grant/utils/auth.py @@ -31,6 +31,25 @@ def verify_token(token): return data +# Custom exception for bad auth +class BadSignatureException(Exception): + pass + +def verify_signed_auth(signature, typed_data): + loaded_typed_data = ast.literal_eval(typed_data) + url = AUTH_URL + "/message/recover" + payload = json.dumps({"sig": signature, "data": loaded_typed_data}) + headers = {'content-type': 'application/json'} + response = requests.request("POST", url, data=payload, headers=headers) + json_response = response.json() + recovered_address = json_response.get('recoveredAddress') + + if not recovered_address: + raise BadSignatureException("Authorization signature is invalid") + + return recovered_address + + def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): @@ -50,24 +69,19 @@ def requires_auth(f): def requires_sm(f): @wraps(f) def decorated(*args, **kwargs): - typed_data = request.headers.get('RawTypedData', None) signature = request.headers.get('MsgSignature', None) + typed_data = request.headers.get('RawTypedData', None) if typed_data and signature: - loaded_typed_data = ast.literal_eval(typed_data) - url = AUTH_URL + "/message/recover" - payload = json.dumps({"sig": signature, "data": loaded_typed_data}) - headers = {'content-type': 'application/json'} - response = requests.request("POST", url, data=payload, headers=headers) - json_response = response.json() - recovered_address = json_response.get('recoveredAddress') + auth_address = None + try: + auth_address = verify_signed_auth(signature, typed_data) + except BadSignatureException: + return jsonify(message="Invalid auth message signature"), 401 - if not recovered_address: - return jsonify(message="No user exists with address: {}".format(recovered_address)), 401 - - user = User.get_by_email_or_account_address(account_address=recovered_address) + user = User.get_by_email_or_account_address(account_address=auth_address) if not user: - return jsonify(message="No user exists with address: {}".format(recovered_address)), 401 + return jsonify(message="No user exists with address: {}".format(auth_address)), 401 g.current_user = user return f(*args, **kwargs) diff --git a/backend/tests/test_data.py b/backend/tests/test_data.py new file mode 100644 index 00000000..cb6d5d07 --- /dev/null +++ b/backend/tests/test_data.py @@ -0,0 +1,82 @@ +import json +import random +from grant.proposal.models import CATEGORIES + +message = { + "sig": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c", + "data": { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ], + "Mail": [ + {"name": "from", "type": "Person"}, + {"name": "to", "type": "Person"}, + {"name": "contents", "type": "string"} + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } +} + +user = { + "accountAddress": '0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826', + "displayName": 'Groot', + "emailAddress": 'iam@groot.com', + "title": 'I am Groot!', + "avatar": { + "link": 'https://avatars2.githubusercontent.com/u/1393943?s=400&v=4' + }, + "socialMedias": [ + { + "link": 'https://github.com/groot' + } + ], + "signedMessage": message["sig"], + "rawTypedData": json.dumps(message["data"]) +} + +team = [user] + +milestones = [ + { + "title": "All the money straightaway", + "description": "cool stuff with it", + "date": "June 2019", + "payoutPercent": "100", + "immediatePayout": False + } +] + +proposal = { + "team": team, + "crowdFundContractAddress": "0x20000", + "content": "## My Proposal", + "title": "Give Me Money", + "milestones": milestones, + "category": random.choice(CATEGORIES) +} \ No newline at end of file diff --git a/backend/tests/user/test_required_sm_decorator.py b/backend/tests/user/test_required_sm_decorator.py index a8b090bb..edd5f2c5 100644 --- a/backend/tests/user/test_required_sm_decorator.py +++ b/backend/tests/user/test_required_sm_decorator.py @@ -1,39 +1,7 @@ import json from ..config import BaseTestConfig - -account_address = '0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826' - -message = { - "sig": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c", - "data": {"types": {"EIP712Domain": [{"name": "name", "type": "string"}, {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"}], - "Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}], - "Mail": [{"name": "from", "type": "Person"}, {"name": "to", "type": "Person"}, - {"name": "contents", "type": "string"}]}, "primaryType": "Mail", - "domain": {"name": "Ether Mail", "version": "1", "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"}, - "message": {"from": {"name": "Cow", "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"}, - "to": {"name": "Bob", "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"}, - "contents": "Hello, Bob!"}} - -} - -user = { - "accountAddress": account_address, - "displayName": 'Groot', - "emailAddress": 'iam@groot.com', - "title": 'I am Groot!', - "avatar": { - "link": 'https://avatars2.githubusercontent.com/u/1393943?s=400&v=4' - }, - "socialMedias": [ - { - "link": 'https://github.com/groot' - } - ] -} +from ..test_data import user, message class TestRequiredSignedMessageDecorator(BaseTestConfig): diff --git a/backend/tests/user/test_user_api.py b/backend/tests/user/test_user_api.py index 0a751bd4..fa0b0af6 100644 --- a/backend/tests/user/test_user_api.py +++ b/backend/tests/user/test_user_api.py @@ -1,49 +1,12 @@ import copy import json -import random -from grant.proposal.models import CATEGORIES from grant.proposal.models import Proposal from grant.user.models import User from ..config import BaseTestConfig +from ..test_data import team, proposal from mock import patch -milestones = [ - { - "title": "All the money straightaway", - "description": "cool stuff with it", - "date": "June 2019", - "payoutPercent": "100", - "immediatePayout": False - } -] - -team = [ - { - "accountAddress": "0x1", - "displayName": 'Groot', - "emailAddress": 'iam@groot.com', - "title": 'I am Groot!', - "avatar": { - "link": 'https://avatars2.githubusercontent.com/u/1393943?s=400&v=4' - }, - "socialMedias": [ - { - "link": 'https://github.com/groot' - } - ] - } -] - -proposal = { - "team": team, - "crowdFundContractAddress": "0x20000", - "content": "## My Proposal", - "title": "Give Me Money", - "milestones": milestones, - "category": random.choice(CATEGORIES) -} - class TestAPI(BaseTestConfig): def test_create_new_user_via_proposal_by_account_address(self): diff --git a/frontend/client/api/api.ts b/frontend/client/api/api.ts index 7cd024cd..9e7b05b4 100644 --- a/frontend/client/api/api.ts +++ b/frontend/client/api/api.ts @@ -63,9 +63,21 @@ export function createUser(payload: { emailAddress: string; displayName: string; title: string; - token: string; + signedMessage: string; + rawTypedData: string; }): Promise<{ data: TeamMember }> { - return axios.post(`/api/v1/users/`, payload).then(res => { + return axios.post('/api/v1/users', payload).then(res => { + res.data = formatTeamMemberFromGet(res.data); + return res; + }); +} + +export function authUser(payload: { + accountAddress: string; + signedMessage: string; + rawTypedData: string; +}): Promise<{ data: TeamMember }> { + return axios.post('/api/v1/users/auth', payload).then(res => { res.data = formatTeamMemberFromGet(res.data); return res; }); diff --git a/frontend/client/api/axios.ts b/frontend/client/api/axios.ts index 2cb799fc..14322f2d 100644 --- a/frontend/client/api/axios.ts +++ b/frontend/client/api/axios.ts @@ -5,4 +5,23 @@ const instance = axios.create({ headers: {}, }); +instance.interceptors.response.use( + // Do nothing to responses + res => res, + // Try to parse error message if possible + err => { + if (err.response && err.response.data) { + // Our backend's handled error responses + if (err.response.data.message) { + err.message = err.response.data.message; + } + // Some flask middlewares return error data like this + if (err.response.data.data) { + err.message = err.response.data.data; + } + } + return Promise.reject(err); + }, +); + export default instance; diff --git a/frontend/client/components/AuthFlow/SignIn.less b/frontend/client/components/AuthFlow/SignIn.less index 5e7f8525..55c48dca 100644 --- a/frontend/client/components/AuthFlow/SignIn.less +++ b/frontend/client/components/AuthFlow/SignIn.less @@ -1,7 +1,9 @@ +@max-width: 460px; + .SignIn { &-container { width: 100%; - max-width: 460px; + max-width: @max-width; margin: 0 auto; padding: 1rem; box-shadow: 0 1px 2px rgba(#000, 0.2); @@ -44,4 +46,9 @@ font-size: 0.8rem; text-align: center; } + + &-error { + max-width: @max-width; + margin: 1rem auto 0; + } } \ No newline at end of file diff --git a/frontend/client/components/AuthFlow/SignIn.tsx b/frontend/client/components/AuthFlow/SignIn.tsx index dee165a9..78ef6a08 100644 --- a/frontend/client/components/AuthFlow/SignIn.tsx +++ b/frontend/client/components/AuthFlow/SignIn.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Button } from 'antd'; +import { Button, Alert } from 'antd'; import { authActions } from 'modules/auth'; import { TeamMember } from 'types'; import { AppState } from 'store/reducers'; @@ -29,7 +29,7 @@ type Props = StateProps & DispatchProps & OwnProps; class SignIn extends React.Component { render() { - const { user } = this.props; + const { user, authUserError } = this.props; return (
@@ -48,6 +48,16 @@ class SignIn extends React.Component {
+ {authUserError && ( + + )} + {/* Temporarily only supporting web3, so there are no other identites

diff --git a/frontend/client/modules/auth/actions.ts b/frontend/client/modules/auth/actions.ts index 089b1dfa..9b39b128 100644 --- a/frontend/client/modules/auth/actions.ts +++ b/frontend/client/modules/auth/actions.ts @@ -3,34 +3,46 @@ import { Dispatch } from 'redux'; import { sleep } from 'utils/helpers'; import { generateAuthSignatureData } from 'utils/auth'; import { AppState } from 'store/reducers'; -import { createUser as apiCreateUser, getUser as apiGetUser } from 'api/api'; +import { + createUser as apiCreateUser, + getUser as apiGetUser, + authUser as apiAuthUser, +} from 'api/api'; import { signData } from 'modules/web3/actions'; +import { AuthSignatureData } from 'types'; type GetState = () => AppState; -const getAuthToken = (address: string, dispatch: Dispatch) => { +const getAuthSignature = ( + address: string, + dispatch: Dispatch, +): Promise => { const sigData = generateAuthSignatureData(address); return (dispatch( signData(sigData.data, sigData.types, sigData.primaryType), - ) as any) as string; + ) as any) as Promise; }; -// Auth from previous state -export function authUser(address: string, signature?: Falsy | string) { +// Auth from previous state, or request signature with new auth +export function authUser(address: string, authSignature?: Falsy | AuthSignatureData) { return async (dispatch: Dispatch) => { dispatch({ type: types.AUTH_USER_PENDING }); try { - const res = await apiGetUser(address); - if (!signature) { - signature = await getAuthToken(address, dispatch); + if (!authSignature) { + authSignature = await getAuthSignature(address, dispatch); } + const res = await apiAuthUser({ + accountAddress: address, + signedMessage: authSignature.signedMessage, + rawTypedData: JSON.stringify(authSignature.rawTypedData), + }); dispatch({ type: types.AUTH_USER_FULFILLED, payload: { user: res.data, - token: signature, + authSignature, }, }); } catch (err) { @@ -53,19 +65,20 @@ export function createUser(user: { dispatch({ type: types.CREATE_USER_PENDING }); try { - const token = await getAuthToken(user.address, dispatch); + const authSignature = await getAuthSignature(user.address, dispatch); const res = await apiCreateUser({ accountAddress: user.address, emailAddress: user.email, displayName: user.name, title: user.title, - token, + signedMessage: authSignature.signedMessage, + rawTypedData: JSON.stringify(authSignature.rawTypedData), }); dispatch({ type: types.CREATE_USER_FULFILLED, payload: { user: res.data, - token, + authSignature, }, }); } catch (err) { diff --git a/frontend/client/modules/auth/persistence.ts b/frontend/client/modules/auth/persistence.ts index 9f05aa69..ebd6b067 100644 --- a/frontend/client/modules/auth/persistence.ts +++ b/frontend/client/modules/auth/persistence.ts @@ -5,5 +5,5 @@ export const authPersistConfig: PersistConfig = { key: 'auth', storage, version: 1, - whitelist: ['token', 'tokenAddress'], + whitelist: ['authSignature', 'authSignatureAddress'], }; diff --git a/frontend/client/modules/auth/reducers.ts b/frontend/client/modules/auth/reducers.ts index 67dc3eb5..4bfa9085 100644 --- a/frontend/client/modules/auth/reducers.ts +++ b/frontend/client/modules/auth/reducers.ts @@ -1,6 +1,6 @@ import types from './types'; // TODO: Use a common User type instead of this -import { TeamMember } from 'types'; +import { TeamMember, AuthSignatureData } from 'types'; export interface AuthState { user: TeamMember | null; @@ -13,10 +13,10 @@ export interface AuthState { isCreatingUser: boolean; createUserError: string | null; - token: string | null; - tokenAddress: string | null; - isSigningToken: boolean; - signTokenError: string | null; + authSignature: AuthSignatureData | null; + authSignatureAddress: string | null; + isSigningAuth: boolean; + signAuthError: string | null; } export const INITIAL_STATE: AuthState = { @@ -30,13 +30,16 @@ export const INITIAL_STATE: AuthState = { checkedUsers: {}, isCheckingUser: false, - token: null, - tokenAddress: null, - isSigningToken: false, - signTokenError: null, + authSignature: null, + authSignatureAddress: null, + isSigningAuth: false, + signAuthError: null, }; -export default function createReducer(state: AuthState = INITIAL_STATE, action: any) { +export default function createReducer( + state: AuthState = INITIAL_STATE, + action: any, +): AuthState { switch (action.type) { case types.AUTH_USER_PENDING: return { @@ -49,8 +52,8 @@ export default function createReducer(state: AuthState = INITIAL_STATE, action: return { ...state, user: action.payload.user, - token: action.payload.token, // TODO: Make this the real token - tokenAddress: action.payload.user.ethAddress, + authSignature: action.payload.authSignature, // TODO: Make this the real token + authSignatureAddress: action.payload.user.ethAddress, isAuthingUser: false, }; case types.AUTH_USER_REJECTED: @@ -70,8 +73,8 @@ export default function createReducer(state: AuthState = INITIAL_STATE, action: return { ...state, user: action.payload.user, - token: action.payload.token, - tokenAddress: action.payload.user.ethAddress, + authSignature: action.payload.authSignature, + authSignatureAddress: action.payload.user.ethAddress, isCreatingUser: false, checkedUsers: { ...state.checkedUsers, @@ -113,30 +116,30 @@ export default function createReducer(state: AuthState = INITIAL_STATE, action: case types.SIGN_TOKEN_PENDING: return { ...state, - token: null, - isSigningToken: true, - signTokenError: null, + authSignature: null, + isSigningAuth: true, + signAuthError: null, }; case types.SIGN_TOKEN_FULFILLED: return { ...state, - token: action.payload.token, - tokenAddress: action.payload.address, - isSigningToken: false, + authSignature: action.payload.authSignature, + authSignatureAddress: action.payload.address, + isSigningAuth: false, }; case types.SIGN_TOKEN_REJECTED: return { ...state, - isSigningToken: false, - signTokenError: action.payload, + isSigningAuth: false, + signAuthError: action.payload, }; case types.LOGOUT: return { ...state, user: null, - token: null, - tokenAddress: null, + authSignature: null, + authSignatureAddress: null, }; } return state; diff --git a/frontend/client/modules/auth/sagas.ts b/frontend/client/modules/auth/sagas.ts index d42b2a31..1322d535 100644 --- a/frontend/client/modules/auth/sagas.ts +++ b/frontend/client/modules/auth/sagas.ts @@ -1,17 +1,17 @@ import { SagaIterator } from 'redux-saga'; import { select, put, all, takeEvery } from 'redux-saga/effects'; import { REHYDRATE } from 'redux-persist'; -import { getAuthTokenAddress, getAuthToken } from './selectors'; +import { getAuthSignature, getAuthSignatureAddress } from './selectors'; import { authUser } from './actions'; export function* authFromToken(): SagaIterator { - const address: ReturnType = yield select( - getAuthTokenAddress, + const address: ReturnType = yield select( + getAuthSignatureAddress, ); if (!address) { return; } - const signature: ReturnType = yield select(getAuthToken); + const signature: ReturnType = yield select(getAuthSignature); // TODO: Figure out how to type redux-saga with thunks yield put(authUser(address, signature)); diff --git a/frontend/client/modules/auth/selectors.ts b/frontend/client/modules/auth/selectors.ts index ad55007e..22b17407 100644 --- a/frontend/client/modules/auth/selectors.ts +++ b/frontend/client/modules/auth/selectors.ts @@ -1,4 +1,4 @@ import { AppState as S } from 'store/reducers'; -export const getAuthToken = (s: S) => s.auth.token; -export const getAuthTokenAddress = (s: S) => s.auth.tokenAddress; +export const getAuthSignature = (s: S) => s.auth.authSignature; +export const getAuthSignatureAddress = (s: S) => s.auth.authSignatureAddress; diff --git a/frontend/client/modules/web3/actions.ts b/frontend/client/modules/web3/actions.ts index ee2726c1..282fe6dd 100644 --- a/frontend/client/modules/web3/actions.ts +++ b/frontend/client/modules/web3/actions.ts @@ -9,7 +9,7 @@ import { fetchProposal, fetchProposals } from 'modules/proposals/actions'; import { PROPOSAL_CATEGORY } from 'api/constants'; import { AppState } from 'store/reducers'; import { Wei } from 'utils/units'; -import { TeamMember } from 'types'; +import { TeamMember, AuthSignatureData } from 'types'; type GetState = () => AppState; @@ -412,17 +412,14 @@ export function withdrawRefund(crowdFundContract: any, address: string) { }; } -// TODO: Fill me out with all param types. -// TODO: _ will be primaryType for EIP-712 -export function signData(data: any, dataTypes: any, _: string) { +// TODO: Fill out params with typed data +export function signData(data: object, dataTypes: object, primaryType: string) { return async (dispatch: Dispatch, getState: GetState) => { dispatch({ type: types.SIGN_DATA_PENDING }); const state = getState(); const { web3, accounts } = state.web3; - // Needed for EIP-712 - // const chainId = await web3.eth.net.getId(); - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { const handleErr = (err: any) => { console.error(err); dispatch({ @@ -438,28 +435,30 @@ export function signData(data: any, dataTypes: any, _: string) { throw new Error('No web3 instance available!'); } - // TODO: This typing is hella broken + const chainId = await web3.eth.net.getId(); + const rawTypedData = { + domain: { + name: 'Grant.io', + version: 1, + chainId, + }, + types: { + ...dataTypes, + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + ], + }, + message: data, + primaryType, + }; + (web3.currentProvider as any).sendAsync( { - method: 'eth_signTypedData', - params: [ - Object.keys(dataTypes).map(k => ({ - ...dataTypes[k], - value: data[k], - })), - accounts[0], - ], - // EIP-712 - // params: [JSON.stringify({ - // primaryType, - // domain: { - // origin: window.location.origin, - // version: 1, - // chainId, - // }, - // types: dataTypes, - // message: data, - // }), accounts[0]], + method: 'eth_signTypedData_v3', + params: [accounts[0], JSON.stringify(rawTypedData)], + from: accounts[0], }, (err: Error | undefined, res: any) => { if (err) { @@ -469,8 +468,12 @@ export function signData(data: any, dataTypes: any, _: string) { const msg = web3ErrorToString(res.error); return handleErr(new Error(msg)); } - dispatch({ type: types.SIGN_DATA_FULFILLED, payload: res.result }); - resolve(res.result); + const payload: AuthSignatureData = { + signedMessage: res.result, + rawTypedData, + }; + dispatch({ type: types.SIGN_DATA_FULFILLED, payload }); + resolve(payload); }, ); } catch (err) { diff --git a/frontend/client/utils/auth.ts b/frontend/client/utils/auth.ts index 7c9a990d..a97c3db5 100644 --- a/frontend/client/utils/auth.ts +++ b/frontend/client/utils/auth.ts @@ -40,15 +40,17 @@ export function generateAuthSignatureData(address: string) { return { data: { message, time }, types: { - message: { - name: 'Message Proof', - type: 'string', - }, - time: { - name: 'Time', - type: 'string', - }, + authorization: [ + { + name: 'Message Proof', + type: 'string', + }, + { + name: 'Time', + type: 'string', + }, + ], }, - primaryType: 'message', + primaryType: 'authorization', }; } diff --git a/frontend/types/api.ts b/frontend/types/api.ts new file mode 100644 index 00000000..b133af82 --- /dev/null +++ b/frontend/types/api.ts @@ -0,0 +1,4 @@ +export interface AuthSignatureData { + signedMessage: string; + rawTypedData: any; +} diff --git a/frontend/types/index.ts b/frontend/types/index.ts index 816293d1..2ccc8fa8 100644 --- a/frontend/types/index.ts +++ b/frontend/types/index.ts @@ -1,43 +1,8 @@ -import { User, TeamMember } from './user'; -import { SocialAccountMap, SOCIAL_TYPE, SocialInfo } from './social'; -import { CreateFormState } from './create'; -import { Comment, UserComment } from './comment'; -import { - MILESTONE_STATE, - Milestone, - ProposalMilestone, - CreateMilestone, -} from './milestone'; -import { Update } from './update'; -import { - Contributor, - CrowdFund, - Proposal, - ProposalWithCrowdFund, - ProposalComments, - ProposalUpdates, - UserProposal, -} from './proposal'; - -export { - User, - UserComment, - UserProposal, - TeamMember, - SocialAccountMap, - SOCIAL_TYPE, - SocialInfo, - CreateFormState, - CreateMilestone, - Contributor, - MILESTONE_STATE, - Milestone, - ProposalMilestone, - CrowdFund, - Proposal, - ProposalWithCrowdFund, - Comment, - ProposalComments, - Update, - ProposalUpdates, -}; +export * from './user'; +export * from './social'; +export * from './create'; +export * from './comment'; +export * from './milestone'; +export * from './update'; +export * from './proposal'; +export * from './api';