Skip to main content

k2db_api_contract/
auth.rs

1// SPDX-FileCopyrightText: 2026 Alexander R. Croft
2// SPDX-License-Identifier: GPL-3.0-only
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct AuthHeader {
8    pub scheme: String,
9    pub key_id: String,
10    pub secret: String,
11}
12
13impl AuthHeader {
14    pub fn parse(raw: &str) -> Option<Self> {
15        let trimmed = raw.trim();
16        let value = trimmed.strip_prefix("ApiKey ")?;
17        let (key_id, secret) = value.split_once('.')?;
18        let key_id = key_id.trim().strip_prefix("rbk_sys_").unwrap_or(key_id.trim());
19        let secret = secret.trim();
20        if key_id.is_empty() || secret.is_empty() {
21            return None;
22        }
23
24        Some(Self {
25            scheme: "ApiKey".to_owned(),
26            key_id: key_id.to_owned(),
27            secret: secret.to_owned(),
28        })
29    }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct ApiKeyIdentity {
34    pub kind: String,
35    pub key_id: String,
36    pub name: String,
37    pub database: String,
38    pub permissions: Vec<String>,
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn parse_accepts_expected_apikey_shape() {
47        let parsed = AuthHeader::parse("ApiKey rbk_sys_demo.secret-value").expect("auth header");
48        assert_eq!(parsed.scheme, "ApiKey");
49        assert_eq!(parsed.key_id, "demo");
50        assert_eq!(parsed.secret, "secret-value");
51    }
52
53    #[test]
54    fn parse_rejects_malformed_values() {
55        assert!(AuthHeader::parse("Bearer token").is_none());
56        assert!(AuthHeader::parse("ApiKey missingdot").is_none());
57        assert!(AuthHeader::parse("ApiKey .secret").is_none());
58        assert!(AuthHeader::parse("ApiKey key.").is_none());
59    }
60}