mirror of
https://github.com/certd/certd.git
synced 2026-04-28 07:57:25 +08:00
chore: passkey登录优化
This commit is contained in:
@@ -101,4 +101,5 @@ export default {
|
|||||||
registerPasskey: "Register Passkey",
|
registerPasskey: "Register Passkey",
|
||||||
deviceName: "Device Name",
|
deviceName: "Device Name",
|
||||||
deviceNameHelper: "Please enter the device name, used to identify the device",
|
deviceNameHelper: "Please enter the device name, used to identify the device",
|
||||||
|
passkeyRegisterHelper: "Site domain change will invalidate passkey",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -103,4 +103,5 @@ export default {
|
|||||||
registerPasskey: "注册Passkey",
|
registerPasskey: "注册Passkey",
|
||||||
deviceName: "设备名称",
|
deviceName: "设备名称",
|
||||||
deviceNameHelper: "请输入当前设备名称,绑定多个时好做区分",
|
deviceNameHelper: "请输入当前设备名称,绑定多个时好做区分",
|
||||||
|
passkeyRegisterHelper: "1、站点域名变更会导致passkey失效;\n2、同一设备同一个用户绑定多次只有最后一次的有效,之前绑定的会失效,需要手动删除",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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({
|
return await request({
|
||||||
url: "/passkey/verifyRegistration",
|
url: "/passkey/verifyRegistration",
|
||||||
method: "post",
|
method: "post",
|
||||||
data: { userId, response, challenge },
|
data: { response, challenge },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generatePasskeyAuthenticationOptions(userId: number) {
|
export async function generatePasskeyAuthenticationOptions() {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/passkey/generateAuthentication",
|
url: "/passkey/generateAuthentication",
|
||||||
method: "post",
|
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({
|
return await request({
|
||||||
url: "/loginByPasskey",
|
url: "/loginByPasskey",
|
||||||
method: "post",
|
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({
|
return await request({
|
||||||
url: "/passkey/register",
|
url: "/passkey/register",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ h6 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.helper {
|
.helper {
|
||||||
color: #aeaeae;
|
color: #8f8f8f;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ export async function OauthBoundUrl(type: string) {
|
|||||||
|
|
||||||
export async function GetPasskeys() {
|
export async function GetPasskeys() {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/mine/passkeys",
|
url: "/mine/passkey/list",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function UnbindPasskey(id: number) {
|
export async function UnbindPasskey(id: number) {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/mine/unbindPasskey",
|
url: "/mine/passkey/unbind",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: { id },
|
data: { id },
|
||||||
});
|
});
|
||||||
@@ -110,39 +110,23 @@ export interface PasskeyCredential {
|
|||||||
|
|
||||||
export async function generatePasskeyRegistrationOptions() {
|
export async function generatePasskeyRegistrationOptions() {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/passkey/generateRegistration",
|
url: "/mine/passkey/generateRegistration",
|
||||||
method: "post",
|
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({
|
return await request({
|
||||||
url: "/passkey/verifyRegistration",
|
url: "/mine/passkey/verifyRegistration",
|
||||||
method: "post",
|
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({
|
return await request({
|
||||||
url: "/passkey/generateAuthentication",
|
url: "/mine/passkey/register",
|
||||||
method: "post",
|
method: "post",
|
||||||
data: { userId },
|
data: { response, challenge, deviceName },
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,11 @@
|
|||||||
<div v-for="passkey in passkeys" :key="passkey.id" class="flex items-center gap-4 p-2 border-b">
|
<div v-for="passkey in passkeys" :key="passkey.id" class="flex items-center gap-4 p-2 border-b">
|
||||||
<fs-icon icon="ion:finger-print" class="text-blue-500 fs-24" />
|
<fs-icon icon="ion:finger-print" class="text-blue-500 fs-24" />
|
||||||
<span class="w-40 truncate" :title="passkey.passkeyId">{{ passkey.deviceName }}</span>
|
<span class="w-40 truncate" :title="passkey.passkeyId">{{ passkey.deviceName }}</span>
|
||||||
<span class="text-sm text-gray-500">{{ formatDate(passkey.registeredAt) }}</span>
|
<span>
|
||||||
|
<div class="text-sm text-gray-500">注册时间:{{ formatDate(passkey.registeredAt) }}</div>
|
||||||
|
<div class="text-sm text-gray-500">最后使用:{{ formatDate(passkey.updateTime) }}</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
<a-button type="primary" danger @click="unbindPasskey(passkey.id)">解绑</a-button>
|
<a-button type="primary" danger @click="unbindPasskey(passkey.id)">解绑</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,6 +46,9 @@
|
|||||||
<div v-if="!passkeySupported" class="text-red-500 text-sm mt-2">
|
<div v-if="!passkeySupported" class="text-red-500 text-sm mt-2">
|
||||||
{{ t("authentication.passkeyNotSupported") }}
|
{{ t("authentication.passkeyNotSupported") }}
|
||||||
</div>
|
</div>
|
||||||
|
<pre class="helper"
|
||||||
|
>{{ t("authentication.passkeyRegisterHelper") }}
|
||||||
|
</pre>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item :label="t('common.handle')">
|
<a-descriptions-item :label="t('common.handle')">
|
||||||
<a-button type="primary" @click="doUpdate">{{ t("authentication.updateProfile") }}</a-button>
|
<a-button type="primary" @click="doUpdate">{{ t("authentication.updateProfile") }}</a-button>
|
||||||
@@ -59,10 +66,11 @@ import ChangePasswordButton from "/@/views/certd/mine/change-password-button.vue
|
|||||||
import { useI18n } from "/src/locales";
|
import { useI18n } from "/src/locales";
|
||||||
import { useUserProfile } from "./use";
|
import { useUserProfile } from "./use";
|
||||||
import { usePasskeyRegister } 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 { useSettingStore } from "/@/store/settings";
|
||||||
import { isEmpty } from "lodash-es";
|
import { isEmpty } from "lodash-es";
|
||||||
import { dict } from "@fast-crud/fast-crud";
|
import { dict } from "@fast-crud/fast-crud";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -190,9 +198,15 @@ async function doRegisterPasskey(deviceName: string) {
|
|||||||
const res: any = await api.generatePasskeyRegistrationOptions();
|
const res: any = await api.generatePasskeyRegistrationOptions();
|
||||||
const options = res;
|
const options = res;
|
||||||
|
|
||||||
navigator.credentials.query({
|
// navigator.credentials.query({
|
||||||
publicKey: options,
|
// 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({
|
const credential = await (navigator.credentials as any).create({
|
||||||
publicKey: {
|
publicKey: {
|
||||||
challenge: Uint8Array.from(atob(options.challenge.replace(/-/g, "+").replace(/_/g, "/")), c => c.charCodeAt(0)),
|
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,
|
pubKeyCredParams: options.pubKeyCredParams,
|
||||||
timeout: options.timeout || 60000,
|
timeout: options.timeout || 60000,
|
||||||
attestation: options.attestation,
|
attestation: options.attestation,
|
||||||
excludeCredentials: options.excludeCredentials || [],
|
// excludeCredentials: excludeCredentials,
|
||||||
user: {
|
user: {
|
||||||
id: Uint8Array.from([res.userId]),
|
id: new TextEncoder().encode(options.userId + ""),
|
||||||
name: userInfo.value.username,
|
name: userInfo.value.username,
|
||||||
displayName: deviceName,
|
displayName: deviceName,
|
||||||
},
|
},
|
||||||
@@ -222,18 +236,20 @@ async function doRegisterPasskey(deviceName: string) {
|
|||||||
clientDataJSON: toBase64Url(credential.response.clientDataJSON),
|
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();
|
await loadPasskeys();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error("Passkey注册失败:", e);
|
console.error("Passkey注册失败:", e);
|
||||||
Modal.error({ title: "错误", content: e.message || "Passkey注册失败" });
|
notification.error({ message: "错误", description: e.message || "Passkey注册失败" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (dateString: string) => {
|
const formatDate = (dateString: string) => {
|
||||||
if (!dateString) return "";
|
if (!dateString) return "";
|
||||||
return new Date(dateString).toLocaleString("zh-CN");
|
return dayjs(dateString).format("YYYY-MM-DD HH:mm:ss");
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkPasskeySupport = () => {
|
const checkPasskeySupport = () => {
|
||||||
|
|||||||
@@ -233,7 +233,6 @@ const handlePasskeyLogin = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loginRes: any = await UserApi.loginByPasskey({
|
const loginRes: any = await UserApi.loginByPasskey({
|
||||||
userId: optionsResponse.userId,
|
|
||||||
credential,
|
credential,
|
||||||
challenge: options.challenge,
|
challenge: options.challenge,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AddonService, BaseController, Constants, SysPublicSettings, SysSettings
|
|||||||
import { CodeService } from "../../../modules/basic/service/code-service.js";
|
import { CodeService } from "../../../modules/basic/service/code-service.js";
|
||||||
import { checkComm } from "@certd/plus-core";
|
import { checkComm } from "@certd/plus-core";
|
||||||
import { CaptchaService } from "../../../modules/basic/service/captcha-service.js";
|
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()
|
@Inject()
|
||||||
captchaService: CaptchaService;
|
captchaService: CaptchaService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
passkeyService: PasskeyService;
|
||||||
|
|
||||||
|
|
||||||
@Post('/login', { summary: Constants.per.guest })
|
@Post('/login', { summary: Constants.per.guest })
|
||||||
public async login(
|
public async login(
|
||||||
@Body(ALL)
|
@Body(ALL)
|
||||||
@@ -81,22 +86,36 @@ export class LoginController extends BaseController {
|
|||||||
return this.ok(token);
|
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({
|
@Post('/passkey/generateAuthentication', { summary: Constants.per.guest })
|
||||||
credential,
|
public async generateAuthentication() {
|
||||||
challenge,
|
const options = await this.passkeyService.generateAuthenticationOptions(
|
||||||
}, this.ctx);
|
this.ctx
|
||||||
|
);
|
||||||
// this.writeTokenCookie(token);
|
|
||||||
return this.ok(token);
|
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 })
|
@Post('/logout', { summary: Constants.per.authOnly })
|
||||||
public logout() {
|
public logout() {
|
||||||
|
|||||||
@@ -50,30 +50,4 @@ export class MineController extends BaseController {
|
|||||||
});
|
});
|
||||||
return this.ok({});
|
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({});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-21
@@ -4,8 +4,8 @@ import { BaseController, Constants } from "@certd/lib-server";
|
|||||||
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
@Controller('/api/passkey')
|
@Controller('/api/mine/passkey')
|
||||||
export class PasskeyController extends BaseController {
|
export class MinePasskeyController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
passkeyService: PasskeyService;
|
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(
|
public async verifyRegistration(
|
||||||
@Body(ALL)
|
@Body(ALL)
|
||||||
body: any
|
body: any
|
||||||
) {
|
) {
|
||||||
const userId = body.userId;
|
const userId = this.getUserId()
|
||||||
const response = body.response;
|
const response = body.response;
|
||||||
const challenge = body.challenge;
|
const challenge = body.challenge;
|
||||||
const deviceName = body.deviceName;
|
const deviceName = body.deviceName;
|
||||||
@@ -60,28 +60,14 @@ export class PasskeyController extends BaseController {
|
|||||||
return this.ok(result);
|
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({
|
@Post('/register', { summary: Constants.per.authOnly })
|
||||||
...options,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Post('/register', { summary: Constants.per.guest })
|
|
||||||
public async registerPasskey(
|
public async registerPasskey(
|
||||||
@Body(ALL)
|
@Body(ALL)
|
||||||
body: any
|
body: any
|
||||||
) {
|
) {
|
||||||
const userId = body.userId;
|
const userId = this.getUserId();
|
||||||
const response = body.response;
|
const response = body.response;
|
||||||
const deviceName = body.deviceName;
|
const deviceName = body.deviceName;
|
||||||
const challenge = body.challenge;
|
const challenge = body.challenge;
|
||||||
@@ -96,4 +82,34 @@ export class PasskeyController extends BaseController {
|
|||||||
|
|
||||||
return this.ok(result);
|
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({});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import { cache } from "@certd/basic";
|
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 { 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 { UserService } from "../../sys/authority/service/user-service.js";
|
||||||
import { PasskeyEntity } from "../entity/passkey.js";
|
import { PasskeyEntity } from "../entity/passkey.js";
|
||||||
import { Repository } from "typeorm";
|
|
||||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
@@ -16,15 +17,31 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
|||||||
@InjectEntityModel(PasskeyEntity)
|
@InjectEntityModel(PasskeyEntity)
|
||||||
repository: Repository<PasskeyEntity>;
|
repository: Repository<PasskeyEntity>;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
sysSettingsService: SysSettingsService;
|
||||||
|
|
||||||
getRepository(): Repository<PasskeyEntity> {
|
getRepository(): Repository<PasskeyEntity> {
|
||||||
return this.repository;
|
return this.repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRpInfo(){
|
||||||
|
let rpName = "Certd"
|
||||||
|
if(isComm()){
|
||||||
|
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
||||||
|
rpName = siteInfo.title || rpName;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
rpName,
|
||||||
|
}
|
||||||
|
}
|
||||||
async generateRegistrationOptions(userId: number, username: string, remoteIp: string, ctx: any) {
|
async generateRegistrationOptions(userId: number, username: string, remoteIp: string, ctx: any) {
|
||||||
const { generateRegistrationOptions } = await import("@simplewebauthn/server");
|
const { generateRegistrationOptions } = await import("@simplewebauthn/server");
|
||||||
const user = await this.userService.info(userId);
|
const user = await this.userService.info(userId);
|
||||||
|
|
||||||
|
const {rpName} = await this.getRpInfo();
|
||||||
|
|
||||||
const options = await generateRegistrationOptions({
|
const options = await generateRegistrationOptions({
|
||||||
rpName: "Certd",
|
rpName: rpName,
|
||||||
rpID: this.getRpId(ctx),
|
rpID: this.getRpId(ctx),
|
||||||
userID: new Uint8Array([userId]),
|
userID: new Uint8Array([userId]),
|
||||||
userName: username,
|
userName: username,
|
||||||
@@ -184,6 +201,7 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
passkey.counter = verification.counter;
|
passkey.counter = verification.counter;
|
||||||
|
passkey.updateTime = new Date();
|
||||||
await this.repository.save(passkey);
|
await this.repository.save(passkey);
|
||||||
|
|
||||||
const user = await this.userService.info(passkey.userId);
|
const user = await this.userService.info(passkey.userId);
|
||||||
|
|||||||
Reference in New Issue
Block a user