Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place

finally found good scopes

Changed files
+103 -39
cli
hosting-service
src
+63 -34
cli/Cargo.lock
···
[[package]]
name = "anstyle-query"
-
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
-
"windows-sys 0.60.2",
]
[[package]]
name = "anstyle-wincon"
-
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
-
"windows-sys 0.60.2",
]
[[package]]
···
[[package]]
name = "bytes"
-
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
dependencies = [
"serde",
]
···
[[package]]
name = "cc"
-
version = "1.2.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe"
dependencies = [
"find-msvc-tools",
"shlex",
···
[[package]]
name = "find-msvc-tools"
-
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "flate2"
···
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
name = "hyper"
-
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f"
dependencies = [
"atomic-waker",
"bytes",
···
[[package]]
name = "hyper-util"
-
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [
"base64 0.22.1",
"bytes",
···
[[package]]
name = "jacquard"
version = "0.9.0"
-
source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65"
dependencies = [
"bytes",
"getrandom 0.2.16",
"http",
"jacquard-api",
"jacquard-common",
···
[[package]]
name = "jacquard-api"
version = "0.9.0"
-
source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65"
dependencies = [
"bon",
"bytes",
···
[[package]]
name = "jacquard-common"
version = "0.9.0"
-
source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65"
dependencies = [
"base64 0.22.1",
"bon",
···
[[package]]
name = "jacquard-derive"
version = "0.9.0"
-
source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65"
dependencies = [
"heck 0.5.0",
"jacquard-lexicon",
···
[[package]]
name = "jacquard-identity"
version = "0.9.1"
-
source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65"
dependencies = [
"bon",
"bytes",
···
[[package]]
name = "jacquard-lexicon"
version = "0.9.1"
-
source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65"
dependencies = [
"cid",
"dashmap",
···
[[package]]
name = "jacquard-oauth"
version = "0.9.0"
-
source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65"
dependencies = [
"base64 0.22.1",
"bytes",
···
[[package]]
name = "rsa"
-
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
dependencies = [
"const-oid",
"digest",
···
[[package]]
name = "serde_with"
-
version = "3.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04"
dependencies = [
"base64 0.22.1",
"chrono",
···
[[package]]
name = "serde_with_macros"
-
version = "3.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955"
dependencies = [
"darling",
"proc-macro2",
···
[[package]]
name = "windows-registry"
-
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
dependencies = [
-
"windows-link 0.1.3",
-
"windows-result 0.3.4",
-
"windows-strings 0.4.2",
]
[[package]]
···
[[package]]
name = "anstyle-query"
+
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
+
"windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
+
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
+
"windows-sys 0.61.2",
]
[[package]]
···
[[package]]
name = "bytes"
+
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
dependencies = [
"serde",
]
···
[[package]]
name = "cc"
+
version = "1.2.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
dependencies = [
"find-msvc-tools",
"shlex",
···
[[package]]
name = "find-msvc-tools"
+
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "flate2"
···
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
+
name = "gloo-storage"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a"
+
dependencies = [
+
"gloo-utils",
+
"js-sys",
+
"serde",
+
"serde_json",
+
"thiserror 1.0.69",
+
"wasm-bindgen",
+
"web-sys",
+
]
+
+
[[package]]
+
name = "gloo-utils"
+
version = "0.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
+
dependencies = [
+
"js-sys",
+
"serde",
+
"serde_json",
+
"wasm-bindgen",
+
"web-sys",
+
]
+
+
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
name = "hyper"
+
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [
"atomic-waker",
"bytes",
···
[[package]]
name = "hyper-util"
+
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
dependencies = [
"base64 0.22.1",
"bytes",
···
[[package]]
name = "jacquard"
version = "0.9.0"
+
source = "git+https://tangled.org/@nonbinary.computer/jacquard#d853091d7de59e18746a78532dc28cfc017079b0"
dependencies = [
"bytes",
"getrandom 0.2.16",
+
"gloo-storage",
"http",
"jacquard-api",
"jacquard-common",
···
[[package]]
name = "jacquard-api"
version = "0.9.0"
+
source = "git+https://tangled.org/@nonbinary.computer/jacquard#d853091d7de59e18746a78532dc28cfc017079b0"
dependencies = [
"bon",
"bytes",
···
[[package]]
name = "jacquard-common"
version = "0.9.0"
+
source = "git+https://tangled.org/@nonbinary.computer/jacquard#d853091d7de59e18746a78532dc28cfc017079b0"
dependencies = [
"base64 0.22.1",
"bon",
···
[[package]]
name = "jacquard-derive"
version = "0.9.0"
+
source = "git+https://tangled.org/@nonbinary.computer/jacquard#d853091d7de59e18746a78532dc28cfc017079b0"
dependencies = [
"heck 0.5.0",
"jacquard-lexicon",
···
[[package]]
name = "jacquard-identity"
version = "0.9.1"
+
source = "git+https://tangled.org/@nonbinary.computer/jacquard#d853091d7de59e18746a78532dc28cfc017079b0"
dependencies = [
"bon",
"bytes",
···
[[package]]
name = "jacquard-lexicon"
version = "0.9.1"
+
source = "git+https://tangled.org/@nonbinary.computer/jacquard#d853091d7de59e18746a78532dc28cfc017079b0"
dependencies = [
"cid",
"dashmap",
···
[[package]]
name = "jacquard-oauth"
version = "0.9.0"
+
source = "git+https://tangled.org/@nonbinary.computer/jacquard#d853091d7de59e18746a78532dc28cfc017079b0"
dependencies = [
"base64 0.22.1",
"bytes",
···
[[package]]
name = "rsa"
+
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88"
dependencies = [
"const-oid",
"digest",
···
[[package]]
name = "serde_with"
+
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1"
dependencies = [
"base64 0.22.1",
"chrono",
···
[[package]]
name = "serde_with_macros"
+
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b"
dependencies = [
"darling",
"proc-macro2",
···
[[package]]
name = "windows-registry"
+
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
+
"windows-link 0.2.1",
+
"windows-result 0.4.1",
+
"windows-strings 0.5.1",
]
[[package]]
+37 -3
cli/src/main.rs
···
async fn main() -> miette::Result<()> {
let args = Args::parse();
-
match args.command {
Some(Commands::Deploy { input, path, site, store, password }) => {
// Dispatch to appropriate authentication method
if let Some(password) = password {
···
if let Some(input) = args.input {
let path = args.path.unwrap_or_else(|| PathBuf::from("."));
let store = args.store.unwrap_or_else(|| "/tmp/wisp-oauth-session.json".to_string());
-
// Dispatch to appropriate authentication method
if let Some(password) = args.password {
run_with_app_password(input, password, path, args.site).await
···
Ok(())
}
}
}
}
···
path: PathBuf,
site: Option<String>,
) -> miette::Result<()> {
-
let oauth = OAuthClient::with_default_config(FileAuthStore::new(&store));
let session = oauth
.login_with_local_server(input, Default::default(), LoopbackConfig::default())
.await?;
···
async fn main() -> miette::Result<()> {
let args = Args::parse();
+
let result = match args.command {
Some(Commands::Deploy { input, path, site, store, password }) => {
// Dispatch to appropriate authentication method
if let Some(password) = password {
···
if let Some(input) = args.input {
let path = args.path.unwrap_or_else(|| PathBuf::from("."));
let store = args.store.unwrap_or_else(|| "/tmp/wisp-oauth-session.json".to_string());
+
// Dispatch to appropriate authentication method
if let Some(password) = args.password {
run_with_app_password(input, password, path, args.site).await
···
Ok(())
}
}
+
};
+
+
// Force exit to avoid hanging on background tasks/connections
+
match result {
+
Ok(_) => std::process::exit(0),
+
Err(e) => {
+
eprintln!("{:?}", e);
+
std::process::exit(1)
+
}
}
}
···
path: PathBuf,
site: Option<String>,
) -> miette::Result<()> {
+
use jacquard::oauth::scopes::Scope;
+
use jacquard::oauth::atproto::AtprotoClientMetadata;
+
use jacquard::oauth::session::ClientData;
+
use url::Url;
+
+
// Request the necessary scopes for wisp.place
+
let scopes = Scope::parse_multiple("atproto repo:place.wisp.fs repo:place.wisp.subfs blob:*/*")
+
.map_err(|e| miette::miette!("Failed to parse scopes: {:?}", e))?;
+
+
// Create redirect URIs that match the loopback server (port 4000, path /oauth/callback)
+
let redirect_uris = vec![
+
Url::parse("http://127.0.0.1:4000/oauth/callback").into_diagnostic()?,
+
Url::parse("http://[::1]:4000/oauth/callback").into_diagnostic()?,
+
];
+
+
// Create client metadata with matching redirect URIs and scopes
+
let client_data = ClientData {
+
keyset: None,
+
config: AtprotoClientMetadata::new_localhost(
+
Some(redirect_uris),
+
Some(scopes),
+
),
+
};
+
+
let oauth = OAuthClient::new(FileAuthStore::new(&store), client_data);
+
let session = oauth
.login_with_local_server(input, Default::default(), LoopbackConfig::default())
.await?;
+1
hosting-service/bun.lock
···
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "wisp-hosting-service",
···
{
"lockfileVersion": 1,
+
"configVersion": 0,
"workspaces": {
"": {
"name": "wisp-hosting-service",
+2 -2
src/lib/oauth-client.ts
···
// Loopback client for local development
// For loopback, scopes and redirect_uri must be in client_id query string
const redirectUri = 'http://127.0.0.1:8000/api/auth/callback';
-
const scope = 'atproto repo:place.wisp.fs repo:place.wisp.domain repo:place.wisp.subfs blob:*/* blob?maxSize=100000000 rpc:*?aud=did:web:api.bsky.app#bsky_appview';
const params = new URLSearchParams();
params.append('redirect_uri', redirectUri);
params.append('scope', scope);
···
application_type: 'web',
token_endpoint_auth_method: 'private_key_jwt',
token_endpoint_auth_signing_alg: "ES256",
-
scope: "atproto repo:place.wisp.fs repo:place.wisp.domain repo:place.wisp.subfs blob:*/* blob?maxSize=100000000 rpc:*?aud=did:web:api.bsky.app#bsky_appview",
dpop_bound_access_tokens: true,
jwks_uri: `${config.domain}/jwks.json`,
subject_type: 'public',
···
// Loopback client for local development
// For loopback, scopes and redirect_uri must be in client_id query string
const redirectUri = 'http://127.0.0.1:8000/api/auth/callback';
+
const scope = 'atproto repo:place.wisp.fs repo:place.wisp.domain repo:place.wisp.subfs blob:*/* rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app#bsky_appview';
const params = new URLSearchParams();
params.append('redirect_uri', redirectUri);
params.append('scope', scope);
···
application_type: 'web',
token_endpoint_auth_method: 'private_key_jwt',
token_endpoint_auth_signing_alg: "ES256",
+
scope: "atproto repo:place.wisp.fs repo:place.wisp.domain repo:place.wisp.subfs blob:*/* rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app#bsky_appview",
dpop_bound_access_tokens: true,
jwks_uri: `${config.domain}/jwks.json`,
subject_type: 'public',