✨ Password reset & user lookup API
This commit is contained in:
		
							
								
								
									
										15
									
								
								.idea/workspace.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								.idea/workspace.xml
									
									
									
										generated
									
									
									
								
							| @@ -4,18 +4,13 @@ | ||||
|     <option name="autoReloadType" value="ALL" /> | ||||
|   </component> | ||||
|   <component name="ChangeListManager"> | ||||
|     <list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":recycle: Improve notify API"> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/views/flow/password-reset.vue" afterDir="false" /> | ||||
|     <list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":sparkles: Reset password APIs"> | ||||
|       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/models/tokens.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/models/tokens.go" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/api/accounts_api.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/server/api/accounts_api.go" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/api/factors_api.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/server/api/factors_api.go" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/api/index.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/server/api/index.go" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/api/security_api.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/server/api/security_api.go" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/services/accounts.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/services/accounts.go" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/services/tokens.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/services/tokens.go" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/web/src/router/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/router/index.ts" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/web/src/views/confirm.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/views/flow/confirm.vue" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/web/src/views/flow/confirm.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/views/flow/confirm.vue" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/web/src/views/flow/password-reset.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/views/flow/password-reset.vue" afterDir="false" /> | ||||
|     </list> | ||||
|     <option name="SHOW_DIALOG" value="false" /> | ||||
|     <option name="HIGHLIGHT_CONFLICTS" value="true" /> | ||||
| @@ -161,7 +156,6 @@ | ||||
|     </option> | ||||
|   </component> | ||||
|   <component name="VcsManagerConfiguration"> | ||||
|     <MESSAGE value=":card_file_box: Add the status model" /> | ||||
|     <MESSAGE value=":bug: Authenticate wrong payload hotfix" /> | ||||
|     <MESSAGE value=":sparkles: Can pick up mfa request" /> | ||||
|     <MESSAGE value=":sparkles: Status system" /> | ||||
| @@ -186,7 +180,8 @@ | ||||
|     <MESSAGE value=":bug: Fix request body validation" /> | ||||
|     <MESSAGE value=":bug: Fix API mapping issue" /> | ||||
|     <MESSAGE value=":recycle: Improve notify API" /> | ||||
|     <option name="LAST_COMMIT_MESSAGE" value=":recycle: Improve notify API" /> | ||||
|     <MESSAGE value=":sparkles: Reset password APIs" /> | ||||
|     <option name="LAST_COMMIT_MESSAGE" value=":sparkles: Reset password APIs" /> | ||||
|   </component> | ||||
|   <component name="VgoProject"> | ||||
|     <settings-migrated>true</settings-migrated> | ||||
|   | ||||
| @@ -14,6 +14,20 @@ import ( | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| func lookupAccount(c *fiber.Ctx) error { | ||||
| 	probe := c.Query("probe") | ||||
| 	if len(probe) == 0 { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, "you must provide a probe") | ||||
| 	} | ||||
|  | ||||
| 	user, err := services.LookupAccount(probe) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(user) | ||||
| } | ||||
|  | ||||
| func getUserinfo(c *fiber.Ctx) error { | ||||
| 	if err := exts.EnsureAuthenticated(c); err != nil { | ||||
| 		return err | ||||
|   | ||||
| @@ -19,6 +19,8 @@ func MapAPIs(app *fiber.App) { | ||||
| 			notify.Put("/:notificationId/read", markNotificationRead) | ||||
| 		} | ||||
|  | ||||
| 		api.Get("/users/lookup", lookupAccount) | ||||
|  | ||||
| 		me := api.Group("/users/me").Name("Myself Operations") | ||||
| 		{ | ||||
|  | ||||
| @@ -34,8 +36,8 @@ func MapAPIs(app *fiber.App) { | ||||
| 			me.Delete("/tickets/:ticketId", killTicket) | ||||
|  | ||||
| 			me.Post("/confirm", doRegisterConfirm) | ||||
| 			me.Post("/reset-password", requestResetPassword) | ||||
| 			me.Patch("/reset-password", confirmResetPassword) | ||||
| 			me.Post("/password-reset", requestResetPassword) | ||||
| 			me.Patch("/password-reset", confirmResetPassword) | ||||
|  | ||||
| 			me.Get("/status", getMyselfStatus) | ||||
| 			me.Post("/status", setStatus) | ||||
|   | ||||
| @@ -141,10 +141,11 @@ func CheckAbleToResetPassword(user models.Account) error { | ||||
| 	if err := database.C. | ||||
| 		Where("account_id = ?", user.ID). | ||||
| 		Where("expired_at < ?", time.Now()). | ||||
| 		Where("type = ?", models.ResetPasswordMagicToken). | ||||
| 		Model(&models.MagicToken{}). | ||||
| 		Count(&count).Error; err != nil { | ||||
| 		return fmt.Errorf("unable to check reset password ability: %v", err) | ||||
| 	} else if count == 0 { | ||||
| 	} else if count > 0 { | ||||
| 		return fmt.Errorf("you requested reset password recently") | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
|         <div> | ||||
|           <v-avatar color="accent" icon="mdi-check-decagram" size="large" class="card-rounded mb-2" /> | ||||
|           <h1 class="text-2xl">Confirm registration</h1> | ||||
|           <p>Confirm your account to keep your account longer than 48 hours.</p> | ||||
|           <p>Confirm your account to unlock more abilities.</p> | ||||
|         </div> | ||||
|  | ||||
|         <v-window :touch="false" :model-value="panel" class="pa-2 mx-[-0.5rem]"> | ||||
| @@ -30,7 +30,7 @@ | ||||
|               <h1 class="font-bold text-xl">Confirmed</h1> | ||||
|               <p>You're done! We successfully confirmed your account.</p> | ||||
|  | ||||
|               <p class="mt-3">Now you can continue use Solarpass, we will redirect to dashboard you soon.</p> | ||||
|               <p class="mt-3">Now you can continue to use Solarpass, we will redirect you to dashboard soon.</p> | ||||
|             </div> | ||||
|           </v-window-item> | ||||
|         </v-window> | ||||
| @@ -77,7 +77,7 @@ async function confirm() { | ||||
|     loading.value = true | ||||
|     panel.value = "callback" | ||||
|     await readProfiles() | ||||
|     router.push({ name: "dashboard" }) | ||||
|     await router.push({ name: "dashboard" }) | ||||
|   } | ||||
|   loading.value = false | ||||
| } | ||||
|   | ||||
| @@ -3,34 +3,54 @@ | ||||
|     <v-card class="w-full max-w-[720px] overflow-auto" :loading="loading"> | ||||
|       <v-card-text class="card-grid pa-9"> | ||||
|         <div> | ||||
|           <v-avatar color="accent" icon="mdi-check-decagram" size="large" class="card-rounded mb-2" /> | ||||
|           <h1 class="text-2xl">Confirm registration</h1> | ||||
|           <p>Confirm your account to keep your account longer than 48 hours.</p> | ||||
|           <v-avatar color="accent" icon="mdi-lock-reset" size="large" class="card-rounded mb-2" /> | ||||
|           <h1 class="text-2xl">Reset password</h1> | ||||
|           <p>Reset password to get back access of your account.</p> | ||||
|         </div> | ||||
|  | ||||
|         <v-window :touch="false" :model-value="panel" class="pa-2 mx-[-0.5rem]"> | ||||
|           <v-window-item value="confirm"> | ||||
|             <div> | ||||
|               <v-expand-transition> | ||||
|                 <v-alert v-show="error" variant="tonal" type="error" class="text-xs mb-3"> | ||||
|                   Something went wrong... {{ error }} | ||||
|                 </v-alert> | ||||
|               </v-expand-transition> | ||||
|             <div class="flex items-center"> | ||||
|               <v-form class="flex-grow-1" @submit.prevent="confirm"> | ||||
|                 <v-text-field | ||||
|                   label="New Password" | ||||
|                   type="password" | ||||
|                   autocomplete="new-password" | ||||
|                   variant="solo" | ||||
|                   density="comfortable" | ||||
|                   :disabled="loading" | ||||
|                   v-model="newPassword" | ||||
|                 /> | ||||
|  | ||||
|               <v-progress-circular v-if="!error" indeterminate size="32" color="grey-darken-3" class="mb-3" /> | ||||
|                 <v-expand-transition> | ||||
|                   <v-alert v-show="error" variant="tonal" type="error" class="text-xs mb-3"> | ||||
|                     Something went wrong... {{ error }} | ||||
|                   </v-alert> | ||||
|                 </v-expand-transition> | ||||
|  | ||||
|               <h1 class="font-bold text-xl">Confirming</h1> | ||||
|               <p>We are confirming your account. Please stand by, this won't took a long time...</p> | ||||
|                 <div class="flex justify-end"> | ||||
|                   <v-btn | ||||
|                     type="submit" | ||||
|                     variant="text" | ||||
|                     color="primary" | ||||
|                     class="justify-self-end" | ||||
|                     append-icon="mdi-arrow-right" | ||||
|                     :disabled="loading" | ||||
|                   > | ||||
|                     Next | ||||
|                   </v-btn> | ||||
|                 </div> | ||||
|               </v-form> | ||||
|             </div> | ||||
|           </v-window-item> | ||||
|           <v-window-item value="callback"> | ||||
|             <div> | ||||
|               <v-icon icon="mdi-fire" size="32" color="grey-darken-3" class="mb-3" /> | ||||
|  | ||||
|               <h1 class="font-bold text-xl">Confirmed</h1> | ||||
|               <p>You're done! We successfully confirmed your account.</p> | ||||
|               <h1 class="font-bold text-xl">Applied</h1> | ||||
|               <p>The password of your account has updated successfully.</p> | ||||
|  | ||||
|               <p class="mt-3">Now you can continue use Solarpass, we will redirect to dashboard you soon.</p> | ||||
|               <p class="mt-3">Now you can continue to use Solarpass, we will redirect you to sign-in soon.</p> | ||||
|             </div> | ||||
|           </v-window-item> | ||||
|         </v-window> | ||||
| @@ -45,12 +65,10 @@ | ||||
| import { ref } from "vue" | ||||
| import { useRoute, useRouter } from "vue-router" | ||||
| import { request } from "@/scripts/request" | ||||
| import { useUserinfo } from "@/stores/userinfo" | ||||
| import Copyright from "@/components/Copyright.vue" | ||||
|  | ||||
| const route = useRoute() | ||||
| const router = useRouter() | ||||
| const { readProfiles } = useUserinfo() | ||||
|  | ||||
| const error = ref<string | null>(null) | ||||
|  | ||||
| @@ -58,17 +76,20 @@ const loading = ref(false) | ||||
|  | ||||
| const panel = ref("confirm") | ||||
|  | ||||
| const newPassword = ref("") | ||||
|  | ||||
| async function confirm() { | ||||
|   if (!route.query["tk"]) { | ||||
|   if (!route.query["code"]) { | ||||
|     error.value = "code was not exists" | ||||
|     return | ||||
|   } | ||||
|  | ||||
|   const res = await request("/api/users/me/confirm", { | ||||
|     method: "POST", | ||||
|   const res = await request("/api/users/me/password-reset", { | ||||
|     method: "PATCH", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify({ | ||||
|       code: route.query["tk"], | ||||
|       code: route.query["code"], | ||||
|       new_password: newPassword.value, | ||||
|     }), | ||||
|   }) | ||||
|   if (res.status !== 200) { | ||||
| @@ -76,13 +97,10 @@ async function confirm() { | ||||
|   } else { | ||||
|     loading.value = true | ||||
|     panel.value = "callback" | ||||
|     await readProfiles() | ||||
|     router.push({ name: "dashboard" }) | ||||
|     await router.push({ name: "auth.sign-in" }) | ||||
|   } | ||||
|   loading.value = false | ||||
| } | ||||
|  | ||||
| confirm() | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user