mirror of https://github.com/certd/certd
chore:
parent
93b37a89c9
commit
e79703e49b
|
@ -9,6 +9,10 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
.fs-iconify{
|
||||||
|
font-size:18px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cd-icon-button {
|
.cd-icon-button {
|
||||||
|
|
|
@ -1,55 +1,53 @@
|
||||||
import { request } from "/src/api/service";
|
import { request } from "/src/api/service";
|
||||||
|
|
||||||
export function createApi() {
|
export const OPEN_API_DOC = "https://apifox.com/apidoc/shared-2e76f8c4-7c58-413b-a32d-a1316529af44/254949529e0";
|
||||||
const apiPrefix = "/open/key";
|
|
||||||
return {
|
|
||||||
async GetList(query: any) {
|
|
||||||
return await request({
|
|
||||||
url: apiPrefix + "/page",
|
|
||||||
method: "post",
|
|
||||||
data: query
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async AddObj(obj: any) {
|
const apiPrefix = "/open/key";
|
||||||
return await request({
|
export const openkeyApi = {
|
||||||
url: apiPrefix + "/add",
|
async GetList(query: any) {
|
||||||
method: "post",
|
return await request({
|
||||||
data: obj
|
url: apiPrefix + "/page",
|
||||||
});
|
method: "post",
|
||||||
},
|
data: query
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
async UpdateObj(obj: any) {
|
async AddObj(obj: any) {
|
||||||
return await request({
|
return await request({
|
||||||
url: apiPrefix + "/update",
|
url: apiPrefix + "/add",
|
||||||
method: "post",
|
method: "post",
|
||||||
data: obj
|
data: obj
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async DelObj(id: number) {
|
async UpdateObj(obj: any) {
|
||||||
return await request({
|
return await request({
|
||||||
url: apiPrefix + "/delete",
|
url: apiPrefix + "/update",
|
||||||
method: "post",
|
method: "post",
|
||||||
params: { id }
|
data: obj
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async GetObj(id: number) {
|
async DelObj(id: number) {
|
||||||
return await request({
|
return await request({
|
||||||
url: apiPrefix + "/info",
|
url: apiPrefix + "/delete",
|
||||||
method: "post",
|
method: "post",
|
||||||
params: { id }
|
params: { id }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async GetApiToken(id: number) {
|
|
||||||
return await request({
|
|
||||||
url: apiPrefix + "/getApiToken",
|
|
||||||
method: "post",
|
|
||||||
data: { id }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const pipelineGroupApi = createApi();
|
async GetObj(id: number) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/info",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async GetApiToken(id: number) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/getApiToken",
|
||||||
|
method: "post",
|
||||||
|
data: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
import { pipelineGroupApi } from "./api";
|
import { OPEN_API_DOC, openkeyApi } from "./api";
|
||||||
import dayjs from "dayjs";
|
|
||||||
import { Modal } from "ant-design-vue";
|
|
||||||
import CertView from "/@/views/certd/pipeline/cert-view.vue";
|
|
||||||
import { useModal } from "/@/use/use-modal";
|
import { useModal } from "/@/use/use-modal";
|
||||||
|
|
||||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const api = pipelineGroupApi;
|
const api = openkeyApi;
|
||||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
return await api.GetList(query);
|
return await api.GetList(query);
|
||||||
};
|
};
|
||||||
|
@ -59,17 +56,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
actionbar: {
|
actionbar: {
|
||||||
buttons: {
|
buttons: {
|
||||||
add: {
|
add: {
|
||||||
text: "生成新的Key",
|
text: "生成新的Key"
|
||||||
click() {
|
|
||||||
Modal.confirm({
|
|
||||||
title: "确认",
|
|
||||||
content: "确定要生成新的Key?",
|
|
||||||
async onOk() {
|
|
||||||
await api.AddObj({});
|
|
||||||
await crudExpose.doRefresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -82,7 +69,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
edit: { show: false },
|
edit: { show: false },
|
||||||
remove: { show: true },
|
remove: { show: true },
|
||||||
gen: {
|
gen: {
|
||||||
text: "测试ApiToken",
|
text: " 接口测试",
|
||||||
|
size: "mini",
|
||||||
|
icon: "devicon-plain:vitest",
|
||||||
|
type: "primary",
|
||||||
async click({ row }) {
|
async click({ row }) {
|
||||||
const apiToken = await api.GetApiToken(row.id);
|
const apiToken = await api.GetApiToken(row.id);
|
||||||
|
|
||||||
|
@ -94,7 +84,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
content: () => {
|
content: () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div class={"m-10 p-10"}>测试ApiKey如下,您可以在3分钟内使用它进行开放接口请求测试</div>
|
<div class={"m-10 p-10"}>
|
||||||
|
测试ApiKey如下,您可以在3分钟内使用它进行
|
||||||
|
<a href={OPEN_API_DOC} target={"_blank"}>
|
||||||
|
开放接口
|
||||||
|
</a>
|
||||||
|
请求测试
|
||||||
|
</div>
|
||||||
<div class={"m-10 p-10"} style={{ border: "1px solid #333" }}>
|
<div class={"m-10 p-10"} style={{ border: "1px solid #333" }}>
|
||||||
<fs-copyable model-value={apiToken}></fs-copyable>
|
<fs-copyable model-value={apiToken}></fs-copyable>
|
||||||
</div>
|
</div>
|
||||||
|
@ -126,26 +122,50 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
},
|
},
|
||||||
keyId: {
|
keyId: {
|
||||||
title: "KeyId",
|
title: "KeyId",
|
||||||
|
type: ["text", "copyable"],
|
||||||
search: {
|
search: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
type: "text",
|
|
||||||
column: {
|
column: {
|
||||||
width: 200,
|
width: 250,
|
||||||
sorter: true
|
sorter: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keySecret: {
|
keySecret: {
|
||||||
title: "KeySecret",
|
title: "KeySecret",
|
||||||
type: "text",
|
type: ["text", "copyable"],
|
||||||
form: {
|
form: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 550,
|
width: 580,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scope: {
|
||||||
|
title: "权限范围",
|
||||||
|
type: "dict-radio",
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: "仅开放接口", value: "open", color: "blue" },
|
||||||
|
{ label: "账户所有权限", value: "user", color: "red" }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
form: {
|
||||||
|
value: "open",
|
||||||
|
show: true,
|
||||||
|
rules: [{ required: true, message: "此项必填" }],
|
||||||
|
helper: "仅开放接口只可以访问开放接口,账户所有权限可以访问所有接口",
|
||||||
|
component: {
|
||||||
|
vModel: "value"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
align: "center",
|
||||||
sorter: true
|
sorter: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,16 +2,20 @@
|
||||||
<fs-page>
|
<fs-page>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="title">开放接口密钥管理</div>
|
<div class="title">开放接口密钥管理</div>
|
||||||
|
<div class="more">
|
||||||
|
<a :href="OPEN_API_DOC" target="_blank">开放接口文档</a>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||||
</fs-page>
|
</fs-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineComponent, onActivated, onMounted } from "vue";
|
import { onActivated, onMounted } from "vue";
|
||||||
import { useFs } from "@fast-crud/fast-crud";
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
import createCrudOptions from "./crud";
|
import createCrudOptions from "./crud";
|
||||||
import { createApi } from "./api";
|
import { OPEN_API_DOC } from "/@/views/certd/open/openkey/api";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "OpenKey"
|
name: "OpenKey"
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@ CREATE TABLE "cd_open_key"
|
||||||
"user_id" integer,
|
"user_id" integer,
|
||||||
"key_id" varchar(50),
|
"key_id" varchar(50),
|
||||||
"key_secret" varchar(100),
|
"key_secret" varchar(100),
|
||||||
|
"scope" varchar(50),
|
||||||
"disabled" boolean NOT NULL DEFAULT (false),
|
"disabled" boolean NOT NULL DEFAULT (false),
|
||||||
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
|
|
@ -37,10 +37,9 @@ export class OpenKeyController extends CrudController<OpenKeyService> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/add', { summary: Constants.per.authOnly })
|
@Post('/add', { summary: Constants.per.authOnly })
|
||||||
async add() {
|
async add(@Body(ALL) body: any) {
|
||||||
const bean: any = {};
|
body.userId = this.getUserId();
|
||||||
bean.userId = this.getUserId();
|
const res = await this.service.add(body);
|
||||||
const res = await this.service.add(bean);
|
|
||||||
return this.ok(res);
|
return this.ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import jwt from 'jsonwebtoken';
|
||||||
import { Constants, SysPrivateSettings, SysSettingsService } from '@certd/lib-server';
|
import { Constants, SysPrivateSettings, SysSettingsService } from '@certd/lib-server';
|
||||||
import { logger } from '@certd/basic';
|
import { logger } from '@certd/basic';
|
||||||
import { AuthService } from '../modules/sys/authority/service/auth-service.js';
|
import { AuthService } from '../modules/sys/authority/service/auth-service.js';
|
||||||
import { Next } from 'koa';
|
|
||||||
import { OpenKeyService } from '../modules/open/service/open-key-service.js';
|
import { OpenKeyService } from '../modules/open/service/open-key-service.js';
|
||||||
|
import { RoleService } from '../modules/sys/authority/service/role-service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限校验
|
* 权限校验
|
||||||
|
@ -18,6 +18,8 @@ export class AuthorityMiddleware implements IWebMiddleware {
|
||||||
@Inject()
|
@Inject()
|
||||||
authService: AuthService;
|
authService: AuthService;
|
||||||
@Inject()
|
@Inject()
|
||||||
|
roleService: RoleService;
|
||||||
|
@Inject()
|
||||||
openKeyService: OpenKeyService;
|
openKeyService: OpenKeyService;
|
||||||
@Inject()
|
@Inject()
|
||||||
sysSettingsService: SysSettingsService;
|
sysSettingsService: SysSettingsService;
|
||||||
|
@ -50,10 +52,6 @@ export class AuthorityMiddleware implements IWebMiddleware {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permission === Constants.per.open) {
|
|
||||||
return this.doOpenHandler(ctx, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
let token = ctx.get('Authorization') || '';
|
let token = ctx.get('Authorization') || '';
|
||||||
token = token.replace('Bearer ', '').trim();
|
token = token.replace('Bearer ', '').trim();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
@ -64,41 +62,61 @@ export class AuthorityMiddleware implements IWebMiddleware {
|
||||||
//尝试从query中获取token
|
//尝试从query中获取token
|
||||||
token = (ctx.query.token as string) || '';
|
token = (ctx.query.token as string) || '';
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
ctx.user = jwt.verify(token, this.secret);
|
if (token) {
|
||||||
} catch (err) {
|
try {
|
||||||
logger.error('token verify error: ', err);
|
ctx.user = jwt.verify(token, this.secret);
|
||||||
ctx.status = 401;
|
} catch (err) {
|
||||||
ctx.body = Constants.res.auth;
|
logger.error('token verify error: ', err);
|
||||||
|
return this.notAuth(ctx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//找找openKey
|
||||||
|
const openKey = await this.doOpenHandler(ctx);
|
||||||
|
if (!openKey) {
|
||||||
|
return this.notAuth(ctx);
|
||||||
|
}
|
||||||
|
if (permission === Constants.per.open) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
} else if (openKey.scope === 'open') {
|
||||||
|
return this.notAuth(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission === Constants.per.authOnly) {
|
||||||
|
await next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permission !== Constants.per.authOnly) {
|
const pass = await this.authService.checkPermission(ctx, permission);
|
||||||
const pass = await this.authService.checkPermission(ctx, permission);
|
if (!pass) {
|
||||||
if (!pass) {
|
logger.info('not permission: ', ctx.req.url);
|
||||||
logger.info('not permission: ', ctx.req.url);
|
ctx.status = 401;
|
||||||
ctx.status = 401;
|
ctx.body = Constants.res.permission;
|
||||||
ctx.body = Constants.res.permission;
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await next();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async doOpenHandler(ctx: IMidwayKoaContext, next: Next) {
|
private notAuth(ctx: IMidwayKoaContext) {
|
||||||
|
ctx.status = 401;
|
||||||
|
ctx.body = Constants.res.auth;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async doOpenHandler(ctx: IMidwayKoaContext) {
|
||||||
//开放接口
|
//开放接口
|
||||||
const openKey = ctx.get('x-api-token') || '';
|
const openKey = ctx.get('x-certd-token') || '';
|
||||||
if (!openKey) {
|
if (!openKey) {
|
||||||
ctx.status = 401;
|
return null;
|
||||||
ctx.body = Constants.res.auth;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//校验 openKey
|
//校验 openKey
|
||||||
const openKeyRes = await this.openKeyService.verifyOpenKey(openKey);
|
const openKeyRes = await this.openKeyService.verifyOpenKey(openKey);
|
||||||
ctx.user = { id: openKeyRes.userId };
|
const roles = await this.roleService.getRoleIdsByUserId(openKeyRes.userId);
|
||||||
|
ctx.user = { id: openKeyRes.userId, roles };
|
||||||
ctx.openKey = openKeyRes;
|
ctx.openKey = openKeyRes;
|
||||||
await next();
|
return openKeyRes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@ export class OpenKeyEntity {
|
||||||
@Column({ name: 'key_secret', comment: 'keySecret' })
|
@Column({ name: 'key_secret', comment: 'keySecret' })
|
||||||
keySecret: string;
|
keySecret: string;
|
||||||
|
|
||||||
|
@Column({ name: 'scope', comment: '权限范围' })
|
||||||
|
scope: string; // open 仅开放接口、 user 用户所有权限
|
||||||
|
|
||||||
@Column({ name: 'create_time', comment: '创建时间', default: () => 'CURRENT_TIMESTAMP' })
|
@Column({ name: 'create_time', comment: '创建时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
createTime: Date;
|
createTime: Date;
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ export type OpenKey = {
|
||||||
keyId: string;
|
keyId: string;
|
||||||
keySecret: string;
|
keySecret: string;
|
||||||
encrypt: boolean;
|
encrypt: boolean;
|
||||||
|
scope: string;
|
||||||
};
|
};
|
||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
@ -29,10 +30,10 @@ export class OpenKeyService extends BaseService<OpenKeyEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(bean: OpenKeyEntity) {
|
async add(bean: OpenKeyEntity) {
|
||||||
return await this.generate(bean.userId);
|
return await this.generate(bean.userId, bean.scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
async generate(userId: number) {
|
async generate(userId: number, scope: string) {
|
||||||
const keyId = utils.id.simpleNanoId(18) + '_key';
|
const keyId = utils.id.simpleNanoId(18) + '_key';
|
||||||
const secretKey = crypto.randomBytes(32);
|
const secretKey = crypto.randomBytes(32);
|
||||||
const keySecret = Buffer.from(secretKey).toString('hex');
|
const keySecret = Buffer.from(secretKey).toString('hex');
|
||||||
|
@ -40,6 +41,7 @@ export class OpenKeyService extends BaseService<OpenKeyEntity> {
|
||||||
entity.userId = userId;
|
entity.userId = userId;
|
||||||
entity.keyId = keyId;
|
entity.keyId = keyId;
|
||||||
entity.keySecret = keySecret;
|
entity.keySecret = keySecret;
|
||||||
|
entity.scope = scope ?? 'open';
|
||||||
await this.repository.save(entity);
|
await this.repository.save(entity);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
@ -84,6 +86,7 @@ export class OpenKeyService extends BaseService<OpenKeyEntity> {
|
||||||
keyId: entity.keyId,
|
keyId: entity.keyId,
|
||||||
keySecret: entity.keySecret,
|
keySecret: entity.keySecret,
|
||||||
encrypt: encrypt,
|
encrypt: encrypt,
|
||||||
|
scope: entity.scope,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue