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:
Dunqing 2023-07-06 16:33:28 +08:00 committed by GitHub
parent dc91d44ce6
commit 817646d68d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 128 additions and 22 deletions

View File

@ -32,7 +32,11 @@ export class JwtService {
}
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> {
@ -54,11 +58,14 @@ export class JwtService {
if (!this.blacklist) {
return null;
}
const { exp } = await this.decode(token);
return this.blacklist.add({
token,
expiration: new Date(exp * 1000).toString(),
});
try {
const { exp } = await this.decode(token);
return this.blacklist.add({
token,
expiration: new Date(exp * 1000).toString(),
});
} catch {
return null;
}
}
}

View File

@ -16,4 +16,4 @@ curl '{domain}/api/roles:check' -H 'Authorization: Bearer {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.

View File

@ -16,4 +16,4 @@ curl '{domain}/api/roles:check' -H 'Authorization: Bearer {API key}'
## 删除 API key
目前删除 API key 并不能使 Key 失效,请注意保管好你的 API key
删除 API key 后,该 Key 将无法继续使用

View File

@ -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 };

View File

@ -2,6 +2,7 @@ import { RecursionField } from '@formily/react';
import { CollectionManagerProvider, SchemaComponentOptions, useCurrentRoles } from '@nocobase/client';
import React from 'react';
import { apiKeysCollection } from '../../collections';
import { ExpiresSelect } from './ExpiresSelect';
import { configurationSchema } from './schema';
export const Configuration = () => {
@ -9,7 +10,7 @@ export const Configuration = () => {
return (
<CollectionManagerProvider collections={[apiKeysCollection]}>
<SchemaComponentOptions scope={{ currentRoles }}>
<SchemaComponentOptions scope={{ currentRoles }} components={{ ExpiresSelect }}>
<RecursionField schema={configurationSchema} />
</SchemaComponentOptions>
</CollectionManagerProvider>

View File

@ -3,7 +3,8 @@ import { uid } from '@formily/shared';
import { useActionContext, useBlockRequestContext, useRecord } from '@nocobase/client';
import { Alert, Modal, Space, Typography } from 'antd';
import React from 'react';
import { generateNTemplate, useTranslation } from '../locale';
import { generateNTemplate } from '../../locale';
import { useTranslation } from '../locale';
const { useModal } = Modal;
const useCreateAction = () => {
@ -99,6 +100,7 @@ export const configurationSchema: ISchema = {
'x-decorator': 'Form',
'x-component': 'Action.Modal',
'x-component-props': {
maskClosable: false,
style: {
maxWidth: '520px',
width: '100%',

View File

@ -6,10 +6,6 @@ export function lang(key: string) {
return i18n.t(key, { ns: NAMESPACE });
}
export function generateNTemplate(key: string) {
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
}
export function useTranslation() {
return useT([NAMESPACE, 'client'], {
nsMode: 'fallback',

View File

@ -9,6 +9,13 @@ const locale = {
'Keys manager': '密钥管理',
'Created at': '创建时间',
'Add API key': '添加 API key',
Never: '永不',
Custom: '自定义',
'Never expires': '永不过期',
'1 Day': '1 天',
'7 Days': '7 天',
'30 Days': '30 天',
'90 Days': '90 天',
};
export default locale;

View File

@ -1,4 +1,5 @@
import type { CollectionOptions } from '@nocobase/database';
import { generateNTemplate } from '../locale';
export default {
namespace: 'api-keys',
@ -53,28 +54,35 @@ export default {
{
name: 'expiresIn',
type: 'string',
interface: 'select',
uiSchema: {
type: 'string',
title: '{{t("Expires")}}',
'x-component': 'Select',
title: generateNTemplate('Expires'),
'x-component': 'ExpiresSelect',
enum: [
{
label: '{{t("1 day")}}',
label: generateNTemplate('1 Day'),
value: '1d',
},
{
label: '{{t("7 days")}}',
label: generateNTemplate('7 Days'),
value: '7d',
},
{
label: '{{t("30 days")}}',
label: generateNTemplate('30 Days'),
value: '30d',
},
{
label: '{{t("90 days")}}',
label: generateNTemplate('90 Days'),
value: '90d',
},
{
label: generateNTemplate('Custom'),
value: 'custom',
},
{
label: generateNTemplate('Never'),
value: 'never',
},
],
},
},

View File

@ -0,0 +1,5 @@
import { NAMESPACE } from './constants';
export function generateNTemplate(key: string) {
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
}