feat: cs login & add vote api file
This commit is contained in:
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 db_conn;
|
||||
mod libs;
|
||||
mod login;
|
||||
mod models;
|
||||
mod random_hasher;
|
||||
mod rds_conn;
|
||||
@@ -27,7 +28,7 @@ use diesel::Connection;
|
||||
use random_hasher::RandomHasher;
|
||||
use rds_conn::{init_rds_client, RdsConn};
|
||||
use std::env;
|
||||
use tokio::time::{interval, Duration};
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
embed_migrations!("migrations/postgres");
|
||||
|
||||
@@ -43,9 +44,8 @@ async fn main() -> Result<(), rocket::Error> {
|
||||
let rconn = RdsConn(rmc.clone());
|
||||
clear_outdate_redis_data(&rconn.clone()).await;
|
||||
tokio::spawn(async move {
|
||||
let mut itv = interval(Duration::from_secs(4 * 60 * 60));
|
||||
loop {
|
||||
itv.tick().await;
|
||||
sleep(Duration::from_secs(4 * 60 * 60)).await;
|
||||
models::Post::annealing(establish_connection(), &rconn).await;
|
||||
}
|
||||
});
|
||||
@@ -71,6 +71,7 @@ async fn main() -> Result<(), rocket::Error> {
|
||||
api::vote::vote,
|
||||
],
|
||||
)
|
||||
.mount("/_login", routes![login::cs_login, login::cs_auth])
|
||||
.register(
|
||||
"/_api",
|
||||
catchers![api::catch_401_error, api::catch_403_error,],
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use crate::cache::*;
|
||||
use crate::db_conn::{Conn, Db};
|
||||
use crate::libs::diesel_logger::LoggingConnection;
|
||||
use crate::random_hasher::random_string;
|
||||
use crate::rds_conn::RdsConn;
|
||||
use crate::schema::*;
|
||||
use chrono::{offset::Utc, DateTime};
|
||||
@@ -368,6 +369,38 @@ impl User {
|
||||
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)]
|
||||
|
||||
@@ -2,6 +2,14 @@ use chrono::{offset::Local, DateTime};
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
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 salt: String,
|
||||
pub start_time: DateTime<Local>,
|
||||
@@ -10,11 +18,7 @@ pub struct RandomHasher {
|
||||
impl RandomHasher {
|
||||
pub fn get_random_one() -> RandomHasher {
|
||||
RandomHasher {
|
||||
salt: thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(16)
|
||||
.map(char::from)
|
||||
.collect(),
|
||||
salt: random_string(16),
|
||||
start_time: Local::now(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user