tuwunel_api_client/
voip.rs1use std::time::{Duration, SystemTime};
2
3use axum::extract::State;
4use base64::{Engine as _, engine::general_purpose::STANDARD};
5use hmac::{Hmac, Mac};
6use ruma::{SecondsSinceUnixEpoch, api::client::voip::get_turn_server_info};
7use sha1::Sha1;
8use tuwunel_core::{Err, Result};
9
10use crate::{Ruma, utils::sender_is_guest};
11
12type HmacSha1 = Hmac<Sha1>;
13
14pub(crate) async fn turn_server_route(
18 State(services): State<crate::State>,
19 body: Ruma<get_turn_server_info::v3::Request>,
20) -> Result<get_turn_server_info::v3::Response> {
21 let provided = services.config.turn.provider().is_some();
22
23 if !provided && services.server.config.turn.uris.is_empty() {
26 return Err!(Request(NotFound("Not Found")));
27 }
28
29 let user = body.sender_user();
30
31 if !services.config.turn.allow_guests && sender_is_guest(&services, &body).await {
32 return Err!(Request(Forbidden("Guest users are not allowed to get TURN credentials")));
33 }
34
35 if provided {
36 return services.turn.credentials().await;
37 }
38
39 let (username, password) = services
40 .globals
41 .turn_secret()
42 .map(|turn_secret| {
43 let expiry = SecondsSinceUnixEpoch::from_system_time(
44 SystemTime::now()
45 .checked_add(Duration::from_secs(services.config.turn.ttl))
46 .expect("TURN TTL should not get this high"),
47 )
48 .expect("time is valid");
49
50 let username = format!("{}:{}", expiry.get(), user);
51 let mut mac = HmacSha1::new_from_slice(turn_secret.as_bytes())
52 .expect("HMAC can take key of any size");
53
54 mac.update(username.as_bytes());
55 let password = STANDARD.encode(mac.finalize().into_bytes());
56
57 (username, password)
58 })
59 .unwrap_or_else(|| {
60 (services.config.turn.username.clone(), services.config.turn.password.clone())
61 });
62
63 Ok(get_turn_server_info::v3::Response {
64 username,
65 password,
66 uris: services.config.turn.uris.clone(),
67 ttl: Duration::from_secs(services.config.turn.ttl),
68 })
69}