feat(users): per-user sealed folders users/<id>/{home,apps,data,permissions}; storage SET_USER/LOCK/PURGE; init sign-in/sign-out loop (purge fresh/removed/guest, seed from factory, session start/stop); shell logout; pm user+system app shelves; terminal SIGNED_OUT; per-program stack (gatekeeper 12p); 128K load scratch

This commit is contained in:
2026-10-28 01:08:50 +01:00
parent 29a25bd8c2
commit 91f7d28834
38 changed files with 1156 additions and 378 deletions

View File

@@ -479,6 +479,11 @@ fn only_init_unlocks_and_only_once() {
let set = |s: &mut Srv, badge, req| call(s, badge, req).0;
let key = |half| StorageRequest::SetKey { half, words: [1, 2] };
let seed = |half| StorageRequest::SetSeed { half, words: [3, 4] };
let user = StorageRequest::SetUser { id: 7, guest: false };
assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Error, "not before saying whose");
assert_eq!(set(&mut s, 5, user), StorageResponse::Error, "a client cannot say");
assert_eq!(set(&mut s, 0, user), StorageResponse::Ok);
assert_eq!(set(&mut s, 0, user), StorageResponse::Error, "once");
assert_eq!(set(&mut s, 5, key(0)), StorageResponse::Error, "a client cannot");
assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Ok);
assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Error, "nor can a half be replaced");
@@ -490,3 +495,40 @@ fn only_init_unlocks_and_only_once() {
assert!(s.unlocked());
assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Error, "once unlocked, never again");
}
#[test]
fn locking_takes_every_grant_back_and_lets_the_next_user_in() {
let mut s = server();
let set = |s: &mut Srv, badge, req| call(s, badge, req).0;
let init = Client::attach(&mut s, 0);
let shell = init.narrowed(&mut s, b"", true);
let app = shell.narrowed(&mut s, b"", false);
for req in [StorageRequest::SetUser { id: 1, guest: false }, StorageRequest::SetKey { half: 0, words: [1, 2] }, StorageRequest::SetKey { half: 1, words: [1, 2] }, StorageRequest::SetSeed { half: 0, words: [1, 2] }, StorageRequest::SetSeed { half: 1, words: [1, 2] }] {
assert_eq!(set(&mut s, 0, req), StorageResponse::Ok);
}
assert!(s.unlocked());
assert_eq!(set(&mut s, shell.badge, StorageRequest::Lock), StorageResponse::Error, "only init signs out");
assert_eq!(set(&mut s, 0, StorageRequest::Lock), StorageResponse::Ok);
assert!(!s.unlocked());
let revoked: Vec<usize> = s.host().revoked.iter().map(|c| badge_of(*c)).collect();
assert_eq!(revoked, [shell.badge, app.badge]);
assert_eq!(set(&mut s, shell.badge, StorageRequest::FileSize { handle: crate::filesystem::FileHandle::new(0) }), StorageResponse::Denied);
assert_eq!(set(&mut s, 0, StorageRequest::SetUser { id: 2, guest: true }), StorageResponse::Ok, "someone else may sign in");
}
#[test]
fn only_init_purges_only_users_and_only_while_locked() {
let mut s = server();
let init = Client::attach(&mut s, 0);
let shell = init.narrowed(&mut s, b"", true);
let purge = |s: &mut Srv, badge: usize, path: &[u8]| {
init.put(s, path);
call(s, badge, StorageRequest::Purge { path_len: path.len() }).0
};
assert_eq!(purge(&mut s, shell.badge, b"users/x"), StorageResponse::Error);
assert_eq!(purge(&mut s, 0, b"bin"), StorageResponse::Denied);
assert_eq!(purge(&mut s, 0, b"users/../bin"), StorageResponse::Denied);
assert_eq!(purge(&mut s, 0, b"users/"), StorageResponse::Denied);
assert_eq!(purge(&mut s, 0, b"users/x"), StorageResponse::Ok);
assert_eq!(purge(&mut s, 0, b"users"), StorageResponse::Ok);
}