Skip to content

Commit 579d4b2

Browse files
committed
feat: generate scoped searchkey
1 parent 8996804 commit 579d4b2

File tree

5 files changed

+100
-92
lines changed

5 files changed

+100
-92
lines changed

typesense/src/client/keys.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
//!
33
//! An `Keys` instance is created via the `Client::keys()` method.
44
5-
use crate::{Client, Error};
6-
use std::sync::Arc;
7-
use typesense_codegen::{
8-
apis::{configuration, keys_api},
9-
models,
5+
use crate::{
6+
models::{self, ScopedKeyParameters},
7+
Client, Error,
108
};
9+
use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
10+
use hmac::{Hmac, Mac};
11+
use sha2::Sha256;
12+
use std::sync::Arc;
13+
use typesense_codegen::apis::{configuration, keys_api};
1114

1215
/// Provides methods for managing a collection of Typesense API keys.
1316
///
@@ -52,4 +55,25 @@ impl<'a> Keys<'a> {
5255
})
5356
.await
5457
}
58+
59+
/// Generate a scoped search API key that can have embedded search parameters in them.
60+
///
61+
/// More info [here](https://typesense.org/docs/latest/api/api-keys.html#generate-scoped-search-key).
62+
pub fn generate_scoped_search_key(
63+
&self,
64+
key: impl AsRef<str>,
65+
params: &ScopedKeyParameters,
66+
) -> anyhow::Result<String> {
67+
let params = serde_json::to_string(params)?;
68+
69+
let mut mac = Hmac::<Sha256>::new_from_slice(key.as_ref().as_bytes())?;
70+
mac.update(params.as_bytes());
71+
let result = mac.finalize();
72+
let digest = Base64Engine.encode(result.into_bytes());
73+
74+
let key_prefix = &key.as_ref()[0..4];
75+
let raw_scoped_key = format!("{}{}{}", digest, key_prefix, params);
76+
77+
Ok(Base64Engine.encode(raw_scoped_key.as_bytes()))
78+
}
5579
}

typesense/src/keys.rs

Lines changed: 0 additions & 83 deletions
This file was deleted.

typesense/src/models/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
//! # Typesense generic models
2-
pub mod search_result;
2+
mod scoped_key_parameters;
3+
mod search_result;
4+
5+
pub use scoped_key_parameters::*;
36
pub use search_result::*;
7+
48
pub use typesense_codegen::models::{
59
AnalyticsEventCreateResponse, AnalyticsEventCreateSchema, AnalyticsRuleDeleteResponse,
610
AnalyticsRuleParameters, AnalyticsRuleParametersDestination, AnalyticsRuleParametersSource,
@@ -18,7 +22,7 @@ pub use typesense_codegen::models::{
1822
MultiSearchResultItem, MultiSearchSearchesParameter, NlSearchModelBase,
1923
NlSearchModelCreateSchema, NlSearchModelDeleteSchema, NlSearchModelSchema, PresetDeleteSchema,
2024
PresetSchema, PresetUpsertSchema, PresetUpsertSchemaValue, PresetsRetrieveSchema,
21-
SchemaChangeStatus, ScopedKeyParameters, SearchGroupedHit, SearchHighlight, SearchOverride,
25+
SchemaChangeStatus, SearchGroupedHit, SearchHighlight, SearchOverride,
2226
SearchOverrideDeleteResponse, SearchOverrideExclude, SearchOverrideInclude, SearchOverrideRule,
2327
SearchOverrideSchema, SearchOverridesResponse, SearchParameters, SearchResultConversation,
2428
SearchResultHitTextMatchInfo, SearchResultRequestParams, SearchResultRequestParamsVoiceQuery,
@@ -79,7 +83,6 @@ pub use typesense_codegen::models::{
7983
preset_upsert_schema_value,
8084
presets_retrieve_schema,
8185
schema_change_status,
82-
scoped_key_parameters,
8386
search_grouped_hit,
8487
search_highlight,
8588
search_override,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use crate::models::SearchParameters;
2+
use serde::Serialize;
3+
4+
/// Defines the parameters for generating a scoped API key.
5+
///
6+
/// A scoped key is a temporary, client-side key that has a specific set of
7+
/// search restrictions and an optional expiration time embedded within it. It allows
8+
/// you to delegate search permissions securely without exposing your main API key.
9+
#[derive(Debug, Clone, Default, Serialize)]
10+
pub struct ScopedKeyParameters {
11+
/// The search parameters to embed in the key. These parameters will be
12+
/// enforced for all searches made with the generated key.
13+
/// For example, you can use `filter_by` to restrict searches to a subset of documents.
14+
#[serde(flatten, skip_serializing_if = "Option::is_none")]
15+
pub search_params: Option<SearchParameters>,
16+
17+
/// The number of `multi_search` requests that can be performed using this key.
18+
/// This is an optional parameter to further restrict the key's capabilities.
19+
#[serde(skip_serializing_if = "Option::is_none")]
20+
pub limit_multi_searches: Option<i64>,
21+
22+
/// The Unix timestamp (in seconds) after which the generated key will expire.
23+
#[serde(skip_serializing_if = "Option::is_none")]
24+
pub expires_at: Option<i64>,
25+
}

typesense/tests/client/keys_test.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::get_client;
2-
use typesense::models::ApiKeySchema;
2+
use typesense::models::{ApiKeySchema, ScopedKeyParameters, SearchParameters};
33

44
#[tokio::test]
55
async fn test_keys_lifecycle() {
@@ -80,3 +80,42 @@ async fn test_keys_lifecycle() {
8080
"API key should not exist after deletion."
8181
);
8282
}
83+
84+
#[test]
85+
fn test_generate_scoped_search_key_with_example_values() {
86+
// The parent key with `documents:search` permissions.
87+
let search_only_api_key = "RN23GFr1s6jQ9kgSNg2O7fYcAUXU7127";
88+
89+
// The parameters to be embedded in the new scoped key.
90+
let params = ScopedKeyParameters {
91+
search_params: Some(SearchParameters {
92+
filter_by: Some("company_id:124".to_string()),
93+
..Default::default()
94+
}),
95+
expires_at: Some(1906054106),
96+
..Default::default()
97+
};
98+
99+
// The known correct output from the Typesense documentation.
100+
let expected_scoped_key = "OW9DYWZGS1Q1RGdSbmo0S1QrOWxhbk9PL2kxbTU1eXA3bCthdmE5eXJKRT1STjIzeyJmaWx0ZXJfYnkiOiJjb21wYW55X2lkOjEyNCIsImV4cGlyZXNfYXQiOjE5MDYwNTQxMDZ9";
101+
102+
let client = get_client();
103+
104+
let generated_key_result = client
105+
.keys()
106+
.generate_scoped_search_key(search_only_api_key, &params);
107+
108+
// First, ensure the function returned an Ok result.
109+
assert!(
110+
generated_key_result.is_ok(),
111+
"Function returned an error: {:?}",
112+
generated_key_result.err()
113+
);
114+
115+
// Unwrap the result and compare it with the expected output.
116+
let generated_key = generated_key_result.unwrap();
117+
assert_eq!(
118+
generated_key, expected_scoped_key,
119+
"The generated key does not match the expected key."
120+
);
121+
}

0 commit comments

Comments
 (0)