Skip to content

#2849 - Generate random password for users#2866

Open
SebinSong wants to merge 14 commits intomasterfrom
sebin/task/#2849-random-generated-password
Open

#2849 - Generate random password for users#2866
SebinSong wants to merge 14 commits intomasterfrom
sebin/task/#2849-random-generated-password

Conversation

@SebinSong
Copy link
Collaborator

@SebinSong SebinSong commented Jul 8, 2025

closes #2849

Below is the updated sign-up form:

@taoeffect Currently the random-generated password is always 32 characters long (32 here is just an arbitrary number I chose), but let me know if you have other opinions re this behavior.

@SebinSong SebinSong self-assigned this Jul 8, 2025
@cypress
Copy link

cypress bot commented Jul 8, 2025

group-income    Run #4846

Run Properties:  status check passed Passed #4846  •  git commit 6d125cd546 ℹ️: Merge b7092b14883705be2206cbcaa1c20f201bee5a92 into dc22f37263bf15ca4e3b84db8434...
Project group-income
Branch Review sebin/task/#2849-random-generated-password
Run status status check passed Passed #4846
Run duration 14m 16s
Commit git commit 6d125cd546 ℹ️: Merge b7092b14883705be2206cbcaa1c20f201bee5a92 into dc22f37263bf15ca4e3b84db8434...
Committer Sebin Song
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 10
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 117
View all changes introduced in this branch ↗︎

@taoeffect taoeffect requested a review from corrideat July 16, 2025 18:57
generateRandomPassword (pwLen = 32) {
let genPassword = ''

if (window?.Cypress) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd use process.env.CI here. If this code makes it to production and someone is using Cypress or some extension that defines Cypress, we wouldn't want to accidentally use a weak password.

Also, not sure why window?, since it should always be defined.

Copy link
Collaborator Author

@SebinSong SebinSong Aug 4, 2025

Choose a reason for hiding this comment

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

will remove ? typo here. Correct me if I'm wrong but process.env.CI is only true when tested as part of GH action workflow?

Copy link
Member

Choose a reason for hiding this comment

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

Correct me if I'm wrong but process.env.CI is only true when tested as part of GH action workflow?

I wouldn't say 'only', but yes, you'd need to manually set it when running grunt dev (e.g., CI=true grunt dev) if you want it locally (something that could be useful, as it will expose some exceptions / error conditions that are swallowed otherwise).

After thinking about it again, I've changed my mind and think that instead of using process.env.CI, this branch should just be removed entirely (see 3 below).

Still, these are the reasons why I don't think the current check is a good idea:

  1. Unlike CI=true, this will be a runtime check. If window.Cypress happens to be defined (browser extension, someone running Cypress to access GI, etc.), it can result in accidentally weak passwords. I don't see a compelling reason to ship this check into production. See point 4 below for a compromise.
  2. For the most part, Cypress doesn't use this new default value, except for the change you made to [signup-and-login.spec.js](https://github.com/okTurtles/group-income/pull/2866/files#diff-14ca527d56d62af00f45c89deb5f3456657bcf6e23a2c2f284422781d2e02a22)
  3. That change to signup-and-login IMHO isn't the best way to test this feature, since the code path taken in production and Cypress will be slightly different. If this something we want to test, I'd say it's best to give it the same behaviour as users will see, or as close to it as possible. For example, what if generateBase58Password didn't work for some reason (e.g., it throws an exception)? Then we wouldn't learn about it from the automated tests. I'd suggest removing the branch entirely and, in the test, checking that the auto-generated password has the expected length.
  4. If you still feel we should have this special case for Cypress, consider maybe changing it to process.env.CI || (process.env.NODE_ENV !== 'production' && window.Cypress), so that the chance of this generating a weak password in production is 0.

Copy link
Collaborator Author

@SebinSong SebinSong Oct 3, 2025

Choose a reason for hiding this comment

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

@corrideat I think my original inline comment was not clear enough.

One thing I do quite often for debugging is,
I run a specific cypress test-suite to save time for creating a group/adding members/setting income-details info etc.
(eg. When I need to debug something in payment area but the scenario is not simple to reproduce, I start by runninf group-paying.spect.js first so that all the payment-related stuffs automatically get ready and I don't have to manually create them all by myself). Then I would launch multiple browser tabs and log in for each users and debug things etc..)
If this auto-generated password is enabled, I would have to know the different passwords of each users created, and this wouldn't help easier/faster development or debugging.

So, using your suggestion in 4. let me update this to if (process.env.CI || process.env.NODE_ENV !== 'production') . So we can still use this default password while running the app locally for development.

Copy link
Member

@corrideat corrideat Oct 3, 2025

Choose a reason for hiding this comment

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

TL;DR: Please fix the tests so that grunt dev (without CI=true) works. In the last part of this comment I also suggest changing the if condition to something else (starts at 'May I suggest then rewriting...').

I see, sometimes I do that too, and agree that the having a different password for each user, especially a random one, would be a hassle. But it doesn't seem like these "random" (123456789) passwords are being used in tests.

I noticed you also added a Math.random() > 1 condition, which will cause the branch to never be taken during local development (unless CI=true is set).

Was this the intent? If so:

  1. The comment seems to be slightly wrong. It should probably mention removing this part rather than the NODD_ENV part.
  2. What is the point of having this branch at all? Is it so that you can comment out the check for local development and not have to type a password (but still have it be predictable)? This could actually be quite useful, but see below.

If I understood this correctly, I suggest the following changes:

  1. Remove the CI check, as now the hardcoded password isn't used in tests (actually, it'd be nice to also add a short test to ensure the random password is working correctly; since it's random, it could be something simple like checking the length).
  2. I like the idea of having a pre-filled default password for development, but I'm not a big fan of having to change the source code for this, since it's tedious and error prone (by this I mean it's easy to forget about this change and commit it).
  3. Actually, tests now fail locally (group-proposals, user6 registers…) unless you set CI=true. This is currently withholding my approval.

May I suggest then rewriting the condition to something like if (process.env.NODE_ENV !== 'production' && process.env.UNSAFE_HARDCODED_TEST_PASSWORD)?

I think that this has the advantages of:

  1. Still providing a way to quickly create an account
  2. Not needing to change the source code for doing it, which can avoid accidental changes
  3. Having tests run against the 'real' code that users will have
  4. Providing a way to test the real code

Otherwise, this PR is looking great. I'll give my approval once tests pass without needing to set CI=true.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Math.random() thing here is something I added for testing... it means nothing. am gonna remove it

Copy link
Collaborator Author

@SebinSong SebinSong Oct 3, 2025

Choose a reason for hiding this comment

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

FYI again, Math.random() > 1 was something I added to test and see locally if the auto-generated password works when process.env.NODE_ENV === production. It seems some part of the previous comment was written thinking this is something meaningful. It's just I forgot to remove it after checking (My bad!). So let me skip that part.

BTW let me think about doing UNSAFE_HARDCODED_TEST_PASSWORD stuff though..
As a front-end dev who will have to do create users -> repeatedly log in/out pretty much as an every day task, I would always want to disable this auto-generated password feature during local development(Unless I'm working on this very feature itself..) So I would want this as a default instead of optional thing for development environment.

EDIT: I ended up adopting your suggestion for UNSAFE_HARDCODED_TEST_PASSWORD. Running UNSAFE_HARDCODED_TEST_PASSWORD=true grunt dev each time during development wouldn't be much hassle I guess!

Copy link
Member

@corrideat corrideat 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. A few general comments summarising my other comments:

  1. I'd use an env variable rather than window.Cypress to avoid accidentally weak passwords
  2. Security-wise it looks fine, but I'd reconsider the choice of alphabet (or encoding) to attain shorter passwords, or to pack more entropy into the existing password length.

@SebinSong
Copy link
Collaborator Author

Thanks @corrideat for reviewing this multiple times!
(I have been busy for something else lately, so will pick this up probably in a few days)

@SebinSong
Copy link
Collaborator Author

PR is updated again.

Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

Great work @SebinSong!

Image

I have some minor UX change requests:

  • Could you make it so that when when the "Copy" button is clicked, a circle with a checkmark inside of it is displayed to the left of the word "Copied" inside of the button, similar to this: ✅ Copied
  • When the "Copy" button is clicked, could you please hide the password, perhaps using the standard ••••••••••• stuff
    • Also consider in addition to this, when the password is first "displayed" completely, applying a slight blur filter on top of it so that they can see it's a password but not what it is.
  • Please make whether or not the "Copy" button has been clicked a condition for enabling the "Create an account" button

I've also ran this through some AIs:

Sonnet 4.5

Details

Bugs Found

1. Typo in prop definition (PasswordForm.vue)

Line 88:

requried: false,

Issue: requried is misspelled. Should be required.

Fix:

required: false,

2. Confusing logic in password generation (PasswordForm.vue)

Lines 117-122:

if (process.env.CI || (Math.random() > 1 && process.env.NODE_ENV !== 'production')) {
  // For easier debugging/development, use the common default password when running in non-production environments.
  // Comment out process.env.NODE_ENV !== 'production' though if you need to use/test the safe auto-generated password feature in local development.
  genPassword = '123456789'
} else {
  genPassword = generateBase58Password(pwLen)
}

Issue: The condition Math.random() > 1 will never be true since Math.random() returns a value between 0 and 1. This makes the entire && process.env.NODE_ENV !== 'production' part dead code. Based on the comment, it seems you want to allow developers to enable/disable the debug password, but the current logic doesn't support that.

Suggested fix:

// Use simple password in CI for easier testing
// Set USE_DEBUG_PASSWORD=true in development if you need to test with a known password
if (process.env.CI || process.env.USE_DEBUG_PASSWORD === 'true') {
  genPassword = '123456789'
} else {
  genPassword = generateBase58Password(pwLen)
}

Or if you want to keep it always using the simple password in CI only:

if (process.env.CI) {
  // For easier debugging in CI, use a simple default password
  genPassword = '123456789'
} else {
  genPassword = generateBase58Password(pwLen)
}

3. Missing CSS class reference (PasswordForm.vue)

Line 166:

.width-with-single-addon.has-ellipsis.c-auto-password(

Question: Is the width-with-single-addon CSS class defined elsewhere in your codebase? I don't see it defined in the style section of this file. Please verify this class exists, otherwise the layout may not work as expected.


4. Missing data-test attribute (PasswordForm.vue)

Line 52:

button.is-success.c-copy-btn(
  type='button'
  @click.stop='copyPassword'
)

Suggestion: For consistency with the rest of the codebase and to support testing, consider adding a data-test attribute:

button.is-success.c-copy-btn(
  type='button'
  data-test='copyPassword'
  @click.stop='copyPassword'
)

Questions / Clarifications Needed

  1. Password visibility toggle in auto mode: In auto mode, should users be able to toggle password visibility? Currently, the toggle button only appears in manual mode. This might be intentional, but please confirm.

  2. Error handling in copyPassword: The navigator.share fallback to clipboard copy is good, but what happens if both fail (e.g., in an insecure context where clipboard API is unavailable)? Consider adding user feedback for this case:

copyPassword () {
  const pw = this.ephemeral.randomPassword
  const copyToClipBoard = () => {
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(pw).then(() => {
        this.ephemeral.showCopyFeedback = true
        setTimeout(() => {
          this.ephemeral.showCopyFeedback = false
        }, 1500)
      }).catch((error) => {
        console.error('Clipboard write failed:', error)
        // Could show an error message to user here
      })
    } else {
      console.error('Clipboard API not available')
      // Could show an error message to user here
    }
  }

  if (navigator.share) {
    navigator.share({
      title: L('Your password'),
      text: pw
    }).catch((error) => {
      console.error('navigator.share failed with:', error)
      copyToClipBoard()
    })
  } else {
    copyToClipBoard()
  }
}
  1. Accessibility concern: The auto-generated password is displayed as plain text in a div. Is this intentional from a security/privacy standpoint? Users might be in public spaces. Consider whether it should be hidden by default with an option to reveal.

Grok 4

Details

Bugs Found

1. Typo in Prop Definition (PasswordForm.vue)

The mode prop has a misspelling in its definition, which could lead to unexpected behavior (e.g., the prop being treated as required when it shouldn't be, or linter errors). FlowType and prop validation might not catch this at runtime, but it's a clear error.

 mode: {
   type: String,
-  requried: false,
+  required: false,
   default: 'manual' // 'manual' | 'auto'
 },

Suggested Fix: Correct the spelling to required. Here's the updated prop definition:

props: {
  name: {
    type: String,
    required: false,
    default: 'password'
  },
  mode: {
    type: String,
    required: false,
    default: 'manual' // 'manual' | 'auto'
  },
  label: {
    type: String,
    required: false
  },
  $v: {
    type: Object,
    required: true
  },
  showPlaceholder: {
    type: Boolean,
    default: false
  },
  showPassword: {
    type: Boolean,
    default: false
  },
  size: {
    type: String,
    required: false
  }
},

2. Impossible Condition in Password Generation Logic (PasswordForm.vue)

In the generateRandomPassword method, the condition Math.random() > 1 is always false because Math.random() returns a value between 0 and 1 (exclusive). This means the default password '123456789' is only used in CI environments, ignoring the non-production check entirely. The comment suggests the intent is to use the default in non-production for easier debugging, but the code doesn't achieve that without manual modification. This could lead to inconsistent behavior between environments (e.g., tests failing locally if they expect the default password but get a random one).

if (process.env.CI || (Math.random() > 1 && process.env.NODE_ENV !== 'production')) {
  // For easier debugging/development, use the common default password when running in non-production environments.
  // Comment out process.env.NODE_ENV !== 'production' though if you need to use/test the safe auto-generated password feature in local development.
  genPassword = '123456789'
} else {
  genPassword = generateBase58Password(pwLen)
}

Suggested Fix: If the intent is to use the default password in non-production environments (unless overridden), adjust the condition to something achievable, like Math.random() > 0 (always true) or remove the random check entirely. Alternatively, make it configurable via an environment variable. Here's a suggestion to always use the default in non-production (matching the comment's spirit), while allowing opt-out:

generateRandomPassword (pwLen = 32) {
  let genPassword = ''

  const useDefaultForDebug = process.env.CI || (process.env.NODE_ENV !== 'production' && !process.env.USE_RANDOM_PASSWORD)

  if (useDefaultForDebug) {
    // For easier debugging/development, use the common default password when running in non-production environments.
    // Set USE_RANDOM_PASSWORD=true in env to force random passwords in local dev.
    genPassword = '123456789'
  } else {
    genPassword = generateBase58Password(pwLen)
  }

  this.ephemeral.randomPassword = genPassword
  this.$v.form[this.name].$model = genPassword
},

This introduces an opt-out via USE_RANDOM_PASSWORD env var for local testing of random passwords.

Improvements Suggested

1. Missing Error Handling in Password Sharing Fallback (PasswordForm.vue)

The copyPassword method uses navigator.share and falls back to navigator.clipboard if it fails, but it doesn't handle cases where the clipboard write also fails (e.g., permissions denied or browser incompatibilities). Currently, it logs to console but doesn't inform the user or update UI. This could leave users thinking the copy succeeded when it didn't.

navigator.share({
  title: L('Your password'),
  text: pw
}).catch((error) => {
  console.error('navigator.share failed with:', error)
  copyToClipBoard()
})

Suggested Fix: Add user-facing error handling (e.g., show a failure message in the UI) and ensure the fallback is robust. Update the method as follows:

copyPassword () {
  const pw = this.ephemeral.randomPassword
  const copyToClipBoard = () => {
    navigator.clipboard.writeText(pw)
      .then(() => {
        this.ephemeral.showCopyFeedback = true
        setTimeout(() => {
          this.ephemeral.showCopyFeedback = false
        }, 1500)
      })
      .catch((error) => {
        console.error('Clipboard copy failed:', error)
        // TODO: Show UI error, e.g., this.ephemeral.showCopyError = true;
      })
  }

  if (navigator.share) {
    navigator.share({
      title: L('Your password'),
      text: pw
    }).catch((error) => {
      console.error('navigator.share failed with:', error)
      copyToClipBoard()
    })
  } else {
    copyToClipBoard()
  }
},

You'd need to add showCopyError: false to ephemeral in data() and display it in the template (similar to showCopyFeedback).

@SebinSong
Copy link
Collaborator Author

@taoeffect Updated the PR again based on your feedbacks. below are some screenshots for the changes you suggested:

[Auto-generated PW is blurred]



[UI change upon clicking the copy button]



A few of the LLM's suggestions were useful too, so I made changes accordingly as well.

@SebinSong
Copy link
Collaborator Author

@corrideat Like you pointed out, the local cypress test now passes without CI=true. As I mentioned in the comment above too, I ended up adopting your suggestion re process.env.UNSAFE_HARDCODED_TEST_PASSWORD. (Running UNSAFE_HARDCODED_TEST_PASSWORD=true grunt dev each time during development won't be a much hassle.)

@taoeffect
Copy link
Member

taoeffect commented Oct 6, 2025

This PR is looking really solid, at least in my testing. I've found one more issue with it, which is that it doesn't seem to work well with password managers, and I was afraid that might be the case. We need to make sure it works well.

Here's what I tried:

  1. I used Strongbox on macOS with the Firefox add-on
  2. I clicked Sign Up
  3. I clicked "Copy"
  4. Then I clicked the username field and clicked + Create from Stongbox's add-on to create a user account and password
  5. This caused the password field to be filled in automatically with the auto-generated password. (Note: if you skip Step 3, this won't happen)
  6. I clicked the checkboxes and clicked Create an account
  7. Then I created a group and logged out.
  8. I tried logging back in using the password-manager generated password but couldn't.

And note that if you skip Step 3 above, the password manager doesn't fill in the password field at all, but still stores the password it generated as the password for this site, so if the user forgets to override it with the copied password, they'll lose access to their account.

So I think we need to somewhat rethink this PR so that it's better compatible with password managers, as the whole point of this PR is to improve user & group security and get users to use password managers. If they are already using one and they prefer the password that the password manager generates then we should let them go with it, as otherwise a conflict might occur and they could end up locked out of their account.

I would say fixing this is lower priority than responding to change requests for other PRs that you have open. And it might require some additional thought on how to approach this, because if fixing this allows users to simply edit the password field, then we need to really still confirm that they know their password and didn't mistype it.

In that case, here's how I would approach this:

  • Allow users to edit the field to their liking
  • When they click "Create an account", don't create an account immediately, but first prompt them to enter their password again. If it doesn't match, remain on the Sign Up modal. This ensures that even if they customized their password they didn't mistype it.
  • Note that some previous change requests (like the blurred password field), may need to be reverted

@corrideat
Copy link
Member

corrideat commented Oct 6, 2025

I also did something similar to what @taoeffect did, which is why I believe tests are failing when not using the 123456789 as an auto-generated password.

  1. Opened up the app, went to register an account
  2. Manually removed disabled from the password input field (as it wouldn't let me edit it)
  3. Set a password there, 12345678.
  4. Tried to log in. 12345678 didn't work, but the random password did. So, it seems like the input field value isn't being used.

(You may want to hold on to these changes until @taoeffect decides how to go forward with this issue, though; I was just describing what I saw)

@SebinSong
Copy link
Collaborator Author

@corrideat
Manually removed disabled from the password input field and then entering your own password -> this is not something I'm required to do as part of #2849 ...

Enabling user to edit the password is something Greg brought up just in previous comment..

@corrideat
Copy link
Member

corrideat commented Oct 6, 2025

Enabling user to edit the password is something Greg brought up just in previous comment..

Yeah, based on that discussion (#2849 (comment) and #2849 (comment)), it could be read that way, and it also seems like this PR is lower priority now, but not implementing this also causes inconveniences (*) when running Cypress tests, apart from not allowing password managers to generate their own password.

FWIW, I'm sorry about this back and forth regarding the 123456789 password. I understand why it's there (and agree it's useful for development) and I think you've implemented the issue well. Just today I was discussing with @taoeffect some ways that this issue could have a better UX, based also on the discussion the two of you had on issue #2849.

(*) I mean:

  • Without UNSAFE_HARDCODED_TEST_PASSWORD, then the auto-password is used, which makes it a bit harder to do things like this: #2849 - Generate random password for users #2866 (comment) (edit: actually, it seems like those use bypassUI so this doesn't apply)
  • With UNSAFE_HARDCODED_TEST_PASSWORD, you can debug as before but now the tests fail at signup-and-login.spec.js (although fixing this wouldn't be done by accepting user passwords, rather by having Cypress skip the length case, e.g., by exposing a global / attribute telling Cypress to do this)

@corrideat
Copy link
Member

corrideat commented Oct 6, 2025

LGTM and all of my feedback has been implemented. All that remains if any of the suggestions from #2866 (comment) are implemented are UI changes that shouldn't affect password security (except, I guess, if users choose a weak password).

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.

Randomly generate user password on Sign Up

3 participants