Skip to main content

y_sweet/
server.rs

1use anyhow::{anyhow, Result};
2use axum::{
3    body::Bytes,
4    extract::{
5        ws::{Message, WebSocket},
6        DefaultBodyLimit, Path, Query, Request, State, WebSocketUpgrade,
7    },
8    http::{
9        header::{HeaderMap, HeaderName},
10        StatusCode,
11    },
12    middleware::{self, Next},
13    response::{IntoResponse, Response},
14    routing::{get, post},
15    Json, Router,
16};
17use axum_extra::typed_header::TypedHeader;
18use dashmap::{mapref::one::MappedRef, DashMap};
19use futures::{SinkExt, StreamExt};
20use serde::Deserialize;
21use serde_json::{json, Value};
22use std::{
23    sync::{Arc, RwLock},
24    time::Duration,
25};
26use tokio::{
27    net::TcpListener,
28    sync::mpsc::{channel, Receiver},
29};
30use tokio_util::{sync::CancellationToken, task::TaskTracker};
31use tracing::{span, Instrument, Level};
32use url::Url;
33use y_sweet_core::{
34    api_types::{
35        validate_doc_name, AuthDocRequest, Authorization, ClientToken, DocCreationRequest,
36        NewDocResponse,
37    },
38    auth::{Authenticator, ExpirationTimeEpochMillis, DEFAULT_EXPIRATION_SECONDS},
39    doc_connection::DocConnection,
40    doc_sync::DocWithSyncKv,
41    store::Store,
42    sync::awareness::Awareness,
43    sync_kv::SyncKv,
44};
45
46const PLANE_VERIFIED_USER_DATA_HEADER: &str = "x-verified-user-data";
47
48// Every 20 seconds, we send a ping to the client.
49const PING_EVERY: Duration = Duration::from_secs(20);
50// If we haven't received a pong in the last 40 seconds, we close the connection.
51// All modern browsers will respond to websocket pings with a pong message.
52const PONG_TIMEOUT: Duration = Duration::from_secs(40);
53
54fn current_time_epoch_millis() -> u64 {
55    let now = std::time::SystemTime::now();
56    let duration_since_epoch = now.duration_since(std::time::UNIX_EPOCH).unwrap();
57    duration_since_epoch.as_millis() as u64
58}
59
60#[derive(Debug)]
61pub struct AppError(StatusCode, anyhow::Error);
62impl std::error::Error for AppError {}
63impl IntoResponse for AppError {
64    fn into_response(self) -> Response {
65        (self.0, format!("Something went wrong: {}", self.1)).into_response()
66    }
67}
68impl<E> From<(StatusCode, E)> for AppError
69where
70    E: Into<anyhow::Error>,
71{
72    fn from((status_code, err): (StatusCode, E)) -> Self {
73        Self(status_code, err.into())
74    }
75}
76impl std::fmt::Display for AppError {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        write!(f, "Status code: {} {}", self.0, self.1)?;
79        Ok(())
80    }
81}
82
83pub struct Server {
84    docs: Arc<DashMap<String, DocWithSyncKv>>,
85    doc_worker_tracker: TaskTracker,
86    store: Option<Arc<Box<dyn Store>>>,
87    checkpoint_freq: Duration,
88    authenticator: Option<Authenticator>,
89    url_prefix: Option<Url>,
90    cancellation_token: CancellationToken,
91    /// Whether to garbage collect docs that are no longer in use.
92    /// Disabled for single-doc mode, since we only have one doc.
93    doc_gc: bool,
94    max_body_size: Option<usize>,
95}
96
97impl Server {
98    pub async fn new(
99        store: Option<Box<dyn Store>>,
100        checkpoint_freq: Duration,
101        authenticator: Option<Authenticator>,
102        url_prefix: Option<Url>,
103        cancellation_token: CancellationToken,
104        doc_gc: bool,
105        max_body_size: Option<usize>,
106    ) -> Result<Self> {
107        Ok(Self {
108            docs: Arc::new(DashMap::new()),
109            doc_worker_tracker: TaskTracker::new(),
110            store: store.map(Arc::new),
111            checkpoint_freq,
112            authenticator,
113            url_prefix,
114            cancellation_token,
115            doc_gc,
116            max_body_size,
117        })
118    }
119
120    pub async fn doc_exists(&self, doc_id: &str) -> bool {
121        if self.docs.contains_key(doc_id) {
122            return true;
123        }
124        if let Some(store) = &self.store {
125            store
126                .exists(&format!("{}/data.ysweet", doc_id))
127                .await
128                .unwrap_or_default()
129        } else {
130            false
131        }
132    }
133
134    pub async fn create_doc(&self) -> Result<String> {
135        let doc_id = nanoid::nanoid!();
136        self.load_doc(&doc_id).await?;
137        tracing::info!(doc_id=?doc_id, "Created doc");
138        Ok(doc_id)
139    }
140
141    pub async fn load_doc(&self, doc_id: &str) -> Result<()> {
142        let (send, recv) = channel(1024);
143
144        let dwskv = DocWithSyncKv::new(doc_id, self.store.clone(), move || {
145            send.try_send(()).unwrap();
146        })
147        .await?;
148
149        dwskv
150            .sync_kv()
151            .persist()
152            .await
153            .map_err(|e| anyhow!("Error persisting: {:?}", e))?;
154
155        {
156            let sync_kv = dwskv.sync_kv();
157            let checkpoint_freq = self.checkpoint_freq;
158            let doc_id = doc_id.to_string();
159            let cancellation_token = self.cancellation_token.clone();
160
161            // Spawn a task to save the document to the store when it changes.
162            self.doc_worker_tracker.spawn(
163                Self::doc_persistence_worker(
164                    recv,
165                    sync_kv,
166                    checkpoint_freq,
167                    doc_id.clone(),
168                    cancellation_token.clone(),
169                )
170                .instrument(span!(Level::INFO, "save_loop", doc_id=?doc_id)),
171            );
172
173            if self.doc_gc {
174                self.doc_worker_tracker.spawn(
175                    Self::doc_gc_worker(
176                        self.docs.clone(),
177                        doc_id.clone(),
178                        checkpoint_freq,
179                        cancellation_token,
180                    )
181                    .instrument(span!(Level::INFO, "gc_loop", doc_id=?doc_id)),
182                );
183            }
184        }
185
186        self.docs.insert(doc_id.to_string(), dwskv);
187        Ok(())
188    }
189
190    async fn doc_gc_worker(
191        docs: Arc<DashMap<String, DocWithSyncKv>>,
192        doc_id: String,
193        checkpoint_freq: Duration,
194        cancellation_token: CancellationToken,
195    ) {
196        let mut checkpoints_without_refs = 0;
197
198        loop {
199            tokio::select! {
200                _ = tokio::time::sleep(checkpoint_freq) => {
201                    if let Some(doc) = docs.get(&doc_id) {
202                        let awareness = Arc::downgrade(&doc.awareness());
203                        if awareness.strong_count() > 1 {
204                            checkpoints_without_refs = 0;
205                            tracing::debug!("doc is still alive - it has {} references", awareness.strong_count());
206                        } else {
207                            checkpoints_without_refs += 1;
208                            tracing::info!("doc has only one reference, candidate for GC. checkpoints_without_refs: {}", checkpoints_without_refs);
209                        }
210                    } else {
211                        break;
212                    }
213
214                    if checkpoints_without_refs >= 2 {
215                        tracing::info!("GCing doc");
216                        if let Some(doc) = docs.get(&doc_id) {
217                            doc.sync_kv().shutdown();
218                        }
219
220                        docs.remove(&doc_id);
221                        break;
222                    }
223                }
224                _ = cancellation_token.cancelled() => {
225                    break;
226                }
227            };
228        }
229        tracing::info!("Exiting gc_loop");
230    }
231
232    async fn doc_persistence_worker(
233        mut recv: Receiver<()>,
234        sync_kv: Arc<SyncKv>,
235        checkpoint_freq: Duration,
236        doc_id: String,
237        cancellation_token: CancellationToken,
238    ) {
239        let mut last_save = std::time::Instant::now();
240
241        loop {
242            let is_done = tokio::select! {
243                v = recv.recv() => v.is_none(),
244                _ = cancellation_token.cancelled() => true,
245                _ = tokio::time::sleep(checkpoint_freq) => {
246                    sync_kv.is_shutdown()
247                }
248            };
249
250            tracing::info!("Received signal. done: {}", is_done);
251            let now = std::time::Instant::now();
252            if !is_done && now - last_save < checkpoint_freq {
253                let sleep = tokio::time::sleep(checkpoint_freq - (now - last_save));
254                tokio::pin!(sleep);
255                tracing::info!("Throttling.");
256
257                loop {
258                    tokio::select! {
259                        _ = &mut sleep => {
260                            break;
261                        }
262                        v = recv.recv() => {
263                            tracing::info!("Received dirty while throttling.");
264                            if v.is_none() {
265                                break;
266                            }
267                        }
268                        _ = cancellation_token.cancelled() => {
269                            tracing::info!("Received cancellation while throttling.");
270                            break;
271                        }
272
273                    }
274                    tracing::info!("Done throttling.");
275                }
276            }
277            tracing::info!("Persisting.");
278            if let Err(e) = sync_kv.persist().await {
279                tracing::error!(?e, "Error persisting.");
280            } else {
281                tracing::info!("Done persisting.");
282            }
283            last_save = std::time::Instant::now();
284
285            if is_done {
286                break;
287            }
288        }
289        tracing::info!("Terminating loop for {}", doc_id);
290    }
291
292    pub async fn get_or_create_doc(
293        &self,
294        doc_id: &str,
295    ) -> Result<MappedRef<String, DocWithSyncKv, DocWithSyncKv>> {
296        if !self.docs.contains_key(doc_id) {
297            tracing::info!(doc_id=?doc_id, "Loading doc");
298            self.load_doc(doc_id).await?;
299        }
300
301        Ok(self
302            .docs
303            .get(doc_id)
304            .ok_or_else(|| anyhow!("Failed to get-or-create doc"))?
305            .map(|d| d))
306    }
307
308    pub fn check_auth(
309        &self,
310        auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
311    ) -> Result<(), AppError> {
312        if let Some(auth) = &self.authenticator {
313            if let Some(TypedHeader(headers::Authorization(bearer))) = auth_header {
314                if let Ok(()) =
315                    auth.verify_server_token(bearer.token(), current_time_epoch_millis())
316                {
317                    return Ok(());
318                }
319            }
320            Err((StatusCode::UNAUTHORIZED, anyhow!("Unauthorized.")))?
321        } else {
322            Ok(())
323        }
324    }
325
326    pub async fn redact_error_middleware(req: Request, next: Next) -> impl IntoResponse {
327        let resp = next.run(req).await;
328        if resp.status().is_server_error() || resp.status().is_client_error() {
329            // If we should redact errors, copy over only the status code and
330            // not the response body.
331            return resp.status().into_response();
332        }
333        resp
334    }
335
336    pub fn routes(self: &Arc<Self>) -> Router {
337        Router::new()
338            .route("/ready", get(ready))
339            .route("/check_store", post(check_store))
340            .route("/check_store", get(check_store_deprecated))
341            .route("/doc/ws/:doc_id", get(handle_socket_upgrade_deprecated))
342            .route("/doc/new", post(new_doc))
343            .route("/doc/:doc_id/auth", post(auth_doc))
344            .route("/doc/:doc_id/as-update", get(get_doc_as_update_deprecated))
345            .route("/doc/:doc_id/update", post(update_doc_deprecated))
346            .route("/d/:doc_id/as-update", get(get_doc_as_update))
347            .route("/d/:doc_id/update", post(update_doc))
348            .route(
349                "/d/:doc_id/ws/:doc_id2",
350                get(handle_socket_upgrade_full_path),
351            )
352            .with_state(self.clone())
353    }
354
355    pub fn single_doc_routes(self: &Arc<Self>) -> Router {
356        Router::new()
357            .route("/ws/:doc_id", get(handle_socket_upgrade_single))
358            .route("/as-update", get(get_doc_as_update_single))
359            .route("/update", post(update_doc_single))
360            .with_state(self.clone())
361    }
362
363    async fn serve_internal(
364        self: Arc<Self>,
365        listener: TcpListener,
366        redact_errors: bool,
367        routes: Router,
368    ) -> Result<()> {
369        let token = self.cancellation_token.clone();
370
371        let mut app = if let Some(max_body_size) = self.max_body_size {
372            routes.layer(DefaultBodyLimit::max(max_body_size))
373        } else {
374            routes
375        };
376
377        app = if redact_errors {
378            app
379        } else {
380            app.layer(middleware::from_fn(Self::redact_error_middleware))
381        };
382
383        axum::serve(listener, app.into_make_service())
384            .with_graceful_shutdown(async move { token.cancelled().await })
385            .await?;
386
387        self.doc_worker_tracker.close();
388        self.doc_worker_tracker.wait().await;
389
390        Ok(())
391    }
392
393    pub async fn serve(self, listener: TcpListener, redact_errors: bool) -> Result<()> {
394        let s = Arc::new(self);
395        let routes = s.routes();
396        s.serve_internal(listener, redact_errors, routes).await
397    }
398
399    pub async fn serve_doc(self, listener: TcpListener, redact_errors: bool) -> Result<()> {
400        let s = Arc::new(self);
401        let routes = s.single_doc_routes();
402        s.serve_internal(listener, redact_errors, routes).await
403    }
404
405    fn verify_doc_token(&self, token: Option<&str>, doc: &str) -> Result<Authorization, AppError> {
406        if let Some(authenticator) = &self.authenticator {
407            if let Some(token) = token {
408                let authorization = authenticator
409                    .verify_doc_token(token, doc, current_time_epoch_millis())
410                    .map_err(|e| (StatusCode::UNAUTHORIZED, e))?;
411                Ok(authorization)
412            } else {
413                Err((StatusCode::UNAUTHORIZED, anyhow!("No token provided.")))?
414            }
415        } else {
416            Ok(Authorization::Full)
417        }
418    }
419
420    fn get_single_doc_id(&self) -> Result<String, AppError> {
421        self.docs
422            .iter()
423            .next()
424            .map(|entry| entry.key().clone())
425            .ok_or_else(|| AppError(StatusCode::NOT_FOUND, anyhow!("No document found")))
426    }
427}
428
429#[derive(Deserialize)]
430struct HandlerParams {
431    token: Option<String>,
432}
433
434async fn get_doc_as_update(
435    State(server_state): State<Arc<Server>>,
436    Path(doc_id): Path<String>,
437    auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
438) -> Result<Response, AppError> {
439    // All authorization types allow reading the document.
440    let token = get_token_from_header(auth_header);
441    let _ = server_state.verify_doc_token(token.as_deref(), &doc_id)?;
442
443    let dwskv = server_state
444        .get_or_create_doc(&doc_id)
445        .await
446        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))?;
447
448    let update = dwskv.as_update();
449    tracing::debug!("update: {:?}", update);
450    Ok(update.into_response())
451}
452
453async fn get_doc_as_update_deprecated(
454    Path(doc_id): Path<String>,
455    State(server_state): State<Arc<Server>>,
456    auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
457) -> Result<Response, AppError> {
458    tracing::warn!("/doc/:doc_id/as-update is deprecated; call /doc/:doc_id/auth instead and then call as-update on the returned base URL.");
459    get_doc_as_update(State(server_state), Path(doc_id), auth_header).await
460}
461
462async fn update_doc_deprecated(
463    Path(doc_id): Path<String>,
464    State(server_state): State<Arc<Server>>,
465    auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
466    body: Bytes,
467) -> Result<Response, AppError> {
468    tracing::warn!("/doc/:doc_id/update is deprecated; call /doc/:doc_id/auth instead and then call update on the returned base URL.");
469    update_doc(Path(doc_id), State(server_state), auth_header, body).await
470}
471
472async fn get_doc_as_update_single(
473    State(server_state): State<Arc<Server>>,
474    auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
475) -> Result<Response, AppError> {
476    let doc_id = server_state.get_single_doc_id()?;
477    get_doc_as_update(State(server_state), Path(doc_id), auth_header).await
478}
479
480async fn update_doc(
481    Path(doc_id): Path<String>,
482    State(server_state): State<Arc<Server>>,
483    auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
484    body: Bytes,
485) -> Result<Response, AppError> {
486    let token = get_token_from_header(auth_header);
487    let authorization = server_state.verify_doc_token(token.as_deref(), &doc_id)?;
488    update_doc_inner(doc_id, server_state, authorization, body).await
489}
490
491async fn update_doc_inner(
492    doc_id: String,
493    server_state: Arc<Server>,
494    authorization: Authorization,
495    body: Bytes,
496) -> Result<Response, AppError> {
497    if !matches!(authorization, Authorization::Full) {
498        return Err(AppError(StatusCode::FORBIDDEN, anyhow!("Unauthorized.")));
499    }
500
501    let dwskv = server_state
502        .get_or_create_doc(&doc_id)
503        .await
504        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))?;
505
506    if let Err(err) = dwskv.apply_update(&body) {
507        tracing::error!(?err, "Failed to apply update");
508        return Err(AppError(StatusCode::INTERNAL_SERVER_ERROR, err));
509    }
510
511    Ok(StatusCode::OK.into_response())
512}
513
514async fn update_doc_single(
515    State(server_state): State<Arc<Server>>,
516    headers: HeaderMap,
517    body: Bytes,
518) -> Result<Response, AppError> {
519    let doc_id = server_state.get_single_doc_id()?;
520    // the doc server is meant to be run in Plane, so we expect verified plane
521    // headers to be used for authorization.
522    let authorization = get_authorization_from_plane_header(headers)?;
523    update_doc_inner(doc_id, server_state, authorization, body).await
524}
525
526async fn handle_socket_upgrade(
527    ws: WebSocketUpgrade,
528    Path(doc_id): Path<String>,
529    authorization: Authorization,
530    State(server_state): State<Arc<Server>>,
531) -> Result<Response, AppError> {
532    if !matches!(authorization, Authorization::Full) && !server_state.docs.contains_key(&doc_id) {
533        return Err(AppError(
534            StatusCode::NOT_FOUND,
535            anyhow!("Doc {} not found", doc_id),
536        ));
537    }
538
539    let dwskv = server_state
540        .get_or_create_doc(&doc_id)
541        .await
542        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))?;
543    let awareness = dwskv.awareness();
544    let cancellation_token = server_state.cancellation_token.clone();
545
546    Ok(ws.on_upgrade(move |socket| {
547        handle_socket(socket, awareness, authorization, cancellation_token)
548    }))
549}
550
551async fn handle_socket_upgrade_deprecated(
552    ws: WebSocketUpgrade,
553    Path(doc_id): Path<String>,
554    Query(params): Query<HandlerParams>,
555    State(server_state): State<Arc<Server>>,
556) -> Result<Response, AppError> {
557    tracing::warn!(
558        "/doc/ws/:doc_id is deprecated; call /doc/:doc_id/auth instead and use the returned URL."
559    );
560    let authorization = server_state.verify_doc_token(params.token.as_deref(), &doc_id)?;
561    handle_socket_upgrade(ws, Path(doc_id), authorization, State(server_state)).await
562}
563
564async fn handle_socket_upgrade_full_path(
565    ws: WebSocketUpgrade,
566    Path((doc_id, doc_id2)): Path<(String, String)>,
567    Query(params): Query<HandlerParams>,
568    State(server_state): State<Arc<Server>>,
569) -> Result<Response, AppError> {
570    if doc_id != doc_id2 {
571        return Err(AppError(
572            StatusCode::BAD_REQUEST,
573            anyhow!("For Yjs compatibility, the doc_id appears twice in the URL. It must be the same in both places, but we got {} and {}.", doc_id, doc_id2),
574        ));
575    }
576    let authorization = server_state.verify_doc_token(params.token.as_deref(), &doc_id)?;
577    handle_socket_upgrade(ws, Path(doc_id), authorization, State(server_state)).await
578}
579
580async fn handle_socket_upgrade_single(
581    ws: WebSocketUpgrade,
582    Path(doc_id): Path<String>,
583    headers: HeaderMap,
584    State(server_state): State<Arc<Server>>,
585) -> Result<Response, AppError> {
586    let single_doc_id = server_state.get_single_doc_id()?;
587    if doc_id != single_doc_id {
588        return Err(AppError(
589            StatusCode::NOT_FOUND,
590            anyhow!("Document not found"),
591        ));
592    }
593
594    // the doc server is meant to be run in Plane, so we expect verified plane
595    // headers to be used for authorization.
596    let authorization = get_authorization_from_plane_header(headers)?;
597    handle_socket_upgrade(ws, Path(single_doc_id), authorization, State(server_state)).await
598}
599
600async fn handle_socket(
601    socket: WebSocket,
602    awareness: Arc<RwLock<Awareness>>,
603    authorization: Authorization,
604    cancellation_token: CancellationToken,
605) {
606    let (mut sink, mut stream) = socket.split();
607    let (send, mut recv) = channel(1024);
608
609    let last_pong = Arc::new(RwLock::new(tokio::time::Instant::now()));
610    let last_pong_clone = last_pong.clone();
611
612    tokio::spawn(async move {
613        let mut ticker = tokio::time::interval(PING_EVERY);
614        ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
615
616        loop {
617            tokio::select! {
618                msg = recv.recv() => {
619                    let Some(msg) = msg else {
620                        break;
621                    };
622                    let _ = sink.send(Message::Binary(msg)).await;
623                }
624                _ = ticker.tick() => {
625                    if last_pong_clone.read().expect("Failed to get read lock on last_pong").elapsed() > PONG_TIMEOUT {
626                        tracing::info!("Pong timeout, closing connection");
627                        break;
628                    }
629                    let _ = sink.send(Message::Ping(vec![])).await;
630                }
631            }
632        }
633    });
634
635    let connection = DocConnection::new(awareness, authorization, move |bytes| {
636        if let Err(e) = send.try_send(bytes.to_vec()) {
637            tracing::warn!(?e, "Error sending message");
638        }
639    });
640
641    loop {
642        tokio::select! {
643            msg = stream.next() => {
644                let Some(msg) = msg else {
645                    break;
646                };
647                let msg = match msg {
648                    Ok(Message::Binary(bytes)) => bytes,
649                    Ok(Message::Close(_)) => break,
650                    Ok(Message::Pong(_)) => {
651                        *last_pong.write().expect("Failed to get write lock on last_pong") = tokio::time::Instant::now();
652                        continue;
653                    }
654                    Err(_e) => {
655                        // The stream will complain about things like
656                        // connections being lost without handshake.
657                        continue;
658                    }
659                    msg => {
660                        tracing::warn!(?msg, "Received non-binary message");
661                        continue;
662                    }
663                };
664
665                if let Err(e) = connection.send(&msg).await {
666                    tracing::warn!(?e, "Error handling message");
667                }
668            }
669            _ = cancellation_token.cancelled() => {
670                tracing::debug!("Closing doc connection due to server cancel...");
671                break;
672            }
673        }
674    }
675}
676
677async fn check_store(
678    auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
679    State(server_state): State<Arc<Server>>,
680) -> Result<Json<Value>, AppError> {
681    server_state.check_auth(auth_header)?;
682
683    if server_state.store.is_none() {
684        return Ok(Json(json!({"ok": false, "error": "No store set."})));
685    };
686
687    // The check_store endpoint for the native server is kind of moot, since
688    // the server will not start if store is not ok.
689    Ok(Json(json!({"ok": true})))
690}
691
692async fn check_store_deprecated(
693    auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
694    State(server_state): State<Arc<Server>>,
695) -> Result<Json<Value>, AppError> {
696    tracing::warn!(
697        "GET check_store is deprecated, use POST check_store with an empty body instead."
698    );
699    check_store(auth_header, State(server_state)).await
700}
701
702/// Always returns a 200 OK response, as long as we are listening.
703async fn ready() -> Result<Json<Value>, AppError> {
704    Ok(Json(json!({"ok": true})))
705}
706
707async fn new_doc(
708    auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
709    State(server_state): State<Arc<Server>>,
710    Json(body): Json<DocCreationRequest>,
711) -> Result<Json<NewDocResponse>, AppError> {
712    server_state.check_auth(auth_header)?;
713
714    let doc_id = if let Some(doc_id) = body.doc_id {
715        if !validate_doc_name(doc_id.as_str()) {
716            Err((StatusCode::BAD_REQUEST, anyhow!("Invalid document name")))?
717        }
718
719        server_state
720            .get_or_create_doc(doc_id.as_str())
721            .await
722            .map_err(|e| {
723                tracing::error!(?e, "Failed to create doc");
724                (StatusCode::INTERNAL_SERVER_ERROR, e)
725            })?;
726
727        doc_id
728    } else {
729        server_state.create_doc().await.map_err(|d| {
730            tracing::error!(?d, "Failed to create doc");
731            (StatusCode::INTERNAL_SERVER_ERROR, d)
732        })?
733    };
734
735    Ok(Json(NewDocResponse { doc_id }))
736}
737
738async fn auth_doc(
739    auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
740    TypedHeader(host): TypedHeader<headers::Host>,
741    State(server_state): State<Arc<Server>>,
742    Path(doc_id): Path<String>,
743    body: Option<Json<AuthDocRequest>>,
744) -> Result<Json<ClientToken>, AppError> {
745    server_state.check_auth(auth_header)?;
746
747    let Json(AuthDocRequest {
748        authorization,
749        valid_for_seconds,
750        ..
751    }) = body.unwrap_or_default();
752
753    if !server_state.doc_exists(&doc_id).await {
754        Err((StatusCode::NOT_FOUND, anyhow!("Doc {} not found", doc_id)))?;
755    }
756
757    let valid_for_seconds = valid_for_seconds.unwrap_or(DEFAULT_EXPIRATION_SECONDS);
758    let expiration_time =
759        ExpirationTimeEpochMillis(current_time_epoch_millis() + valid_for_seconds * 1000);
760
761    let token = if let Some(auth) = &server_state.authenticator {
762        let token = auth.gen_doc_token(&doc_id, authorization, expiration_time);
763        Some(token)
764    } else {
765        None
766    };
767
768    let url = if let Some(url_prefix) = &server_state.url_prefix {
769        let mut url = url_prefix.clone();
770        let scheme = if url.scheme() == "https" { "wss" } else { "ws" };
771        url.set_scheme(scheme).unwrap();
772        url = url.join(&format!("/d/{doc_id}/ws")).unwrap();
773        url.to_string()
774    } else {
775        format!("ws://{host}/d/{doc_id}/ws")
776    };
777
778    let base_url = if let Some(url_prefix) = &server_state.url_prefix {
779        let mut url_prefix = url_prefix.to_string();
780        if !url_prefix.ends_with('/') {
781            url_prefix = format!("{url_prefix}/");
782        }
783
784        format!("{url_prefix}d/{doc_id}")
785    } else {
786        format!("http://{host}/d/{doc_id}")
787    };
788
789    Ok(Json(ClientToken {
790        url,
791        base_url: Some(base_url),
792        doc_id,
793        token,
794        authorization,
795    }))
796}
797
798fn get_token_from_header(
799    auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>,
800) -> Option<String> {
801    if let Some(TypedHeader(headers::Authorization(bearer))) = auth_header {
802        Some(bearer.token().to_string())
803    } else {
804        None
805    }
806}
807
808#[derive(Deserialize)]
809struct PlaneVerifiedUserData {
810    authorization: Authorization,
811}
812
813fn get_authorization_from_plane_header(headers: HeaderMap) -> Result<Authorization, AppError> {
814    if let Some(token) = headers.get(HeaderName::from_static(PLANE_VERIFIED_USER_DATA_HEADER)) {
815        let token_str = token.to_str().map_err(|e| (StatusCode::BAD_REQUEST, e))?;
816        let user_data: PlaneVerifiedUserData =
817            serde_json::from_str(token_str).map_err(|e| (StatusCode::BAD_REQUEST, e))?;
818        Ok(user_data.authorization)
819    } else {
820        Err((StatusCode::UNAUTHORIZED, anyhow!("No token provided.")))?
821    }
822}
823
824#[cfg(test)]
825mod test {
826    use super::*;
827    use y_sweet_core::api_types::Authorization;
828
829    #[tokio::test]
830    async fn test_auth_doc() {
831        let server_state = Server::new(
832            None,
833            Duration::from_secs(60),
834            None,
835            None,
836            CancellationToken::new(),
837            true,
838            None,
839        )
840        .await
841        .unwrap();
842
843        let doc_id = server_state.create_doc().await.unwrap();
844
845        let token = auth_doc(
846            None,
847            TypedHeader(headers::Host::from(http::uri::Authority::from_static(
848                "localhost",
849            ))),
850            State(Arc::new(server_state)),
851            Path(doc_id.clone()),
852            Some(Json(AuthDocRequest {
853                authorization: Authorization::Full,
854                user_id: None,
855                valid_for_seconds: None,
856            })),
857        )
858        .await
859        .unwrap();
860
861        let expected_url = format!("ws://localhost/d/{doc_id}/ws");
862        assert_eq!(token.url, expected_url);
863        assert_eq!(token.doc_id, doc_id);
864        assert!(token.token.is_none());
865    }
866
867    #[tokio::test]
868    async fn test_auth_doc_with_prefix() {
869        let prefix: Url = "https://foo.bar".parse().unwrap();
870        let server_state = Server::new(
871            None,
872            Duration::from_secs(60),
873            None,
874            Some(prefix),
875            CancellationToken::new(),
876            true,
877            None,
878        )
879        .await
880        .unwrap();
881
882        let doc_id = server_state.create_doc().await.unwrap();
883
884        let token = auth_doc(
885            None,
886            TypedHeader(headers::Host::from(http::uri::Authority::from_static(
887                "localhost",
888            ))),
889            State(Arc::new(server_state)),
890            Path(doc_id.clone()),
891            None,
892        )
893        .await
894        .unwrap();
895
896        let expected_url = format!("wss://foo.bar/d/{doc_id}/ws");
897        assert_eq!(token.url, expected_url);
898        assert_eq!(token.doc_id, doc_id);
899        assert!(token.token.is_none());
900    }
901}