diff options
author | Stefan Malzner <stefan@adlk.io> | 2019-01-09 12:57:11 +0100 |
---|---|---|
committer | Stefan Malzner <stefan@adlk.io> | 2019-01-09 12:57:11 +0100 |
commit | eee1fe620473b50ac74ce41a030dee5693227be8 (patch) | |
tree | f9cb24aa00a490b0f0a6e13db6957522fba150b1 /src | |
parent | Ignore ERR_NETWORK_CHANGED error (diff) | |
parent | Add TODO (diff) | |
download | ferdium-app-eee1fe620473b50ac74ce41a030dee5693227be8.tar.gz ferdium-app-eee1fe620473b50ac74ce41a030dee5693227be8.tar.zst ferdium-app-eee1fe620473b50ac74ce41a030dee5693227be8.zip |
Merge branch 'feature/basicAuth' into develop
Diffstat (limited to 'src')
-rw-r--r-- | src/app.js | 1 | ||||
-rw-r--r-- | src/components/layout/AppLayout.js | 2 | ||||
-rw-r--r-- | src/components/ui/Modal/index.js | 59 | ||||
-rw-r--r-- | src/components/ui/Modal/styles.js | 32 | ||||
-rw-r--r-- | src/electron/ipc-api/settings.js | 1 | ||||
-rw-r--r-- | src/features/basicAuth/Component.js | 102 | ||||
-rw-r--r-- | src/features/basicAuth/Form.js | 17 | ||||
-rw-r--r-- | src/features/basicAuth/index.js | 68 | ||||
-rw-r--r-- | src/features/basicAuth/mainIpcHandler.js | 9 | ||||
-rw-r--r-- | src/features/basicAuth/styles.js | 12 | ||||
-rw-r--r-- | src/index.js | 40 | ||||
-rw-r--r-- | src/stores/FeaturesStore.js | 2 | ||||
-rw-r--r-- | src/theme/dark/index.js | 3 | ||||
-rw-r--r-- | src/theme/default/index.js | 3 |
14 files changed, 344 insertions, 7 deletions
diff --git a/src/app.js b/src/app.js index 831dd93ce..6660feb46 100644 --- a/src/app.js +++ b/src/app.js | |||
@@ -62,6 +62,7 @@ window.addEventListener('load', () => { | |||
62 | menu, | 62 | menu, |
63 | touchBar, | 63 | touchBar, |
64 | analytics, | 64 | analytics, |
65 | features: {}, | ||
65 | render() { | 66 | render() { |
66 | const preparedApp = ( | 67 | const preparedApp = ( |
67 | <Provider stores={stores} actions={actions}> | 68 | <Provider stores={stores} actions={actions}> |
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index dbe0bb4b6..28eaa7fdc 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js | |||
@@ -6,6 +6,7 @@ import { TitleBar } from 'electron-react-titlebar'; | |||
6 | 6 | ||
7 | import InfoBar from '../ui/InfoBar'; | 7 | import InfoBar from '../ui/InfoBar'; |
8 | import { Component as DelayApp } from '../../features/delayApp'; | 8 | import { Component as DelayApp } from '../../features/delayApp'; |
9 | import { Component as BasicAuth } from '../../features/basicAuth'; | ||
9 | import ErrorBoundary from '../util/ErrorBoundary'; | 10 | import ErrorBoundary from '../util/ErrorBoundary'; |
10 | 11 | ||
11 | import globalMessages from '../../i18n/globalMessages'; | 12 | import globalMessages from '../../i18n/globalMessages'; |
@@ -161,6 +162,7 @@ export default @observer class AppLayout extends Component { | |||
161 | </InfoBar> | 162 | </InfoBar> |
162 | )} | 163 | )} |
163 | {isDelayAppScreenVisible && (<DelayApp />)} | 164 | {isDelayAppScreenVisible && (<DelayApp />)} |
165 | <BasicAuth /> | ||
164 | {services} | 166 | {services} |
165 | </div> | 167 | </div> |
166 | </div> | 168 | </div> |
diff --git a/src/components/ui/Modal/index.js b/src/components/ui/Modal/index.js new file mode 100644 index 000000000..d84e4c713 --- /dev/null +++ b/src/components/ui/Modal/index.js | |||
@@ -0,0 +1,59 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import ReactModal from 'react-modal'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | import classnames from 'classnames'; | ||
5 | import injectCSS from 'react-jss'; | ||
6 | |||
7 | import styles from './styles'; | ||
8 | |||
9 | export default @injectCSS(styles) class Modal extends Component { | ||
10 | static propTypes = { | ||
11 | children: PropTypes.node.isRequired, | ||
12 | className: PropTypes.string, | ||
13 | classes: PropTypes.object.isRequired, | ||
14 | isOpen: PropTypes.bool.isRequired, | ||
15 | portal: PropTypes.string, | ||
16 | close: PropTypes.func.isRequired, | ||
17 | } | ||
18 | |||
19 | static defaultProps = { | ||
20 | className: null, | ||
21 | portal: 'modal-portal', | ||
22 | } | ||
23 | |||
24 | render() { | ||
25 | const { | ||
26 | children, | ||
27 | className, | ||
28 | classes, | ||
29 | isOpen, | ||
30 | portal, | ||
31 | close, | ||
32 | } = this.props; | ||
33 | |||
34 | return ( | ||
35 | <ReactModal | ||
36 | isOpen={isOpen} | ||
37 | className={classnames({ | ||
38 | [`${classes.modal}`]: true, | ||
39 | [`${className}`]: className, | ||
40 | })} | ||
41 | portalClassName={classes.component} | ||
42 | overlayClassName={classes.overlay} | ||
43 | portal={portal} | ||
44 | onRequestClose={close} | ||
45 | > | ||
46 | {/* <button | ||
47 | type="button" | ||
48 | className={classnames({ | ||
49 | [`${classes.close}`]: true, | ||
50 | 'mdi mdi-close': true, | ||
51 | })} | ||
52 | /> */} | ||
53 | <div className={classes.content}> | ||
54 | {children} | ||
55 | </div> | ||
56 | </ReactModal> | ||
57 | ); | ||
58 | } | ||
59 | } | ||
diff --git a/src/components/ui/Modal/styles.js b/src/components/ui/Modal/styles.js new file mode 100644 index 000000000..56fecbf55 --- /dev/null +++ b/src/components/ui/Modal/styles.js | |||
@@ -0,0 +1,32 @@ | |||
1 | export default theme => ({ | ||
2 | component: { | ||
3 | zIndex: 500, | ||
4 | position: 'absolute', | ||
5 | }, | ||
6 | overlay: { | ||
7 | background: theme.colorModalOverlayBackground, | ||
8 | position: 'fixed', | ||
9 | top: 0, | ||
10 | left: 0, | ||
11 | right: 0, | ||
12 | bottom: 0, | ||
13 | display: 'flex', | ||
14 | }, | ||
15 | modal: { | ||
16 | background: '#FFF', | ||
17 | maxWidth: '90%', | ||
18 | height: 'auto', | ||
19 | margin: 'auto auto', | ||
20 | borderRadius: 6, | ||
21 | boxShadow: '0px 13px 40px 0px rgba(0,0,0,0.2)', | ||
22 | position: 'relative', | ||
23 | }, | ||
24 | content: { | ||
25 | padding: 20, | ||
26 | }, | ||
27 | close: { | ||
28 | position: 'absolute', | ||
29 | top: 0, | ||
30 | right: 0, | ||
31 | }, | ||
32 | }); | ||
diff --git a/src/electron/ipc-api/settings.js b/src/electron/ipc-api/settings.js index ce006bb92..b651db306 100644 --- a/src/electron/ipc-api/settings.js +++ b/src/electron/ipc-api/settings.js | |||
@@ -2,7 +2,6 @@ import { ipcMain } from 'electron'; | |||
2 | 2 | ||
3 | export default (params) => { | 3 | export default (params) => { |
4 | ipcMain.on('getAppSettings', (event, type) => { | 4 | ipcMain.on('getAppSettings', (event, type) => { |
5 | console.log('getAppSettings', type, params.settings[type].all); | ||
6 | params.mainWindow.webContents.send('appSettings', { | 5 | params.mainWindow.webContents.send('appSettings', { |
7 | type, | 6 | type, |
8 | data: params.settings[type].all, | 7 | data: params.settings[type].all, |
diff --git a/src/features/basicAuth/Component.js b/src/features/basicAuth/Component.js new file mode 100644 index 000000000..13395fb40 --- /dev/null +++ b/src/features/basicAuth/Component.js | |||
@@ -0,0 +1,102 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import injectSheet from 'react-jss'; | ||
4 | import { observer } from 'mobx-react'; | ||
5 | import classnames from 'classnames'; | ||
6 | |||
7 | import Modal from '../../components/ui/Modal'; | ||
8 | import Input from '../../components/ui/Input'; | ||
9 | import Button from '../../components/ui/Button'; | ||
10 | |||
11 | import { | ||
12 | state, | ||
13 | resetState, | ||
14 | sendCredentials, | ||
15 | cancelLogin, | ||
16 | } from '.'; | ||
17 | import Form from './Form'; | ||
18 | |||
19 | import styles from './styles'; | ||
20 | |||
21 | export default @injectSheet(styles) @observer class BasicAuthModal extends Component { | ||
22 | static propTypes = { | ||
23 | classes: PropTypes.object.isRequired, | ||
24 | } | ||
25 | |||
26 | submit(e) { | ||
27 | e.preventDefault(); | ||
28 | |||
29 | const values = Form.values(); | ||
30 | console.log('form submit', values); | ||
31 | |||
32 | sendCredentials(values.user, values.password); | ||
33 | resetState(); | ||
34 | } | ||
35 | |||
36 | cancel() { | ||
37 | cancelLogin(); | ||
38 | this.close(); | ||
39 | } | ||
40 | |||
41 | close() { | ||
42 | resetState(); | ||
43 | state.isModalVisible = false; | ||
44 | } | ||
45 | |||
46 | render() { | ||
47 | const { | ||
48 | classes, | ||
49 | } = this.props; | ||
50 | |||
51 | const { | ||
52 | isModalVisible, | ||
53 | authInfo, | ||
54 | } = state; | ||
55 | |||
56 | if (!authInfo) { | ||
57 | return null; | ||
58 | } | ||
59 | |||
60 | return ( | ||
61 | <Modal | ||
62 | isOpen={isModalVisible} | ||
63 | className={classes.modal} | ||
64 | close={this.cancel.bind(this)} | ||
65 | > | ||
66 | <h1>Sign in</h1> | ||
67 | <p> | ||
68 | http | ||
69 | {authInfo.port === 443 && 's'} | ||
70 | :// | ||
71 | {authInfo.host} | ||
72 | </p> | ||
73 | <form | ||
74 | onSubmit={this.submit.bind(this)} | ||
75 | className={classnames('franz-form', classes.form)} | ||
76 | > | ||
77 | <Input | ||
78 | field={Form.$('user')} | ||
79 | showLabel={false} | ||
80 | /> | ||
81 | <Input | ||
82 | field={Form.$('password')} | ||
83 | showLabel={false} | ||
84 | showPasswordToggle | ||
85 | /> | ||
86 | <div className={classes.buttons}> | ||
87 | <Button | ||
88 | type="button" | ||
89 | label="Cancel" | ||
90 | buttonType="secondary" | ||
91 | onClick={this.cancel.bind(this)} | ||
92 | /> | ||
93 | <Button | ||
94 | type="submit" | ||
95 | label="Sign In" | ||
96 | /> | ||
97 | </div> | ||
98 | </form> | ||
99 | </Modal> | ||
100 | ); | ||
101 | } | ||
102 | } | ||
diff --git a/src/features/basicAuth/Form.js b/src/features/basicAuth/Form.js new file mode 100644 index 000000000..95721d0e9 --- /dev/null +++ b/src/features/basicAuth/Form.js | |||
@@ -0,0 +1,17 @@ | |||
1 | import Form from '../../lib/Form'; | ||
2 | |||
3 | export default new Form({ | ||
4 | fields: { | ||
5 | user: { | ||
6 | label: 'user', | ||
7 | placeholder: 'Username', | ||
8 | value: '', | ||
9 | }, | ||
10 | password: { | ||
11 | label: 'Password', | ||
12 | placeholder: 'Password', | ||
13 | value: '', | ||
14 | type: 'password', | ||
15 | }, | ||
16 | }, | ||
17 | }); | ||
diff --git a/src/features/basicAuth/index.js b/src/features/basicAuth/index.js new file mode 100644 index 000000000..03269582c --- /dev/null +++ b/src/features/basicAuth/index.js | |||
@@ -0,0 +1,68 @@ | |||
1 | import { ipcRenderer } from 'electron'; | ||
2 | import { observable } from 'mobx'; | ||
3 | |||
4 | import BasicAuthComponent from './Component'; | ||
5 | |||
6 | const debug = require('debug')('Franz:feature:basicAuth'); | ||
7 | |||
8 | const defaultState = { | ||
9 | isModalVisible: false, | ||
10 | service: null, | ||
11 | authInfo: null, | ||
12 | }; | ||
13 | |||
14 | export const state = observable(defaultState); | ||
15 | |||
16 | export function resetState() { | ||
17 | Object.assign(state, defaultState); | ||
18 | console.log('reset state', state); | ||
19 | } | ||
20 | |||
21 | export default function initialize() { | ||
22 | debug('Initialize basicAuth feature'); | ||
23 | |||
24 | window.franz.features.basicAuth = { | ||
25 | state, | ||
26 | }; | ||
27 | |||
28 | ipcRenderer.on('feature:basic-auth-request', (e, data) => { | ||
29 | debug(e, data); | ||
30 | // state.serviceId = data.serviceId; | ||
31 | state.authInfo = data.authInfo; | ||
32 | state.isModalVisible = true; | ||
33 | }); | ||
34 | |||
35 | // autorun(() => { | ||
36 | // // if (state.serviceId) { | ||
37 | // // const service = stores.services.one(state.serviceId); | ||
38 | // // if (service) { | ||
39 | // // state.service = service; | ||
40 | // // } | ||
41 | // // } | ||
42 | // }); | ||
43 | } | ||
44 | |||
45 | export function mainIpcHandler(mainWindow, authInfo) { | ||
46 | debug('Sending basic auth call', authInfo); | ||
47 | |||
48 | mainWindow.webContents.send('feature:basic-auth-request', { | ||
49 | authInfo, | ||
50 | }); | ||
51 | } | ||
52 | |||
53 | export function sendCredentials(user, password) { | ||
54 | debug('Sending credentials to main', user, password); | ||
55 | |||
56 | ipcRenderer.send('feature-basic-auth-credentials', { | ||
57 | user, | ||
58 | password, | ||
59 | }); | ||
60 | } | ||
61 | |||
62 | export function cancelLogin() { | ||
63 | debug('Cancel basic auth event'); | ||
64 | |||
65 | ipcRenderer.send('feature-basic-auth-cancel'); | ||
66 | } | ||
67 | |||
68 | export const Component = BasicAuthComponent; | ||
diff --git a/src/features/basicAuth/mainIpcHandler.js b/src/features/basicAuth/mainIpcHandler.js new file mode 100644 index 000000000..87ac0b6df --- /dev/null +++ b/src/features/basicAuth/mainIpcHandler.js | |||
@@ -0,0 +1,9 @@ | |||
1 | const debug = require('debug')('Franz:feature:basicAuth:main'); | ||
2 | |||
3 | export default function mainIpcHandler(mainWindow, authInfo) { | ||
4 | debug('Sending basic auth call', authInfo); | ||
5 | |||
6 | mainWindow.webContents.send('feature:basic-auth', { | ||
7 | authInfo, | ||
8 | }); | ||
9 | } | ||
diff --git a/src/features/basicAuth/styles.js b/src/features/basicAuth/styles.js new file mode 100644 index 000000000..6bdaf9a6e --- /dev/null +++ b/src/features/basicAuth/styles.js | |||
@@ -0,0 +1,12 @@ | |||
1 | export default { | ||
2 | modal: { | ||
3 | width: 300, | ||
4 | }, | ||
5 | buttons: { | ||
6 | display: 'flex', | ||
7 | justifyContent: 'space-between', | ||
8 | }, | ||
9 | form: { | ||
10 | marginTop: 15, | ||
11 | }, | ||
12 | }; | ||
diff --git a/src/index.js b/src/index.js index 75da4ff88..195e9e863 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -1,5 +1,8 @@ | |||
1 | import { | 1 | import { |
2 | app, BrowserWindow, shell, ipcMain, | 2 | app, |
3 | BrowserWindow, | ||
4 | shell, | ||
5 | ipcMain, | ||
3 | } from 'electron'; | 6 | } from 'electron'; |
4 | 7 | ||
5 | import fs from 'fs-extra'; | 8 | import fs from 'fs-extra'; |
@@ -7,9 +10,14 @@ import path from 'path'; | |||
7 | import windowStateKeeper from 'electron-window-state'; | 10 | import windowStateKeeper from 'electron-window-state'; |
8 | 11 | ||
9 | import { | 12 | import { |
10 | isDevMode, isMac, isWindows, isLinux, | 13 | isDevMode, |
14 | isMac, | ||
15 | isWindows, | ||
16 | isLinux, | ||
11 | } from './environment'; | 17 | } from './environment'; |
12 | 18 | ||
19 | import { mainIpcHandler as basicAuthHandler } from './features/basicAuth'; | ||
20 | |||
13 | // DEV MODE: Save user data into FranzDev | 21 | // DEV MODE: Save user data into FranzDev |
14 | if (isDevMode) { | 22 | if (isDevMode) { |
15 | app.setPath('userData', path.join(app.getPath('appData'), 'FranzDev')); | 23 | app.setPath('userData', path.join(app.getPath('appData'), 'FranzDev')); |
@@ -263,23 +271,43 @@ app.on('ready', () => { | |||
263 | }); | 271 | }); |
264 | 272 | ||
265 | // This is the worst possible implementation as the webview.webContents based callback doesn't work 🖕 | 273 | // This is the worst possible implementation as the webview.webContents based callback doesn't work 🖕 |
274 | // TODO: rewrite to handle multiple login calls | ||
275 | const noop = () => null; | ||
276 | let authCallback = noop; | ||
266 | app.on('login', (event, webContents, request, authInfo, callback) => { | 277 | app.on('login', (event, webContents, request, authInfo, callback) => { |
267 | event.preventDefault(); | 278 | authCallback = callback; |
268 | debug('browser login event', authInfo); | 279 | debug('browser login event', authInfo); |
280 | event.preventDefault(); | ||
269 | if (authInfo.isProxy && authInfo.scheme === 'basic') { | 281 | if (authInfo.isProxy && authInfo.scheme === 'basic') { |
270 | webContents.send('get-service-id'); | 282 | webContents.send('get-service-id'); |
271 | 283 | ||
272 | ipcMain.on('service-id', (e, id) => { | 284 | ipcMain.once('service-id', (e, id) => { |
273 | debug('Received service id', id); | 285 | debug('Received service id', id); |
274 | 286 | ||
275 | const ps = proxySettings.get(id); | 287 | const ps = proxySettings.get(id); |
276 | callback(ps.user, ps.password); | 288 | callback(ps.user, ps.password); |
277 | }); | 289 | }); |
278 | } else { | 290 | } else if (authInfo.scheme === 'basic') { |
279 | // TODO: implement basic auth | 291 | debug('basic auth handler', authInfo); |
292 | basicAuthHandler(mainWindow, authInfo); | ||
280 | } | 293 | } |
281 | }); | 294 | }); |
282 | 295 | ||
296 | // TODO: evaluate if we need to store the authCallback for every service | ||
297 | ipcMain.on('feature-basic-auth-credentials', (e, { user, password }) => { | ||
298 | debug('Received basic auth credentials', user, '********'); | ||
299 | |||
300 | authCallback(user, password); | ||
301 | authCallback = noop; | ||
302 | }); | ||
303 | |||
304 | ipcMain.on('feature-basic-auth-cancel', () => { | ||
305 | debug('Cancel basic auth'); | ||
306 | |||
307 | authCallback(null); | ||
308 | authCallback = noop; | ||
309 | }); | ||
310 | |||
283 | // Quit when all windows are closed. | 311 | // Quit when all windows are closed. |
284 | app.on('window-all-closed', () => { | 312 | app.on('window-all-closed', () => { |
285 | // On OS X it is common for applications and their menu bar | 313 | // On OS X it is common for applications and their menu bar |
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index 2a0713b6f..2eccf87ee 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js | |||
@@ -6,6 +6,7 @@ import CachedRequest from './lib/CachedRequest'; | |||
6 | import delayApp from '../features/delayApp'; | 6 | import delayApp from '../features/delayApp'; |
7 | import spellchecker from '../features/spellchecker'; | 7 | import spellchecker from '../features/spellchecker'; |
8 | import serviceProxy from '../features/serviceProxy'; | 8 | import serviceProxy from '../features/serviceProxy'; |
9 | import basicAuth from '../features/basicAuth'; | ||
9 | 10 | ||
10 | import { DEFAULT_FEATURES_CONFIG } from '../config'; | 11 | import { DEFAULT_FEATURES_CONFIG } from '../config'; |
11 | 12 | ||
@@ -47,5 +48,6 @@ export default class FeaturesStore extends Store { | |||
47 | delayApp(this.stores, this.actions); | 48 | delayApp(this.stores, this.actions); |
48 | spellchecker(this.stores, this.actions); | 49 | spellchecker(this.stores, this.actions); |
49 | serviceProxy(this.stores, this.actions); | 50 | serviceProxy(this.stores, this.actions); |
51 | basicAuth(this.stores, this.actions); | ||
50 | } | 52 | } |
51 | } | 53 | } |
diff --git a/src/theme/dark/index.js b/src/theme/dark/index.js index fb1713184..429284f9e 100644 --- a/src/theme/dark/index.js +++ b/src/theme/dark/index.js | |||
@@ -11,3 +11,6 @@ export const colorText = legacyStyles.darkThemeTextColor; | |||
11 | // Loader | 11 | // Loader |
12 | export const colorFullscreenLoaderSpinner = '#FFF'; | 12 | export const colorFullscreenLoaderSpinner = '#FFF'; |
13 | export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.darkThemeGrayDarkest, 0.5); | 13 | export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.darkThemeGrayDarkest, 0.5); |
14 | |||
15 | // Modal | ||
16 | export const colorModalOverlayBackground = hexToRgba(legacyStyles.darkThemeGrayDarkest, 0.8); | ||
diff --git a/src/theme/default/index.js b/src/theme/default/index.js index 37827621c..3ce8e7f0e 100644 --- a/src/theme/default/index.js +++ b/src/theme/default/index.js | |||
@@ -27,3 +27,6 @@ export const colorSubscriptionContainerActionButtonColor = '#FFF'; | |||
27 | export const colorAppLoaderSpinner = '#FFF'; | 27 | export const colorAppLoaderSpinner = '#FFF'; |
28 | export const colorFullscreenLoaderSpinner = legacyStyles.themeGrayDark; | 28 | export const colorFullscreenLoaderSpinner = legacyStyles.themeGrayDark; |
29 | export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.themeGrayLighter, 0.8); | 29 | export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.themeGrayLighter, 0.8); |
30 | |||
31 | // Modal | ||
32 | export const colorModalOverlayBackground = hexToRgba(legacyStyles.themeGrayLighter, 0.8); | ||