Rename user-agent header to device-id

This commit is contained in:
Andras Schmelczer 2025-05-22 22:24:30 +01:00
parent 5448c1cf99
commit 70fe45a09d
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
8 changed files with 94 additions and 21 deletions

View file

@ -1,6 +1,7 @@
mod auth; mod auth;
mod create_document; mod create_document;
mod delete_document; mod delete_document;
mod device_id_header;
mod fetch_document_version; mod fetch_document_version;
mod fetch_document_version_content; mod fetch_document_version_content;
mod fetch_latest_document_version; mod fetch_latest_document_version;
@ -32,6 +33,7 @@ use axum::{
response::IntoResponse, response::IntoResponse,
routing::IntoMakeService, routing::IntoMakeService,
}; };
use device_id_header::DEVICE_ID_HEADER_NAME;
use log::{error, info}; use log::{error, info};
use tokio::signal; use tokio::signal;
use tower_http::{ use tower_http::{
@ -79,7 +81,11 @@ pub async fn create_server(config_path: Option<OsString>) -> Result<()> {
.layer( .layer(
CorsLayer::new() CorsLayer::new()
.allow_origin("*".parse::<HeaderValue>().expect("Failed to parse origin")) .allow_origin("*".parse::<HeaderValue>().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]), .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE]),
) )
.layer( .layer(

View file

@ -4,13 +4,16 @@ use axum::{
Extension, Extension,
extract::{Path, State}, extract::{Path, State},
}; };
use axum_extra::{TypedHeader, headers::UserAgent}; use axum_extra::TypedHeader;
use axum_jsonschema::Json; use axum_jsonschema::Json;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use sync_lib::base64_to_bytes; use sync_lib::base64_to_bytes;
use super::requests::{CreateDocumentVersion, CreateDocumentVersionMultipart}; use super::{
device_id_header::DeviceIdHeader,
requests::{CreateDocumentVersion, CreateDocumentVersionMultipart},
};
use crate::{ use crate::{
app_state::{ app_state::{
AppState, AppState,
@ -38,7 +41,7 @@ pub struct CreateDocumentPathParams {
pub async fn create_document_multipart( pub async fn create_document_multipart(
Path(CreateDocumentPathParams { vault_id }): Path<CreateDocumentPathParams>, Path(CreateDocumentPathParams { vault_id }): Path<CreateDocumentPathParams>,
Extension(user): Extension<User>, Extension(user): Extension<User>,
TypedHeader(user_agent): TypedHeader<UserAgent>, TypedHeader(user_agent): TypedHeader<DeviceIdHeader>,
State(state): State<AppState>, State(state): State<AppState>,
TypedMultipart(axum_typed_multipart::TypedMultipart(request)): TypedMultipart< TypedMultipart(axum_typed_multipart::TypedMultipart(request)): TypedMultipart<
CreateDocumentVersionMultipart, CreateDocumentVersionMultipart,
@ -64,7 +67,7 @@ pub async fn create_document_multipart(
pub async fn create_document_json( pub async fn create_document_json(
Path(CreateDocumentPathParams { vault_id }): Path<CreateDocumentPathParams>, Path(CreateDocumentPathParams { vault_id }): Path<CreateDocumentPathParams>,
Extension(user): Extension<User>, Extension(user): Extension<User>,
TypedHeader(user_agent): TypedHeader<UserAgent>, TypedHeader(user_agent): TypedHeader<DeviceIdHeader>,
State(state): State<AppState>, State(state): State<AppState>,
Json(request): Json<CreateDocumentVersion>, Json(request): Json<CreateDocumentVersion>,
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> { ) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> {
@ -88,7 +91,7 @@ pub async fn create_document_json(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn internal_create_document( async fn internal_create_document(
user: User, user: User,
user_agent: UserAgent, user_agent: DeviceIdHeader,
state: AppState, state: AppState,
vault_id: VaultId, vault_id: VaultId,
document_id: Option<DocumentId>, document_id: Option<DocumentId>,
@ -137,7 +140,7 @@ async fn internal_create_document(
updated_date: chrono::Utc::now(), updated_date: chrono::Utc::now(),
is_deleted: false, is_deleted: false,
user_id: user.name, user_id: user.name,
device_id: user_agent.to_string(), device_id: user_agent.0,
}; };
state state

View file

@ -3,12 +3,12 @@ use axum::{
Extension, Extension,
extract::{Path, State}, extract::{Path, State},
}; };
use axum_extra::{TypedHeader, headers::UserAgent}; use axum_extra::TypedHeader;
use axum_jsonschema::Json; use axum_jsonschema::Json;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use super::requests::DeleteDocumentVersion; use super::{device_id_header::DeviceIdHeader, requests::DeleteDocumentVersion};
use crate::{ use crate::{
app_state::{ app_state::{
AppState, AppState,
@ -38,7 +38,7 @@ pub async fn delete_document(
document_id, document_id,
}): Path<DeleteDocumentPathParams>, }): Path<DeleteDocumentPathParams>,
Extension(user): Extension<User>, Extension(user): Extension<User>,
TypedHeader(user_agent): TypedHeader<UserAgent>, TypedHeader(user_agent): TypedHeader<DeviceIdHeader>,
State(state): State<AppState>, State(state): State<AppState>,
Json(request): Json<DeleteDocumentVersion>, Json(request): Json<DeleteDocumentVersion>,
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> { ) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> {
@ -76,7 +76,7 @@ pub async fn delete_document(
updated_date: chrono::Utc::now(), updated_date: chrono::Utc::now(),
is_deleted: true, is_deleted: true,
user_id: user.name, user_id: user.name,
device_id: user_agent.to_string(), device_id: user_agent.0,
}; };
state state

View file

@ -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<Self, headers::Error>
where
I: Iterator<Item = &'i HeaderValue>,
{
let value = values.next().ok_or_else(headers::Error::invalid)?;
Ok(DeviceIdHeader(
value
.to_str()
.map_err(|_| headers::Error::invalid())?
.to_owned(),
))
}
fn encode<E>(&self, values: &mut E)
where
E: Extend<HeaderValue>,
{
let value = HeaderValue::from_static(Box::leak(self.0.to_string().into_boxed_str()));
values.extend(std::iter::once(value));
}
}

View file

@ -4,7 +4,7 @@ use axum::{
Extension, Extension,
extract::{Path, State}, extract::{Path, State},
}; };
use axum_extra::{TypedHeader, headers::UserAgent}; use axum_extra::TypedHeader;
use axum_jsonschema::Json; use axum_jsonschema::Json;
use log::info; use log::info;
use schemars::JsonSchema; use schemars::JsonSchema;
@ -12,6 +12,7 @@ use serde::Deserialize;
use sync_lib::{base64_to_bytes, is_file_type_mergable, merge}; use sync_lib::{base64_to_bytes, is_file_type_mergable, merge};
use super::{ use super::{
device_id_header::DeviceIdHeader,
requests::{UpdateDocumentVersion, UpdateDocumentVersionMultipart}, requests::{UpdateDocumentVersion, UpdateDocumentVersionMultipart},
responses::DocumentUpdateResponse, responses::DocumentUpdateResponse,
}; };
@ -42,7 +43,7 @@ pub async fn update_document_multipart(
document_id, document_id,
}): Path<UpdateDocumentPathParams>, }): Path<UpdateDocumentPathParams>,
Extension(user): Extension<User>, Extension(user): Extension<User>,
TypedHeader(user_agent): TypedHeader<UserAgent>, TypedHeader(user_agent): TypedHeader<DeviceIdHeader>,
State(state): State<AppState>, State(state): State<AppState>,
TypedMultipart(axum_typed_multipart::TypedMultipart(request)): TypedMultipart< TypedMultipart(axum_typed_multipart::TypedMultipart(request)): TypedMultipart<
UpdateDocumentVersionMultipart, UpdateDocumentVersionMultipart,
@ -69,7 +70,7 @@ pub async fn update_document_json(
document_id, document_id,
}): Path<UpdateDocumentPathParams>, }): Path<UpdateDocumentPathParams>,
Extension(user): Extension<User>, Extension(user): Extension<User>,
TypedHeader(user_agent): TypedHeader<UserAgent>, TypedHeader(user_agent): TypedHeader<DeviceIdHeader>,
State(state): State<AppState>, State(state): State<AppState>,
Json(request): Json<UpdateDocumentVersion>, Json(request): Json<UpdateDocumentVersion>,
) -> Result<Json<DocumentUpdateResponse>, SyncServerError> { ) -> Result<Json<DocumentUpdateResponse>, SyncServerError> {
@ -94,7 +95,7 @@ pub async fn update_document_json(
#[allow(clippy::too_many_arguments, clippy::too_many_lines)] #[allow(clippy::too_many_arguments, clippy::too_many_lines)]
async fn internal_update_document( async fn internal_update_document(
user: User, user: User,
user_agent: UserAgent, user_agent: DeviceIdHeader,
state: AppState, state: AppState,
vault_id: VaultId, vault_id: VaultId,
document_id: DocumentId, document_id: DocumentId,
@ -214,7 +215,7 @@ async fn internal_update_document(
updated_date: chrono::Utc::now(), updated_date: chrono::Utc::now(),
is_deleted: false, is_deleted: false,
user_id: user.name, user_id: user.name,
device_id: user_agent.to_string(), device_id: user_agent.0,
}; };
state state

View file

@ -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( private static formatError(
error: components["schemas"]["SerializedError"] error: components["schemas"]["SerializedError"]
): string { ): string {
@ -82,6 +95,9 @@ export class SyncService {
params: { params: {
path: { path: {
vault_id: vaultName vault_id: vaultName
},
header: {
"device-id": this.deviceIdHeader
} }
}, },
// eslint-disable-next-line // eslint-disable-next-line
@ -135,6 +151,9 @@ export class SyncService {
path: { path: {
vault_id: vaultName, vault_id: vaultName,
document_id: documentId document_id: documentId
},
header: {
"device-id": this.deviceIdHeader
} }
}, },
// eslint-disable-next-line // eslint-disable-next-line
@ -175,8 +194,12 @@ export class SyncService {
path: { path: {
vault_id: vaultName, vault_id: vaultName,
document_id: documentId document_id: documentId
},
header: {
"device-id": this.deviceIdHeader
} }
}, },
body: { body: {
relativePath, relativePath,
deviceId: this.deviceId deviceId: this.deviceId

View file

@ -51,7 +51,7 @@ export interface paths {
parameters: { parameters: {
query?: never; query?: never;
header: { header: {
"user-agent": string; "device-id": string;
}; };
path: { path: {
vault_id: string; vault_id: string;
@ -105,7 +105,7 @@ export interface paths {
parameters: { parameters: {
query?: never; query?: never;
header: { header: {
"user-agent": string; "device-id": string;
}; };
path: { path: {
vault_id: string; vault_id: string;
@ -191,7 +191,7 @@ export interface paths {
parameters: { parameters: {
query?: never; query?: never;
header: { header: {
"user-agent": string; "device-id": string;
}; };
path: { path: {
document_id: string; document_id: string;
@ -232,7 +232,7 @@ export interface paths {
parameters: { parameters: {
query?: never; query?: never;
header: { header: {
"user-agent": string; "device-id": string;
}; };
path: { path: {
document_id: string; document_id: string;
@ -285,7 +285,7 @@ export interface paths {
parameters: { parameters: {
query?: never; query?: never;
header: { header: {
"user-agent": string; "device-id": string;
}; };
path: { path: {
document_id: string; document_id: string;

View file

@ -1,5 +1,7 @@
const path = require("path"); const path = require("path");
const { merge } = require("webpack-merge"); const { merge } = require("webpack-merge");
const webpack = require("webpack");
const packageJson = require("./package.json");
const common = { const common = {
entry: "./src/index.ts", entry: "./src/index.ts",
@ -15,6 +17,11 @@ const common = {
} }
] ]
}, },
plugins: [
new webpack.DefinePlugin({
__CURRENT_VERSION__: JSON.stringify(packageJson.version)
})
],
optimization: { optimization: {
// the consuming project should take care of minification // the consuming project should take care of minification
minimize: false minimize: false