tuwunel_api/client/
voip.rs1use std::time::{Duration, SystemTime};
2
3use axum::extract::State;
4use base64::{Engine as _, engine::general_purpose};
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;
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 if services.server.config.turn_uris.is_empty() {
23 return Err!(Request(NotFound("Not Found")));
24 }
25
26 let user = body.sender_user();
27
28 if !services.config.turn_allow_guests
29 && body.appservice_info.is_none()
30 && services
31 .users
32 .is_deactivated(user)
33 .await
34 .unwrap_or(false)
35 {
36 return Err!(Request(Forbidden("Guest users are not allowed to get TURN credentials")));
37 }
38
39 let turn_secret = &services.globals.turn_secret;
40
41 let (username, password) = if let Some(turn_secret) = turn_secret {
42 let expiry = SecondsSinceUnixEpoch::from_system_time(
43 SystemTime::now()
44 .checked_add(Duration::from_secs(services.config.turn_ttl))
45 .expect("TURN TTL should not get this high"),
46 )
47 .expect("time is valid");
48
49 let username: String = format!("{}:{}", expiry.get(), user);
50 let mut mac = HmacSha1::new_from_slice(turn_secret.as_bytes())
51 .expect("HMAC can take key of any size");
52
53 mac.update(username.as_bytes());
54 let password: String = general_purpose::STANDARD.encode(mac.finalize().into_bytes());
55
56 (username, password)
57 } else {
58 (services.config.turn_username.clone(), services.config.turn_password.clone())
59 };
60
61 Ok(get_turn_server_info::v3::Response {
62 username,
63 password,
64 uris: services.config.turn_uris.clone(),
65 ttl: Duration::from_secs(services.config.turn_ttl),
66 })
67}