Content-Length: 629240 | pFad | https://github.com/sebadob/rauthy/commit/c37e1f5bc27ebd679c4424ef568a8bae82c523bc

D2 Merge pull request #436 from sebadob/feat-post-session · sebadob/rauthy@c37e1f5 · GitHub
Skip to content

Commit

Permalink
Merge pull request #436 from sebadob/feat-post-session
Browse files Browse the repository at this point in the history
feat: POST `/session` endpoint + content negotiation for password reset form
  • Loading branch information
sebadob authored May 16, 2024
2 parents d728317 + edc3cd6 commit c37e1f5
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 53 deletions.
4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ test-backend: migrate prepare
clear
just _run cargo build
@docker run --rm -it \
docker run --rm -it \
-v $HOME/.cargo/registry:{{container_cargo_registry}} \
-v {{invocation_directory()}}/:/work/ \
-u $USER \
Expand Down Expand Up @@ -397,7 +397,7 @@ __build-docs:

# mode = release or debug / no-test = no-test or do-test / image = name for the final image
build mode="release" no-test="do-test" image="ghcr.io/sebadob/rauthy": build-ui
build mode="release" no-test="test" image="ghcr.io/sebadob/rauthy": build-ui
#!/usr/bin/env bash
set -euxo pipefail
Expand Down
90 changes: 73 additions & 17 deletions rauthy-handlers/src/oidc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use rauthy_common::constants::{
AUTH_HEADER_FAMILY_NAME, AUTH_HEADER_GIVEN_NAME, AUTH_HEADER_GROUPS, AUTH_HEADER_MFA,
AUTH_HEADER_ROLES, AUTH_HEADER_USER, COOKIE_MFA, DEVICE_GRANT_CODE_LIFETIME,
DEVICE_GRANT_POLL_INTERVAL, DEVICE_GRANT_RATE_LIMIT, GRANT_TYPE_DEVICE_CODE, HEADER_HTML,
HEADER_RETRY_NOT_BEFORE, OPEN_USER_REG, SESSION_LIFETIME,
HEADER_RETRY_NOT_BEFORE, OPEN_USER_REG, SESSION_LIFETIME, SESSION_VALIDATE_IP,
};
use rauthy_common::error_response::{ErrorResponse, ErrorResponseType};
use rauthy_common::utils::real_ip_from_req;
Expand Down Expand Up @@ -126,7 +126,7 @@ pub async fn get_authorize(
}
}

// check for no-prompt
// check for `prompt=no-prompt`
if !force_new_session
&& req_data
.prompt
Expand Down Expand Up @@ -168,7 +168,22 @@ pub async fn get_authorize(
return Ok(HttpResponse::Ok().append_header(HEADER_HTML).body(body));
}

let session = Session::new(*SESSION_LIFETIME, real_ip_from_req(&req));
// check if we can re-use a still valid session or need to create a new one
let session = if let Some(session) = &principal.session {
let remote_ip = if *SESSION_VALIDATE_IP {
Some(real_ip_from_req(&req).unwrap_or_default())
} else {
None
};
if session.is_valid(data.session_timeout, remote_ip) {
session.clone()
} else {
Session::new(*SESSION_LIFETIME, real_ip_from_req(&req))
}
} else {
Session::new(*SESSION_LIFETIME, real_ip_from_req(&req))
};

if let Err(err) = session.save(&data).await {
let status = err.status_code();
let body = Error1Html::build(&colors, &lang, status, Some(err.message));
Expand Down Expand Up @@ -645,15 +660,55 @@ pub async fn rotate_jwk(
.map(|_| HttpResponse::Ok().finish())
}

//github.com/ Create a new session
//github.com/
//github.com/ You can use this endpoint to create a new session outside the `/authorize` page when logging
//github.com/ in. This endpoint is used inside Rauthy's integration tests internally and may be used, if you
//github.com/ want to create your own login or other user facing UI components.
//github.com/ This endpoint will return a session in `Init` state. You will need to log in and verify your
//github.com/ credentials, but it is a prerequisite for using the `/authorize` endpoint.
//github.com/
//github.com/ CAUTION: You will never see the returned CSRF token again - save it somewhere safe!
#[utoipa::path(
post,
path = "/oidc/session",
tag = "oidc",
responses(
(status = 201, description = "Created"),
),
)]
#[post("/oidc/session")]
pub async fn post_session(
data: web::Data<AppState>,
req: HttpRequest,
) -> Result<HttpResponse, ErrorResponse> {
let session = Session::new(*SESSION_LIFETIME, real_ip_from_req(&req));
session.save(&data).await?;
let cookie = session.client_cookie();

let timeout = OffsetDateTime::from_unix_timestamp(session.last_seen)
.unwrap()
.add(::time::Duration::seconds(data.session_timeout as i64));
let info = SessionInfoResponse {
id: session.id.as_str().into(),
csrf_token: Some(session.csrf_token.as_str().into()),
user_id: session.user_id.as_deref().map(|v| v.into()),
roles: session.roles.as_deref().map(|v| v.into()),
groups: session.groups.as_deref().map(|v| v.into()),
exp: OffsetDateTime::from_unix_timestamp(session.exp).unwrap(),
timeout,
state: session.state.clone(),
};

Ok(HttpResponse::Created().cookie(cookie).json(info))
}

//github.com/ OIDC sessioninfo
//github.com/
//github.com/ Returns information about the current session. This is currently only used in the Rauthy Admin UI
//github.com/ and does not make much sense anywhere else. Only works with a fully authenticated session.
//github.com/
//github.com/ **Permissions**
//github.com/ - session-auth
#[utoipa::path(
post,
get,
path = "/oidc/sessioninfo",
tag = "oidc",
responses(
Expand All @@ -668,18 +723,18 @@ pub async fn get_session_info(
principal.validate_session_auth()?;
let session = principal.get_session()?;

// let timeout_secs = session.last_seen.timestamp() + data.session_timeout as i64;
let timeout = OffsetDateTime::from_unix_timestamp(session.last_seen)
.unwrap()
.add(::time::Duration::seconds(data.session_timeout as i64));
let info = SessionInfoResponse {
id: &session.id,
id: session.id.as_str().into(),
csrf_token: None,
user_id: session.user_id.as_ref(),
roles: session.roles.as_ref(),
groups: session.groups.as_ref(),
user_id: session.user_id.as_deref().map(|v| v.into()),
roles: session.roles.as_deref().map(|v| v.into()),
groups: session.groups.as_deref().map(|v| v.into()),
exp: OffsetDateTime::from_unix_timestamp(session.exp).unwrap(),
timeout,
state: session.state.clone(),
};

Ok(HttpResponse::Ok().json(info))
Expand Down Expand Up @@ -718,13 +773,14 @@ pub async fn get_session_xsrf(
.unwrap()
.add(::time::Duration::seconds(data.session_timeout as i64));
let info = SessionInfoResponse {
id: &session.id,
csrf_token: Some(&session.csrf_token),
user_id: session.user_id.as_ref(),
roles: session.roles.as_ref(),
groups: session.groups.as_ref(),
id: session.id.as_str().into(),
csrf_token: Some(session.csrf_token.as_str().into()),
user_id: session.user_id.as_deref().map(|v| v.into()),
roles: session.roles.as_deref().map(|v| v.into()),
groups: session.groups.as_deref().map(|v| v.into()),
exp: OffsetDateTime::from_unix_timestamp(session.exp).unwrap(),
timeout,
state: session.state.clone(),
};
Ok(HttpResponse::Ok().json(info))
}
Expand Down
2 changes: 2 additions & 0 deletions rauthy-handlers/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ use utoipa::{openapi, OpenApi};
oidc::get_logout,
oidc::post_logout,
oidc::rotate_jwk,
oidc::post_session,
oidc::get_session_info,
oidc::get_session_xsrf,
oidc::post_token,
Expand Down Expand Up @@ -212,6 +213,7 @@ use utoipa::{openapi, OpenApi};
response::AppVersionResponse,
response::BlacklistResponse,
response::BlacklistedIp,
response::CsrfTokenResponse,
response::LoginTimeResponse,
response::ClientResponse,
response::DeviceCodeResponse,
Expand Down
40 changes: 29 additions & 11 deletions rauthy-handlers/src/users.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::ReqPrincipal;
use actix_web::http::header::LOCATION;
use actix_web::http::header::{ACCEPT, LOCATION};
use actix_web::http::StatusCode;
use actix_web::{delete, get, post, put, web, HttpRequest, HttpResponse, ResponseError};
use actix_web_validator::{Json, Query};
use rauthy_common::constants::{
COOKIE_MFA, ENABLE_WEB_ID, HEADER_ALLOW_ALL_ORIGINS, HEADER_HTML, OPEN_USER_REG,
COOKIE_MFA, ENABLE_WEB_ID, HEADER_ALLOW_ALL_ORIGINS, HEADER_HTML, HEADER_JSON, OPEN_USER_REG,
PWD_RESET_COOKIE, SSP_THRESHOLD, TEXT_TURTLE, USER_REG_DOMAIN_RESTRICTION,
};
use rauthy_common::error_response::{ErrorResponse, ErrorResponseType};
Expand Down Expand Up @@ -32,8 +32,8 @@ use rauthy_models::request::{
WebauthnAuthStartRequest, WebauthnRegFinishRequest, WebauthnRegStartRequest,
};
use rauthy_models::response::{
DeviceResponse, PasskeyResponse, UserAttrConfigResponse, UserAttrValueResponse,
UserAttrValuesResponse, UserResponse, WebIdResponse,
CsrfTokenResponse, DeviceResponse, PasskeyResponse, UserAttrConfigResponse,
UserAttrValueResponse, UserAttrValuesResponse, UserResponse, WebIdResponse,
};
use rauthy_models::templates::{Error1Html, Error3Html, ErrorHtml, UserRegisterHtml};
use rauthy_service::password_reset;
Expand Down Expand Up @@ -563,7 +563,7 @@ pub async fn get_user_email_confirm(
path = "/users/{id}/reset/{reset_id}",
tag = "users",
responses(
(status = 200, description = "Ok"),
(status = 200, description = "Ok", body = CsrfTokenResponse),
(status = 401, description = "Unauthorized", body = ErrorResponse),
(status = 403, description = "Forbidden", body = ErrorResponse),
),
Expand All @@ -574,13 +574,31 @@ pub async fn get_user_password_reset(
path: web::Path<(String, String)>,
req: HttpRequest,
) -> HttpResponse {
let lang = Language::try_from(&req).unwrap_or_default();
let (user_id, reset_id) = path.into_inner();
match password_reset::handle_get_pwd_reset(&data, req, user_id, reset_id).await {
Ok((html, cookie)) => HttpResponse::Ok()
.cookie(cookie)
.insert_header(HEADER_HTML)
.body(html),
let lang = Language::try_from(&req).unwrap_or_default();
let accept = req
.headers()
.get(ACCEPT)
.map(|v| v.to_str().unwrap_or("text/html"))
.unwrap_or("text/html");
let no_html = accept == "application/json";

match password_reset::handle_get_pwd_reset(&data, req, user_id, reset_id, no_html).await {
Ok((content, cookie)) => {
if no_html {
HttpResponse::Ok()
.cookie(cookie)
.insert_header(HEADER_JSON)
.json(CsrfTokenResponse {
csrf_token: content,
})
} else {
HttpResponse::Ok()
.cookie(cookie)
.insert_header(HEADER_HTML)
.body(content)
}
}
Err(err) => {
let colors = ColorEntity::find_rauthy(&data).await.unwrap_or_default();
let status = err.status_code();
Expand Down
1 change: 1 addition & 0 deletions rauthy-main/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ async fn actix_main(app_state: web::Data<AppState>) -> std::io::Result<()> {
.service(oidc::get_logout)
.service(oidc::post_logout)
.service(oidc::rotate_jwk)
.service(oidc::post_session)
.service(oidc::get_session_info)
.service(oidc::get_session_xsrf)
.service(clients::get_clients)
Expand Down
45 changes: 37 additions & 8 deletions rauthy-main/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
use rauthy_common::constants::CSRF_HEADER;
use rauthy_common::utils::base64_url_encode;
use rauthy_models::request::{LoginRequest, TokenRequest};
use rauthy_models::response::SessionInfoResponse;
use rauthy_service::token_set::TokenSet;
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::header::{HeaderMap, HeaderValue, SET_COOKIE};
use reqwest::{header, Response};
use ring::digest;
use std::env;
Expand Down Expand Up @@ -72,8 +73,8 @@ pub async fn get_token_set() -> TokenSet {

pub async fn session_headers() -> (HeaderMap, TokenSet) {
let backend_url = get_backend_url();
let client = reqwest::Client::new();

// Step 1: GET /authorize for the CSRF token and simulate UI login
let challenge_plain = "oDXug9zfYqfz8ejcqMpALRPXfW8QhbKV2AVuScAt8xrLKDAmaRYQ4yRi2uqcH9ys";
let redirect_uri = format!("{}/oidc/callback", backend_url);
let query = format!(
Expand All @@ -87,8 +88,11 @@ pub async fn session_headers() -> (HeaderMap, TokenSet) {
query, challenge_s256
);
let url_auth = format!("{}/oidc/authorize?{}", backend_url, query_pkce);
let res = reqwest::get(&url_auth).await.unwrap();
let headers = cookie_csrf_headers_from_res(res).await.unwrap();

// we need a session in Init state
let url_session = format!("{}/oidc/session", backend_url);
let res = client.post(&url_session).send().await.unwrap();
let headers = cookie_csrf_headers_from_res_direct(res).await.unwrap();

let req_login = LoginRequest {
email: USERNAME.to_string(),
Expand All @@ -102,14 +106,14 @@ pub async fn session_headers() -> (HeaderMap, TokenSet) {
code_challenge_method: Some("S256".to_string()),
};

let mut res = reqwest::Client::new()
let res = client
.post(&url_auth)
.headers(headers.clone())
.json(&req_login)
.send()
.await
.unwrap();
res = check_status(res, 202).await.unwrap();
assert_eq!(res.status(), 202);

let (code, _state) = code_state_from_headers(res).unwrap();
let req_token = TokenRequest {
Expand All @@ -126,19 +130,44 @@ pub async fn session_headers() -> (HeaderMap, TokenSet) {
};

let url_token = format!("{}/oidc/token", backend_url);
let mut res = reqwest::Client::new()
let res = client
.post(&url_token)
.form(&req_token)
.send()
.await
.unwrap();
res = check_status(res, 200).await.unwrap();
assert_eq!(res.status(), 200);

let ts = res.json::<TokenSet>().await.unwrap();

(headers, ts)
}

//github.com/ extractor for the POST `/oidc/session` endpoint
pub async fn cookie_csrf_headers_from_res_direct(
res: Response,
) -> Result<HeaderMap, Box<dyn Error>> {
assert!(res.status().is_success());

let cookie = res
.headers()
.get(SET_COOKIE)
.expect("Set-Cookie header to exist");
let (session_cookie, _) = cookie.to_str()?.split_once(';').unwrap();

let mut headers = HeaderMap::new();
headers.append(header::COOKIE, HeaderValue::from_str(&session_cookie)?);

let session_info = res.json::<SessionInfoResponse>().await.unwrap();
headers.append(
CSRF_HEADER,
HeaderValue::from_str(&session_info.csrf_token.unwrap())?,
);

Ok(headers)
}

//github.com/ extractor from the `/oidc/authorize` html
pub async fn cookie_csrf_headers_from_res(res: Response) -> Result<HeaderMap, Box<dyn Error>> {
let cookie = res.headers().get(header::SET_COOKIE).unwrap();
let (session_cookie, _) = cookie.to_str()?.split_once(';').unwrap();
Expand Down
Loading

0 comments on commit c37e1f5

Please sign in to comment.








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/sebadob/rauthy/commit/c37e1f5bc27ebd679c4424ef568a8bae82c523bc

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy