feat(core): improve login flow (#15219)

#### PR Dependency Tree


* **PR #15219** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added secure, automatic auth session token refresh and request replay
for expired-token responses across Android, iOS, and Electron.
* Updated sign-in flows to manage sessions without returning tokens to
the app layer.
* Added “Devices” management UI with sign out per device and sign out
all.
  * Enabled support for both Hashcash and Turnstile captcha providers.
* **Bug Fixes**
* Improved refresh de-duplication, inflight cancellation/clear behavior,
and recovery from corrupted/invalid sessions.
* **Tests**
* Expanded auth-session, refresh/revoke, and replay coverage (Electron
unit tests, Android instrumentation tests, iOS auth date parser tests).
* **Chores**
* Removed CAPTCHA site key from build-time configuration and adjusted CI
test execution.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-07-12 18:11:02 +08:00
committed by GitHub
parent 02b25e05d8
commit abf37d3dfa
57 changed files with 2919 additions and 789 deletions
-3
View File
@@ -38,7 +38,6 @@ jobs:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
BUILD_TYPE: ${{ inputs.build-type }}
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine-web'
SENTRY_RELEASE: ${{ inputs.app-version }}
@@ -71,7 +70,6 @@ jobs:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
BUILD_TYPE: ${{ inputs.build-type }}
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine-admin'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
@@ -103,7 +101,6 @@ jobs:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
BUILD_TYPE: ${{ inputs.build-type }}
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: 'affine-mobile'
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+109
View File
@@ -239,6 +239,95 @@ jobs:
working-directory: packages/frontend/apps/android/App
run: ./gradlew :app:assembleCanaryDebug --no-daemon --stacktrace
test-android-app:
name: Test Android app
if: ${{ needs.mobile-native-build-filter.outputs.run-android == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 40
needs:
- mobile-native-build-filter
env:
AFFINE_ANDROID_NATIVE_TARGET: x86_64
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
extra-flags: workspaces focus @affine/monorepo @affine-tools/cli @affine/android
electron-install: false
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'
- name: Setup Rust
uses: ./.github/actions/build-rust
with:
target: 'x86_64-linux-android'
package: 'affine_mobile_native'
no-build: 'true'
- name: Build Android web assets
run: yarn affine @affine/android build
env:
PUBLIC_PATH: '/'
- name: Write CI Firebase config
run: |
cat > packages/frontend/apps/android/App/app/google-services.json <<'JSON'
{
"project_info": {
"project_number": "1",
"project_id": "affine-ci",
"storage_bucket": "affine-ci.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1:android:0000000000000000",
"android_client_info": {
"package_name": "app.affine.pro"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "ci-placeholder"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
JSON
- name: Cap sync
run: yarn workspace @affine/android cap sync
- name: Run Android unit tests
working-directory: packages/frontend/apps/android/App
run: ./gradlew :app:testCanaryDebugUnitTest --no-daemon --stacktrace
- name: Enable KVM
run: sudo chmod 666 /dev/kvm
- name: Run Android instrumentation tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 35
arch: x86_64
profile: pixel_6
disable-animations: true
script: cd packages/frontend/apps/android/App && ./gradlew :app:connectedCanaryDebugAndroidTest --no-daemon --stacktrace
build-ios-app:
name: Build iOS app
if: ${{ needs.mobile-native-build-filter.outputs.run-ios == 'true' }}
@@ -288,6 +377,25 @@ jobs:
CODE_SIGNING_REQUIRED=NO \
build
- name: Run iOS unit tests
run: |
device_id="$(xcrun simctl list devices available -j | ruby -rjson -e '
devices = JSON.parse(STDIN.read).fetch("devices").values.flatten
device = devices.find { |candidate| candidate.fetch("name").start_with?("iPhone") }
abort "No available iPhone simulator" unless device
puts device.fetch("udid")
')"
xcrun simctl boot "$device_id" || true
xcodebuild \
-project packages/frontend/apps/ios/App/App.xcodeproj \
-scheme AFFiNETests \
-configuration Debug \
-sdk iphonesimulator \
-destination "platform=iOS Simulator,id=$device_id" \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO \
test
rust-test-filter:
name: Rust test filter
runs-on: ubuntu-latest
@@ -1390,6 +1498,7 @@ jobs:
- typecheck
- mobile-native-build-filter
- build-android-app
- test-android-app
- build-ios-app
- lint-rust
- check-git-status