Commit graph

23 commits

Author SHA1 Message Date
Daniel
263bd1a1c7 Security hardening: PHI encryption, argon2, DOMPurify, SRI
- App-layer AES-256-GCM crypto helper (src/utils/crypto.js)
- Nextcloud tokens encrypted at rest; transparent migration on next use
- Audio backups encrypted at rest (version byte 0x01 envelope); legacy
  rows still decrypt as-is until overwritten
- argon2id password hashing via src/utils/passwords.js with bcrypt
  fallback; bcrypt hashes rehashed to argon2id on next successful login.
  argon2 package is optional — server keeps running with bcrypt only
  until npm install adds the native dep
- PHI redactor for audit log details (src/utils/redact.js) — strips SSN,
  phone, email, DoB, long IDs; caps at 500 chars; detects note bodies
- DOMPurify (cdnjs, SRI-pinned) replaces custom regex sanitizer in
  Learning Hub content rendering
- SRI integrity hashes added for Font Awesome CSS and Chart.js
- Magic-byte file-type verification on document uploads
  (src/utils/fileType.js)
- Generic 500 error responses via src/utils/errors.js applied to
  nextcloud and audioBackups; full detail still logged server-side
- DATA_ENCRYPTION_KEY env documented in .env.example

Deploy: requires rebuild of the container image to pick up the new
files and `npm install` (adds argon2). Existing users keep working
because bcrypt stays available and crypto helpers pass through
plaintext when the key is not yet set in dev.
2026-04-14 02:49:38 +02:00
Daniel
d12d4f4e63 v6.2: Session management, password change, audit logging, refine context, UI fixes
Security:
- Add session management: users can view/revoke active sessions in Settings
- Add password change in Settings (requires current password, HIBP check)
- Force logout all sessions on password reset
- Fix logout to destroy server-side session (was only clearing cookie)
- Add trust proxy for correct client IP in rate limiting and audit logs
- Add CORS support for multiple domains (CORS_ORIGINS env var)
- Add HIBP breach check endpoint and inline warnings on password fields

Audit logging:
- Add audit logging to all 24 PHI-handling endpoints across 13 route files
- Covers: generation, transcription, TTS, refine, encounters, documents, Nextcloud
- All fire-and-forget (no response delay)

AI improvements:
- Refine now includes original source material (transcript, notes, labs)
  so AI can reference the full input when modifying output
- Add correction tracking (trackAIOutput) to sick visit and well visit tabs
- Fix sickvisit missing from encounter save noteIdMap

UI fixes:
- Non-blocking busy bar for transcription and AI generation (replaces full-screen overlay)
- Fix encounter recording: hide record button during recording (was showing two stop buttons)
- Fix ROS/PE "All WNL" stacking duplicate event handlers; add Clear buttons
- Enlarge AI instructions textarea in Learning Hub CMS

Domain:
- Primary domain now app.pedshub.com, with scribe.pedshub.com and peds.danvics.com as CORS origins
2026-04-08 20:27:45 +02:00
ifedan-ed
78bb30e42f v2.2: Remove milestone admin UI, add CMS content refresh button
REMOVED:
- Milestone editing UI from Admin Panel (per user request)
- Milestones will be managed via hardcoded static data only
- Kept backend routes and database support for future use

ADDED:
- Refresh button in Learning Hub CMS content list
- Manual refresh for AI-generated content updates
- Better discoverability of content refresh functionality

FIXES:
- AI learning content now has visible refresh button
- Users can manually refresh content list after AI generation
- Cleaner admin panel without milestone management clutter

NOTE:
- Developmental milestones still work via static fallback
- Edit milestones by modifying public/js/milestonesData.js
- Backend API still supports milestone management if needed later
2026-04-01 18:16:00 +00:00
ifedan-ed
75a3a47598 v4: Fix milestones display + add OpenID auth + 100MB PDF support
FIXES:
- Milestones now show correctly on encounter page (use static fallback if DB empty)
- Static data preserved as MILESTONES_DATA_STATIC for compatibility
- Database-driven milestones still work (admin can edit via CMS)

NEW FEATURES:
- OpenID Connect (OIDC) authentication support (PocketID, Keycloak, Azure AD, etc.)
- Comprehensive setup guide: OPENID_SETUP.md
- Auto-linking existing users by email on SSO login
- Multiple PDF upload support in Learning Hub (up to 10 files)
- 100 MB per file limit (was 20 MB)
- Full PDF content used for AI generation
- Embeddings use first ~8K chars for semantic search

IMPROVEMENTS:
- Updated UI to show multiple file selection with list
- Drag-and-drop supports multiple files
- Better file upload validation and error handling
- Added clarifying comments about embedding truncation
2026-04-01 17:59:51 +00:00
Daniel Onyejesi
114bc4e7cd Fix CMS button clickability, replace AI topic input with context box
- Add type="button" to all CMS action buttons to prevent default submit
- Change overflow:hidden to overflow:visible on .cms-main to prevent clipping
- Add z-index to .cms-editor-actions for reliable click targeting
- Replace AI "Topic" text input with a descriptive textarea context box
  so users can tell AI what to generate (AI creates its own title)
- Rename AI tab from "By Topic" to "Describe Content"
2026-03-24 11:52:02 -04:00
Daniel Onyejesi
11e4302e97 Fix Learning Hub search and content viewer isolation
Search (was completely broken):
- wireSearch() registered inside tabChanged for 'learning' tab — runs
  after component HTML is in the DOM (lh-search existed but listener
  was never attached because element was null at IIFE load time)
- Proper debounced search with 350ms delay, Escape key to clear
- Search results show count, 'X results for "query"', Clear Search button
- Empty state with helpful message; loading state while fetching

Content viewer showing feed items beneath it (CSS cascade bug):
- .lh-feed{display:flex} and .lh-category-bar{display:flex} defined
  at CSS lines 513/507 — after .hidden{display:none} at line 242
  So classList.add('hidden') was silently overridden
- showFeed() and showViewer() now use element.style.display with
  explicit values ('flex'/'block'/'none') which always win over class rules
- viewer started hidden via class="hidden"; now shown with style.display='block'
2026-03-24 03:13:22 -04:00
Daniel Onyejesi
e3da7c5bb0 Render presentations as slides in Learning Hub viewer
- New GET /api/learning/content/:slug/slides (authMiddleware only)
  Renders Marp markdown via marp-core, returns {css, slides[]} for
  any authenticated user — no moderator access required

- showViewer: when content_type === 'presentation', hides the body card
  and shows a presentation card with title, slide count, and View Slides button
  Auto-opens the slide modal immediately on load; button allows re-opening

- openSlidesFromSlug(): fetches slides from user-accessible endpoint
  then opens the existing slide preview modal (arrow keys, swipe, dots)

- Viewer already correctly hides feed/categories/search when viewing content;
  "Back to Feed" button returns to the main list
2026-03-24 03:04:14 -04:00
Daniel Onyejesi
bc588fc320 Fix login flash, quiz in presentations, revert viewer body, feed labels
Login flash:
- Auth screen now hidden by CSS default (#auth-screen{display:none !important})
- auth.js shows it only when there is no valid token or token is expired
- No more flash of login page on refresh for logged-in users

Presentation quiz option (AI Generate panel):
- Presentation type now shows optional quiz question toggle (same as article)
- Backend: when questionCount>0, returns JSON {marpMarkdown, questions[]}
  instead of plain Marp markdown
- applyAiContent fills both the Marp textarea and question blocks for presentations

Viewer revert:
- Removed the viewer presentation card change (user wants markdown as-is)
- Removed dangling viewer button wiring

Feed/CMS list:
- Presentation type shows correct icon (fa-presentation-screen) and
  label "Slides" in the CMS content list
2026-03-24 02:50:31 -04:00
Daniel Onyejesi
7c7f72f54a Fix WebDAV selection, pdf-parse, prompt(), CSP, security issues 1/2/4/5
pdf-parse (was v2, broken API):
- Downgraded to v1.1.1 — default export is a function again
- pdfParse is not a function error fixed

WebDAV file selection (style.display bug, same cascade issue):
- lh-ai-webdav-selected had inline style="display:flex" always visible
- selectWebdavFile() / deselectWebdavFile() now use element.style.display
- On select: browser div hides, selected indicator shows with file name + X
- On deselect (X): indicator hides, browser shows again

Topic context for Upload and Nextcloud tabs:
- Both tabs now have optional "Topic / context" field
- Sent to backend as 'topic' param to help AI focus the generated content

Inline refine bar (replaces window.prompt):
- Refine Body button shows/hides an amber inline input bar below generate buttons
- Enter instructions, press Apply — no browser dialog, works in all contexts

CSP: remove unsafe-inline from scriptSrc (security issue #2):
- Converted all 26 onclick= handlers in component HTML files to
  data-action / data-target / data-label attributes
- Added delegated click handler in app.js for copy/speak/nc-export actions
- script-src now 'self' only; script-src-attr 'none'

WebDAV path endpoint (security issue #5):
- New POST /api/user/webdav-path (authMiddleware only, not moderator)
- nextcloud.js updated to use new endpoint

nodemailer: already at 6.10.1 (vulnerability fixed).
2026-03-24 02:28:43 -04:00
Daniel Onyejesi
8c1550a437 Fix AI panel options: use style.display, redesign quiz card
Bug: classList.toggle('hidden') was silently ignored because
.lh-ai-opt-field{display:flex} is defined at CSS line 758, after
.hidden{display:none} at line 242 — same specificity, later rule wins.
Fix: updateAiOptions() now uses element.style.display directly (inline
styles always override class rules).

Also fix initial state: lh-ai-slides-field now uses style="display:none"
instead of class="hidden" so it's correctly hidden on load.

Panel syncs content type from editor on open (editorType → ctype select
→ updateAiOptions called immediately).

Per type:
- Quiz: word count hidden, slides hidden, quiz card visible (no toggle)
- Presentation: word count hidden, slides visible, quiz card hidden
- Article/Pearl: word count visible, slides hidden, quiz card with toggle

Quiz card redesign: gradient blue/indigo background, white header with
icon, clean body — replaces the plain grey rectangle.
2026-03-24 02:07:56 -04:00
Daniel Onyejesi
b5bfcc5489 Revert auth to localStorage tokens, fix slide padding, AI panel options
REVERT: httpOnly cookie auth removed — reverts to JWT in localStorage.
The cookie implementation broke the login page (always-hidden auth screen).
localStorage token approach is restored fully:
- Login/register return token in JSON body
- getAuthHeaders() sends Authorization: Bearer header
- Session check reads localStorage token on load
- clearSession() removes localStorage token

Fix: slide preview padding — section elements now have 40px 48px padding
so text is not flush against the frame edges.

Kept from v3.14: AI panel context-aware options (word count, slide count,
quiz question toggle), delete confirmation wording per content type.
2026-03-24 01:57:59 -04:00
Daniel Onyejesi
ad40678b6e AI panel context-aware options, delete wording, httpOnly cookie auth
AI Generate panel:
- Content type synced from editor on open
- Article/Pearl: optional word count field
- Presentation: optional slide count field
- Questions row: checkbox toggle for article/pearl (optional), always on for quiz,
  hidden for presentation; count input shown only when enabled
- Backend: passes wordCount/slideCount to prompt; questionCount=0 skips questions

Delete confirmation:
- Wording built dynamically from content type
- Presentation: no mention of questions
- Quiz: "All quiz questions will be permanently removed"
- Article/pearl: "Any quiz questions attached will also be removed"

Auth refactor — JWT → httpOnly cookie:
- Backend: setAuthCookie() sets ped_auth as httpOnly, Secure, SameSite=Lax, 7d
- Backend: clearAuthCookie() on POST /api/auth/logout (new endpoint)
- Backend: auth middleware reads ped_auth cookie first, falls back to Bearer header
- Login/register: no longer return token in JSON body; cookie is the session
- Frontend: getAuthHeaders() returns only Content-Type (no Authorization header)
- Frontend: session check uses /api/auth/me via cookie (no localStorage token)
- Frontend: clearSession() calls /api/auth/logout to clear cookie server-side
- Frontend: no token in localStorage; window.AUTH_TOKEN removed
- All FormData fetches use credentials:'same-origin' (cookie sent automatically)
- index.html: optimistic auth-screen hide (removed localStorage token check)

Tested: login, logout, /api/auth/me, admin routes, FormData uploads, cookie httpOnly
flag, 401 without cookie, cookie cleared on logout.
2026-03-24 01:46:07 -04:00
Daniel Onyejesi
6471aced55 Fix delete bar always visible, slide preview modal, Marp textarea hint
Delete bar bug:
- Root cause: .lh-delete-confirm-bar{display:flex} defined after .hidden{display:none}
  so flex overrode hidden (no !important on global .hidden rule)
- Fix: remove display:flex from base rule, use .lh-delete-confirm-bar:not(.hidden){display:flex}
  so hidden class always wins regardless of CSS order

Slide preview (replace window.open + document.write approach):
- Backend /preview-slides now returns JSON: {css, slides[]} (individual <section> HTML)
- In-page full-screen modal with dark overlay, white slide frame
- Prev/Next arrow buttons, dot indicators, slide counter
- Keyboard navigation (arrow keys, Escape to close)
- Touch/swipe support for mobile
- Overlay click closes modal
- No popups required, works in all browsers

Marp textarea:
- Placeholder updated to guide users: "Click AI Generate above to create slides"
- Makes it clear AI is the primary authoring path; textarea for review/edits
2026-03-24 01:13:55 -04:00
Daniel Onyejesi
020cc76f2f Delete confirm inline bar, lighter login, Presentation type with Marp+PPTX
Delete confirmation:
- Replaced browser confirm() popup with inline red warning bar in editor
- Shows content title, Cancel and Delete buttons inline in the editor header
- Category delete uses double-click pattern with toast feedback

Login screen:
- Changed from dark blue/purple gradient to light gray background (var(--g50))
- Card now matches 404 page style: white, subtle blue shadow, indigo border

Presentation content type:
- New 'Presentation' button in CMS toolbar (green style)
- Marp markdown textarea editor (dark code editor style) shown when type=presentation
- Body/Tiptap editor hidden; Marp section shown (toggleEditorMode)
- Preview button: renders Marp HTML via @marp-team/marp-core, opens in new tab
- Download PPTX: pure-JS pptxgenjs (no Chromium), parses slides, exports .pptx
- AI generate: returns Marp markdown directly when contentType=presentation
- Body column stores Marp markdown for presentations

Backend:
- POST /api/admin/learning/generate-pptx — pptxgenjs PPTX from Marp markdown
- POST /api/admin/learning/preview-slides — Marp HTML preview

GitHub repo set to private via API.
2026-03-24 00:59:36 -04:00
Daniel Onyejesi
936ad7e8a0 Add AI content generation to Learning Hub CMS
Backend (src/routes/learningAI.js):
- POST /api/admin/learning/ai-generate
  * Accepts: topic text, uploaded file (PDF/TXT/MD/HTML), or Nextcloud WebDAV path
  * Extracts text from PDFs via pdf-parse; plain text read directly
  * Fetches WebDAV files using stored Nextcloud credentials
  * Prompts AI to return structured JSON: title, subject, body (HTML), questions[]
  * Strips code fences, falls back to regex JSON extraction if needed
- POST /api/admin/learning/ai-refine — refine existing body with instructions
- GET  /api/admin/learning/webdav-browse — PROPFIND-based Nextcloud file browser
- POST /api/admin/learning/webdav-path — save user's default browse path

Frontend:
- AI Generate button in CMS editor header (gradient purple→blue)
- Panel with 3 source tabs: By Topic / Upload File / Nextcloud
- Drag-and-drop file dropzone with DataTransfer API support
- WebDAV file browser: navigate folders, select files, breadcrumb path
- Options: question count, content type (article/quiz/pearl), model selector, instructions
- Refine Current Body: prompt-driven AI rewrite of existing content
- Generated content auto-fills title, subject, type, Tiptap body, quiz questions

Settings:
- Nextcloud section: "Learning Hub Default Browse Path" field (shown when connected)
- Saves to webdav_learning_path column via /api/admin/learning/webdav-path

DB: ALTER TABLE users ADD COLUMN IF NOT EXISTS webdav_learning_path TEXT
2026-03-24 00:17:28 -04:00
Daniel Onyejesi
8c8b4b8153 Replace Quill with Tiptap rich text editor
- Bundle Tiptap 2 (StarterKit, Link, Underline) with esbuild into
  public/vendor/tiptap.bundle.js — fully self-hosted, no CDN needed
- Remove Quill (quill.min.js, quill.snow.css, patchQuillTooltip hack)
- Headless Tiptap editor: custom toolbar (bold/italic/underline/strike,
  headings, lists, blockquote, code, link) with active-state highlighting
- Link insertion: inline link bar below toolbar (no popup, no tooltip)
  — type URL, press Apply or Enter; Remove button to unset
- Mini editor for question text and option text (same toggle UX)
- ProseMirror content styles: headings, lists, blockquote, code, links
2026-03-23 23:32:41 -04:00
Daniel Onyejesi
7b91883e88 Fix Quill link tooltip positioning and registration flash
Quill tooltip: move tooltip element to document.body via patchQuillTooltip()
so overflow:hidden on .cms-main cannot clip it. Recalculates position from
container-relative to viewport (fixed) coordinates with viewport clamping.
Removes overflow:hidden from quill wrappers; applies border-radius to toolbar/container.

Registration: hide #show-register by default in HTML; reveal only after
registration-status fetch confirms enabled=true. Endpoint already returns 403
when disabled — no backend change needed.
2026-03-23 23:09:21 -04:00
Daniel Onyejesi
d4e57177e4 Add Quill rich text editor, multi-select questions, per-field rich text toggle
- Replace textarea+toolbar with Quill.js (free, MIT) for body editor
  - Full WYSIWYG: headings, bold/italic, lists, blockquote, code, link (inline, no popup)
- Rich Text toggle button on each question text field and option text field
  - Plain textarea by default; toggle activates mini Quill editor per field
- New 'Multiple Select' question type: checkboxes, all correct must be chosen
  - Backend scoring: allCorrectChosen && noWrongChosen
  - Results show all correct options highlighted, wrong selections flagged
- quiz display uses sanitizeHtml for question/option text (supports formatted HTML)
- sanitizeHtml allows Quill class attrs (ql-indent, etc.)
2026-03-23 22:37:29 -04:00
Daniel Onyejesi
6c0c911dbd Fix module init: defer all tab scripts until component HTML is loaded
All JS modules were running immediately (IIFE/DOMContentLoaded) but
tab HTML is lazy-loaded from /components/*.html, causing null errors
on every addEventListener call. Each module now listens for tabChanged
with its tab name and initializes once after the DOM is ready.

Also: quiz question/explanation boxes changed to textarea for
multi-line support; quiz display uses sanitizeHtml() so formatted
text (bold, lists, etc.) renders correctly.
2026-03-23 22:03:54 -04:00
Daniel Onyejesi
ffacdcbedb Fix loading race condition, improve quiz UX, editor toolbar, settings page
- Fix: component loader now fires tabChanged AFTER HTML is in DOM
  (fixes CMS/settings stuck at loading)
- CMS: separate "New Article", "New Quiz", "New Pearl" buttons
- CMS: editor toolbar with formatting buttons (bold, italic, headings,
  lists, links, tables, quotes, code, hr)
- CMS: quiz questions have numbered headers (Q1, Q2...), larger inputs,
  clear "check correct answer" labels
- CMS: validation prevents saving questions without correct answer marked
- Quiz UX: large card-style question boxes with gradient number badges,
  bigger option buttons with hover effects and selection feedback,
  checkmark/cross icons on results
- Settings: improved card spacing, input styling, section headers with
  bottom borders for visual hierarchy
- Settings: updated compliance section (removed HIPAA references, added
  BAA/institution guidelines language)
2026-03-23 20:37:35 -04:00
Daniel Onyejesi
82a0911d24 Dedicated CMS page, responsive email templates, security fixes in auth
- New dedicated Content Manager tab (WordPress-like layout) with:
  - Stats dashboard (published, drafts, categories, quizzes, attempts)
  - Sidebar with category tree and filter controls (status, category)
  - Table-style content list with search
  - Full-page editor with title bar, meta fields, body editor, quiz builder
- Content Manager visible only for admin and moderator roles in sidebar
- Removed CMS from admin panel (admin panel is admin-only again)
- Email templates: fully responsive with max-width, proper mobile breakpoints,
  table-based layout for Outlook/Gmail compatibility, no overflow
- Security: escape all user data in emails with escHtml(), escape APP_URL
  in verify-email HTML, replace raw err.message with generic errors in auth
- Security: email body from admin settings now HTML-escaped before rendering
2026-03-23 20:13:22 -04:00
Daniel Onyejesi
0327afe94c Fix quiz SQL bug, security hardening, componentize HTML, speed improvements
- Fix: "missing FROM-clause entry for table q" in quiz submission query
- Security: replace broken sanitizeHtml with allowlist-based sanitizer (prevents stored XSS)
- Security: whitelist table names in uniqueSlug() to prevent SQL injection pattern
- Security: replace raw err.message responses with generic errors in learning routes
- Security: cap admin logs query LIMIT to 500
- Refactor: break 1685-line index.html into 12 lazy-loaded component files (280 lines shell)
- Speed: add preconnect hints for Google Fonts and CDN
- Speed: defer all script tags for faster initial paint
- Speed: lazy-load tab HTML on activation (only active tab's DOM is parsed)
- Update: replace HIPAA notice with institution-guidelines cautionary note
2026-03-23 20:01:54 -04:00
Daniel Onyejesi
c6cc9ece71 v3.1: Add Learning Hub with CMS, quizzes, and 3-role user system
- New Learning Hub under Pediatric menu with content feed, category browsing,
  search, content viewer, and interactive quizzes (MCQ, true/false)
- Quiz system with per-option wrong-answer explanations and general explanations
- Admin CMS for creating/editing categories, content (articles, pearls, quizzes),
  and inline question builder with answer options
- 3-role system: admin (full access), moderator (can manage Learning Hub content),
  user (all clinical features + learning hub read access)
- 5 new database tables: learning_categories, learning_content, learning_questions,
  learning_options, learning_progress
- User progress tracking with score history per quiz
- Moderators see "Content Manager" tab instead of full Admin panel
2026-03-23 19:32:47 -04:00