mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-22 04:16:07 +00:00
feat: implement account email mismatch error handling and improve user feedback in authentication flows
This commit is contained in:
@@ -4,6 +4,7 @@ import asyncio
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from .. import group
|
from .. import group
|
||||||
|
from .....entity.errors import account as account_errors
|
||||||
|
|
||||||
|
|
||||||
@group.group_class('user', '/api/v1/user')
|
@group.group_class('user', '/api/v1/user')
|
||||||
@@ -141,6 +142,8 @@ class UserRouterGroup(group.RouterGroup):
|
|||||||
'user': user_obj.user,
|
'user': user_obj.user,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
except account_errors.AccountEmailMismatchError as e:
|
||||||
|
return self.fail(3, str(e))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return self.fail(1, str(e))
|
return self.fail(1, str(e))
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import asyncio
|
|||||||
from ....core import app
|
from ....core import app
|
||||||
from ....entity.persistence import user
|
from ....entity.persistence import user
|
||||||
from ....utils import constants
|
from ....utils import constants
|
||||||
|
from ....entity.errors import account as account_errors
|
||||||
|
|
||||||
|
|
||||||
class UserService:
|
class UserService:
|
||||||
@@ -174,9 +175,7 @@ class UserService:
|
|||||||
# Check if system is already initialized
|
# Check if system is already initialized
|
||||||
is_initialized = await self.is_initialized()
|
is_initialized = await self.is_initialized()
|
||||||
if is_initialized:
|
if is_initialized:
|
||||||
raise ValueError(
|
raise account_errors.AccountEmailMismatchError()
|
||||||
'This LangBot instance already has an account. Please use the existing account to login.'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create new Space user (first time initialization)
|
# Create new Space user (first time initialization)
|
||||||
await self.ap.persistence_mgr.execute_async(
|
await self.ap.persistence_mgr.execute_async(
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
class AccountEmailMismatchError(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'Account email mismatch'
|
||||||
@@ -48,9 +48,15 @@ function SpaceOAuthCallbackContent() {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.push('/home');
|
router.push('/home');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} catch {
|
} catch (err) {
|
||||||
setStatus('error');
|
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],
|
[router, t],
|
||||||
@@ -74,9 +80,13 @@ function SpaceOAuthCallbackContent() {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setStatus('error');
|
setStatus('error');
|
||||||
setErrorMessage(
|
const errorObj = err as { msg?: string };
|
||||||
err instanceof Error ? err.message : t('account.bindSpaceFailed'),
|
const errMsg = (errorObj?.msg || '').toLowerCase();
|
||||||
);
|
if (errMsg.includes('account email mismatch')) {
|
||||||
|
setErrorMessage(t('account.spaceEmailMismatch'));
|
||||||
|
} else {
|
||||||
|
setErrorMessage(t('account.bindSpaceFailed'));
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -753,7 +753,7 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bindSpaceAccount(
|
public async bindSpaceAccount(
|
||||||
code: string,
|
code: string,
|
||||||
state: string,
|
state: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
@@ -761,7 +761,17 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
user: string;
|
user: string;
|
||||||
account_type: 'local' | 'space';
|
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) ============
|
// ============ Space OAuth API (Redirect Flow) ============
|
||||||
@@ -778,10 +788,19 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
return this.get('/api/v1/user/space/authorize-url', params);
|
return this.get('/api/v1/user/space/authorize-url', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public exchangeSpaceOAuthCode(code: string): Promise<{
|
public async exchangeSpaceOAuthCode(code: string): Promise<{
|
||||||
token: string;
|
token: string;
|
||||||
user: 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,9 +93,7 @@ export abstract class BaseHttpClient {
|
|||||||
// 统一错误处理
|
// 统一错误处理
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
const { status, data } = error.response;
|
const { status, data } = error.response;
|
||||||
// Backend uses 'msg' field, also check 'message' for compatibility
|
const errMsg = (data as { msg?: string })?.msg || error.message;
|
||||||
const errMessage =
|
|
||||||
(data as { msg?: string })?.msg || data?.message || error.message;
|
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 401:
|
case 401:
|
||||||
@@ -107,23 +105,23 @@ export abstract class BaseHttpClient {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 403:
|
case 403:
|
||||||
console.error('Permission denied:', errMessage);
|
console.error('Permission denied:', errMsg);
|
||||||
break;
|
break;
|
||||||
case 500:
|
case 500:
|
||||||
console.error('Server error:', errMessage);
|
console.error('Server error:', errMsg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
code: data?.code || status,
|
code: data?.code || status,
|
||||||
message: errMessage,
|
msg: errMsg,
|
||||||
data: data?.data || null,
|
data: data?.data || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
code: -1,
|
code: -1,
|
||||||
message: error.message || 'Network Error',
|
msg: error.message || 'Network Error',
|
||||||
data: null,
|
data: null,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -789,6 +789,8 @@ const enUS = {
|
|||||||
bindSpaceInvalidState:
|
bindSpaceInvalidState:
|
||||||
'Invalid bind request. Please try again from account settings.',
|
'Invalid bind request. Please try again from account settings.',
|
||||||
setPasswordHint: 'Set a password to login with email and password',
|
setPasswordHint: 'Set a password to login with email and password',
|
||||||
|
spaceEmailMismatch:
|
||||||
|
'Space login email does not match the local account email',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -798,6 +798,8 @@ const jaJP = {
|
|||||||
'無効な連携リクエストです。アカウント設定から再度お試しください。',
|
'無効な連携リクエストです。アカウント設定から再度お試しください。',
|
||||||
setPasswordHint:
|
setPasswordHint:
|
||||||
'パスワードを設定するとメールとパスワードでログインできます',
|
'パスワードを設定するとメールとパスワードでログインできます',
|
||||||
|
spaceEmailMismatch:
|
||||||
|
'Spaceログインのメールアドレスがローカルアカウントのメールアドレスと一致しません',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -753,6 +753,7 @@ const zhHans = {
|
|||||||
bindSpaceFailed: '绑定 Space 账户失败',
|
bindSpaceFailed: '绑定 Space 账户失败',
|
||||||
bindSpaceInvalidState: '无效的绑定请求,请从账户设置重新发起',
|
bindSpaceInvalidState: '无效的绑定请求,请从账户设置重新发起',
|
||||||
setPasswordHint: '设置密码后可使用邮箱密码登录',
|
setPasswordHint: '设置密码后可使用邮箱密码登录',
|
||||||
|
spaceEmailMismatch: 'Space登录账号邮箱与本实例账号邮箱不匹配',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -750,6 +750,7 @@ const zhHant = {
|
|||||||
bindSpaceFailed: '綁定 Space 帳戶失敗',
|
bindSpaceFailed: '綁定 Space 帳戶失敗',
|
||||||
bindSpaceInvalidState: '無效的綁定請求,請從帳戶設定重新發起',
|
bindSpaceInvalidState: '無效的綁定請求,請從帳戶設定重新發起',
|
||||||
setPasswordHint: '設定密碼後可使用電子郵件密碼登入',
|
setPasswordHint: '設定密碼後可使用電子郵件密碼登入',
|
||||||
|
spaceEmailMismatch: 'Space登入帳號電子郵件與本實例帳號電子郵件不匹配',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user