95 Commits

Author SHA1 Message Date
26d1fd1447 feat: register_user 添加 customer.admin 角色,注册时同时分配 customer 和 admin 权限 2026-05-31 10:38:32 +08:00
b532548d19 fix: keep previous rp_caches when DB returns empty result
sqlExe can return [] without raising an exception (bad connection,
cursor issue). When load_roleperms gets 0 records but had valid
cache before, keep the old cache instead of replacing with {}.
Prevents intermittent 403 from transient DB issues.
2026-05-30 11:34:13 +08:00
fbbe011a8d fix: rp_caches race condition causing intermittent 403
load_roleperms() was setting self.rp_caches = {} before the async
DB query. During the await, other coroutines saw {} (not None),
skipped the load, and checked permissions against an empty dict,
causing intermittent 403 on random paths.

Fix: build in local dict first, assign atomically when complete.
2026-05-30 11:19:32 +08:00
c776c0b3b5 fix: rp_cache should reload from DB when cache is disabled 2026-05-30 10:19:00 +08:00
04e9b718db fix: check_roles_path supports % wildcard alongside **
load_path.py scripts across modules register paths like '/module/api/%'
using SQL LIKE wildcard, but check_roles_path() only recognized '**' as
wildcard suffix. This caused all %-terminated paths to be treated as
exact matches, resulting in 403 for any sub-path.

Now both '/module/api/%' and '/module/api/**' work as prefix wildcards.
2026-05-29 23:12:22 +08:00
67687883ff fix: RBAC crash when cache disabled - return roles directly from get_userroles
When module_cache.rbac=false in config.json, LRUCache.get() always returns
None and LRUCache.set() is a no-op. This caused get_userroles() to store
roles into a disabled cache, then callers read back None, leading to
TypeError in check_roles_path() when iterating over None.

Fix: get_userroles() now returns the roles list directly. Callers use the
return value instead of relying solely on cache reads. Added safety
fallback to deny access if roles is somehow still None.
2026-05-29 22:43:39 +08:00
cf18e592c7 feat: respect module_cache config for RBAC LRU cache 2026-05-29 17:59:04 +08:00
9d2a94131a feat: improve logout.dspy with refresh button
After logout, show success message with a button to reload the page,
which triggers the sidebar menu to re-render with unauthenticated state.
2026-05-27 17:58:15 +08:00
39f8eb7d94 Revert "feat: add cross-process cache invalidation via Redis Pub/Sub"
This reverts commit 8cec17c04295665eb4b750e2070c17fa3b06a939.
2026-05-26 18:31:04 +08:00
8fdb31a850 Revert "fix: add app parameter to start_cache_sync for aiohttp on_startup hook"
This reverts commit c0bbe63845e1f5ca255a0e2fe821fcf4f88786aa.
2026-05-26 18:31:04 +08:00
c0bbe63845 fix: add app parameter to start_cache_sync for aiohttp on_startup hook 2026-05-26 17:20:52 +08:00
8cec17c042 feat: add cross-process cache invalidation via Redis Pub/Sub
- userperm.py: All invalidate_* and on_* handlers changed to async
  - Each invalidation now broadcasts via cache_sync.invalidate()
  - invalidate_user_cache() -> 'rbac:ur:{userid}'
  - invalidate_all_user_caches() -> 'rbac:ur:all'
  - invalidate_rp_cache() -> 'rbac:rp'

- init.py: Added start_cache_sync() async function
  - Starts Redis Pub/Sub subscription
  - Registers callbacks for rbac:rp and rbac:ur:all channels

- set_role_perms.py: CLI script now sends invalidation after execution
  - send_rbac_invalidation() starts cache_sync, publishes, then stops

Compatible with existing EventDispatcher (already supports async handlers)
2026-05-26 13:52:10 +08:00
f8c8a4ce4d refactor: move RBAC tools logic to rbac/rbac_tools.py, dspy files call via request._run_ns 2026-05-26 09:32:38 +08:00
0b456486db feat: add RBAC tools — list_path_roles, find_unauth_files, and permission registration script 2026-05-26 09:18:04 +08:00
4f103000b9 feat: implement real-time cache invalidation via DB event binding
- Fixed syntax errors in userperm.py __init__ (removed broken 'this' reference
  and incomplete method definition)
- Added 7 production-grade event handlers on UserPermissions:
  - on_user_create/update/delete: invalidate specific user cache
  - on_rolepermission_change: invalidate role-permission cache
  - on_permission_change: invalidate role-permission cache
  - on_role_change: invalidate ALL user + role-permission caches
  - on_userrole_change: invalidate specific user cache by userid
- Added _bind_rbac_events() in init.py with 13 event bindings covering:
  users C/U/D, rolepermission C/U/D, permission U, role C/U/D, userrole C/U/D
- All handlers have try/except error isolation to prevent one failure
  from breaking other handlers
- Events auto-dispatched by sqlor after C/U/D operations (no service restart needed)
- Cleaned up unused imports (DBPools, exception)
2026-05-18 12:42:17 +08:00
15079c356b feat: 支持x-api-key header认证模式
- getAuthenticationUserid增加x-api-key header检查
- 优先调用dapi模块注册的x_api_key_auth处理函数
2026-05-11 15:37:23 +08:00
e01db70dd0 fix(userperm): support ** wildcard and /main prefix in check_roles_path 2026-04-29 23:02:31 +08:00
5781621331 bugfix 2026-04-26 17:05:46 +08:00
8aada101ca fix(rbac): remove MySQL-specific SQL for cross-database compatibility
- Replace DATE_SUB(NOW(), INTERVAL 300 SECOND) with Python-level time check
- Replace NOW() with parameterized timestamps from Python
- Lockout check now done in _is_locked() function (DB-agnostic)
- All UPDATE statements use parameterized values, not DB functions
- Works with MySQL, PostgreSQL, SQLite, SQL Server, Oracle
2026-04-26 11:04:15 +08:00
622b0558b9 fix(rbac): fix high-concurrency race conditions in login and cache
1. Login lockout race condition:
   - Replace SELECT-then-UPDATE with atomic database operations
   - Lockout check now in SQL WHERE clause (DATE_SUB comparison)
   - Fail count increment: UPDATE ... SET count = count + 1 (atomic)
   - Applied to checkUserPassword, basic_auth, up_login.dspy, phone_login.dspy

2. Cache threading.Lock -> asyncio.Lock:
   - LRUCache now uses lazy-init asyncio.Lock
   - Prevents blocking the event loop in async environment
   - UserPermissions._rp_lock also uses asyncio.Lock
   - Double-check pattern in load_roleperms prevents duplicate DB loads

3. Use database NOW() instead of Python curDateString for concurrent updates
2026-04-26 10:58:13 +08:00
3fdd4efeff feat(rbac): add login tracking, lockout, secure cache
- Add created_at, last_login, login_fail_count, last_login_fail fields
- 3 failed logins locks account for 5 minutes
- LRU+TTL cache for UserPermissions, thread-safe
- All login methods update last_login
- Migration SQL for existing databases
2026-04-26 10:49:01 +08:00
4fa991b70f bugfix 2026-04-08 11:09:30 +08:00
877c7bbe19 bugfix 2026-03-24 16:02:57 +08:00
6e7098a3ef bugfix 2026-03-24 16:01:43 +08:00
d4d742ad53 bugfix 2026-03-24 16:00:32 +08:00
dbafd2cf71 bugfix 2026-03-24 15:58:54 +08:00
6ad1abd5cf bugfix 2026-03-24 15:56:41 +08:00
7d0898ee07 bugfix 2026-03-24 15:46:21 +08:00
75207ce85d bugfix 2026-03-24 15:43:24 +08:00
yumoqing
dabaec4fb6 bugfix 2026-03-21 16:14:51 +08:00
yumoqing
668e29b579 bugfix 2026-03-21 16:14:23 +08:00
yumoqing
8ddbca2a36 bugfix 2026-03-21 13:25:13 +08:00
yumoqing
ca7834abf2 bugfix 2026-03-21 13:23:49 +08:00
yumoqing
9a454eed85 bugfix 2026-03-20 21:44:37 +08:00
yumoqing
ebd3765a45 bugfix 2026-03-20 21:39:59 +08:00
yumoqing
fcb5cd8c6a bugfix 2026-03-20 21:36:58 +08:00
yumoqing
ad546f863c bugfix 2026-03-20 21:36:06 +08:00
yumoqing
5c4a6ab3c4 bugfix 2026-03-20 21:33:36 +08:00
yumoqing
4f2dd05196 bugfix 2026-03-20 21:25:53 +08:00
yumoqing
881ca4d6a8 bugfix 2026-03-20 21:23:05 +08:00
yumoqing
ee90d09ca3 bugfix 2026-03-20 21:21:43 +08:00
yumoqing
711edd035e bugfix 2026-03-20 21:20:42 +08:00
yumoqing
469bb1191c bugfix 2026-03-20 21:18:41 +08:00
yumoqing
4d50dce115 bugfix 2026-03-20 21:15:13 +08:00
yumoqing
5b1317d515 buggfix 2026-03-20 21:11:41 +08:00
yumoqing
7ed44525d1 bugfic 2026-03-20 20:49:05 +08:00
8ed0cb9c49 bugfix 2026-03-20 18:30:31 +08:00
ec32a70e67 bugfix 2026-03-20 18:29:09 +08:00
1954aa6fa8 bugfix 2026-03-20 18:28:05 +08:00
d0dbe02285 bugfix 2026-03-20 17:33:42 +08:00