Skip to content

Commit bf34d62

Browse files
authored
Merge pull request #985 from oskarhane/no-initial-empty-auth-check
Don’t auto-connect without credentials
2 parents 6827840 + c2ae680 commit bf34d62

File tree

8 files changed

+110
-80
lines changed

8 files changed

+110
-80
lines changed

src/browser/modules/Stream/Auth/ConnectForm.jsx

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,17 @@ import { FormButton } from 'browser-components/buttons'
2424
import {
2525
StyledConnectionForm,
2626
StyledConnectionTextInput,
27+
StyledConnectionSelect,
2728
StyledConnectionLabel,
2829
StyledConnectionFormEntry
2930
} from './styled'
3031
import InputEnterStepping from 'browser-components/InputEnterStepping/InputEnterStepping'
32+
import { NATIVE, NO_AUTH } from 'services/bolt/boltHelpers'
33+
34+
const readableauthenticationMethods = {
35+
[NATIVE]: 'Username / Password',
36+
[NO_AUTH]: 'No authentication'
37+
}
3138

3239
export default class ConnectForm extends Component {
3340
state = {
@@ -63,31 +70,54 @@ export default class ConnectForm extends Component {
6370
})}
6471
/>
6572
</StyledConnectionFormEntry>
66-
6773
<StyledConnectionFormEntry>
68-
<StyledConnectionLabel>Username</StyledConnectionLabel>
69-
<StyledConnectionTextInput
74+
<StyledConnectionLabel>
75+
Authentication type
76+
</StyledConnectionLabel>
77+
<StyledConnectionSelect
7078
{...getInputPropsForIndex(1, {
71-
'data-testid': 'username',
72-
onChange: this.props.onUsernameChange,
73-
defaultValue: this.props.username,
79+
'data-testid': 'authenticationMethod',
80+
onChange: this.props.onAuthenticationMethodChange,
81+
value: this.props.authenticationMethod,
7482
ref: ref => setRefForIndex(1, ref)
7583
})}
76-
/>
84+
>
85+
{[NATIVE, NO_AUTH].map((auth, i) => (
86+
<option value={auth} key={i}>
87+
{readableauthenticationMethods[auth]}
88+
</option>
89+
))}
90+
</StyledConnectionSelect>
7791
</StyledConnectionFormEntry>
7892

79-
<StyledConnectionFormEntry>
80-
<StyledConnectionLabel>Password</StyledConnectionLabel>
81-
<StyledConnectionTextInput
82-
{...getInputPropsForIndex(2, {
83-
'data-testid': 'password',
84-
onChange: this.props.onPasswordChange,
85-
defaultValue: this.props.password,
86-
type: 'password',
87-
ref: ref => setRefForIndex(2, ref)
88-
})}
89-
/>
90-
</StyledConnectionFormEntry>
93+
{this.props.authenticationMethod === NATIVE && (
94+
<StyledConnectionFormEntry>
95+
<StyledConnectionLabel>Username</StyledConnectionLabel>
96+
<StyledConnectionTextInput
97+
{...getInputPropsForIndex(2, {
98+
'data-testid': 'username',
99+
onChange: this.props.onUsernameChange,
100+
defaultValue: this.props.username,
101+
ref: ref => setRefForIndex(2, ref)
102+
})}
103+
/>
104+
</StyledConnectionFormEntry>
105+
)}
106+
107+
{this.props.authenticationMethod === NATIVE && (
108+
<StyledConnectionFormEntry>
109+
<StyledConnectionLabel>Password</StyledConnectionLabel>
110+
<StyledConnectionTextInput
111+
{...getInputPropsForIndex(3, {
112+
'data-testid': 'password',
113+
onChange: this.props.onPasswordChange,
114+
defaultValue: this.props.password,
115+
type: 'password',
116+
ref: ref => setRefForIndex(3, ref)
117+
})}
118+
/>
119+
</StyledConnectionFormEntry>
120+
)}
91121

92122
<Render if={!this.state.connecting}>
93123
<FormButton data-testid='connect' {...getSubmitProps()}>

src/browser/modules/Stream/Auth/ConnectionForm.jsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { shouldRetainConnectionCredentials } from 'shared/modules/dbMeta/dbMetaD
3535
import { FORCE_CHANGE_PASSWORD } from 'shared/modules/cypher/cypherDuck'
3636
import { changeCurrentUsersPasswordQueryObj } from 'shared/modules/cypher/procedureFactory'
3737
import { generateBoltHost } from 'services/utils'
38-
import { getEncryptionMode } from 'services/bolt/boltHelpers'
38+
import { getEncryptionMode, NATIVE, NO_AUTH } from 'services/bolt/boltHelpers'
3939

4040
import ConnectForm from './ConnectForm'
4141
import ConnectedView from './ConnectedView'
@@ -47,8 +47,12 @@ export class ConnectionForm extends Component {
4747
const connection =
4848
this.props.activeConnectionData || this.props.frame.connectionData
4949
const isConnected = !!props.activeConnection
50+
const authenticationMethod =
51+
(connection && connection.authenticationMethod) || NATIVE
52+
5053
this.state = {
5154
...connection,
55+
authenticationMethod,
5256
isConnected: isConnected,
5357
isLoading: false,
5458
passwordChangeNeeded: props.passwordChangeNeeded || false,
@@ -105,6 +109,14 @@ export class ConnectionForm extends Component {
105109
this.setState({ password })
106110
this.props.error({})
107111
}
112+
onAuthenticationMethodChange (event) {
113+
const authenticationMethod = event.target.value
114+
const username =
115+
authenticationMethod === NO_AUTH ? '' : this.state.username || 'neo4j'
116+
const password = authenticationMethod === NO_AUTH ? '' : this.state.password
117+
this.setState({ authenticationMethod, username, password })
118+
this.props.error({})
119+
}
108120
onHostChange (event) {
109121
const host = event.target.value
110122
this.setState({
@@ -187,7 +199,8 @@ export class ConnectionForm extends Component {
187199
id: this.state.id,
188200
host: this.state.host,
189201
username: this.state.username,
190-
password: this.state.password
202+
password: this.state.password,
203+
authenticationMethod: this.state.authenticationMethod
191204
})
192205
}
193206
componentWillReceiveProps (nextProps) {
@@ -227,6 +240,7 @@ export class ConnectionForm extends Component {
227240
host={this.state.host}
228241
username={this.state.username}
229242
storeCredentials={this.props.storeCredentials}
243+
hideStoreCredentials={this.state.authenticationMethod === NO_AUTH}
230244
/>
231245
)
232246
} else if (!this.state.isConnected && !this.state.passwordChangeNeeded) {
@@ -236,9 +250,13 @@ export class ConnectionForm extends Component {
236250
onHostChange={this.onHostChange.bind(this)}
237251
onUsernameChange={this.onUsernameChange.bind(this)}
238252
onPasswordChange={this.onPasswordChange.bind(this)}
253+
onAuthenticationMethodChange={this.onAuthenticationMethodChange.bind(
254+
this
255+
)}
239256
host={this.state.hostInputVal || this.state.host}
240257
username={this.state.username}
241258
password={this.state.password}
259+
authenticationMethod={this.state.authenticationMethod}
242260
used={this.state.used}
243261
/>
244262
)

src/browser/modules/Stream/Auth/ConnectionFrame.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export class ConnectionFrame extends Component {
5959
<React.Fragment>
6060
<H3>Connect to Neo4j</H3>
6161
<Lead>
62-
Database access requires an authenticated connection.
62+
Database access might require an authenticated connection.
6363
</Lead>
6464
</React.Fragment>
6565
</Render>

src/browser/modules/Stream/Auth/styled.jsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020

2121
import styled from 'styled-components'
22-
import { StyledInput } from 'browser-components/Form'
22+
import { StyledInput, StyledSelect } from 'browser-components/Form'
2323
import { StyledFrameAside } from '../../Frame/styled'
2424

2525
export const StyledConnectionForm = styled.form`
@@ -43,6 +43,12 @@ export const StyledConnectionTextInput = styled(StyledInput)`
4343
min-width: 200px;
4444
width: 44%;
4545
`
46+
47+
export const StyledConnectionSelect = styled(StyledSelect)`
48+
min-width: 200px;
49+
width: 44%;
50+
`
51+
4652
export const StyledConnectionBodyContainer = styled.div`
4753
flex: 1 1 auto;
4854
`

src/shared/modules/connections/connectionsDuck.js

Lines changed: 19 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
import Rx from 'rxjs/Rx'
2222
import bolt from 'services/bolt/bolt'
23-
import { getEncryptionMode } from 'services/bolt/boltHelpers'
23+
import { getEncryptionMode, NO_AUTH } from 'services/bolt/boltHelpers'
2424
import * as discovery from 'shared/modules/discovery/discoveryDuck'
2525
import {
2626
fetchMetaData,
@@ -350,70 +350,43 @@ export const verifyConnectionCredentialsEpic = (action$, store) => {
350350
export const startupConnectEpic = (action$, store) => {
351351
return action$.ofType(discovery.DONE).mergeMap(action => {
352352
const connection = getConnection(store.getState(), discovery.CONNECTION_ID)
353-
if (!connection || !connection.host) {
353+
354+
// No creds stored, fail auto-connect
355+
if (
356+
!connection ||
357+
connection.authenticationMethod === NO_AUTH ||
358+
!(connection.host && connection.username && connection.password)
359+
) {
354360
store.dispatch(setActiveConnection(null))
355361
store.dispatch(
356-
discovery.updateDiscoveryConnection({ username: 'neo4j', password: '' })
362+
discovery.updateDiscoveryConnection({ username: '', password: '' })
357363
)
358364
return Promise.resolve({ type: STARTUP_CONNECTION_FAILED })
359365
}
360366
return new Promise((resolve, reject) => {
367+
// Try to connect with stored creds
361368
bolt
362369
.openConnection(
363-
// Try without creds
364370
connection,
365371
{
366-
withoutCredentials: true,
367372
encrypted: getEncryptionMode(connection),
368373
connectionTimeout: getConnectionTimeout(store.getState())
369374
},
370375
onLostConnection(store.dispatch)
371376
)
372-
.then(r => {
373-
store.dispatch(
374-
discovery.updateDiscoveryConnection({
375-
username: undefined,
376-
password: undefined
377-
})
378-
)
377+
.then(() => {
379378
store.dispatch(setActiveConnection(discovery.CONNECTION_ID))
380379
resolve({ type: STARTUP_CONNECTION_SUCCESS })
381380
})
382-
.catch(() => {
383-
if (!connection || (!connection.username && !connection.password)) {
384-
// No creds stored
385-
store.dispatch(setActiveConnection(null))
386-
store.dispatch(
387-
discovery.updateDiscoveryConnection({
388-
username: 'neo4j',
389-
password: ''
390-
})
391-
)
392-
return resolve({ type: STARTUP_CONNECTION_FAILED })
393-
}
394-
bolt
395-
.openConnection(
396-
connection,
397-
{
398-
encrypted: getEncryptionMode(connection),
399-
connectionTimeout: getConnectionTimeout(store.getState())
400-
},
401-
onLostConnection(store.dispatch)
402-
) // Try with stored creds
403-
.then(connection => {
404-
store.dispatch(setActiveConnection(discovery.CONNECTION_ID))
405-
resolve({ type: STARTUP_CONNECTION_SUCCESS })
406-
})
407-
.catch(e => {
408-
store.dispatch(setActiveConnection(null))
409-
store.dispatch(
410-
discovery.updateDiscoveryConnection({
411-
username: 'neo4j',
412-
password: ''
413-
})
414-
)
415-
resolve({ type: STARTUP_CONNECTION_FAILED })
381+
.catch(e => {
382+
store.dispatch(setActiveConnection(null))
383+
store.dispatch(
384+
discovery.updateDiscoveryConnection({
385+
username: '',
386+
password: ''
416387
})
388+
)
389+
resolve({ type: STARTUP_CONNECTION_FAILED })
417390
})
418391
})
419392
})

src/shared/modules/connections/connectionsDuck.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ describe('connectionsDucks Epics', () => {
200200
expect(store.getActions()).toEqual([
201201
action,
202202
connections.setActiveConnection(null),
203-
updateDiscoveryConnection({ username: 'neo4j', password: '' }),
203+
updateDiscoveryConnection({ username: '', password: '' }),
204204
currentAction
205205
])
206206
expect(bolt.openConnection).toHaveBeenCalledTimes(0)
@@ -285,10 +285,10 @@ describe('startupConnectEpic', () => {
285285
expect(actions).toEqual([
286286
action,
287287
connections.setActiveConnection(null),
288-
updateDiscoveryConnection({ username: 'neo4j', password: '' }),
288+
updateDiscoveryConnection({ username: '', password: '' }),
289289
currentAction
290290
])
291-
expect(bolt.openConnection).toHaveBeenCalledTimes(2)
291+
expect(bolt.openConnection).toHaveBeenCalledTimes(1)
292292
expect(bolt.closeConnection).toHaveBeenCalledTimes(1)
293293
resolve()
294294
} catch (e) {

src/shared/services/bolt/boltConnection.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { v1 as neo4j } from 'neo4j-driver'
2222
import { v4 } from 'uuid'
2323
import { BoltConnectionError, createErrorObject } from '../exceptions'
2424
import { generateBoltHost } from 'services/utils'
25-
import { KERBEROS } from 'services/bolt/boltHelpers'
25+
import { KERBEROS, NATIVE } from 'services/bolt/boltHelpers'
2626

2727
export const DIRECT_CONNECTION = 'DIRECT_CONNECTION'
2828
export const ROUTED_WRITE_CONNECTION = 'ROUTED_WRITE_CONNECTION'
@@ -69,15 +69,17 @@ const validateConnection = (driver, res, rej) => {
6969
})
7070
}
7171

72-
const buildAuthObj = (props, opts) => {
72+
const buildAuthObj = props => {
7373
let auth
7474
if (props.authenticationMethod === KERBEROS) {
7575
auth = neo4j.auth.kerberos(props.password)
76+
} else if (
77+
props.authenticationMethod === NATIVE ||
78+
!props.authenticationMethod
79+
) {
80+
auth = neo4j.auth.basic(props.username, props.password)
7681
} else {
77-
auth =
78-
opts.withoutCredentials || !props.username
79-
? neo4j.auth.basic('', '')
80-
: neo4j.auth.basic(props.username, props.password)
82+
auth = null
8183
}
8284
return auth
8385
}
@@ -95,7 +97,7 @@ const getDriver = (host, auth, opts, onConnectFail = () => {}) => {
9597

9698
export const getDriversObj = (props, opts = {}, onConnectFail = () => {}) => {
9799
const driversObj = {}
98-
const auth = buildAuthObj(props, opts)
100+
const auth = buildAuthObj(props)
99101
const getDirectDriver = () => {
100102
if (driversObj.direct) return driversObj.direct
101103
driversObj.direct = getDriver(props.host, auth, opts, onConnectFail)
@@ -124,7 +126,7 @@ export function directConnect (
124126
shouldValidateConnection = true
125127
) {
126128
const p = new Promise((resolve, reject) => {
127-
const auth = buildAuthObj(props, opts)
129+
const auth = buildAuthObj(props)
128130
const driver = getDriver(props.host, auth, opts)
129131
driver.onError = e => {
130132
onLostConnection(e)

src/shared/services/bolt/boltHelpers.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { getUrlInfo } from 'services/utils'
2323

2424
export const KERBEROS = 'KERBEROS'
2525
export const NATIVE = 'NATIVE'
26+
export const NO_AUTH = 'NO_AUTH'
2627

2728
export const getEncryptionMode = options => {
2829
if (options && typeof options['encrypted'] !== 'undefined') {

0 commit comments

Comments
 (0)