mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
fix(core,cli): end the session on Proton when signing out
Logout only deleted the keyring entry, so the session's tokens stayed valid on Proton's side. It now ends the session first (DELETE auth/v4, bounded to 10 s) and signs out locally either way. `pdfs logout` says when Proton could not be reached (B233).
This commit is contained in:
1 parent
ef0f046939
commit
518763c5c9
5 files changed
+88
-7
No files matched your search
@@ -1977,8 +1977,14 @@ fn key_state_name() -> &'static str {
|
||||
}
|
||||
|
||||
fn cmd_logout() -> Result<()> {
|
||||
auth::logout()?;
|
||||
println!("Stored session cleared.");
|
||||
if auth::logout()? {
|
||||
println!("Signed out: the session is ended on Proton and cleared here.");
|
||||
} else {
|
||||
println!(
|
||||
"Stored session cleared. Proton could not be reached to end the session; \
|
||||
it stays valid until it expires or is signed out in your account's security settings."
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -516,8 +516,59 @@ pub fn lock(forget: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Forget the persisted session (best-effort; absent entry is not an error).
|
||||
pub fn logout() -> Result<()> {
|
||||
/// How long [`logout`] waits for Proton to end the session before it signs
|
||||
/// out locally without.
|
||||
const END_SESSION_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
/// End the stored session on Proton's side (`DELETE auth/v4`). Returns false
|
||||
/// when there is none or Proton could not be reached in time.
|
||||
///
|
||||
/// Runs on a thread of its own, so it works the same whether the caller is
|
||||
/// inside a tokio runtime or not.
|
||||
fn end_server_session() -> bool {
|
||||
let Ok(stored) = load() else {
|
||||
return false;
|
||||
};
|
||||
let ended = std::thread::scope(|scope| {
|
||||
scope
|
||||
.spawn(|| -> std::result::Result<(), String> {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
rt.block_on(async {
|
||||
let session = resume_stored(&stored).map_err(|e| e.to_string())?;
|
||||
match tokio::time::timeout(END_SESSION_TIMEOUT, session.end()).await {
|
||||
Ok(result) => result.map_err(|e| e.to_string()),
|
||||
Err(_) => Err(format!(
|
||||
"no answer within {}s",
|
||||
END_SESSION_TIMEOUT.as_secs()
|
||||
)),
|
||||
}
|
||||
})
|
||||
})
|
||||
.join()
|
||||
.unwrap_or_else(|_| Err("the sign-out thread panicked".into()))
|
||||
});
|
||||
match ended {
|
||||
Ok(()) => true,
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, "ending the session on Proton failed; signing out locally");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// End the session on Proton, then forget it here. Returns whether Proton
|
||||
/// ended it: offline, the session stays valid on Proton's side until it
|
||||
/// expires or is signed out from the account's security settings.
|
||||
///
|
||||
/// Before this, signing out only deleted the keyring entry. The tokens stayed
|
||||
/// valid on Proton, so a copy of them taken from this machine still worked.
|
||||
///
|
||||
/// The local half is best effort; an absent entry is not an error.
|
||||
pub fn logout() -> Result<bool> {
|
||||
let ended = end_server_session();
|
||||
// The persisted entity cache describes the account being logged out of. It
|
||||
// is encrypted under a key derived from that account's passphrases, but
|
||||
// leaving it behind would also mean the next account inherits a store it can
|
||||
@@ -539,7 +590,7 @@ pub fn logout() -> Result<()> {
|
||||
}
|
||||
let _ = kernelkey::UNSAVED_TOKENS.clear();
|
||||
match keyring_entry()?.delete_credential() {
|
||||
Ok(()) | Err(keyring_core::Error::NoEntry) => Ok(()),
|
||||
Ok(()) | Err(keyring_core::Error::NoEntry) => Ok(ended),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,7 +602,7 @@ pub(crate) fn sign_out(ui: &Rc<Ui>) {
|
||||
glib::spawn_future_local(async move {
|
||||
let result = gio::spawn_blocking(|| {
|
||||
service::disable_stop();
|
||||
auth::logout().map_err(|e| e.to_string())
|
||||
auth::logout().map(|_| ()).map_err(|e| e.to_string())
|
||||
})
|
||||
.await;
|
||||
if let Ok(Err(e)) = result {
|
||||
|
||||
@@ -100,6 +100,7 @@ a live account since.
|
||||
|
||||
| Entry | Title |
|
||||
|---|---|
|
||||
| [B233](#b233--signing-out-leaves-the-session-valid-on-proton) | Signing out leaves the session valid on Proton |
|
||||
| [B232](#b232--names-with--or--show-as-empty-rows-or-are-drawn-as-markup) | Names with & or < show as empty rows or are drawn as markup |
|
||||
| [B231](#b231--a-large-upload-under-a-low-upload-limit-never-finishes) | A large upload under a low upload limit never finishes |
|
||||
| [B230](#b230--a-create-queued-offline-adopts-another-devices-file-of-the-same-name) | A create queued offline adopts another device's file of the same name |
|
||||
@@ -335,6 +336,29 @@ a live account since.
|
||||
|
||||
Newest first.
|
||||
|
||||
### B233 — Signing out leaves the session valid on Proton
|
||||
|
||||
**Status:** Fixed, not verified live
|
||||
**Found:** 2026-10-16, architecture audit (`FINDINGS.md` section 3, logout).
|
||||
**Where:** `logout` and `end_server_session` in `crates/pdfs-core/src/auth.rs`; `cmd_logout` in
|
||||
`crates/pdfs-cli/src/main.rs`
|
||||
|
||||
**Repro:** Copy the keyring entry, run `pdfs logout` (or **Sign Out** in the app), and resume a
|
||||
session from the copy.
|
||||
|
||||
**Cause.** Signing out only deleted the keyring entry and the local caches. The session was never
|
||||
ended on Proton's side, so its tokens stayed valid until they expired. The SDK has the call for
|
||||
it (`ProtonApiSession::end`, `DELETE auth/v4`); nothing used it.
|
||||
|
||||
**Fix.** Logout resumes the stored session and ends it on Proton before it clears anything
|
||||
locally. The call is bounded to 10 s and runs on a thread of its own. When Proton cannot be
|
||||
reached, the local sign-out still happens, and `pdfs logout` says that the session stays valid
|
||||
until it expires or is signed out in the account's security settings.
|
||||
|
||||
**Test:** none automated; ending a session needs a live account.
|
||||
|
||||
---
|
||||
|
||||
### B232 — Names with & or < show as empty rows or are drawn as markup
|
||||
|
||||
**Status:** Fixed, not verified live
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ talks to the running service (`proton-drive.service`); `login`, `logout`, `mount
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `pdfs login [-u EMAIL]` | Sign in (password, then a 2FA code if the account has one) and store the session in the system keyring. Restarts the service if it is running. |
|
||||
| `pdfs logout` | Remove the stored session. |
|
||||
| `pdfs logout` | End the session on Proton and remove the stored session. |
|
||||
| `pdfs status` | Account, mount, queue and cache state. |
|
||||
| `pdfs quota` | Storage used across all Proton products. |
|
||||
| `pdfs locations` | Every local place the service occupies: the Proton Drive folder and each synced folder, with mode and state. |
|
||||
|
||||
Reference in new issue
Block a user