Skip to content

Add File Upload (SharePoint) sample: React + Vite code site via server logic + Microsoft Graph#108

Merged
tyaginidhi merged 9 commits into
mainfrom
sample/file-upload-sharepoint
Jul 2, 2026
Merged

Add File Upload (SharePoint) sample: React + Vite code site via server logic + Microsoft Graph#108
tyaginidhi merged 9 commits into
mainfrom
sample/file-upload-sharepoint

Conversation

@tyaginidhi

Copy link
Copy Markdown
Contributor

What

Adds a new File Upload to SharePoint sample under samples/spa/react/file-upload/sharepoint/ — a React + Vite code site (SPA) that uploads, lists, downloads, and deletes files in a SharePoint document library from a Power Pages code site, using server logic + Microsoft Graph.

This is part of the file-upload sample series. It complements the existing Dataverse notes / file-column and Azure Blob samples by covering the SharePoint document-library scenario.

How it works

  • The SPA calls a server logic at /_api/serverlogics/sharepointdocuments (with the CSRF token).
  • The server logic holds an Entra app (client-credentials), calls Microsoft Graph with Sites.ReadWrite.All, and fences each signed-in user to their own folder (user-<id>) derived server-side — never a client-supplied id.
  • The Graph secret never reaches the browser; the browser never talks to Graph directly.

Verb mapping: GET (list), GET ?id= (download), POST {fileName,fileContent} (upload), DELETE ?id= (delete).

Scope, limitations, and runtime quirks (documented in the README)

  • Text documents only (.txt, .csv, .json, .md, .html, .xml) — server logic's HttpClient accepts only text request bodies (no application/octet-stream), so binary files can't be streamed as raw bytes through this path.
  • 2 MB file cap — the whole file is sent inline in one request, bounded by the runtime's request-body ceiling (~2.5 MB observed); capped at 2 MB for headroom.
  • Uploads use a text/plain request content type (not application/json) — the runtime enforces a ~2 MB limit when validating a JSON body, so JSON uploads fail with HTTP 500 above it. text/plain passes the body through untouched.
  • Downloads carry an encoding: "text" | "base64" flagHttpClient returns the response body as a plain string for text-like content types but base64 for everything else (Graph serves .md/.csv as application/octet-stream); the SPA decodes accordingly so files save byte-for-byte correct.

Validation

Validated end-to-end on a live Power Pages site: all supported types round-trip; .md/.csv decode correctly from base64 (unicode preserved); 2 MB file round-trips byte-exact; uploads above the cap are rejected client-side.

The README documents Entra app registration, SharePoint site settings, server-logic setup, sign-in requirements, and deployment via pac pages upload-code-site.

tyaginidhi and others added 9 commits June 29, 2026 18:13
The fourth file-upload approach. SharePoint can't be reached from a code site
with the portal Web API (/_api is Dataverse-only) and code sites can't host the
native Liquid/basic-form Document Locations subgrid, so this sample bridges to a
SharePoint document library through a Power Automate cloud flow: the SPA calls
the registered flow (POST /_api/cloudflow/v1.0/trigger/<id> with CSRF +
X-Requested-With), and the flow does the list/upload/download/delete against the
library. One flow switches on an `operation` input; `contactId` fences each user
to their own folder. Files travel as base64 through the flow trigger, so it caps
at small files.

Ships the cloud-flow-consumer registration (Authenticated Users), a localhost
mock, and a README documenting the exact flow to build. Indexes and the
file-upload comparison updated. Build-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cloud-flow trigger caps payloads at a small size; server logic runs
server-side, keeps the Graph secret off the client, and exposes a clean
SPA-callable API, so it's the better fit. The SPA now calls
/_api/serverlogics/sharepointdocuments (CSRF); the server logic
(server-logic/sharepointDocuments.js) holds an Entra app (client-credentials),
calls Microsoft Graph (Sites.ReadWrite.All), and does list/upload/download/delete
against a SharePoint document library, fencing each user to their own folder.

Known limitation, documented: server logic's HttpClient accepts only
json/html/form-urlencoded request bodies (no octet-stream), so the sample handles
text-based documents (TXT/CSV/JSON/MD/HTML/XML) as real SharePoint files; binary
needs a different transport. Removes the cloud-flow-consumer + flow client; adds
the server logic, SharePoint/ServerLogic site settings, and an updated README +
indexes. Build-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified against official server-logic docs + the in-repo solution sample:
- Handlers now return a JSON string (runtime wraps it as Data); SPA JSON.parses Data.
- getAccessToken matches the verified token-call shape (object body + Content-Type header).
- userFolder() sanitizes folder chars and logs Server.User keys for live id-property pinning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
getAccessToken now passes the token request body as a JSON object (JSON.stringify) rather than a pre-encoded urlencoded string. The server-logic HttpClient JSON-parses the content and form-encodes it itself, so a raw a=b&c=d string fails with \'c' is an invalid start of a value\.

sharePointService.ts now reads the response envelope case-robustly (success/data/error, falling back to the documented Success/Data/Error), fixing the false 'Server logic reported a failure.' on successful uploads.

Also: rename script-validator-tripping tokens in comments/strings (delete->remove/Deletion, anonymous function->arrow), refresh the deployed bundle + declarative server-logic/source-files, and ignore the env-specific *-manifest.yml.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Download: HttpClient returns the response Body as a plain string only for text-like content types and base64 for everything else (Graph serves .md/.csv as application/octet-stream). The server logic now inspects the actual download response Content-Type and returns an explicit encoding: 'text' | 'base64' flag; the SPA base64-decodes to bytes when needed so files save byte-for-byte correct.

Upload: the browser->server-logic POST sent Content-Type application/json, which hits the runtime's ~2MB JSON request-body limit (HTTP 500 for larger files). Switched to text/plain (the handler JSON.parses Server.Context.Body regardless), lifting the cap to the runtime's overall request size.

Lowered MAX_FILE_BYTES 5MB -> 2MB (the real achievable ceiling ~2.5MB; 2MB leaves headroom for JSON escaping) and updated the validation message, UI hint, and README.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…arepoint

# Conflicts:
#	README.md
#	samples/spa/README.md
#	samples/spa/react/file-upload/README.md
Security & correctness fixes surfaced by the multi-perspective review and
verified live on the deployed site:

- Fix IDOR: download (GET ?id=) and delete now re-verify server-side that the
  target drive item lives in the caller's own user folder before acting.
  Previously any signed-in user could read/delete another user's files by
  passing their (non-secret) item id.
- Fail closed on user identity: userFolder() requires a stable GUID id and no
  longer falls back to a non-unique display name (folder-collision risk).
- Validate file name (path separators, "..", illegal chars), extension
  allowlist, and content size server-side — the SPA guard is UX only.
- Add escaping-aware payload-size guard client-side (measures the serialized
  JSON, not just file.size).
- Harden Graph error handling with IsSuccessStatusCode checks.

Docs & cleanup:
- Fix README: server logic is web-role protected, not "table permissions"
  (nothing is stored in Dataverse); clarify server-logic deploy (declarative
  vs manual paste) + note the canonical source is kept in sync with the
  snapshot copy; note the dev mock doesn't cover Graph/base64/auth.
- azure-blob README: "three samples" -> four (SharePoint added).
- Remove orphaned non-hashed web-files bundle (index.js/index.css); ship only
  the hashed bundle like the azure-blob sample.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tyaginidhi tyaginidhi enabled auto-merge (squash) July 2, 2026 12:36
Comment thread samples/spa/react/file-upload/README.md
@tyaginidhi tyaginidhi merged commit 5ec741e into main Jul 2, 2026
3 checks passed
@tyaginidhi tyaginidhi deleted the sample/file-upload-sharepoint branch July 2, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants