Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions v-api/src/endpoints/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ mod macros {
rqctx: RequestContext<$context_type>,
path: Path<OAuthProviderNameParam>,
query: Query<OAuthAuthzCodeQuery>,
) -> Result<Response<Body>, HttpError> {
) -> Result<HttpResponseTemporaryRedirect, HttpError> {
authz_code_redirect_op(&rqctx, path, query).await
}

Expand All @@ -283,7 +283,7 @@ mod macros {
rqctx: RequestContext<$context_type>,
path: Path<OAuthProviderNameParam>,
query: Query<OAuthAuthzCodeReturnQuery>,
) -> Result<Response<Body>, HttpError> {
) -> Result<HttpResponseTemporaryRedirect, HttpError> {
authz_code_callback_op(&rqctx, path, query).await
}

Expand Down
78 changes: 27 additions & 51 deletions v-api/src/endpoints/login/oauth/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@ use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
use chrono::{TimeDelta, Utc};
use cookie::{Cookie, SameSite};
use dropshot::{
Body, ClientErrorStatusCode, HttpError, HttpResponseOk, Path, Query, RequestContext,
RequestInfo, SharedExtractor, TypedBody,
http_response_temporary_redirect, ClientErrorStatusCode, HttpError, HttpResponseOk,
HttpResponseTemporaryRedirect, Path, Query, RequestContext, RequestInfo, SharedExtractor,
TypedBody,
};
use dropshot_authorization_header::basic::BasicAuth;
use http::{
header::{LOCATION, SET_COOKIE},
HeaderValue, StatusCode,
};
use hyper::Response;
use http::{header::SET_COOKIE, HeaderValue};
use newtype_uuid::TypedUuid;
use oauth2::{
AuthorizationCode, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, Scope, TokenResponse,
Expand Down Expand Up @@ -160,7 +157,7 @@ pub async fn authz_code_redirect_op<T>(
rqctx: &RequestContext<impl ApiContext<AppPermissions = T>>,
path: Path<OAuthProviderNameParam>,
query: Query<OAuthAuthzCodeQuery>,
) -> Result<Response<Body>, HttpError>
) -> Result<HttpResponseTemporaryRedirect, HttpError>
where
T: VAppPermission + PermissionStorage,
{
Expand Down Expand Up @@ -237,7 +234,7 @@ fn oauth_redirect_response(
provider: &dyn OAuthProvider,
attempt: &LoginAttempt,
code_challenge: Option<PkceCodeChallenge>,
) -> Result<Response<Body>, HttpError> {
) -> Result<HttpResponseTemporaryRedirect, HttpError> {
// We may fail if the provider configuration is not correctly configured
// TODO: This behavior should be changed so that clients are precomputed. We do not need to be
// constructing a new client on every request. That said, we need to ensure the client does not
Expand Down Expand Up @@ -274,14 +271,10 @@ fn oauth_redirect_response(
authz_url = authz_url.set_pkce_challenge(challenge);
};

Ok(Response::builder()
.status(StatusCode::TEMPORARY_REDIRECT)
.header(SET_COOKIE, login_cookie)
.header(
LOCATION,
HeaderValue::from_str(authz_url.url().0.as_str()).map_err(to_internal_error)?,
)
.body(Body::empty())?)
let mut redirect = http_response_temporary_redirect(authz_url.url().0.to_string())?;
redirect.headers_mut().append(SET_COOKIE, login_cookie);

Ok(redirect)
}

// TODO: Determine if 401 empty responses are correct here
Expand Down Expand Up @@ -347,7 +340,7 @@ pub async fn authz_code_callback_op<T>(
rqctx: &RequestContext<impl ApiContext<AppPermissions = T>>,
path: Path<OAuthProviderNameParam>,
query: Query<OAuthAuthzCodeReturnQuery>,
) -> Result<Response<Body>, HttpError>
) -> Result<HttpResponseTemporaryRedirect, HttpError>
where
T: VAppPermission + PermissionStorage,
{
Expand All @@ -372,17 +365,12 @@ where
cookie.set_max_age(cookie::time::Duration::seconds(0));
let login_cookie = HeaderValue::from_str(&cookie.to_string()).map_err(to_internal_error)?;

Ok(Response::builder()
.status(StatusCode::TEMPORARY_REDIRECT)
.header(SET_COOKIE, login_cookie)
.header(
LOCATION,
HeaderValue::from_str(
&authz_code_callback_op_inner(ctx, &attempt_id, query.code, query.error).await?,
)
.map_err(to_internal_error)?,
)
.body(Body::empty())?)
let mut redirect = http_response_temporary_redirect(
authz_code_callback_op_inner(ctx, &attempt_id, query.code, query.error).await?,
)?;
redirect.headers_mut().append(SET_COOKIE, login_cookie);

Ok(redirect)
}

pub async fn authz_code_callback_op_inner<T>(
Expand Down Expand Up @@ -787,7 +775,7 @@ mod tests {
};

use chrono::{TimeDelta, Utc};
use dropshot::RequestInfo;
use dropshot::{HttpResponse, RequestInfo};
use http::{
header::{COOKIE, LOCATION, SET_COOKIE},
HeaderValue, StatusCode,
Expand Down Expand Up @@ -934,40 +922,28 @@ mod tests {
&attempt,
Some(challenge.clone()),
)
.unwrap()
.to_result()
.unwrap();
let headers = response.headers();

let expected_location = format!("https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=google_web_client_id&state={}&code_challenge={}&code_challenge_method=S256&redirect_uri=https%3A%2F%2Fapi.oxeng.dev%2Flogin%2Foauth%2Fgoogle%2Fcode%2Fcallback&scope=openid+email+profile", attempt.id, challenge.as_str());

assert_eq!(
expected_location,
String::from_utf8(
response
.headers()
.get(LOCATION)
.unwrap()
.as_bytes()
.to_vec()
)
.unwrap()
String::from_utf8(headers.get(LOCATION).unwrap().as_bytes().to_vec()).unwrap()
);
assert_eq!(
format!(
"{}; HttpOnly; SameSite=Lax; Secure; Max-Age=600",
attempt.id
)
.as_str(),
String::from_utf8(
response
.headers()
.get(SET_COOKIE)
.unwrap()
.as_bytes()
.to_vec()
)
.unwrap()
.split_once('=')
.unwrap()
.1
String::from_utf8(headers.get(SET_COOKIE).unwrap().as_bytes().to_vec())
.unwrap()
.split_once('=')
.unwrap()
.1
)
}

Expand Down
Loading