Skip to content

Commit

Permalink
feat: add github social login
Browse files Browse the repository at this point in the history
  • Loading branch information
lizheming committed Jan 8, 2021
1 parent a5aa16c commit 050f6be
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@
"@babel/preset-react": "^7.12.5",
"@reach/router": "^1.3.4",
"@rematch/core": "^1.4.0",
"@svgr/webpack": "^5.5.0",
"babel-loader": "^8.2.1",
"classnames": "^2.2.6",
"css-loader": "^5.0.1",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.0",
"marked": "^1.2.3",
"md5": "^2.3.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/admin/src/components/icon/github.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions packages/admin/src/pages/profile/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import React, { useState } from 'react';
import cls from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import Header from '../../components/Header';
import { updateProfile } from '../../services/user';

import { ReactComponent as GithubIcon } from '../../components/icon/github.svg';

export default function() {
const [isPasswordUpdating, setPasswordUpdating] = useState(false);
const [isProfileUpdating, setProfileUpdating] = useState(false);
Expand Down Expand Up @@ -41,6 +44,13 @@ export default function() {
setPasswordUpdating(false);
}


let baseUrl = 'http://localhost:3000/'; globalThis.serverURL;
if(!baseUrl) {
const match = location.pathname.match(/(.*?\/)ui/);
baseUrl = match ? match[1] : '/';
}

return (
<>
<Header />
Expand Down Expand Up @@ -96,6 +106,25 @@ export default function() {
</form>
</section>
<br/>
<section id="socical-account">
<h3>账号绑定</h3>
<div className="account-list">
<div className={cls('account-item github', {bind: user.github})}>
<a
href={user.gitub ? `https://github.com/${user.github}` : `${baseUrl}oauth?type=github`}
target="_blank"
>
<GithubIcon />
</a>
<div className="account-unbind">
<svg className="vicon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="14" height="14">
<path d="M568.569 512l170.267-170.267c15.556-15.556 15.556-41.012 0-56.569s-41.012-15.556-56.569 0L512 455.431 341.733 285.165c-15.556-15.556-41.012-15.556-56.569 0s-15.556 41.012 0 56.569L455.431 512 285.165 682.267c-15.556 15.556-15.556 41.012 0 56.569 15.556 15.556 41.012 15.556 56.569 0L512 568.569l170.267 170.267c15.556 15.556 41.012 15.556 56.569 0 15.556-15.556 15.556-41.012 0-56.569L568.569 512z"></path>
</svg>
</div>
</div>
</div>
</section>
<br/>
<section id="change-password">
<h3>密码修改</h3>
<form method="post" onSubmit={onPasswordUpdate}>
Expand Down
28 changes: 28 additions & 0 deletions packages/admin/src/style/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,32 @@ html, body {

div[tabindex="-1"] {
height: 100%;
}

.account-item {
display: inline-block;
position: relative;
}

.account-item .account-unbind svg {
background: #FFF;
border: 1px solid #999;
border-radius: 50%;
position: absolute;
top: -3px;
right: -3px;
display: none;
cursor: pointer;
}
.account-item:hover .account-unbind svg {
display: block;
}


.account-item.github path {
fill: grey;
}
.account-item.github:hover path,
.account-item.github.bind path {
fill: #1B1F23;
}
2 changes: 1 addition & 1 deletion packages/admin/src/utils/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default async function request(url, opts = {}) {
opts.headers.Authorization = `Bearer ${token}`;
}

let baseUrl = globalThis.serverURL;
let baseUrl = 'https://imnerd.vercel.app/'; //globalThis.serverURL;
if(!baseUrl) {
const match = location.pathname.match(/(.*?\/)ui/);
baseUrl = match ? match[1] : '/';
Expand Down
4 changes: 4 additions & 0 deletions packages/admin/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ module.exports = {
'css-loader'
]
},
{
test: /\.svg$/,
use: ['@svgr/webpack', 'file-loader'],
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
Expand Down
4 changes: 4 additions & 0 deletions packages/server/src/config/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ module.exports = [
if(/favicon.ico$/.test(ctx.url)) {
return;
}
if (think.isPrevent(err)) {
return false;
}

console.error(err);
}
}
Expand Down
7 changes: 7 additions & 0 deletions packages/server/src/controller/oauth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = class extends think.Controller {
async githubAction() {
const instance = this.service('auth/github', this);
const socialInfo = await instance.getUserInfo();
return this.success(socialInfo);
}
};
10 changes: 10 additions & 0 deletions packages/server/src/extend/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
success(...args) {
this.ctx.success(...args);
return think.prevent();
},
fail(...args) {
this.ctx.fail(...args);
return think.prevent();
}
};
9 changes: 9 additions & 0 deletions packages/server/src/extend/think.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const preventMessage = 'PREVENT_NEXT_PROCESS';
module.exports = {
prevent() {
throw new Error(preventMessage);
},
isPrevent(err) {
return think.isError(err) && err.message === preventMessage;
}
};
4 changes: 4 additions & 0 deletions packages/server/src/logic/oauth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const Base = require('./base');

module.exports = class extends Base {
}
27 changes: 27 additions & 0 deletions packages/server/src/service/auth/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { parse } = require('url');

module.exports = class extends think.Service {
constructor(app) {
super(app);
this.app = app;
}

getCompleteUrl(url = '') {
if (url.slice(0, 4) === 'http') {
try {
const { host } = parse(url);
if (this.app.host.toLowerCase() !== host.toLowerCase()) {
throw new Error(403);
}
return url;
} catch (e) {
// ignore error
}
}
const protocol = this.app.header('x-forwarded-proto') || 'http';
if (!/^\//.test(url)) {
url = '/' + url;
}
return protocol + '://' + this.app.ctx.host + url;
}
}
74 changes: 74 additions & 0 deletions packages/server/src/service/auth/github.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const qs = require('querystring');
const request = require('request-promise-native');
const Base = require('./base');

const OAUTH_URL = 'https://github.com/login/oauth/authorize';
const ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token';
const USER_INFO_URL = 'https://api.github.com/user';
const {GITHUB_ID, GITHUB_SECRET} = process.env;
module.exports = class extends Base {
getAuthUrl(opts) {
const params = {
client_id: GITHUB_ID,
redirect_uri: opts.rdUrl,
scope: 'user'
};
return OAUTH_URL + '?' + qs.stringify(params);
}

async getAccessToken(opts) {
const params = {
client_id: GITHUB_ID,
client_secret: GITHUB_SECRET,
code: opts.code
};

const body = await request.post({
url: ACCESS_TOKEN_URL,
headers: {'Accept': 'application/json'},
form: params,
json: true
});
return body;
}

async getUserInfoByToken(opts) {
const userInfo = await request.get({
url: USER_INFO_URL,
headers: {
'User-Agent': '@waline',
'Authorization': 'token ' + opts.access_token
},
json: true
});

return {
id: userInfo.login,
name: userInfo.name,
desc: userInfo.bio,
avatar: userInfo.avatar_url
};
}

async redirect() {
const {app} = this;
const {type, rdurl} = app.get();
const rdUrlAfterLogin = this.getCompleteUrl(rdurl);

const params = { rdurl: rdUrlAfterLogin, type };
const signinUrl = this.getCompleteUrl('/oauth') + '?' + qs.stringify(params);
const AUTH_URL = this.getAuthUrl({rdUrl: signinUrl});
app.redirect(AUTH_URL);
return app.success();
}

async getUserInfo() {
const {code} = this.app.get();
if (!code) {
return this.redirect();
}

const accessTokenInfo = await this.getAccessToken({code});
return this.getUserInfoByToken(accessTokenInfo);
}
};

0 comments on commit 050f6be

Please sign in to comment.