1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

feat: allow defining initial admin user as env variable (#4927)

Closes #4560
This commit is contained in:
Jonas Strømsodd 2023-10-06 09:07:06 +02:00 committed by GitHub
parent 36343626ab
commit 80c4a8277c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 167 additions and 17 deletions

View File

@ -19,6 +19,10 @@ exports[`should create default config 1`] = `
"customAuthHandler": [Function],
"enableApiToken": true,
"initApiTokens": [],
"initialAdminUser": {
"password": "unleash4all",
"username": "admin",
},
"type": "open-source",
},
"clientFeatureCaching": {

View File

@ -195,6 +195,10 @@ const defaultAuthentication: IAuthOption = {
type: authTypeFromString(process.env.AUTH_TYPE),
customAuthHandler: defaultCustomAuthDenyAll,
createAdminUser: true,
initialAdminUser: {
username: process.env.UNLEASH_DEFAULT_ADMIN_USERNAME ?? 'admin',
password: process.env.UNLEASH_DEFAULT_ADMIN_PASSWORD ?? 'unleash4all',
},
initApiTokens: [],
};

View File

@ -69,7 +69,7 @@ test('Should create new user', async () => {
expect(storedUser.username).toBe('test');
});
test('Should create default user', async () => {
test('Should create default user - with defaults', async () => {
const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock();
@ -102,12 +102,104 @@ test('Should create default user', async () => {
settingService,
});
await service.initAdminUser();
await service.initAdminUser({});
const user = await service.loginUser('admin', 'unleash4all');
expect(user.username).toBe('admin');
});
test('Should create default user - with provided username and password', async () => {
const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock();
const resetTokenStore = new FakeResetTokenStore();
const resetTokenService = new ResetTokenService(
{ resetTokenStore },
config,
);
const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService(
{
settingStore: new FakeSettingStore(),
},
config,
eventService,
);
const service = new UserService({ userStore }, config, {
accessService,
resetTokenService,
emailService,
eventService,
sessionService,
settingService,
});
await service.initAdminUser({
initialAdminUser: {
username: 'admin',
password: 'unleash4all!',
},
});
const user = await service.loginUser('admin', 'unleash4all!');
expect(user.username).toBe('admin');
});
test('Should not create default user - with `createAdminUser` === false', async () => {
const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();
const accessService = new AccessServiceMock();
const resetTokenStore = new FakeResetTokenStore();
const resetTokenService = new ResetTokenService(
{ resetTokenStore },
config,
);
const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService(
{
settingStore: new FakeSettingStore(),
},
config,
eventService,
);
const service = new UserService({ userStore }, config, {
accessService,
resetTokenService,
emailService,
eventService,
sessionService,
settingService,
});
await service.initAdminUser({
createAdminUser: false,
initialAdminUser: {
username: 'admin',
password: 'unleash4all!',
},
});
await expect(
service.loginUser('admin', 'unleash4all!'),
).rejects.toThrowError(
'The combination of password and username you provided is invalid',
);
});
test('Should be a valid password', async () => {
const userStore = new UserStoreMock();
const eventStore = new EventStoreMock();

View File

@ -12,7 +12,7 @@ import InvalidTokenError from '../error/invalid-token-error';
import NotFoundError from '../error/notfound-error';
import OwaspValidationError from '../error/owasp-validation-error';
import { EmailService } from './email-service';
import { IUnleashConfig } from '../types/option';
import { IAuthOption, IUnleashConfig } from '../types/option';
import SessionService from './session-service';
import { IUnleashStores } from '../types/stores';
import PasswordUndefinedError from '../error/password-undefined';
@ -104,8 +104,14 @@ class UserService {
this.emailService = services.emailService;
this.sessionService = services.sessionService;
this.settingService = services.settingService;
if (authentication?.createAdminUser) {
process.nextTick(() => this.initAdminUser());
if (authentication.createAdminUser !== false) {
process.nextTick(() =>
this.initAdminUser({
createAdminUser: authentication.createAdminUser,
initialAdminUser: authentication.initialAdminUser,
}),
);
}
this.baseUriPath = server.baseUriPath || '';
@ -122,27 +128,47 @@ class UserService {
}
}
async initAdminUser(): Promise<void> {
async initAdminUser(
initialAdminUserConfig: Pick<
IAuthOption,
'createAdminUser' | 'initialAdminUser'
>,
): Promise<void> {
let username: string;
let password: string;
if (
initialAdminUserConfig.createAdminUser !== false &&
initialAdminUserConfig.initialAdminUser
) {
username = initialAdminUserConfig.initialAdminUser.username;
password = initialAdminUserConfig.initialAdminUser.password;
} else {
username = 'admin';
password = 'unleash4all';
}
const userCount = await this.store.count();
if (userCount === 0) {
if (userCount === 0 && username && password) {
// create default admin user
try {
const pwd = 'unleash4all';
this.logger.info(
`Creating default user "admin" with password "${pwd}"`,
`Creating default user '${username}' with password '${password}'`,
);
const user = await this.store.insert({
username: 'admin',
username,
});
const passwordHash = await bcrypt.hash(pwd, saltRounds);
const passwordHash = await bcrypt.hash(password, saltRounds);
await this.store.setPasswordHash(user.id, passwordHash);
await this.accessService.setUserRootRole(
user.id,
RoleName.ADMIN,
);
} catch (e) {
this.logger.error('Unable to create default user "admin"');
this.logger.error(
`Unable to create default user '${username}'`,
);
}
}
}
@ -344,7 +370,7 @@ class UserService {
user = await this.store.update(user.id, { name, email });
}
} catch (e) {
// User does not exists. Create if "autoCreate" is enabled
// User does not exists. Create if 'autoCreate' is enabled
if (autoCreate) {
user = await this.createUser({
email,

View File

@ -57,7 +57,11 @@ export interface IAuthOption {
enableApiToken: boolean;
type: IAuthType;
customAuthHandler?: Function;
createAdminUser: boolean;
createAdminUser?: boolean;
initialAdminUser?: {
username: string;
password: string;
};
initApiTokens: ILegacyApiTokenCreate[];
}

View File

@ -63,7 +63,13 @@ afterEach(async () => {
});
test('should create initial admin user', async () => {
await userService.initAdminUser();
await userService.initAdminUser({
createAdminUser: true,
initialAdminUser: {
username: 'admin',
password: 'unleash4all',
},
});
await expect(async () =>
userService.loginUser('admin', 'wrong-password'),
).rejects.toThrow(Error);
@ -78,7 +84,13 @@ test('should not init default user if we already have users', async () => {
password: 'A very strange P4ssw0rd_',
rootRole: adminRole.id,
});
await userService.initAdminUser();
await userService.initAdminUser({
createAdminUser: true,
initialAdminUser: {
username: 'admin',
password: 'unleash4all',
},
});
const users = await userService.getAll();
expect(users).toHaveLength(1);
expect(users[0].username).toBe('test');

View File

@ -66,7 +66,7 @@ unleash.start(unleashOptions);
- `none` - Turn off authentication all together
- `demo` - Only requires an email to sign in (was default in v3)
- `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.
- `createAdminUser`: `boolean` — whether to create an admin user with default password - Defaults to `true`. Can not be set via environment variables. 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.
- `initApiTokens` / `INIT_ADMIN_API_TOKENS` and `INIT_CLIENT_API_TOKENS` (see below): `ApiTokens[]` — Array of API tokens to create on startup. The tokens will only be created if the database doesn't already contain any API tokens. Example:
```ts

View File

@ -31,6 +31,14 @@ To run multiple replicas of Unleash simply point all instances to the same datab
- username: `admin`
- password: `unleash4all`
If you'd like the default admin user to be created with a different username and password, you may define the following environment variables when running Unleash:
- `UNLEASH_DEFAULT_ADMIN_USERNAME`
- UNLEASH_DEFAULT_ADMIN_PASSWORD
The way of defining these variables may vary depending on how you run Unleash.
### Option 1 - use Docker {#option-one---use-docker}
**Useful links:**