OAuth passing tests.

This commit is contained in:
Mauro D 2023-05-14 12:34:49 +00:00
parent 0959a6d737
commit 63cbb70dbc
10 changed files with 667 additions and 46 deletions

View file

@ -65,9 +65,9 @@ impl crate::Config {
.property("jmap.session.cache.ttl")?
.unwrap_or(Duration::from_secs(3600)),
rate_authenticated: settings
.property_or_static("jmap.rate-limit.authenticated.rate", "1000/1s")?,
.property_or_static("jmap.rate-limit.account.rate", "1000/1s")?,
rate_authenticate_req: settings
.property_or_static("jmap.rate-limit.authenticate.rate", "10/1s")?,
.property_or_static("jmap.rate-limit.authentication.rate", "10/1s")?,
rate_anonymous: settings
.property_or_static("jmap.rate-limit.anonymous.rate", "100/1s")?,
rate_use_forwarded: settings

View file

@ -86,11 +86,11 @@ impl JMAP {
Ok(None) => RequestError::not_found().into_http_response(),
Err(err) => {
tracing::error!(event = "error",
context = "blob_store",
account_id = account_id.document_id(),
blob_id = ?blob_id,
error = ?err,
"Failed to download blob");
context = "blob_store",
account_id = account_id.document_id(),
blob_id = ?blob_id,
error = ?err,
"Failed to download blob");
RequestError::internal_server_error().into_http_response()
}
};
@ -161,48 +161,40 @@ impl JMAP {
match (path.next().unwrap_or(""), req.method()) {
("", &Method::GET) => {
// Limit anonymous requests
if let Err(err) = self.is_anonymous_allowed(remote_addr) {
return err.into_http_response();
return match self.is_anonymous_allowed(remote_addr) {
Ok(_) => self.handle_user_device_auth(req).await,
Err(err) => err.into_http_response(),
}
todo!()
}
("", &Method::POST) => {
// Limit authentication requests
if let Err(err) = self.is_auth_allowed(remote_addr) {
return err.into_http_response();
return match self.is_auth_allowed(remote_addr) {
Ok(_) => self.handle_user_device_auth_post(req).await,
Err(err) => err.into_http_response(),
}
todo!()
}
("code", &Method::GET) => {
// Limit anonymous requests
if let Err(err) = self.is_anonymous_allowed(remote_addr) {
return err.into_http_response();
return match self.is_anonymous_allowed(remote_addr) {
Ok(_) => self.handle_user_code_auth(req).await,
Err(err) => err.into_http_response(),
}
todo!()
}
("code", &Method::POST) => {
// Limit authentication requests
if let Err(err) = self.is_auth_allowed(remote_addr) {
return err.into_http_response();
return match self.is_auth_allowed(remote_addr) {
Ok(_) => self.handle_user_code_auth_post(req).await,
Err(err) => err.into_http_response(),
}
todo!()
}
("device", &Method::POST) => {
// Limit anonymous requests
if let Err(err) = self.is_anonymous_allowed(remote_addr) {
return err.into_http_response();
return match self.is_anonymous_allowed(remote_addr) {
Ok(_) => self.handle_device_auth(req, instance).await,
Err(err) => err.into_http_response(),
}
todo!()
}
("token", &Method::POST) => {
// Limit anonymous requests
if let Err(err) = self.is_anonymous_allowed(remote_addr) {
return err.into_http_response();
return match self.is_anonymous_allowed(remote_addr) {
Ok(_) => self.handle_token_request(req).await,
Err(err) => err.into_http_response(),
}
todo!()
}
_ => (),
}
@ -306,7 +298,7 @@ pub async fn fetch_body(
let mut bytes = Vec::with_capacity(1024);
while let Some(Ok(frame)) = req.frame().await {
if let Some(data) = frame.data_ref() {
if bytes.len() + data.len() < max_size {
if bytes.len() + data.len() <= max_size {
bytes.extend_from_slice(data);
} else {
return Err(RequestError::limit(RequestLimitError::Size));
@ -409,8 +401,15 @@ impl ToHttpResponse for UploadResponse {
impl ToHttpResponse for RequestError {
fn into_http_response(self) -> HttpResponse {
JsonResponse::with_status(StatusCode::from_u16(self.status).unwrap(), self)
.into_http_response()
hyper::Response::builder()
.status(StatusCode::from_u16(self.status).unwrap())
.header(header::CONTENT_TYPE, "application/problem+json")
.body(
Full::new(Bytes::from(serde_json::to_string(&self).unwrap()))
.map_err(|never| match never {})
.boxed(),
)
.unwrap()
}
}

View file

@ -25,7 +25,7 @@ use super::{
impl JMAP {
// Code authorization flow, handles an authorization request
pub async fn handle_user_code_auth(req: &mut HttpRequest) -> HttpResponse {
pub async fn handle_user_code_auth(&self, req: &mut HttpRequest) -> HttpResponse {
let params = form_urlencoded::parse(req.uri().query().unwrap_or_default().as_bytes())
.into_owned()
.collect::<HashMap<_, _>>();

View file

@ -199,7 +199,7 @@ impl JMAP {
),
rate_limit_auth: LruCache::with_capacity(
config
.property("jmap.rate-limit.authenticated.size")
.property("jmap.rate-limit.account.size")
.failed("Invalid property")
.unwrap_or(1024),
),

View file

@ -23,6 +23,7 @@ impl JMAP {
let sort_as_tree = request.arguments.sort_as_tree.unwrap_or(false);
let filter_as_tree = request.arguments.filter_as_tree.unwrap_or(false);
let mut filters = Vec::with_capacity(request.filter.len());
let mailbox_ids = self.mailbox_get_or_create(account_id).await?;
for cond in std::mem::take(&mut request.filter) {
match cond {
@ -100,11 +101,7 @@ impl JMAP {
&& (paginate.is_some()
|| (response.total.map_or(false, |total| total > 0) && filter_as_tree))
{
for document_id in self
.get_document_ids(account_id, Collection::Mailbox)
.await?
.unwrap_or_default()
{
for document_id in mailbox_ids {
let parent_id = self
.get_property::<Object<Value>>(
account_id,

View file

@ -20,4 +20,6 @@ serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"]}
bytes = "1.4.0"

View file

@ -47,7 +47,7 @@ pub async fn test(server: Arc<JMAP>, admin_client: &mut Client) {
server
.auth_db
.execute(
"INSERT INTO users (login, secret, name) VALUES (?, ?, ?)",
"INSERT OR REPLACE INTO users (login, secret, name) VALUES (?, ?, ?)",
vec![login.to_string(), secret.to_string(), name.to_string()].into_iter()
)
.await

View file

@ -0,0 +1,176 @@
use std::{sync::Arc, time::Duration};
use jmap::JMAP;
use jmap_client::{
client::{Client, Credentials},
mailbox::{self},
};
use jmap_proto::types::id::Id;
pub async fn test(server: Arc<JMAP>, _client: &mut Client) {
println!("Running Authorization tests...");
// Create test account
assert!(
server
.auth_db
.execute(
"INSERT OR REPLACE INTO users (login, secret, name) VALUES (?, ?, ?)",
vec![
"jdoe@example.com".to_string(),
"12345".to_string(),
"John Doe".to_string()
]
.into_iter()
)
.await
);
let account_id = Id::from(1u64).to_string();
// Wait for rate limit to be restored after running previous tests
//tokio::time::sleep(Duration::from_secs(1)).await;
// Incorrect passwords should be rejected with a 401 error
assert!(matches!(
Client::new()
.credentials(Credentials::basic("jdoe@example.com", "abcde"))
.accept_invalid_certs(true)
.connect("https://127.0.0.1:8899")
.await,
Err(jmap_client::Error::Problem(err)) if err.status() == Some(401)));
// Requests should be rate limited
let mut n_401 = 0;
let mut n_429 = 0;
for n in 0..110 {
if let Err(jmap_client::Error::Problem(problem)) = Client::new()
.credentials(Credentials::basic(
"not_an_account@example.com",
&format!("brute_force{}", n),
))
.accept_invalid_certs(true)
.connect("https://127.0.0.1:8899")
.await
{
if problem.status().unwrap() == 401 {
n_401 += 1;
if n_401 > 100 {
panic!("Rate limiter failed.");
}
} else if problem.status().unwrap() == 429 {
n_429 += 1;
if n_429 > 11 {
panic!("Rate limiter too restrictive.");
}
} else {
panic!("Unexpected error status {}", problem.status().unwrap());
}
} else {
panic!("Unexpected response.");
}
}
// Limit should be restored after 1 second
tokio::time::sleep(Duration::from_secs(1)).await;
// Login with the correct credentials
let client = Client::new()
.credentials(Credentials::basic("jdoe@example.com", "12345"))
.accept_invalid_certs(true)
.connect("https://127.0.0.1:8899")
.await
.unwrap();
assert_eq!(client.session().username(), "jdoe@example.com");
assert_eq!(
client.session().account(&account_id).unwrap().name(),
"jdoe@example.com"
);
assert!(client.session().account(&account_id).unwrap().is_personal());
// Uploads up to 50000000 bytes should be allowed
assert_eq!(
client
.upload(None, vec![b'A'; 5000000], None)
.await
.unwrap()
.size(),
5000000
);
assert!(client
.upload(None, vec![b'A'; 5000001], None)
.await
.is_err());
// Users should be allowed to create identities only
// using email addresses associated to their principal
let implement = "true";
/*client
.identity_create("John Doe", "jdoe@example.com")
.await
.unwrap()
.take_id();
client
.identity_create("John Doe (secondary)", "john.doe@example.com")
.await
.unwrap()
.take_id();
assert!(matches!(
client
.identity_create("John the Spammer", "spammy@mcspamface.com")
.await,
Err(jmap_client::Error::Set(SetError {
type_: SetErrorType::InvalidProperties,
..
}))
));*/
// Concurrent requests check
let client = Arc::new(client);
for _ in 0..8 {
let client_ = client.clone();
tokio::spawn(async move {
client_
.mailbox_query(
mailbox::query::Filter::name("__sleep").into(),
[mailbox::query::Comparator::name()].into(),
)
.await
.unwrap();
});
}
tokio::time::sleep(Duration::from_millis(100)).await;
assert!(matches!(
client
.mailbox_query(
mailbox::query::Filter::name("__sleep").into(),
[mailbox::query::Comparator::name()].into(),
)
.await,
Err(jmap_client::Error::Problem(err)) if err.status() == Some(400)));
// Wait for sleep to be done
tokio::time::sleep(Duration::from_secs(1)).await;
// Concurrent upload test
for _ in 0..4 {
let client_ = client.clone();
tokio::spawn(async move {
client_.upload(None, b"sleep".to_vec(), None).await.unwrap();
});
}
tokio::time::sleep(Duration::from_millis(100)).await;
assert!(matches!(
client.upload(None, b"sleep".to_vec(), None).await,
Err(jmap_client::Error::Problem(err)) if err.status() == Some(400)));
// Destroy test accounts
let implement = "true";
/*admin_client
.set_default_account_id(Id::new(SUPERUSER_ID as u64))
.principal_destroy(&account_id)
.await
.unwrap();
admin_client.principal_destroy(&domain_id).await.unwrap();
server.store.principal_purge().unwrap();
server.store.assert_is_empty();*/
}

View file

@ -0,0 +1,421 @@
use std::{sync::Arc, time::Duration};
use bytes::Bytes;
use jmap::{
auth::oauth::{DeviceAuthResponse, ErrorType, OAuthMetadata, TokenResponse},
JMAP,
};
use jmap_client::{
client::{Client, Credentials},
mailbox::query::Filter,
};
use jmap_proto::types::id::Id;
use reqwest::{header, redirect::Policy};
use serde::de::DeserializeOwned;
use store::ahash::AHashMap;
pub async fn test(server: Arc<JMAP>, _client: &mut Client) {
println!("Running OAuth tests...");
// Create test account
assert!(
server
.auth_db
.execute(
"INSERT OR REPLACE INTO users (login, secret, name) VALUES (?, ?, ?)",
vec![
"jdoe@example.com".to_string(),
"abcde".to_string(),
"John Doe".to_string()
]
.into_iter()
)
.await
);
let john_id = Id::from(1u64).to_string();
// Obtain OAuth metadata
let metadata: OAuthMetadata =
get("https://127.0.0.1:8899/.well-known/oauth-authorization-server").await;
//println!("OAuth metadata: {:#?}", metadata);
// ------------------------
// Authorization code flow
// ------------------------
// Build authorization request
let auth_endpoint = format!(
"{}?response_type=token&client_id=OAuthyMcOAuthFace&state=xyz&redirect_uri=https://localhost",
metadata.authorization_endpoint
);
let mut auth_request = AHashMap::from_iter([
("email".to_string(), "jdoe@example.com".to_string()),
("password".to_string(), "wrong_pass".to_string()),
(
"code".to_string(),
parse_code_input(get_bytes(&auth_endpoint).await),
),
]);
// Exceeding the max failed attempts should redirect with an access_denied code
assert_eq!(
post_expect_redirect(&metadata.authorization_endpoint, &auth_request).await,
"https://localhost?error=access_denied&state=xyz"
);
// Authenticate with the correct password
auth_request.insert("password".to_string(), "abcde".to_string());
auth_request.insert(
"code".to_string(),
parse_code_input(get_bytes(&auth_endpoint).await),
);
let code = parse_code_redirect(
post_expect_redirect(&metadata.authorization_endpoint, &auth_request).await,
"xyz",
);
// Both client_id and redirect_uri have to match
let mut token_params = AHashMap::from_iter([
("client_id".to_string(), "invalid_client".to_string()),
("redirect_uri".to_string(), "https://localhost".to_string()),
("grant_type".to_string(), "authorization_code".to_string()),
("code".to_string(), code),
]);
assert_eq!(
post::<TokenResponse>(&metadata.token_endpoint, &token_params).await,
TokenResponse::Error {
error: ErrorType::InvalidClient
}
);
token_params.insert("client_id".to_string(), "OAuthyMcOAuthFace".to_string());
token_params.insert(
"redirect_uri".to_string(),
"https://some-other.url".to_string(),
);
assert_eq!(
post::<TokenResponse>(&metadata.token_endpoint, &token_params).await,
TokenResponse::Error {
error: ErrorType::InvalidClient
}
);
// Obtain token
token_params.insert("redirect_uri".to_string(), "https://localhost".to_string());
let (token, _, _) = unwrap_token_response(post(&metadata.token_endpoint, &token_params).await);
// Connect to account using token and attempt to search
let john_client = Client::new()
.credentials(Credentials::bearer(&token))
.accept_invalid_certs(true)
.connect("https://127.0.0.1:8899")
.await
.unwrap();
assert_eq!(john_client.default_account_id(), john_id);
assert!(!john_client
.mailbox_query(None::<Filter>, None::<Vec<_>>)
.await
.unwrap()
.ids()
.is_empty());
// ------------------------
// Device code flow
// ------------------------
// Request a device code
let device_code_params = AHashMap::from_iter([("client_id".to_string(), "1234".to_string())]);
let device_response: DeviceAuthResponse =
post(&metadata.device_authorization_endpoint, &device_code_params).await;
//println!("Device response: {:#?}", device_response);
// Status should be pending
let mut token_params = AHashMap::from_iter([
("client_id".to_string(), "1234".to_string()),
(
"grant_type".to_string(),
"urn:ietf:params:oauth:grant-type:device_code".to_string(),
),
(
"device_code".to_string(),
device_response.device_code.to_string(),
),
]);
assert_eq!(
post::<TokenResponse>(&metadata.token_endpoint, &token_params).await,
TokenResponse::Error {
error: ErrorType::AuthorizationPending
}
);
// Invalidate the code by having too many unsuccessful attempts
assert_client_auth(
"jdoe@example.com",
"wrongpass",
&device_response,
"Incorrect",
)
.await;
assert_client_auth(
"jdoe@example.com",
"wrongpass",
&device_response,
"Invalid or expired authentication code.",
)
.await;
assert_eq!(
post::<TokenResponse>(&metadata.token_endpoint, &token_params).await,
TokenResponse::Error {
error: ErrorType::AccessDenied
}
);
// Request a new device code
let device_response: DeviceAuthResponse =
post(&metadata.device_authorization_endpoint, &device_code_params).await;
token_params.insert(
"device_code".to_string(),
device_response.device_code.to_string(),
);
// Let the code expire and make sure it's invalidated
tokio::time::sleep(Duration::from_secs(1)).await;
assert_client_auth(
"jdoe@example.com",
"abcde",
&device_response,
"Invalid or expired authentication code.",
)
.await;
assert_eq!(
post::<TokenResponse>(&metadata.token_endpoint, &token_params).await,
TokenResponse::Error {
error: ErrorType::ExpiredToken
}
);
// Authenticate account using a valid code
let device_response: DeviceAuthResponse =
post(&metadata.device_authorization_endpoint, &device_code_params).await;
token_params.insert(
"device_code".to_string(),
device_response.device_code.to_string(),
);
assert_client_auth("jdoe@example.com", "abcde", &device_response, "successful").await;
// Obtain token
let (token, refresh_token, _) =
unwrap_token_response(post(&metadata.token_endpoint, &token_params).await);
let refresh_token = refresh_token.unwrap();
// Authorization codes can only be used once
assert_eq!(
post::<TokenResponse>(&metadata.token_endpoint, &token_params).await,
TokenResponse::Error {
error: ErrorType::ExpiredToken
}
);
// Connect to account using token and attempt to search
let john_client = Client::new()
.credentials(Credentials::bearer(&token))
.accept_invalid_certs(true)
.connect("https://127.0.0.1:8899")
.await
.unwrap();
assert_eq!(john_client.default_account_id(), john_id);
assert!(!john_client
.mailbox_query(None::<Filter>, None::<Vec<_>>)
.await
.unwrap()
.ids()
.is_empty());
// Connecting using the refresh token should not work
assert_unauthorized("https://127.0.0.1:8899", &refresh_token).await;
// Refreshing a token using the access token should not work
assert_eq!(
post::<TokenResponse>(
&metadata.token_endpoint,
&AHashMap::from_iter([
("client_id".to_string(), "1234".to_string()),
("grant_type".to_string(), "refresh_token".to_string()),
("refresh_token".to_string(), token),
]),
)
.await,
TokenResponse::Error {
error: ErrorType::InvalidGrant
}
);
// Refreshing the access token before expiration should not include a new refresh token
let refresh_params = AHashMap::from_iter([
("client_id".to_string(), "1234".to_string()),
("grant_type".to_string(), "refresh_token".to_string()),
("refresh_token".to_string(), refresh_token),
]);
let (token, new_refresh_token, _) =
unwrap_token_response(post(&metadata.token_endpoint, &refresh_params).await);
assert_eq!(new_refresh_token, None);
// Wait 1 second and make sure the access token expired
tokio::time::sleep(Duration::from_secs(1)).await;
assert_unauthorized("https://127.0.0.1:8899", &token).await;
// Wait another second for the refresh token to be about to expire
// and expect a new refresh token
tokio::time::sleep(Duration::from_secs(1)).await;
let (_, new_refresh_token, _) =
unwrap_token_response(post(&metadata.token_endpoint, &refresh_params).await);
//println!("New refresh token: {:?}", new_refresh_token);
assert_ne!(new_refresh_token, None);
// Wait another second and make sure the refresh token expired
tokio::time::sleep(Duration::from_secs(1)).await;
assert_eq!(
post::<TokenResponse>(&metadata.token_endpoint, &refresh_params).await,
TokenResponse::Error {
error: ErrorType::InvalidGrant
}
);
// Destroy test accounts
let cleanup = "true";
/*for principal_id in [john_id, domain_id] {
admin_client.principal_destroy(&principal_id).await.unwrap();
}
server.store.principal_purge().unwrap();
server.store.assert_is_empty();*/
}
async fn post_bytes(url: &str, params: &AHashMap<String, String>) -> Bytes {
reqwest::Client::builder()
.timeout(Duration::from_millis(500))
.danger_accept_invalid_certs(true)
.build()
.unwrap_or_default()
.post(url)
.form(params)
.send()
.await
.unwrap()
.bytes()
.await
.unwrap()
}
async fn post<T: DeserializeOwned>(url: &str, params: &AHashMap<String, String>) -> T {
serde_json::from_slice(&post_bytes(url, params).await).unwrap()
}
async fn post_expect_redirect(url: &str, params: &AHashMap<String, String>) -> String {
let response = reqwest::Client::builder()
.timeout(Duration::from_millis(500))
.danger_accept_invalid_certs(true)
.redirect(Policy::none())
.build()
.unwrap_or_default()
.post(url)
.form(params)
.send()
.await
.unwrap();
response
.headers()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
.to_string()
}
async fn get_bytes(url: &str) -> Bytes {
reqwest::Client::builder()
.timeout(Duration::from_millis(500))
.danger_accept_invalid_certs(true)
.build()
.unwrap_or_default()
.get(url)
.send()
.await
.unwrap()
.bytes()
.await
.unwrap()
}
async fn get<T: DeserializeOwned>(url: &str) -> T {
serde_json::from_slice(&get_bytes(url).await).unwrap()
}
async fn assert_client_auth(
email: &str,
pass: &str,
device_response: &DeviceAuthResponse,
expect: &str,
) {
let html_response = String::from_utf8_lossy(
&post_bytes(
&device_response.verification_uri,
&AHashMap::from_iter([
("email".to_string(), email.to_string()),
("password".to_string(), pass.to_string()),
("code".to_string(), device_response.user_code.to_string()),
]),
)
.await,
)
.into_owned();
assert!(html_response.contains(expect), "{:#?}", html_response);
}
async fn assert_unauthorized(base_url: &str, token: &str) {
match Client::new()
.credentials(Credentials::bearer(token))
.accept_invalid_certs(true)
.connect(base_url)
.await
{
Ok(_) => panic!("Expected unauthorized access."),
Err(err) => {
let err = err.to_string();
assert!(err.contains("Unauthorized"), "{}", err);
}
}
}
fn parse_code_input(bytes: Bytes) -> String {
let html = String::from_utf8_lossy(&bytes).into_owned();
if let Some((_, code)) = html.split_once("name=\"code\" value=\"") {
if let Some((code, _)) = code.split_once('\"') {
return code.to_string();
}
}
panic!("Could not parse code input: {}", html);
}
fn parse_code_redirect(uri: String, state: &str) -> String {
if let Some(code) = uri.strip_prefix("https://localhost?code=") {
if let Some(code) = code.strip_suffix(&format!("&state={}", state)) {
return code.to_string();
}
}
panic!("Invalid redirect URI: {}", uri);
}
fn unwrap_token_response(response: TokenResponse) -> (String, Option<String>, u64) {
match response {
TokenResponse::Granted {
access_token,
token_type,
expires_in,
refresh_token,
..
} => {
assert_eq!(token_type, "bearer");
(access_token, refresh_token, expires_in)
}
TokenResponse::Error { error } => panic!("Expected granted, got {:?}", error),
}
}

View file

@ -7,7 +7,9 @@ use tokio::sync::watch;
use crate::{add_test_certs, store::TempDir};
pub mod acl;
pub mod auth_acl;
pub mod auth_limits;
pub mod auth_oauth;
pub mod email_changes;
pub mod email_copy;
pub mod email_get;
@ -48,6 +50,19 @@ private-key = 'file://{PK}'
[jmap.protocol]
set.max-objects = 100000
[jmap.protocol.request]
max-concurrent = 8
max-concurrent-total = 512
[jmap.protocol.upload]
max-size = 5000000
max-concurrent = 4
[jmap.rate-limit]
account.rate = '100/1m'
authentication.rate = '100/1m'
anonymous.rate = '1000/1m'
[jmap.auth.database]
type = 'sql'
address = 'sqlite::memory:'
@ -58,6 +73,15 @@ login-by-uid = 'SELECT login FROM users WHERE ROWID - 1 = ?'
secret-by-uid = 'SELECT secret FROM users WHERE ROWID - 1 = ?'
gids-by-uid = 'SELECT gid FROM groups WHERE uid = ?'
[oauth]
key = 'parerga_und_paralipomena'
max-auth-attempts = 1
[oauth.expiry]
user-code = '1s'
token = '1s'
refresh-token = '3s'
refresh-token-renew = '2s'
";
#[tokio::test]
@ -82,7 +106,9 @@ pub async fn jmap_tests() {
//thread_get::test(params.server.clone(), &mut params.client).await;
//thread_merge::test(params.server.clone(), &mut params.client).await;
//mailbox::test(params.server.clone(), &mut params.client).await;
acl::test(params.server.clone(), &mut params.client).await;
//auth_acl::test(params.server.clone(), &mut params.client).await;
//auth_limits::test(params.server.clone(), &mut params.client).await;
auth_oauth::test(params.server.clone(), &mut params.client).await;
if delete {
params.temp_dir.delete();