Skip to main content

squigit_auth/security/
api_keys.rs

1// Copyright 2026 a7mddra
2// SPDX-License-Identifier: Apache-2.0
3
4use std::str::FromStr;
5
6use crate::{ProfileError, Result};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ApiKeyProvider {
10    GoogleAiStudio,
11    ImgBb,
12}
13
14impl ApiKeyProvider {
15    pub fn display_name(self) -> &'static str {
16        match self {
17            Self::GoogleAiStudio => "Google AI Studio",
18            Self::ImgBb => "ImgBB",
19        }
20    }
21
22    pub fn storage_key_name(self) -> &'static str {
23        match self {
24            Self::GoogleAiStudio => "google-ai-studio",
25            Self::ImgBb => "imgbb",
26        }
27    }
28
29    pub fn is_valid_key(self, key: &str) -> bool {
30        match self {
31            Self::GoogleAiStudio => {
32                let is_standard = key.starts_with("AIzaSy") && key.len() == 39;
33                let is_prefixed = key.starts_with("AQ.") && key.len() >= 50 && key.len() <= 60;
34                is_standard || is_prefixed
35            }
36            Self::ImgBb => key.len() == 32,
37        }
38    }
39
40    pub fn validation_hint(self) -> &'static str {
41        match self {
42            Self::GoogleAiStudio => {
43                "Expected a key that starts with 'AIzaSy' (39 chars) or 'AQ.' (50-60 chars)."
44            }
45            Self::ImgBb => "Expected a 32-character API key.",
46        }
47    }
48}
49
50impl FromStr for ApiKeyProvider {
51    type Err = ProfileError;
52
53    fn from_str(value: &str) -> Result<Self> {
54        match value {
55            "google-ai-studio" => Ok(Self::GoogleAiStudio),
56            "imgbb" => Ok(Self::ImgBb),
57            other => Err(ProfileError::InvalidProvider(other.to_owned())),
58        }
59    }
60}
61
62pub fn validate_api_key(provider: ApiKeyProvider, plaintext: &str) -> Result<()> {
63    let trimmed = plaintext.trim();
64    if provider.is_valid_key(trimmed) {
65        return Ok(());
66    }
67
68    Err(ProfileError::byok(
69        crate::ByokErrorCode::InvalidCredential,
70        format!(
71            "Invalid {} API key format. {}",
72            provider.display_name(),
73            provider.validation_hint()
74        ),
75    ))
76}