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
8 changes: 1 addition & 7 deletions nexus/auth/src/authn/external/session_cookie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,13 +396,7 @@ mod test {
}]),
};
let result = authn_with_cookie(&context, Some("session=abc")).await;
assert!(matches!(
result,
SchemeResult::Authenticated(Details {
actor: _,
device_token_expiration: _
})
));
assert!(matches!(result, SchemeResult::Authenticated(Details { .. })));

// valid cookie should have updated time_last_used
let sessions = context.sessions.lock().unwrap();
Expand Down
40 changes: 20 additions & 20 deletions nexus/src/app/device_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,32 +142,32 @@ impl super::Nexus {
// token is being used)

// Validate the requested TTL against the silo's max TTL
if let Some(max) = silo_max_ttl {
if requested_ttl > max.0.into() {
return Err(Error::invalid_request(&format!(
"Requested TTL {} seconds exceeds maximum allowed \
TTL for this silo of {} seconds",
requested_ttl, max
)));
}
if let Some(max) = silo_max_ttl
&& requested_ttl > max.0.into()
{
return Err(Error::invalid_request(&format!(
"Requested TTL {} seconds exceeds maximum allowed \
TTL for this silo of {} seconds",
requested_ttl, max
)));
};

let requested_exp =
Utc::now() + Duration::seconds(requested_ttl.0.into());

// If currently authenticated via token, error if requested exceeds it
if let Some(auth_exp) = opctx.authn.device_token_expiration() {
if requested_exp > auth_exp {
return Err(Error::invalid_request(
"Requested token TTL would exceed the expiration time \
of the token being used to authenticate the confirm \
request. To get the full requested TTL, confirm \
this token using a web console session. Alternatively, \
omit requested TTL to get a token with the longest \
allowed lifetime, determined by the lesser of the silo \
max and the current token's expiration time.",
));
}
if let Some(auth_exp) = opctx.authn.device_token_expiration()
&& requested_exp > auth_exp
{
return Err(Error::invalid_request(
"Requested token TTL would exceed the expiration time \
of the token being used to authenticate the confirm \
request. To get the full requested TTL, confirm \
this token using a web console session. Alternatively, \
omit requested TTL to get a token with the longest \
allowed lifetime, determined by the lesser of the silo \
max and the current token's expiration time.",
));
}

Some(requested_exp)
Expand Down
5 changes: 5 additions & 0 deletions nexus/test-utils/src/http_testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ pub enum AuthnMode {
PrivilegedUser,
SiloUser(SiloUserUuid),
Session(String),
DeviceToken(String),
}

impl AuthnMode {
Expand Down Expand Up @@ -579,6 +580,10 @@ impl AuthnMode {
let header_value = format!("session={}", session_token);
parse_header_pair(http::header::COOKIE, header_value)
}
AuthnMode::DeviceToken(token) => {
let header_value = format!("Bearer {}", token);
parse_header_pair(http::header::AUTHORIZATION, header_value)
}
}
}
}
Expand Down
9 changes: 3 additions & 6 deletions nexus/test-utils/src/resource_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1316,20 +1316,17 @@ pub async fn create_session_for_user(
.to_string()
}

/// Log in with test suite password, return session cookie (formatted for Cookie
/// header)
/// Log in with test suite password. Returns session token.
pub async fn create_console_session<N: NexusServer>(
cptestctx: &ControlPlaneTestContext<N>,
) -> String {
let token = create_session_for_user(
create_session_for_user(
&cptestctx.external_client,
cptestctx.silo_name.as_str(),
cptestctx.user_name.as_ref(),
TEST_SUITE_PASSWORD,
)
.await;

format!("session={}", token)
.await
}

#[derive(Debug)]
Expand Down
3 changes: 2 additions & 1 deletion nexus/tests/integration_tests/audit_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) {
assert_eq!(audit_log.items.len(), 1);

// this this creates its own entry
let session_cookie = create_console_session(ctx).await;
let session_cookie =
format!("session={}", create_console_session(ctx).await);

let t3 = Utc::now(); // after second entry

Expand Down
25 changes: 13 additions & 12 deletions nexus/tests/integration_tests/console_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ async fn test_sessions(cptestctx: &ControlPlaneTestContext) {
.expect("failed to clear cookie and 204 on logout");

// log in and pull the token out of the header so we can use it for authed requests
let session_token = create_console_session(cptestctx).await;
let session_cookie =
format!("session={}", create_console_session(cptestctx).await);

let project_params = ProjectCreate {
identity: IdentityMetadataCreateParams {
Expand Down Expand Up @@ -101,7 +102,7 @@ async fn test_sessions(cptestctx: &ControlPlaneTestContext) {

// now make same requests with cookie
RequestBuilder::new(&testctx, Method::POST, "/v1/projects")
.header(header::COOKIE, &session_token)
.header(header::COOKIE, &session_cookie)
.body(Some(&project_params))
// TODO: explicit expect_status not needed. decide whether to keep it anyway
.expect_status(Some(StatusCode::CREATED))
Expand All @@ -110,7 +111,7 @@ async fn test_sessions(cptestctx: &ControlPlaneTestContext) {
.expect("failed to create org with session cookie");

RequestBuilder::new(&testctx, Method::GET, "/projects/whatever")
.header(header::COOKIE, &session_token)
.header(header::COOKIE, &session_cookie)
.expect_console_asset()
.execute()
.await
Expand All @@ -124,7 +125,7 @@ async fn test_sessions(cptestctx: &ControlPlaneTestContext) {

// logout with an actual session should delete the session in the db
RequestBuilder::new(&testctx, Method::POST, "/v1/logout")
.header(header::COOKIE, &session_token)
.header(header::COOKIE, &session_cookie)
.expect_status(Some(StatusCode::NO_CONTENT))
// logout also clears the cookie client-side
.expect_response_header(
Expand All @@ -151,15 +152,15 @@ async fn test_sessions(cptestctx: &ControlPlaneTestContext) {
// now the same requests with the same session cookie should 401/302 because
// logout also deletes the session server-side
RequestBuilder::new(&testctx, Method::POST, "/v1/projects")
.header(header::COOKIE, &session_token)
.header(header::COOKIE, &session_cookie)
.body(Some(&project_params))
.expect_status(Some(StatusCode::UNAUTHORIZED))
.execute()
.await
.expect("failed to get 401 for unauthed API request");

RequestBuilder::new(&testctx, Method::GET, "/projects/whatever")
.header(header::COOKIE, &session_token)
.header(header::COOKIE, &session_cookie)
.expect_status(Some(StatusCode::FOUND))
.execute()
.await
Expand All @@ -173,8 +174,9 @@ async fn expect_console_page(
) {
let mut builder = RequestBuilder::new(testctx, Method::GET, path);

if let Some(session_token) = session_token {
builder = builder.header(http::header::COOKIE, &session_token)
if let Some(token) = session_token {
builder =
builder.header(http::header::COOKIE, &format!("session={token}"))
}

let console_page = builder
Expand Down Expand Up @@ -954,13 +956,13 @@ async fn test_session_idle_timeout_deletes_session() {
let testctx = &cptestctx.external_client;

// Start session
let session_cookie = create_console_session(&cptestctx).await;
let session_token = create_console_session(&cptestctx).await;

// sleep here not necessary given TTL of 0

// Make a request with the expired session cookie
let me_response = RequestBuilder::new(testctx, Method::GET, "/v1/me")
.header(header::COOKIE, &session_cookie)
.header(header::COOKIE, &format!("session={}", session_token))
.expect_status(Some(StatusCode::UNAUTHORIZED))
.execute()
.await
Expand All @@ -977,10 +979,9 @@ async fn test_session_idle_timeout_deletes_session() {
let opctx =
OpContext::for_tests(cptestctx.logctx.log.new(o!()), datastore.clone());

let token = session_cookie.strip_prefix("session=").unwrap();
let db_token_error = nexus
.datastore()
.session_lookup_by_token(&opctx, token.to_string())
.session_lookup_by_token(&opctx, session_token)
.await
.expect_err("session should be deleted");
assert_matches::assert_matches!(
Expand Down
Loading
Loading