The answer you're looking for may violate the ToS and/or MSA of salesforce.com, so proceed at your own risk. You can lose all your data, have it stolen, etc. No warranty or guarantee of fitness. Imagine the worst possible scenario if anything goes wrong, and double it. That's the risk we're putting out here.
That said, the solution is rather mundane, though it involves a lot of setup. It's arbitrarily difficult to actually set up something like this on purpose. If you're going to risk your data, you have to work for it.
First, create a sessionId
page:
<apex:page
showHeader="false"
contentType="text/plain"
applyBodyTag="false"
applyHtmlTag="false"
>{!$Api.Session_ID}</apex:page>
Pay close attention to spacing, a single space can get you an invalid session Id.
Create a Connected App for you to authenticate against. Give it the refresh and offline scopes, as well as API and Id scopes. You may need additional scopes as well. This is mostly theoretical, and not something I've personally done.
Create a profile with minimum access. No field or object security, etc. It should have "Api Only Access" and "Access API" permissions, and nothing else, to the maximum extent possible. The only exceptions are, of course, they'll need the permission to upload whatever kind of file you want to use.
Create an Auth. Provider for Salesforce. Then, a Remote Site setting to allow access to the Salesforce base URL. Also, you need a CORS entry to allow your remote site to access Salesforce. And a Connected App to authenticate against with offline/refresh scopes and api access, with OAuth capability.
Create a user with a Salesforce license that can use the profile you created above. I believe you can get Integration user licenses these days (or soon) that will be helpful in lowering the cost of this setup. After that, a Named Credential will be authenticated with that user, such that you now have API access from anyone that visits the page.
From here, you just need the Salesforce Base URL and the sessionId:
@AuraEnabled(cacheable=true) public static Id sessionId() {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:proxyUser/apex/sessionId');
req.setMethod('GET');
return new http().send(req).getBody().toString();
}
@AuraEnabled(cacheable=true) public static String baseUrl() {
return Url.getSalesforceBaseUrl().toExternalForm();
}
Make sure that you add the class that has these methods to the profile you created earlier. They need this to get data back.
Now, for the easy part. You've got a session Id that you can use, so we just:
@wire(getSessionId) sessionId;
@wire(getBaseUrl) baseUrl;
async uploadFile(file) {
// We're going to create form data
const form = new FormData();
// The PathOnClient attribute must be set.
form.append(
'entity_content',
new Blob([JSON.stringify({ PathOnClient: file.name })], {
type: 'application/json',
name: '',
}),
''
);
// Attach the File (or Blob) to the VersionData field.
form.append('VersionData', file);
// Wait for the upload to finish.
let result = await fetch(
`${this.baseUrl}/services/data/v57.0/sobjects/ContentVersion`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${this.sessionId}`,
},
body: form,
}
);
let contentVersion = await result.json();
// Now, contentVersion.Id is the Id, if successful.
}
You could also probably substitute a normal license for a Chatter license, and use the Chatter API endpoint for uploading files, but I haven't worked with the Chatter API extensively; I found that a straight file upload is the most straight forward.
I may have missed a step or two, and there might be some minor tweaks to some of the other steps, but this probably shouldn't take more than an hour to experiment with to see if it works.
Note that, as a later step, if you need to create the ContentDocumentLink, you can probably just use the normal createRecord
method from lightning/uiRecordApi
, no shenanigans needed there.