feat: 增加判断oauth登录时是否开启相应applicationPassport
This commit is contained in:
parent
d7d302f329
commit
3f690e6725
|
|
@ -16,6 +16,7 @@ export async function loginByOauth(params, context) {
|
|||
// 验证 state 并获取 OAuth 配置
|
||||
const [state] = await context.select("oauthState", {
|
||||
data: {
|
||||
providerId: 1,
|
||||
provider: {
|
||||
type: 1,
|
||||
clientId: 1,
|
||||
|
|
@ -32,6 +33,31 @@ export async function loginByOauth(params, context) {
|
|||
state: stateCode,
|
||||
},
|
||||
}, { dontCollect: true });
|
||||
const systemId = context.getSystemId();
|
||||
const [applicationPassport] = await context.select('applicationPassport', {
|
||||
data: {
|
||||
id: 1,
|
||||
applicationId: 1,
|
||||
passportId: 1,
|
||||
passport: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
systemId: 1,
|
||||
config: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
passport: {
|
||||
systemId,
|
||||
type: 'oauth',
|
||||
},
|
||||
applicationId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
const allowOauth = !!(state.providerId && applicationPassport?.passport?.config?.oauthIds && applicationPassport?.passport?.config)?.oauthIds.includes(state.providerId);
|
||||
if (!allowOauth) {
|
||||
throw new OakUserException('error::user.loginWayDisabled');
|
||||
}
|
||||
assert(state, '无效的 state 参数');
|
||||
assert(state.provider?.ableState && state.provider?.ableState === 'enabled', '该 OAuth 提供商已被禁用');
|
||||
// 如果已经使用
|
||||
|
|
@ -329,7 +355,7 @@ export async function authorize(params, context) {
|
|||
applicationId: context.getApplicationId(),
|
||||
userId: context.getCurrentUserId(),
|
||||
scope: scope === undefined ? [] : [scope],
|
||||
expiresAt: Date.now() + 10 * 60 * 1000,
|
||||
expiresAt: Date.now() + 10 * 60 * 1000, // 10分钟后过期
|
||||
// PKCE 支持
|
||||
codeChallenge: code_challenge,
|
||||
codeChallengeMethod: code_challenge_method || 'plain',
|
||||
|
|
|
|||
|
|
@ -21,13 +21,43 @@ export default OakComponent({
|
|||
state: '',
|
||||
},
|
||||
lifetimes: {
|
||||
ready() {
|
||||
async ready() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const clientId = searchParams.get('client_id') || '';
|
||||
const responseType = searchParams.get('response_type') || '';
|
||||
const redirectUri = searchParams.get('redirect_uri') || '';
|
||||
const scope = searchParams.get('scope') || '';
|
||||
const state = searchParams.get('state') || '';
|
||||
//判断是否允许oauth登录
|
||||
const application = this.features.application.getApplication();
|
||||
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
|
||||
const oauthPassport = applicationPassports?.find((ele) => ele.passport?.type === 'oauth');
|
||||
const oauthIds = oauthPassport?.config?.oauthIds;
|
||||
let allowOauth = false;
|
||||
if (clientId) {
|
||||
const { data: [oauthProvider] } = await this.features.cache.refresh('oauthProvider', {
|
||||
data: {
|
||||
id: 1,
|
||||
clientId: 1,
|
||||
systemId: 1,
|
||||
},
|
||||
filter: {
|
||||
clientId,
|
||||
systemId: application.systemId,
|
||||
}
|
||||
});
|
||||
if (oauthProvider?.id && oauthIds?.length > 0 && oauthIds.includes(oauthProvider?.id)) {
|
||||
allowOauth = true;
|
||||
}
|
||||
}
|
||||
if (!allowOauth) {
|
||||
this.setState({
|
||||
hasError: true,
|
||||
errorMsg: 'oauth.login',
|
||||
});
|
||||
this.setState({ loading: false });
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
client_id: clientId,
|
||||
response_type: responseType,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"missing_client_id": "缺少 client_id 参数",
|
||||
"unknown": "未知错误,请稍后重试"
|
||||
}
|
||||
}
|
||||
},
|
||||
"login": "当前暂未支持该第三方应用授权登录"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
|
||||
import { EntityDict } from '../../../../../oak-app-domain';
|
||||
declare const Upsert: (props: WebComponentProps<EntityDict, 'oauthProvider', false, {
|
||||
item: RowWithActions<EntityDict, 'oauthProvider'>;
|
||||
declare const Upsert: (props: WebComponentProps<EntityDict, "oauthProvider", false, {
|
||||
item: RowWithActions<EntityDict, "oauthProvider">;
|
||||
}>) => React.JSX.Element;
|
||||
export default Upsert;
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ const Upsert = (props) => {
|
|||
}
|
||||
return (<div className={Styles.id}>
|
||||
<Form layout="vertical" autoComplete="off">
|
||||
<Form.Item label={t('name')} rules={[{ required: true, message: t('nameRequired') }]}>
|
||||
<Form.Item label={t('name')} required={true} rules={[{ required: true, message: t('nameRequired') }]}>
|
||||
<Input placeholder={t('namePlaceholder')} value={item.name || ""} onChange={(v) => {
|
||||
update({ name: v.target.value });
|
||||
}}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('type')} rules={[{ required: true, message: t('typeRequired') }]} extra={item.type && item.type !== 'oak' && item.type !== 'gitea' ? (<Text type="warning">
|
||||
<Form.Item label={t('type')} required={true} 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] : []} // 保持数组形式
|
||||
|
|
@ -37,13 +37,13 @@ const Upsert = (props) => {
|
|||
}}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('authorizationEndpoint')} rules={[{ required: true, message: t('authorizationEndpointRequired') }]}>
|
||||
<Form.Item label={t('authorizationEndpoint')} required={true} rules={[{ required: true, message: t('authorizationEndpointRequired') }]}>
|
||||
<Input placeholder={t('authorizationEndpointPlaceholder')} value={item.authorizationEndpoint || ""} onChange={(v) => {
|
||||
update({ authorizationEndpoint: v.target.value });
|
||||
}}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('tokenEndpoint')} rules={[{ required: true, message: t('tokenEndpointRequired') }]}>
|
||||
<Form.Item label={t('tokenEndpoint')} required={true} rules={[{ required: true, message: t('tokenEndpointRequired') }]}>
|
||||
<Input placeholder={t('tokenEndpointPlaceholder')} value={item.tokenEndpoint || ""} onChange={(v) => {
|
||||
update({ tokenEndpoint: v.target.value });
|
||||
}}/>
|
||||
|
|
@ -67,13 +67,13 @@ const Upsert = (props) => {
|
|||
}}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('clientId')} rules={[{ required: true, message: t('clientIdRequired') }]}>
|
||||
<Form.Item label={t('clientId')} required={true} rules={[{ required: true, message: t('clientIdRequired') }]}>
|
||||
<Input placeholder={t('clientIdPlaceholder')} value={item.clientId || ""} onChange={(v) => {
|
||||
update({ clientId: v.target.value });
|
||||
}}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('clientSecret')} rules={[{ required: true, message: t('clientSecretRequired') }]}>
|
||||
<Form.Item label={t('clientSecret')} required={true} rules={[{ required: true, message: t('clientSecretRequired') }]}>
|
||||
<Input.Password placeholder={t('clientSecretPlaceholder')} value={item.clientSecret || ""} onChange={(v) => {
|
||||
update({ clientSecret: v.target.value });
|
||||
}}/>
|
||||
|
|
@ -85,7 +85,7 @@ const Upsert = (props) => {
|
|||
}} tokenSeparators={[',']} open={false}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('redirectUri')} rules={[{ required: true, message: t('redirectUriRequired') }]}>
|
||||
<Form.Item label={t('redirectUri')} required={true} rules={[{ required: true, message: t('redirectUriRequired') }]}>
|
||||
<Input placeholder={t('redirectUriPlaceholder')} value={item.redirectUri || ""} onChange={(v) => {
|
||||
update({ redirectUri: v.target.value });
|
||||
}}/>
|
||||
|
|
@ -96,7 +96,7 @@ const Upsert = (props) => {
|
|||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('ableState')} valuePropName="checked">
|
||||
<Switch checked={!!item.ableState} onChange={(checked) => update({ ableState: checked ? "enabled" : "disabled" })}/>
|
||||
<Switch checked={item.ableState === 'enabled'} onChange={(checked) => update({ ableState: checked ? "enabled" : "disabled" })}/>
|
||||
</Form.Item>
|
||||
|
||||
{/* <Form.Item>
|
||||
|
|
|
|||
|
|
@ -64,9 +64,11 @@ export default OakComponent({
|
|||
id: 1,
|
||||
name: 1,
|
||||
systemId: 1,
|
||||
ableState: 1,
|
||||
},
|
||||
filter: {
|
||||
systemId,
|
||||
ableState: 'enabled'
|
||||
}
|
||||
});
|
||||
if (oauthProviders && oauthProviders?.length > 0) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Switch, Form, Select, Tag, } from 'antd';
|
||||
import { Switch, Form, Select, Tag, Tooltip, } from 'antd';
|
||||
import Styles from './web.module.less';
|
||||
export default function Oauth(props) {
|
||||
const { passport, t, changeEnabled, updateConfig, oauthOptions } = props;
|
||||
|
|
@ -12,15 +12,17 @@ export default function Oauth(props) {
|
|||
return (<div className={Styles.item}>
|
||||
<div className={Styles.title}>
|
||||
<Tag color={stateColor}>{t(`passport:v.type.${type}`)}</Tag>
|
||||
<Tooltip title={(oauthOptions && oauthOptions?.length > 0) ? '' : '请先启用oauth供应商'}>
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭" checked={enabled} onChange={(checked) => {
|
||||
changeEnabled(checked);
|
||||
}}/>
|
||||
}} disabled={!(oauthOptions && oauthOptions?.length > 0)}/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{enabled &&
|
||||
<div>
|
||||
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 20 }} style={{ maxWidth: 900, marginTop: 16 }}>
|
||||
<Form.Item label='oauth提供商'>
|
||||
<Select mode="multiple" style={{ width: '100%' }} placeholder="请选择oauth提供商" value={oauthIds} onChange={(value) => {
|
||||
<Form.Item label='oauth供应商'>
|
||||
<Select mode="multiple" style={{ width: '100%' }} placeholder="请选择oauth供应商" value={oauthIds} onChange={(value) => {
|
||||
updateConfig(id, config, 'oauthIds', value, 'oauth');
|
||||
}} options={oauthOptions}/>
|
||||
</Form.Item>
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ export default function render(props) {
|
|||
<div>* 如需启用邮箱登录,请先前往配置管理邮箱设置,创建系统邮箱,并完成相关配置</div>
|
||||
<div>* 如需启用小程序授权登录,请先前往应用管理,创建小程序application,并完成基础配置</div>
|
||||
<div>* 如需启用公众号授权登录,请先前往应用管理,创建是服务号的公众号application,并完成基础配置</div>
|
||||
<div>* 如需启用OAuth授权登录,请先前往OAuth管理,创建OAuth供应商,并启用</div>
|
||||
</div>
|
||||
</Row>
|
||||
{passports && passports.map((passport) => {
|
||||
|
|
|
|||
|
|
@ -266,7 +266,8 @@ const i18ns = [
|
|||
"missing_client_id": "缺少 client_id 参数",
|
||||
"unknown": "未知错误,请稍后重试"
|
||||
}
|
||||
}
|
||||
},
|
||||
"login": "当前暂未支持该第三方应用授权登录"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -140,5 +140,68 @@ const triggers = [
|
|||
return count;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '当provider禁用时,更新passport',
|
||||
entity: 'oauthProvider',
|
||||
action: 'update',
|
||||
when: 'after',
|
||||
check: (operation) => {
|
||||
const { data } = operation;
|
||||
return data.hasOwnProperty('ableState') && data.ableState === 'disabled';
|
||||
},
|
||||
fn: async ({ operation }, context, option) => {
|
||||
const { filter } = operation;
|
||||
const [oauthProvider] = await context.select('oauthProvider', {
|
||||
data: {
|
||||
id: 1,
|
||||
systemId: 1,
|
||||
ableState: 1,
|
||||
},
|
||||
filter: filter,
|
||||
}, { forUpdate: true });
|
||||
assert(oauthProvider, '禁用oauthProvider的filter请勿包含abledState');
|
||||
let count = 0;
|
||||
const { id, systemId } = oauthProvider;
|
||||
const [passport] = await context.select('passport', {
|
||||
data: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
config: 1,
|
||||
systemId: 1,
|
||||
enabled: 1,
|
||||
},
|
||||
filter: {
|
||||
type: 'oauth',
|
||||
systemId,
|
||||
config: {
|
||||
oauthIds: {
|
||||
$contains: [id],
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { forUpdate: true });
|
||||
if (passport && passport.enabled) {
|
||||
const { id: passportId, config } = passport;
|
||||
let newConfig = cloneDeep(config);
|
||||
pull(newConfig?.oauthIds, id);
|
||||
if (newConfig?.oauthIds?.length <= 0) {
|
||||
//无可支持的oauthProvider,将启用了的passport关闭
|
||||
await context.operate('passport', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'update',
|
||||
data: {
|
||||
enabled: false,
|
||||
config: newConfig,
|
||||
},
|
||||
filter: {
|
||||
id: passport.id,
|
||||
}
|
||||
}, option);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
];
|
||||
export default triggers;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.authorize = exports.createOAuthState = exports.getOAuthClientInfo = exports.loginByOauth = void 0;
|
||||
exports.loginByOauth = loginByOauth;
|
||||
exports.getOAuthClientInfo = getOAuthClientInfo;
|
||||
exports.createOAuthState = createOAuthState;
|
||||
exports.authorize = authorize;
|
||||
const tslib_1 = require("tslib");
|
||||
const assert_1 = tslib_1.__importDefault(require("assert"));
|
||||
const types_1 = require("oak-domain/lib/types");
|
||||
|
|
@ -20,6 +23,7 @@ async function loginByOauth(params, context) {
|
|||
// 验证 state 并获取 OAuth 配置
|
||||
const [state] = await context.select("oauthState", {
|
||||
data: {
|
||||
providerId: 1,
|
||||
provider: {
|
||||
type: 1,
|
||||
clientId: 1,
|
||||
|
|
@ -36,6 +40,31 @@ async function loginByOauth(params, context) {
|
|||
state: stateCode,
|
||||
},
|
||||
}, { dontCollect: true });
|
||||
const systemId = context.getSystemId();
|
||||
const [applicationPassport] = await context.select('applicationPassport', {
|
||||
data: {
|
||||
id: 1,
|
||||
applicationId: 1,
|
||||
passportId: 1,
|
||||
passport: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
systemId: 1,
|
||||
config: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
passport: {
|
||||
systemId,
|
||||
type: 'oauth',
|
||||
},
|
||||
applicationId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
const allowOauth = !!(state.providerId && applicationPassport?.passport?.config?.oauthIds && applicationPassport?.passport?.config)?.oauthIds.includes(state.providerId);
|
||||
if (!allowOauth) {
|
||||
throw new types_1.OakUserException('error::user.loginWayDisabled');
|
||||
}
|
||||
(0, assert_1.default)(state, '无效的 state 参数');
|
||||
(0, assert_1.default)(state.provider?.ableState && state.provider?.ableState === 'enabled', '该 OAuth 提供商已被禁用');
|
||||
// 如果已经使用
|
||||
|
|
@ -184,7 +213,6 @@ async function loginByOauth(params, context) {
|
|||
return tokenValue;
|
||||
}
|
||||
}
|
||||
exports.loginByOauth = loginByOauth;
|
||||
async function getOAuthClientInfo(params, context) {
|
||||
const { client_id, currentUserId } = params;
|
||||
const closeRootMode = context.openRootMode();
|
||||
|
|
@ -247,7 +275,6 @@ async function getOAuthClientInfo(params, context) {
|
|||
alreadyAuth: !!hasAuth,
|
||||
};
|
||||
}
|
||||
exports.getOAuthClientInfo = getOAuthClientInfo;
|
||||
async function createOAuthState(params, context) {
|
||||
const { providerId, userId, type } = params;
|
||||
const closeRootMode = context.openRootMode();
|
||||
|
|
@ -269,7 +296,6 @@ async function createOAuthState(params, context) {
|
|||
closeRootMode();
|
||||
return state;
|
||||
}
|
||||
exports.createOAuthState = createOAuthState;
|
||||
async function authorize(params, context) {
|
||||
const { response_type, client_id, redirect_uri, scope, state, action, code_challenge, code_challenge_method } = params;
|
||||
if (response_type !== 'code') {
|
||||
|
|
@ -336,7 +362,7 @@ async function authorize(params, context) {
|
|||
applicationId: context.getApplicationId(),
|
||||
userId: context.getCurrentUserId(),
|
||||
scope: scope === undefined ? [] : [scope],
|
||||
expiresAt: Date.now() + 10 * 60 * 1000,
|
||||
expiresAt: Date.now() + 10 * 60 * 1000, // 10分钟后过期
|
||||
// PKCE 支持
|
||||
codeChallenge: code_challenge,
|
||||
codeChallengeMethod: code_challenge_method || 'plain',
|
||||
|
|
@ -366,7 +392,6 @@ async function authorize(params, context) {
|
|||
closeRootMode();
|
||||
throw new Error('unknown action');
|
||||
}
|
||||
exports.authorize = authorize;
|
||||
const fetchOAuthUserInfo = async (code, providerConfig) => {
|
||||
// 1. 使用 code 换取 access_token
|
||||
const tokenResponse = await fetch(providerConfig.tokenEndpoint, {
|
||||
|
|
|
|||
|
|
@ -268,7 +268,8 @@ const i18ns = [
|
|||
"missing_client_id": "缺少 client_id 参数",
|
||||
"unknown": "未知错误,请稍后重试"
|
||||
}
|
||||
}
|
||||
},
|
||||
"login": "当前暂未支持该第三方应用授权登录"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -143,5 +143,68 @@ const triggers = [
|
|||
return count;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '当provider禁用时,更新passport',
|
||||
entity: 'oauthProvider',
|
||||
action: 'update',
|
||||
when: 'after',
|
||||
check: (operation) => {
|
||||
const { data } = operation;
|
||||
return data.hasOwnProperty('ableState') && data.ableState === 'disabled';
|
||||
},
|
||||
fn: async ({ operation }, context, option) => {
|
||||
const { filter } = operation;
|
||||
const [oauthProvider] = await context.select('oauthProvider', {
|
||||
data: {
|
||||
id: 1,
|
||||
systemId: 1,
|
||||
ableState: 1,
|
||||
},
|
||||
filter: filter,
|
||||
}, { forUpdate: true });
|
||||
(0, assert_1.default)(oauthProvider, '禁用oauthProvider的filter请勿包含abledState');
|
||||
let count = 0;
|
||||
const { id, systemId } = oauthProvider;
|
||||
const [passport] = await context.select('passport', {
|
||||
data: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
config: 1,
|
||||
systemId: 1,
|
||||
enabled: 1,
|
||||
},
|
||||
filter: {
|
||||
type: 'oauth',
|
||||
systemId,
|
||||
config: {
|
||||
oauthIds: {
|
||||
$contains: [id],
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { forUpdate: true });
|
||||
if (passport && passport.enabled) {
|
||||
const { id: passportId, config } = passport;
|
||||
let newConfig = (0, lodash_1.cloneDeep)(config);
|
||||
(0, lodash_1.pull)(newConfig?.oauthIds, id);
|
||||
if (newConfig?.oauthIds?.length <= 0) {
|
||||
//无可支持的oauthProvider,将启用了的passport关闭
|
||||
await context.operate('passport', {
|
||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||
action: 'update',
|
||||
data: {
|
||||
enabled: false,
|
||||
config: newConfig,
|
||||
},
|
||||
filter: {
|
||||
id: passport.id,
|
||||
}
|
||||
}, option);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
];
|
||||
exports.default = triggers;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { generateNewIdAsync } from "oak-domain/lib/utils/uuid";
|
|||
import { loadTokenInfo, setUpTokenAndUser } from "./token";
|
||||
import { randomUUID } from "crypto";
|
||||
import { processUserInfo } from "../utils/oauth";
|
||||
import { OAuthConfig } from "../entities/Passport";
|
||||
|
||||
export async function loginByOauth<ED extends EntityDict>(params: {
|
||||
code: string;
|
||||
|
|
@ -26,6 +27,7 @@ export async function loginByOauth<ED extends EntityDict>(params: {
|
|||
// 验证 state 并获取 OAuth 配置
|
||||
const [state] = await context.select("oauthState", {
|
||||
data: {
|
||||
providerId: 1,
|
||||
provider: {
|
||||
type: 1,
|
||||
clientId: 1,
|
||||
|
|
@ -41,7 +43,33 @@ export async function loginByOauth<ED extends EntityDict>(params: {
|
|||
filter: {
|
||||
state: stateCode,
|
||||
},
|
||||
}, { dontCollect: true })
|
||||
}, { dontCollect: true });
|
||||
|
||||
const systemId = context.getSystemId();
|
||||
const [applicationPassport] = await context.select('applicationPassport', {
|
||||
data: {
|
||||
id: 1,
|
||||
applicationId: 1,
|
||||
passportId: 1,
|
||||
passport: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
systemId: 1,
|
||||
config: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
passport: {
|
||||
systemId,
|
||||
type: 'oauth',
|
||||
},
|
||||
applicationId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
const allowOauth = !!(state.providerId && (applicationPassport?.passport?.config as OAuthConfig)?.oauthIds && applicationPassport?.passport?.config as OAuthConfig)?.oauthIds.includes(state.providerId);
|
||||
if (!allowOauth) {
|
||||
throw new OakUserException('error::user.loginWayDisabled');
|
||||
}
|
||||
|
||||
assert(state, '无效的 state 参数');
|
||||
assert(state.provider?.ableState && state.provider?.ableState === 'enabled', '该 OAuth 提供商已被禁用');
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export default OakComponent({
|
|||
state: '',
|
||||
},
|
||||
lifetimes: {
|
||||
ready() {
|
||||
async ready() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const clientId = searchParams.get('client_id') || '';
|
||||
const responseType = searchParams.get('response_type') || '';
|
||||
|
|
@ -32,6 +32,38 @@ export default OakComponent({
|
|||
const scope = searchParams.get('scope') || '';
|
||||
const state = searchParams.get('state') || '';
|
||||
|
||||
//判断是否允许oauth登录
|
||||
const application = this.features.application.getApplication();
|
||||
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
|
||||
const oauthPassport = applicationPassports?.find((ele: EntityDict['applicationPassport']['Schema']) => ele.passport?.type === 'oauth');
|
||||
const oauthIds = oauthPassport?.config?.oauthIds;
|
||||
let allowOauth = false;
|
||||
if (clientId) {
|
||||
const { data: [oauthProvider] } = await this.features.cache.refresh('oauthProvider', {
|
||||
data: {
|
||||
id: 1,
|
||||
clientId: 1,
|
||||
systemId: 1,
|
||||
},
|
||||
filter: {
|
||||
clientId,
|
||||
systemId: application.systemId,
|
||||
}
|
||||
});
|
||||
if (oauthProvider?.id && oauthIds?.length > 0 && oauthIds.includes(oauthProvider?.id)) {
|
||||
allowOauth = true;
|
||||
}
|
||||
}
|
||||
if (!allowOauth) {
|
||||
this.setState({
|
||||
hasError: true,
|
||||
errorMsg: 'oauth.login',
|
||||
});
|
||||
|
||||
this.setState({ loading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
client_id: clientId,
|
||||
response_type: responseType,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"missing_client_id": "缺少 client_id 参数",
|
||||
"unknown": "未知错误,请稍后重试"
|
||||
}
|
||||
}
|
||||
},
|
||||
"login": "当前暂未支持该第三方应用授权登录"
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ const Upsert = (
|
|||
>
|
||||
<Form.Item
|
||||
label={t('name')}
|
||||
required={true}
|
||||
rules={[{ required: true, message: t('nameRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('namePlaceholder')} value={item.name || ""}
|
||||
|
|
@ -42,6 +43,7 @@ const Upsert = (
|
|||
|
||||
<Form.Item
|
||||
label={t('type')}
|
||||
required={true}
|
||||
rules={[{ required: true, message: t('typeRequired') }]}
|
||||
extra={
|
||||
item.type && item.type !== 'oak' && item.type !== 'gitea' ? (
|
||||
|
|
@ -87,6 +89,7 @@ const Upsert = (
|
|||
|
||||
<Form.Item
|
||||
label={t('authorizationEndpoint')}
|
||||
required={true}
|
||||
rules={[{ required: true, message: t('authorizationEndpointRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('authorizationEndpointPlaceholder')} value={item.authorizationEndpoint || ""}
|
||||
|
|
@ -98,6 +101,7 @@ const Upsert = (
|
|||
|
||||
<Form.Item
|
||||
label={t('tokenEndpoint')}
|
||||
required={true}
|
||||
rules={[{ required: true, message: t('tokenEndpointRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('tokenEndpointPlaceholder')} value={item.tokenEndpoint || ""}
|
||||
|
|
@ -139,6 +143,7 @@ const Upsert = (
|
|||
|
||||
<Form.Item
|
||||
label={t('clientId')}
|
||||
required={true}
|
||||
rules={[{ required: true, message: t('clientIdRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('clientIdPlaceholder')} value={item.clientId || ""}
|
||||
|
|
@ -150,6 +155,7 @@ const Upsert = (
|
|||
|
||||
<Form.Item
|
||||
label={t('clientSecret')}
|
||||
required={true}
|
||||
rules={[{ required: true, message: t('clientSecretRequired') }]}
|
||||
>
|
||||
<Input.Password placeholder={t('clientSecretPlaceholder')} value={item.clientSecret || ""}
|
||||
|
|
@ -176,6 +182,7 @@ const Upsert = (
|
|||
|
||||
<Form.Item
|
||||
label={t('redirectUri')}
|
||||
required={true}
|
||||
rules={[{ required: true, message: t('redirectUriRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('redirectUriPlaceholder')} value={item.redirectUri || ""}
|
||||
|
|
@ -196,7 +203,7 @@ const Upsert = (
|
|||
label={t('ableState')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch checked={!!item.ableState} onChange={(checked) => update({ ableState: checked ? "enabled" : "disabled" })} />
|
||||
<Switch checked={item.ableState === 'enabled'} onChange={(checked) => update({ ableState: checked ? "enabled" : "disabled" })} />
|
||||
</Form.Item>
|
||||
|
||||
{/* <Form.Item>
|
||||
|
|
|
|||
|
|
@ -69,9 +69,11 @@ export default OakComponent({
|
|||
id: 1,
|
||||
name: 1,
|
||||
systemId: 1,
|
||||
ableState: 1,
|
||||
},
|
||||
filter: {
|
||||
systemId,
|
||||
ableState: 'enabled'
|
||||
}
|
||||
});
|
||||
if (oauthProviders && oauthProviders?.length > 0) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { EmailConfig, MfwConfig, PfwConfig, SmsConfig, PwdConfig, NameConfig, OAuthConfig } from "../../../entities/Passport";
|
||||
import { EntityDict } from "../../../oak-app-domain";
|
||||
import { Switch, Form, Input, Select, Space, Tag, InputNumber, Radio, } from 'antd';
|
||||
import { Switch, Form, Select, Tag, Tooltip, } from 'antd';
|
||||
import Styles from './web.module.less';
|
||||
|
||||
export default function Oauth(props: {
|
||||
|
|
@ -24,6 +24,7 @@ export default function Oauth(props: {
|
|||
<div className={Styles.item}>
|
||||
<div className={Styles.title}>
|
||||
<Tag color={stateColor}>{t(`passport:v.type.${type}`)}</Tag>
|
||||
<Tooltip title={(oauthOptions && oauthOptions?.length > 0) ? '' : '请先启用oauth供应商'}>
|
||||
<Switch
|
||||
checkedChildren="开启"
|
||||
unCheckedChildren="关闭"
|
||||
|
|
@ -31,7 +32,9 @@ export default function Oauth(props: {
|
|||
onChange={(checked) => {
|
||||
changeEnabled(checked)
|
||||
}}
|
||||
disabled={!(oauthOptions && oauthOptions?.length > 0)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{enabled &&
|
||||
<div>
|
||||
|
|
@ -41,12 +44,12 @@ export default function Oauth(props: {
|
|||
style={{ maxWidth: 900, marginTop: 16 }}
|
||||
>
|
||||
<Form.Item
|
||||
label='oauth提供商'
|
||||
label='oauth供应商'
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="请选择oauth提供商"
|
||||
placeholder="请选择oauth供应商"
|
||||
value={oauthIds}
|
||||
onChange={(value: string[]) => {
|
||||
updateConfig(id, config!, 'oauthIds', value, 'oauth');
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ export default function render(props: WebComponentProps<
|
|||
<div>* 如需启用邮箱登录,请先前往配置管理邮箱设置,创建系统邮箱,并完成相关配置</div>
|
||||
<div>* 如需启用小程序授权登录,请先前往应用管理,创建小程序application,并完成基础配置</div>
|
||||
<div>* 如需启用公众号授权登录,请先前往应用管理,创建是服务号的公众号application,并完成基础配置</div>
|
||||
<div>* 如需启用OAuth授权登录,请先前往OAuth管理,创建OAuth供应商,并启用</div>
|
||||
</div>
|
||||
</Row>
|
||||
{passports && passports.map((passport) => {
|
||||
|
|
|
|||
|
|
@ -268,7 +268,8 @@ const i18ns: I18n[] = [
|
|||
"missing_client_id": "缺少 client_id 参数",
|
||||
"unknown": "未知错误,请稍后重试"
|
||||
}
|
||||
}
|
||||
},
|
||||
"login": "当前暂未支持该第三方应用授权登录"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -147,6 +147,69 @@ const triggers: Trigger<EntityDict, "oauthProvider", BRC<EntityDict>>[] = [
|
|||
return count;
|
||||
}
|
||||
} as RemoveTrigger<EntityDict, "oauthProvider", BRC<EntityDict>>,
|
||||
{
|
||||
name: '当provider禁用时,更新passport',
|
||||
entity: 'oauthProvider',
|
||||
action: 'update',
|
||||
when: 'after',
|
||||
check: (operation) => {
|
||||
const { data } = operation as EntityDict['oauthProvider']['Update'];
|
||||
return data.hasOwnProperty('ableState') && data.ableState === 'disabled';
|
||||
},
|
||||
fn: async ({ operation }, context, option) => {
|
||||
const { filter } = operation;
|
||||
const [oauthProvider] = await context.select('oauthProvider', {
|
||||
data: {
|
||||
id: 1,
|
||||
systemId: 1,
|
||||
ableState: 1,
|
||||
},
|
||||
filter: filter,
|
||||
}, { forUpdate: true });
|
||||
assert(oauthProvider, '禁用oauthProvider的filter请勿包含abledState');
|
||||
let count = 0;
|
||||
const { id, systemId } = oauthProvider;
|
||||
const [passport] = await context.select('passport', {
|
||||
data: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
config: 1,
|
||||
systemId: 1,
|
||||
enabled: 1,
|
||||
},
|
||||
filter: {
|
||||
type: 'oauth',
|
||||
systemId,
|
||||
config: {
|
||||
oauthIds: {
|
||||
$contains: [id!],
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { forUpdate: true })
|
||||
if (passport && passport.enabled) {
|
||||
const { id: passportId, config } = passport;
|
||||
let newConfig = cloneDeep(config) as OAuthConfig;
|
||||
pull(newConfig?.oauthIds, id);
|
||||
if (newConfig?.oauthIds?.length <= 0) {
|
||||
//无可支持的oauthProvider,将启用了的passport关闭
|
||||
await context.operate('passport', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'update',
|
||||
data: {
|
||||
enabled: false,
|
||||
config: newConfig,
|
||||
},
|
||||
filter: {
|
||||
id: passport.id,
|
||||
}
|
||||
}, option);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default triggers;
|
||||
|
|
|
|||
Loading…
Reference in New Issue