Skip to content

Commit 33021ae

Browse files
Merge pull request #64 from OpenDTU-App/60-make-it-possible-to-setchange-password-for-opendtu
2 parents 5d4e04e + 42b4843 commit 33021ae

File tree

8 files changed

+244
-12
lines changed

8 files changed

+244
-12
lines changed

src/api/opendtuapi.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class OpenDtuApi {
114114
this.index,
115115
this.isConnected(),
116116
);
117-
}, 10000); // 10 seconds
117+
}, 5000); // 10 seconds
118118

119119
this.updateHttpState();
120120
}
@@ -274,12 +274,21 @@ class OpenDtuApi {
274274
};
275275
}
276276

277-
public async checkCredentials(
278-
baseUrl: string,
279-
username: string,
280-
password: string,
281-
): Promise<false | OpenDTUAuthenticateResponse> {
277+
public async checkCredentials({
278+
username,
279+
password,
280+
baseUrl = this.baseUrl as string,
281+
}: {
282+
username: string;
283+
password: string;
284+
baseUrl?: string;
285+
}): Promise<false | OpenDTUAuthenticateResponse> {
282286
// GET <url>/api/security/authenticate
287+
if (!baseUrl) {
288+
log.warn('checkCredentials', 'baseUrl is null');
289+
return false;
290+
}
291+
283292
const authData = this.encodeCredentials(username, password);
284293

285294
const requestOptions = {
@@ -666,7 +675,7 @@ class OpenDtuApi {
666675

667676
const authString = this.getAuthString();
668677

669-
const url = `${authString}${this.baseUrl}${route}`;
678+
const url = `${authString ?? ''}${this.baseUrl}${route}`;
670679

671680
// console.log('makeAuthenticatedRequest', url, requestOptions);
672681

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import type { FC } from 'react';
2+
import { useMemo, useCallback, useEffect, useState } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
import { Box } from 'react-native-flex-layout';
5+
import { logger } from 'react-native-logs';
6+
import type { ModalProps } from 'react-native-paper';
7+
import { Button, Portal, Text, useTheme } from 'react-native-paper';
8+
9+
import { updateDtuUserString } from '@/slices/settings';
10+
11+
import BaseModal from '@/components/BaseModal';
12+
import StyledTextInput from '@/components/styled/StyledTextInput';
13+
14+
import { useApi } from '@/api/ApiHandler';
15+
import { defaultUser } from '@/constants';
16+
import { useAppDispatch, useAppSelector } from '@/store';
17+
18+
export interface ChangeOpendtuCredentialsModalProps
19+
extends Omit<ModalProps, 'children'> {
20+
index: number;
21+
}
22+
23+
const log = logger.createLogger();
24+
25+
const ChangeOpendtuCredentialsModal: FC<
26+
ChangeOpendtuCredentialsModalProps
27+
> = props => {
28+
const { onDismiss, index } = props;
29+
const dispatch = useAppDispatch();
30+
const theme = useTheme();
31+
const { t } = useTranslation();
32+
33+
const currentUserString = useAppSelector(
34+
state => state.settings.dtuConfigs[index].userString,
35+
);
36+
37+
const currentUsername = useMemo(() => {
38+
if (!currentUserString) return null;
39+
40+
return atob(currentUserString).split(':')[0];
41+
}, [currentUserString]);
42+
43+
const [username, setUsername] = useState<string>(
44+
currentUsername ?? defaultUser,
45+
);
46+
const [password, setPassword] = useState<string>('');
47+
48+
const [loading, setLoading] = useState<boolean>(false);
49+
const [error, setError] = useState<string | null>(null);
50+
51+
const openDtuApi = useApi();
52+
53+
useEffect(() => {
54+
if (currentUsername) {
55+
setUsername(currentUsername);
56+
}
57+
}, [currentUsername]);
58+
59+
const handleAbort = useCallback(() => {
60+
onDismiss?.();
61+
}, [onDismiss]);
62+
63+
const handleSave = useCallback(async () => {
64+
if (!username || !password) return;
65+
66+
setLoading(true);
67+
setError(null);
68+
69+
const result = await openDtuApi.checkCredentials({
70+
username,
71+
password,
72+
});
73+
74+
if (result === false) {
75+
setError(t('setup.errors.invalidCredentials'));
76+
log.info('Invalid credentials');
77+
setLoading(false);
78+
return;
79+
}
80+
81+
if (result?.authdata) {
82+
onDismiss?.();
83+
setPassword('');
84+
setLoading(false);
85+
86+
dispatch(updateDtuUserString({ userString: result.authdata, index }));
87+
88+
return;
89+
} else {
90+
setError(t('setup.errors.genericError'));
91+
log.info('Something went wrong! Please try again.');
92+
}
93+
94+
setLoading(false);
95+
96+
onDismiss?.();
97+
}, [openDtuApi, username, password, dispatch, index, onDismiss, t]);
98+
99+
const handleClearCredentials = useCallback(() => {
100+
dispatch(updateDtuUserString({ userString: null, index }));
101+
onDismiss?.();
102+
}, [dispatch, index, onDismiss]);
103+
104+
return (
105+
<Portal>
106+
<BaseModal {...props}>
107+
<Box p={16}>
108+
<Box mb={8}>
109+
<Text variant="bodyLarge">
110+
{t('settings.changeOpendtuCredentials')}
111+
</Text>
112+
</Box>
113+
<StyledTextInput
114+
label={t('setup.username')}
115+
mode="outlined"
116+
value={username}
117+
onChangeText={setUsername}
118+
textContentType="username"
119+
style={{ backgroundColor: theme.colors.elevation.level3 }}
120+
/>
121+
<StyledTextInput
122+
label={t('setup.password')}
123+
mode="outlined"
124+
value={password}
125+
onChangeText={setPassword}
126+
textContentType="password"
127+
style={{ backgroundColor: theme.colors.elevation.level3 }}
128+
/>
129+
{error ? (
130+
<Box mt={8}>
131+
<Text variant="bodySmall" style={{ color: theme.colors.error }}>
132+
{error}
133+
</Text>
134+
</Box>
135+
) : null}
136+
</Box>
137+
<Box
138+
style={{
139+
flexDirection: 'row',
140+
justifyContent: 'flex-end',
141+
alignItems: 'center',
142+
padding: 8,
143+
}}
144+
>
145+
<Button
146+
mode="text"
147+
onPress={handleClearCredentials}
148+
style={{ marginRight: 8 }}
149+
disabled={loading}
150+
>
151+
{t('clear')}
152+
</Button>
153+
<Button
154+
mode="text"
155+
onPress={handleAbort}
156+
style={{ marginRight: 8 }}
157+
disabled={loading}
158+
>
159+
{t('cancel')}
160+
</Button>
161+
<Button
162+
mode="text"
163+
onPress={handleSave}
164+
disabled={loading}
165+
loading={loading}
166+
>
167+
{t('change')}
168+
</Button>
169+
</Box>
170+
</BaseModal>
171+
</Portal>
172+
);
173+
};
174+
175+
export default ChangeOpendtuCredentialsModal;

src/constants/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ export const colors = {
22
success: '#4caf50',
33
error: '#f44336',
44
};
5+
6+
export const defaultUser = 'admin';

src/slices/settings.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
EnableAppUpdatesAction,
2222
DebugEnabledAction,
2323
EnableFetchOpenDTUReleasesAction,
24+
UpdateDtuUserStringAction,
2425
} from '@/types/settings';
2526
import { DidNotAskYet } from '@/types/settings';
2627

@@ -174,6 +175,22 @@ const settingsSlice = createSlice({
174175

175176
state.dtuConfigs[action.payload.index].baseUrl = action.payload.baseUrl;
176177
},
178+
updateDtuUserString: (state, action: UpdateDtuUserStringAction) => {
179+
if (state.dtuConfigs.length === 0) {
180+
log.warn('updateDtuUserString: dtuConfigs.length === 0');
181+
return;
182+
}
183+
184+
if (state.dtuConfigs[action.payload.index] === undefined) {
185+
log.warn(
186+
`updateDtuUserString: dtuConfigs[${action.payload.index}] === undefined`,
187+
);
188+
return;
189+
}
190+
191+
state.dtuConfigs[action.payload.index].userString =
192+
action.payload.userString;
193+
},
177194
clearSettings: () => initialState,
178195
setSelectedDtuToFirstOrNull: state => {
179196
if (state.dtuConfigs.length > 0) {
@@ -249,6 +266,7 @@ export const {
249266
updateDtuCustomName,
250267
updateDtuCustomNameIfEmpty,
251268
updateDtuBaseUrl,
269+
updateDtuUserString,
252270
clearSettings,
253271
setSelectedDtuToFirstOrNull,
254272
addDatabaseConfig,

src/translations/translation-files

src/types/settings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ export type UpdateDtuBaseURLAction = PayloadAction<{
8989
baseUrl: string;
9090
}>;
9191

92+
export type UpdateDtuUserStringAction = PayloadAction<{
93+
index: Index;
94+
userString: string | null;
95+
}>;
96+
9297
export type AddDatabaseConfigAction = PayloadAction<{
9398
config: DatabaseConfig;
9499
}>;

src/views/navigation/screens/DeviceSettingsScreen.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { setSelectedDtuConfig } from '@/slices/settings';
99
import { DeviceState } from '@/types/opendtu/state';
1010

1111
import ChangeCustomNameModal from '@/components/modals/ChangeCustomNameModal';
12+
import ChangeOpendtuCredentialsModal from '@/components/modals/ChangeOpendtuCredentialsModal';
1213
import ChangeServerUrlModal from '@/components/modals/ChangeServerUrlModal';
1314
import ConfirmDeleteDeviceModal from '@/components/modals/ConfirmDeleteDeviceModal';
1415

@@ -43,6 +44,9 @@ const DeviceSettingsScreen: FC<PropsWithNavigation> = ({
4344

4445
const [openServerUrlModal, setOpenServerUrlModal] = useState<boolean>(false);
4546

47+
const [openCredentialsModal, setOpenCredentialsModal] =
48+
useState<boolean>(false);
49+
4650
const [openDeleteModal, setOpenDeleteModal] = useState<boolean>(false);
4751

4852
useEffect(() => {
@@ -69,6 +73,11 @@ const DeviceSettingsScreen: FC<PropsWithNavigation> = ({
6973
return atob(config.userString).split(':')[0];
7074
}, [config.userString]);
7175

76+
const hasPassword = useMemo(() => {
77+
if (!config.userString) return false;
78+
return atob(config.userString).split(':')[1] !== '';
79+
}, [config.userString]);
80+
7281
const deviceState = useAppSelector(state => state.opendtu.deviceState[index]);
7382

7483
const hints = useLivedata(state => state?.hints);
@@ -197,6 +206,14 @@ const DeviceSettingsScreen: FC<PropsWithNavigation> = ({
197206
style={{ borderRadius: 8 }}
198207
onPress={() => setOpenServerUrlModal(true)}
199208
/>
209+
<List.Item
210+
title={t('deviceSettings.opendtuCredentials')}
211+
description={hasPassword ? '********' : t('deviceSettings.none')}
212+
left={props => <List.Icon {...props} icon="lock" />}
213+
borderless
214+
style={{ borderRadius: 8 }}
215+
onPress={() => setOpenCredentialsModal(true)}
216+
/>
200217
<List.Item
201218
title={t('deviceSettings.configureDatabase')}
202219
description={databaseName ?? 'Not configured'}
@@ -244,6 +261,11 @@ const DeviceSettingsScreen: FC<PropsWithNavigation> = ({
244261
onDismiss={() => setOpenServerUrlModal(false)}
245262
index={index}
246263
/>
264+
<ChangeOpendtuCredentialsModal
265+
visible={openCredentialsModal}
266+
onDismiss={() => setOpenCredentialsModal(false)}
267+
index={index}
268+
/>
247269
<ConfirmDeleteDeviceModal
248270
visible={openDeleteModal}
249271
onDismiss={() => setOpenDeleteModal(false)}

src/views/navigation/screens/SetupAuthenticateOpenDTUInstanceScreen.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { DeviceState } from '@/types/opendtu/state';
1919
import StyledTextInput from '@/components/styled/StyledTextInput';
2020

2121
import { useApi } from '@/api/ApiHandler';
22+
import { defaultUser } from '@/constants';
2223
import { useAppDispatch, useAppSelector } from '@/store';
2324
import { StyledSafeAreaView } from '@/style';
2425
import type { PropsWithNavigation } from '@/views/navigation/NavigationStack';
@@ -38,7 +39,7 @@ const SetupAuthenticateOpenDTUInstanceScreen: FC<PropsWithNavigation> = ({
3839

3940
const previousStepValid = address !== null;
4041

41-
const [username, setUsername] = useState<string | null>('admin');
42+
const [username, setUsername] = useState<string | null>(defaultUser);
4243
const [password, setPassword] = useState<string | null>(null);
4344
const [visible, setVisible] = useState<boolean>(false);
4445

@@ -53,11 +54,11 @@ const SetupAuthenticateOpenDTUInstanceScreen: FC<PropsWithNavigation> = ({
5354
setLoading(true);
5455
setError(null);
5556

56-
const result = await openDtuApi.checkCredentials(
57-
address,
57+
const result = await openDtuApi.checkCredentials({
58+
baseUrl: address,
5859
username,
5960
password,
60-
);
61+
});
6162

6263
if (result === false) {
6364
setError(t('setup.errors.invalidCredentials'));

0 commit comments

Comments
 (0)