Skip to content

Commit 9b78b45

Browse files
committed
fix: restore steam login alerts with session recovery
1 parent afafa3c commit 9b78b45

12 files changed

Lines changed: 506 additions & 41 deletions

File tree

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ ADMIN_EMAIL=admin@admin.com
2323
ADMIN_PASSWORD=admin123
2424

2525
# Steam polling
26-
STEAM_POLL_INTERVAL_SEC=20
26+
STEAM_POLL_INTERVAL_SEC=5

backend/src/jobs/confirmationPoller.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ import type { FastifyInstance } from 'fastify';
22
import { env } from '../config/env';
33
import { execute, queryRows } from '../db/pool';
44
import { decryptForUser } from '../utils/crypto';
5-
import { decodeAccountSession } from '../utils/accountSession';
5+
import { decodeAccountSession, encodeAccountSession } from '../utils/accountSession';
66
import { parseMaFile } from '../utils/mafile';
7-
import { generateSteamCode, listConfirmations, respondToConfirmation } from '../services/steamService';
7+
import {
8+
generateSteamCode,
9+
listConfirmationsWithSessionRecovery,
10+
respondToConfirmationWithSessionRecovery
11+
} from '../services/steamService';
812
import { wsHub } from '../services/wsHub';
913
import { sendTelegramMessage } from '../services/telegramService';
1014

@@ -170,7 +174,18 @@ async function runCycle(app: FastifyInstance): Promise<void> {
170174
const session = sessions[0]
171175
? decodeAccountSession(sessions[0].session_json, account.password_hash, Number(account.user_id))
172176
: null;
173-
const confirmations = await listConfirmations(ma, session);
177+
const confirmationResult = await listConfirmationsWithSessionRecovery(ma, session);
178+
const confirmations = confirmationResult.confirmations;
179+
const nextSession = confirmationResult.session;
180+
181+
if (confirmationResult.refreshed && nextSession) {
182+
await execute(
183+
`INSERT INTO account_sessions (account_id, session_json)
184+
VALUES (?, ?)
185+
ON DUPLICATE KEY UPDATE session_json = VALUES(session_json)`,
186+
[account.id, encodeAccountSession(nextSession, account.password_hash, Number(account.user_id))]
187+
);
188+
}
174189
const byKind: Record<'trade' | 'login' | 'other', Set<string>> = {
175190
trade: new Set(),
176191
login: new Set(),
@@ -288,15 +303,24 @@ async function runCycle(app: FastifyInstance): Promise<void> {
288303
continue;
289304
}
290305

291-
const ok = await respondToConfirmation({
306+
const response = await respondToConfirmationWithSessionRecovery({
292307
ma,
293-
session,
308+
session: nextSession,
294309
confirmationId: confirmation.id,
295310
nonce: cache.nonce || confirmation.nonce,
296311
accept: true
297312
});
298313

299-
if (!ok) {
314+
if (response.refreshed && response.session) {
315+
await execute(
316+
`INSERT INTO account_sessions (account_id, session_json)
317+
VALUES (?, ?)
318+
ON DUPLICATE KEY UPDATE session_json = VALUES(session_json)`,
319+
[account.id, encodeAccountSession(response.session, account.password_hash, Number(account.user_id))]
320+
);
321+
}
322+
323+
if (!response.success) {
300324
continue;
301325
}
302326

backend/src/routes/accounts.ts

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { generateSteamCode } from '../services/steamService';
77
import { guardWriteByIp } from '../middleware/rateLimiters';
88
import {
99
finishSteamEnrollment,
10+
refreshSteamLoginSession,
1011
startSteamEnrollment,
1112
SteamEnrollmentError
1213
} from '../services/steamEnrollmentService';
@@ -474,7 +475,7 @@ const accountRoutes: FastifyPluginAsync = async (app) => {
474475

475476
app.post<{
476477
Params: { accountId: string };
477-
Body: { steamLoginSecure?: string; sessionid?: string; oauthToken?: string; steamid?: string };
478+
Body: { steamLoginSecure?: string; sessionid?: string; oauthToken?: string; refreshToken?: string; steamid?: string };
478479
}>('/api/accounts/:accountId/session', { preHandler: app.authenticate }, async (request, reply) => {
479480
const accountId = Number(request.params.accountId);
480481
const user = await getUserSecret(request.user.id);
@@ -485,11 +486,17 @@ const accountRoutes: FastifyPluginAsync = async (app) => {
485486
return reply.code(404).send({ message: error.message });
486487
}
487488

489+
const cookieTokenPart =
490+
request.body.steamLoginSecure && request.body.steamLoginSecure.includes('||')
491+
? request.body.steamLoginSecure.split('||')[1]
492+
: undefined;
493+
488494
const session = {
489495
steamid: request.body.steamid,
490496
steamLoginSecure: request.body.steamLoginSecure,
491497
sessionid: request.body.sessionid,
492-
oauthToken: request.body.oauthToken
498+
oauthToken: request.body.oauthToken ?? cookieTokenPart,
499+
refreshToken: request.body.refreshToken
493500
};
494501

495502
await execute(
@@ -507,6 +514,101 @@ const accountRoutes: FastifyPluginAsync = async (app) => {
507514
return { success: true };
508515
});
509516

517+
app.post<{
518+
Params: { accountId: string };
519+
Body: { password?: string; guardCode?: string };
520+
}>('/api/accounts/:accountId/reconnect', { preHandler: app.authenticate }, async (request, reply) => {
521+
try {
522+
await guardWriteByIp(request.ip);
523+
} catch (error: any) {
524+
return reply.code(429).send({ message: error.message });
525+
}
526+
527+
const accountId = Number(request.params.accountId);
528+
const password = request.body.password;
529+
const guardCode = request.body.guardCode?.trim();
530+
const user = await getUserSecret(request.user.id);
531+
532+
if (!password) {
533+
return reply.code(400).send({ message: 'Steam password is required' });
534+
}
535+
536+
let account: AccountRow;
537+
try {
538+
account = await getAccountByOwner(request.user.id, accountId);
539+
} catch (error: any) {
540+
return reply.code(404).send({ message: error.message });
541+
}
542+
543+
try {
544+
const ma = parseMaFile(decryptForUser(account.encrypted_ma, user.password_hash, user.id));
545+
const { steamid, session } = await refreshSteamLoginSession({
546+
accountName: account.account_name,
547+
password,
548+
guardCode,
549+
totpCodeProvider: () => generateSteamCode(ma.shared_secret)
550+
});
551+
552+
ma.Session = {
553+
...(ma.Session ?? {}),
554+
SteamID: session.steamid ?? steamid,
555+
SteamLoginSecure: session.steamLoginSecure,
556+
SessionID: session.sessionid,
557+
OAuthToken: session.oauthToken,
558+
RefreshToken: session.refreshToken
559+
};
560+
561+
await execute(
562+
`UPDATE user_accounts
563+
SET steamid = ?, encrypted_ma = ?
564+
WHERE id = ? AND user_id = ?`,
565+
[
566+
steamid,
567+
encryptForUser(JSON.stringify(ma), user.password_hash, user.id),
568+
accountId,
569+
request.user.id
570+
]
571+
);
572+
573+
await execute(
574+
`INSERT INTO account_sessions (account_id, session_json)
575+
VALUES (?, ?)
576+
ON DUPLICATE KEY UPDATE session_json = VALUES(session_json)`,
577+
[accountId, encodeAccountSession(session, user.password_hash, user.id)]
578+
);
579+
580+
await execute(
581+
"INSERT INTO logs (user_id, account_id, type, details) VALUES (?, ?, 'system', JSON_OBJECT('event', 'session_updated'))",
582+
[request.user.id, accountId]
583+
);
584+
585+
return {
586+
success: true,
587+
steamid
588+
};
589+
} catch (error: any) {
590+
if (error instanceof SteamEnrollmentError) {
591+
if (error.code === 'STEAM_GUARD_REQUIRED') {
592+
return reply.code(428).send({
593+
code: error.code,
594+
guardType: error.guardType,
595+
guardDomain: error.guardDomain ?? null,
596+
message: error.message
597+
});
598+
}
599+
600+
if (error.code === 'STEAM_GUARD_INVALID') {
601+
return reply.code(400).send({
602+
code: error.code,
603+
message: error.message
604+
});
605+
}
606+
}
607+
608+
return reply.code(400).send({ message: error.message || 'Failed to refresh Steam session' });
609+
}
610+
});
611+
510612
app.get<{ Params: { accountId: string } }>(
511613
'/api/accounts/:accountId/code',
512614
{ preHandler: app.authenticate },

backend/src/routes/bot.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import type { FastifyPluginAsync } from 'fastify';
22
import { env } from '../config/env';
33
import { execute, queryRows } from '../db/pool';
44
import { decryptForUser } from '../utils/crypto';
5-
import { decodeAccountSession } from '../utils/accountSession';
5+
import { decodeAccountSession, encodeAccountSession } from '../utils/accountSession';
66
import { parseMaFile } from '../utils/mafile';
7-
import { generateSteamCode, respondToConfirmation } from '../services/steamService';
7+
import { generateSteamCode, respondToConfirmationWithSessionRecovery } from '../services/steamService';
88
import { wsHub } from '../services/wsHub';
99

1010
async function botAuth(request: any, reply: any): Promise<void> {
@@ -204,15 +204,24 @@ const botRoutes: FastifyPluginAsync = async (app) => {
204204
return reply.code(400).send({ message: 'Nonce missing' });
205205
}
206206

207-
const success = await respondToConfirmation({
207+
const response = await respondToConfirmationWithSessionRecovery({
208208
ma,
209209
session,
210210
confirmationId: request.body.confirmationId,
211211
nonce,
212212
accept: true
213213
});
214214

215-
if (!success) {
215+
if (response.refreshed && response.session) {
216+
await execute(
217+
`INSERT INTO account_sessions (account_id, session_json)
218+
VALUES (?, ?)
219+
ON DUPLICATE KEY UPDATE session_json = VALUES(session_json)`,
220+
[request.body.accountId, encodeAccountSession(response.session, user.password_hash, user.id)]
221+
);
222+
}
223+
224+
if (!response.success) {
216225
return reply.code(400).send({ message: 'Steam confirmation failed' });
217226
}
218227

@@ -284,15 +293,24 @@ const botRoutes: FastifyPluginAsync = async (app) => {
284293
return reply.code(400).send({ message: 'Nonce missing' });
285294
}
286295

287-
const success = await respondToConfirmation({
296+
const response = await respondToConfirmationWithSessionRecovery({
288297
ma,
289298
session,
290299
confirmationId: item.confirmation_id,
291300
nonce,
292301
accept: request.body.accept
293302
});
294303

295-
if (!success) {
304+
if (response.refreshed && response.session) {
305+
await execute(
306+
`INSERT INTO account_sessions (account_id, session_json)
307+
VALUES (?, ?)
308+
ON DUPLICATE KEY UPDATE session_json = VALUES(session_json)`,
309+
[item.account_id, encodeAccountSession(response.session, user.password_hash, user.id)]
310+
);
311+
}
312+
313+
if (!response.success) {
296314
return reply.code(400).send({ message: 'Steam confirmation failed' });
297315
}
298316

0 commit comments

Comments
 (0)