refactor: restructure passkey with dedicated package
- Create internal/passkey/ package with challenge session management - Add HTTP handler layer in internal/api/passkey.go - Simplify Passkey model to use blob credential storage - Register passkey routes in router - Update frontend to match new API endpoints - Add .env.example template
This commit is contained in:
@@ -16,26 +16,21 @@ export const useWebAuthStore = defineStore("webauth", () => {
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
const addPasskey = async () => {
|
||||
const addPasskey = async (name?: string) => {
|
||||
error.value = "";
|
||||
loading.value = true;
|
||||
try {
|
||||
// 1. 从后端获取注册选项 (Creation Options)
|
||||
const res = await request.get("/profile/passkey");
|
||||
// console.log("begin:", res.data.data.publicKey);
|
||||
const options = res.data.data.publicKey;
|
||||
const res = await request.post("/webauthn/register/begin", {});
|
||||
const { creation, challenge } = res.data.data;
|
||||
|
||||
// 调用 Web Authentication API 进行注册
|
||||
// const credential = await navigator.credentials.create(options);
|
||||
// console.log("credential:", credential);
|
||||
let attestation;
|
||||
try {
|
||||
// Pass 'undefined' as the second argument if you are not using an AbortSignal
|
||||
attestation = await startRegistration({ optionsJSON: options });
|
||||
// console.log("WebAuthn 注册结果 (Attestation):", JSON.stringify(attestation));
|
||||
attestation = await startRegistration({ optionsJSON: creation });
|
||||
error.value = null;
|
||||
} catch (regError: any) {
|
||||
// console.log("WebAuthn 注册失败或取消:", regError);
|
||||
if (regError.name === "NotAllowedError") {
|
||||
error.value = "Passkey 操作被取消或不允许。";
|
||||
} else {
|
||||
@@ -45,8 +40,11 @@ export const useWebAuthStore = defineStore("webauth", () => {
|
||||
}
|
||||
|
||||
// 3. 将注册结果 (Attestation) 发送到后端进行验证和保存
|
||||
const res2: AxiosResponse = await request.post("/profile/passkey", attestation);
|
||||
// console.log("end:", res2);
|
||||
const res2: AxiosResponse = await request.post("/webauthn/register/complete", {
|
||||
challenge,
|
||||
name: name || "passkey",
|
||||
credential: attestation,
|
||||
});
|
||||
return res2;
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.error || "添加 Passkey 失败,请稍后重试。";
|
||||
@@ -56,20 +54,18 @@ export const useWebAuthStore = defineStore("webauth", () => {
|
||||
}
|
||||
};
|
||||
|
||||
const loginPasskey = async () => {
|
||||
const loginPasskey = async (username?: string) => {
|
||||
error.value = null;
|
||||
loading.value = true;
|
||||
try {
|
||||
// 1. 从后端获取登录选项 (Assertion Options)
|
||||
const res = await request.get("/auth/passkey/begin");
|
||||
// console.log("login begin:", res.data);
|
||||
const options = res.data.data.publicKey;
|
||||
const res = await request.post("/auth/passkey/begin", { username });
|
||||
const { assertion, challenge, user_id } = res.data.data;
|
||||
|
||||
// 2. 调用 Web Authentication API 进行认证
|
||||
let assertion;
|
||||
let credential;
|
||||
try {
|
||||
assertion = await startAuthentication({ optionsJSON: options });
|
||||
// console.log("WebAuthn 认证结果 (Assertion):", JSON.stringify(assertion));
|
||||
credential = await startAuthentication({ optionsJSON: assertion });
|
||||
} catch (loginError: any) {
|
||||
if (loginError.name === "NotAllowedError") {
|
||||
error.value = "Passkey 登录被取消或不允许。";
|
||||
@@ -80,8 +76,11 @@ export const useWebAuthStore = defineStore("webauth", () => {
|
||||
}
|
||||
|
||||
// 3. 将认证结果 (Assertion) 发送到后端进行验证并获取 Token
|
||||
const challenge = options.challenge; // 从 begin 接口返回的 options 中获取 challenge
|
||||
const res2: AxiosResponse = await request.post(`/auth/passkey/finish?challenge=${challenge}`, assertion);
|
||||
const res2: AxiosResponse = await request.post("/auth/passkey/finish", {
|
||||
challenge,
|
||||
credential,
|
||||
user_id,
|
||||
});
|
||||
|
||||
// 4. 处理登录成功的响应,通常包含 Token
|
||||
if (res2.status === 200 && !!res2.data.data?.token) {
|
||||
@@ -103,8 +102,7 @@ export const useWebAuthStore = defineStore("webauth", () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const response = await request.get('/profile/passkeys')
|
||||
// console.log('getPasskeys',response.data.data)
|
||||
const response = await request.get('/webauthn/passkeys')
|
||||
passkeys.value = response.data.data
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.error || '获取token列表失败';
|
||||
@@ -118,7 +116,7 @@ export const useWebAuthStore = defineStore("webauth", () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const response: AxiosResponse = await request.delete(`/profile/passkeys/${id}`)
|
||||
const response: AxiosResponse = await request.delete(`/webauthn/passkeys/${id}`)
|
||||
return response
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.error || `删除passkey ${id} 失败`;
|
||||
|
||||
@@ -170,8 +170,6 @@
|
||||
<tr class="text-xs uppercase tracking-wider text-base-content/50">
|
||||
<th class="pl-4">Name</th>
|
||||
<th>Create Time</th>
|
||||
<th>Sign Count</th>
|
||||
<th>Device</th>
|
||||
<th class="pr-4 text-right"><span class="sr-only">Actions</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -179,8 +177,6 @@
|
||||
<tr v-for="passkey in passkeys" :key="passkey.id" class="border-base-300/40 hover:bg-base-200/50">
|
||||
<td class="pl-4 font-medium">{{ passkey.name }}</td>
|
||||
<td class="tabular-nums text-base-content/70">{{ formatDateTime(passkey.created_at) }}</td>
|
||||
<td class="tabular-nums">{{ passkey.sign_count }}</td>
|
||||
<td class="text-base-content/70">{{ passkey.device_type }}</td>
|
||||
<td class="pr-4 text-right">
|
||||
<button class="btn btn-ghost btn-xs btn-square text-error"
|
||||
@click="confirmRmovePasskey(passkey)" aria-label="Delete passkey">
|
||||
|
||||
Reference in New Issue
Block a user