mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-27 02:53:08 +02:00
Add GitHub auth (#3095)
This commit is contained in:
parent
8d7c033cf5
commit
8646a9c49c
14 changed files with 416 additions and 8 deletions
|
@ -148,6 +148,12 @@ drive:
|
||||||
# consumer_key: example-twitter-consumer-key
|
# consumer_key: example-twitter-consumer-key
|
||||||
# consumer_secret: example-twitter-consumer-secret-key
|
# consumer_secret: example-twitter-consumer-secret-key
|
||||||
|
|
||||||
|
# GitHub integration
|
||||||
|
# You need to set the oauth callback url as : https://<your-misskey-instance>/api/gh/cb
|
||||||
|
#github:
|
||||||
|
# client_id: example-github-client-id
|
||||||
|
# client_secret: example-github-client-secret
|
||||||
|
|
||||||
# Ghost
|
# Ghost
|
||||||
# Ghost account is an account used for the purpose of delegating
|
# Ghost account is an account used for the purpose of delegating
|
||||||
# followers when putting users in the list.
|
# followers when putting users in the list.
|
||||||
|
|
|
@ -417,6 +417,7 @@ common/views/components/signin.vue:
|
||||||
signin: "サインイン"
|
signin: "サインイン"
|
||||||
or: "または"
|
or: "または"
|
||||||
signin-with-twitter: "Twitterでログイン"
|
signin-with-twitter: "Twitterでログイン"
|
||||||
|
signin-with-github: "GitHubでログイン"
|
||||||
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
|
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
|
||||||
|
|
||||||
common/views/components/signup.vue:
|
common/views/components/signup.vue:
|
||||||
|
@ -460,6 +461,14 @@ common/views/components/twitter-setting.vue:
|
||||||
connect: "Twitterと接続する"
|
connect: "Twitterと接続する"
|
||||||
disconnect: "切断する"
|
disconnect: "切断する"
|
||||||
|
|
||||||
|
common/views/components/github-setting.vue:
|
||||||
|
description: "お使いのGitHubアカウントをお使いのMisskeyアカウントに接続しておくと、プロフィールでGitHubアカウント情報が表示されるようになったり、GitHubを用いた便利なサインインを利用できるようになります。"
|
||||||
|
connected-to: "次のGitHubアカウントに接続されています"
|
||||||
|
detail: "詳細..."
|
||||||
|
reconnect: "再接続する"
|
||||||
|
connect: "GitHubと接続する"
|
||||||
|
disconnect: "切断する"
|
||||||
|
|
||||||
common/views/components/uploader.vue:
|
common/views/components/uploader.vue:
|
||||||
waiting: "待機中"
|
waiting: "待機中"
|
||||||
|
|
||||||
|
@ -1528,6 +1537,10 @@ mobile/views/pages/settings.vue:
|
||||||
twitter-connect: "Twitterアカウントに接続する"
|
twitter-connect: "Twitterアカウントに接続する"
|
||||||
twitter-reconnect: "再接続する"
|
twitter-reconnect: "再接続する"
|
||||||
twitter-disconnect: "切断する"
|
twitter-disconnect: "切断する"
|
||||||
|
github: "GitHub連携"
|
||||||
|
github-connect: "GitHubアカウントに接続する"
|
||||||
|
github-reconnect: "再接続する"
|
||||||
|
github-disconnect: "切断する"
|
||||||
update: "Misskey Update"
|
update: "Misskey Update"
|
||||||
version: "バージョン:"
|
version: "バージョン:"
|
||||||
latest-version: "最新のバージョン:"
|
latest-version: "最新のバージョン:"
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
"@types/mongodb": "3.1.12",
|
"@types/mongodb": "3.1.12",
|
||||||
"@types/ms": "0.7.30",
|
"@types/ms": "0.7.30",
|
||||||
"@types/node": "10.12.2",
|
"@types/node": "10.12.2",
|
||||||
|
"@types/oauth": "0.9.1",
|
||||||
"@types/portscanner": "2.1.0",
|
"@types/portscanner": "2.1.0",
|
||||||
"@types/pug": "2.0.4",
|
"@types/pug": "2.0.4",
|
||||||
"@types/qrcode": "1.3.0",
|
"@types/qrcode": "1.3.0",
|
||||||
|
|
63
src/client/app/common/views/components/github-setting.vue
Normal file
63
src/client/app/common/views/components/github-setting.vue
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<template>
|
||||||
|
<div class="mk-github-setting">
|
||||||
|
<p>%i18n:@description%<a :href="`${docsUrl}/link-to-github`" target="_blank">%i18n:@detail%</a></p>
|
||||||
|
<p class="account" v-if="$store.state.i.github" :title="`GitHub ID: ${$store.state.i.github.id}`">%i18n:@connected-to%: <a :href="`https://github.com/${$store.state.i.github.login}`" target="_blank">@{{ $store.state.i.github.login }}</a></p>
|
||||||
|
<p>
|
||||||
|
<a :href="`${apiUrl}/connect/github`" target="_blank" @click.prevent="connect">{{ $store.state.i.github ? '%i18n:@reconnect%' : '%i18n:@connect%' }}</a>
|
||||||
|
<span v-if="$store.state.i.github"> or </span>
|
||||||
|
<a :href="`${apiUrl}/disconnect/github`" target="_blank" v-if="$store.state.i.github" @click.prevent="disconnect">%i18n:@disconnect%</a>
|
||||||
|
</p>
|
||||||
|
<p class="id" v-if="$store.state.i.github">GitHub ID: {{ $store.state.i.github.id }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { apiUrl, docsUrl } from '../../../config';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: null,
|
||||||
|
apiUrl,
|
||||||
|
docsUrl
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$watch('$store.state.i', () => {
|
||||||
|
if (this.$store.state.i.github && this.form)
|
||||||
|
this.form.close();
|
||||||
|
}, {
|
||||||
|
deep: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
connect() {
|
||||||
|
this.form = window.open(apiUrl + '/connect/github',
|
||||||
|
'github_connect_window',
|
||||||
|
'height=570, width=520');
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
window.open(apiUrl + '/disconnect/github',
|
||||||
|
'github_disconnect_window',
|
||||||
|
'height=570, width=520');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.mk-github-setting
|
||||||
|
.account
|
||||||
|
border solid 1px #e1e8ed
|
||||||
|
border-radius 4px
|
||||||
|
padding 16px
|
||||||
|
|
||||||
|
a
|
||||||
|
font-weight bold
|
||||||
|
color inherit
|
||||||
|
|
||||||
|
.id
|
||||||
|
color #8899a6
|
||||||
|
</style>
|
|
@ -37,6 +37,7 @@ import messaging from './messaging.vue';
|
||||||
import messagingRoom from './messaging-room.vue';
|
import messagingRoom from './messaging-room.vue';
|
||||||
import urlPreview from './url-preview.vue';
|
import urlPreview from './url-preview.vue';
|
||||||
import twitterSetting from './twitter-setting.vue';
|
import twitterSetting from './twitter-setting.vue';
|
||||||
|
import githubSetting from './github-setting.vue';
|
||||||
import fileTypeIcon from './file-type-icon.vue';
|
import fileTypeIcon from './file-type-icon.vue';
|
||||||
import Reversi from './games/reversi/reversi.vue';
|
import Reversi from './games/reversi/reversi.vue';
|
||||||
import welcomeTimeline from './welcome-timeline.vue';
|
import welcomeTimeline from './welcome-timeline.vue';
|
||||||
|
@ -90,6 +91,7 @@ Vue.component('mk-messaging', messaging);
|
||||||
Vue.component('mk-messaging-room', messagingRoom);
|
Vue.component('mk-messaging-room', messagingRoom);
|
||||||
Vue.component('mk-url-preview', urlPreview);
|
Vue.component('mk-url-preview', urlPreview);
|
||||||
Vue.component('mk-twitter-setting', twitterSetting);
|
Vue.component('mk-twitter-setting', twitterSetting);
|
||||||
|
Vue.component('mk-github-setting', githubSetting);
|
||||||
Vue.component('mk-file-type-icon', fileTypeIcon);
|
Vue.component('mk-file-type-icon', fileTypeIcon);
|
||||||
Vue.component('mk-reversi', Reversi);
|
Vue.component('mk-reversi', Reversi);
|
||||||
Vue.component('mk-welcome-timeline', welcomeTimeline);
|
Vue.component('mk-welcome-timeline', welcomeTimeline);
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required styl="fill"/>
|
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required styl="fill"/>
|
||||||
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
|
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
|
||||||
<p style="margin: 8px 0;">%i18n:@or% <a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
|
<p style="margin: 8px 0;">%i18n:@or% <a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
|
||||||
|
<p style="margin: 8px 0;">%i18n:@or% <a :href="`${apiUrl}/signin/github`">%i18n:@signin-with-github%</a></p>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,13 @@
|
||||||
<mk-twitter-setting/>
|
<mk-twitter-setting/>
|
||||||
</section>
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
|
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%fa:B github% %i18n:@github%</div>
|
||||||
|
<section>
|
||||||
|
<mk-github-setting/>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui-card class="theme" v-show="page == 'theme'">
|
<ui-card class="theme" v-show="page == 'theme'">
|
||||||
|
|
27
src/client/app/desktop/views/pages/user/user.github.vue
Normal file
27
src/client/app/desktop/views/pages/user/user.github.vue
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<template>
|
||||||
|
<div class="aqooishiizumijmihokohinatamihoaz">
|
||||||
|
<span>%fa:B github%<a :href="`https://github.com/${user.github.login}`" target="_blank">@{{ user.github.login }}</a></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: ['user']
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.aqooishiizumijmihokohinatamihoaz
|
||||||
|
.adsvaidqfznoartcbplullnejvxjphcn
|
||||||
|
padding 32px
|
||||||
|
background #171515
|
||||||
|
border-radius 6px
|
||||||
|
color #fff
|
||||||
|
|
||||||
|
a
|
||||||
|
margin-left 8px
|
||||||
|
color #fff
|
||||||
|
|
||||||
|
</style>
|
|
@ -2,7 +2,7 @@
|
||||||
<mk-ui>
|
<mk-ui>
|
||||||
<div class="xygkxeaeontfaokvqmiblezmhvhostak" v-if="!fetching">
|
<div class="xygkxeaeontfaokvqmiblezmhvhostak" v-if="!fetching">
|
||||||
<div class="is-suspended" v-if="user.isSuspended">%fa:exclamation-triangle% %i18n:@is-suspended%</div>
|
<div class="is-suspended" v-if="user.isSuspended">%fa:exclamation-triangle% %i18n:@is-suspended%</div>
|
||||||
<div class="is-remote" v-if="user.host != null">%fa:exclamation-triangle% %i18n:common.is-remote-user%<a :href="user.url || user.uri" target="_blank">%i18n:common.view-on-remote%</a></div>
|
<div class="is-remote" v-if="user.host">%fa:exclamation-triangle% %i18n:common.is-remote-user%<a :href="user.url || user.uri" target="_blank">%i18n:common.view-on-remote%</a></div>
|
||||||
<main>
|
<main>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<x-header :user="user"/>
|
<x-header :user="user"/>
|
||||||
|
@ -12,14 +12,15 @@
|
||||||
<div class="side">
|
<div class="side">
|
||||||
<div class="instance" v-if="!$store.getters.isSignedIn"><mk-instance/></div>
|
<div class="instance" v-if="!$store.getters.isSignedIn"><mk-instance/></div>
|
||||||
<x-profile :user="user"/>
|
<x-profile :user="user"/>
|
||||||
<x-twitter :user="user" v-if="user.host === null && user.twitter"/>
|
<x-twitter :user="user" v-if="!user.host && user.twitter"/>
|
||||||
|
<x-github :user="user" v-if="!user.host && user.github"/>
|
||||||
<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>
|
<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>
|
||||||
<mk-activity :user="user"/>
|
<mk-activity :user="user"/>
|
||||||
<x-photos :user="user"/>
|
<x-photos :user="user"/>
|
||||||
<x-friends :user="user"/>
|
<x-friends :user="user"/>
|
||||||
<x-followers-you-know v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>
|
<x-followers-you-know v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>
|
||||||
<div class="nav"><mk-nav/></div>
|
<div class="nav"><mk-nav/></div>
|
||||||
<p v-if="user.host === null">%i18n:@last-used-at%: <b><mk-time :time="user.lastUsedAt"/></b></p>
|
<p v-if="!user.host">%i18n:@last-used-at%: <b><mk-time :time="user.lastUsedAt"/></b></p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,6 +38,7 @@ import XPhotos from './user.photos.vue';
|
||||||
import XFollowersYouKnow from './user.followers-you-know.vue';
|
import XFollowersYouKnow from './user.followers-you-know.vue';
|
||||||
import XFriends from './user.friends.vue';
|
import XFriends from './user.friends.vue';
|
||||||
import XTwitter from './user.twitter.vue';
|
import XTwitter from './user.twitter.vue';
|
||||||
|
import XGithub from './user.github.vue'; // ?MEM: Don't fix the intentional typo. (XGitHub -> `<x-git-hub>`)
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
|
@ -46,7 +48,8 @@ export default Vue.extend({
|
||||||
XPhotos,
|
XPhotos,
|
||||||
XFollowersYouKnow,
|
XFollowersYouKnow,
|
||||||
XFriends,
|
XFriends,
|
||||||
XTwitter
|
XTwitter,
|
||||||
|
XGithub // ?MEM: Don't fix the intentional typo. (see L41)
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -125,6 +125,19 @@
|
||||||
</section>
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
|
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%fa:B github% %i18n:@github%</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<p class="account" v-if="$store.state.i.github"><a :href="`https://github.com/${$store.state.i.github.login}`" target="_blank">@{{ $store.state.i.github.login }}</a></p>
|
||||||
|
<p>
|
||||||
|
<a :href="`${apiUrl}/connect/github`" target="_blank">{{ $store.state.i.github ? '%i18n:@github-reconnect%' : '%i18n:@github-connect%' }}</a>
|
||||||
|
<span v-if="$store.state.i.github"> or </span>
|
||||||
|
<a :href="`${apiUrl}/disconnect/github`" target="_blank" v-if="$store.state.i.github">%i18n:@github-disconnect%</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
|
||||||
<mk-api-settings />
|
<mk-api-settings />
|
||||||
|
|
||||||
<ui-card>
|
<ui-card>
|
||||||
|
|
|
@ -74,6 +74,10 @@ export type Source = {
|
||||||
consumer_key: string;
|
consumer_key: string;
|
||||||
consumer_secret: string;
|
consumer_secret: string;
|
||||||
};
|
};
|
||||||
|
github?: {
|
||||||
|
client_id: string;
|
||||||
|
client_secret: string;
|
||||||
|
};
|
||||||
github_bot?: {
|
github_bot?: {
|
||||||
hook_secret: string;
|
hook_secret: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
|
|
@ -82,6 +82,11 @@ export interface ILocalUser extends IUserBase {
|
||||||
userId: string;
|
userId: string;
|
||||||
screenName: string;
|
screenName: string;
|
||||||
};
|
};
|
||||||
|
github: {
|
||||||
|
accessToken: string;
|
||||||
|
id: string;
|
||||||
|
login: string;
|
||||||
|
};
|
||||||
line: {
|
line: {
|
||||||
userId: string;
|
userId: string;
|
||||||
};
|
};
|
||||||
|
@ -280,6 +285,9 @@ export const pack = (
|
||||||
delete _user.twitter.accessToken;
|
delete _user.twitter.accessToken;
|
||||||
delete _user.twitter.accessTokenSecret;
|
delete _user.twitter.accessTokenSecret;
|
||||||
}
|
}
|
||||||
|
if (_user.github) {
|
||||||
|
delete _user.github.accessToken;
|
||||||
|
}
|
||||||
delete _user.line;
|
delete _user.line;
|
||||||
|
|
||||||
// Visible via only the official client
|
// Visible via only the official client
|
||||||
|
|
|
@ -73,6 +73,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
|
||||||
recaptcha: config.recaptcha ? true : false,
|
recaptcha: config.recaptcha ? true : false,
|
||||||
objectStorage: config.drive && config.drive.storage === 'minio',
|
objectStorage: config.drive && config.drive.storage === 'minio',
|
||||||
twitter: config.twitter ? true : false,
|
twitter: config.twitter ? true : false,
|
||||||
|
github: config.github ? true : false,
|
||||||
serviceWorker: config.sw ? true : false,
|
serviceWorker: config.sw ? true : false,
|
||||||
userRecommendation: config.user_recommendation ? config.user_recommendation : {}
|
userRecommendation: config.user_recommendation ? config.user_recommendation : {}
|
||||||
} : undefined
|
} : undefined
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import * as EventEmitter from 'events';
|
import * as EventEmitter from 'events';
|
||||||
|
import * as Koa from 'koa';
|
||||||
import * as Router from 'koa-router';
|
import * as Router from 'koa-router';
|
||||||
import * as request from 'request';
|
import * as request from 'request';
|
||||||
const crypto = require('crypto');
|
import { OAuth2 } from 'oauth';
|
||||||
|
import User, { IUser, pack, ILocalUser } from '../../../models/user';
|
||||||
import User, { IUser } from '../../../models/user';
|
|
||||||
import createNote from '../../../services/note/create';
|
import createNote from '../../../services/note/create';
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
|
import { publishMainStream } from '../../../stream';
|
||||||
|
import redis from '../../../db/redis';
|
||||||
|
import uuid = require('uuid');
|
||||||
|
import signin from '../common/signin';
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
const handler = new EventEmitter();
|
const handler = new EventEmitter();
|
||||||
|
|
||||||
|
@ -28,10 +33,264 @@ const post = async (text: string, home = true) => {
|
||||||
createNote(bot, { text, visibility: home ? 'home' : 'public' });
|
createNote(bot, { text, visibility: home ? 'home' : 'public' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getUserToken(ctx: Koa.Context) {
|
||||||
|
return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareOrigin(ctx: Koa.Context) {
|
||||||
|
function normalizeUrl(url: string) {
|
||||||
|
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const referer = ctx.headers['referer'];
|
||||||
|
|
||||||
|
return (normalizeUrl(referer) == normalizeUrl(config.url));
|
||||||
|
}
|
||||||
|
|
||||||
// Init router
|
// Init router
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
if (config.github_bot != null) {
|
router.get('/disconnect/github', async ctx => {
|
||||||
|
if (!compareOrigin(ctx)) {
|
||||||
|
ctx.throw(400, 'invalid origin');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userToken = getUserToken(ctx);
|
||||||
|
if (!userToken) {
|
||||||
|
ctx.throw(400, 'signin required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findOneAndUpdate({
|
||||||
|
host: null,
|
||||||
|
'token': userToken
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
'github': null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.body = `GitHubの連携を解除しました :v:`;
|
||||||
|
|
||||||
|
// Publish i updated event
|
||||||
|
publishMainStream(user._id, 'meUpdated', await pack(user, user, {
|
||||||
|
detail: true,
|
||||||
|
includeSecrets: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!config.github || !redis) {
|
||||||
|
router.get('/connect/github', ctx => {
|
||||||
|
ctx.body = '現在GitHubへ接続できません (このインスタンスではGitHubはサポートされていません)';
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/signin/github', ctx => {
|
||||||
|
ctx.body = '現在GitHubへ接続できません (このインスタンスではGitHubはサポートされていません)';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const oauth2 = new OAuth2(
|
||||||
|
config.github.client_id,
|
||||||
|
config.github.client_secret,
|
||||||
|
'https://github.com/',
|
||||||
|
'login/oauth/authorize',
|
||||||
|
'login/oauth/access_token');
|
||||||
|
|
||||||
|
router.get('/connect/github', async ctx => {
|
||||||
|
if (!compareOrigin(ctx)) {
|
||||||
|
ctx.throw(400, 'invalid origin');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userToken = getUserToken(ctx);
|
||||||
|
if (!userToken) {
|
||||||
|
ctx.throw(400, 'signin required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
redirect_uri: `${config.url}:8089/api/gh/cb`,
|
||||||
|
scope: ['read:user'],
|
||||||
|
state: uuid()
|
||||||
|
};
|
||||||
|
|
||||||
|
redis.set(userToken, JSON.stringify(params));
|
||||||
|
ctx.redirect(oauth2.getAuthorizeUrl(params));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/signin/github', async ctx => {
|
||||||
|
const sessid = uuid();
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
redirect_uri: `${config.url}:8089/api/gh/cb`,
|
||||||
|
scope: ['read:user'],
|
||||||
|
state: uuid()
|
||||||
|
};
|
||||||
|
|
||||||
|
const expires = 1000 * 60 * 60; // 1h
|
||||||
|
ctx.cookies.set('signin_with_github_session_id', sessid, {
|
||||||
|
path: '/',
|
||||||
|
domain: config.host,
|
||||||
|
secure: config.url.startsWith('https'),
|
||||||
|
httpOnly: true,
|
||||||
|
expires: new Date(Date.now() + expires),
|
||||||
|
maxAge: expires
|
||||||
|
});
|
||||||
|
|
||||||
|
redis.set(sessid, JSON.stringify(params));
|
||||||
|
ctx.redirect(oauth2.getAuthorizeUrl(params));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/gh/cb', async ctx => {
|
||||||
|
const userToken = getUserToken(ctx);
|
||||||
|
|
||||||
|
if (!userToken) {
|
||||||
|
const sessid = ctx.cookies.get('signin_with_github_session_id');
|
||||||
|
|
||||||
|
if (!sessid) {
|
||||||
|
ctx.throw(400, 'invalid session');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = ctx.query.code;
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
ctx.throw(400, 'invalid session');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
|
||||||
|
redis.get(sessid, async (_, state) => {
|
||||||
|
res(JSON.parse(state));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ctx.query.state !== state) {
|
||||||
|
ctx.throw(400, 'invalid session');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { accessToken } = await new Promise<any>((res, rej) =>
|
||||||
|
oauth2.getOAuthAccessToken(
|
||||||
|
code,
|
||||||
|
{ redirect_uri },
|
||||||
|
(err, accessToken, refresh, result) => {
|
||||||
|
if (err)
|
||||||
|
rej(err);
|
||||||
|
else if (result.error)
|
||||||
|
rej(result.error);
|
||||||
|
else
|
||||||
|
res({ accessToken });
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { login, id } = await new Promise<any>((res, rej) =>
|
||||||
|
request({
|
||||||
|
url: 'https://api.github.com/user',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/vnd.github.v3+json',
|
||||||
|
'Authorization': `bearer ${accessToken}`,
|
||||||
|
'User-Agent': config.user_agent
|
||||||
|
}
|
||||||
|
}, (err, response, body) => {
|
||||||
|
if (err)
|
||||||
|
rej(err);
|
||||||
|
else
|
||||||
|
res(JSON.parse(body));
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!login || !id) {
|
||||||
|
ctx.throw(400, 'invalid session');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findOne({
|
||||||
|
host: null,
|
||||||
|
'github.id': id
|
||||||
|
}) as ILocalUser;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
signin(ctx, user, true);
|
||||||
|
} else {
|
||||||
|
const code = ctx.query.code;
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
ctx.throw(400, 'invalid session');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
|
||||||
|
redis.get(userToken, async (_, state) => {
|
||||||
|
res(JSON.parse(state));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ctx.query.state !== state) {
|
||||||
|
ctx.throw(400, 'invalid session');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { accessToken } = await new Promise<any>((res, rej) =>
|
||||||
|
oauth2.getOAuthAccessToken(
|
||||||
|
code,
|
||||||
|
{ redirect_uri },
|
||||||
|
(err, accessToken, refresh, result) => {
|
||||||
|
if (err)
|
||||||
|
rej(err);
|
||||||
|
else if (result.error)
|
||||||
|
rej(result.error);
|
||||||
|
else
|
||||||
|
res({ accessToken });
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { login, id } = await new Promise<any>((res, rej) =>
|
||||||
|
request({
|
||||||
|
url: 'https://api.github.com/user',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/vnd.github.v3+json',
|
||||||
|
'Authorization': `bearer ${accessToken}`,
|
||||||
|
'User-Agent': config.user_agent
|
||||||
|
}
|
||||||
|
}, (err, response, body) => {
|
||||||
|
if (err)
|
||||||
|
rej(err);
|
||||||
|
else
|
||||||
|
res(JSON.parse(body));
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!login || !id) {
|
||||||
|
ctx.throw(400, 'invalid session');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findOneAndUpdate({
|
||||||
|
host: null,
|
||||||
|
token: userToken
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
github: {
|
||||||
|
accessToken,
|
||||||
|
id,
|
||||||
|
login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`;
|
||||||
|
|
||||||
|
// Publish i updated event
|
||||||
|
publishMainStream(user._id, 'meUpdated', await pack(user, user, {
|
||||||
|
detail: true,
|
||||||
|
includeSecrets: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.github_bot) {
|
||||||
const secret = config.github_bot.hook_secret;
|
const secret = config.github_bot.hook_secret;
|
||||||
|
|
||||||
router.post('/hooks/github', ctx => {
|
router.post('/hooks/github', ctx => {
|
||||||
|
|
Loading…
Reference in a new issue