mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 02:03:35 -04:00 
			
		
		
		
	feature: add password reset token endpoint to the admin panel (#2171)
* add password reset token endpoint to the admin panel * add None check on token * add localization message for passowrd reset link button
This commit is contained in:
		| @@ -689,6 +689,7 @@ | ||||
|     "error-cannot-delete-super-user": "Error! Cannot Delete Super User", | ||||
|     "existing-password-does-not-match": "Existing password does not match", | ||||
|     "full-name": "Full Name", | ||||
|     "generate-password-reset-link": "Generate Password Reset Link", | ||||
|     "invite-only": "Invite Only", | ||||
|     "link-id": "Link ID", | ||||
|     "link-name": "Link Name", | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { BaseCRUDAPI } from "../base/base-clients"; | ||||
| import { UnlockResults, UserIn, UserOut } from "~/lib/api/types/user"; | ||||
| import { ForgotPassword, PasswordResetToken, UnlockResults, UserIn, UserOut } from "~/lib/api/types/user"; | ||||
|  | ||||
| const prefix = "/api"; | ||||
|  | ||||
| @@ -7,6 +7,7 @@ const routes = { | ||||
|   adminUsers: `${prefix}/admin/users`, | ||||
|   adminUsersId: (tag: string) => `${prefix}/admin/users/${tag}`, | ||||
|   adminResetLockedUsers: (force: boolean) => `${prefix}/admin/users/unlock?force=${force ? "true" : "false"}`, | ||||
|   adminPasswordResetToken: `${prefix}/admin/users/password-reset-token`, | ||||
| }; | ||||
|  | ||||
| export class AdminUsersApi extends BaseCRUDAPI<UserIn, UserOut, UserOut> { | ||||
| @@ -16,4 +17,8 @@ export class AdminUsersApi extends BaseCRUDAPI<UserIn, UserOut, UserOut> { | ||||
|   async unlockAllUsers(force = false) { | ||||
|     return await this.requests.post<UnlockResults>(routes.adminResetLockedUsers(force), {}); | ||||
|   } | ||||
|  | ||||
|   async generatePasswordResetToken(payload: ForgotPassword) { | ||||
|     return await this.requests.post<PasswordResetToken>(routes.adminPasswordResetToken, payload); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -233,3 +233,6 @@ export interface UserIn { | ||||
| export interface ValidateResetToken { | ||||
|   token: string; | ||||
| } | ||||
| export interface PasswordResetToken { | ||||
|   token: string; | ||||
| } | ||||
|   | ||||
| @@ -27,6 +27,13 @@ | ||||
|             label="User Group" | ||||
|             :rules="[validators.required]" | ||||
|           ></v-select> | ||||
|           <div class="d-flex py-2 pr-2"> | ||||
|             <BaseButton type="button" :loading="generatingToken" create @click.prevent="handlePasswordReset"> | ||||
|               {{ $t("user.generate-password-reset-link") }} | ||||
|             </BaseButton> | ||||
|             <AppButtonCopy v-if="resetUrl" :copy-text="resetUrl"></AppButtonCopy> | ||||
|           </div> | ||||
|  | ||||
|           <AutoForm v-model="user" :items="userForm" update-mode /> | ||||
|         </v-card-text> | ||||
|       </v-card> | ||||
| @@ -67,6 +74,9 @@ export default defineComponent({ | ||||
|  | ||||
|     const userError = ref(false); | ||||
|  | ||||
|     const resetUrl = ref<string | null>(null); | ||||
|     const generatingToken = ref(false); | ||||
|  | ||||
|     onMounted(async () => { | ||||
|       const { data, error } = await adminApi.users.getOne(userId); | ||||
|  | ||||
| @@ -90,6 +100,20 @@ export default defineComponent({ | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     async function handlePasswordReset() { | ||||
|       if (user.value === null) return; | ||||
|       generatingToken.value = true; | ||||
|  | ||||
|       const { response, data } = await adminApi.users.generatePasswordResetToken({ email: user.value.email }); | ||||
|  | ||||
|       if (response?.status === 201 && data) { | ||||
|         const token: string = data.token; | ||||
|         resetUrl.value = `${window.location.origin}/reset-password?token=${token}`; | ||||
|       } | ||||
|  | ||||
|       generatingToken.value = false; | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|       user, | ||||
|       userError, | ||||
| @@ -98,6 +122,9 @@ export default defineComponent({ | ||||
|       handleSubmit, | ||||
|       groups, | ||||
|       validators, | ||||
|       handlePasswordReset, | ||||
|       resetUrl, | ||||
|       generatingToken, | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
|   | ||||
| @@ -10,6 +10,8 @@ from mealie.schema.response.pagination import PaginationQuery | ||||
| from mealie.schema.response.responses import ErrorResponse | ||||
| from mealie.schema.user.auth import UnlockResults | ||||
| from mealie.schema.user.user import UserIn, UserOut, UserPagination | ||||
| from mealie.schema.user.user_passwords import ForgotPassword, PasswordResetToken | ||||
| from mealie.services.user_services.password_reset_service import PasswordResetService | ||||
| from mealie.services.user_services.user_service import UserService | ||||
|  | ||||
| router = APIRouter(prefix="/users", tags=["Admin: Users"]) | ||||
| @@ -65,3 +67,12 @@ class AdminUserManagementRoutes(BaseAdminController): | ||||
|     @router.delete("/{item_id}", response_model=UserOut) | ||||
|     def delete_one(self, item_id: UUID4): | ||||
|         return self.mixins.delete_one(item_id) | ||||
|  | ||||
|     @router.post("/password-reset-token", response_model=PasswordResetToken, status_code=201) | ||||
|     def generate_token(self, email: ForgotPassword): | ||||
|         """Generates a reset token and returns it. This is an authenticated endpoint""" | ||||
|         f_service = PasswordResetService(self.session) | ||||
|         token_entry = f_service.generate_reset_token(email.email) | ||||
|         if not token_entry: | ||||
|             raise HTTPException(status_code=500, detail=ErrorResponse.respond("error while generating reset token")) | ||||
|         return PasswordResetToken(token=token_entry.token) | ||||
|   | ||||
| @@ -9,6 +9,10 @@ class ForgotPassword(MealieModel): | ||||
|     email: str | ||||
|  | ||||
|  | ||||
| class PasswordResetToken(MealieModel): | ||||
|     token: str | ||||
|  | ||||
|  | ||||
| class ValidateResetToken(MealieModel): | ||||
|     token: str | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user