diff --git a/packages/ui/certd-client/src/locales/langs/en-US/authentication.ts b/packages/ui/certd-client/src/locales/langs/en-US/authentication.ts
index 397c061fb..ce6c2bde9 100644
--- a/packages/ui/certd-client/src/locales/langs/en-US/authentication.ts
+++ b/packages/ui/certd-client/src/locales/langs/en-US/authentication.ts
@@ -101,4 +101,5 @@ export default {
registerPasskey: "Register Passkey",
deviceName: "Device Name",
deviceNameHelper: "Please enter the device name, used to identify the device",
+ passkeyRegisterHelper: "Site domain change will invalidate passkey",
};
diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/authentication.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/authentication.ts
index 0c2ac6f71..ff24a1e3c 100644
--- a/packages/ui/certd-client/src/locales/langs/zh-CN/authentication.ts
+++ b/packages/ui/certd-client/src/locales/langs/zh-CN/authentication.ts
@@ -103,4 +103,5 @@ export default {
registerPasskey: "注册Passkey",
deviceName: "设备名称",
deviceNameHelper: "请输入当前设备名称,绑定多个时好做区分",
+ passkeyRegisterHelper: "1、站点域名变更会导致passkey失效;\n2、同一设备同一个用户绑定多次只有最后一次的有效,之前绑定的会失效,需要手动删除",
};
diff --git a/packages/ui/certd-client/src/store/user/api.user.ts b/packages/ui/certd-client/src/store/user/api.user.ts
index 7de04cc65..e358c760c 100644
--- a/packages/ui/certd-client/src/store/user/api.user.ts
+++ b/packages/ui/certd-client/src/store/user/api.user.ts
@@ -115,23 +115,22 @@ export async function generatePasskeyRegistrationOptions() {
});
}
-export async function verifyPasskeyRegistration(userId: number, response: any, challenge: string) {
+export async function verifyPasskeyRegistration(response: any, challenge: string) {
return await request({
url: "/passkey/verifyRegistration",
method: "post",
- data: { userId, response, challenge },
+ data: { response, challenge },
});
}
-export async function generatePasskeyAuthenticationOptions(userId: number) {
+export async function generatePasskeyAuthenticationOptions() {
return await request({
url: "/passkey/generateAuthentication",
method: "post",
- data: { userId },
});
}
-export async function loginByPasskey(form: { userId: number; credential: any; challenge: string }) {
+export async function loginByPasskey(form: { credential: any; challenge: string }) {
return await request({
url: "/loginByPasskey",
method: "post",
@@ -139,7 +138,7 @@ export async function loginByPasskey(form: { userId: number; credential: any; ch
});
}
-export async function registerPasskey(form: { userId: number; response: any; challenge: string }) {
+export async function registerPasskey(form: { response: any; challenge: string }) {
return await request({
url: "/passkey/register",
method: "post",
diff --git a/packages/ui/certd-client/src/style/common.less b/packages/ui/certd-client/src/style/common.less
index a784bea43..fe0cc9c53 100644
--- a/packages/ui/certd-client/src/style/common.less
+++ b/packages/ui/certd-client/src/style/common.less
@@ -295,7 +295,7 @@ h6 {
}
.helper {
- color: #aeaeae;
+ color: #8f8f8f;
font-size: 12px;
margin-top: 3px;
margin-bottom: 3px;
diff --git a/packages/ui/certd-client/src/views/certd/mine/api.ts b/packages/ui/certd-client/src/views/certd/mine/api.ts
index 8798b5881..9ce8648ad 100644
--- a/packages/ui/certd-client/src/views/certd/mine/api.ts
+++ b/packages/ui/certd-client/src/views/certd/mine/api.ts
@@ -58,14 +58,14 @@ export async function OauthBoundUrl(type: string) {
export async function GetPasskeys() {
return await request({
- url: "/mine/passkeys",
+ url: "/mine/passkey/list",
method: "POST",
});
}
export async function UnbindPasskey(id: number) {
return await request({
- url: "/mine/unbindPasskey",
+ url: "/mine/passkey/unbind",
method: "POST",
data: { id },
});
@@ -110,39 +110,23 @@ export interface PasskeyCredential {
export async function generatePasskeyRegistrationOptions() {
return await request({
- url: "/passkey/generateRegistration",
+ url: "/mine/passkey/generateRegistration",
method: "post",
});
}
-export async function verifyPasskeyRegistration(userId: number, response: any, challenge: string, deviceName: string) {
+export async function verifyPasskeyRegistration(response: any, challenge: string, deviceName: string) {
return await request({
- url: "/passkey/verifyRegistration",
+ url: "/mine/passkey/verifyRegistration",
method: "post",
- data: { userId, response, challenge, deviceName },
+ data: { response, challenge, deviceName },
});
}
-export async function generatePasskeyAuthenticationOptions(userId: number) {
+export async function registerPasskey(response: any, challenge: string, deviceName: string) {
return await request({
- url: "/passkey/generateAuthentication",
+ url: "/mine/passkey/register",
method: "post",
- data: { userId },
- });
-}
-
-export async function loginByPasskey(userId: number, credential: any, challenge: string) {
- return await request({
- url: "/passkey/login",
- method: "post",
- data: { userId, credential, challenge },
- });
-}
-
-export async function registerPasskey(userId: number, response: any, challenge: string) {
- return await request({
- url: "/passkey/register",
- method: "post",
- data: { userId, response, challenge },
+ data: { response, challenge, deviceName },
});
}
diff --git a/packages/ui/certd-client/src/views/certd/mine/user-profile.vue b/packages/ui/certd-client/src/views/certd/mine/user-profile.vue
index 807ac49cc..378249a17 100644
--- a/packages/ui/certd-client/src/views/certd/mine/user-profile.vue
+++ b/packages/ui/certd-client/src/views/certd/mine/user-profile.vue
@@ -33,7 +33,11 @@
{{ passkey.deviceName }}
-
{{ formatDate(passkey.registeredAt) }}
+
+ 注册时间:{{ formatDate(passkey.registeredAt) }}
+ 最后使用:{{ formatDate(passkey.updateTime) }}
+
+
解绑
@@ -42,6 +46,9 @@
{{ t("authentication.passkeyNotSupported") }}
+ {{ t("authentication.passkeyRegisterHelper") }}
+
{{ t("authentication.updateProfile") }}
@@ -59,10 +66,11 @@ import ChangePasswordButton from "/@/views/certd/mine/change-password-button.vue
import { useI18n } from "/src/locales";
import { useUserProfile } from "./use";
import { usePasskeyRegister } from "./use";
-import { message, Modal } from "ant-design-vue";
+import { message, Modal, notification } from "ant-design-vue";
import { useSettingStore } from "/@/store/settings";
import { isEmpty } from "lodash-es";
import { dict } from "@fast-crud/fast-crud";
+import dayjs from "dayjs";
const { t } = useI18n();
@@ -190,9 +198,15 @@ async function doRegisterPasskey(deviceName: string) {
const res: any = await api.generatePasskeyRegistrationOptions();
const options = res;
- navigator.credentials.query({
- publicKey: options,
- });
+ // navigator.credentials.query({
+ // publicKey: options,
+ // });
+
+ // const excludeCredentials = passkeys.value.map(item => ({
+ // id: new TextEncoder().encode(item.passkeyId),
+ // type: "public-key",
+ // }));
+
const credential = await (navigator.credentials as any).create({
publicKey: {
challenge: Uint8Array.from(atob(options.challenge.replace(/-/g, "+").replace(/_/g, "/")), c => c.charCodeAt(0)),
@@ -200,9 +214,9 @@ async function doRegisterPasskey(deviceName: string) {
pubKeyCredParams: options.pubKeyCredParams,
timeout: options.timeout || 60000,
attestation: options.attestation,
- excludeCredentials: options.excludeCredentials || [],
+ // excludeCredentials: excludeCredentials,
user: {
- id: Uint8Array.from([res.userId]),
+ id: new TextEncoder().encode(options.userId + ""),
name: userInfo.value.username,
displayName: deviceName,
},
@@ -222,18 +236,20 @@ async function doRegisterPasskey(deviceName: string) {
clientDataJSON: toBase64Url(credential.response.clientDataJSON),
},
};
+ console.log("credential", credential, response);
+ debugger;
- const verifyRes: any = await api.verifyPasskeyRegistration(userInfo.value.id, response, options.challenge, deviceName);
+ const verifyRes: any = await api.verifyPasskeyRegistration(response, options.challenge, deviceName);
await loadPasskeys();
} catch (e: any) {
console.error("Passkey注册失败:", e);
- Modal.error({ title: "错误", content: e.message || "Passkey注册失败" });
+ notification.error({ message: "错误", description: e.message || "Passkey注册失败" });
}
}
const formatDate = (dateString: string) => {
if (!dateString) return "";
- return new Date(dateString).toLocaleString("zh-CN");
+ return dayjs(dateString).format("YYYY-MM-DD HH:mm:ss");
};
const checkPasskeySupport = () => {
diff --git a/packages/ui/certd-client/src/views/framework/login/index.vue b/packages/ui/certd-client/src/views/framework/login/index.vue
index f12aea711..89b4918e0 100644
--- a/packages/ui/certd-client/src/views/framework/login/index.vue
+++ b/packages/ui/certd-client/src/views/framework/login/index.vue
@@ -233,7 +233,6 @@ const handlePasskeyLogin = async () => {
}
const loginRes: any = await UserApi.loginByPasskey({
- userId: optionsResponse.userId,
credential,
challenge: options.challenge,
});
diff --git a/packages/ui/certd-server/src/controller/basic/login/login-controller.ts b/packages/ui/certd-server/src/controller/basic/login/login-controller.ts
index 13ffb56b2..4bf11b43d 100644
--- a/packages/ui/certd-server/src/controller/basic/login/login-controller.ts
+++ b/packages/ui/certd-server/src/controller/basic/login/login-controller.ts
@@ -4,6 +4,7 @@ import { AddonService, BaseController, Constants, SysPublicSettings, SysSettings
import { CodeService } from "../../../modules/basic/service/code-service.js";
import { checkComm } from "@certd/plus-core";
import { CaptchaService } from "../../../modules/basic/service/captcha-service.js";
+import { PasskeyService } from "../../../modules/login/service/passkey-service.js";
/**
*/
@@ -23,6 +24,10 @@ export class LoginController extends BaseController {
@Inject()
captchaService: CaptchaService;
+ @Inject()
+ passkeyService: PasskeyService;
+
+
@Post('/login', { summary: Constants.per.guest })
public async login(
@Body(ALL)
@@ -81,22 +86,36 @@ export class LoginController extends BaseController {
return this.ok(token);
}
- @Post('/loginByPasskey', { summary: Constants.per.guest })
- public async loginByPasskey(
- @Body(ALL)
- body: any
- ) {
- const credential = body.credential;
- const challenge = body.challenge;
+
+
- const token = await this.loginService.loginByPasskey({
- credential,
- challenge,
- }, this.ctx);
-
- // this.writeTokenCookie(token);
- return this.ok(token);
- }
+ @Post('/passkey/generateAuthentication', { summary: Constants.per.guest })
+ public async generateAuthentication() {
+ const options = await this.passkeyService.generateAuthenticationOptions(
+ this.ctx
+ );
+
+ return this.ok({
+ ...options,
+ });
+ }
+
+ @Post('/loginByPasskey', { summary: Constants.per.guest })
+ public async loginByPasskey(
+ @Body(ALL)
+ body: any
+ ) {
+ const credential = body.credential;
+ const challenge = body.challenge;
+
+ const token = await this.loginService.loginByPasskey({
+ credential,
+ challenge,
+ }, this.ctx);
+
+ // this.writeTokenCookie(token);
+ return this.ok(token);
+ }
@Post('/logout', { summary: Constants.per.authOnly })
public logout() {
diff --git a/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts b/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts
index 9a2c86eed..35cf3d7ef 100644
--- a/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts
+++ b/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts
@@ -50,30 +50,4 @@ export class MineController extends BaseController {
});
return this.ok({});
}
-
- @Post('/passkeys', { summary: Constants.per.authOnly })
- public async getPasskeys() {
- const userId = this.getUserId();
- const passkeys = await this.passkeyService.find({
- select: ['id', 'deviceName', 'registeredAt'],
- where: { userId }});
- return this.ok(passkeys);
- }
-
- @Post('/unbindPasskey', { summary: Constants.per.authOnly })
- public async unbindPasskey(@Body(ALL) body: any) {
- const userId = this.getUserId();
- const passkeyId = body.id;
-
- const passkey = await this.passkeyService.findOne({
- where: { id: passkeyId, userId },
- });
-
- if (!passkey) {
- throw new Error('Passkey不存在');
- }
-
- await this.passkeyService.delete([passkey.id]);
- return this.ok({});
- }
}
diff --git a/packages/ui/certd-server/src/controller/basic/login/passkey-controller.ts b/packages/ui/certd-server/src/controller/user/mine/passkey-controller.ts
similarity index 61%
rename from packages/ui/certd-server/src/controller/basic/login/passkey-controller.ts
rename to packages/ui/certd-server/src/controller/user/mine/passkey-controller.ts
index eef51f7eb..7ff49ab55 100644
--- a/packages/ui/certd-server/src/controller/basic/login/passkey-controller.ts
+++ b/packages/ui/certd-server/src/controller/user/mine/passkey-controller.ts
@@ -4,8 +4,8 @@ import { BaseController, Constants } from "@certd/lib-server";
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
@Provide()
-@Controller('/api/passkey')
-export class PasskeyController extends BaseController {
+@Controller('/api/mine/passkey')
+export class MinePasskeyController extends BaseController {
@Inject()
passkeyService: PasskeyService;
@@ -39,12 +39,12 @@ export class PasskeyController extends BaseController {
});
}
- @Post('/verifyRegistration', { summary: Constants.per.guest })
+ @Post('/verifyRegistration', { summary: Constants.per.authOnly })
public async verifyRegistration(
@Body(ALL)
body: any
) {
- const userId = body.userId;
+ const userId = this.getUserId()
const response = body.response;
const challenge = body.challenge;
const deviceName = body.deviceName;
@@ -60,28 +60,14 @@ export class PasskeyController extends BaseController {
return this.ok(result);
}
- @Post('/generateAuthentication', { summary: Constants.per.guest })
- public async generateAuthentication(
- @Body(ALL)
- body: any
- ) {
- const options = await this.passkeyService.generateAuthenticationOptions(
- this.ctx
- );
+
- return this.ok({
- ...options,
- });
- }
-
-
-
- @Post('/register', { summary: Constants.per.guest })
+ @Post('/register', { summary: Constants.per.authOnly })
public async registerPasskey(
@Body(ALL)
body: any
) {
- const userId = body.userId;
+ const userId = this.getUserId();
const response = body.response;
const deviceName = body.deviceName;
const challenge = body.challenge;
@@ -96,4 +82,34 @@ export class PasskeyController extends BaseController {
return this.ok(result);
}
+
+
+ @Post('/list', { summary: Constants.per.authOnly })
+ public async getPasskeys() {
+ const userId = this.getUserId();
+ const passkeys = await this.passkeyService.find({
+ select: ['id', 'deviceName', 'registeredAt', 'transports', 'passkeyId' ,'updateTime'],
+ where: { userId },
+ order: { registeredAt: 'DESC' },
+ });
+ return this.ok(passkeys);
+ }
+
+ @Post('/unbind', { summary: Constants.per.authOnly })
+ public async unbindPasskey(@Body(ALL) body: any) {
+ const userId = this.getUserId();
+ const passkeyId = body.id;
+
+ const passkey = await this.passkeyService.findOne({
+ where: { id: passkeyId, userId },
+ });
+
+ if (!passkey) {
+ throw new Error('Passkey不存在');
+ }
+
+ await this.passkeyService.delete([passkey.id]);
+ return this.ok({});
+ }
+
}
diff --git a/packages/ui/certd-server/src/modules/login/service/passkey-service.ts b/packages/ui/certd-server/src/modules/login/service/passkey-service.ts
index 3a8992bfc..e949f3ffb 100644
--- a/packages/ui/certd-server/src/modules/login/service/passkey-service.ts
+++ b/packages/ui/certd-server/src/modules/login/service/passkey-service.ts
@@ -1,10 +1,11 @@
import { cache } from "@certd/basic";
-import { AuthException, BaseService } from "@certd/lib-server";
+import { AuthException, BaseService, SysSettingsService, SysSiteInfo } from "@certd/lib-server";
+import { isComm } from "@certd/plus-core";
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
+import { InjectEntityModel } from "@midwayjs/typeorm";
+import { Repository } from "typeorm";
import { UserService } from "../../sys/authority/service/user-service.js";
import { PasskeyEntity } from "../entity/passkey.js";
-import { Repository } from "typeorm";
-import { InjectEntityModel } from "@midwayjs/typeorm";
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
@@ -16,15 +17,31 @@ export class PasskeyService extends BaseService {
@InjectEntityModel(PasskeyEntity)
repository: Repository;
+ @Inject()
+ sysSettingsService: SysSettingsService;
+
getRepository(): Repository {
return this.repository;
}
+
+ async getRpInfo(){
+ let rpName = "Certd"
+ if(isComm()){
+ const siteInfo = await this.sysSettingsService.getSetting(SysSiteInfo);
+ rpName = siteInfo.title || rpName;
+ }
+ return {
+ rpName,
+ }
+ }
async generateRegistrationOptions(userId: number, username: string, remoteIp: string, ctx: any) {
const { generateRegistrationOptions } = await import("@simplewebauthn/server");
const user = await this.userService.info(userId);
+ const {rpName} = await this.getRpInfo();
+
const options = await generateRegistrationOptions({
- rpName: "Certd",
+ rpName: rpName,
rpID: this.getRpId(ctx),
userID: new Uint8Array([userId]),
userName: username,
@@ -184,6 +201,7 @@ export class PasskeyService extends BaseService {
}
passkey.counter = verification.counter;
+ passkey.updateTime = new Date();
await this.repository.save(passkey);
const user = await this.userService.info(passkey.userId);