♻️ Update the sign in web page to the latest API
This commit is contained in:
		
							
								
								
									
										59
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <code_scheme name="Project" version="173"> | ||||
|     <HTMLCodeStyleSettings> | ||||
|       <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" /> | ||||
|     </HTMLCodeStyleSettings> | ||||
|     <JSCodeStyleSettings version="0"> | ||||
|       <option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" /> | ||||
|       <option name="FORCE_SEMICOLON_STYLE" value="true" /> | ||||
|       <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" /> | ||||
|       <option name="FORCE_QUOTE_STYlE" value="true" /> | ||||
|       <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" /> | ||||
|       <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" /> | ||||
|       <option name="SPACES_WITHIN_IMPORTS" value="true" /> | ||||
|     </JSCodeStyleSettings> | ||||
|     <TypeScriptCodeStyleSettings version="0"> | ||||
|       <option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" /> | ||||
|       <option name="FORCE_SEMICOLON_STYLE" value="true" /> | ||||
|       <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" /> | ||||
|       <option name="FORCE_QUOTE_STYlE" value="true" /> | ||||
|       <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" /> | ||||
|       <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" /> | ||||
|       <option name="SPACES_WITHIN_IMPORTS" value="true" /> | ||||
|     </TypeScriptCodeStyleSettings> | ||||
|     <VueCodeStyleSettings> | ||||
|       <option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" /> | ||||
|       <option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" /> | ||||
|     </VueCodeStyleSettings> | ||||
|     <codeStyleSettings language="HTML"> | ||||
|       <option name="SOFT_MARGINS" value="120" /> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="JavaScript"> | ||||
|       <option name="SOFT_MARGINS" value="120" /> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="TypeScript"> | ||||
|       <option name="SOFT_MARGINS" value="120" /> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="Vue"> | ||||
|       <option name="SOFT_MARGINS" value="120" /> | ||||
|       <indentOptions> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="2" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|   </code_scheme> | ||||
| </component> | ||||
							
								
								
									
										5
									
								
								.idea/codeStyles/codeStyleConfig.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.idea/codeStyles/codeStyleConfig.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <state> | ||||
|     <option name="USE_PER_PROJECT_SETTINGS" value="true" /> | ||||
|   </state> | ||||
| </component> | ||||
							
								
								
									
										73
									
								
								.idea/workspace.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										73
									
								
								.idea/workspace.xml
									
									
									
										generated
									
									
									
								
							| @@ -5,70 +5,19 @@ | ||||
|   </component> | ||||
|   <component name="ChangeListManager"> | ||||
|     <list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":ambulance: Fix nil map panic"> | ||||
|       <change afterPath="$PROJECT_DIR$/web/.eslintrc.cjs" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/.gitignore" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/.prettierrc.json" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/README.md" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/env.d.ts" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/index.html" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/package.json" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/public/favicon.png" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/assets/utils.css" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/components/Copyright.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/components/NotificationList.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/components/UserMenu.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/components/auth/AccountLocator.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/components/auth/CallbackNotify.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/components/auth/FactorApplicator.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/components/auth/FactorPicker.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/index.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/layouts/master.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/layouts/user-center.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/main.ts" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/router/index.ts" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/scripts/request.ts" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/stores/notifications.ts" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/stores/userinfo.ts" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/views/auth/claims.ts" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/views/auth/connect.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/views/auth/sign-in.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/views/auth/sign-up.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/views/confirm.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/views/dashboard.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/views/personal-page.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/views/personalize.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/views/security.vue" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/tsconfig.app.json" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/tsconfig.json" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/tsconfig.node.json" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/uno.config.ts" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/vite.config.ts" afterDir="false" /> | ||||
|       <change afterPath="$PROJECT_DIR$/web/src/components/auth/AuthenticateCompleted.vue" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/go.mod" beforeDir="false" afterPath="$PROJECT_DIR$/go.mod" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/go.sum" beforeDir="false" afterPath="$PROJECT_DIR$/go.sum" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/embed.go" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/grpc/server.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/grpc/server.go" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/api/auth_api.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/server/api/auth_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/server.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/server/server.go" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/ui/accounts.go" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/ui/index.go" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/ui/mfa.go" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/ui/oauth.go" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/ui/signin.go" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/server/ui/signup.go" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/authorize.gohtml" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/favicon.png" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/index.gohtml" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/layouts/auth.gohtml" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/layouts/user-center.gohtml" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/mfa-apply.gohtml" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/mfa.gohtml" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/partials/header.gohtml" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/signin.gohtml" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/signup.gohtml" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/internal/views/users/me.gohtml" beforeDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/pkg/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/main.go" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/settings.toml" beforeDir="false" afterPath="$PROJECT_DIR$/settings.toml" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/web/src/components/Copyright.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/components/Copyright.vue" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/web/src/components/auth/AccountLocator.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/components/auth/Authenticate.vue" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/web/src/components/auth/FactorApplicator.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/components/auth/FactorApplicator.vue" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/web/src/components/auth/FactorPicker.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/components/auth/FactorPicker.vue" 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/auth/connect.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/views/auth/authorize.vue" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/web/src/views/auth/sign-in.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/views/auth/sign-in.vue" afterDir="false" /> | ||||
|       <change beforePath="$PROJECT_DIR$/web/src/views/confirm.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/views/confirm.vue" afterDir="false" /> | ||||
|     </list> | ||||
|     <option name="SHOW_DIALOG" value="false" /> | ||||
|     <option name="HIGHLIGHT_CONFLICTS" value="true" /> | ||||
|   | ||||
| @@ -36,7 +36,7 @@ func doAuthenticate(c *fiber.Ctx) error { | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"is_finished": ticket.IsAvailable(), | ||||
| 		"is_finished": ticket.IsAvailable() == nil, | ||||
| 		"ticket":      ticket, | ||||
| 	}) | ||||
| } | ||||
| @@ -68,7 +68,7 @@ func doMultiFactorAuthenticate(c *fiber.Ctx) error { | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"is_finished": ticket.IsAvailable(), | ||||
| 		"is_finished": ticket.IsAvailable() == nil, | ||||
| 		"ticket":      ticket, | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,29 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"git.solsynth.dev/hydrogen/passport/pkg/internal/services" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| ) | ||||
|  | ||||
| func getAvailableFactors(c *fiber.Ctx) error { | ||||
| 	ticketId := c.QueryInt("ticketId", 0) | ||||
| 	if ticketId <= 0 { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, "must provide ticket id as a query parameter") | ||||
| 	} | ||||
|  | ||||
| 	ticket, err := services.GetTicket(uint(ticketId)) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("ticket was not found: %v", err)) | ||||
| 	} | ||||
| 	factors, err := services.ListUserFactor(ticket.AccountID) | ||||
| 	if err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(factors) | ||||
| } | ||||
|  | ||||
| func requestFactorToken(c *fiber.Ctx) error { | ||||
| 	id, _ := c.ParamsInt("factorId", 0) | ||||
|  | ||||
|   | ||||
| @@ -55,7 +55,10 @@ func MapAPIs(app *fiber.App) { | ||||
| 		api.Post("/users", doRegister) | ||||
|  | ||||
| 		api.Post("/auth", doAuthenticate) | ||||
| 		api.Post("/auth/mfa", doMultiFactorAuthenticate) | ||||
| 		api.Post("/auth/token", getToken) | ||||
|  | ||||
| 		api.Get("/auth/factors", getAvailableFactors) | ||||
| 		api.Post("/auth/factors/:factorId", requestFactorToken) | ||||
|  | ||||
| 		realms := api.Group("/realms").Name("Realms API") | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
|   <div class="text-xs text-center opacity-80"> | ||||
|     <p>Copyright © {{ new Date().getFullYear() }} Solsynth</p> | ||||
|     <p>Powered by <a class="underline" href="#">Hydrogen.Identity</a></p> | ||||
|     <p>Powered by <a class="underline" href="https://git.solsynth.dev/Hydrogen/Passport">Hydrogen.Passport</a></p> | ||||
|   </div> | ||||
| </template> | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| <template> | ||||
|   <div class="flex items-center"> | ||||
|     <v-form class="flex-grow-1" @submit.prevent="submit"> | ||||
|       <v-text-field label="Account ID" variant="solo" density="comfortable" :disabled="props.loading" v-model="probe" /> | ||||
|       <v-text-field label="Username" variant="solo" density="comfortable" class="mb-3" :hide-details="true" | ||||
|                     :disabled="props.loading" v-model="probe" /> | ||||
|       <v-text-field label="Password" variant="solo" density="comfortable" type="password" :disabled="props.loading" | ||||
|                     v-model="password" /> | ||||
| 
 | ||||
|       <v-expand-transition> | ||||
|         <v-alert v-show="error" variant="tonal" type="error" class="text-xs mb-3"> | ||||
| @@ -32,28 +35,29 @@ import { ref } from "vue" | ||||
| import { request } from "@/scripts/request" | ||||
| 
 | ||||
| const probe = ref("") | ||||
| const password = ref("") | ||||
| 
 | ||||
| const error = ref<string | null>(null) | ||||
| 
 | ||||
| const props = defineProps<{ loading?: boolean }>() | ||||
| const emits = defineEmits(["swap", "update:loading", "update:factors", "update:challenge"]) | ||||
| const emits = defineEmits(["swap", "update:loading", "update:ticket"]) | ||||
| 
 | ||||
| async function submit() { | ||||
|   if (!probe) return | ||||
|   if (!probe.value || !password.value) return | ||||
| 
 | ||||
|   emits("update:loading", true) | ||||
|   const res = await request("/api/auth", { | ||||
|     method: "PUT", | ||||
|     method: "POST", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify({ id: probe.value }), | ||||
|     body: JSON.stringify({ id: probe.value, password: password.value }), | ||||
|   }) | ||||
|   if (res.status !== 200) { | ||||
|     error.value = await res.text() | ||||
|   } else { | ||||
|     const data = await res.json() | ||||
|     emits("update:factors", data["factors"]) | ||||
|     emits("update:challenge", data["challenge"]) | ||||
|     emits("swap", "pick") | ||||
|     emits("update:ticket", data["ticket"]) | ||||
|     if (data.is_finished) emits("swap", "completed") | ||||
|     else emits("swap", "mfa") | ||||
|     error.value = null | ||||
|   } | ||||
|   emits("update:loading", false) | ||||
							
								
								
									
										68
									
								
								web/src/components/auth/AuthenticateCompleted.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								web/src/components/auth/AuthenticateCompleted.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <v-icon icon="mdi-lan-check" size="32" color="grey-darken-3" class="mb-3" /> | ||||
|  | ||||
|     <h1 class="font-bold text-xl">All Done!</h1> | ||||
|     <p>Welcome back! You just signed in right now! We're going to send you to jesus...</p> | ||||
|  | ||||
|     <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> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { request } from "@/scripts/request" | ||||
| import { useUserinfo } from "@/stores/userinfo" | ||||
| import { onMounted, ref } from "vue" | ||||
| import { useRoute, useRouter } from "vue-router" | ||||
|  | ||||
| const route = useRoute() | ||||
| const router = useRouter() | ||||
| const userinfo = useUserinfo() | ||||
|  | ||||
| const props = defineProps<{ loading?: boolean; currentFactor?: any; ticket?: any }>() | ||||
| const emits = defineEmits(["update:loading"]) | ||||
|  | ||||
| const error = ref<string | null>(null) | ||||
|  | ||||
| async function load() { | ||||
|   emits("update:loading", true) | ||||
|   await getToken(props.ticket.grant_token) | ||||
|   await userinfo.readProfiles() | ||||
|   emits("update:loading", false) | ||||
|   setTimeout(() => callback(), 3000) | ||||
| } | ||||
|  | ||||
| onMounted(() => load()) | ||||
|  | ||||
| async function getToken(tk: string) { | ||||
|   const res = await request("/api/auth/token", { | ||||
|     method: "POST", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify({ | ||||
|       code: tk, | ||||
|       grant_type: "grant_token", | ||||
|     }), | ||||
|   }) | ||||
|   if (res.status !== 200) { | ||||
|     const err = await res.text() | ||||
|     error.value = err | ||||
|     throw new Error(err) | ||||
|   } else { | ||||
|     error.value = null | ||||
|   } | ||||
| } | ||||
|  | ||||
| function callback() { | ||||
|   if (route.query["close"]) { | ||||
|     window.close() | ||||
|   } else if (route.query["redirect_uri"]) { | ||||
|     window.open((route.query["redirect_uri"] as string) ?? "/", "_self") | ||||
|   } else { | ||||
|     router.push({ name: "dashboard" }) | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @@ -47,75 +47,37 @@ | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { request } from "@/scripts/request" | ||||
| import { useUserinfo } from "@/stores/userinfo" | ||||
| import { computed, ref } from "vue" | ||||
| import { useRoute, useRouter } from "vue-router" | ||||
|  | ||||
| const password = ref("") | ||||
|  | ||||
| const error = ref<string | null>(null) | ||||
|  | ||||
| const props = defineProps<{ loading?: boolean; currentFactor?: any; challenge?: any }>() | ||||
| const emits = defineEmits(["swap", "update:challenge"]) | ||||
|  | ||||
| const route = useRoute() | ||||
| const router = useRouter() | ||||
|  | ||||
| const { readProfiles } = useUserinfo() | ||||
| const props = defineProps<{ loading?: boolean; currentFactor?: any; ticket?: any }>() | ||||
| const emits = defineEmits(["swap", "update:ticket", "update:loading"]) | ||||
|  | ||||
| async function submit() { | ||||
|   const res = await request(`/api/auth`, { | ||||
|   emits("update:loading", true) | ||||
|   const res = await request(`/api/auth/mfa`, { | ||||
|     method: "POST", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify({ | ||||
|       challenge_id: props.challenge?.id, | ||||
|       ticket_id: props.ticket?.id, | ||||
|       factor_id: props.currentFactor?.id, | ||||
|       secret: password.value, | ||||
|       code: password.value, | ||||
|     }), | ||||
|   }) | ||||
|   if (res.status !== 200) { | ||||
|     error.value = await res.text() | ||||
|   } else { | ||||
|     const data = await res.json() | ||||
|     if (data["is_finished"]) { | ||||
|       await getToken(data["session"]["grant_token"]) | ||||
|       await readProfiles() | ||||
|       callback() | ||||
|     } else { | ||||
|       emits("swap", "pick") | ||||
|       emits("update:challenge", data["challenge"]) | ||||
|       error.value = null | ||||
|       password.value = "" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function getToken(tk: string) { | ||||
|   const res = await request("/api/auth/token", { | ||||
|     method: "POST", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify({ | ||||
|       code: tk, | ||||
|       grant_type: "grant_token", | ||||
|     }), | ||||
|   }) | ||||
|   if (res.status !== 200) { | ||||
|     const err = await res.text() | ||||
|     error.value = err | ||||
|     throw new Error(err) | ||||
|   } else { | ||||
|     error.value = null | ||||
|     password.value = "" | ||||
|     emits("update:ticket", data["ticket"]) | ||||
|     if (data["is_finished"]) emits("swap", "completed") | ||||
|     else emits("swap", "mfa") | ||||
|   } | ||||
| } | ||||
|  | ||||
| function callback() { | ||||
|   if (route.query["closable"]) { | ||||
|     window.close() | ||||
|   } else if (route.query["redirect_uri"]) { | ||||
|     window.open((route.query["redirect_uri"] as string) ?? "/", "_self") | ||||
|   } else { | ||||
|     router.push({ name: "dashboard" }) | ||||
|   } | ||||
|   emits("update:loading", false) | ||||
| } | ||||
|  | ||||
| const inputType = computed(() => { | ||||
| @@ -124,6 +86,8 @@ const inputType = computed(() => { | ||||
|       return "text" | ||||
|     case 1: | ||||
|       return "one-time-password" | ||||
|     default: | ||||
|       return "unknown" | ||||
|   } | ||||
| }) | ||||
| </script> | ||||
|   | ||||
| @@ -4,7 +4,8 @@ | ||||
|       <v-card class="mb-3"> | ||||
|         <v-list density="compact" color="primary"> | ||||
|           <v-list-item | ||||
|             v-for="item in props.factors ?? []" | ||||
|             v-for="(item, idx) in factors ?? []" | ||||
|             :key="idx" | ||||
|             :prepend-icon="getFactorType(item)?.icon" | ||||
|             :title="getFactorType(item)?.label" | ||||
|             :active="focus === item.id" | ||||
| @@ -30,18 +31,32 @@ | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref } from "vue" | ||||
| import { onMounted, ref } from "vue" | ||||
| import { request } from "@/scripts/request" | ||||
|  | ||||
| const focus = ref<number | null>(null) | ||||
| const factors = ref<any[]>([]) | ||||
|  | ||||
| const error = ref<string | null>(null) | ||||
|  | ||||
| const props = defineProps<{ factors?: any[]; challenge?: any }>() | ||||
| const props = defineProps<{ ticket?: any }>() | ||||
| const emits = defineEmits(["swap", "update:loading", "update:currentFactor"]) | ||||
|  | ||||
| async function load() { | ||||
|   emits("update:loading", true) | ||||
|   const res = await request(`/api/auth/factors?ticketId=${props.ticket.ticketId}`) | ||||
|   if (res.status !== 200) { | ||||
|     error.value = await res.text() | ||||
|   } else { | ||||
|     factors.value = await res.json() | ||||
|   } | ||||
|   emits("update:loading", false) | ||||
| } | ||||
|  | ||||
| onMounted(() => load()) | ||||
|  | ||||
| async function submit() { | ||||
|   if (!focus) return | ||||
|   if (!focus.value) return | ||||
|  | ||||
|   emits("update:loading", true) | ||||
|   const res = await request(`/api/auth/factors/${focus.value}`, { | ||||
| @@ -50,7 +65,7 @@ async function submit() { | ||||
|   if (res.status !== 200 && res.status !== 204) { | ||||
|     error.value = await res.text() | ||||
|   } else { | ||||
|     const item = props.factors?.find((item: any) => item.id === focus.value) | ||||
|     const item = factors.value.find((item: any) => item.id === focus.value) | ||||
|     emits("update:currentFactor", item) | ||||
|     emits("swap", "applicator") | ||||
|     error.value = null | ||||
| @@ -61,15 +76,13 @@ async function submit() { | ||||
|  | ||||
| function getFactorType(item: any) { | ||||
|   switch (item.type) { | ||||
|     case 0: | ||||
|       return { icon: "mdi-form-textbox-password", label: "Password Validation" } | ||||
|     case 1: | ||||
|       return { icon: "mdi-email-fast", label: "Email One Time Password" } | ||||
|       return { icon: "mdi-email-fast", label: "Email Validation" } | ||||
|   } | ||||
| } | ||||
|  | ||||
| function getFactorAvailable(factor: any) { | ||||
|   const blacklist: number[] = props.challenge?.blacklist_factors ?? [] | ||||
|   const blacklist: number[] = props.ticket?.blacklist_factors ?? [] | ||||
|   return blacklist.includes(factor.id) | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -6,16 +6,20 @@ import UserCenterLayout from "@/layouts/user-center.vue" | ||||
| const router = createRouter({ | ||||
|   history: createWebHistory(import.meta.env.BASE_URL), | ||||
|   routes: [ | ||||
|     { | ||||
|       path: "/", | ||||
|       redirect: { name: "dashboard" }, | ||||
|     }, | ||||
|     { | ||||
|       path: "/", | ||||
|       component: MasterLayout, | ||||
|       children: [ | ||||
|         { | ||||
|           path: "/", | ||||
|           path: "/users", | ||||
|           component: UserCenterLayout, | ||||
|           children: [ | ||||
|             { | ||||
|               path: "/", | ||||
|               path: "/me", | ||||
|               name: "dashboard", | ||||
|               component: () => import("@/views/dashboard.vue"), | ||||
|               meta: { title: "Your account" }, | ||||
| @@ -43,34 +47,33 @@ const router = createRouter({ | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       path: "/auth", | ||||
|       path: "/", | ||||
|       children: [ | ||||
|         { | ||||
|           path: "sign-in", | ||||
|           path: "/sign-in", | ||||
|           name: "auth.sign-in", | ||||
|           component: () => import("@/views/auth/sign-in.vue"), | ||||
|           meta: { public: true, title: "Sign in" }, | ||||
|         }, | ||||
|         { | ||||
|           path: "sign-up", | ||||
|           path: "/sign-up", | ||||
|           name: "auth.sign-up", | ||||
|           component: () => import("@/views/auth/sign-up.vue"), | ||||
|           meta: { public: true, title: "Sign up" }, | ||||
|         }, | ||||
|         { | ||||
|           path: "o/connect", | ||||
|           name: "openid.connect", | ||||
|           component: () => import("@/views/auth/connect.vue"), | ||||
|         }, | ||||
|  | ||||
|         { | ||||
|           path: "/me/confirm", | ||||
|           name: "callback.confirm", | ||||
|           component: () => import("@/views/confirm.vue"), | ||||
|           meta: { public: true, title: "Confirm registration" }, | ||||
|           path: "authorize", | ||||
|           name: "oauth.authorize", | ||||
|           component: () => import("@/views/auth/authorize.vue"), | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       path: "/users/me/confirm", | ||||
|       name: "callback.confirm", | ||||
|       component: () => import("@/views/confirm.vue"), | ||||
|       meta: { public: true, title: "Confirm registration" }, | ||||
|     }, | ||||
|   ], | ||||
| }) | ||||
|  | ||||
|   | ||||
| @@ -7,24 +7,14 @@ | ||||
|         <div> | ||||
|           <v-avatar color="accent" icon="mdi-login-variant" size="large" class="card-rounded mb-2" /> | ||||
|           <h1 class="text-2xl">Sign in</h1> | ||||
|           <div v-if="challenge" class="flex items-center gap-4"> | ||||
|             <v-tooltip> | ||||
|               <template v-slot:activator="{ props }"> | ||||
|                 <v-progress-circular v-bind="props" size="large" | ||||
|                   :model-value="(challenge?.progress / challenge?.requirements) * 100" /> | ||||
|               </template> | ||||
|               <p><b>Risk: </b> {{ challenge?.risk_level }}</p> | ||||
|               <p><b>Progress: </b> {{ challenge?.progress }}/{{ challenge?.requirements }}</p> | ||||
|             </v-tooltip> | ||||
|             <p>We need to verify that the person trying to access your account is you.</p> | ||||
|           </div> | ||||
|           <p v-if="ticket">We need to verify that the person trying to access your account is you.</p> | ||||
|           <p v-else>Sign in via your Solar ID to access the entire Solar Network.</p> | ||||
|         </div> | ||||
|  | ||||
|         <v-window :touch="false" :model-value="panel" class="pa-2 mx-[-0.5rem]"> | ||||
|           <v-window-item v-for="k in Object.keys(panels)" :value="k"> | ||||
|           <v-window-item v-for="(k, idx) in Object.keys(panels)" :key="idx" :value="k"> | ||||
|             <component :is="panels[k]" @swap="(val: string) => (panel = val)" v-model:loading="loading" | ||||
|               v-model:factors="factors" v-model:currentFactor="currentFactor" v-model:challenge="challenge" /> | ||||
|                        v-model:currentFactor="currentFactor" v-model:ticket="ticket" /> | ||||
|           </v-window-item> | ||||
|         </v-window> | ||||
|       </v-card-text> | ||||
| @@ -35,25 +25,26 @@ | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref, type Component } from "vue" | ||||
| import { type Component, ref } from "vue" | ||||
| import Copyright from "@/components/Copyright.vue" | ||||
| import CallbackNotify from "@/components/auth/CallbackNotify.vue" | ||||
| import AccountLocator from "@/components/auth/AccountLocator.vue" | ||||
| import FactorPicker from "@/components/auth/FactorPicker.vue" | ||||
| import FactorApplicator from "@/components/auth/FactorApplicator.vue" | ||||
| import AccountAuthenticate from "@/components/auth/Authenticate.vue" | ||||
| import AuthenticateCompleted from "@/components/auth/AuthenticateCompleted.vue" | ||||
|  | ||||
| const loading = ref(false) | ||||
|  | ||||
| const factors = ref<any>(null) | ||||
| const currentFactor = ref<any>(null) | ||||
| const challenge = ref<any>(null) | ||||
| const ticket = ref<any>(null) | ||||
|  | ||||
| const panel = ref("locate") | ||||
| const panel = ref("authenticate") | ||||
|  | ||||
| const panels: { [id: string]: Component } = { | ||||
|   locate: AccountLocator, | ||||
|   pick: FactorPicker, | ||||
|   authenticate: AccountAuthenticate, | ||||
|   mfa: FactorPicker, | ||||
|   applicator: FactorApplicator, | ||||
|   completed: AuthenticateCompleted, | ||||
| } | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -28,7 +28,7 @@ | ||||
|               <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 sucessfully confirmed your account.</p> | ||||
|               <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> | ||||
|             </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user