core: add UserSelfSerializer and separate method for users to update themselves with limited fields
rework user settings page to better use form closes #1227 Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> # Conflicts: # authentik/core/api/users.py # web/src/elements/forms/ModelForm.ts # web/src/pages/user-settings/UserDetailsPage.ts # web/src/pages/user-settings/UserSettingsPage.ts
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -200,4 +200,4 @@ media/ | |||||||
| *mmdb | *mmdb | ||||||
|  |  | ||||||
| .idea/ | .idea/ | ||||||
| api/ | /api/ | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_field | |||||||
| from guardian.utils import get_anonymous_user | from guardian.utils import get_anonymous_user | ||||||
| from rest_framework.decorators import action | from rest_framework.decorators import action | ||||||
| from rest_framework.fields import CharField, JSONField, SerializerMethodField | from rest_framework.fields import CharField, JSONField, SerializerMethodField | ||||||
|  | from rest_framework.permissions import IsAuthenticated | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import ( | from rest_framework.serializers import ( | ||||||
| @ -62,12 +63,40 @@ class UserSerializer(ModelSerializer): | |||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserSelfSerializer(ModelSerializer): | ||||||
|  |     """User Serializer for information a user can retrieve about themselves and | ||||||
|  |     update about themselves""" | ||||||
|  |  | ||||||
|  |     is_superuser = BooleanField(read_only=True) | ||||||
|  |     avatar = CharField(read_only=True) | ||||||
|  |     groups = ListSerializer(child=GroupSerializer(), read_only=True, source="ak_groups") | ||||||
|  |     uid = CharField(read_only=True) | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |  | ||||||
|  |         model = User | ||||||
|  |         fields = [ | ||||||
|  |             "pk", | ||||||
|  |             "username", | ||||||
|  |             "name", | ||||||
|  |             "is_active", | ||||||
|  |             "is_superuser", | ||||||
|  |             "groups", | ||||||
|  |             "email", | ||||||
|  |             "avatar", | ||||||
|  |             "uid", | ||||||
|  |         ] | ||||||
|  |         extra_kwargs = { | ||||||
|  |             "is_active": {"read_only": True}, | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
| class SessionUserSerializer(PassiveSerializer): | class SessionUserSerializer(PassiveSerializer): | ||||||
|     """Response for the /user/me endpoint, returns the currently active user (as `user` property) |     """Response for the /user/me endpoint, returns the currently active user (as `user` property) | ||||||
|     and, if this user is being impersonated, the original user in the `original` property.""" |     and, if this user is being impersonated, the original user in the `original` property.""" | ||||||
|  |  | ||||||
|     user = UserSerializer() |     user = UserSelfSerializer() | ||||||
|     original = UserSerializer(required=False) |     original = UserSelfSerializer(required=False) | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserMetricsSerializer(PassiveSerializer): | class UserMetricsSerializer(PassiveSerializer): | ||||||
| @ -158,12 +187,36 @@ class UserViewSet(UsedByMixin, ModelViewSet): | |||||||
|             data={"user": UserSerializer(request.user).data} |             data={"user": UserSerializer(request.user).data} | ||||||
|         ) |         ) | ||||||
|         if SESSION_IMPERSONATE_USER in request._request.session: |         if SESSION_IMPERSONATE_USER in request._request.session: | ||||||
|             serializer.initial_data["original"] = UserSerializer( |             serializer.initial_data["original"] = UserSelfSerializer( | ||||||
|                 request._request.session[SESSION_IMPERSONATE_ORIGINAL_USER] |                 request._request.session[SESSION_IMPERSONATE_ORIGINAL_USER] | ||||||
|             ).data |             ).data | ||||||
|         serializer.is_valid() |         serializer.is_valid() | ||||||
|         return Response(serializer.data) |         return Response(serializer.data) | ||||||
|  |  | ||||||
|  |     @extend_schema( | ||||||
|  |         request=UserSelfSerializer, responses={200: SessionUserSerializer(many=False)} | ||||||
|  |     ) | ||||||
|  |     @action( | ||||||
|  |         methods=["PUT"], | ||||||
|  |         detail=False, | ||||||
|  |         pagination_class=None, | ||||||
|  |         filter_backends=[], | ||||||
|  |         permission_classes=[IsAuthenticated], | ||||||
|  |     ) | ||||||
|  |     def update_self(self, request: Request) -> Response: | ||||||
|  |         """Allow users to change information on their own profile""" | ||||||
|  |         data = UserSelfSerializer( | ||||||
|  |             instance=User.objects.get(pk=request.user.pk), data=request.data | ||||||
|  |         ) | ||||||
|  |         if not data.is_valid(): | ||||||
|  |             return Response(data.errors) | ||||||
|  |         new_user = data.save() | ||||||
|  |         # If we're impersonating, we need to update that user object | ||||||
|  |         # since it caches the full object | ||||||
|  |         if SESSION_IMPERSONATE_USER in request.session: | ||||||
|  |             request.session[SESSION_IMPERSONATE_USER] = new_user | ||||||
|  |         return self.me(request) | ||||||
|  |  | ||||||
|     @permission_required("authentik_core.view_user", ["authentik_events.view_event"]) |     @permission_required("authentik_core.view_user", ["authentik_events.view_event"]) | ||||||
|     @extend_schema(responses={200: UserMetricsSerializer(many=False)}) |     @extend_schema(responses={200: UserMetricsSerializer(many=False)}) | ||||||
|     @action(detail=True, pagination_class=None, filter_backends=[]) |     @action(detail=True, pagination_class=None, filter_backends=[]) | ||||||
|  | |||||||
							
								
								
									
										112
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								schema.yml
									
									
									
									
									
								
							| @ -3185,6 +3185,38 @@ paths: | |||||||
|           $ref: '#/components/schemas/ValidationError' |           $ref: '#/components/schemas/ValidationError' | ||||||
|         '403': |         '403': | ||||||
|           $ref: '#/components/schemas/GenericError' |           $ref: '#/components/schemas/GenericError' | ||||||
|  |   /api/v2beta/core/users/update_self/: | ||||||
|  |     put: | ||||||
|  |       operationId: core_users_update_self_update | ||||||
|  |       description: Allow users to change information on their own profile | ||||||
|  |       tags: | ||||||
|  |       - core | ||||||
|  |       requestBody: | ||||||
|  |         content: | ||||||
|  |           application/json: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/UserSelfRequest' | ||||||
|  |           application/x-www-form-urlencoded: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/UserSelfRequest' | ||||||
|  |           multipart/form-data: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/UserSelfRequest' | ||||||
|  |         required: true | ||||||
|  |       security: | ||||||
|  |       - authentik: [] | ||||||
|  |       - cookieAuth: [] | ||||||
|  |       responses: | ||||||
|  |         '200': | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/SessionUser' | ||||||
|  |           description: '' | ||||||
|  |         '400': | ||||||
|  |           $ref: '#/components/schemas/ValidationError' | ||||||
|  |         '403': | ||||||
|  |           $ref: '#/components/schemas/GenericError' | ||||||
|   /api/v2beta/crypto/certificatekeypairs/: |   /api/v2beta/crypto/certificatekeypairs/: | ||||||
|     get: |     get: | ||||||
|       operationId: crypto_certificatekeypairs_list |       operationId: crypto_certificatekeypairs_list | ||||||
| @ -27577,9 +27609,9 @@ components: | |||||||
|         and, if this user is being impersonated, the original user in the `original` property. |         and, if this user is being impersonated, the original user in the `original` property. | ||||||
|       properties: |       properties: | ||||||
|         user: |         user: | ||||||
|           $ref: '#/components/schemas/User' |           $ref: '#/components/schemas/UserSelf' | ||||||
|         original: |         original: | ||||||
|           $ref: '#/components/schemas/User' |           $ref: '#/components/schemas/UserSelf' | ||||||
|       required: |       required: | ||||||
|       - user |       - user | ||||||
|     SetIconRequest: |     SetIconRequest: | ||||||
| @ -28478,6 +28510,82 @@ components: | |||||||
|       required: |       required: | ||||||
|       - name |       - name | ||||||
|       - username |       - username | ||||||
|  |     UserSelf: | ||||||
|  |       type: object | ||||||
|  |       description: |- | ||||||
|  |         User Serializer for information a user can retrieve about themselves and | ||||||
|  |         update about themselves | ||||||
|  |       properties: | ||||||
|  |         pk: | ||||||
|  |           type: integer | ||||||
|  |           readOnly: true | ||||||
|  |           title: ID | ||||||
|  |         username: | ||||||
|  |           type: string | ||||||
|  |           description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ | ||||||
|  |             only. | ||||||
|  |           pattern: ^[\w.@+-]+$ | ||||||
|  |           maxLength: 150 | ||||||
|  |         name: | ||||||
|  |           type: string | ||||||
|  |           description: User's display name. | ||||||
|  |         is_active: | ||||||
|  |           type: boolean | ||||||
|  |           readOnly: true | ||||||
|  |           title: Active | ||||||
|  |           description: Designates whether this user should be treated as active. Unselect | ||||||
|  |             this instead of deleting accounts. | ||||||
|  |         is_superuser: | ||||||
|  |           type: boolean | ||||||
|  |           readOnly: true | ||||||
|  |         groups: | ||||||
|  |           type: array | ||||||
|  |           items: | ||||||
|  |             $ref: '#/components/schemas/Group' | ||||||
|  |           readOnly: true | ||||||
|  |         email: | ||||||
|  |           type: string | ||||||
|  |           format: email | ||||||
|  |           title: Email address | ||||||
|  |           maxLength: 254 | ||||||
|  |         avatar: | ||||||
|  |           type: string | ||||||
|  |           readOnly: true | ||||||
|  |         uid: | ||||||
|  |           type: string | ||||||
|  |           readOnly: true | ||||||
|  |       required: | ||||||
|  |       - avatar | ||||||
|  |       - groups | ||||||
|  |       - is_active | ||||||
|  |       - is_superuser | ||||||
|  |       - name | ||||||
|  |       - pk | ||||||
|  |       - uid | ||||||
|  |       - username | ||||||
|  |     UserSelfRequest: | ||||||
|  |       type: object | ||||||
|  |       description: |- | ||||||
|  |         User Serializer for information a user can retrieve about themselves and | ||||||
|  |         update about themselves | ||||||
|  |       properties: | ||||||
|  |         username: | ||||||
|  |           type: string | ||||||
|  |           description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ | ||||||
|  |             only. | ||||||
|  |           pattern: ^[\w.@+-]+$ | ||||||
|  |           maxLength: 150 | ||||||
|  |         name: | ||||||
|  |           type: string | ||||||
|  |           description: User's display name. | ||||||
|  |         email: | ||||||
|  |           type: string | ||||||
|  |           format: email | ||||||
|  |           title: Email address | ||||||
|  |           maxLength: 254 | ||||||
|  |       required: | ||||||
|  |       - name | ||||||
|  |       - username | ||||||
|     UserSetting: |     UserSetting: | ||||||
|       type: object |       type: object | ||||||
|       description: Serializer for User settings for stages and sources |       description: Serializer for User settings for stages and sources | ||||||
|  | |||||||
| @ -3,19 +3,21 @@ import { EVENT_REFRESH } from "../../constants"; | |||||||
| import { Form } from "./Form"; | import { Form } from "./Form"; | ||||||
|  |  | ||||||
| export abstract class ModelForm<T, PKT extends string | number> extends Form<T> { | export abstract class ModelForm<T, PKT extends string | number> extends Form<T> { | ||||||
|  |     viewportCheck = true; | ||||||
|  |  | ||||||
|     abstract loadInstance(pk: PKT): Promise<T>; |     abstract loadInstance(pk: PKT): Promise<T>; | ||||||
|  |  | ||||||
|     @property({attribute: false}) |     @property({attribute: false}) | ||||||
|     set instancePk(value: PKT) { |     set instancePk(value: PKT) { | ||||||
|         this._instancePk = value; |         this._instancePk = value; | ||||||
|         if (this.isInViewport) { |         if (this.viewportCheck && !this.isInViewport) { | ||||||
|             this.loadInstance(value).then(instance => { |             return; | ||||||
|  |         } | ||||||
|  |         this.loadInstance(value).then((instance) => { | ||||||
|             this.instance = instance; |             this.instance = instance; | ||||||
|             this.requestUpdate(); |             this.requestUpdate(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private _instancePk?: PKT; |     private _instancePk?: PKT; | ||||||
|  |  | ||||||
|  | |||||||
| @ -1077,7 +1077,7 @@ msgstr "Delete Refresh Code" | |||||||
| msgid "Delete Session" | msgid "Delete Session" | ||||||
| msgstr "Delete Session" | msgstr "Delete Session" | ||||||
|  |  | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| msgid "Delete account" | msgid "Delete account" | ||||||
| msgstr "Delete account" | msgstr "Delete account" | ||||||
|  |  | ||||||
| @ -1297,7 +1297,7 @@ msgstr "Either no applications are defined, or you don't have access to any." | |||||||
| #: src/flows/stages/identification/IdentificationStage.ts | #: src/flows/stages/identification/IdentificationStage.ts | ||||||
| #: src/pages/events/TransportForm.ts | #: src/pages/events/TransportForm.ts | ||||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | #: src/pages/stages/identification/IdentificationStageForm.ts | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/users/UserForm.ts | #: src/pages/users/UserForm.ts | ||||||
| #: src/pages/users/UserViewPage.ts | #: src/pages/users/UserViewPage.ts | ||||||
| msgid "Email" | msgid "Email" | ||||||
| @ -1434,7 +1434,6 @@ msgstr "Everything is ok." | |||||||
| msgid "Exception" | msgid "Exception" | ||||||
| msgstr "Exception" | msgstr "Exception" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts |  | ||||||
| #: src/pages/flows/FlowViewPage.ts | #: src/pages/flows/FlowViewPage.ts | ||||||
| msgid "Execute" | msgid "Execute" | ||||||
| msgstr "Execute" | msgstr "Execute" | ||||||
| @ -1487,7 +1486,6 @@ msgstr "Expiry date" | |||||||
| msgid "Explicit Consent" | msgid "Explicit Consent" | ||||||
| msgstr "Explicit Consent" | msgstr "Explicit Consent" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts |  | ||||||
| #: src/pages/flows/FlowViewPage.ts | #: src/pages/flows/FlowViewPage.ts | ||||||
| msgid "Export" | msgid "Export" | ||||||
| msgstr "Export" | msgstr "Export" | ||||||
| @ -2113,7 +2111,7 @@ msgstr "Load servers" | |||||||
| #: src/flows/stages/prompt/PromptStage.ts | #: src/flows/stages/prompt/PromptStage.ts | ||||||
| #: src/pages/applications/ApplicationViewPage.ts | #: src/pages/applications/ApplicationViewPage.ts | ||||||
| #: src/pages/applications/ApplicationViewPage.ts | #: src/pages/applications/ApplicationViewPage.ts | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/utils.ts | #: src/utils.ts | ||||||
| msgid "Loading" | msgid "Loading" | ||||||
| msgstr "Loading" | msgstr "Loading" | ||||||
| @ -2402,7 +2400,7 @@ msgstr "My Applications" | |||||||
| #: src/pages/stages/user_login/UserLoginStageForm.ts | #: src/pages/stages/user_login/UserLoginStageForm.ts | ||||||
| #: src/pages/stages/user_logout/UserLogoutStageForm.ts | #: src/pages/stages/user_logout/UserLogoutStageForm.ts | ||||||
| #: src/pages/stages/user_write/UserWriteStageForm.ts | #: src/pages/stages/user_write/UserWriteStageForm.ts | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/users/UserForm.ts | #: src/pages/users/UserForm.ts | ||||||
| #: src/pages/users/UserListPage.ts | #: src/pages/users/UserListPage.ts | ||||||
| #: src/pages/users/UserViewPage.ts | #: src/pages/users/UserViewPage.ts | ||||||
| @ -3118,7 +3116,7 @@ msgstr "Request token URL" | |||||||
| msgid "Required" | msgid "Required" | ||||||
| msgstr "Required" | msgstr "Required" | ||||||
|  |  | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/users/UserForm.ts | #: src/pages/users/UserForm.ts | ||||||
| msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only." | msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only." | ||||||
| msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only." | msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only." | ||||||
| @ -3765,7 +3763,7 @@ msgstr "Successfully updated binding." | |||||||
| msgid "Successfully updated certificate-key pair." | msgid "Successfully updated certificate-key pair." | ||||||
| msgstr "Successfully updated certificate-key pair." | msgstr "Successfully updated certificate-key pair." | ||||||
|  |  | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| msgid "Successfully updated details." | msgid "Successfully updated details." | ||||||
| msgstr "Successfully updated details." | msgstr "Successfully updated details." | ||||||
|  |  | ||||||
| @ -4297,7 +4295,7 @@ msgstr "Up-to-date!" | |||||||
| #: src/pages/stages/StageListPage.ts | #: src/pages/stages/StageListPage.ts | ||||||
| #: src/pages/stages/prompt/PromptListPage.ts | #: src/pages/stages/prompt/PromptListPage.ts | ||||||
| #: src/pages/tenants/TenantListPage.ts | #: src/pages/tenants/TenantListPage.ts | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | ||||||
| #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | ||||||
| #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | ||||||
| @ -4400,7 +4398,7 @@ msgstr "Update User" | |||||||
| msgid "Update available" | msgid "Update available" | ||||||
| msgstr "Update available" | msgstr "Update available" | ||||||
|  |  | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSettingsPage.ts | ||||||
| msgid "Update details" | msgid "Update details" | ||||||
| msgstr "Update details" | msgstr "Update details" | ||||||
|  |  | ||||||
| @ -4533,7 +4531,7 @@ msgstr "User {0}" | |||||||
| msgid "User's avatar" | msgid "User's avatar" | ||||||
| msgstr "User's avatar" | msgstr "User's avatar" | ||||||
|  |  | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/users/UserForm.ts | #: src/pages/users/UserForm.ts | ||||||
| msgid "User's display name." | msgid "User's display name." | ||||||
| msgstr "User's display name." | msgstr "User's display name." | ||||||
| @ -4553,7 +4551,7 @@ msgstr "Userinfo URL" | |||||||
| #: src/flows/stages/identification/IdentificationStage.ts | #: src/flows/stages/identification/IdentificationStage.ts | ||||||
| #: src/pages/policies/reputation/UserReputationListPage.ts | #: src/pages/policies/reputation/UserReputationListPage.ts | ||||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | #: src/pages/stages/identification/IdentificationStageForm.ts | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/users/UserForm.ts | #: src/pages/users/UserForm.ts | ||||||
| #: src/pages/users/UserViewPage.ts | #: src/pages/users/UserViewPage.ts | ||||||
| msgid "Username" | msgid "Username" | ||||||
|  | |||||||
| @ -1071,7 +1071,7 @@ msgstr "" | |||||||
| msgid "Delete Session" | msgid "Delete Session" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| msgid "Delete account" | msgid "Delete account" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @ -1289,7 +1289,7 @@ msgstr "" | |||||||
| #: src/flows/stages/identification/IdentificationStage.ts | #: src/flows/stages/identification/IdentificationStage.ts | ||||||
| #: src/pages/events/TransportForm.ts | #: src/pages/events/TransportForm.ts | ||||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | #: src/pages/stages/identification/IdentificationStageForm.ts | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/users/UserForm.ts | #: src/pages/users/UserForm.ts | ||||||
| #: src/pages/users/UserViewPage.ts | #: src/pages/users/UserViewPage.ts | ||||||
| msgid "Email" | msgid "Email" | ||||||
| @ -1426,7 +1426,6 @@ msgstr "" | |||||||
| msgid "Exception" | msgid "Exception" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts |  | ||||||
| #: src/pages/flows/FlowViewPage.ts | #: src/pages/flows/FlowViewPage.ts | ||||||
| msgid "Execute" | msgid "Execute" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -1479,7 +1478,6 @@ msgstr "" | |||||||
| msgid "Explicit Consent" | msgid "Explicit Consent" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts |  | ||||||
| #: src/pages/flows/FlowViewPage.ts | #: src/pages/flows/FlowViewPage.ts | ||||||
| msgid "Export" | msgid "Export" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -2105,7 +2103,7 @@ msgstr "" | |||||||
| #: src/flows/stages/prompt/PromptStage.ts | #: src/flows/stages/prompt/PromptStage.ts | ||||||
| #: src/pages/applications/ApplicationViewPage.ts | #: src/pages/applications/ApplicationViewPage.ts | ||||||
| #: src/pages/applications/ApplicationViewPage.ts | #: src/pages/applications/ApplicationViewPage.ts | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/utils.ts | #: src/utils.ts | ||||||
| msgid "Loading" | msgid "Loading" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -2394,7 +2392,7 @@ msgstr "" | |||||||
| #: src/pages/stages/user_login/UserLoginStageForm.ts | #: src/pages/stages/user_login/UserLoginStageForm.ts | ||||||
| #: src/pages/stages/user_logout/UserLogoutStageForm.ts | #: src/pages/stages/user_logout/UserLogoutStageForm.ts | ||||||
| #: src/pages/stages/user_write/UserWriteStageForm.ts | #: src/pages/stages/user_write/UserWriteStageForm.ts | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/users/UserForm.ts | #: src/pages/users/UserForm.ts | ||||||
| #: src/pages/users/UserListPage.ts | #: src/pages/users/UserListPage.ts | ||||||
| #: src/pages/users/UserViewPage.ts | #: src/pages/users/UserViewPage.ts | ||||||
| @ -3110,7 +3108,7 @@ msgstr "" | |||||||
| msgid "Required" | msgid "Required" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/users/UserForm.ts | #: src/pages/users/UserForm.ts | ||||||
| msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only." | msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only." | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -3757,7 +3755,7 @@ msgstr "" | |||||||
| msgid "Successfully updated certificate-key pair." | msgid "Successfully updated certificate-key pair." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| msgid "Successfully updated details." | msgid "Successfully updated details." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @ -4282,7 +4280,7 @@ msgstr "" | |||||||
| #: src/pages/stages/StageListPage.ts | #: src/pages/stages/StageListPage.ts | ||||||
| #: src/pages/stages/prompt/PromptListPage.ts | #: src/pages/stages/prompt/PromptListPage.ts | ||||||
| #: src/pages/tenants/TenantListPage.ts | #: src/pages/tenants/TenantListPage.ts | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | ||||||
| #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | ||||||
| #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | ||||||
| @ -4385,7 +4383,7 @@ msgstr "" | |||||||
| msgid "Update available" | msgid "Update available" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSettingsPage.ts | ||||||
| msgid "Update details" | msgid "Update details" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @ -4518,7 +4516,7 @@ msgstr "" | |||||||
| msgid "User's avatar" | msgid "User's avatar" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/users/UserForm.ts | #: src/pages/users/UserForm.ts | ||||||
| msgid "User's display name." | msgid "User's display name." | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -4538,7 +4536,7 @@ msgstr "" | |||||||
| #: src/flows/stages/identification/IdentificationStage.ts | #: src/flows/stages/identification/IdentificationStage.ts | ||||||
| #: src/pages/policies/reputation/UserReputationListPage.ts | #: src/pages/policies/reputation/UserReputationListPage.ts | ||||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | #: src/pages/stages/identification/IdentificationStageForm.ts | ||||||
| #: src/pages/user-settings/UserDetailsPage.ts | #: src/pages/user-settings/UserSelfForm.ts | ||||||
| #: src/pages/users/UserForm.ts | #: src/pages/users/UserForm.ts | ||||||
| #: src/pages/users/UserViewPage.ts | #: src/pages/users/UserViewPage.ts | ||||||
| msgid "Username" | msgid "Username" | ||||||
|  | |||||||
| @ -1,100 +0,0 @@ | |||||||
| import { t } from "@lingui/macro"; |  | ||||||
| import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; |  | ||||||
| import PFCard from "@patternfly/patternfly/components/Card/card.css"; |  | ||||||
| import AKGlobal from "../../authentik.css"; |  | ||||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; |  | ||||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; |  | ||||||
| import PFForm from "@patternfly/patternfly/components/Form/form.css"; |  | ||||||
| import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; |  | ||||||
| import { CoreApi, User } from "authentik-api"; |  | ||||||
| import { me } from "../../api/Users"; |  | ||||||
| import { ifDefined } from "lit-html/directives/if-defined"; |  | ||||||
| import { DEFAULT_CONFIG, tenant } from "../../api/Config"; |  | ||||||
| import "../../elements/forms/FormElement"; |  | ||||||
| import "../../elements/EmptyState"; |  | ||||||
| import "../../elements/forms/Form"; |  | ||||||
| import "../../elements/forms/HorizontalFormElement"; |  | ||||||
| import { until } from "lit-html/directives/until"; |  | ||||||
|  |  | ||||||
| @customElement("ak-user-details") |  | ||||||
| export class UserDetailsPage extends LitElement { |  | ||||||
|  |  | ||||||
|     static get styles(): CSSResult[] { |  | ||||||
|         return [PFBase, PFCard, PFForm, PFFormControl, PFButton, AKGlobal]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @property({attribute: false}) |  | ||||||
|     user?: User; |  | ||||||
|  |  | ||||||
|     firstUpdated(): void { |  | ||||||
|         me().then((user) => { |  | ||||||
|             this.user = user.user; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     render(): TemplateResult { |  | ||||||
|         if (!this.user) { |  | ||||||
|             return html`<ak-empty-state |  | ||||||
|                 ?loading="${true}" |  | ||||||
|                 header=${t`Loading`}> |  | ||||||
|             </ak-empty-state>`; |  | ||||||
|         } |  | ||||||
|         return html`<div class="pf-c-card"> |  | ||||||
|             <div class="pf-c-card__title"> |  | ||||||
|                 ${t`Update details`} |  | ||||||
|             </div> |  | ||||||
|             <div class="pf-c-card__body"> |  | ||||||
|                 <ak-form |  | ||||||
|                     successMessage=${t`Successfully updated details.`} |  | ||||||
|                     .send=${(data: unknown) => { |  | ||||||
|                         return new CoreApi(DEFAULT_CONFIG).coreUsersUpdate({ |  | ||||||
|                             id: this.user?.pk || 0, |  | ||||||
|                             userRequest: data as User |  | ||||||
|                         }); |  | ||||||
|                     }}> |  | ||||||
|                     <form class="pf-c-form pf-m-horizontal"> |  | ||||||
|                         <ak-form-element-horizontal |  | ||||||
|                             label=${t`Username`} |  | ||||||
|                             ?required=${true} |  | ||||||
|                             name="username"> |  | ||||||
|                             <input type="text" value="${ifDefined(this.user?.username)}" class="pf-c-form-control" required> |  | ||||||
|                             <p class="pf-c-form__helper-text">${t`Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.`}</p> |  | ||||||
|                         </ak-form-element-horizontal> |  | ||||||
|                         <ak-form-element-horizontal |  | ||||||
|                             label=${t`Name`} |  | ||||||
|                             ?required=${true} |  | ||||||
|                             name="name"> |  | ||||||
|                             <input type="text" value="${ifDefined(this.user?.name)}" class="pf-c-form-control" required> |  | ||||||
|                             <p class="pf-c-form__helper-text">${t`User's display name.`}</p> |  | ||||||
|                         </ak-form-element-horizontal> |  | ||||||
|                         <ak-form-element-horizontal |  | ||||||
|                             label=${t`Email`} |  | ||||||
|                             name="email"> |  | ||||||
|                             <input type="email" value="${ifDefined(this.user?.email)}" class="pf-c-form-control"> |  | ||||||
|                         </ak-form-element-horizontal> |  | ||||||
|  |  | ||||||
|                         <div class="pf-c-form__group pf-m-action"> |  | ||||||
|                             <div class="pf-c-form__horizontal-group"> |  | ||||||
|                                 <div class="pf-c-form__actions"> |  | ||||||
|                                     <button class="pf-c-button pf-m-primary"> |  | ||||||
|                                         ${t`Update`} |  | ||||||
|                                     </button> |  | ||||||
|                                     ${until(tenant().then(tenant => { |  | ||||||
|                                         if (tenant.flowUnenrollment) { |  | ||||||
|                                             return html`<a class="pf-c-button pf-m-danger" |  | ||||||
|                                                 href="/if/flow/${tenant.flowUnenrollment}"> |  | ||||||
|                                                 ${t`Delete account`} |  | ||||||
|                                             </a>`; |  | ||||||
|                                         } |  | ||||||
|                                         return html``; |  | ||||||
|                                     }))} |  | ||||||
|                                 </div> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                     </form> |  | ||||||
|                 </ak-form> |  | ||||||
|             </div> |  | ||||||
|         </div>`; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
							
								
								
									
										100
									
								
								web/src/pages/user-settings/UserSelfForm.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								web/src/pages/user-settings/UserSelfForm.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,100 @@ | |||||||
|  | import { t } from "@lingui/macro"; | ||||||
|  | import { customElement, html, TemplateResult } from "lit-element"; | ||||||
|  | import { CoreApi, UserSelf } from "authentik-api"; | ||||||
|  | import { ifDefined } from "lit-html/directives/if-defined"; | ||||||
|  | import { DEFAULT_CONFIG, tenant } from "../../api/Config"; | ||||||
|  | import "../../elements/forms/FormElement"; | ||||||
|  | import "../../elements/EmptyState"; | ||||||
|  | import "../../elements/forms/Form"; | ||||||
|  | import "../../elements/forms/HorizontalFormElement"; | ||||||
|  | import { until } from "lit-html/directives/until"; | ||||||
|  | import { ModelForm } from "../../elements/forms/ModelForm"; | ||||||
|  |  | ||||||
|  | @customElement("ak-user-self-form") | ||||||
|  | export class UserSelfForm extends ModelForm<UserSelf, number> { | ||||||
|  |     viewportCheck = false; | ||||||
|  |  | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||||
|  |     loadInstance(pk: number): Promise<UserSelf> { | ||||||
|  |         return new CoreApi(DEFAULT_CONFIG).coreUsersMeRetrieve().then((su) => { | ||||||
|  |             return su.user; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getSuccessMessage(): string { | ||||||
|  |         return t`Successfully updated details.`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     send = (data: UserSelf): Promise<UserSelf> => { | ||||||
|  |         return new CoreApi(DEFAULT_CONFIG) | ||||||
|  |             .coreUsersUpdateSelfUpdate({ | ||||||
|  |                 userSelfRequest: data, | ||||||
|  |             }) | ||||||
|  |             .then((su) => { | ||||||
|  |                 return su.user; | ||||||
|  |             }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     renderForm(): TemplateResult { | ||||||
|  |         if (!this.instance) { | ||||||
|  |             return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`; | ||||||
|  |         } | ||||||
|  |         return html`<form class="pf-c-form pf-m-horizontal"> | ||||||
|  |             <ak-form-element-horizontal label=${t`Username`} ?required=${true} name="username"> | ||||||
|  |                 <input | ||||||
|  |                     type="text" | ||||||
|  |                     value="${ifDefined(this.instance?.username)}" | ||||||
|  |                     class="pf-c-form-control" | ||||||
|  |                     required | ||||||
|  |                 /> | ||||||
|  |                 <p class="pf-c-form__helper-text"> | ||||||
|  |                     ${t`Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.`} | ||||||
|  |                 </p> | ||||||
|  |             </ak-form-element-horizontal> | ||||||
|  |             <ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name"> | ||||||
|  |                 <input | ||||||
|  |                     type="text" | ||||||
|  |                     value="${ifDefined(this.instance?.name)}" | ||||||
|  |                     class="pf-c-form-control" | ||||||
|  |                     required | ||||||
|  |                 /> | ||||||
|  |                 <p class="pf-c-form__helper-text">${t`User's display name.`}</p> | ||||||
|  |             </ak-form-element-horizontal> | ||||||
|  |             <ak-form-element-horizontal label=${t`Email`} name="email"> | ||||||
|  |                 <input | ||||||
|  |                     type="email" | ||||||
|  |                     value="${ifDefined(this.instance?.email)}" | ||||||
|  |                     class="pf-c-form-control" | ||||||
|  |                 /> | ||||||
|  |             </ak-form-element-horizontal> | ||||||
|  |  | ||||||
|  |             <div class="pf-c-form__group pf-m-action"> | ||||||
|  |                 <div class="pf-c-form__horizontal-group"> | ||||||
|  |                     <div class="pf-c-form__actions"> | ||||||
|  |                         <button | ||||||
|  |                             @click=${(ev: Event) => { | ||||||
|  |                                 return this.submit(ev); | ||||||
|  |                             }} | ||||||
|  |                             class="pf-c-button pf-m-primary" | ||||||
|  |                         > | ||||||
|  |                             ${t`Update`} | ||||||
|  |                         </button> | ||||||
|  |                         ${until( | ||||||
|  |                             tenant().then((tenant) => { | ||||||
|  |                                 if (tenant.flowUnenrollment) { | ||||||
|  |                                     return html`<a | ||||||
|  |                                         class="pf-c-button pf-m-danger" | ||||||
|  |                                         href="/if/flow/${tenant.flowUnenrollment}" | ||||||
|  |                                     > | ||||||
|  |                                         ${t`Delete account`} | ||||||
|  |                                     </a>`; | ||||||
|  |                                 } | ||||||
|  |                                 return html``; | ||||||
|  |                             }), | ||||||
|  |                         )} | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </form>`; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -20,7 +20,7 @@ import { ifDefined } from "lit-html/directives/if-defined"; | |||||||
| import "../../elements/Tabs"; | import "../../elements/Tabs"; | ||||||
| import "../../elements/PageHeader"; | import "../../elements/PageHeader"; | ||||||
| import "./tokens/UserTokenList"; | import "./tokens/UserTokenList"; | ||||||
| import "./UserDetailsPage"; | import "./UserSelfForm"; | ||||||
| import "./settings/UserSettingsAuthenticatorDuo"; | import "./settings/UserSettingsAuthenticatorDuo"; | ||||||
| import "./settings/UserSettingsAuthenticatorStatic"; | import "./settings/UserSettingsAuthenticatorStatic"; | ||||||
| import "./settings/UserSettingsAuthenticatorTOTP"; | import "./settings/UserSettingsAuthenticatorTOTP"; | ||||||
| @ -95,8 +95,17 @@ export class UserSettingsPage extends LitElement { | |||||||
|                     description=${t`Configure settings relevant to your user profile.`}> |                     description=${t`Configure settings relevant to your user profile.`}> | ||||||
|                 </ak-page-header> |                 </ak-page-header> | ||||||
|                 <ak-tabs ?vertical="${true}" style="height: 100%;"> |                 <ak-tabs ?vertical="${true}" style="height: 100%;"> | ||||||
|                     <section slot="page-details" data-tab-title="${t`User details`}" class="pf-c-page__main-section pf-m-no-padding-mobile"> |                     <section | ||||||
|                         <ak-user-details></ak-user-details> |                         slot="page-details" | ||||||
|  |                         data-tab-title="${t`User details`}" | ||||||
|  |                         class="pf-c-page__main-section pf-m-no-padding-mobile" | ||||||
|  |                     > | ||||||
|  |                         <div class="pf-c-card"> | ||||||
|  |                             <div class="pf-c-card__title">${t`Update details`}</div> | ||||||
|  |                             <div class="pf-c-card__body"> | ||||||
|  |                                 <ak-user-self-form .instancePk=${1}></ak-user-self-form> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|                     </section> |                     </section> | ||||||
|                     <section slot="page-tokens" data-tab-title="${t`Tokens`}" class="pf-c-page__main-section pf-m-no-padding-mobile"> |                     <section slot="page-tokens" data-tab-title="${t`Tokens`}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|                         <ak-user-token-list></ak-user-token-list> |                         <ak-user-token-list></ak-user-token-list> | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer