From 817646d68dad49fbd1c0152a9468d0f5e2a6cefd Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 6 Jul 2023 16:33:28 +0800 Subject: [PATCH] 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 --- packages/core/auth/src/base/jwt-service.ts | 21 +++-- packages/plugins/api-keys/docs/en-US/usage.md | 2 +- packages/plugins/api-keys/docs/zh-CN/usage.md | 2 +- .../client/Configuration/ExpiresSelect.tsx | 80 +++++++++++++++++++ .../src/client/Configuration/index.tsx | 3 +- .../src/client/Configuration/schema.tsx | 4 +- .../api-keys/src/client/locale/index.ts | 4 - .../api-keys/src/client/locale/zh-CN.ts | 7 ++ .../api-keys/src/collections/api-keys.ts | 22 +++-- packages/plugins/api-keys/src/locale.ts | 5 ++ 10 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 packages/plugins/api-keys/src/client/Configuration/ExpiresSelect.tsx create mode 100644 packages/plugins/api-keys/src/locale.ts diff --git a/packages/core/auth/src/base/jwt-service.ts b/packages/core/auth/src/base/jwt-service.ts index 16f22f431..244be1281 100644 --- a/packages/core/auth/src/base/jwt-service.ts +++ b/packages/core/auth/src/base/jwt-service.ts @@ -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 { @@ -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; + } } } diff --git a/packages/plugins/api-keys/docs/en-US/usage.md b/packages/plugins/api-keys/docs/en-US/usage.md index f0c617752..cadf5a6f2 100644 --- a/packages/plugins/api-keys/docs/en-US/usage.md +++ b/packages/plugins/api-keys/docs/en-US/usage.md @@ -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. diff --git a/packages/plugins/api-keys/docs/zh-CN/usage.md b/packages/plugins/api-keys/docs/zh-CN/usage.md index 0bd739bdc..e2a592eba 100644 --- a/packages/plugins/api-keys/docs/zh-CN/usage.md +++ b/packages/plugins/api-keys/docs/zh-CN/usage.md @@ -16,4 +16,4 @@ curl '{domain}/api/roles:check' -H 'Authorization: Bearer {API key}' ## 删除 API key -目前删除 API key 并不能使 Key 失效,请注意保管好你的 API key。 +删除 API key 后,该 Key 将无法继续使用。 diff --git a/packages/plugins/api-keys/src/client/Configuration/ExpiresSelect.tsx b/packages/plugins/api-keys/src/client/Configuration/ExpiresSelect.tsx new file mode 100644 index 000000000..f64433dba --- /dev/null +++ b/packages/plugins/api-keys/src/client/Configuration/ExpiresSelect.tsx @@ -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 ( + + + {isCustom ? ( + { + return time.isSameOrBefore(); + }} + defaultValue={TOMORROW} + onChange={onDatePickerChange} + showToday={false} + allowClear={false} + /> + ) : null} + + ); +}; + +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 {expiresDate}; +}; + +const ExpiresSelect = connect( + InternalExpiresSelect, + mapProps({ + dataSource: 'options', + }), + mapReadPretty(ReadPretty), +); + +export { ExpiresSelect }; diff --git a/packages/plugins/api-keys/src/client/Configuration/index.tsx b/packages/plugins/api-keys/src/client/Configuration/index.tsx index 408e0881d..06019a1f6 100644 --- a/packages/plugins/api-keys/src/client/Configuration/index.tsx +++ b/packages/plugins/api-keys/src/client/Configuration/index.tsx @@ -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 ( - + diff --git a/packages/plugins/api-keys/src/client/Configuration/schema.tsx b/packages/plugins/api-keys/src/client/Configuration/schema.tsx index 20f353315..b348cd561 100644 --- a/packages/plugins/api-keys/src/client/Configuration/schema.tsx +++ b/packages/plugins/api-keys/src/client/Configuration/schema.tsx @@ -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%', diff --git a/packages/plugins/api-keys/src/client/locale/index.ts b/packages/plugins/api-keys/src/client/locale/index.ts index 86e36797c..2b9163b19 100644 --- a/packages/plugins/api-keys/src/client/locale/index.ts +++ b/packages/plugins/api-keys/src/client/locale/index.ts @@ -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', diff --git a/packages/plugins/api-keys/src/client/locale/zh-CN.ts b/packages/plugins/api-keys/src/client/locale/zh-CN.ts index 8ecae6e23..fc6b2c7c7 100644 --- a/packages/plugins/api-keys/src/client/locale/zh-CN.ts +++ b/packages/plugins/api-keys/src/client/locale/zh-CN.ts @@ -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; diff --git a/packages/plugins/api-keys/src/collections/api-keys.ts b/packages/plugins/api-keys/src/collections/api-keys.ts index 1be6f1ff9..6001071e0 100644 --- a/packages/plugins/api-keys/src/collections/api-keys.ts +++ b/packages/plugins/api-keys/src/collections/api-keys.ts @@ -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', + }, ], }, }, diff --git a/packages/plugins/api-keys/src/locale.ts b/packages/plugins/api-keys/src/locale.ts new file mode 100644 index 000000000..8759f9f37 --- /dev/null +++ b/packages/plugins/api-keys/src/locale.ts @@ -0,0 +1,5 @@ +import { NAMESPACE } from './constants'; + +export function generateNTemplate(key: string) { + return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`; +}