diff --git a/backend/sync_server/src/server.rs b/backend/sync_server/src/server.rs index e993ed1..0fd5fa0 100644 --- a/backend/sync_server/src/server.rs +++ b/backend/sync_server/src/server.rs @@ -1,6 +1,7 @@ mod auth; mod create_document; mod delete_document; +mod device_id_header; mod fetch_document_version; mod fetch_document_version_content; mod fetch_latest_document_version; @@ -32,6 +33,7 @@ use axum::{ response::IntoResponse, routing::IntoMakeService, }; +use device_id_header::DEVICE_ID_HEADER_NAME; use log::{error, info}; use tokio::signal; use tower_http::{ @@ -79,7 +81,11 @@ pub async fn create_server(config_path: Option) -> Result<()> { .layer( CorsLayer::new() .allow_origin("*".parse::().expect("Failed to parse origin")) - .allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION]) + .allow_headers([ + http::header::CONTENT_TYPE, + http::header::AUTHORIZATION, + DEVICE_ID_HEADER_NAME.clone(), + ]) .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE]), ) .layer( diff --git a/backend/sync_server/src/server/create_document.rs b/backend/sync_server/src/server/create_document.rs index 0fc9dc3..b9459df 100644 --- a/backend/sync_server/src/server/create_document.rs +++ b/backend/sync_server/src/server/create_document.rs @@ -4,13 +4,16 @@ use axum::{ Extension, extract::{Path, State}, }; -use axum_extra::{TypedHeader, headers::UserAgent}; +use axum_extra::TypedHeader; use axum_jsonschema::Json; use schemars::JsonSchema; use serde::Deserialize; use sync_lib::base64_to_bytes; -use super::requests::{CreateDocumentVersion, CreateDocumentVersionMultipart}; +use super::{ + device_id_header::DeviceIdHeader, + requests::{CreateDocumentVersion, CreateDocumentVersionMultipart}, +}; use crate::{ app_state::{ AppState, @@ -38,7 +41,7 @@ pub struct CreateDocumentPathParams { pub async fn create_document_multipart( Path(CreateDocumentPathParams { vault_id }): Path, Extension(user): Extension, - TypedHeader(user_agent): TypedHeader, + TypedHeader(user_agent): TypedHeader, State(state): State, TypedMultipart(axum_typed_multipart::TypedMultipart(request)): TypedMultipart< CreateDocumentVersionMultipart, @@ -64,7 +67,7 @@ pub async fn create_document_multipart( pub async fn create_document_json( Path(CreateDocumentPathParams { vault_id }): Path, Extension(user): Extension, - TypedHeader(user_agent): TypedHeader, + TypedHeader(user_agent): TypedHeader, State(state): State, Json(request): Json, ) -> Result, SyncServerError> { @@ -88,7 +91,7 @@ pub async fn create_document_json( #[allow(clippy::too_many_arguments)] async fn internal_create_document( user: User, - user_agent: UserAgent, + user_agent: DeviceIdHeader, state: AppState, vault_id: VaultId, document_id: Option, @@ -137,7 +140,7 @@ async fn internal_create_document( updated_date: chrono::Utc::now(), is_deleted: false, user_id: user.name, - device_id: user_agent.to_string(), + device_id: user_agent.0, }; state diff --git a/backend/sync_server/src/server/delete_document.rs b/backend/sync_server/src/server/delete_document.rs index e6855d6..e519c03 100644 --- a/backend/sync_server/src/server/delete_document.rs +++ b/backend/sync_server/src/server/delete_document.rs @@ -3,12 +3,12 @@ use axum::{ Extension, extract::{Path, State}, }; -use axum_extra::{TypedHeader, headers::UserAgent}; +use axum_extra::TypedHeader; use axum_jsonschema::Json; use schemars::JsonSchema; use serde::Deserialize; -use super::requests::DeleteDocumentVersion; +use super::{device_id_header::DeviceIdHeader, requests::DeleteDocumentVersion}; use crate::{ app_state::{ AppState, @@ -38,7 +38,7 @@ pub async fn delete_document( document_id, }): Path, Extension(user): Extension, - TypedHeader(user_agent): TypedHeader, + TypedHeader(user_agent): TypedHeader, State(state): State, Json(request): Json, ) -> Result, SyncServerError> { @@ -76,7 +76,7 @@ pub async fn delete_document( updated_date: chrono::Utc::now(), is_deleted: true, user_id: user.name, - device_id: user_agent.to_string(), + device_id: user_agent.0, }; state diff --git a/backend/sync_server/src/server/device_id_header.rs b/backend/sync_server/src/server/device_id_header.rs new file mode 100644 index 0000000..be36c8d --- /dev/null +++ b/backend/sync_server/src/server/device_id_header.rs @@ -0,0 +1,33 @@ +use axum_extra::headers; +use headers::{Header, HeaderName, HeaderValue}; + +pub struct DeviceIdHeader(pub String); + +pub static DEVICE_ID_HEADER_NAME: HeaderName = HeaderName::from_static("device-id"); + +impl Header for DeviceIdHeader { + fn name() -> &'static HeaderName { &DEVICE_ID_HEADER_NAME } + + fn decode<'i, I>(values: &mut I) -> Result + where + I: Iterator, + { + let value = values.next().ok_or_else(headers::Error::invalid)?; + + Ok(DeviceIdHeader( + value + .to_str() + .map_err(|_| headers::Error::invalid())? + .to_owned(), + )) + } + + fn encode(&self, values: &mut E) + where + E: Extend, + { + let value = HeaderValue::from_static(Box::leak(self.0.to_string().into_boxed_str())); + + values.extend(std::iter::once(value)); + } +} diff --git a/backend/sync_server/src/server/update_document.rs b/backend/sync_server/src/server/update_document.rs index ded4dd0..22eb38b 100644 --- a/backend/sync_server/src/server/update_document.rs +++ b/backend/sync_server/src/server/update_document.rs @@ -4,7 +4,7 @@ use axum::{ Extension, extract::{Path, State}, }; -use axum_extra::{TypedHeader, headers::UserAgent}; +use axum_extra::TypedHeader; use axum_jsonschema::Json; use log::info; use schemars::JsonSchema; @@ -12,6 +12,7 @@ use serde::Deserialize; use sync_lib::{base64_to_bytes, is_file_type_mergable, merge}; use super::{ + device_id_header::DeviceIdHeader, requests::{UpdateDocumentVersion, UpdateDocumentVersionMultipart}, responses::DocumentUpdateResponse, }; @@ -42,7 +43,7 @@ pub async fn update_document_multipart( document_id, }): Path, Extension(user): Extension, - TypedHeader(user_agent): TypedHeader, + TypedHeader(user_agent): TypedHeader, State(state): State, TypedMultipart(axum_typed_multipart::TypedMultipart(request)): TypedMultipart< UpdateDocumentVersionMultipart, @@ -69,7 +70,7 @@ pub async fn update_document_json( document_id, }): Path, Extension(user): Extension, - TypedHeader(user_agent): TypedHeader, + TypedHeader(user_agent): TypedHeader, State(state): State, Json(request): Json, ) -> Result, SyncServerError> { @@ -94,7 +95,7 @@ pub async fn update_document_json( #[allow(clippy::too_many_arguments, clippy::too_many_lines)] async fn internal_update_document( user: User, - user_agent: UserAgent, + user_agent: DeviceIdHeader, state: AppState, vault_id: VaultId, document_id: DocumentId, @@ -214,7 +215,7 @@ async fn internal_update_document( updated_date: chrono::Utc::now(), is_deleted: false, user_id: user.name, - device_id: user_agent.to_string(), + device_id: user_agent.0, }; state diff --git a/frontend/sync-client/src/services/sync-service.ts b/frontend/sync-client/src/services/sync-service.ts index 69eae6c..741aa01 100644 --- a/frontend/sync-client/src/services/sync-service.ts +++ b/frontend/sync-client/src/services/sync-service.ts @@ -44,6 +44,19 @@ export class SyncService { }); } + private get deviceIdHeader(): string { + // @ts-expect-error, injected by webpack + const packageVersion = __CURRENT_VERSION__; // eslint-disable-line + + const platform = + typeof navigator !== "undefined" + ? navigator.platform // eslint-disable-line @typescript-eslint/no-deprecated + : typeof process !== "undefined" + ? process.platform + : "unknown"; + return `vault-link/${packageVersion} (${this.deviceId}; ${platform})`; + } + private static formatError( error: components["schemas"]["SerializedError"] ): string { @@ -82,6 +95,9 @@ export class SyncService { params: { path: { vault_id: vaultName + }, + header: { + "device-id": this.deviceIdHeader } }, // eslint-disable-next-line @@ -135,6 +151,9 @@ export class SyncService { path: { vault_id: vaultName, document_id: documentId + }, + header: { + "device-id": this.deviceIdHeader } }, // eslint-disable-next-line @@ -175,8 +194,12 @@ export class SyncService { path: { vault_id: vaultName, document_id: documentId + }, + header: { + "device-id": this.deviceIdHeader } }, + body: { relativePath, deviceId: this.deviceId diff --git a/frontend/sync-client/src/services/types.ts b/frontend/sync-client/src/services/types.ts index 6a50b1c..4fff201 100644 --- a/frontend/sync-client/src/services/types.ts +++ b/frontend/sync-client/src/services/types.ts @@ -51,7 +51,7 @@ export interface paths { parameters: { query?: never; header: { - "user-agent": string; + "device-id": string; }; path: { vault_id: string; @@ -105,7 +105,7 @@ export interface paths { parameters: { query?: never; header: { - "user-agent": string; + "device-id": string; }; path: { vault_id: string; @@ -191,7 +191,7 @@ export interface paths { parameters: { query?: never; header: { - "user-agent": string; + "device-id": string; }; path: { document_id: string; @@ -232,7 +232,7 @@ export interface paths { parameters: { query?: never; header: { - "user-agent": string; + "device-id": string; }; path: { document_id: string; @@ -285,7 +285,7 @@ export interface paths { parameters: { query?: never; header: { - "user-agent": string; + "device-id": string; }; path: { document_id: string; diff --git a/frontend/sync-client/webpack.config.js b/frontend/sync-client/webpack.config.js index 5efbe8e..d84a5cd 100644 --- a/frontend/sync-client/webpack.config.js +++ b/frontend/sync-client/webpack.config.js @@ -1,5 +1,7 @@ const path = require("path"); const { merge } = require("webpack-merge"); +const webpack = require("webpack"); +const packageJson = require("./package.json"); const common = { entry: "./src/index.ts", @@ -15,6 +17,11 @@ const common = { } ] }, + plugins: [ + new webpack.DefinePlugin({ + __CURRENT_VERSION__: JSON.stringify(packageJson.version) + }) + ], optimization: { // the consuming project should take care of minification minimize: false