chore: rework oauth2 route scoping
This commit is contained in:
@@ -54,6 +54,8 @@ auto_derived!(
|
|||||||
ReadIdentify,
|
ReadIdentify,
|
||||||
#[serde(rename = "read:servers")]
|
#[serde(rename = "read:servers")]
|
||||||
ReadServers,
|
ReadServers,
|
||||||
|
#[serde(rename = "write:files")]
|
||||||
|
WriteFiles,
|
||||||
#[serde(rename = "events")]
|
#[serde(rename = "events")]
|
||||||
Events,
|
Events,
|
||||||
#[serde(rename = "full")]
|
#[serde(rename = "full")]
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use axum::{extract::FromRequestParts, http::request::Parts};
|
use axum::{extract::FromRequestParts, http::request::Parts};
|
||||||
|
|
||||||
|
use revolt_config::config;
|
||||||
|
use revolt_models::v0;
|
||||||
use revolt_result::{create_error, Error, Result};
|
use revolt_result::{create_error, Error, Result};
|
||||||
|
|
||||||
use crate::{Database, User};
|
use crate::{util::oauth2, Database, OAuth2Scope, User};
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl FromRequestParts<Database> for User {
|
impl FromRequestParts<Database> for User {
|
||||||
@@ -17,6 +19,24 @@ impl FromRequestParts<Database> for User {
|
|||||||
{
|
{
|
||||||
let session = db.fetch_session_by_token(session_token).await?;
|
let session = db.fetch_session_by_token(session_token).await?;
|
||||||
db.fetch_user(&session.user_id).await
|
db.fetch_user(&session.user_id).await
|
||||||
|
} else if let Some(Ok(header_oauth_token)) = parts.headers.get("x-oauth2-token").map(|v| v.to_str()) {
|
||||||
|
let config = config().await;
|
||||||
|
|
||||||
|
let claims = oauth2::decode_token(
|
||||||
|
&config.api.security.token_secret,
|
||||||
|
header_oauth_token,
|
||||||
|
).map_err(|_| create_error!(NotAuthenticated))?;
|
||||||
|
|
||||||
|
let required_scope: v0::OAuth2Scope = parts.extensions.get::<OAuth2Scope>()
|
||||||
|
.copied()
|
||||||
|
.ok_or_else(|| create_error!(NotAuthenticated))?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
if claims.scopes.contains(&v0::OAuth2Scope::Full) || claims.scopes.contains(&required_scope) {
|
||||||
|
db.fetch_user(&claims.sub).await
|
||||||
|
} else {
|
||||||
|
Err(create_error!(MissingScope { scope: required_scope.to_string() }))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(create_error!(NotAuthenticated))
|
Err(create_error!(NotAuthenticated))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use authifier::models::Session;
|
use authifier::models::Session;
|
||||||
use revolt_config::config;
|
use revolt_config::config;
|
||||||
|
use revolt_models::v0;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::request::{self, FromRequest, Outcome, Request};
|
use rocket::request::{self, FromRequest, Outcome, Request};
|
||||||
|
|
||||||
@@ -36,14 +37,22 @@ impl<'r> FromRequest<'r> for User {
|
|||||||
} else if let Some(header_oauth_token) = header_oauth_token {
|
} else if let Some(header_oauth_token) = header_oauth_token {
|
||||||
let config = config().await;
|
let config = config().await;
|
||||||
|
|
||||||
let claims = oauth2::decode_token(&config.api.security.token_secret, &header_oauth_token).ok()?;
|
let claims = oauth2::decode_token(
|
||||||
|
&config.api.security.token_secret,
|
||||||
|
&header_oauth_token,
|
||||||
|
)
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
if oauth2::scopes_can_access_route(&claims.scopes, request) {
|
let required_scope: v0::OAuth2Scope = request.local_cache(|| None::<crate::OAuth2Scope>)
|
||||||
|
.as_ref()
|
||||||
|
.copied()?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
if claims.scopes.contains(&v0::OAuth2Scope::Full) || claims.scopes.contains(&required_scope) {
|
||||||
if let Ok(user) = db.fetch_user(&claims.sub).await {
|
if let Ok(user) = db.fetch_user(&claims.sub).await {
|
||||||
return Some(user)
|
return Some(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if let Outcome::Success(session) = request.guard::<Session>().await {
|
} else if let Outcome::Success(session) = request.guard::<Session>().await {
|
||||||
if let Ok(user) = db.fetch_user(&session.user_id).await {
|
if let Ok(user) = db.fetch_user(&session.user_id).await {
|
||||||
return Some(user);
|
return Some(user);
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ impl From<crate::OAuth2Scope> for OAuth2Scope {
|
|||||||
match value {
|
match value {
|
||||||
crate::OAuth2Scope::ReadIdentify => OAuth2Scope::ReadIdentify,
|
crate::OAuth2Scope::ReadIdentify => OAuth2Scope::ReadIdentify,
|
||||||
crate::OAuth2Scope::ReadServers => OAuth2Scope::ReadServers,
|
crate::OAuth2Scope::ReadServers => OAuth2Scope::ReadServers,
|
||||||
|
crate::OAuth2Scope::WriteFiles => OAuth2Scope::WriteFiles,
|
||||||
crate::OAuth2Scope::Events => OAuth2Scope::Events,
|
crate::OAuth2Scope::Events => OAuth2Scope::Events,
|
||||||
crate::OAuth2Scope::Full => OAuth2Scope::Full,
|
crate::OAuth2Scope::Full => OAuth2Scope::Full,
|
||||||
}
|
}
|
||||||
@@ -105,6 +106,7 @@ impl From<OAuth2Scope> for crate::OAuth2Scope {
|
|||||||
match value {
|
match value {
|
||||||
OAuth2Scope::ReadIdentify => crate::OAuth2Scope::ReadIdentify,
|
OAuth2Scope::ReadIdentify => crate::OAuth2Scope::ReadIdentify,
|
||||||
OAuth2Scope::ReadServers => crate::OAuth2Scope::ReadServers,
|
OAuth2Scope::ReadServers => crate::OAuth2Scope::ReadServers,
|
||||||
|
OAuth2Scope::WriteFiles => crate::OAuth2Scope::WriteFiles,
|
||||||
OAuth2Scope::Events => crate::OAuth2Scope::Events,
|
OAuth2Scope::Events => crate::OAuth2Scope::Events,
|
||||||
OAuth2Scope::Full => crate::OAuth2Scope::Full,
|
OAuth2Scope::Full => crate::OAuth2Scope::Full,
|
||||||
}
|
}
|
||||||
|
|||||||
17
crates/core/database/src/util/oauth2/axum.rs
Normal file
17
crates/core/database/src/util/oauth2/axum.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use axum::{extract::FromRequestParts, http::request::Parts};
|
||||||
|
use revolt_result::{Error, Result};
|
||||||
|
|
||||||
|
use super::{OAuth2Scoped, scopes::OAuth2Scope};
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<S: Send + Sync, Scope: OAuth2Scope> FromRequestParts<S> for OAuth2Scoped<Scope> {
|
||||||
|
type Rejection = Error;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self> {
|
||||||
|
parts.extensions.insert(Scope::SCOPE);
|
||||||
|
|
||||||
|
Ok(OAuth2Scoped { _scope: PhantomData })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
use chrono::{TimeDelta, Utc};
|
use chrono::{TimeDelta, Utc};
|
||||||
use jsonwebtoken::{decode, encode, errors::Error, DecodingKey, EncodingKey, Header, Validation};
|
use jsonwebtoken::{decode, encode, errors::Error, DecodingKey, EncodingKey, Header, Validation};
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use revolt_models::v0;
|
|
||||||
use redis_kiss::AsyncCommands;
|
use redis_kiss::AsyncCommands;
|
||||||
|
use revolt_models::v0;
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub mod scopes;
|
||||||
|
pub use scopes::OAuth2Scoped;
|
||||||
|
|
||||||
#[cfg(feature = "rocket")]
|
#[cfg(feature = "rocket")]
|
||||||
use rocket::Request;
|
pub mod rocket;
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
pub mod axum;
|
||||||
|
|
||||||
pub use jsonwebtoken::errors::{Error as JWTError, ErrorKind as JWTErrorKind};
|
pub use jsonwebtoken::errors::{Error as JWTError, ErrorKind as JWTErrorKind};
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
@@ -15,7 +21,7 @@ use ulid::Ulid;
|
|||||||
pub enum TokenType {
|
pub enum TokenType {
|
||||||
Auth,
|
Auth,
|
||||||
Access,
|
Access,
|
||||||
Refresh
|
Refresh,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TokenType {
|
impl TokenType {
|
||||||
@@ -54,9 +60,7 @@ pub fn encode_token(
|
|||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
|
|
||||||
let exp = now
|
let exp = now.checked_add_signed(token_type.lifetime()).unwrap();
|
||||||
.checked_add_signed(token_type.lifetime())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
println!("{now:?} {exp:}");
|
println!("{now:?} {exp:}");
|
||||||
|
|
||||||
@@ -69,7 +73,7 @@ pub fn encode_token(
|
|||||||
client_id,
|
client_id,
|
||||||
scopes,
|
scopes,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
code_challange_method
|
code_challange_method,
|
||||||
};
|
};
|
||||||
|
|
||||||
let encoding_key = EncodingKey::from_secret(token_secret.as_bytes());
|
let encoding_key = EncodingKey::from_secret(token_secret.as_bytes());
|
||||||
@@ -80,48 +84,15 @@ pub fn encode_token(
|
|||||||
pub fn decode_token(token_secret: &str, code: &str) -> Result<Claims, Error> {
|
pub fn decode_token(token_secret: &str, code: &str) -> Result<Claims, Error> {
|
||||||
let decoding_key = DecodingKey::from_secret(token_secret.as_bytes());
|
let decoding_key = DecodingKey::from_secret(token_secret.as_bytes());
|
||||||
|
|
||||||
let data = decode(code, &decoding_key, &Validation::new(jsonwebtoken::Algorithm::HS256))?;
|
let data = decode(
|
||||||
|
code,
|
||||||
|
&decoding_key,
|
||||||
|
&Validation::new(jsonwebtoken::Algorithm::HS256),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(data.claims)
|
Ok(data.claims)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rocket")]
|
|
||||||
pub fn scope_can_access_route(scope: v0::OAuth2Scope, request: &Request<'_>) -> bool {
|
|
||||||
println!("{:?}", request.route());
|
|
||||||
|
|
||||||
// TODO: figure out why request.segments(0..) is skipping the first segment
|
|
||||||
let mut segments = request.uri().path().segments();
|
|
||||||
|
|
||||||
if segments.get(0) == Some("0.8") {
|
|
||||||
segments.next(); // skip first segment
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extract the function name of the route
|
|
||||||
let Some(route_name) = request
|
|
||||||
.route()
|
|
||||||
.and_then(|route| route.name.as_ref())
|
|
||||||
.map(|name| name.as_ref())
|
|
||||||
else {
|
|
||||||
return false
|
|
||||||
};
|
|
||||||
|
|
||||||
match scope {
|
|
||||||
v0::OAuth2Scope::ReadIdentify => route_name == "fetch_self",
|
|
||||||
v0::OAuth2Scope::ReadServers => route_name == "fetch_self_servers" || route_name == "fetch_server",
|
|
||||||
// This only grants access to the events websocket and not any routes
|
|
||||||
v0::OAuth2Scope::Events => false,
|
|
||||||
// TODO: maybe disallow revoking other sessions
|
|
||||||
v0::OAuth2Scope::Full => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rocket")]
|
|
||||||
pub fn scopes_can_access_route(scopes: &[v0::OAuth2Scope], request: &Request<'_>) -> bool {
|
|
||||||
scopes
|
|
||||||
.iter()
|
|
||||||
.any(|&scope| scope_can_access_route(scope, request))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_code_challange(token: &str, code_challenge: &str) -> Result<()> {
|
pub async fn add_code_challange(token: &str, code_challenge: &str) -> Result<()> {
|
||||||
let mut conn = redis_kiss::get_connection()
|
let mut conn = redis_kiss::get_connection()
|
||||||
.await
|
.await
|
||||||
@@ -130,7 +101,7 @@ pub async fn add_code_challange(token: &str, code_challenge: &str) -> Result<()>
|
|||||||
conn.pset_ex::<_, _, ()>(
|
conn.pset_ex::<_, _, ()>(
|
||||||
format!("oauth2:{token}:code_challenge"),
|
format!("oauth2:{token}:code_challenge"),
|
||||||
code_challenge,
|
code_challenge,
|
||||||
TokenType::Access.lifetime().num_milliseconds() as usize
|
TokenType::Access.lifetime().num_milliseconds() as usize,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| create_error!(InternalError))?;
|
.map_err(|_| create_error!(InternalError))?;
|
||||||
53
crates/core/database/src/util/oauth2/rocket.rs
Normal file
53
crates/core/database/src/util/oauth2/rocket.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use std::{convert::Infallible, marker::PhantomData};
|
||||||
|
|
||||||
|
use revolt_okapi::openapi3::{OAuthFlows, SecurityScheme, SecuritySchemeData};
|
||||||
|
use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
|
||||||
|
use rocket::{request::{self, FromRequest}, Request};
|
||||||
|
|
||||||
|
use super::{OAuth2Scoped, scopes::OAuth2Scope};
|
||||||
|
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r, Scope: OAuth2Scope> FromRequest<'r> for OAuth2Scoped<Scope> {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
request.local_cache(|| Some(Scope::SCOPE));
|
||||||
|
|
||||||
|
request::Outcome::Success(OAuth2Scoped { _scope: PhantomData })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, Scope: OAuth2Scope> OpenApiFromRequest<'r> for OAuth2Scoped<Scope> {
|
||||||
|
fn from_request_input(
|
||||||
|
_gen: &mut revolt_rocket_okapi::r#gen::OpenApiGenerator,
|
||||||
|
_name: String,
|
||||||
|
_required: bool,
|
||||||
|
) -> revolt_rocket_okapi::Result<revolt_rocket_okapi::request::RequestHeaderInput> {
|
||||||
|
Ok(RequestHeaderInput::Security(
|
||||||
|
"OAuth2".to_owned(),
|
||||||
|
SecurityScheme {
|
||||||
|
description: None,
|
||||||
|
extensions: Default::default(),
|
||||||
|
data: SecuritySchemeData::OAuth2 {
|
||||||
|
flows: OAuthFlows::AuthorizationCode {
|
||||||
|
authorization_url: "todo".to_string(),
|
||||||
|
token_url: "todo".to_string(),
|
||||||
|
refresh_url: Some("todo".to_string()),
|
||||||
|
scopes: revolt_okapi::map! {
|
||||||
|
"read:identify".to_string() => "".to_string(),
|
||||||
|
"read:servers".to_string() => "".to_string(),
|
||||||
|
"write:files".to_string() => "".to_string(),
|
||||||
|
"events".to_string() => "".to_string(),
|
||||||
|
"full".to_string() => "".to_string(),
|
||||||
|
},
|
||||||
|
extensions: Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
revolt_okapi::map! {
|
||||||
|
"OAuth2".to_owned() => vec![Scope::NAME.to_owned()]
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
27
crates/core/database/src/util/oauth2/scopes.rs
Normal file
27
crates/core/database/src/util/oauth2/scopes.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
pub struct OAuth2Scoped<Scope> {
|
||||||
|
pub(crate) _scope: PhantomData<Scope>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OAuth2Scope {
|
||||||
|
const NAME: &'static str;
|
||||||
|
const SCOPE: crate::OAuth2Scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! define_oauth2_scope {
|
||||||
|
($struct_name:ident, $name:literal) => {
|
||||||
|
pub struct $struct_name;
|
||||||
|
|
||||||
|
impl OAuth2Scope for $struct_name {
|
||||||
|
const NAME: &'static str = $name;
|
||||||
|
const SCOPE: crate::OAuth2Scope = crate::OAuth2Scope::$struct_name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
define_oauth2_scope!(ReadIdentify, "read:identify");
|
||||||
|
define_oauth2_scope!(ReadServers, "read:servers");
|
||||||
|
define_oauth2_scope!(WriteFiles, "write:files");
|
||||||
|
define_oauth2_scope!(Events, "events");
|
||||||
|
define_oauth2_scope!(Full, "full");
|
||||||
@@ -91,6 +91,8 @@ auto_derived!(
|
|||||||
ReadIdentify,
|
ReadIdentify,
|
||||||
#[cfg_attr(feature = "serde", serde(rename = "read:servers"))]
|
#[cfg_attr(feature = "serde", serde(rename = "read:servers"))]
|
||||||
ReadServers,
|
ReadServers,
|
||||||
|
#[cfg_attr(feature = "serde", serde(rename = "write:files"))]
|
||||||
|
WriteFiles,
|
||||||
#[cfg_attr(feature = "serde", serde(rename = "events"))]
|
#[cfg_attr(feature = "serde", serde(rename = "events"))]
|
||||||
Events,
|
Events,
|
||||||
#[cfg_attr(feature = "serde", serde(rename = "full"))]
|
#[cfg_attr(feature = "serde", serde(rename = "full"))]
|
||||||
@@ -103,6 +105,7 @@ impl Display for OAuth2Scope {
|
|||||||
f.write_str(match self {
|
f.write_str(match self {
|
||||||
OAuth2Scope::ReadIdentify => "read:identify",
|
OAuth2Scope::ReadIdentify => "read:identify",
|
||||||
OAuth2Scope::ReadServers => "read:servers",
|
OAuth2Scope::ReadServers => "read:servers",
|
||||||
|
OAuth2Scope::WriteFiles => "write:files",
|
||||||
OAuth2Scope::Events => "events",
|
OAuth2Scope::Events => "events",
|
||||||
OAuth2Scope::Full => "full",
|
OAuth2Scope::Full => "full",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -203,7 +203,13 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
"Sync",
|
"Sync",
|
||||||
"Web Push"
|
"Web Push"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"name": "OAuth2",
|
||||||
|
"tags": [
|
||||||
|
"OAuth2",
|
||||||
|
]
|
||||||
|
},
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -375,6 +381,11 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
description: Some("Send messages from 3rd party services".to_owned()),
|
description: Some("Send messages from 3rd party services".to_owned()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
Tag {
|
||||||
|
name: "OAuth2".to_owned(),
|
||||||
|
description: Some("Integrate Revolt into 3rd party applications".to_owned()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use revolt_database::{
|
use revolt_database::{
|
||||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
util::{oauth2::{scopes, OAuth2Scoped}, permissions::DatabasePermissionQuery, reference::Reference},
|
||||||
Database, User,
|
Database, User,
|
||||||
};
|
};
|
||||||
use revolt_models::v0;
|
use revolt_models::v0;
|
||||||
@@ -14,6 +14,7 @@ use rocket::{serde::json::Json, State};
|
|||||||
#[get("/<target>?<options..>")]
|
#[get("/<target>?<options..>")]
|
||||||
pub async fn fetch_server(
|
pub async fn fetch_server(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
_oauth2_scope: OAuth2Scoped<scopes::ReadServers>,
|
||||||
user: User,
|
user: User,
|
||||||
target: Reference<'_>,
|
target: Reference<'_>,
|
||||||
options: v0::OptionsFetchServer,
|
options: v0::OptionsFetchServer,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use revolt_database::User;
|
use revolt_database::{User, util::oauth2::{OAuth2Scoped, scopes}};
|
||||||
use revolt_models::v0;
|
use revolt_models::v0;
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
@@ -8,6 +8,9 @@ use rocket::serde::json::Json;
|
|||||||
/// Retrieve your user information.
|
/// Retrieve your user information.
|
||||||
#[openapi(tag = "User Information")]
|
#[openapi(tag = "User Information")]
|
||||||
#[get("/@me")]
|
#[get("/@me")]
|
||||||
pub async fn fetch_self(user: User) -> Result<Json<v0::User>> {
|
pub async fn fetch_self(
|
||||||
|
_oauth2_scope: OAuth2Scoped<scopes::ReadIdentify>,
|
||||||
|
user: User
|
||||||
|
) -> Result<Json<v0::User>> {
|
||||||
Ok(Json(user.into_self(false).await))
|
Ok(Json(user.into_self(false).await))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use revolt_database::{util::permissions::DatabasePermissionQuery, Database, User};
|
use revolt_database::{util::{oauth2::{scopes, OAuth2Scoped}, permissions::DatabasePermissionQuery}, Database, User};
|
||||||
use revolt_models::v0;
|
use revolt_models::v0;
|
||||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
@@ -11,6 +11,7 @@ use rocket::{serde::json::Json, State};
|
|||||||
#[get("/@me/servers?<options..>")]
|
#[get("/@me/servers?<options..>")]
|
||||||
pub async fn fetch_self_servers(
|
pub async fn fetch_self_servers(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
_oauth2_scope: OAuth2Scoped<scopes::ReadServers>,
|
||||||
user: User,
|
user: User,
|
||||||
options: v0::OptionsFetchServer,
|
options: v0::OptionsFetchServer,
|
||||||
) -> Result<Json<Vec<v0::FetchServerResponse>>> {
|
) -> Result<Json<Vec<v0::FetchServerResponse>>> {
|
||||||
|
|||||||
Reference in New Issue
Block a user