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
|
||||
400:
|
||||
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:
|
||||
post:
|
||||
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_httpauth::extractors::bearer::BearerAuth;
|
||||
use chrono::{Duration, Utc};
|
||||
|
||||
use crate::config::Config;
|
||||
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::models::oauth::queries::save_oauth_token;
|
||||
use crate::models::oauth::queries::{
|
||||
delete_oauth_token,
|
||||
save_oauth_token,
|
||||
};
|
||||
use crate::models::users::queries::{
|
||||
get_user_by_name,
|
||||
get_user_by_login_address,
|
||||
};
|
||||
use crate::utils::currencies::{validate_wallet_address, Currency};
|
||||
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;
|
||||
|
||||
const ACCESS_TOKEN_EXPIRES_IN: i64 = 86400 * 7;
|
||||
|
@ -87,7 +92,28 @@ async fn token_view(
|
|||
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 {
|
||||
web::scope("/oauth")
|
||||
.service(token_view)
|
||||
.service(revoke_token_view)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::models::users::types::{DbUser, User};
|
|||
pub async fn save_oauth_token(
|
||||
db_client: &impl GenericClient,
|
||||
owner_id: &Uuid,
|
||||
access_token: &str,
|
||||
token: &str,
|
||||
created_at: &DateTime<Utc>,
|
||||
expires_at: &DateTime<Utc>,
|
||||
) -> Result<(), DatabaseError> {
|
||||
|
@ -18,11 +18,41 @@ pub async fn save_oauth_token(
|
|||
INSERT INTO oauth_token (owner_id, token, created_at, expires_at)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
",
|
||||
&[&owner_id, &access_token, &created_at, &expires_at],
|
||||
&[&owner_id, &token, &created_at, &expires_at],
|
||||
).await?;
|
||||
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(
|
||||
db_client: &impl GenericClient,
|
||||
access_token: &str,
|
||||
|
@ -45,3 +75,36 @@ pub async fn get_user_by_oauth_token(
|
|||
let user = User::new(db_user, db_profile);
|
||||
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