summaryrefslogtreecommitdiff
path: root/src/services
diff options
context:
space:
mode:
authormrw1593 <botahamec@outlook.com>2023-06-11 15:34:00 -0400
committermrw1593 <botahamec@outlook.com>2023-06-11 15:34:00 -0400
commitac7317226405fc90e8439a0c1bef91cecd539d02 (patch)
tree61e7009238b6002d51e93ede8f52aa7bef526f2b /src/services
parent83fdd59b13d4bf45bd35d9693ae361ff896636ab (diff)
Implement the authorization code grant
Diffstat (limited to 'src/services')
-rw-r--r--src/services/db/client.rs44
-rw-r--r--src/services/jwt.rs54
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());
}