blatherskite

a toy discord-like chat app backend written for a swe class
Log | Files | Refs | README

commit 1800ffb071b0d9b3f0f7c1f08ee678ddb0f2437c
parent 6942ba3bcdb4a19f296d4d6d36153d5327906bce
Author: quantumish <freifeld.david@gmail.com>
Date:   Thu, 22 Sep 2022 19:11:19 -0700

Attempt authorization, add barebones API

Diffstat:
Mscuttlebutt/Cargo.toml | 6++++++
Mscuttlebutt/src/main.rs | 164++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
2 files changed, 147 insertions(+), 23 deletions(-)

diff --git a/scuttlebutt/Cargo.toml b/scuttlebutt/Cargo.toml @@ -7,7 +7,13 @@ edition = "2021" [dependencies] ctor = "0.1.23" +hmac = "0.12.1" +jwt = "0.16.0" +log = "0.4.17" poem = { version = "1.3.42", features = ["test"] } poem-openapi = { version = "2.0.12", features = ["swagger-ui"] } +rs-snowflake = "0.6.0" +serde = "1.0.144" +sha2 = "0.10.6" tokio = { version = "1", features = ["full"] } tracing-subscriber = "0.3.15" diff --git a/scuttlebutt/src/main.rs b/scuttlebutt/src/main.rs @@ -1,46 +1,156 @@ -use poem::{listener::TcpListener, Route, Server, Result }; -use poem_openapi::{param::Query, payload::{PlainText, Json}, OpenApi, OpenApiService, Object, ApiResponse}; +use poem::{listener::TcpListener, Route, Server, Result, Request, web::Data}; +use poem_openapi::{param::Query, payload::{PlainText, Json}, *, auth::ApiKey}; +use hmac::{Hmac, Mac}; +use log::{warn, info}; +use jwt::{SignWithKey, VerifyWithKey}; +use serde::{Serialize, Deserialize}; +use sha2::Sha256; struct Api; -#[derive(Object)] +#[derive(Object, Serialize, Deserialize)] struct User { - id: u64, + id: i64, username: String, + email: String, +} + +#[derive(Object)] +struct Group { + id: i64, + name: String, + members: Vec<i64>, + channels: Vec<i64>, +} + +#[derive(Object)] +struct Channel { + id: i64, + name: String, + members: Vec<i64>, } +// type ServerKey = Hmac<Sha256>; + +// /// ApiKey authorization +// #[derive(SecurityScheme)] +// #[oai( +// type = "api_key", +// key_name = "X-API-Key", +// in = "header", +// checker = "api_checker" +// )] +// struct MyApiKeyAuthorization(User); + +// async fn api_checker(req: &Request, api_key: ApiKey) -> Option<User> { +// let server_key = req.data::<ServerKey>().unwrap(); +// VerifyWithKey::<User>::verify_with_key(api_key.key.as_str(), server_key).ok() +// } + #[derive(ApiResponse)] enum UserResponse { - /// Returns the user requested + /// Returns the user requested. #[oai(status = 200)] User(Json<User>), /// Returns when there is no user associated with the ID #[oai(status = 404)] - NotFound(PlainText<String>) + NotFound, + /// Recieved a bad argument when specifying the user. Returns error type, such as: + /// - found empty string for any of the arguments + /// - invalid email + #[oai(status = 400)] + BadRequest, } #[OpenApi] impl Api { - #[oai(path = "/hello", method = "get")] - async fn hi(&self) -> PlainText<String> { - PlainText(String::from("whee")) - } - #[oai(path = "/user", method = "get")] /// Gets the user with the given ID /// /// # Example /// - /// Call `/user/1234` to get the user with id 1234 - async fn get_user(&self, id: Query<u64>) -> UserResponse { + /// Call `/user?id=1234` to get the user with id 1234 + async fn get_user(&self, id: Query<i64>) -> UserResponse { todo!() } #[oai(path = "/user", method = "post")] - /// Makes a user + /// Creates a new user async fn make_user(&self, name: Query<String>, email: Query<String>, password: Query<String>) -> Result<Json<User>> { todo!() } + + #[oai(path = "/user", method = "put")] + /// Updates a user's name and email + async fn update_user(&self, id: Query<i64>, name: Query<String>, email: Query<String>) -> UserResponse { + todo!() + } + + #[oai(path = "/user/groups", method = "get")] + /// Gets all groups accessible to a user + async fn get_groups(&self, id: Query<i64>) -> Json<Vec<Group>> { + todo!() + } + + #[oai(path = "/group", method = "get")] + /// Gets the group with the given ID + async fn get_group(&self, id: Query<i64>) -> Json<Group> { + todo!() + } + + #[oai(path = "/group", method = "post")] + /// Creates a new group + async fn make_group(&self, name: Query<String>) -> Json<Group> { + todo!() + } + + #[oai(path = "/group", method = "put")] + /// Updates the name of an existing group + async fn update_group(&self, id: Query<i64>, name: Query<String>) -> Json<Group> { + todo!() + } + + #[oai(path = "/group/members", method = "get")] + /// Gets the members of the specified group + async fn get_group_members(&self, id: Query<i64>) -> Json<Vec<User>> { + todo!() + } + + #[oai(path = "/group/members", method = "put")] + /// Adds a member to an existing group + async fn add_group_member(&self, gid: Query<i64>, uid: Query<i64>) -> Json<Vec<User>> { + todo!() + } + + #[oai(path = "/group/channels", method = "get")] + /// Gets all channels in a group that are accessible to a user + async fn get_channels(&self, gid: Query<i64>, uid: Query<i64>) -> Json<Vec<Channel>> { + todo!() + } + + #[oai(path = "/group/channels", method = "post")] + /// Creates a channel in a group + async fn make_channel(&self, gid: Query<i64>, name: Query<String>) -> Json<Channel> { + todo!() + } + + #[oai(path = "/channel", method = "put")] + /// Updates the name of a channel + async fn update_channel(&self, id: Query<i64>, name: Query<String>) -> Json<Channel> { + todo!() + } + + #[oai(path = "/channel/members", method = "get")] + /// Gets the members that can access a channel + async fn get_channel_members(&self, id: Query<i64>) -> Json<Vec<User>> { + todo!() + } + + #[oai(path = "/channel/members", method = "put")] + /// Adds a member to a channel + async fn add_channel_member(&self, cid: Query<i64>, uid: Query<i64>) -> Json<Channel> { + todo!() + } } #[tokio::main] @@ -51,9 +161,17 @@ async fn main() -> Result<(), std::io::Error> { tracing_subscriber::fmt::init(); let api_service = - OpenApiService::new(Api, "Hello World", "1.0").server("http://localhost:3000/api"); + OpenApiService::new(Api, "Scuttlebutt", "1.0") + .description("Scuttlebutt is the REST API for managing everything but sending/receiving messages \ + - which means creating/updating/deleting all of your users/groups/channels.") + .server("http://localhost:3000/api"); + let ui = api_service.swagger_ui(); + // let wat = b"whee"; + // let server_key = Hmac::<Sha256>::new_from_slice(wat).expect("valid server key"); + // let server_key2 = Hmac::<Sha256>::new_from_hex + // println!("{:?}", server_key.); Server::new(TcpListener::bind("127.0.0.1:3000")) .run(Route::new().nest("/api", api_service).nest("/", ui)) .await @@ -65,15 +183,15 @@ mod tests { use poem::test::TestClient; fn setup() -> TestClient<Route> { - let app = OpenApiService::new(Api, "Hello World", "1.0").server("http://localhost:3000/api"); + let app = OpenApiService::new(Api, "Scuttlebutt", "1.0").server("http://localhost:3000/api"); TestClient::new(Route::new().nest("/api", app)) } - #[tokio::test] - async fn sanity() { - let cli = setup(); - let resp = cli.get("/api/hello").send().await; - resp.assert_status_is_ok(); - resp.assert_text("whee").await; - } + // #[tokio::test] + // async fn sanity() { + // let cli = setup(); + // let resp = cli.get("/api/hello").send().await; + // resp.assert_status_is_ok(); + // resp.assert_text("whee").await; + // } }