Magazi is a content distribution platform that gates access to files using ATProtocol (Bluesky) identity and cryptographic proofs.
download.ngerakines.me/
atprotocol
appview
atprotocol-attestations
1use std::fs;
2use std::path::PathBuf;
3
4use anyhow::{Context, Result};
5use atproto_identity::key::{KeyData, identify_key};
6use serde::{Deserialize, Serialize};
7
8use crate::entitlements::default_requirements;
9
10/// A catalog entry from catalog.json (without download URL).
11#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct CatalogEntry {
13 pub id: String,
14 pub name: String,
15 pub description: String,
16 /// Optional content type override for download and getBlob responses.
17 /// When set, this value is used instead of mime_guess inference.
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub content_type: Option<String>,
20 /// Optional FontAwesome icon class (e.g., "fa-solid fa-book").
21 /// When not set, defaults to "fa-regular fa-file".
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub icon: Option<String>,
24 /// JSONLogic rule for determining entitlement to this catalog item.
25 /// When not set, uses the default requirements (authenticated + supporter proof + broker proof).
26 #[serde(default = "default_requirements")]
27 pub requirements: serde_json::Value,
28}
29
30#[derive(Clone)]
31pub struct Config {
32 pub http_port: u16,
33 pub http_external: String,
34 pub cookie_key: Vec<u8>,
35 pub oauth_client_credentials: KeyData,
36 pub creator_identity: String,
37 pub broker_identity: String,
38 pub download_key: KeyData,
39 pub files_path: PathBuf,
40 pub catalog: Vec<CatalogEntry>,
41}
42
43impl Config {
44 pub fn from_env() -> Result<Self> {
45 let http_port: u16 = std::env::var("HTTP_PORT")
46 .context("HTTP_PORT not set")?
47 .parse()
48 .context("HTTP_PORT must be a valid port number")?;
49
50 let http_external = std::env::var("HTTP_EXTERNAL").context("HTTP_EXTERNAL not set")?;
51
52 let cookie_key_b64 = std::env::var("HTTP_COOKIE_KEY").context("HTTP_COOKIE_KEY not set")?;
53 let cookie_key =
54 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &cookie_key_b64)
55 .context("HTTP_COOKIE_KEY must be valid base64")?;
56
57 let oauth_client_credentials_str = std::env::var("OAUTH_CLIENT_CREDENTIALS")
58 .context("OAUTH_CLIENT_CREDENTIALS not set")?;
59 let oauth_client_credentials = identify_key(&oauth_client_credentials_str)
60 .map_err(|e| anyhow::anyhow!("Invalid OAUTH_CLIENT_CREDENTIALS: {}", e))?;
61
62 let creator_identity =
63 std::env::var("CREATOR_IDENTITY").context("CREATOR_IDENTITY not set")?;
64
65 let broker_identity =
66 std::env::var("BROKER_IDENTITY").context("BROKER_IDENTITY not set")?;
67
68 let download_key_str = std::env::var("DOWNLOAD_KEY").context("DOWNLOAD_KEY not set")?;
69 let download_key = identify_key(&download_key_str)
70 .map_err(|e| anyhow::anyhow!("Invalid DOWNLOAD_KEY: {}", e))?;
71
72 let files_path = PathBuf::from(std::env::var("FILES").context("FILES not set")?);
73
74 let catalog_path = PathBuf::from(std::env::var("CATALOG").context("CATALOG not set")?);
75 let catalog_contents = fs::read_to_string(&catalog_path)
76 .with_context(|| format!("Failed to read catalog file: {}", catalog_path.display()))?;
77 let catalog: Vec<CatalogEntry> = serde_json::from_str(&catalog_contents)
78 .with_context(|| format!("Failed to parse catalog file: {}", catalog_path.display()))?;
79
80 Ok(Config {
81 http_port,
82 http_external,
83 cookie_key,
84 oauth_client_credentials,
85 creator_identity,
86 broker_identity,
87 download_key,
88 files_path,
89 catalog,
90 })
91 }
92
93 pub fn client_id(&self) -> String {
94 format!("https://{}/oauth-client-metadata.json", self.http_external)
95 }
96
97 pub fn redirect_uri(&self) -> String {
98 format!("https://{}/login/callback", self.http_external)
99 }
100
101 pub fn external_url(&self) -> String {
102 format!("https://{}", self.http_external)
103 }
104
105 /// Find a catalog entry by its ID (CID).
106 pub fn find_catalog_entry(&self, id: &str) -> Option<&CatalogEntry> {
107 self.catalog.iter().find(|e| e.id == id)
108 }
109}