fix(auth): unify imported sessions with account gating

This commit is contained in:
jjbrunton committed 2026-08-08 16:53:55 +01:00
1 parent 654bbda457
commit ea92a1a92b
15 files changed
+123 -49

No files matched your search

+5 -7
View File
@@ -19,9 +19,8 @@ const ROOM_URL_PREFIX = 'https://chat.reddit.com/room/';
export interface TokenRefresher {
/**
* Re-extract a fresh Matrix bearer token (e.g. by re-running the browser
* sniff against the persistent session) and apply it to the underlying
* client. Resolves when the client is ready to be retried.
* Requests replacement Matrix credentials. Extension-only authentication
* deliberately rejects this until the user imports a fresh dump.
*/
refresh(): Promise<void>;
}
@@ -175,9 +174,8 @@ export class ChatService {
}
/**
* Run a Matrix call; if it 401/403s, re-extract the bearer via the
* configured `TokenRefresher` and retry once. Single retry only — if the
* refresh itself fails, we propagate.
* Run a Matrix call; if it 401/403s, invalidate the shared account gate.
* The configured refresher reports that a new extension dump is required.
*/
private async withAuth<T>(fn: () => Promise<T>): Promise<T> {
if (!this.accountAuth?.isFullyAuthenticated()) {
@@ -189,7 +187,7 @@ export class ChatService {
return await fn();
} catch (err) {
if (err instanceof MatrixAuthError && this.tokenRefresher) {
this.logger.warn('[chat] Matrix token rejected — refreshing via browser session');
this.logger.warn('[chat] Matrix token rejected — waiting for a new extension dump');
try {
await this.tokenRefresher.refresh();
} catch (refreshError) {
+10
View File
@@ -1,10 +1,12 @@
import type { Cookie } from 'playwright';
import type { MatrixClient } from './chat/matrix/client.js';
import type { MatrixCredentials } from './chat/matrix/types.js';
import type { AccountAuthContext } from './account-auth-context.js';
interface BrowserSessionTarget {
importRedditCookies(cookies: Cookie[]): Promise<void>;
isSessionValid(): Promise<boolean>;
getAuthenticatedUsername(): Promise<string | null>;
}
interface ChatSessionTarget {
@@ -20,6 +22,7 @@ export class RedditSessionImporter {
private readonly chat: ChatSessionTarget,
private readonly createMatrixClient: MatrixClientFactory,
private readonly saveCredentials: CredentialStore,
private readonly accountAuth: AccountAuthContext,
) {}
async import(cookies: Cookie[], credentials: MatrixCredentials): Promise<void> {
@@ -41,8 +44,15 @@ export class RedditSessionImporter {
if (!(await this.browser.isSessionValid())) {
throw new Error('Imported cookies did not authenticate with Reddit');
}
const redditUsername = await this.browser.getAuthenticatedUsername();
if (!redditUsername) throw new Error('Reddit did not return an authenticated account identity');
this.saveCredentials(credentials);
this.chat.setMatrixClient(matrixClient);
this.accountAuth.markAuthenticated({
accountId: userId,
redditUsername,
matrixUserId: userId,
});
}
}
+3
View File
@@ -3,6 +3,7 @@ import type { Logger } from '../utils/logger.js';
import type { MessagesRepository, SuccessfulMessagesRepository, SentMessageRecord, ConversationOutcomesRepository } from '../database/repositories.js';
import type { PostsRepository } from '../database/repositories.js';
import { classifyConversationOutcome } from './conversation-outcome.js';
import { denyAccountAuth, type AccountAuthGate } from './account-auth-context.js';
export interface ResponseCheckResult {
messageId: number;
@@ -18,11 +19,13 @@ export class ResponseCheckerService {
private successfulMessagesRepo: SuccessfulMessagesRepository,
private postsRepo: PostsRepository,
private logger: Logger,
private accountAuth: AccountAuthGate = denyAccountAuth,
private outcomesRepo?: ConversationOutcomesRepository,
) { }
async checkForResponses(): Promise<ResponseCheckResult[]> {
const results: ResponseCheckResult[] = [];
if (!this.accountAuth.isFullyAuthenticated()) return results;
// Get messages that haven't been checked recently (at least 1 hour old, recheck every 6 hours)
const minAgeMs = 60 * 60 * 1000; // 1 hour
+1 -4
View File
@@ -1,5 +1,3 @@
import type { RedditSessionState } from '../browser/client.js';
/** Starts and stops account-bound work as authentication changes. */
export class SessionBoundRuntime {
private active = false;
@@ -9,8 +7,7 @@ export class SessionBoundRuntime {
private readonly stop: () => void,
) {}
reconcile(state: RedditSessionState): boolean {
const authenticated = state === 'authenticated';
reconcile(authenticated: boolean): boolean {
if (authenticated && !this.active) {
this.active = true;
this.start();