diff --git a/.changeset/extract_livekit_token_crate.md b/.changeset/extract_livekit_token_crate.md new file mode 100644 index 000000000..a85efcdf5 --- /dev/null +++ b/.changeset/extract_livekit_token_crate.md @@ -0,0 +1,15 @@ +--- +livekit: patch +livekit-api: patch +livekit-ffi: patch +livekit-token: patch +livekit-uniffi: patch +--- + +Moves access-token generation and verification into a new `livekit-token` crate. +`livekit_api::access_token::*` continues to resolve to the same types via a +re-export, so no consumer changes are needed. + +Also fixes the `services-tokio` and `services-async` features, which used the +access-token types without declaring the `access-token` feature. Building with +`--no-default-features --features services-tokio` previously failed to compile. diff --git a/Cargo.lock b/Cargo.lock index 0aeee4884..bfa1b206e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3934,14 +3934,13 @@ dependencies = [ "device-info", "flate2", "futures", - "hmac", "http 1.4.2", "isahc", - "jsonwebtoken", "livekit-common", "livekit-net", "livekit-protocol", "livekit-runtime", + "livekit-token", "log", "os_info", "parking_lot", @@ -3953,7 +3952,6 @@ dependencies = [ "serde", "serde_json", "sha2", - "signature", "thiserror 2.0.19", "tokio", "url", @@ -4083,6 +4081,19 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "livekit-token" +version = "0.1.0" +dependencies = [ + "hmac", + "jsonwebtoken", + "livekit-protocol", + "serde", + "sha2", + "signature", + "thiserror 2.0.19", +] + [[package]] name = "livekit-token-source" version = "0.1.1" @@ -4103,9 +4114,9 @@ dependencies = [ "bytes", "camino", "futures-util", - "livekit-api", "livekit-datatrack", "livekit-protocol", + "livekit-token", "log", "once_cell", "prost 0.12.6", diff --git a/Cargo.toml b/Cargo.toml index 0bf4c91f0..7c64dfa64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "livekit-ffi", "livekit-uniffi", "livekit-datatrack", + "livekit-token", "livekit-token-source", "livekit-ffi-node-bindings", "livekit-net", @@ -54,6 +55,7 @@ livekit = { version = "0.8.3", path = "livekit" } livekit-api = { version = "0.6.3", path = "livekit-api" } livekit-ffi = { version = "0.12.75", path = "livekit-ffi" } livekit-datatrack = { version = "0.1.13", path = "livekit-datatrack" } +livekit-token = { version = "0.1.0", path = "livekit-token" } livekit-token-source = { version = "0.1.1", path = "livekit-token-source" } livekit-common = { version = "0.1.1", path = "livekit-common" } livekit-data-stream = { version = "0.1.2", path = "livekit-data-stream" } diff --git a/knope.toml b/knope.toml index 2855e2f69..4034078e6 100644 --- a/knope.toml +++ b/knope.toml @@ -85,6 +85,15 @@ versioned_files = [ changelog = "livekit-net/CHANGELOG.md" scopes = ["livekit-net"] +[packages.livekit-token] +versioned_files = [ + "livekit-token/Cargo.toml", + "Cargo.lock", + { path = "Cargo.toml", dependency = "livekit-token" }, +] +changelog = "livekit-token/CHANGELOG.md" +scopes = ["livekit-token"] + [packages.libwebrtc] versioned_files = [ "libwebrtc/Cargo.toml", diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 00255140a..64323f21b 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -31,9 +31,9 @@ signal-client-tokio = ["signal-client", "livekit-net/native-tokio", "liveki signal-client-async = ["signal-client", "livekit-net/native-async", "livekit-runtime/async"] signal-client-dispatcher = ["signal-client", "livekit-net/native-dispatcher", "livekit-runtime/dispatcher"] -services-tokio = ["dep:reqwest", "dep:tokio", "tokio/time", "dep:livekit-runtime", "livekit-runtime/tokio"] -services-async = ["dep:isahc", "dep:livekit-runtime", "livekit-runtime/async"] -access-token = ["dep:jsonwebtoken", "dep:hmac", "dep:signature"] +services-tokio = ["access-token", "dep:reqwest", "dep:tokio", "tokio/time", "dep:livekit-runtime", "livekit-runtime/tokio"] +services-async = ["access-token", "dep:isahc", "dep:livekit-runtime", "livekit-runtime/async"] +access-token = ["dep:livekit-token"] webhooks = ["access-token", "dep:serde_json", "dep:base64"] # TLS Configuration @@ -71,6 +71,7 @@ __rustls-tls = ["livekit-net?/__rustls-tls", "reqwest?/__ [dependencies] livekit-net = { workspace = true, optional = true } livekit-protocol = { workspace = true } +livekit-token = { workspace = true, optional = true } livekit-common = { workspace = true } thiserror = { workspace = true } serde = { workspace = true, features = ["derive"] } @@ -85,12 +86,6 @@ pbjson-types = "0.6" serde_json = { workspace = true, optional = true } base64 = { version = "0.21", optional = true, features = ["std"] } -# access_token: HS256 only, via the in-crate HMAC CryptoProvider (jwt_provider.rs). -# Dropping jsonwebtoken's rust_crypto bundle avoids linking RSA/EC/EdDSA. -jsonwebtoken = { version = "10", default-features = false, optional = true } -hmac = { version = "0.12", optional = true } -signature = { version = "2", optional = true } - # signal_client livekit-runtime = { workspace = true, optional = true, default-features = false } tokio = { workspace = true, default-features = false, features = ["sync", "macros", "signal", "io-util", "net"], optional = true } diff --git a/livekit-api/src/lib.rs b/livekit-api/src/lib.rs index 2164d48af..d953e5e1e 100644 --- a/livekit-api/src/lib.rs +++ b/livekit-api/src/lib.rs @@ -14,11 +14,12 @@ #![doc = include_str!("../README.md")] +// The token implementation lives in the livekit-token crate. This alias keeps +// the historical `livekit_api::access_token::*` paths working, and remains the +// documented, supported way to reach these types. #[cfg(feature = "access-token")] -pub mod access_token; - -#[cfg(feature = "access-token")] -mod jwt_provider; +#[doc(inline)] +pub use livekit_token as access_token; #[cfg(any(feature = "services-tokio", feature = "services-async"))] pub mod services; @@ -37,10 +38,3 @@ mod region; #[cfg(feature = "webhooks")] pub mod webhooks; - -#[allow(dead_code)] -pub(crate) fn get_env_keys() -> Result<(String, String), std::env::VarError> { - let api_key = std::env::var("LIVEKIT_API_KEY")?; - let api_secret = std::env::var("LIVEKIT_API_SECRET")?; - Ok((api_key, api_secret)) -} diff --git a/livekit-api/src/services/agent_dispatch.rs b/livekit-api/src/services/agent_dispatch.rs index 41f69a926..419aa72cd 100644 --- a/livekit-api/src/services/agent_dispatch.rs +++ b/livekit-api/src/services/agent_dispatch.rs @@ -13,12 +13,9 @@ // limitations under the License. use super::{twirp_client::TwirpClient, ServiceBase, ServiceResult, LIVEKIT_PACKAGE}; -use crate::{ - access_token::{AccessTokenError, VideoGrants}, - get_env_keys, -}; use http::header::HeaderMap; use livekit_protocol as proto; +use livekit_token::{get_env_keys, AccessTokenError, VideoGrants}; const SVC: &str = "AgentDispatchService"; diff --git a/livekit-api/src/services/api_test.rs b/livekit-api/src/services/api_test.rs index 08f5cb1cd..9bd8f53b6 100644 --- a/livekit-api/src/services/api_test.rs +++ b/livekit-api/src/services/api_test.rs @@ -35,7 +35,7 @@ use super::failover::FailoverConfig; use super::sip::CreateSIPParticipantOptions; use super::twirp_client::{ServerError, ServerResult, TwirpClient}; use super::{LiveKitApi, ServiceError, SipCallError, LIVEKIT_PACKAGE}; -use crate::access_token::{AccessToken, VideoGrants}; +use livekit_token::{AccessToken, VideoGrants}; fn base_url() -> String { std::env::var("LK_TEST_SERVER_URL").unwrap_or_else(|_| "http://127.0.0.1:9999".to_owned()) diff --git a/livekit-api/src/services/connector.rs b/livekit-api/src/services/connector.rs index 9b97fa551..40df206fe 100644 --- a/livekit-api/src/services/connector.rs +++ b/livekit-api/src/services/connector.rs @@ -18,7 +18,8 @@ use std::time::Duration; use super::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE}; use crate::services::dial_timeout::DEFAULT_RINGING_TIMEOUT; -use crate::{access_token::VideoGrants, get_env_keys, services::twirp_client::TwirpClient}; +use crate::services::twirp_client::TwirpClient; +use livekit_token::{get_env_keys, VideoGrants}; const SVC: &str = "Connector"; diff --git a/livekit-api/src/services/egress.rs b/livekit-api/src/services/egress.rs index 1969c97ff..da0c106fa 100644 --- a/livekit-api/src/services/egress.rs +++ b/livekit-api/src/services/egress.rs @@ -15,7 +15,8 @@ use livekit_protocol as proto; use super::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE}; -use crate::{access_token::VideoGrants, get_env_keys, services::twirp_client::TwirpClient}; +use crate::services::twirp_client::TwirpClient; +use livekit_token::{get_env_keys, VideoGrants}; #[derive(Clone, Copy, Debug, Default)] pub enum AudioMixing { diff --git a/livekit-api/src/services/ingress.rs b/livekit-api/src/services/ingress.rs index a8e6a0e46..d3a38787e 100644 --- a/livekit-api/src/services/ingress.rs +++ b/livekit-api/src/services/ingress.rs @@ -15,7 +15,8 @@ use livekit_protocol as proto; use super::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE}; -use crate::{access_token::VideoGrants, get_env_keys, services::twirp_client::TwirpClient}; +use crate::services::twirp_client::TwirpClient; +use livekit_token::{get_env_keys, VideoGrants}; #[derive(Default, Clone, Debug)] pub struct CreateIngressOptions { diff --git a/livekit-api/src/services/livekit_api.rs b/livekit-api/src/services/livekit_api.rs index 65c88cb4c..5b55520dc 100644 --- a/livekit-api/src/services/livekit_api.rs +++ b/livekit-api/src/services/livekit_api.rs @@ -21,8 +21,8 @@ use super::ingress::IngressClient; use super::room::RoomClient; use super::sip::SIPClient; use super::{ServiceBase, ServiceResult}; -use crate::get_env_keys; use crate::http_client; +use livekit_token::get_env_keys; /// A single entry point to every LiveKit server API, exposing each service /// through an accessor (`room()`, `egress()`, `ingress()`, `sip()`, diff --git a/livekit-api/src/services/mod.rs b/livekit-api/src/services/mod.rs index fa470435a..69e30aa08 100644 --- a/livekit-api/src/services/mod.rs +++ b/livekit-api/src/services/mod.rs @@ -18,7 +18,7 @@ use std::fmt::{Debug, Display}; use http::header::{HeaderMap, HeaderValue, AUTHORIZATION}; use thiserror::Error; -use crate::access_token::{AccessToken, AccessTokenError, SIPGrants, VideoGrants}; +use livekit_token::{AccessToken, AccessTokenError, SIPGrants, VideoGrants}; pub use livekit_api::LiveKitApi; pub use twirp_client::{ diff --git a/livekit-api/src/services/room.rs b/livekit-api/src/services/room.rs index 063131099..f1d9c8da0 100644 --- a/livekit-api/src/services/room.rs +++ b/livekit-api/src/services/room.rs @@ -16,7 +16,8 @@ use livekit_protocol as proto; use std::collections::HashMap; use super::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE}; -use crate::{access_token::VideoGrants, get_env_keys, services::twirp_client::TwirpClient}; +use crate::services::twirp_client::TwirpClient; +use livekit_token::{get_env_keys, VideoGrants}; use rand::Rng; const SVC: &str = "RoomService"; diff --git a/livekit-api/src/services/sip.rs b/livekit-api/src/services/sip.rs index 5ccddb038..31be4f308 100644 --- a/livekit-api/src/services/sip.rs +++ b/livekit-api/src/services/sip.rs @@ -16,11 +16,10 @@ use livekit_protocol as proto; use std::collections::HashMap; use std::time::Duration; -use crate::access_token::{SIPGrants, VideoGrants}; -use crate::get_env_keys; use crate::services::dial_timeout::{dial_timeout, DEFAULT_RINGING_TIMEOUT}; use crate::services::twirp_client::TwirpClient; use crate::services::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE}; +use livekit_token::{get_env_keys, SIPGrants, VideoGrants}; use pbjson_types::Duration as ProtoDuration; const SVC: &str = "SIP"; diff --git a/livekit-api/src/webhooks.rs b/livekit-api/src/webhooks.rs index 407b4bfe9..3091d7e1e 100644 --- a/livekit-api/src/webhooks.rs +++ b/livekit-api/src/webhooks.rs @@ -17,7 +17,7 @@ use livekit_protocol as proto; use sha2::{Digest, Sha256}; use thiserror::Error; -use crate::access_token::{AccessTokenError, TokenVerifier}; +use livekit_token::{AccessTokenError, TokenVerifier}; #[derive(Debug, Error)] pub enum WebhookError { diff --git a/livekit-token/Cargo.toml b/livekit-token/Cargo.toml new file mode 100644 index 000000000..f3e130864 --- /dev/null +++ b/livekit-token/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "livekit-token" +version = "0.1.0" +license.workspace = true +description = "Core logic for generating LiveKit tokens" +edition.workspace = true +repository.workspace = true +readme = "README.md" + +[dependencies] +livekit-protocol = { workspace = true } +thiserror = { workspace = true } +serde = { workspace = true, features = ["derive"] } +sha2 = "0.10" + +# access_token: HS256 only, via the in-crate HMAC CryptoProvider (jwt_provider.rs). +# Dropping jsonwebtoken's rust_crypto bundle avoids linking RSA/EC/EdDSA. +jsonwebtoken = { version = "10", default-features = false } +hmac = "0.12" +signature = "2" diff --git a/livekit-token/README.md b/livekit-token/README.md new file mode 100644 index 000000000..6e0df02ac --- /dev/null +++ b/livekit-token/README.md @@ -0,0 +1,6 @@ +# LiveKit Token + +**Important**: +This is an internal crate which contains logic to generate new LiveKit tokens. + +To use this logic in an end application, consume it via its reexport in the `livekit-api` crate. diff --git a/livekit-api/src/access_token.rs b/livekit-token/src/access_token.rs similarity index 100% rename from livekit-api/src/access_token.rs rename to livekit-token/src/access_token.rs diff --git a/livekit-api/src/jwt_provider.rs b/livekit-token/src/jwt_provider.rs similarity index 100% rename from livekit-api/src/jwt_provider.rs rename to livekit-token/src/jwt_provider.rs diff --git a/livekit-token/src/lib.rs b/livekit-token/src/lib.rs new file mode 100644 index 000000000..b039dabc3 --- /dev/null +++ b/livekit-token/src/lib.rs @@ -0,0 +1,32 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![doc = include_str!("../README.md")] + +mod access_token; +mod jwt_provider; + +pub use access_token::*; + +/// Reads the API key and secret from the `LIVEKIT_API_KEY` and +/// `LIVEKIT_API_SECRET` environment variables. +/// +/// Used by [`AccessToken::new`] and [`TokenVerifier::new`], and by the server-API +/// service clients in `livekit-api` for their own env-based constructors. +#[doc(hidden)] +pub fn get_env_keys() -> Result<(String, String), std::env::VarError> { + let api_key = std::env::var("LIVEKIT_API_KEY")?; + let api_secret = std::env::var("LIVEKIT_API_SECRET")?; + Ok((api_key, api_secret)) +} diff --git a/livekit-api/src/test_token.txt b/livekit-token/src/test_token.txt similarity index 100% rename from livekit-api/src/test_token.txt rename to livekit-token/src/test_token.txt diff --git a/livekit-uniffi/Cargo.toml b/livekit-uniffi/Cargo.toml index 665f2ca55..3d92a5654 100644 --- a/livekit-uniffi/Cargo.toml +++ b/livekit-uniffi/Cargo.toml @@ -13,7 +13,7 @@ publish = false [dependencies] livekit-protocol = { workspace = true } -livekit-api = { workspace = true, default-features = false, features = ["access-token"] } +livekit-token = { workspace = true } livekit-datatrack = { workspace = true, features = ["uniffi"] } uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns", "tokio"] } log = { workspace = true } diff --git a/livekit-uniffi/src/access_token.rs b/livekit-uniffi/src/access_token.rs index b5b205877..d86ce4f57 100644 --- a/livekit-uniffi/src/access_token.rs +++ b/livekit-uniffi/src/access_token.rs @@ -12,10 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use livekit_api::access_token::{ - self, AccessToken, AccessTokenError, SIPGrants, TokenVerifier, VideoGrants, -}; use livekit_protocol::{self as proto, RoomAgentDispatch}; +use livekit_token::{self, AccessToken, AccessTokenError, SIPGrants, TokenVerifier, VideoGrants}; use std::{collections::HashMap, time::Duration}; /// An error that can occur during token generation or verification. @@ -123,8 +121,8 @@ pub struct Claims { pub room_configuration: Option, } -impl From for Claims { - fn from(claims: livekit_api::access_token::Claims) -> Self { +impl From for Claims { + fn from(claims: livekit_token::Claims) -> Self { Self { exp: claims.exp as u64, iss: claims.iss, @@ -261,6 +259,6 @@ pub fn token_verify( /// #[uniffi::export] pub fn token_claims_from_unverified(token: &str) -> Result { - let claims = access_token::Claims::from_unverified(token)?; + let claims = livekit_token::Claims::from_unverified(token)?; Ok(claims.into()) }