Skip to content

Commit 0865bc4

Browse files
authored
🪙 feat: Sync Balance Config on Login (danny-avila#6671)
* chore: Add deprecation warnings for environment variables in checks * chore: Change deprecatedVariables to a const declaration in checks.js * fix: Add date validation in checkBalanceRecord to prevent invalid date errors * feat: Add setBalanceConfig middleware to synchronize user balance settings * chore: Reorder middleware imports in oauth.js for better readability
1 parent 57faae8 commit 0865bc4

File tree

7 files changed

+145
-20
lines changed

7 files changed

+145
-20
lines changed

api/models/balanceMethods.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ const { getMultiplier } = require('./tx');
55
const { logger } = require('~/config');
66
const Balance = require('./Balance');
77

8+
function isInvalidDate(date) {
9+
return isNaN(date);
10+
}
11+
812
/**
913
* Simple check method that calculates token cost and returns balance info.
1014
* The auto-refill logic has been moved to balanceMethods.js to prevent circular dependencies.
@@ -48,13 +52,12 @@ const checkBalanceRecord = async function ({
4852
// Only perform auto-refill if spending would bring the balance to 0 or below
4953
if (balance - tokenCost <= 0 && record.autoRefillEnabled && record.refillAmount > 0) {
5054
const lastRefillDate = new Date(record.lastRefill);
51-
const nextRefillDate = addIntervalToDate(
52-
lastRefillDate,
53-
record.refillIntervalValue,
54-
record.refillIntervalUnit,
55-
);
5655
const now = new Date();
57-
if (now >= nextRefillDate) {
56+
if (
57+
isInvalidDate(lastRefillDate) ||
58+
now >=
59+
addIntervalToDate(lastRefillDate, record.refillIntervalValue, record.refillIntervalUnit)
60+
) {
5861
try {
5962
/** @type {{ rate: number, user: string, balance: number, transaction: import('@librechat/data-schemas').ITransaction}} */
6063
const result = await Transaction.createAutoRefillTransaction({

api/server/middleware/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const concurrentLimiter = require('./concurrentLimiter');
88
const validateEndpoint = require('./validateEndpoint');
99
const requireLocalAuth = require('./requireLocalAuth');
1010
const canDeleteAccount = require('./canDeleteAccount');
11+
const setBalanceConfig = require('./setBalanceConfig');
1112
const requireLdapAuth = require('./requireLdapAuth');
1213
const abortMiddleware = require('./abortMiddleware');
1314
const checkInviteUser = require('./checkInviteUser');
@@ -41,6 +42,7 @@ module.exports = {
4142
requireLocalAuth,
4243
canDeleteAccount,
4344
validateEndpoint,
45+
setBalanceConfig,
4446
concurrentLimiter,
4547
checkDomainAllowed,
4648
validateMessageReq,
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
const { getBalanceConfig } = require('~/server/services/Config');
2+
const Balance = require('~/models/Balance');
3+
const { logger } = require('~/config');
4+
5+
/**
6+
* Middleware to synchronize user balance settings with current balance configuration.
7+
* @function
8+
* @param {Object} req - Express request object containing user information.
9+
* @param {Object} res - Express response object.
10+
* @param {import('express').NextFunction} next - Next middleware function.
11+
*/
12+
const setBalanceConfig = async (req, res, next) => {
13+
try {
14+
const balanceConfig = await getBalanceConfig();
15+
if (!balanceConfig?.enabled) {
16+
return next();
17+
}
18+
if (balanceConfig.startBalance == null) {
19+
return next();
20+
}
21+
22+
const userId = req.user._id;
23+
const userBalanceRecord = await Balance.findOne({ user: userId }).lean();
24+
const updateFields = buildUpdateFields(balanceConfig, userBalanceRecord);
25+
26+
if (Object.keys(updateFields).length === 0) {
27+
return next();
28+
}
29+
30+
await Balance.findOneAndUpdate(
31+
{ user: userId },
32+
{ $set: updateFields },
33+
{ upsert: true, new: true },
34+
);
35+
36+
next();
37+
} catch (error) {
38+
logger.error('Error setting user balance:', error);
39+
next(error);
40+
}
41+
};
42+
43+
/**
44+
* Build an object containing fields that need updating
45+
* @param {Object} config - The balance configuration
46+
* @param {Object|null} userRecord - The user's current balance record, if any
47+
* @returns {Object} Fields that need updating
48+
*/
49+
function buildUpdateFields(config, userRecord) {
50+
const updateFields = {};
51+
52+
// Ensure user record has the required fields
53+
if (!userRecord) {
54+
updateFields.user = userRecord?.user;
55+
updateFields.tokenCredits = config.startBalance;
56+
}
57+
58+
if (userRecord?.tokenCredits == null && config.startBalance != null) {
59+
updateFields.tokenCredits = config.startBalance;
60+
}
61+
62+
const isAutoRefillConfigValid =
63+
config.autoRefillEnabled &&
64+
config.refillIntervalValue != null &&
65+
config.refillIntervalUnit != null &&
66+
config.refillAmount != null;
67+
68+
if (!isAutoRefillConfigValid) {
69+
return updateFields;
70+
}
71+
72+
if (userRecord?.autoRefillEnabled !== config.autoRefillEnabled) {
73+
updateFields.autoRefillEnabled = config.autoRefillEnabled;
74+
}
75+
76+
if (userRecord?.refillIntervalValue !== config.refillIntervalValue) {
77+
updateFields.refillIntervalValue = config.refillIntervalValue;
78+
}
79+
80+
if (userRecord?.refillIntervalUnit !== config.refillIntervalUnit) {
81+
updateFields.refillIntervalUnit = config.refillIntervalUnit;
82+
}
83+
84+
if (userRecord?.refillAmount !== config.refillAmount) {
85+
updateFields.refillAmount = config.refillAmount;
86+
}
87+
88+
return updateFields;
89+
}
90+
91+
module.exports = setBalanceConfig;

api/server/routes/auth.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const {
2323
checkInviteUser,
2424
registerLimiter,
2525
requireLdapAuth,
26+
setBalanceConfig,
2627
requireLocalAuth,
2728
resetPasswordLimiter,
2829
validateRegistration,
@@ -40,6 +41,7 @@ router.post(
4041
loginLimiter,
4142
checkBan,
4243
ldapAuth ? requireLdapAuth : requireLocalAuth,
44+
setBalanceConfig,
4345
loginController,
4446
);
4547
router.post('/refresh', refreshController);

api/server/routes/oauth.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
// file deepcode ignore NoRateLimitingForLogin: Rate limiting is handled by the `loginLimiter` middleware
22
const express = require('express');
33
const passport = require('passport');
4-
const { loginLimiter, logHeaders, checkBan, checkDomainAllowed } = require('~/server/middleware');
4+
const {
5+
checkBan,
6+
logHeaders,
7+
loginLimiter,
8+
setBalanceConfig,
9+
checkDomainAllowed,
10+
} = require('~/server/middleware');
511
const { setAuthTokens } = require('~/server/services/AuthService');
612
const { logger } = require('~/config');
713

@@ -56,6 +62,7 @@ router.get(
5662
session: false,
5763
scope: ['openid', 'profile', 'email'],
5864
}),
65+
setBalanceConfig,
5966
oauthHandler,
6067
);
6168

@@ -80,6 +87,7 @@ router.get(
8087
scope: ['public_profile'],
8188
profileFields: ['id', 'email', 'name'],
8289
}),
90+
setBalanceConfig,
8391
oauthHandler,
8492
);
8593

@@ -100,6 +108,7 @@ router.get(
100108
failureMessage: true,
101109
session: false,
102110
}),
111+
setBalanceConfig,
103112
oauthHandler,
104113
);
105114

@@ -122,6 +131,7 @@ router.get(
122131
session: false,
123132
scope: ['user:email', 'read:user'],
124133
}),
134+
setBalanceConfig,
125135
oauthHandler,
126136
);
127137

@@ -144,6 +154,7 @@ router.get(
144154
session: false,
145155
scope: ['identify', 'email'],
146156
}),
157+
setBalanceConfig,
147158
oauthHandler,
148159
);
149160

@@ -164,6 +175,7 @@ router.post(
164175
failureMessage: true,
165176
session: false,
166177
}),
178+
setBalanceConfig,
167179
oauthHandler,
168180
);
169181

api/server/services/start/checks.js

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ const secretDefaults = {
1313
JWT_REFRESH_SECRET: 'eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418',
1414
};
1515

16+
const deprecatedVariables = [
17+
{
18+
key: 'CHECK_BALANCE',
19+
description:
20+
'Please use the `balance` field in the `librechat.yaml` config file instead.\nMore info: https://librechat.ai/docs/configuration/librechat_yaml/object_structure/balance#overview',
21+
},
22+
{
23+
key: 'START_BALANCE',
24+
description:
25+
'Please use the `balance` field in the `librechat.yaml` config file instead.\nMore info: https://librechat.ai/docs/configuration/librechat_yaml/object_structure/balance#overview',
26+
},
27+
{
28+
key: 'GOOGLE_API_KEY',
29+
description:
30+
'Please use the `GOOGLE_SEARCH_API_KEY` environment variable for the Google Search Tool instead.',
31+
},
32+
];
33+
1634
/**
1735
* Checks environment variables for default secrets and deprecated variables.
1836
* Logs warnings for any default secret values being used and for usage of deprecated `GOOGLE_API_KEY`.
@@ -37,19 +55,11 @@ function checkVariables() {
3755
\u200B`);
3856
}
3957

40-
if (process.env.GOOGLE_API_KEY) {
41-
logger.warn(
42-
'The `GOOGLE_API_KEY` environment variable is deprecated.\nPlease use the `GOOGLE_SEARCH_API_KEY` environment variable instead.',
43-
);
44-
}
45-
46-
if (process.env.OPENROUTER_API_KEY) {
47-
logger.warn(
48-
`The \`OPENROUTER_API_KEY\` environment variable is deprecated and its functionality will be removed soon.
49-
Use of this environment variable is highly discouraged as it can lead to unexpected errors when using custom endpoints.
50-
Please use the config (\`librechat.yaml\`) file for setting up OpenRouter, and use \`OPENROUTER_KEY\` or another environment variable instead.`,
51-
);
52-
}
58+
deprecatedVariables.forEach(({ key, description }) => {
59+
if (process.env[key]) {
60+
logger.warn(`The \`${key}\` environment variable is deprecated. ${description}`);
61+
}
62+
});
5363

5464
checkPasswordReset();
5565
}

api/typedefs.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,11 @@
771771
* @typedef {import('@librechat/data-schemas').IMongoFile} MongoFile
772772
* @memberof typedefs
773773
*/
774+
/**
775+
* @exports IBalance
776+
* @typedef {import('@librechat/data-schemas').IBalance} IBalance
777+
* @memberof typedefs
778+
*/
774779

775780
/**
776781
* @exports MongoUser

0 commit comments

Comments
 (0)