Add API method for revoking access token
This commit is contained in:
parent
b3d03507ad
commit
445177d9a7
4 changed files with 118 additions and 5 deletions
|
@ -59,6 +59,25 @@ paths:
|
||||||
example: 1639747526
|
example: 1639747526
|
||||||
400:
|
400:
|
||||||
description: Invalid token request
|
description: Invalid token request
|
||||||
|
/oauth/revoke:
|
||||||
|
post:
|
||||||
|
summary: Revoke an access token to make it no longer valid for use.
|
||||||
|
security:
|
||||||
|
- tokenAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
description: The previously obtained token, to be invalidated.
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful operation
|
||||||
|
403:
|
||||||
|
description: Token doesn't belong to user.
|
||||||
/api/v1/accounts:
|
/api/v1/accounts:
|
||||||
post:
|
post:
|
||||||
summary: Creates a user and profile records.
|
summary: Creates a user and profile records.
|
||||||
|
|
|
@ -31,3 +31,8 @@ impl TokenResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct RevocationRequest {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
use actix_web::{post, web, HttpResponse, Scope as ActixScope};
|
use actix_web::{post, web, HttpResponse, Scope as ActixScope};
|
||||||
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::database::{Pool, get_database_client};
|
use crate::database::{Pool, get_database_client};
|
||||||
use crate::errors::{HttpError, ValidationError};
|
use crate::errors::{DatabaseError, HttpError, ValidationError};
|
||||||
use crate::ethereum::eip4361::verify_eip4361_signature;
|
use crate::ethereum::eip4361::verify_eip4361_signature;
|
||||||
use crate::models::oauth::queries::save_oauth_token;
|
use crate::models::oauth::queries::{
|
||||||
|
delete_oauth_token,
|
||||||
|
save_oauth_token,
|
||||||
|
};
|
||||||
use crate::models::users::queries::{
|
use crate::models::users::queries::{
|
||||||
get_user_by_name,
|
get_user_by_name,
|
||||||
get_user_by_login_address,
|
get_user_by_login_address,
|
||||||
};
|
};
|
||||||
use crate::utils::currencies::{validate_wallet_address, Currency};
|
use crate::utils::currencies::{validate_wallet_address, Currency};
|
||||||
use crate::utils::passwords::verify_password;
|
use crate::utils::passwords::verify_password;
|
||||||
use super::types::{TokenRequest, TokenResponse};
|
use super::auth::get_current_user;
|
||||||
|
use super::types::{RevocationRequest, TokenRequest, TokenResponse};
|
||||||
use super::utils::generate_access_token;
|
use super::utils::generate_access_token;
|
||||||
|
|
||||||
const ACCESS_TOKEN_EXPIRES_IN: i64 = 86400 * 7;
|
const ACCESS_TOKEN_EXPIRES_IN: i64 = 86400 * 7;
|
||||||
|
@ -87,7 +92,28 @@ async fn token_view(
|
||||||
Ok(HttpResponse::Ok().json(token_response))
|
Ok(HttpResponse::Ok().json(token_response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/revoke")]
|
||||||
|
async fn revoke_token_view(
|
||||||
|
auth: BearerAuth,
|
||||||
|
db_pool: web::Data<Pool>,
|
||||||
|
request_data: web::Json<RevocationRequest>,
|
||||||
|
) -> Result<HttpResponse, HttpError> {
|
||||||
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
|
match delete_oauth_token(
|
||||||
|
db_client,
|
||||||
|
¤t_user.id,
|
||||||
|
&request_data.token,
|
||||||
|
).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(DatabaseError::NotFound(_)) => return Err(HttpError::PermissionError),
|
||||||
|
Err(other_error) => return Err(other_error.into()),
|
||||||
|
};
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn oauth_api_scope() -> ActixScope {
|
pub fn oauth_api_scope() -> ActixScope {
|
||||||
web::scope("/oauth")
|
web::scope("/oauth")
|
||||||
.service(token_view)
|
.service(token_view)
|
||||||
|
.service(revoke_token_view)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::models::users::types::{DbUser, User};
|
||||||
pub async fn save_oauth_token(
|
pub async fn save_oauth_token(
|
||||||
db_client: &impl GenericClient,
|
db_client: &impl GenericClient,
|
||||||
owner_id: &Uuid,
|
owner_id: &Uuid,
|
||||||
access_token: &str,
|
token: &str,
|
||||||
created_at: &DateTime<Utc>,
|
created_at: &DateTime<Utc>,
|
||||||
expires_at: &DateTime<Utc>,
|
expires_at: &DateTime<Utc>,
|
||||||
) -> Result<(), DatabaseError> {
|
) -> Result<(), DatabaseError> {
|
||||||
|
@ -18,11 +18,41 @@ pub async fn save_oauth_token(
|
||||||
INSERT INTO oauth_token (owner_id, token, created_at, expires_at)
|
INSERT INTO oauth_token (owner_id, token, created_at, expires_at)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
",
|
",
|
||||||
&[&owner_id, &access_token, &created_at, &expires_at],
|
&[&owner_id, &token, &created_at, &expires_at],
|
||||||
).await?;
|
).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn delete_oauth_token(
|
||||||
|
db_client: &mut impl GenericClient,
|
||||||
|
current_user_id: &Uuid,
|
||||||
|
token: &str,
|
||||||
|
) -> Result<(), DatabaseError> {
|
||||||
|
let transaction = db_client.transaction().await?;
|
||||||
|
let maybe_row = transaction.query_opt(
|
||||||
|
"
|
||||||
|
SELECT owner_id FROM oauth_token
|
||||||
|
WHERE token = $1
|
||||||
|
FOR UPDATE
|
||||||
|
",
|
||||||
|
&[&token],
|
||||||
|
).await?;
|
||||||
|
if let Some(row) = maybe_row {
|
||||||
|
let owner_id: Uuid = row.try_get("owner_id")?;
|
||||||
|
if owner_id != *current_user_id {
|
||||||
|
// Return error if token is owned by a different user
|
||||||
|
return Err(DatabaseError::NotFound("token"));
|
||||||
|
} else {
|
||||||
|
transaction.execute(
|
||||||
|
"DELETE FROM oauth_token WHERE token = $1",
|
||||||
|
&[&token],
|
||||||
|
).await?;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
transaction.commit().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_user_by_oauth_token(
|
pub async fn get_user_by_oauth_token(
|
||||||
db_client: &impl GenericClient,
|
db_client: &impl GenericClient,
|
||||||
access_token: &str,
|
access_token: &str,
|
||||||
|
@ -45,3 +75,36 @@ pub async fn get_user_by_oauth_token(
|
||||||
let user = User::new(db_user, db_profile);
|
let user = User::new(db_user, db_profile);
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serial_test::serial;
|
||||||
|
use crate::database::test_utils::create_test_database;
|
||||||
|
use crate::models::users::queries::create_user;
|
||||||
|
use crate::models::users::types::UserCreateData;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_delete_oauth_token() {
|
||||||
|
let db_client = &mut create_test_database().await;
|
||||||
|
let user_data = UserCreateData {
|
||||||
|
username: "test".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let user = create_user(db_client, user_data).await.unwrap();
|
||||||
|
let token = "test-token";
|
||||||
|
save_oauth_token(
|
||||||
|
db_client,
|
||||||
|
&user.id,
|
||||||
|
token,
|
||||||
|
&Utc::now(),
|
||||||
|
&Utc::now(),
|
||||||
|
).await.unwrap();
|
||||||
|
delete_oauth_token(
|
||||||
|
db_client,
|
||||||
|
&user.id,
|
||||||
|
token,
|
||||||
|
).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue