Skip to content

Encrypted phone, admin create checks, validator updates, unified login#811

Merged
nevil-mathew merged 5 commits intodevelopfrom
admin-create-imp
Sep 3, 2025
Merged

Encrypted phone, admin create checks, validator updates, unified login#811
nevil-mathew merged 5 commits intodevelopfrom
admin-create-imp

Conversation

@nevil-mathew
Copy link
Collaborator

@nevil-mathew nevil-mathew commented Sep 2, 2025

Summary by CodeRabbit

  • New Features

    • Sign-up accepts username and phone (with country code) in addition to email.
    • Login supports a single identifier: email, phone, or username.
  • Bug Fixes

    • Stronger validation: email optional with stricter checks; username, phone, and phone_code validations added; require at least one of email or phone and enforce country code when phone is used.
    • Admin creation now detects duplicates across email, phone, and username and stores phone encrypted.

@coderabbitai
Copy link

coderabbitai bot commented Sep 2, 2025

Walkthrough

Adds optional email/username/phone inputs, encrypts phone and email when present, and replaces single-field duplicate checks with a transactional OR-based existence query across encrypted email, encrypted phone (with phone_code), and username; validators updated for multi-identifier create/login and cross-field rules.

Changes

Cohort / File(s) Summary
Admin creation service updates
src/services/admin.js
Compute lowercase plaintext email if present; encrypt email and phone when provided; build dynamic OR criteria [{email}, {phone, phone_code}, {username}] and perform transactional [Op.or] existence check scoped to tenant; throw ADMIN_USER_ALREADY_EXISTS on match; write encrypted phone into payload before insert.
Validators: create/login multi-identifier support
src/validators/v1/admin.js
Create: make email optional with regex and conditional normalization; add optional username, phone, phone_code; enforce "email or phone" and require phone_code when phone present. Login: replace email with unified identifier accepting email/phone/username, apply conditional normalization/validation, and require phone_code when identifier is a phone.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant V as Validator (`v1/admin`)
  participant S as AdminService (`AdminHelper.create`)
  participant DB as Database

  rect rgba(230,245,255,0.6)
    C->>V: POST /admin (email?/phone?/username?, password, phone_code?)
    V->>V: Validate fields and cross-field rules
    V-->>C: 400 on validation error
  end

  V->>S: AdminHelper.create(bodyData)
  S->>S: normalize email lowercase (if present)
  S->>S: encrypt email (if present) and phone (if present)
  S->>DB: SELECT id WHERE tenant AND [Op.or]: {email}, {phone + phone_code}, {username} (txn)
  alt existing user found
    S-->>C: Error: ADMIN_USER_ALREADY_EXISTS
  else
    S->>S: set bodyData.phone = encryptedPhone (if any)
    S->>DB: INSERT admin (txn)
    DB-->>S: Created
    S-->>C: 201 Created
  end
Loading
sequenceDiagram
  autonumber
  participant C as Client
  participant V as Validator (`v1/admin`)
  note over C,V: Login with unified identifier
  C->>V: POST /admin/login (identifier, password, [phone_code])
  V->>V: Determine identifier type:
  V->>V: - email → normalize & validate  
  V->>V: - phone → validate digits/length & require phone_code  
  V->>V: - username → validate pattern
  V-->>C: 400 on invalid format/missing deps
  V-->>C: pass to auth handler on success
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

I nibble at keys beneath moonlight,
Encrypt the digits, tuck them tight.
Email, phone, or name that gleams,
I check each burrow for duplicate dreams.
A happy hop — a user who beams. 🐇🔐


📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ba3cddc and 8cd3daf.

📒 Files selected for processing (1)
  • src/services/admin.js (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/admin.js
✨ 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-create-imp

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 or @coderabbit 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 Encrypted phone, admin create checks, validator updates, unified login Sep 2, 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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/services/admin.js (2)

444-446: Duplicate validation helper functions

The same validation helper functions (isEmail, isPhone, isUsername) are defined multiple times in the file (lines 311-313 and 444-446). This violates the DRY principle.

Extract these validation helpers to a shared utility module:

// Create a new file: src/utils/identifierValidation.js
module.exports = {
    isEmail: (str) => /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(str),
    isPhone: (str) => /^\+?[1-9]\d{1,14}$/.test(str),
    isUsername: (str) => /^[a-zA-Z0-9_]{3,30}$/.test(str)
}

Then import and use them in the service file:

+const { isEmail, isPhone, isUsername } = require('@utils/identifierValidation')

 // Remove the duplicate definitions at lines 311-313 and 444-446
-const isEmail = (str) => /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(str)
-const isPhone = (str) => /^\+?[1-9]\d{1,14}$/.test(str)
-const isUsername = (str) => /^[a-zA-Z0-9_]{3,30}$/.test(str)

310-313: Align phone validation patterns across services and validators
Service helpers use /^\+?[1-9]\d{1,14}$/ (e.g. src/services/admin.js:312, src/services/account.js:674), while validators use /^[0-9]{7,15}$/ and ^\d{6,15}$ (e.g. src/validators/v1/admin.js:45,88; src/validators/v1/account.js:69,120). Standardize on a single regex (or E.164 format) and extract it into a shared helper.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7b7e749 and 8248690.

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

⚙️ CodeRabbit configuration file

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

Files:

  • src/services/admin.js
src/validators/**

⚙️ CodeRabbit configuration file

Validate all incoming data thoroughly. Check for missing or incomplete validation rules.

Files:

  • src/validators/v1/admin.js
🧬 Code graph analysis (2)
src/services/admin.js (2)
src/services/account.js (8)
  • encryptedPhoneNumber (117-117)
  • encryptedPhoneNumber (1185-1185)
  • emailEncryption (29-29)
  • criteria (126-126)
  • criteria (1194-1194)
  • encryptedEmailId (108-108)
  • encryptedEmailId (1176-1176)
  • encryptedEmailId (1792-1792)
src/services/organization.js (2)
  • emailEncryption (17-17)
  • encryptedEmailId (70-70)
src/validators/v1/admin.js (3)
src/constants/blacklistConfig.js (1)
  • admin (136-208)
src/services/admin.js (6)
  • isEmail (311-311)
  • isEmail (444-444)
  • isPhone (312-312)
  • isPhone (445-445)
  • isUsername (313-313)
  • isUsername (446-446)
src/services/account.js (9)
  • isEmail (673-673)
  • isEmail (1022-1022)
  • isEmail (1352-1352)
  • isPhone (674-674)
  • isPhone (1023-1023)
  • isPhone (1353-1353)
  • isUsername (675-675)
  • isUsername (1024-1024)
  • isUsername (1354-1354)
🔇 Additional comments (3)
src/services/admin.js (1)

136-146: Transaction handling within existence check

The transaction is passed to the query but not used consistently throughout the function. This could lead to data inconsistency if other operations fail after the check.

Good use of transaction for the existence check to ensure consistency.

src/validators/v1/admin.js (2)

64-78: Cross-field validation logic is correct

The validation ensures that at least one identifier (email or phone) is provided and that phone_code is required when phone is present. This is a good implementation of dependent field validation.


98-99: Conditional email normalization implementation

Good implementation of conditional email normalization - it only normalizes when the identifier is detected as an email format.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/services/admin.js (2)

195-205: Return plaintext phone (or omit) instead of ciphertext in API response

processedUser replaces email with plaintext but leaves phone encrypted, leaking ciphertext and creating an inconsistent API surface.

Apply:

- const processedUser = { ...user, email: plaintextEmailId }
+ const processedUser = {
+   ...user,
+   email: plaintextEmailId,
+   phone: user?.phone ? emailEncryption.decrypt(user.phone) : null,
+ }

319-329: Require or omit phone_code in phone-login query
In the login query (src/services/admin.js 319–329), isPhone uses /^\+?[1-9]\d{1,14}$/ so it matches both E.164 (+…) and local numbers, yet you always include phone_code (often undefined for +-prefixed values), causing Sequelize to generate phone_code IS NULL clauses and ambiguous matches (github.com). The validator only enforces phone_code on pure-digit identifiers, so for E.164 you should drop it and for local numbers enforce it. Update this block accordingly:

 } else if (isPhone(identifier)) {
-   // expects bodyData.phone_code when phone login is used
-   query[Op.or].push({
-     phone: emailEncryption.encrypt(identifier),
-     phone_code: bodyData.phone_code,
-   })
+   if (identifier.startsWith('+')) {
+     query[Op.or].push({ phone: emailEncryption.encrypt(identifier) })
+   } else {
+     if (!bodyData.phone_code) 
+       return failure('PHONE_CODE_REQUIRED', httpStatusCode.bad_request)
+     query[Op.or].push({
+       phone: emailEncryption.encrypt(identifier),
+       phone_code: bodyData.phone_code,
+     })
+   }
♻️ Duplicate comments (1)
src/services/admin.js (1)

131-142: OK to skip empty-criteria guard given validator guarantee

Given validator-enforced presence of at least one identifier, calling findOne with a non-empty [Op.or] is safe. Removing the dead criteria.length === 0 block was the right call.

If helpful, verify the validator rule exists:

#!/bin/bash
# Expect to find rules enforcing at least one of email/phone/username for admin.create
rg -nC2 -i 'admin.*create|at least one|identifier|required|phone_code' --type=js src/validators
🧹 Nitpick comments (3)
src/services/admin.js (3)

115-117: Normalize phone before encryption; avoid mismatches across flows

Confirm that validators normalize phone to a canonical E.164 string before this encryption. Otherwise, dedup/login may miss matches.


301-310: Username lowercasing: confirm storage/compare semantics

Lowercasing the entire identifier makes username login case-insensitive. Confirm usernames are normalized to lowercase at creation or that DB collation is case-insensitive; otherwise logins may fail.

Example tweak (only lowercase when email):

- const identifier = rawIdentifier.toLowerCase()
+ const identifier = isEmail(rawIdentifier) ? rawIdentifier.toLowerCase() : rawIdentifier

126-135: Add DB indexes to keep multi-criteria lookups fast

Ensure indexes on (tenant_code, email), (tenant_code, phone, phone_code) (or (tenant_code, phone) if E.164), and (tenant_code, username) to support the new OR query without regressions.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8248690 and ba3cddc.

📒 Files selected for processing (2)
  • src/services/admin.js (3 hunks)
  • src/validators/v1/admin.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/validators/v1/admin.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/admin.js
🧠 Learnings (2)
📓 Common learnings
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#811
File: src/services/admin.js:126-133
Timestamp: 2025-09-02T14:15:52.372Z
Learning: In the admin service create method, validation of required fields (email, phone, username) is handled at the validator layer, making the criteria.length === 0 check unreachable code that should be removed.
📚 Learning: 2025-09-02T14:15:52.372Z
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#811
File: src/services/admin.js:126-133
Timestamp: 2025-09-02T14:15:52.372Z
Learning: In the admin service create method, validation of required fields (email, phone, username) is handled at the validator layer, making the criteria.length === 0 check unreachable code that should be removed.

Applied to files:

  • src/services/admin.js
🧬 Code graph analysis (1)
src/services/admin.js (5)
src/services/org-admin.js (6)
  • plaintextEmailId (669-669)
  • bodyData (435-435)
  • emailEncryption (27-27)
  • userQueries (14-14)
  • userQueries (45-48)
  • userQueries (117-117)
src/services/organization.js (3)
  • plaintextEmailId (69-69)
  • encryptedEmailId (70-70)
  • emailEncryption (17-17)
src/services/account.js (12)
  • plaintextEmailId (109-109)
  • plaintextEmailId (1177-1177)
  • plaintextEmailId (1791-1791)
  • encryptedEmailId (108-108)
  • encryptedEmailId (1176-1176)
  • encryptedEmailId (1792-1792)
  • emailEncryption (29-29)
  • encryptedPhoneNumber (117-117)
  • encryptedPhoneNumber (1185-1185)
  • criteria (126-126)
  • criteria (1194-1194)
  • userQueries (17-17)
src/services/user.js (2)
  • emailEncryption (21-21)
  • userQueries (11-11)
src/database/queries/users.js (1)
  • emailEncryption (6-6)
🔇 Additional comments (1)
src/services/admin.js (1)

159-160: LGTM: encrypted-at-rest propagation

Persisting the encrypted phone aligns with the new policy. Ensure phone_code is also persisted (if provided) for consistent lookups.

@nevil-mathew nevil-mathew merged commit 2a81b0b into develop Sep 3, 2025
1 of 2 checks passed
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.

1 participant