mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-15 11:20:36 +08:00
http: Remove session and all read-write endpoints
Updates API version to 2.0.0 and crate to 0.13.0
This commit is contained in:
Generated
-13
@@ -209,18 +209,6 @@ dependencies = [
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-auth"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8169113a185f54f68614fcfc3581df585d30bf8542bcb99496990e1025e4120a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"base64 0.21.7",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.3"
|
||||
@@ -1724,7 +1712,6 @@ version = "0.12.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"axum-auth",
|
||||
"axum-server",
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
|
||||
@@ -23,7 +23,6 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
anyhow = { version = "1" }
|
||||
axum = { version = "0.7.2", default-features = false, features = ["json", "query", "tokio", "http1"] }
|
||||
axum-auth = { version= "0.7.0", default-features = false, features = ["auth-bearer"] }
|
||||
axum-server = { version = "0.6.0", default-features = false }
|
||||
base64 = "0.21.3"
|
||||
chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
pub mod auth;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -14,17 +11,15 @@ use radicle::patch::cache::Patches as _;
|
||||
use radicle::storage::git::Repository;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use tokio::sync::RwLock;
|
||||
use tower_http::cors::{self, CorsLayer};
|
||||
|
||||
use radicle::cob::{issue, patch, Author};
|
||||
use radicle::identity::{DocAt, RepoId};
|
||||
use radicle::node::policy::Scope;
|
||||
use radicle::node::routing::Store;
|
||||
use radicle::node::AliasStore;
|
||||
use radicle::node::{Handle, NodeId};
|
||||
use radicle::node::{AliasStore, NodeId};
|
||||
use radicle::storage::{ReadRepository, ReadStorage};
|
||||
use radicle::{Node, Profile};
|
||||
use radicle::Profile;
|
||||
|
||||
mod error;
|
||||
mod json;
|
||||
@@ -38,13 +33,9 @@ pub const RADICLE_VERSION: &str = env!("RADICLE_VERSION");
|
||||
// This version has to be updated on every breaking change to the radicle-httpd API.
|
||||
pub const API_VERSION: &str = "1.2.0";
|
||||
|
||||
/// Identifier for sessions
|
||||
type SessionId = String;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Context {
|
||||
profile: Arc<Profile>,
|
||||
sessions: Arc<RwLock<HashMap<SessionId, auth::Session>>>,
|
||||
cache: Option<Cache>,
|
||||
}
|
||||
|
||||
@@ -52,7 +43,6 @@ impl Context {
|
||||
pub fn new(profile: Arc<Profile>, options: &Options) -> Self {
|
||||
Self {
|
||||
profile,
|
||||
sessions: Default::default(),
|
||||
cache: options.cache.map(Cache::new),
|
||||
}
|
||||
}
|
||||
@@ -106,11 +96,6 @@ impl Context {
|
||||
pub fn profile(&self) -> &Arc<Profile> {
|
||||
&self.profile
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn sessions(&self) -> &Arc<RwLock<HashMap<SessionId, auth::Session>>> {
|
||||
&self.sessions
|
||||
}
|
||||
}
|
||||
|
||||
pub fn router(ctx: Context) -> Router {
|
||||
@@ -342,12 +327,3 @@ mod project {
|
||||
pub seeding: usize,
|
||||
}
|
||||
}
|
||||
|
||||
/// Announce refs to the network for the given RID.
|
||||
pub fn announce_refs(mut node: Node, rid: RepoId) -> Result<(), Error> {
|
||||
match node.announce_refs(rid) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) if e.is_connection_err() => Ok(()),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::serde::timestamp;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
use radicle::crypto::PublicKey;
|
||||
use radicle::node::Alias;
|
||||
|
||||
use crate::api::error::Error;
|
||||
use crate::api::Context;
|
||||
|
||||
pub const UNAUTHORIZED_SESSIONS_EXPIRATION: Duration = Duration::seconds(60);
|
||||
pub const AUTHORIZED_SESSIONS_EXPIRATION: Duration = Duration::weeks(1);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AuthState {
|
||||
Authorized,
|
||||
Unauthorized,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Session {
|
||||
pub status: AuthState,
|
||||
pub public_key: PublicKey,
|
||||
pub alias: Alias,
|
||||
#[serde(with = "timestamp")]
|
||||
pub issued_at: OffsetDateTime,
|
||||
#[serde(with = "timestamp")]
|
||||
pub expires_at: OffsetDateTime,
|
||||
}
|
||||
|
||||
pub async fn validate(ctx: &Context, token: &str) -> Result<(), Error> {
|
||||
let sessions_store = ctx.sessions.read().await;
|
||||
let session = sessions_store
|
||||
.get(token)
|
||||
.ok_or(Error::Auth("Unauthorized"))?;
|
||||
|
||||
if session.status != AuthState::Authorized || session.expires_at <= OffsetDateTime::now_utc() {
|
||||
return Err(Error::Auth("Unauthorized"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -97,10 +97,6 @@ pub enum Error {
|
||||
/// Node error.
|
||||
#[error(transparent)]
|
||||
Node(#[from] radicle::node::Error),
|
||||
|
||||
/// Invalid update to issue or patch.
|
||||
#[error("{0}")]
|
||||
BadRequest(String),
|
||||
}
|
||||
|
||||
impl IntoResponse for Error {
|
||||
@@ -135,7 +131,6 @@ impl IntoResponse for Error {
|
||||
Error::StorageRef(err) if err.is_not_found() => {
|
||||
(StatusCode::NOT_FOUND, Some(err.to_string()))
|
||||
}
|
||||
Error::BadRequest(msg) => (StatusCode::BAD_REQUEST, Some(msg)),
|
||||
other => {
|
||||
tracing::error!("Error: {message}");
|
||||
tracing::debug!("Error Debug: {:?}", other);
|
||||
|
||||
@@ -21,8 +21,6 @@ use radicle_surf::blob::Blob;
|
||||
use radicle_surf::tree::{EntryKind, Tree};
|
||||
use radicle_surf::{Commit, Oid};
|
||||
|
||||
use crate::api::auth::Session;
|
||||
|
||||
/// Returns JSON of a commit.
|
||||
pub(crate) fn commit(commit: &Commit) -> Value {
|
||||
json!({
|
||||
@@ -42,18 +40,6 @@ pub(crate) fn commit(commit: &Commit) -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns JSON of a session.
|
||||
pub(crate) fn session(session_id: String, session: &Session) -> Value {
|
||||
json!({
|
||||
"sessionId": session_id,
|
||||
"status": session.status,
|
||||
"publicKey": session.public_key,
|
||||
"alias": session.alias,
|
||||
"issuedAt": session.issued_at.unix_timestamp(),
|
||||
"expiresAt": session.expires_at.unix_timestamp()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns JSON for a blob with a given `path`.
|
||||
pub(crate) fn blob<T: AsRef<[u8]>>(blob: &Blob<T>, path: &str) -> Value {
|
||||
json!({
|
||||
|
||||
@@ -2,7 +2,6 @@ mod delegates;
|
||||
mod node;
|
||||
mod profile;
|
||||
mod projects;
|
||||
mod sessions;
|
||||
mod stats;
|
||||
|
||||
use axum::extract::State;
|
||||
@@ -22,7 +21,6 @@ pub fn router(ctx: Context) -> Router {
|
||||
.merge(root_router)
|
||||
.merge(node::router(ctx.clone()))
|
||||
.merge(profile::router(ctx.clone()))
|
||||
.merge(sessions::router(ctx.clone()))
|
||||
.merge(delegates::router(ctx.clone()))
|
||||
.merge(projects::router(ctx.clone()))
|
||||
.merge(stats::router(ctx));
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
use axum::extract::State;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::{get, put};
|
||||
use axum::routing::get;
|
||||
use axum::{Json, Router};
|
||||
use axum_auth::AuthBearer;
|
||||
use hyper::StatusCode;
|
||||
use serde_json::json;
|
||||
|
||||
use radicle::identity::RepoId;
|
||||
use radicle::node::address::Store as AddressStore;
|
||||
use radicle::node::routing::Store;
|
||||
use radicle::node::{AliasStore, Handle, NodeId, DEFAULT_TIMEOUT};
|
||||
use radicle::node::{AliasStore, Handle, NodeId};
|
||||
use radicle::Node;
|
||||
|
||||
use crate::api::error::Error;
|
||||
use crate::api::{self, Context, PoliciesQuery};
|
||||
use crate::axum_extra::{Path, Query};
|
||||
use crate::api::Context;
|
||||
use crate::axum_extra::Path;
|
||||
|
||||
pub fn router(ctx: Context) -> Router {
|
||||
Router::new()
|
||||
.route("/node", get(node_handler))
|
||||
.route("/node/policies/repos", get(node_policies_repos_handler))
|
||||
.route(
|
||||
"/node/policies/repos/:rid",
|
||||
put(node_policies_seed_handler)
|
||||
.delete(node_policies_unseed_handler)
|
||||
.get(node_policies_repo_handler),
|
||||
)
|
||||
.route("/node/policies/repos/:rid", get(node_policies_repo_handler))
|
||||
.route("/nodes/:nid", get(nodes_handler))
|
||||
.route("/nodes/:nid/inventory", get(nodes_inventory_handler))
|
||||
.with_state(ctx)
|
||||
@@ -106,42 +99,6 @@ async fn node_policies_repo_handler(
|
||||
Ok::<_, Error>(Json(*policy))
|
||||
}
|
||||
|
||||
/// Seed a new repo.
|
||||
/// `PUT /node/policies/repos/:rid`
|
||||
async fn node_policies_seed_handler(
|
||||
State(ctx): State<Context>,
|
||||
AuthBearer(token): AuthBearer,
|
||||
Path(project): Path<RepoId>,
|
||||
Query(qs): Query<PoliciesQuery>,
|
||||
) -> impl IntoResponse {
|
||||
api::auth::validate(&ctx, &token).await?;
|
||||
let mut node = Node::new(ctx.profile.socket());
|
||||
node.seed(project, qs.scope.unwrap_or_default())?;
|
||||
|
||||
if let Some(from) = qs.from {
|
||||
let results = node.fetch(project, from, DEFAULT_TIMEOUT)?;
|
||||
return Ok::<_, Error>((
|
||||
StatusCode::OK,
|
||||
Json(json!({ "success": true, "results": results })),
|
||||
));
|
||||
}
|
||||
Ok::<_, Error>((StatusCode::OK, Json(json!({ "success": true }))))
|
||||
}
|
||||
|
||||
/// Unseed a repo.
|
||||
/// `DELETE /node/policies/repos/:rid`
|
||||
async fn node_policies_unseed_handler(
|
||||
State(ctx): State<Context>,
|
||||
AuthBearer(token): AuthBearer,
|
||||
Path(project): Path<RepoId>,
|
||||
) -> impl IntoResponse {
|
||||
api::auth::validate(&ctx, &token).await?;
|
||||
let mut node = Node::new(ctx.profile.socket());
|
||||
node.unseed(project)?;
|
||||
|
||||
Ok::<_, Error>((StatusCode::OK, Json(json!({ "success": true }))))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod routes {
|
||||
use std::net::SocketAddr;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,201 +0,0 @@
|
||||
use std::iter::repeat_with;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::{post, put};
|
||||
use axum::{Json, Router};
|
||||
use axum_auth::AuthBearer;
|
||||
use hyper::StatusCode;
|
||||
use radicle::crypto::{PublicKey, Signature};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::api::auth::{self, AuthState, Session};
|
||||
use crate::api::error::Error;
|
||||
use crate::api::json;
|
||||
use crate::api::Context;
|
||||
use crate::axum_extra::Path;
|
||||
|
||||
pub fn router(ctx: Context) -> Router {
|
||||
Router::new()
|
||||
.route("/sessions", post(session_create_handler))
|
||||
.route(
|
||||
"/sessions/:id",
|
||||
put(session_signin_handler)
|
||||
.get(session_handler)
|
||||
.delete(session_delete_handler),
|
||||
)
|
||||
.with_state(ctx)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct AuthChallenge {
|
||||
sig: Signature,
|
||||
pk: PublicKey,
|
||||
}
|
||||
|
||||
/// Create session.
|
||||
/// `POST /sessions`
|
||||
async fn session_create_handler(State(ctx): State<Context>) -> impl IntoResponse {
|
||||
let mut rng = fastrand::Rng::new();
|
||||
let session_id = repeat_with(|| rng.alphanumeric())
|
||||
.take(32)
|
||||
.collect::<String>();
|
||||
let signer = ctx.profile.signer().map_err(Error::from)?;
|
||||
let session = Session {
|
||||
status: AuthState::Unauthorized,
|
||||
public_key: *signer.public_key(),
|
||||
alias: ctx.profile.config.node.alias.clone(),
|
||||
issued_at: OffsetDateTime::now_utc(),
|
||||
expires_at: OffsetDateTime::now_utc()
|
||||
.checked_add(auth::UNAUTHORIZED_SESSIONS_EXPIRATION)
|
||||
.unwrap(),
|
||||
};
|
||||
let mut sessions = ctx.sessions.write().await;
|
||||
sessions.insert(session_id.clone(), session.clone());
|
||||
|
||||
Ok::<_, Error>((
|
||||
StatusCode::CREATED,
|
||||
Json(json::session(session_id, &session)),
|
||||
))
|
||||
}
|
||||
|
||||
/// Get a session.
|
||||
/// `GET /sessions/:id`
|
||||
async fn session_handler(
|
||||
State(ctx): State<Context>,
|
||||
Path(session_id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let sessions = ctx.sessions.read().await;
|
||||
let session = sessions.get(&session_id).ok_or(Error::NotFound)?;
|
||||
|
||||
Ok::<_, Error>(Json(json::session(session_id, session)))
|
||||
}
|
||||
|
||||
/// Update session.
|
||||
/// `PUT /sessions/:id`
|
||||
async fn session_signin_handler(
|
||||
State(ctx): State<Context>,
|
||||
Path(session_id): Path<String>,
|
||||
Json(request): Json<AuthChallenge>,
|
||||
) -> impl IntoResponse {
|
||||
let mut sessions = ctx.sessions.write().await;
|
||||
let session = sessions.get_mut(&session_id).ok_or(Error::NotFound)?;
|
||||
if session.status == AuthState::Unauthorized {
|
||||
if session.public_key != request.pk {
|
||||
return Err(Error::Auth("Invalid public key"));
|
||||
}
|
||||
if session.expires_at <= OffsetDateTime::now_utc() {
|
||||
return Err(Error::Auth("Session expired"));
|
||||
}
|
||||
let payload = format!("{}:{}", session_id, request.pk);
|
||||
request
|
||||
.pk
|
||||
.verify(payload.as_bytes(), &request.sig)
|
||||
.map_err(Error::from)?;
|
||||
session.status = AuthState::Authorized;
|
||||
session.expires_at = OffsetDateTime::now_utc()
|
||||
.checked_add(auth::AUTHORIZED_SESSIONS_EXPIRATION)
|
||||
.unwrap();
|
||||
|
||||
return Ok::<_, Error>(Json(json!({ "success": true })));
|
||||
}
|
||||
|
||||
Err(Error::Auth("Session already authorized"))
|
||||
}
|
||||
|
||||
/// Delete session.
|
||||
/// `DELETE /sessions/:id`
|
||||
async fn session_delete_handler(
|
||||
State(ctx): State<Context>,
|
||||
AuthBearer(token): AuthBearer,
|
||||
Path(session_id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
if token != session_id {
|
||||
return Err(Error::Auth("Not authorized to delete this session"));
|
||||
}
|
||||
let mut sessions = ctx.sessions.write().await;
|
||||
sessions.remove_entry(&token).ok_or(Error::NotFound)?;
|
||||
|
||||
Ok::<_, Error>(Json(json!({ "success": true })))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod routes {
|
||||
use axum::body::Body;
|
||||
use axum::http::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use radicle::crypto::{PublicKey, Signature, Signer};
|
||||
|
||||
use crate::api::auth::{AuthState, Session};
|
||||
use crate::test::{self, get, post, put};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SessionInfo {
|
||||
pub session_id: String,
|
||||
pub public_key: PublicKey,
|
||||
}
|
||||
|
||||
pub fn sign(
|
||||
signer: Box<dyn Signer>,
|
||||
session: &SessionInfo,
|
||||
) -> Result<Signature, anyhow::Error> {
|
||||
signer
|
||||
.try_sign(format!("{}:{}", session.session_id, session.public_key).as_bytes())
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_session() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = test::seed(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
|
||||
// Create session.
|
||||
let response = post(&app, "/sessions", None, None).await;
|
||||
let status = response.status();
|
||||
let json = response.json().await;
|
||||
let session_info: SessionInfo = serde_json::from_value(json).unwrap();
|
||||
|
||||
assert_eq!(status, StatusCode::CREATED);
|
||||
|
||||
// Check that an unauthorized session has been created.
|
||||
let response = get(&app, format!("/sessions/{}", session_info.session_id)).await;
|
||||
let status = response.status();
|
||||
let json = response.json().await;
|
||||
let body: Session = serde_json::from_value(json).unwrap();
|
||||
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body.status, AuthState::Unauthorized);
|
||||
|
||||
// Create request body
|
||||
let signer = ctx.profile.signer().unwrap();
|
||||
let signature = sign(signer, &session_info).unwrap();
|
||||
let body = serde_json::to_vec(&super::AuthChallenge {
|
||||
sig: signature,
|
||||
pk: session_info.public_key,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let response = put(
|
||||
&app,
|
||||
format!("/sessions/{}", session_info.session_id),
|
||||
Some(Body::from(body)),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
// Check that session has been authorized.
|
||||
let response = get(&app, format!("/sessions/{}", session_info.session_id)).await;
|
||||
let status = response.status();
|
||||
let json = response.json().await;
|
||||
let body: Session = serde_json::from_value(json).unwrap();
|
||||
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body.status, AuthState::Authorized);
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,9 @@ use axum::body::{Body, Bytes};
|
||||
use axum::http::{Method, Request};
|
||||
use axum::Router;
|
||||
use serde_json::Value;
|
||||
use time::OffsetDateTime;
|
||||
use tower::ServiceExt;
|
||||
|
||||
use radicle::cob::patch::MergeTarget;
|
||||
use radicle::crypto::ssh::keystore::MemorySigner;
|
||||
use radicle::crypto::ssh::Keystore;
|
||||
use radicle::crypto::{KeyPair, Seed, Signer};
|
||||
use radicle::git::{raw as git2, RefString};
|
||||
@@ -23,7 +21,7 @@ use radicle::Storage;
|
||||
use radicle::{node, profile};
|
||||
use radicle_crypto::test::signer::MockSigner;
|
||||
|
||||
use crate::api::{auth, Context};
|
||||
use crate::api::Context;
|
||||
|
||||
pub const RID: &str = "rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp";
|
||||
pub const RID_PRIVATE: &str = "rad:zLuTzcmoWMcdK37xqArS8eckp9vK";
|
||||
@@ -32,14 +30,8 @@ pub const PARENT: &str = "ee8d6a29304623a78ebfa5eeed5af674d0e58f83";
|
||||
pub const INITIAL_COMMIT: &str = "f604ce9fd5b7cc77b7609beda45ea8760bee78f7";
|
||||
pub const DID: &str = "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi";
|
||||
pub const ISSUE_ID: &str = "ca67d195c0b308b51810dedd93157a20764d5db5";
|
||||
pub const ISSUE_DISCUSSION_ID: &str = "41e2823caa54f1d53e375035ed4aabd0a89fa855";
|
||||
pub const ISSUE_COMMENT_ID: &str = "e9f963fab82ad875e46b29a327c5d3d51f825cdc";
|
||||
pub const SESSION_ID: &str = "u9MGAkkfkMOv0uDDB2WeUHBT7HbsO2Dy";
|
||||
pub const TIMESTAMP: u64 = 1671125284;
|
||||
pub const CONTRIBUTOR_RID: &str = "rad:z4XaCmN3jLSeiMvW15YTDpNbDHFhG";
|
||||
pub const CONTRIBUTOR_DID: &str = "did:key:z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8";
|
||||
pub const CONTRIBUTOR_ALIAS: &str = "seed";
|
||||
pub const CONTRIBUTOR_PATCH_ID: &str = "3e3f0dc34b3eeb64cfbc7218fbd52b97246e0564";
|
||||
|
||||
/// Create a new profile.
|
||||
pub fn profile(home: &Path, seed: [u8; 32]) -> radicle::Profile {
|
||||
@@ -81,17 +73,6 @@ pub fn seed(dir: &Path) -> Context {
|
||||
seed_with_signer(dir, profile, &signer)
|
||||
}
|
||||
|
||||
pub fn contributor(dir: &Path) -> Context {
|
||||
let mut seed = [0xff; 32];
|
||||
*seed.last_mut().unwrap() = 0xee;
|
||||
|
||||
let home = dir.join("radicle");
|
||||
let profile = profile(home.as_path(), seed);
|
||||
let signer = MemorySigner::load(&profile.keystore, None).unwrap();
|
||||
|
||||
seed_with_signer(dir, profile, &signer)
|
||||
}
|
||||
|
||||
fn seed_with_signer<G: Signer>(dir: &Path, profile: radicle::Profile, signer: &G) -> Context {
|
||||
const DEFAULT_BRANCH: &str = "master";
|
||||
|
||||
@@ -319,24 +300,6 @@ fn seed_with_signer<G: Signer>(dir: &Path, profile: radicle::Profile, signer: &G
|
||||
Context::new(Arc::new(profile), &options)
|
||||
}
|
||||
|
||||
/// Adds an authorized session to the Context::sessions HashMap.
|
||||
pub async fn create_session(ctx: Context) {
|
||||
let issued_at = OffsetDateTime::now_utc();
|
||||
let mut sessions = ctx.sessions().write().await;
|
||||
sessions.insert(
|
||||
String::from(SESSION_ID),
|
||||
auth::Session {
|
||||
status: auth::AuthState::Authorized,
|
||||
public_key: ctx.profile().public_key,
|
||||
alias: ctx.profile().config.node.alias.clone(),
|
||||
issued_at,
|
||||
expires_at: issued_at
|
||||
.checked_add(auth::AUTHORIZED_SESSIONS_EXPIRATION)
|
||||
.unwrap(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn get(app: &Router, path: impl ToString) -> Response {
|
||||
Response(
|
||||
app.clone()
|
||||
@@ -346,48 +309,6 @@ pub async fn get(app: &Router, path: impl ToString) -> Response {
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn post(
|
||||
app: &Router,
|
||||
path: impl ToString,
|
||||
body: Option<Body>,
|
||||
auth: Option<String>,
|
||||
) -> Response {
|
||||
Response(
|
||||
app.clone()
|
||||
.oneshot(request(path, Method::POST, body, auth))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn patch(
|
||||
app: &Router,
|
||||
path: impl ToString,
|
||||
body: Option<Body>,
|
||||
auth: Option<String>,
|
||||
) -> Response {
|
||||
Response(
|
||||
app.clone()
|
||||
.oneshot(request(path, Method::PATCH, body, auth))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn put(
|
||||
app: &Router,
|
||||
path: impl ToString,
|
||||
body: Option<Body>,
|
||||
auth: Option<String>,
|
||||
) -> Response {
|
||||
Response(
|
||||
app.clone()
|
||||
.oneshot(request(path, Method::PUT, body, auth))
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn request(
|
||||
path: impl ToString,
|
||||
method: Method,
|
||||
@@ -414,20 +335,6 @@ impl Response {
|
||||
serde_json::from_slice(&body).unwrap()
|
||||
}
|
||||
|
||||
pub async fn id(self) -> radicle::git::Oid {
|
||||
let json = self.json().await;
|
||||
let string = json["id"].as_str().unwrap();
|
||||
|
||||
radicle::git::Oid::from_str(string).unwrap()
|
||||
}
|
||||
|
||||
pub async fn success(self) -> bool {
|
||||
let json = self.json().await;
|
||||
let success = json["success"].as_bool();
|
||||
|
||||
success.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn status(&self) -> axum::http::StatusCode {
|
||||
self.0.status()
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { SessionRoute } from "@app/lib/router/definitions";
|
||||
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import * as httpd from "@app/lib/httpd";
|
||||
import * as modal from "@app/lib/modal";
|
||||
import * as router from "@app/lib/router";
|
||||
import { experimental } from "@app/lib/appearance";
|
||||
|
||||
import AppLayout from "@app/App/AppLayout.svelte";
|
||||
import Loading from "@app/components/Loading.svelte";
|
||||
|
||||
import AuthenticationErrorModal from "@app/modals/AuthenticationErrorModal.svelte";
|
||||
import ErrorModal from "@app/modals/ErrorModal.svelte";
|
||||
|
||||
export let activeRoute: SessionRoute;
|
||||
|
||||
onMount(async () => {
|
||||
const port = Number.parseInt(activeRoute.params.apiAddr.split(":")[1]);
|
||||
if (port > 0 && port < 2 ** 16) {
|
||||
httpd.changeHttpdPort(port);
|
||||
}
|
||||
const isAuthenticated = await httpd.authenticate(activeRoute.params);
|
||||
|
||||
if (!isAuthenticated && !$experimental) {
|
||||
modal.show({
|
||||
component: ErrorModal,
|
||||
props: {
|
||||
title: "Authentication failed",
|
||||
subtitle: [
|
||||
"Authentication only works in experimental mode.",
|
||||
"Go to Settings, set experimental mode to On and try again.",
|
||||
],
|
||||
error: {
|
||||
message: "Can't authenticate in read-only mode.",
|
||||
stack: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (!isAuthenticated) {
|
||||
modal.show({
|
||||
component: AuthenticationErrorModal,
|
||||
props: {},
|
||||
});
|
||||
}
|
||||
|
||||
void router.navigateToUrl(
|
||||
"push",
|
||||
new URL(activeRoute.params.path || "", window.location.origin),
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<AppLayout>
|
||||
<Loading center />
|
||||
</AppLayout>
|
||||
Reference in New Issue
Block a user