From 55b2ef1871fd5815e85991d9cdc10f5c45afbef7 Mon Sep 17 00:00:00 2001 From: sreecharan-desu Date: Sun, 25 Jan 2026 19:34:32 +0530 Subject: [PATCH 1/2] chore(auth-server): minor code cleanup and fixes * Fix typo 'seperately' -> 'separately' in oauth api test * Replace deprecated rmdirSync with rmSync in sass compiler script * Improve error handling in sass compiler script --- .../lib/senders/emails/sass-compile-files.ts | 17 +++++------------ packages/fxa-auth-server/test/oauth/api.js | 12 +++++++++--- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/fxa-auth-server/lib/senders/emails/sass-compile-files.ts b/packages/fxa-auth-server/lib/senders/emails/sass-compile-files.ts index 70680755e8c..d81ef2ec092 100644 --- a/packages/fxa-auth-server/lib/senders/emails/sass-compile-files.ts +++ b/packages/fxa-auth-server/lib/senders/emails/sass-compile-files.ts @@ -3,13 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { renderSync } from 'sass'; -import { - writeFileSync, - mkdirSync, - existsSync, - rmdirSync, - readdirSync, -} from 'fs'; +import { writeFileSync, mkdirSync, existsSync, rmSync, readdirSync } from 'fs'; import path from 'path'; const getDirectories = (source: string) => @@ -23,22 +17,21 @@ const templates = getDirectories(path.join(__dirname, 'templates')); const layouts = getDirectories(path.join(__dirname, 'layouts')); async function compileSass(dir: string, subdir: string) { - let styleResult: Record = {}; try { - styleResult = renderSync({ + const styleResult = renderSync({ file: dir, outFile: subdir, }); + writeFileSync(subdir, styleResult.css, 'utf8'); } catch (e) { - console.log(e); + console.error(e); } - writeFileSync(subdir, styleResult.css, 'utf8'); } async function main(directories: Record) { // remove css directory if already present if (existsSync(path.join(__dirname, 'css'))) { - rmdirSync(path.join(__dirname, 'css'), { recursive: true }); + rmSync(path.join(__dirname, 'css'), { recursive: true, force: true }); } mkdirSync(path.join(__dirname, 'css')); diff --git a/packages/fxa-auth-server/test/oauth/api.js b/packages/fxa-auth-server/test/oauth/api.js index cfe1d20d5ba..15a26660544 100644 --- a/packages/fxa-auth-server/test/oauth/api.js +++ b/packages/fxa-auth-server/test/oauth/api.js @@ -2064,7 +2064,10 @@ describe('#integration - /v1', function () { assert.equal(res.statusCode, 200); assertSecurityHeaders(res); - assert.equal(res.result.scope, 'email https://identity.mozilla.com/apps/notes'); + assert.equal( + res.result.scope, + 'email https://identity.mozilla.com/apps/notes' + ); }); }); }); @@ -2296,7 +2299,10 @@ describe('#integration - /v1', function () { assert.equal(res.statusCode, 200); assert.ok(res.result.access_token); // Should contain all requested scopes (including invalid ones) - assert.equal(res.result.scope, 'profile email invalid:scope another:invalid'); + assert.equal( + res.result.scope, + 'profile email invalid:scope another:invalid' + ); }); }); }); @@ -3337,7 +3343,7 @@ describe('#integration - /v1', function () { assert.equal(clients2[0].client_id, client2Id.toString('hex')); }); - it('should seperately list different refresh tokens from the same client', async () => { + it('should separately list different refresh tokens from the same client', async () => { await makeAccessToken(client1, user1, ['profile']); await makeAccessToken(client1, user1, ['other', 'scope']); await makeRefreshToken(client2, user1, ['profile']); From 1910ae5262314cffbb59ea6ccec9e078a0d06e4c Mon Sep 17 00:00:00 2001 From: sreecharan-desu Date: Mon, 9 Feb 2026 12:07:23 +0530 Subject: [PATCH 2/2] Update session-token with improvements and add tests --- .../db/models/auth/session-token.ts | 10 +- .../test/db/models/auth/session-token.spec.ts | 121 ++++++++++++++++++ 2 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 packages/fxa-shared/test/db/models/auth/session-token.spec.ts diff --git a/packages/fxa-shared/db/models/auth/session-token.ts b/packages/fxa-shared/db/models/auth/session-token.ts index a0fa250aa4d..ae375aaef9c 100644 --- a/packages/fxa-shared/db/models/auth/session-token.ts +++ b/packages/fxa-shared/db/models/auth/session-token.ts @@ -11,12 +11,14 @@ import { } from '../../transformers'; import { convertError, notFound } from '../../mysql'; -//TODO FIXME unhardcode the 28 day expiry function notExpired(token: SessionToken) { - return !!(token.deviceId || token.createdAt > Date.now() - 2419200000); + return !!( + token.deviceId || + token.createdAt > Date.now() - SessionToken.sessionExpiryMs + ); } -const VERIFICATION_METHOD = { +export const VERIFICATION_METHOD = { email: 0, 'email-2fa': 1, 'totp-2fa': 2, @@ -54,10 +56,12 @@ export function verificationMethodToString( export class SessionToken extends BaseToken { public static tableName = 'sessionTokens'; public static idColumn = 'tokenId'; + public static sessionExpiryMs = 2419200000; // 28 days protected $uuidFields = [ 'tokenId', 'uid', + 'tokenData', 'deviceId', 'tokenVerificationId', diff --git a/packages/fxa-shared/test/db/models/auth/session-token.spec.ts b/packages/fxa-shared/test/db/models/auth/session-token.spec.ts new file mode 100644 index 00000000000..31568257167 --- /dev/null +++ b/packages/fxa-shared/test/db/models/auth/session-token.spec.ts @@ -0,0 +1,121 @@ +import 'mocha'; +import { assert } from 'chai'; +import sinon from 'sinon'; +import { SessionToken } from '../../../../db/models/auth'; +import { uuidTransformer } from '../../../../db/transformers'; +import crypto from 'crypto'; + +const toRandomBuff = (size) => + uuidTransformer.to(crypto.randomBytes(size).toString('hex')); + +describe('SessionToken Unit Tests', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('filters out expired tokens (older than 28 days by default)', async () => { + const now = Date.now(); + // 28 days in milliseconds is 2419200000 + const validTime = now - 2419200000 + 10000; // 10 seconds remaining + const expiredTime = now - 2419200000 - 10000; // 10 seconds overdue + + const uidBuff = toRandomBuff(16); + + const validRow = { + tokenId: toRandomBuff(16), + tokenData: toRandomBuff(16), + uid: uidBuff, + createdAt: validTime, + lastAccessTime: validTime, + authAt: 1, + verificationMethod: 0, + verifiedAt: 0, + mustVerify: 0, + deviceId: null, // explicit null for no device + emailVerified: 1, + email: 'test@test.com', + emailCode: 'code', + deviceCallbackIsExpired: 0, + }; + + const expiredRow = { + ...validRow, + tokenId: toRandomBuff(16), + createdAt: expiredTime, + }; + + sandbox.stub(SessionToken, 'callProcedure').resolves({ + rows: [validRow, expiredRow], + }); + + const tokens = await SessionToken.findByUid( + '0123456789abcdef0123456789abcdef' + ); + + assert.equal( + tokens.length, + 1, + 'Should return exactly 1 token (the valid one)' + ); + assert.equal(tokens[0].createdAt, validTime); + }); + + it('respects configured expiry', async () => { + const originalExpiry = SessionToken.sessionExpiryMs; + try { + // Set expiry to 1 hour + SessionToken.sessionExpiryMs = 60 * 60 * 1000; // 1 hour + const now = Date.now(); + const validTime = now - 30 * 60 * 1000; // 30 mins old (valid) + const expiredTime = now - 90 * 60 * 1000; // 90 mins old (expired) + + const uidBuff = toRandomBuff(16); + + const validRow = { + tokenId: toRandomBuff(16), + tokenData: toRandomBuff(16), + uid: uidBuff, + createdAt: validTime, + lastAccessTime: validTime, + authAt: 1, + verificationMethod: 0, + verifiedAt: 0, + mustVerify: 0, + deviceId: null, + emailVerified: 1, + email: 'test@test.com', + emailCode: 'code', + deviceCallbackIsExpired: 0, + }; + + const expiredRow = { + ...validRow, + tokenId: toRandomBuff(16), + createdAt: expiredTime, + }; + + sandbox.stub(SessionToken, 'callProcedure').resolves({ + rows: [validRow, expiredRow], + }); + + const tokens = await SessionToken.findByUid( + '0123456789abcdef0123456789abcdef' + ); + + assert.equal( + tokens.length, + 1, + 'Should return exactly 1 token (the valid one)' + ); + assert.equal(tokens[0].createdAt, validTime); + } finally { + SessionToken.sessionExpiryMs = originalExpiry; + } + }); +});