feat: login config

This commit is contained in:
zerosoul
2022-03-31 17:43:58 +08:00
parent b2254c5fb7
commit 265d4653a7
7 changed files with 686 additions and 570 deletions
+28 -34
View File
@@ -1,27 +1,28 @@
import { fetchBaseQuery } from "@reduxjs/toolkit/query";
import toast from "react-hot-toast";
import dayjs from "dayjs";
import { updateToken, resetAuthData } from "../slices/auth.data";
import BASE_URL, { tokenHeader } from "../config";
import { fetchBaseQuery } from '@reduxjs/toolkit/query';
import toast from 'react-hot-toast';
import dayjs from 'dayjs';
import { updateToken, resetAuthData } from '../slices/auth.data';
import BASE_URL, { tokenHeader } from '../config';
const whiteList = [
"login",
"register",
"checkInviteTokenValid",
"getServer",
"getOpenid",
"getMetamaskNonce",
"renew",
'login',
'register',
'checkInviteTokenValid',
'getLoginConfig',
'getServer',
'getOpenid',
'getMetamaskNonce',
'renew'
];
const baseQuery = fetchBaseQuery({
baseUrl: BASE_URL,
prepareHeaders: (headers, { getState, endpoint }) => {
console.log("req", endpoint);
console.log('req', endpoint);
const { token } = getState().authData;
if (token && !whiteList.includes(endpoint)) {
headers.set(tokenHeader, token);
}
return headers;
},
}
});
let waitingForRenew = null;
const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
@@ -29,25 +30,18 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
await waitingForRenew;
}
// 先检查token是否过期,过期则renew
const {
token,
refreshToken,
expireTime = new Date().getTime(),
} = api.getState().authData;
const { token, refreshToken, expireTime = new Date().getTime() } = api.getState().authData;
let result = null;
if (
!whiteList.includes(api.endpoint) &&
dayjs().isAfter(new Date(expireTime - 20 * 1000))
) {
if (!whiteList.includes(api.endpoint) && dayjs().isAfter(new Date(expireTime - 20 * 1000))) {
// 快过期了,renew
waitingForRenew = baseQuery(
{
url: "/token/renew",
method: "POST",
url: '/token/renew',
method: 'POST',
body: {
token,
refresh_token: refreshToken,
},
refresh_token: refreshToken
}
},
api,
extraOptions
@@ -64,21 +58,21 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
result = await baseQuery(args, api, extraOptions);
}
if (result?.error) {
console.log("api error", result.error, api.endpoint);
console.log('api error', result.error, api.endpoint);
switch (result.error.originalStatus || result.error.status) {
case "FETCH_ERROR":
case 'FETCH_ERROR':
{
toast.error(`${api.endpoint}: Failed to fetch`);
}
break;
case 404:
{
toast.error("Request Not Found");
toast.error('Request Not Found');
}
break;
case 500:
{
toast.error(result.error.data || "server error");
toast.error(result.error.data || 'server error');
}
break;
case 401:
@@ -86,15 +80,15 @@ const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
// if (api.endpoint === "renew") {
// toast.error("token expired, please login again");
api.dispatch(resetAuthData());
location.href = "/#/login";
location.href = '/#/login';
// } else {
toast.error("API Not Authenticated");
toast.error('API Not Authenticated');
// return;
// }
}
break;
case 403:
toast.error("Request Not Allowed");
toast.error('Request Not Allowed');
break;
default:
break;
+53 -44
View File
@@ -1,10 +1,10 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import BASE_URL from "../config";
import { updateInviteLink, updateInfo } from "../slices/server";
import baseQuery from "./base.query";
import { createApi } from '@reduxjs/toolkit/query/react';
import BASE_URL from '../config';
import { updateInviteLink, updateInfo } from '../slices/server';
import baseQuery from './base.query';
const defaultExpireDuration = 7 * 24 * 60 * 60;
export const serverApi = createApi({
reducerPath: "serverApi",
reducerPath: 'serverApi',
baseQuery,
endpoints: (builder) => ({
getServer: builder.query({
@@ -18,104 +18,111 @@ export const serverApi = createApi({
const { data: server } = await queryFulfilled;
dispatch(updateInfo(server));
} catch {
console.log("get server info error");
console.log('get server info error');
}
}
},
}),
getMetrics: builder.query({
query: () => ({ url: `/admin/system/metrics` }),
query: () => ({ url: `/admin/system/metrics` })
}),
getFirebaseConfig: builder.query({
query: () => ({ url: `admin/fcm/config` }),
query: () => ({ url: `admin/fcm/config` })
}),
sendTestEmail: builder.mutation({
query: (data) => ({
url: `/admin/system/send_mail`,
method: "POST",
body: data,
}),
method: 'POST',
body: data
})
}),
updateFirebaseConfig: builder.mutation({
query: (data) => ({
url: `admin/fcm/config`,
method: "POST",
body: data,
}),
method: 'POST',
body: data
})
}),
getAgoraConfig: builder.query({
query: () => ({ url: `admin/agora/config` }),
query: () => ({ url: `admin/agora/config` })
}),
updateAgoraConfig: builder.mutation({
query: (data) => ({
url: `admin/agora/config`,
method: "POST",
body: data,
}),
method: 'POST',
body: data
})
}),
getSMTPConfig: builder.query({
query: () => ({ url: `admin/smtp/config` }),
query: () => ({ url: `admin/smtp/config` })
}),
updateSMTPConfig: builder.mutation({
query: (data) => ({
url: `admin/smtp/config`,
method: "POST",
body: data,
method: 'POST',
body: data
})
}),
getLoginConfig: builder.query({
query: () => ({ url: `admin/login/config` })
}),
updateLoginConfig: builder.mutation({
query: (data) => ({
url: `admin/login/config`,
method: 'POST',
body: data
})
}),
updateLogo: builder.mutation({
query: (data) => ({
headers: {
"content-type": "image/png",
'content-type': 'image/png'
},
url: `admin/system/organization/logo`,
method: "POST",
body: data,
method: 'POST',
body: data
}),
async onQueryStarted(data, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
dispatch(
updateInfo({
logo: `${BASE_URL}/resource/organization/logo?t=${new Date().getTime()}`,
logo: `${BASE_URL}/resource/organization/logo?t=${new Date().getTime()}`
})
);
} catch {
console.log("update server logo error");
console.log('update server logo error');
}
}
},
}),
createInviteLink: builder.query({
query: (expired_in = defaultExpireDuration) => ({
headers: {
"content-type": "text/plain",
accept: "text/plain",
'content-type': 'text/plain',
accept: 'text/plain'
},
url: `/admin/user/create_invite_link?expired_in=${expired_in}`,
responseHandler: (response) => response.text(),
responseHandler: (response) => response.text()
}),
transformResponse: (link) => {
// 替换掉域名
const invite = new URL(link);
return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`;
},
async onQueryStarted(
expire = defaultExpireDuration,
{ dispatch, queryFulfilled }
) {
async onQueryStarted(expire = defaultExpireDuration, { dispatch, queryFulfilled }) {
try {
const { data: link } = await queryFulfilled;
console.log("link", link);
console.log('link', link);
dispatch(updateInviteLink({ expire, link }));
} catch {
console.log("invite link error");
console.log('invite link error');
}
}
},
}),
updateServer: builder.mutation({
query: (data) => ({
url: `admin/system/organization`,
method: "POST",
body: data,
method: 'POST',
body: data
}),
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
const { name: prevName, description: prevDesc } = getState().server;
@@ -125,15 +132,17 @@ export const serverApi = createApi({
} catch {
dispatch(updateInfo({ name: prevName, description: prevDesc }));
}
},
}),
}),
}
})
})
});
export const {
useSendTestEmailMutation,
useUpdateFirebaseConfigMutation,
useGetFirebaseConfigQuery,
useGetLoginConfigQuery,
useUpdateLoginConfigMutation,
useGetSMTPConfigQuery,
useUpdateSMTPConfigMutation,
useGetAgoraConfigQuery,
@@ -143,5 +152,5 @@ export const {
useLazyGetServerQuery,
useUpdateServerMutation,
useUpdateLogoMutation,
useLazyCreateInviteLinkQuery,
useLazyCreateInviteLinkQuery
} = serverApi;
@@ -0,0 +1,115 @@
// import { useState, useEffect } from "react";
import StyledContainer from './StyledContainer';
// import Input from "../../styled/Input";
import Textarea from '../../styled/Textarea';
import Toggle from '../../styled/Toggle';
import Label from '../../styled/Label';
import SaveTip from '../../SaveTip';
import useConfig from './useConfig';
export default function Logins() {
const { values, updateConfig, setValues, reset, changed } = useConfig('login');
const handleUpdate = () => {
// const { token_url, description } = values;
updateConfig(values);
};
const handleChange = (evt) => {
const newValue = evt.target.value;
const { type } = evt.target.dataset;
const items = newValue ? newValue.split('\n') : [];
setValues((prev) => {
return { ...prev, [type]: items };
});
};
const handleToggle = (val) => {
setValues((prev) => {
return { ...prev, ...val };
});
};
if (!values) return null;
const { google, metamask, password, oidc = [] } = values ?? {};
return (
<StyledContainer>
<div className="inputs">
<div className="input row">
<Label>Password</Label>
<Toggle
onClick={handleToggle.bind(null, { password: !password })}
data-checked={password}
></Toggle>
</div>
<div className="input row">
<Label>Google</Label>
<Toggle onClick={handleToggle.bind(null, { google: !google })} data-checked={google}></Toggle>
</div>
<div className="input row">
<Label>Metamask</Label>
<Toggle
onClick={handleToggle.bind(null, { metamask: !metamask })}
data-checked={metamask}
></Toggle>
</div>
<div className="input">
<Label htmlFor="desc">OIDC</Label>
<Textarea
rows={10}
data-type="oidc"
onChange={handleChange}
value={oidc.join('\n')}
name="oidc"
placeholder="Input issuer list, one line, one issuer"
/>
</div>
{/* <div className="input">
<Label htmlFor="name">Token Url</Label>
<Input
disabled={!enabled}
data-type="token_url"
onChange={handleChange}
value={token_url || "https://oauth2.googleapis.com/token"}
name="token_url"
placeholder="Token URL"
/>
</div>
<div className="input">
<Label htmlFor="desc">Project ID</Label>
<Input
disabled={!enabled}
type={"number"}
data-type="project_id"
onChange={handleChange}
value={project_id}
name="project_id"
placeholder="Project ID"
/>
</div>
<div className="input">
<Label htmlFor="desc">Private Key</Label>
<Textarea
rows={10}
disabled={!enabled}
data-type="private_key"
onChange={handleChange}
value={private_key}
name="private_key"
placeholder="Private key"
/>
</div>
<div className="input">
<Label htmlFor="desc">Client Email</Label>
<Input
disabled={!enabled}
data-type="client_email"
onChange={handleChange}
value={client_email}
name="client_email"
placeholder="Client Email address"
/>
</div> */}
</div>
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
{/* <button onClick={handleUpdate} className="btn">update</button> */}
</StyledContainer>
);
}
@@ -1,55 +1,51 @@
import { useEffect, useState } from "react";
import { isObjectEqual } from "../../../utils";
import toast from "react-hot-toast";
import { useEffect, useState } from 'react';
import { isObjectEqual } from '../../../utils';
import toast from 'react-hot-toast';
import {
useGetAgoraConfigQuery,
useGetFirebaseConfigQuery,
useGetSMTPConfigQuery,
useGetLoginConfigQuery,
useUpdateLoginConfigMutation,
useUpdateSMTPConfigMutation,
useUpdateAgoraConfigMutation,
useUpdateFirebaseConfigMutation,
} from "../../../../app/services/server";
export default function useConfig(config = "smtp") {
useUpdateFirebaseConfigMutation
} from '../../../../app/services/server';
export default function useConfig(config = 'smtp') {
const [changed, setChanged] = useState(false);
const [values, setValues] = useState({});
const { data: Login, refetch: refetchLogin } = useGetLoginConfigQuery();
const [updateLoginConfig, { isSuccess: LoginUpdated }] = useUpdateLoginConfigMutation();
const { data: SMTP, refetch: refetchSMTP } = useGetSMTPConfigQuery();
const [
updateSMTPConfig,
{ isSuccess: SMTPUpdated },
] = useUpdateSMTPConfigMutation();
const [updateSMTPConfig, { isSuccess: SMTPUpdated }] = useUpdateSMTPConfigMutation();
const { data: Agora, refetch: refetchAgora } = useGetAgoraConfigQuery();
const [
updateAgoraConfig,
{ isSuccess: AgoraUpdated },
] = useUpdateAgoraConfigMutation();
const {
data: Firebase,
refetch: refetchFirebase,
} = useGetFirebaseConfigQuery();
const [
updateFirebaseConfig,
{ isSuccess: FirebaseUpdated },
] = useUpdateFirebaseConfigMutation();
const [updateAgoraConfig, { isSuccess: AgoraUpdated }] = useUpdateAgoraConfigMutation();
const { data: Firebase, refetch: refetchFirebase } = useGetFirebaseConfigQuery();
const [updateFirebaseConfig, { isSuccess: FirebaseUpdated }] = useUpdateFirebaseConfigMutation();
const datas = {
login: Login,
smtp: SMTP,
agora: Agora,
firebase: Firebase,
firebase: Firebase
};
const updateFns = {
login: updateLoginConfig,
smtp: updateSMTPConfig,
agora: updateAgoraConfig,
firebase: updateFirebaseConfig,
firebase: updateFirebaseConfig
};
const refetchs = {
smtp: refetchSMTP,
agora: refetchAgora,
firebase: refetchFirebase,
login: refetchLogin
};
const updateds = {
login: LoginUpdated,
smtp: SMTPUpdated,
agora: AgoraUpdated,
firebase: FirebaseUpdated,
firebase: FirebaseUpdated
};
const data = datas[config];
const updateConfig = updateFns[config];
@@ -66,12 +62,12 @@ export default function useConfig(config = "smtp") {
};
useEffect(() => {
if (updated) {
toast.success("Configuration Updated!");
toast.success('Configuration Updated!');
refetch();
}
}, [updated]);
useEffect(() => {
console.log("wtf", data);
console.log('wtf', data);
// if (data) {
setValues(data ?? {});
// }
@@ -91,6 +87,6 @@ export default function useConfig(config = "smtp") {
updateConfig,
values,
setValues,
toggleEnable,
toggleEnable
};
}
+57 -51
View File
@@ -1,85 +1,91 @@
import { useSelector } from "react-redux";
import MyAccount from "./MyAccount";
import Overview from "./Overview";
import ConfigFirebase from "./config/Firebase";
import ConfigSMTP from "./config/SMTP";
import Notifications from "./Notifications";
import ManageMembers from "../ManageMembers";
import FAQ from "../FAQ";
import ConfigAgora from "./config/Agora";
import { useSelector } from 'react-redux';
import MyAccount from './MyAccount';
import Overview from './Overview';
import Logins from './config/Logins';
import ConfigFirebase from './config/Firebase';
import ConfigSMTP from './config/SMTP';
import Notifications from './Notifications';
import ManageMembers from '../ManageMembers';
import FAQ from '../FAQ';
import ConfigAgora from './config/Agora';
const navs = [
{
title: "General",
title: 'General',
items: [
{
name: "overview",
title: "Overview",
component: <Overview />,
name: 'overview',
title: 'Overview',
component: <Overview />
},
{
name: "members",
title: "Members",
name: 'members',
title: 'Members',
component: <ManageMembers />,
admin: true,
admin: true
},
{
name: "notification",
title: "Notification",
component: <Notifications />,
},
],
name: 'notification',
title: 'Notification',
component: <Notifications />
}
]
},
{
title: "User",
title: 'User',
items: [
{
name: "my_account",
title: "My Account",
component: <MyAccount />,
},
],
name: 'my_account',
title: 'My Account',
component: <MyAccount />
}
]
},
{
title: "Configuration",
title: 'Configuration',
items: [
{
name: "firebase",
title: "Firebase",
component: <ConfigFirebase />,
name: 'firebase',
title: 'Firebase',
component: <ConfigFirebase />
},
{
name: "agora",
title: "Agora",
component: <ConfigAgora />,
name: 'agora',
title: 'Agora',
component: <ConfigAgora />
},
{
name: "smtp",
title: "SMTP",
component: <ConfigSMTP />,
name: 'smtp',
title: 'SMTP',
component: <ConfigSMTP />
},
{
name: 'social_login',
title: 'Social Login',
component: <Logins />
}
],
admin: true,
admin: true
},
{
title: "About",
title: 'About',
items: [
{
name: "faq",
title: "FAQ",
component: <FAQ />,
name: 'faq',
title: 'FAQ',
component: <FAQ />
},
{
name: "terms",
title: "Terms & Privacy",
component: "Terms & Privacy",
name: 'terms',
title: 'Terms & Privacy',
component: 'Terms & Privacy'
},
{
name: "feedback",
title: "Feedback",
component: "feedback",
},
],
},
name: 'feedback',
title: 'Feedback',
component: 'feedback'
}
]
}
];
const useNavs = () => {
const loginUser = useSelector((store) => {
+9 -13
View File
@@ -1,33 +1,29 @@
/* eslint-disable no-undef */
import { useEffect } from "react";
import { useGetOpenidMutation } from "../../app/services/auth";
import solidSvg from "../../assets/icons/solid.svg?url";
import { StyledSocialButton } from "./styled";
import { useEffect } from 'react';
import { useGetOpenidMutation } from '../../app/services/auth';
import solidSvg from '../../assets/icons/solid.svg?url';
import { StyledSocialButton } from './styled';
export default function SolidLoginButton() {
export default function SolidLoginButton({ issuers }) {
const [getOpenId, { data, isLoading, isSuccess }] = useGetOpenidMutation();
const handleSolidLogin = () => {
getOpenId({
// issuer: "solidweb.org",
issuer: "broker.pod.inrupt.com",
redirect_uri: `${location.origin}/#/login`,
issuer: issuers[0],
redirect_uri: `${location.origin}/#/login`
});
};
useEffect(() => {
if (isSuccess) {
console.log("wtf", data);
console.log('wtf', data);
const { url } = data;
location.href = url;
}
}, [data, isSuccess]);
return (
<StyledSocialButton
disabled={isLoading}
onClick={handleSolidLogin}
href="#"
>
<StyledSocialButton disabled={isLoading} onClick={handleSolidLogin} href="#">
<img src={solidSvg} className="icon" alt="solid icon" />
{isLoading ? `Redirecting...` : `Sign in with Solid`}
</StyledSocialButton>
+37 -37
View File
@@ -1,36 +1,38 @@
/* eslint-disable no-undef */
import { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import toast from "react-hot-toast";
import BASE_URL from "../../app/config";
import { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import toast from 'react-hot-toast';
import BASE_URL from '../../app/config';
// import web3 from "web3";
import StyledWrapper from "./styled";
import MetamaskLoginButton from "./MetamaskLoginButton";
import SolidLoginButton from "./SolidLoginButton";
import Input from "../../common/component/styled/Input";
import Button from "../../common/component/styled/Button";
import GoogleLoginButton from "./GoogleLoginButton";
import { useLoginMutation } from "../../app/services/auth";
import { setAuthData } from "../../app/slices/auth.data";
import StyledWrapper from './styled';
import MetamaskLoginButton from './MetamaskLoginButton';
import SolidLoginButton from './SolidLoginButton';
import Input from '../../common/component/styled/Input';
import Button from '../../common/component/styled/Button';
import GoogleLoginButton from './GoogleLoginButton';
import { useLoginMutation } from '../../app/services/auth';
import { useGetLoginConfigQuery } from '../../app/services/server';
import { setAuthData } from '../../app/slices/auth.data';
export default function LoginPage() {
const [login, { data, isSuccess, isLoading, error }] = useLoginMutation();
const { data: loginConfig, isSuccess: loginConfigSuccess } = useGetLoginConfigQuery();
const navigateTo = useNavigate();
const dispatch = useDispatch();
const [input, setInput] = useState({
email: "",
password: "",
email: '',
password: ''
});
useEffect(() => {
const query = new URLSearchParams(location.search);
const code = query.get("code");
const state = query.get("state");
const code = query.get('code');
const state = query.get('state');
if (code && state) {
login({
code,
state,
type: "oidc",
type: 'oidc'
});
}
}, []);
@@ -39,17 +41,17 @@ export default function LoginPage() {
if (error) {
console.log(error);
switch (error.status) {
case "PARSING_ERROR":
case 'PARSING_ERROR':
toast.error(error.data);
break;
case 401:
toast.error("username or password incorrect");
toast.error('username or password incorrect');
break;
case 404:
toast.error("account not exsit");
toast.error('account not exsit');
break;
default:
toast.error("something error");
toast.error('something error');
break;
}
return;
@@ -58,19 +60,19 @@ export default function LoginPage() {
useEffect(() => {
if (isSuccess && data) {
// 更新本地认证信息
console.log("login data", data);
toast.success("login success");
console.log('login data', data);
toast.success('login success');
dispatch(setAuthData(data));
navigateTo("/");
navigateTo('/');
}
}, [isSuccess, data]);
const handleLogin = (evt) => {
evt.preventDefault();
console.log("wtf", input);
console.log('wtf', input);
login({
...input,
type: "password",
type: 'password'
});
};
@@ -84,15 +86,13 @@ export default function LoginPage() {
});
};
const { email, password } = input;
if (!loginConfigSuccess) return null;
const { google: enableGoogleLogin, metamask: enableMetamaskLogin, oidc } = loginConfig;
return (
<StyledWrapper>
<div className="form">
<div className="tips">
<img
src={`${BASE_URL}/resource/organization/logo`}
alt="logo"
className="logo"
/>
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
<h2 className="title">Login to Rustchat</h2>
<span className="desc">Please enter your details.</span>
</div>
@@ -117,13 +117,13 @@ export default function LoginPage() {
placeholder="Enter your password"
/>
<Button type="submit" disabled={isLoading}>
{isLoading ? "Signing" : `Sign in`}
{isLoading ? 'Signing' : `Sign in`}
</Button>
</form>
<hr className="or" />
<GoogleLoginButton login={login} />
<MetamaskLoginButton login={login} />
<SolidLoginButton />
{(enableGoogleLogin || enableMetamaskLogin || oidc.length > 0) && <hr className="or" />}
{enableGoogleLogin && <GoogleLoginButton login={login} />}
{enableMetamaskLogin && <MetamaskLoginButton login={login} />}
{oidc.length > 0 && <SolidLoginButton issuers={oidc} />}
</div>
</StyledWrapper>
);