Skip to content

Commit ea5324d

Browse files
authored
Merge pull request tdjsnelling#3 from tdjsnelling/feature/unregistered-viewing
Feature/unregistered viewing
2 parents 9e341a3 + 62830fb commit ea5324d

11 files changed

Lines changed: 186 additions & 53 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ If your configuration is not valid, sqtracker will fail to start.
8080
| SQ_BP_EARNED_PER_GB | envs | 1 | Number of bonus points awarded to a user for each GB they upload |
8181
| 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) |
8282
| 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) |
83-
| SQ_SITE_WIDE_FREELEECH | envs | `true` | Whether or not to enable freeleech on all torrents |
83+
| SQ_SITE_WIDE_FREELEECH | envs | `false` | Whether or not to enable freeleech on all torrents |
8484
| SQ_TORRENT_CATEGORIES | envs | `{ "Movies": ["HD", ...], "TV": [...] }` | A dictionary of categories, each with an array of zero or more sources available within that category |
85+
| 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. |
8586
| SQ_BASE_URL | envs | https://demo.sqtracker.dev | The URL of your tracker site |
8687
| 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` |
8788
| 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` |

api/src/controllers/torrent.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,15 @@ export const listLatest = (tracker) => async (req, res, next) => {
674674
}
675675
};
676676

677+
export const listAll = async (req, res, next) => {
678+
try {
679+
const torrents = await Torrent.find({}, { infoHash: 1 }).lean();
680+
res.json(torrents);
681+
} catch (e) {
682+
next(e);
683+
}
684+
};
685+
677686
export const searchTorrents = (tracker) => async (req, res, next) => {
678687
const { query, category, source, tag, page } = req.query;
679688
try {

api/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ validateConfig(config).then(() => {
122122
keyGenerator: (req) => {
123123
if (
124124
req.headers["x-forwarded-for"] &&
125-
req.headers["x-sq-server-secret"] === process.env.SQ_SERVER_SECET
125+
req.headers["x-sq-server-secret"] === process.env.SQ_SERVER_SECRET
126126
)
127127
return req.headers["x-forwarded-for"].split(",")[0];
128128
return req.ip;

api/src/middleware/auth.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ const auth = async (req, res, next) => {
2525
} catch (err) {
2626
res.status(500).send(err);
2727
}
28+
} else if (
29+
req.headers["x-sq-public-access"] === "true" &&
30+
req.headers["x-sq-server-secret"] === process.env.SQ_SERVER_SECRET
31+
) {
32+
next();
2833
} else {
2934
res.sendStatus(401);
3035
}

api/src/routes/torrent.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
deleteTorrent,
66
fetchTorrent,
77
listLatest,
8+
listAll,
89
removeVote,
910
searchTorrents,
1011
toggleFreeleech,
@@ -28,6 +29,7 @@ export default (tracker) => {
2829
router.post("/toggle-freeleech/:infoHash", toggleFreeleech);
2930
router.post("/bookmark/:infoHash", toggleBookmark);
3031
router.get("/latest", listLatest(tracker));
32+
router.get("/all", listAll);
3133
router.get("/search", searchTorrents(tracker));
3234
return router;
3335
};

api/src/utils/validateConfig.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const configSchema = yup
3838
}, {});
3939
return yup.object(entries).required();
4040
}),
41+
SQ_ALLOW_UNREGISTERED_VIEW: yup.boolean().required(),
4142
SQ_BASE_URL: yup.string().matches(httpRegex).required(),
4243
SQ_API_URL: yup.string().matches(httpRegex).required(),
4344
SQ_MONGO_URL: yup.string().matches(mongoRegex).required(),

client/pages/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export const getServerSideProps = withAuthServerSideProps(
188188
return { props: {} };
189189
}
190190
},
191+
false,
191192
true
192193
);
193194

client/pages/sitemap.xml.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import getConfig from "next/config";
2+
3+
const Sitemap = () => {};
4+
5+
export const getServerSideProps = async ({ req, res }) => {
6+
const {
7+
publicRuntimeConfig: {
8+
SQ_BASE_URL,
9+
SQ_API_URL,
10+
SQ_ALLOW_UNREGISTERED_VIEW,
11+
},
12+
serverRuntimeConfig: { SQ_SERVER_SECRET },
13+
} = getConfig();
14+
15+
const urls = [SQ_BASE_URL, `${SQ_BASE_URL}/login`, `${SQ_BASE_URL}/register`];
16+
17+
if (SQ_ALLOW_UNREGISTERED_VIEW) {
18+
try {
19+
const listRes = await fetch(`${SQ_API_URL}/torrent/all`, {
20+
headers: {
21+
"Content-Type": "application/json",
22+
"X-Forwarded-For":
23+
req.headers["x-forwarded-for"] ?? req.socket.remoteAddress,
24+
"X-Sq-Server-Secret": SQ_SERVER_SECRET,
25+
"X-Sq-Public-Access": true,
26+
},
27+
});
28+
const torrents = await listRes.json();
29+
for (const { infoHash } of torrents) {
30+
urls.push(`${SQ_BASE_URL}/torrent/${infoHash}`);
31+
}
32+
} catch (e) {
33+
console.error(`[sq] could not list torrents: ${e}`);
34+
}
35+
}
36+
37+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
38+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
39+
${urls
40+
.map(
41+
(url) => `<url>
42+
<loc>${url}</loc>
43+
</url>`
44+
)
45+
.join("\n")}
46+
</urlset>
47+
`;
48+
49+
res.setHeader("Content-Type", "text/xml");
50+
res.write(sitemap);
51+
res.end();
52+
53+
return { props: {} };
54+
};
55+
56+
export default Sitemap;

client/pages/torrent/[infoHash].js

Lines changed: 86 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => {
126126
const [comments, setComments] = useState(torrent.comments);
127127
const [isFreeleech, setIsFreeleech] = useState(torrent.freeleech);
128128
const [hasGroup, setHasGroup] = useState(!!torrent.group);
129-
const [bookmarked, setBookmarked] = useState(torrent.fetchedBy.bookmarked);
129+
const [bookmarked, setBookmarked] = useState(torrent.fetchedBy?.bookmarked);
130130

131131
const { addNotification } = useContext(NotificationContext);
132132
const { setLoading } = useContext(LoadingContext);
@@ -478,9 +478,20 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => {
478478
)}
479479
</Text>
480480
<Box display="flex" alignItems="center" ml={3}>
481-
<Button onClick={handleBookmark} variant="secondary" px="10px" mr={3}>
482-
{bookmarked ? <Bookmark size={18} /> : <BookmarkEmpty size={18} />}
483-
</Button>
481+
{!!userId && (
482+
<Button
483+
onClick={handleBookmark}
484+
variant="secondary"
485+
px="10px"
486+
mr={3}
487+
>
488+
{bookmarked ? (
489+
<Bookmark size={18} />
490+
) : (
491+
<BookmarkEmpty size={18} />
492+
)}
493+
</Button>
494+
)}
484495
{(userRole === "admin" || userId === torrent.uploadedBy._id) && (
485496
<>
486497
<Button
@@ -504,13 +515,19 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => {
504515
{isFreeleech ? "Unset" : "Set"} freeleech
505516
</Button>
506517
)}
507-
<Button
508-
as="a"
509-
href={`${SQ_API_URL}/torrent/download/${torrent.infoHash}/${uid}`}
510-
target="_blank"
511-
>
512-
Download .torrent
513-
</Button>
518+
{!!userId ? (
519+
<Button
520+
as="a"
521+
href={`${SQ_API_URL}/torrent/download/${torrent.infoHash}/${uid}`}
522+
target="_blank"
523+
>
524+
Download .torrent
525+
</Button>
526+
) : (
527+
<Link href="/login" passHref>
528+
<Button as="a">Log in to download</Button>
529+
</Link>
530+
)}
514531
</Box>
515532
</Box>
516533
<Info
@@ -645,29 +662,43 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => {
645662
display="flex"
646663
borderBottom="1px solid"
647664
borderColor="border"
648-
pb={5}
665+
pb={userId ? 5 : 0}
649666
mb={5}
650667
>
651-
<Button onClick={() => handleVote("up")} variant="noBackground" mr={2}>
652-
<Text icon={Like} iconColor={userVote === "up" ? "green" : undefined}>
653-
{votes.up || 0}
654-
</Text>
655-
</Button>
656-
<Button
657-
onClick={() => handleVote("down")}
658-
variant="noBackground"
659-
mr={2}
660-
>
661-
<Text
662-
icon={Dislike}
663-
iconColor={userVote === "down" ? "red" : undefined}
664-
>
665-
{votes.down || 0}
666-
</Text>
667-
</Button>
668-
<Button onClick={() => setShowReportModal(true)} variant="noBackground">
669-
Report
670-
</Button>
668+
{!!userId && (
669+
<>
670+
<Button
671+
onClick={() => handleVote("up")}
672+
variant="noBackground"
673+
mr={2}
674+
>
675+
<Text
676+
icon={Like}
677+
iconColor={userVote === "up" ? "green" : undefined}
678+
>
679+
{votes.up || 0}
680+
</Text>
681+
</Button>
682+
<Button
683+
onClick={() => handleVote("down")}
684+
variant="noBackground"
685+
mr={2}
686+
>
687+
<Text
688+
icon={Dislike}
689+
iconColor={userVote === "down" ? "red" : undefined}
690+
>
691+
{votes.down || 0}
692+
</Text>
693+
</Button>
694+
<Button
695+
onClick={() => setShowReportModal(true)}
696+
variant="noBackground"
697+
>
698+
Report
699+
</Button>
700+
</>
701+
)}
671702
</Box>
672703
<Box borderBottom="1px solid" borderColor="border" pb={5} mb={5}>
673704
<Box
@@ -690,11 +721,13 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => {
690721
Remove this torrent
691722
</Button>
692723
)}
693-
<Link href={`/upload?groupWith=${torrent.infoHash}`} passHref>
694-
<Button as="a" ml={3}>
695-
Add a torrent
696-
</Button>
697-
</Link>
724+
{!!userId && (
725+
<Link href={`/upload?groupWith=${torrent.infoHash}`} passHref>
726+
<Button as="a" ml={3}>
727+
Add a torrent
728+
</Button>
729+
</Link>
730+
)}
698731
</Box>
699732
</Box>
700733
{torrent.groupTorrents.length && hasGroup ? (
@@ -709,15 +742,16 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => {
709742
<Text as="h2" mb={4}>
710743
Comments
711744
</Text>
712-
<form onSubmit={handleComment}>
745+
<form onSubmit={userId ? handleComment : undefined}>
713746
<Input
714747
ref={commentInputRef}
715748
name="comment"
716749
label="Post a comment"
717750
rows="5"
751+
disabled={!userId}
718752
mb={4}
719753
/>
720-
<Button display="block" ml="auto">
754+
<Button disabled={!userId} display="block" ml="auto">
721755
Post
722756
</Button>
723757
</form>
@@ -797,15 +831,23 @@ const Torrent = ({ token, torrent = {}, userId, userRole, uid }) => {
797831
};
798832

799833
export const getServerSideProps = withAuthServerSideProps(
800-
async ({ token, userId, fetchHeaders, query: { infoHash } }) => {
801-
if (!token) return { props: {} };
834+
async ({
835+
token,
836+
userId,
837+
fetchHeaders,
838+
isPublicAccess,
839+
query: { infoHash },
840+
}) => {
841+
if (!token && !isPublicAccess) return { props: {} };
802842

803843
const {
804844
publicRuntimeConfig: { SQ_API_URL },
805845
serverRuntimeConfig: { SQ_JWT_SECRET },
806846
} = getConfig();
807847

808-
const { id, role } = jwt.verify(token, SQ_JWT_SECRET);
848+
const { id, role } = token
849+
? jwt.verify(token, SQ_JWT_SECRET)
850+
: { id: null, role: null };
809851

810852
try {
811853
const torrentRes = await fetch(`${SQ_API_URL}/torrent/info/${infoHash}`, {
@@ -827,7 +869,8 @@ export const getServerSideProps = withAuthServerSideProps(
827869
if (e === "banned") throw "banned";
828870
return { props: {} };
829871
}
830-
}
872+
},
873+
true
831874
);
832875

833876
export default Torrent;

client/utils/withAuth.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,39 +30,53 @@ export const withAuth = (Component, noRedirect = false) => {
3030

3131
export const withAuthServerSideProps = (
3232
getServerSideProps,
33+
publicAccess = false,
3334
noRedirect = false
3435
) => {
3536
return async (ctx) => {
36-
const { token, userId } = getReqCookies(ctx.req);
37+
let { token, userId } = getReqCookies(ctx.req);
3738

38-
if (!token && !noRedirect)
39+
const {
40+
serverRuntimeConfig: { SQ_SERVER_SECRET },
41+
publicRuntimeConfig: { SQ_ALLOW_UNREGISTERED_VIEW },
42+
} = getConfig();
43+
44+
const isPublicAccess = publicAccess && SQ_ALLOW_UNREGISTERED_VIEW && !token;
45+
46+
if (!token && !noRedirect && !isPublicAccess)
3947
return {
4048
redirect: {
4149
permanent: false,
4250
destination: "/login",
4351
},
4452
};
4553

46-
if (!token && noRedirect) return { props: {} };
54+
if (!token && noRedirect && !isPublicAccess) return { props: {} };
4755

48-
try {
49-
const {
50-
serverRuntimeConfig: { SQ_SERVER_SECRET },
51-
} = getConfig();
56+
if (isPublicAccess) {
57+
token = null;
58+
userId = null;
59+
}
5260

61+
try {
5362
const fetchHeaders = {
5463
"Content-Type": "application/json",
55-
Authorization: `Bearer ${token}`,
5664
"X-Forwarded-For":
5765
ctx.req.headers["x-forwarded-for"] ?? ctx.req.socket.remoteAddress,
5866
"X-Sq-Server-Secret": SQ_SERVER_SECRET,
67+
"X-Sq-Public-Access": isPublicAccess,
5968
};
6069

70+
if (token) {
71+
fetchHeaders["Authorization"] = `Bearer ${token}`;
72+
}
73+
6174
const { props: ssProps } = await getServerSideProps({
6275
...ctx,
6376
token,
6477
userId,
6578
fetchHeaders,
79+
isPublicAccess,
6680
});
6781
return { props: { ...ssProps, token } };
6882
} catch (e) {

0 commit comments

Comments
 (0)