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}