Skip to main content

rws_server/sso/
client_store.rs

1//! Registered OAuth 2.0 clients for [`super::server::AuthServer`].
2
3/// An OAuth 2.0 grant type a registered client is authorized to use.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum GrantType {
6    /// `grant_type=client_credentials` — machine-to-machine, no end user.
7    ClientCredentials,
8    /// `grant_type=authorization_code` — end-user login via `/oauth/authorize`.
9    AuthorizationCode,
10    /// `grant_type=refresh_token` — exchange a refresh token for a new access token.
11    RefreshToken,
12}
13
14/// A registered OAuth 2.0 client.
15#[derive(Debug, Clone)]
16pub struct OAuthClient {
17    /// The client identifier sent as `client_id`.
18    pub client_id: String,
19    /// The client secret, for confidential clients. `None` for a public
20    /// client (e.g. a single-page app using PKCE with no secret).
21    pub client_secret: Option<String>,
22    /// Redirect URIs this client is allowed to receive an authorization
23    /// code at. Required (non-empty) for clients using
24    /// [`GrantType::AuthorizationCode`]; ignored otherwise.
25    pub redirect_uris: Vec<String>,
26    /// Grant types this client may use.
27    pub grants: Vec<GrantType>,
28    /// Scopes this client may request. A token request naming a scope
29    /// outside this list is rejected with `invalid_scope`.
30    pub scopes: Vec<String>,
31}
32
33/// In-memory registry of [`OAuthClient`]s, looked up by `client_id`.
34///
35/// # Example
36///
37/// ```rust
38/// # extern crate rws_server as rust_web_server;
39/// use rust_web_server::sso::client_store::{ClientStore, OAuthClient, GrantType};
40///
41/// let clients = ClientStore::new()
42///     .add(OAuthClient {
43///         client_id:     "spa-frontend".into(),
44///         client_secret: None,
45///         redirect_uris: vec!["https://spa.example.com/callback".into()],
46///         grants:        vec![GrantType::AuthorizationCode],
47///         scopes:        vec!["openid".into(), "email".into()],
48///     })
49///     .add(OAuthClient {
50///         client_id:     "backend-service".into(),
51///         client_secret: Some("s3cr3t".into()),
52///         redirect_uris: vec![],
53///         grants:        vec![GrantType::ClientCredentials],
54///         scopes:        vec!["api:read".into(), "api:write".into()],
55///     });
56///
57/// assert!(clients.get("spa-frontend").is_some());
58/// assert!(clients.get("unknown-client").is_none());
59/// ```
60#[derive(Debug, Clone, Default)]
61pub struct ClientStore {
62    clients: Vec<OAuthClient>,
63}
64
65impl ClientStore {
66    /// Create an empty client store.
67    pub fn new() -> Self {
68        ClientStore { clients: Vec::new() }
69    }
70
71    /// Register a client. Later registrations with a duplicate `client_id`
72    /// are still stored; [`ClientStore::get`] returns the first match.
73    pub fn add(mut self, client: OAuthClient) -> Self {
74        self.clients.push(client);
75        self
76    }
77
78    /// Look up a registered client by `client_id`.
79    pub fn get(&self, client_id: &str) -> Option<&OAuthClient> {
80        self.clients.iter().find(|c| c.client_id == client_id)
81    }
82}