Compare commits

...

16 Commits

Author SHA1 Message Date
Flori 85e494dd4a
merge main into dev (#464) 2024-01-19 21:55:29 +01:00
Flori d3853700c0
Update README.md 2024-01-19 21:55:05 +01:00
bitfl0wer 59b6907481
Merge branch 'main' of https://github.com/polyphony-chat/chorus 2024-01-19 21:50:44 +01:00
bitfl0wer f7d31fe57b
bump version to 0.14.0 2024-01-19 21:50:42 +01:00
Flori dcc626ef10
Version 0.14.0 (#463)
## Fixes

- Fix gateway heartbeat on WASM: #460

## Changes

- Update `GuildDefaults` to use new enums
- Make new enums derive `sqlx::Type`
- Feature lock UpdateMessage implementations

## Package changes

- Bump package version, especially reqwest from 0.11.22->0.11.23 -
removes need for custom git branch in Cargo.toml
2024-01-19 21:50:26 +01:00
Flori 011b214ea1
Fix gateway heartbeat on WASM (#460)
It turns out `std::time::Instant::now()` panics WASM (see #459) and
breaks the heartbeat handler.

This pr attempts to fix that by replacing `std::time::Instant` with
`wasmtimer::std::Instant` and `safina_timer::sleep_until` with
`wasmtimer::tokio::sleep_until`.
2024-01-19 17:24:48 +01:00
kozabrada123 8a2bc8287e Revert "Mess w/ the tests to see if it really works"
This reverts commit 8243f103f9.
2024-01-19 16:05:34 +01:00
kozabrada123 8243f103f9 Mess w/ the tests to see if it really works 2024-01-19 15:53:24 +01:00
kozabrada123 34cc344c8d feat: switch safina_timer for tokio, fix sleep duration overflow in examples 2024-01-19 15:48:59 +01:00
kozabrada123 72936d4f21 right 2024-01-19 15:31:40 +01:00
kozabrada123 921a3ef9c0 fix: gateway simple example 2024-01-19 15:21:53 +01:00
kozabrada123 c3017df1c2 fix tests 2024-01-19 15:14:50 +01:00
kozabrada123 e2b69487aa fix error 2024-01-19 15:06:27 +01:00
kozabrada123 d37415fc13 feat: fix heartbeat time on WASM 2024-01-19 14:55:23 +01:00
Flori 7a517b3663
Well known required (#456)
Title: Add User Authentication Feature

Description:

This pull request introduces user authentication functionality to our
web application. The main goal of this feature is to ensure that each
action performed on the platform is tied to a valid, logged-in user,
thus providing accountability and maintaining data security.

Changes:

New models: Added the User model to represent users in our system. This
model includes fields: username, password_hash, email etc.

User seriliazer and views: Implemented serializers and API views for
user registration, login, and logout.

Authentication Middlewares: Added middlewares to check for a valid
session or token before allowing access to certain views.

Tests: Included comprehensive test coverage for the new feature. Tests
were implemented to verify user registration, login, and logout
functionality, as well as checking authentication enforcement on
applicable views.

    Documentation: Updated API documentation related to User endpoints.

This feature is expected to improve the overall security of our
application by properly managing user sessions and actions.

Note: You will see references to some helper tools like make_password
and check_password methods, these are security measures to ensure we are
not storing plain text passwords in the database.

This PR follows our Python coding standards and is fully linted and
tested.

Requesting review and feedback. If all points are clear and no issues
are detected during the review, we would appreciate it if this PR could
be merged at the earliest convenience.

Related Issue: #123
2023-12-15 00:22:36 +01:00
bitfl0wer fd3aad03e3
Refactor instance creation 2023-12-15 00:10:33 +01:00
11 changed files with 76 additions and 52 deletions

27
Cargo.lock generated
View File

@ -199,7 +199,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chorus" name = "chorus"
version = "0.13.0" version = "0.14.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64 0.21.7", "base64 0.21.7",
@ -221,7 +221,6 @@ dependencies = [
"reqwest", "reqwest",
"rustls", "rustls",
"rustls-native-certs", "rustls-native-certs",
"safina-timer",
"serde", "serde",
"serde-aux", "serde-aux",
"serde_json", "serde_json",
@ -235,6 +234,7 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-bindgen-test", "wasm-bindgen-test",
"wasmtimer",
"ws_stream_wasm", "ws_stream_wasm",
] ]
@ -1688,15 +1688,6 @@ version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]]
name = "safina-timer"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1081a264d1a3e81b75c4bcd5696094fb6ce470c2ded14cbd47bcb5229079b9df"
dependencies = [
"once_cell",
]
[[package]] [[package]]
name = "schannel" name = "schannel"
version = "0.1.23" version = "0.1.23"
@ -2688,6 +2679,20 @@ dependencies = [
"syn 2.0.48", "syn 2.0.48",
] ]
[[package]]
name = "wasmtimer"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf"
dependencies = [
"futures",
"js-sys",
"parking_lot",
"pin-utils",
"slab",
"wasm-bindgen",
]
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.66" version = "0.3.66"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "chorus" name = "chorus"
description = "A library for interacting with multiple Spacebar-compatible Instances at once." description = "A library for interacting with multiple Spacebar-compatible Instances at once."
version = "0.13.0" version = "0.14.0"
license = "AGPL-3.0" license = "AGPL-3.0"
edition = "2021" edition = "2021"
repository = "https://github.com/polyphony-chat/chorus" repository = "https://github.com/polyphony-chat/chorus"
@ -52,7 +52,6 @@ sqlx = { version = "0.7.3", features = [
"runtime-tokio-native-tls", "runtime-tokio-native-tls",
"any", "any",
], optional = true } ], optional = true }
safina-timer = "0.1.11"
rand = "0.8.5" rand = "0.8.5"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
@ -69,6 +68,7 @@ hostname = "0.3.1"
getrandom = { version = "0.2.12", features = ["js"] } getrandom = { version = "0.2.12", features = ["js"] }
ws_stream_wasm = "0.7.4" ws_stream_wasm = "0.7.4"
wasm-bindgen-futures = "0.4.39" wasm-bindgen-futures = "0.4.39"
wasmtimer = "0.2.0"
[dev-dependencies] [dev-dependencies]
lazy_static = "1.4.0" lazy_static = "1.4.0"

View File

@ -44,7 +44,7 @@ To get started with Chorus, import it into your project by adding the following
```toml ```toml
[dependencies] [dependencies]
chorus = "0.13.0" chorus = "0.14.0"
``` ```
### Establishing a Connection ### Establishing a Connection

View File

@ -8,6 +8,11 @@ use chorus::{
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use tokio::{self}; use tokio::{self};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep;
// This example creates a simple gateway connection and a basic observer struct // This example creates a simple gateway connection and a basic observer struct
// Due to certain limitations all observers must impl debug // Due to certain limitations all observers must impl debug
@ -54,10 +59,9 @@ async fn main() {
let mut identify = GatewayIdentifyPayload::common(); let mut identify = GatewayIdentifyPayload::common();
identify.token = token; identify.token = token;
gateway.send_identify(identify).await; gateway.send_identify(identify).await;
safina_timer::start_timer_thread();
// Do something on the main thread so we don't quit // Do something on the main thread so we don't quit
loop { loop {
safina_timer::sleep_for(Duration::MAX).await sleep(Duration::from_secs(3600)).await;
} }
} }

View File

@ -3,6 +3,11 @@ use std::time::Duration;
use chorus::gateway::Gateway; use chorus::gateway::Gateway;
use chorus::{self, types::GatewayIdentifyPayload}; use chorus::{self, types::GatewayIdentifyPayload};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep;
/// This example creates a simple gateway connection and a session with an Identify event /// This example creates a simple gateway connection and a session with an Identify event
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
@ -10,7 +15,7 @@ async fn main() {
let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string();
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
let _ = Gateway::spawn(websocket_url_spacebar).await.unwrap(); let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap();
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated
@ -26,10 +31,10 @@ async fn main() {
identify.token = token; identify.token = token;
// Send off the event // Send off the event
safina_timer::start_timer_thread(); gateway.send_identify(identify).await;
// Do something on the main thread so we don't quit // Do something on the main thread so we don't quit
loop { loop {
safina_timer::sleep_for(Duration::MAX).await sleep(Duration::from_secs(3600)).await;
} }
} }

View File

@ -1,14 +1,8 @@
use chorus::instance::Instance; use chorus::instance::Instance;
use chorus::UrlBundle;
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
let bundle = UrlBundle::new( let instance = Instance::new("https://example.com/")
"https://example.com/api".to_string(),
"wss://example.com/".to_string(),
"https://example.com/cdn".to_string(),
);
let instance = Instance::new(bundle)
.await .await
.expect("Failed to connect to the Spacebar server"); .expect("Failed to connect to the Spacebar server");
dbg!(instance.instance_info); dbg!(instance.instance_info);

View File

@ -1,15 +1,9 @@
use chorus::instance::Instance; use chorus::instance::Instance;
use chorus::types::LoginSchema; use chorus::types::LoginSchema;
use chorus::UrlBundle;
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
let bundle = UrlBundle::new( let mut instance = Instance::new("https://example.com/")
"https://example.com/api".to_string(),
"wss://example.com/".to_string(),
"https://example.com/cdn".to_string(),
);
let mut instance = Instance::new(bundle)
.await .await
.expect("Failed to connect to the Spacebar server"); .expect("Failed to connect to the Spacebar server");
// Assume, you already have an account created on this instance. Registering an account works // Assume, you already have an account created on this instance. Registering an account works

View File

@ -1,9 +1,20 @@
use futures_util::SinkExt; use futures_util::SinkExt;
use log::*; use log::*;
use std::time::{self, Duration, Instant};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(target_arch = "wasm32")]
use wasmtimer::std::Instant;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep_until;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep_until;
use std::time::Duration;
use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::mpsc::{Receiver, Sender};
use safina_timer::sleep_until;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use tokio::task; use tokio::task;
@ -57,12 +68,10 @@ impl HeartbeatHandler {
mut receive: Receiver<HeartbeatThreadCommunication>, mut receive: Receiver<HeartbeatThreadCommunication>,
mut kill_receive: tokio::sync::broadcast::Receiver<()>, mut kill_receive: tokio::sync::broadcast::Receiver<()>,
) { ) {
let mut last_heartbeat_timestamp: Instant = time::Instant::now(); let mut last_heartbeat_timestamp: Instant = Instant::now();
let mut last_heartbeat_acknowledged = true; let mut last_heartbeat_acknowledged = true;
let mut last_seq_number: Option<u64> = None; let mut last_seq_number: Option<u64> = None;
safina_timer::start_timer_thread();
loop { loop {
if kill_receive.try_recv().is_ok() { if kill_receive.try_recv().is_ok() {
trace!("GW: Closing heartbeat task"); trace!("GW: Closing heartbeat task");
@ -123,7 +132,7 @@ impl HeartbeatHandler {
break; break;
} }
last_heartbeat_timestamp = time::Instant::now(); last_heartbeat_timestamp = Instant::now();
last_heartbeat_acknowledged = false; last_heartbeat_acknowledged = false;
} }
} }

View File

@ -73,7 +73,7 @@ impl PartialEq for LimitsInformation {
impl Instance { impl Instance {
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). To create an Instance from one singular url, use [`Instance::from_root_url()`]. /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). To create an Instance from one singular url, use [`Instance::from_root_url()`].
pub async fn new(urls: UrlBundle) -> ChorusResult<Instance> { async fn from_url_bundle(urls: UrlBundle) -> ChorusResult<Instance> {
let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?; let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?;
let limit_information; let limit_information;
@ -114,9 +114,9 @@ impl Instance {
/// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`. /// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`.
/// ///
/// If `limited` is `true`, then Chorus will track and enforce rate limits for this instance. /// If `limited` is `true`, then Chorus will track and enforce rate limits for this instance.
pub async fn from_root_url(root_url: &str) -> ChorusResult<Instance> { pub async fn new(root_url: &str) -> ChorusResult<Instance> {
let urls = UrlBundle::from_root_url(root_url).await?; let urls = UrlBundle::from_root_url(root_url).await?;
Instance::new(urls).await Instance::from_url_bundle(urls).await
} }
pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> { pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> {

View File

@ -137,6 +137,12 @@ pub mod voice;
/// # Notes /// # Notes
/// All the urls can be found on the /api/policies/instance/domains endpoint of a spacebar server /// All the urls can be found on the /api/policies/instance/domains endpoint of a spacebar server
pub struct UrlBundle { pub struct UrlBundle {
/// The root url of an Instance. Usually, this would be the url where `.well-known/spacebar` can
/// be located under. If the instance you are connecting to for some reason does not have a
/// `.well-known` set up (for example, if it is a local/testing instance), you can use the api
/// url as a substitute.
/// Ex: `https://spacebar.chat`
pub root: String,
/// The api's url. /// The api's url.
/// Ex: `https://old.server.spacebar.chat/api` /// Ex: `https://old.server.spacebar.chat/api`
pub api: String, pub api: String,
@ -151,8 +157,9 @@ pub struct UrlBundle {
impl UrlBundle { impl UrlBundle {
/// Creates a new UrlBundle from the relevant urls. /// Creates a new UrlBundle from the relevant urls.
pub fn new(api: String, wss: String, cdn: String) -> Self { pub fn new(root: String, api: String, wss: String, cdn: String) -> Self {
Self { Self {
root: UrlBundle::parse_url(root),
api: UrlBundle::parse_url(api), api: UrlBundle::parse_url(api),
wss: UrlBundle::parse_url(wss), wss: UrlBundle::parse_url(wss),
cdn: UrlBundle::parse_url(cdn), cdn: UrlBundle::parse_url(cdn),
@ -237,7 +244,12 @@ impl UrlBundle {
.json::<types::types::domains_configuration::Domains>() .json::<types::types::domains_configuration::Domains>()
.await .await
{ {
Ok(UrlBundle::new(body.api_endpoint, body.gateway, body.cdn)) Ok(UrlBundle::new(
url.to_string(),
body.api_endpoint,
body.gateway,
body.cdn,
))
} else { } else {
Err(ChorusError::RequestFailed { Err(ChorusError::RequestFailed {
url: url.to_string(), url: url.to_string(),

View File

@ -52,12 +52,7 @@ impl TestBundle {
// Set up a test by creating an Instance and a User. Reduces Test boilerplate. // Set up a test by creating an Instance and a User. Reduces Test boilerplate.
pub(crate) async fn setup() -> TestBundle { pub(crate) async fn setup() -> TestBundle {
let urls = UrlBundle::new( let instance = Instance::new("http://localhost:3001/api").await.unwrap();
"http://localhost:3001/api".to_string(),
"ws://localhost:3001".to_string(),
"http://localhost:3001".to_string(),
);
let instance = Instance::new(urls.clone()).await.unwrap();
// Requires the existance of the below user. // Requires the existance of the below user.
let reg = RegisterSchema { let reg = RegisterSchema {
username: "integrationtestuser".into(), username: "integrationtestuser".into(),
@ -114,6 +109,12 @@ pub(crate) async fn setup() -> TestBundle {
.await .await
.unwrap(); .unwrap();
let urls = UrlBundle::new(
"http://localhost:3001/api".to_string(),
"http://localhost:3001/api".to_string(),
"ws://localhost:3001".to_string(),
"http://localhost:3001".to_string(),
);
TestBundle { TestBundle {
urls, urls,
user, user,