Skip to content

Commit 336654d

Browse files
authored
TW-4612: add authentication multi-credential support (#302)
1 parent e17d7ef commit 336654d

File tree

14 files changed

+614
-15
lines changed

14 files changed

+614
-15
lines changed

.claude/settings.local.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"permissions": {
33
"allow": [
4-
"Bash(export JAVA_HOME=/Users/[email protected]/Library/Java/JavaVirtualMachines/corretto-11.0.29/Contents/Home:*)",
54
"Bash(./gradlew test:*)",
65
"Bash(export JAVA_HOME:*)",
76
"Bash(./gradlew jacocoTestReport:*)",
@@ -10,7 +9,8 @@
109
"Bash(find:*)",
1110
"Bash(./gradlew clean build:*)",
1211
"Bash(java -version:*)",
13-
"Bash(/usr/libexec/java_home:*)"
12+
"Bash(/usr/libexec/java_home:*)",
13+
"Bash(./gradlew clean test:*)"
1414
]
1515
}
1616
}

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Nylas Java SDK Changelog
22

3+
## [Unreleased]
4+
5+
### Added
6+
* Multi-credential authentication support allowing multiple provider credentials per Connector
7+
- `CreateCredentialRequest.Connector` class for creating connector credentials with `client_id`, `client_secret`, and optional extra properties like `tenant`
8+
- `credentialId` field in `UrlForAuthenticationConfig` for hosted auth URL generation via `urlForOAuth2` and `urlForOAuth2PKCE`
9+
- `credentialId` field in `CreateGrantRequest` for custom authentication
10+
- `credentialId` field in `Grant` response model
11+
- `activeCredentialId` field in `Connector` response model
12+
- `activeCredentialId` field in `UpdateConnectorRequest` for setting the active credential on a Connector
13+
* Enhanced `CredentialData.ConnectorOverride` to support optional `clientId` and `clientSecret` fields
14+
15+
### Deprecated
16+
* `CreateCredentialRequest.Override` - Use `CreateCredentialRequest.Connector` instead
17+
318
## [2.14.1]
419

520
### Added

src/main/kotlin/com/nylas/models/Connector.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ sealed class Connector(
1212
*/
1313
@Json(name = "provider")
1414
val provider: AuthProvider,
15+
/**
16+
* The ID of the active credential for this connector (for multi-credential setups).
17+
*/
18+
@Json(name = "active_credential_id")
19+
open val activeCredentialId: String? = null,
1520
) {
1621
/**
1722
* Class representing a Google connector creation request.
@@ -27,7 +32,12 @@ sealed class Connector(
2732
*/
2833
@Json(name = "scope")
2934
val scope: List<String>? = null,
30-
) : Connector(AuthProvider.GOOGLE)
35+
/**
36+
* The ID of the active credential for this connector
37+
*/
38+
@Json(name = "active_credential_id")
39+
override val activeCredentialId: String? = null,
40+
) : Connector(AuthProvider.GOOGLE, activeCredentialId)
3141

3242
/**
3343
* Class representing a Microsoft connector creation request.
@@ -43,7 +53,12 @@ sealed class Connector(
4353
*/
4454
@Json(name = "scope")
4555
val scope: List<String>? = null,
46-
) : Connector(AuthProvider.MICROSOFT)
56+
/**
57+
* The ID of the active credential for this connector
58+
*/
59+
@Json(name = "active_credential_id")
60+
override val activeCredentialId: String? = null,
61+
) : Connector(AuthProvider.MICROSOFT, activeCredentialId)
4762

4863
/**
4964
* Class representing an IMAP connector creation request.

src/main/kotlin/com/nylas/models/CreateCredentialRequest.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,28 @@ sealed class CreateCredentialRequest(
5656
) : CreateCredentialRequest(name, credentialData, CredentialType.SERVICEACCOUNT)
5757

5858
/**
59-
* Class representing a request to create an override credential
59+
* Class representing a request to create a connector credential.
60+
* For multi-credential OAuth flows, provide clientId and clientSecret in credentialData.
61+
* For other overrides, use extraProperties in credentialData.
6062
*/
63+
data class Connector(
64+
/**
65+
* Unique name of this credential
66+
*/
67+
@Json(name = "name")
68+
override val name: String,
69+
/**
70+
* Data that specifies the credential details (client_id/client_secret for OAuth, or extraProperties for overrides)
71+
*/
72+
@Json(name = "credential_data")
73+
override val credentialData: CredentialData.ConnectorOverride,
74+
) : CreateCredentialRequest(name, credentialData, CredentialType.CONNECTOR)
75+
76+
/**
77+
* Alias for [Connector] to maintain backward compatibility.
78+
* @deprecated Use [Connector] instead.
79+
*/
80+
@Deprecated("Use Connector instead", ReplaceWith("Connector"))
6181
data class Override(
6282
/**
6383
* Unique name of this credential
@@ -77,6 +97,6 @@ sealed class CreateCredentialRequest(
7797
PolymorphicJsonAdapterFactory.of(CreateCredentialRequest::class.java, "credential_type")
7898
.withSubtype(Microsoft::class.java, CredentialType.ADMINCONSENT.value)
7999
.withSubtype(Google::class.java, CredentialType.SERVICEACCOUNT.value)
80-
.withSubtype(Override::class.java, CredentialType.CONNECTOR.value)
100+
.withSubtype(Connector::class.java, CredentialType.CONNECTOR.value)
81101
}
82102
}

src/main/kotlin/com/nylas/models/CreateGrantRequest.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ data class CreateGrantRequest(
2626
*/
2727
@Json(name = "scope")
2828
val scope: List<String>? = null,
29+
/**
30+
* The credential ID to use for authentication (for multi-credential setups).
31+
*/
32+
@Json(name = "credential_id")
33+
val credentialId: String? = null,
2934
) {
3035
/**
3136
* Builder for [CreateGrantRequest].
@@ -38,6 +43,7 @@ data class CreateGrantRequest(
3843
) {
3944
private var state: String? = null
4045
private var scopes: List<String>? = null
46+
private var credentialId: String? = null
4147

4248
/**
4349
* Set the state value to return to developer's website after authentication flow is completed.
@@ -53,10 +59,17 @@ data class CreateGrantRequest(
5359
*/
5460
fun scopes(scopes: List<String>) = apply { this.scopes = scopes }
5561

62+
/**
63+
* Set the credential ID to use for authentication (for multi-credential setups).
64+
* @param credentialId The credential ID
65+
* @return This builder
66+
*/
67+
fun credentialId(credentialId: String) = apply { this.credentialId = credentialId }
68+
5669
/**
5770
* Build the [CreateGrantRequest].
5871
* @return The built [CreateGrantRequest]
5972
*/
60-
fun build() = CreateGrantRequest(provider, settings, state, scopes)
73+
fun build() = CreateGrantRequest(provider, settings, state, scopes, credentialId)
6174
}
6275
}

src/main/kotlin/com/nylas/models/CredentialData.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ sealed class CredentialData(
3333
) : CredentialData(extraProperties)
3434

3535
/**
36-
* Class representing additional data needed to create a credential for a Connector Override
36+
* Class representing additional data needed to create a credential for a Connector Override.
37+
* For multi-credential OAuth flows, provide clientId and clientSecret.
38+
* For other overrides, use extraProperties.
3739
*/
3840
data class ConnectorOverride(
39-
override val extraProperties: Map<String, String>,
41+
@Json(name = "client_id")
42+
val clientId: String? = null,
43+
@Json(name = "client_secret")
44+
val clientSecret: String? = null,
45+
override val extraProperties: Map<String, String>? = emptyMap(),
4046
) : CredentialData(extraProperties)
4147
}

src/main/kotlin/com/nylas/models/Grant.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,9 @@ data class Grant(
6767
*/
6868
@Json(name = "settings")
6969
val settings: Map<String, Any>? = null,
70+
/**
71+
* The credential ID associated with this grant (for multi-credential setups).
72+
*/
73+
@Json(name = "credential_id")
74+
val credentialId: String? = null,
7075
)

src/main/kotlin/com/nylas/models/UpdateConnectorRequest.kt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@ sealed class UpdateConnectorRequest {
2020
*/
2121
@Json(name = "scope")
2222
val scope: List<String>? = null,
23+
/**
24+
* The ID of the active credential for this connector (for multi-credential setups)
25+
*/
26+
@Json(name = "active_credential_id")
27+
val activeCredentialId: String? = null,
2328
) : UpdateConnectorRequest() {
2429
class Builder {
2530
private var settings: GoogleConnectorSettings? = null
2631
private var scope: List<String>? = null
32+
private var activeCredentialId: String? = null
2733

2834
/**
2935
* Set the Google OAuth provider credentials and settings
@@ -39,11 +45,18 @@ sealed class UpdateConnectorRequest {
3945
*/
4046
fun scope(scope: List<String>) = apply { this.scope = scope }
4147

48+
/**
49+
* Set the active credential ID for this connector
50+
* @param activeCredentialId The active credential ID
51+
* @return The builder
52+
*/
53+
fun activeCredentialId(activeCredentialId: String) = apply { this.activeCredentialId = activeCredentialId }
54+
4255
/**
4356
* Build the Google connector creation request
4457
* @return The Google connector creation request
4558
*/
46-
fun build() = Google(settings, scope)
59+
fun build() = Google(settings, scope, activeCredentialId)
4760
}
4861
}
4962

@@ -61,10 +74,16 @@ sealed class UpdateConnectorRequest {
6174
*/
6275
@Json(name = "scope")
6376
val scope: List<String>? = null,
77+
/**
78+
* The ID of the active credential for this connector (for multi-credential setups)
79+
*/
80+
@Json(name = "active_credential_id")
81+
val activeCredentialId: String? = null,
6482
) : UpdateConnectorRequest() {
6583
class Builder {
6684
private var settings: MicrosoftConnectorSettings? = null
6785
private var scope: List<String>? = null
86+
private var activeCredentialId: String? = null
6887

6988
/**
7089
* Set the Microsoft OAuth provider credentials and settings
@@ -80,11 +99,18 @@ sealed class UpdateConnectorRequest {
8099
*/
81100
fun scope(scope: List<String>) = apply { this.scope = scope }
82101

102+
/**
103+
* Set the active credential ID for this connector
104+
* @param activeCredentialId The active credential ID
105+
* @return The builder
106+
*/
107+
fun activeCredentialId(activeCredentialId: String) = apply { this.activeCredentialId = activeCredentialId }
108+
83109
/**
84110
* Build the Microsoft connector creation request
85111
* @return The Microsoft connector creation request
86112
*/
87-
fun build() = Microsoft(settings, scope)
113+
fun build() = Microsoft(settings, scope, activeCredentialId)
88114
}
89115
}
90116
}

src/main/kotlin/com/nylas/models/UrlForAuthenticationConfig.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ data class UrlForAuthenticationConfig(
5454
*/
5555
@Json(name = "login_hint")
5656
val loginHint: String? = null,
57+
/**
58+
* The credential ID to use for authentication (for multi-credential setups).
59+
* Allowed when response_type is "code".
60+
*/
61+
@Json(name = "credential_id")
62+
val credentialId: String? = null,
5763
) {
5864
/**
5965
* Builder for [UrlForAuthenticationConfig].
@@ -71,6 +77,7 @@ data class UrlForAuthenticationConfig(
7177
private var includeGrantScopes: Boolean? = null
7278
private var state: String? = null
7379
private var loginHint: String? = null
80+
private var credentialId: String? = null
7481

7582
/**
7683
* Set the integration provider type that you already had set up with Nylas for this application.
@@ -124,6 +131,13 @@ data class UrlForAuthenticationConfig(
124131
*/
125132
fun loginHint(loginHint: String) = apply { this.loginHint = loginHint }
126133

134+
/**
135+
* Set the credential ID to use for authentication (for multi-credential setups).
136+
* @param credentialId The credential ID.
137+
* @return This builder.
138+
*/
139+
fun credentialId(credentialId: String) = apply { this.credentialId = credentialId }
140+
127141
/**
128142
* Build the [UrlForAuthenticationConfig].
129143
* @return The [UrlForAuthenticationConfig].
@@ -138,6 +152,7 @@ data class UrlForAuthenticationConfig(
138152
includeGrantScopes,
139153
state,
140154
loginHint,
155+
credentialId,
141156
)
142157
}
143158
}

src/main/kotlin/com/nylas/util/CredentialDataAdapter.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,22 +122,28 @@ class GoogleServiceAccountCredentialDataAdapter {
122122
class ConnectorOverrideCredentialDataAdapter {
123123
@FromJson
124124
fun fromJson(reader: JsonReader): CredentialData.ConnectorOverride {
125+
var clientId: String? = null
126+
var clientSecret: String? = null
125127
val extraProperties = mutableMapOf<String, String>()
126128

127129
reader.beginObject()
128130
while (reader.hasNext()) {
129-
val key = reader.nextName()
130-
val value = reader.nextString()
131-
extraProperties[key] = value
131+
when (val key = reader.nextName()) {
132+
"client_id" -> clientId = reader.nextString()
133+
"client_secret" -> clientSecret = reader.nextString()
134+
else -> extraProperties[key] = reader.nextString()
135+
}
132136
}
133137
reader.endObject()
134138

135-
return CredentialData.ConnectorOverride(extraProperties)
139+
return CredentialData.ConnectorOverride(clientId, clientSecret, extraProperties)
136140
}
137141

138142
@ToJson
139143
fun toJson(writer: JsonWriter, value: CredentialData.ConnectorOverride?) {
140144
writer.beginObject()
145+
value?.clientId?.let { writer.name("client_id").value(it) }
146+
value?.clientSecret?.let { writer.name("client_secret").value(it) }
141147
value?.extraProperties?.forEach { (k, v) ->
142148
writer.name(k).value(v)
143149
}

0 commit comments

Comments
 (0)