Skip to content

Commit 55f6e7f

Browse files
authored
feat: add support for token-based authentication (#116)
* fix: use "Bearer" instead of "Token" so I can use token-based auth * feat: implement token-based authentication support for Deepgram API * fix: add .DS_Store to .gitignore * fix: RedactedString linter errors * refactor: change visibility of RedactedString and AuthMethod enums, simplify header_value method
1 parent b5cdc94 commit 55f6e7f

File tree

4 files changed

+126
-12
lines changed

4 files changed

+126
-12
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/target
22
/Cargo.lock
3-
your_output_file.wav
3+
your_output_file.wav
4+
.DS_Store

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,38 @@ This SDK implements the Deepgram API found at [https://developers.deepgram.com](
1515

1616
Documentation and examples can be found on our [Docs.rs page](https://docs.rs/deepgram/latest/deepgram/).
1717

18-
## Getting an API Key
18+
## Quick Start
19+
20+
Check out the [examples folder](./examples/) for practical code examples showing how to use the SDK.
21+
22+
## Authentication
1923

2024
🔑 To access the Deepgram API you will need a [free Deepgram API Key](https://console.deepgram.com/signup?jump=keys).
2125

26+
There are two ways to authenticate with the Deepgram API:
27+
28+
1. **API Key**: This is the simplest method. You can get a free API key from the
29+
[Deepgram Console](https://console.deepgram.com/signup?jump=keys).
30+
31+
```rust
32+
use deepgram::Deepgram;
33+
34+
let dg = Deepgram::new("YOUR_DEEPGRAM_API_KEY");
35+
```
36+
37+
2. **Temporary Tokens**: If you are building an application where you need to
38+
grant temporary access to the Deepgram API, you can use temporary tokens.
39+
This is useful for client-side applications where you don't want to expose
40+
your API key.
41+
42+
You can create temporary tokens using the Deepgram API. Learn more about
43+
[token-based authentication](https://developers.deepgram.com/guides/fundamentals/token-based-authentication).
44+
45+
```rust
46+
use deepgram::Deepgram;
47+
48+
let dg = Deepgram::with_temp_token("YOUR_TEMPORARY_TOKEN");
49+
```
2250

2351
## Current Status
2452

src/lib.rs

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ impl Transcription<'_> {
8989
}
9090

9191
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
92+
/// A string wrapper that redacts its contents when formatted with `Debug`.
9293
pub(crate) struct RedactedString(pub String);
9394

9495
impl fmt::Debug for RedactedString {
@@ -105,13 +106,35 @@ impl Deref for RedactedString {
105106
}
106107
}
107108

109+
/// Authentication method for Deepgram API requests.
110+
#[derive(Debug, Clone, PartialEq, Eq)]
111+
pub(crate) enum AuthMethod {
112+
/// Use an API key with "Token" prefix (e.g., "Token dg_xxx").
113+
/// This is for permanent API keys created in the Deepgram console.
114+
ApiKey(RedactedString),
115+
116+
/// Use a temporary token with "Bearer" prefix (e.g., "Bearer dg_xxx").
117+
/// This is for temporary tokens obtained via token-based authentication.
118+
TempToken(RedactedString),
119+
}
120+
121+
impl AuthMethod {
122+
/// Get the authorization header value for this authentication method.
123+
pub(crate) fn header_value(&self) -> String {
124+
match self {
125+
AuthMethod::ApiKey(key) => format!("Token {}", key.0),
126+
AuthMethod::TempToken(token) => format!("Bearer {}", token.0),
127+
}
128+
}
129+
}
130+
108131
/// A client for the Deepgram API.
109132
///
110133
/// Make transcriptions requests using [`Deepgram::transcription`].
111134
#[derive(Debug, Clone)]
112135
pub struct Deepgram {
113136
#[cfg_attr(not(feature = "listen"), allow(unused))]
114-
api_key: Option<RedactedString>,
137+
auth: Option<AuthMethod>,
115138
#[cfg_attr(not(feature = "listen"), allow(unused))]
116139
base_url: Url,
117140
#[cfg_attr(not(feature = "listen"), allow(unused))]
@@ -200,11 +223,20 @@ impl Deepgram {
200223
///
201224
/// Errors under the same conditions as [`reqwest::ClientBuilder::build`].
202225
pub fn new<K: AsRef<str>>(api_key: K) -> Result<Self> {
203-
let api_key = Some(api_key.as_ref().to_owned());
226+
let auth = AuthMethod::ApiKey(RedactedString(api_key.as_ref().to_owned()));
204227
// This cannot panic because we are converting a static value
205228
// that is known-good.
206229
let base_url = DEEPGRAM_BASE_URL.try_into().unwrap();
207-
Self::inner_constructor(base_url, api_key)
230+
Self::inner_constructor(base_url, Some(auth))
231+
}
232+
233+
/// Construct a new Deepgram client with a temporary token.
234+
///
235+
/// This uses the "Bearer" prefix for authentication, suitable for temporary tokens.
236+
pub fn with_temp_token<T: AsRef<str>>(temp_token: T) -> Result<Self> {
237+
let auth = AuthMethod::TempToken(RedactedString(temp_token.as_ref().to_owned()));
238+
let base_url = DEEPGRAM_BASE_URL.try_into().unwrap();
239+
Self::inner_constructor(base_url, Some(auth))
208240
}
209241

210242
/// Construct a new Deepgram client with the specified base URL.
@@ -281,10 +313,23 @@ impl Deepgram {
281313
K: AsRef<str>,
282314
{
283315
let base_url = base_url.try_into().map_err(|_| DeepgramError::InvalidUrl)?;
284-
Self::inner_constructor(base_url, Some(api_key.as_ref().to_owned()))
316+
let auth = AuthMethod::ApiKey(RedactedString(api_key.as_ref().to_owned()));
317+
Self::inner_constructor(base_url, Some(auth))
318+
}
319+
320+
/// Construct a new Deepgram client with the specified base URL and temp token.
321+
pub fn with_base_url_and_temp_token<U, T>(base_url: U, temp_token: T) -> Result<Self>
322+
where
323+
U: TryInto<Url>,
324+
U::Error: std::fmt::Debug,
325+
T: AsRef<str>,
326+
{
327+
let base_url = base_url.try_into().map_err(|_| DeepgramError::InvalidUrl)?;
328+
let auth = AuthMethod::TempToken(RedactedString(temp_token.as_ref().to_owned()));
329+
Self::inner_constructor(base_url, Some(auth))
285330
}
286331

287-
fn inner_constructor(base_url: Url, api_key: Option<String>) -> Result<Self> {
332+
fn inner_constructor(base_url: Url, auth: Option<AuthMethod>) -> Result<Self> {
288333
static USER_AGENT: &str = concat!(
289334
env!("CARGO_PKG_NAME"),
290335
"/",
@@ -297,16 +342,17 @@ impl Deepgram {
297342
}
298343
let authorization_header = {
299344
let mut header = HeaderMap::new();
300-
if let Some(api_key) = &api_key {
301-
if let Ok(value) = HeaderValue::from_str(&format!("Token {}", api_key)) {
345+
if let Some(auth) = &auth {
346+
let header_value = auth.header_value();
347+
if let Ok(value) = HeaderValue::from_str(&header_value) {
302348
header.insert("Authorization", value);
303349
}
304350
}
305351
header
306352
};
307353

308354
Ok(Deepgram {
309-
api_key: api_key.map(RedactedString),
355+
auth,
310356
base_url,
311357
client: reqwest::Client::builder()
312358
.user_agent(USER_AGENT)
@@ -334,3 +380,42 @@ async fn send_and_translate_response<R: DeserializeOwned>(
334380
}),
335381
}
336382
}
383+
384+
#[cfg(test)]
385+
mod tests {
386+
use super::*;
387+
388+
#[test]
389+
fn test_auth_method_header_value() {
390+
let api_key = AuthMethod::ApiKey(RedactedString("test_api_key".to_string()));
391+
assert_eq!(api_key.header_value(), "Token test_api_key".to_string());
392+
393+
let temp_token = AuthMethod::TempToken(RedactedString("test_temp_token".to_string()));
394+
assert_eq!(
395+
temp_token.header_value(),
396+
"Bearer test_temp_token".to_string()
397+
);
398+
}
399+
400+
#[test]
401+
fn test_deepgram_new_with_temp_token() {
402+
let client = Deepgram::with_temp_token("test_temp_token").unwrap();
403+
assert_eq!(
404+
client.auth,
405+
Some(AuthMethod::TempToken(RedactedString(
406+
"test_temp_token".to_string()
407+
)))
408+
);
409+
}
410+
411+
#[test]
412+
fn test_deepgram_new_with_api_key() {
413+
let client = Deepgram::new("test_api_key").unwrap();
414+
assert_eq!(
415+
client.auth,
416+
Some(AuthMethod::ApiKey(RedactedString(
417+
"test_api_key".to_string()
418+
)))
419+
);
420+
}
421+
}

src/listen/websocket.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,8 +666,8 @@ impl WebsocketHandle {
666666
.header("upgrade", "websocket")
667667
.header("sec-websocket-version", "13");
668668

669-
let builder = if let Some(api_key) = builder.deepgram.api_key.as_deref() {
670-
http_builder.header("authorization", format!("Token {}", api_key))
669+
let builder = if let Some(auth) = &builder.deepgram.auth {
670+
http_builder.header("authorization", auth.header_value())
671671
} else {
672672
http_builder
673673
};

0 commit comments

Comments
 (0)