Skip to main content

rustauth_plugins/api_key/routes/
list.rs

1use std::cmp::Ordering;
2use std::collections::BTreeSet;
3
4use http::{Method, StatusCode};
5use rustauth_core::db::SortDirection;
6use serde::{Deserialize, Serialize};
7
8use super::{
9    current_identity, endpoint, error, json, query_param, query_usize, SharedConfigurations,
10};
11use crate::api_key::errors;
12use crate::api_key::models::{ApiKeyPublicRecord, ApiKeyRecord};
13use crate::api_key::options::ApiKeyReference;
14use crate::api_key::organization::{ensure_organization_permission, ApiKeyAction};
15use crate::api_key::storage::{ApiKeyStore, ListOptions};
16
17#[derive(Debug, Clone, Deserialize, Serialize, Default)]
18#[serde(rename_all = "camelCase")]
19pub struct ListApiKeysQuery {
20    pub config_id: Option<String>,
21    pub organization_id: Option<String>,
22    pub limit: Option<usize>,
23    pub offset: Option<usize>,
24    pub sort_by: Option<String>,
25    pub sort_direction: Option<String>,
26}
27
28#[derive(Debug, Clone, Serialize)]
29#[serde(rename_all = "camelCase")]
30struct ListApiKeysResponse {
31    api_keys: Vec<ApiKeyPublicRecord>,
32    total: u64,
33    limit: Option<usize>,
34    offset: Option<usize>,
35}
36
37pub fn list_endpoint(
38    configurations: SharedConfigurations,
39) -> rustauth_core::api::AsyncAuthEndpoint {
40    endpoint(
41        "/api-key/list",
42        Method::GET,
43        configurations,
44        |context, request, configurations| async move {
45            let config_id = query_param(&request, "configId");
46            let organization_id = query_param(&request, "organizationId");
47            let Some(identity) = current_identity(&context, &request).await? else {
48                return error(StatusCode::UNAUTHORIZED, errors::UNAUTHORIZED_SESSION);
49            };
50            let expected_reference = if organization_id.is_some() {
51                ApiKeyReference::Organization
52            } else {
53                ApiKeyReference::User
54            };
55            let reference_id = if let Some(organization_id) = organization_id {
56                if let Err(error) = ensure_organization_permission(
57                    &context,
58                    &identity.user.id,
59                    &organization_id,
60                    ApiKeyAction::Read,
61                )
62                .await
63                {
64                    return error_response_from_rustauth(error);
65                }
66                organization_id
67            } else {
68                identity.user.id
69            };
70            let limit = query_usize(&request, "limit");
71            let offset = query_usize(&request, "offset");
72            let sort_direction = match query_param(&request, "sortDirection").as_deref() {
73                Some("desc") => SortDirection::Desc,
74                _ => SortDirection::Asc,
75            };
76            let sort_by = query_param(&request, "sortBy");
77            let mut api_keys = if let Some(config_id) = config_id.as_deref() {
78                let options = configurations.resolve(Some(config_id))?;
79                if options.reference != expected_reference {
80                    Vec::new()
81                } else {
82                    let store = ApiKeyStore::new(&context, &options);
83                    let mut result = store
84                        .list(
85                            &reference_id,
86                            ListOptions {
87                                config_id: Some(
88                                    options
89                                        .config_id
90                                        .clone()
91                                        .unwrap_or_else(|| "default".to_owned()),
92                                ),
93                                limit: None,
94                                offset: None,
95                                sort_by: sort_by.clone(),
96                                sort_direction,
97                            },
98                        )
99                        .await?
100                        .api_keys;
101                    for api_key in &mut result {
102                        store.migrate_metadata_if_needed(api_key).await;
103                    }
104                    result
105                }
106            } else {
107                list_all_configurations(
108                    &context,
109                    &configurations,
110                    &reference_id,
111                    expected_reference,
112                    sort_by.clone(),
113                    sort_direction,
114                )
115                .await?
116            };
117            sort_api_keys(&mut api_keys, sort_by.as_deref(), sort_direction);
118            let total = api_keys.len() as u64;
119            let api_keys = paginate(api_keys, offset, limit)
120                .into_iter()
121                .map(|api_key| api_key.public())
122                .collect::<Vec<_>>();
123            json(
124                StatusCode::OK,
125                &ListApiKeysResponse {
126                    api_keys,
127                    total,
128                    limit,
129                    offset,
130                },
131            )
132        },
133    )
134}
135
136async fn list_all_configurations(
137    context: &rustauth_core::context::AuthContext,
138    configurations: &SharedConfigurations,
139    reference_id: &str,
140    expected_reference: ApiKeyReference,
141    sort_by: Option<String>,
142    sort_direction: SortDirection,
143) -> Result<Vec<ApiKeyRecord>, rustauth_core::error::RustAuthError> {
144    let mut seen = BTreeSet::new();
145    let mut all = Vec::new();
146    for configuration in configurations.all() {
147        let options = configurations.resolve(configuration.config_id.as_deref())?;
148        if options.reference != expected_reference {
149            continue;
150        }
151        let config_id = options
152            .config_id
153            .clone()
154            .unwrap_or_else(|| "default".to_owned());
155        let store = ApiKeyStore::new(context, &options);
156        let mut result = store
157            .list(
158                reference_id,
159                ListOptions {
160                    config_id: Some(config_id),
161                    limit: None,
162                    offset: None,
163                    sort_by: sort_by.clone(),
164                    sort_direction,
165                },
166            )
167            .await?;
168        for api_key in &mut result.api_keys {
169            store.migrate_metadata_if_needed(api_key).await;
170        }
171        for api_key in result.api_keys {
172            if seen.insert(api_key.id.clone()) {
173                all.push(api_key);
174            }
175        }
176    }
177    Ok(all)
178}
179
180fn sort_api_keys(
181    api_keys: &mut [ApiKeyRecord],
182    sort_by: Option<&str>,
183    sort_direction: SortDirection,
184) {
185    let Some(sort_by) = sort_by else {
186        return;
187    };
188    api_keys.sort_by(|left, right| compare_api_keys(left, right, sort_by));
189    if sort_direction == SortDirection::Desc {
190        api_keys.reverse();
191    }
192}
193
194fn paginate(
195    api_keys: Vec<ApiKeyRecord>,
196    offset: Option<usize>,
197    limit: Option<usize>,
198) -> Vec<ApiKeyRecord> {
199    let iter = api_keys.into_iter().skip(offset.unwrap_or(0));
200    match limit {
201        Some(limit) => iter.take(limit).collect(),
202        None => iter.collect(),
203    }
204}
205
206fn compare_api_keys(left: &ApiKeyRecord, right: &ApiKeyRecord, field: &str) -> Ordering {
207    match field {
208        "createdAt" | "created_at" => left.created_at.cmp(&right.created_at),
209        "updatedAt" | "updated_at" => left.updated_at.cmp(&right.updated_at),
210        "name" => left.name.cmp(&right.name),
211        "expiresAt" | "expires_at" => left.expires_at.cmp(&right.expires_at),
212        _ => left.id.cmp(&right.id),
213    }
214}
215
216fn error_response_from_rustauth(
217    error: rustauth_core::error::RustAuthError,
218) -> Result<rustauth_core::api::ApiResponse, rustauth_core::error::RustAuthError> {
219    let message = error.to_string();
220    if message.contains(errors::message(errors::USER_NOT_MEMBER_OF_ORGANIZATION)) {
221        return super::error(
222            StatusCode::FORBIDDEN,
223            errors::USER_NOT_MEMBER_OF_ORGANIZATION,
224        );
225    }
226    if message.contains(errors::message(errors::ORGANIZATION_PLUGIN_REQUIRED)) {
227        return super::error(
228            StatusCode::INTERNAL_SERVER_ERROR,
229            errors::ORGANIZATION_PLUGIN_REQUIRED,
230        );
231    }
232    super::error(
233        StatusCode::FORBIDDEN,
234        errors::INSUFFICIENT_API_KEY_PERMISSIONS,
235    )
236}