Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3cf2d96
feat(data): AppSync Events Library Shells (#3009)
tylerjroach Apr 1, 2025
7be8e23
feat(data): AppSync Authorizers (#3011)
tylerjroach Apr 2, 2025
cbc383c
feat(data): Events API Contract shells (#3012)
tylerjroach Apr 3, 2025
3ca0692
feat(data): Event REST Publish for 200 codes (#3013)
tylerjroach Apr 8, 2025
880b91d
feat(data): Complete events class (#3016)
tylerjroach Apr 9, 2025
9ee12ba
feat(data): Events WebSocket Connection Logic + Subscribe (#3018)
tylerjroach Apr 11, 2025
d28f9e9
feat(data): Events API Connection ack timeout handling (#3020)
tylerjroach Apr 17, 2025
cd3110f
feat(data): Events Lib WS Publishing (#3027)
tylerjroach Apr 22, 2025
6d820c5
feat(data): Events Lib consistent exception handling (#3028)
tylerjroach Apr 23, 2025
ac4a7a4
feat(data): Update Events API Contract (#3029)
tylerjroach Apr 24, 2025
f6d81ee
feat(data): Fix Data event parsing + unit tests (#3031)
tylerjroach Apr 29, 2025
356f8ad
feat(data): Rename events packages (#3034)
tylerjroach Apr 30, 2025
ef3e097
feat(data): Events logs data removal (#3036)
tylerjroach Apr 30, 2025
140dea2
feat(data) Events Publish upate (#3037)
tylerjroach May 1, 2025
d60d032
feat(data) Events api contract lock (#3038)
tylerjroach May 1, 2025
cd7c149
feat(data): Update Events API Lib to new agreement (#3040)
tylerjroach May 6, 2025
01cc218
feat(data) AppSync Events WebSocket Unit Tests + Integration Tests (#…
tylerjroach May 9, 2025
33ea252
fix(data): Ignore test (#3050)
tylerjroach May 9, 2025
86326e6
fix(data): Events licenses (#3051)
tylerjroach May 9, 2025
7bdf665
feat(data): Pull Events Integration Test Data (#3052)
tylerjroach May 9, 2025
bfaec73
fix(data) Events fix test lockup (#3053)
tylerjroach May 9, 2025
3aa14dc
Merge branch 'main' into feat/appsync-events
tylerjroach May 12, 2025
f300d1e
feat(data): Add Events Unit Tests (#3055)
tylerjroach May 12, 2025
ace1910
fix(data) Events timeout fix (#3058)
tylerjroach May 13, 2025
bdbef6f
run integration tests
tylerjroach May 13, 2025
7814a4e
attempt to run events integration tests
tylerjroach May 13, 2025
67d9459
attempt to run events integration tests
tylerjroach May 13, 2025
3deeb5c
attempt to run events integration tests
tylerjroach May 13, 2025
cd05372
attempt to run events integration tests and stabilize unit tests
tylerjroach May 13, 2025
33d00b0
lint
tylerjroach May 13, 2025
18aecdd
lint
tylerjroach May 13, 2025
ac25f2a
lint
tylerjroach May 13, 2025
a4467e2
test update
tylerjroach May 13, 2025
19c9d90
test update
tylerjroach May 13, 2025
ee41e1d
fix(data) Modify unsubscribe event scenarios (#3059)
tylerjroach May 13, 2025
19f7fa6
set companion object to internal
tylerjroach May 13, 2025
356ec33
apiCheck fix
tylerjroach May 14, 2025
6579497
remove duplicate code
tylerjroach May 14, 2025
4bf0e9b
Merge branch 'main' into feat/appsync-events
tylerjroach May 15, 2025
7f9d7f3
Add README, CHANGELOG, and publishing prep
tylerjroach May 15, 2025
7873c53
Update scopes for Events
tylerjroach May 15, 2025
90533ec
ignore Events scopes on Amplify
tylerjroach May 15, 2025
27082fc
Update appsync/README.md
tylerjroach May 15, 2025
f573683
Update appsync/README.md
tylerjroach May 15, 2025
b157783
Improve test feedback
tylerjroach May 15, 2025
20ee84f
Merge remote-tracking branch 'origin/feat/appsync-events' into feat/a…
tylerjroach May 15, 2025
b1844ee
stabilize tests
tylerjroach May 15, 2025
d53cfe6
Fix events test lockups (#3067)
tylerjroach May 20, 2025
a65d62e
Merge branch 'main' into feat/appsync-events
tylerjroach May 20, 2025
659bd37
lint
tylerjroach May 20, 2025
5cc326d
fix test where order doesn't matter on received events
tylerjroach May 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ __pycache__/
#amplify
.amplify/
amplify/
#keep amplify gen2 backend projects
!**/androidTest/backend/amplify/
build/
dist/
node_modules/
Expand Down
1 change: 1 addition & 0 deletions appsync/aws-sdk-appsync-amplify/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
18 changes: 18 additions & 0 deletions appsync/aws-sdk-appsync-amplify/api/aws-sdk-appsync-amplify.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
public final class com/amazonaws/sdk/appsync/amplify/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
public static final field VERSION_NAME Ljava/lang/String;
public fun <init> ()V
}

public final class com/amazonaws/sdk/appsync/amplify/authorizers/AmplifyIamAuthorizer : com/amazonaws/sdk/appsync/core/AppSyncAuthorizer {
public fun <init> (Ljava/lang/String;)V
public fun getAuthorizationHeaders (Lcom/amazonaws/sdk/appsync/core/AppSyncRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class com/amazonaws/sdk/appsync/amplify/authorizers/AmplifyUserPoolAuthorizer : com/amazonaws/sdk/appsync/core/AppSyncAuthorizer {
public fun <init> ()V
public fun getAuthorizationHeaders (Lcom/amazonaws/sdk/appsync/core/AppSyncRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

50 changes: 50 additions & 0 deletions appsync/aws-sdk-appsync-amplify/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import java.util.Properties

plugins {
id("com.android.library")
id("kotlin-android")
}

apply(from = rootProject.file("configuration/publishing.gradle"))

fun readVersion() = Properties().run {
file("../version.properties").inputStream().use { load(it) }
get("VERSION_NAME").toString()
}

project.setProperty("VERSION_NAME", readVersion())
group = properties["POM_GROUP"].toString()

android {
namespace = "com.amazonaws.sdk.appsync.amplify"
}

dependencies {

api(project(":aws-sdk-appsync-core"))
api(project(":core"))

implementation(project(":aws-auth-cognito"))
implementation(project(":aws-core"))
implementation(libs.aws.signing)

testImplementation(libs.test.junit)
testImplementation(libs.test.mockk)
testImplementation(libs.test.kotlin.coroutines)
testImplementation(libs.test.kotest.assertions)
}
5 changes: 5 additions & 0 deletions appsync/aws-sdk-appsync-amplify/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
POM_GROUP=com.amazonaws
POM_ARTIFACT_ID=aws-sdk-appsync-amplify
POM_NAME=Amplify Extensions for AWS AppSync
POM_DESCRIPTION=Amplify Extensions for AWS AppSync
POM_PACKAGING=aar
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.sdk.appsync.amplify.authorizers

import com.amazonaws.sdk.appsync.amplify.util.AppSyncRequestSigner
import com.amazonaws.sdk.appsync.core.AppSyncAuthorizer
import com.amazonaws.sdk.appsync.core.AppSyncRequest
import com.amazonaws.sdk.appsync.core.authorizers.IamAuthorizer
import org.jetbrains.annotations.VisibleForTesting

/**
* Authorizer implementation that provides IAM signing through Amplify & AWS Kotlin SDK (Smithy).
*/
class AmplifyIamAuthorizer @VisibleForTesting internal constructor(
private val region: String,
private val requestSigner: AppSyncRequestSigner
) : AppSyncAuthorizer {

constructor(region: String) : this(region, requestSigner = AppSyncRequestSigner())

private val iamAuthorizer = IamAuthorizer { requestSigner.signAppSyncRequest(it, region) }

override suspend fun getAuthorizationHeaders(request: AppSyncRequest) =
iamAuthorizer.getAuthorizationHeaders(request)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.sdk.appsync.amplify.authorizers

import com.amazonaws.sdk.appsync.core.AppSyncAuthorizer
import com.amazonaws.sdk.appsync.core.AppSyncRequest
import com.amazonaws.sdk.appsync.core.authorizers.AuthTokenAuthorizer
import com.amplifyframework.auth.AuthCredentialsProvider
import com.amplifyframework.auth.CognitoCredentialsProvider
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
import org.jetbrains.annotations.VisibleForTesting

/**
* Authorizer implementation that provides Cognito User Pool tokens via Amplify Auth.
*/
class AmplifyUserPoolAuthorizer @VisibleForTesting internal constructor(
private val accessTokenProvider: AccessTokenProvider
) : AppSyncAuthorizer {

constructor() : this(accessTokenProvider = AccessTokenProvider())

private val authTokenAuthorizer = AuthTokenAuthorizer(
fetchLatestAuthToken = accessTokenProvider::fetchLatestCognitoAuthToken
)

override suspend fun getAuthorizationHeaders(request: AppSyncRequest) =
authTokenAuthorizer.getAuthorizationHeaders(request)
}

internal class AccessTokenProvider(
private val credentialsProvider: AuthCredentialsProvider = CognitoCredentialsProvider()
) {
suspend fun fetchLatestCognitoAuthToken() = credentialsProvider.getAccessToken()

private suspend fun AuthCredentialsProvider.getAccessToken() = suspendCoroutine { continuation ->
getAccessToken(
{ token -> continuation.resume(token) },
{ error -> continuation.resumeWithException(error) }
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.sdk.appsync.amplify.util

import aws.smithy.kotlin.runtime.InternalApi
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSignedBodyHeader
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningConfig
import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner
import aws.smithy.kotlin.runtime.http.Headers
import aws.smithy.kotlin.runtime.http.HttpBody
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.http.toHttpBody
import aws.smithy.kotlin.runtime.net.url.Url
import com.amazonaws.sdk.appsync.core.AppSyncRequest
import com.amplifyframework.auth.AuthCredentialsProvider
import com.amplifyframework.auth.CognitoCredentialsProvider

internal class AppSyncRequestSigner(
private val credentialsProvider: AuthCredentialsProvider = CognitoCredentialsProvider(),
private val awsSigner: AwsSigner = DefaultAwsSigner
) {

suspend fun signAppSyncRequest(request: AppSyncRequest, region: String): Map<String, String> {
// First translate the Apollo request to a Smithy request so it can be signed
val smithyRequest = request.toSmithyRequest()

// Sign the Smithy request
val signedRequest = signRequest(
awsRegion = region,
credentials = credentialsProvider.resolve(),
service = "appsync",
request = smithyRequest
)

// Return the headers from the signed request
return signedRequest.headers.entries().associate { it.key to it.value.first() }
}

@OptIn(InternalApi::class)
private suspend fun signRequest(
awsRegion: String,
credentials: Credentials,
service: String,
request: HttpRequest
): HttpRequest {
val signingConfig = AwsSigningConfig {
region = awsRegion
useDoubleUriEncode = true
this.service = service
this.credentials = credentials
signedBodyHeader = AwsSignedBodyHeader.X_AMZ_CONTENT_SHA256
}

// Generate the signature for the smithy request
return awsSigner.sign(request, signingConfig).output
}

private fun AppSyncRequest.toSmithyRequest() = HttpRequest(
method = method.toSmithyMethod(),
url = Url.parse(url),
headers = createSmithyHeaders(headers),
body = body?.toHttpBody() ?: HttpBody.Empty
)

private fun AppSyncRequest.HttpMethod.toSmithyMethod() = when (this) {
AppSyncRequest.HttpMethod.GET -> aws.smithy.kotlin.runtime.http.HttpMethod.GET
AppSyncRequest.HttpMethod.POST -> aws.smithy.kotlin.runtime.http.HttpMethod.POST
}

private fun createSmithyHeaders(headers: Map<String, String>) = Headers {
headers.forEach {
this.append(it.key, it.value)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.sdk.appsync.amplify.authorizers

import com.amazonaws.sdk.appsync.amplify.util.AppSyncRequestSigner
import com.amazonaws.sdk.appsync.core.AppSyncRequest
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.maps.shouldContainExactly
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Test

class AmplifyIamAuthorizerTest {
private val region = "us-east-1"

@Test
fun `iam authorizer gets token from amplify`() = runTest {
val request = mockk<AppSyncRequest>()
val signer = mockk<AppSyncRequestSigner> {
coEvery {
signAppSyncRequest(request, region)
} returns mapOf("Authorization" to "test-signature")
}

val authorizer = AmplifyIamAuthorizer(region, signer)

authorizer.getAuthorizationHeaders(request) shouldContainExactly mapOf("Authorization" to "test-signature")
}

@Test
fun `iam authorizer throws if failed to fetch token from amplify`() = runTest {
val request = mockk<AppSyncRequest>()
val signer = mockk<AppSyncRequestSigner> {
coEvery {
signAppSyncRequest(request, region)
} throws IllegalStateException()
}

val authorizer = AmplifyIamAuthorizer(region, signer)

shouldThrow<IllegalStateException> {
authorizer.getAuthorizationHeaders(request)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.sdk.appsync.amplify.authorizers

import com.amplifyframework.auth.AuthCredentialsProvider
import com.amplifyframework.core.Consumer
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.maps.shouldContainExactly
import io.mockk.CapturingSlot
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Test

class AmplifyUserPoolAuthorizerTest {

@Test
fun `user pool authorizer gets token from amplify`() = runTest {
val expectedValue = "test-signature"
val slot = CapturingSlot<Consumer<String>>()
val cognitoCredentialsProvider = mockk<AuthCredentialsProvider> {
every { getAccessToken(capture(slot), any()) } answers {
slot.captured.accept(expectedValue)
}
}
val accessTokenProvider = AccessTokenProvider(cognitoCredentialsProvider)
val authorizer = AmplifyUserPoolAuthorizer(accessTokenProvider)

authorizer.getAuthorizationHeaders(mockk()) shouldContainExactly mapOf("Authorization" to expectedValue)
}

@Test
fun `user pool authorizer throws if failed to fetch token from amplify`() = runTest {
val cognitoCredentialsProvider = mockk<AuthCredentialsProvider> {
every { getAccessToken(any(), any()) } throws IllegalStateException()
}
val accessTokenProvider = AccessTokenProvider(cognitoCredentialsProvider)
val authorizer = AmplifyUserPoolAuthorizer(accessTokenProvider)

shouldThrow<IllegalStateException> {
authorizer.getAuthorizationHeaders(mockk())
}
}
}
Loading