Fill OTP fields with SMS codes (#900)

* Fill OTP fields with SMS codes

* Allow SMS OTP fill also for web origins

* Introduce free and nonFree build variants

* Fix up workflow

* Improve layout and feature detection

* Workflow changes

* Add Changelog entry

* github: update release workflow for nonFree/Free split

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* Switch to lifecycleScope

* github: make snapshot deploy free variant

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
Fabian Henneke 2020-07-02 13:49:32 +02:00 committed by GitHub
parent c702d4aa9e
commit ca9c951a53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 330 additions and 25 deletions

View file

@ -51,7 +51,7 @@ jobs:
run: ./gradlew dependencies run: ./gradlew dependencies
- name: Build release app - name: Build release app
run: ./gradlew :app:assembleRelease run: ./gradlew :app:assembleFreeRelease
env: env:
SNAPSHOT: "true" SNAPSHOT: "true"

View file

@ -7,7 +7,7 @@ jobs:
strategy: strategy:
matrix: matrix:
api-level: [23, 29] api-level: [23, 29]
variant: [Debug, Release] variant: [freeDebug, freeRelease, nonFreeRelease]
steps: steps:
- name: Check if relevant files have changed - name: Check if relevant files have changed

View file

@ -50,20 +50,26 @@ jobs:
- name: Download gradle dependencies - name: Download gradle dependencies
run: ./gradlew dependencies run: ./gradlew dependencies
- name: Build release APK and bundle - name: Build release binaries
run: ./gradlew :app:assembleRelease :app:bundleRelease run: ./gradlew :app:assembleFreeRelease :app:assembleNonFreeRelease :app:bundleNonFreeRelease
- name: Upload release APK - name: Upload non-free release APK
uses: actions/upload-artifact@master uses: actions/upload-artifact@master
with: with:
name: APS Release APK name: APS Non-Free Release APK
path: app/build/outputs/apk/release/app-release.apk path: app/build/outputs/apk/nonFree/release/app-release.apk
- name: Upload release Bundle - name: Upload non-free release Bundle
uses: actions/upload-artifact@master uses: actions/upload-artifact@master
with: with:
name: APS Release Bundle name: APS Non-Free Release Bundle
path: app/build/outputs/bundle/release/app-release.aab path: app/build/outputs/bundle/nonFree/release/app-release.aab
- name: Upload free release APK
uses: actions/upload-artifact@master
with:
name: APS Free Release APK
path: app/build/outputs/apk/free/release/app-release.apk
- name: Clean secrets - name: Clean secrets
if: always() if: always()
@ -77,17 +83,23 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v1 uses: actions/checkout@v1
- name: Get APK - name: Get Non-Free Release APK
uses: actions/download-artifact@v1 uses: actions/download-artifact@v1
with: with:
name: APS Release APK name: APS Non-Free Release APK
path: artifacts path: artifacts/nonFree
- name: Get Bundle - name: Get Non-Free Bundle
uses: actions/download-artifact@v1 uses: actions/download-artifact@v1
with: with:
name: APS Release Bundle name: APS Non-Free Release Bundle
path: artifacts path: artifacts/nonFree
- name: Get Free Release APK
uses: actions/download-artifact@v1
with:
name: APS Free Release APK
path: artifacts/free
- name: Get Changelog Entry - name: Get Changelog Entry
id: changelog_reader id: changelog_reader
@ -112,22 +124,32 @@ jobs:
id: get_version id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
- name: Upload Release Apk - name: Upload Non-Free Release Apk
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./artifacts/app-release.apk asset_path: ./artifacts/nonFree/app-release.apk
asset_name: APS_${{ steps.get_version.outputs.VERSION }}.apk asset_name: APS-nonFree_${{ steps.get_version.outputs.VERSION }}.apk
asset_content_type: application/vnd.android.package-archive asset_content_type: application/vnd.android.package-archive
- name: Upload Release Bundle - name: Upload Non-Free Release Bundle
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./artifacts/app-release.aab asset_path: ./artifacts/nonFree/app-release.aab
asset_name: APS_${{ steps.get_version.outputs.VERSION }}.aab asset_name: APS-nonFree_${{ steps.get_version.outputs.VERSION }}.aab
asset_content_type: application/octet-stream asset_content_type: application/octet-stream
- name: Upload Free Release Apk
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./artifacts/free/app-release.apk
asset_name: APS-free_${{ steps.get_version.outputs.VERSION }}.apk
asset_content_type: application/vnd.android.package-archive

View file

@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
- TOTP support is reintroduced by popular demand. HOTP continues to be unsupported and heavily discouraged. - TOTP support is reintroduced by popular demand. HOTP continues to be unsupported and heavily discouraged.
- Initial support for detecting and filling OTP fields with Autofill - Initial support for detecting and filling OTP fields with Autofill
- OTP codes can be automatically filled from SMS (requires Android P+ and Google Play Services)
- Importing TOTP secrets using QR codes - Importing TOTP secrets using QR codes
- Navigate into newly created folders and scroll to newly created passwords - Navigate into newly created folders and scroll to newly created passwords

View file

@ -68,6 +68,15 @@ android {
buildTypes.release.signingConfig = signingConfigs.release buildTypes.release.signingConfig = signingConfigs.release
buildTypes.debug.signingConfig = signingConfigs.release buildTypes.debug.signingConfig = signingConfigs.release
} }
flavorDimensions "free"
productFlavors {
free {
versionNameSuffix "-free"
}
nonFree {
}
}
} }
dependencies { dependencies {
@ -117,6 +126,8 @@ dependencies {
debugImplementation deps.third_party.whatthestack debugImplementation deps.third_party.whatthestack
} }
nonFreeImplementation deps.non_free.google_play_auth_api_phone
// Testing-only dependencies // Testing-only dependencies
androidTestImplementation deps.testing.junit androidTestImplementation deps.testing.junit
androidTestImplementation deps.testing.kotlin_test_junit androidTestImplementation deps.testing.kotlin_test_junit

View file

@ -0,0 +1,28 @@
/*
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package com.zeapo.pwdstore.autofill.oreo.ui
import android.content.Context
import android.content.IntentSender
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.zeapo.pwdstore.autofill.oreo.FormOrigin
@RequiresApi(Build.VERSION_CODES.O)
@Suppress("UNUSED_PARAMETER")
class AutofillSmsActivity : AppCompatActivity() {
companion object {
fun shouldOfferFillFromSms(context: Context): Boolean {
return false
}
fun makeFillOtpFromSmsIntentSender(context: Context): IntentSender {
throw NotImplementedError("Filling OTPs from SMS requires non-free dependencies")
}
}
}

View file

@ -138,6 +138,11 @@
<activity <activity
android:name=".autofill.oreo.ui.AutofillSaveActivity" android:name=".autofill.oreo.ui.AutofillSaveActivity"
android:theme="@style/NoBackgroundTheme" /> android:theme="@style/NoBackgroundTheme" />
<activity
android:name=".autofill.oreo.ui.AutofillSmsActivity"
android:configChanges="orientation"
android:theme="@style/DialogLikeTheme"
android:windowSoftInputMode="adjustNothing" />
<activity <activity
android:name=".autofill.oreo.ui.AutofillPublisherChangedActivity" android:name=".autofill.oreo.ui.AutofillPublisherChangedActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"

View file

@ -87,7 +87,7 @@ val AssistStructure.ViewNode.webOrigin: String?
"$scheme://$domain" "$scheme://$domain"
} }
data class Credentials(val username: String?, val password: String, val otp: String?) { data class Credentials(val username: String?, val password: String?, val otp: String?) {
companion object { companion object {
fun fromStoreEntry( fun fromStoreEntry(
context: Context, context: Context,
@ -141,6 +141,13 @@ fun makeGenerateAndFillRemoteView(context: Context, formOrigin: FormOrigin): Rem
return makeRemoteView(context, title, summary, iconRes) return makeRemoteView(context, title, summary, iconRes)
} }
fun makeFillOtpFromSmsRemoteView(context: Context, formOrigin: FormOrigin): RemoteViews {
val title = formOrigin.getPrettyIdentifier(context, untrusted = true)
val summary = context.getString(R.string.oreo_autofill_fill_otp_from_sms)
val iconRes = R.drawable.ic_autofill_sms
return makeRemoteView(context, title, summary, iconRes)
}
fun makePlaceholderRemoteView(context: Context): RemoteViews { fun makePlaceholderRemoteView(context: Context): RemoteViews {
return makeRemoteView(context, "PLACEHOLDER", "PLACEHOLDER", R.mipmap.ic_launcher) return makeRemoteView(context, "PLACEHOLDER", "PLACEHOLDER", R.mipmap.ic_launcher)
} }

View file

@ -14,7 +14,7 @@ import androidx.annotation.RequiresApi
import com.github.ajalt.timberkt.e import com.github.ajalt.timberkt.e
enum class AutofillAction { enum class AutofillAction {
Match, Search, Generate Match, Search, Generate, FillOtpFromSms
} }
/** /**
@ -112,8 +112,13 @@ sealed class AutofillScenario<out T : Any> {
AutofillAction.Match -> passwordFieldsToFillOnMatch + listOfNotNull(otp) AutofillAction.Match -> passwordFieldsToFillOnMatch + listOfNotNull(otp)
AutofillAction.Search -> passwordFieldsToFillOnSearch + listOfNotNull(otp) AutofillAction.Search -> passwordFieldsToFillOnSearch + listOfNotNull(otp)
AutofillAction.Generate -> passwordFieldsToFillOnGenerate AutofillAction.Generate -> passwordFieldsToFillOnGenerate
AutofillAction.FillOtpFromSms -> listOfNotNull(otp)
} }
return when { return when {
action == AutofillAction.FillOtpFromSms -> {
// When filling from an SMS, we cannot get any data other than the OTP itself.
credentialFieldsToFill
}
credentialFieldsToFill.isNotEmpty() -> { credentialFieldsToFill.isNotEmpty() -> {
// If the current action would fill into any password field, we also fill into the // If the current action would fill into any password field, we also fill into the
// username field if possible. // username field if possible.

View file

@ -25,6 +25,7 @@ import com.zeapo.pwdstore.autofill.oreo.ui.AutofillDecryptActivity
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillFilterView import com.zeapo.pwdstore.autofill.oreo.ui.AutofillFilterView
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillPublisherChangedActivity import com.zeapo.pwdstore.autofill.oreo.ui.AutofillPublisherChangedActivity
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSaveActivity import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSaveActivity
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSmsActivity
import java.io.File import java.io.File
/** /**
@ -285,6 +286,14 @@ class FillableForm private constructor(
return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Generate) return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Generate)
} }
private fun makeFillOtpFromSmsDataset(context: Context): Dataset? {
if (scenario.fieldsToFillOn(AutofillAction.FillOtpFromSms).isEmpty()) return null
if (!AutofillSmsActivity.shouldOfferFillFromSms(context)) return null
val remoteView = makeFillOtpFromSmsRemoteView(context, formOrigin)
val intentSender = AutofillSmsActivity.makeFillOtpFromSmsIntentSender(context)
return makePlaceholderDataset(remoteView, intentSender, AutofillAction.FillOtpFromSms)
}
private fun makePublisherChangedDataset( private fun makePublisherChangedDataset(
context: Context, context: Context,
publisherChangedException: AutofillPublisherChangedException publisherChangedException: AutofillPublisherChangedException
@ -341,6 +350,10 @@ class FillableForm private constructor(
hasDataset = true hasDataset = true
addDataset(it) addDataset(it)
} }
makeFillOtpFromSmsDataset(context)?.let {
hasDataset = true
addDataset(it)
}
if (!hasDataset) return null if (!hasDataset) return null
makeSaveInfo()?.let { setSaveInfo(it) } makeSaveInfo()?.let { setSaveInfo(it) }
setClientState(clientState) setClientState(clientState)

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM9,11L7,11L7,9h2v2zM13,11h-2L11,9h2v2zM17,11h-2L15,9h2v2z" />
</vector>

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
~ SPDX-License-Identifier: GPL-3.0-only
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10dp"
tools:context="com.zeapo.pwdstore.autofill.oreo.ui.AutofillFilterView">
<ImageView
android:id="@+id/cover"
android:layout_width="0dp"
android:layout_height="50dp"
android:contentDescription="@string/app_name"
android:src="@drawable/ic_launcher_foreground"
app:layout_constraintBottom_toTopOf="@id/text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="10dp"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/oreo_autofill_waiting_for_sms"
android:layout_margin="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cover" />
<ProgressBar
android:id="@+id/progress"
style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:layout_constraintBottom_toTopOf="@id/cancelButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text" />
<Button
android:id="@+id/cancelButton"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_margin="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_cancel"
android:textColor="?attr/colorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/progress" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -253,6 +253,8 @@
<string name="oreo_autofill_save_app_not_supported">This app is currently not supported</string> <string name="oreo_autofill_save_app_not_supported">This app is currently not supported</string>
<string name="oreo_autofill_save_passwords_dont_match">Passwords don\'t match</string> <string name="oreo_autofill_save_passwords_dont_match">Passwords don\'t match</string>
<string name="oreo_autofill_generate_password">Generate password…</string> <string name="oreo_autofill_generate_password">Generate password…</string>
<string name="oreo_autofill_fill_otp_from_sms">Extract code from SMS…</string>
<string name="oreo_autofill_waiting_for_sms">Waiting for SMS…</string>
<string name="oreo_autofill_max_matches_reached">Maximum number of matches (%1$d) reached; clear matches before adding new ones.</string> <string name="oreo_autofill_max_matches_reached">Maximum number of matches (%1$d) reached; clear matches before adding new ones.</string>
<string name="oreo_autofill_warning_publisher_header">This app\'s publisher has changed since you first associated a Password Store entry with it:</string> <string name="oreo_autofill_warning_publisher_header">This app\'s publisher has changed since you first associated a Password Store entry with it:</string>
<string name="oreo_autofill_warning_publisher_footer"><b>The currently installed app may be trying to steal your credentials by pretending to be a trusted app.</b>\n\nTry to uninstall and reinstall the app from a trusted source, such as the Play Store, Amazon Appstore, F-Droid, or your phone manufacturer\'s store.</string> <string name="oreo_autofill_warning_publisher_footer"><b>The currently installed app may be trying to steal your credentials by pretending to be a trusted app.</b>\n\nTry to uninstall and reinstall the app from a trusted source, such as the Play Store, Amazon Appstore, F-Droid, or your phone manufacturer\'s store.</string>

View file

@ -0,0 +1,136 @@
/*
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package com.zeapo.pwdstore.autofill.oreo.ui
import android.app.Activity
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.IntentSender
import android.os.Build
import android.os.Bundle
import android.view.autofill.AutofillManager
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.github.ajalt.timberkt.e
import com.github.ajalt.timberkt.w
import com.google.android.gms.auth.api.phone.SmsCodeRetriever
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.tasks.Tasks
import com.zeapo.pwdstore.autofill.oreo.AutofillAction
import com.zeapo.pwdstore.autofill.oreo.Credentials
import com.zeapo.pwdstore.autofill.oreo.FillableForm
import com.zeapo.pwdstore.databinding.ActivityOreoAutofillSmsBinding
import com.zeapo.pwdstore.utils.viewBinding
import kotlinx.coroutines.launch
@RequiresApi(Build.VERSION_CODES.O)
class AutofillSmsActivity : AppCompatActivity() {
companion object {
private var fillOtpFromSmsRequestCode = 1
fun shouldOfferFillFromSms(context: Context): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
return false
val googleApiAvailabilityInstance = GoogleApiAvailability.getInstance()
val googleApiStatus = googleApiAvailabilityInstance.isGooglePlayServicesAvailable(context)
if (googleApiStatus != ConnectionResult.SUCCESS) {
w { "Google Play Services unavailable or not updated: ${googleApiAvailabilityInstance.getErrorString(googleApiStatus)}" }
return false
}
// https://developer.android.com/guide/topics/text/autofill-services#sms-autofill
if (googleApiAvailabilityInstance.getApkVersion(context) < 190056000) {
w { "Google Play Service 19.0.56 or higher required for SMS OTP Autofill" }
return false
}
return true
}
fun makeFillOtpFromSmsIntentSender(context: Context): IntentSender {
val intent = Intent(context, AutofillSmsActivity::class.java)
return PendingIntent.getActivity(
context,
fillOtpFromSmsRequestCode++,
intent,
PendingIntent.FLAG_CANCEL_CURRENT
).intentSender
}
}
private val binding by viewBinding(ActivityOreoAutofillSmsBinding::inflate)
private lateinit var clientState: Bundle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setResult(RESULT_CANCELED)
binding.cancelButton.setOnClickListener {
finish()
}
}
override fun onStart() {
super.onStart()
clientState = intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE) ?: run {
e { "AutofillSmsActivity started without EXTRA_CLIENT_STATE" }
finish()
return
}
registerReceiver(smsCodeRetrievedReceiver, IntentFilter(SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION), SmsRetriever.SEND_PERMISSION, null)
lifecycleScope.launch {
waitForSms()
}
}
// Retry starting the SMS code retriever after a permission request.
@Suppress("DEPRECATION")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != Activity.RESULT_OK)
return
lifecycleScope.launch {
waitForSms()
}
}
private fun waitForSms() {
val smsClient = SmsCodeRetriever.getAutofillClient(this@AutofillSmsActivity)
try {
Tasks.await(smsClient.startSmsCodeRetriever())
} catch (e: ResolvableApiException) {
e.startResolutionForResult(this, 1)
} catch (e: Exception) {
e(e)
finish()
}
}
private val smsCodeRetrievedReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val smsCode = intent.getStringExtra(SmsCodeRetriever.EXTRA_SMS_CODE)
val fillInDataset =
FillableForm.makeFillInDataset(
this@AutofillSmsActivity,
Credentials(null, null, smsCode),
clientState,
AutofillAction.FillOtpFromSms
)
setResult(RESULT_OK, Intent().apply {
putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset)
})
finish()
}
}
}

View file

@ -64,6 +64,10 @@ ext.deps = [
whatthestack: 'com.github.haroldadmin:WhatTheStack:0.0.3', whatthestack: 'com.github.haroldadmin:WhatTheStack:0.0.3',
], ],
non_free: [
google_play_auth_api_phone: 'com.google.android.gms:play-services-auth-api-phone:17.4.0',
],
testing: [ testing: [
junit: 'junit:junit:4.13', junit: 'junit:junit:4.13',
kotlin_test_junit: 'org.jetbrains.kotlin:kotlin-test-junit:1.3.72', kotlin_test_junit: 'org.jetbrains.kotlin:kotlin-test-junit:1.3.72',

View file

@ -5,7 +5,7 @@ mkdir -p "$SSHDIR"
echo "$ACTIONS_DEPLOY_KEY" > "$SSHDIR/key" echo "$ACTIONS_DEPLOY_KEY" > "$SSHDIR/key"
chmod 600 "$SSHDIR/key" chmod 600 "$SSHDIR/key"
export SERVER_DEPLOY_STRING="$SSH_USERNAME@$SERVER_ADDRESS:$SERVER_DESTINATION" export SERVER_DEPLOY_STRING="$SSH_USERNAME@$SERVER_ADDRESS:$SERVER_DESTINATION"
cd "$GITHUB_WORKSPACE/app/build/outputs/apk/release" cd "$GITHUB_WORKSPACE/app/build/outputs/apk/free/release"
rm output.json rm output.json
rsync -ahvcr --omit-dir-times --progress --delete --no-o --no-g -e "ssh -i $SSHDIR/key -o StrictHostKeyChecking=no -p $SSH_PORT" . "$SERVER_DEPLOY_STRING" || exit 1 rsync -ahvcr --omit-dir-times --progress --delete --no-o --no-g -e "ssh -i $SSHDIR/key -o StrictHostKeyChecking=no -p $SSH_PORT" . "$SERVER_DEPLOY_STRING" || exit 1
exit 0 exit 0