fix(auth): unify imported sessions with account gating
This commit is contained in:
1 parent
654bbda457
commit
ea92a1a92b
15 files changed
+123
-49
No files matched your search
@@ -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) {
|
||||
|
||||
@@ -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,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,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();
|
||||
|
||||
Reference in new issue
Block a user