#2849 - Generate random password for users#2866
Conversation
group-income
|
||||||||||||||||||||||||||||
| Project |
group-income
|
| Branch Review |
sebin/task/#2849-random-generated-password
|
| Run status |
|
| Run duration | 14m 16s |
| Commit |
|
| Committer | Sebin Song |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
10
|
|
|
0
|
|
|
117
|
| View all changes introduced in this branch ↗︎ | |
…ndom-generated-password
| generateRandomPassword (pwLen = 32) { | ||
| let genPassword = '' | ||
|
|
||
| if (window?.Cypress) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Correct me if I'm wrong but
process.env.CIis 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:
- Unlike
CI=true, this will be a runtime check. Ifwindow.Cypresshappens 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. - 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) - That change to
signup-and-loginIMHO 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 ifgenerateBase58Passworddidn'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. - 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.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
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:
- The comment seems to be slightly wrong. It should probably mention removing this part rather than the NODD_ENV part.
- 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:
- 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).
- 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).
- 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:
- Still providing a way to quickly create an account
- Not needing to change the source code for doing it, which can avoid accidental changes
- Having tests run against the 'real' code that users will have
- 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.
There was a problem hiding this comment.
Math.random() thing here is something I added for testing... it means nothing. am gonna remove it
There was a problem hiding this comment.
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!
…ndom-generated-password
corrideat
left a comment
There was a problem hiding this comment.
Looks good. A few general comments summarising my other comments:
- I'd use an env variable rather than
window.Cypressto avoid accidentally weak passwords - 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.
|
Thanks @corrideat for reviewing this multiple times! |
|
PR is updated again. |
taoeffect
left a comment
There was a problem hiding this comment.
Great work @SebinSong!
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
-
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.
-
Error handling in copyPassword: The
navigator.sharefallback 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()
}
}- 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).
|
@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. |
|
@corrideat Like you pointed out, the local cypress test now passes without |
|
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:
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:
|
|
I also did something similar to what @taoeffect did, which is why I believe tests are failing when not using the
(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) |
|
@corrideat 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 FWIW, I'm sorry about this back and forth regarding the (*) I mean:
|
|
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). |



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.