Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ class KotlinMultiplatformConventionPlugin : Plugin<Project> {
}
}
}

compilerOptions {
freeCompilerArgs.addAll(amplifyInternalMarkers.map { "-opt-in=$it" })
freeCompilerArgs.add("-Xconsistent-data-class-copy-visibility")
}
}
}
}
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ plugins {
alias(libs.plugins.amplify.kover) apply false
alias(libs.plugins.amplify.ktlint) apply false
alias(libs.plugins.amplify.licenses) apply false

alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.android.kotlin.multiplatform.library) apply false
alias(libs.plugins.android.lint) apply false
}

tasks.register<Delete>("clean").configure {
Expand Down
1 change: 1 addition & 0 deletions foundation-bridge/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
21 changes: 21 additions & 0 deletions foundation-bridge/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
alias(libs.plugins.amplify.kmp)
}

kotlin {
sourceSets {
commonMain {
dependencies {
api(project(":foundation"))
api(libs.aws.credentials)
}
}

commonTest {
dependencies {
implementation(libs.test.kotest.assertions)
implementation(libs.test.kotlin.coroutines)
}
}
}
}
2 changes: 2 additions & 0 deletions foundation-bridge/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2026 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.amplifyframework.foundation.credentials

import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
import aws.smithy.kotlin.runtime.collections.Attributes
import aws.smithy.kotlin.runtime.time.Instant as SmithyInstant
import com.amplifyframework.annotations.InternalAmplifyApi
import kotlin.time.ExperimentalTime
import kotlin.time.Instant

@InternalAmplifyApi
@OptIn(ExperimentalTime::class)
fun AwsCredentials.toSmithyCredentials(): Credentials = when (this) {
is AwsCredentials.Static -> Credentials(
accessKeyId = this.accessKeyId,
secretAccessKey = this.secretAccessKey
)
is AwsCredentials.Temporary -> Credentials(
accessKeyId = this.accessKeyId,
secretAccessKey = this.secretAccessKey,
sessionToken = this.sessionToken,
expiration = this.expiration.toSmithyInstant()
)
}

@InternalAmplifyApi
fun AwsCredentialsProvider<*>.toSmithyProvider() = object : CredentialsProvider {
override suspend fun resolve(attributes: Attributes) = this@toSmithyProvider.resolve().toSmithyCredentials()
}

@OptIn(ExperimentalTime::class)
private fun Instant.toSmithyInstant() = SmithyInstant.fromEpochSeconds(epochSeconds)
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2026 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.amplifyframework.foundation.credentials

import aws.smithy.kotlin.runtime.collections.emptyAttributes
import aws.smithy.kotlin.runtime.time.Instant as SmithyInstant
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.shouldBe
import kotlin.time.ExperimentalTime
import kotlin.time.Instant
import kotlinx.coroutines.test.runTest
import org.junit.Test

@OptIn(ExperimentalTime::class)
class SmithyCredentialsTest {
private val expiration = Instant.fromEpochSeconds(1770130997)

@Test
fun `maps static credentials`() {
val awsCredentials = AwsCredentials.Static(
accessKeyId = "access",
secretAccessKey = "secret"
)

val mapped = awsCredentials.toSmithyCredentials()

mapped.accessKeyId shouldBe "access"
mapped.secretAccessKey shouldBe "secret"
mapped.sessionToken.shouldBeNull()
mapped.expiration.shouldBeNull()
}

@Test
fun `maps temporary credentials`() {
val awsCredentials = AwsCredentials.Temporary(
accessKeyId = "access",
secretAccessKey = "secret",
sessionToken = "session",
expiration = expiration
)

val mapped = awsCredentials.toSmithyCredentials()

mapped.accessKeyId shouldBe "access"
mapped.secretAccessKey shouldBe "secret"
mapped.sessionToken shouldBe "session"
mapped.expiration shouldBe SmithyInstant.fromEpochSeconds(expiration.epochSeconds)
}

@Test
fun `provider maps static credentials`() = runTest {
val staticProvider = AwsCredentialsProvider<AwsCredentials> {
AwsCredentials.Static(accessKeyId = "access", secretAccessKey = "secret")
}
val mappedProvider = staticProvider.toSmithyProvider()
val credentials = mappedProvider.resolve(emptyAttributes())

credentials.accessKeyId shouldBe "access"
credentials.secretAccessKey shouldBe "secret"
credentials.sessionToken.shouldBeNull()
credentials.expiration.shouldBeNull()
}

@Test
fun `provider maps temporary credentials`() = runTest {
val temporaryProvider = AwsCredentialsProvider<AwsCredentials> {
AwsCredentials.Temporary(
accessKeyId = "access",
secretAccessKey = "secret",
sessionToken = "session",
expiration = expiration
)
}
val mappedProvider = temporaryProvider.toSmithyProvider()
val credentials = mappedProvider.resolve(emptyAttributes())

credentials.accessKeyId shouldBe "access"
credentials.secretAccessKey shouldBe "secret"
credentials.sessionToken shouldBe "session"
credentials.expiration shouldBe SmithyInstant.fromEpochSeconds(expiration.epochSeconds)
}
}
1 change: 1 addition & 0 deletions foundation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
55 changes: 55 additions & 0 deletions foundation/api/foundation.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
public abstract interface class com/amplifyframework/foundation/credentials/AwsCredentials {
public abstract fun getAccessKeyId ()Ljava/lang/String;
public abstract fun getSecretAccessKey ()Ljava/lang/String;
}

public final class com/amplifyframework/foundation/credentials/AwsCredentials$Static : com/amplifyframework/foundation/credentials/AwsCredentials {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public fun getAccessKeyId ()Ljava/lang/String;
public fun getSecretAccessKey ()Ljava/lang/String;
}

public final class com/amplifyframework/foundation/credentials/AwsCredentials$Temporary : com/amplifyframework/foundation/credentials/AwsCredentials {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/time/Instant;)V
public fun getAccessKeyId ()Ljava/lang/String;
public final fun getExpiration ()Lkotlin/time/Instant;
public fun getSecretAccessKey ()Ljava/lang/String;
public final fun getSessionToken ()Ljava/lang/String;
}

public abstract interface class com/amplifyframework/foundation/credentials/AwsCredentialsProvider {
public abstract fun resolve (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public abstract class com/amplifyframework/foundation/exceptions/AmplifyException : java/lang/Exception {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getRecoverySuggestion ()Ljava/lang/String;
public fun toString ()Ljava/lang/String;
}

public abstract interface class com/amplifyframework/foundation/results/Result {
}

public final class com/amplifyframework/foundation/results/Result$Failure : com/amplifyframework/foundation/results/Result {
public fun <init> (Ljava/lang/Object;)V
public final fun component1 ()Ljava/lang/Object;
public final fun copy (Ljava/lang/Object;)Lcom/amplifyframework/foundation/results/Result$Failure;
public static synthetic fun copy$default (Lcom/amplifyframework/foundation/results/Result$Failure;Ljava/lang/Object;ILjava/lang/Object;)Lcom/amplifyframework/foundation/results/Result$Failure;
public fun equals (Ljava/lang/Object;)Z
public final fun getError ()Ljava/lang/Object;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/amplifyframework/foundation/results/Result$Success : com/amplifyframework/foundation/results/Result {
public fun <init> (Ljava/lang/Object;)V
public final fun component1 ()Ljava/lang/Object;
public final fun copy (Ljava/lang/Object;)Lcom/amplifyframework/foundation/results/Result$Success;
public static synthetic fun copy$default (Lcom/amplifyframework/foundation/results/Result$Success;Ljava/lang/Object;ILjava/lang/Object;)Lcom/amplifyframework/foundation/results/Result$Success;
public fun equals (Ljava/lang/Object;)Z
public final fun getData ()Ljava/lang/Object;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

21 changes: 21 additions & 0 deletions foundation/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
alias(libs.plugins.amplify.kmp)
alias(libs.plugins.amplify.publishing)
}

kotlin {
sourceSets {
commonMain {
dependencies {
api(project(":annotations"))
}
}

commonTest {
dependencies {
implementation(libs.test.kotest.assertions)
implementation(project(":testutils"))
}
}
}
}
4 changes: 4 additions & 0 deletions foundation/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
POM_ARTIFACT_ID=foundation
POM_NAME=Amplify Framework for Android - Foundation
POM_DESCRIPTION=Amplify Framework for Android - Foundational library
POM_PACKAGING=aar
2 changes: 2 additions & 0 deletions foundation/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2026 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.amplifyframework.foundation.credentials

import kotlin.time.ExperimentalTime
import kotlin.time.Instant

/**
* Provides access to the AWS credentials used for accessing AWS services: AWS
* access key ID and secret access key. These credentials are used to securely
* sign requests to AWS services.
*
* For more details on AWS access keys,
* [see](https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html)
*/
sealed interface AwsCredentials {
/**
* The AWS access key ID for this credentials object.
*/
val accessKeyId: String

/**
* The AWS secret access key for this credentials object.
*/
val secretAccessKey: String

/**
* Long-term AWS credentials, such as those granted to Admin Users
*/
class Static(
override val accessKeyId: String,
override val secretAccessKey: String
) : AwsCredentials

/**
* Temporary credentials, such as those vended by STS
*/
@OptIn(ExperimentalTime::class)
class Temporary(
override val accessKeyId: String,
override val secretAccessKey: String,
val sessionToken: String,
val expiration: Instant
) : AwsCredentials
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2026 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.amplifyframework.foundation.credentials

/**
* Interface for providers of AWS Credentials
*/
fun interface AwsCredentialsProvider<out T : AwsCredentials> {
/**
* Resolve and return the credentials
*/
suspend fun resolve(): T
}
Loading