Feat/mobile application - #724
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds Capacitor-based mobile app support with backend mobile auth and session CSRF, native download and OTA flows, mobile build/publish tooling, and generated Android/iOS project scaffolding. ChangesMobile Authentication, OTA, and Native App Logic
Generated Android and iOS Native Project Scaffolding
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant MobileApp
participant Backend
participant OIDCProvider
participant MobileAuthExchange
MobileApp->>Backend: OIDC request with mobile_scheme and code_challenge
Backend->>OIDCProvider: redirect to authorization endpoint
OIDCProvider-->>Backend: callback after login
Backend->>Backend: cache one-time token
Backend-->>MobileApp: scheme://auth?token=...
MobileApp->>MobileAuthExchange: POST token + code_verifier
MobileAuthExchange->>MobileAuthExchange: verify PKCE and create session
MobileAuthExchange-->>MobileApp: csrf_token + session cookie
sequenceDiagram
participant Bootstrap
participant ConfigAPI
participant ManifestS3
participant CapacitorUpdater
Bootstrap->>CapacitorUpdater: notifyAppReady()
Bootstrap->>ConfigAPI: resolve MOBILE_OTA_MANIFEST_URL
ConfigAPI-->>Bootstrap: manifest URL
Bootstrap->>ManifestS3: fetch manifest
ManifestS3-->>Bootstrap: version, checksum, sessionKey
Bootstrap->>CapacitorUpdater: download and activate update
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
f0fa21d to
15209c0
Compare
️✅ There are no secrets present in this pull request anymore.If these secrets were true positive and are still valid, we highly recommend you to revoke them. 🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request. |
15209c0 to
a3fd7db
Compare
That's a secret for development purpose only. |
a3fd7db to
c74b571
Compare
There was a problem hiding this comment.
Actionable comments posted: 26
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/frontend/Dockerfile (1)
12-21: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winMove
/etc/passwdwrite access after dependency installation.
npm installcan run package lifecycle scripts, and this change makes/etc/passwdgroup-writable before those scripts execute. Keep the runtime fix, but apply it after dependencies are installed.Suggested adjustment
-# Let a process running with an arbitrary uid (the host uid on bind mounts) and -# group root register itself in /etc/passwd at runtime. The Capacitor CLI calls -# os.userInfo(), which throws ENOENT under musl when the uid has no passwd entry -# (see the frontend-mobile entrypoint in compose.yaml). Harmless otherwise. -RUN chmod g=u /etc/passwd - # Install deps and run all npm commands as the user running the container USER ${DOCKER_USER} COPY --chown=${DOCKER_USER} package*.json ./ RUN npm install + +USER root +# Let a process running with an arbitrary uid and group root register itself in +# /etc/passwd at runtime for Capacitor CLI os.userInfo() under musl. +RUN chmod g=u /etc/passwd +USER ${DOCKER_USER}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/frontend/Dockerfile` around lines 12 - 21, Move the /etc/passwd permission change out of the pre-install step in src/frontend/Dockerfile: keep the runtime compatibility fix for arbitrary UIDs, but apply chmod g=u /etc/passwd only after npm install has finished. Update the build sequence around the USER ${DOCKER_USER} and npm install steps so package lifecycle scripts run before /etc/passwd becomes group-writable.src/frontend/android/gradlew.bat (1)
1-95: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winConvert this batch script to CRLF.
The file is committed with LF-only line endings, which can break
cmd.exelabel parsing and prevent the Gradle wrapper from running on Windows.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/frontend/android/gradlew.bat` around lines 1 - 95, The Gradle wrapper batch script is using LF-only line endings, which can break Windows cmd parsing. Convert the entire gradlew.bat script to CRLF line endings while preserving its content, so labels and commands like findJavaFromJavaHome, execute, and fail remain runnable in cmd.exe.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/mobile-poc.md`:
- Around line 171-172: The setup instructions mix repo-root frontend paths with
shorter paths, so update the references in the mobile PoC doc to consistently
include the `src/frontend/` prefix. Check the auth and OTA steps that mention
`AUTH_CALLBACK_SCHEME` and related files like `src/features/native/auth.ts` and
`src/features/native/ota.ts`, and rewrite them so they resolve correctly from a
root checkout.
- Around line 295-300: The OTA security section is inaccurate because it says
the POC ships unsigned bundles, but the publish flow in publish-ota.mjs already
signs and encrypts OTA artifacts. Update the docs/mobile-poc.md wording to
reflect the actual release pipeline, and explicitly distinguish the current
publish-time signing/encryption behavior from any separate runtime verification
or minimum-version checks handled by the frontend OTA flow.
In `@docs/mobile.md`:
- Around line 203-212: The path references in the OTA documentation are
inconsistent with the repo layout, so update the symbols mentioned in this
paragraph to use the repo-root-prefixed paths. Specifically, change the
references to notifyOtaAppReady() and checkAndApplyOtaUpdate() in
src/features/native/ota.ts, and the startup call from main.tsx, so they are
prefixed with src/frontend/src/ to match the rest of the doc and the actual
project structure.
In `@env.d/development/frontend.defaults`:
- Around line 66-71: The commented dev defaults still contain a real OTA signing
private key in the frontend defaults block, which should not live in git
history. Remove the private key value from the
MOBILE_OTA_SIGNING_PRIVATE_KEY_B64 entry, replace it with a safe placeholder,
and keep the existing MOBILE_OTA_SIGNING_PUBLIC_KEY_B64 guidance plus the make
mobile-ota-keygen flow for local dev use. Use the
MOBILE_OTA_SIGNING_PRIVATE_KEY_B64 and MOBILE_OTA_SIGNING_PUBLIC_KEY_B64 entries
to locate and update the block.
In `@Makefile`:
- Around line 614-617: The MOBILE_OTA_BUILD_ID derivation in the Makefile relies
on git rev-list --count HEAD, which is incorrect with a shallow checkout. Update
the CI workflow to fetch full history before this variable is computed, or
ensure MOBILE_OTA_BUILD_ID is overridden in CI, so the monotonic build count
remains accurate for version ordering. Keep the existing MOBILE_OTA_BUILD_ID
definition intact for local/non-CI use.
In `@src/backend/core/api/viewsets/mobile_auth.py`:
- Around line 61-68: The token consumption in mobile auth is not atomic because
`cache.get()` and `cache.delete()` in the `mobile_auth.py` exchange flow leave a
race window for duplicate success. Update the token handling in the exchange
path around the `cache_key` / `payload` logic to use a single atomic consume
step, such as a Redis GETDEL-style operation or a per-token lock, so only one
verifier can ever read and consume the payload.
In `@src/backend/core/authentication/views.py`:
- Around line 97-105: The mobile handoff token stored in the auth flow uses the
default cache via cache.set in the authentication view, but the default backend
may be LocMemCache and won’t be shared across workers. Update the mobile login
path in the authentication views to either enforce a shared cache backend (for
example by validating settings before issuing the token) or fail fast with a
clear error when CACHES["default"] is local. Reference the token write/read flow
around the mobile auth exchange logic and the MOBILE_AUTH_TOKEN_CACHE_PREFIX
usage so the guard is placed where the token is created.
In `@src/backend/core/management/commands/create_bucket.py`:
- Around line 29-33: The --public option in create_bucket is too broad because
it can expose any selected storage, not just OTA data. Update the create_bucket
command to enforce an OTA-only guard in the argument handling and execution path
of create_bucket() so anonymous read access can only be granted for the OTA
bucket/prefix, or require an explicit confirmation flag before applying public
access to non-OTA storage. Use the existing --storage and --public options in
create_bucket to gate the public ACL logic and prevent accidental exposure of
private buckets.
In `@src/frontend/android/app/build.gradle`:
- Around line 19-24: The asset packaging configuration is placed in the wrong
Gradle block; move the ignore pattern setup from the current
defaultConfig-related area into an androidResources block under android in the
same build script. Update the existing aaptOptions-based configuration to the
AGP 8.13-compatible androidResources configuration while preserving the
ignoreAssetsPattern value and keeping the change localized to the Android app
module’s build.gradle.
In
`@src/frontend/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java`:
- Line 24: The package-name assertion in ExampleInstrumentedTest is hardcoded to
the Capacitor template value, but appContext.getPackageName() returns the app’s
actual applicationId from Gradle. Update the test to compare against the
build-derived applicationId instead of a fixed string, using the Android test
context in ExampleInstrumentedTest so the assertion stays aligned with the build
configuration.
In `@src/frontend/android/app/src/main/res/values/styles.xml`:
- Around line 12-21: The theme attributes in AppTheme.NoActionBar and
AppTheme.NoActionBarLaunch are using background values that are ignored for
these themes, so update the styles in styles.xml to use the proper window/splash
attrs instead. In AppTheme.NoActionBar, switch the window background to
android:windowBackground, and in AppTheme.NoActionBarLaunch, use
windowSplashScreenBackground along with postSplashScreenTheme so the splash
screen hands off correctly to the main theme. Locate the changes in the
AppTheme.NoActionBar and AppTheme.NoActionBarLaunch style definitions.
In `@src/frontend/android/app/src/main/res/xml/file_paths.xml`:
- Around line 3-4: The FileProvider configuration currently exposes an
unnecessary broad external storage mapping. Update the file_paths.xml resource
used by the FileProvider to remove the external-path entry and keep only the
cache-path mapping, since the app only shares cache files; make sure the
remaining cache path stays intact and is still referenced by the same
FileProvider setup.
In `@src/frontend/capacitor.config.ts`:
- Around line 39-43: Gate the unconditional cleartext setting in the Capacitor
server config so it is only enabled for dev/live-reload builds. In
capacitor.config.ts, update the server object around devServerUrl so cleartext
is included only when the same dev flag is true, matching how server.url is
already conditionally applied. This keeps android:usesCleartextTraffic out of
production builds while preserving local dev access for the WebView.
In `@src/frontend/ios/App/App/Info.plist`:
- Around line 26-31: The ATS exception in App/Info.plist is currently applied in
both Debug and Release, so scope the local networking allowance to development
only. Update the Info.plist handling around the NSAllowsLocalNetworking entry so
it is conditionally included via DEBUG-only preprocessing (for example using
INFOPLIST_PREPROCESS with GCC_PREPROCESSOR_DEFINITIONS) or move Release to a
separate plist, and verify the App/Info.plist and project.pbxproj configuration
paths are aligned.
In `@src/frontend/ios/App/App/WebAuthSessionPlugin.swift`:
- Around line 26-58: The WebAuthSessionPlugin.start method allows overlapping
ASWebAuthenticationSession launches, and the shared self.session can be
overwritten so an earlier completion clears a newer in-flight session. Add a
guard in start to reject or no-op when self.session is already non-nil, and only
clear self.session in the completion handler if it still refers to the same
session instance. Use the existing start, self.session, and
ASWebAuthenticationSession completion block to implement the protection.
In `@src/frontend/public/manifest.json`:
- Around line 8-52: The manifest icon entries use .webp files but still declare
image/png, so update the icon metadata in manifest.json to match the actual WebP
format. Fix every asset reference under the icons array (the
assets/icons/icon-*.webp entries) by changing the type field consistently, so
browser icon selection and install prompts can recognize them correctly.
In `@src/frontend/scripts/create-ota-bucket.mjs`:
- Around line 14-39: The public-read bucket policy in create-ota-bucket.mjs is
too broad because it grants s3:GetObject on the entire bucket even when
otaConfig() provides a prefix. Update the PutBucketPolicyCommand policy
construction to scope the Resource to the configured prefix when present, and
keep the existing bucket-wide behavior only when no prefix is set. Use
otaConfig(), bucket, and the policy JSON in create-ota-bucket.mjs to locate the
change.
In `@src/frontend/scripts/publish-ota.mjs`:
- Around line 108-121: The publish flow in the OTA script currently allows a
same-version publish to slip through, which can overwrite an already-published
manifest/zip for the same version. Update the logic around readManifest,
versionCount, and the channel publish check so that any existing manifest with
the same version is rejected by default, not only lower/equal ordering cases.
Make the same-version publish path fail unless values.force is set, and apply
the same safeguard in the other publish path referenced by the existing
manifest/version handling.
- Around line 87-123: The private signing key is written before the protected
cleanup path, so failures in otaConfig(), readManifest(), or the downgrade guard
can leave the PEM behind. Move the key-file creation into the existing
try/finally around the publish flow in publish-ota.mjs, and ensure the cleanup
in the finally removes the temp key no matter where the error occurs. Prefer
generating the key inside a unique temp directory or otherwise using a per-run
temp path so the cleanup logic can reliably target it.
In `@src/frontend/src/features/auth/index.tsx`:
- Around line 17-19: The native logout path in the auth flow is ignoring
possible failures by discarding the promise from nativeLogout(). Update the
logout handling in the auth entry point so it mirrors the native login pattern:
call nativeLogout() with a local catch or equivalent rejection handling, log or
absorb the error, and still continue the reload/logout flow from the same
branch.
In `@src/frontend/src/features/native/auth-session.ts`:
- Around line 47-64: The openAuthSession flow currently ignores failures from
Browser.open(), which can leave the promise pending and the
App.addListener/browserFinished listeners registered. Update the openAuthSession
logic to handle the Browser.open() rejection path by rejecting the outer promise
and calling cleanup() when Browser.open({ url }) fails, keeping the existing
listener setup and cancellation behavior intact.
In `@src/frontend/src/features/native/auth.ts`:
- Around line 70-80: The nativeLogout flow in auth.ts only clears
CapacitorCookies and the local CSRF token, so the Django session remains valid;
add a mobile-safe logout endpoint on the backend that invalidates just the
server-side Django session without triggering RP-initiated IdP logout, then call
that endpoint from nativeLogout before clearing local state. Use nativeLogout
and setNativeCsrfToken as the key entry points when updating the logout
sequence.
In `@src/frontend/src/features/native/ota.ts`:
- Around line 80-124: Add a minimal runtime shape check in ota.ts right after
the CapacitorHttp.get fetch and before using manifest in
CapacitorUpdater.current(), versionCount(), and CapacitorUpdater.download(). The
issue is that response.data is cast to OtaManifest without validation, so
malformed or partial manifests can fail later with vague errors; fix it by
verifying the required fields (at least url, version, and checksum, plus any
other mandatory OtaManifest fields) and throwing/logging a clear error
immediately when the manifest is invalid.
- Around line 104-117: The boot-loop guard in ota.ts is too broad because it
uses CapacitorUpdater.list() with status === "error", which also catches
download/install failures. Update the check around manifest.version to look for
a boot-specific failure marker instead of any generic error status, and keep the
warning/early return in the same OTA version guard logic so only versions that
truly failed to boot are blocked.
In `@src/frontend/src/styles/globals.scss`:
- Around line 213-218: Replace the hardcoded 768px media query in globals.scss
with the existing breakpoint(tablet) mixin used elsewhere in this file. Update
the responsive rule for .c__modal:not(.c__modal--full) .c__modal__close so it
follows the shared breakpoint definition and stays consistent if the tablet
breakpoint changes.
---
Outside diff comments:
In `@src/frontend/android/gradlew.bat`:
- Around line 1-95: The Gradle wrapper batch script is using LF-only line
endings, which can break Windows cmd parsing. Convert the entire gradlew.bat
script to CRLF line endings while preserving its content, so labels and commands
like findJavaFromJavaHome, execute, and fail remain runnable in cmd.exe.
In `@src/frontend/Dockerfile`:
- Around line 12-21: Move the /etc/passwd permission change out of the
pre-install step in src/frontend/Dockerfile: keep the runtime compatibility fix
for arbitrary UIDs, but apply chmod g=u /etc/passwd only after npm install has
finished. Update the build sequence around the USER ${DOCKER_USER} and npm
install steps so package lifecycle scripts run before /etc/passwd becomes
group-writable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 241865a3-f78d-4517-a671-c85a1a85f9c7
⛔ Files ignored due to path filters (118)
src/frontend/android/app/src/main/res/drawable-land-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-night/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jarsrc/frontend/assets/icon-dark.pngis excluded by!**/*.pngsrc/frontend/assets/icon-foreground.pngis excluded by!**/*.pngsrc/frontend/assets/icon-only.pngis excluded by!**/*.pngsrc/frontend/assets/splash-dark.pngis excluded by!**/*.pngsrc/frontend/assets/splash.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.pngis excluded by!**/*.pngsrc/frontend/package-lock.jsonis excluded by!**/package-lock.jsonsrc/frontend/public/images/anct/app-icon-2x.pngis excluded by!**/*.pngsrc/frontend/public/images/anct/app-icon.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-144-144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-192-192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-48-48.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-512-512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-72-72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-96-96.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-144x144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-192x192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-48x48.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-512x512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-72x72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-96x96.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/100.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/1024.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/114.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/120.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/128.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/152.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/16.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/167.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/180.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/20.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/256.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/29.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/32.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/40.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/50.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/57.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/58.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/60.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/64.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/76.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/80.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/87.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-120.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-128.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-152.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-180.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-384.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-96.pngis excluded by!**/*.pngsrc/frontend/src/features/api/gen/models/user_with_abilities.tsis excluded by!**/gen/**
📒 Files selected for processing (107)
Makefilecompose.yamldocs/env.mddocs/mobile-poc.mddocs/mobile.mdenv.d/development/backend.defaultsenv.d/development/frontend.defaultssrc/backend/core/api/openapi.jsonsrc/backend/core/api/serializers.pysrc/backend/core/api/viewsets/mobile_auth.pysrc/backend/core/authentication/views.pysrc/backend/core/management/commands/create_bucket.pysrc/backend/core/tests/api/test_attachments.pysrc/backend/core/tests/api/test_users.pysrc/backend/core/tests/authentication/test_mobile_auth.pysrc/backend/core/urls.pysrc/backend/messages/settings.pysrc/frontend/Dockerfilesrc/frontend/android/.gitignoresrc/frontend/android/app/.gitignoresrc/frontend/android/app/build.gradlesrc/frontend/android/app/capacitor.build.gradlesrc/frontend/android/app/proguard-rules.prosrc/frontend/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.javasrc/frontend/android/app/src/main/AndroidManifest.xmlsrc/frontend/android/app/src/main/java/local/suitenumerique/messages/MainActivity.javasrc/frontend/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xmlsrc/frontend/android/app/src/main/res/drawable/ic_launcher_background.xmlsrc/frontend/android/app/src/main/res/layout/activity_main.xmlsrc/frontend/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xmlsrc/frontend/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xmlsrc/frontend/android/app/src/main/res/values/ic_launcher_background.xmlsrc/frontend/android/app/src/main/res/values/strings.xmlsrc/frontend/android/app/src/main/res/values/styles.xmlsrc/frontend/android/app/src/main/res/xml/file_paths.xmlsrc/frontend/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.javasrc/frontend/android/build.gradlesrc/frontend/android/capacitor.settings.gradlesrc/frontend/android/gradle.propertiessrc/frontend/android/gradle/wrapper/gradle-wrapper.propertiessrc/frontend/android/gradlewsrc/frontend/android/gradlew.batsrc/frontend/android/settings.gradlesrc/frontend/android/variables.gradlesrc/frontend/capacitor.config.tssrc/frontend/eslint.config.mjssrc/frontend/ios/.gitignoresrc/frontend/ios/App/App.xcodeproj/project.pbxprojsrc/frontend/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plistsrc/frontend/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolvedsrc/frontend/ios/App/App.xcodeproj/xcshareddata/xcschemes/App.xcschemesrc/frontend/ios/App/App/AppDelegate.swiftsrc/frontend/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.jsonsrc/frontend/ios/App/App/Assets.xcassets/Contents.jsonsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Contents.jsonsrc/frontend/ios/App/App/Base.lproj/LaunchScreen.storyboardsrc/frontend/ios/App/App/Base.lproj/Main.storyboardsrc/frontend/ios/App/App/Info.plistsrc/frontend/ios/App/App/MainViewController.swiftsrc/frontend/ios/App/App/WebAuthSessionPlugin.swiftsrc/frontend/ios/App/CapApp-SPM/.gitignoresrc/frontend/ios/App/CapApp-SPM/Package.swiftsrc/frontend/ios/App/CapApp-SPM/README.mdsrc/frontend/ios/App/CapApp-SPM/Sources/CapApp-SPM/CapApp-SPM.swiftsrc/frontend/ios/debug.xcconfigsrc/frontend/package.jsonsrc/frontend/public/assets/icons/icon-128.webpsrc/frontend/public/assets/icons/icon-192.webpsrc/frontend/public/assets/icons/icon-256.webpsrc/frontend/public/assets/icons/icon-48.webpsrc/frontend/public/assets/icons/icon-512.webpsrc/frontend/public/assets/icons/icon-72.webpsrc/frontend/public/assets/icons/icon-96.webpsrc/frontend/public/browserconfig.xmlsrc/frontend/public/locales/common/en-US.jsonsrc/frontend/public/locales/common/fr-FR.jsonsrc/frontend/public/manifest.jsonsrc/frontend/scripts/create-ota-bucket.mjssrc/frontend/scripts/generate-ota-keys.mjssrc/frontend/scripts/ota-lib.mjssrc/frontend/scripts/publish-ota.mjssrc/frontend/src/features/api/csrf.test.tssrc/frontend/src/features/api/csrf.tssrc/frontend/src/features/api/utils.tssrc/frontend/src/features/auth/index.tsxsrc/frontend/src/features/layouts/components/main/header/_index.scsssrc/frontend/src/features/layouts/components/thread-view/components/attachment-preview-modal/index.tsxsrc/frontend/src/features/layouts/components/thread-view/components/thread-attachment-list/attachment-item.tsxsrc/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-actions.tsxsrc/frontend/src/features/native/auth-session.tssrc/frontend/src/features/native/auth.tssrc/frontend/src/features/native/csrf.test.tssrc/frontend/src/features/native/csrf.tssrc/frontend/src/features/native/download.test.tssrc/frontend/src/features/native/download.tssrc/frontend/src/features/native/ota.test.tssrc/frontend/src/features/native/ota.tssrc/frontend/src/features/native/pkce.test.tssrc/frontend/src/features/native/pkce.tssrc/frontend/src/features/native/platform.tssrc/frontend/src/features/native/sso-invariants.test.tssrc/frontend/src/features/native/use-native-download.tsxsrc/frontend/src/main.tsxsrc/frontend/src/styles/globals.scsssrc/frontend/src/vite-env.d.tssrc/frontend/vite.config.tssrc/keycloak/realm.json
c74b571 to
0b0cbcd
Compare
There was a problem hiding this comment.
Actionable comments posted: 12
♻️ Duplicate comments (1)
env.d/development/frontend.defaults (1)
66-71: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winCommitted RSA private key remains in the repo.
Same private key flagged in a prior review pass and by the PR's GitGuardian scan comment (RSA private key at this commit). Even as a "throwaway dev-only" key, it's now permanently in git history and will keep tripping secret scanners. Recommend replacing the literal value with a placeholder and requiring
make mobile-ota-keygenlocally, and rotating/removing the key from history per the GitGuardian recommendation.🔒 Suggested change
-# MOBILE_OTA_SIGNING_PRIVATE_KEY_B64=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ... +# MOBILE_OTA_SIGNING_PRIVATE_KEY_B64=<base64-encoded-dev-private-key-generated-locally>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@env.d/development/frontend.defaults` around lines 66 - 71, The committed MOBILE_OTA_SIGNING_PRIVATE_KEY_B64 value in the development defaults file is still a real RSA private key and must be removed from source control. Replace the literal key in the frontend defaults entry with a non-secret placeholder and keep the nearby comment pointing developers to run make mobile-ota-keygen locally; also ensure the existing MOBILE_OTA_SIGNING_PUBLIC_KEY_B64 remains the only checked-in value. If this key was already committed elsewhere, follow the GitGuardian guidance to rotate it and remove it from history.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/mobile.md`:
- Line 31: Add a language identifier to the three fenced Markdown code blocks
that currently have no hint, covering the architecture diagram, sequence
diagram, and key-pair output sections, so they satisfy MD040. Update the
affected fenced blocks in the documentation to use the most appropriate language
tag for each block and keep the existing content unchanged.
In `@src/backend/core/api/viewsets/mobile_auth.py`:
- Around line 53-68: The mobile auth token exchange path in the viewset accepts
request.data values without verifying they are strings, so a non-string
code_verifier can reach _s256() and raise instead of returning a controlled
response. In the token-verification flow inside the mobile auth handler, add
explicit type validation for token and code_verifier before cache lookup and
hashing, and return a 400 response when either field is missing or not a string.
Keep the existing single-use cache deletion and compare_digest logic in place
after the validated inputs are normalized.
In `@src/frontend/android/app/src/main/AndroidManifest.xml`:
- Around line 22-33: The deep-link callback in AndroidManifest uses the private
stmessages scheme, which can be intercepted by another app; make sure the mobile
OIDC flow in AUTH_CALLBACK_SCHEME and the App.addListener("appUrlOpen") handling
are fully protected by PKCE so the authorization code cannot be redeemed without
the verifier. If the PKCE verifier is not already guaranteed end-to-end in the
native auth path, tighten that flow before relying on this scheme, and consider
moving the callback to verified Android App Links in a future change for
stronger OS-level routing.
- Around line 3-4: The AndroidManifest application block currently leaves
android:allowBackup enabled, which can back up WebView/session storage; update
the manifest’s application configuration to disable backup or explicitly exclude
the auth storage using android:dataExtractionRules and/or
android:fullBackupContent. Make the change in the <application> declaration so
the mobile auth/session data introduced by this PR is not restored onto another
device.
In
`@src/frontend/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml`:
- Line 4: The adaptive icon in ic_launcher_round.xml is still pointing at the
old mipmap background instead of the new values-defined resource. Update the
inset drawable reference in the round launcher icon to use the actual launcher
background resource, matching the fix applied in ic_launcher.xml, so the round
icon resolves the correct asset.
In `@src/frontend/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml`:
- Line 4: The adaptive launcher icon is pointing to the wrong background
resource, so update the inset drawable reference in ic_launcher.xml to match the
actual launcher background resource defined in
values/ic_launcher_background.xml, or move the asset into a drawable/mipmap
resource if that is the intended target. Use the ic_launcher.xml adaptive icon
definition and the ic_launcher_background resource name to ensure the launcher
resolves the background cleanly.
In `@src/frontend/capacitor.config.ts`:
- Around line 67-74: The OTA config in capacitor.config.ts currently allows
NEXT_PUBLIC_MOBILE_OTA_MANIFEST_URL builds to proceed without a signing key,
which can leave signature verification disabled. Update the config/build setup
around the CapacitorUpdater block so that when the OTA manifest URL is enabled,
the build fails unless MOBILE_OTA_SIGNING_PUBLIC_KEY_B64 is present; use
otaPublicKey and the surrounding config initialization to enforce this
requirement early and prevent unsigned OTA fetching.
In `@src/frontend/Dockerfile`:
- Around line 12-17: The chmod in the Dockerfile’s `/etc/passwd` setup is
broader than the current `frontend-mobile` use case and affects every service
built from the shared frontend base images. Update the surrounding comment or
implementation in this Dockerfile to make it clear that `RUN chmod g=u
/etc/passwd` is a shared side effect for all `frontend-deps`-based containers
running with arbitrary UIDs, or narrow the change if it should apply only to
`frontend-mobile`’s entrypoint path.
In `@src/frontend/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json`:
- Around line 1-14: The AppIcon asset catalog only defines the marketing image,
so the iOS icon set is incomplete. Update Contents.json in AppIcon.appiconset to
include all required device-sized icon entries alongside the existing universal
1024x1024 image, and ensure each slot points to the correct PNG filename. Use
the AppIcon.appiconset asset catalog structure as the reference point and
populate every missing image declaration so packaging and validation can
succeed.
In `@src/frontend/scripts/publish-ota.mjs`:
- Around line 47-68: The publish-ota script currently accepts version from
parseArgs and then uses it in zipName, cleanup paths, and bundleKey without
validating its shape. Update publish-ota.mjs so the version value is checked
immediately after parsing, before any file or S3 key construction, and reject
unsafe values containing path separators or traversal segments; keep the
existing validateChannel flow and make the version validation happen alongside
it using the version-related symbols in the script.
In `@src/frontend/src/features/native/download.ts`:
- Around line 18-35: The `nativeDownloadFile` helper is passing the untrusted
`filename` straight into `Filesystem.writeFile`, which can allow path traversal
or separator injection. Update `nativeDownloadFile` to sanitize or validate the
`filename` before using it as `path` (for example, reduce it to a basename or
reject unsafe values), and keep the existing call sites that pass
`attachment.name` and `file.title` relying on this helper for protection.
In `@src/frontend/src/features/native/ota.test.ts`:
- Around line 40-44: Stub both NEXT_PUBLIC_MOBILE_OTA_MANIFEST_URL and
NEXT_PUBLIC_MOBILE_DEV_SERVER_URL inside loadOta before each fresh import so the
import-time constants in the OTA module always see a controlled environment.
Update the loadOta helper in ota.test.ts to reset modules and set both env vars
for every test case, with the manifest URL only applied when provided, so paths
in the OTA update flow and the “no manifest URL is configured” case are
deterministic.
---
Duplicate comments:
In `@env.d/development/frontend.defaults`:
- Around line 66-71: The committed MOBILE_OTA_SIGNING_PRIVATE_KEY_B64 value in
the development defaults file is still a real RSA private key and must be
removed from source control. Replace the literal key in the frontend defaults
entry with a non-secret placeholder and keep the nearby comment pointing
developers to run make mobile-ota-keygen locally; also ensure the existing
MOBILE_OTA_SIGNING_PUBLIC_KEY_B64 remains the only checked-in value. If this key
was already committed elsewhere, follow the GitGuardian guidance to rotate it
and remove it from history.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5ded2b0a-4062-4f3e-888d-021d42b26661
⛔ Files ignored due to path filters (117)
src/frontend/android/app/src/main/res/drawable-land-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-night/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jarsrc/frontend/assets/icon-dark.pngis excluded by!**/*.pngsrc/frontend/assets/icon-foreground.pngis excluded by!**/*.pngsrc/frontend/assets/icon-only.pngis excluded by!**/*.pngsrc/frontend/assets/splash-dark.pngis excluded by!**/*.pngsrc/frontend/assets/splash.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.pngis excluded by!**/*.pngsrc/frontend/package-lock.jsonis excluded by!**/package-lock.jsonsrc/frontend/public/images/anct/app-icon-2x.pngis excluded by!**/*.pngsrc/frontend/public/images/anct/app-icon.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-144-144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-192-192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-48-48.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-512-512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-72-72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-96-96.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-144x144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-192x192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-48x48.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-512x512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-72x72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-96x96.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/100.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/1024.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/114.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/120.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/128.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/152.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/16.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/167.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/180.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/20.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/256.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/29.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/32.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/40.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/50.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/57.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/58.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/60.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/64.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/76.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/80.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/87.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-120.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-128.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-152.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-180.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-384.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-96.pngis excluded by!**/*.png
📒 Files selected for processing (100)
Makefilecompose.yamldocs/env.mddocs/mobile.mdenv.d/development/backend.defaultsenv.d/development/frontend.defaultssrc/backend/core/api/viewsets/mobile_auth.pysrc/backend/core/authentication/views.pysrc/backend/core/management/commands/create_bucket.pysrc/backend/core/tests/authentication/test_mobile_auth.pysrc/backend/core/urls.pysrc/backend/messages/settings.pysrc/frontend/Dockerfilesrc/frontend/android/.gitignoresrc/frontend/android/app/.gitignoresrc/frontend/android/app/build.gradlesrc/frontend/android/app/capacitor.build.gradlesrc/frontend/android/app/proguard-rules.prosrc/frontend/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.javasrc/frontend/android/app/src/main/AndroidManifest.xmlsrc/frontend/android/app/src/main/java/local/suitenumerique/messages/MainActivity.javasrc/frontend/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xmlsrc/frontend/android/app/src/main/res/drawable/ic_launcher_background.xmlsrc/frontend/android/app/src/main/res/layout/activity_main.xmlsrc/frontend/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xmlsrc/frontend/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xmlsrc/frontend/android/app/src/main/res/values/ic_launcher_background.xmlsrc/frontend/android/app/src/main/res/values/strings.xmlsrc/frontend/android/app/src/main/res/values/styles.xmlsrc/frontend/android/app/src/main/res/xml/file_paths.xmlsrc/frontend/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.javasrc/frontend/android/build.gradlesrc/frontend/android/capacitor.settings.gradlesrc/frontend/android/gradle.propertiessrc/frontend/android/gradle/wrapper/gradle-wrapper.propertiessrc/frontend/android/gradlewsrc/frontend/android/gradlew.batsrc/frontend/android/settings.gradlesrc/frontend/android/variables.gradlesrc/frontend/capacitor.config.tssrc/frontend/eslint.config.mjssrc/frontend/ios/.gitignoresrc/frontend/ios/App/App.xcodeproj/project.pbxprojsrc/frontend/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plistsrc/frontend/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolvedsrc/frontend/ios/App/App.xcodeproj/xcshareddata/xcschemes/App.xcschemesrc/frontend/ios/App/App/AppDelegate.swiftsrc/frontend/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.jsonsrc/frontend/ios/App/App/Assets.xcassets/Contents.jsonsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Contents.jsonsrc/frontend/ios/App/App/Base.lproj/LaunchScreen.storyboardsrc/frontend/ios/App/App/Base.lproj/Main.storyboardsrc/frontend/ios/App/App/Info.plistsrc/frontend/ios/App/App/MainViewController.swiftsrc/frontend/ios/App/App/WebAuthSessionPlugin.swiftsrc/frontend/ios/App/CapApp-SPM/.gitignoresrc/frontend/ios/App/CapApp-SPM/Package.swiftsrc/frontend/ios/App/CapApp-SPM/README.mdsrc/frontend/ios/App/CapApp-SPM/Sources/CapApp-SPM/CapApp-SPM.swiftsrc/frontend/ios/debug.xcconfigsrc/frontend/package.jsonsrc/frontend/public/assets/icons/icon-128.webpsrc/frontend/public/assets/icons/icon-192.webpsrc/frontend/public/assets/icons/icon-256.webpsrc/frontend/public/assets/icons/icon-48.webpsrc/frontend/public/assets/icons/icon-512.webpsrc/frontend/public/assets/icons/icon-72.webpsrc/frontend/public/assets/icons/icon-96.webpsrc/frontend/public/browserconfig.xmlsrc/frontend/public/locales/common/en-US.jsonsrc/frontend/public/locales/common/fr-FR.jsonsrc/frontend/public/manifest.jsonsrc/frontend/scripts/create-ota-bucket.mjssrc/frontend/scripts/generate-ota-keys.mjssrc/frontend/scripts/ota-lib.mjssrc/frontend/scripts/publish-ota.mjssrc/frontend/src/features/api/utils.tssrc/frontend/src/features/auth/index.tsxsrc/frontend/src/features/layouts/components/main/header/_index.scsssrc/frontend/src/features/layouts/components/thread-view/components/attachment-preview-modal/index.tsxsrc/frontend/src/features/layouts/components/thread-view/components/thread-attachment-list/attachment-item.tsxsrc/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-actions.tsxsrc/frontend/src/features/native/auth-session.tssrc/frontend/src/features/native/auth.tssrc/frontend/src/features/native/csrf.test.tssrc/frontend/src/features/native/csrf.tssrc/frontend/src/features/native/download.test.tssrc/frontend/src/features/native/download.tssrc/frontend/src/features/native/ota.test.tssrc/frontend/src/features/native/ota.tssrc/frontend/src/features/native/pkce.test.tssrc/frontend/src/features/native/pkce.tssrc/frontend/src/features/native/platform.tssrc/frontend/src/features/native/sso-invariants.test.tssrc/frontend/src/features/native/use-native-download.tsxsrc/frontend/src/main.tsxsrc/frontend/src/styles/globals.scsssrc/frontend/src/vite-env.d.tssrc/frontend/vite.config.tssrc/keycloak/realm.json
| <!-- | ||
| Deep-link scheme ending the mobile OIDC flow: routes the | ||
| stmessages://auth?token=… redirect from the Custom Tab back to | ||
| the app (caught by App.addListener("appUrlOpen")). | ||
| Source of truth: src/features/native/auth.ts (AUTH_CALLBACK_SCHEME). | ||
| --> | ||
| <intent-filter> | ||
| <action android:name="android.intent.action.VIEW" /> | ||
| <category android:name="android.intent.category.DEFAULT" /> | ||
| <category android:name="android.intent.category.BROWSABLE" /> | ||
| <data android:scheme="stmessages" /> | ||
| </intent-filter> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔵 Trivial
Custom URI scheme deep link is inherently interceptable — confirm PKCE fully covers this.
stmessages:// is a private-use scheme; per RFC 8252, any app can register the same scheme and the OS may route the callback to it instead. This is mitigated when the redemption of the delivered token requires the PKCE verifier held only by the legitimate app instance — which matches this PR's design (mobile OIDC handoff + PKCE exchange in dependent layers). As defense-in-depth, consider migrating to verified Android App Links (https + autoVerify="true" + Digital Asset Links) for the callback host in a future iteration, since it removes ambiguity in OS routing entirely rather than only in code redemption.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/frontend/android/app/src/main/AndroidManifest.xml` around lines 22 - 33,
The deep-link callback in AndroidManifest uses the private stmessages scheme,
which can be intercepted by another app; make sure the mobile OIDC flow in
AUTH_CALLBACK_SCHEME and the App.addListener("appUrlOpen") handling are fully
protected by PKCE so the authorization code cannot be redeemed without the
verifier. If the PKCE verifier is not already guaranteed end-to-end in the
native auth path, tighten that flow before relying on this scheme, and consider
moving the callback to verified Android App Links in a future change for
stronger OS-level routing.
0b0cbcd to
4e0a7f7
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/frontend/android/gradlew.bat (1)
1-95: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winNormalize this batch file to CRLF line endings.
LF-only
.batfiles can fail to parse reliably undercmd.exe, so the wrapper may break on Windows until this file is converted.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/frontend/android/gradlew.bat` around lines 1 - 95, The Gradle wrapper batch script needs Windows-safe line endings; convert the gradlew.bat content to CRLF throughout so cmd.exe can parse it reliably. Preserve the existing script logic and only normalize the file’s line endings for the wrapper entrypoints in gradlew.bat.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Makefile`:
- Around line 192-194: The mobile-ota-keygen target can emit private key
material to stdout, so guard the Makefile target and the underlying
mobile:ota:keygen path so it only runs in an interactive operator session and
fails fast in CI-like environments. Update the mobile-ota-keygen rule to detect
common non-interactive/CI indicators before invoking COMPOSE_RUN and npm run
--silent mobile:ota:keygen, and make the safety check explicit in the target
name/doc comment so it is not accidentally wired into automated jobs.
In `@src/frontend/android/app/build.gradle`:
- Around line 27-32: Release build is configured to use proguardFiles in
buildTypes.release, but minifyEnabled is disabled so R8/ProGuard never runs.
Update the release configuration in the app’s buildTypes block to enable
minification, keeping the existing proguardFiles entry so the release APK is
actually shrunk and obfuscated.
In `@src/frontend/ios/App/App/AppDelegate.swift`:
- Around line 9-12: Backup exclusion is only being applied at launch, so newly
created WebKit/Cookies data may miss the exclusion before the first
background/backup event. Update AppDelegate so excludeWebViewDataFromBackup() is
also called from applicationDidEnterBackground, alongside
didFinishLaunchingWithOptions, to ensure the WebView/cookie directories are
marked after they exist and before backups run.
In `@src/frontend/ios/App/CapApp-SPM/Package.swift`:
- Around line 13-20: The local Swift Package dependencies in Package.swift
resolve through relative paths into node_modules, so Xcode/SPM will fail unless
those packages already exist. Make sure the build pipeline or CI runs npm
install (or equivalent dependency bootstrap) before invoking the Xcode build,
and update the relevant build/setup steps so CapacitorApp, CapacitorBrowser,
CapacitorFilesystem, CapacitorShare, and CapgoCapacitorUpdater can be resolved
successfully.
In `@src/frontend/src/bootstrap.tsx`:
- Around line 18-35: The anti-rollback call in bootstrap.tsx is firing too early
at module scope, before the app has successfully mounted. Move the
notifyOtaAppReady() call out of the isNativePlatform() initialization block and
into the successful bootstrap() flow after the initial React tree renders, so
the bundle is only marked healthy once startup completes.
---
Outside diff comments:
In `@src/frontend/android/gradlew.bat`:
- Around line 1-95: The Gradle wrapper batch script needs Windows-safe line
endings; convert the gradlew.bat content to CRLF throughout so cmd.exe can parse
it reliably. Preserve the existing script logic and only normalize the file’s
line endings for the wrapper entrypoints in gradlew.bat.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b0ee8fda-3f68-4c65-9fb1-a47640170041
⛔ Files ignored due to path filters (119)
src/frontend/android/app/src/main/res/drawable-land-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-land-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-night/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-hdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-ldpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-mdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-xhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-xxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable-port-xxxhdpi/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/drawable/splash.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc/frontend/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jarsrc/frontend/assets/icon-dark.pngis excluded by!**/*.pngsrc/frontend/assets/icon-foreground.pngis excluded by!**/*.pngsrc/frontend/assets/icon-only.pngis excluded by!**/*.pngsrc/frontend/assets/splash-dark.pngis excluded by!**/*.pngsrc/frontend/assets/splash.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.pngis excluded by!**/*.pngsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.pngis excluded by!**/*.pngsrc/frontend/package-lock.jsonis excluded by!**/package-lock.jsonsrc/frontend/public/images/anct/app-icon-2x.pngis excluded by!**/*.pngsrc/frontend/public/images/anct/app-icon.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-144-144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-192-192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-48-48.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-512-512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-72-72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/android-launchericon-96-96.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-144x144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-192x192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-48x48.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-512x512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-72x72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/android/launchericon-96x96.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/100.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/1024.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/114.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/120.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/128.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/152.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/16.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/167.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/180.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/20.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/256.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/29.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/32.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/40.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/50.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/57.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/58.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/60.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/64.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/76.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/80.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/ios/87.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-120.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-128.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-144.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-152.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-180.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-192.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-384.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-512.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-72.pngis excluded by!**/*.pngsrc/frontend/public/images/pwa/icons/pwa/icon-96.pngis excluded by!**/*.pngsrc/frontend/src/features/api/gen/models/config_retrieve200.tsis excluded by!**/gen/**src/frontend/src/features/api/gen/models/user_with_abilities.tsis excluded by!**/gen/**
📒 Files selected for processing (90)
Makefilecompose.yamldocs/env.mddocs/mobile.mdenv.d/development/backend.defaultsenv.d/development/frontend.defaultssrc/backend/core/api/openapi.jsonsrc/backend/core/api/serializers.pysrc/backend/core/api/viewsets/config.pysrc/backend/core/api/viewsets/mobile_auth.pysrc/backend/core/authentication/views.pysrc/backend/core/management/commands/create_bucket.pysrc/backend/core/tests/api/test_attachments.pysrc/backend/core/tests/api/test_config.pysrc/backend/core/tests/api/test_users.pysrc/backend/core/tests/authentication/test_mobile_auth.pysrc/backend/core/urls.pysrc/backend/messages/settings.pysrc/frontend/Dockerfilesrc/frontend/android/.gitignoresrc/frontend/android/app/.gitignoresrc/frontend/android/app/build.gradlesrc/frontend/android/app/capacitor.build.gradlesrc/frontend/android/app/proguard-rules.prosrc/frontend/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.javasrc/frontend/android/app/src/main/AndroidManifest.xmlsrc/frontend/android/app/src/main/java/local/suitenumerique/messages/MainActivity.javasrc/frontend/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xmlsrc/frontend/android/app/src/main/res/drawable/ic_launcher_background.xmlsrc/frontend/android/app/src/main/res/layout/activity_main.xmlsrc/frontend/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xmlsrc/frontend/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xmlsrc/frontend/android/app/src/main/res/values-night/colors.xmlsrc/frontend/android/app/src/main/res/values/colors.xmlsrc/frontend/android/app/src/main/res/values/strings.xmlsrc/frontend/android/app/src/main/res/values/styles.xmlsrc/frontend/android/app/src/main/res/xml/file_paths.xmlsrc/frontend/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.javasrc/frontend/android/build.gradlesrc/frontend/android/capacitor.settings.gradlesrc/frontend/android/gradle.propertiessrc/frontend/android/gradle/wrapper/gradle-wrapper.propertiessrc/frontend/android/gradlewsrc/frontend/android/gradlew.batsrc/frontend/android/settings.gradlesrc/frontend/android/variables.gradlesrc/frontend/capacitor.config.tssrc/frontend/eslint.config.mjssrc/frontend/ios/.gitignoresrc/frontend/ios/App/App.xcodeproj/project.pbxprojsrc/frontend/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plistsrc/frontend/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolvedsrc/frontend/ios/App/App.xcodeproj/xcshareddata/xcschemes/App.xcschemesrc/frontend/ios/App/App/AppDelegate.swiftsrc/frontend/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.jsonsrc/frontend/ios/App/App/Assets.xcassets/Contents.jsonsrc/frontend/ios/App/App/Assets.xcassets/Splash.imageset/Contents.jsonsrc/frontend/ios/App/App/Base.lproj/LaunchScreen.storyboardsrc/frontend/ios/App/App/Base.lproj/Main.storyboardsrc/frontend/ios/App/App/Info.plistsrc/frontend/ios/App/App/MainViewController.swiftsrc/frontend/ios/App/App/WebAuthSessionPlugin.swiftsrc/frontend/ios/App/CapApp-SPM/.gitignoresrc/frontend/ios/App/CapApp-SPM/Package.swiftsrc/frontend/ios/App/CapApp-SPM/README.mdsrc/frontend/ios/App/CapApp-SPM/Sources/CapApp-SPM/CapApp-SPM.swiftsrc/frontend/ios/debug.xcconfigsrc/frontend/package.jsonsrc/frontend/public/assets/icons/icon-128.webpsrc/frontend/public/assets/icons/icon-192.webpsrc/frontend/public/assets/icons/icon-256.webpsrc/frontend/public/assets/icons/icon-48.webpsrc/frontend/public/assets/icons/icon-512.webpsrc/frontend/public/assets/icons/icon-72.webpsrc/frontend/public/assets/icons/icon-96.webpsrc/frontend/public/browserconfig.xmlsrc/frontend/public/locales/common/en-US.jsonsrc/frontend/public/locales/common/fr-FR.jsonsrc/frontend/public/manifest.jsonsrc/frontend/scripts/create-ota-bucket.mjssrc/frontend/scripts/generate-ota-keys.mjssrc/frontend/scripts/ota-lib.mjssrc/frontend/scripts/publish-ota.mjssrc/frontend/src/bootstrap.tsxsrc/frontend/src/features/api/csrf.test.tssrc/frontend/src/features/api/csrf.tssrc/frontend/src/features/api/utils.tssrc/frontend/src/features/auth/index.tsxsrc/frontend/src/features/config/resolve.test.tssrc/frontend/src/features/config/resolve.ts
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/env.md (1)
209-209: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDocument the deprecated frontend OTA fallback.
MOBILE_OTA_MANIFEST_URL=unsetonly disables OTA for builds that do not still embedNEXT_PUBLIC_MOBILE_OTA_MANIFEST_URL; older bundles keep polling through the compatibility path insrc/frontend/src/features/config/resolve.ts. Without that caveat, operators can think clearing the backend var is always sufficient.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/env.md` at line 209, Update the `MOBILE_OTA_MANIFEST_URL` documentation to mention the deprecated frontend compatibility fallback in `src/frontend/src/features/config/resolve.ts`: clearing the backend variable alone does not disable OTA for older bundles that still embed `NEXT_PUBLIC_MOBILE_OTA_MANIFEST_URL`. Clarify that `unset` only fully disables OTA for builds without that embedded frontend value, and that older clients may continue polling via the fallback path until they are replaced.
♻️ Duplicate comments (1)
env.d/development/frontend.defaults (1)
73-84: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRemove the committed OTA private key.
GitGuardian is still flagging this block, so the dev defaults appear to contain a real RSA private key. Keep only a placeholder here and generate the local value with
make mobile-ota-keygen.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@env.d/development/frontend.defaults` around lines 73 - 84, The OTA signing defaults still look like they contain a real private key, so replace the committed value with a harmless placeholder in the MOBILE_OTA_SIGNING_PRIVATE_KEY_B64 entry and keep the guidance for generating local secrets via make mobile-ota-keygen. Check the development defaults block around the OTA signing comments and ensure only commented placeholder keys remain, with no actual PEM material or base64 private key content.Source: Pipeline failures
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/e2e/src/__tests__/thread-event.spec.ts`:
- Around line 57-74: Add failure diagnostics around the meResponse.ok() check in
fetchCurrentUser so CI failures include the HTTP status and response body when
the request is not OK. Replace the bare truthy assertion with an assertion or
explicit check that reports useful context from meResponse, keeping the existing
fetchCurrentUser helper and its JSON return shape unchanged.
---
Outside diff comments:
In `@docs/env.md`:
- Line 209: Update the `MOBILE_OTA_MANIFEST_URL` documentation to mention the
deprecated frontend compatibility fallback in
`src/frontend/src/features/config/resolve.ts`: clearing the backend variable
alone does not disable OTA for older bundles that still embed
`NEXT_PUBLIC_MOBILE_OTA_MANIFEST_URL`. Clarify that `unset` only fully disables
OTA for builds without that embedded frontend value, and that older clients may
continue polling via the fallback path until they are replaced.
---
Duplicate comments:
In `@env.d/development/frontend.defaults`:
- Around line 73-84: The OTA signing defaults still look like they contain a
real private key, so replace the committed value with a harmless placeholder in
the MOBILE_OTA_SIGNING_PRIVATE_KEY_B64 entry and keep the guidance for
generating local secrets via make mobile-ota-keygen. Check the development
defaults block around the OTA signing comments and ensure only commented
placeholder keys remain, with no actual PEM material or base64 private key
content.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7d649dab-8c07-4baa-81f7-d8ed4c3b8164
📒 Files selected for processing (4)
docs/env.mddocs/mobile.mdenv.d/development/frontend.defaultssrc/e2e/src/__tests__/thread-event.spec.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/frontend/src/bootstrap.tsx (1)
91-91: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winDelay OTA readiness until React has mounted.
root.render(...)does not guarantee that code after it runs after the initial mounted tree/effects, so Line 91 can still mark a downloaded bundle healthy before startup has actually completed. React documents that code afterroot.render()may run before effects for that render. (react.dev)Proposed direction
+const OtaAppReadyNotifier = () => { + useEffect(() => { + void notifyOtaAppReady(); + }, []); + + return null; +}; + -createRoot(container).render(<RouterProvider router={router} />); +createRoot(container).render( + <> + <OtaAppReadyNotifier /> + <RouterProvider router={router} /> + </>, +); ... -void notifyOtaAppReady();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/frontend/src/bootstrap.tsx` at line 91, The OTA readiness call is happening too early because `notifyOtaAppReady()` runs immediately after `root.render(...)` instead of after React has actually mounted and run startup effects. Move the readiness notification into a React lifecycle point that is guaranteed to run after the initial mount, such as a top-level component effect, and keep `root.render` focused only on rendering the app so the bundle is marked healthy only when startup has completed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@src/frontend/src/bootstrap.tsx`:
- Line 91: The OTA readiness call is happening too early because
`notifyOtaAppReady()` runs immediately after `root.render(...)` instead of after
React has actually mounted and run startup effects. Move the readiness
notification into a React lifecycle point that is guaranteed to run after the
initial mount, such as a top-level component effect, and keep `root.render`
focused only on rendering the app so the bundle is marked healthy only when
startup has completed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 619842b3-f43d-4fb6-b7d3-87ada20079be
📒 Files selected for processing (2)
src/frontend/ios/App/App/AppDelegate.swiftsrc/frontend/src/bootstrap.tsx
The upcoming Capacitor mobile shell replays the Django session cookie through its native HTTP layer but not the `csrftoken` cookie, so cookie-based CSRF would break every mutation on mobile. Enabling CSRF_USE_SESSIONS moves the secret server-side and removes the need for a JS-readable cookie: the token is now delivered on the authenticated /users/me/ response, cached in memory by the SPA and echoed as X-CSRFToken. On web this is equivalent or safer — the secret is no longer readable by scripts nor overridable via cross-subdomain cookie tossing.
Capacitor apps must run the OIDC flow in the system browser (the IdP cookie has to live there to provide cross-app SSO), but the browser's cookies never reach the app's native HTTP layer, so the Django session created by the callback would be stranded. The callback now redirects to an allowlisted app deep link with a one-time token that the app exchanges for its session cookie and CSRF token. The token is bound to the initiating app instance with a PKCE S256 verifier, single-use, short-lived (MOBILE_AUTH_TOKEN_TTL) and the anonymous exchange endpoint is throttled per IP to cap brute-force guessing. An empty MOBILE_AUTH_CALLBACK_SCHEMES (the default) keeps the whole handoff disabled.
The backend sends acr_values=eidas1 on every authorization request (OIDC_AUTH_REQUEST_EXTRA_PARAMS) but the dev realm had an empty acr.loa.map, so Keycloak treated the value as an unknown essential acr claim. Web logins survived it, but the mobile system-browser flow (ASWebAuthenticationSession / Custom Tabs) failed the login round-trip, breaking cross-app SSO in dev. Mapping eidas1 to LoA 1 mirrors what the production IdP declares.
Ship the existing SPA as native iOS/Android apps without forking the codebase: Capacitor wraps the web build, and every mobile-specific behavior is gated behind isNativePlatform() so the web app is untouched. The native shells route fetch/cookies through the native HTTP layer (CapacitorHttp) — the WebView cookie jar is unreliable for cross-origin sessions — which is why login runs in the system browser (cross-app SSO via the shared IdP cookie) and finishes through the backend session handoff, with the deep-link scheme pinned by sso-invariants tests. Downloads/share go through the Filesystem/Share plugins since WebView navigation would lose the session.
Store review cycles make shipping web-layer fixes through the stores too slow, so the apps update their JS bundle over the air. The chain is fully self-hosted to keep sovereignty: bundles and channel manifests live on an anonymous-read S3 bucket (create_bucket --public / the create-ota-bucket script) and the Capgo plugin is driven entirely from JS against that manifest (autoUpdate off — no Capgo server involved). Bundles are RSA-signed at publish time and verified against the per-instance public key baked in at cap sync, so a tampered zip on the public bucket is rejected. Versions use a git-derived <count>-<sha> id stamped into the builtin bundle so a fresh install does not re-download its own commit, and channels (dev/staging/prod) are fully independent because NEXT_PUBLIC_* vars are inlined at build time. Also ships docs/mobile.md.
aa42dba to
02c35ca
Compare
Purpose
Bootstrap app mobile by :
Summary by CodeRabbit