feat: cs login & add vote api file
This commit is contained in:
@@ -13,9 +13,12 @@ diesel = { version = "1.4.8", features = ["postgres", "chrono"] }
|
|||||||
diesel_migrations = "1.4.0"
|
diesel_migrations = "1.4.0"
|
||||||
tokio = "1.17.0"
|
tokio = "1.17.0"
|
||||||
redis = { version="0.21.5", features = ["aio", "tokio-comp"] }
|
redis = { version="0.21.5", features = ["aio", "tokio-comp"] }
|
||||||
chrono = { version="0.4.19", features =["serde"] }
|
chrono = { version="0.4.19", features = ["serde"] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
sha2 = "0.10.2"
|
sha2 = "0.10.2"
|
||||||
log = "0.4.16"
|
log = "0.4.16"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
|
|
||||||
|
url = "2.2.2"
|
||||||
|
reqwest = { version = "0.11.10", features = ["json"] }
|
||||||
|
|||||||
70
src/api/vote.rs
Normal file
70
src/api/vote.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use crate::api::{CurrentUser, JsonAPI, PolicyError::*};
|
||||||
|
use crate::rds_conn::RdsConn;
|
||||||
|
use crate::rds_models::*;
|
||||||
|
use rocket::form::Form;
|
||||||
|
use rocket::futures::future;
|
||||||
|
use rocket::serde::json::{json, Value};
|
||||||
|
|
||||||
|
pub async fn get_poll_dict(pid: i32, rconn: &RdsConn, namehash: &str) -> Option<Value> {
|
||||||
|
let opts = PollOption::init(pid, rconn)
|
||||||
|
.get_list()
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
if opts.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let choice = future::join_all(opts.iter().enumerate().map(|(idx, opt)| async move {
|
||||||
|
PollVote::init(pid, idx, rconn)
|
||||||
|
.has(namehash)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
.then(|| opt)
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| x)
|
||||||
|
.collect::<Vec<&String>>()
|
||||||
|
.pop();
|
||||||
|
Some(json!({
|
||||||
|
"answers": future::join_all(
|
||||||
|
opts.iter().enumerate().map(|(idx, opt)| async move {
|
||||||
|
json!({
|
||||||
|
"option": opt,
|
||||||
|
"votes": PollVote::init(pid, idx, rconn).count().await.unwrap_or_default(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
).await,
|
||||||
|
"vote": choice,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct VoteInput {
|
||||||
|
pid: i32,
|
||||||
|
vote: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/vote", data = "<vi>")]
|
||||||
|
pub async fn vote(vi: Form<VoteInput>, user: CurrentUser, rconn: RdsConn) -> JsonAPI {
|
||||||
|
let pid = vi.pid;
|
||||||
|
let opts = PollOption::init(pid, &rconn).get_list().await?;
|
||||||
|
if opts.is_empty() {
|
||||||
|
Err(NotAllowed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx in 0..opts.len() {
|
||||||
|
if PollVote::init(pid, idx, &rconn).has(&user.namehash).await? {
|
||||||
|
Err(NotAllowed)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx: usize = opts
|
||||||
|
.iter()
|
||||||
|
.position(|x| x.eq(&vi.vote))
|
||||||
|
.ok_or_else(|| NotAllowed)?;
|
||||||
|
|
||||||
|
PollVote::init(pid, idx, &rconn).add(&user.namehash).await?;
|
||||||
|
|
||||||
|
code0!(get_poll_dict(vi.pid, &rconn, &user.namehash).await)
|
||||||
|
}
|
||||||
114
src/login.rs
Normal file
114
src/login.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
use crate::db_conn::Db;
|
||||||
|
use crate::models::User;
|
||||||
|
use rocket::request::{FromRequest, Outcome, Request};
|
||||||
|
use rocket::response::Redirect;
|
||||||
|
use rocket::serde::Deserialize;
|
||||||
|
use std::env;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
pub struct RefHeader(pub String);
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for RefHeader {
|
||||||
|
type Error = ();
|
||||||
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
|
match request.headers().get_one("Referer") {
|
||||||
|
Some(h) => Outcome::Success(RefHeader(h.to_string())),
|
||||||
|
None => Outcome::Forward(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/?p=cs")]
|
||||||
|
pub fn cs_login(r: RefHeader) -> Redirect {
|
||||||
|
let mast_url = env::var("MAST_BASE_URL").unwrap();
|
||||||
|
let mast_cli = env::var("MAST_CLIENT").unwrap();
|
||||||
|
let mast_scope = env::var("MAST_SCOPE").unwrap();
|
||||||
|
|
||||||
|
let mut redirect_url = Url::parse(&r.0).unwrap();
|
||||||
|
redirect_url.set_path("/_login/cs/auth");
|
||||||
|
redirect_url.set_query(None);
|
||||||
|
|
||||||
|
redirect_url = Url::parse_with_params(
|
||||||
|
redirect_url.as_str(),
|
||||||
|
&[("redirect_url", redirect_url.as_str())],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let url = Url::parse_with_params(
|
||||||
|
&format!("{}oauth/authorize", mast_url),
|
||||||
|
&[
|
||||||
|
("redirect_uri", redirect_url.as_str()),
|
||||||
|
("client_id", &mast_cli),
|
||||||
|
("scope", &mast_scope),
|
||||||
|
("response_type", "code"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Redirect::to(url.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
struct Token {
|
||||||
|
pub access_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
struct Account {
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
#[get("/cs/auth?<code>&<redirect_url>")]
|
||||||
|
pub async fn cs_auth(code: String, redirect_url: String, db: Db) -> Redirect {
|
||||||
|
let mast_url = env::var("MAST_BASE_URL").unwrap();
|
||||||
|
let mast_cli = env::var("MAST_CLIENT").unwrap();
|
||||||
|
let mast_sec = env::var("MAST_SECRET").unwrap();
|
||||||
|
let mast_scope = env::var("MAST_SCOPE").unwrap();
|
||||||
|
|
||||||
|
// to keep same
|
||||||
|
let redirect_url = Url::parse_with_params(
|
||||||
|
redirect_url.as_str(),
|
||||||
|
&[("redirect_url", redirect_url.as_str())],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let token: Token = client
|
||||||
|
.post(format!("{}oauth/token", &mast_url))
|
||||||
|
.form(&[
|
||||||
|
("client_id", mast_cli.as_str()),
|
||||||
|
("client_secret", mast_sec.as_str()),
|
||||||
|
("scope", mast_scope.as_str()),
|
||||||
|
("redirect_uri", redirect_url.as_str()),
|
||||||
|
("grant_type", "authorization_code"),
|
||||||
|
("code", code.as_str()),
|
||||||
|
])
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
//dbg!(&token);
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let account = client
|
||||||
|
.get(format!("{}api/v1/accounts/verify_credentials", &mast_url))
|
||||||
|
.bearer_auth(token.access_token)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.json::<Account>()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
//dbg!(&account);
|
||||||
|
|
||||||
|
let tk = User::find_or_create_token(&db, &format!("cs_{}", &account.id), false)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Redirect::to(format!("/?token={}", tk))
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ mod api;
|
|||||||
mod cache;
|
mod cache;
|
||||||
mod db_conn;
|
mod db_conn;
|
||||||
mod libs;
|
mod libs;
|
||||||
|
mod login;
|
||||||
mod models;
|
mod models;
|
||||||
mod random_hasher;
|
mod random_hasher;
|
||||||
mod rds_conn;
|
mod rds_conn;
|
||||||
@@ -27,7 +28,7 @@ use diesel::Connection;
|
|||||||
use random_hasher::RandomHasher;
|
use random_hasher::RandomHasher;
|
||||||
use rds_conn::{init_rds_client, RdsConn};
|
use rds_conn::{init_rds_client, RdsConn};
|
||||||
use std::env;
|
use std::env;
|
||||||
use tokio::time::{interval, Duration};
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
embed_migrations!("migrations/postgres");
|
embed_migrations!("migrations/postgres");
|
||||||
|
|
||||||
@@ -43,9 +44,8 @@ async fn main() -> Result<(), rocket::Error> {
|
|||||||
let rconn = RdsConn(rmc.clone());
|
let rconn = RdsConn(rmc.clone());
|
||||||
clear_outdate_redis_data(&rconn.clone()).await;
|
clear_outdate_redis_data(&rconn.clone()).await;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut itv = interval(Duration::from_secs(4 * 60 * 60));
|
|
||||||
loop {
|
loop {
|
||||||
itv.tick().await;
|
sleep(Duration::from_secs(4 * 60 * 60)).await;
|
||||||
models::Post::annealing(establish_connection(), &rconn).await;
|
models::Post::annealing(establish_connection(), &rconn).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -71,6 +71,7 @@ async fn main() -> Result<(), rocket::Error> {
|
|||||||
api::vote::vote,
|
api::vote::vote,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
.mount("/_login", routes![login::cs_login, login::cs_auth])
|
||||||
.register(
|
.register(
|
||||||
"/_api",
|
"/_api",
|
||||||
catchers![api::catch_401_error, api::catch_403_error,],
|
catchers![api::catch_401_error, api::catch_403_error,],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use crate::cache::*;
|
use crate::cache::*;
|
||||||
use crate::db_conn::{Conn, Db};
|
use crate::db_conn::{Conn, Db};
|
||||||
use crate::libs::diesel_logger::LoggingConnection;
|
use crate::libs::diesel_logger::LoggingConnection;
|
||||||
|
use crate::random_hasher::random_string;
|
||||||
use crate::rds_conn::RdsConn;
|
use crate::rds_conn::RdsConn;
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
use chrono::{offset::Utc, DateTime};
|
use chrono::{offset::Utc, DateTime};
|
||||||
@@ -368,6 +369,38 @@ impl User {
|
|||||||
Some(u)
|
Some(u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_or_create_token(
|
||||||
|
db: &Db,
|
||||||
|
name: &str,
|
||||||
|
force_refresh: bool,
|
||||||
|
) -> QueryResult<String> {
|
||||||
|
let name = name.to_string();
|
||||||
|
db.run(move |c| {
|
||||||
|
if let Some(u) = {
|
||||||
|
if force_refresh {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
users::table
|
||||||
|
.filter(users::name.eq(&name))
|
||||||
|
.first::<Self>(with_log!(c))
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
Ok(u.token)
|
||||||
|
} else {
|
||||||
|
let token = random_string(16);
|
||||||
|
diesel::insert_into(users::table)
|
||||||
|
.values((users::name.eq(&name), users::token.eq(&token)))
|
||||||
|
.on_conflict(users::name)
|
||||||
|
.do_update()
|
||||||
|
.set(users::token.eq(&token))
|
||||||
|
.execute(with_log!(c))?;
|
||||||
|
Ok(token)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
|
|||||||
@@ -2,6 +2,14 @@ use chrono::{offset::Local, DateTime};
|
|||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
pub fn random_string(len: usize) -> String {
|
||||||
|
thread_rng()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(len)
|
||||||
|
.map(char::from)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RandomHasher {
|
pub struct RandomHasher {
|
||||||
pub salt: String,
|
pub salt: String,
|
||||||
pub start_time: DateTime<Local>,
|
pub start_time: DateTime<Local>,
|
||||||
@@ -10,11 +18,7 @@ pub struct RandomHasher {
|
|||||||
impl RandomHasher {
|
impl RandomHasher {
|
||||||
pub fn get_random_one() -> RandomHasher {
|
pub fn get_random_one() -> RandomHasher {
|
||||||
RandomHasher {
|
RandomHasher {
|
||||||
salt: thread_rng()
|
salt: random_string(16),
|
||||||
.sample_iter(&Alphanumeric)
|
|
||||||
.take(16)
|
|
||||||
.map(char::from)
|
|
||||||
.collect(),
|
|
||||||
start_time: Local::now(),
|
start_time: Local::now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user