Skip to main content

sa_token_core/oauth2/
mod.rs

1// Author: 金书记 | Author: Jin Shuji
2//! OAuth2 authorization-code / refresh / password / client-credentials.
3//! OAuth2 授权码 / 刷新 / 密码 / 客户端凭证。
4
5mod password;
6mod pkce;
7mod secret;
8
9pub use password::PasswordVerifier;
10pub use pkce::{CodeChallengeMethod, PkceChallenge};
11pub use secret::ClientSecretHasher;
12
13use std::sync::Arc;
14use std::time::Duration;
15
16use chrono::{DateTime, Utc};
17use serde::{Deserialize, Serialize};
18use uuid::Uuid;
19
20use crate::config::SaTokenConfig;
21use crate::dao::SaTokenDao;
22use crate::error::{SaTokenError, SaTokenResult};
23use crate::manager::SaTokenManager;
24
25/// OAuth2 client registration record.
26/// OAuth2 客户端注册记录。
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct OAuth2Client {
29    /// Client identifier.
30    /// 客户端标识。
31    pub client_id: String,
32    /// Stored Argon2 PHC string. Never log this value.
33    /// 存储的 Argon2 PHC 串。禁止写入日志。
34    #[serde(default, alias = "client_secret")]
35    pub client_secret_hash: String,
36    /// Transient plaintext used only at registration; never serialized.
37    /// 仅注册时使用的明文,永不序列化。
38    #[serde(default, skip_serializing, skip_deserializing)]
39    pub client_secret: String,
40    /// Allowed redirect URIs (exact match).
41    /// 允许的重定向 URI(精确匹配)。
42    pub redirect_uris: Vec<String>,
43    /// Supported grant types.
44    /// 支持的授权类型。
45    pub grant_types: Vec<String>,
46    /// Allowed scopes.
47    /// 允许的权限范围。
48    pub scope: Vec<String>,
49    /// Public client: no secret; PKCE required.
50    /// 公共客户端:无密钥;必须 PKCE。
51    #[serde(default)]
52    pub public_client: bool,
53}
54
55/// Authorization code payload stored until exchange / consume.
56/// 兑换/消费前存储的授权码载荷。
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct AuthorizationCode {
59    /// Opaque authorization code value.
60    /// 不透明授权码值。
61    pub code: String,
62    /// Issuing client id.
63    /// 签发方客户端 id。
64    pub client_id: String,
65    /// Resource owner id.
66    /// 资源所有者 id。
67    pub user_id: String,
68    /// Bound redirect URI.
69    /// 绑定的重定向 URI。
70    pub redirect_uri: String,
71    /// Granted scopes.
72    /// 已授予的权限范围。
73    pub scope: Vec<String>,
74    /// Creation time.
75    /// 创建时间。
76    pub created_at: DateTime<Utc>,
77    /// Expiration time.
78    /// 过期时间。
79    pub expires_at: DateTime<Utc>,
80    /// Optional PKCE challenge.
81    /// 可选 PKCE 挑战。
82    pub pkce: Option<PkceChallenge>,
83    /// Optional OAuth `state`.
84    /// 可选 OAuth `state`。
85    pub state: Option<String>,
86}
87
88/// Access token response returned to the client.
89/// 返回给客户端的访问令牌响应。
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct AccessToken {
92    /// Access token value.
93    /// 访问令牌值。
94    pub access_token: String,
95    /// Token type (usually Bearer).
96    /// 令牌类型(通常为 Bearer)。
97    pub token_type: String,
98    /// Lifetime in seconds.
99    /// 有效期(秒)。
100    pub expires_in: i64,
101    /// Optional refresh token.
102    /// 可选刷新令牌。
103    pub refresh_token: Option<String>,
104    /// Granted scopes.
105    /// 已授予的权限范围。
106    pub scope: Vec<String>,
107}
108
109/// Persisted access-token metadata.
110/// 持久化的访问令牌元数据。
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct OAuth2TokenInfo {
113    /// Access token value.
114    /// 访问令牌值。
115    pub access_token: String,
116    /// Client id.
117    /// 客户端 id。
118    pub client_id: String,
119    /// Resource owner id.
120    /// 资源所有者 id。
121    pub user_id: String,
122    /// Granted scopes.
123    /// 已授予的权限范围。
124    pub scope: Vec<String>,
125    /// Creation time.
126    /// 创建时间。
127    pub created_at: DateTime<Utc>,
128    /// Expiration time.
129    /// 过期时间。
130    pub expires_at: DateTime<Utc>,
131    /// Linked refresh token if any.
132    /// 关联的刷新令牌(如有)。
133    pub refresh_token: Option<String>,
134}
135
136/// Refresh-token record used for atomic rotation.
137/// 用于原子轮换的刷新令牌记录。
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct OAuth2RefreshRecord {
140    /// Resource owner id.
141    /// 资源所有者 id。
142    pub user_id: String,
143    /// Client id.
144    /// 客户端 id。
145    pub client_id: String,
146    /// Granted scopes.
147    /// 已授予的权限范围。
148    pub scope: Vec<String>,
149    /// Current access token to revoke on refresh.
150    /// 刷新时待撤销的当前访问令牌。
151    pub access_token: String,
152    /// Creation time.
153    /// 创建时间。
154    pub created_at: DateTime<Utc>,
155}
156
157/// Unified token-endpoint request.
158/// 统一的 token 端点请求。
159#[derive(Debug, Default)]
160pub struct TokenIssueRequest {
161    /// Grant type name.
162    /// 授权类型名称。
163    pub grant_type: String,
164    /// Client id.
165    /// 客户端 id。
166    pub client_id: String,
167    /// Client secret (empty for public clients).
168    /// 客户端密钥(公共客户端可为空)。
169    pub client_secret: String,
170    /// Authorization code (authorization_code grant).
171    /// 授权码(authorization_code 模式)。
172    pub code: Option<String>,
173    /// Redirect URI (authorization_code grant).
174    /// 重定向 URI(authorization_code 模式)。
175    pub redirect_uri: Option<String>,
176    /// Refresh token (refresh_token grant).
177    /// 刷新令牌(refresh_token 模式)。
178    pub refresh_token: Option<String>,
179    /// Username (password grant).
180    /// 用户名(password 模式)。
181    pub username: Option<String>,
182    /// Password (password grant).
183    /// 密码(password 模式)。
184    pub password: Option<String>,
185    /// Requested scopes.
186    /// 请求的权限范围。
187    pub scope: Vec<String>,
188    /// PKCE code_verifier.
189    /// PKCE code_verifier。
190    pub code_verifier: Option<String>,
191}
192
193/// OAuth2 protocol manager backed by [`SaTokenDao`].
194/// 基于 [`SaTokenDao`] 的 OAuth2 协议管理器。
195pub struct OAuth2Manager {
196    dao: Arc<SaTokenDao>,
197    code_ttl: i64,
198    token_ttl: i64,
199    refresh_token_ttl: i64,
200    require_pkce: bool,
201    allow_legacy_plain_secret: bool,
202    password_verifier: Option<Arc<dyn PasswordVerifier>>,
203}
204
205impl std::fmt::Debug for OAuth2Manager {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        f.write_str("OAuth2Manager { .. }")
208    }
209}
210
211impl OAuth2Manager {
212    /// Build from raw storage with default config / key prefix.
213    /// 从原始存储构建(默认配置与键前缀)。
214    pub fn new(storage: Arc<dyn sa_token_adapter::storage::SaStorage>) -> Self {
215        let dao = Arc::new(SaTokenDao::new(storage, Arc::new(SaTokenConfig::default())));
216        Self::from_dao(dao)
217    }
218
219    /// Build from an existing Dao (preferred when sharing Manager keys).
220    /// 从已有 Dao 构建(与 Manager 共享键时推荐)。
221    pub fn from_dao(dao: Arc<SaTokenDao>) -> Self {
222        Self {
223            dao,
224            code_ttl: 600,
225            token_ttl: 3600,
226            refresh_token_ttl: 2592000,
227            require_pkce: false,
228            allow_legacy_plain_secret: false,
229            password_verifier: None,
230        }
231    }
232
233    /// Align Dao / key prefix with an existing manager.
234    /// 与已有 manager 对齐 Dao / 键前缀。
235    pub fn from_manager(manager: &SaTokenManager) -> Self {
236        Self::from_dao(manager.dao().clone())
237    }
238
239    /// Override code / access / refresh TTLs (seconds).
240    /// 覆盖授权码 / 访问令牌 / 刷新令牌 TTL(秒)。
241    pub fn with_ttl(mut self, code_ttl: i64, token_ttl: i64, refresh_token_ttl: i64) -> Self {
242        self.code_ttl = code_ttl;
243        self.token_ttl = token_ttl;
244        self.refresh_token_ttl = refresh_token_ttl;
245        self
246    }
247
248    /// Require PKCE for confidential clients as well.
249    /// 机密客户端也强制要求 PKCE。
250    pub fn with_require_pkce(mut self, require: bool) -> Self {
251        self.require_pkce = require;
252        self
253    }
254
255    /// Allow verifying legacy plaintext secrets stored under the hash field.
256    /// 允许校验哈希字段中残留的历史明文密钥。
257    pub fn with_allow_legacy_plain_secret(mut self, allow: bool) -> Self {
258        self.allow_legacy_plain_secret = allow;
259        self
260    }
261
262    /// Inject password grant verifier (required for password grant).
263    /// 注入密码模式校验器(password grant 必需)。
264    pub fn with_password_verifier(mut self, verifier: Arc<dyn PasswordVerifier>) -> Self {
265        self.password_verifier = Some(verifier);
266        self
267    }
268
269    /// Register a client after hashing `plain_secret` (unless public).
270    /// 注册客户端:对 `plain_secret` 哈希后落库(公共客户端除外)。
271    pub async fn register_client_with_secret(
272        &self,
273        mut client: OAuth2Client,
274        plain_secret: &str,
275    ) -> SaTokenResult<()> {
276        if client.public_client {
277            client.client_secret_hash.clear();
278        } else {
279            client.client_secret_hash = ClientSecretHasher::hash_plain_secret(plain_secret)?;
280        }
281        client.client_secret.clear();
282        let key = self.dao.keys().oauth2_client(&client.client_id);
283        self.dao.set_object(&key, &client, None).await
284    }
285
286    /// Compatibility wrapper: hashes `client.client_secret` when hash is empty.
287    /// 兼容包装:hash 为空时哈希 `client.client_secret`。
288    pub async fn register_client(&self, client: &OAuth2Client) -> SaTokenResult<()> {
289        self.register_client_with_secret(client.clone(), &client.client_secret)
290            .await
291    }
292
293    /// Load a registered client by id.
294    /// 按 id 加载已注册客户端。
295    pub async fn get_client(&self, client_id: &str) -> SaTokenResult<OAuth2Client> {
296        let key = self.dao.keys().oauth2_client(client_id);
297        self.dao
298            .get_object(&key)
299            .await?
300            .ok_or(SaTokenError::OAuth2ClientNotFound)
301    }
302
303    /// Verify client credentials (public clients always succeed).
304    /// 校验客户端凭据(公共客户端恒成功)。
305    pub async fn verify_client(&self, client_id: &str, client_secret: &str) -> SaTokenResult<bool> {
306        let client = self.get_client(client_id).await?;
307        if client.public_client {
308            return Ok(true);
309        }
310        if ClientSecretHasher::is_hashed(&client.client_secret_hash) {
311            return ClientSecretHasher::verify_plain_secret(
312                client_secret,
313                &client.client_secret_hash,
314            );
315        }
316        if self.allow_legacy_plain_secret {
317            return Ok(crate::http_basic::ct_eq(
318                client_secret.as_bytes(),
319                client.client_secret_hash.as_bytes(),
320            ));
321        }
322        Ok(false)
323    }
324
325    /// Build an authorization code (does not persist).
326    /// 构造授权码(不落库)。
327    pub fn generate_authorization_code(
328        &self,
329        client_id: String,
330        user_id: String,
331        redirect_uri: String,
332        scope: Vec<String>,
333        pkce: Option<PkceChallenge>,
334        state: Option<String>,
335    ) -> AuthorizationCode {
336        let now = Utc::now();
337        AuthorizationCode {
338            code: format!("code_{}", Uuid::new_v4().simple()),
339            client_id,
340            user_id,
341            redirect_uri,
342            scope,
343            created_at: now,
344            expires_at: now + chrono::Duration::seconds(self.code_ttl),
345            pkce,
346            state,
347        }
348    }
349
350    /// Persist an authorization code with TTL.
351    /// 以 TTL 持久化授权码。
352    pub async fn store_authorization_code(
353        &self,
354        auth_code: &AuthorizationCode,
355    ) -> SaTokenResult<()> {
356        let key = self.dao.keys().oauth2_code(&auth_code.code);
357        let ttl = Some(Duration::from_secs(self.code_ttl as u64));
358        self.dao.set_object(&key, auth_code, ttl).await
359    }
360
361    /// Atomically consume an authorization code (`take_string`).
362    /// 原子消费授权码(`take_string`)。
363    pub async fn consume_authorization_code(&self, code: &str) -> SaTokenResult<AuthorizationCode> {
364        let key = self.dao.keys().oauth2_code(code);
365        let raw = self
366            .dao
367            .take_string(&key)
368            .await?
369            .ok_or(SaTokenError::OAuth2CodeNotFound)?;
370        let auth_code: AuthorizationCode = self.dao.decode(&raw)?;
371        if Utc::now() > auth_code.expires_at {
372            return Err(SaTokenError::TokenExpired);
373        }
374        Ok(auth_code)
375    }
376
377    /// Exchange authorization code for tokens (with optional PKCE).
378    /// 用授权码兑换令牌(可选 PKCE)。
379    pub async fn exchange_code_for_token(
380        &self,
381        code: &str,
382        client_id: &str,
383        client_secret: &str,
384        redirect_uri: &str,
385        code_verifier: Option<&str>,
386    ) -> SaTokenResult<AccessToken> {
387        let client = self.get_client(client_id).await?;
388        if !client.public_client && !self.verify_client(client_id, client_secret).await? {
389            return Err(SaTokenError::OAuth2InvalidCredentials);
390        }
391        let auth_code = self.consume_authorization_code(code).await?;
392        if auth_code.client_id != client_id {
393            return Err(SaTokenError::OAuth2ClientIdMismatch);
394        }
395        if auth_code.redirect_uri != redirect_uri {
396            return Err(SaTokenError::OAuth2RedirectUriMismatch);
397        }
398        let need_pkce = client.public_client || self.require_pkce || auth_code.pkce.is_some();
399        if client.public_client {
400            let pkce = auth_code
401                .pkce
402                .as_ref()
403                .ok_or(SaTokenError::OAuth2PkceRequiredForPublicClient)?;
404            if !matches!(pkce.code_challenge_method, CodeChallengeMethod::S256) {
405                return Err(SaTokenError::OAuth2PkceRequiredForPublicClient);
406            }
407            let verifier = code_verifier.ok_or(SaTokenError::OAuth2PkceRequired)?;
408            pkce.verify(verifier)?;
409        } else if need_pkce {
410            let pkce = auth_code
411                .pkce
412                .as_ref()
413                .ok_or(SaTokenError::OAuth2PkceRequired)?;
414            let verifier = code_verifier.ok_or(SaTokenError::OAuth2PkceRequired)?;
415            pkce.verify(verifier)?;
416        }
417        self.generate_access_token(&auth_code.client_id, &auth_code.user_id, auth_code.scope)
418            .await
419    }
420
421    /// Issue and persist an access + refresh token pair.
422    /// 签发并持久化访问令牌 + 刷新令牌对。
423    pub async fn generate_access_token(
424        &self,
425        client_id: &str,
426        user_id: &str,
427        scope: Vec<String>,
428    ) -> SaTokenResult<AccessToken> {
429        let now = Utc::now();
430        let access_token = format!("at_{}", Uuid::new_v4().simple());
431        let refresh_token = format!("rt_{}", Uuid::new_v4().simple());
432        let token_info = OAuth2TokenInfo {
433            access_token: access_token.clone(),
434            client_id: client_id.to_string(),
435            user_id: user_id.to_string(),
436            scope: scope.clone(),
437            created_at: now,
438            expires_at: now + chrono::Duration::seconds(self.token_ttl),
439            refresh_token: Some(refresh_token.clone()),
440        };
441        let at_key = self.dao.keys().oauth2_token(&access_token);
442        self.dao
443            .set_object(
444                &at_key,
445                &token_info,
446                Some(Duration::from_secs(self.token_ttl as u64)),
447            )
448            .await?;
449        let record = OAuth2RefreshRecord {
450            user_id: user_id.to_string(),
451            client_id: client_id.to_string(),
452            scope: scope.clone(),
453            access_token: access_token.clone(),
454            created_at: now,
455        };
456        let rt_key = self.dao.keys().oauth2_refresh(&refresh_token);
457        self.dao
458            .set_object(
459                &rt_key,
460                &record,
461                Some(Duration::from_secs(self.refresh_token_ttl as u64)),
462            )
463            .await?;
464        Ok(AccessToken {
465            access_token,
466            token_type: "Bearer".to_string(),
467            expires_in: self.token_ttl,
468            refresh_token: Some(refresh_token),
469            scope,
470        })
471    }
472
473    /// Load and validate an access token.
474    /// 加载并校验访问令牌。
475    pub async fn verify_access_token(&self, access_token: &str) -> SaTokenResult<OAuth2TokenInfo> {
476        let key = self.dao.keys().oauth2_token(access_token);
477        let info: OAuth2TokenInfo = self
478            .dao
479            .get_object(&key)
480            .await?
481            .ok_or(SaTokenError::OAuth2AccessTokenNotFound)?;
482        if Utc::now() > info.expires_at {
483            let _ = self.dao.delete(&key).await;
484            return Err(SaTokenError::TokenExpired);
485        }
486        Ok(info)
487    }
488
489    /// Rotate refresh token atomically (`take_string` + rewrite on failure).
490    /// 原子轮换刷新令牌(`take_string`;失败时回写)。
491    pub async fn refresh_access_token(
492        &self,
493        refresh_token: &str,
494        client_id: &str,
495        client_secret: &str,
496    ) -> SaTokenResult<AccessToken> {
497        if !self.verify_client(client_id, client_secret).await? {
498            return Err(SaTokenError::OAuth2InvalidCredentials);
499        }
500        let rt_key = self.dao.keys().oauth2_refresh(refresh_token);
501        let raw = self
502            .dao
503            .take_string(&rt_key)
504            .await?
505            .ok_or(SaTokenError::OAuth2RefreshTokenNotFound)?;
506        let record: OAuth2RefreshRecord = self.dao.decode(&raw)?;
507        if record.client_id != client_id {
508            let ttl = Some(Duration::from_secs(self.refresh_token_ttl as u64));
509            let _ = self.dao.set_string(&rt_key, &raw, ttl).await;
510            return Err(SaTokenError::OAuth2ClientIdMismatch);
511        }
512        match self
513            .generate_access_token(&record.client_id, &record.user_id, record.scope.clone())
514            .await
515        {
516            Ok(new_token) => {
517                let old_at = self.dao.keys().oauth2_token(&record.access_token);
518                self.dao.delete(&old_at).await?;
519                Ok(new_token)
520            }
521            Err(e) => {
522                let ttl = Some(Duration::from_secs(self.refresh_token_ttl as u64));
523                self.dao.set_string(&rt_key, &raw, ttl).await?;
524                Err(e)
525            }
526        }
527    }
528
529    /// Revoke access and/or refresh token keys (errors propagate).
530    /// 撤销访问/刷新令牌键(错误上抛)。
531    pub async fn revoke_token(&self, token: &str) -> SaTokenResult<()> {
532        let access_key = self.dao.keys().oauth2_token(token);
533        let refresh_key = self.dao.keys().oauth2_refresh(token);
534        self.dao.delete(&access_key).await?;
535        self.dao.delete(&refresh_key).await?;
536        Ok(())
537    }
538
539    /// Exact-match redirect URI validation (rejects empty / fragment).
540    /// 精确匹配重定向 URI(拒绝空串 / fragment)。
541    pub fn validate_redirect_uri(&self, client: &OAuth2Client, redirect_uri: &str) -> bool {
542        if redirect_uri.is_empty() || redirect_uri.contains('#') {
543            return false;
544        }
545        client.redirect_uris.iter().any(|uri| uri == redirect_uri)
546    }
547
548    /// True when every requested scope is registered on the client.
549    /// 请求的每个 scope 均已在客户端注册时返回 true。
550    pub fn validate_scope(&self, client: &OAuth2Client, requested_scope: &[String]) -> bool {
551        requested_scope.iter().all(|s| client.scope.contains(s))
552    }
553
554    /// True when the client lists the grant type.
555    /// 客户端声明了该授权类型时返回 true。
556    pub fn supports_grant_type(client: &OAuth2Client, grant_type: &str) -> bool {
557        client.grant_types.iter().any(|g| g == grant_type)
558    }
559
560    /// Resource-owner password grant (requires injected verifier).
561    /// 资源所有者密码模式(需注入校验器)。
562    pub async fn password_grant(
563        &self,
564        client_id: &str,
565        client_secret: &str,
566        username: &str,
567        password: &str,
568        scope: Vec<String>,
569    ) -> SaTokenResult<AccessToken> {
570        let verifier = self.password_verifier.as_ref().ok_or_else(|| {
571            SaTokenError::ConfigError("password verifier is not configured".into())
572        })?;
573        let client = self.get_client(client_id).await?;
574        if !Self::supports_grant_type(&client, "password") {
575            return Err(SaTokenError::OAuth2UnsupportedGrant);
576        }
577        if !self.verify_client(client_id, client_secret).await? {
578            return Err(SaTokenError::OAuth2InvalidCredentials);
579        }
580        if !self.validate_scope(&client, &scope) {
581            return Err(SaTokenError::OAuth2InvalidScope);
582        }
583        verifier.verify_password(username, password).await?;
584        self.generate_access_token(client_id, username, scope).await
585    }
586
587    /// Client-credentials grant (confidential clients only).
588    /// 客户端凭证模式(仅机密客户端)。
589    pub async fn client_credentials_grant(
590        &self,
591        client_id: &str,
592        client_secret: &str,
593        scope: Vec<String>,
594    ) -> SaTokenResult<AccessToken> {
595        let client = self.get_client(client_id).await?;
596        if !Self::supports_grant_type(&client, "client_credentials") {
597            return Err(SaTokenError::OAuth2UnsupportedGrant);
598        }
599        if client.public_client {
600            return Err(SaTokenError::OAuth2InvalidCredentials);
601        }
602        if !self.verify_client(client_id, client_secret).await? {
603            return Err(SaTokenError::OAuth2InvalidCredentials);
604        }
605        if !self.validate_scope(&client, &scope) {
606            return Err(SaTokenError::OAuth2InvalidScope);
607        }
608        let subject = format!("client:{client_id}");
609        self.generate_access_token(client_id, &subject, scope).await
610    }
611
612    /// Dispatch token issuance by grant type.
613    /// 按授权类型分发令牌签发。
614    pub async fn issue_token(&self, req: TokenIssueRequest) -> SaTokenResult<AccessToken> {
615        match req.grant_type.as_str() {
616            "authorization_code" => {
617                let code = req.code.ok_or(SaTokenError::OAuth2CodeNotFound)?;
618                let redirect_uri = req
619                    .redirect_uri
620                    .ok_or(SaTokenError::OAuth2RedirectUriMismatch)?;
621                self.exchange_code_for_token(
622                    &code,
623                    &req.client_id,
624                    &req.client_secret,
625                    &redirect_uri,
626                    req.code_verifier.as_deref(),
627                )
628                .await
629            }
630            "refresh_token" => {
631                let refresh = req
632                    .refresh_token
633                    .ok_or(SaTokenError::OAuth2RefreshTokenNotFound)?;
634                self.refresh_access_token(&refresh, &req.client_id, &req.client_secret)
635                    .await
636            }
637            "password" => {
638                let username = req.username.ok_or(SaTokenError::OAuth2InvalidCredentials)?;
639                let password = req.password.ok_or(SaTokenError::OAuth2InvalidCredentials)?;
640                self.password_grant(
641                    &req.client_id,
642                    &req.client_secret,
643                    &username,
644                    &password,
645                    req.scope,
646                )
647                .await
648            }
649            "client_credentials" => {
650                self.client_credentials_grant(&req.client_id, &req.client_secret, req.scope)
651                    .await
652            }
653            _ => Err(SaTokenError::OAuth2UnsupportedGrant),
654        }
655    }
656
657    /// Validate client + redirect + scope + PKCE, then generate and store a code.
658    /// 校验客户端 / 重定向 / scope / PKCE 后生成并存储授权码。
659    pub async fn issue_authorization_code(
660        &self,
661        client_id: String,
662        user_id: String,
663        redirect_uri: String,
664        scope: Vec<String>,
665        pkce: Option<PkceChallenge>,
666        state: Option<String>,
667    ) -> SaTokenResult<AuthorizationCode> {
668        let client = self.get_client(&client_id).await?;
669        if !self.validate_redirect_uri(&client, &redirect_uri) {
670            return Err(SaTokenError::OAuth2RedirectUriMismatch);
671        }
672        if !self.validate_scope(&client, &scope) {
673            return Err(SaTokenError::OAuth2InvalidScope);
674        }
675        if (client.public_client || self.require_pkce) && pkce.is_none() {
676            return Err(if client.public_client {
677                SaTokenError::OAuth2PkceRequiredForPublicClient
678            } else {
679                SaTokenError::OAuth2PkceRequired
680            });
681        }
682        let code =
683            self.generate_authorization_code(client_id, user_id, redirect_uri, scope, pkce, state);
684        self.store_authorization_code(&code).await?;
685        Ok(code)
686    }
687}