Reproducibly confirmed (3/3 vs 3/3 direct comparison inside the API
container) that chatgpt.com's Cloudflare WAF returns 403 for the
"tessera/1.0" User-Agent regardless of Accept header, and 200 for a
real Chrome UA string. Parameterized fetchWithRedirectGuard's
User-Agent (defaulting to the existing "tessera/1.0") and override it
only for fetchIconBytes -- the HTML-discovery path (discoverFavoriteIconUrl)
keeps its original User-Agent unchanged, per the no-regression constraint
on that flow.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A bare "image/*" Accept paired with the tessera/1.0 User-Agent tripped
Cloudflare bot mitigation on some sites -- caught live testing against
chatgpt.com/favicon.ico, which returned 403 with this combo but 200
with a realistic browser-style image Accept list. Isolated via direct
fetch comparison inside the API container before landing the fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
<img src={fav.iconUrl}> hotlinked the external favicon directly; sites
that send Cross-Origin-Resource-Policy: same-origin (e.g. claude.ai)
get blocked by the browser (net::ERR_BLOCKED_BY_RESPONSE.NotSameOrigin),
leaving only the letter fallback. Points src at the new same-origin
/api-proxy/favorites/:id/icon route instead (same pattern already used
for the user avatar image). Render guard and onError fallback unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ownership-scoped (userId, matching update/remove) icon byte proxy.
Loads the row's stored iconUrl server-side and streams it through
IconDiscoveryService.fetchIconBytes -- never accepts a client-supplied
URL, so this can't become an open SSRF proxy.
Not-found/not-owned/no-icon -> 404. Upstream fetch failure (unreachable,
timeout, non-image, SSRF-blocked) -> 502, never a 200 with a placeholder.
Success sets Cache-Control so the browser doesn't refetch every load.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extracts the manual-redirect/per-hop-revalidation/timeout loop from
fetchHtml into a shared fetchWithRedirectGuard, exports isPublicHttpUrl,
and adds fetchIconBytes() -- an image-content-type-gated, 1MB-capped
byte fetch reusing the same SSRF guard as the existing HTML discovery
path. discoverFavoriteIconUrl behavior is unchanged.
Prepares the fix for favicon hotlinks breaking on sites that send
Cross-Origin-Resource-Policy: same-origin (e.g. claude.ai) -- Tessera
will proxy the bytes through its own origin instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The admin.ldap message block referenced throughout the LDAP admin
page (t('title'), t('connectionTitle'), t('serverUrl'), field-mapping
and sync labels, etc.) didn't exist in de.json/en.json at all, so the
whole page rendered raw translation keys instead of text -- a
pre-existing gap surfaced while testing the new AD-prefill/group-filter
feature on this same page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CreateLdapConfigDto's optional fields (searchFilter, syncIntervalMin,
isActive, groupFilterDns) had class-field default initializers.
NestJS's ValidationPipe instantiates DTOs via plainToInstance, which
applies those defaults even when the field is absent from the request
body -- so any partial PATCH not including a given field silently
reset it to the hardcoded default instead of leaving it untouched.
Caught by testing the new groupFilterDns-only PATCH: saving the group
filter alone reset searchFilter back to "(objectClass=person)",
clobbering the configured Active Directory filter. The service layer
already has its own `?? default` fallback for create, so the DTO
initializers were redundant and unsafe. Removing them makes updateConfig's
existing "only set if dto.field !== undefined" pattern behave correctly
for every optional field, not just the ones sent together in one request.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New-config form now defaults to the CTL Active Directory connection
values (balios.ctl.local:3268, dc=ctl,dc=local, AD person filter,
down-level bind-DN hint) with the password left blank; editing an
existing config still shows its real saved values.
Adds a group/OU import filter section: discover AD groups/OUs via
GET /ldap/groups, toggle selection or add DNs manually, persist via
PATCH /ldap/config. Empty selection keeps today's "import everyone
under base DN" behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds listGroups() to browse AD groups/OUs under base DN, and
collectSearchEntries() to restrict syncUsersForTenant to members of
selected groups or users under selected OUs. Group DNs are matched
via escaped memberOf clauses (RFC 4515); OU DNs become extra search
bases. Empty groupFilterDns keeps the original single-base-DN search
unchanged. Controller sync endpoint and the sync scheduler both pass
groupFilterDns through so manual and scheduled syncs honor it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Persists per-tenant AD group/OU DNs to restrict which directory
entries get synced. Empty array (default) preserves current
behavior — import everyone under base DN.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
applyAccentColor() forced --sidebar-accent-foreground to the raw accent
color regardless of theme. Works in dark mode (bright text on dark-tinted
bg) but in light mode the tinted bg is near-white, so full-saturation
yellow text on pale-yellow bg was nearly invisible. Now only overrides
the foreground in dark mode; light mode keeps the theme's default
high-contrast foreground token.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Note widget toggle button rendered raw key "widgets.note.editMode"
instead of translated label — keys were never added to either locale.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
System /bin/chromium crashed during DOM rendering; switch to
--browser chromium so MCP uses Playwright's own managed browser.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phases 1, 3, 4, 6 were fully executed (all plans have SUMMARY files)
but the roadmap still showed them as In Progress/Not started. Phase 2
is the actual gap: plan 02-05 (visual verification checkpoint) was
never run.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Column already existed in production DB — migration failed with 42701.
Hotfixed via psql UPDATE on _prisma_migrations; migration SQL updated
to prevent recurrence on fresh deploys.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Column was added to schema but migration was missing, causing
PrismaClientKnownRequestError P2022 on prod API startup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dashboard-grid.test: add noCompactor to react-grid-layout mock
- note-widget.test: enable edit mode before typing (onChange is undefined
when isEditing=false), replace native dispatchEvent with fireEvent.change
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Test matcher types (toBeInTheDocument etc.) from @testing-library/jest-dom
were not globally visible to tsc because module augmentations from setup.ts
don't propagate across unconnected files in the same compilation. Excluding
test files from the main tsconfig is the standard Next.js + Vitest pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cert-manager was seeded and activated in the DB but missing from the
frontend whitelist, causing the dynamic route to always show "module
not found".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- actions.ts: mergeCertsAction(files, outputFormat, password?) builds FormData with
multiple file fields; delegates to postForm('merge', ...) (T-09-02/T-09-04)
- MergeTab.tsx: multi-file state (local), file input (multiple), output selector
(pem|pfx), Zusammenfuehren button disabled when < 2 files (data-testid for tests),
onOutputFormatChange callback to page.tsx for shared PasswordField visibility
- ConvertTab.tsx: gains pfx option + onTargetFormatChange callback (same pattern)
- page.tsx: lifts mergeOutputFormat + convertOutputFormat state; showPassword now
also true when active tab's output format is 'pfx'; passes callbacks to tabs
- cert-manager.test.tsx: 5 new tests — MergeTab disabled/enabled by file count,
shared PasswordField appears on pfx output, downloadBase64 called on success;
ConvertTab pfx option present; all 19/19 web tests green
- All production cert-manager files type-clean (pre-existing test type issues unchanged)
- Add FileResponse interface to actions.ts
- Add convertCertAction(input, targetFormat): builds FormData with
file/pemText/password + targetFormat, calls postForm convert endpoint
- Implement ConvertTab: native select for pem/der/p7b targetFormat,
Konvertieren button with loading swap, error classification, empty state
- On success: calls downloadBase64(filename, content, mimeType)
- 3 new ConvertTab tests: format selector options, downloadBase64 invoked
on success, text-destructive error on format rejection
- All 14 web cert-manager tests green
- Add FileResponse interface and FORMAT_MIME map to service
- Implement convertCert: parses any input format (PEM/DER/PFX/P7B) via
same logic as parseCert; serializes to pem/der/p7b targetFormat
- DER output uses bytesToHex→Buffer.from(hex,'hex') to avoid utf-8
corruption (Pitfall 1 / T-09-06)
- P7B output: pkcs7.createSignedData + pem.encode (PEM-wrapped PKCS7)
- Wrap all forge ops in try/catch → BadRequestException (T-09-01)
- Controller: add @Body('pemText') + reject when neither file nor pemText
- Fix: re-add NotImplementedException import for mergeCerts stub
- All 23 API cert-manager tests green (including 4 new convertCert)
- splitCerts fullchain PEM: expects count 2, two single-PEM-block certs with correct CN
- splitCerts P7B PEM bundle: expects at least one cert in result
- splitCerts malformed input: expects BadRequestException
- All three tests FAIL against NotImplementedException stub (RED confirmed)
- All 16 prior tests still pass
- Added parseCert describe block with 5 failing tests
- Covers PEM input, DER input, PFX+correct-password, PFX+wrong-password (BadRequestException), malformed input (BadRequestException)
- Existing 11 helper tests still pass
- Fixtures built via node-forge (RSA-1024, DER from asn1.toDer, PFX via toPkcs12Asn1)