feat: github oauth login view

pull/100/head
moonrailgun 2 years ago
parent ed1d7cc1d6
commit e81e7ad64f

@ -314,3 +314,11 @@ export const [pluginGroupConfigItems, regPluginGroupConfigItem] = buildRegList<{
loading: boolean; loading: boolean;
}) => ReactElement; }) => ReactElement;
}>(); }>();
/**
*
*/
export const [pluginLoginAction, regLoginAction] = buildRegList<{
name: string;
component: React.ComponentType;
}>();

@ -4,12 +4,10 @@ import {
isValidStr, isValidStr,
loginWithEmail, loginWithEmail,
t, t,
useAppSelector,
useAsyncFn, useAsyncFn,
useGlobalConfigStore, useGlobalConfigStore,
} from 'tailchat-shared'; } from 'tailchat-shared';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Spinner } from '../../components/Spinner';
import { string } from 'yup'; import { string } from 'yup';
import { useLocation, useNavigate } from 'react-router'; import { useLocation, useNavigate } from 'react-router';
import { setUserJWT } from '../../utils/jwt-helper'; import { setUserJWT } from '../../utils/jwt-helper';
@ -23,6 +21,7 @@ import { LanguageSelect } from '@/components/LanguageSelect';
import { EntryInput } from './components/Input'; import { EntryInput } from './components/Input';
import { SecondaryBtn } from './components/SecondaryBtn'; import { SecondaryBtn } from './components/SecondaryBtn';
import { PrimaryBtn } from './components/PrimaryBtn'; import { PrimaryBtn } from './components/PrimaryBtn';
import { pluginLoginAction } from '@/plugin/common';
/** /**
* TODO: * TODO:
@ -156,6 +155,12 @@ export const LoginView: React.FC = React.memo(() => {
<Icon icon="mdi:arrow-right" className="ml-1 inline" /> <Icon icon="mdi:arrow-right" className="ml-1 inline" />
</SecondaryBtn> </SecondaryBtn>
)} )}
{pluginLoginAction.map((item) => {
const { name, component: Component } = item;
return <Component key={name} />;
})}
</div> </div>
<div className="absolute bottom-4 left-0 space-x-2"> <div className="absolute bottom-4 left-0 space-x-2">

@ -1,6 +1,7 @@
import { Spinner } from '@/components/Spinner'; import { Spinner } from '@/components/Spinner';
import clsx from 'clsx'; import clsx from 'clsx';
import React, { ButtonHTMLAttributes } from 'react'; import React, { ButtonHTMLAttributes } from 'react';
import _omit from 'lodash/omit';
export const PrimaryBtn: React.FC< export const PrimaryBtn: React.FC<
ButtonHTMLAttributes<HTMLButtonElement> & { ButtonHTMLAttributes<HTMLButtonElement> & {
@ -10,7 +11,7 @@ export const PrimaryBtn: React.FC<
return ( return (
<button <button
disabled={props.loading} disabled={props.loading}
{...props} {..._omit(props, ['loading'])}
className={clsx( className={clsx(
'w-full py-2 px-4 mb-2 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50', 'w-full py-2 px-4 mb-2 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50',
props.className props.className

@ -26,6 +26,7 @@ class FimService extends TcService {
return availableStrategies.map((s) => ({ return availableStrategies.map((s) => ({
name: s.name, name: s.name,
type: s.type, type: s.type,
icon: s.icon,
})); }));
}); });
@ -71,7 +72,10 @@ class FimService extends TcService {
userId: fimRecord.userId, userId: fimRecord.userId,
}); });
return { type: 'token', token }; return generatePostMessageHtml({
type: 'token',
token: token,
});
} }
// 不存在记录,查找是否已经注册过,如果已经注册过需要绑定,如果没有注册过则创建账号并绑定用户关系 // 不存在记录,查找是否已经注册过,如果已经注册过需要绑定,如果没有注册过则创建账号并绑定用户关系
@ -80,9 +84,7 @@ class FimService extends TcService {
}); });
if (!!userInfo) { if (!!userInfo) {
// 用户已存在,需要登录后才能确定绑定关系 // 用户已存在,需要登录后才能确定绑定关系
return { return generatePostMessageHtml({ type: 'existed' });
type: 'existed',
};
} }
const newUserInfo: UserStructWithToken = await ctx.call( const newUserInfo: UserStructWithToken = await ctx.call(
@ -100,14 +102,21 @@ class FimService extends TcService {
userId: String(newUserInfo._id), userId: String(newUserInfo._id),
}); });
return { return generatePostMessageHtml({
type: 'token', type: 'token',
isNew: true, isNew: true,
token: newUserInfo.token, token: newUserInfo.token,
}; });
}, },
}; };
} }
} }
function generatePostMessageHtml(obj: Record<string, any>) {
return {
__raw: true,
html: `<script>window.postMessage(${JSON.stringify(obj)})</script>`,
};
}
export default FimService; export default FimService;

@ -15,12 +15,13 @@ const redirect_uri = `${config.apiUrl}/api/plugin:com.msgbyte.fim/github/redirec
export const GithubStrategy: StrategyType = { export const GithubStrategy: StrategyType = {
name: 'github', name: 'github',
type: 'oauth', type: 'oauth',
icon: '/images/avatar/github-dark.svg',
checkAvailable: () => !!clientInfo.id && !!clientInfo.secret, checkAvailable: () => !!clientInfo.id && !!clientInfo.secret,
getUrl: () => { getUrl: () => {
return `${authorize_uri}?client_id=${clientInfo.id}&redirect_uri=${redirect_uri}`; return `${authorize_uri}?client_id=${clientInfo.id}&redirect_uri=${redirect_uri}`;
}, },
getUserInfo: async (code) => { getUserInfo: async (code) => {
console.log('authorization code:', code); console.log('[github oauth] authorization code:', code);
const tokenResponse = await got const tokenResponse = await got
.post(access_token_uri, { .post(access_token_uri, {
@ -36,7 +37,7 @@ export const GithubStrategy: StrategyType = {
.json<{ access_token: string }>(); .json<{ access_token: string }>();
const accessToken = tokenResponse.access_token; const accessToken = tokenResponse.access_token;
console.log(`access token: ${accessToken}`); console.log(`[github oauth] access token: ${accessToken}`);
const result = await got const result = await got
.get(userinfo_uri, { .get(userinfo_uri, {
@ -47,6 +48,8 @@ export const GithubStrategy: StrategyType = {
}) })
.json<{ id: number; name: string; email: string; avatar_url: string }>(); .json<{ id: number; name: string; email: string; avatar_url: string }>();
console.log(`[github oauth] user info:`, result);
return { return {
id: String(result.id), id: String(result.id),
nickname: result.name, nickname: result.name,

@ -1,6 +1,7 @@
export interface StrategyType { export interface StrategyType {
name: string; name: string;
type: 'oauth'; type: 'oauth';
icon: string;
checkAvailable: () => boolean; checkAvailable: () => boolean;
getUrl: () => string; getUrl: () => string;
getUserInfo: (code: string) => Promise<{ getUserInfo: (code: string) => Promise<{

@ -0,0 +1,54 @@
import React from 'react';
import { useAsync } from '@capital/common';
import { Divider, Image, Tooltip } from '@capital/component';
import { request } from './request';
export const FimAction: React.FC = React.memo(() => {
const { loading, value: strategies } = useAsync(async () => {
const { data: strategies } = await request.get('availableStrategies');
return strategies;
}, []);
if (loading) {
return null;
}
if (Array.isArray(strategies) && strategies.length > 0) {
return (
<div>
<Divider />
<div style={{ display: 'flex', justifyContent: 'center' }}>
{strategies.map((s) => (
<Tooltip key={s.name} title={s.name}>
<Image
style={{
width: 40,
height: 40,
cursor: 'pointer',
borderRadius: 20,
}}
src={s.icon}
onClick={async () => {
if (s.type === 'oauth') {
const { data: url } = await request.get(
`${s.name}.loginUrl`
);
const win = window.open(url, 'square', 'frame=true');
win.addEventListener('message', (...args) => {
console.log(...args);
});
}
}}
/>
</Tooltip>
))}
</div>
</div>
);
}
return null;
});
FimAction.displayName = 'FimAction';

@ -1 +1,9 @@
import { regLoginAction } from '@capital/common';
import { FimAction } from './FimAction';
console.log('Plugin Federated Identity Management is loaded'); console.log('Plugin Federated Identity Management is loaded');
regLoginAction({
name: 'fim',
component: FimAction,
});

@ -0,0 +1,3 @@
import { createPluginRequest } from '@capital/common';
export const request = createPluginRequest('com.msgbyte.fim');
Loading…
Cancel
Save