Compare commits

...

5 Commits

Author SHA1 Message Date
567a0bed68 Update README.md 2025-01-18 03:52:13 +01:00
63c16a797b Merge pull request #166 from cupcakearmy/fix-race-condition
fix: introduce locks for delete endpoint to guarantee view counter
2025-01-17 18:01:30 +01:00
ea50590532 fix: introduce locks for delete endpoint to guarantee view counter 2025-01-17 17:34:32 +01:00
b22c3122d7 Merge pull request #163 from werewolfboy13/update-redis-link
Update Redis documentation link in Docker Compose file
2025-01-15 16:11:47 +01:00
Marek
18af2b2f45 Update Redis documentation link in Docker Compose file
Fixes #162

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/cupcakearmy/cryptgeon/issues/162?shareId=XXXX-XXXX-XXXX-XXXX).
2025-01-09 04:08:29 -06:00
6 changed files with 468 additions and 229 deletions

View File

@@ -63,6 +63,8 @@ client side with the <code>key</code> and then sent to the server. data is store
never persisted to disk. the server never sees the encryption key and cannot decrypt the contents
of the notes even if it tried to.
> View counts are guaranteed with one running instance of cryptgeon. Multiple instances connected to the same Redis instance can run into race conditions, where a note might be retrieved more than the view count allows.
## Screenshot
![screenshot](./design/Screens.png)

View File

@@ -2,7 +2,7 @@ services:
redis:
image: redis:7-alpine
# Set a size limit. See link below on how to customise.
# https://redis.io/docs/manual/eviction/
# https://redis.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/
# command: redis-server --maxmemory 1gb --maxmemory-policy allkeys-lru
app:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Clone)]
pub struct SharedState {
pub locks: LockMap,
}
pub type LockMap = Arc<Mutex<HashMap<String, Arc<Mutex<()>>>>>;

View File

@@ -1,9 +1,13 @@
use std::{collections::HashMap, sync::Arc};
use axum::{
extract::{DefaultBodyLimit, Request},
routing::{delete, get, post},
Router, ServiceExt,
};
use dotenv::dotenv;
use lock::SharedState;
use tokio::sync::Mutex;
use tower::Layer;
use tower_http::{
compression::CompressionLayer,
@@ -16,6 +20,7 @@ extern crate lazy_static;
mod config;
mod health;
mod lock;
mod note;
mod status;
mod store;
@@ -24,9 +29,13 @@ mod store;
async fn main() {
dotenv().ok();
let shared_state = SharedState {
locks: Arc::new(Mutex::new(HashMap::new())),
};
if !store::can_reach_redis() {
println!("cannot reach redis");
panic!("canont reach redis");
panic!("cannot reach redis");
}
let notes_routes = Router::new()
@@ -53,7 +62,8 @@ async fn main() {
.deflate(true)
.gzip(true)
.zstd(true),
);
)
.with_state(shared_state);
let app = NormalizePathLayer::trim_trailing_slash().layer(app);

View File

@@ -5,11 +5,12 @@ use axum::{
Json,
};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use std::{sync::Arc, time::SystemTime};
use tokio::sync::Mutex;
use crate::config;
use crate::note::{generate_id, Note, NoteInfo};
use crate::store;
use crate::{config, lock::SharedState};
use super::NotePublic;
@@ -80,11 +81,20 @@ pub async fn create(Json(mut n): Json<Note>) -> Response {
}
}
pub async fn delete(Path(OneNoteParams { id }): Path<OneNoteParams>) -> Response {
pub async fn delete(
Path(OneNoteParams { id }): Path<OneNoteParams>,
state: axum::extract::State<SharedState>,
) -> Response {
let mut locks_map = state.locks.lock().await;
let lock = locks_map
.entry(id.clone())
.or_insert_with(|| Arc::new(Mutex::new(())))
.clone();
drop(locks_map);
let _guard = lock.lock().await;
let note = store::get(&id);
match note {
// Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
// Ok(None) => return HttpResponse::NotFound().finish(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
Ok(None) => (StatusCode::NOT_FOUND).into_response(),
Ok(Some(note)) => {