feat(server): passkey pre-refactor (#15060)

#### PR Dependency Tree


* **PR #15060** 👈

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**
* OpenApp native sign-in and native session exchange (JWT) for mobile &
desktop.
  * Centralized short-lived auth challenge store for one-time tokens.
* Encrypted per-endpoint token storage and native token handlers
(Android, iOS, Electron).

* **Improvements**
* Richer auth-method reporting (password, magic link, OAuth, passkey)
and improved sign-in flows.
* Hardened magic-link, OAuth, and session issuance; JWT-backed sessions
and websocket JWT support.
* UX tweaks: form-based password submit, OTP autocomplete, adjusted
captcha flow.

* **Bug Fixes**
  * Expanded tests and auth-state resets to avoid cross-test leakage.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-06-01 17:11:15 +08:00
committed by GitHub
parent 5b9d51b41b
commit ce9841df9d
74 changed files with 3719 additions and 939 deletions
@@ -25,6 +25,7 @@ import { useEffect, useState } from 'react';
export const ChangePasswordDialog = ({
close,
hasPassword: hasPasswordProp,
server: serverBaseUrl,
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['change-password']>) => {
const t = useI18n();
@@ -44,7 +45,8 @@ export const ChangePasswordDialog = ({
const authService = server.scope.get(AuthService);
const account = useLiveData(authService.session.account$);
const email = account?.email;
const hasPassword = account?.info?.hasPassword;
const hasPassword =
hasPasswordProp ?? account?.info?.authMethods?.password.bound ?? false;
const [hasSentEmail, setHasSentEmail] = useState(false);
const [loading, setLoading] = useState(false);
const passwordLimits = useLiveData(
@@ -201,13 +201,19 @@ export const AccountSetting = ({
const onPasswordButtonClick = useCallback(() => {
globalDialogService.open('change-password', {
hasPassword: account?.info?.authMethods?.password.bound,
server: serverService.server.baseUrl,
});
}, [globalDialogService, serverService.server.baseUrl]);
}, [
account?.info?.authMethods?.password.bound,
globalDialogService,
serverService.server.baseUrl,
]);
if (!account) {
return null;
}
const hasPassword = account.info?.authMethods?.password.bound;
return (
<>
@@ -233,7 +239,7 @@ export const AccountSetting = ({
desc={t['com.affine.settings.password.message']()}
>
<Button onClick={onPasswordButtonClick}>
{account.info?.hasPassword
{hasPassword
? t['com.affine.settings.password.action.change']()
: t['com.affine.settings.password.action.set']()}
</Button>