Skip to content

Commit d07bf06

Browse files
authored
Merge pull request #20 from tiphub-io/blockstack-validate
Blockstack server-side validation
2 parents 2da412b + d962d2a commit d07bf06

File tree

5 files changed

+63
-8
lines changed

5 files changed

+63
-8
lines changed

backend/boltathon/util/blockstack.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import requests
2+
from jose import jwt
3+
from flask import current_app
4+
from boltathon.util.errors import RequestError
5+
6+
# Given a Blockstack private key, validate that it was signed by the same
7+
# key as owns a username
8+
def validate_blockstack_auth(id, username, token):
9+
try:
10+
# Match up data with arguments
11+
token = jwt.decode(token, [], options={ 'verify_signature': False })
12+
if username != token.get('username'):
13+
return False
14+
if id not in token.get('profile_url'):
15+
return False
16+
17+
# Match up data with Blockstack's atlas node
18+
res = requests.get(f'https://core.blockstack.org/v1/names/{username}')
19+
res = res.json()
20+
if res.get('address') != id:
21+
return False
22+
23+
return True
24+
except Exception as e:
25+
print(e)
26+
current_app.logger.error(f'Could not lookup username {username} from blockstack.org: {e}')
27+
raise RequestError(code=500, message='Failed to lookup Blockstack identity')

backend/boltathon/views/api.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from boltathon.util.node import get_pubkey_from_credentials, make_invoice, lookup_invoice
99
from boltathon.util.errors import RequestError
1010
from boltathon.util.mail import send_email_once
11+
from boltathon.util.blockstack import validate_blockstack_auth
1112
from boltathon.models.user import User, self_user_schema, public_user_schema, public_users_schema
1213
from boltathon.models.connection import Connection, public_connections_schema
1314
from boltathon.models.tip import Tip, tip_schema, tips_schema
@@ -164,10 +165,14 @@ def get_tip(tip_id):
164165
@use_args({
165166
'id': fields.Str(required=True),
166167
'username': fields.Str(required=True),
168+
'token': fields.Str(required=True),
167169
})
168170
def blockstack_auth(args):
169-
# TODO: Server-side verification
170-
# Find or create a new user and add the connection
171+
# Assert that they generated a valid token
172+
if not validate_blockstack_auth(args.get('id'), args.get('username'), args.get('token')):
173+
raise RequestError(code=400, message='Invalid Blockstack token provided')
174+
175+
# Find or create a new user and add the connection
171176
user = Connection.get_user_by_connection(
172177
site='blockstack',
173178
site_id=args.get('id'),

backend/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ bitstring==3.1.5
1919
webargs==5.2.0
2020
flask-cors==3.0.7
2121
sendgrid==6.0.4
22+
23+
# JWT package that supports ES256K for blockstack
24+
git+git://github.com/pohutukawa/python-jose@28cd302d#egg=python-jose

frontend/src/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class API {
9393
return this.request<SelfUser>('POST', '/auth/blockstack', {
9494
id: data.identityAddress,
9595
username: data.username,
96+
token: data.authResponseToken,
9697
});
9798
}
9899

frontend/src/pages/BlockstackAuth.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,39 @@
11
import React from 'react';
2-
import { Loader } from 'semantic-ui-react';
2+
import { Loader, Message } from 'semantic-ui-react';
33
import { withRouter, RouteComponentProps } from 'react-router';
44
import * as blockstack from 'blockstack.js';
55
import { UserData } from 'blockstack.js/lib/auth/authApp';
6-
import api, { User } from '../api';
6+
import api from '../api';
7+
8+
interface State {
9+
error: string | null;
10+
}
11+
12+
class BlockstackAuth extends React.Component<RouteComponentProps, State> {
13+
state: State = {
14+
error: null,
15+
};
716

8-
class BlockstackAuth extends React.Component<RouteComponentProps> {
917
componentDidMount() {
1018
if (blockstack.isUserSignedIn()) {
1119
this.auth(blockstack.loadUserData());
1220
} else if (blockstack.isSignInPending()) {
13-
blockstack.handlePendingSignIn().then(this.auth)
21+
blockstack.handlePendingSignIn().then(this.auth);
1422
}
1523
}
1624

1725
render() {
26+
const { error } = this.state;
27+
28+
if (error) {
29+
return (
30+
<Message negative>
31+
<Message.Header>Failed to authenticate with Blockstack</Message.Header>
32+
<Message.Content>{error}</Message.Content>
33+
</Message>
34+
);
35+
}
36+
1837
return <Loader size="huge" active>Connecting to Blockstack...</Loader>;
1938
}
2039

@@ -23,9 +42,9 @@ class BlockstackAuth extends React.Component<RouteComponentProps> {
2342
api.blockstackAuth(data).then(res => {
2443
history.replace('/user/me');
2544
}).catch(err => {
26-
alert(err.message);
45+
this.setState({ error: err.message });
2746
});
2847
};
2948
};
3049

31-
export default withRouter(BlockstackAuth);
50+
export default withRouter(BlockstackAuth);

0 commit comments

Comments
 (0)