Skip to content

Conversation

@nevil-mathew
Copy link
Collaborator

@nevil-mathew nevil-mathew commented Aug 12, 2025

Summary by CodeRabbit

  • New Features

    • Admin login supports email, phone, or username identifiers.
  • Improvements

    • Tokens now include organization and tenant metadata.
    • Stronger admin validation, optional active-session limits, and standardized error responses.
    • User payloads sanitized and profile image URLs resolved.
  • Changes

    • Registration email flow now uses environment template code (no template DB fetch).
    • Domain provisioning adds audit and tenant context.
    • Session creation only sets refresh token when a refresh token is present.
  • Documentation

    • Login docs updated to reflect multi-identifier flow and enriched token payload.

@coderabbitai
Copy link

coderabbitai bot commented Aug 12, 2025

Walkthrough

Replaces the admin login with a tenant-aware multi-identifier flow (email/phone/username), adds bcrypt password verification, admin-role and session-limit checks, session creation and Redis sync, richer token payloads, and sanitization. Also updates registration email template usage, domain creation payload, and refresh_token assignment logic.

Changes

Cohort / File(s) Summary of changes
Admin login flow overhaul
src/services/admin.js
Replaces email-only lookup with normalized identifier (email/phone/username) scoped to DEFAULT_TENANT_CODE; multi-branch DB query for user + orgs/roles; bcryptjs password verification; admin-role enforcement and optional active-session limits; session creation and Redis persistence; DTO-based user transform; enriched token payload (organization_ids/codes, organizations, tenant_code); sanitized user response; standardized failure helper; docblock expanded (signature unchanged).
Registration email template usage
src/services/account.js
Removes early construction of result object and removal of DB fetch for registration email template; notification flow now uses environment-provided template code directly (no templateData lookup).
Organization domain metadata
src/services/organization.js
Adds updated_by and tenant_code to each domain creation payload when provisioning domains for a new organization.
Session refresh token assignment
src/services/user-sessions.js
Adjusts createUserSession logic to set refresh_token only when refreshToken is non-empty (previously checked accessToken), avoiding empty refresh_token values.

Sequence Diagram(s)

sequenceDiagram
  participant C as Client
  participant A as AdminService (src/services/admin.js)
  participant DB as Database
  participant B as bcryptjs
  participant S as SessionService / Redis
  participant T as TokenService

  C->>A: login(identifier, password, deviceInfo)
  A->>A: Normalize identifier -> build tenant-scoped query
  A->>DB: Query user + organizations + roles
  DB-->>A: User record + orgs/roles
  A->>B: Verify password
  B-->>A: Match / Fail
  A->>A: Verify admin role
  A->>S: Check/enforce active-session limit
  A->>S: Create session
  S-->>A: Session details
  A->>A: Build enriched token payload (org ids/codes, organizations, tenant_code)
  A->>T: Generate access & refresh tokens
  T-->>A: Tokens
  A->>A: Sanitize user, resolve image URL, attach identifier
  A->>S: Persist session data in Redis
  S-->>A: Stored
  A-->>C: Return { tokens, user payload }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

I twitch my nose at login doors,
I bind the keys to tenants' cores.
Email, phone, or name in hand,
I set the tokens, mark the land.
Redis hums — the session's home,
A rabbit hops where admins roam.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch admin-login

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@coderabbitai coderabbitai bot changed the title @coderabbitai Add tenant-aware admin login with multi-identifier and session limits Aug 12, 2025
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

🧹 Nitpick comments (3)
src/services/admin.js (3)

281-287: Consider extracting the failure helper as a module-level function

The failure helper function is defined inside the login method, but it could be reused across other methods in this class. Consider extracting it as a private static method or a module-level utility to improve code reusability and maintainability.

+	/**
+	 * Helper for consistent failure responses
+	 * @private
+	 */
+	static #failure(message, status = httpStatusCode.bad_request) {
+		return responses.failureResponse({
+			message,
+			statusCode: status,
+			responseCode: 'CLIENT_ERROR',
+		})
+	}
+
 	static async login(bodyData, deviceInformation) {
 		try {
-			// helper for consistent failure responses
-			const failure = (message, status = httpStatusCode.bad_request) =>
-				responses.failureResponse({
-					message,
-					statusCode: status,
-					responseCode: 'CLIENT_ERROR',
-				})

Then update all failure() calls to AdminHelper.#failure().


294-297: Consider using established validation libraries for identifier type checking

The regex patterns for email, phone, and username validation are defined inline. These patterns may not cover all edge cases and international formats. Consider using well-tested validation libraries like validator.js for more robust validation.

Example with validator.js:

const validator = require('validator')

// Email validation
const isEmail = (str) => validator.isEmail(str)

// Phone validation with international format support
const isPhone = (str) => validator.isMobilePhone(str, 'any')

// Username validation can remain custom or use validator's isAlphanumeric with length check
const isUsername = (str) => validator.isAlphanumeric(str, 'en-US', { ignore: '_' }) && str.length >= 3 && str.length <= 30

408-409: Improve error logging for production

Using console.log for error logging in production is not ideal. Consider using a proper logging framework with appropriate log levels and structured logging.

 		} catch (error) {
-			console.log(error)
+			// Use a proper logger instead of console.log
+			// logger.error('Login failed', { error: error.message, stack: error.stack })
 			throw error
 		}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad49496 and ee0c855.

📒 Files selected for processing (1)
  • src/services/admin.js (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/services/**

⚙️ CodeRabbit Configuration File

This is core business logic. Please check for correctness, efficiency, and potential edge cases.

Files:

  • src/services/admin.js
🔇 Additional comments (5)
src/services/admin.js (5)

325-329: Good use of bcrypt for secure password comparison

The implementation correctly uses bcrypt's async compare method for password verification, which is secure and prevents timing attacks. The error message appropriately doesn't reveal whether the identifier or password was incorrect.


331-338: Admin role check logic looks secure

The nested check for admin role properly validates through the user's organizations and their associated roles, ensuring only users with the ADMIN_ROLE title can proceed. The generic error message maintains security by not revealing the specific reason for failure.


386-389: Good security practice: removing sensitive data

The code properly removes the password field before returning the user object and handles the image URL conversion for downloadable links. This is a good security practice.


392-392: Consider sanitizing the identifier before returning

The original identifier is being returned directly to the user. While this is likely safe since it was already validated, consider if any additional sanitization is needed for security purposes.

Is there any risk in returning the original identifier without additional sanitization? The identifier has already been validated against specific patterns, but double-check if there are any XSS or injection concerns.


366-369: Ensure user.organizations Is Always Defined

The mappings here assume user.organizations is a non-null array. Without a guaranteed default, calling .map may throw at runtime.

• Location: src/services/admin.js (lines 366–369)
• Add a fallback (e.g. user.organizations = user.organizations || []) in your transform/DTO or API parsing layer.
• If you have a UserTransformDTO.transform (or equivalent) method, ensure it initializes organizations to an empty array when absent.

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: 1

🔭 Outside diff range comments (1)
src/services/organization.js (1)

215-221: Mirror tenant_code and updated_by in the update flow to avoid inconsistent domain rows

When adding domains during update(), new rows currently lack tenant_code and updated_by. This creates inconsistent domain records versus the create() path and can break tenant scoping or audit assumptions downstream.

Apply this diff to include these fields (orgDetailsBeforeUpdate is already available in scope):

-          domains.map(async (domain) => {
-            let domainCreationData = {
-              domain: domain,
-              organization_id: id,
-              created_by: loggedInUserId,
-            }
-            await orgDomainQueries.create(domainCreationData)
-          })
+          domains.map(async (domain) => {
+            const domainCreationData = {
+              domain: domain,
+              organization_id: id,
+              created_by: loggedInUserId,
+              updated_by: loggedInUserId,
+              tenant_code: orgDetailsBeforeUpdate?.tenant_code,
+            }
+            await orgDomainQueries.create(domainCreationData)
+          })
🧹 Nitpick comments (3)
src/services/organization.js (1)

54-65: Normalize and dedupe input domains to prevent case/whitespace duplicates

While create() assumes a new organization (so duplicates are unlikely), it can still receive duplicate or mixed-case domains. Consider lowercasing and trimming domains and deduping before insert to avoid unique constraint violations or duplicate rows.

Example approach within this block (pseudocode):

  • Transform bodyData.domains into a set of normalized domains: domainsNormalized = [...new Set(bodyData.domains.map(d => d.trim().toLowerCase()))]
  • Iterate over domainsNormalized for creation.
src/services/user-sessions.js (2)

21-28: Document tenantCode in the JSDoc for createUserSession

The signature includes tenantCode, but the parameter is missing in the JSDoc. Add it to keep docs aligned with the API.

  /**
   * Create a user session.
   * @param {number} userId - The ID of the user.
   * @param {string} [refreshToken=''] - Optional. The refresh token associated with the session.
   * @param {string} [accessToken=''] - Optional. The access token associated with the session.
   * @param {Object} deviceInfo - Information about the device used for the session.
+  * @param {string} tenantCode - Tenant code associated with the session.
   * @returns {Promise<Object>} - A promise that resolves to a success response with the created session details.
   * @throws {Error} - Throws an error if any issue occurs during the process.
   */

30-46: Optionally mirror the trimming check for accessToken for consistency

If you adopt trimming for refresh_token, consider the same for access token to avoid persisting whitespace-only tokens.

Minimal change:

-      if (accessToken !== '') {
-        userSessionDetails.token = accessToken
-      }
+      if (typeof accessToken === 'string' && accessToken.trim() !== '') {
+        userSessionDetails.token = accessToken.trim()
+      }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee0c855 and 5d6117e.

📒 Files selected for processing (3)
  • src/services/account.js (0 hunks)
  • src/services/organization.js (1 hunks)
  • src/services/user-sessions.js (1 hunks)
💤 Files with no reviewable changes (1)
  • src/services/account.js
🧰 Additional context used
📓 Path-based instructions (1)
src/services/**

⚙️ CodeRabbit Configuration File

This is core business logic. Please check for correctness, efficiency, and potential edge cases.

Files:

  • src/services/organization.js
  • src/services/user-sessions.js
🧬 Code Graph Analysis (2)
src/services/organization.js (1)
src/services/org-admin.js (1)
  • bodyData (435-435)
src/services/user-sessions.js (2)
src/services/admin.js (1)
  • refreshToken (379-383)
src/services/account.js (3)
  • refreshToken (527-531)
  • refreshToken (772-776)
  • refreshToken (1456-1460)
🔇 Additional comments (2)
src/services/organization.js (1)

59-61: Adds audit and tenant context for domain provisioning — good change

Including updated_by and tenant_code in domainCreationData improves auditability and ensures domain rows are tenant-scoped at insert time. This aligns well with the tenant-aware login/session work.

src/services/user-sessions.js (1)

30-46: No null/undefined passed as refreshToken at call sites
A repository-wide search confirms every createUserSession invocation supplies an empty string ('') rather than null or undefined. No further changes are needed here.

Comment on lines +44 to 46
if (refreshToken !== '') {
userSessionDetails.refresh_token = refreshToken
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against null/whitespace refresh tokens before persisting

The new check prevents empty strings, but null/undefined or whitespace strings could still slip through from some call sites. Persist only non-empty, trimmed strings to keep DB clean and consistent.

Apply this diff:

-      if (refreshToken !== '') {
-        userSessionDetails.refresh_token = refreshToken
-      }
+      if (typeof refreshToken === 'string' && refreshToken.trim() !== '') {
+        userSessionDetails.refresh_token = refreshToken.trim()
+      }
📝 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
if (refreshToken !== '') {
userSessionDetails.refresh_token = refreshToken
}
if (typeof refreshToken === 'string' && refreshToken.trim() !== '') {
userSessionDetails.refresh_token = refreshToken.trim()
}
🤖 Prompt for AI Agents
In src/services/user-sessions.js around lines 44 to 46, the current check only
blocks the empty string but allows null/undefined or whitespace-only tokens;
update the guard to first verify refreshToken is not null/undefined, trim it,
and persist only if the trimmed value has length > 0 (e.g., const token = typeof
refreshToken === 'string' ? refreshToken.trim() : ''; if (token)
userSessionDetails.refresh_token = token). This ensures only non-empty, trimmed
strings are saved.

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