mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-27 02:41:53 +00:00
assorted fixes
This commit is contained in:
@@ -30,34 +30,6 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"18aa5fba13df20495611fbbf7cb331f4b6da5b906148e0d9565905d3bb10071c": {
|
||||
"query": "SELECT logged_out FROM session WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "logged_out",
|
||||
"ordinal": 0,
|
||||
"type_info": "Datetime"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
}
|
||||
},
|
||||
"2932aa02735b6422fca4ba889abfb3de8598178d4690076dc278898753d9df62": {
|
||||
"query": "UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"3efd0daa61f4f8bead1adbe78a8225bc31fb940406d0415b578d3adc03a5e414": {
|
||||
"query": "SELECT hash FROM password",
|
||||
"describe": {
|
||||
@@ -124,6 +96,16 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"63785dc5f193ea31e6f641a910c75857ccd288a3f6e9c4f704331531e4f0689f": {
|
||||
"query": "UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = ? AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"6440354d73a67c041ea29508b43b5f309d45837a44f1a562051ad540d894c7d6": {
|
||||
"query": "DELETE FROM ssh_keys WHERE fingerprint = ?",
|
||||
"describe": {
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
use anyhow::anyhow;
|
||||
use basic_cookies::Cookie;
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use clap::ArgMatches;
|
||||
use http::header::COOKIE;
|
||||
use http::HeaderValue;
|
||||
use indexmap::IndexMap;
|
||||
use rpc_toolkit::command;
|
||||
use rpc_toolkit::command_helpers::prelude::{RequestParts, ResponseParts};
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::context::EitherContext;
|
||||
use crate::middleware::auth::{get_id, hash_token};
|
||||
use crate::util::{display_none, display_serializable, IoFormat};
|
||||
use crate::{Error, ResultExt};
|
||||
use crate::{ensure_code, Error, ResultExt};
|
||||
|
||||
#[command(subcommands(login, logout))]
|
||||
#[command(subcommands(login, logout, session))]
|
||||
pub fn auth(#[context] ctx: EitherContext) -> Result<EitherContext, Error> {
|
||||
Ok(ctx)
|
||||
}
|
||||
@@ -58,12 +59,16 @@ pub async fn login(
|
||||
.fetch_one(&mut handle)
|
||||
.await?
|
||||
.hash;
|
||||
argon2::verify_encoded(&pw_hash, password.as_bytes()).map_err(|_| {
|
||||
Error::new(
|
||||
anyhow!("Password Incorrect"),
|
||||
crate::ErrorKind::Authorization,
|
||||
)
|
||||
})?;
|
||||
ensure_code!(
|
||||
argon2::verify_encoded(&pw_hash, password.as_bytes()).map_err(|_| {
|
||||
Error::new(
|
||||
anyhow!("Password Incorrect"),
|
||||
crate::ErrorKind::Authorization,
|
||||
)
|
||||
})?,
|
||||
crate::ErrorKind::Authorization,
|
||||
"Password Incorrect"
|
||||
);
|
||||
let token = base32::encode(
|
||||
base32::Alphabet::RFC4648 { padding: false },
|
||||
&rand::random::<[u8; 16]>(),
|
||||
@@ -107,7 +112,7 @@ pub async fn logout(
|
||||
if let Some(session) = cookies.iter().find(|c| c.get_name() == "session") {
|
||||
let token = session.get_value();
|
||||
let id = hash_token(token);
|
||||
kill(ctx, id).await?;
|
||||
kill(ctx, vec![id]).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -116,8 +121,8 @@ pub async fn logout(
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Session {
|
||||
logged_in: NaiveDateTime,
|
||||
last_active: NaiveDateTime,
|
||||
logged_in: DateTime<Utc>,
|
||||
last_active: DateTime<Utc>,
|
||||
user_agent: Option<String>,
|
||||
metadata: Value,
|
||||
}
|
||||
@@ -187,8 +192,8 @@ pub async fn list(
|
||||
Ok((
|
||||
row.id,
|
||||
Session {
|
||||
logged_in: row.logged_in,
|
||||
last_active: row.last_active,
|
||||
logged_in: DateTime::from_utc(row.logged_in, Utc),
|
||||
last_active: DateTime::from_utc(row.last_active, Utc),
|
||||
user_agent: row.user_agent,
|
||||
metadata: serde_json::from_str(&row.metadata)
|
||||
.with_kind(crate::ErrorKind::Database)?,
|
||||
@@ -199,13 +204,20 @@ pub async fn list(
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches<'_>) -> Result<Vec<String>, RpcError> {
|
||||
Ok(arg.split(",").map(|s| s.to_owned()).collect())
|
||||
}
|
||||
|
||||
#[command(display(display_none))]
|
||||
pub async fn kill(#[context] ctx: EitherContext, #[arg] id: String) -> Result<(), Error> {
|
||||
pub async fn kill(
|
||||
#[context] ctx: EitherContext,
|
||||
#[arg(parse(parse_comma_separated))] ids: Vec<String>,
|
||||
) -> Result<(), Error> {
|
||||
let rpc_ctx = ctx.as_rpc().unwrap();
|
||||
sqlx::query!(
|
||||
"UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = ?",
|
||||
id
|
||||
)
|
||||
sqlx::query(&format!(
|
||||
"UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id IN ('{}')",
|
||||
ids.join("','")
|
||||
))
|
||||
.execute(&mut rpc_ctx.secret_store.acquire().await?)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
||||
@@ -38,7 +38,7 @@ fn inner_main() -> Result<(), Error> {
|
||||
|e: RpcError| {
|
||||
match e.data {
|
||||
Some(Value::String(s)) => eprintln!("{}: {}", e.message, s),
|
||||
Some(Value::Object(o)) => if let Some(Value::String(s)) = o.get("message") {
|
||||
Some(Value::Object(o)) => if let Some(Value::String(s)) = o.get("details") {
|
||||
eprintln!("{}: {}", e.message, s)
|
||||
}
|
||||
Some(a) => eprintln!("{}: {}", e.message, a),
|
||||
|
||||
@@ -35,7 +35,7 @@ fn inner_main() -> Result<(), Error> {
|
||||
|e: RpcError| {
|
||||
match e.data {
|
||||
Some(Value::String(s)) => eprintln!("{}: {}", e.message, s),
|
||||
Some(Value::Object(o)) => if let Some(Value::String(s)) = o.get("message") {
|
||||
Some(Value::Object(o)) => if let Some(Value::String(s)) = o.get("details") {
|
||||
eprintln!("{}: {}", e.message, s)
|
||||
}
|
||||
Some(a) => eprintln!("{}: {}", e.message, a),
|
||||
|
||||
@@ -20,6 +20,7 @@ use tokio_tungstenite::WebSocketStream;
|
||||
pub use self::model::DatabaseModel;
|
||||
use self::util::WithRevision;
|
||||
use crate::context::{EitherContext, RpcContext};
|
||||
use crate::middleware::auth::is_authed;
|
||||
use crate::util::{display_serializable, IoFormat};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
@@ -55,6 +56,9 @@ async fn ws_handler<
|
||||
}
|
||||
|
||||
pub async fn subscribe(ctx: RpcContext, req: Request<Body>) -> Result<Response<Body>, Error> {
|
||||
let (parts, body) = req.into_parts();
|
||||
// is_authed(&ctx, &parts).await?;
|
||||
let req = Request::from_parts(parts, body);
|
||||
let (res, ws_fut) = hyper_ws_listener::create_ws(req).with_kind(crate::ErrorKind::Network)?;
|
||||
if let Some(ws_fut) = ws_fut {
|
||||
tokio::task::spawn(async move {
|
||||
|
||||
@@ -204,6 +204,8 @@ pub struct InstalledPackageDataEntry {
|
||||
pub manifest: Manifest,
|
||||
pub system_pointers: Vec<SystemPointerSpec>,
|
||||
#[model]
|
||||
pub dependency_info: IndexMap<PackageId, StaticDependencyInfo>,
|
||||
#[model]
|
||||
pub current_dependents: IndexMap<PackageId, CurrentDependencyInfo>,
|
||||
#[model]
|
||||
pub current_dependencies: IndexMap<PackageId, CurrentDependencyInfo>,
|
||||
@@ -211,6 +213,13 @@ pub struct InstalledPackageDataEntry {
|
||||
pub interface_addresses: InterfaceAddressMap,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct StaticDependencyInfo {
|
||||
pub manifest: Option<Manifest>,
|
||||
pub icon: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct CurrentDependencyInfo {
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::time::Duration;
|
||||
use anyhow::anyhow;
|
||||
use emver::VersionRange;
|
||||
use futures::TryStreamExt;
|
||||
use http::HeaderMap;
|
||||
use http::{HeaderMap, StatusCode};
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::{
|
||||
@@ -28,7 +28,8 @@ use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt};
|
||||
use self::progress::{InstallProgress, InstallProgressTracker};
|
||||
use crate::context::{EitherContext, ExtendedContext, RpcContext};
|
||||
use crate::db::model::{
|
||||
CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry, StaticFiles,
|
||||
CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry, StaticDependencyInfo,
|
||||
StaticFiles,
|
||||
};
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::s9pk::reader::S9pkReader;
|
||||
@@ -272,6 +273,76 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
.await?;
|
||||
log::info!("Install {}@{}: Unpacked Manifest", pkg_id, version);
|
||||
|
||||
log::info!("Install {}@{}: Fetching Dependency Info", pkg_id, version);
|
||||
let mut dependency_info = IndexMap::with_capacity(manifest.dependencies.0.len());
|
||||
let reg_url = ctx.package_registry_url().await?;
|
||||
for (dep, info) in &manifest.dependencies.0 {
|
||||
let manifest: Option<Manifest> = match reqwest::get(format!(
|
||||
"{}/package/manifest/{}?version={}",
|
||||
reg_url, dep, info.version
|
||||
))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Registry)?
|
||||
.error_for_status()
|
||||
{
|
||||
Ok(a) => Ok(Some(
|
||||
a.json()
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Deserialization)?,
|
||||
)),
|
||||
Err(e) if e.status() == Some(StatusCode::BAD_REQUEST) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
.with_kind(crate::ErrorKind::Registry)?;
|
||||
if let Some(manifest) = manifest {
|
||||
let dir = Path::new(PKG_PUBLIC_DIR)
|
||||
.join(&manifest.id)
|
||||
.join(manifest.version.as_str());
|
||||
let icon_path = dir.join(format!("icon.{}", manifest.assets.icon_type()));
|
||||
if tokio::fs::metadata(&icon_path).await.is_err() {
|
||||
tokio::fs::create_dir_all(&dir).await?;
|
||||
let icon = reqwest::get(format!(
|
||||
"{}/package/icon/{}?version={}",
|
||||
reg_url, dep, info.version
|
||||
))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Registry)?;
|
||||
let mut dst = File::create(&icon_path).await?;
|
||||
tokio::io::copy(
|
||||
&mut tokio_util::io::StreamReader::new(icon.bytes_stream().map_err(|e| {
|
||||
std::io::Error::new(
|
||||
if e.is_connect() {
|
||||
std::io::ErrorKind::ConnectionRefused
|
||||
} else if e.is_timeout() {
|
||||
std::io::ErrorKind::TimedOut
|
||||
} else {
|
||||
std::io::ErrorKind::Other
|
||||
},
|
||||
e,
|
||||
)
|
||||
})),
|
||||
&mut dst,
|
||||
)
|
||||
.await?;
|
||||
dst.sync_all().await?;
|
||||
}
|
||||
|
||||
dependency_info.insert(
|
||||
dep.clone(),
|
||||
StaticDependencyInfo {
|
||||
icon: format!(
|
||||
"/public/package-data/{}/{}/icon.{}",
|
||||
manifest.id,
|
||||
manifest.version,
|
||||
manifest.assets.icon_type()
|
||||
),
|
||||
manifest: Some(manifest),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
log::info!("Install {}@{}: Fetched Dependency Info", pkg_id, version);
|
||||
|
||||
let public_dir_path = Path::new(PKG_PUBLIC_DIR)
|
||||
.join(pkg_id)
|
||||
.join(version.as_str());
|
||||
@@ -408,6 +479,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
},
|
||||
manifest: manifest.clone(),
|
||||
system_pointers: Vec::new(),
|
||||
dependency_info,
|
||||
current_dependents: {
|
||||
// search required dependencies
|
||||
let mut deps = IndexMap::new();
|
||||
|
||||
@@ -47,18 +47,18 @@ pub fn hash_token(token: &str) -> String {
|
||||
.to_lowercase()
|
||||
}
|
||||
|
||||
async fn is_authed(ctx: &RpcContext, req: &RequestParts) -> Result<(), Error> {
|
||||
pub async fn is_authed(ctx: &RpcContext, req: &RequestParts) -> Result<(), Error> {
|
||||
let id = get_id(req)?;
|
||||
let exp = sqlx::query!("SELECT logged_out FROM session WHERE id = ?", id)
|
||||
.fetch_one(&mut ctx.secret_store.acquire().await?)
|
||||
let session = sqlx::query!("UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = ? AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP", id)
|
||||
.execute(&mut ctx.secret_store.acquire().await?)
|
||||
.await?;
|
||||
match exp.logged_out {
|
||||
Some(exp) if exp >= Utc::now().naive_utc() => Err(Error::new(
|
||||
if session.rows_affected() == 0 {
|
||||
return Err(Error::new(
|
||||
anyhow!("UNAUTHORIZED"),
|
||||
crate::ErrorKind::Authorization,
|
||||
)),
|
||||
_ => Ok(()),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn auth<M: Metadata>(ctx: RpcContext) -> DynMiddleware<M> {
|
||||
|
||||
@@ -22,7 +22,7 @@ export class SessionsPage {
|
||||
|
||||
async ngOnInit () {
|
||||
try {
|
||||
this.sessionInfo = await this.embassyApi.getSessions({ })
|
||||
this.sessionInfo = await this.embassyApi.getSessions({})
|
||||
} catch (e) {
|
||||
this.errToast.present(e.message)
|
||||
} finally {
|
||||
|
||||
@@ -19,7 +19,7 @@ export module RR {
|
||||
export type LoginReq = { password: string, metadata: SessionMetadata } // auth.login - unauthed
|
||||
export type loginRes = null
|
||||
|
||||
export type LogoutReq = { } // auth.logout
|
||||
export type LogoutReq = {} // auth.logout
|
||||
export type LogoutRes = null
|
||||
|
||||
// server
|
||||
@@ -30,26 +30,26 @@ export module RR {
|
||||
export type GetServerLogsReq = { before?: string } // server.logs
|
||||
export type GetServerLogsRes = Log[]
|
||||
|
||||
export type GetServerMetricsReq = { } // server.metrics
|
||||
export type GetServerMetricsReq = {} // server.metrics
|
||||
export type GetServerMetricsRes = Metrics
|
||||
|
||||
export type UpdateServerReq = WithExpire<{ }> // server.update
|
||||
export type UpdateServerReq = WithExpire<{}> // server.update
|
||||
export type UpdateServerRes = WithRevision<null>
|
||||
|
||||
export type RestartServerReq = { } // server.restart
|
||||
export type RestartServerReq = {} // server.restart
|
||||
export type RestartServerRes = null
|
||||
|
||||
export type ShutdownServerReq = { } // server.shutdown
|
||||
export type ShutdownServerReq = {} // server.shutdown
|
||||
export type ShutdownServerRes = null
|
||||
|
||||
// network
|
||||
|
||||
export type RefreshLanReq = { } // network.lan.refresh
|
||||
export type RefreshLanReq = {} // network.lan.refresh
|
||||
export type RefreshLanRes = null
|
||||
|
||||
// sessions
|
||||
|
||||
export type GetSessionsReq = { } // sessions.list
|
||||
export type GetSessionsReq = {} // sessions.list
|
||||
export type GetSessionsRes = {
|
||||
current: string,
|
||||
sessions: { [hash: string]: Session }
|
||||
@@ -78,7 +78,7 @@ export module RR {
|
||||
export type DeleteNotificationReq = { id: string } // notification.delete
|
||||
export type DeleteNotificationRes = null
|
||||
|
||||
export type DeleteAllNotificationsReq = { } // notification.delete.all
|
||||
export type DeleteAllNotificationsReq = {} // notification.delete.all
|
||||
export type DeleteAllNotificationsRes = null
|
||||
|
||||
// wifi
|
||||
@@ -100,7 +100,7 @@ export module RR {
|
||||
|
||||
// ssh
|
||||
|
||||
export type GetSSHKeysReq = { } // ssh.get
|
||||
export type GetSSHKeysReq = {} // ssh.get
|
||||
export type GetSSHKeysRes = SSHKeys
|
||||
|
||||
export type AddSSHKeyReq = { pubkey: string } // ssh.add
|
||||
@@ -119,7 +119,7 @@ export module RR {
|
||||
|
||||
// disk
|
||||
|
||||
export type GetDisksReq = { } // disk.list
|
||||
export type GetDisksReq = {} // disk.list
|
||||
export type GetDisksRes = DiskInfo
|
||||
|
||||
export type EjectDisksReq = { logicalname: string } // disk.eject
|
||||
@@ -178,10 +178,10 @@ export module RR {
|
||||
|
||||
// marketplace
|
||||
|
||||
export type GetMarketplaceDataReq = { }
|
||||
export type GetMarketplaceDataReq = {}
|
||||
export type GetMarketplaceDataRes = MarketplaceData
|
||||
|
||||
export type GetMarketplaceEOSReq = { }
|
||||
export type GetMarketplaceEOSReq = {}
|
||||
export type GetMarketplaceEOSRes = MarketplaceEOS
|
||||
|
||||
export type GetMarketplacePackagesReq = {
|
||||
@@ -195,10 +195,10 @@ export module RR {
|
||||
export type GetMarketplacePackagesRes = MarketplacePkg[]
|
||||
|
||||
export type GetReleaseNotesReq = { id: string }
|
||||
export type GetReleaseNotesRes = { [version: string]: string}
|
||||
export type GetReleaseNotesRes = { [version: string]: string }
|
||||
|
||||
export type GetLatestVersionReq = { ids: string[] }
|
||||
export type GetLatestVersionRes = { [id: string]: string}
|
||||
export type GetLatestVersionRes = { [id: string]: string }
|
||||
|
||||
}
|
||||
|
||||
@@ -334,8 +334,8 @@ export enum NotificationLevel {
|
||||
}
|
||||
|
||||
export type NotificationData<T> = T extends 0 ? null :
|
||||
T extends 1 ? BackupReport :
|
||||
any
|
||||
T extends 1 ? BackupReport :
|
||||
any
|
||||
|
||||
export interface BackupReport {
|
||||
server: {
|
||||
|
||||
Reference in New Issue
Block a user