diff --git a/VoidCat/Controllers/AuthController.cs b/VoidCat/Controllers/AuthController.cs index 55305ed..97a2590 100644 --- a/VoidCat/Controllers/AuthController.cs +++ b/VoidCat/Controllers/AuthController.cs @@ -75,7 +75,8 @@ public class AuthController : Controller var claims = new List() { new(ClaimTypes.NameIdentifier, user.Id.ToString()), - new(ClaimTypes.Expiration, DateTimeOffset.UtcNow.AddHours(6).ToUnixTimeSeconds().ToString()) + new(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddHours(6).ToUnixTimeSeconds().ToString()), + new(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()) }; claims.AddRange(user.Roles.Select(a => new Claim(ClaimTypes.Role, a))); diff --git a/VoidCat/Controllers/StatsController.cs b/VoidCat/Controllers/StatsController.cs index f52cfc1..47ee48e 100644 --- a/VoidCat/Controllers/StatsController.cs +++ b/VoidCat/Controllers/StatsController.cs @@ -1,7 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using Prometheus; using VoidCat.Model; -using VoidCat.Services; using VoidCat.Services.Abstractions; namespace VoidCat.Controllers @@ -19,7 +17,7 @@ namespace VoidCat.Controllers } [HttpGet] - [ResponseCache(Location = ResponseCacheLocation.Client, Duration = 60)] + [ResponseCache(Location = ResponseCacheLocation.Any, Duration = 60)] public async Task GetGlobalStats() { var bw = await _statsReporter.GetBandwidth(); diff --git a/VoidCat/Controllers/UploadController.cs b/VoidCat/Controllers/UploadController.cs index 94addfa..582f08f 100644 --- a/VoidCat/Controllers/UploadController.cs +++ b/VoidCat/Controllers/UploadController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Newtonsoft.Json; @@ -8,6 +9,7 @@ using VoidCat.Services.Abstractions; namespace VoidCat.Controllers { + [EnableCors(CorsPolicy.Upload)] [Route("upload")] public class UploadController : Controller { @@ -25,14 +27,6 @@ namespace VoidCat.Controllers _paywallFactory = paywallFactory; } - [HttpOptions] - public IActionResult UploadFileOptions() - { - // just return 200 status for pre-flight calls - // manging CORS headers is not managed inside void.cat - return Ok(); - } - [HttpPost] [DisableRequestSizeLimit] [DisableFormValueModelBinding] diff --git a/VoidCat/Model/Roles.cs b/VoidCat/Model/Roles.cs index de15645..bd550cd 100644 --- a/VoidCat/Model/Roles.cs +++ b/VoidCat/Model/Roles.cs @@ -9,4 +9,10 @@ public static class Roles public static class Policies { public const string RequireAdmin = "RequireAdmin"; +} + +public static class CorsPolicy +{ + public const string Default = "default"; + public const string Upload = "upload"; } \ No newline at end of file diff --git a/VoidCat/Model/VoidSettings.cs b/VoidCat/Model/VoidSettings.cs index 536e4e6..3f2b925 100644 --- a/VoidCat/Model/VoidSettings.cs +++ b/VoidCat/Model/VoidSettings.cs @@ -15,6 +15,8 @@ namespace VoidCat.Model public StrikeApiSettings? Strike { get; init; } public SmtpSettings? Smtp { get; init; } + + public List CorsOrigins { get; init; } = new(); } public sealed record TorSettings(Uri TorControl, string PrivateKey, string ControlPassword); diff --git a/VoidCat/Program.cs b/VoidCat/Program.cs index 03d5d6b..0073db8 100644 --- a/VoidCat/Program.cs +++ b/VoidCat/Program.cs @@ -33,6 +33,24 @@ if (useRedis) services.AddSingleton(cx.GetDatabase()); } +services.AddCors(opt => +{ + opt.AddPolicy(CorsPolicy.Default, p => + { + p.AllowAnyMethod() + .AllowAnyHeader() + .WithOrigins(voidSettings.CorsOrigins.Select(a => a.OriginalString).ToArray()); + }); + + opt.AddPolicy(CorsPolicy.Upload, p => + { + p.AllowCredentials() + .AllowAnyMethod() + .WithHeaders("V-Content-Type", "V-Filename", "V-Digest", "Content-Type", "Authorization") + .WithOrigins(voidSettings.CorsOrigins.Select(a => a.OriginalString).ToArray()); + }); +}); + services.AddRouting(); services.AddControllers().AddNewtonsoftJson((opt) => { @@ -112,6 +130,7 @@ app.UseStaticFiles(); #endif app.UseRouting(); +app.UseCors(CorsPolicy.Default); app.UseAuthentication(); app.UseAuthorization(); diff --git a/VoidCat/spa/src/Admin/FileList.js b/VoidCat/spa/src/Admin/FileList.js index 106dad6..f71c0ca 100644 --- a/VoidCat/spa/src/Admin/FileList.js +++ b/VoidCat/spa/src/Admin/FileList.js @@ -1,6 +1,6 @@ import moment from "moment"; import {Link} from "react-router-dom"; -import {useDispatch, useSelector} from "react-redux"; +import {useDispatch} from "react-redux"; import {useEffect, useState} from "react"; import {FormatBytes} from "../Util"; import {useApi} from "../Api"; @@ -8,9 +8,8 @@ import {logout} from "../LoginState"; import {PagedSortBy, PageSortOrder} from "../Const"; import {PageSelector} from "../PageSelector"; -export function FileList(props) { +export function FileList() { const {AdminApi} = useApi(); - const auth = useSelector((state) => state.login.jwt); const dispatch = useDispatch(); const [files, setFiles] = useState(); const [page, setPage] = useState(0); @@ -24,7 +23,7 @@ export function FileList(props) { sortBy: PagedSortBy.Date, sortOrder: PageSortOrder.Dsc }; - let req = await AdminApi.fileList(auth, pageReq); + let req = await AdminApi.fileList(pageReq); if (req.ok) { setFiles(await req.json()); } else if (req.status === 401) { @@ -37,7 +36,7 @@ export function FileList(props) { async function deleteFile(e, id) { e.target.disabled = true; if (window.confirm(`Are you sure you want to delete: ${id}?`)) { - let req = await AdminApi.deleteFile(auth, id); + let req = await AdminApi.deleteFile(id); if (req.ok) { setFiles({ ...files, @@ -75,7 +74,7 @@ export function FileList(props) { if (accessDenied === true) { return

Access Denied

; } - + return ( diff --git a/VoidCat/spa/src/Admin/UserList.js b/VoidCat/spa/src/Admin/UserList.js index ccf1e87..da82f42 100644 --- a/VoidCat/spa/src/Admin/UserList.js +++ b/VoidCat/spa/src/Admin/UserList.js @@ -1,4 +1,4 @@ -import {useDispatch, useSelector} from "react-redux"; +import {useDispatch} from "react-redux"; import {useEffect, useState} from "react"; import {PagedSortBy, PageSortOrder} from "../Const"; import {useApi} from "../Api"; @@ -8,7 +8,6 @@ import moment from "moment"; export function UserList() { const {AdminApi} = useApi(); - const auth = useSelector((state) => state.login.jwt); const dispatch = useDispatch(); const [users, setUsers] = useState(); const [page, setPage] = useState(0); @@ -22,12 +21,12 @@ export function UserList() { sortBy: PagedSortBy.Id, sortOrder: PageSortOrder.Asc }; - let req = await AdminApi.userList(auth, pageReq); + let req = await AdminApi.userList(pageReq); if (req.ok) { setUsers(await req.json()); } else if (req.status === 401) { dispatch(logout()); - } else if(req.status === 403) { + } else if (req.status === 403) { setAccessDenied(true); } } @@ -47,7 +46,7 @@ export function UserList() { ); } - + useEffect(() => { loadUserList(); }, [page]); @@ -55,7 +54,7 @@ export function UserList() { if (accessDenied === true) { return

Access Denied

; } - + return (
diff --git a/VoidCat/spa/src/Api.js b/VoidCat/spa/src/Api.js index 554ee7f..f9d6f3e 100644 --- a/VoidCat/spa/src/Api.js +++ b/VoidCat/spa/src/Api.js @@ -4,12 +4,12 @@ import {ApiHost} from "./Const"; export function useApi() { const auth = useSelector(state => state.login.jwt); - async function getJson(method, url, body) { + async function getJson(method, url, body, token) { let headers = { "Accept": "application/json" }; - if (auth) { - headers["Authorization"] = `Bearer ${auth}`; + if (token) { + headers["Authorization"] = `Bearer ${token}`; } if (body) { headers["Content-Type"] = "application/json"; @@ -18,20 +18,21 @@ export function useApi() { return await fetch(`${ApiHost}${url}`, { method, headers, + mode: "cors", body: body ? JSON.stringify(body) : undefined }); } return { AdminApi: { - fileList: (pageReq) => getJson("POST", "/admin/file", pageReq), - deleteFile: (id) => getJson("DELETE", `/admin/file/${id}`), - userList: (pageReq) => getJson("POST", `/admin/user`, pageReq) + fileList: (pageReq) => getJson("POST", "/admin/file", pageReq, auth), + deleteFile: (id) => getJson("DELETE", `/admin/file/${id}`, undefined, auth), + userList: (pageReq) => getJson("POST", `/admin/user`, pageReq, auth) }, Api: { stats: () => getJson("GET", "/stats"), fileInfo: (id) => getJson("GET", `/upload/${id}`), - setPaywallConfig: (id, cfg) => getJson("POST", `/upload/${id}/paywall`, cfg), + setPaywallConfig: (id, cfg) => getJson("POST", `/upload/${id}/paywall`, cfg, auth), createOrder: (id) => getJson("GET", `/upload/${id}/paywall`), getOrder: (file, order) => getJson("GET", `/upload/${file}/paywall/${order}`), login: (username, password) => getJson("POST", `/auth/login`, {username, password}),