Skip to content

Commit

Permalink
Fixed Dropbox login and logout bug; released official version
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert-Stackflow committed Nov 7, 2023
1 parent cb9907d commit 67a3744
Show file tree
Hide file tree
Showing 15 changed files with 73 additions and 43 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Introduction

This is a local two-factor authenticator for Android (may support cloud backup in the future).
This is a awesome two-factor authenticator for Android which supports dropbox.

The algorithm part comes from https://github.com/freeotp/freeotp-android.

Expand All @@ -14,7 +14,6 @@ The algorithm part comes from https://github.com/freeotp/freeotp-android.
- Support password lock and biometric identification
- Support dark mode and switching theme colors
- Support multiple languages: English, Simplified Chinese, Traditional Chinese, Japanese
- Support two view styles: single column and double column

## Screenshots

Expand Down
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ android {
minSdkVersion buildConfig.minSdk
targetSdkVersion buildConfig.targetSdk
versionCode 4
versionName "1.0"
versionName "1.0.0"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
Expand Down
47 changes: 27 additions & 20 deletions app/src/main/java/com/cloudchewie/otp/activity/DropboxActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.blankj.utilcode.util.ThreadUtils
import com.blankj.utilcode.util.TimeUtils
import com.cloudchewie.otp.R
import com.cloudchewie.otp.databinding.ActivityDropboxBinding
import com.cloudchewie.otp.entity.ImportAnalysis
import com.cloudchewie.otp.entity.SyncConfig
import com.cloudchewie.otp.external.AESStringCypher
import com.cloudchewie.otp.external.SyncManager
Expand Down Expand Up @@ -55,7 +56,6 @@ open class DropboxActivity : BaseActivity(), SecretBottomSheet.OnConfirmListener
private var haveBackup = false
private lateinit var binding: ActivityDropboxBinding
private var dropboxSyncConfig: SyncConfig = SyncConfig()
private var firstLogin: Boolean = false
private var loadingDialog: LoadingDialog? = null
private var fileMetadata: FileMetadata? = null
private var redirectToSignin: Boolean = false
Expand Down Expand Up @@ -97,7 +97,7 @@ open class DropboxActivity : BaseActivity(), SecretBottomSheet.OnConfirmListener
}

binding.activityDropboxSigninButton.setOnClickListener {
firstLogin = true
redirectToSignin = true
Auth.startOAuth2Authentication(this, APPKEY)
}

Expand Down Expand Up @@ -126,10 +126,10 @@ open class DropboxActivity : BaseActivity(), SecretBottomSheet.OnConfirmListener

loadingDialog = LoadingDialog(this)

loadConfig()
loadConfig(true)
}

private fun loadConfig() {
private fun loadConfig(init: Boolean) {
val temp = LocalStorage.getAppDatabase().syncConfigDao().get(SyncService.DROPBOX.key)
if (temp != null && temp.name.isNotEmpty()) {
dropboxSyncConfig = temp
Expand All @@ -141,11 +141,13 @@ open class DropboxActivity : BaseActivity(), SecretBottomSheet.OnConfirmListener
binding.activityDropboxInfoLayout.visibility = View.GONE
binding.activityDropboxPushPullLayout.visibility = View.GONE
binding.activityDropboxLogoutButton.visibility = View.GONE
binding.activityDropboxSigninButton.visibility = View.VISIBLE
} else {
binding.activityDropboxSigninButton.visibility = View.GONE
}
refreshState(true)
getAccessToken()
refreshState(init)
if (init)
getAccessToken()
}

private fun refreshState(init: Boolean) {
Expand All @@ -170,16 +172,14 @@ open class DropboxActivity : BaseActivity(), SecretBottomSheet.OnConfirmListener
}

private fun getAccessToken() {
if (dropboxSyncConfig.accessToken == null) {
redirectToSignin = true
if (dropboxSyncConfig.accessToken == null && redirectToSignin) {
val accessToken = Auth.getOAuth2Token()
redirectToSignin = false
if (accessToken != null) {
dropboxSyncConfig.accessToken = accessToken
syncManager!!.update(dropboxSyncConfig)
signin()
}
} else {
} else if (dropboxSyncConfig.accessToken != null) {
signin()
}
}
Expand All @@ -193,11 +193,12 @@ open class DropboxActivity : BaseActivity(), SecretBottomSheet.OnConfirmListener
object : DropboxSigninTask.Callback {
override fun onSignin(account: FullAccount) {
loadingDialog!!.close()
if (firstLogin)
if (redirectToSignin)
IToast.showBottom(
this@DropboxActivity,
getString(R.string.signin_success)
)
redirectToSignin = false
binding.activityDropboxEmail.editText.setText(account.email)
binding.activityDropboxNickname.editText.setText(account.name.displayName)
binding.activityDropboxSigninButton.visibility = View.GONE
Expand All @@ -207,6 +208,7 @@ open class DropboxActivity : BaseActivity(), SecretBottomSheet.OnConfirmListener
}

override fun onNetworkError(error: NetworkIOException?) {
redirectToSignin = false
loadingDialog!!.close()
IToast.showBottom(
this@DropboxActivity,
Expand All @@ -215,11 +217,12 @@ open class DropboxActivity : BaseActivity(), SecretBottomSheet.OnConfirmListener
}

override fun onError(error: Exception?) {
redirectToSignin = false
loadingDialog!!.close()
IToast.showBottom(this@DropboxActivity, getString(R.string.outdated_login))
dropboxSyncConfig = SyncConfig()
syncManager!!.delete(SyncService.DROPBOX)
recreate()
loadConfig(false)
}
},
)
Expand All @@ -241,7 +244,7 @@ open class DropboxActivity : BaseActivity(), SecretBottomSheet.OnConfirmListener
this@DropboxActivity,
getString(R.string.logout_success)
)
recreate()
loadConfig(false)
}

override fun onNetworkError(error: NetworkIOException?) {
Expand Down Expand Up @@ -387,16 +390,20 @@ open class DropboxActivity : BaseActivity(), SecretBottomSheet.OnConfirmListener
val bytes = ByteArray(mEncryptedFile!!.length().toInt())
try {
FileInputStream(mEncryptedFile).read(bytes)
ImportTokenUtil.mergeTokens(
ImportTokenUtil.jsonToTokenList(
AESStringCypher.decryptString(
AESStringCypher.CipherTextIvMac(String(bytes)),
AESStringCypher.generateKeyFromPassword(secret, secret)
)
val importAnalysis = ImportAnalysis()
val tokens = ImportTokenUtil.jsonToTokenList(
AESStringCypher.decryptString(
AESStringCypher.CipherTextIvMac(String(bytes)),
AESStringCypher.generateKeyFromPassword(secret, secret)
)
)
importAnalysis.foundTokenCount = tokens.size
importAnalysis.realAddTokenCount = ImportTokenUtil.mergeTokens(tokens)
loadingDialog!!.close()
IToast.showBottom(this@DropboxActivity, getString(R.string.pull_success))
IToast.showBottom(
this@DropboxActivity,
importAnalysis.toPullToast(this@DropboxActivity)
)
askToSaveSecret(secret)
} catch (ex: GeneralSecurityException) {
ex.printStackTrace()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.view.View;

import com.cloudchewie.otp.R;
import com.cloudchewie.otp.entity.ImportAnalysis;
import com.cloudchewie.otp.util.ExploreUtil;
import com.cloudchewie.otp.util.authenticator.ExportTokenUtil;
import com.cloudchewie.otp.util.authenticator.ImportTokenUtil;
Expand Down Expand Up @@ -208,9 +209,9 @@ public void onNegtiveClick() {
public void onPositiveClick() {
try {
loadingDialog.setLoadingText(getString(R.string.loading_import)).show();
ImportTokenUtil.importUriFile(EximportActivity.this, uri);
ImportAnalysis importAnalysis = ImportTokenUtil.importUriFile(EximportActivity.this, uri);
loadingDialog.close();
IToast.showBottom(EximportActivity.this, getString(R.string.import_success));
IToast.showBottom(EximportActivity.this, importAnalysis.toToast(EximportActivity.this));
} catch (Exception e) {
loadingDialog.close();
IToast.showBottom(EximportActivity.this, getString(R.string.import_fail));
Expand Down Expand Up @@ -245,9 +246,9 @@ public void onNegtiveClick() {
public void onPositiveClick() {
try {
loadingDialog.setLoadingText(getString(R.string.loading_import)).show();
ImportTokenUtil.importJsonFile(EximportActivity.this, uri);
ImportAnalysis importAnalysis = ImportTokenUtil.importJsonFile(EximportActivity.this, uri);
loadingDialog.close();
IToast.showBottom(EximportActivity.this, getString(R.string.import_success));
IToast.showBottom(EximportActivity.this, importAnalysis.toToast(EximportActivity.this));
} catch (Exception e) {
e.printStackTrace();
loadingDialog.close();
Expand Down Expand Up @@ -282,9 +283,9 @@ void exportEncryptFile(Uri uri, String secret) {
void importEncryptFile(Uri uri, String secret) {
try {
loadingDialog.setLoadingText(getString(R.string.loading_import)).show();
ImportTokenUtil.importEncryptFile(EximportActivity.this, uri, secret);
ImportAnalysis importAnalysis = ImportTokenUtil.importEncryptFile(EximportActivity.this, uri, secret);
loadingDialog.close();
IToast.showBottom(EximportActivity.this, getString(R.string.import_success));
IToast.showBottom(EximportActivity.this, importAnalysis.toToast(EximportActivity.this));
askToSaveSecret(secret);
} catch (GeneralSecurityException e) {
e.printStackTrace();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,8 +520,7 @@ public void onPositiveClick() {
loadingDialog.setLoadingText(getString(R.string.loading_import)).show();
ImportAnalysis importAnalysis = ImportTokenUtil.importUriFile(MainActivity.this, uri);
loadingDialog.close();
IToast.showBottom(MainActivity.this, importAnalysis.toString());
// IToast.showBottom(MainActivity.this, getString(R.string.import_success));
IToast.showBottom(MainActivity.this, importAnalysis.toToast(MainActivity.this));
} catch (Exception e) {
loadingDialog.close();
IToast.showBottom(MainActivity.this, getString(R.string.import_fail));
Expand All @@ -546,8 +545,7 @@ public void onPositiveClick() {
loadingDialog.setLoadingText(getString(R.string.loading_import)).show();
ImportAnalysis importAnalysis = ImportTokenUtil.importJsonFile(MainActivity.this, uri);
loadingDialog.close();
IToast.showBottom(MainActivity.this, importAnalysis.toString());
// IToast.showBottom(MainActivity.this, getString(R.string.import_success));
IToast.showBottom(MainActivity.this, importAnalysis.toToast(MainActivity.this));
} catch (Exception e) {
e.printStackTrace();
loadingDialog.close();
Expand Down Expand Up @@ -584,8 +582,7 @@ void importEncryptFile(Uri uri, String secret) {
loadingDialog.setLoadingText(getString(R.string.loading_import)).show();
ImportAnalysis importAnalysis = ImportTokenUtil.importEncryptFile(MainActivity.this, uri, secret);
loadingDialog.close();
IToast.showBottom(MainActivity.this, importAnalysis.toString());
// IToast.showBottom(MainActivity.this, getString(R.string.import_success));
IToast.showBottom(MainActivity.this, importAnalysis.toToast(this));
askToSaveSecret(secret);
} catch (GeneralSecurityException e) {
e.printStackTrace();
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/com/cloudchewie/otp/entity/ImportAnalysis.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.cloudchewie.otp.entity;

import android.content.Context;

import androidx.annotation.NonNull;

import com.cloudchewie.otp.R;

public class ImportAnalysis {
int skipLineCount;
int foundTokenCount;
Expand All @@ -16,6 +20,14 @@ public ImportAnalysis(int skipLineCount, int foundTokenCount, int realAddTokenCo
this.realAddTokenCount = realAddTokenCount;
}

public String toToast(Context context) {
return String.format(context.getString(R.string.import_success_info), foundTokenCount, realAddTokenCount);
}

public String toPullToast(Context context) {
return String.format(context.getString(R.string.pull_success_info), foundTokenCount, realAddTokenCount);
}

@NonNull
@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static void createFile(Activity activity, String mimeType, String fileNam
intent.addCategory(Intent.CATEGORY_OPENABLE);
String fullName = fileName;
if (appendTimestamp) {
fullName += DateFormatUtil.getSimpleDateFormat(DateFormatUtil.FULL_FORMAT).format(new Date());
fullName += DateFormatUtil.getSimpleDateFormat(DateFormatUtil.FILE_FORMAT).format(new Date());
}
fullName += ".";
fullName += fileExtension;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.List;

public class ExportTokenUtil {
public static void exportJsonFile(Context context, Uri fileUri) {
try {
OutputStream outputStream = context.getContentResolver().openOutputStream(fileUri, "w");
OutputStream outputStream = context.getContentResolver().openOutputStream(fileUri);
List<OtpToken> tokens = LocalStorage.getAppDatabase().otpTokenDao().getAll();
outputStream.write(new Gson().toJson(tokens.toArray()).getBytes(StandardCharsets.UTF_8));
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.write(new Gson().toJson(tokens));
printWriter.flush();
outputStream.flush();
printWriter.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
Expand All @@ -32,12 +35,13 @@ public static void exportJsonFile(Context context, Uri fileUri) {

public static void exportUriFile(Context context, Uri fileUri) {
try {
OutputStream outputStream = context.getContentResolver().openOutputStream(fileUri, "w");
OutputStream outputStream = context.getContentResolver().openOutputStream(fileUri);
PrintWriter printWriter = new PrintWriter(outputStream);
List<OtpToken> tokens = LocalStorage.getAppDatabase().otpTokenDao().getAll();
for (OtpToken token : tokens)
printWriter.println(OtpTokenParser.toUri(token).toString());
printWriter.flush();
outputStream.flush();
printWriter.close();
outputStream.close();
} catch (IOException e) {
Expand Down Expand Up @@ -66,10 +70,11 @@ public static String getEncryptedData(String password) throws GeneralSecurityExc

public static void exportEncryptFile(Context context, Uri fileUri, String password) {
try {
OutputStream outputStream = context.getContentResolver().openOutputStream(fileUri, "w");
OutputStream outputStream = context.getContentResolver().openOutputStream(fileUri);
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.write(getEncryptedData(password));
printWriter.flush();
outputStream.flush();
printWriter.close();
outputStream.close();
} catch (IOException | GeneralSecurityException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

Expand All @@ -37,7 +38,7 @@ public static ImportAnalysis importJsonFile(Context context, Uri fileUri) {
String line;
while ((line = bufferedReader.readLine()) != null)
content.append(line.trim());
List<OtpToken> otpTokens = jsonToTokenList(content.toString());
List<OtpToken> otpTokens = Arrays.asList(new Gson().fromJson(content.toString(), OtpToken[].class));
bufferedReader.close();
inputStream.close();
importAnalysis.setFoundTokenCount(otpTokens.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class TokenCodeUtil @Inject constructor() {

OtpTokenType.TOTP -> {
val counter: Long = cur / 1000 / otpToken.period
Log.d("xuruida", counter.toString())
return TokenCode(
getHOTP(otpToken, counter + 0),
(counter + 0) * otpToken.period * 1000,
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
android:background="@drawable/shape_button_round"
android:backgroundTint="?attr/colorPrimary"
android:gravity="center"
android:stateListAnimator="@null"
android:paddingTop="@dimen/dp5"
android:paddingBottom="@dimen/dp5"
android:text="@string/null_code"
Expand Down
2 changes: 2 additions & 0 deletions util/src/main/res/values-en/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,6 @@
<string name="interval_too_long">Refresh interval is too long</string>
<string name="counter_too_long">Counter too long</string>
<string name="counter_blank">Counter cannot be empty</string>
<string name="import_success_info">Import successfully, a total of %1$d tokens were found, and %2$d tokens were actually imported</string>
<string name="pull_success_info">Pull successfully, a total of %1$d tokens were found, and %2$d tokens were actually imported</string>
</resources>
2 changes: 2 additions & 0 deletions util/src/main/res/values-ja/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,6 @@
<string name="interval_too_long">更新間隔が長すぎます</string>
<string name="counter_too_long">カウンタが長すぎます</string>
<string name="counter_blank">カウンターを空にすることはできません</string>
<string name="import_success_info">インポートは成功し、合計 %1$d 個のトークンが見つかり、%2$d 個のトークンが実際にインポートされました</string>
<string name="pull_success_info">プルは成功し、合計 %1$d トークンが見つかり、%2$d トークンが実際にインポートされました</string>
</resources>
2 changes: 2 additions & 0 deletions util/src/main/res/values-zh-rTW/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,6 @@
<string name="interval_too_long">刷新間隔過長</string>
<string name="counter_too_long">計數器過長</string>
<string name="counter_blank">計數器不能為空</string>
<string name="import_success_info">導入成功,共發現%1$d個令牌,實際導入%2$d個令牌</string>
<string name="pull_success_info">拉取成功,共發現%1$d個令牌,實際導入%2$d個令牌</string>
</resources>
Loading

0 comments on commit 67a3744

Please sign in to comment.