Skip to content

Commit 082c8f6

Browse files
RobertCraigiestainless-app[bot]
authored andcommitted
chore: revert temporary commit
1 parent 82b301f commit 082c8f6

13 files changed

Lines changed: 654 additions & 6 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,20 @@ while (page != null) {
217217

218218
---
219219

220+
---
221+
222+
## Webhook Verification
223+
224+
We provide helper methods for verifying that a webhook request came from Finch, and not a malicious third party.
225+
226+
You can use `finch.webhooks().verifySignature(body, headers, secret?)` or `finch.webhooks().unwrap(body, headers, secret?)`,
227+
both of which will raise an error if the signature is invalid.
228+
229+
Note that the "body" parameter must be the raw JSON string sent from the server (do not parse it first).
230+
The `.unwrap()` method can parse this JSON for you.
231+
232+
---
233+
220234
## Error handling
221235

222236
This library throws exceptions in a single hierarchy for easy handling:

finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClient.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package com.tryfinch.api.client
66

7+
import com.fasterxml.jackson.annotation.JsonProperty
78
import com.tryfinch.api.models.*
89
import com.tryfinch.api.services.blocking.*
910

@@ -26,4 +27,26 @@ interface FinchClient {
2627
fun jobs(): JobService
2728

2829
fun sandbox(): SandboxService
30+
31+
fun getAccessToken(
32+
clientId: String,
33+
clientSecret: String,
34+
code: String,
35+
redirectUri: String
36+
): String
37+
38+
fun getAuthUrl(products: String, redirectUri: String, sandbox: Boolean): String
39+
40+
fun withAccessToken(accessToken: String): FinchClient
41+
42+
private data class GetAccessTokenParams(
43+
@JsonProperty("client_id") val clientId: String,
44+
@JsonProperty("client_secret") val clientSecret: String,
45+
@JsonProperty("code") val code: String,
46+
@JsonProperty("redirect_uri") val redirectUri: String,
47+
)
48+
49+
private data class GetAccessTokenResponse(
50+
@JsonProperty("accessToken") val accessToken: String,
51+
)
2952
}

finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientAsync.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
package com.tryfinch.api.client
66

7+
import com.fasterxml.jackson.annotation.JsonProperty
78
import com.tryfinch.api.models.*
89
import com.tryfinch.api.services.async.*
10+
import java.util.concurrent.CompletableFuture
911

1012
interface FinchClientAsync {
1113

@@ -26,4 +28,26 @@ interface FinchClientAsync {
2628
fun jobs(): JobServiceAsync
2729

2830
fun sandbox(): SandboxServiceAsync
31+
32+
fun getAccessToken(
33+
clientId: String,
34+
clientSecret: String,
35+
code: String,
36+
redirectUri: String
37+
): CompletableFuture<String>
38+
39+
fun getAuthUrl(products: String, redirectUri: String, sandbox: Boolean): String
40+
41+
fun withAccessToken(accessToken: String): FinchClientAsync
42+
43+
private data class GetAccessTokenParams(
44+
@JsonProperty("client_id") val clientId: String,
45+
@JsonProperty("client_secret") val clientSecret: String,
46+
@JsonProperty("code") val code: String,
47+
@JsonProperty("redirect_uri") val redirectUri: String,
48+
)
49+
50+
private data class GetAccessTokenResponse(
51+
@JsonProperty("accessToken") val accessToken: String,
52+
)
2953
}

finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientAsyncImpl.kt

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@
22

33
package com.tryfinch.api.client
44

5+
import com.fasterxml.jackson.annotation.JsonProperty
56
import com.tryfinch.api.core.ClientOptions
7+
import com.tryfinch.api.core.http.HttpMethod
8+
import com.tryfinch.api.core.http.HttpRequest
69
import com.tryfinch.api.core.http.HttpResponse.Handler
710
import com.tryfinch.api.errors.FinchError
11+
import com.tryfinch.api.errors.FinchException
812
import com.tryfinch.api.models.*
913
import com.tryfinch.api.services.async.*
1014
import com.tryfinch.api.services.errorHandler
15+
import com.tryfinch.api.services.json
16+
import com.tryfinch.api.services.jsonHandler
17+
import com.tryfinch.api.services.withErrorHandler
18+
import java.net.URLEncoder
19+
import java.util.concurrent.CompletableFuture
1120

1221
class FinchClientAsyncImpl
1322
constructor(
@@ -38,6 +47,9 @@ constructor(
3847

3948
private val sandbox: SandboxServiceAsync by lazy { SandboxServiceAsyncImpl(clientOptions) }
4049

50+
private val getAccessTokenHandler: Handler<GetAccessTokenResponse> =
51+
jsonHandler<GetAccessTokenResponse>(clientOptions.jsonMapper).withErrorHandler(errorHandler)
52+
4153
override fun sync(): FinchClient = sync
4254

4355
override fun accessTokens(): AccessTokenServiceAsync = accessTokens
@@ -55,4 +67,76 @@ constructor(
5567
override fun jobs(): JobServiceAsync = jobs
5668

5769
override fun sandbox(): SandboxServiceAsync = sandbox
70+
71+
override fun getAccessToken(
72+
clientId: String,
73+
clientSecret: String,
74+
code: String,
75+
redirectUri: String
76+
): CompletableFuture<String> {
77+
if (clientOptions.clientId == null) {
78+
throw FinchException("clientId must be set in order to call getAccessToken")
79+
}
80+
if (clientOptions.clientSecret == null) {
81+
throw FinchException("clientSecret must be set in order to call getAccessToken")
82+
}
83+
val request =
84+
HttpRequest.builder()
85+
.method(HttpMethod.POST)
86+
.addPathSegments("auth", "token")
87+
.body(
88+
json(
89+
clientOptions.jsonMapper,
90+
GetAccessTokenParams(
91+
clientId,
92+
clientSecret,
93+
code,
94+
redirectUri,
95+
)
96+
)
97+
)
98+
.build()
99+
return clientOptions.httpClient.executeAsync(request).thenApply {
100+
getAccessTokenHandler.handle(it).accessToken
101+
}
102+
}
103+
104+
override fun getAuthUrl(products: String, redirectUri: String, sandbox: Boolean): String {
105+
if (clientOptions.clientId == null) {
106+
throw FinchException("Expected the clientId to be set in order to call getAuthUrl")
107+
}
108+
return "https://connect.tryfinch.com/authorize" +
109+
"?client_id=${URLEncoder.encode(clientOptions.clientId, Charsets.UTF_8.name())}" +
110+
"&products=${URLEncoder.encode(products, Charsets.UTF_8.name())}" +
111+
"&redirect_uri=${URLEncoder.encode(redirectUri, Charsets.UTF_8.name())}" +
112+
"&sandbox=${if (sandbox) "true" else "false"}"
113+
}
114+
115+
override fun withAccessToken(accessToken: String): FinchClientAsync {
116+
return FinchClientAsyncImpl(
117+
ClientOptions.builder()
118+
.httpClient(clientOptions.httpClient)
119+
.jsonMapper(clientOptions.jsonMapper)
120+
.clock(clientOptions.clock)
121+
.baseUrl(clientOptions.baseUrl)
122+
.accessToken(accessToken)
123+
.clientId(clientOptions.clientId)
124+
.clientSecret(clientOptions.clientSecret)
125+
.webhookSecret(clientOptions.webhookSecret)
126+
.headers(clientOptions.headers.asMap())
127+
.responseValidation(clientOptions.responseValidation)
128+
.build()
129+
)
130+
}
131+
132+
private data class GetAccessTokenParams(
133+
@JsonProperty("client_id") val clientId: String,
134+
@JsonProperty("client_secret") val clientSecret: String,
135+
@JsonProperty("code") val code: String,
136+
@JsonProperty("redirect_uri") val redirectUri: String,
137+
)
138+
139+
private data class GetAccessTokenResponse(
140+
@JsonProperty("accessToken") val accessToken: String,
141+
)
58142
}

finch-java-core/src/main/kotlin/com/tryfinch/api/client/FinchClientImpl.kt

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22

33
package com.tryfinch.api.client
44

5+
import com.fasterxml.jackson.annotation.JsonProperty
56
import com.tryfinch.api.core.ClientOptions
7+
import com.tryfinch.api.core.http.HttpMethod
8+
import com.tryfinch.api.core.http.HttpRequest
69
import com.tryfinch.api.core.http.HttpResponse.Handler
710
import com.tryfinch.api.errors.FinchError
11+
import com.tryfinch.api.errors.FinchException
812
import com.tryfinch.api.models.*
913
import com.tryfinch.api.services.blocking.*
1014
import com.tryfinch.api.services.errorHandler
15+
import com.tryfinch.api.services.json
16+
import com.tryfinch.api.services.jsonHandler
17+
import com.tryfinch.api.services.withErrorHandler
18+
import java.net.URLEncoder
1119

1220
class FinchClientImpl
1321
constructor(
@@ -36,6 +44,9 @@ constructor(
3644

3745
private val sandbox: SandboxService by lazy { SandboxServiceImpl(clientOptions) }
3846

47+
private val getAccessTokenHandler: Handler<GetAccessTokenResponse> =
48+
jsonHandler<GetAccessTokenResponse>(clientOptions.jsonMapper).withErrorHandler(errorHandler)
49+
3950
override fun async(): FinchClientAsync = async
4051

4152
override fun accessTokens(): AccessTokenService = accessTokens
@@ -53,4 +64,76 @@ constructor(
5364
override fun jobs(): JobService = jobs
5465

5566
override fun sandbox(): SandboxService = sandbox
67+
68+
override fun getAccessToken(
69+
clientId: String,
70+
clientSecret: String,
71+
code: String,
72+
redirectUri: String
73+
): String {
74+
if (clientOptions.clientId == null) {
75+
throw FinchException("clientId must be set in order to call getAccessToken")
76+
}
77+
if (clientOptions.clientSecret == null) {
78+
throw FinchException("clientSecret must be set in order to call getAccessToken")
79+
}
80+
val request =
81+
HttpRequest.builder()
82+
.method(HttpMethod.POST)
83+
.addPathSegments("auth", "token")
84+
.body(
85+
json(
86+
clientOptions.jsonMapper,
87+
GetAccessTokenParams(
88+
clientId,
89+
clientSecret,
90+
code,
91+
redirectUri,
92+
)
93+
)
94+
)
95+
.build()
96+
return clientOptions.httpClient.execute(request).let {
97+
getAccessTokenHandler.handle(it).accessToken
98+
}
99+
}
100+
101+
override fun getAuthUrl(products: String, redirectUri: String, sandbox: Boolean): String {
102+
if (clientOptions.clientId == null) {
103+
throw FinchException("Expected the clientId to be set in order to call getAuthUrl")
104+
}
105+
return "https://connect.tryfinch.com/authorize" +
106+
"?client_id=${URLEncoder.encode(clientOptions.clientId, Charsets.UTF_8.name())}" +
107+
"&products=${URLEncoder.encode(products, Charsets.UTF_8.name())}" +
108+
"&redirect_uri=${URLEncoder.encode(redirectUri, Charsets.UTF_8.name())}" +
109+
"&sandbox=${if (sandbox) "true" else "false"}"
110+
}
111+
112+
override fun withAccessToken(accessToken: String): FinchClient {
113+
return FinchClientImpl(
114+
ClientOptions.builder()
115+
.httpClient(clientOptions.httpClient)
116+
.jsonMapper(clientOptions.jsonMapper)
117+
.clock(clientOptions.clock)
118+
.baseUrl(clientOptions.baseUrl)
119+
.accessToken(accessToken)
120+
.clientId(clientOptions.clientId)
121+
.clientSecret(clientOptions.clientSecret)
122+
.webhookSecret(clientOptions.webhookSecret)
123+
.headers(clientOptions.headers.asMap())
124+
.responseValidation(clientOptions.responseValidation)
125+
.build()
126+
)
127+
}
128+
129+
private data class GetAccessTokenParams(
130+
@JsonProperty("client_id") val clientId: String,
131+
@JsonProperty("client_secret") val clientSecret: String,
132+
@JsonProperty("code") val code: String,
133+
@JsonProperty("redirect_uri") val redirectUri: String,
134+
)
135+
136+
private data class GetAccessTokenResponse(
137+
@JsonProperty("accessToken") val accessToken: String,
138+
)
56139
}

finch-java-core/src/main/kotlin/com/tryfinch/api/services/async/AccessTokenServiceAsyncImpl.kt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.tryfinch.api.core.http.HttpMethod
88
import com.tryfinch.api.core.http.HttpRequest
99
import com.tryfinch.api.core.http.HttpResponse.Handler
1010
import com.tryfinch.api.errors.FinchError
11+
import com.tryfinch.api.errors.FinchException
1112
import com.tryfinch.api.models.AccessTokenCreateParams
1213
import com.tryfinch.api.models.CreateAccessTokenResponse
1314
import com.tryfinch.api.services.errorHandler
@@ -32,14 +33,36 @@ constructor(
3233
params: AccessTokenCreateParams,
3334
requestOptions: RequestOptions
3435
): CompletableFuture<CreateAccessTokenResponse> {
36+
val builder = params.toBuilder()
37+
38+
if (!params.clientSecret().isPresent) {
39+
if (clientOptions.clientSecret == null || clientOptions.clientSecret.isEmpty()) {
40+
throw FinchException(
41+
"client_secret must be provided as an argument or with the FINCH_CLIENT_SECRET environment variable"
42+
)
43+
}
44+
builder.clientSecret(clientOptions.clientSecret)
45+
}
46+
47+
if (!params.clientId().isPresent) {
48+
if (clientOptions.clientId == null || clientOptions.clientId.isEmpty()) {
49+
throw FinchException(
50+
"client_id must be provided as an argument or with the FINCH_CLIENT_ID environment variable"
51+
)
52+
}
53+
builder.clientId(clientOptions.clientId)
54+
}
55+
56+
val modifiedParams = builder.build()
57+
3558
val request =
3659
HttpRequest.builder()
3760
.method(HttpMethod.POST)
3861
.addPathSegments("auth", "token")
3962
.putAllQueryParams(params.getQueryParams())
4063
.putAllHeaders(clientOptions.headers)
4164
.putAllHeaders(params.getHeaders())
42-
.body(json(clientOptions.jsonMapper, params.getBody()))
65+
.body(json(clientOptions.jsonMapper, modifiedParams.getBody()))
4366
.build()
4467
return clientOptions.httpClient.executeAsync(request, requestOptions).thenApply { response
4568
->

finch-java-core/src/main/kotlin/com/tryfinch/api/services/async/WebhookServiceAsync.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,16 @@
44

55
package com.tryfinch.api.services.async
66

7-
interface WebhookServiceAsync
7+
import com.google.common.collect.ListMultimap
8+
import com.tryfinch.api.models.WebhookEvent
9+
10+
interface WebhookServiceAsync {
11+
12+
fun unwrap(
13+
payload: String,
14+
headers: ListMultimap<String, String>,
15+
secret: String?
16+
): WebhookEvent
17+
18+
fun verifySignature(payload: String, headers: ListMultimap<String, String>, secret: String?)
19+
}

0 commit comments

Comments
 (0)