feat: allow user use Authing to get token

This commit is contained in:
tiddlygit-test 2021-04-13 01:06:19 +08:00
parent af32534288
commit 79847adbb3
5 changed files with 118 additions and 192 deletions

View file

@ -42,6 +42,7 @@
"ImportWiki": "导入WIKI",
"CloneWiki": "克隆Wiki",
"NotLoggedIn": "未登录",
"LogoutToGetStorageServiceToken": "登录在线存储服务以获取凭证",
"LogoutGithubAccount": "登出Github账号",
"LoginGithubAccount": "登录Github账号",
"GitToken": "Git Token",

View file

@ -0,0 +1,111 @@
import React, { useMemo, useCallback } from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import AuthingSSO from '@authing/sso';
import { TextField, Button } from '@material-ui/core';
import { SupportedStorageServices, IAuthingUserInfo } from '@services/types';
import { useUserInfoObservable } from '@services/auth/hooks';
import { usePromiseValueAndSetter } from '@/helpers/useServiceValue';
import GitHubLogin from './AuthingLoginButton';
import { APP_ID, APP_DOMAIN } from '@/constants/auth';
import { IUserInfos } from '@services/auth/interface';
const AuthingLoginButton = styled(Button)`
white-space: nowrap;
width: 100%;
`;
const GitTokenInput = styled(TextField)``;
export function GitTokenForm(props: {
children?: JSX.Element | Array<JSX.Element | undefined | string>;
storageService: SupportedStorageServices;
}): JSX.Element {
const { children, storageService } = props;
const { t } = useTranslation();
/**
* Update tiddlywiki's editor user name when first time creating new workspace
*/
const [userName, userNameSetter] = usePromiseValueAndSetter(
async () => await window.service.auth.get('userName'),
async (newUserName) => await window.service.auth.set('userName', newUserName),
);
const authing = useMemo(
() =>
new AuthingSSO({
appId: APP_ID,
appDomain: APP_DOMAIN,
redirectUrl: 'http://localhost:3000',
}),
[],
);
const onFailure = useCallback(async (error: Error) => {}, []);
const onClickLogin = useCallback(async () => {
// clear token first, otherwise github login window won't give us a chance to see the form
// void this.auth.logout();
// window.remote.clearStorageData();
try {
await authing.login();
const { session, ...response } = await authing.trackSession();
const isLogin = session !== null && session !== undefined;
if (isLogin && 'userInfo' in response && response.userInfo?.thirdPartyIdentity?.accessToken !== undefined) {
const accessTokenToSet = response?.userInfo?.thirdPartyIdentity?.accessToken;
const authDataString = response?.userInfo?.oauth;
if (accessTokenToSet !== undefined) {
void window.service.auth.set((`${storageService}-token` as unknown) as keyof IUserInfos, accessTokenToSet);
}
// all data we need
if (accessTokenToSet !== undefined && authDataString !== undefined) {
const authData = JSON.parse(authDataString);
const nextUserInfo: IAuthingUserInfo = {
...response.userInfo,
...authData,
...response.userInfo?.thirdPartyIdentity,
};
delete nextUserInfo.oauth;
delete nextUserInfo.thirdPartyIdentity;
void window.service.auth.set((`${storageService}-userName` as unknown) as keyof IUserInfos, nextUserInfo.username);
if (userName === undefined || userName === '') {
userNameSetter(nextUserInfo.username);
}
}
}
} catch (error) {
void onFailure(error);
}
}, [authing, onFailure]);
const onClickLogout = useCallback(async () => {
const { code, message } = await authing.logout();
await window.service.window.clearStorageData();
if (code === 200) {
// TODO: clear the input
} else {
console.error(message);
}
}, [authing, onFailure]);
const userInfo = useUserInfoObservable();
if (userInfo === undefined) {
return <div>Loading...</div>;
}
return (
<>
<AuthingLoginButton>{t('AddWorkspace.LogoutToGetStorageServiceToken')}</AuthingLoginButton>
<GitTokenInput
helperText={t('AddWorkspace.GitTokenDescription')}
fullWidth
onChange={(event) => {
void window.service.auth.set(`${storageService}-token`, event.target.value);
}}
value={userInfo[`${storageService}-token`]}
/>
{children}
</>
);
}

View file

@ -5,6 +5,9 @@ import { TabPanel, TabContext, TabList } from '@material-ui/lab';
import { SupportedStorageServices } from '@services/types';
import { useTranslation } from 'react-i18next';
import GitHubLogin from './AuthingLoginButton';
import { GitTokenForm } from './GitTokenForm';
const Container = styled.div`
width: 100%;
display: flex;
@ -47,7 +50,9 @@ export function TokenForm(): JSX.Element {
<Tab label="GitLab" value={SupportedStorageServices.gitlab} />
<Tab label="Gitee" value={SupportedStorageServices.gitee} />
</TabList>
<TabPanel value={SupportedStorageServices.github}>Item One</TabPanel>
<TabPanel value={SupportedStorageServices.github}>
<GitTokenForm storageService={SupportedStorageServices.github} />
</TabPanel>
<TabPanel value={SupportedStorageServices.gitlab}>Item Two</TabPanel>
<TabPanel value={SupportedStorageServices.gitee}>Item Two</TabPanel>
</TabsContainer>

View file

@ -1,77 +0,0 @@
import React from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import TextField from '@material-ui/core/TextField';
import GitHubLogin from './github-login';
import type { IAuthingUserInfo } from '@services/types';
import { useUserInfoObservable } from '@services/auth/hooks';
const GitTokenInput = styled(TextField)``;
export const setGithubToken = async (token: string | undefined): Promise<void> => await window.service.auth.set('github-token', token);
export const getGithubToken = async (): Promise<string | undefined> => await window.service.auth.get('github-token');
export function GithubTokenForm(props: { children?: JSX.Element | Array<JSX.Element | undefined | string> }): JSX.Element {
const { children } = props;
const { t } = useTranslation();
useEffect(() => {
// on startup, loading the cachedGithubToken
if (accessToken === undefined && cachedGithubToken !== undefined) {
graphqlClient.setHeader('Authorization', `bearer ${cachedGithubToken}`);
accessTokenSetter(cachedGithubToken);
} else if (accessToken !== undefined && accessToken !== cachedGithubToken) {
// if user or login button changed the token, we use latest token
Object.keys(graphqlClient.headers).map((key) => graphqlClient.removeHeader(key));
accessTokenSetter(accessToken);
void setGithubToken(accessToken);
}
}, [cachedGithubToken, accessToken]);
const userInfo = useUserInfoObservable();
if (userInfo === undefined) {
return <div>Loading...</div>;
}
return (
<>
<GitHubLogin
onSuccess={(response) => {
const accessTokenToSet = response?.userInfo?.thirdPartyIdentity?.accessToken;
const authDataString = response?.userInfo?.oauth;
if (accessTokenToSet !== undefined) {
void window.service.auth.set('github-token', accessTokenToSet);
}
// all data we need
if (accessTokenToSet !== undefined && authDataString !== undefined) {
const authData = JSON.parse(authDataString);
const nextUserInfo = {
...response.userInfo,
...authData,
...response.userInfo?.thirdPartyIdentity,
};
delete nextUserInfo.oauth;
delete nextUserInfo.thirdPartyIdentity;
void window.service.auth.set('github-userName', (nextUserInfo as IAuthingUserInfo).username);
}
}}
onLogout={() => {
void window.service.auth.set('github-token', '');
void window.service.auth.set('github-userName', '');
}}
onFailure={() => {
void window.service.auth.set('github-token', '');
void window.service.auth.set('github-userName', '');
}}
/>
<GitTokenInput
helperText={t('AddWorkspace.GitTokenDescription')}
fullWidth
onChange={(event) => {
void window.service.auth.set('github-token', event.target.value);
}}
value={userInfo['github-token']}
/>
{children}
</>
);
}

View file

@ -1,114 +0,0 @@
/* eslint-disable promise/no-nesting */
import React, { Component } from 'react';
import styled from 'styled-components';
import Button from '@material-ui/core/Button';
import GithubIcon from '@material-ui/icons/GitHub';
import AuthingSSO, { ILoginInfo } from '@authing/sso';
import { withTranslation } from 'react-i18next';
import { APP_DOMAIN, APP_ID } from '../../constants/auth';
const SyncToGithubButton = styled(Button)`
white-space: nowrap;
width: 100%;
`;
interface Props {
t: (x: string) => string;
onRequest: () => unknown;
onSuccess: (info: Partial<ILoginInfo>) => unknown;
onLogout: () => unknown;
onFailure: (error: Error) => unknown;
}
interface State {
isLogin: boolean;
}
class GitHubLogin extends Component<Props, State> {
static defaultProps: Partial<Props> = {
onRequest: () => {},
onSuccess: () => {},
onLogout: () => {},
onFailure: () => {},
};
auth: AuthingSSO;
intervalHandel?: NodeJS.Timeout;
constructor(props: Props) {
super(props);
this.state = {
isLogin: false,
};
this.auth = new AuthingSSO({
appId: APP_ID,
appDomain: APP_DOMAIN,
redirectUrl: 'http://localhost:3000',
});
this.updateLoginState();
}
async isLogin(): Promise<boolean> {
const { onSuccess, onLogout } = this.props;
const { session, ...rest } = await this.auth.trackSession();
const isLogin = session !== null && session !== undefined;
if (isLogin) {
onSuccess(rest);
} else {
onLogout();
}
return isLogin;
}
updateLoginState(): void {
this.intervalHandel = setInterval(() => {
void this.isLogin().then((isLogin) => {
this.setState({ isLogin });
if (this.intervalHandel !== undefined) {
clearInterval(this.intervalHandel);
}
});
}, 500);
}
render(): JSX.Element {
const { onRequest, onLogout, onFailure, t } = this.props;
const { isLogin } = this.state;
return isLogin ? (
<SyncToGithubButton
onClick={async () => {
const { code, message } = await this.auth.logout();
await window.service.window.clearStorageData();
if (code === 200) {
this.setState({ isLogin: false });
this.updateLoginState();
onLogout();
} else {
console.error(message);
}
}}
color="secondary"
endIcon={<GithubIcon />}>
{t('AddWorkspace.LogoutGithubAccount')}
</SyncToGithubButton>
) : (
<SyncToGithubButton
onClick={async () => {
// clear token first, otherwise github login window won't give us a chance to see the form
// void this.auth.logout();
// window.remote.clearStorageData();
try {
onRequest();
await this.auth.login();
} catch (error) {
onFailure(error);
}
}}
color="secondary"
endIcon={<GithubIcon />}>
{t('AddWorkspace.LoginGithubAccount')}
</SyncToGithubButton>
);
}
}
export default withTranslation()(GitHubLogin);