Skip to content

Commit cee7cb8

Browse files
committed
working on auth flow code
1 parent 1662c85 commit cee7cb8

File tree

7 files changed

+186
-6
lines changed

7 files changed

+186
-6
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ unicode-truncate = "0.2.0"
3232
dns-lookup = "1.0.6"
3333
rocket-client-addr = "0.4.6"
3434
openidconnect = "2.0.1"
35+
anyhow = "1.0.40"
3536

3637

3738
[dependencies.rocket_contrib]

devops/https-nginx.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ http {
1616
server {
1717
server_name queue.csc.kth.se;
1818

19-
location ~ ^/(api/|auth|login) {
19+
location ~ ^/(api/|auth|login|oidc_auth) {
2020
proxy_pass http://localhost:8000;
2121
proxy_http_version 1.1;
2222
proxy_set_header Upgrade $http_upgrade;

src/config.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ pub fn from_env() -> Config {
4848
.expect("PORT environment variable should parse to an integer");
4949

5050
let address = env::var("ROCKET_ADDRESS").unwrap_or_else(|_| "127.0.0.1".to_string());
51-
// let application_id = env::var("APPLICATION_ID").unwrap_or_else(|_| "unset".to_string());
52-
// let client_secret = env::var("CLIENT_SECRET").unwrap_or_else(|_| "unset".to_string());
5351

5452
let mut database_config = HashMap::new();
5553
let mut databases = HashMap::new();

src/lib.rs

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

src/routes/user_events.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::auth::{validate_auth, Auth, AuthLevel};
2-
use crate::db::{self};
2+
use crate::db;
33
use rocket::request::Form;
44

55
use rocket_contrib::json::JsonValue;

src/routes/users.rs

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
use crate::auth::Auth;
22
use crate::config::AppState;
3-
use crate::db::{self};
3+
use crate::db;
44
use crate::errors::{Errors, FieldValidator};
55
use crate::util::{handle_login, Ticket};
6+
use anyhow::{anyhow, Result};
7+
use openidconnect::core::{
8+
CoreAuthenticationFlow, CoreClient, CoreProviderMetadata, CoreUserInfoClaims,
9+
};
10+
11+
use openidconnect::{
12+
AccessTokenHash, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce,
13+
PkceCodeChallenge, RedirectUrl, Scope,
14+
};
615
use rocket::http::{Cookie, Cookies};
716
use rocket::request::Form;
817
use rocket::response::Redirect;
918
use rocket::State;
1019
use rocket_client_addr::ClientAddr;
1120
use rocket_contrib::json::{Json, JsonValue};
1221
use serde::Deserialize;
22+
use std::env;
23+
24+
use openidconnect::reqwest::http_client;
25+
26+
use openidconnect::{OAuth2TokenResponse, TokenResponse};
1327

1428
#[derive(Deserialize)]
1529
pub struct LoginUser {
@@ -59,9 +73,39 @@ pub fn post_users_login(
5973

6074
#[get("/login")]
6175
pub fn kth_login() -> Redirect {
76+
if let Ok(oidc) = env::var("USE_OIDC") {
77+
println!("use oidc: {}", oidc);
78+
match oidc.as_str() {
79+
"true" => return use_oidc(),
80+
_ => {}
81+
}
82+
}
6283
Redirect::to("https://login.kth.se/login?service=https://queue.csc.kth.se/auth")
6384
}
6485

86+
#[derive(FromForm, Default)]
87+
pub struct Code {
88+
code: Option<String>,
89+
state: Option<String>,
90+
}
91+
92+
#[get("/oidc-auth?<params..>")]
93+
pub fn kth_oidc_auth(
94+
mut cookies: Cookies,
95+
conn: db::DbConn,
96+
state: State<AppState>,
97+
params: Form<Code>,
98+
client_addr: &ClientAddr,
99+
) -> Redirect {
100+
match get_oidc_user(params) {
101+
Ok(_) => println!("good login!"),
102+
Err(err) => {
103+
println!("oidc error: {:?}", err);
104+
}
105+
}
106+
Redirect::to("/")
107+
}
108+
65109
#[get("/auth?<params..>")]
66110
pub fn kth_auth(
67111
mut cookies: Cookies,
@@ -82,3 +126,129 @@ pub fn kth_auth(
82126
}
83127
Redirect::to("/")
84128
}
129+
130+
pub fn get_client() -> Result<CoreClient> {
131+
// "https://login.ug.kth.se/adfs/.well-known/openid-configuration".to_string(),
132+
// println!(
133+
// "metadata: {:?}",
134+
// &IssuerUrl::new(
135+
// "https://login.ug.kth.se/adfs/.well-known/openid-configuration".to_string(),
136+
// )?
137+
// );
138+
let provider_metadata = CoreProviderMetadata::discover(
139+
&IssuerUrl::new("https://login.ug.kth.se/adfs".to_string())?,
140+
http_client,
141+
)?;
142+
143+
// Create an OpenID Connect client by specifying the client ID, client secret, authorization URL
144+
// and token URL.
145+
let application_id = env::var("APPLICATION_ID").expect("OIDC need an application ID");
146+
let client_secret = env::var("CLIENT_SECRET").expect("OIDC need a client secret");
147+
let client = CoreClient::from_provider_metadata(
148+
provider_metadata,
149+
ClientId::new(application_id),
150+
Some(ClientSecret::new(client_secret)),
151+
)
152+
// Set the URL the user will be redirected to after the authorization process.
153+
.set_redirect_uri(RedirectUrl::new(
154+
"https://queue.csc.kth.se/oidc-auth".to_string(),
155+
)?);
156+
Ok(client)
157+
}
158+
159+
pub fn use_oidc() -> Redirect {
160+
match generate_redirect() {
161+
Ok(redirect) => redirect,
162+
Err(err) => {
163+
println!("oidc error: {:?}", err);
164+
165+
Redirect::to("https://queue.csc.kth.se/failed_login")
166+
}
167+
}
168+
}
169+
170+
pub fn generate_redirect() -> Result<Redirect> {
171+
println!("generating redirect");
172+
let client = get_client()?;
173+
174+
// Generate the full authorization URL.
175+
let (auth_url, _csrf_token, _nonce) = client
176+
.authorize_url(
177+
CoreAuthenticationFlow::AuthorizationCode,
178+
CsrfToken::new_random,
179+
Nonce::new_random,
180+
)
181+
// Set the desired scopes.
182+
// .add_scope(Scope::new("openid".to_string()))
183+
.url();
184+
185+
// This is the URL you should redirect the user to, in order to trigger the authorization
186+
// process.
187+
println!("Browse to: {}", auth_url);
188+
189+
Ok(Redirect::to(auth_url.to_string()))
190+
}
191+
192+
pub fn get_oidc_user(params: Form<Code>) -> Result<()> {
193+
let client = get_client()?;
194+
println!("getting oidc_user");
195+
let code = params
196+
.code
197+
.as_ref()
198+
.ok_or_else(|| anyhow!("got no code in request"))?;
199+
println!("code: {}", code);
200+
let nonce = Nonce::new("fake_nonce".to_string());
201+
// Once the user has been redirected to the redirect URL, you'll have access to the
202+
// authorization code. For security reasons, your code should verify that the `state`
203+
// parameter returned by the server matches `csrf_state`.
204+
205+
// Now you can exchange it for an access token and ID token.
206+
let token_response = client
207+
.exchange_code(AuthorizationCode::new(code.to_string()))
208+
.request(http_client)?;
209+
210+
println!("Got token response");
211+
// Extract the ID token claims after verifying its authenticity and nonce.
212+
let id_token = token_response
213+
.id_token()
214+
.ok_or_else(|| anyhow!("Server did not return an ID token"))?;
215+
let claims = id_token.claims(&client.id_token_verifier(), &nonce)?;
216+
217+
println!("Got the claims: {:?}", claims);
218+
// Verify the access token hash to ensure that the access token hasn't been substituted for
219+
// another user's.
220+
if let Some(expected_access_token_hash) = claims.access_token_hash() {
221+
let actual_access_token_hash =
222+
AccessTokenHash::from_token(token_response.access_token(), &id_token.signing_alg()?)?;
223+
if actual_access_token_hash != *expected_access_token_hash {
224+
return Err(anyhow!("Invalid access token"));
225+
}
226+
}
227+
println!("almost done now!");
228+
229+
// The authenticated user's identity is now available. See the IdTokenClaims struct for a
230+
// complete listing of the available claims.
231+
println!(
232+
"User {} with e-mail address {} has authenticated successfully",
233+
claims.subject().as_str(),
234+
claims
235+
.email()
236+
.map(|email| email.as_str())
237+
.unwrap_or("<not provided>"),
238+
);
239+
240+
// If available, we can use the UserInfo endpoint to request additional information.
241+
242+
// The user_info request uses the AccessToken returned in the token response. To parse custom
243+
// claims, use UserInfoClaims directly (with the desired type parameters) rather than using the
244+
// CoreUserInfoClaims type alias.
245+
let _userinfo: CoreUserInfoClaims = client
246+
.user_info(token_response.access_token().to_owned(), None)
247+
.map_err(|err| anyhow!("No user info endpoint: {:?}", err))?
248+
.request(http_client)
249+
.map_err(|err| anyhow!("Failed requesting user info: {:?}", err))?;
250+
251+
// See the OAuth2TokenResponse trait for a listing of other available fields such as
252+
// access_token() and refresh_token().
253+
Ok(())
254+
}

0 commit comments

Comments
 (0)