diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 2b368d6..d90a30a 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -2,15 +2,24 @@ # Release Authenticator +## Version 4.8.1 + +### bugfix + +- Fixed Proxy Validation +- Fixed Logging issue + ## Version 4.8.0 ### added + - Deactivation of OS proxy settings now requires mandatory Proxy Address and Port fields entries - IP Validation added for Proxy Ignore List in Settings Page - Sensitive data is now stored using the Credential Manager - New Help page with informative links for better understanding ### bugfix + - SMC-B flow crash after successful HBA flow issue fixed - Improved URL parsing mechanism for accurate identification of host and path in IDP service - Config.json storage path adjusted for specified ENV parameters diff --git a/package-lock.json b/package-lock.json index b6a2f54..dc58148 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "authenticator", - "version": "4.8.0", + "version": "4.8.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "authenticator", - "version": "4.8.0", + "version": "4.8.1", "hasInstallScript": true, "license": "European Union Public License 1.2", "dependencies": { diff --git a/package.json b/package.json index f911a58..77ae1c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "authenticator", - "version": "4.8.0", + "version": "4.8.1", "private": true, "author": "Gematik GmbH", "description": "Authenticator Desktop Client", diff --git a/src/main/event-listeners.ts b/src/main/event-listeners.ts index 9920671..277fe77 100644 --- a/src/main/event-listeners.ts +++ b/src/main/event-listeners.ts @@ -44,9 +44,13 @@ ipcMain.on(IPC_SELECT_FOLDER, (event) => { }); // Logger events -ipcMain.on(IPC_ERROR_LOG_EVENT_TYPES.ERROR, (_event, args) => logger.error(args)); +// @ts-ignore +ipcMain.on(IPC_ERROR_LOG_EVENT_TYPES.ERROR, (_event, args) => logger.error(...args)); +// @ts-ignore ipcMain.on(IPC_ERROR_LOG_EVENT_TYPES.DEBUG, (_event, args) => logger.debug(args)); +// @ts-ignore ipcMain.on(IPC_ERROR_LOG_EVENT_TYPES.INFO, (_event, args) => logger.info(args)); +// @ts-ignore ipcMain.on(IPC_ERROR_LOG_EVENT_TYPES.WARN, (_event, args) => logger.warn(args)); /** diff --git a/src/main/services/logging.ts b/src/main/services/logging.ts index acd3a06..fdcf226 100644 --- a/src/main/services/logging.ts +++ b/src/main/services/logging.ts @@ -20,8 +20,9 @@ import 'winston-daily-rotate-file'; import { IS_DEV } from '@/constants'; import { zip } from 'zip-a-folder'; import { validateMockVersion } from '@/renderer/utils/validate-mock-version'; +import { TransformableInfo } from 'logform'; -const { combine, printf, simple, timestamp, splat } = winston.format; +const { combine, printf, simple } = winston.format; const logLevel = IS_DEV || validateMockVersion() ? 'debug' : 'info'; @@ -48,20 +49,22 @@ const { createLogger } = winston; * Custom formatter */ const myFormat = printf((info) => { - // electron bridge breaks the data structure, we try to log it here properly - let message = info.message; - if (Array.isArray(info.message) && info.message.length > 1) { - message = info.message[0] + '\n Data: '; - message += JSON.stringify(info.message.splice(0, 1)); + const date = new Date(); + const timestamp = date.toLocaleDateString('de') + ' ' + date.toLocaleTimeString('de'); + + // if the message is not an array, it is probably main process logging and should be logged as usual + if (!Array.isArray(info.message)) { + return `${timestamp} [${info.level}]: ${info.message}${renderExtraData(info)}`; } - return `${info.timestamp} [${info.level}]: ${message}${renderExtraData(info)}`; + // electron bridge breaks the data structure, we try to log it here properly + return `${timestamp} [${info.level}]: ${info.message[0]}${renderExtraData(info, info.message.slice(1))}`; }); export const logger = createLogger({ level: logLevel, exitOnError: false, - format: combine(splat(), timestamp(), simple(), myFormat), + format: myFormat, transports: [transport], }); @@ -77,31 +80,36 @@ if (IS_DEV) { /** * Render extra data with error details * @param info + * @param restMessages */ -const renderExtraData = (info: any) => { +const renderExtraData = (info: TransformableInfo, restMessages: any[] = []) => { const cleanInfo = { ...info }; delete cleanInfo.message; + // @ts-ignore delete cleanInfo.level; delete cleanInfo.timestamp; - // return if there is no data - if (!Object.keys(cleanInfo).length) { - return ''; - } - // extract error and print pretty - let error = ''; + let logText = ''; if (cleanInfo.stack) { - error = `\n Stack: ${cleanInfo.stack}`; + logText = `\n Stack: ${cleanInfo.stack}`; delete cleanInfo.stack; } - // return if there is no left data - if (!Object.keys(cleanInfo).length) { - return error; - } + const symbolForOriginalData = Symbol.for('splat'); + const splatData = restMessages.concat(info[symbolForOriginalData]); + + splatData?.forEach((data: any) => { + if (typeof data === 'object' || Array.isArray(data)) { + logText += `\n Data: ${JSON.stringify(data, null, 2)}`; + return; + } else if (typeof data === 'string' || typeof data === 'number') { + logText += `\n Data: ${data}`; + return; + } + }); - return `\n Data: ${JSON.stringify(cleanInfo)}${error}`; + return logText; }; /** diff --git a/src/renderer/modules/gem-idp/event-listeners/GemIdpAuthFlowProcess.vue b/src/renderer/modules/gem-idp/event-listeners/GemIdpAuthFlowProcess.vue index 15d59cc..c84a6c4 100644 --- a/src/renderer/modules/gem-idp/event-listeners/GemIdpAuthFlowProcess.vue +++ b/src/renderer/modules/gem-idp/event-listeners/GemIdpAuthFlowProcess.vue @@ -84,6 +84,7 @@ export default defineComponent({ }, data() { return { + stepCounter: 0, // eslint-disable-next-line @typescript-eslint/no-unused-vars rejectPendingCardActionTimeout: (_error: UserfacingError) => { /* do nothing here */ @@ -119,6 +120,7 @@ export default defineComponent({ }, methods: { async createQueue(event: Event, args: TOidcProtocol2UrlSpec) { + this.logStep('createQueue'); if (this.isAuthProcessActive) { this.authQueue.push({ event, args }); logger.info('Auth process is already active, adding to queue'); @@ -128,6 +130,7 @@ export default defineComponent({ } }, async showMultiCardSelectDialogModal(): Promise { + this.logStep('showMultiCardSelectDialogModal'); this.showMultiCardSelectModal = true; window.api.focusToApp(); @@ -145,6 +148,10 @@ export default defineComponent({ }, async finishAndStartNextFlow() { + this.logStep('finishAndStartNextFlow'); + + this.stepCounter = 0; + // first clear the stores this.$store.commit('connectorStore/resetStore'); this.$store.commit('gemIdpServiceStore/resetStore'); @@ -303,6 +310,8 @@ export default defineComponent({ await this.finishAndStartNextFlow(); }, async getCardData(cardType: ECardTypes): Promise { + this.logStep('getCardData'); + // Init Card and get CardHandle await this.getCardHandle(cardType); @@ -343,6 +352,7 @@ export default defineComponent({ * Throws only for connector connection errors */ async getCardTerminals(): Promise { + this.logStep('getCardTerminals'); try { await this.$store.dispatch('connectorStore/getCardTerminals'); logger.info('getTerminals finished'); @@ -359,6 +369,7 @@ export default defineComponent({ * @param e */ async handleErrors(e: ConnectorError | UserfacingError | Error): Promise { + this.logStep('handleErrors'); // focus to app to show the error window.api.focusToApp(); @@ -383,6 +394,7 @@ export default defineComponent({ } }, async signChallengeForCardType(cardType: ECardTypes): Promise { + this.logStep('signChallengeForCardType'); try { const certificate = this.$store.state.connectorStore?.cards[cardType]?.certificate; const challenge = this.$store.state.gemIdpServiceStore?.challenge; @@ -409,6 +421,7 @@ export default defineComponent({ } }, async getCardCertificate(cardType: ECardTypes): Promise { + this.logStep('getCardCertificate'); // get card certificate try { await this.$store.dispatch('connectorStore/getCardCertificate', cardType); @@ -426,6 +439,7 @@ export default defineComponent({ * @param cardType */ async getCardHandle(cardType: ECardTypes): Promise { + this.logStep('getCardHandle'); try { /** * Get Card Handle @@ -512,6 +526,7 @@ export default defineComponent({ }, onUserCancelledCardInsert() { + this.logStep('onUserCancelledCardInsert'); // user has clicked the cancel, we throw an error! This is reject function of cardInsertReTryTimeout Promise this.rejectPendingCardActionTimeout( new UserfacingError(this.$t(LOGIN_CANCELLED_BY_USER), '', ERROR_CODES.AUTHCL_0006), @@ -522,6 +537,7 @@ export default defineComponent({ * @param cardType */ async checkPinStatus(cardType: ECardTypes): Promise { + this.logStep('checkPinStatus'); try { const isPinStatusVerified = await this.$store.dispatch('connectorStore/checkPinStatus', cardType); @@ -568,6 +584,7 @@ export default defineComponent({ * @param cardType */ async verifyPin(cardType: string): Promise { + this.logStep('verifyPin'); try { // ask user for enter pin await this.$store.dispatch('connectorStore/verifyPin', cardType); @@ -588,6 +605,7 @@ export default defineComponent({ } }, async getChallengeDataFromIdp(): Promise { + this.logStep('getChallengeDataFromIdp'); try { return await this.$store.dispatch('gemIdpServiceStore/getChallengeData'); } catch (err) { @@ -616,6 +634,7 @@ export default defineComponent({ * @returns URL */ async getRedirectUriWithToken(error: UserfacingError | null = null): Promise { + this.logStep('getRedirectUriWithToken'); /** * send signed challenge to idp * this never throws error! @@ -657,6 +676,7 @@ export default defineComponent({ }, validateParamsAndSetState(args: TOidcProtocol2UrlSpec): boolean { + this.logStep('validateParamsAndSetState'); try { validateLauncherArguments(args); const authReqParameters: TOidcProtocol2UrlSpec = { @@ -687,6 +707,7 @@ export default defineComponent({ } }, setCaughtError(errorCode: string, errorDescription?: string, errorType?: OAUTH2_ERROR_TYPE) { + this.logStep('setCaughtError'); this.$store.commit('gemIdpServiceStore/setErrorShown'); if (!this.$store.state.gemIdpServiceStore.caughtAuthFlowError) { @@ -703,6 +724,7 @@ export default defineComponent({ * @param secs */ cardInsertReTryTimeout(secs: number) { + this.logStep('cardInsertReTryTimeout'); return new Promise((resolve, reject) => { this.rejectPendingCardActionTimeout = reject; setTimeout(() => resolve(), secs); @@ -715,6 +737,8 @@ export default defineComponent({ * @param authFlowEndState */ async openClientIfNeeded(authFlowEndState: TAuthFlowEndState): Promise { + this.logStep('openClientIfNeeded'); + let url: string | null = authFlowEndState.url; const caughtErrorObject = this.$store.state.gemIdpServiceStore.caughtAuthFlowError; @@ -782,6 +806,7 @@ export default defineComponent({ * and get the host name of it */ parseAndSetIdpHost() { + this.logStep('parseAndSetIdpHost'); const challengePath = this.$store.state.gemIdpServiceStore.challengePath; const parseAndSetIdpHost = removeLastPartOfChallengePath(challengePath); this.$store.commit('gemIdpServiceStore/setIdpHost', parseAndSetIdpHost); @@ -792,6 +817,7 @@ export default defineComponent({ * @param paramName */ popParamFromChallengePath(paramName: string, challengePath?: string): string | null { + this.logStep('popParamFromChallengePath'); if (challengePath && challengePath.includes(paramName)) { const parsedPath = parse(challengePath); const value = parsedPath[paramName]; @@ -807,6 +833,7 @@ export default defineComponent({ return ''; }, setCallback(callbackValue: string | null): boolean { + this.logStep('setCallback'); this.$store.commit('gemIdpServiceStore/setCallback', TCallback.OPEN_TAB); if (callbackValue) { @@ -828,6 +855,7 @@ export default defineComponent({ * @param url */ async sendAutomaticRedirectRequest(url: string) { + this.logStep('sendAutomaticRedirectRequest'); const successMessage = 'Redirecting automatically flow completed'; try { // send the user agent with the clientId to the main process @@ -858,6 +886,11 @@ export default defineComponent({ } } }, + logStep(log: string) { + logger.info('\n'); + logger.info('### Step ' + this.stepCounter + ': ' + log + ' ###'); + this.stepCounter++; + }, }, }); diff --git a/src/renderer/modules/settings/screens/formBuilder.ts b/src/renderer/modules/settings/screens/formBuilder.ts index bf32470..0fe1767 100644 --- a/src/renderer/modules/settings/screens/formBuilder.ts +++ b/src/renderer/modules/settings/screens/formBuilder.ts @@ -12,6 +12,13 @@ * permissions and limitations under the Licence. */ +/* @if MOCK_MODE == 'ENABLED' */ +import { + MOCK_CONNECTOR_CERTS_CONFIG, + MOCK_CONNECTOR_CONFIG, +} from '@/renderer/modules/connector/connector-mock/mock-config'; + +/* @endif */ import { IConfig, IConfigSection, TlsAuthType } from '@/@types/common-types'; import { CHECK_UPDATES_AUTOMATICALLY_CONFIG, @@ -30,16 +37,9 @@ import { copyUploadedFileToTargetDir } from '@/renderer/utils/read-tls-certifica import Swal from 'sweetalert2'; import i18n from '@/renderer/i18n'; import ConnectorIcon from '@/assets/icon-connector.svg'; - -/* @if MOCK_MODE == 'ENABLED' */ -import { - MOCK_CONNECTOR_CERTS_CONFIG, - MOCK_CONNECTOR_CONFIG, -} from '@/renderer/modules/connector/connector-mock/mock-config'; -import { getMatch } from 'ip-matching'; import { logger } from '@/renderer/service/logger'; -/* @endif */ +import { getMatch } from 'ip-matching'; const translate = i18n.global.t; diff --git a/tests/unit/__snapshots__/base-template.spec.ts.snap b/tests/unit/__snapshots__/base-template.spec.ts.snap index b5fe9de..1f80d9c 100644 --- a/tests/unit/__snapshots__/base-template.spec.ts.snap +++ b/tests/unit/__snapshots__/base-template.spec.ts.snap @@ -62,7 +62,7 @@ exports[`base template screen render with settings button 1`] = `
Version dev
diff --git a/tests/unit/logger.spec.ts b/tests/unit/logger.spec.ts index 4745400..37e233e 100644 --- a/tests/unit/logger.spec.ts +++ b/tests/unit/logger.spec.ts @@ -23,13 +23,16 @@ describe('test winston logger', () => { const loggedText = 'Tests log has been successfully written!'; beforeAll(async () => { + // delete log file if exists + if (fs.existsSync(getLogFilePath())) { + fs.unlinkSync(getLogFilePath()); + } + // write the log - await logger.info(loggedText); + logger.info(loggedText); // wait for log file changes - await new Promise((resolve) => { - setTimeout(resolve, 1000); - }); + await sleep(); }); it('creates log file properly', function () { @@ -46,21 +49,42 @@ describe('test winston logger', () => { try { throw new Error('some error text'); } catch (e) { - await logger.error('Error: ', e, { more: 'even more data' }); + logger.error('Error: ', e, { more: 'even more data' }); const logFileContent = fs.readFileSync(getLogFilePath(), 'utf-8'); - logger.info('getLogFilePath:' + fs.readFileSync(getLogFilePath())); - logger.info('logFileContent:' + logFileContent); expect(logFileContent.includes('Stack: Error: some error text')).toBeTruthy(); } }); it('logs data properly', async function () { - await logger.error('Some important data: ', { code: 'nice code' }); + logger.error('Some important data: ', { code: 'nice code' }); + + // wait a second for the log file to be written + await sleep(); const logFileContent = fs.readFileSync(getLogFilePath(), 'utf-8'); - setTimeout(() => { - expect(logFileContent.includes('Data: {"code":"nice code"}')).toBeTruthy(); - }); + + expect(logFileContent.includes('Data: {\n' + ' "code": "nice code"\n' + '}')).toBeTruthy(); + }); + + it('logs array properly', async function () { + logger.error('Some important data: ', ['test', 'test2', 'test3']); + + // wait a second for the log file to be written + await sleep(); + const logFileContent = fs.readFileSync(getLogFilePath(), 'utf-8'); + + expect(logFileContent.includes(' Data: [\n' + ' "test",\n' + ' "test2",\n' + ' "test3"\n' + ']')).toBeTruthy(); + }); + + it('logs second string properly', async function () { + const string = 'string'; + logger.error('Some important data: ', string); + + // wait a second for the log file to be written + await sleep(); + const logFileContent = fs.readFileSync(getLogFilePath(), 'utf-8'); + + expect(logFileContent.includes('Data: ' + string)).toBeTruthy(); }); }); @@ -77,3 +101,5 @@ function getLogFilePath(): string { function zeroFill(i: number): string { return (i < 10 ? '0' : '') + i; } + +const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000));