feat: update to rAuth v1

This commit is contained in:
Paul Makles
2022-06-04 18:20:38 +01:00
parent 020f2a1b1a
commit 7390b3c087
27 changed files with 596 additions and 1022 deletions

79
.env Normal file
View File

@@ -0,0 +1,79 @@
# MongoDB URI
MONGODB=mongodb://localhost
# URL to where the Revolt app is publicly accessible
REVOLT_APP_URL=http://local.revolt.chat:5000
# URL to where the API is publicly accessible
REVOLT_PUBLIC_URL=http://local.revolt.chat:8000
VITE_API_URL=http://local.revolt.chat:8000
# URL to where the WebSocket server is publicly accessible
REVOLT_EXTERNAL_WS_URL=ws://local.revolt.chat:9000
# URL to where Autumn is publicly available
AUTUMN_PUBLIC_URL=http://local.revolt.chat:3000
# URL to where January is publicly available
JANUARY_PUBLIC_URL=http://local.revolt.chat:7000
# URL to where Vortex is publicly available
# VOSO_PUBLIC_URL=https://voso.revolt.chat
##
## hCaptcha Settings
##
# If you are sure that you don't want to use hCaptcha, set to 1.
REVOLT_UNSAFE_NO_CAPTCHA=1
# hCaptcha API key
# REVOLT_HCAPTCHA_KEY=0x0000000000000000000000000000000000000000
# hCaptcha site key
# REVOLT_HCAPTCHA_SITEKEY=10000000-ffff-ffff-ffff-000000000001
##
## Email Settings
##
# If you are sure that you don't want to use email verification, set to 1.
REVOLT_UNSAFE_NO_EMAIL=1
# SMTP host
# REVOLT_SMTP_HOST=smtp.example.com
# SMTP username
# REVOLT_SMTP_USERNAME=noreply@example.com
# SMTP password
# REVOLT_SMTP_PASSWORD=CHANGEME
# SMTP From header
# REVOLT_SMTP_FROM=Revolt <noreply@example.com>
##
## Application Settings
##
# Whether to only allow users to sign up if they have an invite code
REVOLT_INVITE_ONLY=0
# Maximum number of people that can be in a group chat
REVOLT_MAX_GROUP_SIZE=150
# VAPID keys for push notifications
# Generate using this guide: https://gitlab.insrt.uk/revolt/delta/-/wikis/vapid
# --> Please replace these keys before going into production! <--
REVOLT_VAPID_PRIVATE_KEY=LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJSUWpyTWxLRnBiVWhsUHpUbERvcEliYk1yeVNrNXpKYzVYVzIxSjJDS3hvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFWnkrQkg2TGJQZ2hEa3pEempXOG0rUXVPM3pCajRXT1phdkR6ZU00c0pqbmFwd1psTFE0WAp1ZDh2TzVodU94QWhMQlU3WWRldVovWHlBdFpWZmNyQi9BPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
REVOLT_VAPID_PUBLIC_KEY=BGcvgR-i2z4IQ5Mw841vJvkLjt8wY-FjmWrw83jOLCY52qcGZS0OF7nfLzuYbjsQISwVO2HXrmf18gLWVX3Kwfw=
##
## Vortex configuration
##
# VOSO_MANAGE_TOKEN=CHANGEME

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
Rocket.toml Rocket.toml
target target
.data

1026
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -44,7 +44,6 @@ async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"]
# internal util # internal util
lettre = "0.10.0-alpha.4" lettre = "0.10.0-alpha.4"
rauth = { git = "https://github.com/insertish/rauth", rev = "001a9698c56cea79e69e4ae71d7bc2cb48aec1a6" }
# redis # redis
redis = { version = "0.21.2", features = ["async-std-comp"] } redis = { version = "0.21.2", features = ["async-std-comp"] }
@@ -52,15 +51,15 @@ mobc = { version = "0.7.3" }
mobc-redis = { version = "0.7.0", default-features = false, features = ["async-std-comp"] } mobc-redis = { version = "0.7.0", default-features = false, features = ["async-std-comp"] }
# web # web
rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "rc1" } rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
rocket = { version = "0.5.0-rc.1", default-features = false, features = ["json"] } rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "master" }
mongodb = { version = "1.2.2", features = ["async-std-runtime"], default-features = false } rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "a82cb5d23da4787e75d6f3c795189ce31e1590c4" }
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" } rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" }
# spec generation # spec generation
schemars = "0.8.8" schemars = "0.8.8"
# rocket_okapi = "0.8.0-rc.1" # rocket_okapi = "0.8.0-rc.1"
rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "dcf0df115596ee07a587a7a543cddf3d7944645b", features = [ "swagger" ] } rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "a1048d0c8cd771e424ec97d33d825c32e06aa120", features = [ "swagger" ] }
# quark # quark
revolt-quark = { path = "../quark" } revolt-quark = { path = "../quark" }

View File

@@ -13,10 +13,10 @@ pub mod util;
pub mod version; pub mod version;
use log::info; use log::info;
use rauth::{ use revolt_quark::rauth::config::{
config::{Captcha, Config, EmailVerification, SMTPSettings, Template, Templates}, Captcha, Config, EmailVerificationConfig, SMTPSettings, Template, Templates,
logic::Auth,
}; };
use revolt_quark::rauth::RAuth;
use revolt_quark::variables::delta::{ use revolt_quark::variables::delta::{
APP_URL, HCAPTCHA_KEY, INVITE_ONLY, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD, SMTP_USERNAME, APP_URL, HCAPTCHA_KEY, INVITE_ONLY, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD, SMTP_USERNAME,
USE_EMAIL, USE_HCAPTCHA, USE_EMAIL, USE_HCAPTCHA,
@@ -25,8 +25,8 @@ use revolt_quark::DatabaseInfo;
use rocket_cors::AllowedOrigins; use rocket_cors::AllowedOrigins;
use std::str::FromStr; use std::str::FromStr;
#[async_std::main] #[launch]
async fn main() { async fn rocket() -> _ {
let _guard = revolt_quark::setup_logging(); let _guard = revolt_quark::setup_logging();
info!( info!(
@@ -58,7 +58,7 @@ async fn main() {
let mut config = Config { let mut config = Config {
email_verification: if *USE_EMAIL { email_verification: if *USE_EMAIL {
EmailVerification::Enabled { EmailVerificationConfig::Enabled {
smtp: SMTPSettings { smtp: SMTPSettings {
from: (*SMTP_FROM).to_string(), from: (*SMTP_FROM).to_string(),
host: (*SMTP_HOST).to_string(), host: (*SMTP_HOST).to_string(),
@@ -86,7 +86,7 @@ async fn main() {
}, },
} }
} else { } else {
EmailVerification::Disabled EmailVerificationConfig::Disabled
}, },
..Default::default() ..Default::default()
}; };
@@ -101,23 +101,20 @@ async fn main() {
}; };
} }
// Setup database
let db = DatabaseInfo::Auto.connect().await.unwrap(); let db = DatabaseInfo::Auto.connect().await.unwrap();
db.migrate_database().await.unwrap(); db.migrate_database().await.unwrap();
// This is entirely temporary code until rauth is migrated to quark. // Setup rAuth
// (and / or otherwise gets updated to MongoDB v2 driver) let rauth = RAuth {
let mongo_db = mongodb::Client::with_uri_str( database: db.clone().into(),
&std::env::var("MONGODB").unwrap_or_else(|_| "mongodb://localhost".to_string()), config,
) };
.await
.expect("Failed to init db connection.");
rauth::entities::sync_models(&mongo_db.database("revolt")).await;
// Launch background task workers. // Launch background task workers.
async_std::task::spawn(revolt_quark::tasks::start_workers(db.clone())); async_std::task::spawn(revolt_quark::tasks::start_workers(db.clone()));
let auth = Auth::new(mongo_db.database("revolt"), config); // Configure Rocket
let rocket = rocket::build(); let rocket = rocket::build();
routes::mount(rocket) routes::mount(rocket)
.mount("/", rocket_cors::catch_all_options_routes()) .mount("/", rocket_cors::catch_all_options_routes())
@@ -129,14 +126,11 @@ async fn main() {
..Default::default() ..Default::default()
}), }),
) )
.manage(auth) .manage(rauth)
.manage(db) .manage(db)
.manage(cors.clone()) .manage(cors.clone())
.attach(util::ratelimiter::RatelimitFairing) .attach(util::ratelimiter::RatelimitFairing)
.attach(cors) .attach(cors)
.launch()
.await
.unwrap();
} }
/// Resolve asset /// Resolve asset

View File

@@ -25,8 +25,9 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
"/channels" => channels::routes(), "/channels" => channels::routes(),
"/servers" => servers::routes(), "/servers" => servers::routes(),
"/invites" => invites::routes(), "/invites" => invites::routes(),
"/auth/account" => rauth::web::account::routes(), "/auth/account" => rocket_rauth::routes::account::routes(),
"/auth/session" => rauth::web::session::routes(), "/auth/session" => rocket_rauth::routes::session::routes(),
"/auth/mfa" => rocket_rauth::routes::mfa::routes(),
"/onboard" => onboard::routes(), "/onboard" => onboard::routes(),
"/push" => push::routes(), "/push" => push::routes(),
"/sync" => sync::routes(), "/sync" => sync::routes(),

View File

@@ -1,7 +1,6 @@
use crate::util::regex::RE_USERNAME; use crate::util::regex::RE_USERNAME;
use revolt_quark::{models::User, Database, EmptyResponse, Error, Result}; use revolt_quark::{models::User, rauth::models::Session, Database, EmptyResponse, Error, Result};
use rauth::entities::Session;
use rocket::{serde::json::Json, State}; use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;

View File

@@ -1,5 +1,4 @@
use rauth::entities::Session; use revolt_quark::{models::User, rauth::models::Session};
use revolt_quark::models::User;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use serde::Serialize; use serde::Serialize;

View File

@@ -1,9 +1,11 @@
use revolt_quark::{EmptyResponse, Error, Result}; use revolt_quark::{
rauth::{
use rauth::{ models::{Session, WebPushSubscription},
entities::{Model, Session, WebPushSubscription}, RAuth,
logic::Auth, },
EmptyResponse, Error, Result,
}; };
use rocket::{serde::json::Json, State}; use rocket::{serde::json::Json, State};
/// # Push Subscribe /// # Push Subscribe
@@ -14,13 +16,13 @@ use rocket::{serde::json::Json, State};
#[openapi(tag = "Web Push")] #[openapi(tag = "Web Push")]
#[post("/subscribe", data = "<data>")] #[post("/subscribe", data = "<data>")]
pub async fn req( pub async fn req(
auth: &State<Auth>, rauth: &State<RAuth>,
mut session: Session, mut session: Session,
data: Json<WebPushSubscription>, data: Json<WebPushSubscription>,
) -> Result<EmptyResponse> { ) -> Result<EmptyResponse> {
session.subscription = Some(data.into_inner()); session.subscription = Some(data.into_inner());
session session
.save(&auth.db, None) .save(&rauth)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
.map_err(|_| Error::DatabaseError { .map_err(|_| Error::DatabaseError {

View File

@@ -1,9 +1,8 @@
use revolt_quark::{EmptyResponse, Error, Result}; use revolt_quark::{
rauth::{models::Session, RAuth},
use rauth::{ EmptyResponse, Error, Result,
entities::{Model, Session},
logic::Auth,
}; };
use rocket::State; use rocket::State;
/// # Unsubscribe /// # Unsubscribe
@@ -11,10 +10,10 @@ use rocket::State;
/// Remove the Web Push subscription associated with the current session. /// Remove the Web Push subscription associated with the current session.
#[openapi(tag = "Web Push")] #[openapi(tag = "Web Push")]
#[post("/unsubscribe")] #[post("/unsubscribe")]
pub async fn req(auth: &State<Auth>, mut session: Session) -> Result<EmptyResponse> { pub async fn req(rauth: &State<RAuth>, mut session: Session) -> Result<EmptyResponse> {
session.subscription = None; session.subscription = None;
session session
.save(&auth.db, None) .save(&rauth)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
.map_err(|_| Error::DatabaseError { .map_err(|_| Error::DatabaseError {

View File

@@ -1,5 +1,5 @@
use revolt_quark::models::User; use revolt_quark::models::User;
use revolt_quark::r#impl::generic::users::user_settings::UserSettingsImpl; use revolt_quark::r#impl::UserSettingsImpl;
use revolt_quark::{Db, EmptyResponse, Result}; use revolt_quark::{Db, EmptyResponse, Result};
use chrono::prelude::*; use chrono::prelude::*;

View File

@@ -1,6 +1,5 @@
use crate::util::regex::RE_USERNAME; use crate::util::regex::RE_USERNAME;
use rauth::entities::Account; use revolt_quark::{models::User, rauth::models::Account, Database, Error, Result};
use revolt_quark::{models::User, Database, Error, Result};
use rocket::{serde::json::Json, State}; use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;

View File

@@ -1,5 +1,4 @@
use async_std::sync::Mutex; use async_std::sync::Mutex;
use mongodb::bson::doc;
use revolt_quark::{Error, Result}; use revolt_quark::{Error, Result};
use rocket::http::Status; use rocket::http::Status;
use rocket::request::{FromRequest, Outcome}; use rocket::request::{FromRequest, Outcome};

View File

@@ -8,6 +8,7 @@ use std::hash::Hasher;
use std::ops::Add; use std::ops::Add;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
use revolt_quark::rauth::models::Session;
use rocket::fairing::{Fairing, Info, Kind}; use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::uri::Origin; use rocket::http::uri::Origin;
use rocket::http::{Method, Status}; use rocket::http::{Method, Status};
@@ -207,10 +208,9 @@ impl<'r> FromRequest<'r> for Ratelimiter {
let ratelimiter = request let ratelimiter = request
.local_cache_async(async { .local_cache_async(async {
use rocket::outcome::Outcome; use rocket::outcome::Outcome;
let identifier = if let Outcome::Success(session) = let identifier = if let Outcome::Success(session) = request.guard::<Session>().await
request.guard::<rauth::entities::Session>().await
{ {
session.id.expect("`id` on User") session.id
} else { } else {
to_real_ip(request) to_real_ip(request)
}; };

View File

@@ -8,8 +8,9 @@ edition = "2021"
[features] [features]
mongo = [ "mongodb" ] mongo = [ "mongodb" ]
rocket_impl = [ "rocket", "rocket_empty" ] rocket_impl = [ "rocket", "rocket_empty", "rauth/database-mongodb", "rauth/rocket_impl", "rauth/okapi_impl" ]
test = [ "async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl", "rauth" ]
test = [ "async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl" ]
default = [ "test" ] default = [ "test" ]
[dependencies] [dependencies]
@@ -26,8 +27,8 @@ bson = { version = "2.1.0", features = ["chrono-0_4"] }
# Spec Generation # Spec Generation
schemars = "0.8.8" schemars = "0.8.8"
okapi = { git = "https://github.com/insertish/okapi", rev = "dcf0df115596ee07a587a7a543cddf3d7944645b" } okapi = { git = "https://github.com/insertish/okapi", rev = "a1048d0c8cd771e424ec97d33d825c32e06aa120" }
rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "dcf0df115596ee07a587a7a543cddf3d7944645b" } rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "a1048d0c8cd771e424ec97d33d825c32e06aa120" }
# okapi = "0.7.0-rc.1" # okapi = "0.7.0-rc.1"
# rocket_okapi = "0.8.0-rc.1" # rocket_okapi = "0.8.0-rc.1"
@@ -63,10 +64,12 @@ base64 = "0.13.0"
web-push = "0.7.2" web-push = "0.7.2"
# Implementations # Implementations
rauth = { optional = true, git = "https://github.com/insertish/rauth", rev = "001a9698c56cea79e69e4ae71d7bc2cb48aec1a6" } rocket_http = { optional = true, version = "0.5.0-rc.2" }
rocket = { optional = true, version = "=0.5.0-rc.1", default-features = false, features = ["json"] } rocket = { optional = true, version = "0.5.0-rc.2", default-features = false, features = ["json"] }
rocket_http = { optional = true, version = "=0.5.0-rc.1" } rocket_empty = { optional = true, git = "https://github.com/insertish/rocket_empty", branch = "master" }
rocket_empty = { optional = true, git = "https://github.com/insertish/rocket_empty", branch = "rc1" }
# rAuth
rauth = { git = "https://github.com/insertish/rauth", rev = "a82cb5d23da4787e75d6f3c795189ce31e1590c4", features = [ "async-std-runtime" ] }
# Sentry # Sentry
sentry = "0.25.0" sentry = "0.25.0"

View File

@@ -1,8 +1,7 @@
use std::env; use std::env;
use std::ops::Deref; use std::ops::Deref;
use crate::r#impl::dummy::DummyDb; use crate::r#impl::{DummyDb, MongoDb};
use crate::r#impl::mongo::MongoDb;
use crate::AbstractDatabase; use crate::AbstractDatabase;
/// Database information to use to create a client /// Database information to use to create a client
@@ -61,3 +60,14 @@ impl Deref for Database {
} }
} }
} }
impl From<Database> for rauth::Database {
fn from(val: Database) -> Self {
match val {
Database::Dummy(_) => rauth::Database::default(),
Database::MongoDb(MongoDb(client)) => {
rauth::Database::MongoDb(rauth::database::MongoDb(client.database("revolt")))
}
}
}
}

View File

@@ -8,9 +8,6 @@ use crate::{perms, Database, Error, Result};
use futures::try_join; use futures::try_join;
use impl_ops::impl_op_ex_commutative; use impl_ops::impl_op_ex_commutative;
use okapi::openapi3::{SecurityScheme, SecuritySchemeData};
use rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use std::ops; use std::ops;
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 }); impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
@@ -359,72 +356,3 @@ impl User {
} }
} }
} }
use rauth::entities::Session;
use rocket::http::Status;
use rocket::request::{self, FromRequest, Outcome, Request};
#[rocket::async_trait]
impl<'r> FromRequest<'r> for User {
type Error = rauth::util::Error;
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let user: &Option<User> = request
.local_cache_async(async {
let db = request
.rocket()
.state::<Database>()
.expect("Database state not reachable!");
let header_bot_token = request
.headers()
.get("x-bot-token")
.next()
.map(|x| x.to_string());
if let Some(bot_token) = header_bot_token {
if let Ok(user) = User::from_token(db, &bot_token, UserHint::Bot).await {
return Some(user);
}
} else if let Outcome::Success(session) = request.guard::<Session>().await {
// This uses a guard so can't really easily be refactored into from_token at this stage.
if let Ok(user) = db.fetch_user(&session.user_id).await {
return Some(user);
}
}
None
})
.await;
if let Some(user) = user {
Outcome::Success(user.clone())
} else {
Outcome::Failure((Status::Unauthorized, rauth::util::Error::InvalidSession))
}
}
}
impl<'r> OpenApiFromRequest<'r> for User {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> rocket_okapi::Result<RequestHeaderInput> {
let mut requirements = schemars::Map::new();
requirements.insert("Api Key".to_owned(), vec![]);
Ok(RequestHeaderInput::Security(
"Api Key".to_owned(),
SecurityScheme {
data: SecuritySchemeData::ApiKey {
name: "x-session-token".to_owned(),
location: "header".to_owned(),
},
description: Some("Session Token".to_owned()),
extensions: schemars::Map::new(),
},
requirements,
))
}
}

View File

@@ -1,3 +1,10 @@
pub mod dummy; mod dummy;
pub mod generic; mod generic;
pub mod mongo; mod mongo;
#[cfg(feature = "rocket_impl")]
mod rocket;
pub use self::generic::users::user_settings::UserSettingsImpl;
pub use dummy::DummyDb;
pub use mongo::MongoDb;

View File

@@ -17,7 +17,7 @@ struct MigrationInfo {
revision: i32, revision: i32,
} }
pub const LATEST_REVISION: i32 = 15; pub const LATEST_REVISION: i32 = 16;
pub async fn migrate_database(db: &MongoDb) { pub async fn migrate_database(db: &MongoDb) {
let migrations = db.col::<Document>("migrations"); let migrations = db.col::<Document>("migrations");
@@ -603,6 +603,15 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.unwrap(); .unwrap();
} }
if revision <= 15 {
info!("Running migration [revision 15 / 04-06-2022]: Migrate rAuth to latest version.");
let db = rauth::Database::MongoDb(rauth::database::MongoDb(db.db()));
db.run_migration(rauth::Migration::M2022_06_03EnsureUpToSpec)
.await
.unwrap();
}
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`. // Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
// Reminder to update LATEST_REVISION when adding new migrations. // Reminder to update LATEST_REVISION when adding new migrations.

View File

@@ -0,0 +1,73 @@
use okapi::openapi3::{SecurityScheme, SecuritySchemeData};
use rauth::models::Session;
use rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use rocket::http::Status;
use rocket::request::{self, FromRequest, Outcome, Request};
use crate::models::user::UserHint;
use crate::models::User;
use crate::Database;
#[rocket::async_trait]
impl<'r> FromRequest<'r> for User {
type Error = rauth::Error;
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let user: &Option<User> = request
.local_cache_async(async {
let db = request.rocket().state::<Database>().expect("`Database`");
let header_bot_token = request
.headers()
.get("x-bot-token")
.next()
.map(|x| x.to_string());
if let Some(bot_token) = header_bot_token {
if let Ok(user) = User::from_token(db, &bot_token, UserHint::Bot).await {
return Some(user);
}
} else if let Outcome::Success(session) = request.guard::<Session>().await {
// This uses a guard so can't really easily be refactored into from_token at this stage.
if let Ok(user) = db.fetch_user(&session.user_id).await {
return Some(user);
}
}
None
})
.await;
if let Some(user) = user {
Outcome::Success(user.clone())
} else {
Outcome::Failure((Status::Unauthorized, rauth::Error::InvalidSession))
}
}
}
impl<'r> OpenApiFromRequest<'r> for User {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> rocket_okapi::Result<RequestHeaderInput> {
let mut requirements = schemars::Map::new();
requirements.insert("Api Key".to_owned(), vec![]);
Ok(RequestHeaderInput::Security(
"Api Key".to_owned(),
SecurityScheme {
data: SecuritySchemeData::ApiKey {
name: "x-session-token".to_owned(),
location: "header".to_owned(),
},
description: Some("Session Token".to_owned()),
extensions: schemars::Map::new(),
},
requirements,
))
}
}

View File

@@ -18,6 +18,7 @@ extern crate bitfield;
pub extern crate bson; pub extern crate bson;
pub use iso8601_timestamp::Timestamp; pub use iso8601_timestamp::Timestamp;
pub use rauth;
pub use redis_kiss; pub use redis_kiss;
pub mod events; pub mod events;
@@ -27,6 +28,9 @@ pub mod presence;
pub mod tasks; pub mod tasks;
pub mod types; pub mod types;
#[cfg(feature = "rocket_impl")]
pub mod web;
mod database; mod database;
mod permissions; mod permissions;
mod traits; mod traits;
@@ -38,38 +42,11 @@ pub use traits::*;
pub use permissions::defn::*; pub use permissions::defn::*;
pub use permissions::{get_relationship, perms}; pub use permissions::{get_relationship, perms};
pub use util::r#ref::Ref; pub use util::{
pub use util::result::{Error, Result}; log::setup_logging,
pub use util::variables; r#ref::Ref,
result::{Error, Result},
variables,
};
#[cfg(feature = "rocket_impl")] pub use web::{Db, EmptyResponse};
pub use rocket_empty::EmptyResponse;
#[cfg(feature = "rocket_impl")]
use rocket::State;
#[cfg(feature = "rocket_impl")]
pub type Db = State<Database>;
/// Configure logging and common Rust variables
pub fn setup_logging() -> sentry::ClientInitGuard {
dotenv::dotenv().ok();
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info");
}
if std::env::var("ROCKET_ADDRESS").is_err() {
std::env::set_var("ROCKET_ADDRESS", "0.0.0.0");
}
pretty_env_logger::init();
sentry::init((
"https://62fd0e02c5354905b4e286757f4beb16@sentry.insert.moe/4",
sentry::ClientOptions {
release: sentry::release_name!(),
..Default::default()
},
))
}

View File

@@ -18,7 +18,7 @@ pub async fn start_workers(db: Database) {
task::spawn(ack::worker(db.clone())); task::spawn(ack::worker(db.clone()));
task::spawn(last_message_id::worker(db.clone())); task::spawn(last_message_id::worker(db.clone()));
task::spawn(process_embeds::worker(db.clone())); task::spawn(process_embeds::worker(db.clone()));
task::spawn(web_push::worker(db.clone())); task::spawn(web_push::worker(db.clone().into()));
} }
} }

View File

@@ -1,9 +1,8 @@
use crate::bson::doc;
use crate::util::variables::delta::VAPID_PRIVATE_KEY; use crate::util::variables::delta::VAPID_PRIVATE_KEY;
use crate::{bson::doc, r#impl::mongo::MongoDb, Database};
use deadqueue::limited::Queue; use deadqueue::limited::Queue;
use futures::StreamExt; use rauth::Database;
use rauth::entities::Session;
use web_push::{ use web_push::{
ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient, ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient,
WebPushMessageBuilder, WebPushMessageBuilder,
@@ -43,90 +42,69 @@ pub async fn worker(db: Database) {
let key = base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE) let key = base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE)
.expect("valid `VAPID_PRIVATE_KEY`"); .expect("valid `VAPID_PRIVATE_KEY`");
if let Database::MongoDb(MongoDb(db)) = db { loop {
loop { let task = Q.pop().await;
let task = Q.pop().await;
// ! FIXME: this is hard-coded until rauth is merged into quark if let Ok(sessions) = db.find_sessions_with_subscription(&task.recipients).await {
if let Ok(mut cursor) = db for session in sessions {
.database("revolt") if let Some(sub) = session.subscription {
.collection::<Session>("sessions") let subscription = SubscriptionInfo {
.find( endpoint: sub.endpoint,
doc! { keys: SubscriptionKeys {
"user_id": { auth: sub.auth,
"$in": task.recipients p256dh: sub.p256dh,
}, },
"subscription": { };
"$exists": true
}
},
None,
)
.await
{
while let Some(Ok(session)) = cursor.next().await {
if let Some(sub) = session.subscription {
let subscription = SubscriptionInfo {
endpoint: sub.endpoint,
keys: SubscriptionKeys {
auth: sub.auth,
p256dh: sub.p256dh,
},
};
match WebPushMessageBuilder::new(&subscription) { match WebPushMessageBuilder::new(&subscription) {
Ok(mut builder) => { Ok(mut builder) => {
match VapidSignatureBuilder::from_pem( match VapidSignatureBuilder::from_pem(
std::io::Cursor::new(&key), std::io::Cursor::new(&key),
&subscription, &subscription,
) { ) {
Ok(sig_builder) => match sig_builder.build() { Ok(sig_builder) => match sig_builder.build() {
Ok(signature) => { Ok(signature) => {
builder.set_vapid_signature(signature); builder.set_vapid_signature(signature);
builder.set_payload( builder.set_payload(
ContentEncoding::AesGcm, ContentEncoding::AesGcm,
task.payload.as_bytes(), task.payload.as_bytes(),
); );
match builder.build() { match builder.build() {
Ok(msg) => match client.send(msg).await { Ok(msg) => match client.send(msg).await {
Ok(_) => { Ok(_) => {
info!( info!(
"Sent Web Push notification to {:?}.", "Sent Web Push notification to {:?}.",
session.id session.id
)
}
Err(err) => {
error!(
"Hit error sending Web Push! {:?}",
err
)
}
},
Err(err) => {
error!(
"Failed to build message for {}! {:?}",
session.user_id, err
) )
} }
Err(err) => {
error!("Hit error sending Web Push! {:?}", err)
}
},
Err(err) => {
error!(
"Failed to build message for {}! {:?}",
session.user_id, err
)
} }
} }
Err(err) => error!( }
"Failed to build signature for {}! {:?}",
session.user_id, err
),
},
Err(err) => error!( Err(err) => error!(
"Failed to create signature builder for {}! {:?}", "Failed to build signature for {}! {:?}",
session.user_id, err session.user_id, err
), ),
} },
Err(err) => error!(
"Failed to create signature builder for {}! {:?}",
session.user_id, err
),
} }
Err(err) => error!(
"Invalid subscription information for {}! {:?}",
session.user_id, err
),
} }
Err(err) => error!(
"Invalid subscription information for {}! {:?}",
session.user_id, err
),
} }
} }
} }

View File

@@ -0,0 +1,22 @@
/// Configure logging and common Rust variables
pub fn setup_logging() -> sentry::ClientInitGuard {
dotenv::dotenv().ok();
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info");
}
if std::env::var("ROCKET_ADDRESS").is_err() {
std::env::set_var("ROCKET_ADDRESS", "0.0.0.0");
}
pretty_env_logger::init();
sentry::init((
"https://62fd0e02c5354905b4e286757f4beb16@sentry.insert.moe/4",
sentry::ClientOptions {
release: sentry::release_name!(),
..Default::default()
},
))
}

View File

@@ -1,3 +1,4 @@
pub mod log;
pub mod manipulation; pub mod manipulation;
pub mod r#ref; pub mod r#ref;
pub mod result; pub mod result;

View File

@@ -0,0 +1,5 @@
use crate::Database;
use rocket::State;
pub use rocket_empty::EmptyResponse;
pub type Db = State<Database>;

12
docker-compose.yml Normal file
View File

@@ -0,0 +1,12 @@
version: "3.3"
services:
redis:
image: eqalpha/keydb
ports:
- "6379:6379"
database:
image: mongo
ports:
- "27017:27017"
volumes:
- ./.data/db:/data/db