~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
111 lines · 4.7 KB Raw History
//! A browser logged in to several accounts at once: logging in to another
//! keeps the one before, switching needs no login, and logging out of one
//! goes on as the other used last. The pages take the theme of the account
//! in use, or the system's when it chose none.

use super::*;

/// The cookies `jar` holds (`;` between) once a response `set` them: a
/// cookie set empty is gone.
fn kept(jar: &str, set: &[String]) -> String {
    let mut all: Vec<(String, String)> = jar
        .split(';')
        .filter_map(|p| p.split_once('='))
        .map(|(k, v)| (k.to_string(), v.to_string()))
        .collect();
    for cookie in set {
        let (name, value) = pair(cookie).split_once('=').unwrap();
        all.retain(|(k, _)| k != name);
        if !value.is_empty() && name != "knotless_login" {
            all.push((name.to_string(), value.to_string()));
        }
    }
    let pairs: Vec<String> = all.iter().map(|(k, v)| format!("{k}={v}")).collect();
    pairs.join(";")
}

#[test]
fn switching_between_accounts_on_the_pages() {
    let server = gateway_with(None, true);
    let one = person(account());
    let two = person(account());
    let viewer = |jar: &str| {
        let url = format!("{}/xrpc/dev.knotless.getViewer", server.base);
        browse("GET", &url, Some(jar), None, None).2
    };
    let switch = |jar: &str, did: &str, origin: Option<&str>| {
        let url = format!("{}/oauth/switch", server.base);
        browse("POST", &url, Some(jar), origin, Some(json!({ "did": did })))
    };
    let logout = |jar: &str| {
        let url = format!("{}/oauth/logout", server.base);
        browse("POST", &url, Some(jar), None, None)
    };

    let themed = |jar: &str| {
        let theme = jar.split(';').find_map(|p| p.strip_prefix("theme="));
        theme.map(str::to_string)
    };

    // One account: nothing else to switch to. It chooses Dracula, and
    // logging in again brings it.
    let set = log_in(&server, &one.account, "");
    assert!(set.iter().all(|c| !c.starts_with("knotless_accounts=")));
    let first = kept("", &set);
    let url = format!("{}/xrpc/dev.knotless.putPreferences", server.base);
    let dracula =
        json!({"$type": "dev.knotless.getPreferences#appearancePref", "theme": "dracula"});
    let body = json!({ "preferences": [dracula] });
    assert_eq!(browse("POST", &url, Some(&first), None, Some(body)).0, 200);
    let set = log_in_beside(&server, &one.account, "", &first);
    let first = kept(&first, &set);
    assert_eq!(themed(&first).as_deref(), Some("dracula"));
    let shown = viewer(&first);
    assert_eq!(shown["actor"]["did"], one.account.did);
    assert_eq!(shown.get("accounts"), None);

    // Logging in to another keeps the first beside it, and again keeps it
    // once.
    let set = log_in_beside(&server, &two.account, "", &first);
    let both = kept(&first, &set);
    assert_eq!(themed(&both), None, "the second chose no theme");
    let shown = viewer(&both);
    assert_eq!(shown["actor"]["did"], two.account.did);
    assert_eq!(shown["accounts"][0]["did"], one.account.did);
    assert_eq!(shown["accounts"][0]["handle"], one.account.handle);
    let set = log_in_beside(&server, &two.account, "", &both);
    let again = kept(&both, &set);
    let shown = viewer(&again);
    assert_eq!(shown["actor"]["did"], two.account.did);
    assert_eq!(shown["accounts"].as_array().unwrap().len(), 1);

    // Switching swaps them, with no login; only from this site, and only
    // to an account this browser is logged in to.
    let away = Some("https://evil.example");
    assert_eq!(switch(&both, &one.account.did, away).0, 403);
    assert_eq!(switch(&first, &two.account.did, None).0, 400);
    let (status, set, _) = switch(&both, &one.account.did, None);
    assert_eq!(status, 200);
    let swapped = kept(&both, &set);
    assert_eq!(themed(&swapped).as_deref(), Some("dracula"));
    let shown = viewer(&swapped);
    assert_eq!(shown["actor"]["did"], one.account.did);
    assert_eq!(shown["accounts"][0]["did"], two.account.did);

    // Logging out of one goes on as the other, and leaves none of the
    // first's cookies standing.
    let (status, set, _) = logout(&swapped);
    assert_eq!(status, 200);
    let left = kept(&swapped, &set);
    assert!(!left.contains("knotless_accounts="), "{left}");
    assert_eq!(themed(&left), None, "the other chose no theme");
    let shown = viewer(&left);
    assert_eq!(shown["actor"]["did"], two.account.did);
    assert_eq!(shown.get("accounts"), None);
    assert_eq!(viewer(&swapped), json!({"login": true}));

    // And out of the last, as no one.
    let (_, set, _) = logout(&left);
    assert!(set[0].starts_with("knotless_session=;"), "{set:?}");
    assert_eq!(viewer(&kept(&left, &set)), json!({"login": true}));
}