Skip to content

Commit

Permalink
feat: pr comment and settings (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
christophehurpeau committed May 21, 2020
1 parent 76ded6c commit e631a42
Show file tree
Hide file tree
Showing 51 changed files with 3,144 additions and 944 deletions.
988 changes: 773 additions & 215 deletions dist/index-node10-dev.cjs.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index-node10-dev.cjs.js.map

Large diffs are not rendered by default.

988 changes: 773 additions & 215 deletions dist/index-node10.cjs.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index-node10.cjs.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -68,6 +68,7 @@
"body-parser": "1.19.0",
"cookie-parser": "1.4.5",
"dotenv": "8.2.0",
"issue-parser": "^6.0.0",
"jsonwebtoken": "8.5.1",
"liwi": "1.1.0",
"liwi-mongo": "4.2.1",
Expand All @@ -89,6 +90,7 @@
"@pob/root": "1.4.0",
"@types/cookie-parser": "1.4.2",
"@types/express": "4.17.0",
"@types/issue-parser": "^3.0.0",
"@types/jest": "24.0.18",
"@types/jsonwebtoken": "8.3.2",
"@types/nock": "11.1.0",
Expand Down
4 changes: 2 additions & 2 deletions scripts/test-slack.js
Expand Up @@ -3,13 +3,13 @@
require('dotenv').config();
const { WebClient } = require('@slack/web-api');

if (!process.env.ORNIKAR_SLACK_TOKEN) {
if (!process.env.SLACK_TOKEN) {
console.error('Missing slack token');
process.exit(1);
}

(async () => {
const slackClient = new WebClient(process.env.ORNIKAR_SLACK_TOKEN);
const slackClient = new WebClient(process.env.SLACK_TOKEN);
const slackUsers = new Map();
await slackClient.paginate('users.list', {}, (page) => {
page.members.forEach((member) => {
Expand Down
167 changes: 167 additions & 0 deletions src/app/auth.tsx
@@ -0,0 +1,167 @@
import { promisify } from 'util';
import { Octokit } from 'probot';
import type { Router, Request, Response } from 'express';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { sign, verify } from 'jsonwebtoken';
import * as githubAuth from '../auth/github';
import Layout from '../views/Layout';

if (!process.env.AUTH_SECRET_KEY) {
throw new Error('Missing env variable: AUTH_SECRET_KEY');
}

const AUTH_SECRET_KEY: string = process.env.AUTH_SECRET_KEY;

const signPromisified: any = promisify(sign);
const verifyPromisified: any = promisify(verify);

const secure =
!!process.env.SECURE_COOKIE && process.env.SECURE_COOKIE !== 'false';

const createRedirectUri = (req: Request, strategy: string): string => {
const host = `http${secure ? 's' : ''}://${req.hostname}${
req.hostname === 'localhost' ? `:${process.env.PORT}` : ''
}`;
return `${host}/app/${strategy}/login-response`;
};

interface AuthInfo {
id: number;
login: string;
accessToken: string;
time: number;
}

const readAuthCookie = (
req: Request,
strategy: string,
): undefined | Promise<undefined | AuthInfo> => {
const cookie = req.cookies[`auth_${strategy}`];
if (!cookie) return;

return verifyPromisified(cookie, AUTH_SECRET_KEY, {
algorithm: 'HS512',
audience: req.headers['user-agent'],
});
};

const getAuthInfoFromCookie = async (
req: Request,
res: Response,
): Promise<undefined | AuthInfo> => {
const strategy = 'gh'; // req.params.strategy
const authInfo = await readAuthCookie(req, strategy);

if (authInfo?.id) {
return authInfo;
}

res.clearCookie(`auth_${strategy}`);
return undefined;
};

export const getUser = async (
req: Request,
res: Response,
): Promise<{ authInfo: AuthInfo; api: Octokit } | null> => {
const authInfo = await getAuthInfoFromCookie(req, res);
if (!authInfo) {
res.redirect('/app/gh/login');
return null;
}

return {
authInfo,
api: new Octokit({ auth: `token ${authInfo.accessToken}` }),
};
};

export default function auth(router: Router): void {
router.get('/gh/login', async (req: Request, res: Response) => {
if (await getAuthInfoFromCookie(req, res)) {
return res.redirect('/app/gh');
}

// const state = await randomHex(8);
// res.cookie(`auth_${strategy}_${state}`, strategy, {
// maxAge: 10 * 60 * 1000,
// httpOnly: true,
// secure,
// });

const redirectUri = githubAuth.oauth2.authorizationCode.authorizeURL({
redirect_uri: createRedirectUri(req, 'gh'),
scope: 'read:user,repo',
// state,
// grant_type: options.grantType,
// access_type: options.accessType,
// login_hint: req.query.loginHint,
// include_granted_scopes: options.includeGrantedScopes,
});

// console.log(redirectUri);

res.redirect(redirectUri);
});

router.get('/gh/login-response', async (req, res) => {
if (req.query.error) {
res.send(req.query.error_description);
return;
}

const strategy = 'gh';
const code: string = req.query.code as string;
// const state = req.query.state;
// const cookieName = `auth_${strategy}_${state}`;
// const cookie = req.cookies && req.cookies[cookieName];
// if (!cookie) {
// // res.redirect(`/${strategy}/login`);
// res.send(
// '<html><body>No cookie for this state. <a href="/app/gh/login">Retry ?</a></body></html>',
// );
// return;
// }
// res.clearCookie(cookieName);

const result = await githubAuth.oauth2.authorizationCode.getToken({
code,
redirect_uri: createRedirectUri(req, strategy),
});

if (!result) {
res.send(
renderToStaticMarkup(
<Layout>
<div>
Could not get access token. <a href="/app/gh/login">Retry ?</a>
</div>
</Layout>,
),
);
return;
}

const accessToken = result.access_token;
const octokit = new Octokit({ auth: `token ${accessToken}` });
const user = await octokit.users.getAuthenticated({});
const id = user.data.id;
const login = user.data.login;

const authInfo: AuthInfo = { id, login, accessToken, time: Date.now() };
console.log({ authInfo });
const token = await signPromisified(authInfo, AUTH_SECRET_KEY, {
algorithm: 'HS512',
audience: req.headers['user-agent'],
expiresIn: '10 days',
});

res.cookie(`auth_${strategy}`, token, {
httpOnly: true,
secure,
});

res.redirect('/app/gh');
});
}
45 changes: 45 additions & 0 deletions src/app/home.tsx
@@ -0,0 +1,45 @@
import type { Router } from 'express';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { GitHubAPI } from 'probot/lib/github';
import type { MongoStores } from '../mongo';
import Layout from '../views/Layout';
import { getUser } from './auth';

export default function home(
router: Router,
api: GitHubAPI,
mongoStores: MongoStores,
): void {
router.get('/gh', async (req, res) => {
const user = await getUser(req, res);
if (!user) return;

const orgs = await user.api.orgs.listForAuthenticatedUser();

res.send(
renderToStaticMarkup(
<Layout>
<div>
<h1>{process.env.REVIEWFLOW_NAME}</h1>
<div style={{ display: 'flex' }}>
<div style={{ flexGrow: 1 }}>
<h4>Choose your account</h4>
<ul>
<li>
<a href="/app/gh/user">{user.authInfo.login}</a>
</li>
{orgs.data.map((org) => (
<li key={org.id}>
<a href={`/app/gh/org/${org.login}`}>{org.login}</a>
</li>
))}
</ul>
</div>
</div>
</div>
</Layout>,
),
);
});
}

0 comments on commit e631a42

Please sign in to comment.