-
-
Notifications
You must be signed in to change notification settings - Fork 735
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
feat: Add configurable default email for initial admin user #6447
feat: Add configurable default email for initial admin user #6447
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
@00Chaotic is attempting to deploy a commit to the unleash-team Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❌ Code Health Quality Gates: FAILED
- Declining Code Health: 1 findings(s) 🚩
- Improving Code Health: 0 findings(s) ✅
- Affected Hotspots: 0 files(s) 🔥
Recommended Review Level: Detailed -- Inspect the code that degrades in code health.
View detailed results in CodeScene
🚩 Declining Code Health (highest to lowest):
- Complex Conditional user-service.ts: UserService.initAdminUser
} | ||
|
||
const userCount = await this.store.count(); | ||
|
||
if (userCount === 0 && username && password) { | ||
if (userCount === 0 && username && password && email) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❌ Getting worse: Complex Conditional
UserService.initAdminUser increases from 1 complex conditionals with 2 branches to 1 complex conditionals with 3 branches, threshold = 2
Why does this problem occur?
A complex conditional is an expression inside a branch (e.g. if, for, while) which consists of multiple, logical operators such as AND/OR. The more logical operators in an expression, the more severe the code smell. Read more.
To ignore this warning click here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey! Thanks for putting the work in and creating the PR 😄 I think this a good addition.
I've got some minor comments left inline (including stuff like which email address to use etc), but there is one point specifically that I think bears some extra discussion:
Previously, there was no way to log in as the admin user in demo mode, but now you can by default. Will this potentially cause any security concerns where anyone can log in as the admin where they couldn't before?
Would it be more sensible to default to not giving the admin an email, but giving you the option to add an email on startup?
@@ -22,6 +22,7 @@ exports[`should create default config 1`] = ` | |||
"initialAdminUser": { | |||
"password": "unleash4all", | |||
"username": "admin", | |||
"email": "[email protected]", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't own unleash.com, so it would probably be better to do something like "[email protected]". Or maybe something like "[email protected]"? Depending on what we want to change it to, that would obviously apply to all the places that use that email in this PR.
"email": "admin@unleash.com", | |
"email": "admin@getunleash.io" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather keep our official domain out of it. we can use [email protected] instead in our tests
} | ||
|
||
const userCount = await this.store.count(); | ||
|
||
if (userCount === 0 && username && password) { | ||
if (userCount === 0 && username && password && email) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we actually need to check for the existence of the email here? The only time this'll be falsy is if you provide an empty string as an env variable, right, but still ask unleash to create the initial admin user?
I don't think we should not create the admin user if you provide an empty email 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 good call imo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
@@ -66,7 +66,7 @@ unleash.start(unleashOptions); | |||
- `demo` - Only requires an email to sign in (was default in v3) | |||
- `none` - _Deprecated_ Turn off authentication completely. If no API token is provided towards /`api/client` or `/api/frontend` you will receive configuration for the "default" environment. We generally recommend you use the `demo` type for simple, insecure usage of Unleash. This auth type has many known limitations, particularly related to personalized capabilities such as favorites and [notifications](../../reference/notifications.md). | |||
- `customAuthHandler`: function `(app: any, config: IUnleashConfig): void` — custom express middleware handling authentication. Used when type is set to `custom`. Can not be set via environment variables. | |||
- `initialAdminUser`: `{ username: string, password: string} | null` — whether to create an admin user with default password - Defaults to using `admin` and `unleash4all` as the username and password. Can not be overridden by setting the `UNLEASH_DEFAULT_ADMIN_USERNAME` and `UNLEASH_DEFAULT_ADMIN_PASSWORD` environment variables. | |||
- `initialAdminUser`: `{ username: string, password: string, email: string} | null` — whether to create an admin user with default password - Defaults to using `admin` and `unleash4all` as the username and password, and `[email protected]` as the email. Can not be overridden by setting the `UNLEASH_DEFAULT_ADMIN_USERNAME`, `UNLEASH_DEFAULT_ADMIN_PASSWORD` and `UNLEASH_DEFAULT_ADMIN_EMAIL` environment variables. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I found a mistake in here that's not yours:
- `initialAdminUser`: `{ username: string, password: string, email: string} | null` — whether to create an admin user with default password - Defaults to using `admin` and `unleash4all` as the username and password, and `[email protected]` as the email. Can not be overridden by setting the `UNLEASH_DEFAULT_ADMIN_USERNAME`, `UNLEASH_DEFAULT_ADMIN_PASSWORD` and `UNLEASH_DEFAULT_ADMIN_EMAIL` environment variables. | |
- `initialAdminUser`: `{ username: string, password: string, email: string} | null` — whether to create an admin user with default password - Defaults to using `admin` and `unleash4all` as the username and password, and `[email protected]` as the email. Can be overridden by setting the `UNLEASH_DEFAULT_ADMIN_USERNAME`, `UNLEASH_DEFAULT_ADMIN_PASSWORD` and `UNLEASH_DEFAULT_ADMIN_EMAIL` environment variables. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget changing the domain in the email here as well, if we end up with a default email
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the default email and specify that the email is only set if it is provided.
// create default admin user | ||
try { | ||
this.logger.info( | ||
`Creating default user '${username}' with password '${password}'`, | ||
`Creating default user '${username}', password '${password}', and email '${email}'`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can leave the old "with" in here, personally. Not major, but it keeps the format slightly more consistent. Not that it matters much 🤷🏼
`Creating default user '${username}', password '${password}', and email '${email}'`, | |
`Creating default user '${username}' with password '${password}' and email '${email}'`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I have much input here. This looks good as an overall approach. I think @thomasheartman's poke about the email is a great suggestion though
Seeing as this is a feature that's in use I would say this should be a requirement, that it has to be optional and that it has to be opt-in. Otherwise others setups will change as a consequence |
Yeah, that's fair. I'm leaning towards the same. However, seeing as admin users are only created on initial startup (when there's no existing users), we could potentially be more lenient, because it won't change any existing installs. However, I agree that we should err on the side of caution here and make it opt-in instead. |
@@ -69,6 +69,7 @@ export interface IAuthOption { | |||
initialAdminUser?: { | |||
username: string; | |||
password: string; | |||
email: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this needs to be optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the username and password be optional too, in the case that a user only wants to specify email, but not username or password (since the defaults will be used if they are not present)?
@@ -267,6 +267,7 @@ const defaultAuthentication: IAuthOption = { | |||
initialAdminUser: { | |||
username: process.env.UNLEASH_DEFAULT_ADMIN_USERNAME ?? 'admin', | |||
password: process.env.UNLEASH_DEFAULT_ADMIN_PASSWORD ?? 'unleash4all', | |||
email: process.env.UNLEASH_DEFAULT_ADMIN_EMAIL ?? '[email protected]', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- unleash.com is not associated with the Unleash project.
- It should be undefined if not set. We should not force it on everyone.
@@ -145,6 +145,7 @@ test('Should create default user - with provided username and password', async ( | |||
initialAdminUser: { | |||
username: 'admin', | |||
password: 'unleash4all!', | |||
email: '[email protected]', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should not be set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming we are still keeping the email functionality but making it opt-in, it would be beneficial to have a test for it. Would it be better to create a separate test case for the email rather than having it in this one?
@@ -190,6 +191,7 @@ test('Should not create default user - with `createAdminUser` === false', async | |||
initialAdminUser: { | |||
username: 'admin', | |||
password: 'unleash4all!', | |||
email: '[email protected]', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should not be set.
} | ||
|
||
const userCount = await this.store.count(); | ||
|
||
if (userCount === 0 && username && password) { | ||
if (userCount === 0 && username && password && email) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
email should be optional and only used if it exists.
Hi, I suggest that email needs to be optional. This raises a new challenge, what if the user wants to create an initial admin user, but forget to specify the email the first time Unleash runs with the flag? Should we always do an upsert of the admin user if the flag is specified? An alternative solution to the problem described in #6398 is to change the input form of demo-login to not require email if the input is simply "admin". |
Thanks all, it's definitely a good idea to make the admin email an opt-in and remove the default.
@ivarconr Would this not still present the security issue of anyone being able to access the admin user? I'll be making the admin email optional for now while we continue discussion on the whether to always do an upsert if the flag is specified, or whether to go with an alternate solution entirely. |
@00Chaotic Sorry for the delay here 🙇🏼
As long as the admin user can log in without a password, that risk is present. So whether we accept
Is there a third way where we don't do an upsert, but allow you to set the admin email only when it creates default users? That may not be very useful, though. I can imagine that you often only realize that you want the admin to be able to log in after you've set up Unleash. But actually, scratch that. I think modifying the demo auth middleware ( I think this would be beneficial because:
It should only work if you've started Unleash with a It would also be an option to allow them to log in with just Let me know your thoughts and we can take it from there |
Small addendum to the above comment: it appears that we don't do any validation on the server-side that you fill out an email, so it may be possible to just update the UI a bit. Ivar had a suggestion that is to set the input field's type to be: type={value === 'admin' ? "text" : "email"} That might work. Of course, we could also use the If we go that way, then admin could log in with |
Of course, there's nothing stopping a general user from using the same username (even today, with a bit of UI inspection), but that's the same as if you were to use an email. |
Thanks for getting back to me @thomasheartman
Slightly disagree, while it's by no means as secure as a password, requiring the user to specify an email rather than providing a default one would add a level of randomness/variance to improve security. Of course, this is also influenced by how the user specifies the email - if they just hardcode it in then it's less secure than if they treated it like a secret and loaded it in from elsewhere.
Just to confirm, do you mean we add a case to the While I feel that would be less secure than forcing a custom-set email, you're right in that neither of them are super secure, so this solution would be fine. I agree that there is less potential for unforeseen impacts this way. I can go ahead and make the change if we're all happy with this. N.B. Do we know if there is any logic for the demo auth mode that may break if a user does not have an email? Any logging or user retrieval functionality that assumes the user has an email when using demo mode? |
That's fair. But I don't think that people in general will rotate the email if they set it. That seems like a lot of work. I think that if you want security, then you shouldn't be using demo auth.
Yeah, that's what I'm saying. Except there is no validation happening on the API level, so all you'd need to update is the middleware and the UI code saying that the input has to be an email.
You're free to go check it out yourself, but there is nothing as far as I can tell. You can try yourself by logging into the the demo instance by providing a string that isn't an email. You'll have to manually change the input's type (by inspecting it in the browser and removing the
We've talked about it internally, and this seems like a good way to solve it in our opinion. This would mean:
There may of course be some other minor changes to make, but that should be the brunt of it. |
Awesome, I'll get a change pushed out as soon as I can. I'll be making a new branch and PR and linking to this one for context |
I've realised we can't use the default admin username/password for |
Right! If you follow the login flow in the demo auth, you'll see that it uses if (email === 'admin') {
user = await this.store.getByQuery({ id: 1 });
} Now, you could of course update the |
I'd probably also add a check for the flag in that specific method to ensure that it's never invoked successfully without the flag being present. |
Seems the |
Let me do some digging for you around that. I think we can get away with checking whether the user has an email OR if their username is admin, but I wanna make doubly sure before we go ahead with it. |
Any idea which tests would be best suited to add these new cases to? Seems there are multiple that hit the |
Doesn't look like we have any direct tests for that middleware yet 🤔 What tests did you find? I think I'd look at We probably want to test that:
|
Thanks, I'm a little unfamiliar with testing in JS and with how the tests are set up in this project specifically. I've seen that Would you happen to know what the correct way to create the default root roles would be? From what I can see, it seems the root roles are actually created through SQL rather than by using the access service, which only allows for roles of type If it helps, I can push my current code to a branch to give you a better idea of what it looks like. |
Right. Yeah, setup can be a bit weird, but it should be very possible to achieve. So yeah, please make a branch that I can have a look at and I'll see what I can do 🙋🏼 |
Haha the setup has definitely been tricky. I'm on Windows and I assume this project is mainly aimed at development using Mac/Linux. I ended up using WSL to try running it which still seems to error out on the initial I've pushed the changes to https://github.com/00Chaotic/unleash/tree/feat/allow_admin_login_using_demo_auth. I believe it's just the tests left that need to be sorted out. |
Cool; I'll have a look 🙋🏼 As for development, I know most of our internal devs are on Linux or macOS, but we do have some running Windows. I haven't heard about any issues with it, though. @Tymek, you're on Windows, aren't you? Do you need to do some weird magic to make it work? |
@00Chaotic , I don't have push access to your branch, but this appears to work (the first test fails, but I'll leave it to you to look into that, if you don't mind): import { IAuthType } from '../server-impl';
import dbInit, { ITestDb } from '../../test/e2e/helpers/database-init';
import { IUnleashStores } from '../types';
import { setupAppWithCustomAuth } from '../../test/e2e/helpers/test-helper';
let db: ITestDb;
let stores: IUnleashStores;
beforeAll(async () => {
db = await dbInit('demo_auth_serial');
stores = db.stores;
});
afterAll(async () => {
await db?.destroy();
});
const getApp = (adminLoginEnabled: boolean) =>
setupAppWithCustomAuth(stores, () => {}, {
authentication: {
authDemoAllowAdminLogin: adminLoginEnabled,
type: IAuthType.DEMO,
createAdminUser: true,
},
});
test('should allow login with admin user if flag enabled', async () => {
const app = await getApp(true);
return app.request
.post(`/auth/demo/login`)
.send({ email: 'admin' })
.expect(200)
.expect((res) => {
expect(res.body.id).toBe(1);
expect(res.body.username).toBe('admin');
});
});
test('should create regular user with flag enabled', async () => {
const app = await getApp(true);
return app.request
.post(`/auth/demo/login`)
.send({ email: '[email protected]' })
.expect(200)
.expect((res) => {
expect(res.body.email).toBe('[email protected]');
expect(res.body.id).not.toBe(1);
});
});
test('should return 403 for admin user if flag disabled', async () => {
const app = await getApp(false);
return app.request
.post(`/auth/demo/login`)
.send({ email: 'admin' })
.expect(403);
}); |
Hmm the tests seem to hang at the After some investigation, it just seems unable to connect:
I'm not sure what it's trying to connect to. I tried running the usual app postgres docker container just in case, but to no avail. Is it expecting a local postgres server or something by any chance? |
Oh, sorry, yeah, I think you're right. I've just been running these with a dockerized postgres running always, so I didn't really think about that. I think it will attempt to connect with the default Unleash credentials etc. I'm not sure whether it needs the schema to exist etc. or if it will create it itself. If it helps, here's the output of my
I can have a closer look tomorrow if it doesn't work when just spinning up a local version. |
That's pretty much the same as my I tried tearing down my postgres container and running a fresh container with the instructions in CONTRIBUTING.md, but it still seems unable to connect. That file was last updated 10 months ago so not sure if it's still up-to-date, or it could be a different issue altogether. Edit: Tried running the app before the tests, after freshly creating the container in case the migrations or similar made any difference, but still seeing the same error |
@00Chaotic Hey, sorry for causing you problems! I sat down to figure it out. In short, this worked for me: docker run --name unleash-postgres -p 5432:5432 -e POSTGRES_USER=unleash_user -e POSTGRES_PASSWORD=password -e POSTGRES_DB=unleash_test -d postgres:15 Importantly, we:
|
This is sourced from checking the values in |
No worries about the trouble, thanks for being so patient haha. It's working now with the latest command. I'll leave it to you to decide whether any test-related docs need to be created/updated to indicate the correct container setup for the tests. I needed to remove the Regarding the first test failing, it seems the admin user does not exist so it throws a Edit: Nevermind, the user does seem to exist in the database so I'm not sure why it isn't being found by the app. |
I think adding that would be helpful, but that shouldn't be on you. I'll see what I can do, though.
Sorry, what
Yeah, it should exist, I think. Not sure why it's not finding it, though, but it's probably debuggable. Do you wanna make a new PR for this, by the way, so that we can look at the set of changes and discuss it in context? |
There was some confusion on how to run the e2e tests in #6447, so I'm adding a small section to CONTRIBUTING.md to make it clearer.
Apologies, I think I added that statement in and then didn't realise it was my own addition haha, it doesn't seem to be present in the code you provided.
I was planning on raising a PR once we had the functionality+tests working so that the discussion on that PR would be about improvements rather than about just getting it working in the first place, but yes I can raise a PR if we want to move discussion to that 🙂 I'll be a little busy for the next few days though so I'll be able to get to it by the middle or end of next week. |
Sure thing; whatever works for you! I'll be out for the rest of this week and until next Tuesday anyway, so there's no rush. |
I've been poking around but haven't been able to find a solution for the successful admin login test case. It did seem to give the correct output one time but I hadn't actually changed anything, and then it went back to giving the I tried commenting the other test cases out in case they were messing with this one but it doesn't seem to have made a difference. Also, I needed to add a 20sec timeout to the I've updated the branch with my current WIP code for the two tests that are working and the one that isn't, in case you'd like to take a look. Hopefully you have more luck than I have had thus far 😅 |
Huh, that sounds strange. I'll see if anything jumps out at me. Let me have a look and get back to you later today, yeah?
No, this doesn't sound familiar to me. I'm not sure what's going on there? |
) There was some confusion on how to run the e2e tests in #6447, so I'm adding a small section to CONTRIBUTING.md to make it clearer.
So looking at this a bit:
In However, there's also this in the console:
I think we've seen that happen due to missing Does that give you enough to go on to debug this further? I also noticed that I made a mistake previously, assuming that you'd get a 403 if you tried to log in with an invalid email. Turns out you don't. But the response should be the same as with any other non-email. So I think that test should be changed to something like this: test('if the flag is disabled, using `admin` should have the same result as any other invalid email', async () => {
const app = await getApp(false);
const nonAdminUsername = 'not-an-email'
const adminUsername = 'admin'
const nonAdminUser = await app.request
.post(`/auth/demo/login`)
.send({ email: nonAdminUsername })
const adminUser = await app.request
.post(`/auth/demo/login`)
.send({ email: adminUsername })
// verify that the status is the same
expect(nonAdminUser.status).toBe(adminUser.status);
for (const user of [nonAdminUser, adminUser]) {
// verify that the body has the same structure
expect(user.body).toMatchObject({
error: expect.stringMatching(/^Could not sign in with /)
})
}
}); That also means that you can remove this if statement from the middleware: if (!authentication.authDemoAllowAdminLogin && email === 'admin') {
return res.status(403)
.json({ error: `Admin login not enabled for demo auth type`})
.end;
} |
Regarding needing the timeout in the
Since you don't have this issue, it may be something weird with my setup so I'll remove the timeout on the next commit and we can see if it passes the PR checks once raised. I looked into the Thanks for looking into the failing test case, I was able to replicate the behaviour you noticed with the timing so you're right, it's most likely that the admin user hasn't yet been populated when the test case is run. We could just add a small timeout to delay that specific test case but that feels like a bit of a hacky workaround. Does the current testing implementation include any way to check that the stores are initialised before running the test cases? I was thinking of checking the |
Yeah, maybe. Mine run immediately, so I'm not sure what's going on.
That's also interesting. Again I think we've seen that one previously when there's an unawaited expression somewhere. But I might be wrong. That said, I don't see it now for some reason 🤷🏼 You can probably ignore that, then.
We could add the delay, but as you mentioned that is hacky and I'm not sure it would work. As far as I'm aware, there's no specific way of checking the stores are initialized correctly, but it shouldn't be necessary. This isn't the only e2e test we have, and it's not like the others don't use the stores. I'm not sure what's going on. However, in other hacks that might fix it: try putting that first test last in the file. Doing that on my end seems to make it pass consistently. Again, it's not ideal, but this feels like it's on our plate to deal with and not yours 😅 |
Haha that's all good, thanks for your patience this far. Seems the tests have been more of a headache than the actual code implementation 😅 The test case seems to be working for me too if I move it to the end of the file as you suggested. Regarding the below:
Would it not be more informative to give the 403 if Also, since we've now fixed any issues not actually related to the implementation of this change (darn pesky tests), I'll go ahead and raise a PR and we can continue discussion there regarding what the change should/shouldn't include. |
Created PR #6808 Feel free to close this PR if there is nothing further that should be discussed here instead of the new PR |
Of course; happy to! Thanks for contributing 🙌🏼
Yeah, that happens quite a bit, I'm afraid, but that's how it is. It's especially tricky with e2e tests like this.
This is a good point. The reason I suggested a 403 initially was that that's usually what happens if you try to log in with invalid credentials ... But I also think I might have mixed it up with 401, sooo 😅 Anyway: should we return a special response when you use If you know about this feature (because you've read the docs or the release notes or whatever), then you'll probably understand why you're not able to log in with Of course, I'd be happy to hear further arguments against this if you think the other way is better.
Sick! I'll go take a look and we can continue any discussions (or start new ones) there. |
This PR introduces a configuration option (`authentication.demoAllowAdminLogin`) that allows you to log in as admin when using demo authentication. To do this, use the username `admin`. ## About the changes The `admin` user currently cannot be accessed in `demo` authentication mode, as the auth mode requires only an email to log in, and the admin user is not created with an email. This change allows for logging in as the admin user only if an `AUTH_DEMO_ALLOW_ADMIN_LOGIN` is set to `true` (or the corresponding `authDemoAllowAdminLogin` config is enabled). <!-- Does it close an issue? Multiple? --> Closes #6398 ### Important files [demo-authentication.ts](https://github.com/Unleash/unleash/compare/main...00Chaotic:unleash:feat/allow_admin_login_using_demo_auth?expand=1#diff-c166f00f0a8ca4425236b3bcba40a8a3bd07a98d067495a0a092eec26866c9f1R25) ## Discussion points Can continue discussion of [this comment](#6447 (comment)) in this PR. --------- Co-authored-by: Thomas Heartman <[email protected]>
Ensures initial admin user is created with a default email so that it can be accessed in demo mode. Also allows for custom email to be used, similar to existing username and password config.
Closes #6398