Skip to content

Commit

Permalink
Openid Connect implementation (#262)
Browse files Browse the repository at this point in the history
* Openidconnect implementation
  • Loading branch information
xerbalind authored Nov 8, 2023
1 parent 462a1e7 commit 68e900b
Show file tree
Hide file tree
Showing 14 changed files with 510 additions and 170 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ db/*
.envrc
static/dist/
node_modules/
keys/*.pem
79 changes: 79 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ tempfile = "3.1"
parking_lot = { version = "0.12" }
thiserror = "1.0"
validator = { version = "0.16", features = [ "derive" ] }
jsonwebtoken = "9.1"
openssl = "0.10"

[build-dependencies]
openssl = "0.10"
3 changes: 3 additions & 0 deletions Rocket.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ maximum_pending_users = 25

[debug]
secret_key = "1vwCFFPSdQya895gNiO556SzmfShG6MokstgttLvwjw="
ec_private_key = "keys/jwt_key.pem"
bcrypt_cost = 4
seed_database = true

Expand All @@ -29,6 +30,8 @@ port = 8000
# Values you want to fill in for production use
# admin_email = # Email address to send admin notifications to (e.g. [email protected])
# secret_key = # used to encrypt cookies (generate a new one!)
# ec_private_key = # Path to ECDSA private key for signing jwt's. Key Algo needs to be ES384 in PKCS#8 form.
# generate by running: openssl ecparam -genkey -noout -name secp384r1 | openssl pkcs8 -topk8 -nocrypt -out ec-private.pem)
# base_url = # URL where the application is hosten (e.g. https://auth.zeus.gent)
# mail_from = # From header to set when sending emails (e.g. [email protected])
# mail_server = # domain of the SMTP server used to send mail (e.g. smtp.zeus.gent)
Expand Down
18 changes: 18 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::fs::File;
use std::io::Write;
use std::path::Path;

use openssl::ec::{EcGroup, EcKey};
use openssl::nid::Nid;
use openssl::pkey::PKey;

fn main() {
let path = Path::new("keys/jwt_key.pem");
if !path.exists() {
let group = EcGroup::from_curve_name(Nid::SECP384R1).unwrap();
let pkey = PKey::from_ec_key(EcKey::generate(&group).unwrap()).unwrap();
let mut f = File::create(path).unwrap();
let pem = pkey.private_key_to_pem_pkcs8().unwrap();
f.write_all(&pem).unwrap();
}
}
Empty file added keys/.gitkeep
Empty file.
1 change: 1 addition & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct Config {
pub secure_token_length: usize,
pub bcrypt_cost: u32,
pub base_url: String,
pub ec_private_key: String,
pub mail_queue_size: usize,
pub mail_queue_wait_seconds: u64,
pub mail_from: String,
Expand Down
41 changes: 36 additions & 5 deletions src/controllers/oauth_controller.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use jsonwebtoken::jwk::JwkSet;
use rocket::form::Form;
use rocket::http::{Cookie, CookieJar};
use rocket::response::{Redirect, Responder};
Expand All @@ -10,6 +11,7 @@ use crate::ephemeral::session::UserSession;
use crate::errors::Either::{Left, Right};
use crate::errors::*;
use crate::http_authentication::BasicAuthentication;
use crate::jwt::JWTBuilder;
use crate::models::client::*;
use crate::models::session::*;
use crate::models::user::*;
Expand Down Expand Up @@ -161,6 +163,7 @@ pub struct UserToken {
pub client_id: i32,
pub client_name: String,
pub redirect_uri: String,
pub scope: Option<String>,
}

#[get("/oauth/grant")]
Expand Down Expand Up @@ -215,6 +218,7 @@ async fn authorization_granted(
let authorization_code = token_store
.create_token(UserToken {
user_id: user.id,
scope: state.scope.clone(),
username: user.username.clone(),
client_id: state.client_id.clone(),
client_name: state.client_name.clone(),
Expand All @@ -240,6 +244,8 @@ fn authorization_denied(state: AuthState) -> Redirect {
pub struct TokenSuccess {
access_token: String,
token_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
id_token: Option<String>,
expires_in: i64,
}

Expand All @@ -258,6 +264,7 @@ pub async fn token(
form: Form<TokenFormData>,
config: &State<Config>,
token_state: &State<TokenStore<UserToken>>,
jwt_builder: &State<JWTBuilder>,
db: DbConn,
) -> Result<Json<TokenSuccess>> {
let data = form.into_inner();
Expand Down Expand Up @@ -306,13 +313,37 @@ pub async fn token(
)))
} else {
let user = User::find(token.user_id, &db).await?;
let session =
Session::create_client_session(&user, &client, &config, &db)
.await?;
let id_token = token
.scope
.as_ref()
.map(|scope| -> Option<String> {
match scope.contains("openid") {
true => {
jwt_builder.encode_id_token(&client, &user, config).ok()
},
false => None,
}
})
.flatten();

let session = Session::create_client_session(
&user,
&client,
token.scope,
&config,
&db,
)
.await?;
Ok(Json(TokenSuccess {
access_token: session.key.unwrap().clone(),
token_type: String::from("bearer"),
expires_in: config.client_session_seconds,
token_type: String::from("bearer"),
id_token,
expires_in: config.client_session_seconds,
}))
}
}

#[get("/oauth/jwks")]
pub async fn jwks(jwt_builder: &State<JWTBuilder>) -> Json<JwkSet> {
Json(jwt_builder.jwks.clone())
}
2 changes: 2 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ pub enum InternalError {
BincodeError(#[from] Box<bincode::ErrorKind>),
#[error("B64 decode error")]
Base64DecodeError(#[from] base64::DecodeError),
#[error("JWT error")]
JWTError(#[from] jsonwebtoken::errors::Error),
}
pub type InternalResult<T> = std::result::Result<T, InternalError>;

Expand Down
Loading

0 comments on commit 68e900b

Please sign in to comment.