A local-first private AI assistant for everyday use. Runs on-device models with encrypted P2P sync, and supports sharing chats publicly on ATProto.
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: fixed inverse logic in checking DID auth for incoming msg

madclaws 0d757807 9682b0ff

+64 -29
+14
CHANGELOG.md
··· 5 5 6 6 ## [Unreleased] 7 7 8 + ## [0.4.5] - 2026-03-23 9 + 10 + ### Added 11 + - Added P2P device linking v1 in [#106](https://github.com/tilesprivacy/tiles/pull/106). 12 + - Works both online and in offline networks 13 + - Utility Commands for device linking 14 + - `tiles link enable` - creates the ticket and listens for an link requests 15 + - `tiles link enable <ticket>`- Device that need to join will run this command with the ticket from the sender. **NOTE**: The ticket sharing is out-of-band. 16 + - `tiles link list-peers` - Shows the info (DID, nickname etc) of the linked devices. 17 + - `tiles link disable <DID>` - Unlinks a linked device 18 + 19 + ### Fixed 20 + - Fixed the permission issues while trying to update Tiles using `tiles update` in [$104](https://github.com/tilesprivacy/tiles/pull/104). This was due to new binary location is in `/usr/` instead of `~/.local/`. Running the internal script with `sudo` fixed it. 21 + 8 22 ## [0.4.4] - 2026-03-16 9 23 10 24 ### Added
+7 -7
scripts/bundler.sh
··· 22 22 cp "target/${TARGET}/${BINARY_NAME}" "${DIST_DIR}/tmp/" 23 23 24 24 # flushing this folder, else the final zip will have previous app-server zips too (#84) 25 - # rm -rf "${SERVER_DIR}/stack_export_prod" 25 + rm -rf "${SERVER_DIR}/stack_export_prod" 26 26 27 - # echo "🔒 Locking the venvstack...." 27 + echo "🔒 Locking the venvstack...." 28 28 29 - # venvstacks lock server/stack/venvstacks.toml 29 + venvstacks lock server/stack/venvstacks.toml 30 30 31 - # echo "🛠️ Building the venvstack...." 31 + echo "🛠️ Building the venvstack...." 32 32 33 - # venvstacks build server/stack/venvstacks.toml 33 + venvstacks build server/stack/venvstacks.toml 34 34 35 - # echo "📦 Publishing the venvstack...." 35 + echo "📦 Publishing the venvstack...." 36 36 37 - # venvstacks publish --tag-outputs --output-dir ../stack_export_prod server/stack/venvstacks.toml 37 + venvstacks publish --tag-outputs --output-dir ../stack_export_prod server/stack/venvstacks.toml 38 38 39 39 cp -r "${SERVER_DIR}" "${DIST_DIR}/tmp/" 40 40
+1 -1
tilekit/src/accounts.rs
··· 35 35 /// 36 36 /// - `app`- The service for which Identity is made (for ex: tiles) 37 37 /// - `did` - The `Identity` of the service 38 - pub fn get_secret_key(app: &str, did: &Identity) -> Result<SecretKey> { 38 + pub fn get_secret_key(app: &str, did: &str) -> Result<SecretKey> { 39 39 let entry = Entry::new(app, did)?; 40 40 let mut bytes: [u8; 64] = [0u8; 64]; 41 41 let secret_pair = entry.get_secret()?;
+26 -10
tiles/src/core/accounts.rs
··· 1 1 //! Accounts 2 2 // Stuff related to account and identity system 3 3 use anyhow::{Result, anyhow}; 4 + use iroh::SecretKey; 4 5 use rusqlite::{Connection, types::FromSqlError}; 5 6 use std::{ 6 7 fmt::Display, 7 8 time::{SystemTime, UNIX_EPOCH}, 8 9 }; 9 - use tilekit::accounts::create_identity; 10 + use tilekit::accounts::{create_identity, get_secret_key}; 10 11 use toml::Table; 11 12 use uuid::Uuid; 12 13 ··· 29 30 // root account, created in the system 30 31 LOCAL, 31 32 32 - // remote account but same previlege as your local account 33 - SELF, 33 + // remote account 34 + PEER, 34 35 } 35 36 36 37 #[derive(Debug)] ··· 50 51 let value_lower = value.to_lowercase(); 51 52 match value_lower.as_str() { 52 53 "local" => Ok(ACCOUNT::LOCAL), 53 - "self" => Ok(ACCOUNT::SELF), 54 + "peer" => Ok(ACCOUNT::PEER), 54 55 _ => Err(AccountError { 55 56 error: "Invalid account type".to_owned(), 56 57 }), ··· 61 62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 62 63 match self { 63 64 Self::LOCAL => write!(f, "{}", String::from("local")), 64 - Self::SELF => write!(f, "{}", String::from("self")), 65 + Self::PEER => write!(f, "{}", String::from("peer")), 65 66 } 66 67 } 67 68 } ··· 287 288 288 289 // TODO: We could add unique user_id constraints, but 289 290 // we will wait for it until we solve the sync part 290 - pub fn save_self_account_db(db_conn: &Connection, user_id: &str, nickname: &str) -> Result<()> { 291 + pub fn save_peer_account_db(db_conn: &Connection, user_id: &str, nickname: &str) -> Result<()> { 291 292 let user = User { 292 293 id: Uuid::now_v7(), 293 294 user_id: String::from(user_id), 294 295 username: String::from(nickname), 295 - account_type: ACCOUNT::SELF, 296 + account_type: ACCOUNT::PEER, 296 297 active_profile: false, 297 298 root: false, 298 299 created_at: SystemTime::now() ··· 331 332 332 333 fn create_root_user(root_user_config: &Table, nickname: Option<String>) -> Result<Table> { 333 334 let mut root_user_table = root_user_config.clone(); 334 - match create_identity("tiles") { 335 + let app_name = if cfg!(debug_assertions) { 336 + "tiles_dev" 337 + } else { 338 + "tiles" 339 + }; 340 + match create_identity(app_name) { 335 341 Ok(did) => { 336 342 root_user_table.insert("id".to_owned(), toml::Value::String(did)); 337 343 if let Some(nickname) = nickname { ··· 389 395 Ok(_) => Ok(()), 390 396 Err(err) => Err(anyhow!("Unable to unlink the peer due to {:?}", err)), 391 397 } 398 + } 399 + 400 + pub fn get_app_secret_key(did: &str) -> Result<SecretKey> { 401 + let app_name = if cfg!(debug_assertions) { 402 + "tiles_dev" 403 + } else { 404 + "tiles" 405 + }; 406 + let signing_key = get_secret_key(app_name, did)?; 407 + Ok(SecretKey::from_bytes(&signing_key)) 392 408 } 393 409 394 410 #[cfg(test)] ··· 777 793 fn test_list_peers_with_more_than_0_peer() { 778 794 let conn = setup_db_schema(); 779 795 let _local_user = create_user(&conn, ACCOUNT::LOCAL); 780 - save_self_account_db(&conn, "varathan", "did:jey:varathan").unwrap(); 796 + save_peer_account_db(&conn, "did:jey:varathan", "varathan").unwrap(); 781 797 let user_list = get_peer_list(&conn).unwrap(); 782 798 783 799 assert!(!user_list.is_empty()) ··· 787 803 fn test_unlink_valid_peer() { 788 804 let conn = setup_db_schema(); 789 805 let _local_user = create_user(&conn, ACCOUNT::LOCAL); 790 - save_self_account_db(&conn, "did:jey:varathan", "varathan").unwrap(); 806 + save_peer_account_db(&conn, "did:jey:varathan", "varathan").unwrap(); 791 807 let user_list = get_peer_list(&conn).unwrap(); 792 808 793 809 assert!(!user_list.is_empty());
+16 -11
tiles/src/core/network/mod.rs
··· 11 11 use anyhow::Result; 12 12 use futures_util::{StreamExt, TryStreamExt}; 13 13 use iroh::{ 14 - Endpoint, EndpointId, NET_REPORT_TIMEOUT, PublicKey, SecretKey, 14 + Endpoint, EndpointId, NET_REPORT_TIMEOUT, PublicKey, 15 15 address_lookup::{self, MdnsAddressLookup, mdns}, 16 16 endpoint::{BindError, presets}, 17 17 endpoint_info::UserData, ··· 24 24 use iroh_ping::Ping; 25 25 use iroh_tickets::endpoint::EndpointTicket; 26 26 use rusqlite::Connection; 27 - use tilekit::accounts::{ 28 - get_did_from_public_key, get_random_bytes, get_random_bytes_32, get_secret_key, 29 - }; 27 + use tilekit::accounts::{get_did_from_public_key, get_random_bytes, get_random_bytes_32}; 30 28 use tokio::task::spawn_blocking; 31 29 use uuid::Uuid; 32 30 33 31 use crate::core::{ 34 - accounts::{self, get_current_user, get_user_by_user_id, save_self_account_db}, 32 + accounts::{ 33 + self, get_app_secret_key, get_current_user, get_user_by_user_id, save_peer_account_db, 34 + }, 35 35 network::ticket::{EndpointUserData, LinkTicket}, 36 36 storage::db::{DBTYPE, get_db_conn}, 37 37 }; ··· 289 289 } 290 290 291 291 if let Err(err) = 292 - save_self_account_db(&db_conn, &msg.from_did, &msg.from_nickname) 292 + save_peer_account_db(&db_conn, &msg.from_did, &msg.from_nickname) 293 293 { 294 294 println!("Failed to add the peer locally due to {:?}", err); 295 295 ··· 319 319 println!("\nLink accepted by {}({})", msg.from_nickname, msg.from_did); 320 320 321 321 if let Err(err) = 322 - save_self_account_db(&db_conn, &msg.from_did, &msg.from_nickname) 322 + save_peer_account_db(&db_conn, &msg.from_did, &msg.from_nickname) 323 323 { 324 324 println!("Failed to add the peer locally due to {:?}", err); 325 325 return Ok(()); ··· 346 346 // tiles keypair in keychain 347 347 let usr_data = EndpointUserData::new(&user.user_id, &user.username); 348 348 if !cfg!(debug_assertions) { 349 - let signing_key = get_secret_key("tiles", &user.user_id)?; 350 - let secret_key = SecretKey::from_bytes(&signing_key); 349 + let secret_key = get_app_secret_key(&user.user_id)?; 351 350 Endpoint::builder(presets::N0) 352 351 .user_data_for_address_lookup(UserData::try_from(usr_data.to_string())?) 353 352 .secret_key(secret_key) ··· 435 434 } 436 435 437 436 // We handle the parsing in this way since ticket can be an encoded `LinkTicket` 438 - // or just a 5 byte hex if linking over mDNS 437 + // or just a 4 byte hex if linking over mDNS 439 438 fn parse_link_ticket( 440 439 ticket: &str, 441 440 ) -> Result<(Option<EndpointId>, String, String, Option<TopicId>)> { ··· 456 455 } 457 456 458 457 fn is_did_valid(did: &str, pub_key: PublicKey) -> Result<bool> { 459 - Ok(get_did_from_public_key(&pub_key)? != did) 458 + // on debug mode, we skip the auth check, since we will be testing 459 + // with random endpoitns but w DID from config atp 460 + if cfg!(debug_assertions) { 461 + Ok(true) 462 + } else { 463 + Ok(get_did_from_public_key(&pub_key)? == did) 464 + } 460 465 } 461 466 // fn subsribe_mdns_events(mdns_events) {} 462 467 //TODO: Add tests, can we get some from iroh reference?