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:
88
crates/servers/storage-server/src/server/session.rs
Normal file
88
crates/servers/storage-server/src/server/session.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
//! Who is signed in, as far as storage cares: init says whose areas open,
|
||||
//! hands over the key and seed, and at sign-out locks everything again.
|
||||
//! Every one of these is init's alone (the unbadged holder).
|
||||
|
||||
use types::user::UserFolder;
|
||||
|
||||
use super::{Host, StorageServer};
|
||||
use crate::filesystem::{FileHandle, FileSystem};
|
||||
use crate::grants::MAX_GRANTS;
|
||||
use crate::protocol::{MAX_PATH, StorageResponse};
|
||||
|
||||
impl<F: FileSystem, H: Host> StorageServer<F, H> {
|
||||
/// Once per sign-in, before the key.
|
||||
pub(super) fn set_user(&mut self, badge: usize, id: u64, guest: bool) -> StorageResponse {
|
||||
if badge != 0 || self.unlocked || self.user.is_some() {
|
||||
return StorageResponse::Error;
|
||||
}
|
||||
self.user = Some(if guest { UserFolder::GUEST } else { UserFolder::of(id) });
|
||||
StorageResponse::Ok
|
||||
}
|
||||
|
||||
/// Each half only once per sign-in: nobody can replace the key or the
|
||||
/// seed while someone is in. Without a user said first, refused.
|
||||
pub(super) fn set_half(&mut self, badge: usize, seed: bool, half: usize, words: [usize; 2]) -> StorageResponse {
|
||||
let halves = if seed { &mut self.seed } else { &mut self.key };
|
||||
match (badge, self.unlocked, self.user, halves.get_mut(half)) {
|
||||
(0, false, Some(_), Some(slot @ None)) => *slot = Some(words),
|
||||
_ => return StorageResponse::Error,
|
||||
}
|
||||
if let ([Some(k0), Some(k1)], [Some(s0), Some(s1)], Some(user)) = (self.key, self.seed, self.user) {
|
||||
let bytes = |w: [usize; 4]| -> [u8; 32] { core::array::from_fn(|i| w[i / 8].to_le_bytes()[i % 8]) };
|
||||
let (mut master, mut seed) = (bytes([k0[0], k0[1], k1[0], k1[1]]), bytes([s0[0], s0[1], s1[0], s1[1]]));
|
||||
self.fs.unlock(&master, &seed, user.as_bytes());
|
||||
master.fill(0);
|
||||
seed.fill(0);
|
||||
self.key = [None; 2];
|
||||
self.seed = [None; 2];
|
||||
self.unlocked = true;
|
||||
}
|
||||
StorageResponse::Ok
|
||||
}
|
||||
|
||||
/// Sign-out: every grant goes (and with them every app's files and
|
||||
/// buffers), init's own files close, the key is forgotten.
|
||||
pub(super) fn lock(&mut self, badge: usize) -> StorageResponse {
|
||||
if badge != 0 {
|
||||
return StorageResponse::Error;
|
||||
}
|
||||
let mut roots = [0usize; MAX_GRANTS];
|
||||
let n = self.grants.roots(&mut roots);
|
||||
for &root in &roots[..n] {
|
||||
self.release(root);
|
||||
}
|
||||
for (raw, owner) in self.owners.iter_mut().enumerate() {
|
||||
if owner.take().is_some() {
|
||||
self.fs.close(FileHandle::new(raw as u16));
|
||||
}
|
||||
}
|
||||
self.fs.lock();
|
||||
self.key = [None; 2];
|
||||
self.seed = [None; 2];
|
||||
self.user = None;
|
||||
self.unlocked = false;
|
||||
StorageResponse::Ok
|
||||
}
|
||||
|
||||
/// While nobody is in, so no one's open file can be pulled away.
|
||||
pub(super) fn purge(&mut self, badge: usize, path_len: usize) -> StorageResponse {
|
||||
if badge != 0 || self.unlocked || !self.fs.writable() {
|
||||
return StorageResponse::Error;
|
||||
}
|
||||
let Some(client) = self.clients.iter().flatten().find(|c| c.badge == Some(0)) else {
|
||||
return StorageResponse::NotAttached;
|
||||
};
|
||||
let mut path = [0u8; MAX_PATH];
|
||||
let len = path_len.min(MAX_PATH).min(client.buf.len());
|
||||
path[..len].copy_from_slice(&client.buf[..len]);
|
||||
let path = &path[..len];
|
||||
let path = path.strip_prefix(b"/").unwrap_or(path);
|
||||
// `users` (everyone, on a new keystore) or what is in it.
|
||||
let inside = path == b"users"
|
||||
|| path.strip_prefix(b"users/").is_some_and(|rest| !rest.split(|b| *b == b'/').any(|p| p.is_empty() || p == b"." || p == b".."));
|
||||
if !inside {
|
||||
return StorageResponse::Denied;
|
||||
}
|
||||
if self.fs.purge(path) { StorageResponse::Ok } else { StorageResponse::Error }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user