Commit Graph

16 Commits

Author SHA1 Message Date
matevip
e0ce5e2ea7 fix(sso): gate SSO runtime beans on mateclaw.sso.enabled
SsoService / SsoStateService / SsoController / SsoProviderRegistry were
unconditional component-scanned beans, but their configuration
(SsoProperties) is only registered by the conditional auto-configuration.
With SSO disabled (the default) the services were still instantiated and
startup failed: "required a bean of type SsoProperties that could not be
found". Gate the four beans on the same mateclaw.sso.enabled=true
condition so the SSO stack loads as a unit — disabled = no beans and no
exposed endpoints; enabled = the auto-configuration provides SsoProperties
and everything wires.

Verified: backend starts clean with SSO disabled (default).
2026-06-26 10:27:16 +08:00
matevip
b048718298 fix(sso): inline FQN→import + harden auto-create orphan rollback
- Replace inline fully-qualified names with top-of-file imports across
  SsoService / SsoStateService / FeishuSsoProvider (ObjectMapper, Autowired,
  Map.of, Date, DuplicateKeyException, Mac, URLEncoder) per code style.
- createSsoUser: roll back the freshly inserted user on any non-duplicate
  identity-insert failure, preventing passwordless orphan accounts. The two
  inserts share no transaction — the method is self-invoked and the enclosing
  callback performs a network call, so a method-level @Transactional would not
  apply; an explicit rollback in the catch is the correct guard here.
2026-06-26 10:09:43 +08:00
倪程伟
03a6d61131
feat(sso): 飞书 OAuth2 单点登录 (ISSUE #405 P0) (#419)
* feat(sso): feishu OAuth2 single sign-on (ISSUE #405 P0)

Implements the SSO design (ISSUE #405) with feishu as the first IdP
and a generic OAuth2 provider abstraction for future dingtalk/wecom
extensions. SSO is disabled by default — existing deployments are
unaffected until mateclaw.sso.enabled=true.

Backend:
- SsoProvider interface + SsoUserInfo record: generic IdP abstraction
- FeishuSsoProvider: OAuth2 authorization-code flow (app_access_token
  with Caffeine cache → user_access_token → user info). apiBase switches
  between feishu.cn / larksuite.com by domain config.
- SsoProviderRegistry: conditional registration, lists enabled providers
- SsoStateService: HMAC-signed OAuth2 state + self-contained bind_token
  JWT, both persisted to sso_state DB table for multi-node correctness.
  State is one-time-consumable (conditional UPDATE), bind_token jti
  anti-replay via PK insert. Hourly ShedLock purge (LambdaQuery + Java
  time, works on all 3 dialects).
- SsoService: authorize/callback/bind, user mapping (union_id first →
  external_id fallback), auto-create with concurrent idempotency
  (DuplicateKeyException → rollback orphan user → re-query), link-only
  mode issues bind_token for existing-account binding.
- SsoController: 4 endpoints (/providers, /authorize, /callback, /bind)
  all permitAll.
- V159 migration (h2/mysql/kingbase): mate_user_external_identity,
  sso_state, ALTER mate_user.password NULL (SSO-only users).
- AuthService: generateToken promoted to public; login() guards
  password=null (SSO-only users cannot password-login).
- SecurityConfig: /auth/sso/** added to permitAll whitelist.
- LoginRateLimitFilter: expanded to cover /auth/sso/bind (brute-force
  surface equivalent to /auth/login).
- application.yml: mateclaw.sso.* config block (all env-var driven).

Frontend:
- Login.vue: dynamic SSO buttons (only shown when providers configured),
  OAuth2 callback detection (?sso=callback), link-only bind dialog,
  shared applyLogin flow (localStorage + workspace + route).
- api/index.ts: ssoApi (providers, authorize, callback, bind).

Tests: SsoStateServiceTest (11) — state issue/verify/replay/tamper,
bind_token issue/verify/anti-replay/garbage. Regression: PAT (23) +
Approval resolve (13) all green.

Not in scope (P1/P2): link-only bind/unbind management endpoints,
user enable/disable endpoint, dingtalk/wecom providers, admin SSO
config page. Workspace assignment for auto-created users remains a
product decision (design doc §12 item 2).

* fix(sso): self-review fixes — P0 security + P1 quality

P0-1 BindRequired serialization: replaced the R.fail(200, Map.toString())
hack with a structured SsoCallbackResponse record. Controller no longer
catches an exception for a non-error path; frontend reads bindRequired
flag directly instead of regex-parsing a stringified map.

P0-2 createSsoUser unbounded recursion: added a retry flag — second
DuplicateKeyException (extreme race where identity was concurrently
deleted) now throws a 503 instead of recursing to stack overflow.

P0-3 state TTL not enforced: verifyState's conditional UPDATE now
includes created_at > cutoff, so a state unused for 5+ min is rejected
at consumption time, not just at the 1h purge. Without this the 5-min
window was advisory only.

P1-5 SsoStateService unused ObjectMapper: removed dead injection.

P1-6 audit JSON string concat: replaced with ObjectMapper serialization
(provider/externalId no longer risk breaking the JSON structure).

P1-7 LoginRateLimitFilter shared counter: documented the intentional
decision that login + bind share a per-IP counter (same brute-force
surface) with guidance on switching to per-path if finer isolation
is needed.
2026-06-26 10:00:31 +08:00
matevip
2f5e06f286 fix(approval): three minor issues surfaced by end-to-end testing 2026-05-27 14:08:12 +08:00
matevip
fe072191ea feat(approval): REST surface for auto-grant strategies with tiered authorization 2026-05-27 14:07:56 +08:00
matevip
a9c2d45790 Harden goal approval and workspace flows 2026-05-23 22:55:16 +08:00
matevip
3ba22b99f3 feat(workspace,system): add @RequireGlobalAdmin and gate admin-only controllers 2026-05-15 10:18:10 +08:00
matevip
a8d94681e5 fix(auth): suppress tokenHash from PAT JSON responses 2026-05-02 15:41:47 +08:00
matevip
9411dfff7b fix(auth): move PersonalAccessTokenMapper under repository/ for @MapperScan 2026-05-02 15:41:41 +08:00
matevip
3c33422a08 feat(auth): personal access tokens for headless / CI integration 2026-05-02 15:41:34 +08:00
matevip
03c8584910 fix(channels): resolve issue #19 — non-admin member errors on channel page
Three bugs surfaced when a non-admin workspace member opened the channel
admin page:

- vue-i18n "Invalid linked format" when '@' appeared in message strings
  without the linked-format escape. Replaced literal '@' with vue-i18n v9
  literal interpolation {'@'} in both zh-CN.ts and en-US.ts (6 strings:
  QQ guide step3, accessControl requireMention/Tooltip).

- 403 from WorkspaceAccessInterceptor was being treated as 401 by the
  axios interceptor and the chat SSE handler, clearing the token and
  redirecting to /login. Split the two:
    * 401 = authentication failure  -> handleAuthFailure (logout)
    * 403 = authorization failure   -> keep session, surface to caller
  Now a member who lacks workspace permission sees a toast instead of
  being silently logged out.

- Two backend exception sites threw with the default code=500 for what
  is semantically an auth/authz event, contradicting the codes returned
  elsewhere for the same business event:
    * AuthService.login() bad credentials  500 -> 401
    * WorkspaceService.requirePermission() 500 -> 403
  This aligns service-layer denials with SecurityConfig (401 for missing
  JWT) and WorkspaceAccessInterceptor (403 for permission denied), so
  the same business event always produces the same code.
2026-04-27 19:31:46 +08:00
matevip
762218cd32 refactor(db): drop @TableLogic on all entities, purge soft-deleted rows 2026-04-18 23:31:48 +08:00
matevip
8fbe30d7ac feat(auth): password change dialog + workspace member provisioning improvements 2026-04-17 00:11:10 +08:00
matevip
af9addaaec feat(i18n): structured i18n keys for exceptions and auto-translate 100+ error messages 2026-04-11 17:52:25 +08:00
matevip
3d58a48eae feat(platform): add workspace foundation and channel execution upgrades 2026-04-09 10:29:16 +08:00
matevip
579d60125b Initial commit: MateClaw — Java + Vue 3 AI Assistant System
Full-stack AI assistant built on Spring AI Alibaba.
Features: ReAct Agent, Plan-and-Execute, MCP Protocol, Multi-Model, Multi-Channel.

Apache-2.0 License
2026-04-04 19:03:49 +08:00