mirror of
https://github.com/certd/certd.git
synced 2026-04-24 20:57:26 +08:00
perf: 支持passkey登录
This commit is contained in:
@@ -68,6 +68,14 @@ export default {
|
|||||||
|
|
||||||
smsTab: "Login via SMS code",
|
smsTab: "Login via SMS code",
|
||||||
passwordTab: "Password login",
|
passwordTab: "Password login",
|
||||||
|
passkeyTab: "Passkey Login",
|
||||||
|
passkeyLogin: "Passkey Login",
|
||||||
|
passkeyHelper: "Login with your biometric or security key",
|
||||||
|
passkeyNotSupported: "Your browser does not support Passkey",
|
||||||
|
passkeyRegister: "Register Passkey",
|
||||||
|
passkeyRegistered: "Passkey Registered",
|
||||||
|
passkeyRegisterSuccess: "Passkey registered successfully",
|
||||||
|
passkeyRegisterFailed: "Passkey registration failed",
|
||||||
title: "Change Password",
|
title: "Change Password",
|
||||||
weakPasswordWarning: "For your account security, please change your password immediately",
|
weakPasswordWarning: "For your account security, please change your password immediately",
|
||||||
changeNow: "Change Now",
|
changeNow: "Change Now",
|
||||||
@@ -90,4 +98,7 @@ export default {
|
|||||||
updateProfile: "Update Profile",
|
updateProfile: "Update Profile",
|
||||||
oauthLoginTitle: "Other ways of login",
|
oauthLoginTitle: "Other ways of login",
|
||||||
oauthOnlyLoginTitle: "Login",
|
oauthOnlyLoginTitle: "Login",
|
||||||
|
registerPasskey: "Register Passkey",
|
||||||
|
deviceName: "Device Name",
|
||||||
|
deviceNameHelper: "Please enter the device name, used to identify the device",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -68,6 +68,14 @@ export default {
|
|||||||
|
|
||||||
smsTab: "手机号登录/注册",
|
smsTab: "手机号登录/注册",
|
||||||
passwordTab: "密码登录",
|
passwordTab: "密码登录",
|
||||||
|
passkeyTab: "Passkey登录",
|
||||||
|
passkeyLogin: "Passkey登录",
|
||||||
|
passkeyHelper: "使用您的生物识别或安全密钥登录",
|
||||||
|
passkeyNotSupported: "您的浏览器不支持Passkey",
|
||||||
|
passkeyRegister: "注册Passkey",
|
||||||
|
passkeyRegistered: "Passkey已注册",
|
||||||
|
passkeyRegisterSuccess: "Passkey注册成功",
|
||||||
|
passkeyRegisterFailed: "Passkey注册失败",
|
||||||
|
|
||||||
title: "修改密码",
|
title: "修改密码",
|
||||||
weakPasswordWarning: "为了您的账户安全,请立即修改密码",
|
weakPasswordWarning: "为了您的账户安全,请立即修改密码",
|
||||||
@@ -92,4 +100,7 @@ export default {
|
|||||||
|
|
||||||
oauthLoginTitle: "其他登录方式",
|
oauthLoginTitle: "其他登录方式",
|
||||||
oauthOnlyLoginTitle: "登录",
|
oauthOnlyLoginTitle: "登录",
|
||||||
|
registerPasskey: "注册Passkey",
|
||||||
|
deviceName: "设备名称",
|
||||||
|
deviceNameHelper: "请输入当前设备名称,绑定多个时好做区分",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ export const certdResources = [
|
|||||||
meta: {
|
meta: {
|
||||||
icon: "ion:person-outline",
|
icon: "ion:person-outline",
|
||||||
auth: true,
|
auth: true,
|
||||||
isMenu: false,
|
isMenu: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -107,3 +107,42 @@ export async function OauthProviders() {
|
|||||||
method: "post",
|
method: "post",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function generatePasskeyRegistrationOptions() {
|
||||||
|
return await request({
|
||||||
|
url: "/passkey/generateRegistration",
|
||||||
|
method: "post",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifyPasskeyRegistration(userId: number, response: any, challenge: string) {
|
||||||
|
return await request({
|
||||||
|
url: "/passkey/verifyRegistration",
|
||||||
|
method: "post",
|
||||||
|
data: { userId, response, challenge },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generatePasskeyAuthenticationOptions(userId: number) {
|
||||||
|
return await request({
|
||||||
|
url: "/passkey/generateAuthentication",
|
||||||
|
method: "post",
|
||||||
|
data: { userId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loginByPasskey(form: { userId: number; credential: any; challenge: string }) {
|
||||||
|
return await request({
|
||||||
|
url: "/loginByPasskey",
|
||||||
|
method: "post",
|
||||||
|
data: form,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function registerPasskey(form: { userId: number; response: any; challenge: string }) {
|
||||||
|
return await request({
|
||||||
|
url: "/passkey/register",
|
||||||
|
method: "post",
|
||||||
|
data: form,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -92,6 +92,16 @@ export const useUserStore = defineStore({
|
|||||||
const loginRes = await UserApi.loginByTwoFactor(form);
|
const loginRes = await UserApi.loginByTwoFactor(form);
|
||||||
return await this.onLoginSuccess(loginRes);
|
return await this.onLoginSuccess(loginRes);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async loginByPasskey(form: any) {
|
||||||
|
const loginRes = await UserApi.loginByPasskey(form);
|
||||||
|
return await this.onLoginSuccess(loginRes);
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerPasskey(form: any) {
|
||||||
|
return await UserApi.registerPasskey(form);
|
||||||
|
},
|
||||||
|
|
||||||
async getUserInfoAction(): Promise<UserInfoRes> {
|
async getUserInfoAction(): Promise<UserInfoRes> {
|
||||||
const userInfo = await UserApi.mine();
|
const userInfo = await UserApi.mine();
|
||||||
this.setUserInfo(userInfo);
|
this.setUserInfo(userInfo);
|
||||||
|
|||||||
@@ -55,3 +55,94 @@ export async function OauthBoundUrl(type: string) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function GetPasskeys() {
|
||||||
|
return await request({
|
||||||
|
url: "/mine/passkeys",
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function UnbindPasskey(id: number) {
|
||||||
|
return await request({
|
||||||
|
url: "/mine/unbindPasskey",
|
||||||
|
method: "POST",
|
||||||
|
data: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PasskeyRegistrationOptions {
|
||||||
|
rp: {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
user: {
|
||||||
|
id: Uint8Array;
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
};
|
||||||
|
challenge: string;
|
||||||
|
pubKeyCredParams: {
|
||||||
|
type: string;
|
||||||
|
alg: number;
|
||||||
|
}[];
|
||||||
|
timeout: number;
|
||||||
|
attestation: string;
|
||||||
|
excludeCredentials: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PasskeyAuthenticationOptions {
|
||||||
|
rpId: string;
|
||||||
|
challenge: string;
|
||||||
|
timeout: number;
|
||||||
|
allowCredentials: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PasskeyCredential {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
rawId: string;
|
||||||
|
response: {
|
||||||
|
attestationObject: string;
|
||||||
|
clientDataJSON: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generatePasskeyRegistrationOptions() {
|
||||||
|
return await request({
|
||||||
|
url: "/passkey/generateRegistration",
|
||||||
|
method: "post",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifyPasskeyRegistration(userId: number, response: any, challenge: string, deviceName: string) {
|
||||||
|
return await request({
|
||||||
|
url: "/passkey/verifyRegistration",
|
||||||
|
method: "post",
|
||||||
|
data: { userId, response, challenge, deviceName },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generatePasskeyAuthenticationOptions(userId: number) {
|
||||||
|
return await request({
|
||||||
|
url: "/passkey/generateAuthentication",
|
||||||
|
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 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -108,3 +108,54 @@ export function useUserProfile() {
|
|||||||
openEditProfileDialog,
|
openEditProfileDialog,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function usePasskeyRegister() {
|
||||||
|
const { openCrudFormDialog } = useFormWrapper();
|
||||||
|
const wrapperRef = ref();
|
||||||
|
async function openRegisterDialog(req: { onSubmit?: (ctx: any) => void }) {
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const deviceNameRef = ref();
|
||||||
|
|
||||||
|
const crudOptions: any = {
|
||||||
|
form: {
|
||||||
|
wrapper: {
|
||||||
|
title: t("authentication.registerPasskey"),
|
||||||
|
width: 500,
|
||||||
|
onOpened(opts: { form: any }) {
|
||||||
|
opts.form.deviceName = "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onSubmit: req.onSubmit,
|
||||||
|
afterSubmit: null,
|
||||||
|
onSuccess: null,
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
deviceName: {
|
||||||
|
title: t("authentication.deviceName"),
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
component: {
|
||||||
|
class: "w-full",
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
span: 24,
|
||||||
|
},
|
||||||
|
helper: t("authentication.deviceNameHelper"),
|
||||||
|
rules: [{ required: true, message: t("authentication.deviceName") }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = await openCrudFormDialog({ crudOptions });
|
||||||
|
wrapperRef.value = wrapper;
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
openRegisterDialog,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item :label="t('authentication.email')">{{ userInfo.email }}</a-descriptions-item>
|
<a-descriptions-item :label="t('authentication.email')">{{ userInfo.email }}</a-descriptions-item>
|
||||||
<a-descriptions-item :label="t('authentication.phoneNumber')">{{ userInfo.phoneCode }}{{ userInfo.mobile }}</a-descriptions-item>
|
<a-descriptions-item :label="t('authentication.phoneNumber')">{{ userInfo.phoneCode }}{{ userInfo.mobile }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="角色">
|
||||||
|
<fs-values-format :model-value="userInfo.roleIds" :dict="roleDict" />
|
||||||
|
</a-descriptions-item>
|
||||||
<a-descriptions-item v-if="settingStore.sysPublic.oauthEnabled && settingStore.isPlus" label="第三方账号绑定">
|
<a-descriptions-item v-if="settingStore.sysPublic.oauthEnabled && settingStore.isPlus" label="第三方账号绑定">
|
||||||
<template v-for="item in computedOauthBounds" :key="item.name">
|
<template v-for="item in computedOauthBounds" :key="item.name">
|
||||||
<div v-if="item.addonId" class="flex items-center gap-2 mb-2">
|
<div v-if="item.addonId" class="flex items-center gap-2 mb-2">
|
||||||
@@ -25,6 +28,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="Passkey">
|
||||||
|
<div v-if="passkeys.length > 0" class="flex flex-col gap-2">
|
||||||
|
<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" />
|
||||||
|
<span class="w-40 truncate" :title="passkey.passkeyId">{{ passkey.deviceName }}</span>
|
||||||
|
<span class="text-sm text-gray-500">{{ formatDate(passkey.registeredAt) }}</span>
|
||||||
|
<a-button type="primary" danger @click="unbindPasskey(passkey.id)">解绑</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-gray-500">暂无Passkey</div>
|
||||||
|
<a-button v-if="passkeySupported" type="primary" class="mt-2" @click="registerPasskey">注册Passkey</a-button>
|
||||||
|
<div v-if="!passkeySupported" class="text-red-500 text-sm mt-2">
|
||||||
|
{{ t("authentication.passkeyNotSupported") }}
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
<change-password-button class="ml-10" :show-button="true"> </change-password-button>
|
<change-password-button class="ml-10" :show-button="true"> </change-password-button>
|
||||||
@@ -40,9 +58,11 @@ import { computed, onMounted, Ref, ref } from "vue";
|
|||||||
import ChangePasswordButton from "/@/views/certd/mine/change-password-button.vue";
|
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 { Modal } from "ant-design-vue";
|
import { usePasskeyRegister } from "./use";
|
||||||
|
import { message, Modal } 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";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -53,11 +73,20 @@ defineOptions({
|
|||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
|
|
||||||
const userInfo: Ref = ref({});
|
const userInfo: Ref = ref({});
|
||||||
|
const passkeys = ref([]);
|
||||||
|
const passkeySupported = ref(false);
|
||||||
|
|
||||||
const getUserInfo = async () => {
|
const getUserInfo = async () => {
|
||||||
userInfo.value = await api.getMineInfo();
|
userInfo.value = await api.getMineInfo();
|
||||||
};
|
};
|
||||||
|
const roleDict = dict({
|
||||||
|
url: "/basic/user/getSimpleRoles",
|
||||||
|
value: "id",
|
||||||
|
label: "name",
|
||||||
|
});
|
||||||
|
|
||||||
const { openEditProfileDialog } = useUserProfile();
|
const { openEditProfileDialog } = useUserProfile();
|
||||||
|
const { openRegisterDialog } = usePasskeyRegister();
|
||||||
|
|
||||||
function doUpdate() {
|
function doUpdate() {
|
||||||
openEditProfileDialog({
|
openEditProfileDialog({
|
||||||
@@ -69,10 +98,12 @@ function doUpdate() {
|
|||||||
|
|
||||||
const oauthBounds = ref([]);
|
const oauthBounds = ref([]);
|
||||||
const oauthProviders = ref([]);
|
const oauthProviders = ref([]);
|
||||||
|
|
||||||
async function loadOauthBounds() {
|
async function loadOauthBounds() {
|
||||||
const res = await api.GetOauthBounds();
|
const res = await api.GetOauthBounds();
|
||||||
oauthBounds.value = res;
|
oauthBounds.value = res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadOauthProviders() {
|
async function loadOauthProviders() {
|
||||||
const res = await api.GetOauthProviders();
|
const res = await api.GetOauthProviders();
|
||||||
oauthProviders.value = res;
|
oauthProviders.value = res;
|
||||||
@@ -102,12 +133,116 @@ async function unbind(type: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function bind(type: string) {
|
async function bind(type: string) {
|
||||||
//获取第三方登录URL
|
|
||||||
const res = await api.OauthBoundUrl(type);
|
const res = await api.OauthBoundUrl(type);
|
||||||
const loginUrl = res.loginUrl;
|
const loginUrl = res.loginUrl;
|
||||||
window.location.href = loginUrl;
|
window.location.href = loginUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadPasskeys() {
|
||||||
|
try {
|
||||||
|
const res = await api.GetPasskeys();
|
||||||
|
passkeys.value = res;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error("加载Passkey失败:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unbindPasskey(id: number) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: "确认解绑吗?",
|
||||||
|
okText: "确认",
|
||||||
|
okType: "danger",
|
||||||
|
onOk: async () => {
|
||||||
|
await api.UnbindPasskey(id);
|
||||||
|
await loadPasskeys();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const toBase64Url = (buffer: ArrayBuffer) => {
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
let binary = "";
|
||||||
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
async function registerPasskey() {
|
||||||
|
if (!passkeySupported.value) {
|
||||||
|
Modal.error({ title: "错误", content: "浏览器不支持Passkey" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await openRegisterDialog({
|
||||||
|
onSubmit: async (ctx: any) => {
|
||||||
|
const deviceName = ctx.form.deviceName;
|
||||||
|
if (!deviceName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await doRegisterPasskey(deviceName);
|
||||||
|
message.success("Passkey注册成功");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doRegisterPasskey(deviceName: string) {
|
||||||
|
try {
|
||||||
|
const res: any = await api.generatePasskeyRegistrationOptions();
|
||||||
|
const options = res;
|
||||||
|
|
||||||
|
navigator.credentials.query({
|
||||||
|
publicKey: options,
|
||||||
|
});
|
||||||
|
const credential = await (navigator.credentials as any).create({
|
||||||
|
publicKey: {
|
||||||
|
challenge: Uint8Array.from(atob(options.challenge.replace(/-/g, "+").replace(/_/g, "/")), c => c.charCodeAt(0)),
|
||||||
|
rp: options.rp,
|
||||||
|
pubKeyCredParams: options.pubKeyCredParams,
|
||||||
|
timeout: options.timeout || 60000,
|
||||||
|
attestation: options.attestation,
|
||||||
|
excludeCredentials: options.excludeCredentials || [],
|
||||||
|
user: {
|
||||||
|
id: Uint8Array.from([res.userId]),
|
||||||
|
name: userInfo.value.username,
|
||||||
|
displayName: deviceName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!credential) {
|
||||||
|
throw new Error("Passkey注册失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
id: credential.id,
|
||||||
|
type: credential.type,
|
||||||
|
rawId: toBase64Url(credential.rawId),
|
||||||
|
response: {
|
||||||
|
attestationObject: toBase64Url(credential.response.attestationObject),
|
||||||
|
clientDataJSON: toBase64Url(credential.response.clientDataJSON),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyRes: any = await api.verifyPasskeyRegistration(userInfo.value.id, response, options.challenge, deviceName);
|
||||||
|
await loadPasskeys();
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error("Passkey注册失败:", e);
|
||||||
|
Modal.error({ title: "错误", content: e.message || "Passkey注册失败" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
if (!dateString) return "";
|
||||||
|
return new Date(dateString).toLocaleString("zh-CN");
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkPasskeySupport = () => {
|
||||||
|
passkeySupported.value = false;
|
||||||
|
if (typeof window !== "undefined" && "credentials" in navigator && "PublicKeyCredential" in window) {
|
||||||
|
passkeySupported.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const userAvatar = computed(() => {
|
const userAvatar = computed(() => {
|
||||||
if (isEmpty(userInfo.value.avatar)) {
|
if (isEmpty(userInfo.value.avatar)) {
|
||||||
return "";
|
return "";
|
||||||
@@ -123,5 +258,7 @@ onMounted(async () => {
|
|||||||
await getUserInfo();
|
await getUserInfo();
|
||||||
await loadOauthBounds();
|
await loadOauthBounds();
|
||||||
await loadOauthProviders();
|
await loadOauthProviders();
|
||||||
|
await loadPasskeys();
|
||||||
|
checkPasskeySupport();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -46,11 +46,21 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="passkey" :tab="t('authentication.passkeyTab')">
|
||||||
|
<template v-if="formState.loginType === 'passkey'">
|
||||||
|
<div v-if="!passkeySupported" class="text-red-500 text-sm mt-2">
|
||||||
|
{{ t("authentication.passkeyNotSupported") }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" size="large" html-type="button" :loading="loading" class="login-button" @click="handleFinish">
|
<a-button v-if="formState.loginType !== 'passkey'" type="primary" size="large" html-type="button" :loading="loading" class="login-button" @click="handleFinish">
|
||||||
{{ queryBindCode ? t("authentication.bindButton") : t("authentication.loginButton") }}
|
{{ queryBindCode ? t("authentication.bindButton") : t("authentication.loginButton") }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<a-button v-else type="primary" size="large" html-type="button" :loading="loading" class="login-button" :disabled="!passkeySupported" @click="handlePasskeyLogin">
|
||||||
|
{{ t("authentication.passkeyLogin") }}
|
||||||
|
</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<div class="mt-2 flex justify-between items-center">
|
<div class="mt-2 flex justify-between items-center">
|
||||||
@@ -94,8 +104,8 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, nextTick, reactive, ref, toRaw } from "vue";
|
import { computed, nextTick, reactive, ref, toRaw, onMounted } from "vue";
|
||||||
import { useUserStore } from "/src/store/user";
|
import { useUserStore } from "/src/store/user";
|
||||||
import { useSettingStore } from "/@/store/settings";
|
import { useSettingStore } from "/@/store/settings";
|
||||||
import { utils } from "@fast-crud/fast-crud";
|
import { utils } from "@fast-crud/fast-crud";
|
||||||
@@ -103,20 +113,18 @@ import SmsCode from "/@/views/framework/login/sms-code.vue";
|
|||||||
import { useI18n } from "/@/locales";
|
import { useI18n } from "/@/locales";
|
||||||
import { LanguageToggle } from "/@/vben/layouts";
|
import { LanguageToggle } from "/@/vben/layouts";
|
||||||
import CaptchaInput from "/@/components/captcha/captcha-input.vue";
|
import CaptchaInput from "/@/components/captcha/captcha-input.vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import OauthFooter from "/@/views/framework/oauth/oauth-footer.vue";
|
import OauthFooter from "/@/views/framework/oauth/oauth-footer.vue";
|
||||||
import * as oauthApi from "../oauth/api";
|
import * as oauthApi from "../oauth/api";
|
||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
export default defineComponent({
|
import { request } from "/src/api/service";
|
||||||
name: "LoginPage",
|
import * as UserApi from "/src/store/user/api.user";
|
||||||
components: { LanguageToggle, SmsCode, CaptchaInput, OauthFooter },
|
|
||||||
setup() {
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const queryBindCode = ref(route.query.bindCode as string | undefined);
|
const queryBindCode = ref(route.query.bindCode as string | undefined);
|
||||||
|
|
||||||
const queryOauthOnly = route.query.oauthOnly as string;
|
const queryOauthOnly = route.query.oauthOnly as string;
|
||||||
const urlLoginType = route.query.loginType as string | undefined;
|
const urlLoginType = route.query.loginType as string | undefined;
|
||||||
const verifyCodeInputRef = ref();
|
const verifyCodeInputRef = ref();
|
||||||
@@ -135,7 +143,7 @@ export default defineComponent({
|
|||||||
phoneCode: "86",
|
phoneCode: "86",
|
||||||
mobile: "",
|
mobile: "",
|
||||||
password: "",
|
password: "",
|
||||||
loginType: urlLoginType || defaultLoginType, //password
|
loginType: urlLoginType || defaultLoginType,
|
||||||
smsCode: "",
|
smsCode: "",
|
||||||
captcha: null,
|
captcha: null,
|
||||||
smsCaptcha: null,
|
smsCaptcha: null,
|
||||||
@@ -182,38 +190,74 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function afterLoginSuccess() {
|
|
||||||
if (queryBindCode.value) {
|
|
||||||
await oauthApi.BindUser(queryBindCode.value);
|
|
||||||
notification.success({ message: "绑定第三方账号成功" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const twoFactor = reactive({
|
const twoFactor = reactive({
|
||||||
loginId: "",
|
loginId: "",
|
||||||
verifyCode: "",
|
verifyCode: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleTwoFactorSubmit = async () => {
|
const passkeySupported = ref(false);
|
||||||
await userStore.loginByTwoFactor(twoFactor);
|
const passkeyEnabled = ref(false);
|
||||||
afterLoginSuccess();
|
|
||||||
|
const checkPasskeySupport = () => {
|
||||||
|
passkeySupported.value = false;
|
||||||
|
if (typeof window !== "undefined" && "credentials" in navigator && "PublicKeyCredential" in window) {
|
||||||
|
passkeySupported.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePasskeyLogin = async () => {
|
||||||
|
if (!passkeySupported.value) {
|
||||||
|
notification.error({ message: t("authentication.passkeyNotSupported") });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const optionsResponse: any = await request({
|
||||||
|
url: "/passkey/generateAuthentication",
|
||||||
|
method: "post",
|
||||||
|
});
|
||||||
|
const options = optionsResponse;
|
||||||
|
|
||||||
|
const credential = await (navigator.credentials as any).get({
|
||||||
|
publicKey: {
|
||||||
|
challenge: Uint8Array.from(atob(options.challenge.replace(/-/g, "+").replace(/_/g, "/")), c => c.charCodeAt(0)),
|
||||||
|
rpId: options.rpId,
|
||||||
|
allowCredentials: options.allowCredentials || [],
|
||||||
|
timeout: options.timeout || 60000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!credential) {
|
||||||
|
throw new Error("Passkey认证失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginRes: any = await UserApi.loginByPasskey({
|
||||||
|
userId: optionsResponse.userId,
|
||||||
|
credential,
|
||||||
|
challenge: options.challenge,
|
||||||
|
});
|
||||||
|
|
||||||
|
await userStore.onLoginSuccess(loginRes);
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error("Passkey登录失败:", e);
|
||||||
|
notification.error({ message: e.message || "Passkey登录失败" });
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFinish = async () => {
|
const handleFinish = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
// formState.captcha = await doCaptchaValidate();
|
|
||||||
// if (!formState.captcha) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
const loginType = formState.loginType;
|
const loginType = formState.loginType;
|
||||||
await userStore.login(loginType, toRaw(formState));
|
await userStore.login(loginType, toRaw(formState));
|
||||||
afterLoginSuccess();
|
if (queryBindCode.value) {
|
||||||
|
await oauthApi.BindUser(queryBindCode.value);
|
||||||
|
notification.success({ message: "绑定第三方账号成功" });
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
//@ts-ignore
|
|
||||||
if (e.code === 10020) {
|
if (e.code === 10020) {
|
||||||
//双重认证
|
|
||||||
//@ts-ignore
|
|
||||||
twoFactor.loginId = e.data;
|
twoFactor.loginId = e.data;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
verifyCodeInputRef.value.focus();
|
verifyCodeInputRef.value.focus();
|
||||||
@@ -230,20 +274,19 @@ export default defineComponent({
|
|||||||
utils.logger.log(errors);
|
utils.logger.log(errors);
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetForm = () => {
|
const handleTwoFactorSubmit = async () => {
|
||||||
formRef.value.resetFields();
|
await userStore.loginByTwoFactor(twoFactor);
|
||||||
|
if (queryBindCode.value) {
|
||||||
|
await oauthApi.BindUser(queryBindCode.value);
|
||||||
|
notification.success({ message: "绑定第三方账号成功" });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoginError = ref();
|
|
||||||
|
|
||||||
const sysPublicSettings = settingStore.getSysPublic;
|
const sysPublicSettings = settingStore.getSysPublic;
|
||||||
|
|
||||||
function hasRegisterTypeEnabled() {
|
const hasRegisterTypeEnabled = () => {
|
||||||
return sysPublicSettings.registerEnabled && (sysPublicSettings.usernameRegisterEnabled || sysPublicSettings.emailRegisterEnabled || sysPublicSettings.mobileRegisterEnabled || sysPublicSettings.smsLoginEnabled);
|
return sysPublicSettings.registerEnabled && (sysPublicSettings.usernameRegisterEnabled || sysPublicSettings.emailRegisterEnabled || sysPublicSettings.mobileRegisterEnabled || sysPublicSettings.smsLoginEnabled);
|
||||||
}
|
};
|
||||||
|
|
||||||
const captchaInputRef = ref();
|
|
||||||
const captchaInputForSmsCode = ref();
|
|
||||||
|
|
||||||
const isOauthOnly = computed(() => {
|
const isOauthOnly = computed(() => {
|
||||||
if (queryOauthOnly === "false" || queryOauthOnly === "0") {
|
if (queryOauthOnly === "false" || queryOauthOnly === "0") {
|
||||||
@@ -252,44 +295,17 @@ export default defineComponent({
|
|||||||
return sysPublicSettings.oauthOnly && settingStore.isPlus && sysPublicSettings.oauthEnabled;
|
return sysPublicSettings.oauthOnly && settingStore.isPlus && sysPublicSettings.oauthEnabled;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
onMounted(() => {
|
||||||
t,
|
checkPasskeySupport();
|
||||||
loading,
|
|
||||||
formState,
|
|
||||||
formRef,
|
|
||||||
rules,
|
|
||||||
layout,
|
|
||||||
isOauthOnly,
|
|
||||||
handleFinishFailed,
|
|
||||||
handleFinish,
|
|
||||||
resetForm,
|
|
||||||
isLoginError,
|
|
||||||
sysPublicSettings,
|
|
||||||
hasRegisterTypeEnabled,
|
|
||||||
twoFactor,
|
|
||||||
handleTwoFactorSubmit,
|
|
||||||
verifyCodeInputRef,
|
|
||||||
settingStore,
|
|
||||||
captchaInputRef,
|
|
||||||
captchaInputForSmsCode,
|
|
||||||
queryBindCode,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.login-page.main {
|
.login-page.main {
|
||||||
//margin: 20px !important;
|
|
||||||
margin-bottom: 100px;
|
margin-bottom: 100px;
|
||||||
|
|
||||||
.user-layout-login {
|
.user-layout-login {
|
||||||
//label {
|
|
||||||
// font-size: 14px;
|
|
||||||
//}
|
|
||||||
|
|
||||||
.fs-icon {
|
.fs-icon {
|
||||||
// color: rgba(0, 0, 0, 0.45);
|
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +344,6 @@ export default defineComponent({
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
//line-height: 22px;
|
|
||||||
|
|
||||||
.item-icon {
|
.item-icon {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
|||||||
title: t("certd.roles"),
|
title: t("certd.roles"),
|
||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
dict: dict({
|
dict: dict({
|
||||||
url: "/sys/authority/role/list",
|
url: "/basic/user/getSimpleRoles",
|
||||||
value: "id",
|
value: "id",
|
||||||
label: "name",
|
label: "name",
|
||||||
}), // 数据字典
|
}), // 数据字典
|
||||||
|
|||||||
@@ -93,16 +93,16 @@ export default (req: any) => {
|
|||||||
// with options
|
// with options
|
||||||
"/api": {
|
"/api": {
|
||||||
//配套后端 https://github.com/fast-crud/fs-server-js
|
//配套后端 https://github.com/fast-crud/fs-server-js
|
||||||
target: "https://127.0.0.1:7002",
|
target: "http://127.0.0.1:7001",
|
||||||
//忽略证书
|
//忽略证书
|
||||||
agent: new https.Agent({ rejectUnauthorized: false }),
|
// agent: new https.Agent({ rejectUnauthorized: false }),
|
||||||
},
|
},
|
||||||
"/certd/api": {
|
"/certd/api": {
|
||||||
//配套后端 https://github.com/fast-crud/fs-server-js
|
//配套后端 https://github.com/fast-crud/fs-server-js
|
||||||
target: "https://127.0.0.1:7002/api",
|
target: "http://127.0.0.1:7001/api",
|
||||||
rewrite: path => path.replace(/^\/certd\/api/, ""),
|
rewrite: path => path.replace(/^\/certd\/api/, ""),
|
||||||
//忽略证书
|
//忽略证书
|
||||||
agent: new https.Agent({ rejectUnauthorized: false }),
|
// agent: new https.Agent({ rejectUnauthorized: false }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
CREATE TABLE "sys_passkey"
|
||||||
|
(
|
||||||
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
"user_id" integer NOT NULL,
|
||||||
|
"device_name" varchar(512) NOT NULL,
|
||||||
|
"passkey_id" varchar(512) NOT NULL,
|
||||||
|
"public_key" varchar(1024) NOT NULL,
|
||||||
|
"counter" integer NOT NULL,
|
||||||
|
"transports" varchar(512) NULL,
|
||||||
|
"registered_at" integer NOT NULL,
|
||||||
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE INDEX "index_passkey_user_id" ON "sys_passkey" ("user_id");
|
||||||
|
CREATE INDEX "index_passkey_passkey_id" ON "sys_passkey" ("passkey_id");
|
||||||
@@ -82,6 +82,8 @@
|
|||||||
"@midwayjs/upload": "3.20.13",
|
"@midwayjs/upload": "3.20.13",
|
||||||
"@midwayjs/validate": "3.20.13",
|
"@midwayjs/validate": "3.20.13",
|
||||||
"@peculiar/x509": "^1.11.0",
|
"@peculiar/x509": "^1.11.0",
|
||||||
|
"@simplewebauthn/browser": "^13.2.2",
|
||||||
|
"@simplewebauthn/server": "^13.2.3",
|
||||||
"@ucloud-sdks/ucloud-sdk-js": "^0.2.4",
|
"@ucloud-sdks/ucloud-sdk-js": "^0.2.4",
|
||||||
"@volcengine/openapi": "^1.28.1",
|
"@volcengine/openapi": "^1.28.1",
|
||||||
"ali-oss": "^6.21.0",
|
"ali-oss": "^6.21.0",
|
||||||
|
|||||||
@@ -81,6 +81,23 @@ 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({
|
||||||
|
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() {
|
||||||
this.ctx.cookies.set("certd_token", "", {
|
this.ctx.cookies.set("certd_token", "", {
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { ALL, Body, Controller, Inject, Post, Provide, RequestIP } from "@midwayjs/core";
|
||||||
|
import { PasskeyService } from "../../../modules/login/service/passkey-service.js";
|
||||||
|
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 {
|
||||||
|
@Inject()
|
||||||
|
passkeyService: PasskeyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
userService: UserService;
|
||||||
|
|
||||||
|
@Post('/generateRegistration', { summary: Constants.per.authOnly })
|
||||||
|
public async generateRegistration(
|
||||||
|
@Body(ALL)
|
||||||
|
body: any,
|
||||||
|
@RequestIP()
|
||||||
|
remoteIp: string
|
||||||
|
) {
|
||||||
|
const userId = this.getUserId()
|
||||||
|
const user = await this.userService.info(userId);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('用户不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = await this.passkeyService.generateRegistrationOptions(
|
||||||
|
userId,
|
||||||
|
user.username,
|
||||||
|
remoteIp,
|
||||||
|
this.ctx
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.ok({
|
||||||
|
...options,
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/verifyRegistration', { summary: Constants.per.guest })
|
||||||
|
public async verifyRegistration(
|
||||||
|
@Body(ALL)
|
||||||
|
body: any
|
||||||
|
) {
|
||||||
|
const userId = body.userId;
|
||||||
|
const response = body.response;
|
||||||
|
const challenge = body.challenge;
|
||||||
|
const deviceName = body.deviceName;
|
||||||
|
|
||||||
|
const result = await this.passkeyService.registerPasskey(
|
||||||
|
userId,
|
||||||
|
response,
|
||||||
|
challenge,
|
||||||
|
deviceName,
|
||||||
|
this.ctx
|
||||||
|
);
|
||||||
|
|
||||||
|
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 })
|
||||||
|
public async registerPasskey(
|
||||||
|
@Body(ALL)
|
||||||
|
body: any
|
||||||
|
) {
|
||||||
|
const userId = body.userId;
|
||||||
|
const response = body.response;
|
||||||
|
const deviceName = body.deviceName;
|
||||||
|
const challenge = body.challenge;
|
||||||
|
|
||||||
|
const result = await this.passkeyService.registerPasskey(
|
||||||
|
userId,
|
||||||
|
response,
|
||||||
|
challenge,
|
||||||
|
deviceName,
|
||||||
|
this.ctx
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { In } from 'typeorm';
|
|||||||
import { AuthService } from '../../../modules/sys/authority/service/auth-service.js';
|
import { AuthService } from '../../../modules/sys/authority/service/auth-service.js';
|
||||||
import { UserService } from '../../../modules/sys/authority/service/user-service.js';
|
import { UserService } from '../../../modules/sys/authority/service/user-service.js';
|
||||||
import { BasicController } from '../../basic/code-controller.js';
|
import { BasicController } from '../../basic/code-controller.js';
|
||||||
|
import { RoleService } from '../../../modules/sys/authority/service/role-service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知
|
* 通知
|
||||||
@@ -15,6 +16,8 @@ export class BasicUserController extends BasicController {
|
|||||||
service: UserService;
|
service: UserService;
|
||||||
@Inject()
|
@Inject()
|
||||||
authService: AuthService;
|
authService: AuthService;
|
||||||
|
@Inject()
|
||||||
|
roleService: RoleService;
|
||||||
|
|
||||||
getService(): UserService {
|
getService(): UserService {
|
||||||
return this.service;
|
return this.service;
|
||||||
@@ -57,4 +60,15 @@ export class BasicUserController extends BasicController {
|
|||||||
return this.ok(users);
|
return this.ok(users);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/getSimpleRoles', {summary: Constants.per.authOnly})
|
||||||
|
async getSimpleRoles() {
|
||||||
|
const roles = await this.roleService.find({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.ok(roles);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
|
|
||||||
import { BaseController, Constants } from '@certd/lib-server';
|
import { BaseController, Constants } from '@certd/lib-server';
|
||||||
import { UserService } from '../../../modules/sys/authority/service/user-service.js';
|
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
|
||||||
|
import { PasskeyService } from '../../../modules/login/service/passkey-service.js';
|
||||||
import { RoleService } from '../../../modules/sys/authority/service/role-service.js';
|
import { RoleService } from '../../../modules/sys/authority/service/role-service.js';
|
||||||
|
import { UserService } from '../../../modules/sys/authority/service/user-service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@@ -10,8 +11,14 @@ import { RoleService } from '../../../modules/sys/authority/service/role-service
|
|||||||
export class MineController extends BaseController {
|
export class MineController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
roleService: RoleService;
|
roleService: RoleService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
passkeyService: PasskeyService;
|
||||||
|
|
||||||
|
|
||||||
@Post('/info', { summary: Constants.per.authOnly })
|
@Post('/info', { summary: Constants.per.authOnly })
|
||||||
public async info() {
|
public async info() {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
@@ -43,4 +50,30 @@ 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({});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('sys_passkey')
|
||||||
|
export class PasskeyEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column({ name: 'user_id', comment: '用户id' })
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
@Column({ name: 'device_name', comment: '设备名称' })
|
||||||
|
deviceName: string;
|
||||||
|
|
||||||
|
@Column({ name: 'passkey_id', comment: 'passkey_id' })
|
||||||
|
passkeyId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'public_key', comment: '公钥', type: 'text' })
|
||||||
|
publicKey: string;
|
||||||
|
|
||||||
|
@Column({ name: 'counter', comment: '计数器' })
|
||||||
|
counter: number;
|
||||||
|
|
||||||
|
@Column({ name: 'transports', comment: '传输方式', type: 'text', nullable: true })
|
||||||
|
transports: string;
|
||||||
|
|
||||||
|
@Column({ name: 'registered_at', comment: '注册时间' })
|
||||||
|
registeredAt: number;
|
||||||
|
|
||||||
|
@Column({ name: 'create_time', comment: '创建时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
createTime: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'update_time', comment: '修改时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
updateTime: Date;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import { UserSettingsService } from "../../mine/service/user-settings-service.js
|
|||||||
import { isPlus } from "@certd/plus-core";
|
import { isPlus } from "@certd/plus-core";
|
||||||
import { AddonService } from "@certd/lib-server";
|
import { AddonService } from "@certd/lib-server";
|
||||||
import { OauthBoundService } from "./oauth-bound-service.js";
|
import { OauthBoundService } from "./oauth-bound-service.js";
|
||||||
|
import { PasskeyService } from "./passkey-service.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@@ -45,6 +46,9 @@ export class LoginService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
oauthBoundService: OauthBoundService;
|
oauthBoundService: OauthBoundService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
passkeyService: PasskeyService;
|
||||||
|
|
||||||
checkIsBlocked(username: string) {
|
checkIsBlocked(username: string) {
|
||||||
const blockDurationKey = `login_block_duration:${username}`;
|
const blockDurationKey = `login_block_duration:${username}`;
|
||||||
const value = cache.get(blockDurationKey);
|
const value = cache.get(blockDurationKey);
|
||||||
@@ -254,4 +258,10 @@ export class LoginService {
|
|||||||
}
|
}
|
||||||
return this.generateToken(info);
|
return this.generateToken(info);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
async loginByPasskey(req: { credential: any; challenge: string }, ctx: any) {
|
||||||
|
const {credential, challenge} = req;
|
||||||
|
const user = await this.passkeyService.loginByPasskey(credential, challenge, ctx);
|
||||||
|
return this.generateToken(user);
|
||||||
|
}}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import { cache } from "@certd/basic";
|
||||||
|
import { AuthException, BaseService } from "@certd/lib-server";
|
||||||
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
|
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 })
|
||||||
|
export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
userService: UserService;
|
||||||
|
|
||||||
|
@InjectEntityModel(PasskeyEntity)
|
||||||
|
repository: Repository<PasskeyEntity>;
|
||||||
|
|
||||||
|
getRepository(): Repository<PasskeyEntity> {
|
||||||
|
return this.repository;
|
||||||
|
}
|
||||||
|
async generateRegistrationOptions(userId: number, username: string, remoteIp: string, ctx: any) {
|
||||||
|
const { generateRegistrationOptions } = await import("@simplewebauthn/server");
|
||||||
|
const user = await this.userService.info(userId);
|
||||||
|
|
||||||
|
const options = await generateRegistrationOptions({
|
||||||
|
rpName: "Certd",
|
||||||
|
rpID: this.getRpId(ctx),
|
||||||
|
userID: new Uint8Array([userId]),
|
||||||
|
userName: username,
|
||||||
|
userDisplayName: user.nickName || username,
|
||||||
|
timeout: 60000,
|
||||||
|
attestationType: "none",
|
||||||
|
excludeCredentials: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
cache.set(`passkey:registration:${options.challenge}`, userId, {
|
||||||
|
ttl: 5 * 60 * 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyRegistrationResponse(
|
||||||
|
userId: number,
|
||||||
|
response: any,
|
||||||
|
challenge: string,
|
||||||
|
ctx: any
|
||||||
|
) {
|
||||||
|
const { verifyRegistrationResponse } = await import("@simplewebauthn/server");
|
||||||
|
|
||||||
|
const storedUserId = cache.get(`passkey:registration:${challenge}`);
|
||||||
|
if (!storedUserId || storedUserId !== userId) {
|
||||||
|
throw new AuthException("注册验证失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
const verification = await verifyRegistrationResponse({
|
||||||
|
response,
|
||||||
|
expectedChallenge: challenge,
|
||||||
|
expectedOrigin: this.getOrigin(ctx),
|
||||||
|
expectedRPID: this.getRpId(ctx),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!verification.verified) {
|
||||||
|
throw new AuthException("注册验证失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.delete(`passkey:registration:${challenge}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
credentialId: verification.registrationInfo.credential.id,
|
||||||
|
credentialPublicKey: verification.registrationInfo.credential.publicKey,
|
||||||
|
counter: verification.registrationInfo.credential.counter,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateAuthenticationOptions(ctx: any) {
|
||||||
|
const { generateAuthenticationOptions } = await import("@simplewebauthn/server");
|
||||||
|
const options = await generateAuthenticationOptions({
|
||||||
|
rpID: this.getRpId(ctx),
|
||||||
|
timeout: 60000,
|
||||||
|
allowCredentials: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// cache.set(`passkey:authentication:${options.challenge}`, userId, {
|
||||||
|
// ttl: 5 * 60 * 1000,
|
||||||
|
// });
|
||||||
|
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyAuthenticationResponse(
|
||||||
|
credential: any,
|
||||||
|
challenge: string,
|
||||||
|
ctx: any
|
||||||
|
) {
|
||||||
|
const { verifyAuthenticationResponse } = await import("@simplewebauthn/server");
|
||||||
|
|
||||||
|
const passkey = await this.repository.findOne({
|
||||||
|
where: {
|
||||||
|
passkeyId: credential.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!passkey) {
|
||||||
|
throw new AuthException("Passkey不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
const verification = await verifyAuthenticationResponse({
|
||||||
|
response:credential,
|
||||||
|
expectedChallenge: challenge,
|
||||||
|
expectedOrigin: this.getOrigin(ctx),
|
||||||
|
expectedRPID: this.getRpId(ctx),
|
||||||
|
credential: {
|
||||||
|
id: passkey.passkeyId,
|
||||||
|
publicKey: new Uint8Array(Buffer.from(passkey.publicKey, 'base64')),
|
||||||
|
counter: passkey.counter,
|
||||||
|
transports: passkey.transports as any,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!verification.verified) {
|
||||||
|
throw new AuthException("认证验证失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.delete(`passkey:authentication:${challenge}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
credentialId: verification.authenticationInfo.credentialID,
|
||||||
|
counter: verification.authenticationInfo.newCounter,
|
||||||
|
userId: passkey.userId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerPasskey(
|
||||||
|
userId: number,
|
||||||
|
response: any,
|
||||||
|
challenge: string,
|
||||||
|
deviceName: string,
|
||||||
|
ctx: any
|
||||||
|
) {
|
||||||
|
const verification = await this.verifyRegistrationResponse(
|
||||||
|
userId,
|
||||||
|
response,
|
||||||
|
challenge,
|
||||||
|
ctx
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.add({
|
||||||
|
userId,
|
||||||
|
passkeyId: verification.credentialId,
|
||||||
|
publicKey: Buffer.from(verification.credentialPublicKey).toString('base64'),
|
||||||
|
counter: verification.counter,
|
||||||
|
deviceName,
|
||||||
|
registeredAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async loginByPasskey( credential: any, challenge: string, ctx: any) {
|
||||||
|
const verification = await this.verifyAuthenticationResponse(
|
||||||
|
credential,
|
||||||
|
challenge,
|
||||||
|
ctx
|
||||||
|
);
|
||||||
|
|
||||||
|
const passkey = await this.repository.findOne({
|
||||||
|
where: {
|
||||||
|
passkeyId: verification.credentialId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!passkey) {
|
||||||
|
throw new AuthException("Passkey不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verification.counter <= passkey.counter) {
|
||||||
|
throw new AuthException("认证失败:计数器异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
passkey.counter = verification.counter;
|
||||||
|
await this.repository.save(passkey);
|
||||||
|
|
||||||
|
const user = await this.userService.info(passkey.userId);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRpId(ctx: any): string {
|
||||||
|
if (ctx && ctx.request && ctx.request.host) {
|
||||||
|
return ctx.request.host.split(':')[0];
|
||||||
|
}
|
||||||
|
return 'localhost';
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOrigin(ctx: any): string {
|
||||||
|
if (ctx && ctx.request) {
|
||||||
|
const protocol = ctx.request.protocol;
|
||||||
|
const host = ctx.request.host;
|
||||||
|
return `${protocol}://${host}`;
|
||||||
|
}
|
||||||
|
return 'https://localhost';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ export const AdminRoleId = 1
|
|||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
export class UserService extends BaseService<UserEntity> {
|
export class UserService extends BaseService<UserEntity> {
|
||||||
|
|
||||||
|
|
||||||
@InjectEntityModel(UserEntity)
|
@InjectEntityModel(UserEntity)
|
||||||
repository: Repository<UserEntity>;
|
repository: Repository<UserEntity>;
|
||||||
@Inject()
|
@Inject()
|
||||||
@@ -278,6 +279,10 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
return user.username;
|
return user.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getByUsername(username: any) {
|
||||||
|
return await this.findOne({ username });
|
||||||
|
}
|
||||||
|
|
||||||
async changePassword(userId: any, form: any) {
|
async changePassword(userId: any, form: any) {
|
||||||
const user = await this.info(userId);
|
const user = await this.info(userId);
|
||||||
const passwordChecked = await this.checkPassword(form.password, user.password, user.passwordVersion);
|
const passwordChecked = await this.checkPassword(form.password, user.password, user.passwordVersion);
|
||||||
|
|||||||
Generated
+231
-61
@@ -49,7 +49,7 @@ importers:
|
|||||||
packages/core/acme-client:
|
packages/core/acme-client:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../basic
|
version: link:../basic
|
||||||
'@peculiar/x509':
|
'@peculiar/x509':
|
||||||
specifier: ^1.11.0
|
specifier: ^1.11.0
|
||||||
@@ -213,11 +213,11 @@ importers:
|
|||||||
packages/core/pipeline:
|
packages/core/pipeline:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../basic
|
version: link:../basic
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: 1.38.12
|
version: 1.39.0
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.7
|
specifier: ^1.11.7
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
@@ -412,7 +412,7 @@ importers:
|
|||||||
packages/libs/lib-k8s:
|
packages/libs/lib-k8s:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@kubernetes/client-node':
|
'@kubernetes/client-node':
|
||||||
specifier: 0.21.0
|
specifier: 0.21.0
|
||||||
@@ -452,20 +452,20 @@ importers:
|
|||||||
packages/libs/lib-server:
|
packages/libs/lib-server:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/acme-client':
|
'@certd/acme-client':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/acme-client
|
version: link:../../core/acme-client
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plugin-lib':
|
'@certd/plugin-lib':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../plugins/plugin-lib
|
version: link:../../plugins/plugin-lib
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: 1.38.12
|
version: 1.39.0
|
||||||
'@midwayjs/cache':
|
'@midwayjs/cache':
|
||||||
specifier: 3.14.0
|
specifier: 3.14.0
|
||||||
version: 3.14.0
|
version: 3.14.0
|
||||||
@@ -610,16 +610,16 @@ importers:
|
|||||||
packages/plugins/plugin-cert:
|
packages/plugins/plugin-cert:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/acme-client':
|
'@certd/acme-client':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/acme-client
|
version: link:../../core/acme-client
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plugin-lib':
|
'@certd/plugin-lib':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../plugin-lib
|
version: link:../plugin-lib
|
||||||
psl:
|
psl:
|
||||||
specifier: ^1.9.0
|
specifier: ^1.9.0
|
||||||
@@ -683,17 +683,17 @@ importers:
|
|||||||
specifier: ^3.964.0
|
specifier: ^3.964.0
|
||||||
version: 3.964.0(aws-crt@1.26.2)
|
version: 3.964.0(aws-crt@1.26.2)
|
||||||
'@certd/acme-client':
|
'@certd/acme-client':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/acme-client
|
version: link:../../core/acme-client
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: 1.38.12
|
version: 1.39.0
|
||||||
'@kubernetes/client-node':
|
'@kubernetes/client-node':
|
||||||
specifier: 0.21.0
|
specifier: 0.21.0
|
||||||
version: 0.21.0
|
version: 0.21.0
|
||||||
@@ -783,16 +783,16 @@ importers:
|
|||||||
packages/pro/commercial-core:
|
packages/pro/commercial-core:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.38.8
|
specifier: ^1.38.12
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/lib-server':
|
'@certd/lib-server':
|
||||||
specifier: ^1.38.8
|
specifier: ^1.38.12
|
||||||
version: link:../../libs/lib-server
|
version: link:../../libs/lib-server
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.38.8
|
specifier: ^1.38.12
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.38.8
|
specifier: ^1.38.12
|
||||||
version: link:../plus-core
|
version: link:../plus-core
|
||||||
'@midwayjs/core':
|
'@midwayjs/core':
|
||||||
specifier: 3.20.11
|
specifier: 3.20.11
|
||||||
@@ -865,16 +865,16 @@ importers:
|
|||||||
packages/pro/plugin-plus:
|
packages/pro/plugin-plus:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.38.8
|
specifier: ^1.38.12
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.38.8
|
specifier: ^1.38.12
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plugin-lib':
|
'@certd/plugin-lib':
|
||||||
specifier: ^1.38.8
|
specifier: ^1.38.12
|
||||||
version: link:../../plugins/plugin-lib
|
version: link:../../plugins/plugin-lib
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.38.8
|
specifier: ^1.38.12
|
||||||
version: link:../plus-core
|
version: link:../plus-core
|
||||||
crypto-js:
|
crypto-js:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
@@ -950,7 +950,7 @@ importers:
|
|||||||
packages/pro/plus-core:
|
packages/pro/plus-core:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.38.8
|
specifier: ^1.38.12
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.7
|
specifier: ^1.11.7
|
||||||
@@ -1246,10 +1246,10 @@ importers:
|
|||||||
version: 0.1.3(zod@3.24.4)
|
version: 0.1.3(zod@3.24.4)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@certd/lib-iframe':
|
'@certd/lib-iframe':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../libs/lib-iframe
|
version: link:../../libs/lib-iframe
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@rollup/plugin-commonjs':
|
'@rollup/plugin-commonjs':
|
||||||
specifier: ^25.0.7
|
specifier: ^25.0.7
|
||||||
@@ -1444,47 +1444,47 @@ importers:
|
|||||||
specifier: ^3.990.0
|
specifier: ^3.990.0
|
||||||
version: 3.990.0(aws-crt@1.26.2)
|
version: 3.990.0(aws-crt@1.26.2)
|
||||||
'@certd/acme-client':
|
'@certd/acme-client':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/acme-client
|
version: link:../../core/acme-client
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/commercial-core':
|
'@certd/commercial-core':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: 1.38.12(better-sqlite3@11.10.0)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.9.3))
|
version: 1.39.0(better-sqlite3@11.10.0)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.9.3))
|
||||||
'@certd/cv4pve-api-javascript':
|
'@certd/cv4pve-api-javascript':
|
||||||
specifier: ^8.4.2
|
specifier: ^8.4.2
|
||||||
version: 8.4.2
|
version: 8.4.2
|
||||||
'@certd/jdcloud':
|
'@certd/jdcloud':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../libs/lib-jdcloud
|
version: link:../../libs/lib-jdcloud
|
||||||
'@certd/lib-huawei':
|
'@certd/lib-huawei':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../libs/lib-huawei
|
version: link:../../libs/lib-huawei
|
||||||
'@certd/lib-k8s':
|
'@certd/lib-k8s':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../libs/lib-k8s
|
version: link:../../libs/lib-k8s
|
||||||
'@certd/lib-server':
|
'@certd/lib-server':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../libs/lib-server
|
version: link:../../libs/lib-server
|
||||||
'@certd/midway-flyway-js':
|
'@certd/midway-flyway-js':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../libs/midway-flyway-js
|
version: link:../../libs/midway-flyway-js
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plugin-cert':
|
'@certd/plugin-cert':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../plugins/plugin-cert
|
version: link:../../plugins/plugin-cert
|
||||||
'@certd/plugin-lib':
|
'@certd/plugin-lib':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: link:../../plugins/plugin-lib
|
version: link:../../plugins/plugin-lib
|
||||||
'@certd/plugin-plus':
|
'@certd/plugin-plus':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: 1.38.12
|
version: 1.39.0
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.38.12
|
specifier: ^1.39.0
|
||||||
version: 1.38.12
|
version: 1.39.0
|
||||||
'@google-cloud/publicca':
|
'@google-cloud/publicca':
|
||||||
specifier: ^1.3.0
|
specifier: ^1.3.0
|
||||||
version: 1.3.0(encoding@0.1.13)
|
version: 1.3.0(encoding@0.1.13)
|
||||||
@@ -1539,6 +1539,12 @@ importers:
|
|||||||
'@peculiar/x509':
|
'@peculiar/x509':
|
||||||
specifier: ^1.11.0
|
specifier: ^1.11.0
|
||||||
version: 1.12.3
|
version: 1.12.3
|
||||||
|
'@simplewebauthn/browser':
|
||||||
|
specifier: ^13.2.2
|
||||||
|
version: 13.2.2
|
||||||
|
'@simplewebauthn/server':
|
||||||
|
specifier: ^13.2.3
|
||||||
|
version: 13.2.3
|
||||||
'@ucloud-sdks/ucloud-sdk-js':
|
'@ucloud-sdks/ucloud-sdk-js':
|
||||||
specifier: ^0.2.4
|
specifier: ^0.2.4
|
||||||
version: 0.2.4
|
version: 0.2.4
|
||||||
@@ -2820,17 +2826,17 @@ packages:
|
|||||||
'@better-scroll/zoom@2.5.1':
|
'@better-scroll/zoom@2.5.1':
|
||||||
resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==}
|
resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==}
|
||||||
|
|
||||||
'@certd/commercial-core@1.38.12':
|
'@certd/commercial-core@1.39.0':
|
||||||
resolution: {integrity: sha512-kP4vM3F+D6TME9NYSM7Q1YqTx6Ig1dseWQUFVf7GnfG9E3A2odaRwmeYwy5QomChF0vbPHPkJqOtfYv9WItikQ==}
|
resolution: {integrity: sha512-amnJsyLdNMTUU+v67exGMHe6igU5m0vaCJaWjq6c+CQN7PKkM9Bt7H7f7FlbHRon8q6AngtpIgQNhIx7473Dmg==}
|
||||||
|
|
||||||
'@certd/cv4pve-api-javascript@8.4.2':
|
'@certd/cv4pve-api-javascript@8.4.2':
|
||||||
resolution: {integrity: sha512-udGce7ewrVl4DmZvX+17PjsnqsdDIHEDatr8QP0AVrY2p+8JkaSPW4mXCKiLGf82C9K2+GXgT+qNIqgW7tfF9Q==}
|
resolution: {integrity: sha512-udGce7ewrVl4DmZvX+17PjsnqsdDIHEDatr8QP0AVrY2p+8JkaSPW4mXCKiLGf82C9K2+GXgT+qNIqgW7tfF9Q==}
|
||||||
|
|
||||||
'@certd/plugin-plus@1.38.12':
|
'@certd/plugin-plus@1.39.0':
|
||||||
resolution: {integrity: sha512-I0aQAzIRaDFSwM2vb/ycLqaNjVcb/fWUOgmX/BpHEnN446oNk5+pl8d4KFE+OMzEDQcwOwZhdXhjrOsskqY3PA==}
|
resolution: {integrity: sha512-/cS9XYwjgP6JNgysHKz5tN93D8+CIOviBtLdnkkaZ6nXluLueRSBqNNvCqQM8dE1GndS7sEYj3RnkX7ver/Q3A==}
|
||||||
|
|
||||||
'@certd/plus-core@1.38.12':
|
'@certd/plus-core@1.39.0':
|
||||||
resolution: {integrity: sha512-2BKhInDmMrH4l/WRKcSq7E6PA4ANcE1PVMIVoWlhSnnW+sghYUioymRaSoGTxJYfSvGHlLrqZXAassQHAzm98g==}
|
resolution: {integrity: sha512-wLIMH38oBCtPBu3xxG2JGBiFchT0ko9Q/iPIoF1YJnKooGYS69qjtqEwGoGo5nHFqrRJB4RGmd880wj7wCb5cg==}
|
||||||
|
|
||||||
'@certd/vue-js-cron-core@6.0.3':
|
'@certd/vue-js-cron-core@6.0.3':
|
||||||
resolution: {integrity: sha512-kqzoAMhYz9j6FGNWEODRYtt4NpUEUwjpkU89z5WVg2tCtOcI5VhwyUGOd8AxiBCRfd6PtXvzuqw85PaOps9wrQ==}
|
resolution: {integrity: sha512-kqzoAMhYz9j6FGNWEODRYtt4NpUEUwjpkU89z5WVg2tCtOcI5VhwyUGOd8AxiBCRfd6PtXvzuqw85PaOps9wrQ==}
|
||||||
@@ -3528,6 +3534,9 @@ packages:
|
|||||||
'@hapi/topo@5.1.0':
|
'@hapi/topo@5.1.0':
|
||||||
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
|
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
|
||||||
|
|
||||||
|
'@hexagon/base64@1.1.28':
|
||||||
|
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
|
||||||
|
|
||||||
'@httptoolkit/websocket-stream@6.0.1':
|
'@httptoolkit/websocket-stream@6.0.1':
|
||||||
resolution: {integrity: sha512-A0NOZI+Glp3Xgcz6Na7i7o09+/+xm2m0UCU8gdtM2nIv6/cjLmhMZMqehSpTlgbx9omtLmV8LVqOskPEyWnmZQ==}
|
resolution: {integrity: sha512-A0NOZI+Glp3Xgcz6Na7i7o09+/+xm2m0UCU8gdtM2nIv6/cjLmhMZMqehSpTlgbx9omtLmV8LVqOskPEyWnmZQ==}
|
||||||
|
|
||||||
@@ -3776,6 +3785,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-VxP+PLlw62B14hP5g19RA6svqZHNN4J1P2eIzDpwQb2RNeBMuZMUveJMgiZVKF5nNjCh2mm4mKk11wcTr+v+vw==}
|
resolution: {integrity: sha512-VxP+PLlw62B14hP5g19RA6svqZHNN4J1P2eIzDpwQb2RNeBMuZMUveJMgiZVKF5nNjCh2mm4mKk11wcTr+v+vw==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
|
||||||
|
'@levischuck/tiny-cbor@0.2.11':
|
||||||
|
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
|
||||||
|
|
||||||
'@manypkg/find-root@2.2.3':
|
'@manypkg/find-root@2.2.3':
|
||||||
resolution: {integrity: sha512-jtEZKczWTueJYHjGpxU3KJQ08Gsrf4r6Q2GjmPp/RGk5leeYAA1eyDADSAF+KVCsQ6EwZd/FMcOFCoMhtqdCtQ==}
|
resolution: {integrity: sha512-jtEZKczWTueJYHjGpxU3KJQ08Gsrf4r6Q2GjmPp/RGk5leeYAA1eyDADSAF+KVCsQ6EwZd/FMcOFCoMhtqdCtQ==}
|
||||||
engines: {node: '>=14.18.0'}
|
engines: {node: '>=14.18.0'}
|
||||||
@@ -4016,39 +4028,76 @@ packages:
|
|||||||
'@paralleldrive/cuid2@2.2.2':
|
'@paralleldrive/cuid2@2.2.2':
|
||||||
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
|
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-android@2.6.0':
|
||||||
|
resolution: {integrity: sha512-cBRCKtYPF7vJGN76/yG8VbxRcHLPF3HnkoHhKOZeHpoVtbMYfY9ROKtH3DtYUY9m8uI1Mh47PRhHf2hSK3xcSQ==}
|
||||||
|
|
||||||
'@peculiar/asn1-cms@2.3.15':
|
'@peculiar/asn1-cms@2.3.15':
|
||||||
resolution: {integrity: sha512-B+DoudF+TCrxoJSTjjcY8Mmu+lbv8e7pXGWrhNp2/EGJp9EEcpzjBCar7puU57sGifyzaRVM03oD5L7t7PghQg==}
|
resolution: {integrity: sha512-B+DoudF+TCrxoJSTjjcY8Mmu+lbv8e7pXGWrhNp2/EGJp9EEcpzjBCar7puU57sGifyzaRVM03oD5L7t7PghQg==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-cms@2.6.1':
|
||||||
|
resolution: {integrity: sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==}
|
||||||
|
|
||||||
'@peculiar/asn1-csr@2.3.15':
|
'@peculiar/asn1-csr@2.3.15':
|
||||||
resolution: {integrity: sha512-caxAOrvw2hUZpxzhz8Kp8iBYKsHbGXZPl2KYRMIPvAfFateRebS3136+orUpcVwHRmpXWX2kzpb6COlIrqCumA==}
|
resolution: {integrity: sha512-caxAOrvw2hUZpxzhz8Kp8iBYKsHbGXZPl2KYRMIPvAfFateRebS3136+orUpcVwHRmpXWX2kzpb6COlIrqCumA==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-csr@2.6.1':
|
||||||
|
resolution: {integrity: sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==}
|
||||||
|
|
||||||
'@peculiar/asn1-ecc@2.3.15':
|
'@peculiar/asn1-ecc@2.3.15':
|
||||||
resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
|
resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-ecc@2.6.1':
|
||||||
|
resolution: {integrity: sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==}
|
||||||
|
|
||||||
'@peculiar/asn1-pfx@2.3.15':
|
'@peculiar/asn1-pfx@2.3.15':
|
||||||
resolution: {integrity: sha512-E3kzQe3J2xV9DP6SJS4X6/N1e4cYa2xOAK46VtvpaRk8jlheNri8v0rBezKFVPB1rz/jW8npO+u1xOvpATFMWg==}
|
resolution: {integrity: sha512-E3kzQe3J2xV9DP6SJS4X6/N1e4cYa2xOAK46VtvpaRk8jlheNri8v0rBezKFVPB1rz/jW8npO+u1xOvpATFMWg==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-pfx@2.6.1':
|
||||||
|
resolution: {integrity: sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==}
|
||||||
|
|
||||||
'@peculiar/asn1-pkcs8@2.3.15':
|
'@peculiar/asn1-pkcs8@2.3.15':
|
||||||
resolution: {integrity: sha512-/PuQj2BIAw1/v76DV1LUOA6YOqh/UvptKLJHtec/DQwruXOCFlUo7k6llegn8N5BTeZTWMwz5EXruBw0Q10TMg==}
|
resolution: {integrity: sha512-/PuQj2BIAw1/v76DV1LUOA6YOqh/UvptKLJHtec/DQwruXOCFlUo7k6llegn8N5BTeZTWMwz5EXruBw0Q10TMg==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-pkcs8@2.6.1':
|
||||||
|
resolution: {integrity: sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==}
|
||||||
|
|
||||||
'@peculiar/asn1-pkcs9@2.3.15':
|
'@peculiar/asn1-pkcs9@2.3.15':
|
||||||
resolution: {integrity: sha512-yiZo/1EGvU1KiQUrbcnaPGWc0C7ElMMskWn7+kHsCFm+/9fU0+V1D/3a5oG0Jpy96iaXggQpA9tzdhnYDgjyFg==}
|
resolution: {integrity: sha512-yiZo/1EGvU1KiQUrbcnaPGWc0C7ElMMskWn7+kHsCFm+/9fU0+V1D/3a5oG0Jpy96iaXggQpA9tzdhnYDgjyFg==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-pkcs9@2.6.1':
|
||||||
|
resolution: {integrity: sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==}
|
||||||
|
|
||||||
'@peculiar/asn1-rsa@2.3.15':
|
'@peculiar/asn1-rsa@2.3.15':
|
||||||
resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
|
resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-rsa@2.6.1':
|
||||||
|
resolution: {integrity: sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==}
|
||||||
|
|
||||||
'@peculiar/asn1-schema@2.3.15':
|
'@peculiar/asn1-schema@2.3.15':
|
||||||
resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
|
resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-schema@2.6.0':
|
||||||
|
resolution: {integrity: sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==}
|
||||||
|
|
||||||
'@peculiar/asn1-x509-attr@2.3.15':
|
'@peculiar/asn1-x509-attr@2.3.15':
|
||||||
resolution: {integrity: sha512-TWJVJhqc+IS4MTEML3l6W1b0sMowVqdsnI4dnojg96LvTuP8dga9f76fjP07MUuss60uSyT2ckoti/2qHXA10A==}
|
resolution: {integrity: sha512-TWJVJhqc+IS4MTEML3l6W1b0sMowVqdsnI4dnojg96LvTuP8dga9f76fjP07MUuss60uSyT2ckoti/2qHXA10A==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-x509-attr@2.6.1':
|
||||||
|
resolution: {integrity: sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==}
|
||||||
|
|
||||||
'@peculiar/asn1-x509@2.3.15':
|
'@peculiar/asn1-x509@2.3.15':
|
||||||
resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
|
resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
|
||||||
|
|
||||||
|
'@peculiar/asn1-x509@2.6.1':
|
||||||
|
resolution: {integrity: sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==}
|
||||||
|
|
||||||
'@peculiar/x509@1.12.3':
|
'@peculiar/x509@1.12.3':
|
||||||
resolution: {integrity: sha512-+Mzq+W7cNEKfkNZzyLl6A6ffqc3r21HGZUezgfKxpZrkORfOqgRXnS80Zu0IV6a9Ue9QBJeKD7kN0iWfc3bhRQ==}
|
resolution: {integrity: sha512-+Mzq+W7cNEKfkNZzyLl6A6ffqc3r21HGZUezgfKxpZrkORfOqgRXnS80Zu0IV6a9Ue9QBJeKD7kN0iWfc3bhRQ==}
|
||||||
|
|
||||||
|
'@peculiar/x509@1.14.3':
|
||||||
|
resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -4329,6 +4378,13 @@ packages:
|
|||||||
'@simonwep/pickr@1.8.2':
|
'@simonwep/pickr@1.8.2':
|
||||||
resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==}
|
resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==}
|
||||||
|
|
||||||
|
'@simplewebauthn/browser@13.2.2':
|
||||||
|
resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==}
|
||||||
|
|
||||||
|
'@simplewebauthn/server@13.2.3':
|
||||||
|
resolution: {integrity: sha512-ZhcVBOw63birYx9jVfbhK6rTehckVes8PeWV324zpmdxr0BUfylospwMzcrxrdMcOi48MHWj2LCA+S528LnGvg==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@sinclair/typebox@0.27.8':
|
'@sinclair/typebox@0.27.8':
|
||||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||||
|
|
||||||
@@ -5874,6 +5930,7 @@ packages:
|
|||||||
basic-ftp@5.0.5:
|
basic-ftp@5.0.5:
|
||||||
resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==}
|
resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
deprecated: Security vulnerability fixed in 5.2.0, please upgrade
|
||||||
|
|
||||||
bcrypt-pbkdf@1.0.2:
|
bcrypt-pbkdf@1.0.2:
|
||||||
resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
|
resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
|
||||||
@@ -15037,12 +15094,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@better-scroll/core': 2.5.1
|
'@better-scroll/core': 2.5.1
|
||||||
|
|
||||||
'@certd/commercial-core@1.38.12(better-sqlite3@11.10.0)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.9.3))':
|
'@certd/commercial-core@1.39.0(better-sqlite3@11.10.0)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.9.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic': link:packages/core/basic
|
'@certd/basic': link:packages/core/basic
|
||||||
'@certd/lib-server': link:packages/libs/lib-server
|
'@certd/lib-server': link:packages/libs/lib-server
|
||||||
'@certd/pipeline': link:packages/core/pipeline
|
'@certd/pipeline': link:packages/core/pipeline
|
||||||
'@certd/plus-core': 1.38.12
|
'@certd/plus-core': 1.39.0
|
||||||
'@midwayjs/core': 3.20.11
|
'@midwayjs/core': 3.20.11
|
||||||
'@midwayjs/koa': 3.20.13
|
'@midwayjs/koa': 3.20.13
|
||||||
'@midwayjs/logger': 3.4.2
|
'@midwayjs/logger': 3.4.2
|
||||||
@@ -15077,19 +15134,19 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@certd/plugin-plus@1.38.12':
|
'@certd/plugin-plus@1.39.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic': link:packages/core/basic
|
'@certd/basic': link:packages/core/basic
|
||||||
'@certd/pipeline': link:packages/core/pipeline
|
'@certd/pipeline': link:packages/core/pipeline
|
||||||
'@certd/plugin-lib': link:packages/plugins/plugin-lib
|
'@certd/plugin-lib': link:packages/plugins/plugin-lib
|
||||||
'@certd/plus-core': 1.38.12
|
'@certd/plus-core': 1.39.0
|
||||||
crypto-js: 4.2.0
|
crypto-js: 4.2.0
|
||||||
dayjs: 1.11.13
|
dayjs: 1.11.13
|
||||||
form-data: 4.0.2
|
form-data: 4.0.2
|
||||||
jsrsasign: 11.1.0
|
jsrsasign: 11.1.0
|
||||||
querystring: 0.2.1
|
querystring: 0.2.1
|
||||||
|
|
||||||
'@certd/plus-core@1.38.12':
|
'@certd/plus-core@1.39.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic': link:packages/core/basic
|
'@certd/basic': link:packages/core/basic
|
||||||
dayjs: 1.11.13
|
dayjs: 1.11.13
|
||||||
@@ -15712,6 +15769,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@hapi/hoek': 9.3.0
|
'@hapi/hoek': 9.3.0
|
||||||
|
|
||||||
|
'@hexagon/base64@1.1.28': {}
|
||||||
|
|
||||||
'@httptoolkit/websocket-stream@6.0.1':
|
'@httptoolkit/websocket-stream@6.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/ws': 8.18.1
|
'@types/ws': 8.18.1
|
||||||
@@ -16176,6 +16235,8 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
|
'@levischuck/tiny-cbor@0.2.11': {}
|
||||||
|
|
||||||
'@manypkg/find-root@2.2.3':
|
'@manypkg/find-root@2.2.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@manypkg/tools': 1.1.2
|
'@manypkg/tools': 1.1.2
|
||||||
@@ -16552,6 +16613,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@noble/hashes': 1.8.0
|
'@noble/hashes': 1.8.0
|
||||||
|
|
||||||
|
'@peculiar/asn1-android@2.6.0':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-cms@2.3.15':
|
'@peculiar/asn1-cms@2.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.3.15
|
||||||
@@ -16560,6 +16627,14 @@ snapshots:
|
|||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-cms@2.6.1':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
'@peculiar/asn1-x509': 2.6.1
|
||||||
|
'@peculiar/asn1-x509-attr': 2.6.1
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-csr@2.3.15':
|
'@peculiar/asn1-csr@2.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.3.15
|
||||||
@@ -16567,6 +16642,13 @@ snapshots:
|
|||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-csr@2.6.1':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
'@peculiar/asn1-x509': 2.6.1
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-ecc@2.3.15':
|
'@peculiar/asn1-ecc@2.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.3.15
|
||||||
@@ -16574,6 +16656,13 @@ snapshots:
|
|||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-ecc@2.6.1':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
'@peculiar/asn1-x509': 2.6.1
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-pfx@2.3.15':
|
'@peculiar/asn1-pfx@2.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-cms': 2.3.15
|
'@peculiar/asn1-cms': 2.3.15
|
||||||
@@ -16583,6 +16672,15 @@ snapshots:
|
|||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-pfx@2.6.1':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-cms': 2.6.1
|
||||||
|
'@peculiar/asn1-pkcs8': 2.6.1
|
||||||
|
'@peculiar/asn1-rsa': 2.6.1
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-pkcs8@2.3.15':
|
'@peculiar/asn1-pkcs8@2.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.3.15
|
||||||
@@ -16590,6 +16688,13 @@ snapshots:
|
|||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-pkcs8@2.6.1':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
'@peculiar/asn1-x509': 2.6.1
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-pkcs9@2.3.15':
|
'@peculiar/asn1-pkcs9@2.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-cms': 2.3.15
|
'@peculiar/asn1-cms': 2.3.15
|
||||||
@@ -16601,6 +16706,17 @@ snapshots:
|
|||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-pkcs9@2.6.1':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-cms': 2.6.1
|
||||||
|
'@peculiar/asn1-pfx': 2.6.1
|
||||||
|
'@peculiar/asn1-pkcs8': 2.6.1
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
'@peculiar/asn1-x509': 2.6.1
|
||||||
|
'@peculiar/asn1-x509-attr': 2.6.1
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-rsa@2.3.15':
|
'@peculiar/asn1-rsa@2.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.3.15
|
||||||
@@ -16608,12 +16724,25 @@ snapshots:
|
|||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-rsa@2.6.1':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
'@peculiar/asn1-x509': 2.6.1
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-schema@2.3.15':
|
'@peculiar/asn1-schema@2.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
pvtsutils: 1.3.6
|
pvtsutils: 1.3.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-schema@2.6.0':
|
||||||
|
dependencies:
|
||||||
|
asn1js: 3.0.6
|
||||||
|
pvtsutils: 1.3.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-x509-attr@2.3.15':
|
'@peculiar/asn1-x509-attr@2.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.3.15
|
||||||
@@ -16621,6 +16750,13 @@ snapshots:
|
|||||||
asn1js: 3.0.6
|
asn1js: 3.0.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-x509-attr@2.6.1':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
'@peculiar/asn1-x509': 2.6.1
|
||||||
|
asn1js: 3.0.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/asn1-x509@2.3.15':
|
'@peculiar/asn1-x509@2.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.15
|
'@peculiar/asn1-schema': 2.3.15
|
||||||
@@ -16628,6 +16764,13 @@ snapshots:
|
|||||||
pvtsutils: 1.3.6
|
pvtsutils: 1.3.6
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@peculiar/asn1-x509@2.6.1':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
asn1js: 3.0.6
|
||||||
|
pvtsutils: 1.3.6
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@peculiar/x509@1.12.3':
|
'@peculiar/x509@1.12.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-cms': 2.3.15
|
'@peculiar/asn1-cms': 2.3.15
|
||||||
@@ -16642,6 +16785,20 @@ snapshots:
|
|||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
tsyringe: 4.10.0
|
tsyringe: 4.10.0
|
||||||
|
|
||||||
|
'@peculiar/x509@1.14.3':
|
||||||
|
dependencies:
|
||||||
|
'@peculiar/asn1-cms': 2.6.1
|
||||||
|
'@peculiar/asn1-csr': 2.6.1
|
||||||
|
'@peculiar/asn1-ecc': 2.6.1
|
||||||
|
'@peculiar/asn1-pkcs9': 2.6.1
|
||||||
|
'@peculiar/asn1-rsa': 2.6.1
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
'@peculiar/asn1-x509': 2.6.1
|
||||||
|
pvtsutils: 1.3.6
|
||||||
|
reflect-metadata: 0.2.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
tsyringe: 4.10.0
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -16906,6 +17063,19 @@ snapshots:
|
|||||||
core-js: 3.42.0
|
core-js: 3.42.0
|
||||||
nanopop: 2.4.2
|
nanopop: 2.4.2
|
||||||
|
|
||||||
|
'@simplewebauthn/browser@13.2.2': {}
|
||||||
|
|
||||||
|
'@simplewebauthn/server@13.2.3':
|
||||||
|
dependencies:
|
||||||
|
'@hexagon/base64': 1.1.28
|
||||||
|
'@levischuck/tiny-cbor': 0.2.11
|
||||||
|
'@peculiar/asn1-android': 2.6.0
|
||||||
|
'@peculiar/asn1-ecc': 2.6.1
|
||||||
|
'@peculiar/asn1-rsa': 2.6.1
|
||||||
|
'@peculiar/asn1-schema': 2.6.0
|
||||||
|
'@peculiar/asn1-x509': 2.6.1
|
||||||
|
'@peculiar/x509': 1.14.3
|
||||||
|
|
||||||
'@sinclair/typebox@0.27.8': {}
|
'@sinclair/typebox@0.27.8': {}
|
||||||
|
|
||||||
'@sindresorhus/is@0.14.0': {}
|
'@sindresorhus/is@0.14.0': {}
|
||||||
@@ -20659,13 +20829,13 @@ snapshots:
|
|||||||
resolve: 1.22.10
|
resolve: 1.22.10
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
|
|
||||||
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8):
|
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 7.32.0
|
eslint: 7.32.0
|
||||||
prettier: 2.8.8
|
prettier: 2.8.8
|
||||||
prettier-linter-helpers: 1.0.0
|
prettier-linter-helpers: 1.0.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-config-prettier: 8.10.0(eslint@8.57.0)
|
eslint-config-prettier: 8.10.0(eslint@7.32.0)
|
||||||
|
|
||||||
eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8):
|
eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -23069,7 +23239,7 @@ snapshots:
|
|||||||
eslint: 7.32.0
|
eslint: 7.32.0
|
||||||
eslint-config-prettier: 8.10.0(eslint@7.32.0)
|
eslint-config-prettier: 8.10.0(eslint@7.32.0)
|
||||||
eslint-plugin-node: 11.1.0(eslint@7.32.0)
|
eslint-plugin-node: 11.1.0(eslint@7.32.0)
|
||||||
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8)
|
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8)
|
||||||
execa: 5.1.1
|
execa: 5.1.1
|
||||||
inquirer: 7.3.3
|
inquirer: 7.3.3
|
||||||
json5: 2.2.3
|
json5: 2.2.3
|
||||||
|
|||||||
Reference in New Issue
Block a user