From a15372d402e9417b481fd400587e3045ef761feb Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Thu, 26 Feb 2026 16:27:07 -0800 Subject: [PATCH] Implement outbox for profile lookup --- .ackrc | 2 + backend/src/api.rs | 23 ++++++++- backend/src/config.rs | 17 ++++++- frontend/bun.lock | 3 ++ frontend/package.json | 1 + frontend/src/App.tsx | 9 ++-- frontend/src/components/Navbar.tsx | 5 +- frontend/src/lib/api.ts | 8 +++ frontend/src/lib/nostr.ts | 48 +++++++++++++++--- frontend/src/pages/Account.tsx | 29 +++++------ frontend/src/pages/admin/AdminTenants.tsx | 59 +++++++++++++++++++++-- 11 files changed, 163 insertions(+), 41 deletions(-) create mode 100644 .ackrc diff --git a/.ackrc b/.ackrc new file mode 100644 index 0000000..159d911 --- /dev/null +++ b/.ackrc @@ -0,0 +1,2 @@ +--ignore-dir=target +--ignore-dir=ref diff --git a/backend/src/api.rs b/backend/src/api.rs index 7ff16f0..1294688 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -35,6 +35,7 @@ pub fn router(state: AppState) -> Router { .route("/tenant/billing", put(update_tenant_billing)); let admin_routes = Router::new() + .route("/admin/check", get(admin_check)) .route("/admin/tenants", get(admin_list_tenants)) .route( "/admin/tenants/:pubkey", @@ -108,7 +109,7 @@ fn extract_auth_pubkey(headers: &HeaderMap, method: &Method, uri: &Uri) -> Resul let path = uri.path_and_query().map(|v| v.as_str()).unwrap_or(uri.path()); let url = format!("{}://{}{}", scheme, host, path); let pubkey = verify_nip98(auth_header, &url, method.as_str())?; - Ok(pubkey.to_string()) + Ok(pubkey.to_hex()) } async fn get_tenant( @@ -396,6 +397,26 @@ async fn admin_list_tenants( } } +#[derive(Debug, Serialize)] +struct AdminCheckResponse { + is_admin: bool, +} + +async fn admin_check( + State(state): State, + headers: HeaderMap, + method: Method, + uri: Uri, +) -> Response { + let pubkey = match extract_auth_pubkey(&headers, &method, &uri) { + Ok(pubkey) => pubkey, + Err(_) => return unauthorized(), + }; + + let is_admin = state.admin_pubkeys.contains(&pubkey); + (StatusCode::OK, Json(AdminCheckResponse { is_admin })).into_response() +} + async fn admin_get_tenant( State(state): State, headers: HeaderMap, diff --git a/backend/src/config.rs b/backend/src/config.rs index 3ad3186..154f817 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -1,5 +1,8 @@ use std::env; use std::path::Path; +use std::str::FromStr; + +use nostr_sdk::nostr::key::PublicKey; #[derive(Debug, Clone)] pub struct Config { @@ -31,7 +34,7 @@ impl Config { let admin_pubkeys = env::var("PLATFORM_ADMIN_PUBKEYS") .unwrap_or_default() .split(',') - .map(|v| v.trim().to_string()) + .filter_map(normalize_pubkey) .filter(|v| !v.is_empty()) .collect::>(); let zooid_api_url = @@ -74,6 +77,18 @@ impl Config { } } +fn normalize_pubkey(value: &str) -> Option { + let trimmed = value.trim(); + if trimmed.is_empty() { + return None; + } + + match PublicKey::from_str(trimmed) { + Ok(pubkey) => Some(pubkey.to_hex()), + Err(_) => Some(trimmed.to_lowercase()), + } +} + fn resolve_database_url(database_url: String) -> String { const PREFIX: &str = "sqlite://"; if !database_url.starts_with(PREFIX) { diff --git a/frontend/bun.lock b/frontend/bun.lock index f4d8699..3d14afe 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -13,6 +13,7 @@ "applesauce-accounts": "^5.1.0", "applesauce-common": "^5.1.0", "applesauce-core": "^5.1.0", + "applesauce-loaders": "^5.1.0", "applesauce-relay": "^5.1.0", "applesauce-signers": "^5.1.0", "applesauce-wallet-connect": "^5.0.1", @@ -281,6 +282,8 @@ "applesauce-core": ["applesauce-core@5.1.0", "", { "dependencies": { "debug": "^4.4.0", "fast-deep-equal": "^3.1.3", "hash-sum": "^2.0.0", "nanoid": "^5.0.9", "nostr-tools": "~2.19", "rxjs": "^7.8.1" } }, "sha512-kk4nHndK4zjS8Sa6mC8LGtQ0LDSP4hlCGPJ9lpyIln7MkZaNFWD9eFd+fsEhfE9kyrne9IyYuVfJNp+EqY1b9w=="], + "applesauce-loaders": ["applesauce-loaders@5.1.0", "", { "dependencies": { "applesauce-core": "^5.1.0", "nanoid": "^5.0.9", "rxjs": "^7.8.1" } }, "sha512-xllWYl7KxG0oaJqKVdZNzxN8OZQoDMaMmaTAO9Ao1Son+mmJyR9Q4UVicSwSlzzHarf59WCfvJeSdrSHIitkHg=="], + "applesauce-relay": ["applesauce-relay@5.1.0", "", { "dependencies": { "@noble/hashes": "^1.7.1", "applesauce-core": "^5.1.0", "nanoid": "^5.0.9", "nostr-tools": "~2.19", "rxjs": "^7.8.1" } }, "sha512-d0LTJmQmr5gsYFm9A6efPEo2Bx/ewoL7LNsIdieMx34QohZBpPb137RvU9KQ1lFIXTm0tudd8VYfAPncqti2OQ=="], "applesauce-signers": ["applesauce-signers@5.1.0", "", { "dependencies": { "@noble/secp256k1": "^1.7.1", "applesauce-core": "^5.1.0", "debug": "^4.4.0", "nanoid": "^5.0.9", "rxjs": "^7.8.2" } }, "sha512-sdQe6J1txYV1GVX8/zSGZDyyXuuZomePHSfUDozZmNAnXhCXE0wqVfhLK0yegVMnomSgoeDUCsGmJiTE2BHqoQ=="], diff --git a/frontend/package.json b/frontend/package.json index b1a3097..75ea692 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "applesauce-accounts": "^5.1.0", "applesauce-common": "^5.1.0", "applesauce-core": "^5.1.0", + "applesauce-loaders": "^5.1.0", "applesauce-relay": "^5.1.0", "applesauce-signers": "^5.1.0", "applesauce-wallet-connect": "^5.0.1", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e82a5c2..be1f655 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -15,7 +15,7 @@ import AdminRelays from "./pages/admin/AdminRelays" import AdminRelayDetail from "./pages/admin/AdminRelayDetail" import AdminRelayEdit from "./pages/admin/AdminRelayEdit" import { useActiveAccount } from "./lib/nostr" -import { adminListTenants } from "./lib/api" +import { adminCheck as fetchAdminCheck } from "./lib/api" function Layout(props: { children?: any }) { const location = useLocation() @@ -54,10 +54,7 @@ export default function App() { const account = useActiveAccount() const [adminCheck] = createResource( () => account()?.id, - async () => { - await adminListTenants() - return true - }, + () => fetchAdminCheck(), ) createEffect(() => { @@ -68,7 +65,7 @@ export default function App() { Checking admin access...}> You do not have admin access.} > diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index 60bcfbf..5dacf40 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -9,14 +9,11 @@ export default function Navbar() { return (