parent
8f50af917f
commit
cf70bd2dc1
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
@ -1,72 +0,0 @@
|
||||
.alert-info-block {
|
||||
padding-block: 6px;
|
||||
padding-inline: 10px 16px;
|
||||
position: relative;
|
||||
padding-left: 32px;
|
||||
text-align: left;
|
||||
border-radius: var(--border-radius-base);
|
||||
margin: 0;
|
||||
border: 1px solid transparent;
|
||||
|
||||
.ant-typography {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
border-color: var(--ant-color-error-border);
|
||||
background-color: var(--ant-color-error-bg);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
border-color: var(--ant-color-warning-border);
|
||||
background-color: var(--ant-color-warning-bg);
|
||||
}
|
||||
|
||||
&.transition {
|
||||
color: var(--ant-geekblue-7);
|
||||
background: var(--ant-geekblue-1);
|
||||
border-color: var(--ant-geekblue-3);
|
||||
}
|
||||
|
||||
&.success {
|
||||
border: 1px solid var(--ant-color-success);
|
||||
color: var(--ant-color-success);
|
||||
background: var(--ant-color-success-bg);
|
||||
|
||||
.content.success {
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
padding: 5px 10px;
|
||||
border-radius: var(--border-radius-base) var(--border-radius-base) 0 0;
|
||||
|
||||
.info-icon {
|
||||
&.danger {
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: var(--ant-color-warning);
|
||||
}
|
||||
|
||||
&.transition {
|
||||
color: var(--ant-geekblue-7);
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: var(--ant-color-success);
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { createStyles } from 'antd-style/lib/functions';
|
||||
|
||||
const useStyles = createStyles(({ token, css }) => ({
|
||||
errorIcon: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background-color: ${token.colorErrorBgActive};
|
||||
color: ${token.colorError};
|
||||
`
|
||||
}));
|
||||
|
||||
const ErrorIcon: React.FC<{ size?: number }> = ({ size }) => {
|
||||
const { styles } = useStyles();
|
||||
return (
|
||||
<div className={styles.errorIcon}>
|
||||
<CloseOutlined style={{ fontSize: size || 30 }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorIcon;
|
||||
@ -0,0 +1,98 @@
|
||||
import { initialPasswordAtom } from '@/atoms/user';
|
||||
import {
|
||||
CRYPT_TEXT,
|
||||
REMEMBER_ME_KEY,
|
||||
getRememberMe,
|
||||
rememberMe,
|
||||
removeRememberMe
|
||||
} from '@/utils/localstore/index';
|
||||
import { FormInstance } from 'antd';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useEffect } from 'react';
|
||||
import { login } from '../apis';
|
||||
|
||||
interface UseLocalAuthOptions {
|
||||
fetchUserInfo: () => Promise<any>;
|
||||
onSuccess?: (userInfo: any) => void;
|
||||
onError?: (error: Error) => void;
|
||||
form: FormInstance;
|
||||
}
|
||||
|
||||
export const useLocalAuth = ({
|
||||
fetchUserInfo,
|
||||
onSuccess,
|
||||
onError,
|
||||
form
|
||||
}: UseLocalAuthOptions) => {
|
||||
const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom);
|
||||
|
||||
// Encrypt password before storing
|
||||
const encryptPassword = (password: string) => {
|
||||
const psw = CryptoJS.AES?.encrypt?.(password, CRYPT_TEXT).toString();
|
||||
return psw;
|
||||
};
|
||||
|
||||
const decryptPassword = (password: string) => {
|
||||
const bytes = CryptoJS.AES?.decrypt?.(password, CRYPT_TEXT);
|
||||
const res = bytes.toString(CryptoJS.enc.Utf8);
|
||||
return res;
|
||||
};
|
||||
|
||||
const callGetRememberMe = async () => {
|
||||
const rememberMe = await getRememberMe(REMEMBER_ME_KEY);
|
||||
|
||||
if (rememberMe?.f) {
|
||||
const username = decryptPassword(rememberMe?.um);
|
||||
const password = decryptPassword(rememberMe?.pw);
|
||||
form.setFieldsValue({ username, password, autoLogin: true });
|
||||
}
|
||||
};
|
||||
|
||||
// remember me
|
||||
const callRememberMe = async (values: any) => {
|
||||
const { autoLogin } = values;
|
||||
if (autoLogin) {
|
||||
await rememberMe(REMEMBER_ME_KEY, {
|
||||
um: encryptPassword(values.username),
|
||||
pw: encryptPassword(values.password),
|
||||
f: true
|
||||
});
|
||||
} else {
|
||||
await removeRememberMe(REMEMBER_ME_KEY);
|
||||
}
|
||||
};
|
||||
|
||||
// click login button
|
||||
const handleLogin = async (values: any) => {
|
||||
try {
|
||||
await login({
|
||||
username: values.username,
|
||||
password: values.password
|
||||
});
|
||||
|
||||
const userInfo = await fetchUserInfo();
|
||||
if (values.autoLogin) {
|
||||
await callRememberMe(values);
|
||||
} else {
|
||||
await removeRememberMe(REMEMBER_ME_KEY);
|
||||
}
|
||||
|
||||
if (userInfo?.require_password_change) {
|
||||
setInitialPassword(encryptPassword(values.password));
|
||||
}
|
||||
|
||||
onSuccess?.(userInfo);
|
||||
} catch (error) {
|
||||
onError?.(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
callGetRememberMe();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
handleLogin
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,77 @@
|
||||
// hooks/useSSOAuth.ts
|
||||
import { history } from '@umijs/max';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
type LoginOption = {
|
||||
saml: boolean;
|
||||
oidc: boolean;
|
||||
};
|
||||
|
||||
// sso configuration
|
||||
async function fetchAuthConfig(url: string) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
console.error('OIDC config error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function useSSOAuth({
|
||||
fetchUserInfo,
|
||||
onSuccess,
|
||||
onError
|
||||
}: {
|
||||
fetchUserInfo: () => Promise<any>;
|
||||
onSuccess?: (userInfo: any) => void;
|
||||
onError?: (err: Error) => void;
|
||||
}) {
|
||||
const [loginOption, setLoginOption] = useState<LoginOption>({
|
||||
saml: false,
|
||||
oidc: false
|
||||
});
|
||||
|
||||
const { location } = history;
|
||||
const params = new URLSearchParams(location.search);
|
||||
const sso = params.get('sso');
|
||||
|
||||
const oidcLogin = () => {
|
||||
window.location.href = '/auth/oidc/login';
|
||||
};
|
||||
|
||||
const samlLogin = () => {
|
||||
window.location.href = '/auth/saml/login';
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAuthConfig('/auth_config')
|
||||
.then((authConfig) => {
|
||||
setLoginOption({
|
||||
oidc: !!authConfig.is_oidc,
|
||||
saml: !!authConfig.is_saml
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setLoginOption({ oidc: false, saml: false });
|
||||
});
|
||||
|
||||
if (sso) {
|
||||
fetchUserInfo()
|
||||
.then((userInfo) => {
|
||||
onSuccess?.(userInfo);
|
||||
})
|
||||
.catch((error) => {
|
||||
onError?.(error);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isSSOLogin: !!sso,
|
||||
options: loginOption,
|
||||
loginWithOIDC: oidcLogin,
|
||||
loginWithSAML: samlLogin
|
||||
};
|
||||
}
|
||||
Loading…
Reference in new issue