feat(api-keys): the expiration field support custom option (#2186)
* feat(api-keys): the expiration field support custom option * feat: support never option * feat: support if expiresIn = never the expiresIn will replace to 1000y * fix: toggle datepicker * feat: update syntax * fix: option order * docs: update * fix: maskCloseable should be false * refactor: performance and remove unused code * feat: should not allow clear * fix: decode maybe fail * fix: i18n
This commit is contained in:
parent
dc91d44ce6
commit
817646d68d
@ -32,7 +32,11 @@ export class JwtService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sign(payload: SignPayload, options?: SignOptions) {
|
sign(payload: SignPayload, options?: SignOptions) {
|
||||||
return jwt.sign(payload, this.secret(), { expiresIn: this.expiresIn(), ...options });
|
const opt = { expiresIn: this.expiresIn(), ...options };
|
||||||
|
if (opt.expiresIn === 'never') {
|
||||||
|
opt.expiresIn = '1000y';
|
||||||
|
}
|
||||||
|
return jwt.sign(payload, this.secret(), opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
decode(token: string): Promise<any> {
|
decode(token: string): Promise<any> {
|
||||||
@ -54,11 +58,14 @@ export class JwtService {
|
|||||||
if (!this.blacklist) {
|
if (!this.blacklist) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
const { exp } = await this.decode(token);
|
const { exp } = await this.decode(token);
|
||||||
|
|
||||||
return this.blacklist.add({
|
return this.blacklist.add({
|
||||||
token,
|
token,
|
||||||
expiration: new Date(exp * 1000).toString(),
|
expiration: new Date(exp * 1000).toString(),
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,4 +16,4 @@ curl '{domain}/api/roles:check' -H 'Authorization: Bearer {API key}'
|
|||||||
|
|
||||||
## Deleting an API key
|
## Deleting an API key
|
||||||
|
|
||||||
Currently, deleting an API key does not make it invalid. Please keep your API key safe.
|
After deleting the API key, it will no longer be usable.
|
||||||
|
@ -16,4 +16,4 @@ curl '{domain}/api/roles:check' -H 'Authorization: Bearer {API key}'
|
|||||||
|
|
||||||
## 删除 API key
|
## 删除 API key
|
||||||
|
|
||||||
目前删除 API key 并不能使 Key 失效,请注意保管好你的 API key。
|
删除 API key 后,该 Key 将无法继续使用。
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
|
import { useRecord } from '@nocobase/client';
|
||||||
|
import { useBoolean } from 'ahooks';
|
||||||
|
import { DatePicker, Select, Space, Typography } from 'antd';
|
||||||
|
import moment from 'moment';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { useTranslation } from '../locale';
|
||||||
|
|
||||||
|
const TOMORROW = moment().add(1, 'days');
|
||||||
|
|
||||||
|
const spaceCSS = css`
|
||||||
|
width: 100%;
|
||||||
|
& > .ant-space-item {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InternalExpiresSelect = (props) => {
|
||||||
|
const { onChange } = props;
|
||||||
|
const [isCustom, { toggle: toggleShowDatePicker, setFalse }] = useBoolean();
|
||||||
|
|
||||||
|
const onSelectChange = (v) => {
|
||||||
|
if (v === 'custom') {
|
||||||
|
onChange('1d');
|
||||||
|
return toggleShowDatePicker();
|
||||||
|
} else {
|
||||||
|
setFalse();
|
||||||
|
onChange(v);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDatePickerChange = (v: moment.Moment) => {
|
||||||
|
v = v.milliseconds(0).second(0);
|
||||||
|
const NOW = moment().milliseconds(0).seconds(0);
|
||||||
|
const value = `${v.diff(NOW, 'd')}d`;
|
||||||
|
onChange(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space className={spaceCSS}>
|
||||||
|
<Select {...props} value={isCustom ? 'custom' : props.value} onChange={onSelectChange}></Select>
|
||||||
|
{isCustom ? (
|
||||||
|
<DatePicker
|
||||||
|
disabledDate={(time) => {
|
||||||
|
return time.isSameOrBefore();
|
||||||
|
}}
|
||||||
|
defaultValue={TOMORROW}
|
||||||
|
onChange={onDatePickerChange}
|
||||||
|
showToday={false}
|
||||||
|
allowClear={false}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ReadPretty = () => {
|
||||||
|
const { expiresIn, createdAt } = useRecord();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const expiresDate = useMemo(() => {
|
||||||
|
if (expiresIn === 'never') return t('Never expires');
|
||||||
|
|
||||||
|
return moment(createdAt)
|
||||||
|
.add(expiresIn?.replace('d', '') || 0, 'days')
|
||||||
|
.format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}, [createdAt, expiresIn]);
|
||||||
|
|
||||||
|
return <Typography.Text>{expiresDate}</Typography.Text>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExpiresSelect = connect(
|
||||||
|
InternalExpiresSelect,
|
||||||
|
mapProps({
|
||||||
|
dataSource: 'options',
|
||||||
|
}),
|
||||||
|
mapReadPretty(ReadPretty),
|
||||||
|
);
|
||||||
|
|
||||||
|
export { ExpiresSelect };
|
@ -2,6 +2,7 @@ import { RecursionField } from '@formily/react';
|
|||||||
import { CollectionManagerProvider, SchemaComponentOptions, useCurrentRoles } from '@nocobase/client';
|
import { CollectionManagerProvider, SchemaComponentOptions, useCurrentRoles } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { apiKeysCollection } from '../../collections';
|
import { apiKeysCollection } from '../../collections';
|
||||||
|
import { ExpiresSelect } from './ExpiresSelect';
|
||||||
import { configurationSchema } from './schema';
|
import { configurationSchema } from './schema';
|
||||||
|
|
||||||
export const Configuration = () => {
|
export const Configuration = () => {
|
||||||
@ -9,7 +10,7 @@ export const Configuration = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CollectionManagerProvider collections={[apiKeysCollection]}>
|
<CollectionManagerProvider collections={[apiKeysCollection]}>
|
||||||
<SchemaComponentOptions scope={{ currentRoles }}>
|
<SchemaComponentOptions scope={{ currentRoles }} components={{ ExpiresSelect }}>
|
||||||
<RecursionField schema={configurationSchema} />
|
<RecursionField schema={configurationSchema} />
|
||||||
</SchemaComponentOptions>
|
</SchemaComponentOptions>
|
||||||
</CollectionManagerProvider>
|
</CollectionManagerProvider>
|
||||||
|
@ -3,7 +3,8 @@ import { uid } from '@formily/shared';
|
|||||||
import { useActionContext, useBlockRequestContext, useRecord } from '@nocobase/client';
|
import { useActionContext, useBlockRequestContext, useRecord } from '@nocobase/client';
|
||||||
import { Alert, Modal, Space, Typography } from 'antd';
|
import { Alert, Modal, Space, Typography } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { generateNTemplate, useTranslation } from '../locale';
|
import { generateNTemplate } from '../../locale';
|
||||||
|
import { useTranslation } from '../locale';
|
||||||
const { useModal } = Modal;
|
const { useModal } = Modal;
|
||||||
|
|
||||||
const useCreateAction = () => {
|
const useCreateAction = () => {
|
||||||
@ -99,6 +100,7 @@ export const configurationSchema: ISchema = {
|
|||||||
'x-decorator': 'Form',
|
'x-decorator': 'Form',
|
||||||
'x-component': 'Action.Modal',
|
'x-component': 'Action.Modal',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
|
maskClosable: false,
|
||||||
style: {
|
style: {
|
||||||
maxWidth: '520px',
|
maxWidth: '520px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
@ -6,10 +6,6 @@ export function lang(key: string) {
|
|||||||
return i18n.t(key, { ns: NAMESPACE });
|
return i18n.t(key, { ns: NAMESPACE });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateNTemplate(key: string) {
|
|
||||||
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTranslation() {
|
export function useTranslation() {
|
||||||
return useT([NAMESPACE, 'client'], {
|
return useT([NAMESPACE, 'client'], {
|
||||||
nsMode: 'fallback',
|
nsMode: 'fallback',
|
||||||
|
@ -9,6 +9,13 @@ const locale = {
|
|||||||
'Keys manager': '密钥管理',
|
'Keys manager': '密钥管理',
|
||||||
'Created at': '创建时间',
|
'Created at': '创建时间',
|
||||||
'Add API key': '添加 API key',
|
'Add API key': '添加 API key',
|
||||||
|
Never: '永不',
|
||||||
|
Custom: '自定义',
|
||||||
|
'Never expires': '永不过期',
|
||||||
|
'1 Day': '1 天',
|
||||||
|
'7 Days': '7 天',
|
||||||
|
'30 Days': '30 天',
|
||||||
|
'90 Days': '90 天',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default locale;
|
export default locale;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { CollectionOptions } from '@nocobase/database';
|
import type { CollectionOptions } from '@nocobase/database';
|
||||||
|
import { generateNTemplate } from '../locale';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespace: 'api-keys',
|
namespace: 'api-keys',
|
||||||
@ -53,28 +54,35 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'expiresIn',
|
name: 'expiresIn',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
interface: 'select',
|
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: '{{t("Expires")}}',
|
title: generateNTemplate('Expires'),
|
||||||
'x-component': 'Select',
|
'x-component': 'ExpiresSelect',
|
||||||
enum: [
|
enum: [
|
||||||
{
|
{
|
||||||
label: '{{t("1 day")}}',
|
label: generateNTemplate('1 Day'),
|
||||||
value: '1d',
|
value: '1d',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '{{t("7 days")}}',
|
label: generateNTemplate('7 Days'),
|
||||||
value: '7d',
|
value: '7d',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '{{t("30 days")}}',
|
label: generateNTemplate('30 Days'),
|
||||||
value: '30d',
|
value: '30d',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '{{t("90 days")}}',
|
label: generateNTemplate('90 Days'),
|
||||||
value: '90d',
|
value: '90d',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: generateNTemplate('Custom'),
|
||||||
|
value: 'custom',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: generateNTemplate('Never'),
|
||||||
|
value: 'never',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
5
packages/plugins/api-keys/src/locale.ts
Normal file
5
packages/plugins/api-keys/src/locale.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { NAMESPACE } from './constants';
|
||||||
|
|
||||||
|
export function generateNTemplate(key: string) {
|
||||||
|
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user