forked from Lonami/Talaria
Compare commits
No commits in common. "2fa-support" and "master" have entirely different histories.
2fa-suppor
...
master
|
@ -1,7 +1,5 @@
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
Clone the repo, then open the project in Android Studio.
|
|
||||||
|
|
||||||
Make sure the required Android NDK platforms are installed, and the environment
|
Make sure the required Android NDK platforms are installed, and the environment
|
||||||
variable `ANDROID_NDK_TOOLCHAIN_DIR` is configured correctly.
|
variable `ANDROID_NDK_TOOLCHAIN_DIR` is configured correctly.
|
||||||
|
|
||||||
|
@ -10,9 +8,3 @@ On Windows, this might be a path such as the following (NDK "Side by side" SDK t
|
||||||
```
|
```
|
||||||
%LOCALAPPDATA%\Android\Sdk\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin
|
%LOCALAPPDATA%\Android\Sdk\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin
|
||||||
```
|
```
|
||||||
|
|
||||||
Set your API ID and hash in the rust code.
|
|
||||||
|
|
||||||
Change the Cargo.toml to point to a local grammers source tree and remove the version argument.
|
|
||||||
|
|
||||||
Sync gradle files, and Android Studio's "build" and "run" should Just Work.
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ fun TalariaApp() {
|
||||||
ChatScreen(selectedDialog)
|
ChatScreen(selectedDialog)
|
||||||
}
|
}
|
||||||
composable(route = TalariaScreen.Login.name) {
|
composable(route = TalariaScreen.Login.name) {
|
||||||
LoginScreen(onLoginComplete = {
|
LoginScreen(onConfirmOtp = {
|
||||||
navController.navigate(TalariaScreen.Dialog.name)
|
navController.navigate(TalariaScreen.Dialog.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,7 @@ object Native {
|
||||||
external fun initClient()
|
external fun initClient()
|
||||||
external fun needLogin(): Boolean
|
external fun needLogin(): Boolean
|
||||||
external fun requestLoginCode(phone: String): Long
|
external fun requestLoginCode(phone: String): Long
|
||||||
// Returns true if sign in complete, false if 2fa password needed
|
external fun signIn(tokenPtr: Long, code: String)
|
||||||
// TODO: more rich return type including password hint / type of error
|
|
||||||
external fun signIn(tokenPtr: Long, code: String): Boolean
|
|
||||||
external fun checkPassword(password: String)
|
|
||||||
external fun getDialogs(): Long
|
external fun getDialogs(): Long
|
||||||
external fun dialogCount(dialogsPtr: Long): Int
|
external fun dialogCount(dialogsPtr: Long): Int
|
||||||
external fun dialogPacked(dialogsPtr: Long, index: Int): String
|
external fun dialogPacked(dialogsPtr: Long, index: Int): String
|
||||||
|
|
|
@ -24,12 +24,10 @@ import dev.lonami.talaria.ui.theme.TalariaTheme
|
||||||
enum class LoginStage {
|
enum class LoginStage {
|
||||||
ASK_PHONE,
|
ASK_PHONE,
|
||||||
ASK_CODE,
|
ASK_CODE,
|
||||||
ASK_PASSWORD,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPhoneValid(phone: String): Boolean = phone.trim('+', ' ').isNotEmpty()
|
fun isPhoneValid(phone: String): Boolean = phone.trim('+', ' ').isNotEmpty()
|
||||||
fun isLoginCodeValid(code: String): Boolean = code.trim().count { it.isDigit() } == 5
|
fun isLoginCodeValid(code: String): Boolean = code.trim().count { it.isDigit() } == 5
|
||||||
fun isPasswordValid(password: String): Boolean = password.isNotEmpty()
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PhoneInput(phone: String, onPhoneChanged: (String) -> Unit, onSendCode: () -> Unit) {
|
fun PhoneInput(phone: String, onPhoneChanged: (String) -> Unit, onSendCode: () -> Unit) {
|
||||||
|
@ -82,50 +80,16 @@ fun OtpInput(otp: String, onOtpChanged: (String) -> Unit, onConfirmOtp: () -> Un
|
||||||
enabled = isLoginCodeValid(otp),
|
enabled = isLoginCodeValid(otp),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = onConfirmOtp
|
onClick = onConfirmOtp
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.do_continue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PasswordInput(
|
|
||||||
password: String,
|
|
||||||
onPasswordChanged: (String) -> Unit,
|
|
||||||
onConfirmPassword: () -> Unit
|
|
||||||
) {
|
|
||||||
val focusManager = LocalFocusManager.current
|
|
||||||
|
|
||||||
Text(stringResource(R.string.enter_2fa_password))
|
|
||||||
TextField(
|
|
||||||
password,
|
|
||||||
label = { Text(stringResource(R.string.password)) },
|
|
||||||
placeholder = { Text(stringResource(R.string.password_example)) },
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Password,
|
|
||||||
imeAction = ImeAction.Done
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onValueChange = onPasswordChanged
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
Button(
|
|
||||||
enabled = isPasswordValid(password),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = onConfirmPassword
|
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.do_login))
|
Text(stringResource(R.string.do_login))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreen(onLoginComplete: () -> Unit) {
|
fun LoginScreen(onConfirmOtp: () -> Unit) {
|
||||||
var stage by remember { mutableStateOf(LoginStage.ASK_PHONE) }
|
var stage by remember { mutableStateOf(LoginStage.ASK_PHONE) }
|
||||||
var phone by remember { mutableStateOf("") }
|
var phone by remember { mutableStateOf("") }
|
||||||
var otp by remember { mutableStateOf("") }
|
var otp by remember { mutableStateOf("") }
|
||||||
var password by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
|
|
||||||
var tokenPtr by remember { mutableStateOf(0L) }
|
var tokenPtr by remember { mutableStateOf(0L) }
|
||||||
|
|
||||||
|
@ -157,21 +121,10 @@ fun LoginScreen(onLoginComplete: () -> Unit) {
|
||||||
otp,
|
otp,
|
||||||
onOtpChanged = { otp = it },
|
onOtpChanged = { otp = it },
|
||||||
onConfirmOtp = {
|
onConfirmOtp = {
|
||||||
val signInComplete = Native.signIn(tokenPtr, otp)
|
Native.signIn(tokenPtr, otp)
|
||||||
if (signInComplete) {
|
onConfirmOtp()
|
||||||
onLoginComplete()
|
|
||||||
} else {
|
|
||||||
stage = LoginStage.ASK_PASSWORD;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
LoginStage.ASK_PASSWORD -> PasswordInput(
|
|
||||||
password,
|
|
||||||
onPasswordChanged = { password = it },
|
|
||||||
onConfirmPassword = {
|
|
||||||
Native.checkPassword(password)
|
|
||||||
onLoginComplete();
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,6 +133,6 @@ fun LoginScreen(onLoginComplete: () -> Unit) {
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginPreview() {
|
fun LoginPreview() {
|
||||||
TalariaTheme {
|
TalariaTheme {
|
||||||
LoginScreen(onLoginComplete = { })
|
LoginScreen(onConfirmOtp = { })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,6 @@
|
||||||
<string name="enter_otp">Enter the code you received:</string>
|
<string name="enter_otp">Enter the code you received:</string>
|
||||||
<string name="otp">Code</string>
|
<string name="otp">Code</string>
|
||||||
<string name="otp_example">12345</string>
|
<string name="otp_example">12345</string>
|
||||||
<string name="enter_2fa_password">Enter your 2-factor authentication password:</string>
|
|
||||||
<string name="password">Password</string>
|
|
||||||
<string name="password_example">hunter2</string>
|
|
||||||
<string name="do_continue">Continue</string>
|
|
||||||
<string name="do_login">Login</string>
|
<string name="do_login">Login</string>
|
||||||
<string name="profile_photo">Profile Picture</string>
|
<string name="profile_photo">Profile Picture</string>
|
||||||
<string name="write_message">Write a message…</string>
|
<string name="write_message">Write a message…</string>
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use grammers_client::types::{Dialog, LoginToken, PasswordToken};
|
use grammers_client::{Client, Config};
|
||||||
use grammers_client::{Client, Config, SignInError};
|
use grammers_client::types::{Dialog, LoginToken};
|
||||||
use grammers_session::{PackedChat, Session};
|
use grammers_session::{PackedChat, Session};
|
||||||
|
use jni::JNIEnv;
|
||||||
use jni::objects::{JObject, JString};
|
use jni::objects::{JObject, JString};
|
||||||
use jni::sys::{jboolean, jint, jlong, jstring};
|
use jni::sys::{jboolean, jint, jlong, jstring};
|
||||||
use jni::JNIEnv;
|
|
||||||
use log;
|
use log;
|
||||||
use log::{error, info, Level};
|
use log::{error, info, Level};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
@ -61,7 +61,7 @@ async fn init_client() -> Result<()> {
|
||||||
api_hash: API_HASH.to_string(),
|
api_hash: API_HASH.to_string(),
|
||||||
params: Default::default(),
|
params: Default::default(),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
info!("Connected!");
|
info!("Connected!");
|
||||||
|
|
||||||
|
@ -83,31 +83,10 @@ async fn request_login_code(phone: &str) -> Result<LoginToken> {
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sign_in(token: LoginToken, code: &str) -> Result<bool> {
|
async fn sign_in(token: LoginToken, code: &str) -> Result<()> {
|
||||||
let client = CLIENT.get().ok_or("Client not initialized")?;
|
let client = CLIENT.get().ok_or("Client not initialized")?;
|
||||||
return match client.sign_in(&token, &code).await {
|
client.sign_in(&token, &code).await?;
|
||||||
Err(SignInError::PasswordRequired(token)) => {
|
Ok(())
|
||||||
info!("Sign in 2fa password required. Hint: {:?}", token.hint());
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
client.sign_out().await?;
|
|
||||||
Err(Box::new(e))
|
|
||||||
}
|
|
||||||
Ok(_) => Ok(true),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check_password(password: &str) -> Result<()> {
|
|
||||||
let client = CLIENT.get().ok_or("Client not initialized")?;
|
|
||||||
let token = PasswordToken::new("hint");
|
|
||||||
match client.check_password(token, &password).await {
|
|
||||||
Err(e) => {
|
|
||||||
client.sign_out().await?;
|
|
||||||
Err(Box::new(e))
|
|
||||||
}
|
|
||||||
Ok(_) => Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_dialogs() -> Result<Vec<Dialog>> {
|
async fn get_dialogs() -> Result<Vec<Dialog>> {
|
||||||
|
@ -172,46 +151,14 @@ pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_signIn(
|
||||||
_: JObject,
|
_: JObject,
|
||||||
token_ptr: jlong,
|
token_ptr: jlong,
|
||||||
code: JString,
|
code: JString,
|
||||||
) -> jboolean {
|
) {
|
||||||
let token = *Box::from_raw(token_ptr as *mut LoginToken);
|
let token = *Box::from_raw(token_ptr as *mut LoginToken);
|
||||||
let code = CString::from(CStr::from_ptr(env.get_string(code).unwrap().as_ptr()));
|
let code = CString::from(CStr::from_ptr(env.get_string(code).unwrap().as_ptr()));
|
||||||
|
|
||||||
return match block_on(sign_in(token, code.to_str().unwrap())) {
|
match block_on(sign_in(token, code.to_str().unwrap())) {
|
||||||
Ok(sign_in_complete) => {
|
Ok(_) => info!("Sign in success"),
|
||||||
if sign_in_complete {
|
Err(e) => error!("Failed to sign in: {}", e),
|
||||||
info!("Sign in success");
|
}
|
||||||
}
|
|
||||||
sign_in_complete.into()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to sign in: {}", e);
|
|
||||||
true.into()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_checkPassword(
|
|
||||||
env: JNIEnv,
|
|
||||||
_: JObject,
|
|
||||||
token_ptr: jlong,
|
|
||||||
code: JString,
|
|
||||||
) -> jboolean {
|
|
||||||
let token = *Box::from_raw(token_ptr as *mut LoginToken);
|
|
||||||
let code = CString::from(CStr::from_ptr(env.get_string(code).unwrap().as_ptr()));
|
|
||||||
|
|
||||||
return match block_on(sign_in(token, code.to_str().unwrap())) {
|
|
||||||
Ok(sign_in_complete) => {
|
|
||||||
if sign_in_complete {
|
|
||||||
info!("Sign in success");
|
|
||||||
}
|
|
||||||
sign_in_complete.into()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to sign in: {}", e);
|
|
||||||
true.into()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|
Loading…
Reference in New Issue