Skip to content

Commit a88d0f8

Browse files
committed
Refactor auth and remove old login route
1 parent 05ead05 commit a88d0f8

File tree

6 files changed

+211
-268
lines changed

6 files changed

+211
-268
lines changed

README.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@ cp .env.example .env
1616
### Get rust nightly
1717
Get [rustup](https://rustup.rs/) (nightly version)
1818

19-
run script, but pick nightly instead of stable. Can be changed for the project with rustup if you forget.
19+
run script, but pick nightly instead of stable. Subject to change when next time rocket [is upgraded](https://github.com/SergioBenitez/Rocket/milestone/8)
2020
```bash
2121
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
2222
````
2323

24-
If it doesn't compile you might need to install our currently used nightly version:
25-
```bash
26-
rustup toolchain install nightly-2021-05-11
27-
```
24+
rust version is managed by rust-toolchain.toml and should be automatically handled by ```cargo build```
2825

2926

3027
Install Diesel CLI
@@ -68,21 +65,24 @@ All the settings to set this up on a standard ubuntu 18.04, using Systemd and Ng
6865
The current setting assume that you use let's encrypt.
6966
Example instructions for how to set up can be found [here](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-18-04).
7067
71-
## Building
68+
69+
## Working Localy
70+
71+
### Building
7272
Build the project
7373
```bash
7474
cargo build
7575
```
7676
77-
## Starting the webserver
77+
### Starting the webserver
7878
Start the project using
7979
```bash
8080
cargo run
8181
```
8282
8383
In this mode anyone can log in as anyone by going to /mocklogin
8484
85-
## Tips and tricks
85+
### Tips and tricks
8686
Run new migrations with
8787
```bash
8888
diesel migration run
@@ -93,6 +93,7 @@ revert migrations with
9393
diesel migration revert
9494
```
9595
96+
9697
Install debuggin extensions for React and Redux
9798
(Chrome)
9899
- [React](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)
@@ -114,9 +115,6 @@ Install debuggin extensions for React and Redux
114115
Issues on mobile devices:
115116
* Images and text does not adapt to mobile devices
116117
117-
Backend needs:
118-
* Fix validate ticket regex index (switch to get)
119-
120118
Styling:
121119
* Location/Comment/Help section on /Queue/:queueName
122120
* Assistant/Teacher options drop-down on /Queue/:queueName

src/lib.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,7 @@ pub fn rocket() -> rocket::Rocket {
7878
.mount("/", StaticFiles::from("public/build"))
7979
.mount(
8080
"/",
81-
routes![
82-
routes::users::kth_auth,
83-
routes::users::kth_oidc_auth,
84-
routes::users::kth_login,
85-
],
81+
routes![routes::login::kth_oidc_auth, routes::login::kth_login,],
8682
)
8783
.mount(
8884
"/api",

src/routes/login.rs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
use crate::config::AppState;
2+
use crate::db;
3+
use crate::util::ugkthid_to_user;
4+
use anyhow::{anyhow, Result};
5+
use openidconnect::core::{
6+
CoreAuthDisplay, CoreAuthPrompt, CoreAuthenticationFlow, CoreErrorResponseType,
7+
CoreGenderClaim, CoreJsonWebKey, CoreJsonWebKeyType, CoreJsonWebKeyUse,
8+
CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm, CoreProviderMetadata,
9+
CoreRevocableToken, CoreRevocationErrorResponse, CoreTokenIntrospectionResponse, CoreTokenType,
10+
};
11+
use serde::Serialize;
12+
13+
use openidconnect::{
14+
AccessTokenHash, AdditionalClaims, AuthorizationCode, Client, ClientId, ClientSecret,
15+
CsrfToken, EmptyExtraTokenFields, IdTokenFields, IssuerUrl, Nonce, RedirectUrl, Scope,
16+
StandardErrorResponse, StandardTokenResponse,
17+
};
18+
use rocket::http::{Cookie, Cookies};
19+
use rocket::request::Form;
20+
use rocket::response::Redirect;
21+
use rocket::State;
22+
use rocket_client_addr::ClientAddr;
23+
use serde::Deserialize;
24+
use std::env;
25+
26+
use openidconnect::reqwest::http_client;
27+
use openidconnect::{OAuth2TokenResponse, TokenResponse};
28+
29+
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
30+
pub struct KthAdditionalClaims {
31+
kthid: std::string::String,
32+
}
33+
impl AdditionalClaims for KthAdditionalClaims {}
34+
35+
pub type KthIdTokenFields = IdTokenFields<
36+
KthAdditionalClaims,
37+
EmptyExtraTokenFields,
38+
CoreGenderClaim,
39+
CoreJweContentEncryptionAlgorithm,
40+
CoreJwsSigningAlgorithm,
41+
CoreJsonWebKeyType,
42+
>;
43+
44+
pub type KthTokenResponse = StandardTokenResponse<KthIdTokenFields, CoreTokenType>;
45+
46+
// EmptyAdditionalClaims,
47+
type KthClient = Client<
48+
KthAdditionalClaims,
49+
CoreAuthDisplay,
50+
CoreGenderClaim,
51+
CoreJweContentEncryptionAlgorithm,
52+
CoreJwsSigningAlgorithm,
53+
CoreJsonWebKeyType,
54+
CoreJsonWebKeyUse,
55+
CoreJsonWebKey,
56+
CoreAuthPrompt,
57+
StandardErrorResponse<CoreErrorResponseType>,
58+
KthTokenResponse,
59+
CoreTokenType,
60+
CoreTokenIntrospectionResponse,
61+
CoreRevocableToken,
62+
CoreRevocationErrorResponse,
63+
>;
64+
65+
#[get("/login")]
66+
pub fn kth_login(cookies: Cookies) -> Redirect {
67+
match use_oidc(cookies) {
68+
Ok(redirect) => redirect,
69+
Err(err) => {
70+
println!("oidc error: {:?}", err);
71+
Redirect::to("https://queue.csc.kth.se/failed_login")
72+
}
73+
}
74+
}
75+
76+
#[derive(FromForm, Default)]
77+
pub struct Code {
78+
code: Option<String>,
79+
#[allow(dead_code)]
80+
state: Option<String>,
81+
}
82+
83+
#[get("/oidc-auth?<params..>")]
84+
pub fn kth_oidc_auth(
85+
mut cookies: Cookies,
86+
conn: db::DbConn,
87+
state: State<AppState>,
88+
params: Form<Code>,
89+
client_addr: &ClientAddr,
90+
) -> Redirect {
91+
println!("starting oidc auth");
92+
// cookies.add(Cookie::new("nonce", nonce.secret().clone()));
93+
match cookies.get("nonce") {
94+
Some(nonce) => {
95+
println!("got nonce: {}", nonce.value());
96+
match get_oidc_user(params, Nonce::new(nonce.value().to_string())) {
97+
Ok(ugkthid) => {
98+
println!("good login!");
99+
match ugkthid_to_user(&conn, ugkthid) {
100+
Some(user) => {
101+
println!("User logged in: {:?}", user);
102+
cookies.add(Cookie::new(
103+
"userdata",
104+
json!(user.to_user_auth(&conn, &state.secret, client_addr))
105+
.to_string(),
106+
));
107+
}
108+
None => println!("Login failed for some reason..."),
109+
}
110+
}
111+
Err(err) => {
112+
println!("oidc error: {:?}", err);
113+
}
114+
}
115+
}
116+
None => println!("failed to get nonce"),
117+
}
118+
Redirect::to("/")
119+
}
120+
121+
pub fn get_client() -> Result<KthClient> {
122+
let provider_metadata = CoreProviderMetadata::discover(
123+
&IssuerUrl::new("https://login.ug.kth.se/adfs".to_string())?,
124+
http_client,
125+
)?;
126+
127+
// Create an OpenID Connect client by specifying the client ID, client secret, authorization URL
128+
// and token URL.
129+
let application_id = env::var("APPLICATION_ID").expect("OIDC need an application ID");
130+
let client_secret = env::var("CLIENT_SECRET").expect("OIDC need a client secret");
131+
let client = KthClient::from_provider_metadata(
132+
provider_metadata,
133+
ClientId::new(application_id),
134+
Some(ClientSecret::new(client_secret)),
135+
)
136+
// Set the URL the user will be redirected to after the authorization process.
137+
.set_redirect_uri(RedirectUrl::new(
138+
"https://queue.csc.kth.se/oidc-auth".to_string(),
139+
)?);
140+
Ok(client)
141+
}
142+
143+
pub fn use_oidc(mut cookies: Cookies) -> Result<Redirect> {
144+
let client = get_client()?;
145+
146+
// Generate the full authorization URL.
147+
let (auth_url, _csrf_token, nonce) = client
148+
.authorize_url(
149+
CoreAuthenticationFlow::AuthorizationCode,
150+
CsrfToken::new_random,
151+
Nonce::new_random,
152+
)
153+
// Set the desired scopes.
154+
.add_scope(Scope::new("kthid".to_string()))
155+
.url();
156+
157+
cookies.add(Cookie::new("nonce", nonce.secret().clone()));
158+
// println!("wrote nonce: {:?}", nonce.secret());
159+
Ok(Redirect::to(auth_url.to_string()))
160+
}
161+
162+
pub fn get_oidc_user(params: Form<Code>, nonce: Nonce) -> Result<String> {
163+
// println!("got nonce: {:?}", nonce.secret());
164+
let client = get_client()?;
165+
let code = params
166+
.code
167+
.as_ref()
168+
.ok_or_else(|| anyhow!("got no code in request"))?;
169+
// Once the user has been redirected to the redirect URL, you'll have access to the
170+
// authorization code. For security reasons, your code should verify that the `state`
171+
// parameter returned by the server matches `csrf_state`.
172+
173+
// Now you can exchange it for an access token and ID token.
174+
let token_response = client
175+
.exchange_code(AuthorizationCode::new(code.to_string()))
176+
.request(http_client)?;
177+
178+
// Extract the ID token claims after verifying its authenticity and nonce.
179+
let id_token = token_response
180+
.id_token()
181+
.ok_or_else(|| anyhow!("Server did not return an ID token"))?;
182+
let claims = id_token.claims(&client.id_token_verifier(), &nonce)?;
183+
184+
println!("Got the claims: {:?}", claims);
185+
// Verify the access token hash to ensure that the access token hasn't been substituted for
186+
// another user's.
187+
if let Some(expected_access_token_hash) = claims.access_token_hash() {
188+
let actual_access_token_hash =
189+
AccessTokenHash::from_token(token_response.access_token(), &id_token.signing_alg()?)?;
190+
if actual_access_token_hash != *expected_access_token_hash {
191+
return Err(anyhow!("Invalid access token"));
192+
}
193+
}
194+
println!("almost done now!");
195+
196+
// The authenticated user's identity is now available. See the IdTokenClaims struct for a
197+
// complete listing of the available claims.
198+
println!("Got kthid: {:?}", claims.additional_claims().kthid);
199+
Ok(claims.additional_claims().kthid.clone())
200+
}

src/routes/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod admins;
2+
pub mod login;
23
pub mod queue_entries;
34
pub mod queues;
45
pub mod super_admins;

0 commit comments

Comments
 (0)