| | |
| 66 | 66 | Ok(Api { store: Mutex::new(store), objects }) |
| 67 | 67 | } |
| 68 | 68 | |
| 69 | /// The store, even if an earlier request panicked while holding it. Store writes are |
| 70 | /// all-or-nothing, so a panic can't leave it half-changed. |
| 71 | fn store(&self) -> std::sync::MutexGuard<'_, Store> { |
| 72 | self.store.lock().unwrap_or_else(|poisoned| poisoned.into_inner()) |
| 73 | } |
| 74 | |
| 69 | 75 | pub fn handle(&self, req: Request) -> Response { |
| 70 | 76 | match self.route(&req) { |
| 71 | 77 | Ok(r) | Err(r) => r, |
| | |
| 75 | 81 | fn actor(&self, req: &Request) -> Result<Option<Actor>, Response> { |
| 76 | 82 | let Some(h) = req.header("authorization") else { return Ok(None) }; |
| 77 | 83 | let token = h.strip_prefix("Bearer ").ok_or_else(|| err(401, "use Authorization: Bearer <token>"))?; |
| 78 | | let store = self.store.lock().unwrap(); |
| 84 | let store = self.store(); |
| 79 | 85 | store.authenticate(token.trim()).map(Some).ok_or_else(|| err(401, "unknown or revoked token")) |
| 80 | 86 | } |
| 81 | 87 | |
| | |
| 90 | 96 | ("GET", ["api", "health"]) => ok(obj([("ok", true.into())])), |
| 91 | 97 | ("GET", ["api", "whoami"]) => { |
| 92 | 98 | let a = self.required(req)?; |
| 93 | | let store = self.store.lock().unwrap(); |
| 99 | let store = self.store(); |
| 94 | 100 | let agent_name = a.agent.as_ref().and_then(|id| store.state().agent(id)).map(|g| g.name.clone()); |
| 95 | 101 | ok(obj([("person", a.person.clone().into()), ("agent", a.agent.clone().into()), ("agent_name", agent_name.into())])) |
| 96 | 102 | } |
| | |
| 129 | 135 | return Err(err(403, "agents are registered in person")); |
| 130 | 136 | } |
| 131 | 137 | let name = field(&body(req)?, "name")?.to_string(); |
| 132 | | let id = self.store.lock().unwrap().add_agent(&a.person, &name).map_err(from_store)?; |
| 138 | let id = self.store().add_agent(&a.person, &name).map_err(from_store)?; |
| 133 | 139 | ok(obj([("id", id.into())])) |
| 134 | 140 | } |
| 135 | 141 | ("POST", ["api", "tokens"]) => { |
| | |
| 139 | 145 | } |
| 140 | 146 | let b = body(req)?; |
| 141 | 147 | let agent = b.get("agent").and_then(Value::as_str).map(str::to_string); |
| 142 | | let token = self.store.lock().unwrap().issue_token(&a.person, agent.as_deref()).map_err(from_store)?; |
| 148 | let token = self.store().issue_token(&a.person, agent.as_deref()).map_err(from_store)?; |
| 143 | 149 | ok(obj([("token", token.into())])) |
| 144 | 150 | } |
| 145 | 151 | ("POST", ["api", "groups"]) => { |
| 146 | 152 | let a = self.required(req)?; |
| 147 | 153 | let name = field(&body(req)?, "name")?.to_string(); |
| 148 | | self.store.lock().unwrap().create_group(&a, &name).map_err(from_store)?; |
| 154 | self.store().create_group(&a, &name).map_err(from_store)?; |
| 149 | 155 | ok(obj([("name", name.into())])) |
| 150 | 156 | } |
| 151 | 157 | ("POST", ["api", "groups", group, "members"]) => { |
| 152 | 158 | let a = self.required(req)?; |
| 153 | 159 | let person = field(&body(req)?, "person")?.to_string(); |
| 154 | | self.store.lock().unwrap().add_member(&a, group, &person).map_err(from_store)?; |
| 160 | self.store().add_member(&a, group, &person).map_err(from_store)?; |
| 155 | 161 | ok(obj([("added", person.into())])) |
| 156 | 162 | } |
| 157 | 163 | |
| | |
| 162 | 168 | let owner = b.get("owner").and_then(Value::as_str).unwrap_or(&a.person).to_string(); |
| 163 | 169 | let name = field(&b, "name")?.to_string(); |
| 164 | 170 | let public = !matches!(b.get("public"), Some(Value::Bool(false))); |
| 165 | | self.store.lock().unwrap().create_project(&a, &owner, &name, public).map_err(from_store)?; |
| 171 | self.store().create_project(&a, &owner, &name, public).map_err(from_store)?; |
| 166 | 172 | ok(obj([("owner", owner.into()), ("name", name.into())])) |
| 167 | 173 | } |
| 168 | 174 | (_, ["api", "projects", owner, name, rest @ ..]) => self.project_route(req, m, owner, name, rest), |
| | |
| 172 | 178 | |
| 173 | 179 | fn project_route(&self, req: &Request, m: &str, owner: &str, name: &str, rest: &[&str]) -> Out { |
| 174 | 180 | let actor = self.actor(req)?; |
| 175 | | let mut store = self.store.lock().unwrap(); |
| 181 | let mut store = self.store(); |
| 176 | 182 | { |
| 177 | 183 | let p = store.project(owner, name).map_err(|_| err(404, format!("no project {owner}/{name}")))?; |
| 178 | 184 | let can_read = p.public || actor.as_ref().is_some_and(|a| store.state().owns(&p.owner, &a.person)); |