1. 01 Mar, 2026 9 commits
  2. 26 Feb, 2026 4 commits
  3. 25 Feb, 2026 2 commits
  4. 24 Feb, 2026 3 commits
    • Steven's avatar
      fix(web): use BroadcastChannel to sync token refreshes across tabs · bbdc9986
      Steven authored
      When multiple tabs are open and a token expires, each tab independently
      attempts a refresh. With server-side token rotation this causes all but
      the first tab to fail, logging the user out.
      
      Add a BroadcastChannel (memos_token_sync) so that when any tab
      successfully refreshes, it broadcasts the new token to all other tabs.
      Receiving tabs adopt the token in-memory immediately, skipping their own
      refresh request and avoiding conflicts with token rotation.
      
      Falls back gracefully when BroadcastChannel is unavailable (e.g. some
      privacy modes).
      bbdc9986
    • Steven's avatar
      refactor: consolidate duplicated auth logic into auth package · 26d10212
      Steven authored
      Add ApplyToContext and AuthenticateToUser helpers to the auth package,
      then remove the duplicated auth code spread across the MCP middleware,
      file server, Connect interceptor, and gRPC-Gateway middleware.
      
      - auth.ApplyToContext: single place to set claims/user into context after Authenticate()
      - auth.AuthenticateToUser: resolves any credential (bearer token or refresh cookie) to a *store.User
      - MCP middleware: replaced manual PAT DB lookup + expiry check with Authenticator.AuthenticateByPAT
      - File server: replaced authenticateByBearerToken/authenticateByRefreshToken with AuthenticateToUser
      - Connect interceptor + Gateway middleware: replaced duplicated context-setting block with ApplyToContext
      - MCPService now accepts secret to construct its own Authenticator
      26d10212
    • Steven's avatar
      feat: add MCP server with PAT authentication · 47d94147
      Steven authored
      Embeds a Model Context Protocol (MCP) server into the Memos HTTP
      process, exposing memo operations as MCP tools at POST/GET /mcp using
      Streamable HTTP transport.
      
      Authentication is PAT-only — requests without a valid personal access
      token receive HTTP 401. Six tools are exposed: list_memos, get_memo,
      create_memo, update_memo, delete_memo, and search_memos, all scoped to the authenticated user.
      47d94147
  5. 23 Feb, 2026 8 commits
    • Steven's avatar
      chore: fix codeowners · 71263736
      Steven authored
      71263736
    • Mudkip's avatar
      ff3e4c5c
    • Steven's avatar
      fix(web): fix spurious logout on page reload with expired access token · 9ecd7b87
      Steven authored
      Two bugs caused users to be redirected to /auth too frequently:
      
      1. Race condition in Promise.all([initInstance(), initAuth()]):
         initInstance() makes a gRPC request whose auth interceptor calls
         getAccessToken() synchronously. When the access token was expired,
         getAccessToken() eagerly deleted it from localStorage as a "cleanup"
         side-effect. By the time initAuth() ran and checked hasStoredToken(),
         localStorage was already empty, so it skipped the getCurrentUser()
         call and the token refresh cycle entirely — logging the user out even
         when the refresh-token cookie was still valid. Fix: remove the
         localStorage deletion from getAccessToken(); clearAccessToken()
         (called on confirmed auth failure and logout) handles proper cleanup.
      
      2. React Query retry: 1 caused a second refresh+redirect attempt after
         auth failures. The auth interceptor already handles token refresh and
         request retry internally. If it still throws Unauthenticated, the
         redirect is already in flight — a React Query retry only fires another
         failed refresh and a redundant redirectOnAuthFailure() call. Fix: use
         a shouldRetry function that skips retries for Unauthenticated errors
         while keeping the existing once-retry behaviour for other errors.
      9ecd7b87
    • Steven's avatar
      fix(web): fix explore page showing private tags and improve stats hook · 03c30b8c
      Steven authored
      The explore page sidebar was showing tags from the current user's private
      memos because the default ListMemos query applies a server-side OR filter
      (creator_id == X || visibility in [...]), mixing private content in.
      
      Fix by using a visibility-scoped ListMemos request in the explore context
      so private memos are always excluded via the AND'd server auth filter.
      
      Also consolidate two always-firing useMemos calls into one context-aware
      query, unify activity stats computation with countBy across all branches,
      and extract a toDateString helper to remove duplicated formatting logic.
      03c30b8c
    • Steven's avatar
      fix(web): make MonthNavigator month label reactive to language changes · 1cea9b0a
      Steven authored
      Use useTranslation() hook instead of the static i18n import so that
      the month label re-computes when the language changes.
      1cea9b0a
    • Steven's avatar
      fix(store): allow memo/attachment deletion when local file is missing · 704503e5
      Steven authored
      Fixes two bugs reported in #5603:
      
      1. store/attachment.go: ignore os.ErrNotExist when removing a local
         attachment file so that a missing file on disk (broken state from
         failed uploads) no longer blocks deletion of the DB record, allowing
         memos referencing corrupt attachments to be deleted normally.
      
      2. memo_attachment_service.go: add nil guard on GetAttachment result
         before dereferencing it in SetMemoAttachments, preventing a nil
         pointer panic when an attachment UID no longer exists in the DB.
      704503e5
    • Steven's avatar
    • Steven's avatar
      fix(webhook): remediate SSRF vulnerability in webhook dispatcher · 150371d2
      Steven authored
      - Add plugin/webhook/validate.go as single source of truth for SSRF
        protection: reserved CIDR list parsed once at init(), isReservedIP(),
        and exported ValidateURL() used at registration/update time
      - Replace unguarded http.Client in webhook.go with safeClient whose
        Transport uses a custom DialContext that re-resolves hostnames at
        dial time, defeating DNS rebinding attacks
      - Call webhook.ValidateURL() in CreateUserWebhook and both
        UpdateUserWebhook paths to reject non-http/https schemes and
        reserved/private IP targets before persisting
      - Strip internal service response body from non-2xx error log messages
        to prevent data leakage via application logs
      150371d2
  6. 22 Feb, 2026 7 commits
  7. 14 Feb, 2026 1 commit
  8. 12 Feb, 2026 3 commits
  9. 11 Feb, 2026 3 commits