Skip to content

Commit c1a6d19

Browse files
committed
add OAuth support for BackendApi + rename SamouraiFee -> MinerFee
1 parent 83024b3 commit c1a6d19

File tree

7 files changed

+164
-53
lines changed

7 files changed

+164
-53
lines changed

java/com/samourai/wallet/api/backend/BackendApi.java

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,37 @@
44
import com.samourai.wallet.api.backend.beans.MultiAddrResponse;
55
import com.samourai.wallet.api.backend.beans.RefreshTokenResponse;
66
import com.samourai.wallet.api.backend.beans.UnspentResponse;
7+
import com.samourai.wallet.util.oauth.OAuthApi;
8+
import com.samourai.wallet.util.oauth.OAuthManager;
79
import org.apache.commons.lang3.StringUtils;
810
import org.slf4j.Logger;
911
import org.slf4j.LoggerFactory;
1012

1113
import java.util.*;
1214

13-
public class BackendApi {
15+
public class BackendApi implements OAuthApi {
1416
private Logger log = LoggerFactory.getLogger(BackendApi.class);
1517

1618
private static final String URL_UNSPENT = "/unspent?active=";
1719
private static final String URL_MULTIADDR = "/multiaddr?active=";
1820
private static final String URL_INIT_BIP84 = "/xpub";
19-
private static final String URL_FEES = "/fees";
21+
private static final String URL_MINER_FEES = "/fees";
2022
private static final String URL_PUSHTX = "/pushtx/";
2123
private static final String URL_GET_AUTH_LOGIN = "/auth/login";
2224
private static final String URL_GET_AUTH_REFRESH = "/auth/refresh";
2325

2426
private IBackendClient httpClient;
2527
private String urlBackend;
26-
private String apiKey;
28+
private OAuthManager oAuthManager;
2729

28-
public BackendApi(IBackendClient httpClient, String urlBackend, String apiKey) {
30+
public BackendApi(IBackendClient httpClient, String urlBackend, OAuthManager oAuthManager) {
2931
this.httpClient = httpClient;
3032
this.urlBackend = urlBackend;
31-
this.apiKey = apiKey; // may be null
33+
this.oAuthManager = oAuthManager; // may be null
34+
if (log.isDebugEnabled()) {
35+
String oAuthStr = oAuthManager != null ? "yes" : "no";
36+
log.debug("urlBackend=" + urlBackend + ", oAuth=" + oAuthStr);
37+
}
3238
}
3339

3440
public List<UnspentResponse.UnspentOutput> fetchUtxos(String zpub) throws Exception {
@@ -92,14 +98,14 @@ public void initBip84(String zpub) throws Exception {
9298
httpClient.postUrlEncoded(url, Void.class, headers, postBody);
9399
}
94100

95-
public SamouraiFee fetchFees() throws Exception {
96-
String url = computeAuthUrl(urlBackend + URL_FEES);
101+
public MinerFee fetchMinerFee() throws Exception {
102+
String url = computeAuthUrl(urlBackend + URL_MINER_FEES);
97103
Map<String,String> headers = computeHeaders();
98104
Map<String, Integer> feeResponse = httpClient.getJson(url, Map.class, headers);
99105
if (feeResponse == null) {
100-
throw new Exception("Invalid fee response from server");
106+
throw new Exception("Invalid miner fee response from server");
101107
}
102-
return new SamouraiFee(feeResponse);
108+
return new MinerFee(feeResponse);
103109
}
104110

105111
public void pushTx(String txHex) throws Exception {
@@ -130,13 +136,48 @@ public void pushTx(String txHex) throws Exception {
130136
}
131137
}
132138

133-
protected RefreshTokenResponse.Authorization tokenAuthenticate() throws Exception {
139+
public boolean testConnectivity() {
140+
try {
141+
fetchMinerFee();
142+
return true;
143+
} catch (Exception e) {
144+
log.error("", e);
145+
return false;
146+
}
147+
}
148+
149+
protected Map<String,String> computeHeaders() throws Exception {
150+
Map<String,String> headers = new HashMap<String, String>();
151+
if (oAuthManager != null) {
152+
// add auth token
153+
headers.put("Authorization<", "Bearer " + oAuthManager.computeAccessToken(this));
154+
}
155+
return headers;
156+
}
157+
158+
protected String computeAuthUrl(String url) throws Exception {
159+
// override for auth support
160+
return url;
161+
}
162+
163+
protected IBackendClient getHttpClient() {
164+
return httpClient;
165+
}
166+
167+
public String getUrlBackend() {
168+
return urlBackend;
169+
}
170+
171+
// OAuthAPI
172+
173+
@Override
174+
public RefreshTokenResponse.Authorization oAuthAuthenticate(String apiKey) throws Exception {
134175
String url = getUrlBackend() + URL_GET_AUTH_LOGIN;
135176
if (log.isDebugEnabled()) {
136177
log.debug("tokenAuthenticate");
137178
}
138179
Map<String, String> postBody = new HashMap<String, String>();
139-
postBody.put("apikey", getApiKey());
180+
postBody.put("apikey", apiKey);
140181
RefreshTokenResponse response =
141182
getHttpClient().postUrlEncoded(url, RefreshTokenResponse.class, null, postBody);
142183

@@ -146,13 +187,14 @@ protected RefreshTokenResponse.Authorization tokenAuthenticate() throws Exceptio
146187
return response.authorizations;
147188
}
148189

149-
protected String tokenRefresh(String refreshToken) throws Exception {
190+
@Override
191+
public String oAuthRefresh(String refreshTokenStr) throws Exception {
150192
String url = getUrlBackend() + URL_GET_AUTH_REFRESH;
151193
if (log.isDebugEnabled()) {
152194
log.debug("tokenRefresh");
153195
}
154196
Map<String, String> postBody = new HashMap<String, String>();
155-
postBody.put("rt", refreshToken);
197+
postBody.put("rt", refreshTokenStr);
156198
RefreshTokenResponse response =
157199
getHttpClient().postUrlEncoded(url, RefreshTokenResponse.class, null, postBody);
158200

@@ -161,26 +203,4 @@ protected String tokenRefresh(String refreshToken) throws Exception {
161203
}
162204
return response.authorizations.access_token;
163205
}
164-
165-
protected Map<String,String> computeHeaders() throws Exception {
166-
Map<String,String> headers = new HashMap<String, String>();
167-
return headers;
168-
}
169-
170-
protected String computeAuthUrl(String url) throws Exception {
171-
// override for auth support
172-
return url;
173-
}
174-
175-
protected IBackendClient getHttpClient() {
176-
return httpClient;
177-
}
178-
179-
protected String getApiKey() {
180-
return apiKey;
181-
}
182-
183-
public String getUrlBackend() {
184-
return urlBackend;
185-
}
186206
}

java/com/samourai/wallet/api/backend/SamouraiFee.java renamed to java/com/samourai/wallet/api/backend/MinerFee.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
import java.util.Map;
44

5-
public class SamouraiFee {
5+
public class MinerFee {
66
private Map<String, Integer> feesResponse;
77

8-
public SamouraiFee(Map<String, Integer> feesResponse) {
8+
public MinerFee(Map<String, Integer> feesResponse) {
99
this.feesResponse = feesResponse;
1010
}
1111

12-
public int get(SamouraiFeeTarget feeTarget) {
12+
public int get(MinerFeeTarget feeTarget) {
1313
int fee = feesResponse.get(feeTarget.getValue());
1414
return fee;
1515
}

java/com/samourai/wallet/api/backend/SamouraiFeeTarget.java renamed to java/com/samourai/wallet/api/backend/MinerFeeTarget.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.samourai.wallet.api.backend;
22

3-
public enum SamouraiFeeTarget {
3+
public enum MinerFeeTarget {
44
BLOCKS_2("2"),
55
BLOCKS_4("4"),
66
BLOCKS_6("6"),
@@ -9,7 +9,7 @@ public enum SamouraiFeeTarget {
99

1010
private String value;
1111

12-
SamouraiFeeTarget(String value) {
12+
MinerFeeTarget(String value) {
1313
this.value = value;
1414
}
1515

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.samourai.wallet.util.oauth;
2+
3+
import com.samourai.wallet.api.backend.beans.RefreshTokenResponse;
4+
5+
public interface OAuthApi {
6+
String oAuthRefresh(String refreshTokenStr) throws Exception;
7+
RefreshTokenResponse.Authorization oAuthAuthenticate(String apiKey) throws Exception;
8+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.samourai.wallet.util.oauth;
2+
3+
import com.auth0.jwt.JWT;
4+
import com.auth0.jwt.interfaces.DecodedJWT;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
import java.util.Date;
9+
10+
public class OAuthImpl {
11+
private Logger log = LoggerFactory.getLogger(OAuthImpl.class);
12+
13+
public boolean validate(DecodedJWT token) {
14+
// check expiration
15+
boolean valid = token.getExpiresAt().after(new Date());
16+
if (log.isDebugEnabled()) {
17+
log.debug(
18+
"accessToken is "
19+
+ (valid ? "VALID" : "EXPIRED")
20+
+ ", expiresAt="
21+
+ token.getExpiresAt());
22+
}
23+
return valid;
24+
}
25+
26+
public DecodedJWT decode(String tokenStr) {
27+
return JWT.decode(tokenStr);
28+
}
29+
30+
public String tokenToString(DecodedJWT token) {
31+
return token.getToken();
32+
}
33+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.samourai.wallet.util.oauth;
2+
3+
import com.auth0.jwt.interfaces.DecodedJWT;
4+
import com.samourai.wallet.api.backend.beans.RefreshTokenResponse;
5+
6+
public class OAuthManager {
7+
private OAuthImpl oAuthImpl;
8+
private String apiKey;
9+
10+
private DecodedJWT accessToken;
11+
private DecodedJWT refreshToken;
12+
13+
public OAuthManager(String apiKey) {
14+
this.oAuthImpl = new OAuthImpl();
15+
this.apiKey = apiKey;
16+
}
17+
18+
public String computeAccessToken(OAuthApi oAuthApi) throws Exception {
19+
if (accessToken != null) {
20+
boolean valid = oAuthImpl.validate(accessToken);
21+
if (valid) {
22+
// accessToken is valid
23+
return oAuthImpl.tokenToString(accessToken);
24+
}
25+
}
26+
return newAccessToken(oAuthApi);
27+
}
28+
29+
public String newAccessToken(OAuthApi oAuthApi) throws Exception {
30+
if (refreshToken != null) {
31+
boolean valid = oAuthImpl.validate(refreshToken);
32+
if (valid) {
33+
// refreshToken is valid => refresh
34+
String accessTokenStr = oAuthApi.oAuthRefresh(oAuthImpl.tokenToString(refreshToken));
35+
this.accessToken = oAuthImpl.decode(accessTokenStr);
36+
return oAuthImpl.tokenToString(accessToken);
37+
}
38+
}
39+
40+
// no refreshToken => authenticate
41+
RefreshTokenResponse.Authorization auth = oAuthApi.oAuthAuthenticate(apiKey);
42+
this.accessToken = oAuthImpl.decode(auth.access_token);
43+
this.refreshToken = oAuthImpl.decode(auth.refresh_token);
44+
return oAuthImpl.tokenToString(accessToken);
45+
}
46+
}

pom.xml

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,6 @@
2626
<artifactId>json</artifactId>
2727
<version>20180130</version>
2828
</dependency>
29-
<dependency>
30-
<groupId>org.junit.platform</groupId>
31-
<artifactId>junit-platform-launcher</artifactId>
32-
<version>1.2.0</version>
33-
<scope>test</scope>
34-
</dependency>
35-
<dependency>
36-
<groupId>org.junit.jupiter</groupId>
37-
<artifactId>junit-jupiter-engine</artifactId>
38-
<version>5.2.0</version>
39-
<scope>test</scope>
40-
</dependency>
4129
<dependency>
4230
<groupId>com.github.Samourai-Wallet.bitcoinj</groupId>
4331
<artifactId>bitcoinj-core</artifactId>
@@ -48,10 +36,26 @@
4836
<artifactId>jackson-annotations</artifactId>
4937
<version>2.9.0</version>
5038
</dependency>
39+
<dependency>
40+
<groupId>com.auth0</groupId>
41+
<artifactId>java-jwt</artifactId>
42+
<version>3.8.1</version>
43+
</dependency>
5144
<dependency>
5245
<groupId>com.fasterxml.jackson.core</groupId>
5346
<artifactId>jackson-databind</artifactId>
54-
<version>2.9.10</version>
47+
<version>2.9.10.1</version>
48+
</dependency>
49+
<dependency>
50+
<groupId>org.junit.platform</groupId>
51+
<artifactId>junit-platform-launcher</artifactId>
52+
<version>1.2.0</version>
53+
<scope>test</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.junit.jupiter</groupId>
57+
<artifactId>junit-jupiter-engine</artifactId>
58+
<version>5.2.0</version>
5559
<scope>test</scope>
5660
</dependency>
5761
</dependencies>

0 commit comments

Comments
 (0)