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