mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: allow admin login using demo auth (#6808)
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](https://github.com/Unleash/unleash/pull/6447#issuecomment-2042405647) in this PR. --------- Co-authored-by: Thomas Heartman <thomasheartman+github@gmail.com>
This commit is contained in:
parent
9ba6be6000
commit
13aa58e0e9
@ -63,7 +63,7 @@ const DemoAuth: VFC<IDemoAuthProps> = ({ authDetails, redirect }) => {
|
||||
id='email'
|
||||
data-testid={LOGIN_EMAIL_ID}
|
||||
required
|
||||
type='email'
|
||||
type={email === 'admin' ? 'text' : 'email'}
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
@ -17,6 +17,7 @@ exports[`should create default config 1`] = `
|
||||
"authentication": {
|
||||
"createAdminUser": true,
|
||||
"customAuthHandler": [Function],
|
||||
"demoAllowAdminLogin": false,
|
||||
"enableApiToken": true,
|
||||
"initApiTokens": [],
|
||||
"initialAdminUser": {
|
||||
|
@ -226,6 +226,11 @@ test('should handle cases where no env var specified for tokens', async () => {
|
||||
expect(config.authentication.initApiTokens).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should default demo admin login to false', async () => {
|
||||
const config = createConfig({});
|
||||
expect(config.authentication.demoAllowAdminLogin).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should load environment overrides from env var', async () => {
|
||||
process.env.ENABLED_ENVIRONMENTS = 'default,production';
|
||||
|
||||
|
@ -308,6 +308,10 @@ const defaultVersionOption: IVersionOption = {
|
||||
};
|
||||
|
||||
const defaultAuthentication: IAuthOption = {
|
||||
demoAllowAdminLogin: parseEnvVarBoolean(
|
||||
process.env.AUTH_DEMO_ALLOW_ADMIN_LOGIN,
|
||||
false,
|
||||
),
|
||||
enableApiToken: parseEnvVarBoolean(process.env.AUTH_ENABLE_API_TOKEN, true),
|
||||
type: authTypeFromString(process.env.AUTH_TYPE),
|
||||
customAuthHandler: defaultCustomAuthDenyAll,
|
||||
|
73
src/lib/middleware/demo-authentication.e2e.test.ts
Normal file
73
src/lib/middleware/demo-authentication.e2e.test.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import dbInit from '../../test/e2e/helpers/database-init';
|
||||
import { IAuthType } from '../server-impl';
|
||||
import { setupAppWithCustomAuth } from '../../test/e2e/helpers/test-helper';
|
||||
import type { ITestDb } from '../../test/e2e/helpers/database-init';
|
||||
import type { IUnleashStores } from '../types';
|
||||
|
||||
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: {
|
||||
demoAllowAdminLogin: adminLoginEnabled,
|
||||
type: IAuthType.DEMO,
|
||||
createAdminUser: true,
|
||||
},
|
||||
});
|
||||
|
||||
test('the demoAllowAdminLogin flag should not affect regular user login/creation', async () => {
|
||||
const app = await getApp(true);
|
||||
return app.request
|
||||
.post(`/auth/demo/login`)
|
||||
.send({ email: 'test@example.com' })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.email).toBe('test@example.com');
|
||||
expect(res.body.id).not.toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
test('if the demoAllowAdminLogin 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 });
|
||||
|
||||
expect(nonAdminUser.status).toBe(adminUser.status);
|
||||
|
||||
for (const user of [nonAdminUser, adminUser]) {
|
||||
expect(user.body).toMatchObject({
|
||||
error: expect.stringMatching(/^Could not sign in with /),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('should allow you to login as admin if the demoAllowAdminLogin 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');
|
||||
});
|
||||
});
|
@ -4,7 +4,7 @@ import type { IUnleashServices } from '../types/services';
|
||||
import type { IUnleashConfig } from '../types/option';
|
||||
import ApiUser from '../types/api-user';
|
||||
import { ApiTokenType } from '../types/models/api-token';
|
||||
import type { IAuthRequest } from '../server-impl';
|
||||
import type { IAuthRequest, IUser } from '../server-impl';
|
||||
import type { IApiRequest } from '../routes/unleash-types';
|
||||
import { encrypt } from '../util';
|
||||
|
||||
@ -19,14 +19,19 @@ function demoAuthentication(
|
||||
): void {
|
||||
app.post(`${basePath}/auth/demo/login`, async (req: IAuthRequest, res) => {
|
||||
let { email } = req.body;
|
||||
let user: IUser;
|
||||
|
||||
try {
|
||||
if (authentication.demoAllowAdminLogin && email === 'admin') {
|
||||
user = await userService.loginDemoAuthDefaultAdmin();
|
||||
} else {
|
||||
email = flagResolver.isEnabled('encryptEmails', { email })
|
||||
? encrypt(email)
|
||||
: email;
|
||||
try {
|
||||
const user = await userService.loginUserWithoutPassword(
|
||||
email,
|
||||
true,
|
||||
);
|
||||
|
||||
user = await userService.loginUserWithoutPassword(email, true);
|
||||
}
|
||||
|
||||
req.session.user = user;
|
||||
return res.status(200).json(user);
|
||||
} catch (e) {
|
||||
@ -37,7 +42,7 @@ function demoAuthentication(
|
||||
});
|
||||
|
||||
app.use(`${basePath}/api/admin/`, (req: IAuthRequest, res, next) => {
|
||||
if (req.session.user?.email) {
|
||||
if (req.session.user?.email || req.session.user?.username === 'admin') {
|
||||
req.user = req.session.user;
|
||||
}
|
||||
next();
|
||||
|
@ -379,6 +379,12 @@ class UserService {
|
||||
return user;
|
||||
}
|
||||
|
||||
async loginDemoAuthDefaultAdmin(): Promise<IUser> {
|
||||
const user = await this.store.getByQuery({ id: 1 });
|
||||
await this.store.successfullyLogin(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
async changePassword(userId: number, password: string): Promise<void> {
|
||||
this.validatePassword(password);
|
||||
const passwordHash = await bcrypt.hash(password, saltRounds);
|
||||
|
@ -67,6 +67,7 @@ export type CustomAuthHandler = (
|
||||
) => void;
|
||||
|
||||
export interface IAuthOption {
|
||||
demoAllowAdminLogin?: boolean;
|
||||
enableApiToken: boolean;
|
||||
type: IAuthType;
|
||||
customAuthHandler?: CustomAuthHandler;
|
||||
|
Loading…
Reference in New Issue
Block a user