Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add endpoint to refresh auth token #981

Conversation

Rushi1109
Copy link
Contributor

Changes

  • Add a new endpoint to hit, when the auth token is expired.
  • Add logic in controller to refresh the auth token.

Completes the backend part for #915

Copy link

coderabbitai bot commented Oct 17, 2024

Walkthrough

The changes in this pull request introduce a new function, refreshAuthToken, in the authController.js file that allows users to obtain a new authentication token using a valid refresh token. A corresponding POST endpoint is added in authRoute.js to facilitate this functionality. Additionally, a success message for the token refresh operation is included in messages.js. The updates enhance the authentication capabilities without altering existing user-related routes.

Changes

File Change Summary
Server/controllers/authController.js Added refreshAuthToken function to generate a new authentication token using a valid refresh token. Exported the function.
Server/routes/authRoute.js Added a new POST endpoint /refresh that utilizes the refreshAuthToken function.
Server/utils/messages.js Added a new success message AUTH_TOKEN_REFRESHED for successful token refresh operations.
Server/index.js Updated import path for AppSettings and adjusted service initialization sequence.
Server/openapi.json Added new endpoint POST /auth/refresh to the OpenAPI specification for token refresh.
Server/tests/controllers/authController.test.js Introduced tests for refreshAuthToken, covering various scenarios including success and error cases.

Possibly related PRs

  • Generate refresh token & add it to response #960: This PR introduces a new function, issueRefreshToken, which is closely related to the refreshAuthToken function added in the main PR, as both deal with generating and managing refresh tokens in the authentication process.
  • Add middleware to verify refresh token #973: This PR adds middleware to verify refresh tokens, which complements the refreshAuthToken function by ensuring that the refresh tokens being processed are valid, thereby enhancing the overall security and functionality of the authentication system.
  • Feat/server auth controller tests #922: This PR enhances the testing framework for the authController, which includes tests for the refreshAuthToken function added in the main PR, ensuring its functionality is validated.
  • feat/be/settings service tests #1001: This PR focuses on enhancing the SettingsService, which is relevant because the refreshAuthToken function relies on application settings for token management, linking it to the overall configuration and management of tokens.

Suggested reviewers

  • ajhollid
  • marcelluscaio

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between c28231d and 043698e.

📒 Files selected for processing (3)
  • Server/controllers/authController.js (2 hunks)
  • Server/routes/authRoute.js (2 hunks)
  • Server/utils/messages.js (1 hunks)
🧰 Additional context used
🪛 Biome
Server/controllers/authController.js

[error] 222-222: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 223-223: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🔇 Additional comments (2)
Server/routes/authRoute.js (1)

14-14: Yo, this import's lookin' fresh, dawg!

The addition of refreshAuthToken to the import list is on point. It's settin' us up for that sweet token refresh action we've been waitin' for.

Server/utils/messages.js (1)

78-78: Yo, this message is straight fire! 🔥

Aight, check it out, homie. This new success message is droppin' some serious heat in the Auth Controller section. It's like mom's spaghetti, but for auth tokens, ya feel me?

Let's make sure this message is the real MVP. Run this script to see if it's used properly:

✅ Verification successful

Uh oh, the search didn't go as planned! 🚧

Looks like rg doesn’t recognize "javascript" as a file type. Let’s switch it up to "js" and give it another shot:


Everything's smooth with AUTH_TOKEN_REFRESHED! 🎉

Looks like the new success message is shining bright and being used just right in the Auth Controller. No hiccups found!

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Yo, we're gonna search for this fresh message usage, dawg
rg --type javascript "AUTH_TOKEN_REFRESHED" -C 3

Length of output: 83


Script:

#!/bin/bash
# Correcting the file type to JS for proper search
rg --type js "AUTH_TOKEN_REFRESHED" -C 3

Length of output: 975

Comment on lines 220 to 223
const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true });
// delete old token related data
delete payloadData.iat;
delete payloadData.exp;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don't let your security get shaky with ignoreExpiration: true

By verifying oldAuthToken with ignoreExpiration: true, you're potentially allowing expired or tampered tokens to slip through, which could make your security knees weak. This could be exploited by an attacker using an old or malicious token. Instead, consider validating the token without ignoring expiration or fetch the user data securely from a trusted source before issuing a new token.

🧰 Tools
🪛 Biome

[error] 222-222: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 223-223: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

Comment on lines 202 to 214
jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
return next(error);
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

You're missing a beat with the asynchronous jwt.verify callback

The way the code is structured, jwt.verify is used with a callback, making it asynchronous. However, the code that generates the new access token marches on outside this callback. This could lead to generating a new access token even if the refresh token is invalid—a security misstep you don't want to take. To keep the flow steady and secure, move the dependent code inside the callback after successful verification.

Apply this diff to keep the rhythm on point:

 const refreshAuthToken = (req, res, next) => {
   try {
     // check for refreshToken 
     const refreshToken = req.headers["x-refresh-token"];

     if (!refreshToken) {
       // No refresh token provided
       const error = new Error(errorMessages.NO_REFRESH_TOKEN);
       error.status = 401;
       error.service = SERVICE_NAME;
       error.method = "handleExpiredJwtToken";
       return next(error);
     }

     // Verify refresh token
     const { refreshTokenSecret } = req.settingsService.getSettings();

-    jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => {
-      if (refreshErr) {
-        // Invalid or expired refresh token, trigger logout
-        const errorMessage =
-          refreshErr.name === "TokenExpiredError"
-            ? errorMessages.EXPIRED_REFRESH_TOKEN
-            : errorMessages.INVALID_REFRESH_TOKEN;
-        const error = new Error(errorMessage);
-        error.status = 401;
-        error.service = SERVICE_NAME;
-        return next(error);
-      }
-    });
-
+    jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => {
+      if (refreshErr) {
+        // Invalid or expired refresh token, trigger logout
+        const errorMessage =
+          refreshErr.name === "TokenExpiredError"
+            ? errorMessages.EXPIRED_REFRESH_TOKEN
+            : errorMessages.INVALID_REFRESH_TOKEN;
+        const error = new Error(errorMessage);
+        error.status = 401;
+        error.service = SERVICE_NAME;
+        return next(error);
+      }
+
+      // Refresh token is valid and unexpired, generate new access token
+      const oldAuthToken = getTokenFromHeaders(req.headers);
+      const { jwtSecret } = req.settingsService.getSettings();
+
+      const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true });
+      // delete old token related data
+      delete payloadData.iat;
+      delete payloadData.exp;
+
+      const newAuthToken = issueToken(payloadData, tokenType.ACCESS_TOKEN, req.settingsService.getSettings());
+
+      return res.status(200).json({
+        success: true,
+        msg: successMessages.AUTH_TOKEN_REFRESHED,
+        data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken },
+      });
+    });

   } catch (error) {
     next(handleError(error, SERVICE_NAME, "refreshAuthToken"));
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
return next(error);
}
});
jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
return next(error);
}
// Refresh token is valid and unexpired, generate new access token
const oldAuthToken = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true });
// delete old token related data
delete payloadData.iat;
delete payloadData.exp;
const newAuthToken = issueToken(payloadData, tokenType.ACCESS_TOKEN, req.settingsService.getSettings());
return res.status(200).json({
success: true,
msg: successMessages.AUTH_TOKEN_REFRESHED,
data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken },
});
});

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 043698e and 9f861f9.

📒 Files selected for processing (1)
  • Server/controllers/authController.js (2 hunks)
🧰 Additional context used
🪛 Biome
Server/controllers/authController.js

[error] 221-221: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 222-222: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🔇 Additional comments (3)
Server/controllers/authController.js (3)

176-198: Yo, this function declaration is straight fire! 🔥

The JSDoc comments are on point, givin' us the 411 on what this function's all about. The initial error handling is tight, checkin' for that refresh token like a bouncer at the club. Keep it up, homie!


232-235: Error handling's tighter than Mom's spaghetti grip! 👌

You're wrappin' this function in a try-catch like Eminem wraps his lyrics around a beat. That handleError function is probably handlin' errors across the whole app like a pro. Keep that consistency flowin', it's music to a developer's ears!


520-520: Exportin' like a boss! 🚀

You've added refreshAuthToken to the export list smoother than Eminem adds a new track to his album. It's ready to drop some fresh authentication beats in other parts of the app. Nice work, MC Dev!

const error = new Error(errorMessages.NO_REFRESH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "handleExpiredJwtToken";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we do not have a refreshToken. should the method be handleExpiredJwtToken?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error.method is meant to refer to the method that is throwing the error so we have more information in the logs to quickly track down where errors are being thrown.

In this case the method name should be refreshAuthToken

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error.method is meant to refer to the method that is throwing the error so we have more information in the logs to quickly track down where errors are being thrown.

In this case the method name should be refreshAuthToken

Yeah. I am aware of that, I copied the code from the middleware, so forgot to update the method name there. I'll update it.

@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Oct 18, 2024
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Oct 18, 2024
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Oct 18, 2024
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Oct 18, 2024
Copy link
Collaborator

@ajhollid ajhollid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good to me for the most part 👍

The error.method value should be the name of the method that is throwing the error, so that ca be updated.

Please include updated tests for authController and document the new endpoint in the openapi.json file so we have complete documentation and tests.

const error = new Error(errorMessages.NO_REFRESH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "handleExpiredJwtToken";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error.method is meant to refer to the method that is throwing the error so we have more information in the logs to quickly track down where errors are being thrown.

In this case the method name should be refreshAuthToken

@Rushi1109 Rushi1109 marked this pull request as draft October 18, 2024 04:28
@Rushi1109
Copy link
Contributor Author

Please include updated tests for authController and document the new endpoint in the openapi.json file so we have complete documentation and tests.

Hello @ajhollid,
I tried adding test cases but some lines are uncovered. I am having difficulties, so I would need some help here.

@ajhollid
Copy link
Collaborator

Please include updated tests for authController and document the new endpoint in the openapi.json file so we have complete documentation and tests.

Hello @ajhollid, I tried adding test cases but some lines are uncovered. I am having difficulties, so I would need some help here.

Hi @Rushi1109,

I'd be happy to help with some test cases, but I would need you to grant me access to your forked repo so I can push to this branch.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork

@Rushi1109
Copy link
Contributor Author

I'd be happy to help with some test cases, but I would need you to grant me access to your forked repo so I can push to this branch.

Hello @ajhollid,

image
I guess It was enabled by default.

@ajhollid
Copy link
Collaborator

I'd be happy to help with some test cases, but I would need you to grant me access to your forked repo so I can push to this branch.

Hello @ajhollid,

image
I guess It was enabled by default.

OK ill give it a try when I have a chance. It's the weekend coming up so it might be a couple days. I will get to it as soon as I can 👍

@Rushi1109
Copy link
Contributor Author

OK ill give it a try when I have a chance. It's the weekend coming up so it might be a couple days. I will get to it as soon as I can 👍

Sure.

@ajhollid
Copy link
Collaborator

ajhollid commented Oct 19, 2024

Hey @Rushi1109 ,

I refactored the function slightly, I'm not sure why all code following verifying refresh token was carried out in the callback, but I don't think there's any reason to. Please review the changes I made and make sure everything is still functioning as you expect.

I also added the missing test cases so you've got 100% coverage again now.

@ajhollid
Copy link
Collaborator

@Rushi1109 do you still want to finish this PR? I will close it otherwise.

Thanks!

@Rushi1109
Copy link
Contributor Author

Rushi1109 commented Oct 22, 2024

Hello @ajhollid,
Sorry for the inactivity.

I also added the missing test cases so you've got 100% coverage again now.

Thanks for adding those test cases.

I refactored the function slightly, I'm not sure why all code following verifying refresh token was carried out in the callback, but I don't think there's any reason to. Please review the changes I made and make sure everything is still functioning as you expect.

Yeah I guess it should not be inside the callback. Thanks for correction.

I have reviewed the changes and it looks good to me.

@Rushi1109 Rushi1109 marked this pull request as ready for review October 22, 2024 05:06
@Rushi1109
Copy link
Contributor Author

I think the PR is ready to be merged now.

@ajhollid.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (4)
Server/index.js (1)

Line range hint 147-158: Yo, this service initialization is fire, but let's make it bulletproof!

Aight, I see what you did there with the service initialization. It's like you're cookin' up mom's spaghetti, but in the right order this time. That's dope!

But listen up, we gotta make sure this pasta don't stick to the pot, you feel me? Here's what I'm thinkin':

  1. After connectDbAndRunServer, throw in some error handling. What if the DB connection fails? We don't want our whole app to choke.
  2. Same deal with loadSettings. If that fails, we're in trouble, dawg.
  3. For the emailService, what if the settings ain't loaded right? We gotta have a plan B.

Here's a little somethin' to make it even more fire:

 await connectDbAndRunServer(app, db);
+if (!db.isConnected()) {
+  throw new Error("DB connection failed. Can't serve mom's spaghetti without a plate, yo!");
+}
 const settingsService = new SettingsService(AppSettings);

 await settingsService.loadSettings();
+if (!settingsService.isLoaded()) {
+  throw new Error("Failed to load settings. We're lost in the sauce, homie!");
+}
 const emailService = new EmailService(
   settingsService,
   fs,
   path,
   compile,
   mjml2html,
   nodemailer,
   logger
 );
+if (!emailService.isInitialized()) {
+  logger.warn("Email service init failed. We might not be able to send out invites to the spaghetti party.");
+}

This way, if somethin' goes wrong, we know exactly where we dropped the spaghetti, you feel me?

Server/openapi.json (3)

264-334: Yo, this new endpoint's got some fire, but let's spice it up a bit more!

The /auth/refresh endpoint is lookin' fresh, but there's room for improvement, ya feel me? Here's what's cookin':

  1. That empty request body? It's like servin' an empty plate. Let's ditch it if we ain't usin' it.
  2. The authorization header needs more explanation. What's it for? Old access token? Let's spell it out.
  3. The 401 response could use some extra flavor. What kinda unauthorized we talkin' about?

Here's how we can make it pop:

 "/auth/refresh": {
   "post": {
     "tags": [
       "auth"
     ],
     "description": "Generates a new auth token if the refresh token is valid.",
-    "requestBody": {
-      "content": {
-        "application/json": {
-          "schema": {
-            "type": "object",
-            "properties": {}
-          }
-        }
-      },
-      "required": false
-    },
     "parameters": [
       {
         "name": "x-refresh-token",
         "in": "header",
         "description": "Refresh token required to generate a new auth token.",
         "required": true,
         "schema": {
           "type": "string"
         }
       },
       {
         "name": "authorization",
         "in": "header",
-        "description": "Old access token, used to extract payload).",
+        "description": "Old access token (Bearer token), used to extract payload for validation.",
         "required": true,
         "schema": {
           "type": "string"
         }
       }
     ],
     "responses": {
       "200": {
         "description": "New access token generated.",
         "content": {
           "application/json": {
             "schema": {
               "$ref": "#/components/schemas/SuccessResponse"
             }
           }
         }
       },
       "401": {
-        "description": "Unauthorized or invalid refresh token.",
+        "description": "Unauthorized. Either the refresh token is invalid or expired, or the old access token is invalid.",
         "content": {
           "application/json": {
             "schema": {
               "$ref": "#/components/schemas/ErrorResponse"
             }
           }
         }
       },
       "500": {
         "description": "Internal Server Error",
         "content": {
           "application/json": {
             "schema": {
               "$ref": "#/components/schemas/ErrorResponse"
             }
           }
         }
       }
     }
   }
 },

These changes gonna make your API documentation as smooth as mom's spaghetti, you dig?


Line range hint 1-2576: Yo, we gotta tighten up our security game, it's looking shakier than my knees right now!

Listen up, fam! Our static analysis tools are throwin' up red flags about our security setup. Here's the 411:

  1. We're missin' some global security rules. It's like leavin' the front door open, you feel me?
  2. Some of our operations might be rockin' empty security. That's a no-go, chief.

But hold up, let's not panic yet. I peeped that we got a bearerAuth scheme defined in our components. That's solid. But we gotta make sure we're usin' it consistently across the board.

Here's how we can lock this down tighter than mom's spaghetti recipe:

  1. Add a global security requirement. This'll be our default bouncer at the door.
  2. Double-check all our endpoints. Make sure they're either using the global security or have their own VIP list.

Try adding this at the root level of your OpenAPI spec:

"security": [
  {
    "bearerAuth": []
  }
]

Then, for any public endpoints that don't need auth, you can override it like this:

"security": []

Let's get this security tight, so our API's not sweatin' like it just ran a marathon, ya dig?

You want me to whip up a script to check all our endpoints for proper security? Just say the word, and I'll cook up something faster than you can say "mom's spaghetti"!


Line range hint 1-2576: Yo, our API's structure is tight, but we can make it flow smoother than Eminem's rhymes!

Aight, check it. Our API's got a solid foundation, but we can take it from good to straight fire. Here's the breakdown:

  1. Descriptions be hittin' different levels. Some are droppin' knowledge bombs, others are keepin' it too real (as in, too brief).
  2. Error responses are all over the place. Some are specific like mom's spaghetti recipe, others are vague like "what's in this mystery meat?"
  3. Tags are consistent, but we could group these endpoints tighter than a rapper's crew.

Here's how we level up:

  1. Standardize them descriptions. Every endpoint should tell a story - what it does, what it needs, what it gives back.
  2. Error response game strong. Let's break down possible errors for each endpoint. Users should know what went wrong, you feel me?
  3. Consider adding some sub-categories to our tags. Like, under "monitors", we could have "create", "read", "update", "delete". Makes it easier to navigate than finding your way outta 8 Mile.

Example for standardizing descriptions:

 "/monitors/{monitorId}": {
   "get": {
     "tags": [
       "monitors"
     ],
-    "description": "Get monitor by id",
+    "description": "Retrieves detailed information about a specific monitor. Requires a valid monitor ID. Returns monitor configuration, status, and recent check results.",
     ...
   }
 }

For error responses, let's get specific:

 "responses": {
   "200": {
     "description": "OK",
     ...
   },
+  "400": {
+    "description": "Bad Request - Invalid monitor ID format",
+    ...
+  },
   "422": {
-    "description": "Unprocessable Content",
+    "description": "Unprocessable Entity - Monitor not found or user lacks permission",
     ...
   },
   "500": {
     "description": "Internal Server Error",
     ...
   }
 }

These changes gonna make our API docs clearer than Eminem's diction in Rap God. Users'll navigate this easier than B-Rabbit in a rap battle!

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 9f861f9 and 225938d.

📒 Files selected for processing (5)
  • Server/controllers/authController.js (2 hunks)
  • Server/index.js (1 hunks)
  • Server/openapi.json (2 hunks)
  • Server/tests/controllers/authController.test.js (3 hunks)
  • Server/utils/messages.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Server/utils/messages.js
🧰 Additional context used
🪛 Biome
Server/controllers/authController.js

[error] 221-221: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 222-222: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

Server/tests/controllers/authController.test.js

[error] 340-340: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🪛 checkov
Server/openapi.json

[HIGH] 1-2576: Ensure that the global security field has rules defined

(CKV_OPENAPI_4)


[HIGH] 1-2576: Ensure that security operations is not empty.

(CKV_OPENAPI_5)

@@ -36,7 +36,7 @@ import mjml2html from "mjml";

// Settings Service and dependencies
import SettingsService from "./service/settingsService.js";
import AppSettings from "../db/models/AppSettings.js";
import AppSettings from "./db/models/AppSettings.js";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

AppSettings.js import path is invalid!
The new path "./db/models/AppSettings.js" does not exist. Please verify the correct location of AppSettings.js and update the import path accordingly.

🔗 Analysis chain

Yo, check that import path, homie!

Aight, so we switched up the import path for AppSettings. It's like we're tryin' to find mom's spaghetti in a new kitchen, you feel me? Make sure this new path "./db/models/AppSettings.js" is legit and we ain't gonna trip when we try to serve up those settings.

Let's run this script to make sure we ain't lost in the sauce:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Yo, we're gonna check if that AppSettings file is where we think it is

# First, let's see if the file exists
if [ -f "./db/models/AppSettings.js" ]; then
    echo "AppSettings.js is right where it should be, fam!"
else
    echo "Uh oh, AppSettings.js is MIA. We might have a problem, dawg."
fi

# Now, let's make sure it's got the goods
grep -q "export default" "./db/models/AppSettings.js" && echo "AppSettings is exportin' something, we good!" || echo "Hold up, AppSettings ain't exportin'. We might need to fix that, yo."

Length of output: 437

Comment on lines +186 to +233
const refreshAuthToken = async (req, res, next) => {
try {
// check for refreshToken
const refreshToken = req.headers["x-refresh-token"];

if (!refreshToken) {
// No refresh token provided
const error = new Error(errorMessages.NO_REFRESH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "refreshAuthToken";
return next(error);
}

// Verify refresh token
const appSettings = await req.settingsService.getSettings();
const { refreshTokenSecret } = appSettings;
jwt.verify(refreshToken, refreshTokenSecret, async (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
return next(error);
}
});
// Refresh token is valid and unexpired, generate new access token
const oldAuthToken = getTokenFromHeaders(req.headers);
const { jwtSecret } = await req.settingsService.getSettings();
const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true });
// delete old token related data
delete payloadData.iat;
delete payloadData.exp;
const newAuthToken = issueToken(payloadData, tokenType.ACCESS_TOKEN, appSettings);

return res.status(200).json({
success: true,
msg: successMessages.AUTH_TOKEN_REFRESHED,
data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "refreshAuthToken"));
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Yo, dawg! Your token refresh game's weak, knees shaky!

Listen up, homie! Your token refresh function's got more issues than Eminem's got rhymes. Let's break it down:

  1. You're lettin' expired tokens slide with ignoreExpiration: true. That's like leaving your front door wide open in the hood! Fix that security hole, fam!

  2. You're using delete on lines 221-222 like it's going out of style. That's slower than my grandma's dial-up! Try setting those props to undefined instead, it'll make your code run faster than Usain Bolt!

  3. Your error handling's as basic as vanilla ice cream. Spice it up with some proper logging, ya feel me?

Here's how you can clean up your act:

- const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true });
+ const payloadData = jwt.verify(oldAuthToken, jwtSecret);

- delete payloadData.iat;
- delete payloadData.exp;
+ payloadData.iat = undefined;
+ payloadData.exp = undefined;

+ logger.info('Auth token refreshed', { userId: payloadData.userId });

Don't let your code be a one-hit wonder, make it a classic!

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const refreshAuthToken = async (req, res, next) => {
try {
// check for refreshToken
const refreshToken = req.headers["x-refresh-token"];
if (!refreshToken) {
// No refresh token provided
const error = new Error(errorMessages.NO_REFRESH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "refreshAuthToken";
return next(error);
}
// Verify refresh token
const appSettings = await req.settingsService.getSettings();
const { refreshTokenSecret } = appSettings;
jwt.verify(refreshToken, refreshTokenSecret, async (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
return next(error);
}
});
// Refresh token is valid and unexpired, generate new access token
const oldAuthToken = getTokenFromHeaders(req.headers);
const { jwtSecret } = await req.settingsService.getSettings();
const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true });
// delete old token related data
delete payloadData.iat;
delete payloadData.exp;
const newAuthToken = issueToken(payloadData, tokenType.ACCESS_TOKEN, appSettings);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_TOKEN_REFRESHED,
data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "refreshAuthToken"));
}
};
const refreshAuthToken = async (req, res, next) => {
try {
// check for refreshToken
const refreshToken = req.headers["x-refresh-token"];
if (!refreshToken) {
// No refresh token provided
const error = new Error(errorMessages.NO_REFRESH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "refreshAuthToken";
return next(error);
}
// Verify refresh token
const appSettings = await req.settingsService.getSettings();
const { refreshTokenSecret } = appSettings;
jwt.verify(refreshToken, refreshTokenSecret, async (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
return next(error);
}
});
// Refresh token is valid and unexpired, generate new access token
const oldAuthToken = getTokenFromHeaders(req.headers);
const { jwtSecret } = await req.settingsService.getSettings();
const payloadData = jwt.verify(oldAuthToken, jwtSecret);
// delete old token related data
payloadData.iat = undefined;
payloadData.exp = undefined;
const newAuthToken = issueToken(payloadData, tokenType.ACCESS_TOKEN, appSettings);
logger.info('Auth token refreshed', { userId: payloadData.userId });
return res.status(200).json({
success: true,
msg: successMessages.AUTH_TOKEN_REFRESHED,
data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "refreshAuthToken"));
}
};
🧰 Tools
🪛 Biome

[error] 221-221: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 222-222: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

@ajhollid
Copy link
Collaborator

Hello @ajhollid,
Sorry for the inactivity.

I also added the missing test cases so you've got 100% coverage again now.

Thanks for adding those test cases.

I refactored the function slightly, I'm not sure why all code following verifying refresh token was carried out in the callback, but I don't think there's any reason to. Please review the changes I made and make sure everything is still functioning as you expect.

Yeah I guess it should not be inside the callback. Thanks for correction.

I have reviewed the changes and it looks good to me.

No problem, just wanted to make sure you were still with us 😂

@ajhollid
Copy link
Collaborator

I think the PR is ready to be merged now.

@ajhollid.

Awesome thanks for your work! I will review as soon as I can 👍

@Rushi1109
Copy link
Contributor Author

No problem, just wanted to make sure you were still with us 😂

Yeah, It was a weekend. So, I wasn't working on it.

Copy link
Collaborator

@ajhollid ajhollid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks for making revisions 👍

@ajhollid ajhollid merged commit a58c1a4 into bluewave-labs:develop Oct 23, 2024
1 check passed
@Rushi1109 Rushi1109 deleted the 915-be-add-new-endpoint-for-refreshing-access-token branch October 23, 2024 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants