1use std::{env::var, fs::File, io::Read};
2
3use axum::{
4 extract::Request,
5 http::HeaderMap,
6 middleware::{self, Next},
7 response::{IntoResponse, Response},
8 routing::post,
9 Json, Router,
10};
11use diesel::prelude::Insertable;
12use password::check_password;
13use quco::Query;
14use ryz::{
15 dict::dict,
16 err::{self, Error},
17 path,
18 res::Res,
19 time::Time,
20};
21use serde::{Deserialize, Serialize};
22use token::{new_at, verify_rt};
23use user::{get_by_id, get_by_rt, User};
24use user_change::UserChange;
25
26pub mod db;
27mod password;
28pub mod quco;
29pub mod ryz;
30mod schema;
31pub mod token;
32pub mod user;
33pub mod user_change;
34
35lazy_static::lazy_static! {
36 static ref APPRC: Apprc = get_apprc();
37}
38
39fn get_apprc() -> Apprc {
40 let mut file =
41 File::open(path::cwd().unwrap().join("corund.cfg.yml")).unwrap();
42 let mut content = String::new();
43 file.read_to_string(&mut content).unwrap();
44 let mut mode_to_apprc: dict<String, Apprc> =
45 serde_yml::from_str(&content).unwrap();
46 let mode = get_mode();
47 mode_to_apprc.remove(&mode).unwrap()
48}
49
50pub fn get_mode() -> String {
51 return match var("CORUND_MODE") {
52 Err(_) => "prod".to_string(),
53 Ok(mode) => mode,
54 };
55}
56
57#[derive(Debug, Deserialize)]
58struct Apprc {
59 sql: SqlCfg,
60 domain: DomainCfg,
61}
62
63#[derive(Debug, Deserialize)]
64struct DomainCfg {
65 secret: String,
66}
67
68#[derive(Debug, Deserialize)]
69struct SqlCfg {
70 url: String,
71 is_cleaning_allowed: bool,
72}
73
74#[derive(Deserialize)]
75struct Login {
76 username: String,
77 password: String,
78}
79
80#[derive(Deserialize)]
81struct RtData {
82 rt: String,
83}
84
85#[derive(Serialize, Deserialize)]
86pub struct Reg {
87 pub username: String,
88 pub password: String,
89 pub firstname: Option<String>,
90 pub patronym: Option<String>,
91 pub surname: Option<String>,
92}
93
94#[derive(Insertable)]
95#[diesel(table_name=schema::appuser)]
96pub struct InsertReg {
97 pub username: String,
98 pub hpassword: String,
99 pub firstname: Option<String>,
100 pub patronym: Option<String>,
101 pub surname: Option<String>,
102}
103
104#[derive(Deserialize)]
105struct GetChanges {
106 from: Time,
107}
108
109async fn err_middleware(req: Request, next: Next) -> Response {
110 let res = next.run(req).await;
111
112 res
116}
117
118impl IntoResponse for Error {
119 fn into_response(self) -> Response {
120 Json(self).into_response()
121 }
122}
123
124async fn rpc_reg(headers: HeaderMap, Json(reg): Json<Reg>) -> Res<Json<User>> {
125 verify_domain_secret_from_headers(headers)?;
126 let con = &mut db::con().unwrap();
127 let user = user::new(®, con).unwrap();
128 Ok(Json(user))
129}
130
131async fn rpc_dereg(headers: HeaderMap, Json(query): Json<Query>) {
132 verify_domain_secret_from_headers(headers).unwrap();
133 let con = &mut db::con().unwrap();
134 user::del(&query, con).unwrap();
135}
136
137async fn rpc_login(Json(login): Json<Login>) -> Res<String> {
144 let con = &mut db::con().unwrap();
145 let Ok((user, hpassword)) = user::get_by_username(&login.username, con)
146 else {
147 return err::res_msg(
148 format!("invalid username {}", login.username.to_owned()).as_str(),
149 );
150 };
151 if !check_password(&login.password, &hpassword) {
152 return err::res_msg("incorrect password");
153 }
154 let rt = token::new_rt(user.id).unwrap();
155 let con = &mut db::con().unwrap();
156 user::set_rt_for_username(&login.username, &rt, con).unwrap();
157 Ok(rt)
158}
159
160async fn rpc_logout(Json(rtdata): Json<RtData>) {
161 let con = &mut db::con().unwrap();
162 user::del_rt(&rtdata.rt, con).unwrap();
163}
164
165async fn rpc_current(Json(rtdata): Json<RtData>) -> Res<Json<User>> {
166 let con = &mut db::con().unwrap();
167 Ok(Json(get_by_rt(&rtdata.rt, con)?.0))
168}
169
170async fn rpc_access(Json(rtdata): Json<RtData>) -> Res<String> {
171 let rt = rtdata.rt;
172 let claims = verify_rt(&rt).unwrap();
173 let con = &mut db::con().unwrap();
174 let user = get_by_id(claims.user_id, con).unwrap();
175 if user.rt != Some(rt) {
176 return err::res_msg("no such refresh token for user");
177 }
178 Ok(new_at(claims.user_id).unwrap())
180}
181
182async fn rpc_get_user_changes(
183 headers: HeaderMap,
184 Json(get_changes): Json<GetChanges>,
185) -> Res<Json<Vec<UserChange>>> {
186 verify_domain_secret_from_headers(headers)?;
187 let con = &mut db::con().unwrap();
188 let changes = user_change::get_many(get_changes.from, con).unwrap();
189 Ok(Json(changes))
190}
191
192fn verify_domain_secret_from_headers(headers: HeaderMap) -> Res<()> {
193 match headers.get("domain_secret") {
194 Some(secret) => {
195 if secret.to_str().unwrap() != &APPRC.domain.secret {
196 return err::res_msg("invalid secret");
197 }
198 }
199 None => return err::res_msg("missing server api token"),
200 }
201 Ok(())
202}
203
204pub fn get_router() -> Router {
205 Router::new()
206 .route("/rpc/login", post(rpc_login))
207 .route("/rpc/logout", post(rpc_logout))
208 .route("/rpc/current", post(rpc_current))
209 .route("/rpc/access", post(rpc_access))
210 .route("/rpc/server/reg", post(rpc_reg))
212 .route("/rpc/server/dereg", post(rpc_dereg))
213 .route("/rpc/server/get_user_changes", post(rpc_get_user_changes))
214 .layer(middleware::from_fn(err_middleware))
215}