mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
Verification code CLI (#111707)
* Add verification code CLI * Added suggestion from code review * Fixed types * Added extra test * Added CLI dist scripts * Fixed typo * Write code to data instead of config directory
This commit is contained in:
parent
34581ff1ed
commit
db5cf95724
12 changed files with 346 additions and 9 deletions
9
scripts/kibana_verification_code.js
Normal file
9
scripts/kibana_verification_code.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('../src/cli_verification_code/dev');
|
39
src/cli_verification_code/cli_verification_code.js
Normal file
39
src/cli_verification_code/cli_verification_code.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { kibanaPackageJson, getDataPath } from '@kbn/utils';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
import Command from '../cli/command';
|
||||||
|
|
||||||
|
const program = new Command('bin/kibana-verification-code');
|
||||||
|
|
||||||
|
program
|
||||||
|
.version(kibanaPackageJson.version)
|
||||||
|
.description('Tool to get Kibana verification code')
|
||||||
|
.action(() => {
|
||||||
|
const fpath = path.join(getDataPath(), 'verification_code');
|
||||||
|
try {
|
||||||
|
const code = fs.readFileSync(fpath).toString();
|
||||||
|
console.log(
|
||||||
|
`Your verification code is: ${chalk.black.bgCyanBright(
|
||||||
|
` ${code.substr(0, 3)} ${code.substr(3)} `
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Couldn't find verification code.
|
||||||
|
|
||||||
|
If Kibana hasn't been configured yet, restart Kibana to generate a new code.
|
||||||
|
|
||||||
|
Otherwise, you can safely ignore this message and start using Kibana.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
10
src/cli_verification_code/dev.js
Normal file
10
src/cli_verification_code/dev.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('../setup_node_env');
|
||||||
|
require('./cli_verification_code');
|
10
src/cli_verification_code/dist.js
Normal file
10
src/cli_verification_code/dist.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('../setup_node_env/dist');
|
||||||
|
require('./cli_verification_code');
|
29
src/dev/build/tasks/bin/scripts/kibana-verification-code
Executable file
29
src/dev/build/tasks/bin/scripts/kibana-verification-code
Executable file
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/sh
|
||||||
|
SCRIPT=$0
|
||||||
|
|
||||||
|
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
|
||||||
|
while [ -h "$SCRIPT" ] ; do
|
||||||
|
ls=$(ls -ld "$SCRIPT")
|
||||||
|
# Drop everything prior to ->
|
||||||
|
link=$(expr "$ls" : '.*-> \(.*\)$')
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
SCRIPT="$link"
|
||||||
|
else
|
||||||
|
SCRIPT=$(dirname "$SCRIPT")/"$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
DIR="$(dirname "${SCRIPT}")/.."
|
||||||
|
CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"}
|
||||||
|
NODE="${DIR}/node/bin/node"
|
||||||
|
test -x "$NODE"
|
||||||
|
if [ ! -x "$NODE" ]; then
|
||||||
|
echo "unable to find usable node.js executable."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "${CONFIG_DIR}/node.options" ]; then
|
||||||
|
KBN_NODE_OPTS="$(grep -v ^# < ${CONFIG_DIR}/node.options | xargs)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
NODE_OPTIONS="$KBN_NODE_OPTS $NODE_OPTIONS" "${NODE}" "${DIR}/src/cli_verification_code/dist" "$@"
|
35
src/dev/build/tasks/bin/scripts/kibana-verification-code.bat
Executable file
35
src/dev/build/tasks/bin/scripts/kibana-verification-code.bat
Executable file
|
@ -0,0 +1,35 @@
|
||||||
|
@echo off
|
||||||
|
|
||||||
|
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||||
|
|
||||||
|
set SCRIPT_DIR=%~dp0
|
||||||
|
for %%I in ("%SCRIPT_DIR%..") do set DIR=%%~dpfI
|
||||||
|
|
||||||
|
set NODE=%DIR%\node\node.exe
|
||||||
|
|
||||||
|
If Not Exist "%NODE%" (
|
||||||
|
Echo unable to find usable node.js executable.
|
||||||
|
Exit /B 1
|
||||||
|
)
|
||||||
|
|
||||||
|
set CONFIG_DIR=%KBN_PATH_CONF%
|
||||||
|
If ["%KBN_PATH_CONF%"] == [] (
|
||||||
|
set "CONFIG_DIR=%DIR%\config"
|
||||||
|
)
|
||||||
|
|
||||||
|
IF EXIST "%CONFIG_DIR%\node.options" (
|
||||||
|
for /F "usebackq eol=# tokens=*" %%i in ("%CONFIG_DIR%\node.options") do (
|
||||||
|
If [!NODE_OPTIONS!] == [] (
|
||||||
|
set "NODE_OPTIONS=%%i"
|
||||||
|
) Else (
|
||||||
|
set "NODE_OPTIONS=!NODE_OPTIONS! %%i"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
TITLE Kibana Verification Code
|
||||||
|
"%NODE%" "%DIR%\src\cli_verification_code\dist" %*
|
||||||
|
|
||||||
|
:finally
|
||||||
|
|
||||||
|
ENDLOCAL
|
|
@ -12,6 +12,7 @@ import React, { useEffect, useRef } from 'react';
|
||||||
import useList from 'react-use/lib/useList';
|
import useList from 'react-use/lib/useList';
|
||||||
import useUpdateEffect from 'react-use/lib/useUpdateEffect';
|
import useUpdateEffect from 'react-use/lib/useUpdateEffect';
|
||||||
|
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
import { euiThemeVars } from '@kbn/ui-shared-deps/theme';
|
import { euiThemeVars } from '@kbn/ui-shared-deps/theme';
|
||||||
|
|
||||||
export interface SingleCharsFieldProps {
|
export interface SingleCharsFieldProps {
|
||||||
|
@ -124,6 +125,10 @@ export const SingleCharsField: FunctionComponent<SingleCharsFieldProps> = ({
|
||||||
maxLength={1}
|
maxLength={1}
|
||||||
isInvalid={isInvalid}
|
isInvalid={isInvalid}
|
||||||
style={{ textAlign: 'center' }}
|
style={{ textAlign: 'center' }}
|
||||||
|
aria-label={i18n.translate('interactiveSetup.singleCharsField.digitLabel', {
|
||||||
|
defaultMessage: 'Digit {index}',
|
||||||
|
values: { index: i + 1 },
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fireEvent, render, waitFor } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { coreMock } from 'src/core/public/mocks';
|
||||||
|
|
||||||
|
import { Providers } from './plugin';
|
||||||
|
import { VerificationCodeForm } from './verification_code_form';
|
||||||
|
|
||||||
|
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||||
|
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('VerificationCodeForm', () => {
|
||||||
|
jest.setTimeout(20_000);
|
||||||
|
|
||||||
|
it('calls enrollment API when submitting form', async () => {
|
||||||
|
const coreStart = coreMock.createStart();
|
||||||
|
coreStart.http.post.mockResolvedValue({});
|
||||||
|
|
||||||
|
const onSuccess = jest.fn();
|
||||||
|
|
||||||
|
const { findByRole, findByLabelText } = render(
|
||||||
|
<Providers http={coreStart.http}>
|
||||||
|
<VerificationCodeForm onSuccess={onSuccess} />
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
fireEvent.input(await findByLabelText('Digit 1'), {
|
||||||
|
target: { value: '1' },
|
||||||
|
});
|
||||||
|
fireEvent.input(await findByLabelText('Digit 2'), {
|
||||||
|
target: { value: '2' },
|
||||||
|
});
|
||||||
|
fireEvent.input(await findByLabelText('Digit 3'), {
|
||||||
|
target: { value: '3' },
|
||||||
|
});
|
||||||
|
fireEvent.input(await findByLabelText('Digit 4'), {
|
||||||
|
target: { value: '4' },
|
||||||
|
});
|
||||||
|
fireEvent.input(await findByLabelText('Digit 5'), {
|
||||||
|
target: { value: '5' },
|
||||||
|
});
|
||||||
|
fireEvent.input(await findByLabelText('Digit 6'), {
|
||||||
|
target: { value: '6' },
|
||||||
|
});
|
||||||
|
fireEvent.click(await findByRole('button', { name: 'Verify', hidden: true }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/interactive_setup/verify', {
|
||||||
|
body: JSON.stringify({ code: '123456' }),
|
||||||
|
});
|
||||||
|
expect(onSuccess).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates form', async () => {
|
||||||
|
const coreStart = coreMock.createStart();
|
||||||
|
const onSuccess = jest.fn();
|
||||||
|
|
||||||
|
const { findAllByText, findByRole, findByLabelText } = render(
|
||||||
|
<Providers http={coreStart.http}>
|
||||||
|
<VerificationCodeForm onSuccess={onSuccess} />
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(await findByRole('button', { name: 'Verify', hidden: true }));
|
||||||
|
|
||||||
|
await findAllByText(/Enter a verification code/i);
|
||||||
|
|
||||||
|
fireEvent.input(await findByLabelText('Digit 1'), {
|
||||||
|
target: { value: '1' },
|
||||||
|
});
|
||||||
|
|
||||||
|
await findAllByText(/Enter all six digits/i);
|
||||||
|
});
|
||||||
|
});
|
|
@ -9,6 +9,7 @@
|
||||||
import {
|
import {
|
||||||
EuiButton,
|
EuiButton,
|
||||||
EuiCallOut,
|
EuiCallOut,
|
||||||
|
EuiCode,
|
||||||
EuiEmptyPrompt,
|
EuiEmptyPrompt,
|
||||||
EuiForm,
|
EuiForm,
|
||||||
EuiFormRow,
|
EuiFormRow,
|
||||||
|
@ -69,8 +70,8 @@ export const VerificationCodeForm: FunctionComponent<VerificationCodeFormProps>
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response?.status === 403) {
|
if ((error as IHttpFetchError).response?.status === 403) {
|
||||||
form.setError('code', error.body?.message);
|
form.setError('code', (error as IHttpFetchError).body?.message);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -111,7 +112,10 @@ export const VerificationCodeForm: FunctionComponent<VerificationCodeFormProps>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="interactiveSetup.verificationCodeForm.codeDescription"
|
id="interactiveSetup.verificationCodeForm.codeDescription"
|
||||||
defaultMessage="Copy the verification code from Kibana server."
|
defaultMessage="Copy the code from the Kibana server or run {command} to retrieve it."
|
||||||
|
values={{
|
||||||
|
command: <EuiCode lang="bash">./bin/kibana-verification-code</EuiCode>,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
|
|
|
@ -17,7 +17,7 @@ import type { ConfigSchema, ConfigType } from './config';
|
||||||
import { ElasticsearchService } from './elasticsearch_service';
|
import { ElasticsearchService } from './elasticsearch_service';
|
||||||
import { KibanaConfigWriter } from './kibana_config_writer';
|
import { KibanaConfigWriter } from './kibana_config_writer';
|
||||||
import { defineRoutes } from './routes';
|
import { defineRoutes } from './routes';
|
||||||
import { VerificationCode } from './verification_code';
|
import { VerificationService } from './verification_service';
|
||||||
|
|
||||||
// List of the Elasticsearch hosts Kibana uses by default.
|
// List of the Elasticsearch hosts Kibana uses by default.
|
||||||
const DEFAULT_ELASTICSEARCH_HOSTS = [
|
const DEFAULT_ELASTICSEARCH_HOSTS = [
|
||||||
|
@ -29,7 +29,7 @@ const DEFAULT_ELASTICSEARCH_HOSTS = [
|
||||||
export class InteractiveSetupPlugin implements PrebootPlugin {
|
export class InteractiveSetupPlugin implements PrebootPlugin {
|
||||||
readonly #logger: Logger;
|
readonly #logger: Logger;
|
||||||
readonly #elasticsearch: ElasticsearchService;
|
readonly #elasticsearch: ElasticsearchService;
|
||||||
readonly #verificationCode: VerificationCode;
|
readonly #verification: VerificationService;
|
||||||
|
|
||||||
#elasticsearchConnectionStatusSubscription?: Subscription;
|
#elasticsearchConnectionStatusSubscription?: Subscription;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ export class InteractiveSetupPlugin implements PrebootPlugin {
|
||||||
this.#elasticsearch = new ElasticsearchService(
|
this.#elasticsearch = new ElasticsearchService(
|
||||||
this.initializerContext.logger.get('elasticsearch')
|
this.initializerContext.logger.get('elasticsearch')
|
||||||
);
|
);
|
||||||
this.#verificationCode = new VerificationCode(
|
this.#verification = new VerificationService(
|
||||||
this.initializerContext.logger.get('verification')
|
this.initializerContext.logger.get('verification')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,14 @@ export class InteractiveSetupPlugin implements PrebootPlugin {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const verificationCode = this.#verification.setup();
|
||||||
|
if (!verificationCode) {
|
||||||
|
this.#logger.error(
|
||||||
|
'Interactive setup mode could not be activated. Ensure Kibana has permission to write to its config folder.'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let completeSetup: (result: { shouldReloadConfig: boolean }) => void;
|
let completeSetup: (result: { shouldReloadConfig: boolean }) => void;
|
||||||
core.preboot.holdSetupUntilResolved(
|
core.preboot.holdSetupUntilResolved(
|
||||||
'Validating Elasticsearch connection configuration…',
|
'Validating Elasticsearch connection configuration…',
|
||||||
|
@ -93,6 +101,7 @@ export class InteractiveSetupPlugin implements PrebootPlugin {
|
||||||
elasticsearch: core.elasticsearch,
|
elasticsearch: core.elasticsearch,
|
||||||
connectionCheckInterval: this.#getConfig().connectionCheck.interval,
|
connectionCheckInterval: this.#getConfig().connectionCheck.interval,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#elasticsearchConnectionStatusSubscription = elasticsearch.connectionStatus$.subscribe(
|
this.#elasticsearchConnectionStatusSubscription = elasticsearch.connectionStatus$.subscribe(
|
||||||
(status) => {
|
(status) => {
|
||||||
if (status === ElasticsearchConnectionStatus.Configured) {
|
if (status === ElasticsearchConnectionStatus.Configured) {
|
||||||
|
@ -104,10 +113,9 @@ export class InteractiveSetupPlugin implements PrebootPlugin {
|
||||||
this.#logger.debug(
|
this.#logger.debug(
|
||||||
'Starting interactive setup mode since Kibana cannot to connect to Elasticsearch at http://localhost:9200.'
|
'Starting interactive setup mode since Kibana cannot to connect to Elasticsearch at http://localhost:9200.'
|
||||||
);
|
);
|
||||||
const { code } = this.#verificationCode;
|
|
||||||
const pathname = core.http.basePath.prepend('/');
|
const pathname = core.http.basePath.prepend('/');
|
||||||
const { protocol, hostname, port } = core.http.getServerInfo();
|
const { protocol, hostname, port } = core.http.getServerInfo();
|
||||||
const url = `${protocol}://${hostname}:${port}${pathname}?code=${code}`;
|
const url = `${protocol}://${hostname}:${port}${pathname}?code=${verificationCode.code}`;
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`
|
console.log(`
|
||||||
|
@ -135,7 +143,7 @@ Go to ${chalk.cyanBright.underline(url)} to get started.
|
||||||
preboot: { ...core.preboot, completeSetup },
|
preboot: { ...core.preboot, completeSetup },
|
||||||
kibanaConfigWriter: new KibanaConfigWriter(configPath, this.#logger.get('kibana-config')),
|
kibanaConfigWriter: new KibanaConfigWriter(configPath, this.#logger.get('kibana-config')),
|
||||||
elasticsearch,
|
elasticsearch,
|
||||||
verificationCode: this.#verificationCode,
|
verificationCode,
|
||||||
getConfig: this.#getConfig.bind(this),
|
getConfig: this.#getConfig.bind(this),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -155,5 +163,6 @@ Go to ${chalk.cyanBright.underline(url)} to get started.
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#elasticsearch.stop();
|
this.#elasticsearch.stop();
|
||||||
|
this.#verification.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
import { loggingSystemMock } from 'src/core/server/mocks';
|
||||||
|
|
||||||
|
import { VerificationCode } from './verification_code';
|
||||||
|
import { VerificationService } from './verification_service';
|
||||||
|
|
||||||
|
jest.mock('fs');
|
||||||
|
jest.mock('@kbn/utils', () => ({
|
||||||
|
getDataPath: jest.fn().mockReturnValue('/data/'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const loggerMock = loggingSystemMock.createLogger();
|
||||||
|
|
||||||
|
describe('VerificationService', () => {
|
||||||
|
describe('setup()', () => {
|
||||||
|
it('should generate verification code', () => {
|
||||||
|
const service = new VerificationService(loggerMock);
|
||||||
|
const setup = service.setup();
|
||||||
|
expect(setup).toBeInstanceOf(VerificationCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should write verification code to disk', () => {
|
||||||
|
const service = new VerificationService(loggerMock);
|
||||||
|
const setup = service.setup()!;
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalledWith('/data/verification_code', setup.code);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not return verification code if cannot write to disk', () => {
|
||||||
|
const service = new VerificationService(loggerMock);
|
||||||
|
(fs.writeFileSync as jest.Mock).mockImplementationOnce(() => {
|
||||||
|
throw new Error('Write error');
|
||||||
|
});
|
||||||
|
const setup = service.setup();
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalledWith('/data/verification_code', expect.anything());
|
||||||
|
expect(setup).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('stop()', () => {
|
||||||
|
it('should remove verification code from disk', () => {
|
||||||
|
const service = new VerificationService(loggerMock);
|
||||||
|
service.stop();
|
||||||
|
expect(fs.unlinkSync).toHaveBeenCalledWith('/data/verification_code');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
49
src/plugins/interactive_setup/server/verification_service.ts
Normal file
49
src/plugins/interactive_setup/server/verification_service.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
import { getDataPath } from '@kbn/utils';
|
||||||
|
import type { Logger } from 'src/core/server';
|
||||||
|
|
||||||
|
import { getDetailedErrorMessage } from './errors';
|
||||||
|
import { VerificationCode } from './verification_code';
|
||||||
|
|
||||||
|
export class VerificationService {
|
||||||
|
private fileName: string;
|
||||||
|
|
||||||
|
constructor(private readonly logger: Logger) {
|
||||||
|
this.fileName = path.join(getDataPath(), 'verification_code');
|
||||||
|
}
|
||||||
|
|
||||||
|
public setup() {
|
||||||
|
const verificationCode = new VerificationCode(this.logger);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(this.fileName, verificationCode.code);
|
||||||
|
this.logger.debug(`Successfully wrote verification code to ${this.fileName}`);
|
||||||
|
return verificationCode;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to write verification code to ${this.fileName}: ${getDetailedErrorMessage(error)}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(this.fileName);
|
||||||
|
this.logger.debug(`Successfully removed ${this.fileName}`);
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
||||||
|
this.logger.error(`Failed to remove ${this.fileName}: ${getDetailedErrorMessage(error)}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue