@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 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.
@@ -4,4 +4,5 @@ export * from "./userNotifications.js";
4
4
  export * from "./deviceTypes.js";
5
5
  export * from "./timezones.js";
6
6
  export * from "./dataDownloads.js";
7
+ export * from "./terms.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export const TERMS_VERSION_MAX_LENGTH: 32;
2
+ //# sourceMappingURL=terms.d.ts.map
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAyKA;;;iBAEC;yBAtGwB,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;4BAkDT,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;oCArBF,8BAA8B;mCAqE3D,2BAA2B;8BAA3B,2BAA2B;gCAA3B,2BAA2B;mCAA3B,2BAA2B;+DAsB3B,mCAAmC;qCAAnC,mCAAmC;mEAAnC,mCAAmC;yEAAnC,mCAAmC;mEAAnC,mCAAmC;qEAAnC,mCAAmC;yEAAnC,mCAAmC;2EAAnC,mCAAmC;2BAjGD,2BAA2B;6BAA3B,2BAA2B;oCAI7D,4BAA4B;+BAA5B,4BAA4B;6BAQ5B,4BAA4B;mCAA5B,4BAA4B;uCAA5B,4BAA4B;yCAA5B,4BAA4B;0CAgC5B,8BAA8B;kDAA9B,8BAA8B;4CAA9B,8BAA8B;0CAA9B,8BAA8B;0BAhBX,sBAAsB;oCASzC,uBAAuB;iCAhCG,0BAA0B;mCAgCpD,uBAAuB;kCAAvB,uBAAuB;oCACM,qBAAqB;mCADlD,uBAAuB;+BAAvB,uBAAuB;gCAAvB,uBAAuB;kCAAvB,uBAAuB"}
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":"AA6FA,8BA0GE;AAiCF,wBAAmD;AA/NnD,iDAA4D;AAC5D,6CAAmD;AAUnD,4DA0BC"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justair/justair-library",
3
- "version": "7.6.0",
3
+ "version": "7.7.0-alpha.3af709a",
4
4
  "description": "JustAir Internal Library",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -13,3 +13,4 @@ export * from "./userNotifications.js";
13
13
  export * from "./deviceTypes.js";
14
14
  export * from "./timezones.js";
15
15
  export * from "./dataDownloads.js";
16
+ export * from "./terms.js";
@@ -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,
@@ -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,