From bdf48f2d78a5ed0862c49ace893bf716a562954a Mon Sep 17 00:00:00 2001 From: Tom Snelling Date: Wed, 1 Mar 2023 16:18:48 +0000 Subject: [PATCH 1/3] adds unregistered viewing config option & implements unregistered view of torrent page --- api/src/index.js | 2 +- api/src/middleware/auth.js | 5 ++ api/src/utils/validateConfig.js | 1 + client/pages/index.js | 1 + client/pages/torrent/[infoHash].js | 129 +++++++++++++++++++---------- client/utils/withAuth.js | 30 +++++-- 6 files changed, 116 insertions(+), 52 deletions(-) diff --git a/api/src/index.js b/api/src/index.js index b7f7aad..e6e3895 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -122,7 +122,7 @@ validateConfig(config).then(() => { keyGenerator: (req) => { if ( req.headers["x-forwarded-for"] && - req.headers["x-sq-server-secret"] === process.env.SQ_SERVER_SECET + req.headers["x-sq-server-secret"] === process.env.SQ_SERVER_SECRET ) return req.headers["x-forwarded-for"].split(",")[0]; return req.ip; diff --git a/api/src/middleware/auth.js b/api/src/middleware/auth.js index 9b2a81a..4da4b86 100644 --- a/api/src/middleware/auth.js +++ b/api/src/middleware/auth.js @@ -25,6 +25,11 @@ const auth = async (req, res, next) => { } catch (err) { res.status(500).send(err); } + } else if ( + req.headers["x-sq-public-access"] === "true" && + req.headers["x-sq-server-secret"] === process.env.SQ_SERVER_SECRET + ) { + next(); } else { res.sendStatus(401); } diff --git a/api/src/utils/validateConfig.js b/api/src/utils/validateConfig.js index a27a2bf..8b89282 100644 --- a/api/src/utils/validateConfig.js +++ b/api/src/utils/validateConfig.js @@ -38,6 +38,7 @@ const configSchema = yup }, {}); return yup.object(entries).required(); }), + SQ_ALLOW_UNREGISTERED_VIEW: yup.boolean().required(), SQ_BASE_URL: yup.string().matches(httpRegex).required(), SQ_API_URL: yup.string().matches(httpRegex).required(), SQ_MONGO_URL: yup.string().matches(mongoRegex).required(), diff --git a/client/pages/index.js b/client/pages/index.js index 9fb4cbd..dd504e8 100644 --- a/client/pages/index.js +++ b/client/pages/index.js @@ -188,6 +188,7 @@ export const getServerSideProps = withAuthServerSideProps( return { props: {} }; } }, + false, true ); diff --git a/client/pages/torrent/[infoHash].js b/client/pages/torrent/[infoHash].js index 001f116..3c9832d 100644 --- a/client/pages/torrent/[infoHash].js +++ b/client/pages/torrent/[infoHash].js @@ -126,7 +126,7 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => { const [comments, setComments] = useState(torrent.comments); const [isFreeleech, setIsFreeleech] = useState(torrent.freeleech); const [hasGroup, setHasGroup] = useState(!!torrent.group); - const [bookmarked, setBookmarked] = useState(torrent.fetchedBy.bookmarked); + const [bookmarked, setBookmarked] = useState(torrent.fetchedBy?.bookmarked); const { addNotification } = useContext(NotificationContext); const { setLoading } = useContext(LoadingContext); @@ -477,9 +477,20 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => { )} - + {!!userId && ( + + )} {(userRole === "admin" || userId === torrent.uploadedBy._id) && ( <> )} - + {!!userId ? ( + + ) : ( + + + + )} { display="flex" borderBottom="1px solid" borderColor="border" - pb={5} + pb={userId ? 5 : 0} mb={5} > - - - + {!!userId && ( + <> + + + + + )} { Remove this torrent )} - - - + {!!userId && ( + + + + )} {torrent.groupTorrents.length && hasGroup ? ( @@ -693,15 +726,16 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => { Comments -
+ -
@@ -780,15 +814,23 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => { }; export const getServerSideProps = withAuthServerSideProps( - async ({ token, userId, fetchHeaders, query: { infoHash } }) => { - if (!token) return { props: {} }; + async ({ + token, + userId, + fetchHeaders, + isPublicAccess, + query: { infoHash }, + }) => { + if (!token && !isPublicAccess) return { props: {} }; const { publicRuntimeConfig: { SQ_API_URL }, serverRuntimeConfig: { SQ_JWT_SECRET }, } = getConfig(); - const { id, role } = jwt.verify(token, SQ_JWT_SECRET); + const { id, role } = token + ? jwt.verify(token, SQ_JWT_SECRET) + : { id: null, role: null }; try { const torrentRes = await fetch(`${SQ_API_URL}/torrent/info/${infoHash}`, { @@ -810,7 +852,8 @@ export const getServerSideProps = withAuthServerSideProps( if (e === "banned") throw "banned"; return { props: {} }; } - } + }, + true ); export default Torrent; diff --git a/client/utils/withAuth.js b/client/utils/withAuth.js index 4ab861e..41f9eba 100644 --- a/client/utils/withAuth.js +++ b/client/utils/withAuth.js @@ -30,12 +30,20 @@ export const withAuth = (Component, noRedirect = false) => { export const withAuthServerSideProps = ( getServerSideProps, + publicAccess = false, noRedirect = false ) => { return async (ctx) => { - const { token, userId } = getReqCookies(ctx.req); + let { token, userId } = getReqCookies(ctx.req); - if (!token && !noRedirect) + const { + serverRuntimeConfig: { SQ_SERVER_SECRET }, + publicRuntimeConfig: { SQ_ALLOW_UNREGISTERED_VIEW }, + } = getConfig(); + + const isPublicAccess = publicAccess && SQ_ALLOW_UNREGISTERED_VIEW && !token; + + if (!token && !noRedirect && !isPublicAccess) return { redirect: { permanent: false, @@ -43,26 +51,32 @@ export const withAuthServerSideProps = ( }, }; - if (!token && noRedirect) return { props: {} }; + if (!token && noRedirect && !isPublicAccess) return { props: {} }; - try { - const { - serverRuntimeConfig: { SQ_SERVER_SECRET }, - } = getConfig(); + if (isPublicAccess) { + token = null; + userId = null; + } + try { const fetchHeaders = { "Content-Type": "application/json", - Authorization: `Bearer ${token}`, "X-Forwarded-For": ctx.req.headers["x-forwarded-for"] ?? ctx.req.socket.remoteAddress, "X-Sq-Server-Secret": SQ_SERVER_SECRET, + "X-Sq-Public-Access": isPublicAccess, }; + if (token) { + fetchHeaders["Authorization"] = `Bearer ${token}`; + } + const { props: ssProps } = await getServerSideProps({ ...ctx, token, userId, fetchHeaders, + isPublicAccess, }); return { props: { ...ssProps, token } }; } catch (e) { From 8c41c61d0499be9752b55053ddc4db0a05472a8a Mon Sep 17 00:00:00 2001 From: Tom Snelling Date: Thu, 2 Mar 2023 12:26:03 +0000 Subject: [PATCH 2/3] add sitemap.xml, with torrent urls if unregistered viewing enabled --- api/src/controllers/torrent.js | 9 ++++++ api/src/routes/torrent.js | 2 ++ client/pages/sitemap.xml.js | 56 ++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 client/pages/sitemap.xml.js diff --git a/api/src/controllers/torrent.js b/api/src/controllers/torrent.js index 0461de5..6ba7c9e 100644 --- a/api/src/controllers/torrent.js +++ b/api/src/controllers/torrent.js @@ -671,6 +671,15 @@ export const listLatest = (tracker) => async (req, res, next) => { } }; +export const listAll = async (req, res, next) => { + try { + const torrents = await Torrent.find({}, { infoHash: 1 }).lean(); + res.json(torrents); + } catch (e) { + next(e); + } +}; + export const searchTorrents = (tracker) => async (req, res, next) => { const { query, category, source, tag, page } = req.query; try { diff --git a/api/src/routes/torrent.js b/api/src/routes/torrent.js index 5c30dd3..f4f17ab 100644 --- a/api/src/routes/torrent.js +++ b/api/src/routes/torrent.js @@ -5,6 +5,7 @@ import { deleteTorrent, fetchTorrent, listLatest, + listAll, removeVote, searchTorrents, toggleFreeleech, @@ -28,6 +29,7 @@ export default (tracker) => { router.post("/toggle-freeleech/:infoHash", toggleFreeleech); router.post("/bookmark/:infoHash", toggleBookmark); router.get("/latest", listLatest(tracker)); + router.get("/all", listAll); router.get("/search", searchTorrents(tracker)); return router; }; diff --git a/client/pages/sitemap.xml.js b/client/pages/sitemap.xml.js new file mode 100644 index 0000000..b667930 --- /dev/null +++ b/client/pages/sitemap.xml.js @@ -0,0 +1,56 @@ +import getConfig from "next/config"; + +const Sitemap = () => {}; + +export const getServerSideProps = async ({ req, res }) => { + const { + publicRuntimeConfig: { + SQ_BASE_URL, + SQ_API_URL, + SQ_ALLOW_UNREGISTERED_VIEW, + }, + serverRuntimeConfig: { SQ_SERVER_SECRET }, + } = getConfig(); + + const urls = [SQ_BASE_URL, `${SQ_BASE_URL}/login`, `${SQ_BASE_URL}/register`]; + + if (SQ_ALLOW_UNREGISTERED_VIEW) { + try { + const listRes = await fetch(`${SQ_API_URL}/torrent/all`, { + headers: { + "Content-Type": "application/json", + "X-Forwarded-For": + req.headers["x-forwarded-for"] ?? req.socket.remoteAddress, + "X-Sq-Server-Secret": SQ_SERVER_SECRET, + "X-Sq-Public-Access": true, + }, + }); + const torrents = await listRes.json(); + for (const { infoHash } of torrents) { + urls.push(`${SQ_BASE_URL}/torrent/${infoHash}`); + } + } catch (e) { + console.error(`[sq] could not list torrents: ${e}`); + } + } + + const sitemap = ` + + ${urls + .map( + (url) => ` + ${url} + ` + ) + .join("\n")} + +`; + + res.setHeader("Content-Type", "text/xml"); + res.write(sitemap); + res.end(); + + return { props: {} }; +}; + +export default Sitemap; From 62830fb832b38b856c26e8b2c52970effee04d7b Mon Sep 17 00:00:00 2001 From: Tom Snelling Date: Thu, 2 Mar 2023 12:47:23 +0000 Subject: [PATCH 3/3] update config docs --- README.md | 3 ++- config.example.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e2b8b5..b1d42f6 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,9 @@ If your configuration is not valid, sqtracker will fail to start. | SQ_BP_EARNED_PER_GB | envs | 1 | Number of bonus points awarded to a user for each GB they upload | | SQ_BP_COST_PER_INVITE | envs | 3 | Number of bonus it costs a user to buy 1 invite (set to 0 to disable buying invites) | | SQ_BP_COST_PER_GB | envs | 3 | Number of bonus it costs a user to buy 1 GB of upload (set to 0 to disable buying upload) | -| SQ_SITE_WIDE_FREELEECH | envs | `true` | Whether or not to enable freeleech on all torrents | +| SQ_SITE_WIDE_FREELEECH | envs | `false` | Whether or not to enable freeleech on all torrents | | SQ_TORRENT_CATEGORIES | envs | `{ "Movies": ["HD", ...], "TV": [...] }` | A dictionary of categories, each with an array of zero or more sources available within that category | +| SQ_ALLOW_UNREGISTERED_VIEW | envs | `false` | Whether or not torrent pages can be viewed by unregistered users. If true, only logged in users will be able to download/interact, but anyone (search engines included) will be able to view/read torrent info. | | SQ_BASE_URL | envs | https://demo.sqtracker.dev | The URL of your tracker site | | SQ_API_URL | envs | https://demo.sqtracker.dev/api | The URL of your API. Under the recommended setup, it should be `${SQ_BASE_URL}/api` | | SQ_MONGO_URL | envs | mongodb://sq_mongodb/sq | The URL of your MongoDB server. Under the recommended setup, it should be `mongodb://sq_mongodb/sq` | diff --git a/config.example.js b/config.example.js index e8b9c89..e0432aa 100644 --- a/config.example.js +++ b/config.example.js @@ -21,6 +21,7 @@ module.exports = { SQ_BP_COST_PER_INVITE: 3, SQ_BP_COST_PER_GB: 3, SQ_SITE_WIDE_FREELEECH: false, + SQ_ALLOW_UNREGISTERED_VIEW: false, SQ_BASE_URL: "https://sqtracker.dev", SQ_API_URL: "https://sqtracker.dev/api", SQ_MONGO_URL: "mongodb://sq_mongodb",