Commit graph

125 commits

Author SHA1 Message Date
Adam Brown
810ae23c0d Encrypt auth tokens at rest per platform (F-4)
Replace the plaintext FileAuthTokenStore binding with platform-specific
encrypted stores behind the same AuthTokenStore interface, wired via a new
expect/actual authTokenStoreModule.

Android: EncryptedSharedPrefsAuthTokenStore backed by EncryptedSharedPreferences
with a Keystore-backed AES256_GCM master key (androidx.security:security-crypto).

Desktop: EncryptedFileAuthTokenStore writes the token-map JSON as AES/GCM/NoPadding
to the config directory. The key is derived (PBKDF2WithHmacSHA256) from the OS user
name and home dir plus a static salt, with no key file on disk, so a copied token
file is useless on another machine or user. A random 12-byte IV is prepended per
write and owner-only POSIX perms are applied best-effort. Decryption failure is
treated as no tokens rather than crashing. This guards against casual disk
scraping and off-machine copies, not same-user local malware that can re-derive
the key.

iOS: still uses the plaintext file store pending a Keychain-backed implementation
(TODO marker in the iOS binding).

Migration: a legacy plaintext auth_tokens.json from an intermediate build is
imported into the encrypted store and deleted on first access; existing encrypted
tokens win on key collision so a stale plaintext entry cannot clobber a fresh
session.
2026-06-24 16:07:40 -07:00
Adam Brown
c5ee60feee Encode dynamic project-name path segments to stop URL segment injection
Every project-scoped client API built its request path by raw string
interpolation of projectName, e.g. "/api/project/$userId/$projectName/begin_sync".
That string reached the shared url() builder whose only path handling was
pathSegments = path.split("/"). Because the split ran on the already-interpolated
string, a projectName containing "/" was split into extra discrete path segments
and a ".." survived as a literal traversal dot-segment, so the outbound request
could target a different endpoint than the {userId}/{projectName}/{action}
template intended (e.g. a malicious sync server returning a project named
"p/../../../api/account/test_auth").

The shared ProjectNameValidator permits "/", "\" and "." (they are encoded to
disk-safe lookalikes only when used as a directory name), so a server-supplied
project name persists verbatim and then injects into every subsequent
project-scoped request under the same host with the bearer token attached.

Fix: each dynamic value is now percent-encoded into a single opaque path
segment via String.encodeUrlPathSegment() before interpolation, and the sink
sets encodedPath directly. Embedded "/" becomes %2F so it cannot create extra
segments, and an all-dots segment is encoded to %2E so a ".." name cannot act as
a traversal segment. The validator is intentionally left unchanged: tightening
it to reject "/" or "." would break syncing for already-valid existing project
names, so encoding is the backward-compatible fix and the on-disk
encodeForFilename behavior is untouched.

Adds a MockEngine test asserting a malicious projectName collapses to a single
encoded segment in the outbound URL across ProjectDataApi, ServerProjectApi and
WritingActivityApi.
2026-06-24 14:45:27 -07:00
Adam Brown
3318c1f284
Make SceneTree state Compose-stable with immutable collections (#621)
Mark the scene-tree state types @Immutable/@Stable and move them onto
kotlinx.collections.immutable so Compose can skip recomposition when the
tree is unchanged: TreeValue.children becomes ImmutableList, SceneSummary
.hasDirtyBuffer a PersistentSet (sourced as such from SceneContentRepository),
and SceneList.State.archivedScenes an ImmutableList. Also cache ImmutableTree
.nodeIndex/hashCode lazily and gate compose-compiler stability reports behind
the composeCompilerReports property.

* Harden onSceneBufferUpdate to reduce from oldState

Read the scene summary from the getAndUpdate lambda's oldState argument
instead of a snapshot captured before the CAS, so the reducer stays a pure
function of its input and composes correctly if buffer updates ever run off
the main dispatcher.
2026-06-19 23:29:41 -07:00
Adam Brown
4b912129b4
Replace fluidsonic (#597) 2026-06-13 02:38:21 -07:00
Adam Brown
347baf82f0
Reworked PDF export (#585)
Better formatting now!
2026-06-11 00:44:47 -07:00
Adam Brown
0791954de0
Add docx export format 2026-06-11 00:18:00 -07:00
Adam Brown
27659be26c
Export to PDF support 2026-06-07 23:07:20 -07:00
Adam Brown
a2998adcba
Re-enable public-storage projects on F-Droid builds (#568)
Restores the "store projects in public storage" feature, gated to F-Droid builds (the required MANAGE_EXTERNAL_STORAGE permission is disallowed on Google Play).

- Expose the build channel at runtime via BuildConfig.FDROID in the common module.
- Declare the storage permissions only in src/fdroid/AndroidManifest.xml, swapped in for F-Droid builds.
- Restore the storage-location toggle + file-access UI, gated on BuildConfig.FDROID; reconcile the toggle with the real location on open.
- Build the GitHub release APK as the F-Droid flavor.
- Extract the directory move into a tested FileSystem.moveDirectory() helper (fixes the same-path data-loss crash; runs off the UI thread).
- Read the fdroid flag consistently across settings.gradle.kts and module scripts.
- Document the F-Droid build flag in DEVELOPMENT.md.
2026-06-07 19:16:42 -07:00
Adam Brown
08c8b1791f
Replace vendored kzip with no.synth:kmp-zip dependency (#509)
Delete the in-tree de.jonasbroeckmann.kzip ZIP format implementation
(~700 lines across commonMain/jvmMain/iosMain) and the kompress.core
dependency, in favor of the published no.synth:kmp-zip 0.11.3 library
plus its okio adapter.

ZipUtils now wraps zipTo/unzipFrom for directory operations and uses
the synchronous ZipInputStream(ByteArray) constructor for the in-memory
extract path, removing the previous write-temp-file workaround. The
skipHiddenFiles parameter is dropped (production callers already passed
false) and the corresponding tests are removed.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-27 00:02:25 -07:00
Adam Brown
88ffe142d5
Add epub export format
Also refactor the export system, much cleaner now
2026-05-24 10:22:05 -07:00
Wavesonics
a3b077824a Add iOS support via Compose Multiplatform
iOS now runs the same shared Compose UI as Android and Desktop. The
existing Decompose component graph (IosRoot, ProjectSelection,
ProjectRoot) is reused; the Swift app shrinks to an AppDelegate, a
Koin bootstrap, and a UIViewControllerRepresentable that hands off to
a Kotlin ComposeUIViewController. The SwiftUI starter under
/ios/ios/ui and /ios/ios/DecomposeHelpers is deleted.

Hammer.framework moves from :common to :composeUi so the framework
ships the Compose entry point; Xcode's Run Script invokes
:composeUi:embedAndSignAppleFrameworkForXcode. iosArm64 and
iosSimulatorArm64 targets are declared with libbacktrace source-info
so K/N crashes report file:line. 17 expects across composeUi get iOS
actuals (file pickers via filekit, image loading via Coil3,
LanguageUtil via NSLocale.preferredLanguages, etc).

The adaptive nav scaffolds — bottom bar at compact width, side nav
rail otherwise — are extracted from the Android activities into
shared ProjectSelectScaffold and ProjectRootScaffold in
composeUi/commonMain, along with their Modifier helpers and the
close-confirm dialogs. Android activities are now thin shells that
delegate to these; iOS calls the same scaffolds, so phone/tablet
layout is unified across all three platforms.

App icon and CFBundleDisplayName set so the home screen reads
"Hammer" with the brand logo instead of the blueprint placeholder.
2026-05-20 00:36:29 -07:00
Wavesonics
7664c1fe8d Upgrade to Gradle and AGP 9 2026-05-18 18:48:39 -07:00
Adam Brown
244cc78e3c
Drop iosX64 target (removed from Compose Multiplatform 1.11) 2026-05-13 21:58:48 -07:00
Adam Brown
a5b57ca23d Implement scene archiving!
Also dramatically improve test fidelity around client sync code
2026-01-11 21:41:06 -08:00
Adam Brown
cab419f618 Zip file handling rewritten 2025-12-29 01:03:30 -08:00
Adam Brown
56dad1bf64 Migrate to Android KMP plugin 2025-12-21 11:51:40 -08:00
Adam Brown
18f59ff0eb Converted from Moko Resources to Compose Resources 2025-12-10 01:24:49 -08:00
Adam Brown
74aaa7bdf1 Swapped SymSpell out for PlatformSpellChecker 2025-12-07 23:03:10 -08:00
Adam Brown
02c857a4ba Fix Moko 2025-12-03 00:28:57 -08:00
Adam Brown
47e076ca28 android.defaults.buildfeatures.buildconfig is deprecated 2025-05-29 01:17:51 -07:00
Adam Brown
9be4c21155
Text Editor 2.0 (#411)
Spell Checking!
2025-03-26 18:13:39 -07:00
Adam Brown
754954b662 Fix TimelineRepository tests 2024-10-19 09:33:31 -07:00
Adam Brown
9dc2a43175
Globalsettings datasources (#382)
* Split file I/O out into datasources
* Added datasource tests
* Added repository tests
2024-10-16 20:47:37 -08:00
Adam Brown
c58670b554 Attempted fix 2024-10-07 19:34:30 -07:00
Adam Brown
25cbee8389 Possible release fix 2024-10-07 18:33:17 -07:00
Adam Brown
2fa3029fc7 Use Kotlin STD Uuid 2024-10-06 15:37:20 -07:00
Adam Brown
df108cf659 Adding rename Project API endpoint
Adding tests for it, and I upgraded to JUnit 5 at the same time
2024-10-02 21:23:43 -07:00
Adam Brown
4cf9736b73 Android clients can now use external data storage! 2024-09-04 00:09:15 -07:00
Adam Brown
8991f4a9bd Working on E2E tests
- Added test database
2024-07-18 23:25:16 -07:00
Adam Brown
4a433ec1e8 ProjectMetadataDatasource tests
- Added PowerAssert
2024-06-27 23:55:26 -07:00
Adam Brown
2feba4da35 Merge branch 'develop' of https://github.com/Wavesonics/hammer-editor into develop
# Conflicts:
#	common/build.gradle.kts
#	gradle/libs.versions.toml
2024-06-14 18:17:07 -07:00
Adam Brown
9cec7d7dd3 Remove parcelize darwin 2024-06-14 18:16:03 -07:00
Adam Brown
82f0cbbac6 Gradle DSL update 2024-06-10 17:57:24 -06:00
renovate[bot]
00a58ceaff
Update moko.resources to v0.24.0 (#295)
* Update moko.resources to v0.24.0

* Fixed breaking changes for Moko

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Adam Brown <adamwbrown@gmail.com>
2024-06-10 15:29:53 -08:00
renovate[bot]
3c35888415
Update jetbrains.kover to v0.8.0 (#279)
* Update jetbrains.kover to v0.8.0

* Updated Kover DSL

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Adam Brown <adamwbrown@gmail.com>
2024-05-26 20:15:32 -08:00
Adam Brown
07fc5c2a71
Decompose 3.0 upgrade (#263)
* Remove parcelize
2024-05-25 22:38:30 -08:00
Adam Brown
4552279d75
Ios example project (#225)
* Implemented iOS example project installer zip file handling
2024-01-20 20:04:57 -08:00
Wavesonics
dcf8c64655 iOS gradle tweaks 2024-01-14 23:44:51 -08:00
Adam Brown
e3860bb124 Wire up Android keep screen on 2024-01-13 22:13:21 -08:00
Wavesonics
66cf647091 Upgrade Koin 2023-09-14 22:39:57 -07:00
Wavesonics
34271c94a2 Fix Kover task? 2023-09-08 00:52:52 -07:00
Wavesonics
391c3fa818 KMP update 2023-09-08 00:37:44 -07:00
Wavesonics
5d974b8f15 Fix Kover 2023-09-07 23:03:04 -07:00
Wavesonics
432f531d8b Switched TOML libraries, hopefully it fixes #40 2023-09-02 00:41:24 -07:00
Wavesonics
4ef8cd1f19 Possible iOS build fix? 2023-08-30 22:14:53 -07:00
Wavesonics
b61c200082 Fixed Draft Compare
Changes to the "merged content" text are now actually taken

- Upgraded Compose & Decompose & other libs

Kotlin 1.9.0!
Compose 1.5.0!
2023-08-28 22:04:55 -07:00
Adam Brown
2a4e467309
Aboutlib (#45)
* Library UI now working
2023-08-21 20:55:05 -08:00
Adam Brown
a9db0f9dd2 Working on server localization 2023-08-16 22:28:30 -07:00
Wavesonics
9dee328875 moko test 2023-08-14 23:05:16 -07:00
Wavesonics
39e1c857cc Drive JVM version from libs.versions.toml 2023-08-07 11:54:25 -07:00