Skip to content

Commit 3873cdc

Browse files
committed
Refactoring authentication
1 parent 9a2bb96 commit 3873cdc

39 files changed

+1875
-797
lines changed

atcoder-problems-backend/Cargo.lock

Lines changed: 701 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

atcoder-problems-backend/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@ sql-client = { path = "./sql-client" }
2424
atcoder-client = { path = "./atcoder-client" }
2525

2626
# Web framework
27-
actix-web = "4.0.0-beta.18"
27+
actix-web = "4.0.0-rc.2"
28+
actix-service = "2.0.2"
2829
reqwest = { version = "0.11", features = ["json"] }
2930

3031
async-trait = "0.1"
3132

3233
anyhow = "1.0"
34+
futures-util = "0.3.19"
3335

3436
[dev-dependencies]
37+
httpmock = "0.6.6"
3538

3639
[workspace]
3740
members = ["sql-client", "atcoder-client"]
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
use std::env;
22

3+
use atcoder_problems_backend::server::middleware::github_auth::GithubClient;
34
use atcoder_problems_backend::server::run_server;
4-
use atcoder_problems_backend::server::GitHubAuthentication;
55
use atcoder_problems_backend::utils::init_log_config;
66

77
#[actix_web::main]
88
async fn main() {
9-
// std::env::set_var("RUST_LOG", "actix_web=info");
109
init_log_config().unwrap();
1110
let database_url = env::var("SQL_URL").expect("SQL_URL is not set.");
1211
let port = 8080;
1312

1413
let client_id = env::var("CLIENT_ID").unwrap_or_else(|_| String::new());
1514
let client_secret = env::var("CLIENT_SECRET").unwrap_or_else(|_| String::new());
1615

17-
let auth = GitHubAuthentication::new(&client_id, &client_secret);
1816
let pg_pool = sql_client::initialize_pool(&database_url)
1917
.await
2018
.expect("Failed to initialize the connection pool");
21-
run_server(pg_pool, auth, port)
19+
let github = GithubClient::new(&client_id, &client_secret, "https://api.github.com")
20+
.expect("Failed to create github client");
21+
run_server(pg_pool, github, port)
2222
.await
2323
.expect("Failed to run server");
2424
}

atcoder-problems-backend/src/server/auth.rs

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
use crate::server::AppData;
2-
use actix_web::http::header::LOCATION;
3-
use actix_web::{cookie::Cookie, error, web, HttpRequest, HttpResponse, Result};
1+
use actix_web::{error, Result};
42
use async_trait::async_trait;
53
use serde::{Deserialize, Serialize};
6-
use sql_client::internal::user_manager::UserManager;
7-
8-
const REDIRECT_URL: &str = "https://kenkoooo.com/atcoder/";
94

105
#[async_trait(?Send)]
116
pub trait Authentication {
@@ -84,43 +79,3 @@ impl GitHubAuthentication {
8479
}
8580
}
8681
}
87-
88-
#[derive(Deserialize)]
89-
pub(crate) struct Query {
90-
code: String,
91-
redirect_to: Option<String>,
92-
}
93-
94-
pub(crate) async fn get_token<A: Authentication + Clone>(
95-
_request: HttpRequest,
96-
data: web::Data<AppData<A>>,
97-
query: web::Query<Query>,
98-
) -> Result<HttpResponse> {
99-
let client = data.authentication.clone();
100-
let conn = data.pg_pool.clone();
101-
102-
let token = client
103-
.get_token(&query.code)
104-
.await
105-
.map_err(error::ErrorInternalServerError)?;
106-
let response = client
107-
.get_user_id(&token)
108-
.await
109-
.map_err(error::ErrorInternalServerError)?;
110-
let internal_user_id = response.id.to_string();
111-
conn.register_user(&internal_user_id)
112-
.await
113-
.map_err(error::ErrorInternalServerError)?;
114-
115-
let cookie = Cookie::build("token", token).path("/").finish();
116-
let redirect_fragment = query
117-
.redirect_to
118-
.clone()
119-
.unwrap_or_else(|| "/login/user".to_string());
120-
let redirect_url = format!("{}#{}", REDIRECT_URL, redirect_fragment);
121-
let response = HttpResponse::Found()
122-
.insert_header((LOCATION, redirect_url))
123-
.cookie(cookie)
124-
.finish();
125-
Ok(response)
126-
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use actix_web::{get, HttpResponse, Responder};
2+
3+
#[get("/healthcheck")]
4+
pub async fn get_healthcheck() -> impl Responder {
5+
HttpResponse::Ok().finish()
6+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use actix_web::{post, web, HttpResponse, Responder, Result};
2+
use serde::Deserialize;
3+
use sql_client::{
4+
internal::virtual_contest_manager::{VirtualContestItem, VirtualContestManager},
5+
PgPool,
6+
};
7+
8+
use crate::server::{error::ApiResult, middleware::github_auth::GithubToken};
9+
10+
#[derive(Deserialize)]
11+
pub struct UpdateItemsQuery {
12+
contest_id: String,
13+
problems: Vec<VirtualContestItem>,
14+
}
15+
16+
#[post("/internal-api/contest/item/update")]
17+
pub async fn update_items(
18+
token: web::ReqData<GithubToken>,
19+
pool: web::Data<PgPool>,
20+
query: web::Json<UpdateItemsQuery>,
21+
) -> Result<impl Responder> {
22+
let user_id = token.id.to_string();
23+
pool.update_items(&query.contest_id, &query.problems, &user_id)
24+
.await
25+
.map_internal_server_err()?;
26+
let response = HttpResponse::Ok().finish();
27+
Ok(response)
28+
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
pub mod item;
2+
3+
use actix_web::{get, post, web, HttpResponse, Responder, Result};
4+
use serde::{Deserialize, Serialize};
5+
use sql_client::{
6+
internal::virtual_contest_manager::{
7+
VirtualContestInfo, VirtualContestItem, VirtualContestManager,
8+
},
9+
PgPool,
10+
};
11+
12+
use crate::server::{error::ApiResult, middleware::github_auth::GithubToken};
13+
14+
#[derive(Deserialize)]
15+
pub struct CreateContestQuery {
16+
title: String,
17+
memo: String,
18+
start_epoch_second: i64,
19+
duration_second: i64,
20+
mode: Option<String>,
21+
is_public: Option<bool>,
22+
penalty_second: i64,
23+
}
24+
25+
#[post("/internal-api/contest/create")]
26+
pub async fn create_contest(
27+
token: web::ReqData<GithubToken>,
28+
pool: web::Data<PgPool>,
29+
query: web::Json<CreateContestQuery>,
30+
) -> Result<impl Responder> {
31+
let user_id = token.id.to_string();
32+
let contest_id = pool
33+
.create_contest(
34+
&query.title,
35+
&query.memo,
36+
&user_id,
37+
query.start_epoch_second,
38+
query.duration_second,
39+
query.mode.as_deref(),
40+
query.is_public.unwrap_or(true),
41+
query.penalty_second,
42+
)
43+
.await
44+
.map_internal_server_err()?;
45+
let body = serde_json::json!({ "contest_id": contest_id });
46+
let response = HttpResponse::Ok().json(&body);
47+
Ok(response)
48+
}
49+
50+
#[derive(Deserialize)]
51+
pub struct UpdateContestQuery {
52+
id: String,
53+
title: String,
54+
memo: String,
55+
start_epoch_second: i64,
56+
duration_second: i64,
57+
mode: Option<String>,
58+
is_public: Option<bool>,
59+
penalty_second: i64,
60+
}
61+
62+
#[post("/internal-api/contest/update")]
63+
pub async fn update_contest(
64+
_: web::ReqData<GithubToken>,
65+
pool: web::Data<PgPool>,
66+
query: web::Json<UpdateContestQuery>,
67+
) -> Result<impl Responder> {
68+
// TODO authorize
69+
pool.update_contest(
70+
&query.id,
71+
&query.title,
72+
&query.memo,
73+
query.start_epoch_second,
74+
query.duration_second,
75+
query.mode.as_deref(),
76+
query.is_public.unwrap_or(true),
77+
query.penalty_second,
78+
)
79+
.await
80+
.map_internal_server_err()?;
81+
let response = HttpResponse::Ok().finish();
82+
Ok(response)
83+
}
84+
85+
#[get("/internal-api/contest/get/{contest_id}")]
86+
pub async fn get_single_contest(
87+
pool: web::Data<PgPool>,
88+
contest_id: web::Path<String>,
89+
) -> Result<HttpResponse> {
90+
#[derive(Serialize)]
91+
struct VirtualContestDetails {
92+
info: VirtualContestInfo,
93+
problems: Vec<VirtualContestItem>,
94+
participants: Vec<String>,
95+
}
96+
let info = pool
97+
.get_single_contest_info(&contest_id)
98+
.await
99+
.map_internal_server_err()?;
100+
let participants = pool
101+
.get_single_contest_participants(&contest_id)
102+
.await
103+
.map_internal_server_err()?;
104+
let problems = pool
105+
.get_single_contest_problems(&contest_id)
106+
.await
107+
.map_internal_server_err()?;
108+
let contest = VirtualContestDetails {
109+
info,
110+
problems,
111+
participants,
112+
};
113+
let response = HttpResponse::Ok().json(&contest);
114+
Ok(response)
115+
}
116+
117+
#[derive(Deserialize)]
118+
pub struct SingleContestQuery {
119+
contest_id: String,
120+
}
121+
122+
#[post("/internal-api/contest/join")]
123+
pub async fn join_contest(
124+
token: web::ReqData<GithubToken>,
125+
pool: web::Data<PgPool>,
126+
query: web::Json<SingleContestQuery>,
127+
) -> Result<HttpResponse> {
128+
let user_id = token.id.to_string();
129+
pool.join_contest(&query.contest_id, &user_id)
130+
.await
131+
.map_internal_server_err()?;
132+
let response = HttpResponse::Ok().finish();
133+
Ok(response)
134+
}
135+
136+
#[post("/internal-api/contest/leave")]
137+
pub async fn leave_contest(
138+
token: web::ReqData<GithubToken>,
139+
pool: web::Data<PgPool>,
140+
query: web::Json<SingleContestQuery>,
141+
) -> Result<HttpResponse> {
142+
let user_id = token.id.to_string();
143+
pool.leave_contest(&query.contest_id, &user_id)
144+
.await
145+
.map_internal_server_err()?;
146+
let response = HttpResponse::Ok().finish();
147+
Ok(response)
148+
}
149+
150+
#[get("/internal-api/contest/my")]
151+
pub async fn get_my_contests(
152+
token: web::ReqData<GithubToken>,
153+
pool: web::Data<PgPool>,
154+
) -> Result<HttpResponse> {
155+
let user_id = token.id.to_string();
156+
let contests = pool
157+
.get_own_contests(&user_id)
158+
.await
159+
.map_internal_server_err()?;
160+
let response = HttpResponse::Ok().json(&contests);
161+
Ok(response)
162+
}
163+
164+
#[get("/internal-api/contest/joined")]
165+
pub async fn get_participated(
166+
token: web::ReqData<GithubToken>,
167+
pool: web::Data<PgPool>,
168+
) -> Result<HttpResponse> {
169+
let user_id = token.id.to_string();
170+
let contests = pool
171+
.get_participated_contests(&user_id)
172+
.await
173+
.map_internal_server_err()?;
174+
let response = HttpResponse::Ok().json(&contests);
175+
Ok(response)
176+
}
177+
178+
#[get("/internal-api/contest/recent")]
179+
pub async fn get_recent_contests(pool: web::Data<PgPool>) -> Result<HttpResponse> {
180+
let contest = pool
181+
.get_recent_contest_info()
182+
.await
183+
.map_internal_server_err()?;
184+
let response = HttpResponse::Ok().json(&contest);
185+
Ok(response)
186+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use actix_web::{post, web, HttpResponse, Responder, Result};
2+
use serde::Deserialize;
3+
use sql_client::{internal::problem_list_manager::ProblemListManager, PgPool};
4+
5+
use crate::server::{error::ApiResult, middleware::github_auth::GithubToken};
6+
7+
#[derive(Deserialize)]
8+
pub struct AddItemQuery {
9+
internal_list_id: String,
10+
problem_id: String,
11+
}
12+
13+
#[post("/internal-api/list/item/add")]
14+
pub async fn add_item(
15+
query: web::Json<AddItemQuery>,
16+
pool: web::Data<PgPool>,
17+
_: web::ReqData<GithubToken>,
18+
) -> Result<impl Responder> {
19+
// TODO authorize
20+
pool.add_item(&query.internal_list_id, &query.problem_id)
21+
.await
22+
.map_internal_server_err()?;
23+
let response = HttpResponse::Ok().finish();
24+
Ok(response)
25+
}
26+
27+
#[derive(Deserialize)]
28+
pub struct UpdateItemQuery {
29+
internal_list_id: String,
30+
problem_id: String,
31+
memo: String,
32+
}
33+
34+
#[post("/internal-api/list/item/update")]
35+
pub async fn update_item(
36+
query: web::Json<UpdateItemQuery>,
37+
pool: web::Data<PgPool>,
38+
_: web::ReqData<GithubToken>,
39+
) -> Result<impl Responder> {
40+
// TODO authorize
41+
pool.update_item(&query.internal_list_id, &query.problem_id, &query.memo)
42+
.await
43+
.map_internal_server_err()?;
44+
let response = HttpResponse::Ok().finish();
45+
Ok(response)
46+
}
47+
48+
#[derive(Deserialize)]
49+
pub struct DeleteItemQuery {
50+
internal_list_id: String,
51+
problem_id: String,
52+
}
53+
54+
#[post("/internal-api/list/item/delete")]
55+
pub async fn delete_item(
56+
query: web::Json<DeleteItemQuery>,
57+
pool: web::Data<PgPool>,
58+
_: web::ReqData<GithubToken>,
59+
) -> Result<impl Responder> {
60+
// TODO authorize
61+
pool.delete_item(&query.internal_list_id, &query.problem_id)
62+
.await
63+
.map_internal_server_err()?;
64+
let response = HttpResponse::Ok().finish();
65+
Ok(response)
66+
}

0 commit comments

Comments
 (0)