Skip to main content

max / makenotwork

8.6 KB · 248 lines History Blame Raw
1 //! Internal API endpoints for service-to-service communication.
2 //!
3 //! These endpoints are protected by `ServiceAuth` (Bearer token) and are
4 //! called by the CLI SSH server running on the same host.
5
6 pub(crate) mod alerts;
7 mod cli_features;
8 mod content;
9 mod creators;
10 mod git;
11 mod items;
12 mod repos;
13 mod synckit;
14 mod uploads;
15
16 pub(super) use git::restart_status;
17
18 use axum::routing::get;
19
20 use crate::{
21 AppState,
22 csrf::{CsrfRouter, delete_csrf_skip, post_csrf_skip, put_csrf_skip, with_csrf_skip},
23 };
24
25 /// All routes in this file are HMAC-bearer authed via `ServiceAuth` (no
26 /// session); CSRF is not applicable. Each registration carries the same
27 /// `Skip` reason so the posture is visible at the call site.
28 const INTERNAL_SKIP: &str = "internal API: HMAC bearer auth, no session";
29
30 /// Internal service-to-service routes (ServiceAuth, no rate limit).
31 pub(super) fn internal_routes() -> CsrfRouter<AppState> {
32 CsrfRouter::new()
33 .route_get("/api/internal/ssh-key-lookup", get(git::ssh_key_lookup))
34 .route_get(
35 "/api/internal/synckit/client-versions",
36 get(synckit::client_versions),
37 )
38 .route(
39 "/api/internal/creator/projects",
40 with_csrf_skip(
41 INTERNAL_SKIP,
42 get(creators::creator_projects).post(cli_features::create_project),
43 ),
44 )
45 .route_get(
46 "/api/internal/creator/projects/{id}/items",
47 get(creators::creator_project_items),
48 )
49 .route_get("/api/internal/creator/stats", get(creators::creator_stats))
50 // Git repo + SSH key management for the CLI. Keyed by repo NAME, not
51 // id: the caller is a person at a terminal who has the name and would
52 // otherwise have to look a UUID up first. See internal/repos.rs.
53 .route_get("/api/internal/creator/repos", get(repos::repo_list))
54 .route(
55 "/api/internal/creator/repos/{name}",
56 with_csrf_skip(
57 INTERNAL_SKIP,
58 get(repos::repo_info).delete(repos::repo_delete),
59 ),
60 )
61 .route(
62 "/api/internal/creator/repos/{name}/visibility",
63 put_csrf_skip(INTERNAL_SKIP, repos::repo_set_visibility),
64 )
65 .route(
66 "/api/internal/creator/repos/{name}/description",
67 put_csrf_skip(INTERNAL_SKIP, repos::repo_set_description),
68 )
69 .route(
70 "/api/internal/creator/ssh-keys/{fingerprint}",
71 delete_csrf_skip(INTERNAL_SKIP, repos::key_remove),
72 )
73 .route(
74 "/api/internal/creator/items",
75 post_csrf_skip(INTERNAL_SKIP, items::create_item),
76 )
77 .route(
78 "/api/internal/upload/presign",
79 post_csrf_skip(INTERNAL_SKIP, uploads::presign_upload),
80 )
81 .route(
82 "/api/internal/upload/confirm",
83 post_csrf_skip(INTERNAL_SKIP, uploads::confirm_upload),
84 )
85 .route(
86 "/api/internal/upload/multipart/start",
87 post_csrf_skip(INTERNAL_SKIP, uploads::multipart_start),
88 )
89 .route(
90 "/api/internal/upload/multipart/parts",
91 post_csrf_skip(INTERNAL_SKIP, uploads::multipart_parts),
92 )
93 .route(
94 "/api/internal/upload/multipart/complete",
95 post_csrf_skip(INTERNAL_SKIP, uploads::multipart_complete),
96 )
97 .route(
98 "/api/internal/upload/multipart/abort",
99 post_csrf_skip(INTERNAL_SKIP, uploads::multipart_abort),
100 )
101 .route_get(
102 "/api/internal/creator/storage",
103 get(uploads::creator_storage),
104 )
105 .route_get("/api/internal/creator/items/{id}", get(items::get_item))
106 .route(
107 "/api/internal/creator/items/{id}",
108 put_csrf_skip(INTERNAL_SKIP, items::update_item),
109 )
110 .route(
111 "/api/internal/creator/items/{id}",
112 delete_csrf_skip(INTERNAL_SKIP, items::delete_item),
113 )
114 .route(
115 "/api/internal/creator/items/{id}/publish",
116 post_csrf_skip(INTERNAL_SKIP, items::publish_item),
117 )
118 .route(
119 "/api/internal/creator/items/{id}/unpublish",
120 post_csrf_skip(INTERNAL_SKIP, items::unpublish_item),
121 )
122 .route_get(
123 "/api/internal/creator/items/{id}/versions",
124 get(items::item_versions),
125 )
126 // Blog posts
127 .route_get(
128 "/api/internal/creator/projects/{id}/blog",
129 get(content::list_blog_posts),
130 )
131 .route(
132 "/api/internal/creator/blog",
133 post_csrf_skip(INTERNAL_SKIP, content::create_blog_post),
134 )
135 .route(
136 "/api/internal/creator/blog/{id}",
137 delete_csrf_skip(INTERNAL_SKIP, content::delete_blog_post),
138 )
139 // Promo codes
140 .route(
141 "/api/internal/creator/promo-codes",
142 with_csrf_skip(
143 INTERNAL_SKIP,
144 get(content::list_promo_codes).post(content::create_promo_code),
145 ),
146 )
147 .route(
148 "/api/internal/creator/promo-codes/{id}",
149 delete_csrf_skip(INTERNAL_SKIP, content::delete_promo_code),
150 )
151 // License keys
152 .route(
153 "/api/internal/creator/items/{id}/keys",
154 with_csrf_skip(
155 INTERNAL_SKIP,
156 get(content::list_license_keys).post(content::generate_license_key),
157 ),
158 )
159 .route(
160 "/api/internal/creator/keys/{id}/revoke",
161 post_csrf_skip(INTERNAL_SKIP, content::revoke_license_key),
162 )
163 // Analytics + export
164 .route_get(
165 "/api/internal/creator/analytics",
166 get(creators::creator_analytics),
167 )
168 .route_get(
169 "/api/internal/creator/transactions",
170 get(creators::creator_transactions),
171 )
172 .route_get(
173 "/api/internal/creator/export/sales",
174 get(creators::export_sales),
175 )
176 // Settings
177 .route_get("/api/internal/creator/ssh-keys", get(git::list_ssh_keys))
178 // Git authorization
179 .route(
180 "/api/internal/git/authorize",
181 post_csrf_skip(INTERNAL_SKIP, git::git_authorize),
182 )
183 .route(
184 "/api/internal/restart-warning",
185 post_csrf_skip(INTERNAL_SKIP, git::set_restart_warning),
186 )
187 // CLI features: tags
188 .route_get(
189 "/api/internal/creator/items/{id}/tags",
190 get(cli_features::list_item_tags),
191 )
192 .route(
193 "/api/internal/creator/items/tags",
194 post_csrf_skip(INTERNAL_SKIP, cli_features::add_item_tag),
195 )
196 .route(
197 "/api/internal/creator/items/tags/remove",
198 post_csrf_skip(INTERNAL_SKIP, cli_features::remove_item_tag),
199 )
200 .route_get("/api/internal/tags/search", get(cli_features::search_tags))
201 // CLI features: broadcast
202 .route(
203 "/api/internal/creator/broadcast",
204 post_csrf_skip(INTERNAL_SKIP, cli_features::send_broadcast),
205 )
206 // CLI features: tiers
207 .route_get(
208 "/api/internal/creator/projects/{id}/tiers",
209 get(cli_features::list_tiers),
210 )
211 // CLI features: collections
212 .route(
213 "/api/internal/creator/collections",
214 with_csrf_skip(
215 INTERNAL_SKIP,
216 get(cli_features::list_collections).post(cli_features::create_collection),
217 ),
218 )
219 .route(
220 "/api/internal/creator/collections/{id}",
221 delete_csrf_skip(INTERNAL_SKIP, cli_features::delete_collection),
222 )
223 // CLI features: custom domains
224 .route(
225 "/api/internal/creator/domain",
226 with_csrf_skip(
227 INTERNAL_SKIP,
228 get(cli_features::get_domain)
229 .post(cli_features::add_domain)
230 .delete(cli_features::remove_domain),
231 ),
232 )
233 .route(
234 "/api/internal/creator/domain/verify",
235 post_csrf_skip(INTERNAL_SKIP, cli_features::verify_domain),
236 )
237 // Inbound infra alerts (PoM/MT). Bearer-authed via `AlertsAuth` on a
238 // dedicated token, not `ServiceAuth`; no session, so CSRF is skipped.
239 .route(
240 "/api/internal/alerts",
241 post_csrf_skip(ALERTS_SKIP, alerts::ingest_alert),
242 )
243 }
244
245 /// The alerts ingestion route is bearer-authed via `AlertsAuth` (dedicated
246 /// token, no session); CSRF is not applicable.
247 const ALERTS_SKIP: &str = "internal alerts: AlertsAuth bearer, no session";
248