spacetimedb_client_api/routes/
identity.rs1use std::time::Duration;
2
3use axum::extract::{Path, State};
4use axum::response::IntoResponse;
5use http::header::CONTENT_TYPE;
6use http::StatusCode;
7use serde::{Deserialize, Serialize};
8
9use spacetimedb_lib::de::serde::DeserializeWrapper;
10use spacetimedb_lib::Identity;
11
12use crate::auth::{JwtAuthProvider, SpacetimeAuth, SpacetimeAuthRequired};
13use crate::{log_and_500, ControlStateDelegate, NodeDelegate};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct CreateIdentityResponse {
17 identity: Identity,
18 token: String,
19}
20
21pub async fn create_identity<S: ControlStateDelegate + NodeDelegate>(
22 State(ctx): State<S>,
23) -> axum::response::Result<impl IntoResponse> {
24 let auth = SpacetimeAuth::alloc(&ctx).await?;
25
26 let identity_response = CreateIdentityResponse {
27 identity: auth.identity,
28 token: auth.creds.token().to_owned(),
29 };
30 Ok(axum::Json(identity_response))
31}
32
33#[derive(derive_more::Into, Clone, Debug, Copy)]
45pub struct IdentityForUrl(Identity);
46
47impl From<Identity> for IdentityForUrl {
48 fn from(i: Identity) -> Self {
49 IdentityForUrl(i)
50 }
51}
52
53impl IdentityForUrl {
54 pub fn into_inner(&self) -> Identity {
55 self.0
56 }
57}
58
59impl<'de> serde::Deserialize<'de> for IdentityForUrl {
60 fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
61 <_>::deserialize(de).map(|DeserializeWrapper(b)| IdentityForUrl(Identity::from_be_byte_array(b)))
62 }
63}
64
65#[derive(Deserialize)]
66pub struct GetDatabasesParams {
67 identity: IdentityForUrl,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct GetDatabasesResponse {
72 identities: Vec<Identity>,
73}
74
75pub async fn get_databases<S: ControlStateDelegate>(
76 State(ctx): State<S>,
77 Path(GetDatabasesParams { identity }): Path<GetDatabasesParams>,
78) -> axum::response::Result<impl IntoResponse> {
79 let identity = identity.into();
80 let all_dbs = ctx.get_databases().map_err(|e| {
82 log::error!("Failure when retrieving databases for search: {e}");
83 StatusCode::INTERNAL_SERVER_ERROR
84 })?;
85 let identities = all_dbs
86 .iter()
87 .filter(|db| db.owner_identity == identity)
88 .map(|db| db.database_identity)
89 .collect();
90 Ok(axum::Json(GetDatabasesResponse { identities }))
91}
92
93#[derive(Debug, Serialize)]
94pub struct WebsocketTokenResponse {
95 pub token: String,
96}
97
98pub async fn create_websocket_token<S: NodeDelegate>(
102 State(ctx): State<S>,
103 SpacetimeAuthRequired(auth): SpacetimeAuthRequired,
104) -> axum::response::Result<impl IntoResponse> {
105 let expiry = Duration::from_secs(60);
106 let token = auth
107 .re_sign_with_expiry(ctx.jwt_auth_provider(), expiry)
108 .map_err(log_and_500)?;
109 Ok(axum::Json(WebsocketTokenResponse { token }))
111}
112
113#[derive(Deserialize)]
114pub struct ValidateTokenParams {
115 identity: IdentityForUrl,
116}
117
118pub async fn validate_token(
119 Path(ValidateTokenParams { identity }): Path<ValidateTokenParams>,
120 SpacetimeAuthRequired(auth): SpacetimeAuthRequired,
121) -> axum::response::Result<impl IntoResponse> {
122 let identity = Identity::from(identity);
123
124 if auth.identity != identity {
125 return Err(StatusCode::BAD_REQUEST.into());
126 }
127
128 Ok(StatusCode::NO_CONTENT)
129}
130
131pub async fn get_public_key<S: NodeDelegate>(State(ctx): State<S>) -> axum::response::Result<impl IntoResponse> {
132 Ok((
133 [(CONTENT_TYPE, "application/pem-certificate-chain")],
134 ctx.jwt_auth_provider().public_key_bytes().to_owned(),
135 ))
136}
137
138pub fn router<S>() -> axum::Router<S>
139where
140 S: NodeDelegate + ControlStateDelegate + Clone + 'static,
141{
142 use axum::routing::{get, post};
143 axum::Router::new()
144 .route("/", post(create_identity::<S>))
145 .route("/public-key", get(get_public_key::<S>))
146 .route("/websocket-token", post(create_websocket_token::<S>))
147 .route("/:identity/verify", get(validate_token))
148 .route("/:identity/databases", get(get_databases::<S>))
149}