Skip to content

Commit 203e7ff

Browse files
committed
fix: merge in oauth2 updates
2 parents c776ae7 + 3ab4040 commit 203e7ff

18 files changed

+668
-127
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# [100.6.0](https://github.com/dhis2/settings-app/compare/v100.5.0...v100.6.0) (2025-03-24)
2+
3+
4+
### Features
5+
6+
* allow oauth 2 clients for 42 ([#1425](https://github.com/dhis2/settings-app/issues/1425)) ([3a10fa8](https://github.com/dhis2/settings-app/commit/3a10fa878b1e4a108a4f3c45ffe6a6fa5980e9f6))
7+
18
# [100.5.0](https://github.com/dhis2/settings-app/compare/v100.4.2...v100.5.0) (2025-03-18)
29

310

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "settings-app",
3-
"version": "100.5.0",
3+
"version": "100.6.0",
44
"description": "",
55
"license": "BSD-3-Clause",
66
"private": true,
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import i18n from '@dhis2/d2-i18n'
2+
import { Button, Modal, ModalTitle, ModalContent } from '@dhis2/ui'
3+
import { getInstance as getD2 } from 'd2'
4+
import FormBuilder from 'd2-ui/lib/forms/FormBuilder.component.js'
5+
import { isUrlArray, isRequired } from 'd2-ui/lib/forms/Validators.js'
6+
import PropTypes from 'prop-types'
7+
import React from 'react'
8+
import MultiToggle from '../form-fields/multi-toggle.jsx'
9+
import TextField from '../form-fields/text-field.jsx'
10+
import styles from './ClientForm.module.css'
11+
12+
const formFieldStyle = {
13+
width: '100%',
14+
}
15+
16+
const validateClientID = async (v) => {
17+
const d2 = await getD2()
18+
const list = await d2.models.oAuth2Clients.list({
19+
paging: false,
20+
filter: [`cid:eq:${v}`],
21+
})
22+
if (list.size > 0) {
23+
throw i18n.t('This client ID is already taken')
24+
}
25+
}
26+
27+
const ClientForm = ({ clientModel, onUpdate, onSave, onCancel }) => {
28+
const grantTypes = ((clientModel && clientModel.grantTypes) || []).reduce(
29+
(curr, prev) => {
30+
curr[prev] = true
31+
return curr
32+
},
33+
{}
34+
)
35+
36+
const fields = [
37+
{
38+
name: 'name',
39+
value: clientModel.name,
40+
component: TextField,
41+
props: {
42+
floatingLabelText: i18n.t('Name'),
43+
style: formFieldStyle,
44+
changeEvent: 'onBlur',
45+
},
46+
validators: [
47+
{
48+
validator: isRequired,
49+
message: i18n.t('Required'),
50+
},
51+
],
52+
},
53+
{
54+
name: 'cid',
55+
value: clientModel.cid,
56+
component: TextField,
57+
props: {
58+
floatingLabelText: i18n.t('Client ID'),
59+
style: formFieldStyle,
60+
changeEvent: 'onBlur',
61+
},
62+
validators: [
63+
{
64+
validator: isRequired,
65+
message: i18n.t('Required'),
66+
},
67+
{
68+
validator: (v) => v.toString().trim().length > 0,
69+
message: i18n.t('Required'),
70+
},
71+
],
72+
asyncValidators: [validateClientID],
73+
},
74+
{
75+
name: 'secret',
76+
value: clientModel && clientModel.secret,
77+
component: TextField,
78+
props: {
79+
floatingLabelText: i18n.t('Client Secret'),
80+
disabled: true,
81+
style: formFieldStyle,
82+
},
83+
},
84+
{
85+
name: 'grantTypes',
86+
component: MultiToggle,
87+
style: formFieldStyle,
88+
props: {
89+
label: i18n.t('Grant Types'),
90+
items: [
91+
{
92+
name: 'password',
93+
text: i18n.t('Password'),
94+
value: grantTypes.password,
95+
},
96+
{
97+
name: 'refresh_token',
98+
text: i18n.t('Refresh token'),
99+
value: grantTypes.refresh_token,
100+
},
101+
{
102+
name: 'authorization_code',
103+
text: i18n.t('Authorization code'),
104+
value: grantTypes.authorization_code,
105+
},
106+
],
107+
},
108+
},
109+
{
110+
name: 'redirectUris',
111+
value: (clientModel.redirectUris || []).join('\n'),
112+
component: TextField,
113+
props: {
114+
hintText: i18n.t('One URL per line'),
115+
floatingLabelText: i18n.t('Redirect URIs'),
116+
multiLine: true,
117+
style: formFieldStyle,
118+
changeEvent: 'onBlur',
119+
},
120+
validators: [
121+
{
122+
validator: isUrlArray,
123+
message: i18n.t('This field should contain a list of URLs'),
124+
},
125+
],
126+
},
127+
]
128+
129+
const headerText =
130+
clientModel.id === undefined
131+
? i18n.t('Create new OAuth2 Client')
132+
: i18n.t('Edit OAuth2 Client')
133+
return (
134+
<Modal onClose={onCancel}>
135+
<ModalTitle>{headerText}</ModalTitle>
136+
<ModalContent>
137+
<FormBuilder fields={fields} onUpdateField={onUpdate} />
138+
<div style={{ marginTop: '1rem' }}>
139+
<Button primary onClick={onSave}>
140+
{i18n.t('Save')}
141+
</Button>
142+
<Button
143+
secondary
144+
onClick={onCancel}
145+
className={styles.cancelBtn}
146+
>
147+
{i18n.t('Cancel')}
148+
</Button>
149+
</div>
150+
</ModalContent>
151+
</Modal>
152+
)
153+
}
154+
155+
ClientForm.propTypes = {
156+
clientModel: PropTypes.object.isRequired,
157+
onCancel: PropTypes.func.isRequired,
158+
onSave: PropTypes.func.isRequired,
159+
onUpdate: PropTypes.func.isRequired,
160+
}
161+
162+
export default ClientForm
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.cancelBtn {
2+
float: right;
3+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import i18n from '@dhis2/d2-i18n'
2+
import {
3+
CenteredContent,
4+
Table,
5+
TableBody,
6+
TableCell,
7+
TableCellHead,
8+
TableHead,
9+
TableRow,
10+
TableRowHead,
11+
Button,
12+
} from '@dhis2/ui'
13+
import PropTypes from 'prop-types'
14+
import React from 'react'
15+
import styles from './ClientsList.module.css'
16+
17+
const ClientsList = ({ clients, onClientEdit, onClientDelete }) => {
18+
if (clients.length === 0) {
19+
return (
20+
<CenteredContent>
21+
<p>
22+
{i18n.t('There are currently no OAuth2 clients registered')}
23+
</p>
24+
</CenteredContent>
25+
)
26+
}
27+
28+
return (
29+
<Table>
30+
<TableHead>
31+
<TableRowHead>
32+
<TableCellHead>{i18n.t('Name')}</TableCellHead>
33+
<TableCellHead>{i18n.t('Password')}</TableCellHead>
34+
<TableCellHead>{i18n.t('Refresh token')}</TableCellHead>
35+
<TableCellHead>
36+
{i18n.t('Authorization code')}
37+
</TableCellHead>
38+
<TableCellHead>{/* Buttons column */}</TableCellHead>
39+
</TableRowHead>
40+
</TableHead>
41+
<TableBody>
42+
{clients.map((client) => (
43+
<TableRow key={client.authorization_code}>
44+
<TableCell>{client.name}</TableCell>
45+
<TableCell>{client.password}</TableCell>
46+
<TableCell>{client.refresh_token}</TableCell>
47+
<TableCell>{client.authorization_code}</TableCell>
48+
<TableCell>
49+
<Button
50+
small
51+
primary
52+
className={styles.editBtn}
53+
onClick={() => onClientEdit(client)}
54+
>
55+
{i18n.t('Edit')}
56+
</Button>
57+
<Button
58+
small
59+
destructive
60+
onClick={() => onClientDelete(client)}
61+
>
62+
{i18n.t('Delete')}
63+
</Button>
64+
</TableCell>
65+
</TableRow>
66+
))}
67+
</TableBody>
68+
</Table>
69+
)
70+
}
71+
72+
ClientsList.propTypes = {
73+
clients: PropTypes.array.isRequired,
74+
onClientDelete: PropTypes.func.isRequired,
75+
onClientEdit: PropTypes.func.isRequired,
76+
}
77+
78+
export default ClientsList
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.editBtn {
2+
margin-right: var(--spacers-dp16);
3+
}

0 commit comments

Comments
 (0)