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
@@ -10,7 +10,7 @@ describe('AccountAuthContext', () => {
|
||||
const refreshMatrixCredentials = vi.fn();
|
||||
|
||||
await expect(authenticateCurrentAccount({
|
||||
getRedditUsername: vi.fn().mockResolvedValue(null),
|
||||
getRedditIdentity: vi.fn().mockResolvedValue(null),
|
||||
refreshMatrixCredentials,
|
||||
})).resolves.toBeNull();
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('AccountAuthContext', () => {
|
||||
it('fails closed until Reddit and Matrix resolve to one authenticated account', async () => {
|
||||
const auth = new AccountAuthContext(createMockLogger());
|
||||
const authenticate = vi.fn().mockResolvedValue({
|
||||
accountId: '@t2_account:reddit.com',
|
||||
accountId: 't2_account',
|
||||
redditUsername: 'account',
|
||||
matrixUserId: '@t2_account:reddit.com',
|
||||
});
|
||||
@@ -34,7 +34,7 @@ describe('AccountAuthContext', () => {
|
||||
expect(auth.isFullyAuthenticated()).toBe(true);
|
||||
expect(auth.getSnapshot()).toMatchObject({
|
||||
status: 'authenticated',
|
||||
accountId: '@t2_account:reddit.com',
|
||||
accountId: 't2_account',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('AccountAuthContext', () => {
|
||||
const second = auth.ensureFullyAuthenticated();
|
||||
await vi.waitFor(() => expect(authenticate).toHaveBeenCalledOnce());
|
||||
resolveAuth?.({
|
||||
accountId: '@t2_account:reddit.com',
|
||||
accountId: 't2_account',
|
||||
redditUsername: 'account',
|
||||
matrixUserId: '@t2_account:reddit.com',
|
||||
});
|
||||
@@ -63,7 +63,7 @@ describe('AccountAuthContext', () => {
|
||||
it('invalidates the whole account context when either login layer expires', async () => {
|
||||
const auth = new AccountAuthContext(createMockLogger());
|
||||
auth.markAuthenticated({
|
||||
accountId: '@t2_account:reddit.com',
|
||||
accountId: 't2_account',
|
||||
redditUsername: 'account',
|
||||
matrixUserId: '@t2_account:reddit.com',
|
||||
});
|
||||
@@ -86,7 +86,7 @@ describe('AccountAuthContext', () => {
|
||||
now: () => now,
|
||||
});
|
||||
auth.markAuthenticated({
|
||||
accountId: '@t2_account:reddit.com',
|
||||
accountId: 't2_account',
|
||||
redditUsername: 'account',
|
||||
matrixUserId: '@t2_account:reddit.com',
|
||||
});
|
||||
@@ -115,4 +115,20 @@ describe('AccountAuthContext', () => {
|
||||
await auth.ensureFullyAuthenticated();
|
||||
expect(authenticate).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('fails closed when Reddit and Matrix belong to different accounts', async () => {
|
||||
const auth = new AccountAuthContext(createMockLogger());
|
||||
auth.setAuthenticator(vi.fn().mockResolvedValue({
|
||||
accountId: 't2_cookie',
|
||||
redditUsername: 'cookie-account',
|
||||
matrixUserId: '@t2_chat:reddit.com',
|
||||
}));
|
||||
|
||||
await expect(auth.ensureFullyAuthenticated()).resolves.toBe(false);
|
||||
expect(auth.getSnapshot()).toMatchObject({
|
||||
status: 'login-required',
|
||||
accountId: null,
|
||||
reason: expect.stringMatching(/does not match Matrix account/),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -36,10 +36,10 @@ describe('BrowserClient', () => {
|
||||
expect(JSON.stringify(logger)).not.toContain('secret');
|
||||
});
|
||||
|
||||
it('reads the username from Reddit\'s nested me.json account response', async () => {
|
||||
it('reads the username and stable account ID from Reddit\'s nested me.json response', async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ kind: 't2', data: { name: 'bot-account' } }),
|
||||
json: async () => ({ kind: 't2', data: { name: 'bot-account', id: 'abc123' } }),
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
const page = {
|
||||
@@ -50,6 +50,10 @@ describe('BrowserClient', () => {
|
||||
const client = new BrowserClient(getTestConfig(), createLogger(), runtime);
|
||||
|
||||
await expect(client.getAuthenticatedUsername()).resolves.toBe('bot-account');
|
||||
await expect(client.getAuthenticatedIdentity()).resolves.toEqual({
|
||||
username: 'bot-account',
|
||||
accountId: 't2_abc123',
|
||||
});
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'https://www.reddit.com/api/me.json',
|
||||
{ credentials: 'include' },
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { createSessionExportDownload } from '../extensions/reddit-session-exporter/session-export.js';
|
||||
import {
|
||||
assertMatchingSessionIdentity,
|
||||
createSessionExportDownload,
|
||||
} from '../extensions/reddit-session-exporter/session-export.js';
|
||||
|
||||
describe('createSessionExportDownload', () => {
|
||||
it('creates a JSON data URL that Chrome can download from a service worker', () => {
|
||||
@@ -49,3 +52,19 @@ describe('createSessionExportDownload', () => {
|
||||
expect(() => createSessionExportDownload([], null)).toThrow(/Reddit Chat/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assertMatchingSessionIdentity', () => {
|
||||
it('accepts a Matrix user bound to the current Reddit account ID', () => {
|
||||
expect(() => assertMatchingSessionIdentity(
|
||||
{ username: 'bot-account', accountId: 'bot' },
|
||||
'@t2_bot:reddit.com',
|
||||
)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects a stale Matrix capture from another Reddit account', () => {
|
||||
expect(() => assertMatchingSessionIdentity(
|
||||
{ username: 'bot-account', accountId: 't2_bot' },
|
||||
'@t2_other:reddit.com',
|
||||
)).toThrow(/Reload Reddit Chat after switching accounts/);
|
||||
});
|
||||
});
|
||||
@@ -18,12 +18,12 @@ describe('RedditSessionImporter', () => {
|
||||
const browser = {
|
||||
importRedditCookies: vi.fn(),
|
||||
isSessionValid: vi.fn(async () => true),
|
||||
getAuthenticatedUsername: vi.fn(async () => 'bot-account'),
|
||||
getAuthenticatedIdentity: vi.fn(async () => ({ username: 'bot-account', accountId: 't2_bot' })),
|
||||
};
|
||||
const matrix = { whoami: vi.fn(async () => credentials.userId) };
|
||||
const chat = { setMatrixClient: vi.fn() };
|
||||
const save = vi.fn();
|
||||
const accountAuth = { markAuthenticated: vi.fn() };
|
||||
const accountAuth = { markAuthenticated: vi.fn(), invalidate: vi.fn() };
|
||||
const importer = new RedditSessionImporter(browser, chat as never, vi.fn(() => matrix as never), save, accountAuth as never);
|
||||
|
||||
await importer.import(cookies, credentials);
|
||||
@@ -33,20 +33,40 @@ describe('RedditSessionImporter', () => {
|
||||
expect(chat.setMatrixClient).toHaveBeenCalledWith(matrix);
|
||||
expect(save).toHaveBeenCalledWith(credentials);
|
||||
expect(accountAuth.markAuthenticated).toHaveBeenCalledWith({
|
||||
accountId: credentials.userId,
|
||||
accountId: 't2_bot',
|
||||
redditUsername: 'bot-account',
|
||||
matrixUserId: credentials.userId,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a dump whose Reddit cookies and Matrix token belong to different accounts', async () => {
|
||||
const browser = {
|
||||
importRedditCookies: vi.fn(),
|
||||
isSessionValid: vi.fn(async () => true),
|
||||
getAuthenticatedIdentity: vi.fn(async () => ({ username: 'other-account', accountId: 't2_other' })),
|
||||
};
|
||||
const matrix = { whoami: vi.fn(async () => credentials.userId) };
|
||||
const chat = { setMatrixClient: vi.fn() };
|
||||
const save = vi.fn();
|
||||
const accountAuth = { markAuthenticated: vi.fn(), invalidate: vi.fn() };
|
||||
const importer = new RedditSessionImporter(browser, chat as never, vi.fn(() => matrix as never), save, accountAuth as never);
|
||||
|
||||
await expect(importer.import(cookies, credentials)).rejects.toThrow(/does not match Matrix account/);
|
||||
|
||||
expect(accountAuth.invalidate).toHaveBeenCalledWith(expect.stringMatching(/does not match Matrix account/));
|
||||
expect(save).not.toHaveBeenCalled();
|
||||
expect(chat.setMatrixClient).not.toHaveBeenCalled();
|
||||
expect(accountAuth.markAuthenticated).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects non-Reddit Matrix homeservers without making a request', async () => {
|
||||
const createMatrix = vi.fn();
|
||||
const importer = new RedditSessionImporter(
|
||||
{ importRedditCookies: vi.fn(), isSessionValid: vi.fn(), getAuthenticatedUsername: vi.fn() },
|
||||
{ importRedditCookies: vi.fn(), isSessionValid: vi.fn(), getAuthenticatedIdentity: vi.fn() },
|
||||
{ setMatrixClient: vi.fn() } as never,
|
||||
createMatrix,
|
||||
vi.fn(),
|
||||
{ markAuthenticated: vi.fn() } as never,
|
||||
{ markAuthenticated: vi.fn(), invalidate: vi.fn() } as never,
|
||||
);
|
||||
|
||||
await expect(importer.import(cookies, { ...credentials, homeserver: 'https://attacker.example' }))
|
||||
|
||||
Reference in new issue
Block a user