Skip to content

Commit

Permalink
Security patch (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
ccxzhang authored Dec 26, 2024
2 parents b7dc81c + 053027f commit 6d17f5c
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 12 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ jobs:
- name: Build project
run: npm run build:linux
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}

- name: List dist directory contents
run: ls -R dist
Expand All @@ -48,6 +50,7 @@ jobs:
- name: Run Playwright tests
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" npm run test:playwright
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}
ELECTRON_NO_ATTACH_CONSOLE: true
ELECTRON_DISABLE_SANDBOX: true
DEBUG: pw:api
Expand Down Expand Up @@ -84,10 +87,13 @@ jobs:

- name: Build project
run: npm run build:mac
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}

- name: Run Playwright tests
run: npm run test:playwright
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}
ELECTRON_NO_ATTACH_CONSOLE: true
ELECTRON_DISABLE_SANDBOX: true
DEBUG: pw:api
Expand All @@ -111,10 +117,13 @@ jobs:

- name: Build project
run: npm run build:win
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}

- name: Run Playwright tests
run: npm run test:playwright
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}
ELECTRON_NO_ATTACH_CONSOLE: true
ELECTRON_DISABLE_SANDBOX: true
DEBUG: pw:api
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Release

on:
push:
tags:
Expand Down Expand Up @@ -27,6 +28,8 @@ jobs:

- name: Build application
run: npm run build:${{ matrix.os == 'macos-latest' && 'mac' || 'win' }}
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}

- name: Upload artifact
uses: actions/upload-artifact@v3
Expand Down
21 changes: 14 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@electron-toolkit/utils": "^3.0.0",
"@playwright/test": "^1.45.1",
"@reduxjs/toolkit": "^2.2.6",
"crypto-js": "^4.2.0",
"d3": "^7.9.0",
"dexie": "^4.0.7",
"dexie-react-hooks": "^1.1.7",
Expand Down
7 changes: 7 additions & 0 deletions src/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import fs from 'fs'

const dotenv = require('dotenv')
dotenv.config()

const iconPath = join(__dirname, '../../resources/icon')

app.commandLine.appendSwitch('ignore-gpu-blacklist', 'true')
Expand Down Expand Up @@ -84,6 +87,10 @@ ipcMain.handle('dialog:saveFile', async () => {
}
})

ipcMain.handle('get-secret-key', async () => {
return process.env.SECRET_KEY
})

ipcMain.handle('trigger-gc', () => {
if (global.gc) {
global.gc()
Expand Down
6 changes: 6 additions & 0 deletions src/preload/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@ const fileSystemAPI = {
}
}

const secureAPI = {
getSecretKey: () => ipcRenderer.invoke('get-secret-key')
}

if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
contextBridge.exposeInMainWorld('secureApi', secureAPI)
contextBridge.exposeInMainWorld('fileSystem', fileSystemAPI)
contextBridge.exposeInMainWorld('electronAPI', {
selectSaveLocation: () => ipcRenderer.invoke('dialog:saveFile')
Expand All @@ -37,6 +42,7 @@ if (process.contextIsolated) {
} else {
window.electron = electronAPI
window.api = api
window.secureApi = secureAPI
window.fileSystem = fileSystemAPI
window.electronAPI = {
selectSaveLocation: () => ipcRenderer.invoke('dialog:saveFile')
Expand Down
10 changes: 9 additions & 1 deletion src/renderer/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'
import { HashRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import { useSelector, useDispatch } from 'react-redux'
import { initializeAuth } from './reducers/authReducer'
import { initializeSecretKey } from './utils/sessionManager'
import MainPage from './pages/MainPage'
import Login from './pages/Login'
import Footer from './pages/Footer'
Expand All @@ -21,7 +22,14 @@ const App = () => {
const { dhis2Url, username, password, accessToken } = useSelector((state) => state.auth)

useEffect(() => {
dispatch(initializeAuth())
;(async () => {
try {
await initializeSecretKey()
dispatch(initializeAuth())
} catch (error) {
console.error('Failed to initialize application:', error)
}
})()
}, [dispatch])

const handleDisconnect = async () => {
Expand Down
16 changes: 12 additions & 4 deletions src/renderer/src/reducers/authReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
} from '../service/useApi'
import { dictionaryDb, servicesDb, queryDb } from '../service/db'
import { triggerLoading, triggerNotification } from '../reducers/statusReducer'
import { getSecretKey } from '../utils/sessionManager'
import { encryptPassword, decryptPassword } from '../utils/cryptoUtils'

const initialState = {
dhis2Url: '',
Expand Down Expand Up @@ -50,13 +52,17 @@ export const initializeAuth = () => async (dispatch) => {
const storedAccessToken = localStorage.getItem('accessToken')
const storedDhis2Url = localStorage.getItem('dhis2Url')
const storedUsername = localStorage.getItem('username')
const storedPassword = localStorage.getItem('password')
const encryptedPassword = localStorage.getItem('encryptedPassword')
let password
if (encryptedPassword) {
password = decryptPassword(encryptedPassword, getSecretKey())
}

if (storedAccessToken && storedDhis2Url && storedUsername && storedPassword) {
if (storedAccessToken && storedDhis2Url && storedUsername && password) {
dispatch(setAccessToken(storedAccessToken))
dispatch(setDhis2Url(storedDhis2Url))
dispatch(setUsername(storedUsername))
dispatch(setPassword(storedPassword))
dispatch(setPassword(password))
} else if (storedDhis2Url && storedUsername) {
dispatch(setDhis2Url(storedDhis2Url))
dispatch(setUsername(storedUsername))
Expand All @@ -77,8 +83,10 @@ export const connect = (dhis2Url, username, password) => async (dispatch) => {
dispatch(setAccessToken(token))
localStorage.setItem('accessToken', token)
localStorage.setItem('username', username)
localStorage.setItem('password', password)
// localStorage.setItem('password', password)
localStorage.setItem('dhis2Url', dhis2Url)
const encryptedPassword = encryptPassword(password, getSecretKey())
localStorage.setItem('encryptedPassword', encryptedPassword)

const [
elements,
Expand Down
20 changes: 20 additions & 0 deletions src/renderer/src/utils/cryptoUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import CryptoJS from 'crypto-js'

export const encryptPassword = (password, secretKey) => {
return CryptoJS.AES.encrypt(password, secretKey).toString()
}

export const decryptPassword = (encryptedPassword, secretKey) => {
const bytes = CryptoJS.AES.decrypt(encryptedPassword, secretKey)
return bytes.toString(CryptoJS.enc.Utf8)
}

export const isValidPassword = (encryptedPassword, secretKey, plainPassword) => {
try {
const decryptedPassword = decryptPassword(encryptedPassword, secretKey)
return decryptedPassword === plainPassword
} catch (error) {
console.error('Decryption failed:', error)
return false
}
}
18 changes: 18 additions & 0 deletions src/renderer/src/utils/sessionManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
let secretKey = null

export const initializeSecretKey = async () => {
try {
secretKey = await window.secureApi.getSecretKey()
console.log('Secret key initialized')
} catch (error) {
console.error('Failed to initialize secret key:', error)
throw error
}
}

export const getSecretKey = () => {
if (!secretKey) {
throw new Error('Secret key has not been initialized')
}
return secretKey
}

0 comments on commit 6d17f5c

Please sign in to comment.