mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-15 15:10:31 -08:00
feat: use oauth url to sign in github and get token
This commit is contained in:
parent
98bc7f1f6c
commit
9f8b24354f
11 changed files with 124 additions and 214 deletions
|
|
@ -66,7 +66,6 @@
|
|||
"menubar": "9.3.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"oidc-client-ts": "^2.4.0",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"registry-js": "1.15.1",
|
||||
"rxjs": "7.8.1",
|
||||
|
|
|
|||
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
|
|
@ -110,9 +110,6 @@ dependencies:
|
|||
node-fetch:
|
||||
specifier: 3.3.2
|
||||
version: 3.3.2
|
||||
oidc-client-ts:
|
||||
specifier: ^2.4.0
|
||||
version: 2.4.0
|
||||
reflect-metadata:
|
||||
specifier: 0.1.13
|
||||
version: 0.1.13
|
||||
|
|
@ -5911,10 +5908,6 @@ packages:
|
|||
randomfill: 1.0.4
|
||||
dev: true
|
||||
|
||||
/crypto-js@4.2.0:
|
||||
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||
dev: false
|
||||
|
||||
/csp-html-webpack-plugin@5.1.0(html-webpack-plugin@5.5.3)(webpack@5.88.1):
|
||||
resolution: {integrity: sha512-6l/s6hACE+UA01PLReNKZfgLZWM98f7ewWmE79maDWIbEXiPcIWQGB3LQR/Zw+hPBj4XPZZ5zNrrO+aygqaLaQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -9241,10 +9234,6 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/jwt-decode@3.1.2:
|
||||
resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==}
|
||||
dev: false
|
||||
|
||||
/keycode@2.2.1:
|
||||
resolution: {integrity: sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==}
|
||||
dev: true
|
||||
|
|
@ -10307,14 +10296,6 @@ packages:
|
|||
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
|
||||
dev: true
|
||||
|
||||
/oidc-client-ts@2.4.0:
|
||||
resolution: {integrity: sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
dependencies:
|
||||
crypto-js: 4.2.0
|
||||
jwt-decode: 3.1.2
|
||||
dev: false
|
||||
|
||||
/omggif@1.0.10:
|
||||
resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==}
|
||||
dev: false
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Button, TextField } from '@mui/material';
|
|||
import { useUserInfoObservable } from '@services/auth/hooks';
|
||||
import { getServiceBranchTypes, getServiceEmailTypes, getServiceTokenTypes, getServiceUserNameTypes } from '@services/auth/interface';
|
||||
import { SupportedStorageServices } from '@services/types';
|
||||
import { useAuth } from './gitTokenHooks';
|
||||
import { useAuth, useGetGithubUserInfoOnLoad } from './gitTokenHooks';
|
||||
|
||||
const AuthingLoginButton = styled(Button)`
|
||||
width: 100%;
|
||||
|
|
@ -33,9 +33,10 @@ export function GitTokenForm(props: {
|
|||
const { children, storageService } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [onClickLogin] = useAuth(storageService);
|
||||
|
||||
const userInfo = useUserInfoObservable();
|
||||
const [onClickLogin] = useAuth(storageService);
|
||||
useGetGithubUserInfoOnLoad();
|
||||
|
||||
if (userInfo === undefined) {
|
||||
return <div>{t('Loading')}</div>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,69 +1,58 @@
|
|||
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
||||
import { IGitUserInfosWithoutToken } from '@services/git/interface';
|
||||
import { SupportedStorageServices } from '@services/types';
|
||||
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export function useAuth(storageService: SupportedStorageServices, userInfo: IGitUserInfosWithoutToken): [() => Promise<void>, () => Promise<void>] {
|
||||
const [userManager, setUserManager] = useState<UserManager>();
|
||||
|
||||
useEffect(() => {
|
||||
void window.service.context.get('LOGIN_REDIRECT_PATH').then( (LOGIN_REDIRECT_PATH) => {
|
||||
const settings = {
|
||||
authority: 'https://github.com/',
|
||||
client_id: userInfo.gitUserName,
|
||||
redirect_uri: LOGIN_REDIRECT_PATH,
|
||||
response_type: 'code',
|
||||
scope: 'openid',
|
||||
// post_logout_redirect_uri: '<YOUR_POST_LOGOUT_REDIRECT_URI>',
|
||||
userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||
};
|
||||
|
||||
const userManager = new UserManager(settings);
|
||||
setUserManager(userManager);
|
||||
|
||||
userManager.events.addUserLoaded(async user => {
|
||||
// Handle the loaded user information here
|
||||
// Save the access token and other user details
|
||||
if (user.access_token) {
|
||||
await window.service.auth.set(`${storageService}-token`, user.access_token);
|
||||
}
|
||||
if (userInfo.gitUserName) {
|
||||
await window.service.auth.set(`${storageService}-userName`, userInfo.gitUserName);
|
||||
}
|
||||
if (userInfo.email) {
|
||||
await window.service.auth.set(`${storageService}-email`, userInfo.email);
|
||||
}
|
||||
// Additional user information might be available in the user.profile object
|
||||
});
|
||||
|
||||
userManager.events.addSilentRenewError(error => {
|
||||
console.error('Silent renew error', error);
|
||||
});
|
||||
|
||||
// userManager.events.addUserSignedOut(async () => {
|
||||
// // Handle user sign-out event
|
||||
// await window.service.window.clearStorageData();
|
||||
// });
|
||||
});
|
||||
}, [storageService, userInfo.email, userInfo.gitUserName]);
|
||||
|
||||
const onClickLogin = useCallback(async () => {
|
||||
try {
|
||||
await userManager?.signinRedirect();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}, [userManager]);
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
export function useAuth(storageService: SupportedStorageServices): [() => Promise<void>, () => Promise<void>] {
|
||||
const onClickLogout = useCallback(async () => {
|
||||
try {
|
||||
await userManager?.signoutRedirect();
|
||||
await window.service.auth.set(`${storageService}-token`, '');
|
||||
await window.service.window.clearStorageData();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}, [userManager]);
|
||||
}, [storageService]);
|
||||
|
||||
const onClickLogin = useCallback(async () => {
|
||||
await onClickLogout();
|
||||
try {
|
||||
// redirect current page to oauth login page
|
||||
switch (storageService) {
|
||||
case SupportedStorageServices.github: {
|
||||
location.href = await window.service.context.get('GITHUB_OAUTH_PATH');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}, [onClickLogout, storageService]);
|
||||
|
||||
return [onClickLogin, onClickLogout];
|
||||
}
|
||||
|
||||
export function useGetGithubUserInfoOnLoad(): void {
|
||||
useEffect(() => {
|
||||
void window.service.auth.get(`${SupportedStorageServices.github}-token`).then(async (githubToken) => {
|
||||
try {
|
||||
// DEBUG: console githubToken
|
||||
console.log(`githubToken`, githubToken);
|
||||
if (githubToken) {
|
||||
// get user name and email using github api
|
||||
const response = await fetch('https://api.github.com/user', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${githubToken}`,
|
||||
},
|
||||
});
|
||||
const userInfo = await (response.json() as Promise<{ email: string; login: string; name: string }>);
|
||||
// DEBUG: console userInfo
|
||||
console.log(`userInfo`, userInfo);
|
||||
await window.service.auth.set(`${SupportedStorageServices.github}-userName`, userInfo.login);
|
||||
await window.service.auth.set('userName', userInfo.name);
|
||||
await window.service.auth.set(`${SupportedStorageServices.github}-email`, userInfo.email);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,3 +2,15 @@ export const GITHUB_GRAPHQL_API = 'https://api.github.com/graphql';
|
|||
export const TIDGI_AUTH_TOKEN_HEADER = 'x-tidgi-auth-token';
|
||||
export const getTidGiAuthHeaderWithToken = (authToken: string) => `${TIDGI_AUTH_TOKEN_HEADER}-${authToken}`;
|
||||
export const DEFAULT_USER_NAME = 'TidGi User';
|
||||
/**
|
||||
* Github OAuth Apps TidGi-SignIn Setting in https://github.com/organizations/tiddly-gittly/settings/applications/1326590
|
||||
*/
|
||||
export const GITHUB_LOGIN_REDIRECT_PATH = 'http://127.0.0.1:3012/tidgi-auth/github';
|
||||
export const GITHUB_OAUTH_APP_CLIENT_ID = '7b6e0fc33f4afd71a4bb';
|
||||
export const GITHUB_OAUTH_APP_CLIENT_SECRET = 'e356c4499e1e38548a44da5301ef42c11ec14173';
|
||||
const GITHUB_SCOPES = 'user:email,read:user,repo,workflow';
|
||||
/**
|
||||
* Will redirect to `http://127.0.0.1:3012/tidgi-auth/github?code=65xxxxxxx` after login, which is 404, and handled by src/preload/common/authRedirect.ts
|
||||
*/
|
||||
export const GITHUB_OAUTH_PATH =
|
||||
`https://github.com/login/oauth/authorize?client_id=${GITHUB_OAUTH_APP_CLIENT_ID}&scope=${GITHUB_SCOPES}&redirect_uri=${GITHUB_LOGIN_REDIRECT_PATH}`;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { isMac } from '../helpers/system';
|
||||
import { GITHUB_LOGIN_REDIRECT_PATH, GITHUB_OAUTH_APP_CLIENT_ID } from './auth';
|
||||
import { isDevelopmentOrTest } from './environment';
|
||||
import { developmentWikiFolderName, localizationFolderName } from './fileNames';
|
||||
|
||||
|
|
@ -19,7 +20,6 @@ const menuBarIconFileName = isMac ? 'menubarTemplate@2x.png' : 'menubar@2x.png';
|
|||
export const MENUBAR_ICON_PATH = path.resolve(isDevelopmentOrTest ? buildResourcePath : process.resourcesPath, menuBarIconFileName);
|
||||
|
||||
export const CHROME_ERROR_PATH = 'chrome-error://chromewebdata/';
|
||||
export const LOGIN_REDIRECT_PATH = 'http://127.0.0.1:3012/tidgi-github-auth';
|
||||
export const DESKTOP_PATH = path.join(os.homedir(), 'Desktop');
|
||||
|
||||
export const PACKAGE_PATH_BASE = isDevelopmentOrTest
|
||||
|
|
|
|||
|
|
@ -1,57 +1,78 @@
|
|||
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
||||
// on production build, if we try to redirect to http://localhost:3012 , we will reach chrome-error://chromewebdata/ , but we can easily get back
|
||||
// this happens when we are redirected by OAuth login
|
||||
import { SupportedStorageServices } from '@services/types';
|
||||
import { WindowNames } from '@services/windows/WindowProperties';
|
||||
import { windowName } from './browserViewMetaData';
|
||||
import { context, window as windowService } from './services';
|
||||
|
||||
const CHECK_LOADED_INTERVAL = 500;
|
||||
let constantsFetched = false;
|
||||
let CHROME_ERROR_PATH: string | undefined;
|
||||
let LOGIN_REDIRECT_PATH: string | undefined;
|
||||
let GITHUB_LOGIN_REDIRECT_PATH: string | undefined;
|
||||
let MAIN_WINDOW_WEBPACK_ENTRY: string | undefined;
|
||||
let GITHUB_OAUTH_APP_CLIENT_SECRET: string | undefined;
|
||||
let GITHUB_OAUTH_APP_CLIENT_ID: string | undefined;
|
||||
|
||||
async function refresh(): Promise<void> {
|
||||
if (CHROME_ERROR_PATH === undefined || MAIN_WINDOW_WEBPACK_ENTRY === undefined || LOGIN_REDIRECT_PATH === undefined) {
|
||||
// get path from src/constants/paths.ts
|
||||
if (!constantsFetched) {
|
||||
await Promise.all([
|
||||
context.get('CHROME_ERROR_PATH').then((pathName) => {
|
||||
CHROME_ERROR_PATH = pathName;
|
||||
}),
|
||||
context.get('LOGIN_REDIRECT_PATH').then((pathName) => {
|
||||
LOGIN_REDIRECT_PATH = pathName;
|
||||
}),
|
||||
context.get('MAIN_WINDOW_WEBPACK_ENTRY').then((pathName) => {
|
||||
MAIN_WINDOW_WEBPACK_ENTRY = pathName;
|
||||
}),
|
||||
context.get('GITHUB_LOGIN_REDIRECT_PATH').then((pathName) => {
|
||||
GITHUB_LOGIN_REDIRECT_PATH = pathName;
|
||||
}),
|
||||
context.get('GITHUB_OAUTH_APP_CLIENT_SECRET').then((pathName) => {
|
||||
GITHUB_OAUTH_APP_CLIENT_SECRET = pathName;
|
||||
}),
|
||||
context.get('GITHUB_OAUTH_APP_CLIENT_ID').then((pathName) => {
|
||||
GITHUB_OAUTH_APP_CLIENT_ID = pathName;
|
||||
}),
|
||||
]);
|
||||
setTimeout(() => void refresh(), CHECK_LOADED_INTERVAL);
|
||||
constantsFetched = true;
|
||||
await refresh();
|
||||
return;
|
||||
}
|
||||
if (window.location.href === CHROME_ERROR_PATH || window.location.href.startsWith(`${LOGIN_REDIRECT_PATH}/?code=`)) {
|
||||
if (window.location.href.startsWith(GITHUB_LOGIN_REDIRECT_PATH!)) {
|
||||
// currently content will be something like `/tidgi-auth/github 404 not found`, we need to write something to tell user we are handling login, this is normal.
|
||||
// center the text and make it large
|
||||
document.body.innerHTML = '<div style="text-align: center; font-size: 2rem;">Handling Github login, please wait...</div>';
|
||||
// get the code
|
||||
const code = window.location.href.split('code=')[1];
|
||||
if (code) {
|
||||
// exchange the code for an access token in github
|
||||
const response = await fetch('https://github.com/login/oauth/access_token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
client_id: GITHUB_OAUTH_APP_CLIENT_ID,
|
||||
client_secret: GITHUB_OAUTH_APP_CLIENT_SECRET,
|
||||
code,
|
||||
}),
|
||||
});
|
||||
// get the access token from the response
|
||||
const { access_token: token } = await (response.json() as Promise<{ access_token: string }>);
|
||||
await window.service.auth.set(`${SupportedStorageServices.github}-token`, token);
|
||||
}
|
||||
await windowService.loadURL(windowName, MAIN_WINDOW_WEBPACK_ENTRY);
|
||||
} else if (window.location.href === CHROME_ERROR_PATH) {
|
||||
await windowService.loadURL(windowName, MAIN_WINDOW_WEBPACK_ENTRY);
|
||||
} else {
|
||||
setTimeout(() => void refresh(), CHECK_LOADED_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
interface IAuthingPostMessageEvent {
|
||||
code?: number;
|
||||
data?: {
|
||||
token?: string;
|
||||
};
|
||||
from?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting window and add workspace window may be used to login, and will be redirect, we catch it and redirect back.
|
||||
*/
|
||||
if (![WindowNames.main, WindowNames.view].includes(windowName)) {
|
||||
setTimeout(() => void refresh(), CHECK_LOADED_INTERVAL);
|
||||
// https://stackoverflow.com/questions/55544936/communication-between-preload-and-client-given-context-isolation-in-electron
|
||||
window.addEventListener(
|
||||
'message',
|
||||
(event: MessageEvent<IAuthingPostMessageEvent>) => {
|
||||
if (typeof event?.data?.code === 'number' && typeof event?.data?.data?.token === 'string' && event?.data.from !== 'preload') {
|
||||
// This message will be catch by this handler again, so we add a 'from' to indicate that it is re-send by ourself
|
||||
// we re-send this, so authing in this window can catch it
|
||||
window.postMessage({ ...event.data, from: 'preload' }, '*');
|
||||
}
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable unicorn/no-null */
|
||||
import { IGitUserInfos } from '@services/git/interface';
|
||||
import { logger } from '@services/libs/log';
|
||||
import { IAuthingUserInfo, SupportedStorageServices } from '@services/types';
|
||||
import { SupportedStorageServices } from '@services/types';
|
||||
import { IWorkspace } from '@services/workspaces/interface';
|
||||
import settings from 'electron-settings';
|
||||
import { injectable } from 'inversify';
|
||||
|
|
@ -13,7 +13,6 @@ import { IAuthenticationService, IUserInfos, ServiceBranchTypes, ServiceEmailTyp
|
|||
|
||||
const defaultUserInfos = {
|
||||
userName: '',
|
||||
authing: undefined as IAuthingUserInfo | undefined,
|
||||
};
|
||||
|
||||
@injectable()
|
||||
|
|
|
|||
|
|
@ -6,14 +6,16 @@ import os from 'os';
|
|||
import process from 'process';
|
||||
|
||||
import * as appPaths from '@/constants/appPaths';
|
||||
import * as auth from '@/constants/auth';
|
||||
import { supportedLanguagesMap, tiddlywikiLanguagesMap } from '@/constants/languages';
|
||||
import * as paths from '@/constants/paths';
|
||||
import { IConstants, IContext, IContextService, IPaths } from './interface';
|
||||
import { IAuthConstants, IConstants, IContext, IContextService, IPaths } from './interface';
|
||||
|
||||
@injectable()
|
||||
export class ContextService implements IContextService {
|
||||
// @ts-expect-error Property 'MAIN_WINDOW_WEBPACK_ENTRY' is missing, esbuild will make it `pathConstants = { ..._constants_paths__WEBPACK_IMPORTED_MODULE_4__, ..._constants_appPaths__WEBPACK_IMPORTED_MODULE_5__, 'http://localhost:3012/main_window' };`
|
||||
private readonly pathConstants: IPaths = { ...paths, ...appPaths };
|
||||
private readonly authConstants: IAuthConstants = { ...auth };
|
||||
private readonly constants: IConstants = {
|
||||
isDevelopment: isElectronDevelopment,
|
||||
platform: process.platform,
|
||||
|
|
@ -31,6 +33,7 @@ export class ContextService implements IContextService {
|
|||
this.context = {
|
||||
...this.pathConstants,
|
||||
...this.constants,
|
||||
...this.authConstants,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ export interface IPaths {
|
|||
HTTPS_CERT_KEY_FOLDER: string;
|
||||
LANGUAGE_MODEL_FOLDER: string;
|
||||
LOCALIZATION_FOLDER: string;
|
||||
LOGIN_REDIRECT_PATH: string;
|
||||
LOG_FOLDER: string;
|
||||
MAIN_WINDOW_WEBPACK_ENTRY: string;
|
||||
MENUBAR_ICON_PATH: string;
|
||||
|
|
@ -19,6 +18,13 @@ export interface IPaths {
|
|||
TIDDLYWIKI_TEMPLATE_FOLDER_PATH: string;
|
||||
V8_CACHE_FOLDER: string;
|
||||
}
|
||||
|
||||
export interface IAuthConstants {
|
||||
GITHUB_LOGIN_REDIRECT_PATH: string;
|
||||
GITHUB_OAUTH_APP_CLIENT_ID: string;
|
||||
GITHUB_OAUTH_APP_CLIENT_SECRET: string;
|
||||
GITHUB_OAUTH_PATH: string;
|
||||
}
|
||||
/**
|
||||
* Available values about running environment
|
||||
*/
|
||||
|
|
@ -33,7 +39,7 @@ export interface IConstants {
|
|||
tiddlywikiLanguagesMap: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
export interface IContext extends IPaths, IConstants {}
|
||||
export interface IContext extends IPaths, IConstants, IAuthConstants {}
|
||||
|
||||
/**
|
||||
* Manage constant value like `isDevelopment` and many else, so you can know about about running environment in main and renderer process easily.
|
||||
|
|
|
|||
|
|
@ -14,104 +14,3 @@ export enum SupportedStorageServices {
|
|||
/** SocialLinkedData, a privacy first DApp platform leading by Tim Berners-Lee, you can run a server by you own */
|
||||
solid = 'solid',
|
||||
}
|
||||
|
||||
export interface IAuthingResponse {
|
||||
session?: null | { appId: string; type: string; userId: string };
|
||||
urlParams?: {
|
||||
access_token: string;
|
||||
code: string;
|
||||
// 这些参数是从 url 中获取到的,需要开发者自己存储以备使用
|
||||
id_token: string;
|
||||
};
|
||||
userInfo?: {
|
||||
oauth?: string;
|
||||
thirdPartyIdentity?: {
|
||||
accessToken?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// {"email":"linonetwo012@gmail.com","phone":"","emailVerified":false,"phoneVerified":false,"username":"lin onetwo","nickname":"","company":"ByteDance","photo":"https://avatars1.githubusercontent.com/u/3746270?v=4","browser":"","device":"","loginsCount":26,"registerMethod":"oauth:github","blocked":false,"isDeleted":false,"phoneCode":"","name":"lin onetwo","givenName":"","familyName":"","middleName":"","profile":"","preferredUsername":"","website":"","gender":"","birthdate":"","zoneinfo":"","locale":"","address":"","formatted":"","streetAddress":"","locality":"","region":"","postalCode":"","country":"","updatedAt":"2020-07-04T05:08:53.472Z","metadata":"","_operate_history":[],"sendSMSCount":0,"sendSMSLimitCount":1000,"_id":"5efdd5475b9f7bc0990d7377","unionid":"3746270","lastIP":"61.223.86.45","registerInClient":"5efdd30d48432dfae5d047da","lastLogin":"2020-07-04T05:08:53.665Z","signedUp":"2020-07-02T12:38:31.485Z","__v":0,"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImVtYWlsIjoibGlub2","tokenExpiredAt":"2020-07-19T05:08:53.000Z","__Token 验证方式说明":"https://docs.authing.cn/authing/advanced/authentication/verify-jwt-token#fa-song-token-gei-authing-fu-wu-qi-yan-zheng","login":"linonetwo","id":3746270,"node_id":"MDQ6VXNlcjM3NDYyNzA=","avatar_url":"https://avatars1.githubusercontent.com/u/3746270?v=4","gravatar_id":"","url":"https://api.github.com/users/linonetwo","html_url":"https://github.com/linonetwo","followers_url":"https://api.github.com/users/linonetwo/followers","following_url":"https://api.github.com/users/linonetwo/following{/other_user}","gists_url":"https://api.github.com/users/linonetwo/gists{/gist_id}","starred_url":"https://api.github.com/users/linonetwo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/linonetwo/subscriptions","organizations_url":"https://api.github.com/users/linonetwo/orgs","repos_url":"https://api.github.com/users/linonetwo/repos","events_url":"https://api.github.com/users/linonetwo/events{/privacy}","received_events_url":"https://api.github.com/users/linonetwo/received_events","type":"User","site_admin":false,"blog":"https://onetwo.ren","location":"Shanghaitech University","hireable":null,"bio":"Use Web technology to create dev-tool and knowledge tools for procedural content generation. Hopefully will create a knowledge-driven PCG in game cosmos one day","twitter_username":null,"public_repos":146,"public_gists":13,"followers":167,"following":120,"created_at":"2013-03-02T07:09:13Z","updated_at":"2020-07-02T12:36:46Z","accessToken":"f5610134da3c51632e43e8a2413863987e8ad16e","scope":"repo","provider":"github","expiresIn":null}
|
||||
export interface IAuthingUserInfo {
|
||||
'__Token 验证方式说明': string;
|
||||
__v: number;
|
||||
_id: string;
|
||||
_operate_history: any[];
|
||||
accessToken: string;
|
||||
address: string;
|
||||
avatar_url: string;
|
||||
bio: string;
|
||||
birthdate: string;
|
||||
blocked: boolean;
|
||||
blog: string;
|
||||
browser: string;
|
||||
company: string;
|
||||
country: string;
|
||||
created_at: string;
|
||||
device: string;
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
events_url: string;
|
||||
expiresIn: null;
|
||||
familyName: string;
|
||||
followers: number;
|
||||
followers_url: string;
|
||||
following: number;
|
||||
following_url: string;
|
||||
formatted: string;
|
||||
gender: string;
|
||||
gists_url: string;
|
||||
givenName: string;
|
||||
gravatar_id: string;
|
||||
hireable: null;
|
||||
html_url: string;
|
||||
id: number;
|
||||
isDeleted: boolean;
|
||||
lastIP: string;
|
||||
lastLogin: string;
|
||||
locale: string;
|
||||
locality: string;
|
||||
location: string;
|
||||
login: string;
|
||||
loginsCount: number;
|
||||
metadata: string;
|
||||
middleName: string;
|
||||
name: string;
|
||||
nickname: string;
|
||||
node_id: string;
|
||||
organizations_url: string;
|
||||
phone: string;
|
||||
phoneCode: string;
|
||||
phoneVerified: boolean;
|
||||
photo: string;
|
||||
postalCode: string;
|
||||
preferredUsername: string;
|
||||
profile: string;
|
||||
provider: string;
|
||||
public_gists: number;
|
||||
public_repos: number;
|
||||
received_events_url: string;
|
||||
region: string;
|
||||
registerInClient: string;
|
||||
registerMethod: string;
|
||||
repos_url: string;
|
||||
scope: string;
|
||||
sendSMSCount: number;
|
||||
sendSMSLimitCount: number;
|
||||
signedUp: string;
|
||||
site_admin: boolean;
|
||||
starred_url: string;
|
||||
streetAddress: string;
|
||||
subscriptions_url: string;
|
||||
token: string;
|
||||
tokenExpiredAt: string;
|
||||
twitter_username: null;
|
||||
type: string;
|
||||
unionid: string;
|
||||
updatedAt: string;
|
||||
updated_at: string;
|
||||
url: string;
|
||||
username: string;
|
||||
website: string;
|
||||
zoneinfo: string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue