@@ -4,9 +4,10 @@ import { ShellParent, AppHomeWrapper } from '@blockstack/ui'
44import { Initial , LegacyGaia } from './views'
55import { bindActionCreators } from 'redux'
66import { connect } from 'react-redux'
7+ import { randomBytes } from 'crypto'
78import { AuthActions } from './store/auth'
89import { IdentityActions } from '../profiles/store/identity'
9- import { decodeToken } from 'jsontokens'
10+ import { decodeToken , TokenSigner } from 'jsontokens'
1011import { parseZoneFile } from 'zone-file'
1112import queryString from 'query-string'
1213import {
@@ -15,7 +16,8 @@ import {
1516 redirectUserToApp ,
1617 getAppBucketUrl ,
1718 isLaterVersion ,
18- updateQueryStringParameter
19+ updateQueryStringParameter ,
20+ getPublicKeyFromPrivate
1921} from 'blockstack'
2022import { AppsNode } from '@utils/account-utils'
2123import {
@@ -27,7 +29,10 @@ import { HDNode } from 'bitcoinjs-lib'
2729import log4js from 'log4js'
2830import { uploadProfile } from '../account/utils'
2931import { signProfileForUpload } from '@utils'
30- import { validateScopes , appRequestSupportsDirectHub } from './utils'
32+ import {
33+ validateScopes ,
34+ appRequestSupportsDirectHub
35+ } from './utils'
3136import {
3237 selectApi ,
3338 selectCoreHost ,
@@ -53,6 +58,7 @@ import {
5358} from '@common/store/selectors/account'
5459import { formatAppManifest } from '@common'
5560import Modal from 'react-modal'
61+ import url from 'url'
5662
5763const views = [ Initial , LegacyGaia ]
5864
@@ -86,6 +92,21 @@ function mapDispatchToProps(dispatch) {
8692 return bindActionCreators ( actions , dispatch )
8793}
8894
95+ function makeGaiaAssociationToken ( secretKeyHex : string , childPublicKeyHex : string ) {
96+ const LIFETIME_SECONDS = 365 * 24 * 3600
97+ const signerKeyHex = secretKeyHex . slice ( 0 , 64 )
98+ const compressedPublicKeyHex = getPublicKeyFromPrivate ( signerKeyHex )
99+ const salt = randomBytes ( 16 ) . toString ( 'hex' )
100+ const payload = { childToAssociate : childPublicKeyHex ,
101+ iss : compressedPublicKeyHex ,
102+ exp : LIFETIME_SECONDS + ( new Date ( ) / 1000 ) ,
103+ iat : Date . now ( ) / 1000 ,
104+ salt }
105+
106+ const token = new TokenSigner ( 'ES256K' , signerKeyHex ) . sign ( payload )
107+ return token
108+ }
109+
89110class AuthPage extends React . Component {
90111 static contextTypes = {
91112 router : PropTypes . object
@@ -171,24 +192,34 @@ class AuthPage extends React.Component {
171192 if ( redirectURI ) {
172193 // Get the current localhost authentication url that the app will redirect back to,
173194 // and remove the 'echo' param from it.
174- const authContinuationURI = updateQueryStringParameter ( window . location . href , 'echo' , '' )
175- redirectURI = updateQueryStringParameter ( redirectURI , 'echoReply' , this . state . echoRequestId )
176- redirectURI = updateQueryStringParameter ( redirectURI , 'authContinuation' , encodeURIComponent ( authContinuationURI ) )
195+ const authContinuationURI = updateQueryStringParameter (
196+ window . location . href ,
197+ 'echo' ,
198+ ''
199+ )
200+ redirectURI = updateQueryStringParameter (
201+ redirectURI ,
202+ 'echoReply' ,
203+ this . state . echoRequestId
204+ )
205+ redirectURI = updateQueryStringParameter (
206+ redirectURI ,
207+ 'authContinuation' ,
208+ encodeURIComponent ( authContinuationURI )
209+ )
177210 } else {
178211 throw new Error ( 'Invalid redirect echo reply URI' )
179212 }
180213 this . setState ( { responseSent : true } )
181214 window . location = redirectURI
182215 }
183-
184- componentWillReceiveProps ( nextProps ) {
185216
217+ componentWillReceiveProps ( nextProps ) {
186218 if ( ! this . state . responseSent ) {
187-
188219 if ( this . state . echoRequestId ) {
189220 this . redirectUserToEchoReply ( )
190221 return
191- }
222+ }
192223
193224 const storageConnected = this . props . api . storageConnected
194225 this . setState ( {
@@ -251,9 +282,7 @@ class AuthPage extends React.Component {
251282
252283 if ( identity . zoneFile && identity . zoneFile . length > 0 ) {
253284 const zoneFileJson = parseZoneFile ( identity . zoneFile )
254- const profileUrlFromZonefile = getTokenFileUrlFromZoneFile (
255- zoneFileJson
256- )
285+ const profileUrlFromZonefile = getTokenFileUrlFromZoneFile ( zoneFileJson )
257286 if (
258287 profileUrlFromZonefile !== null &&
259288 profileUrlFromZonefile !== undefined
@@ -267,6 +296,7 @@ class AuthPage extends React.Component {
267296 const gaiaUrlBase = nextProps . api . gaiaHubConfig . url_prefix
268297
269298 if ( ! profileUrlPromise ) {
299+ // use default Gaia hub if we can't tell from the profile where the profile Gaia hub is
270300 profileUrlPromise = fetchProfileLocations (
271301 gaiaUrlBase ,
272302 identityAddress ,
@@ -358,10 +388,7 @@ class AuthPage extends React.Component {
358388 }
359389
360390 getFreshIdentities = async ( ) => {
361- await this . props . refreshIdentities (
362- this . props . api ,
363- this . props . addresses
364- )
391+ await this . props . refreshIdentities ( this . props . api , this . props . addresses )
365392 this . setState ( { refreshingIdentities : false } )
366393 }
367394
@@ -392,6 +419,8 @@ class AuthPage extends React.Component {
392419
393420 let transitPublicKey = undefined
394421 let hubUrl = undefined
422+ let blockstackAPIUrl = undefined
423+ let associationToken = undefined
395424
396425 let requestVersion = '0'
397426 if ( this . state . decodedToken . payload . hasOwnProperty ( 'version' ) ) {
@@ -407,6 +436,13 @@ class AuthPage extends React.Component {
407436 if ( appRequestSupportsDirectHub ( this . state . decodedToken . payload ) ) {
408437 hubUrl = this . props . api . gaiaHubUrl
409438 }
439+ if ( isLaterVersion ( requestVersion , '1.3.0' ) ) {
440+ const compressedAppPublicKey = getPublicKeyFromPrivate ( appPrivateKey . slice ( 0 , 64 ) )
441+ const parsedCoreUrl = url . parse ( this . props . api . nameLookupUrl )
442+
443+ blockstackAPIUrl = `${ parsedCoreUrl . protocol } //${ parsedCoreUrl . host } `
444+ associationToken = makeGaiaAssociationToken ( privateKey , compressedAppPublicKey )
445+ }
410446
411447 const authResponse = makeAuthResponse (
412448 privateKey ,
@@ -417,14 +453,17 @@ class AuthPage extends React.Component {
417453 appPrivateKey ,
418454 undefined ,
419455 transitPublicKey ,
420- hubUrl
456+ hubUrl ,
457+ blockstackAPIUrl ,
458+ associationToken
421459 )
422460
423461 this . props . clearSessionToken ( appDomain )
424462
425463 logger . info (
426464 `login(): id index ${ this . state . currentIdentityIndex } is logging in`
427465 )
466+
428467 this . setState ( { responseSent : true } )
429468 redirectUserToApp ( this . state . authRequest , authResponse )
430469 }
@@ -541,7 +580,11 @@ class AuthPage extends React.Component {
541580 }
542581
543582 render ( ) {
544- const { appManifest, appManifestLoading, appManifestLoadingError } = this . props
583+ const {
584+ appManifest,
585+ appManifestLoading,
586+ appManifestLoadingError
587+ } = this . props
545588
546589 if ( appManifestLoadingError ) {
547590 return (
@@ -596,11 +639,18 @@ class AuthPage extends React.Component {
596639 )
597640 } ,
598641 deny : ( ) => console . log ( 'go back to app' ) ,
642+ backLabel : 'Cancel' ,
643+ backView : ( ) => {
644+ if ( document . referrer === '' ) {
645+ window . close ( )
646+ } else {
647+ history . back ( )
648+ }
649+ } ,
599650 accounts : this . props . localIdentities ,
600651 processing : this . state . processing ,
601652 refreshingIdentities : this . state . refreshingIdentities ,
602- selectedIndex : this . state . currentIdentityIndex ,
603- disableBackOnView : 0
653+ selectedIndex : this . state . currentIdentityIndex
604654 }
605655 } ,
606656 {
@@ -630,4 +680,7 @@ class AuthPage extends React.Component {
630680 }
631681}
632682
633- export default connect ( mapStateToProps , mapDispatchToProps ) ( AuthPage )
683+ export default connect (
684+ mapStateToProps ,
685+ mapDispatchToProps
686+ ) ( AuthPage )
0 commit comments