mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
feat: allow defining initial admin user as env variable (#4927)
Closes #4560
This commit is contained in:
parent
36343626ab
commit
80c4a8277c
@ -19,6 +19,10 @@ exports[`should create default config 1`] = `
|
|||||||
"customAuthHandler": [Function],
|
"customAuthHandler": [Function],
|
||||||
"enableApiToken": true,
|
"enableApiToken": true,
|
||||||
"initApiTokens": [],
|
"initApiTokens": [],
|
||||||
|
"initialAdminUser": {
|
||||||
|
"password": "unleash4all",
|
||||||
|
"username": "admin",
|
||||||
|
},
|
||||||
"type": "open-source",
|
"type": "open-source",
|
||||||
},
|
},
|
||||||
"clientFeatureCaching": {
|
"clientFeatureCaching": {
|
||||||
|
@ -195,6 +195,10 @@ const defaultAuthentication: IAuthOption = {
|
|||||||
type: authTypeFromString(process.env.AUTH_TYPE),
|
type: authTypeFromString(process.env.AUTH_TYPE),
|
||||||
customAuthHandler: defaultCustomAuthDenyAll,
|
customAuthHandler: defaultCustomAuthDenyAll,
|
||||||
createAdminUser: true,
|
createAdminUser: true,
|
||||||
|
initialAdminUser: {
|
||||||
|
username: process.env.UNLEASH_DEFAULT_ADMIN_USERNAME ?? 'admin',
|
||||||
|
password: process.env.UNLEASH_DEFAULT_ADMIN_PASSWORD ?? 'unleash4all',
|
||||||
|
},
|
||||||
initApiTokens: [],
|
initApiTokens: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ test('Should create new user', async () => {
|
|||||||
expect(storedUser.username).toBe('test');
|
expect(storedUser.username).toBe('test');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should create default user', async () => {
|
test('Should create default user - with defaults', async () => {
|
||||||
const userStore = new UserStoreMock();
|
const userStore = new UserStoreMock();
|
||||||
const eventStore = new EventStoreMock();
|
const eventStore = new EventStoreMock();
|
||||||
const accessService = new AccessServiceMock();
|
const accessService = new AccessServiceMock();
|
||||||
@ -102,12 +102,104 @@ test('Should create default user', async () => {
|
|||||||
settingService,
|
settingService,
|
||||||
});
|
});
|
||||||
|
|
||||||
await service.initAdminUser();
|
await service.initAdminUser({});
|
||||||
|
|
||||||
const user = await service.loginUser('admin', 'unleash4all');
|
const user = await service.loginUser('admin', 'unleash4all');
|
||||||
expect(user.username).toBe('admin');
|
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 () => {
|
test('Should be a valid password', async () => {
|
||||||
const userStore = new UserStoreMock();
|
const userStore = new UserStoreMock();
|
||||||
const eventStore = new EventStoreMock();
|
const eventStore = new EventStoreMock();
|
||||||
|
@ -12,7 +12,7 @@ import InvalidTokenError from '../error/invalid-token-error';
|
|||||||
import NotFoundError from '../error/notfound-error';
|
import NotFoundError from '../error/notfound-error';
|
||||||
import OwaspValidationError from '../error/owasp-validation-error';
|
import OwaspValidationError from '../error/owasp-validation-error';
|
||||||
import { EmailService } from './email-service';
|
import { EmailService } from './email-service';
|
||||||
import { IUnleashConfig } from '../types/option';
|
import { IAuthOption, IUnleashConfig } from '../types/option';
|
||||||
import SessionService from './session-service';
|
import SessionService from './session-service';
|
||||||
import { IUnleashStores } from '../types/stores';
|
import { IUnleashStores } from '../types/stores';
|
||||||
import PasswordUndefinedError from '../error/password-undefined';
|
import PasswordUndefinedError from '../error/password-undefined';
|
||||||
@ -104,8 +104,14 @@ class UserService {
|
|||||||
this.emailService = services.emailService;
|
this.emailService = services.emailService;
|
||||||
this.sessionService = services.sessionService;
|
this.sessionService = services.sessionService;
|
||||||
this.settingService = services.settingService;
|
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 || '';
|
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();
|
const userCount = await this.store.count();
|
||||||
|
|
||||||
if (userCount === 0) {
|
if (userCount === 0 && username && password) {
|
||||||
// create default admin user
|
// create default admin user
|
||||||
try {
|
try {
|
||||||
const pwd = 'unleash4all';
|
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`Creating default user "admin" with password "${pwd}"`,
|
`Creating default user '${username}' with password '${password}'`,
|
||||||
);
|
);
|
||||||
const user = await this.store.insert({
|
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.store.setPasswordHash(user.id, passwordHash);
|
||||||
await this.accessService.setUserRootRole(
|
await this.accessService.setUserRootRole(
|
||||||
user.id,
|
user.id,
|
||||||
RoleName.ADMIN,
|
RoleName.ADMIN,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} 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 });
|
user = await this.store.update(user.id, { name, email });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// User does not exists. Create if "autoCreate" is enabled
|
// User does not exists. Create if 'autoCreate' is enabled
|
||||||
if (autoCreate) {
|
if (autoCreate) {
|
||||||
user = await this.createUser({
|
user = await this.createUser({
|
||||||
email,
|
email,
|
||||||
|
@ -57,7 +57,11 @@ export interface IAuthOption {
|
|||||||
enableApiToken: boolean;
|
enableApiToken: boolean;
|
||||||
type: IAuthType;
|
type: IAuthType;
|
||||||
customAuthHandler?: Function;
|
customAuthHandler?: Function;
|
||||||
createAdminUser: boolean;
|
createAdminUser?: boolean;
|
||||||
|
initialAdminUser?: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
initApiTokens: ILegacyApiTokenCreate[];
|
initApiTokens: ILegacyApiTokenCreate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,13 @@ afterEach(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should create initial admin user', async () => {
|
test('should create initial admin user', async () => {
|
||||||
await userService.initAdminUser();
|
await userService.initAdminUser({
|
||||||
|
createAdminUser: true,
|
||||||
|
initialAdminUser: {
|
||||||
|
username: 'admin',
|
||||||
|
password: 'unleash4all',
|
||||||
|
},
|
||||||
|
});
|
||||||
await expect(async () =>
|
await expect(async () =>
|
||||||
userService.loginUser('admin', 'wrong-password'),
|
userService.loginUser('admin', 'wrong-password'),
|
||||||
).rejects.toThrow(Error);
|
).rejects.toThrow(Error);
|
||||||
@ -78,7 +84,13 @@ test('should not init default user if we already have users', async () => {
|
|||||||
password: 'A very strange P4ssw0rd_',
|
password: 'A very strange P4ssw0rd_',
|
||||||
rootRole: adminRole.id,
|
rootRole: adminRole.id,
|
||||||
});
|
});
|
||||||
await userService.initAdminUser();
|
await userService.initAdminUser({
|
||||||
|
createAdminUser: true,
|
||||||
|
initialAdminUser: {
|
||||||
|
username: 'admin',
|
||||||
|
password: 'unleash4all',
|
||||||
|
},
|
||||||
|
});
|
||||||
const users = await userService.getAll();
|
const users = await userService.getAll();
|
||||||
expect(users).toHaveLength(1);
|
expect(users).toHaveLength(1);
|
||||||
expect(users[0].username).toBe('test');
|
expect(users[0].username).toBe('test');
|
||||||
|
@ -66,7 +66,7 @@ unleash.start(unleashOptions);
|
|||||||
- `none` - Turn off authentication all together
|
- `none` - Turn off authentication all together
|
||||||
- `demo` - Only requires an email to sign in (was default in v3)
|
- `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.
|
- `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:
|
- `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
|
```ts
|
||||||
|
@ -31,6 +31,14 @@ To run multiple replicas of Unleash simply point all instances to the same datab
|
|||||||
- username: `admin`
|
- username: `admin`
|
||||||
- password: `unleash4all`
|
- 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}
|
### Option 1 - use Docker {#option-one---use-docker}
|
||||||
|
|
||||||
**Useful links:**
|
**Useful links:**
|
||||||
|
Loading…
Reference in New Issue
Block a user