feat: implement account email mismatch error handling and improve user feedback in authentication flows

This commit is contained in:
Junyan Qin
2026-01-01 17:01:32 +08:00
parent 61f08f3218
commit 02e12cc1e4
10 changed files with 60 additions and 19 deletions

View File

@@ -4,6 +4,7 @@ import asyncio
import traceback
from .. import group
from .....entity.errors import account as account_errors
@group.group_class('user', '/api/v1/user')
@@ -141,6 +142,8 @@ class UserRouterGroup(group.RouterGroup):
'user': user_obj.user,
}
)
except account_errors.AccountEmailMismatchError as e:
return self.fail(3, str(e))
except ValueError as e:
traceback.print_exc()
return self.fail(1, str(e))

View File

@@ -10,6 +10,7 @@ import asyncio
from ....core import app
from ....entity.persistence import user
from ....utils import constants
from ....entity.errors import account as account_errors
class UserService:
@@ -174,9 +175,7 @@ class UserService:
# Check if system is already initialized
is_initialized = await self.is_initialized()
if is_initialized:
raise ValueError(
'This LangBot instance already has an account. Please use the existing account to login.'
)
raise account_errors.AccountEmailMismatchError()
# Create new Space user (first time initialization)
await self.ap.persistence_mgr.execute_async(

View File

@@ -0,0 +1,6 @@
from __future__ import annotations
class AccountEmailMismatchError(Exception):
def __str__(self):
return 'Account email mismatch'

View File

@@ -48,9 +48,15 @@ function SpaceOAuthCallbackContent() {
setTimeout(() => {
router.push('/home');
}, 1000);
} catch {
} catch (err) {
setStatus('error');
setErrorMessage(t('common.spaceLoginFailed'));
const errorObj = err as { msg?: string };
const errMsg = (errorObj?.msg || '').toLowerCase();
if (errMsg.includes('account email mismatch')) {
setErrorMessage(t('account.spaceEmailMismatch'));
} else {
setErrorMessage(t('common.spaceLoginFailed'));
}
}
},
[router, t],
@@ -74,9 +80,13 @@ function SpaceOAuthCallbackContent() {
}, 1000);
} catch (err) {
setStatus('error');
setErrorMessage(
err instanceof Error ? err.message : t('account.bindSpaceFailed'),
);
const errorObj = err as { msg?: string };
const errMsg = (errorObj?.msg || '').toLowerCase();
if (errMsg.includes('account email mismatch')) {
setErrorMessage(t('account.spaceEmailMismatch'));
} else {
setErrorMessage(t('account.bindSpaceFailed'));
}
} finally {
setIsProcessing(false);
}

View File

@@ -753,7 +753,7 @@ export class BackendClient extends BaseHttpClient {
});
}
public bindSpaceAccount(
public async bindSpaceAccount(
code: string,
state: string,
): Promise<{
@@ -761,7 +761,17 @@ export class BackendClient extends BaseHttpClient {
user: string;
account_type: 'local' | 'space';
}> {
return this.post('/api/v1/user/bind-space', { code, state });
const response = await this.instance.post('/api/v1/user/bind-space', {
code,
state,
});
if (response.data.code !== 0) {
throw {
code: response.data.code,
msg: response.data.msg || 'Unknown error',
};
}
return response.data.data;
}
// ============ Space OAuth API (Redirect Flow) ============
@@ -778,10 +788,19 @@ export class BackendClient extends BaseHttpClient {
return this.get('/api/v1/user/space/authorize-url', params);
}
public exchangeSpaceOAuthCode(code: string): Promise<{
public async exchangeSpaceOAuthCode(code: string): Promise<{
token: string;
user: string;
}> {
return this.post('/api/v1/user/space/callback', { code });
const response = await this.instance.post('/api/v1/user/space/callback', {
code,
});
if (response.data.code !== 0) {
throw {
code: response.data.code,
msg: response.data.msg || 'Unknown error',
};
}
return response.data.data;
}
}

View File

@@ -93,9 +93,7 @@ export abstract class BaseHttpClient {
// 统一错误处理
if (error.response) {
const { status, data } = error.response;
// Backend uses 'msg' field, also check 'message' for compatibility
const errMessage =
(data as { msg?: string })?.msg || data?.message || error.message;
const errMsg = (data as { msg?: string })?.msg || error.message;
switch (status) {
case 401:
@@ -107,23 +105,23 @@ export abstract class BaseHttpClient {
}
break;
case 403:
console.error('Permission denied:', errMessage);
console.error('Permission denied:', errMsg);
break;
case 500:
console.error('Server error:', errMessage);
console.error('Server error:', errMsg);
break;
}
return Promise.reject({
code: data?.code || status,
message: errMessage,
msg: errMsg,
data: data?.data || null,
});
}
return Promise.reject({
code: -1,
message: error.message || 'Network Error',
msg: error.message || 'Network Error',
data: null,
});
},

View File

@@ -789,6 +789,8 @@ const enUS = {
bindSpaceInvalidState:
'Invalid bind request. Please try again from account settings.',
setPasswordHint: 'Set a password to login with email and password',
spaceEmailMismatch:
'Space login email does not match the local account email',
},
};

View File

@@ -798,6 +798,8 @@ const jaJP = {
'無効な連携リクエストです。アカウント設定から再度お試しください。',
setPasswordHint:
'パスワードを設定するとメールとパスワードでログインできます',
spaceEmailMismatch:
'Spaceログインのメールアドレスがローカルアカウントのメールアドレスと一致しません',
},
};

View File

@@ -753,6 +753,7 @@ const zhHans = {
bindSpaceFailed: '绑定 Space 账户失败',
bindSpaceInvalidState: '无效的绑定请求,请从账户设置重新发起',
setPasswordHint: '设置密码后可使用邮箱密码登录',
spaceEmailMismatch: 'Space登录账号邮箱与本实例账号邮箱不匹配',
},
};

View File

@@ -750,6 +750,7 @@ const zhHant = {
bindSpaceFailed: '綁定 Space 帳戶失敗',
bindSpaceInvalidState: '無效的綁定請求,請從帳戶設定重新發起',
setPasswordHint: '設定密碼後可使用電子郵件密碼登入',
spaceEmailMismatch: 'Space登入帳號電子郵件與本實例帳號電子郵件不匹配',
},
};