assorted fixes

This commit is contained in:
Aiden McClelland
2021-08-13 12:34:29 -06:00
parent 26dd880633
commit c223894943
10 changed files with 154 additions and 75 deletions

View File

@@ -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": {

View File

@@ -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(())

View File

@@ -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),

View File

@@ -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),

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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> {

View File

@@ -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 {

View File

@@ -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: {