Skip to content

Commit e584ecb

Browse files
committed
Unverified token
1 parent ac05a93 commit e584ecb

File tree

2 files changed

+96
-16
lines changed

2 files changed

+96
-16
lines changed

livekit-api/src/access_token.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,28 @@ pub struct Claims {
151151
pub room_config: Option<livekit_protocol::RoomConfiguration>,
152152
}
153153

154+
impl Claims {
155+
pub fn with_unverified(token: &str) -> Result<Self, AccessTokenError> {
156+
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
157+
validation.validate_exp = true;
158+
#[cfg(test)]
159+
{
160+
validation.validate_exp = false;
161+
}
162+
validation.validate_nbf = true;
163+
validation.set_required_spec_claims::<String>(&[]);
164+
validation.insecure_disable_signature_validation();
165+
166+
let token = jsonwebtoken::decode::<Claims>(
167+
token,
168+
&DecodingKey::from_secret(&[]),
169+
&validation,
170+
)?;
171+
172+
Ok(token.claims)
173+
}
174+
}
175+
154176
#[derive(Clone)]
155177
pub struct AccessToken {
156178
api_key: String,
@@ -320,7 +342,7 @@ impl TokenVerifier {
320342
mod tests {
321343
use std::time::Duration;
322344

323-
use super::{AccessToken, TokenVerifier, VideoGrants};
345+
use super::{AccessToken, Claims, TokenVerifier, VideoGrants};
324346

325347
const TEST_API_KEY: &str = "myapikey";
326348
const TEST_API_SECRET: &str = "thiskeyistotallyunsafe";
@@ -383,4 +405,44 @@ mod tests {
383405
claims
384406
);
385407
}
408+
409+
#[test]
410+
fn test_unverified_token() {
411+
let claims = Claims::with_unverified(TEST_TOKEN).expect("Failed to parse token");
412+
413+
assert_eq!(claims.sub, "identity");
414+
assert_eq!(claims.name, "name");
415+
assert_eq!(claims.iss, TEST_API_KEY);
416+
assert_eq!(
417+
claims.room_config,
418+
Some(livekit_protocol::RoomConfiguration {
419+
agents: vec![livekit_protocol::RoomAgentDispatch {
420+
agent_name: "test-agent".to_string(),
421+
metadata: "test-metadata".to_string(),
422+
}],
423+
..Default::default()
424+
})
425+
);
426+
427+
let token = AccessToken::with_api_key(TEST_API_KEY, TEST_API_SECRET)
428+
.with_ttl(Duration::from_secs(60))
429+
.with_identity("test")
430+
.with_name("test")
431+
.with_grants(VideoGrants { room_join: true, room: "test-room".to_string(), ..Default::default() })
432+
.to_jwt()
433+
.unwrap();
434+
435+
let claims = Claims::with_unverified(&token).expect("Failed to parse fresh token");
436+
assert_eq!(claims.sub, "test");
437+
assert_eq!(claims.name, "test");
438+
assert_eq!(claims.video.room, "test-room");
439+
assert!(claims.video.room_join);
440+
441+
let parts: Vec<&str> = token.split('.').collect();
442+
let malformed_token = format!("{}.{}.wrongsignature", parts[0], parts[1]);
443+
444+
let claims = Claims::with_unverified(&malformed_token).expect("Failed to parse token with wrong signature");
445+
assert_eq!(claims.sub, "test");
446+
assert_eq!(claims.name, "test");
447+
}
386448
}

livekit-uniffi/src/access_token.rs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// limitations under the License.
1414

1515
use livekit_api::access_token::{
16-
AccessToken, AccessTokenError, SIPGrants, TokenVerifier, VideoGrants,
16+
self, AccessToken, AccessTokenError, SIPGrants, TokenVerifier, VideoGrants,
1717
};
1818
use std::{collections::HashMap, time::Duration};
1919

@@ -79,6 +79,25 @@ pub struct Claims {
7979
pub room_name: String,
8080
}
8181

82+
impl From<livekit_api::access_token::Claims> for Claims {
83+
fn from(claims: livekit_api::access_token::Claims) -> Self {
84+
let room_name = claims.room_config.map_or(String::default(), |config| config.name);
85+
Self {
86+
exp: claims.exp as u64,
87+
iss: claims.iss,
88+
nbf: claims.nbf as u64,
89+
sub: claims.sub,
90+
name: claims.name,
91+
video: claims.video,
92+
sip: claims.sip,
93+
sha256: claims.sha256,
94+
metadata: claims.metadata,
95+
attributes: claims.attributes,
96+
room_name,
97+
}
98+
}
99+
}
100+
82101
/// API credentials for access token generation and verification.
83102
#[derive(uniffi::Record)]
84103
pub struct ApiCredentials {
@@ -179,18 +198,17 @@ pub fn verify_token(
179198
None => TokenVerifier::new()?,
180199
};
181200
let claims = verifier.verify(token)?;
182-
let room_name = claims.room_config.map_or(String::default(), |config| config.name);
183-
Ok(Claims {
184-
exp: claims.exp as u64,
185-
iss: claims.iss,
186-
nbf: claims.nbf as u64,
187-
sub: claims.sub,
188-
name: claims.name,
189-
video: claims.video,
190-
sip: claims.sip,
191-
sha256: claims.sha256,
192-
metadata: claims.metadata,
193-
attributes: claims.attributes,
194-
room_name,
195-
})
201+
Ok(claims.into())
202+
}
203+
204+
/// Parses an access token without verifying its signature.
205+
///
206+
/// This is useful when you want to inspect token contents without having the secret.
207+
/// The token's expiration (exp) and not-before (nbf) times are still validated.
208+
/// WARNING: Do not use this for authentication - the signature is not verified!
209+
///
210+
#[uniffi::export]
211+
pub fn unverified_token(token: &str) -> Result<Claims, AccessTokenError> {
212+
let claims = access_token::Claims::with_unverified(token)?;
213+
Ok(claims.into())
196214
}

0 commit comments

Comments
 (0)