[598-51][@ant] fix: разрешить пайщику получать собственные кошельки и не терять их при частичном отказе запроса — RolesGuard пропускал self-access только через вложенные data.username/filter.username, из-за чего обычные пайщики (role=user, не председатель и не член совета) получали 401 на getUserWallets; Promise.all в useWalletStore.loadUserWallet из-за этого обнулял program_wallets и agreements целиком, поэтому мини-кошелёк в левом drawer'е не появлялся, а гейт подписи ЦПП зацикливался и требовал подписать уже подписанные документы заново
Typecheck / desktop (pull_request) Successful in 14m45s
Typecheck / controller (pull_request) Has been cancelled

This commit is contained in:
ant
2026-07-21 14:16:40 +00:00
parent 6788699e45
commit 15e80a47dc
2 changed files with 24 additions and 17 deletions
@@ -11,7 +11,7 @@ export class RolesGuard implements CanActivate {
* Работает следующим образом:
* 1. Если присутствует заголовок `server-secret`, доступ разрешён.
* 2. Если роли не заданы через декоратор `@AuthRoles`, доступ открыт.
* 3. Если пользователь обращается к своим ресурсам (поле `username` внутри объекта `data` в запросе совпадает с `user.username`), доступ разрешён.
* 3. Если пользователь обращается к своим ресурсам (поле `username` — вложенное в `data`/`filter`, либо плоский аргумент — совпадает с `user.username`), доступ разрешён.
* 4. Если пользователь имеет хотя бы одну из разрешённых ролей, доступ разрешён.
* 5. В иных случаях доступ запрещён, выбрасывается ошибка с кодом 401.
*
@@ -50,7 +50,8 @@ export class RolesGuard implements CanActivate {
const filter = args.filter;
if ((data && data.username && user.username === data.username) ||
(filter && filter.username && user.username === filter.username)) {
(filter && filter.username && user.username === filter.username) ||
(args.username && user.username === args.username)) {
return true; // Если username совпадает, разрешаем доступ
}
@@ -123,9 +123,18 @@ export const useWalletStore = defineStore(namespace, (): IWalletStore => {
_patches.value = [];
};
// Запросы независимы (разные срезы кошелька/соглашений) — allSettled, а не
// all: падение одного (напр. недостаточно прав на один из резолверов) не
// должно обнулять остальные пять уже успешно загруженных.
function unwrap<T>(result: PromiseSettledResult<T>, fallback: T): T {
if (result.status === 'fulfilled') return result.value ?? fallback;
console.error(result.reason);
return fallback;
}
const loadUserWallet = async (params: ILoadUserWallet) => {
try {
const data = await Promise.all([
const [depositsRes, withdrawsRes, programWalletsRes, methodsRes, agreementsRes, userWalletsRes] =
await Promise.allSettled([
api.loadUserDepositsData(params),
api.loadUserWithdrawsData(params),
api.loadUserProgramWalletsData(params),
@@ -134,19 +143,16 @@ export const useWalletStore = defineStore(namespace, (): IWalletStore => {
api.loadUserWalletsData(params),
]);
deposits.value = data[0] ?? [];
withdraws.value = data[1] ?? [];
_program_wallets_base.value = data[2] ?? [];
methods.value = data[3] ?? [];
agreements.value = data[4] ?? [];
user_wallets.value = data[5] ?? [];
// Серверная правда выигрывает — все наложенные оптимистичные патчи
// сбрасываются. Если расхождение есть, оно будет видно сразу (а не
// как «откат через TTL» через несколько секунд).
clearOptimisticPatches();
} catch (e: any) {
console.log(e);
}
deposits.value = unwrap(depositsRes, []);
withdraws.value = unwrap(withdrawsRes, []);
_program_wallets_base.value = unwrap(programWalletsRes, []);
methods.value = unwrap(methodsRes, []);
agreements.value = unwrap(agreementsRes, []);
user_wallets.value = unwrap(userWalletsRes, []);
// Серверная правда выигрывает — все наложенные оптимистичные патчи
// сбрасываются. Если расхождение есть, оно будет видно сразу (а не
// как «откат через TTL» через несколько секунд).
clearOptimisticPatches();
};
return {