style: cargo fmt across Phase 3.5 changes
Pure formatting pass — no behavior changes. Catches the line-wrapping drift across the new authz / api_keys / middleware / handler edits that piled up during the implementation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -383,8 +383,9 @@ impl TryFrom<AdminUserRecord> for AdminUserRow {
|
||||
id: r.id.into(),
|
||||
username: r.username,
|
||||
is_active: r.is_active,
|
||||
instance_role: InstanceRole::from_db_str(&r.instance_role)
|
||||
.ok_or(AdminUserRepositoryError::InvalidInstanceRole(r.instance_role))?,
|
||||
instance_role: InstanceRole::from_db_str(&r.instance_role).ok_or(
|
||||
AdminUserRepositoryError::InvalidInstanceRole(r.instance_role),
|
||||
)?,
|
||||
email: r.email,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at,
|
||||
@@ -410,8 +411,9 @@ impl TryFrom<AdminCredsRecord> for AdminUserCredentials {
|
||||
username: r.username,
|
||||
password_hash: r.password_hash,
|
||||
is_active: r.is_active,
|
||||
instance_role: InstanceRole::from_db_str(&r.instance_role)
|
||||
.ok_or(AdminUserRepositoryError::InvalidInstanceRole(r.instance_role))?,
|
||||
instance_role: InstanceRole::from_db_str(&r.instance_role).ok_or(
|
||||
AdminUserRepositoryError::InvalidInstanceRole(r.instance_role),
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,8 +162,7 @@ async fn create_admin(
|
||||
// owner — admin cannot self-elevate (or elevate someone else)
|
||||
// beyond their own ceiling. Owner-creation by env-var bootstrap
|
||||
// bypasses this path.
|
||||
if input.instance_role == InstanceRole::Owner
|
||||
&& principal.instance_role != InstanceRole::Owner
|
||||
if input.instance_role == InstanceRole::Owner && principal.instance_role != InstanceRole::Owner
|
||||
{
|
||||
return Err(AdminApiError::CannotEscalate);
|
||||
}
|
||||
|
||||
@@ -112,10 +112,8 @@ pub trait ApiKeyRepository: Send + Sync {
|
||||
/// into `set_active(false)` so deactivation invalidates both
|
||||
/// sessions (already done by `AdminSessionRepository::delete_for_user`)
|
||||
/// and bearer keys at the same moment.
|
||||
async fn expire_all_for_user(
|
||||
&self,
|
||||
user_id: AdminUserId,
|
||||
) -> Result<u64, ApiKeyRepositoryError>;
|
||||
async fn expire_all_for_user(&self, user_id: AdminUserId)
|
||||
-> Result<u64, ApiKeyRepositoryError>;
|
||||
}
|
||||
|
||||
pub struct PostgresApiKeyRepository {
|
||||
@@ -132,7 +130,8 @@ impl PostgresApiKeyRepository {
|
||||
#[async_trait]
|
||||
impl ApiKeyRepository for PostgresApiKeyRepository {
|
||||
async fn create(&self, key: NewApiKey) -> Result<ApiKeyRow, ApiKeyRepositoryError> {
|
||||
let scope_strings: Vec<String> = key.scopes.iter().map(|s| s.as_str().to_string()).collect();
|
||||
let scope_strings: Vec<String> =
|
||||
key.scopes.iter().map(|s| s.as_str().to_string()).collect();
|
||||
let row = sqlx::query_as::<_, ApiKeyRecord>(
|
||||
"INSERT INTO api_keys \
|
||||
(user_id, hash, prefix, name, scopes, app_id, expires_at) \
|
||||
|
||||
@@ -19,10 +19,7 @@ pub enum AppMembersRepositoryError {
|
||||
Db(#[from] sqlx::Error),
|
||||
|
||||
#[error("membership row not found: app={app_id}, user={user_id}")]
|
||||
NotFound {
|
||||
app_id: AppId,
|
||||
user_id: AdminUserId,
|
||||
},
|
||||
NotFound { app_id: AppId, user_id: AdminUserId },
|
||||
|
||||
#[error("invalid app_role stored in DB: {0}")]
|
||||
InvalidRole(String),
|
||||
|
||||
@@ -27,10 +27,7 @@ pub trait AppRepository: Send + Sync {
|
||||
async fn list(&self) -> Result<Vec<App>, ScriptRepositoryError>;
|
||||
/// Only apps the user has an `app_members` row for. Drives the
|
||||
/// membership-filtered `GET /admin/apps` for `member` callers.
|
||||
async fn list_for_user(
|
||||
&self,
|
||||
user_id: AdminUserId,
|
||||
) -> Result<Vec<App>, ScriptRepositoryError>;
|
||||
async fn list_for_user(&self, user_id: AdminUserId) -> Result<Vec<App>, ScriptRepositoryError>;
|
||||
async fn get_by_id(&self, id: AppId) -> Result<Option<App>, ScriptRepositoryError>;
|
||||
async fn get_by_slug(&self, slug: &str) -> Result<Option<App>, ScriptRepositoryError>;
|
||||
async fn get_by_slug_or_history(
|
||||
@@ -100,10 +97,7 @@ impl AppRepository for PostgresAppRepository {
|
||||
Ok(rows.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
async fn list_for_user(
|
||||
&self,
|
||||
user_id: AdminUserId,
|
||||
) -> Result<Vec<App>, ScriptRepositoryError> {
|
||||
async fn list_for_user(&self, user_id: AdminUserId) -> Result<Vec<App>, ScriptRepositoryError> {
|
||||
let rows = sqlx::query_as::<_, AppRow>(
|
||||
"SELECT a.id, a.slug, a.name, a.description, a.created_at, a.updated_at \
|
||||
FROM apps a \
|
||||
|
||||
@@ -223,6 +223,9 @@ mod tests {
|
||||
let b = generate_api_key().expect("mint b");
|
||||
assert_ne!(a.raw, b.raw);
|
||||
assert_ne!(a.hash, b.hash);
|
||||
assert_ne!(a.prefix, b.prefix, "32 random bytes → prefix collision is negligible");
|
||||
assert_ne!(
|
||||
a.prefix, b.prefix,
|
||||
"32 random bytes → prefix collision is negligible"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,10 @@ async fn logout(State(state): State<AuthState>, req: Request<Body>) -> Response
|
||||
(StatusCode::NO_CONTENT, headers).into_response()
|
||||
}
|
||||
|
||||
async fn me(State(state): State<AuthState>, Extension(principal): Extension<Principal>) -> Response {
|
||||
async fn me(
|
||||
State(state): State<AuthState>,
|
||||
Extension(principal): Extension<Principal>,
|
||||
) -> Response {
|
||||
// /me consumes the resolved Principal directly; we re-fetch the
|
||||
// user row only to surface a fresh username (it can change via
|
||||
// PATCH while a session/key is still valid).
|
||||
|
||||
@@ -272,7 +272,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn populated_db_is_noop() {
|
||||
let repo = InMemoryRepo::default();
|
||||
repo.create("seeded", "x", InstanceRole::Owner).await.unwrap();
|
||||
repo.create("seeded", "x", InstanceRole::Owner)
|
||||
.await
|
||||
.unwrap();
|
||||
let env = BootstrapEnv {
|
||||
username: Some("alice".into()),
|
||||
password: Some("supersecret".into()),
|
||||
|
||||
@@ -96,11 +96,7 @@ pub async fn require_authenticated(
|
||||
/// `require_admin` keeps working without an immediate rename. New
|
||||
/// wiring should call `require_authenticated`.
|
||||
#[deprecated(note = "renamed to require_authenticated")]
|
||||
pub async fn require_admin(
|
||||
state: State<AuthState>,
|
||||
req: Request<Body>,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
pub async fn require_admin(state: State<AuthState>, req: Request<Body>, next: Next) -> Response {
|
||||
require_authenticated(state, req, next).await
|
||||
}
|
||||
|
||||
@@ -164,10 +160,7 @@ async fn verify_session(
|
||||
/// against the full `rest`. At most one match is expected; multiple
|
||||
/// candidates with the same prefix is statistically negligible but
|
||||
/// handled correctly (verify each, take the first match).
|
||||
async fn verify_api_key(
|
||||
state: &AuthState,
|
||||
rest: &str,
|
||||
) -> Result<Option<Principal>, InternalError> {
|
||||
async fn verify_api_key(state: &AuthState, rest: &str) -> Result<Option<Principal>, InternalError> {
|
||||
if rest.len() <= API_KEY_PREFIX_LEN {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -224,7 +217,10 @@ async fn username_for(state: &AuthState, id: AdminUserId) -> Option<String> {
|
||||
Ok(Some(u)) => Some(u.username),
|
||||
Ok(None) => None,
|
||||
Err(err) => {
|
||||
tracing::warn!(?err, "username lookup for AuthedAdmin failed; skipping legacy ext");
|
||||
tracing::warn!(
|
||||
?err,
|
||||
"username lookup for AuthedAdmin failed; skipping legacy ext"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,9 +65,9 @@ impl Capability {
|
||||
#[must_use]
|
||||
pub const fn app_id(self) -> Option<AppId> {
|
||||
match self {
|
||||
Self::InstanceCreateApp
|
||||
| Self::InstanceManageUsers
|
||||
| Self::InstanceManageSettings => None,
|
||||
Self::InstanceCreateApp | Self::InstanceManageUsers | Self::InstanceManageSettings => {
|
||||
None
|
||||
}
|
||||
Self::AppRead(id)
|
||||
| Self::AppWriteScript(id)
|
||||
| Self::AppWriteRoute(id)
|
||||
@@ -85,9 +85,9 @@ impl Capability {
|
||||
#[must_use]
|
||||
pub const fn required_scope(self) -> Scope {
|
||||
match self {
|
||||
Self::InstanceCreateApp
|
||||
| Self::InstanceManageUsers
|
||||
| Self::InstanceManageSettings => Scope::InstanceAdmin,
|
||||
Self::InstanceCreateApp | Self::InstanceManageUsers | Self::InstanceManageSettings => {
|
||||
Scope::InstanceAdmin
|
||||
}
|
||||
Self::AppRead(_) => Scope::ScriptRead,
|
||||
Self::AppWriteScript(_) => Scope::ScriptWrite,
|
||||
Self::AppWriteRoute(_) => Scope::RouteWrite,
|
||||
@@ -314,7 +314,12 @@ mod tests {
|
||||
user_id: UserId,
|
||||
app_id: AppId,
|
||||
) -> Result<Option<AppRole>, AuthzError> {
|
||||
Ok(self.memberships.lock().await.get(&(user_id, app_id)).copied())
|
||||
Ok(self
|
||||
.memberships
|
||||
.lock()
|
||||
.await
|
||||
.get(&(user_id, app_id))
|
||||
.copied())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,25 +366,35 @@ mod tests {
|
||||
Decision::Allow,
|
||||
);
|
||||
assert_eq!(
|
||||
can(&repo, &p, Capability::InstanceManageUsers).await.unwrap(),
|
||||
can(&repo, &p, Capability::InstanceManageUsers)
|
||||
.await
|
||||
.unwrap(),
|
||||
Decision::Allow,
|
||||
);
|
||||
assert_eq!(
|
||||
can(&repo, &p, Capability::InstanceManageSettings).await.unwrap(),
|
||||
can(&repo, &p, Capability::InstanceManageSettings)
|
||||
.await
|
||||
.unwrap(),
|
||||
Decision::Deny,
|
||||
);
|
||||
// Editor-like grants succeed
|
||||
assert_eq!(
|
||||
can(&repo, &p, Capability::AppWriteScript(app)).await.unwrap(),
|
||||
can(&repo, &p, Capability::AppWriteScript(app))
|
||||
.await
|
||||
.unwrap(),
|
||||
Decision::Allow,
|
||||
);
|
||||
assert_eq!(
|
||||
can(&repo, &p, Capability::AppWriteRoute(app)).await.unwrap(),
|
||||
can(&repo, &p, Capability::AppWriteRoute(app))
|
||||
.await
|
||||
.unwrap(),
|
||||
Decision::Allow,
|
||||
);
|
||||
// App-admin grants do not
|
||||
assert_eq!(
|
||||
can(&repo, &p, Capability::AppManageDomains(app)).await.unwrap(),
|
||||
can(&repo, &p, Capability::AppManageDomains(app))
|
||||
.await
|
||||
.unwrap(),
|
||||
Decision::Deny,
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -418,10 +433,18 @@ mod tests {
|
||||
let app = AppId::new();
|
||||
repo.grant(p.user_id, app, AppRole::Viewer).await;
|
||||
|
||||
assert!(can(&repo, &p, Capability::AppRead(app)).await.unwrap().is_allow());
|
||||
assert!(can(&repo, &p, Capability::AppLogRead(app)).await.unwrap().is_allow());
|
||||
assert!(can(&repo, &p, Capability::AppRead(app))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_allow());
|
||||
assert!(can(&repo, &p, Capability::AppLogRead(app))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_allow());
|
||||
assert_eq!(
|
||||
can(&repo, &p, Capability::AppWriteScript(app)).await.unwrap(),
|
||||
can(&repo, &p, Capability::AppWriteScript(app))
|
||||
.await
|
||||
.unwrap(),
|
||||
Decision::Deny
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -437,8 +460,14 @@ mod tests {
|
||||
let app = AppId::new();
|
||||
repo.grant(p.user_id, app, AppRole::Editor).await;
|
||||
|
||||
assert!(can(&repo, &p, Capability::AppWriteScript(app)).await.unwrap().is_allow());
|
||||
assert!(can(&repo, &p, Capability::AppWriteRoute(app)).await.unwrap().is_allow());
|
||||
assert!(can(&repo, &p, Capability::AppWriteScript(app))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_allow());
|
||||
assert!(can(&repo, &p, Capability::AppWriteRoute(app))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_allow());
|
||||
assert_eq!(
|
||||
can(&repo, &p, Capability::AppAdmin(app)).await.unwrap(),
|
||||
Decision::Deny
|
||||
@@ -452,12 +481,20 @@ mod tests {
|
||||
let app = AppId::new();
|
||||
repo.grant(p.user_id, app, AppRole::AppAdmin).await;
|
||||
|
||||
assert!(can(&repo, &p, Capability::AppAdmin(app)).await.unwrap().is_allow());
|
||||
assert!(can(&repo, &p, Capability::AppManageDomains(app)).await.unwrap().is_allow());
|
||||
assert!(can(&repo, &p, Capability::AppAdmin(app))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_allow());
|
||||
assert!(can(&repo, &p, Capability::AppManageDomains(app))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_allow());
|
||||
// Membership in App A does NOT grant access to App B
|
||||
let other_app = AppId::new();
|
||||
assert_eq!(
|
||||
can(&repo, &p, Capability::AppAdmin(other_app)).await.unwrap(),
|
||||
can(&repo, &p, Capability::AppAdmin(other_app))
|
||||
.await
|
||||
.unwrap(),
|
||||
Decision::Deny
|
||||
);
|
||||
}
|
||||
@@ -473,9 +510,14 @@ mod tests {
|
||||
scopes: Some(vec![Scope::ScriptRead]),
|
||||
app_binding: None,
|
||||
};
|
||||
assert!(can(&repo, &p, Capability::AppRead(app)).await.unwrap().is_allow());
|
||||
assert!(can(&repo, &p, Capability::AppRead(app))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_allow());
|
||||
assert_eq!(
|
||||
can(&repo, &p, Capability::AppWriteScript(app)).await.unwrap(),
|
||||
can(&repo, &p, Capability::AppWriteScript(app))
|
||||
.await
|
||||
.unwrap(),
|
||||
Decision::Deny
|
||||
);
|
||||
// Even though the user is owner — the key's scope set is the
|
||||
@@ -502,7 +544,9 @@ mod tests {
|
||||
.unwrap()
|
||||
.is_allow());
|
||||
assert_eq!(
|
||||
can(&repo, &p, Capability::AppWriteScript(other_app)).await.unwrap(),
|
||||
can(&repo, &p, Capability::AppWriteScript(other_app))
|
||||
.await
|
||||
.unwrap(),
|
||||
Decision::Deny
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ use picloud_executor_core::{Engine, Limits};
|
||||
use picloud_manager_core::{
|
||||
admin_router, admins_router, api_keys_router, apps_api, apps_router, auth_router,
|
||||
compile_routes, migrations, require_authenticated, route_admin_router, AdminSessionRepository,
|
||||
AdminState, AdminUserRepository, AdminsState, ApiKeyRepository, ApiKeysState, AppDomainRepository,
|
||||
AppRepository, AppsState, AuthState, AuthzRepo, PostgresAdminSessionRepository,
|
||||
PostgresAdminUserRepository, PostgresApiKeyRepository, PostgresAppDomainRepository,
|
||||
PostgresAppMembersRepository, PostgresAppRepository, PostgresExecutionLogRepository,
|
||||
PostgresExecutionLogSink, PostgresRouteRepository, PostgresScriptRepository, RepoResolver,
|
||||
RouteAdminState, RouteRepository, SandboxCeiling,
|
||||
AdminState, AdminUserRepository, AdminsState, ApiKeyRepository, ApiKeysState,
|
||||
AppDomainRepository, AppRepository, AppsState, AuthState, AuthzRepo,
|
||||
PostgresAdminSessionRepository, PostgresAdminUserRepository, PostgresApiKeyRepository,
|
||||
PostgresAppDomainRepository, PostgresAppMembersRepository, PostgresAppRepository,
|
||||
PostgresExecutionLogRepository, PostgresExecutionLogSink, PostgresRouteRepository,
|
||||
PostgresScriptRepository, RepoResolver, RouteAdminState, RouteRepository, SandboxCeiling,
|
||||
};
|
||||
use picloud_orchestrator_core::routing::{AppDomainTable, RouteTable};
|
||||
use picloud_orchestrator_core::{
|
||||
@@ -164,9 +164,7 @@ pub async fn build_app(pool: PgPool, auth: AuthDeps) -> anyhow::Result<Router> {
|
||||
keys: auth.keys.clone(),
|
||||
authz,
|
||||
};
|
||||
let api_keys_state = ApiKeysState {
|
||||
keys: auth.keys,
|
||||
};
|
||||
let api_keys_state = ApiKeysState { keys: auth.keys };
|
||||
|
||||
// /admin/auth/login + /logout are unguarded by design (login is how
|
||||
// you get in). /admin/auth/me applies the middleware internally so
|
||||
|
||||
@@ -100,7 +100,10 @@ async fn warn_on_multi_owner_install(users: &dyn AdminUserRepository) {
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
tracing::warn!(?err, "could not count active owners for multi-owner startup check");
|
||||
tracing::warn!(
|
||||
?err,
|
||||
"could not count active owners for multi-owner startup check"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,9 @@ async fn boot(pool: PgPool) -> Seeded {
|
||||
.await
|
||||
.expect("seed owner");
|
||||
|
||||
let app = picloud::build_app(pool.clone(), auth).await.expect("build_app");
|
||||
let app = picloud::build_app(pool.clone(), auth)
|
||||
.await
|
||||
.expect("build_app");
|
||||
let server = TestServer::new(app).expect("TestServer");
|
||||
|
||||
// Default app id (seeded by migration 0005).
|
||||
@@ -109,18 +111,33 @@ async fn login_token(server: &TestServer, username: &str, password: &str) -> Str
|
||||
/// at arbitrary roles. The API enforces "owners only create owners"
|
||||
/// which is correct production behavior but inconvenient for test
|
||||
/// fixtures.
|
||||
async fn seed_user(pool: &PgPool, username: &str, password: &str, role: InstanceRole) -> AdminUserId {
|
||||
async fn seed_user(
|
||||
pool: &PgPool,
|
||||
username: &str,
|
||||
password: &str,
|
||||
role: InstanceRole,
|
||||
) -> AdminUserId {
|
||||
let repo = PostgresAdminUserRepository::new(pool.clone());
|
||||
let hash = hash_password(password).expect("hash");
|
||||
repo.create(username, &hash, role).await.expect("seed user").id
|
||||
repo.create(username, &hash, role)
|
||||
.await
|
||||
.expect("seed user")
|
||||
.id
|
||||
}
|
||||
|
||||
async fn grant_membership(pool: &PgPool, user: AdminUserId, app: AppId, role: AppRole) {
|
||||
let repo = PostgresAppMembersRepository::new(pool.clone());
|
||||
repo.upsert(app, user, role).await.expect("grant membership");
|
||||
repo.upsert(app, user, role)
|
||||
.await
|
||||
.expect("grant membership");
|
||||
}
|
||||
|
||||
async fn create_script_via_api(server: &TestServer, token: &str, app_id: AppId, name: &str) -> Value {
|
||||
async fn create_script_via_api(
|
||||
server: &TestServer,
|
||||
token: &str,
|
||||
app_id: AppId,
|
||||
name: &str,
|
||||
) -> Value {
|
||||
let r = server
|
||||
.post("/api/v1/admin/scripts")
|
||||
.add_header("authorization", format!("Bearer {token}"))
|
||||
@@ -135,11 +152,7 @@ async fn create_script_via_api(server: &TestServer, token: &str, app_id: AppId,
|
||||
}
|
||||
|
||||
/// Mint an API key for the caller — wraps POST /api-keys.
|
||||
async fn mint_key(
|
||||
server: &TestServer,
|
||||
cred_token: &str,
|
||||
body: Value,
|
||||
) -> axum_test::TestResponse {
|
||||
async fn mint_key(server: &TestServer, cred_token: &str, body: Value) -> axum_test::TestResponse {
|
||||
server
|
||||
.post("/api/v1/admin/api-keys")
|
||||
.add_header("authorization", format!("Bearer {cred_token}"))
|
||||
@@ -329,7 +342,10 @@ async fn bearer_and_cookie_produce_same_principal(pool: PgPool) {
|
||||
.await;
|
||||
via_key.assert_status_ok();
|
||||
|
||||
assert_eq!(via_session.json::<Value>()["id"], via_key.json::<Value>()["id"]);
|
||||
assert_eq!(
|
||||
via_session.json::<Value>()["id"],
|
||||
via_key.json::<Value>()["id"]
|
||||
);
|
||||
assert_eq!(
|
||||
via_session.json::<Value>()["username"],
|
||||
via_key.json::<Value>()["username"]
|
||||
@@ -352,7 +368,10 @@ async fn read_only_key_cannot_write_scripts(pool: PgPool) {
|
||||
)
|
||||
.await;
|
||||
mint.assert_status(axum::http::StatusCode::CREATED);
|
||||
let raw = mint.json::<Value>()["raw_token"].as_str().unwrap().to_string();
|
||||
let raw = mint.json::<Value>()["raw_token"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let denied = s
|
||||
.server
|
||||
@@ -399,7 +418,10 @@ async fn bound_key_cannot_escape_its_app(pool: PgPool) {
|
||||
)
|
||||
.await;
|
||||
mint.assert_status(axum::http::StatusCode::CREATED);
|
||||
let raw = mint.json::<Value>()["raw_token"].as_str().unwrap().to_string();
|
||||
let raw = mint.json::<Value>()["raw_token"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
// Writing into the bound app: allowed.
|
||||
let ok = s
|
||||
@@ -473,7 +495,11 @@ async fn member_list_endpoints_filter_at_sql(pool: PgPool) {
|
||||
.iter()
|
||||
.map(|a| a["slug"].as_str().unwrap().to_string())
|
||||
.collect();
|
||||
assert_eq!(app_slugs, vec!["default"], "member must see only their apps");
|
||||
assert_eq!(
|
||||
app_slugs,
|
||||
vec!["default"],
|
||||
"member must see only their apps"
|
||||
);
|
||||
|
||||
let scripts = s
|
||||
.server
|
||||
@@ -489,8 +515,7 @@ async fn member_list_endpoints_filter_at_sql(pool: PgPool) {
|
||||
.map(|s| s["name"].as_str().unwrap().to_string())
|
||||
.collect();
|
||||
assert!(
|
||||
names.iter().any(|n| n == "default-script")
|
||||
&& !names.iter().any(|n| n == "secret-script"),
|
||||
names.iter().any(|n| n == "default-script") && !names.iter().any(|n| n == "secret-script"),
|
||||
"member listing leaked another app's script: {names:?}"
|
||||
);
|
||||
}
|
||||
@@ -515,7 +540,10 @@ async fn deactivating_user_revokes_their_api_keys(pool: PgPool) {
|
||||
)
|
||||
.await;
|
||||
mint.assert_status(axum::http::StatusCode::CREATED);
|
||||
let raw = mint.json::<Value>()["raw_token"].as_str().unwrap().to_string();
|
||||
let raw = mint.json::<Value>()["raw_token"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
// Key works.
|
||||
let before = s
|
||||
@@ -549,7 +577,9 @@ async fn deactivating_user_revokes_their_api_keys(pool: PgPool) {
|
||||
rows.iter().all(|r| r.expires_at.is_some()),
|
||||
"every key must have an expiry after deactivation"
|
||||
);
|
||||
assert!(rows.iter().all(|r| r.expires_at.unwrap() <= chrono::Utc::now()));
|
||||
assert!(rows
|
||||
.iter()
|
||||
.all(|r| r.expires_at.unwrap() <= chrono::Utc::now()));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -571,7 +601,10 @@ async fn bound_key_with_instance_scope_is_rejected(pool: PgPool) {
|
||||
"app_id": s.default_app.to_string(),
|
||||
}))
|
||||
.await;
|
||||
assert_eq!(r.status_code(), axum::http::StatusCode::UNPROCESSABLE_ENTITY);
|
||||
assert_eq!(
|
||||
r.status_code(),
|
||||
axum::http::StatusCode::UNPROCESSABLE_ENTITY
|
||||
);
|
||||
let body: Value = r.json();
|
||||
assert!(
|
||||
body["error"].as_str().unwrap().contains("bound"),
|
||||
|
||||
@@ -196,7 +196,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn instance_role_round_trip() {
|
||||
for role in [InstanceRole::Owner, InstanceRole::Admin, InstanceRole::Member] {
|
||||
for role in [
|
||||
InstanceRole::Owner,
|
||||
InstanceRole::Admin,
|
||||
InstanceRole::Member,
|
||||
] {
|
||||
assert_eq!(InstanceRole::from_db_str(role.as_str()), Some(role));
|
||||
}
|
||||
assert_eq!(InstanceRole::from_db_str("bogus"), None);
|
||||
|
||||
Reference in New Issue
Block a user