Skip to content

Commit d6930ae

Browse files
Extends CrudApiPlugin with support for CORS. Closes #1191 (#1192)
1 parent 4144783 commit d6930ae

File tree

2 files changed

+48
-12
lines changed

2 files changed

+48
-12
lines changed

dev-proxy-plugins/Mocks/CrudApiPlugin.cs

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public class CrudApiConfiguration
6565
{
6666
public string ApiFile { get; set; } = "api.json";
6767
public string BaseUrl { get; set; } = string.Empty;
68+
[JsonPropertyName("enableCors")]
69+
public bool EnableCORS { get; set; } = true;
6870
public string DataFile { get; set; } = string.Empty;
6971
public IEnumerable<CrudApiAction> Actions { get; set; } = [];
7072
[System.Text.Json.Serialization.JsonConverter(typeof(JsonStringEnumConverter))]
@@ -165,6 +167,13 @@ protected virtual Task OnRequestAsync(object? sender, ProxyRequestArgs e)
165167
return Task.CompletedTask;
166168
}
167169

170+
if (IsCORSPreflightRequest(request) && _configuration.EnableCORS)
171+
{
172+
SendEmptyResponse(HttpStatusCode.NoContent, e.Session);
173+
Logger.LogRequest("CORS preflight request", MessageType.Mocked, new LoggingContext(e.Session));
174+
return Task.CompletedTask;
175+
}
176+
168177
if (!AuthorizeRequest(e))
169178
{
170179
SendUnauthorizedResponse(e.Session);
@@ -193,6 +202,35 @@ protected virtual Task OnRequestAsync(object? sender, ProxyRequestArgs e)
193202
return Task.CompletedTask;
194203
}
195204

205+
private static bool IsCORSPreflightRequest(Request request)
206+
{
207+
return request.Method == "OPTIONS" &&
208+
request.Headers.Any(h => h.Name.Equals("Origin", StringComparison.OrdinalIgnoreCase));
209+
}
210+
211+
private void AddCORSHeaders(Request request, List<HttpHeader> headers)
212+
{
213+
var origin = request.Headers.FirstOrDefault(h => h.Name.Equals("Origin", StringComparison.OrdinalIgnoreCase))?.Value;
214+
if (string.IsNullOrEmpty(origin))
215+
{
216+
return;
217+
}
218+
219+
headers.Add(new HttpHeader("access-control-allow-origin", origin));
220+
221+
if (_configuration.EntraAuthConfig is not null)
222+
{
223+
headers.Add(new HttpHeader("access-control-allow-headers", "authorization"));
224+
}
225+
226+
var methods = string.Join(", ", _configuration.Actions
227+
.Where(a => a.Method is not null)
228+
.Select(a => a.Method)
229+
.Distinct());
230+
231+
headers.Add(new HttpHeader("access-control-allow-methods", methods));
232+
}
233+
196234
private bool AuthorizeRequest(ProxyRequestArgs e, CrudApiAction? action = null)
197235
{
198236
var authType = action is null ? _configuration.Auth : action.Auth;
@@ -304,12 +342,12 @@ private static bool HasPermission(string permission, string permissionString)
304342
return permissions.Contains(permission, StringComparer.OrdinalIgnoreCase);
305343
}
306344

307-
private static void SendUnauthorizedResponse(SessionEventArgs e)
345+
private void SendUnauthorizedResponse(SessionEventArgs e)
308346
{
309347
SendJsonResponse("{\"error\":{\"message\":\"Unauthorized\"}}", HttpStatusCode.Unauthorized, e);
310348
}
311349

312-
private static void SendNotFoundResponse(SessionEventArgs e)
350+
private void SendNotFoundResponse(SessionEventArgs e)
313351
{
314352
SendJsonResponse("{\"error\":{\"message\":\"Not found\"}}", HttpStatusCode.NotFound, e);
315353
}
@@ -327,25 +365,19 @@ private static string ReplaceParams(string query, IDictionary<string, string> pa
327365
return result;
328366
}
329367

330-
private static void SendEmptyResponse(HttpStatusCode statusCode, SessionEventArgs e)
368+
private void SendEmptyResponse(HttpStatusCode statusCode, SessionEventArgs e)
331369
{
332370
var headers = new List<HttpHeader>();
333-
if (e.HttpClient.Request.Headers.Any(h => h.Name == "Origin"))
334-
{
335-
headers.Add(new HttpHeader("access-control-allow-origin", "*"));
336-
}
371+
AddCORSHeaders(e.HttpClient.Request, headers);
337372
e.GenericResponse("", statusCode, headers);
338373
}
339374

340-
private static void SendJsonResponse(string body, HttpStatusCode statusCode, SessionEventArgs e)
375+
private void SendJsonResponse(string body, HttpStatusCode statusCode, SessionEventArgs e)
341376
{
342377
var headers = new List<HttpHeader> {
343378
new("content-type", "application/json; charset=utf-8")
344379
};
345-
if (e.HttpClient.Request.Headers.Any(h => h.Name == "Origin"))
346-
{
347-
headers.Add(new HttpHeader("access-control-allow-origin", "*"));
348-
}
380+
AddCORSHeaders(e.HttpClient.Request, headers);
349381
e.GenericResponse(body, statusCode, headers);
350382
}
351383

schemas/v0.28.0/crudapiplugin.apifile.schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
"type": "string",
1313
"description": "Base URL where Dev Proxy exposes the API. Dev Proxy prepends this base URL to the URLs defined in actions."
1414
},
15+
"enableCors": {
16+
"type": "boolean",
17+
"description": "Set to true to enable CORS for the API. Default is true."
18+
},
1519
"dataFile": {
1620
"type": "string",
1721
"description": "Path to the file that contains the data for the API. The file must define a JSON array."

0 commit comments

Comments
 (0)