2018-10-19 22:18:27 -07:00
|
|
|
import ast
|
|
|
|
import json
|
2018-09-10 09:55:26 -07:00
|
|
|
from functools import wraps
|
|
|
|
|
2018-10-19 22:18:27 -07:00
|
|
|
import requests
|
2018-09-10 09:55:26 -07:00
|
|
|
from flask import request, g, jsonify
|
|
|
|
from itsdangerous import SignatureExpired, BadSignature
|
|
|
|
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
|
|
|
|
|
2018-10-19 22:18:27 -07:00
|
|
|
from grant.settings import SECRET_KEY, AUTH_URL
|
2018-11-13 06:17:06 -08:00
|
|
|
from ..proposal.models import Proposal
|
2018-10-19 22:18:27 -07:00
|
|
|
from ..user.models import User
|
2018-09-10 09:55:26 -07:00
|
|
|
|
|
|
|
TWO_WEEKS = 1209600
|
|
|
|
|
|
|
|
|
|
|
|
def generate_token(user, expiration=TWO_WEEKS):
|
|
|
|
s = Serializer(SECRET_KEY, expires_in=expiration)
|
|
|
|
token = s.dumps({
|
|
|
|
'id': user.id,
|
|
|
|
'email': user.email,
|
|
|
|
}).decode('utf-8')
|
|
|
|
return token
|
|
|
|
|
|
|
|
|
|
|
|
def verify_token(token):
|
|
|
|
s = Serializer(SECRET_KEY)
|
|
|
|
try:
|
|
|
|
data = s.loads(token)
|
|
|
|
except (BadSignature, SignatureExpired):
|
|
|
|
return None
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
2018-11-07 11:08:42 -08:00
|
|
|
# Custom exception for bad auth
|
|
|
|
class BadSignatureException(Exception):
|
|
|
|
pass
|
|
|
|
|
2018-11-13 06:17:06 -08:00
|
|
|
|
2018-11-07 11:08:42 -08:00
|
|
|
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
|
2018-10-19 22:18:27 -07:00
|
|
|
|
|
|
|
|
2018-11-13 06:17:06 -08:00
|
|
|
# Decorator that requires you to have EIP-712 message signature headers for auth
|
2018-10-19 22:18:27 -07:00
|
|
|
def requires_sm(f):
|
|
|
|
@wraps(f)
|
|
|
|
def decorated(*args, **kwargs):
|
|
|
|
signature = request.headers.get('MsgSignature', None)
|
2018-11-07 11:08:42 -08:00
|
|
|
typed_data = request.headers.get('RawTypedData', None)
|
2018-10-19 22:18:27 -07:00
|
|
|
|
|
|
|
if typed_data and signature:
|
2018-11-07 11:08:42 -08:00
|
|
|
try:
|
|
|
|
auth_address = verify_signed_auth(signature, typed_data)
|
|
|
|
except BadSignatureException:
|
|
|
|
return jsonify(message="Invalid auth message signature"), 401
|
|
|
|
|
2018-11-13 06:17:06 -08:00
|
|
|
user = User.get_by_identifier(account_address=auth_address)
|
2018-10-19 22:18:27 -07:00
|
|
|
if not user:
|
2018-11-07 11:08:42 -08:00
|
|
|
return jsonify(message="No user exists with address: {}".format(auth_address)), 401
|
2018-10-19 22:18:27 -07:00
|
|
|
|
|
|
|
g.current_user = user
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
|
|
|
|
return jsonify(message="Authentication is required to access this resource"), 401
|
|
|
|
|
|
|
|
return decorated
|
2018-11-13 06:17:06 -08:00
|
|
|
|
|
|
|
|
|
|
|
# Decorator that requires you to be the user you're interacting with
|
|
|
|
def requires_same_user_auth(f):
|
|
|
|
@wraps(f)
|
|
|
|
def decorated(*args, **kwargs):
|
|
|
|
user_identity = kwargs["user_identity"]
|
|
|
|
if not user_identity:
|
|
|
|
return jsonify(message="Decorator requires_same_user_auth requires path variable <user_identity>"), 500
|
|
|
|
|
|
|
|
user = User.get_by_identifier(account_address=user_identity, email_address=user_identity)
|
|
|
|
if user.id != g.current_user.id:
|
|
|
|
return jsonify(message="You are not authorized to modify this user"), 403
|
|
|
|
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
|
|
|
|
return requires_sm(decorated)
|
|
|
|
|
|
|
|
|
|
|
|
# Decorator that requires you to be a team member of a proposal to access
|
|
|
|
def requires_team_member_auth(f):
|
|
|
|
@wraps(f)
|
|
|
|
def decorated(*args, **kwargs):
|
|
|
|
proposal_id = kwargs["proposal_id"]
|
|
|
|
if not proposal_id:
|
|
|
|
return jsonify(message="Decorator requires_team_member_auth requires path variable <proposal_id>"), 500
|
|
|
|
|
|
|
|
proposal = Proposal.query.filter_by(id=proposal_id).first()
|
|
|
|
if not proposal:
|
|
|
|
return jsonify(message="No proposal exists with id: {}".format(proposal_id)), 404
|
|
|
|
|
|
|
|
if not g.current_user in proposal.team:
|
|
|
|
return jsonify(message="You are not authorized to modify this proposal"), 403
|
|
|
|
|
|
|
|
g.current_proposal = proposal
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
|
|
|
|
return requires_sm(decorated)
|