优化部署流程
This commit is contained in:
1 parent
16e5db3b26
commit
2ecc72d027
20 files changed
+597
-116
No files matched your search
@@ -120,55 +120,15 @@ pub async fn load(
|
||||
},
|
||||
Err(error) => return Err(error.into()),
|
||||
};
|
||||
let mut accounts = Accounts {
|
||||
Ok(Accounts {
|
||||
credentials,
|
||||
grants,
|
||||
sessions: BTreeMap::new(),
|
||||
attempts: VecDeque::new(),
|
||||
changes: VecDeque::new(),
|
||||
};
|
||||
if accounts.credentials.is_empty() {
|
||||
let name = std::env::var("ADMIN_USERNAME").unwrap_or_else(|_| "admin".into());
|
||||
username(&name)?;
|
||||
let hash =
|
||||
std::env::var("ADMIN_PASSWORD_HASH").context("首次启动必须设置 ADMIN_PASSWORD_HASH")?;
|
||||
let parsed = PasswordHash::new(&hash).map_err(|_| anyhow::anyhow!("管理员哈希格式无效"))?;
|
||||
ensure!(
|
||||
parsed.algorithm.as_str() == "argon2id",
|
||||
"管理员哈希必须为 Argon2id"
|
||||
);
|
||||
accounts
|
||||
.credentials
|
||||
.insert(name.clone(), Credential { hash });
|
||||
accounts.grants.publishers.insert(
|
||||
name,
|
||||
Grant {
|
||||
enabled: true,
|
||||
role: Role::Admin,
|
||||
prefixes: vec![],
|
||||
ssh_key: None,
|
||||
},
|
||||
);
|
||||
atomic(config, &serde_json::to_vec_pretty(&accounts.grants)?, 0o644).await?;
|
||||
atomic(
|
||||
&data.join("users.json"),
|
||||
&serde_json::to_vec(&accounts.credentials)?,
|
||||
0o600,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ensure!(
|
||||
accounts
|
||||
.grants
|
||||
.publishers
|
||||
.iter()
|
||||
.any(|(name, g)| g.role == Role::Admin
|
||||
&& g.enabled
|
||||
&& accounts.credentials.contains_key(name)),
|
||||
"至少需要一个启用的管理员"
|
||||
);
|
||||
Ok(accounts)
|
||||
})
|
||||
}
|
||||
|
||||
fn rate(accounts: &mut Accounts) -> ApiResult<()> {
|
||||
let now = Instant::now();
|
||||
while accounts
|
||||
|
||||
@@ -602,6 +602,66 @@ async fn request(
|
||||
)
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn empty_accounts_start_and_first_user_remains_reader_after_reload() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let app = app(dir.path()).await;
|
||||
tokio::fs::remove_file(dir.path().join("users.json"))
|
||||
.await
|
||||
.unwrap();
|
||||
tokio::fs::remove_file(&app.ssh_config).await.unwrap();
|
||||
*app.accounts.lock().await =
|
||||
accounts::load(dir.path(), &app.ssh_config, dir.path().join("ingress.git"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(app.accounts.lock().await.grants.publishers.is_empty());
|
||||
let (status, _, value) = request(
|
||||
app.clone(),
|
||||
"POST",
|
||||
"/api/auth/register",
|
||||
json!({"username":"first", "password":"First-user-password-123"}),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(value["role"], "reader");
|
||||
*app.accounts.lock().await =
|
||||
accounts::load(dir.path(), &app.ssh_config, dir.path().join("ingress.git"))
|
||||
.await
|
||||
.unwrap();
|
||||
let (status, headers, value) = request(
|
||||
app.clone(),
|
||||
"POST",
|
||||
"/api/auth/login",
|
||||
json!({"username":"first", "password":"First-user-password-123"}),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(app.accounts.lock().await.grants.publishers.len(), 1);
|
||||
assert_eq!(
|
||||
app.accounts.lock().await.grants.publishers["first"].role,
|
||||
Role::Reader
|
||||
);
|
||||
let cookie = headers[header::SET_COOKIE]
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.split(';')
|
||||
.next()
|
||||
.unwrap();
|
||||
let (status, _, _) = request(
|
||||
app,
|
||||
"GET",
|
||||
"/api/admin/users",
|
||||
json!(null),
|
||||
Some(cookie),
|
||||
value["csrf"].as_str(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::FORBIDDEN);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn registration_login_grants_revocation_and_private_content_boundaries() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let app = app(dir.path()).await;
|
||||
|
||||
Reference in new issue
Block a user