feat: 修改实体,将provider的type定义改为string,以实现自定义的类型注入。

feat: 在OAuthApplication中新增强制PKCE参数
fix: 修复了oauth相关upsert组件的部分问题
This commit is contained in:
Pan Qiancheng 2025-10-28 10:38:29 +08:00
parent 8367447700
commit 0f90d5c360
47 changed files with 155 additions and 69 deletions

View File

@ -11,6 +11,7 @@ export default OakComponent({
isConfidential: 1,
scopes: 1,
ableState: 1,
requirePKCE: 1,
},
filters: [{
filter() {

View File

@ -10,6 +10,7 @@ export default OakComponent({
isConfidential: 1,
scopes: 1, // string[]
ableState: 1,
requirePKCE: 1,
},
formData({ data, features }) {
if (!data) {

View File

@ -18,5 +18,7 @@
"ableState": "启用状态",
"noData": "无数据",
"clientId": "客户端ID",
"clientIdPlaceholder": "自动生成的客户端ID"
"clientIdPlaceholder": "自动生成的客户端ID",
"requirePKCE": "强制 PKCE",
"requirePKCETooltip": "启用后授权请求必须使用PKCE扩展以增强安全性。"
}

View File

@ -62,6 +62,11 @@ const Upsert = (props) => {
<Form.Item label={t('ableState')} valuePropName="checked">
<Switch checked={!!item.ableState} onChange={(checked) => update({ ableState: checked ? "enabled" : "disabled" })}/>
</Form.Item>
{/* requirePKCE */}
<Form.Item label={t('requirePKCE')} valuePropName="checked" tooltip={t('requirePKCETooltip')}>
<Switch checked={!!item.requirePKCE} onChange={(checked) => update({ requirePKCE: checked })}/>
</Form.Item>
</Form>
</div>);
};

View File

@ -10,7 +10,7 @@ const OauthProvider = (props) => {
const attrs = [
"id", "name", "description", "redirectUris",
"logo", "isConfidential", "scopes",
"ableState"
"ableState", "requirePKCE",
];
const [upsertId, setUpsertId] = React.useState(null);
const handleAction = (row, action) => {

View File

@ -12,6 +12,7 @@ export default OakComponent({
revokeEndpoint: 1,
refreshEndpoint: 1,
clientId: 1,
scopes: 1,
clientSecret: 1,
redirectUri: 1,
autoRegister: 1,

View File

@ -5,6 +5,7 @@ export default OakComponent({
name: 1,
type: 1,
logo: 1,
scopes: 1,
authorizationEndpoint: 1,
tokenEndpoint: 1,
userInfoEndpoint: 1,

View File

@ -1,6 +1,7 @@
import React from 'react';
import { Form, Input, Switch, Select } from 'antd';
import { Form, Input, Switch, Select, Typography } from 'antd';
import Styles from './styles.module.less';
const { Text } = Typography;
const Upsert = (props) => {
const { item } = props.data;
const { t, update } = props.methods;
@ -15,26 +16,19 @@ const Upsert = (props) => {
}}/>
</Form.Item>
<Form.Item label={t('type')} rules={[{ required: true, message: t('typeRequired') }]}>
<Select placeholder={t('typePlaceholder')} value={item.type || ""} onChange={(v) => {
update({ type: v });
}}>
<Select.Option value="oak">Oak</Select.Option>
<Select.Option value="gitea">Gitea</Select.Option>
<Select.Option value="github">GitHub</Select.Option>
<Select.Option value="google">Google</Select.Option>
<Select.Option value="facebook">Facebook</Select.Option>
<Select.Option value="twitter">Twitter</Select.Option>
<Select.Option value="linkedin">LinkedIn</Select.Option>
<Select.Option value="custom">Custom</Select.Option>
<Select.Option value="gitlab">GitLab</Select.Option>
<Select.Option value="microsoft">Microsoft</Select.Option>
<Select.Option value="apple">Apple</Select.Option>
<Select.Option value="tencent">Tencent</Select.Option>
<Select.Option value="weixin">Weixin</Select.Option>
<Select.Option value="weibo">Weibo</Select.Option>
<Select.Option value="dingtalk">DingTalk</Select.Option>
</Select>
<Form.Item label={t('type')} rules={[{ required: true, message: t('typeRequired') }]} extra={item.type && item.type !== 'oak' && item.type !== 'gitea' ? (<Text type="warning">
{item.type}不是预设类型请自行注入 handler
</Text>) : undefined}>
<Select mode="tags" placeholder={t('typePlaceholder')} value={item.type ? [item.type] : []} // 保持数组形式
onChange={(v) => {
// 只取最后一个输入或选择的值
const last = v.slice(-1)[0];
update({ type: last });
}} tokenSeparators={[',']} maxTagCount={1} // 只显示一个标签
options={[
{ value: 'oak', label: 'Oak' },
{ value: 'gitea', label: 'Gitea' },
]}/>
</Form.Item>
<Form.Item label={t('logo')}>
@ -54,7 +48,7 @@ const Upsert = (props) => {
update({ tokenEndpoint: v.target.value });
}}/>
</Form.Item>
<Form.Item label={t('refreshEndpoint')}>
<Input placeholder={t('refreshEndpointPlaceholder')} value={item.refreshEndpoint || ""} onChange={(v) => {
update({ refreshEndpoint: v.target.value });

View File

@ -18,7 +18,8 @@ export const selectFreeEntities = [
'userEntityGrant',
'wechatMpJump',
'applicationPassport',
'passport'
'passport',
'oauthProvider'
];
// 可以自由更新的对象
export const updateFreeDict = {

View File

@ -398,7 +398,9 @@ const i18ns = [
"ableState": "启用状态",
"noData": "无数据",
"clientId": "客户端ID",
"clientIdPlaceholder": "自动生成的客户端ID"
"clientIdPlaceholder": "自动生成的客户端ID",
"requirePKCE": "强制 PKCE",
"requirePKCETooltip": "启用后授权请求必须使用PKCE扩展以增强安全性。"
}
},
{

View File

@ -14,6 +14,7 @@ export interface Schema extends EntityShape {
logo?: String<512>;
isConfidential: Boolean;
scopes?: StringListJson;
requirePKCE: Boolean;
}
export type SecretAction = 'resetSecret';
export type Action = AbleAction | SecretAction;

View File

@ -16,6 +16,7 @@ export const entityDesc = {
logo: '应用 Logo',
isConfidential: '是否保密',
scopes: '应用权限范围',
requirePKCE: '强制 PKCE',
},
action: {
enable: '启用',

View File

@ -8,7 +8,7 @@ import { StringListJson } from '../types/datatype';
export interface Schema extends EntityShape {
system: System;
name: String<64>;
type: "oak" | "gitea" | "github" | "google" | "facebook" | "twitter" | "linkedin" | "custom" | "gitlab" | "microsoft" | "apple" | "tencent" | "weixin" | "weibo" | "dingtalk";
type: String<64>;
logo?: String<512>;
authorizationEndpoint: String<512>;
tokenEndpoint: String<512>;

View File

@ -40,6 +40,10 @@ export const desc = {
scopes: {
type: "object"
},
requirePKCE: {
notNull: true,
type: "boolean"
},
ableState: {
type: "enum",
enumeration: ["enabled", "disabled"]

View File

@ -14,6 +14,7 @@ export type OpSchema = EntityShape & {
logo?: String<512> | null;
isConfidential: Boolean;
scopes?: StringListJson | null;
requirePKCE: Boolean;
ableState?: AbleState | null;
} & {
[A in ExpressionKey]?: any;
@ -32,6 +33,7 @@ export type OpFilter = {
logo: Q_StringValue;
isConfidential: Q_BooleanValue;
scopes: JsonFilter<StringListJson>;
requirePKCE: Q_BooleanValue;
ableState: Q_EnumValue<AbleState>;
} & ExprOp<OpAttr | string>;
export type OpProjection = {
@ -49,6 +51,7 @@ export type OpProjection = {
logo?: number;
isConfidential?: number;
scopes?: number | JsonProjection<StringListJson>;
requirePKCE?: number;
ableState?: number;
} & Partial<ExprOp<OpAttr | string>>;
export type OpSortAttr = Partial<{
@ -64,6 +67,7 @@ export type OpSortAttr = Partial<{
logo: number;
isConfidential: number;
scopes: number;
requirePKCE: number;
ableState: number;
[k: string]: any;
} | ExprOp<OpAttr | string>>;

View File

@ -9,7 +9,8 @@
"redirectUris": "重定向 URI",
"logo": "应用 Logo",
"isConfidential": "是否保密",
"scopes": "应用权限范围"
"scopes": "应用权限范围",
"requirePKCE": "强制 PKCE"
},
"action": {
"enable": "启用",

View File

@ -15,8 +15,10 @@ export const desc = {
},
type: {
notNull: true,
type: "enum",
enumeration: ["oak", "gitea", "github", "google", "facebook", "twitter", "linkedin", "custom", "gitlab", "microsoft", "apple", "tencent", "weixin", "weibo", "dingtalk"]
type: "varchar",
params: {
length: 64
}
},
logo: {
type: "varchar",

View File

@ -8,7 +8,7 @@ import { StringListJson } from "../../types/datatype";
export type OpSchema = EntityShape & {
systemId: ForeignKey<"system">;
name: String<64>;
type: "oak" | "gitea" | "github" | "google" | "facebook" | "twitter" | "linkedin" | "custom" | "gitlab" | "microsoft" | "apple" | "tencent" | "weixin" | "weibo" | "dingtalk";
type: String<64>;
logo?: String<512> | null;
authorizationEndpoint: String<512>;
tokenEndpoint: String<512>;
@ -32,7 +32,7 @@ export type OpFilter = {
$$updateAt$$: Q_DateValue;
systemId: Q_StringValue;
name: Q_StringValue;
type: Q_EnumValue<"oak" | "gitea" | "github" | "google" | "facebook" | "twitter" | "linkedin" | "custom" | "gitlab" | "microsoft" | "apple" | "tencent" | "weixin" | "weibo" | "dingtalk">;
type: Q_StringValue;
logo: Q_StringValue;
authorizationEndpoint: Q_StringValue;
tokenEndpoint: Q_StringValue;

View File

@ -1,2 +1,2 @@
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>>)[];
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>>)[];
export default _default;

View File

@ -13,6 +13,8 @@ const triggers = [
const systemId = context.getSystemId();
data.systemId = systemId;
data.clientSecret = randomUUID();
// 默认不强制 PKCE
data.requirePKCE = data.requirePKCE ?? false;
return 0; // 没有引起数据库行修改
}
},

View File

@ -14,7 +14,7 @@ export declare function createToDo<ED extends EntityDict & BaseEntityDict, T ext
redirectTo: EntityDict['toDo']['OpSchema']['redirectTo'];
entity: any;
entityId: string;
}, userIds?: string[]): Promise<1 | 0>;
}, userIds?: string[]): Promise<0 | 1>;
/**
* todo例程entity对象上进行action操作时filtertodo完成
* entity的action的后trigger中调用

View File

@ -21,7 +21,8 @@ exports.selectFreeEntities = [
'userEntityGrant',
'wechatMpJump',
'applicationPassport',
'passport'
'passport',
'oauthProvider'
];
// 可以自由更新的对象
exports.updateFreeDict = {

View File

@ -400,7 +400,9 @@ const i18ns = [
"ableState": "启用状态",
"noData": "无数据",
"clientId": "客户端ID",
"clientIdPlaceholder": "自动生成的客户端ID"
"clientIdPlaceholder": "自动生成的客户端ID",
"requirePKCE": "强制 PKCE",
"requirePKCETooltip": "启用后授权请求必须使用PKCE扩展以增强安全性。"
}
},
{

View File

@ -14,6 +14,7 @@ export interface Schema extends EntityShape {
logo?: String<512>;
isConfidential: Boolean;
scopes?: StringListJson;
requirePKCE: Boolean;
}
export type SecretAction = 'resetSecret';
export type Action = AbleAction | SecretAction;

View File

@ -19,6 +19,7 @@ exports.entityDesc = {
logo: '应用 Logo',
isConfidential: '是否保密',
scopes: '应用权限范围',
requirePKCE: '强制 PKCE',
},
action: {
enable: '启用',

View File

@ -8,7 +8,7 @@ import { StringListJson } from '../types/datatype';
export interface Schema extends EntityShape {
system: System;
name: String<64>;
type: "oak" | "gitea" | "github" | "google" | "facebook" | "twitter" | "linkedin" | "custom" | "gitlab" | "microsoft" | "apple" | "tencent" | "weixin" | "weibo" | "dingtalk";
type: String<64>;
logo?: String<512>;
authorizationEndpoint: String<512>;
tokenEndpoint: String<512>;

View File

@ -43,6 +43,10 @@ exports.desc = {
scopes: {
type: "object"
},
requirePKCE: {
notNull: true,
type: "boolean"
},
ableState: {
type: "enum",
enumeration: ["enabled", "disabled"]

View File

@ -14,6 +14,7 @@ export type OpSchema = EntityShape & {
logo?: String<512> | null;
isConfidential: Boolean;
scopes?: StringListJson | null;
requirePKCE: Boolean;
ableState?: AbleState | null;
} & {
[A in ExpressionKey]?: any;
@ -32,6 +33,7 @@ export type OpFilter = {
logo: Q_StringValue;
isConfidential: Q_BooleanValue;
scopes: JsonFilter<StringListJson>;
requirePKCE: Q_BooleanValue;
ableState: Q_EnumValue<AbleState>;
} & ExprOp<OpAttr | string>;
export type OpProjection = {
@ -49,6 +51,7 @@ export type OpProjection = {
logo?: number;
isConfidential?: number;
scopes?: number | JsonProjection<StringListJson>;
requirePKCE?: number;
ableState?: number;
} & Partial<ExprOp<OpAttr | string>>;
export type OpSortAttr = Partial<{
@ -64,6 +67,7 @@ export type OpSortAttr = Partial<{
logo: number;
isConfidential: number;
scopes: number;
requirePKCE: number;
ableState: number;
[k: string]: any;
} | ExprOp<OpAttr | string>>;

View File

@ -9,7 +9,8 @@
"redirectUris": "重定向 URI",
"logo": "应用 Logo",
"isConfidential": "是否保密",
"scopes": "应用权限范围"
"scopes": "应用权限范围",
"requirePKCE": "强制 PKCE"
},
"action": {
"enable": "启用",

View File

@ -18,8 +18,10 @@ exports.desc = {
},
type: {
notNull: true,
type: "enum",
enumeration: ["oak", "gitea", "github", "google", "facebook", "twitter", "linkedin", "custom", "gitlab", "microsoft", "apple", "tencent", "weixin", "weibo", "dingtalk"]
type: "varchar",
params: {
length: 64
}
},
logo: {
type: "varchar",

View File

@ -8,7 +8,7 @@ import { StringListJson } from "../../types/datatype";
export type OpSchema = EntityShape & {
systemId: ForeignKey<"system">;
name: String<64>;
type: "oak" | "gitea" | "github" | "google" | "facebook" | "twitter" | "linkedin" | "custom" | "gitlab" | "microsoft" | "apple" | "tencent" | "weixin" | "weibo" | "dingtalk";
type: String<64>;
logo?: String<512> | null;
authorizationEndpoint: String<512>;
tokenEndpoint: String<512>;
@ -32,7 +32,7 @@ export type OpFilter = {
$$updateAt$$: Q_DateValue;
systemId: Q_StringValue;
name: Q_StringValue;
type: Q_EnumValue<"oak" | "gitea" | "github" | "google" | "facebook" | "twitter" | "linkedin" | "custom" | "gitlab" | "microsoft" | "apple" | "tencent" | "weixin" | "weibo" | "dingtalk">;
type: Q_StringValue;
logo: Q_StringValue;
authorizationEndpoint: Q_StringValue;
tokenEndpoint: Q_StringValue;

View File

@ -1,2 +1,2 @@
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>>)[];
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>>)[];
export default _default;

View File

@ -16,6 +16,8 @@ const triggers = [
const systemId = context.getSystemId();
data.systemId = systemId;
data.clientSecret = (0, crypto_1.randomUUID)();
// 默认不强制 PKCE
data.requirePKCE = data.requirePKCE ?? false;
return 0; // 没有引起数据库行修改
}
},

View File

@ -12,6 +12,7 @@ export default OakComponent({
isConfidential: 1,
scopes: 1,
ableState: 1,
requirePKCE: 1,
},
filters: [{
filter() {

View File

@ -11,6 +11,7 @@ export default OakComponent({
isConfidential: 1,
scopes: 1, // string[]
ableState: 1,
requirePKCE: 1,
},
formData({ data, features }) {
if (!data) {

View File

@ -18,5 +18,7 @@
"ableState": "启用状态",
"noData": "无数据",
"clientId": "客户端ID",
"clientIdPlaceholder": "自动生成的客户端ID"
"clientIdPlaceholder": "自动生成的客户端ID",
"requirePKCE": "强制 PKCE",
"requirePKCETooltip": "启用后授权请求必须使用PKCE扩展以增强安全性。"
}

View File

@ -149,6 +149,18 @@ const Upsert = (
onChange={(checked) => update({ ableState: checked ? "enabled" : "disabled" })}
/>
</Form.Item>
{/* requirePKCE */}
<Form.Item
label={t('requirePKCE')}
valuePropName="checked"
tooltip={t('requirePKCETooltip')}
>
<Switch
checked={!!item.requirePKCE}
onChange={(checked) => update({ requirePKCE: checked })}
/>
</Form.Item>
</Form>
</div>
);

View File

@ -26,7 +26,7 @@ const OauthProvider = (
const attrs = [
"id", "name", "description", "redirectUris",
"logo", "isConfidential", "scopes",
"ableState"
"ableState", "requirePKCE",
]
const [upsertId, setUpsertId] = React.useState<string | null>(null);

View File

@ -13,6 +13,7 @@ export default OakComponent({
revokeEndpoint: 1,
refreshEndpoint: 1,
clientId: 1,
scopes: 1,
clientSecret: 1,
redirectUri: 1,
autoRegister: 1,

View File

@ -5,6 +5,7 @@ export default OakComponent({
name: 1,
type: 1,
logo: 1,
scopes: 1,
authorizationEndpoint: 1,
tokenEndpoint: 1,
userInfoEndpoint: 1,

View File

@ -1,9 +1,10 @@
import React, { useEffect } from 'react';
import { Form, Input, Switch, Button, Space, Upload, Select } from 'antd';
import { Form, Input, Switch, Button, Space, Upload, Select, Typography } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import Styles from './styles.module.less';
import { EntityDict } from '../../../../../oak-app-domain';
const { Text } = Typography;
const Upsert = (
props: WebComponentProps<
@ -42,28 +43,36 @@ const Upsert = (
<Form.Item
label={t('type')}
rules={[{ required: true, message: t('typeRequired') }]}
extra={
item.type && item.type !== 'oak' && item.type !== 'gitea' ? (
<Text type="warning">
{item.type} handler
</Text>
) : undefined
}
>
<Select placeholder={t('typePlaceholder')} value={item.type || ""}
<Select
mode="tags"
placeholder={t('typePlaceholder')}
value={item.type ? [item.type] : []} // 保持数组形式
onChange={(v) => {
update({ type: v as "oak" | "gitea" | "github" | "google" | "facebook" | "twitter" | "linkedin" | "custom" | "gitlab" | "microsoft" | "apple" | "tencent" | "weixin" | "weibo" | "dingtalk" | null | undefined });
// 只取最后一个输入或选择的值
const last = v.slice(-1)[0] as
| "oak"
| "gitea"
| string
| undefined;
update({ type: last });
}}
>
<Select.Option value="oak">Oak</Select.Option>
<Select.Option value="gitea">Gitea</Select.Option>
<Select.Option value="github">GitHub</Select.Option>
<Select.Option value="google">Google</Select.Option>
<Select.Option value="facebook">Facebook</Select.Option>
<Select.Option value="twitter">Twitter</Select.Option>
<Select.Option value="linkedin">LinkedIn</Select.Option>
<Select.Option value="custom">Custom</Select.Option>
<Select.Option value="gitlab">GitLab</Select.Option>
<Select.Option value="microsoft">Microsoft</Select.Option>
<Select.Option value="apple">Apple</Select.Option>
<Select.Option value="tencent">Tencent</Select.Option>
<Select.Option value="weixin">Weixin</Select.Option>
<Select.Option value="weibo">Weibo</Select.Option>
<Select.Option value="dingtalk">DingTalk</Select.Option>
</Select>
tokenSeparators={[',']}
maxTagCount={1} // 只显示一个标签
options={[
{ value: 'oak', label: 'Oak' },
{ value: 'gitea', label: 'Gitea' },
]}
/>
</Form.Item>
<Form.Item
@ -97,7 +106,7 @@ const Upsert = (
}}
/>
</Form.Item>
<Form.Item
label={t('refreshEndpoint')}
>

View File

@ -22,7 +22,8 @@ export const selectFreeEntities: SelectFreeEntities<EntityDict> = [
'userEntityGrant',
'wechatMpJump',
'applicationPassport',
'passport'
'passport',
'oauthProvider'
];
// 可以自由更新的对象

View File

@ -400,7 +400,9 @@ const i18ns: I18n[] = [
"ableState": "启用状态",
"noData": "无数据",
"clientId": "客户端ID",
"clientIdPlaceholder": "自动生成的客户端ID"
"clientIdPlaceholder": "自动生成的客户端ID",
"requirePKCE": "强制 PKCE",
"requirePKCETooltip": "启用后授权请求必须使用PKCE扩展以增强安全性。"
}
},
{

View File

@ -20,6 +20,7 @@ export interface Schema extends EntityShape {
logo?: String<512>;
isConfidential: Boolean;
scopes?: StringListJson;
requirePKCE: Boolean;
};
export type SecretAction = 'resetSecret';
@ -43,6 +44,7 @@ export const entityDesc: EntityDesc<Schema, Action, '', {
logo: '应用 Logo',
isConfidential: '是否保密',
scopes: '应用权限范围',
requirePKCE: '强制 PKCE',
},
action: {
enable: '启用',

View File

@ -14,7 +14,7 @@ import { StringListJson } from '../types/datatype';
export interface Schema extends EntityShape {
system: System;
name: String<64>;
type: "oak" | "gitea" | "github" | "google" | "facebook" | "twitter" | "linkedin" | "custom" | "gitlab" | "microsoft" | "apple" | "tencent" | "weixin" | "weibo" | "dingtalk";
type: String<64>; // OAuth 提供方类型 (可以自己实现默认注入oak, gitea)
logo?: String<512>;
// OAuth 端点 (RFC 6749 Section 3)

View File

@ -20,6 +20,9 @@ const triggers: Trigger<EntityDict, "oauthApplication", BRC<EntityDict>>[] = [
data.systemId = systemId;
data.clientSecret = randomUUID();
// 默认不强制 PKCE
data.requirePKCE = data.requirePKCE ?? false;
return 0; // 没有引起数据库行修改
}
} as CreateTrigger<EntityDict, "oauthApplication", BRC<EntityDict>>,

10
upgrade/5.11.1/01.sql Normal file
View File

@ -0,0 +1,10 @@
SET SESSION sql_mode = 'TRADITIONAL';
START TRANSACTION;
alter table oauthApplication
add requirePKCE tinyint(1) default false not null after ableState;
alter table oauthProvider
modify type varchar(64) not null;
COMMIT;