This repository was archived by the owner on Aug 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathBlockstackSignIn.kt
More file actions
150 lines (136 loc) · 7.05 KB
/
BlockstackSignIn.kt
File metadata and controls
150 lines (136 loc) · 7.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package org.blockstack.android.sdk
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import me.uport.sdk.jwt.JWTTools
import me.uport.sdk.jwt.model.JwtHeader
import me.uport.sdk.signer.KPSigner
import org.blockstack.android.sdk.extensions.toBtcAddress
import org.blockstack.android.sdk.extensions.toHexPublicKey64
import org.blockstack.android.sdk.model.BlockstackConfig
import org.blockstack.android.sdk.model.SessionData
import org.kethereum.crypto.CryptoAPI
import org.kethereum.crypto.toECKeyPair
import org.kethereum.extensions.toHexStringNoPrefix
import org.kethereum.model.PrivateKey
import org.komputing.khex.model.HexString
import java.util.*
data class AppDetails(val name: String, val icon: String)
class BlockstackSignIn(private val sessionStore: ISessionStore,
private val appConfig: BlockstackConfig,
private val appDetails: AppDetails? = null,
val dispatcher: CoroutineDispatcher = Dispatchers.IO) {
/**
* Generates an authentication request that can be sent to the Blockstack browser
* for the user to approve sign in. This authentication request can then be used for
* sign in by passing it to the redirectToSignInWithAuthRequest method.
*
* Note: This method should only be used if you want to roll your own authentication flow.
* Typically you'd use redirectUserToSignIn which takes care of this under the hood.
*
* @param transitPrivateKey hex encoded transit private key
* @param expiresAt the time at which this request is no longer valid
* @param extraParams key, value pairs that are transferred with the auth request,
* only Boolean and String values are supported
*/
suspend fun makeAuthRequest(transitPrivateKey: String, expiresAt: Long = Date().time + 3600 * 24 * 7, sendToSignIn: Boolean = false, extraParams: Map<String, Any>? = null): String = withContext(dispatcher) {
val domainName = appConfig.appDomain.getOrigin()
val manifestUrl = "${domainName}${appConfig.manifestPath}"
val redirectUrl = "${domainName}${appConfig.redirectPath}"
val transitKeyPair = PrivateKey(HexString(transitPrivateKey)).toECKeyPair()
val btcAddress = transitKeyPair.toBtcAddress()
val issuerDID = "did:btc-addr:${btcAddress}"
val payload = mutableMapOf(
"jti" to UUID.randomUUID().toString(),
"iat" to Date().time / 1000,
"exp" to expiresAt / 1000,
"iss" to issuerDID,
"public_keys" to arrayOf(transitKeyPair.toHexPublicKey64()),
"domain_name" to domainName,
"manifest_uri" to manifestUrl,
"redirect_uri" to redirectUrl,
"version" to "1.3.1",
"do_not_include_profile" to true,
"supports_hub_url" to true,
"scopes" to appConfig.scopes.map { it.name },
"sendToSignIn" to sendToSignIn,
"client" to "android"
)
if (appDetails != null) {
payload["appDetails"] = mapOf("name" to appDetails.name, "icon" to appDetails.icon)
}
if (extraParams != null) {
payload.putAll(extraParams)
}
return@withContext JWTTools().createJWT(payload, issuerDID, KPSigner(transitPrivateKey), algorithm = JwtHeader.ES256K)
}
suspend fun redirectUserToSignIn(context: Context, sendToSignIn: Boolean = false) {
val transitPrivateKey = generateAndStoreTransitKey()
val authRequest = makeAuthRequest(transitPrivateKey, sendToSignIn = sendToSignIn)
redirectToSignInWithAuthRequest(context, authRequest, this.appConfig.authenticatorUrl, sendToSignIn = sendToSignIn)
}
/**
* Redirects the user to the Blockstack browser to approve the sign in request.
* To construct a request see the [[makeAuthRequest]] function.
*
* The user is redirected to the authenticator URL specified in the `AppConfig` or the default authenticator url
*
* @param authRequest A request string built by the [[makeAuthRequest]] function
* @param blockstackIDHost The ID of the Blockstack Browser application.
* @param sendToSignIn Whether the user should go straight to the 'sign in' flow (false) or be presented with the 'sign up' flow (true) instead.
* @param dispatcher Context for where to run the method, default is Dispatchers.Main
*
*/
suspend fun redirectToSignInWithAuthRequest(context: Context, authRequest: String, blockstackIDHost: String? = null, sendToSignIn: Boolean = false, dispatcher: CoroutineDispatcher = Dispatchers.Main) = withContext(dispatcher){
val hostUrl = blockstackIDHost ?: DEFAULT_BLOCKSTACK_ID_HOST
val path = if (sendToSignIn) "sign-in" else "sign-up"
val httpsURI = "${hostUrl}/#/${path}?authRequest=${authRequest}"
openUrl(context, httpsURI)
}
fun generateAndStoreTransitKey(): String {
val keyPair = CryptoAPI.keyPairGenerator.generate()
val transitPrivateKey = keyPair.privateKey.key.toHexStringNoPrefix()
sessionStore.sessionData = SessionData(sessionStore.sessionData.json
.put("transitKey", transitPrivateKey))
return transitPrivateKey
}
suspend fun openUrl(context: Context, location: String) = withContext(Dispatchers.Main) {
val locationUri = Uri.parse(location)
if (shouldLaunchInCustomTabs) {
val builder = CustomTabsIntent.Builder()
val options = BitmapFactory.Options()
options.outWidth = 24
options.outHeight = 24
options.inScaled = true
val backButton = BitmapFactory.decodeResource(context.resources, R.drawable.ic_arrow_back, options)
builder.setCloseButtonIcon(backButton)
builder.setToolbarColor(ContextCompat.getColor(context, R.color.org_blockstack_purple_50_logos_types))
builder.setToolbarColor(ContextCompat.getColor(context, R.color.org_blockstack_purple_85_lines))
builder.setShowTitle(true)
val customTabsIntent = builder.build()
customTabsIntent.launchUrl(context, locationUri)
} else {
context.startActivity(Intent(Intent.ACTION_VIEW, locationUri).addCategory(Intent.CATEGORY_BROWSABLE))
}
}
companion object {
val TAG = BlockstackSignIn::class.java.simpleName
/**
* Flag indicating whether the authentication flow should be started in custom tabs.
* Defaults to true.
*
* Set this to false only if you can't use Verified App Links.
*/
var shouldLaunchInCustomTabs = true
/**
* Flag indicating that verified app links should not be checked for correct configuration
*/
var doNotVerifyAppLinkConfiguration = false
}
}