From 86c03c0aa5b8c13feaffb4fdafd7f3bb3a703cd5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:04:44 +0300 Subject: [PATCH 1/9] Update website staging image tag to stage-d205505d-1732550603 --- k8s/website/values-stage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/website/values-stage.yaml b/k8s/website/values-stage.yaml index 07c3d24220..89bada8b0d 100644 --- a/k8s/website/values-stage.yaml +++ b/k8s/website/values-stage.yaml @@ -6,7 +6,7 @@ app: replicaCount: 2 image: repository: eu.gcr.io/airqo-250220/airqo-stage-website-api - tag: stage-db11491e-1732546250 + tag: stage-d205505d-1732550603 nameOverride: '' fullnameOverride: '' podAnnotations: {} From 45bbce859687926fce6ad5f84e28273f5a28f6cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:05:04 +0300 Subject: [PATCH 2/9] Update AirQo exceedance production image tag to prod-eccfdc59-1732550643 --- k8s/exceedance/values-prod-airqo.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/exceedance/values-prod-airqo.yaml b/k8s/exceedance/values-prod-airqo.yaml index 09d2361f22..83cb59160c 100644 --- a/k8s/exceedance/values-prod-airqo.yaml +++ b/k8s/exceedance/values-prod-airqo.yaml @@ -4,6 +4,6 @@ app: configmap: env-exceedance-production image: repository: eu.gcr.io/airqo-250220/airqo-exceedance-job - tag: prod-6fe30608-1732546291 + tag: prod-eccfdc59-1732550643 nameOverride: '' fullnameOverride: '' From 5eaffe40bea041ae638ccde9e364fabec721d5e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:05:11 +0300 Subject: [PATCH 3/9] Update KCCA exceedance production image tag to prod-eccfdc59-1732550643 --- k8s/exceedance/values-prod-kcca.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/exceedance/values-prod-kcca.yaml b/k8s/exceedance/values-prod-kcca.yaml index db33018727..1c1c20cc78 100644 --- a/k8s/exceedance/values-prod-kcca.yaml +++ b/k8s/exceedance/values-prod-kcca.yaml @@ -4,6 +4,6 @@ app: configmap: env-exceedance-production image: repository: eu.gcr.io/airqo-250220/kcca-exceedance-job - tag: prod-6fe30608-1732546291 + tag: prod-eccfdc59-1732550643 nameOverride: '' fullnameOverride: '' From 83391a080f8d7cd172a0ed59581da541463746d0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:05:44 +0300 Subject: [PATCH 4/9] Update auth service production image tag to prod-eccfdc59-1732550643 --- k8s/auth-service/values-prod.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/auth-service/values-prod.yaml b/k8s/auth-service/values-prod.yaml index 8ab8a43a1a..b2ba8a0981 100644 --- a/k8s/auth-service/values-prod.yaml +++ b/k8s/auth-service/values-prod.yaml @@ -6,7 +6,7 @@ app: replicaCount: 3 image: repository: eu.gcr.io/airqo-250220/airqo-auth-api - tag: prod-6fe30608-1732546291 + tag: prod-eccfdc59-1732550643 nameOverride: '' fullnameOverride: '' podAnnotations: {} From a2f41ce6d3ce2d93160dd671d00d592c05d8bc36 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:05:55 +0300 Subject: [PATCH 5/9] Update device registry production image tag to prod-eccfdc59-1732550643 --- k8s/device-registry/values-prod.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/device-registry/values-prod.yaml b/k8s/device-registry/values-prod.yaml index 2fc8ab7805..c8cf61edc0 100644 --- a/k8s/device-registry/values-prod.yaml +++ b/k8s/device-registry/values-prod.yaml @@ -6,7 +6,7 @@ app: replicaCount: 3 image: repository: eu.gcr.io/airqo-250220/airqo-device-registry-api - tag: prod-6fe30608-1732546291 + tag: prod-eccfdc59-1732550643 nameOverride: '' fullnameOverride: '' podAnnotations: {} From 03ef2ba12407bf0abf14771828e9daa8b3f03d0d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:06:48 +0300 Subject: [PATCH 6/9] Update analytics production image tag to prod-eccfdc59-1732550643 --- k8s/analytics/values-prod.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/analytics/values-prod.yaml b/k8s/analytics/values-prod.yaml index bf6c39d2ef..15a4301138 100644 --- a/k8s/analytics/values-prod.yaml +++ b/k8s/analytics/values-prod.yaml @@ -8,7 +8,7 @@ images: celeryWorker: eu.gcr.io/airqo-250220/airqo-analytics-celery-worker reportJob: eu.gcr.io/airqo-250220/airqo-analytics-report-job devicesSummaryJob: eu.gcr.io/airqo-250220/airqo-analytics-devices-summary-job - tag: prod-6fe30608-1732546291 + tag: prod-eccfdc59-1732550643 api: name: airqo-analytics-api label: analytics-api From 70d6bd4e45a4230a9b8bb98c6be0ff3f63dd0706 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:08:34 +0300 Subject: [PATCH 7/9] Update predict production image tag to prod-eccfdc59-1732550643 --- k8s/predict/values-prod.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/predict/values-prod.yaml b/k8s/predict/values-prod.yaml index ef5b1458a9..343e8ba847 100644 --- a/k8s/predict/values-prod.yaml +++ b/k8s/predict/values-prod.yaml @@ -7,7 +7,7 @@ images: predictJob: eu.gcr.io/airqo-250220/airqo-predict-job trainJob: eu.gcr.io/airqo-250220/airqo-train-job predictPlaces: eu.gcr.io/airqo-250220/airqo-predict-places-air-quality - tag: prod-6fe30608-1732546291 + tag: prod-eccfdc59-1732550643 api: name: airqo-prediction-api label: prediction-api From b54ff645681b497be9f6b5aba34e5228a94f3abf Mon Sep 17 00:00:00 2001 From: baalmart Date: Mon, 25 Nov 2024 20:40:45 +0300 Subject: [PATCH 8/9] enhancements to the User schema to prevent accidental updates --- src/auth-service/models/User.js | 201 +++++++++++++++++++++----------- 1 file changed, 135 insertions(+), 66 deletions(-) diff --git a/src/auth-service/models/User.js b/src/auth-service/models/User.js index 14d6c5de9f..91def111b6 100644 --- a/src/auth-service/models/User.js +++ b/src/auth-service/models/User.js @@ -17,6 +17,7 @@ const logger = require("log4js").getLogger( const validUserTypes = ["user", "guest"]; const { HttpError } = require("@utils/errors"); const mailer = require("@utils/mailer"); +const ORGANISATIONS_LIMIT = 6; function oneMonthFromNow() { var d = new Date(); @@ -247,14 +248,27 @@ UserSchema.path("group_roles.userType").validate(function (value) { return validUserTypes.includes(value); }, "Invalid userType value"); -UserSchema.pre("save", function (next) { +UserSchema.pre("save", async function (next) { + // Password hashing if (this.isModified("password")) { this.password = bcrypt.hashSync(this.password, saltRounds); } + + // Validate contact information if (!this.email && !this.phoneNumber) { return next(new Error("Phone number or email is required!")); } + // Profile picture validation + if (this.profilePicture && !validateProfilePicture(this.profilePicture)) { + return next( + new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, { + message: "Invalid profile picture URL", + }) + ); + } + + // Network roles handling if (!this.network_roles || this.network_roles.length === 0) { if ( !constants || @@ -281,6 +295,7 @@ UserSchema.pre("save", function (next) { ]; } + // Group roles handling if (!this.group_roles || this.group_roles.length === 0) { if ( !constants || @@ -292,7 +307,7 @@ UserSchema.pre("save", function (next) { httpStatus.INTERNAL_SERVER_ERROR, { message: - "Contact support@airqo.net -- unable to retrieve the default Group or Role to which the User will belong", + "Contact support@airqo.net -- unable to retrieve the default Group or Role", } ); } @@ -307,23 +322,119 @@ UserSchema.pre("save", function (next) { ]; } - if (!this.verified) { - this.verified = false; + // Permissions handling + if (this.permissions && this.permissions.length > 0) { + // Additional permissions validation can be added here if needed + this.permissions = [...new Set(this.permissions)]; // Ensure unique permissions } - if (!this.analyticsVersion) { - this.analyticsVersion = 2; - } + // Ensure default values + this.verified = this.verified ?? false; + this.analyticsVersion = this.analyticsVersion ?? 2; return next(); }); -UserSchema.pre("update", function (next) { - if (this.isModified("password")) { - this.password = bcrypt.hashSync(this.password, saltRounds); +UserSchema.pre( + ["updateOne", "findOneAndUpdate", "updateMany", "update", "save"], + function (next) { + if (this.getUpdate) { + const updates = this.getUpdate(); + const fieldsToValidate = [ + "_id", + "firstName", + "lastName", + "userName", + "email", + "organization", + "long_organization", + "privilege", + "country", + "profilePicture", + "phoneNumber", + "createdAt", + "updatedAt", + "rateLimit", + "lastLogin", + "iat", + ]; + + fieldsToValidate.forEach((field) => { + // Check for empty/null/undefined values + const value = updates[field] || (updates.$set && updates.$set[field]); + + if (value === undefined || value === null || value === "") { + return next( + new HttpError("Validation Error", httpStatus.BAD_REQUEST, { + message: `${field} cannot be empty, null, or undefined`, + }) + ); + } + }); + if (updates) { + // Prevent modification of certain immutable fields + const immutableFields = ["firebase_uid", "email", "createdAt", "_id"]; + + immutableFields.forEach((field) => { + if (updates[field]) delete updates[field]; + + if (updates.$set && updates.$set[field]) { + return next( + new HttpError( + "Modification Not Allowed", + httpStatus.BAD_REQUEST, + { message: `Cannot modify ${field} after creation` } + ) + ); + } + + if (updates.$set) delete updates.$set[field]; + if (updates.$push) delete updates.$push[field]; + }); + + // Validate network roles and group roles limits + if ( + updates.network_roles && + updates.network_roles.length > ORGANISATIONS_LIMIT + ) { + return next( + new HttpError("Validation Error", httpStatus.BAD_REQUEST, { + message: `Maximum ${ORGANISATIONS_LIMIT} network roles allowed`, + }) + ); + } + + if ( + updates.group_roles && + updates.group_roles.length > ORGANISATIONS_LIMIT + ) { + return next( + new HttpError("Validation Error", httpStatus.BAD_REQUEST, { + message: `Maximum ${ORGANISATIONS_LIMIT} group roles allowed`, + }) + ); + } + + // Ensure password is hashed if modified + if (updates.password) { + updates.password = bcrypt.hashSync(updates.password, saltRounds); + } + } + } + + // Additional checks for new documents + if (this.isNew) { + const requiredFields = ["firstName", "lastName", "email"]; + requiredFields.forEach((field) => { + if (this.isModified(field) && !this[field]) { + return next(new Error(`${field} is required`)); + } + }); + } + + next(); } - return next(); -}); +); UserSchema.index({ email: 1 }, { unique: true }); UserSchema.index({ userName: 1 }, { unique: true }); @@ -702,61 +813,18 @@ UserSchema.statics = { async modify({ filter = {}, update = {} } = {}, next) { try { logText("the user modification function........"); - let options = { new: true }; + const options = { new: true }; const fieldNames = Object.keys(update); const fieldsString = fieldNames.join(" "); - let modifiedUpdate = update; - modifiedUpdate["$addToSet"] = {}; - - if (update.password) { - modifiedUpdate.password = bcrypt.hashSync(update.password, saltRounds); - } - - if (modifiedUpdate.profilePicture) { - if (!validateProfilePicture(modifiedUpdate.profilePicture)) { - next( - new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, { - message: "Invalid profile picture URL", - }) - ); - } - } - - if (modifiedUpdate.network_roles) { - if (isEmpty(modifiedUpdate.network_roles.network)) { - delete modifiedUpdate.network_roles; - } else { - modifiedUpdate["$addToSet"] = { - network_roles: { $each: modifiedUpdate.network_roles }, - }; - delete modifiedUpdate.network_roles; - } - } - - if (modifiedUpdate.group_roles) { - if (isEmpty(modifiedUpdate.group_roles.group)) { - delete modifiedUpdate.group_roles; - } else { - modifiedUpdate["$addToSet"] = { - group_roles: { $each: modifiedUpdate.group_roles }, - }; - delete modifiedUpdate.group_roles; - } - } - - if (modifiedUpdate.permissions) { - modifiedUpdate["$addToSet"]["permissions"] = {}; - modifiedUpdate["$addToSet"]["permissions"]["$each"] = - modifiedUpdate.permissions; - delete modifiedUpdate["permissions"]; - } + // Find and update user const updatedUser = await this.findOneAndUpdate( filter, - modifiedUpdate, + update, options ).select(fieldsString); + // Handle update result if (!isEmpty(updatedUser)) { const { _id, ...userData } = updatedUser._doc; return { @@ -765,16 +833,17 @@ UserSchema.statics = { data: userData, status: httpStatus.OK, }; - } else if (isEmpty(updatedUser)) { - next( - new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, { - message: "user does not exist, please crosscheck", - }) - ); } + + // User not found + return next( + new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, { + message: "user does not exist, please crosscheck", + }) + ); } catch (error) { logger.error(`🐛🐛 Internal Server Error -- ${error.message}`); - next( + return next( new HttpError( "Internal Server Error", httpStatus.INTERNAL_SERVER_ERROR, From 4d10a7f23d1fcde73ddc8af9671121577b35dc63 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 20:48:06 +0300 Subject: [PATCH 9/9] Update auth service staging image tag to stage-bd23841e-1732556820 --- k8s/auth-service/values-stage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/auth-service/values-stage.yaml b/k8s/auth-service/values-stage.yaml index b6d2d289b5..bd0736700e 100644 --- a/k8s/auth-service/values-stage.yaml +++ b/k8s/auth-service/values-stage.yaml @@ -6,7 +6,7 @@ app: replicaCount: 2 image: repository: eu.gcr.io/airqo-250220/airqo-stage-auth-api - tag: stage-e0815cc8-1731824148 + tag: stage-bd23841e-1732556820 nameOverride: '' fullnameOverride: '' podAnnotations: {}