Start working on GitHub callback

This commit is contained in:
Dominik Korsa 2021-02-12 00:16:54 +01:00
parent 8083bcef8e
commit 576f82f029
No known key found for this signature in database
GPG key ID: C6697282ADE724F2
5 changed files with 134 additions and 1 deletions

View file

@ -0,0 +1,22 @@
import gql from 'graphql-tag';
export const getViewerQuery = gql`query GetViewer {
viewer {
login
name
avatarUrl
id
}
}
`;
export interface Viewer {
login: string;
name: string | null;
avatarUrl: string;
id: string;
}
export interface GetViewerQueryResult {
viewer: Viewer
}

View file

@ -2,13 +2,23 @@ import { GraphQLClient } from 'graphql-request';
import { requireEnv } from '../../utils';
import type { GetUserQueryResult, User } from './queries/get-user';
import { getUserQuery } from './queries/get-user';
import type { GetViewerQueryResult, Viewer } from './queries/get-viewer';
import { getViewerQuery } from './queries/get-viewer';
const client = new GraphQLClient('https://api.github.com/graphql');
client.setHeader('Authorization', `bearer ${requireEnv('GITHUB_API_TOKEN')}`);
export async function getUser(login: string): Promise<User> {
const { user } = await client.request<GetUserQueryResult>(getUserQuery, {
login,
}, {
Authorization: `bearer ${requireEnv('GITHUB_API_TOKEN')}`,
});
return user;
}
export async function getViewer(accessToken: string, tokenType: string): Promise<Viewer> {
const { viewer } = await client.request<GetViewerQueryResult>(getViewerQuery, {}, {
Authorization: `${tokenType} ${accessToken}`,
});
return viewer;
}

View file

@ -0,0 +1,96 @@
import got from 'got';
import urlJoin from 'url-join';
import Developer from '../../../database/entities/developer';
import User from '../../../database/entities/user';
import { ParamError } from '../../../errors';
import { getViewer } from '../../../graphql/github/sdk';
import type SessionData from '../../../session-data';
import type { GitHubAuthorization, MyFastifyInstance } from '../../../types';
import {
getSessionData, isObject, requireEnv, validateOptionalParam, validateParam,
} from '../../../utils';
function getAuthorization(sessionData: SessionData, state?: string): GitHubAuthorization | null {
if (!state) return null;
return sessionData.gitHubAuthorizations.get(state) ?? null;
}
export default function registerGitHubCallback(server: MyFastifyInstance) {
server.get('/developer/github-callback', async (
request,
reply,
) => {
const sessionData = getSessionData(request.session);
if (!isObject(request.query)) {
throw server.httpErrors.badRequest('Request query is not an object');
}
try {
validateOptionalParam('error', request.query.error);
validateOptionalParam('state', request.query.state);
} catch (error) {
server.log.error(error);
if (error instanceof ParamError) {
throw server.httpErrors.badRequest(error.message);
}
throw server.httpErrors.internalServerError();
}
const authorization = getAuthorization(sessionData, request.query.state);
if (request.query.error) {
if (authorization && request.query.state) sessionData.gitHubAuthorizations.delete(request.query.state);
if (request.query.error === 'access_denied') {
await reply.redirect(urlJoin(
'/developer/',
authorization?.returnTo ?? '/',
));
}
throw server.httpErrors.internalServerError(`Got error response: "${request.query.error}"`);
}
if (!request.query.state) throw server.httpErrors.badRequest('Missing state param');
if (!authorization) throw server.httpErrors.badRequest('Authorization not found');
try {
validateParam('code', request.query.code);
} catch (error) {
server.log.error(error);
if (error instanceof ParamError) {
throw server.httpErrors.badRequest(error.message);
}
throw server.httpErrors.internalServerError();
}
try {
const response = await got.post<{
token_type: string;
access_token: string;
scope: string;
}>('https://github.com/login/oauth/access_token', {
searchParams: {
client_id: requireEnv('GITHUB_CLIENT_ID'),
client_secret: requireEnv('GITHUB_CLIENT_SECRET'),
code: request.query.code,
},
responseType: 'json',
});
const viewer = await getViewer(response.body.access_token, response.body.token_type);
console.log(viewer);
let developer = await Developer.findOne({
where: {
gitHubId: viewer.id,
},
});
if (!developer) {
developer = new Developer();
developer.gitHubId = viewer.id;
}
developer.gitHubLogin = viewer.login;
await developer.save();
console.log(developer);
} catch (error) {
server.log.error(error);
throw server.httpErrors.internalServerError();
}
// TODO: Store login info in session
// TODO: Redirect to returnTo
await reply.send('DONE');
});
}

View file

@ -33,6 +33,9 @@ export default function registerGitHubSignIn(server: MyFastifyInstance): void {
authorizeUrl.searchParams.set('response_type', 'code');
authorizeUrl.searchParams.set('redirect_uri', requireEnv('GITHUB_REDIRECT_URL'));
authorizeUrl.searchParams.set('state', state);
sessionData.gitHubAuthorizations.set(state, {
returnTo: request.query.return_to,
});
await reply.redirect(authorizeUrl.toString());
});
}

View file

@ -4,6 +4,7 @@ import type { ApolloContext, MyFastifyInstance } from '../../types';
import { getSessionData } from '../../utils';
import registerAllow from './allow';
import registerDeny from './deny';
import registerGitHubCallback from './developer/github-callback';
import registerGitHubSignIn from './developer/github-sign-in';
import CreateUserResolver from './resolvers/authenticate-prompt/create-user-resolver';
import LoginResolver from './resolvers/authenticate-prompt/login-resolver';
@ -38,4 +39,5 @@ export default async function registerWebsiteApi(server: MyFastifyInstance): Pro
registerAllow(server);
registerGitHubSignIn(server);
registerGitHubCallback(server);
}