Hi! I’d like to be able to use the AdminAPI client library to be able to create and edit posts from my Obsidian vault (electron-based markdown note taking app).
I am working on a plugin for Obsidian to do just this. Any user who wants to use the plugin would have to enter their own API key into the plugin settings cache.
here is my repo for the plugin, specifically the instantiation of the GhostAdminApiClient
but I am unable to POST or PUT a post to my ghost site because of a CORS error
Access to XMLHttpRequest at ‘https://{myGhostUrl}/ghost/api/admin/posts/{postId}/?source=html’ from origin ‘app://obsidian.md’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
I have read and understand that this admin api should not be used in a front-end client setting as to now expose the admin API key and capabilities but in this case since the user is entering their own key and not exposing it elsewhere I’m wondering if there’s a way I can work around this restriction? I haven’t found anything in my own searching…
This is a problem with a setting on the server side. Where are you hosting Ghost? I have a product that successfully makes calls from the web browser, both to self hosted and Ghost Pro, so it’s doable!
I see I’m treading a path well-trod. I ran into the exact same issue while trying to build basically the exact same extension.
The Admin API JS client uses Axios, which in turn uses XMLHttpRequest to make requests. This is subject to CORS enforcement. Ghost isn’t returning any CORS headers, so the request fails pre-flight checks and isn’t performed.
I found the Ghost code that determines whether the CORS headers are returned. The origin header on the request must match one of the following:
localhost
The admin URL host
The main page URL host
The IP address of the server
As a test, I set admin__url: app://obsidian.md in my docker-compose.yml to configure the admin URL. This obviously completely broke the admin page, which now attempts to redirect to that URL. However, it also made the server return the appropriate headers, enabling the plugin requests to work.
Would the Obsidian team consider allowing users to configure additional allowed hosts when creating custom integrations? Or is there a better approach I’m not considering?
yeah I get that it isn’t the simplest solution, though the change would be small and hopefully not too hard to keep up with upstream.
with obsidian being closed source it would probably be the fastest solution, rather than trying to petition a feature to obsidian or a ghost. I am not all that experienced in solving this type of problem though, so it’s highly possible I’m missing some idea
The main one seems to be using Obsidian’s requestUrl method to interact with Ghost’s Admin API, it doesn’t add an Origin header so it bypasses CORS. You can either make requests to the Admin API directly or you can use the @tryghost/admin-api package and override makeRequest when passing options in so it uses requestUrl instead of axios. The default makeRequest implementation can be seen here
I appreciate the input. I hadn’t considered overriding the makeRequest implementation, but I see the SDK is designed in a way that makes that straightforward. I suspect that may make the plugin inoperable on mobile Obsidian, but I’ll explore further. Thank you!
I’m writing a plugin uploading images from obsidian to ghost. When running the code bellow it triggers CORS due to the use of axios (SDK). See also CORS using admin api from electron app (Obsidian) - #6 by subract - however I see no one who has published the code or similar to how they salvaged the issue.
To Reproduce
Code bellow and run the plugin. (Ignore if there's a parenthesis missing or something, I went back to try and reconstruct it).
async function uploadImages(html: string) {
// Find images that Ghost Upload supports
let imageRegex = /!*\[\[(.*?)\]\]/g;
let imagePromises = [];
// Get full-path to images
let imageDirectory: string;
let adapter = app.vault.adapter;
if (adapter instanceof FileSystemAdapter) {
imageDirectory = adapter.getBasePath(); // Vault directory
if (settings.screenshotsFolder) {
imageDirectory = `${imageDirectory}${settings.screenshotsFolder}`;
}
if (frontmatter.imageDirectory) { // Extends the image directory
imageDirectory = `${imageDirectory}${frontmatter.imageDirectory}`;
}
}
console.log("Image Directory", imageDirectory);
let result: RegExpExecArray | null; // Declare the 'result' variable
while((result = imageRegex.exec(html)) !== null) {
let file = `${imageDirectory}/${result[1]}`;
// Upload the image, using the original matched filename as a reference
imagePromises.push(api.images.upload({
ref: file,
file: file
}));
}
return Promise
.all(imagePromises)
.then(images => {
images.forEach(image => html = html.replace(image.ref, image.url));
return html;
});
}
Execute the plugin in obsidian and get the following:
Access to XMLHttpRequest at 'http://localhost:2368/ghost/api/v4/admin/images/upload/' from origin 'app://obsidian.md' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If you have solved this issue and if you could post the code snippets I would greatly appreciate it.