@justair/justair-library 7.6.0 → 7.7.0-alpha.3af709a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SECURITY.md +21 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/terms.d.ts +2 -0
- package/dist/constants/terms.d.ts.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/models/users.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants/index.js +1 -0
- package/src/constants/terms.js +14 -0
- package/src/constants/tests/constantsBarrel.test.js +6 -0
- package/src/index.js +4 -0
- package/src/models/users.js +26 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Security fixes are prioritized for the current default branch and actively supported releases of `JustAir-Solutions/justair-library`. Older or retired versions may not receive fixes.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
Do not open a public GitHub issue for a suspected vulnerability.
|
|
10
|
+
|
|
11
|
+
Send the initial report to `info@justair.co` with the subject `Security vulnerability: justair-library`. Include the affected repository or component, the potential impact, minimal reproduction steps, and a way to contact you.
|
|
12
|
+
|
|
13
|
+
Do not include passwords, tokens, private keys, personal data, regulated data, or other sensitive payloads in the initial message. Ask for an approved secure transfer method when sensitive evidence is necessary.
|
|
14
|
+
|
|
15
|
+
## Testing boundaries
|
|
16
|
+
|
|
17
|
+
This policy does not grant authorization to access data that is not yours, disrupt service, perform denial-of-service testing, use social engineering, destroy data, or establish persistence. Stop testing and report the issue if sensitive data or credentials are encountered.
|
|
18
|
+
|
|
19
|
+
## Coordinated disclosure
|
|
20
|
+
|
|
21
|
+
Please allow JustAir reasonable time to investigate and remediate before public disclosure. Receipt of a report does not create a promise of a specific response or remediation timeline.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terms.d.ts","sourceRoot":"","sources":["../../src/constants/terms.js"],"names":[],"mappings":"AAaA,0CAA2C"}
|
package/dist/index.d.ts
CHANGED
|
@@ -103,6 +103,7 @@ import { PARAMETERS } from "./constants/pollutants.js";
|
|
|
103
103
|
import { HEAVY_METALS } from "./constants/pollutants.js";
|
|
104
104
|
import { ALERT_LEVEL_WEIGHTS } from "./constants/alertLevels.js";
|
|
105
105
|
import { getAlertWeight } from "./constants/alertLevels.js";
|
|
106
|
+
import { TERMS_VERSION_MAX_LENGTH } from "./constants/terms.js";
|
|
106
107
|
import { DEVICE_TYPES } from "./constants/deviceTypes.js";
|
|
107
108
|
import { DEVICE_TYPE_VALUES } from "./constants/deviceTypes.js";
|
|
108
109
|
import { isAqiCapableDeviceType } from "./constants/deviceTypes.js";
|
|
@@ -121,5 +122,5 @@ import { validateQuietHours } from "./utils/quietHours.js";
|
|
|
121
122
|
import { getZoneMinutes } from "./utils/quietHours.js";
|
|
122
123
|
import { isInQuietWindow } from "./utils/quietHours.js";
|
|
123
124
|
import { resolveQuietHours } from "./utils/quietHours.js";
|
|
124
|
-
export { Database, adminSchema, Admin, configurationsSchema, Configurations, measurementsSchema, Measurements, monitorRequestsSchema, MonitorRequests, monitorsSchema, Monitors, organizationsSchema, Organizations, usersSchema, Users, COOKIE_CONSENT_CATEGORIES, COOKIE_CONSENT_VALUES, validateCookieConsent, eventsSchema, Events, lightMonitorSchema, LightMonitors, monitorSuppliersSchema, MonitorSuppliers, contextsSchema, Contexts, parametersSchema, Parameters, announcementSchema, Announcements, jobsSchema, Jobs, apiKeySchema, ApiKey, UsageMetrics, usageMetricsSchema, Audit, auditSchema, EventsAudit, eventsAuditSchema, MonitorAudit, monitorAuditSchema, parametersEnum, deploymentTypesEnum, AlertsAudit, Alerts, alertsSchema, alertsAuditSchema, Features, featuresSchema, dataCompletenessSchema, DataCompleteness, networkMetricsSchema, NetworkMetrics, rateOfChangeSchema, RateOfChange, changeStreamCheckpointsSchema, ChangeStreamCheckpoints, sitesSchema, Sites, sampleSitesSchema, SampleSites, sampleSiteAuditSchema, SampleSiteAudit, sampleParametersEnum, samplesSchema, Samples, samplesAuditSchema, SamplesAudit, sampleParameterReferenceConcentrations, passwordResetTokenSchema, PasswordResetToken, monitorEpisodesSchema, MonitorEpisode, userNotificationsSchema, UserNotification, AqiAlertNotification, DeviceOfflineNotification, WeeklyDigestNotification, AccountNotification, NOTIFICATION_KINDS, NOTIFICATION_CHANNELS, DELIVERY_STATUSES, DATA_DOWNLOAD_TYPES, dataDownloadSchema, DataDownloads, AqiDataDownload, SampleDataDownload, DATA_DOWNLOAD_MAX_RANGE_DAYS, DATA_DOWNLOAD_ERRORS, validateDataDownloadDateOrder, validateDataDownloadMaxDateRange, validateDataDownloadEmail, normalizeDataDownloadEmail, validateDataDownloadTimezone, normalizeDataDownloadTimezone, PARAMETERS, HEAVY_METALS, ALERT_LEVEL_WEIGHTS, getAlertWeight, DEVICE_TYPES, DEVICE_TYPE_VALUES, isAqiCapableDeviceType, AQI_CAPABLE_DEVICE_TYPES, projectSubscribedMonitors, resolveSubscribedMonitorIdsByUser, VISIBLE_SITE_MONITOR_FILTER, SUBSCRIBED_DEVICES_ERRORS, splitName, DEFAULT_QUIET_HOURS, DEFAULT_TIMEZONE, QUIET_HOURS_ERRORS, isValidTimeString, isValidIANATimezone, validateQuietHours, getZoneMinutes, isInQuietWindow, resolveQuietHours };
|
|
125
|
+
export { Database, adminSchema, Admin, configurationsSchema, Configurations, measurementsSchema, Measurements, monitorRequestsSchema, MonitorRequests, monitorsSchema, Monitors, organizationsSchema, Organizations, usersSchema, Users, COOKIE_CONSENT_CATEGORIES, COOKIE_CONSENT_VALUES, validateCookieConsent, eventsSchema, Events, lightMonitorSchema, LightMonitors, monitorSuppliersSchema, MonitorSuppliers, contextsSchema, Contexts, parametersSchema, Parameters, announcementSchema, Announcements, jobsSchema, Jobs, apiKeySchema, ApiKey, UsageMetrics, usageMetricsSchema, Audit, auditSchema, EventsAudit, eventsAuditSchema, MonitorAudit, monitorAuditSchema, parametersEnum, deploymentTypesEnum, AlertsAudit, Alerts, alertsSchema, alertsAuditSchema, Features, featuresSchema, dataCompletenessSchema, DataCompleteness, networkMetricsSchema, NetworkMetrics, rateOfChangeSchema, RateOfChange, changeStreamCheckpointsSchema, ChangeStreamCheckpoints, sitesSchema, Sites, sampleSitesSchema, SampleSites, sampleSiteAuditSchema, SampleSiteAudit, sampleParametersEnum, samplesSchema, Samples, samplesAuditSchema, SamplesAudit, sampleParameterReferenceConcentrations, passwordResetTokenSchema, PasswordResetToken, monitorEpisodesSchema, MonitorEpisode, userNotificationsSchema, UserNotification, AqiAlertNotification, DeviceOfflineNotification, WeeklyDigestNotification, AccountNotification, NOTIFICATION_KINDS, NOTIFICATION_CHANNELS, DELIVERY_STATUSES, DATA_DOWNLOAD_TYPES, dataDownloadSchema, DataDownloads, AqiDataDownload, SampleDataDownload, DATA_DOWNLOAD_MAX_RANGE_DAYS, DATA_DOWNLOAD_ERRORS, validateDataDownloadDateOrder, validateDataDownloadMaxDateRange, validateDataDownloadEmail, normalizeDataDownloadEmail, validateDataDownloadTimezone, normalizeDataDownloadTimezone, PARAMETERS, HEAVY_METALS, ALERT_LEVEL_WEIGHTS, getAlertWeight, TERMS_VERSION_MAX_LENGTH, DEVICE_TYPES, DEVICE_TYPE_VALUES, isAqiCapableDeviceType, AQI_CAPABLE_DEVICE_TYPES, projectSubscribedMonitors, resolveSubscribedMonitorIdsByUser, VISIBLE_SITE_MONITOR_FILTER, SUBSCRIBED_DEVICES_ERRORS, splitName, DEFAULT_QUIET_HOURS, DEFAULT_TIMEZONE, QUIET_HOURS_ERRORS, isValidTimeString, isValidIANATimezone, validateQuietHours, getZoneMinutes, isInQuietWindow, resolveQuietHours };
|
|
125
126
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AA0KA;;;iBAEC;yBAvGwB,oBAAoB;qBADxB,gBAAgB;4BApEF,mBAAmB;sBAAnB,mBAAmB;qCAI/C,4BAA4B;+BAA5B,4BAA4B;mCAM5B,0BAA0B;6BAA1B,0BAA0B;sCAI1B,6BAA6B;gCAA7B,6BAA6B;+BAQ7B,sBAAsB;yBAAtB,sBAAsB;oCACsB,2BAA2B;8BAA3B,2BAA2B;4BAcvE,mBAAmB;sBAAnB,mBAAmB;0CAAnB,mBAAmB;sCAAnB,mBAAmB;sCAAnB,mBAAmB;6BAMnB,oBAAoB;uBAApB,oBAAoB;mCAnBuB,2BAA2B;8BAA3B,2BAA2B;uCAItE,8BAA8B;iCAA9B,8BAA8B;+BACI,sBAAsB;yBAAtB,sBAAsB;iCAClB,wBAAwB;2BAAxB,wBAAwB;mCAcnB,2BAA2B;8BAA3B,2BAA2B;2BAC5C,kBAAkB;qBAAlB,kBAAkB;6BACd,oBAAoB;uBAApB,oBAAoB;6BACR,0BAA0B;mCAA1B,0BAA0B;sBArCpE,0BAA0B;4BAA1B,0BAA0B;4BAiC1B,oBAAoB;kCAApB,oBAAoB;6BArBpB,sBAAsB;mCAAtB,sBAAsB;+BAAtB,sBAAsB;oCAAtB,sBAAsB;4BA+BtB,oBAAoB;uBAApB,oBAAoB;6BAApB,oBAAoB;kCAApB,oBAAoB;yBACc,sBAAsB;+BAAtB,sBAAsB;uCAIxD,8BAA8B;iCAA9B,8BAA8B;qCAI9B,4BAA4B;+BAA5B,4BAA4B;mCACc,0BAA0B;6BAA1B,0BAA0B;8CAIpE,qCAAqC;wCAArC,qCAAqC;4BAmDT,mBAAmB;sBAAnB,mBAAmB;kCAS/C,yBAAyB;4BAAzB,yBAAyB;sCAAzB,yBAAyB;gCAAzB,yBAAyB;qCAAzB,yBAAyB;8BASzB,qBAAqB;wBAArB,qBAAqB;mCAArB,qBAAqB;6BAArB,qBAAqB;uDAArB,qBAAqB;yCAIrB,gCAAgC;mCAAhC,gCAAgC;sCArDhC,6BAA6B;+BAA7B,6BAA6B;wCAW7B,+BAA+B;iCAA/B,+BAA+B;qCAA/B,+BAA+B;0CAA/B,+BAA+B;yCAA/B,+BAA+B;oCAA/B,+BAA+B;mCAA/B,+BAA+B;sCAA/B,+BAA+B;kCAA/B,+BAA+B;oCAtBF,8BAA8B;mCAsE3D,2BAA2B;8BAA3B,2BAA2B;gCAA3B,2BAA2B;mCAA3B,2BAA2B;+DAsB3B,mCAAmC;qCAAnC,mCAAmC;mEAAnC,mCAAmC;yEAAnC,mCAAmC;mEAAnC,mCAAmC;qEAAnC,mCAAmC;yEAAnC,mCAAmC;2EAAnC,mCAAmC;2BAlGD,2BAA2B;6BAA3B,2BAA2B;oCAI7D,4BAA4B;+BAA5B,4BAA4B;yCAGM,sBAAsB;6BAMxD,4BAA4B;mCAA5B,4BAA4B;uCAA5B,4BAA4B;yCAA5B,4BAA4B;0CAgC5B,8BAA8B;kDAA9B,8BAA8B;4CAA9B,8BAA8B;0CAA9B,8BAA8B;0BAhBX,sBAAsB;oCASzC,uBAAuB;iCAjCG,0BAA0B;mCAiCpD,uBAAuB;kCAAvB,uBAAuB;oCACM,qBAAqB;mCADlD,uBAAuB;+BAAvB,uBAAuB;gCAAvB,uBAAuB;kCAAvB,uBAAuB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/models/users.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/models/users.js"],"names":[],"mappings":"AA8FA,8BAmIE;AAiCF,wBAAmD;AAxPnD,iDAA4D;AAC5D,6CAAmD;AAUnD,4DA0BC"}
|
package/package.json
CHANGED
package/src/constants/index.js
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Terms-of-service agreement vocabulary.
|
|
2
|
+
//
|
|
3
|
+
// Users.termsVersion stores the policy-document version a public user agreed
|
|
4
|
+
// to at registration (e.g. "2026.1" — the published doc's tab name). The
|
|
5
|
+
// VALUE is owned by the frontend signup flow; nothing in the
|
|
6
|
+
// backend hardcodes a current version, so there is deliberately no
|
|
7
|
+
// TERMS_CURRENT_VERSION constant here.
|
|
8
|
+
//
|
|
9
|
+
// The cap below is the one number shared by the two enforcement points in
|
|
10
|
+
// different repos: the Users schema (`maxlength`) and JustAirUserService's
|
|
11
|
+
// registration route validator. Import it from either barrel rather than
|
|
12
|
+
// inlining the literal — if the two drift, the route accepts what the schema
|
|
13
|
+
// rejects and a signup 500s instead of 400ing.
|
|
14
|
+
export const TERMS_VERSION_MAX_LENGTH = 32;
|
|
@@ -8,6 +8,7 @@ import { PARAMETERS, HEAVY_METALS } from '../pollutants.js';
|
|
|
8
8
|
import { ALERT_LEVEL_WEIGHTS, getAlertWeight } from '../alertLevels.js';
|
|
9
9
|
import { DEFAULT_TIMEZONE } from '../timezones.js';
|
|
10
10
|
import { DATA_DOWNLOAD_TYPES } from '../dataDownloads.js';
|
|
11
|
+
import { TERMS_VERSION_MAX_LENGTH } from '../terms.js';
|
|
11
12
|
|
|
12
13
|
test('barrel re-exports the pollutants constants unchanged (identity, not copies)', () => {
|
|
13
14
|
expect(barrel.PARAMETERS).toBe(PARAMETERS);
|
|
@@ -56,6 +57,11 @@ test('barrel exports DATA_DOWNLOAD_TYPES (browser-safe, no Mongoose)', () => {
|
|
|
56
57
|
expect(barrel.DATA_DOWNLOAD_TYPES).toBe(DATA_DOWNLOAD_TYPES);
|
|
57
58
|
});
|
|
58
59
|
|
|
60
|
+
test('barrel exports TERMS_VERSION_MAX_LENGTH (browser-safe, no Mongoose)', () => {
|
|
61
|
+
expect(barrel.TERMS_VERSION_MAX_LENGTH).toBe(TERMS_VERSION_MAX_LENGTH);
|
|
62
|
+
expect(TERMS_VERSION_MAX_LENGTH).toBe(32);
|
|
63
|
+
});
|
|
64
|
+
|
|
59
65
|
// models/dataDownloads.js destructures this list positionally to build the two
|
|
60
66
|
// discriminators, so a reorder would silently swap the persisted dataType
|
|
61
67
|
// values on every future download. Pin contents AND order.
|
package/src/index.js
CHANGED
|
@@ -75,6 +75,7 @@ import {
|
|
|
75
75
|
} from "./constants/alertLevels.js";
|
|
76
76
|
import { DEFAULT_TIMEZONE } from "./constants/timezones.js";
|
|
77
77
|
import { DATA_DOWNLOAD_TYPES } from "./constants/dataDownloads.js";
|
|
78
|
+
import { TERMS_VERSION_MAX_LENGTH } from "./constants/terms.js";
|
|
78
79
|
import {
|
|
79
80
|
DEVICE_TYPES,
|
|
80
81
|
DEVICE_TYPE_VALUES,
|
|
@@ -280,6 +281,9 @@ export {
|
|
|
280
281
|
// AQI alert-level hierarchy (single source of truth)
|
|
281
282
|
ALERT_LEVEL_WEIGHTS,
|
|
282
283
|
getAlertWeight,
|
|
284
|
+
// Terms agreement (JA-3396) — cap shared by the Users schema and
|
|
285
|
+
// JustAirUserService's registration route validator
|
|
286
|
+
TERMS_VERSION_MAX_LENGTH,
|
|
283
287
|
// V2 device-type vocabulary (single source of truth) — JA-3110 / JA-3335
|
|
284
288
|
DEVICE_TYPES,
|
|
285
289
|
DEVICE_TYPE_VALUES,
|
package/src/models/users.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
QUIET_HOURS_ERRORS,
|
|
7
7
|
} from "../utils/quietHours.js";
|
|
8
8
|
import { DEVICE_TYPE_VALUES } from "../constants/deviceTypes.js";
|
|
9
|
+
import { TERMS_VERSION_MAX_LENGTH } from "../constants/terms.js";
|
|
9
10
|
|
|
10
11
|
const COOKIE_CONSENT_CATEGORIES = ["analytics_performance"];
|
|
11
12
|
const COOKIE_CONSENT_VALUES = ["accept", "reject"];
|
|
@@ -193,6 +194,31 @@ const usersSchema = mongoose.Schema(
|
|
|
193
194
|
message: "timezone must be a valid IANA timezone identifier",
|
|
194
195
|
},
|
|
195
196
|
},
|
|
197
|
+
// Terms agreement: the policy-document version the user agreed to at
|
|
198
|
+
// registration (e.g. "2026.1"). The client supplies the value (the signup
|
|
199
|
+
// FE owns it); the backend never hardcodes a
|
|
200
|
+
// current version. Written once at account creation — `immutable` means
|
|
201
|
+
// save() mutations are ignored and strict-mode query updates are
|
|
202
|
+
// stripped, silently by design; treat any need to change it as a product
|
|
203
|
+
// decision, not a bug. Absent = registered before terms tracking existed
|
|
204
|
+
// (or a legacy client that sent only { phone }); absence is legal
|
|
205
|
+
// forever. Deliberately NO acceptance boolean (an account cannot be
|
|
206
|
+
// created without pressing "I Agree", so it would be true on every
|
|
207
|
+
// record) and NO separate acceptance timestamp — createdAt IS the
|
|
208
|
+
// agreement date (grooming decision, 2026-09-03).
|
|
209
|
+
//
|
|
210
|
+
// Blank guard: immutability makes a stored value permanent, so a blank or
|
|
211
|
+
// padded string ("" / " 2026.1") would be an uncorrectable third state —
|
|
212
|
+
// looks present, records nothing. trim + minlength reject it here as the
|
|
213
|
+
// backstop; the registration route validator (JA-3397) is the primary
|
|
214
|
+
// 400 path, both sharing TERMS_VERSION_MAX_LENGTH.
|
|
215
|
+
termsVersion: {
|
|
216
|
+
type: String,
|
|
217
|
+
trim: true,
|
|
218
|
+
minlength: 1,
|
|
219
|
+
immutable: true,
|
|
220
|
+
maxlength: TERMS_VERSION_MAX_LENGTH,
|
|
221
|
+
},
|
|
196
222
|
},
|
|
197
223
|
{
|
|
198
224
|
timestamps: true,
|