mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-10 21:48:48 +08:00
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:
@@ -0,0 +1,95 @@
|
||||
import { notify } from '@affine/component';
|
||||
import {
|
||||
AuthService,
|
||||
type DeviceAuthSession,
|
||||
} from '@affine/core/modules/cloud';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { SettingGroup } from '../group';
|
||||
import { RowLayout } from '../row.layout';
|
||||
|
||||
export const DevicesGroup = () => {
|
||||
const t = useI18n();
|
||||
const auth = useService(AuthService);
|
||||
const [sessions, setSessions] = useState<DeviceAuthSession[]>([]);
|
||||
|
||||
const reload = useCallback(() => {
|
||||
void auth
|
||||
.listDeviceSessions()
|
||||
.then(setSessions)
|
||||
.catch(error => {
|
||||
notify.error({
|
||||
title: t['com.affine.settings.devices.load-failed'](),
|
||||
message: String(error),
|
||||
});
|
||||
});
|
||||
}, [auth, t]);
|
||||
|
||||
useEffect(reload, [reload]);
|
||||
|
||||
const revoke = useCallback(
|
||||
async (session: DeviceAuthSession) => {
|
||||
if (
|
||||
!window.confirm(
|
||||
t['com.affine.settings.devices.confirm']({
|
||||
device: session.deviceName ?? session.platform,
|
||||
})
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await auth.revokeDeviceSession(session.id, session.current);
|
||||
if (!session.current) reload();
|
||||
} catch (error) {
|
||||
notify.error({
|
||||
title: t['com.affine.settings.devices.sign-out-failed'](),
|
||||
message: String(error),
|
||||
});
|
||||
}
|
||||
},
|
||||
[auth, reload, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<SettingGroup title={t['com.affine.settings.devices.title']()}>
|
||||
{sessions.map(session => (
|
||||
<RowLayout
|
||||
key={session.id}
|
||||
label={
|
||||
<div>
|
||||
<div>{`${session.deviceName ?? session.platform}${session.current ? ` (${t['com.affine.settings.devices.current']()})` : ''}`}</div>
|
||||
<div>
|
||||
{t['com.affine.settings.devices.last-used']({
|
||||
time: new Date(session.lastSeenAt).toLocaleString(),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
onClick={() => void revoke(session)}
|
||||
>
|
||||
{t['com.affine.settings.devices.sign-out']()}
|
||||
</RowLayout>
|
||||
))}
|
||||
{sessions.length > 1 ? (
|
||||
<RowLayout
|
||||
label={t['com.affine.settings.devices.sign-out-all']()}
|
||||
onClick={() => {
|
||||
if (
|
||||
window.confirm(t['com.affine.settings.devices.confirm-all']())
|
||||
) {
|
||||
void auth.revokeAllDeviceSessions().catch(error => {
|
||||
notify.error({
|
||||
title: t['com.affine.settings.devices.sign-out-all-failed'](),
|
||||
message: String(error),
|
||||
});
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</SettingGroup>
|
||||
);
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import { useEffect } from 'react';
|
||||
|
||||
import { AboutGroup } from './about';
|
||||
import { AppearanceGroup } from './appearance';
|
||||
import { DevicesGroup } from './devices';
|
||||
import { ExperimentalFeatureSetting } from './experimental';
|
||||
import { OthersGroup } from './others';
|
||||
import * as styles from './style.css';
|
||||
@@ -26,6 +27,7 @@ const MobileSetting = () => {
|
||||
<UserProfile />
|
||||
<UserSubscription />
|
||||
<UserUsage />
|
||||
<DevicesGroup />
|
||||
<AppearanceGroup />
|
||||
<AboutGroup />
|
||||
<ExperimentalFeatureSetting />
|
||||
|
||||
Reference in New Issue
Block a user