1mod 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#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct OAuth2Client {
29 pub client_id: String,
32 #[serde(default, alias = "client_secret")]
35 pub client_secret_hash: String,
36 #[serde(default, skip_serializing, skip_deserializing)]
39 pub client_secret: String,
40 pub redirect_uris: Vec<String>,
43 pub grant_types: Vec<String>,
46 pub scope: Vec<String>,
49 #[serde(default)]
52 pub public_client: bool,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct AuthorizationCode {
59 pub code: String,
62 pub client_id: String,
65 pub user_id: String,
68 pub redirect_uri: String,
71 pub scope: Vec<String>,
74 pub created_at: DateTime<Utc>,
77 pub expires_at: DateTime<Utc>,
80 pub pkce: Option<PkceChallenge>,
83 pub state: Option<String>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct AccessToken {
92 pub access_token: String,
95 pub token_type: String,
98 pub expires_in: i64,
101 pub refresh_token: Option<String>,
104 pub scope: Vec<String>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct OAuth2TokenInfo {
113 pub access_token: String,
116 pub client_id: String,
119 pub user_id: String,
122 pub scope: Vec<String>,
125 pub created_at: DateTime<Utc>,
128 pub expires_at: DateTime<Utc>,
131 pub refresh_token: Option<String>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct OAuth2RefreshRecord {
140 pub user_id: String,
143 pub client_id: String,
146 pub scope: Vec<String>,
149 pub access_token: String,
152 pub created_at: DateTime<Utc>,
155}
156
157#[derive(Debug, Default)]
160pub struct TokenIssueRequest {
161 pub grant_type: String,
164 pub client_id: String,
167 pub client_secret: String,
170 pub code: Option<String>,
173 pub redirect_uri: Option<String>,
176 pub refresh_token: Option<String>,
179 pub username: Option<String>,
182 pub password: Option<String>,
185 pub scope: Vec<String>,
188 pub code_verifier: Option<String>,
191}
192
193pub 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 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 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 pub fn from_manager(manager: &SaTokenManager) -> Self {
236 Self::from_dao(manager.dao().clone())
237 }
238
239 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 pub fn with_require_pkce(mut self, require: bool) -> Self {
251 self.require_pkce = require;
252 self
253 }
254
255 pub fn with_allow_legacy_plain_secret(mut self, allow: bool) -> Self {
258 self.allow_legacy_plain_secret = allow;
259 self
260 }
261
262 pub fn with_password_verifier(mut self, verifier: Arc<dyn PasswordVerifier>) -> Self {
265 self.password_verifier = Some(verifier);
266 self
267 }
268
269 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn validate_scope(&self, client: &OAuth2Client, requested_scope: &[String]) -> bool {
551 requested_scope.iter().all(|s| client.scope.contains(s))
552 }
553
554 pub fn supports_grant_type(client: &OAuth2Client, grant_type: &str) -> bool {
557 client.grant_types.iter().any(|g| g == grant_type)
558 }
559
560 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 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 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 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}