Bind Reddit and Matrix session identities
This commit is contained in:
1 parent
34ae532cdd
commit
492efe0f8a
12 files changed
+234
-55
No files matched your search
@@ -13,3 +13,5 @@ This local Chrome extension exports `reddit.com` cookies and the current Reddit
|
||||
6. Delete the downloaded JSON file once the dashboard confirms that the session is authenticated.
|
||||
|
||||
The extension observes only requests to Reddit's Matrix homeservers, verifies the captured token with Matrix `whoami`, reads Reddit cookies, and saves the combined export locally. It does not send the dump to any server; you upload it directly to your RedditBot dashboard.
|
||||
|
||||
Before downloading, the extension independently resolves the current Reddit cookie identity and refuses the export unless its `t2` account ID matches Matrix `whoami`. Changing Reddit accounts clears the stored Matrix capture, so Reddit Chat must be reopened after every account switch.
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "RedditBot Session Exporter",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Exports Reddit cookies and Reddit Chat credentials for a self-hosted RedditBot dashboard.",
|
||||
"permissions": ["cookies", "downloads", "storage", "webRequest"],
|
||||
"host_permissions": [
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { createSessionExportDownload } from './session-export.js';
|
||||
import {
|
||||
assertMatchingSessionIdentity,
|
||||
createSessionExportDownload,
|
||||
} from './session-export.js';
|
||||
|
||||
const MATRIX_URLS = [
|
||||
'https://matrix.redditspace.com/*',
|
||||
@@ -27,6 +30,29 @@ chrome.webRequest.onBeforeSendHeaders.addListener(
|
||||
['requestHeaders', 'extraHeaders'],
|
||||
);
|
||||
|
||||
chrome.cookies.onChanged.addListener(async ({ cookie }) => {
|
||||
if (!['reddit_session', 'token_v2'].includes(cookie.name)) return;
|
||||
await chrome.storage.local.remove('matrixCapture');
|
||||
await chrome.action.setBadgeText({ text: '' });
|
||||
await chrome.action.setTitle({
|
||||
title: 'Reddit account changed. Open Reddit Chat before exporting.',
|
||||
});
|
||||
});
|
||||
|
||||
async function getRedditIdentity() {
|
||||
const response = await fetch('https://www.reddit.com/api/me.json', {
|
||||
credentials: 'include',
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) throw new Error('Reddit cookies did not authenticate. Sign in and try again.');
|
||||
const payload = await response.json();
|
||||
const account = payload?.data ?? payload;
|
||||
if (!account?.name || !account?.id) {
|
||||
throw new Error('Reddit did not return an authenticated account identity.');
|
||||
}
|
||||
return { username: account.name, accountId: account.id };
|
||||
}
|
||||
|
||||
async function showError(message) {
|
||||
await chrome.action.setBadgeText({ text: 'ERR' });
|
||||
await chrome.action.setBadgeBackgroundColor({ color: '#dc3545' });
|
||||
@@ -48,6 +74,14 @@ chrome.action.onClicked.addListener(async () => {
|
||||
const identity = await whoami.json();
|
||||
if (!identity?.user_id) throw new Error('Reddit Chat did not return an account identity.');
|
||||
|
||||
const redditIdentity = await getRedditIdentity();
|
||||
try {
|
||||
assertMatchingSessionIdentity(redditIdentity, identity.user_id);
|
||||
} catch (error) {
|
||||
await chrome.storage.local.remove('matrixCapture');
|
||||
throw error;
|
||||
}
|
||||
|
||||
await chrome.downloads.download({
|
||||
...createSessionExportDownload(cookies, { ...matrixCapture, userId: identity.user_id }),
|
||||
saveAs: true,
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
const REDDITBOT_SESSION_FILENAME = 'redditbot-session.json';
|
||||
|
||||
export function normalizeRedditAccountId(accountId) {
|
||||
const trimmed = String(accountId ?? '').trim();
|
||||
const normalized = trimmed.startsWith('t2_') ? trimmed : `t2_${trimmed}`;
|
||||
if (!/^t2_[a-z0-9]+$/i.test(normalized)) {
|
||||
throw new Error('Reddit returned an invalid account ID.');
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function assertMatchingSessionIdentity(redditIdentity, matrixUserId) {
|
||||
const accountId = normalizeRedditAccountId(redditIdentity?.accountId);
|
||||
const username = String(redditIdentity?.username ?? '').trim();
|
||||
if (!username) throw new Error('Reddit did not return an authenticated username.');
|
||||
const expectedMatrixUserId = `@${accountId}:reddit.com`;
|
||||
if (matrixUserId !== expectedMatrixUserId) {
|
||||
throw new Error(
|
||||
`Reddit is logged in as u/${username} (${accountId}), but Chat is authenticated as ${matrixUserId}. Reload Reddit Chat after switching accounts, then export again.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function isRedditDomain(domain) {
|
||||
const normalised = domain.toLowerCase().replace(/^\./, '');
|
||||
return normalised === 'reddit.com' || normalised.endsWith('.reddit.com');
|
||||
|
||||
Reference in new issue
Block a user