Add File Upload (SharePoint) sample: React + Vite code site via server logic + Microsoft Graph#108
Merged
Merged
Conversation
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>
priyanshu92
approved these changes
Jul 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
/_api/serverlogics/sharepointdocuments(with the CSRF token).Sites.ReadWrite.All, and fences each signed-in user to their own folder (user-<id>) derived server-side — never a client-supplied id.Verb mapping:
GET(list),GET ?id=(download),POST {fileName,fileContent}(upload),DELETE ?id=(delete).Scope, limitations, and runtime quirks (documented in the README)
.txt,.csv,.json,.md,.html,.xml) — server logic'sHttpClientaccepts only text request bodies (noapplication/octet-stream), so binary files can't be streamed as raw bytes through this path.text/plainrequest content type (notapplication/json) — the runtime enforces a ~2 MB limit when validating a JSON body, so JSON uploads fail with HTTP 500 above it.text/plainpasses the body through untouched.encoding: "text" | "base64"flag —HttpClientreturns the response body as a plain string for text-like content types but base64 for everything else (Graph serves.md/.csvasapplication/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/.csvdecode 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.