@plone-collective/volto-identity 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/.changelog.draft +261 -0
  2. package/.release-it.json +34 -0
  3. package/CHANGELOG.md +269 -0
  4. package/LICENSE +21 -0
  5. package/README.md +228 -0
  6. package/babel.config.js +17 -0
  7. package/locales/de/LC_MESSAGES/volto.po +1162 -0
  8. package/locales/en/LC_MESSAGES/volto.po +1162 -0
  9. package/locales/es/LC_MESSAGES/volto.po +1169 -0
  10. package/locales/pt_BR/LC_MESSAGES/volto.po +1167 -0
  11. package/locales/volto.pot +1164 -0
  12. package/news/.changelog_template.jinja +15 -0
  13. package/news/.gitkeep +0 -0
  14. package/package.json +66 -0
  15. package/public/.gitkeep +0 -0
  16. package/src/actions/actions.test.ts +226 -0
  17. package/src/actions/index.ts +530 -0
  18. package/src/components/.gitkeep +0 -0
  19. package/src/components/Applications/Applications.tsx +169 -0
  20. package/src/components/Applications/ApplicationsPanel.scss +59 -0
  21. package/src/components/Applications/ApplicationsPanel.stories.tsx +82 -0
  22. package/src/components/Applications/ApplicationsPanel.test.tsx +235 -0
  23. package/src/components/Applications/ApplicationsPanel.tsx +342 -0
  24. package/src/components/Callback/Callback.scss +17 -0
  25. package/src/components/Callback/Callback.stories.tsx +43 -0
  26. package/src/components/Callback/Callback.test.tsx +262 -0
  27. package/src/components/Callback/Callback.tsx +170 -0
  28. package/src/components/Consent/Consent.test.tsx +75 -0
  29. package/src/components/Consent/Consent.tsx +98 -0
  30. package/src/components/Consent/ConsentPanel.scss +37 -0
  31. package/src/components/Consent/ConsentPanel.stories.tsx +80 -0
  32. package/src/components/Consent/ConsentPanel.test.tsx +138 -0
  33. package/src/components/Consent/ConsentPanel.tsx +162 -0
  34. package/src/components/ControlPanel/ClientsControlPanel.stories.tsx +38 -0
  35. package/src/components/ControlPanel/ClientsControlPanel.tsx +321 -0
  36. package/src/components/ControlPanel/ClientsPanel.scss +21 -0
  37. package/src/components/ControlPanel/ClientsPanel.stories.tsx +71 -0
  38. package/src/components/ControlPanel/ClientsPanel.test.tsx +267 -0
  39. package/src/components/ControlPanel/ClientsPanel.tsx +386 -0
  40. package/src/components/ControlPanel/ConfirmModal.stories.tsx +35 -0
  41. package/src/components/ControlPanel/ConfirmModal.test.tsx +66 -0
  42. package/src/components/ControlPanel/ConfirmModal.tsx +68 -0
  43. package/src/components/ControlPanel/ProviderForm.stories.tsx +107 -0
  44. package/src/components/ControlPanel/ProvidersControlPanel.scss +26 -0
  45. package/src/components/ControlPanel/ProvidersControlPanel.stories.tsx +88 -0
  46. package/src/components/ControlPanel/ProvidersControlPanel.tsx +600 -0
  47. package/src/components/ControlPanel/RenderUsers.stories.tsx +112 -0
  48. package/src/components/ControlPanel/RenderUsers.test.tsx +145 -0
  49. package/src/components/ControlPanel/RenderUsers.tsx +263 -0
  50. package/src/components/ControlPanel/SecretReveal.scss +32 -0
  51. package/src/components/ControlPanel/SecretReveal.stories.tsx +33 -0
  52. package/src/components/ControlPanel/SecretReveal.test.tsx +101 -0
  53. package/src/components/ControlPanel/SecretReveal.tsx +105 -0
  54. package/src/components/ControlPanel/UserAccount.stories.tsx +69 -0
  55. package/src/components/ControlPanel/UserAccount.test.tsx +117 -0
  56. package/src/components/ControlPanel/UserAccount.tsx +125 -0
  57. package/src/components/ControlPanel/UserAccountPanel.scss +38 -0
  58. package/src/components/ControlPanel/UserAccountPanel.stories.tsx +77 -0
  59. package/src/components/ControlPanel/UserAccountPanel.test.tsx +173 -0
  60. package/src/components/ControlPanel/UserAccountPanel.tsx +273 -0
  61. package/src/components/FirstLogin/FirstLogin.scss +12 -0
  62. package/src/components/FirstLogin/FirstLogin.stories.tsx +50 -0
  63. package/src/components/FirstLogin/FirstLogin.tsx +94 -0
  64. package/src/components/Identities/Identities.stories.tsx +51 -0
  65. package/src/components/Identities/Identities.test.tsx +134 -0
  66. package/src/components/Identities/Identities.tsx +217 -0
  67. package/src/components/Identities/IdentitiesList.scss +49 -0
  68. package/src/components/Identities/IdentitiesList.stories.tsx +71 -0
  69. package/src/components/Identities/IdentitiesList.test.tsx +218 -0
  70. package/src/components/Identities/IdentitiesList.tsx +207 -0
  71. package/src/components/Identities/ProfileEmails.scss +62 -0
  72. package/src/components/Identities/ProfileEmails.stories.tsx +75 -0
  73. package/src/components/Identities/ProfileEmails.test.tsx +132 -0
  74. package/src/components/Identities/ProfileEmails.tsx +217 -0
  75. package/src/components/Login/Login.stories.tsx +63 -0
  76. package/src/components/Login/Login.test.tsx +189 -0
  77. package/src/components/Login/Login.tsx +163 -0
  78. package/src/components/Login/LoginForm.scss +39 -0
  79. package/src/components/Login/LoginForm.stories.tsx +112 -0
  80. package/src/components/Login/LoginForm.test.tsx +483 -0
  81. package/src/components/Login/LoginForm.tsx +321 -0
  82. package/src/components/Login/LoginOverlay.scss +44 -0
  83. package/src/components/Login/LoginOverlay.stories.tsx +73 -0
  84. package/src/components/Login/LoginOverlay.test.tsx +98 -0
  85. package/src/components/Login/LoginOverlay.tsx +107 -0
  86. package/src/components/Login/LoginPanel.scss +50 -0
  87. package/src/components/Login/LoginPanel.stories.tsx +40 -0
  88. package/src/components/Login/LoginPanel.tsx +60 -0
  89. package/src/components/Login/MagicLinkForm.scss +25 -0
  90. package/src/components/Login/MagicLinkForm.stories.tsx +27 -0
  91. package/src/components/Login/MagicLinkForm.tsx +133 -0
  92. package/src/components/Login/PasswordForm.scss +82 -0
  93. package/src/components/Login/PasswordForm.stories.tsx +26 -0
  94. package/src/components/Login/PasswordForm.tsx +159 -0
  95. package/src/components/Login/ProviderButton.scss +103 -0
  96. package/src/components/Login/ProviderButton.stories.tsx +74 -0
  97. package/src/components/Login/ProviderButton.test.tsx +99 -0
  98. package/src/components/Login/ProviderButton.tsx +97 -0
  99. package/src/components/ProfileGate/ProfileGate.stories.tsx +118 -0
  100. package/src/components/ProfileGate/ProfileGate.test.tsx +370 -0
  101. package/src/components/ProfileGate/ProfileGate.tsx +172 -0
  102. package/src/components/Toolbar/PersonalTools.stories.tsx +111 -0
  103. package/src/components/Toolbar/PersonalTools.test.tsx +165 -0
  104. package/src/components/Toolbar/PersonalTools.tsx +177 -0
  105. package/src/components/Toolbar/Toolbar.jsx +766 -0
  106. package/src/components/UserMenu/ApplicationsMenuItem.stories.tsx +62 -0
  107. package/src/components/UserMenu/ApplicationsMenuItem.test.tsx +109 -0
  108. package/src/components/UserMenu/ApplicationsMenuItem.tsx +82 -0
  109. package/src/components/UserMenu/IdentitiesMenuItem.stories.tsx +35 -0
  110. package/src/components/UserMenu/IdentitiesMenuItem.test.tsx +103 -0
  111. package/src/components/UserMenu/IdentitiesMenuItem.tsx +34 -0
  112. package/src/components/UserMenu/MenuItem.stories.tsx +32 -0
  113. package/src/components/UserMenu/MenuItem.tsx +48 -0
  114. package/src/components/UserMenu/ProfileMenuItem.stories.tsx +76 -0
  115. package/src/components/UserMenu/ProfileMenuItem.test.tsx +90 -0
  116. package/src/components/UserMenu/ProfileMenuItem.tsx +60 -0
  117. package/src/components/UserMenu/UserAvatar.scss +23 -0
  118. package/src/components/UserMenu/UserAvatar.stories.tsx +121 -0
  119. package/src/components/UserMenu/UserAvatar.test.tsx +120 -0
  120. package/src/components/UserMenu/UserAvatar.tsx +119 -0
  121. package/src/components/UserMenu/UserMenuPlugs.stories.tsx +101 -0
  122. package/src/components/UserMenu/UserMenuPlugs.test.tsx +202 -0
  123. package/src/components/UserMenu/UserMenuPlugs.tsx +142 -0
  124. package/src/components/UserMenu/UserProfileLoader.test.tsx +211 -0
  125. package/src/components/UserMenu/UserProfileLoader.tsx +54 -0
  126. package/src/components/Views/GroupView.scss +36 -0
  127. package/src/components/Views/GroupView.stories.tsx +98 -0
  128. package/src/components/Views/GroupView.test.tsx +137 -0
  129. package/src/components/Views/GroupView.tsx +178 -0
  130. package/src/components/Views/ProfileView.scss +27 -0
  131. package/src/components/Views/ProfileView.stories.tsx +34 -0
  132. package/src/components/Views/ProfileView.test.tsx +82 -0
  133. package/src/components/Views/ProfileView.tsx +100 -0
  134. package/src/components/Widgets/ProviderIconWidget.scss +21 -0
  135. package/src/components/Widgets/ProviderIconWidget.stories.tsx +30 -0
  136. package/src/components/Widgets/ProviderIconWidget.test.tsx +55 -0
  137. package/src/components/Widgets/ProviderIconWidget.tsx +143 -0
  138. package/src/config/menu.ts +35 -0
  139. package/src/config/reducers.ts +7 -0
  140. package/src/config/routes.test.ts +177 -0
  141. package/src/config/routes.ts +143 -0
  142. package/src/config/settings.test.ts +34 -0
  143. package/src/config/settings.ts +72 -0
  144. package/src/config/views.ts +30 -0
  145. package/src/config/widgets.ts +25 -0
  146. package/src/constants/ActionTypes.ts +42 -0
  147. package/src/constants/vocabularies.ts +20 -0
  148. package/src/customizations/volto/components/manage/Controlpanels/Users/RenderUsers.tsx +23 -0
  149. package/src/customizations/volto/components/manage/Toolbar/PersonalTools.tsx +21 -0
  150. package/src/customizations/volto/components/manage/Toolbar/Toolbar.jsx +21 -0
  151. package/src/helpers/avatar.test.ts +79 -0
  152. package/src/helpers/avatar.ts +85 -0
  153. package/src/helpers/callback.test.ts +60 -0
  154. package/src/helpers/callback.ts +71 -0
  155. package/src/helpers/clientSchema.test.ts +182 -0
  156. package/src/helpers/clientSchema.ts +181 -0
  157. package/src/helpers/firstLogin.test.ts +90 -0
  158. package/src/helpers/firstLogin.ts +76 -0
  159. package/src/helpers/groupmap.test.ts +81 -0
  160. package/src/helpers/groupmap.ts +50 -0
  161. package/src/helpers/identities.ts +22 -0
  162. package/src/helpers/navigate.test.ts +132 -0
  163. package/src/helpers/navigate.ts +126 -0
  164. package/src/helpers/profileGate.test.ts +150 -0
  165. package/src/helpers/profileGate.ts +191 -0
  166. package/src/helpers/profileSource.ts +41 -0
  167. package/src/helpers/propertymap.test.ts +71 -0
  168. package/src/helpers/propertymap.ts +43 -0
  169. package/src/helpers/providerSchema.test.ts +410 -0
  170. package/src/helpers/providerSchema.ts +362 -0
  171. package/src/helpers/returnUrl.test.ts +42 -0
  172. package/src/helpers/returnUrl.ts +41 -0
  173. package/src/helpers/rowmap.ts +110 -0
  174. package/src/helpers/showPloneLogin.test.ts +78 -0
  175. package/src/helpers/showPloneLogin.ts +55 -0
  176. package/src/helpers/token.test.ts +62 -0
  177. package/src/helpers/token.ts +71 -0
  178. package/src/index.ts +22 -0
  179. package/src/reducers/index.ts +369 -0
  180. package/src/reducers/reducers.test.ts +369 -0
  181. package/src/stories/fixtures.tsx +640 -0
  182. package/src/storybook/withPage.test.tsx +90 -0
  183. package/src/storybook/withPage.tsx +110 -0
  184. package/src/storybook/withUserMenu.test.tsx +78 -0
  185. package/src/storybook/withUserMenu.tsx +136 -0
  186. package/src/styles.css +262 -0
  187. package/src/testing.tsx +86 -0
  188. package/src/types.ts +441 -0
  189. package/towncrier.toml +38 -0
  190. package/tsconfig.json +22 -0
  191. package/vitest.config.mjs +47 -0
@@ -0,0 +1,15 @@
1
+ {% if sections[""] %}
2
+ {% for category, val in definitions.items() if category in sections[""] %}
3
+
4
+ ### {{ definitions[category]['name'] }}
5
+
6
+ {% for text, values in sections[""][category].items() %}
7
+ - {{ text }} {{ values|join(', ') }}
8
+ {% endfor %}
9
+
10
+ {% endfor %}
11
+ {% else %}
12
+ No significant changes.
13
+
14
+
15
+ {% endif %}
package/news/.gitkeep ADDED
File without changes
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@plone-collective/volto-identity",
3
+ "version": "1.0.0-alpha.1",
4
+ "description": "Volto add-on for multi-provider external authentication in Plone, with identity linking",
5
+ "main": "src/index.ts",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "volto-addon",
9
+ "volto",
10
+ "plone",
11
+ "react"
12
+ ],
13
+ "author": "Érico Andrei",
14
+ "homepage": "https://collective.github.io/pas-plugins-identity/",
15
+ "bugs": {
16
+ "url": "https://github.com/collective/pas-plugins-identity/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git@github.com:collective/pas-plugins-identity"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "addons": [],
26
+ "theme": "",
27
+ "dependencies": {},
28
+ "peerDependencies": {
29
+ "react": "^18.2.0",
30
+ "react-dom": "^18.2.0",
31
+ "react-redux": "^8.1.2",
32
+ "react-aria-components": "^1.17.0",
33
+ "react-router-dom": "^5.2.0",
34
+ "@plone/components": "4.2.1"
35
+ },
36
+ "devDependencies": {
37
+ "@types/react": "^18.3.1",
38
+ "@types/react-dom": "^18.3.1",
39
+ "typescript": "^5.7.3",
40
+ "@testing-library/react": "^16.2.0",
41
+ "release-it": "^19.0.5",
42
+ "vitest": "^3.1.2",
43
+ "react-redux": "8.1.2",
44
+ "react-intl": "3.12.1",
45
+ "react-toastify": "5.5.0",
46
+ "semantic-ui-react": "2.1.5",
47
+ "react-router-dom": "5.2.0",
48
+ "@types/react-redux": "^7.1.34",
49
+ "@types/react-router-dom": "^5.3.3",
50
+ "react-aria-components": "^1.17.0",
51
+ "react-cookie": "8.1.2",
52
+ "@storybook/react": "^8.0.4",
53
+ "@plone/scripts": "4.0.1",
54
+ "@plone/types": "2.0.1",
55
+ "@plone/components": "4.2.1",
56
+ "@plone/registry": "3.0.1"
57
+ },
58
+ "scripts": {
59
+ "typecheck": "tsc --noEmit -p tsconfig.json",
60
+ "i18n": "rm -rf build/messages && NODE_ENV=production i18n --addon",
61
+ "dry-release": "release-it --dry-run",
62
+ "release": "release-it",
63
+ "release-major-alpha": "release-it major --preRelease=alpha",
64
+ "release-alpha": "release-it --preRelease=alpha"
65
+ }
66
+ }
File without changes
@@ -0,0 +1,226 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ completeCallback,
5
+ confirmMagicLink,
6
+ createClient,
7
+ deleteClient,
8
+ getMyProfile,
9
+ listClients,
10
+ listKeys,
11
+ listLoginProviders,
12
+ rotateClientSecret,
13
+ rotateKey,
14
+ sendMagicLink,
15
+ setPreferredEmail,
16
+ getUserProfile,
17
+ listIdentities,
18
+ startLinking,
19
+ startProviderLogin,
20
+ updateClient,
21
+ } from './index';
22
+
23
+ describe('listLoginProviders', () => {
24
+ it('reads the listing', () => {
25
+ expect(listLoginProviders().request).toEqual({
26
+ op: 'get',
27
+ path: '/@login-providers',
28
+ });
29
+ });
30
+ });
31
+
32
+ describe('listIdentities', () => {
33
+ it('reads the listing', () => {
34
+ expect(listIdentities().request.path).toBe('/@identities');
35
+ });
36
+
37
+ it('can ask for the providers alongside, in one request', () => {
38
+ expect(listIdentities(true).request.path).toBe(
39
+ '/@identities?expand=login-providers',
40
+ );
41
+ });
42
+ });
43
+
44
+ describe('getUserProfile', () => {
45
+ it("reads Plone's own user endpoint", () => {
46
+ // Not an endpoint of this package's own: the serializer adds fields to
47
+ // the one Plone already serves rather than inventing a parallel view.
48
+ expect(getUserProfile('alice').request.path).toBe('/@users/alice');
49
+ });
50
+ });
51
+
52
+ describe('startProviderLogin', () => {
53
+ it('addresses one provider', () => {
54
+ expect(startProviderLogin('dex').request.path).toBe(
55
+ '/@login-providers/dex',
56
+ );
57
+ });
58
+
59
+ it('carries came_from when there is one', () => {
60
+ const { path } = startProviderLogin('dex', '/some/page').request;
61
+
62
+ expect(path).toBe('/@login-providers/dex?came_from=%2Fsome%2Fpage');
63
+ });
64
+
65
+ it('encodes came_from rather than pasting it in', () => {
66
+ const { path } = startProviderLogin('dex', '/a page?with=query').request;
67
+
68
+ // An unencoded value would end the query string early and silently drop
69
+ // the rest of the target.
70
+ expect(path).not.toContain(' ');
71
+ expect(path).toContain('%3Fwith%3Dquery');
72
+ });
73
+
74
+ it('omits the parameter entirely when there is nothing to carry', () => {
75
+ expect(startProviderLogin('dex', '').request.path).toBe(
76
+ '/@login-providers/dex',
77
+ );
78
+ });
79
+ });
80
+
81
+ describe('completeCallback', () => {
82
+ it('posts all three parts', () => {
83
+ const { op, path, data } = completeCallback(
84
+ 'dex',
85
+ 'the-code',
86
+ 'the-state',
87
+ ).request;
88
+
89
+ expect(op).toBe('post');
90
+ expect(path).toBe('/@identity-callback');
91
+ expect(data).toEqual({
92
+ provider: 'dex',
93
+ code: 'the-code',
94
+ state: 'the-state',
95
+ });
96
+ });
97
+ });
98
+
99
+ describe('magic link', () => {
100
+ it('sends an address', () => {
101
+ const { op, path, data } = sendMagicLink('erico@plone.org').request;
102
+
103
+ expect(op).toBe('post');
104
+ expect(path).toBe('/@magic-link');
105
+ expect(data).toEqual({ email: 'erico@plone.org' });
106
+ });
107
+
108
+ it('confirms a token', () => {
109
+ const { path, data } = confirmMagicLink('tok').request;
110
+
111
+ expect(path).toBe('/@magic-link-confirm');
112
+ expect(data).toEqual({ token: 'tok' });
113
+ });
114
+ });
115
+
116
+ describe('getMyProfile', () => {
117
+ it('reads the routing endpoint', () => {
118
+ expect(getMyProfile().request).toEqual({
119
+ op: 'get',
120
+ path: '/@my-profile',
121
+ });
122
+ });
123
+ });
124
+
125
+ describe('setPreferredEmail', () => {
126
+ it('patches the profile with the whole list', () => {
127
+ // The whole list, because `emails` is a field: a PATCH carrying one
128
+ // address would replace the list with it rather than move it.
129
+ expect(
130
+ setPreferredEmail('/identity-profiles/alice', [
131
+ 'alice@example.org',
132
+ 'alice@example.com',
133
+ ]).request,
134
+ ).toEqual({
135
+ op: 'patch',
136
+ path: '/identity-profiles/alice',
137
+ data: { emails: ['alice@example.org', 'alice@example.com'] },
138
+ });
139
+ });
140
+ });
141
+
142
+ describe('the OAuth client registry', () => {
143
+ it('lists clients', () => {
144
+ expect(listClients().request).toEqual({
145
+ op: 'get',
146
+ path: '/@identity-clients',
147
+ });
148
+ });
149
+
150
+ it('registers one', () => {
151
+ const { op, path, data } = createClient({ client_id: 'app' }).request;
152
+
153
+ expect(op).toBe('post');
154
+ expect(path).toBe('/@identity-clients');
155
+ expect(data).toEqual({ client_id: 'app' });
156
+ });
157
+
158
+ it('amends one', () => {
159
+ const { op, path, data } = updateClient('app', { enabled: false }).request;
160
+
161
+ expect(op).toBe('patch');
162
+ expect(path).toBe('/@identity-clients/app');
163
+ expect(data).toEqual({ enabled: false });
164
+ });
165
+
166
+ it('unregisters one', () => {
167
+ const { op, path } = deleteClient('app').request;
168
+
169
+ expect(op).toBe('del');
170
+ expect(path).toBe('/@identity-clients/app');
171
+ });
172
+
173
+ it('rotates a secret', () => {
174
+ const { op, path } = rotateClientSecret('app').request;
175
+
176
+ expect(op).toBe('post');
177
+ expect(path).toBe('/@identity-clients/app/rotate-secret');
178
+ });
179
+
180
+ it('escapes a client id with a slash in it', () => {
181
+ // A client id is operator-supplied and is not validated as a URL
182
+ // segment, so an unescaped one would silently address a different path.
183
+ expect(deleteClient('a/b').request.path).toBe('/@identity-clients/a%2Fb');
184
+ });
185
+ });
186
+
187
+ describe('the signing key ring', () => {
188
+ it('reads the ring', () => {
189
+ expect(listKeys().request).toEqual({
190
+ op: 'get',
191
+ path: '/@identity-keys',
192
+ });
193
+ });
194
+
195
+ it('rotates the key', () => {
196
+ const { op, path } = rotateKey().request;
197
+
198
+ expect(op).toBe('post');
199
+ expect(path).toBe('/@identity-keys/rotate');
200
+ });
201
+ });
202
+
203
+ describe('startLinking', () => {
204
+ it('names the provider to link', () => {
205
+ expect(startLinking('github', '/identities').request.data).toEqual({
206
+ provider: 'github',
207
+ came_from: '/identities',
208
+ });
209
+ });
210
+
211
+ it('sends no address for a redirect provider', () => {
212
+ // The backend takes none, and an empty one would be a malformed request
213
+ // rather than an omitted field.
214
+ expect(startLinking('github').request.data).not.toHaveProperty('email');
215
+ });
216
+
217
+ it('carries the address for the email provider', () => {
218
+ expect(
219
+ startLinking('email', '/identities', 'erico@plone.org').request.data,
220
+ ).toEqual({
221
+ provider: 'email',
222
+ came_from: '/identities',
223
+ email: 'erico@plone.org',
224
+ });
225
+ });
226
+ });