feat: add oauth plugin

This commit is contained in:
shuai 2023-01-13 11:38:01 +08:00
parent a88b476803
commit 479892b156
10 changed files with 144 additions and 57 deletions

View File

@ -747,6 +747,13 @@ ui:
lang: lang:
label: Interface Language label: Interface Language
text: User interface language. It will change when you refresh the page. text: User interface language. It will change when you refresh the page.
my_logins:
title: My Logins
lable: Log in or sign up on this site using these accounts.
modal_title: Remove Login
modal_content: Are you sure you want to remove this login from your account?
modal_confirm_btn: Remove
remove_success: Removed successfully
toast: toast:
update: update success update: update success
update_password: Password changed successfully. update_password: Password changed successfully.
@ -1362,7 +1369,7 @@ ui:
btn_submit: Save btn_submit: Save
not_found_props: "Required property {{ key }} not found." not_found_props: "Required property {{ key }} not found."
select: Select select: Select
page_review: page_review:
review: Review review: Review
proposed: proposed proposed: proposed

View File

@ -521,3 +521,14 @@ export interface OauthBindEmailReq {
email: string; email: string;
must: boolean; must: boolean;
} }
export interface OauthConnectorItem {
icon: string;
name: string;
link: string;
}
export interface UserOauthConnectorItem extends OauthConnectorItem {
binding: boolean;
external_id: string;
}

View File

@ -1,30 +0,0 @@
import { memo, FC } from 'react';
import { Button } from 'react-bootstrap';
import classnames from 'classnames';
import { Icon } from '@/components';
interface Props {
// data: any[]; // should use oauth plugin schemes
className?: string;
}
const Index: FC<Props> = ({ className }) => {
return (
<div className={classnames('d-grid gap-2', className)}>
<Button
variant="outline-secondary"
href="https://github.com/login/oauth/authorize?client_id=8cb9d4760cfd71c24de9&edirect_uri=http://10.0.20.88:8080/answer/api/v1/connector/redirect/github">
<Icon name="github" className="me-2" />
<span>Connect with Github</span>
</Button>
<Button variant="outline-secondary">
<Icon name="twitter" className="me-2" />
<span>Connect with Google</span>
</Button>
</div>
);
};
export default memo(Index);

View File

@ -32,7 +32,6 @@ import CustomizeTheme from './CustomizeTheme';
import PageTags from './PageTags'; import PageTags from './PageTags';
import QuestionListLoader from './QuestionListLoader'; import QuestionListLoader from './QuestionListLoader';
import TagsLoader from './TagsLoader'; import TagsLoader from './TagsLoader';
import OauthButtons from './OauthButtons';
export { export {
Avatar, Avatar,
@ -71,6 +70,5 @@ export {
PageTags, PageTags,
QuestionListLoader, QuestionListLoader,
TagsLoader, TagsLoader,
OauthButtons,
}; };
export type { EditorRef, JSONSchema, UISchema }; export type { EditorRef, JSONSchema, UISchema };

View File

@ -302,3 +302,9 @@ a {
.bg-fade-out { .bg-fade-out {
animation: 2s ease 1s bg-fade-out; animation: 2s ease 1s bg-fade-out;
} }
.btnSvg, .btnSvg:hover {
fill: currentColor;
vertical-align: -0.125em;
}

View File

@ -11,7 +11,8 @@ import type {
ImgCodeRes, ImgCodeRes,
FormDataType, FormDataType,
} from '@/common/interface'; } from '@/common/interface';
import { Unactivate, OauthButtons } from '@/components'; import { Unactivate } from '@/components';
import { PluginOauth } from '@/plugins';
import { loggedUserInfoStore, loginSettingStore } from '@/stores'; import { loggedUserInfoStore, loginSettingStore } from '@/stores';
import { guard, floppyNavigation, handleFormError } from '@/utils'; import { guard, floppyNavigation, handleFormError } from '@/utils';
import { login, checkImgCode } from '@/services'; import { login, checkImgCode } from '@/services';
@ -173,7 +174,7 @@ const Index: React.FC = () => {
<h3 className="text-center mb-5">{t('page_title')}</h3> <h3 className="text-center mb-5">{t('page_title')}</h3>
{step === 1 && ( {step === 1 && (
<Col className="mx-auto" md={3}> <Col className="mx-auto" md={3}>
<OauthButtons className="mb-5" /> <PluginOauth className="mb-5" />
<Form noValidate onSubmit={handleSubmit}> <Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId="email" className="mb-3"> <Form.Group controlId="email" className="mb-3">
<Form.Label>{t('email.label')}</Form.Label> <Form.Label>{t('email.label')}</Form.Label>

View File

@ -1,40 +1,65 @@
import { memo } from 'react'; import { memo } from 'react';
import { Button } from 'react-bootstrap'; import { Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Icon, Modal } from '@/components'; import { Modal } from '@/components';
import { useOauthConnectorInfoByUser, userOauthUnbind } from '@/services';
import { useToast } from '@/hooks';
const Index = () => { const Index = () => {
const deleteLogins = (type) => { const { data, mutate } = useOauthConnectorInfoByUser();
const toast = useToast();
const { t } = useTranslation('translation', {
keyPrefix: 'settings.my_logins',
});
const deleteLogins = (e, item) => {
if (!item.binding) {
return;
}
e.preventDefault();
Modal.confirm({ Modal.confirm({
title: 'Remove Login', title: t('modal_title'),
content: 'Are you sure you want to delete this logins?', content: t('modal_content'),
confirmBtnVariant: 'danger', confirmBtnVariant: 'danger',
confirmText: 'Remove', confirmText: t('modal_confirm_btn'),
onConfirm: () => { onConfirm: () => {
console.log('delete login by: ', type); userOauthUnbind({ external_id: item.external_id }).then(() => {
mutate();
toast.onShow({
msg: t('remove_success'),
variant: 'success',
});
});
}, },
}); });
}; };
return ( return (
<div className="mt-5"> <div className="mt-5">
<div className="form-label">My Logins</div> <div className="form-label">{t('title')}</div>
<small className="form-text mt-0"> <small className="form-text mt-0">{t('lable')}</small>
Log in or sign up on this site using these accounts.
</small>
<div className="mt-3"> <div className="mt-3">
<Button variant="outline-secondary" className="d-block mb-2"> {data?.map((item) => {
<Icon name="google" className="me-2" /> return (
<span>Connect with Google</span> <Button
</Button> variant={item.binding ? 'outline-danger' : 'outline-secondary'}
href={item.link}
<Button onClick={(e) => deleteLogins(e, item)}
variant="outline-danger" key={item.name}>
className="mb-2" <svg
onClick={() => deleteLogins('github')}> xmlns="http://www.w3.org/2000/svg"
<Icon name="github" className="me-2" /> width="16"
<span>Remove GitHub</span> height="16"
</Button> className="btnSvg"
viewBox="0 0 24 24">
<path d={item.icon} />
</svg>
<span> {item.name}</span>
</Button>
);
})}
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,36 @@
import { memo, FC } from 'react';
import { Button } from 'react-bootstrap';
import classnames from 'classnames';
import { useGetStartUseOauthConnector } from '@/services';
interface Props {
className?: string;
}
const Index: FC<Props> = ({ className }) => {
const { data } = useGetStartUseOauthConnector();
if (!data?.length) return null;
return (
<div className={classnames('d-grid gap-2', className)}>
{data?.map((item) => {
return (
<Button variant="outline-secondary" href={item.link} key={item.name}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
className="btnSvg"
viewBox="0 0 24 24">
<path d={item.icon} />
</svg>
<span> {item.name}</span>
</Button>
);
})}
</div>
);
};
export default memo(Index);

3
ui/src/plugins/index.ts Normal file
View File

@ -0,0 +1,3 @@
import PluginOauth from './PluginOauth';
export { PluginOauth };

View File

@ -1,6 +1,36 @@
import useSWR from 'swr';
import request from '@/utils/request'; import request from '@/utils/request';
import type * as Type from '@/common/interface'; import type * as Type from '@/common/interface';
export const oAuthBindEmail = (data: Type.OauthBindEmailReq) => { export const oAuthBindEmail = (data: Type.OauthBindEmailReq) => {
return request.post('/answer/api/v1/connector/binding/email', data); return request.post('/answer/api/v1/connector/binding/email', data);
}; };
export const useOauthConnectorInfoByUser = () => {
const { data, error, mutate } = useSWR<Type.UserOauthConnectorItem[]>(
'/answer/api/v1/connector/user/info',
request.instance.get,
);
return {
data,
mutate,
isLoading: !data && !error,
error,
};
};
export const userOauthUnbind = (data: { external_id: string }) => {
return request.delete('/answer/api/v1/connector/user/unbinding', data);
};
export const useGetStartUseOauthConnector = () => {
const { data, error } = useSWR<Type.OauthConnectorItem[]>(
'/answer/api/v1/connector/info',
request.instance.get,
);
return {
data,
error,
};
};