diff options
| author | mrw1593 <botahamec@outlook.com> | 2023-06-11 15:34:00 -0400 |
|---|---|---|
| committer | mrw1593 <botahamec@outlook.com> | 2023-06-11 15:34:00 -0400 |
| commit | ac7317226405fc90e8439a0c1bef91cecd539d02 (patch) | |
| tree | 61e7009238b6002d51e93ede8f52aa7bef526f2b /src/services | |
| parent | 83fdd59b13d4bf45bd35d9693ae361ff896636ab (diff) | |
Implement the authorization code grant
Diffstat (limited to 'src/services')
| -rw-r--r-- | src/services/db/client.rs | 44 | ||||
| -rw-r--r-- | src/services/jwt.rs | 54 |
2 files changed, 80 insertions, 18 deletions
diff --git a/src/services/db/client.rs b/src/services/db/client.rs index c25ad0d..70701d7 100644 --- a/src/services/db/client.rs +++ b/src/services/db/client.rs @@ -21,6 +21,13 @@ pub struct ClientRow { pub default_scopes: Option<String>, } +#[derive(Clone, FromRow)] +struct HashRow { + secret_hash: Option<Vec<u8>>, + secret_salt: Option<Vec<u8>>, + secret_version: Option<u32>, +} + pub async fn client_id_exists<'c>( executor: impl Executor<'c, Database = MySql>, id: Uuid, @@ -47,6 +54,19 @@ pub async fn client_alias_exists<'c>( .unexpect() } +pub async fn get_client_id_by_alias<'c>( + executor: impl Executor<'c, Database = MySql>, + alias: &str, +) -> Result<Option<Uuid>, RawUnexpected> { + query_scalar!( + "SELECT id as `id: Uuid` FROM clients WHERE alias = ?", + alias + ) + .fetch_optional(executor) + .await + .unexpect() +} + pub async fn get_client_response<'c>( executor: impl Executor<'c, Database = MySql>, id: Uuid, @@ -116,6 +136,28 @@ pub async fn get_client_default_scopes<'c>( Ok(scopes.map(|s| s.map(Box::from))) } +pub async fn get_client_secret<'c>( + executor: impl Executor<'c, Database = MySql>, + id: Uuid, +) -> Result<Option<PasswordHash>, RawUnexpected> { + let hash = query_as!( + HashRow, + r"SELECT secret_hash, secret_salt, secret_version + FROM clients WHERE id = ?", + id + ) + .fetch_optional(executor) + .await?; + + let Some(hash) = hash else { return Ok(None) }; + let Some(version) = hash.secret_version else { return Ok(None) }; + let Some(salt) = hash.secret_hash else { return Ok(None) }; + let Some(hash) = hash.secret_salt else { return Ok(None) }; + + let hash = PasswordHash::from_fields(&hash, &salt, version as u8); + Ok(Some(hash)) +} + pub async fn get_client_redirect_uris<'c>( executor: impl Executor<'c, Database = MySql>, id: Uuid, @@ -136,7 +178,7 @@ pub async fn get_client_redirect_uris<'c>( pub async fn client_has_redirect_uri<'c>( executor: impl Executor<'c, Database = MySql>, id: Uuid, - url: Url, + url: &Url, ) -> Result<bool, RawUnexpected> { query_scalar!( r"SELECT EXISTS( diff --git a/src/services/jwt.rs b/src/services/jwt.rs index 7841afb..822101f 100644 --- a/src/services/jwt.rs +++ b/src/services/jwt.rs @@ -32,6 +32,7 @@ pub struct Claims { client_id: Uuid, auth_code_id: Uuid, token_type: TokenType, + redirect_uri: Option<Url>, } #[derive(Debug, Clone, Copy, sqlx::Type)] @@ -43,18 +44,19 @@ pub enum RevokedRefreshTokenReason { impl Claims { pub async fn auth_code<'c>( - db: MySqlPool, + db: &MySqlPool, self_id: Url, client_id: Uuid, scopes: &str, + redirect_uri: &Url, ) -> Result<Self, RawUnexpected> { let five_minutes = Duration::minutes(5); - let id = new_id(&db, db::auth_code_exists).await?; + let id = new_id(db, db::auth_code_exists).await?; let time = Utc::now(); let exp = time + five_minutes; - db::create_auth_code(&db, id, exp).await?; + db::create_auth_code(db, id, exp).await?; Ok(Self { iss: self_id, @@ -67,22 +69,23 @@ impl Claims { client_id, auth_code_id: id, token_type: TokenType::Authorization, + redirect_uri: Some(redirect_uri.clone()), }) } pub async fn access_token<'c>( - db: MySqlPool, + db: &MySqlPool, auth_code_id: Uuid, self_id: Url, client_id: Uuid, duration: Duration, scopes: &str, ) -> Result<Self, RawUnexpected> { - let id = new_id(&db, db::access_token_exists).await?; + let id = new_id(db, db::access_token_exists).await?; let time = Utc::now(); let exp = time + duration; - db::create_access_token(&db, id, auth_code_id, exp) + db::create_access_token(db, id, auth_code_id, exp) .await .unexpect()?; @@ -97,19 +100,23 @@ impl Claims { client_id, auth_code_id, token_type: TokenType::Access, + redirect_uri: None, }) } - pub async fn refresh_token(db: MySqlPool, other_token: Claims) -> Result<Self, RawUnexpected> { + pub async fn refresh_token( + db: &MySqlPool, + other_token: &Claims, + ) -> Result<Self, RawUnexpected> { let one_day = Duration::days(1); - let id = new_id(&db, db::refresh_token_exists).await?; + let id = new_id(db, db::refresh_token_exists).await?; let time = Utc::now(); let exp = other_token.exp + one_day; - db::create_refresh_token(&db, id, other_token.auth_code_id, exp).await?; + db::create_refresh_token(db, id, other_token.auth_code_id, exp).await?; - let mut claims = other_token; + let mut claims = other_token.clone(); claims.exp = exp; claims.iat = Some(time); claims.jti = id; @@ -119,15 +126,15 @@ impl Claims { } pub async fn refreshed_access_token( - db: MySqlPool, + db: &MySqlPool, refresh_token: Claims, exp_time: Duration, ) -> Result<Self, RawUnexpected> { - let id = new_id(&db, db::access_token_exists).await?; + let id = new_id(db, db::access_token_exists).await?; let time = Utc::now(); let exp = time + exp_time; - db::create_access_token(&db, id, refresh_token.auth_code_id, exp).await?; + db::create_access_token(db, id, refresh_token.auth_code_id, exp).await?; let mut claims = refresh_token; claims.exp = exp; @@ -142,6 +149,10 @@ impl Claims { self.jti } + pub fn expires_in(&self) -> i64 { + (self.exp - Utc::now()).num_seconds() + } + pub fn scopes(&self) -> &str { &self.scope } @@ -163,6 +174,8 @@ pub enum VerifyJwtError { WrongClient, #[error("The given audience parameter does not contain this issuer")] BadAudience, + #[error("The redirect URI doesn't match what's in the token")] + IncorrectRedirectUri, #[error("The token is expired")] ExpiredToken, #[error("The token cannot be used yet")] @@ -211,16 +224,23 @@ fn verify_jwt( } pub async fn verify_auth_code<'c>( - db: MySqlPool, + db: &MySqlPool, token: &str, self_id: Url, client_id: Uuid, + redirect_uri: Url, ) -> Result<Claims, Expect<VerifyJwtError>> { let claims = verify_jwt(token, self_id, client_id)?; - if db::delete_auth_code(&db, claims.jti).await? { - db::delete_access_tokens_with_auth_code(&db, claims.jti).await?; - db::revoke_refresh_tokens_with_auth_code(&db, claims.jti).await?; + if let Some(claimed_uri) = &claims.redirect_uri { + if claimed_uri.clone() != redirect_uri { + yeet!(VerifyJwtError::IncorrectRedirectUri.into()); + } + } + + if db::delete_auth_code(db, claims.jti).await? { + db::delete_access_tokens_with_auth_code(db, claims.jti).await?; + db::revoke_refresh_tokens_with_auth_code(db, claims.jti).await?; yeet!(VerifyJwtError::JwtRevoked.into()); } |
