Skip to content

Commit

Permalink
add DELETE /v1/me/token
Browse files Browse the repository at this point in the history
  • Loading branch information
Fleeym committed Mar 2, 2024
1 parent 88754d9 commit 6e31bfa
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 20 deletions.
124 changes: 124 additions & 0 deletions openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,23 @@ paths:
$ref: "#/components/responses/InternalServerError"

/v1/me:
get:
tags:
- user
summary: Get your own info
security:
- bearerAuth: []
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/DeveloperProfile"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalServerError"
put:
tags:
- user
Expand All @@ -491,6 +508,55 @@ paths:
"500":
$ref: "#/components/responses/InternalServerError"

/v1/me/mods:
get:
tags:
- user
summary: Get your own mods
parameters:
- name: validated
in: query
description: Filter by validation status
schema:
type: boolean
required: false
security:
- bearerAuth: []
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: ""
payload:
type: array
items:
$ref: "#/components/schemas/DeveloperMod"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalServerError"

/v1/me/token:
delete:
tags:
- user
summary: Revoke current token
security:
- bearerAuth: []
responses:
"204":
description: No Content (Token revoked)
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalServerError"

/v1/me/tokens:
delete:
tags:
Expand Down Expand Up @@ -669,6 +735,64 @@ components:
examples:
- true

DeveloperProfile:
type: object
properties:
id:
type: integer
username:
type: string
description: The username of the developer. At the moment it's the same as the GitHub username of the developer.
examples:
- fleeym
display_name:
type: string
examples:
- Flame
verified:
type: boolean,
description: Whether the developer can post mods without admin verification
examples:
- true
admin:
type: boolean,
description: Whether the developer is an admin (which can verify mods and developers)
examples:
- true

DeveloperMod:
type: object
properties:
id:
$ref: "#/components/schemas/ModID"
featured:
type: boolean
description: Whether the mod should is featured or not
example: true
download_count:
type: integer
example: 500
versions:
type: array
items:
$ref: "#/components/schemas/DeveloperModVersion"

DeveloperModVersion:
type: object
properties:
name:
type: string
examples:
- Devtools
version:
$ref: "#/components/schemas/ModVersionString"
download_count:
type: integer
example: 500
validated:
type: boolean
example: true

Platform:
type: string
enum:
Expand Down
28 changes: 28 additions & 0 deletions src/auth/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,31 @@ pub async fn invalidate_tokens_for_developer(
};
Ok(())
}

pub async fn invalidate_token_for_developer(
id: i32,
token: String,
pool: &mut PgConnection,
) -> Result<(), ApiError> {
let hash = sha256::digest(token);
let result = match sqlx::query!(
"DELETE FROM auth_tokens WHERE developer_id = $1 AND token = $2",
id,
hash
)
.execute(&mut *pool)
.await
{
Err(e) => {
log::error!("{}", e);
return Err(ApiError::DbError);
}
Ok(r) => r,
};

if result.rows_affected() == 0 {
log::error!("Couldn't delete token for developer {}", id);
return Err(ApiError::InternalError);
}
Ok(())
}
37 changes: 31 additions & 6 deletions src/endpoints/developers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub async fn add_developer_to_mod(
json: web::Json<AddDevPayload>,
auth: Auth,
) -> Result<impl Responder, ApiError> {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?;
let mut transaction = pool.begin().await.or(Err(ApiError::TransactionError))?;
if !(Developer::owns_mod(dev.id, &path.id, &mut transaction).await?) {
Expand Down Expand Up @@ -94,7 +94,7 @@ pub async fn remove_dev_from_mod(
path: web::Path<RemoveDevPath>,
auth: Auth,
) -> Result<impl Responder, ApiError> {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?;
let mut transaction = pool.begin().await.or(Err(ApiError::TransactionError))?;
if !(Developer::owns_mod(dev.id, &path.id, &mut transaction).await?) {
Expand Down Expand Up @@ -127,12 +127,37 @@ pub async fn remove_dev_from_mod(
Ok(HttpResponse::NoContent())
}

#[delete("v1/me/token")]
pub async fn delete_token(
data: web::Data<AppData>,
auth: Auth,
) -> Result<impl Responder, ApiError> {
let dev = auth.developer()?;
let token = auth.token()?;
let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?;
let mut transaction = pool.begin().await.or(Err(ApiError::TransactionError))?;
if let Err(e) =
token::invalidate_token_for_developer(dev.id, token.to_string(), &mut transaction).await
{
transaction
.rollback()
.await
.or(Err(ApiError::TransactionError))?;
return Err(e);
}
transaction
.commit()
.await
.or(Err(ApiError::TransactionError))?;
Ok(HttpResponse::NoContent())
}

#[delete("v1/me/tokens")]
pub async fn delete_tokens(
data: web::Data<AppData>,
auth: Auth,
) -> Result<impl Responder, ApiError> {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?;
let mut transaction = pool.begin().await.or(Err(ApiError::TransactionError))?;
if let Err(e) = token::invalidate_tokens_for_developer(dev.id, &mut transaction).await {
Expand Down Expand Up @@ -160,7 +185,7 @@ pub async fn update_profile(
json: web::Json<UploadProfilePayload>,
auth: Auth,
) -> Result<impl Responder, ApiError> {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?;
let mut transaction = pool.begin().await.or(Err(ApiError::TransactionError))?;
if let Err(e) = Developer::update_profile(dev.id, &json.display_name, &mut transaction).await {
Expand Down Expand Up @@ -188,7 +213,7 @@ pub async fn get_own_mods(
query: web::Query<GetOwnModsQuery>,
auth: Auth,
) -> Result<impl Responder, ApiError> {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?;
let validated = query.validated.unwrap_or(true);
let mods: Vec<SimpleDevMod> = Mod::get_all_for_dev(dev.id, validated, &mut pool).await?;
Expand All @@ -200,7 +225,7 @@ pub async fn get_own_mods(

#[get("v1/me")]
pub async fn get_me(auth: Auth) -> Result<impl Responder, ApiError> {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
Ok(HttpResponse::Ok().json(ApiResponse {
error: "".to_string(),
payload: DeveloperProfile {
Expand Down
4 changes: 2 additions & 2 deletions src/endpoints/mod_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ pub async fn create_version(
payload: web::Json<CreateQueryParams>,
auth: Auth,
) -> Result<impl Responder, ApiError> {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?;

if Mod::get_one(&path.id, &mut pool).await?.is_none() {
Expand Down Expand Up @@ -181,7 +181,7 @@ pub async fn update_version(
payload: web::Json<UpdatePayload>,
auth: Auth,
) -> Result<impl Responder, ApiError> {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
if !dev.admin {
return Err(ApiError::Forbidden);
}
Expand Down
6 changes: 3 additions & 3 deletions src/endpoints/mods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub async fn index(
let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?;

if query.pending_validation.is_some() {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
if !dev.admin {
return Err(ApiError::Forbidden);
}
Expand Down Expand Up @@ -96,7 +96,7 @@ pub async fn create(
payload: web::Json<CreateQueryParams>,
auth: Auth,
) -> Result<impl Responder, ApiError> {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?;
let mut file_path = download_geode_file(&payload.download_link).await?;
let json = ModJson::from_zip(&mut file_path, &payload.download_link)?;
Expand Down Expand Up @@ -173,7 +173,7 @@ pub async fn update_mod(
payload: web::Json<UpdateModPayload>,
auth: Auth,
) -> Result<impl Responder, ApiError> {
let dev = auth.into_developer()?;
let dev = auth.developer()?;
if !dev.admin {
return Err(ApiError::Forbidden);
}
Expand Down
46 changes: 37 additions & 9 deletions src/extractors/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,25 @@ use crate::{
};

pub struct Auth {
pub developer: Option<FetchedDeveloper>,
developer: Option<FetchedDeveloper>,
token: Option<Uuid>,
}

impl Auth {
/**
* Returns Ok(developer) if token was valid in request or returns ApiError::Unauthorized otherwise
*/
pub fn into_developer(self) -> Result<FetchedDeveloper, ApiError> {
match self.developer {
pub fn developer(&self) -> Result<FetchedDeveloper, ApiError> {
match &self.developer {
None => Err(ApiError::Unauthorized),
Some(d) => Ok(d),
Some(d) => Ok(d.clone()),
}
}

pub fn token(&self) -> Result<Uuid, ApiError> {
match self.token {
None => Err(ApiError::Unauthorized),
Some(t) => Ok(t),
}
}
}
Expand All @@ -34,21 +42,35 @@ impl FromRequest for Auth {
let headers = req.headers().clone();
Box::pin(async move {
let token = match headers.get("Authorization") {
None => return Ok(Auth { developer: None }),
None => {
return Ok(Auth {
developer: None,
token: None,
})
}
Some(t) => match t.to_str() {
Err(e) => {
log::error!("Failed to parse auth token: {}", e);
return Ok(Auth { developer: None });
return Ok(Auth {
developer: None,
token: None,
});
}
Ok(str) => {
let split = str.split(' ').collect::<Vec<&str>>();
if split.len() != 2 || split[0] != "Bearer" {
return Ok(Auth { developer: None });
return Ok(Auth {
developer: None,
token: None,
});
}
match Uuid::try_parse(split[1]) {
Err(e) => {
log::error!("Failed to parse auth token {}, error: {}", str, e);
return Ok(Auth { developer: None });
return Ok(Auth {
developer: None,
token: None,
});
}
Ok(token) => token,
}
Expand All @@ -73,13 +95,19 @@ impl FromRequest for Auth {
return Err(ApiError::DbError);
}
Ok(d) => match d {
None => return Ok(Auth { developer: None }),
None => {
return Ok(Auth {
developer: None,
token: None,
})
}
Some(data) => data,
},
};

Ok(Auth {
developer: Some(developer),
token: Some(token),
})
})
}
Expand Down
Loading

0 comments on commit 6e31bfa

Please sign in to comment.