diff --git a/package.json b/package.json index 63d0469..8fc5a00 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,11 @@ "@types/graphql": "^0.10.2", "@types/jest": "^20.0.7", "@types/jsonwebtoken": "^7.2.3", + "@types/kcors": "^2.2.1", "@types/koa": "^2.0.39", + "@types/koa-bodyparser": "^3.0.23", + "@types/koa-logger": "^2.0.2", + "@types/koa-router": "^7.0.22", "@types/lodash": "^4.14.73", "@types/mongodb": "^2.2.10", "@types/mongoose": "^4.7.20", @@ -68,7 +72,8 @@ ], "coveragePathIgnorePatterns": [ "/__tests__/", - "\\.(test|spec)\\.ts$" + "\\.(test|spec)\\.ts$", + "\\.d\\.ts$" ], "moduleFileExtensions": [ "ts", diff --git a/src/__mocks__/axios.ts b/src/__mocks__/axios.ts new file mode 100644 index 0000000..8f59e83 --- /dev/null +++ b/src/__mocks__/axios.ts @@ -0,0 +1,60 @@ +import { omit } from 'lodash'; +import { AxiosInstance, AxiosResponse } from 'axios'; + +interface AxiosMockedInstance extends AxiosInstance { + _setupMock: (params: [MockParams]) => void; + _clean: () => void; +} + +type HTTPMethod = 'post' | 'delete' | 'put' | 'get' | 'options'; + +interface MockParams extends AxiosResponse { + method: HTTPMethod; + url: string; +} + +interface MockStructure { + delete: { [key: string]: AxiosResponse }; + get: { [key: string]: AxiosResponse }; + post: { [key: string]: AxiosResponse }; + put: { [key: string]: AxiosResponse }; + options: { [key: string]: AxiosResponse }; +} + +const FROZEN_DATA: MockStructure = { + delete: {}, + get: {}, + post: {}, + put: {}, + options: {} +}; + +const DEFAULT_RESPONSE = { + status: 200 +}; + +let _cached = FROZEN_DATA; + +const clean = () => { + _cached = Object.create(FROZEN_DATA); +}; + +const setupMock = (params: [MockParams]) => { + params.forEach(param => { + _cached[param.method][param.url] = omit(param, ['method', 'url']); + }); +}; + +const generateMethod = (method: HTTPMethod) => ( + (url: string): Promise => Promise.resolve(_cached[method][url] || DEFAULT_RESPONSE) +); + +const axios: AxiosMockedInstance = jest.genMockFromModule('axios'); +axios._setupMock = setupMock; +axios._clean = clean; +axios.post = generateMethod('post'); +axios.get = generateMethod('get'); +axios.delete = generateMethod('delete'); +axios.put = generateMethod('put'); + +export = axios; diff --git a/src/__mocks__/github.ts b/src/__mocks__/github.ts new file mode 100644 index 0000000..300cbc7 --- /dev/null +++ b/src/__mocks__/github.ts @@ -0,0 +1,53 @@ +import { set } from 'lodash'; + +type Entity = 'users' | 'repo'; + +interface MockParams { + data: any; + entity: Entity; + method: string; + resolve: boolean; +} + +interface MockResponse { + data: any; +} + +interface Handlers { + [key: string]: { + [key: string]: () => Promise + }; +} + +let cached: Handlers = {}; + +class MockedGithub { + auth: any; + + constructor () { + const that = this; + + Object.keys(cached).forEach(key => { + set(that, key, cached[key]); + }); + } + + static _setupMock (params: [MockParams]) { + params.forEach(param => { + set(cached, `${param.entity}.${param.method}`, () => param.resolve + ? Promise.resolve({ data: param.data }) + : Promise.reject({ data: param.data }) + ); + }); + } + + static _clean () { + cached = Object.create(null); + } + + authenticate (auth: any) { + this.auth = auth; + } +} + +export = MockedGithub; diff --git a/src/__tests__/user/__snapshots__/resolvers.ts.snap b/src/__tests__/user/__snapshots__/resolvers.ts.snap deleted file mode 100644 index c31e3ee..0000000 --- a/src/__tests__/user/__snapshots__/resolvers.ts.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`user resolvers mutation user can register with email and password 1`] = ` -Object { - "createUser": Object { - "email": "a@b.ru", - "isAdmin": false, - }, -} -`; - -exports[`user resolvers mutation user cant register with invalid data 1`] = ` -Array [ - [GraphQLError: User validation failed: email: Email address \`a@\` is incorrect], -] -`; diff --git a/src/declarations.d.ts b/src/declarations.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/helpers/github-create-client.ts b/src/helpers/github-create-client.ts new file mode 100644 index 0000000..1c71269 --- /dev/null +++ b/src/helpers/github-create-client.ts @@ -0,0 +1,8 @@ +import * as Github from 'github'; +import settings from 'settings'; + +export default (token: string) => { + const client: Github = new Github({ debug: !settings.isProduction, headers: { 'user-agent': '@flyin/git-chat-server' } }); + client.authenticate({ token, type: 'oauth' }); + return client; +}; diff --git a/src/helpers/github-get-primary-email.ts b/src/helpers/github-get-primary-email.ts new file mode 100644 index 0000000..4fedc78 --- /dev/null +++ b/src/helpers/github-get-primary-email.ts @@ -0,0 +1,20 @@ +import { find } from 'lodash'; +import * as Github from 'github'; +import { GithubResponse } from './interfaces'; + +type EmailResponse = { + email: string; + primary: boolean; + verified: boolean; +}; + +export default async (client: Github) => { + const emails: GithubResponse<[EmailResponse]> = await client.users.getEmails({ page: 1, per_page: 100 }); + const email = find(emails.data, { primary: true, verified: true }); + + if (!email) { + throw new Error('primary_email_not_found'); + } + + return email; +}; diff --git a/src/helpers/github-get-token.ts b/src/helpers/github-get-token.ts new file mode 100644 index 0000000..23d0efe --- /dev/null +++ b/src/helpers/github-get-token.ts @@ -0,0 +1,29 @@ +import { get } from 'lodash'; +import settings from 'settings'; +import axios from 'axios'; + +type GithubAccessToken = { + access_token?: string; + scope?: string; + token_type?: string; + error_description?: string; +}; + +export default async (code: string) => { + const tokenResponse = await axios.post( + 'https://github.com/login/oauth/access_token', + + { + client_id: settings.github.clientId, + client_secret: settings.github.clientSecret, + code + }, + { + headers: { + 'Accept': 'application/json' + } + } + ); + + return get(tokenResponse, 'data', {}); +}; diff --git a/src/helpers/github-get-user.ts b/src/helpers/github-get-user.ts new file mode 100644 index 0000000..9040bd7 --- /dev/null +++ b/src/helpers/github-get-user.ts @@ -0,0 +1,13 @@ +import * as Github from 'github'; +import { GithubResponse } from './interfaces'; + +type UserResponse = { + avatar_url?: string; + id: number; + name: string; +}; + +export default async (client: Github) => { + const user: GithubResponse = await client.users.get({}); + return user.data; +}; diff --git a/src/helpers/index.ts b/src/helpers/index.ts new file mode 100644 index 0000000..3d6a0d4 --- /dev/null +++ b/src/helpers/index.ts @@ -0,0 +1,5 @@ +export * from './interfaces'; +export { default as githubCreateClient } from './github-create-client'; +export { default as githubGetPrimaryEmail } from './github-get-primary-email'; +export { default as githubGetToken } from './github-get-token'; +export { default as githubGetUser } from './github-get-user'; diff --git a/src/helpers/interfaces.ts b/src/helpers/interfaces.ts new file mode 100644 index 0000000..8730c7c --- /dev/null +++ b/src/helpers/interfaces.ts @@ -0,0 +1,3 @@ +export interface GithubResponse { + data: T; +} diff --git a/src/models/channel.ts b/src/models/channel.ts index 826eac7..7bb7391 100644 --- a/src/models/channel.ts +++ b/src/models/channel.ts @@ -17,4 +17,4 @@ const Channel = new Schema({ timestamps: true }); -mongoose.model('Channel', Channel); +export default mongoose.model('Channel', Channel); diff --git a/src/models/index.ts b/src/models/index.ts index a38ee0c..67fc0fc 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,3 +1,3 @@ -export * from './channel'; -export * from './message'; -export * from './user'; +export { default as Channel } from './channel'; +export { default as Message } from './message'; +export { default as User } from './user'; diff --git a/src/models/message.ts b/src/models/message.ts index 17f0894..a3e9546 100644 --- a/src/models/message.ts +++ b/src/models/message.ts @@ -2,7 +2,7 @@ import * as mongoose from 'mongoose'; const Schema = mongoose.Schema; -const schema = new Schema({ +const Message = new Schema({ channel: { _id: { ref: 'Channel', @@ -17,4 +17,4 @@ const schema = new Schema({ timestamps: true }); -mongoose.model('Message', schema); +export default mongoose.model('Message', Message); diff --git a/src/models/user.ts b/src/models/user.ts index 237f1ae..678417a 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -5,7 +5,6 @@ import * as bcrypt from 'bcrypt'; const SALT_FACTOR = 5; export interface UserModel extends mongoose.Document { - avatar: string; email: string; github?: GithubModel; isAdmin: boolean; @@ -14,6 +13,7 @@ export interface UserModel extends mongoose.Document { } export interface GithubModel extends mongoose.Document { + avatar: string; accessToken: string; githubId: number; name: string; @@ -22,16 +22,15 @@ export interface GithubModel extends mongoose.Document { } const Github = new mongoose.Schema({ + avatar: { default: null, type: String }, accessToken: { required: true, type: String }, - githubId: { required: true, type: Number }, + githubId: { required: true, type: Number, index: true }, name: { default: null, type: String }, refreshToken: { default: null, type: String }, scopes: { type: [String] } }); -const user = new mongoose.Schema({ - avatar: { default: null, type: String }, - +const User = new mongoose.Schema({ email: { required: true, trim: true, @@ -51,7 +50,7 @@ const user = new mongoose.Schema({ timestamps: true }); -user.pre('save', async function (this: UserModel, next) { +User.pre('save', async function (this: UserModel, next) { if (!this.isModified('password') && this.password) { return next(); } @@ -67,8 +66,8 @@ user.pre('save', async function (this: UserModel, next) { } }); -user.methods.passwordIsValid = async function (this: UserModel, password: string) { +User.methods.passwordIsValid = async function (this: UserModel, password: string) { return await bcrypt.compare(password, this.password); }; -mongoose.model('User', user); +export default mongoose.model('User', User); diff --git a/src/__tests__/chat/__snapshots__/resolvers.ts.snap b/src/resolvers/__tests__/chat/__snapshots__/resolvers.ts.snap similarity index 100% rename from src/__tests__/chat/__snapshots__/resolvers.ts.snap rename to src/resolvers/__tests__/chat/__snapshots__/resolvers.ts.snap diff --git a/src/__tests__/chat/resolvers.ts b/src/resolvers/__tests__/chat/resolvers.ts similarity index 100% rename from src/__tests__/chat/resolvers.ts rename to src/resolvers/__tests__/chat/resolvers.ts diff --git a/src/resolvers/__tests__/user/__snapshots__/resolvers.ts.snap b/src/resolvers/__tests__/user/__snapshots__/resolvers.ts.snap new file mode 100644 index 0000000..bad3ba7 --- /dev/null +++ b/src/resolvers/__tests__/user/__snapshots__/resolvers.ts.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`user resolvers mutation user can attach github to his account 1`] = ` +Object { + "email": "flyin@example.com", + "github": Object { + "avatar": null, + "githubId": "1234", + "name": "flyin", + }, + "isAdmin": false, +} +`; + +exports[`user resolvers mutation user can register with email and password 1`] = ` +Object { + "createUser": Object { + "email": "a@b.ru", + "isAdmin": false, + }, +} +`; + +exports[`user resolvers mutation user can register with github token 1`] = ` +Object { + "email": "primary-verified@example.com", + "github": Object { + "avatar": null, + "githubId": "1234", + "name": "flyin", + }, + "isAdmin": false, +} +`; + +exports[`user resolvers mutation user cant register with invalid data 1`] = ` +Array [ + [GraphQLError: User validation failed: email: Email address \`a@\` is incorrect], +] +`; diff --git a/src/__tests__/user/resolvers.ts b/src/resolvers/__tests__/user/resolvers.ts similarity index 54% rename from src/__tests__/user/resolvers.ts rename to src/resolvers/__tests__/user/resolvers.ts index 8782c4e..a48bb3d 100644 --- a/src/__tests__/user/resolvers.ts +++ b/src/resolvers/__tests__/user/resolvers.ts @@ -1,27 +1,70 @@ import { get } from 'lodash'; import { graphql } from 'graphql'; - import { initTestCase } from 'utils/tests-utils'; import schema from 'schema'; import userResolvers from 'resolvers/user'; import { mongoose } from 'services/mongoose'; import { UserModel } from 'models/user'; +jest.mock('axios'); +jest.mock('github'); initTestCase(); +const axios = require('axios'); + +// TODO implement mock setup for response +const Github = require('github'); + const { createUser } = userResolvers.Mutation; const userModel = mongoose.model('User'); describe('user resolvers', () => { describe('mutation', () => { + beforeEach(() => { + axios._setupMock([ + { + url: 'https://github.com/login/oauth/access_token', + method: 'post', + data: { access_token: 'some-token', scope: 'user:email,admin:repo_hook' } + } + ]); + + Github._setupMock([ + { + data: { id: 1234, name: 'flyin', avatar: 'http://avatar.example.com' }, + entity: 'users', + method: 'get', + resolve: true + }, + + { + data: [ + { email: 'wrong@example.com', verified: false, primary: false }, + { email: 'verified@example.com', verified: true, primary: false }, + { email: 'primary-verified@example.com', verified: true, primary: true }, + { email: 'another-verified@example.com', verified: true, primary: false } + ], + + entity: 'users', + method: 'getEmails', + resolve: true + } + ]); + }); + + afterEach(() => { + axios._clean(); + Github._clean(); + }); + const userCreateQuery = ` - mutation user($email: String!, $password: String!) { - createUser(email: $email, password: $password) { - email - isAdmin - } + mutation user($email: String!, $password: String!) { + createUser(email: $email, password: $password) { + email + isAdmin } - `; + } + `; it('user can register with email and password', async () => { const user = { email: 'a@b.ru', password: '123' }; @@ -63,10 +106,63 @@ describe('user resolvers', () => { expect(get(returnData, 'user')).toMatchObject({ email: user.email }); }); - it('user can register with github token'); - it('user can reset their password'); - it('user can connect github to account'); + it('user can register with github token', async () => { + const query = ` + mutation token($code: String!) { + createTokenByCode(code: $code) { + token + user { + email + isAdmin + github { + githubId + name + avatar + } + } + } + } + `; + + const result = await graphql(schema, query, {}, {}, { code: 'some-github-code' }); + const returnData = get(result.data, 'createTokenByCode', {}); + + expect(result.errors).toBeUndefined(); + expect(returnData).toHaveProperty('token'); + expect(returnData).toHaveProperty('user'); + expect(get(returnData, 'user')).toMatchSnapshot(); + }); + + it('user can attach github to his account', async () => { + const user = await createUser(null, { email: 'flyin@example.com', password: '123123' }); + + const query = ` + mutation token($code: String!) { + createTokenByCode(code: $code) { + token + user { + email + isAdmin + github { + githubId + name + avatar + } + } + } + } + `; + + const result = await graphql(schema, query, {}, { currentUser: Promise.resolve(user) }, { code: 'some-github-code' }); + const returnData = get(result.data, 'createTokenByCode', {}); + expect(result.errors).toBeUndefined(); + expect(returnData).toHaveProperty('token'); + expect(returnData).toHaveProperty('user'); + expect(get(returnData, 'user')).toMatchSnapshot(); + }); + it('user can disconnect github from account'); + it('user can reset their password'); }); describe('query', () => { @@ -93,7 +189,7 @@ describe('user resolvers', () => { expect(get(result.data, 'getUser.email')).toBe(user.email); }); - it('admin user can access to any account', async () => { + it('admin user can access to any profile', async () => { let result; const user = await createUser(null, { email: 'aa@aa.ru', password: '123' }); diff --git a/src/resolvers/chat/create-channel.ts b/src/resolvers/chat/create-channel.ts index 1029093..92840aa 100644 --- a/src/resolvers/chat/create-channel.ts +++ b/src/resolvers/chat/create-channel.ts @@ -1,8 +1,6 @@ -import { mongoose } from 'services/mongoose'; +import { Channel } from 'models'; import pubsub from 'services/subscriptions'; -const Channel = mongoose.model('Channel'); - export default async (_: any, { name }: { name: string }) => { // const currentUser = await context.currentUser.get(); diff --git a/src/resolvers/chat/create-message.ts b/src/resolvers/chat/create-message.ts index 111472b..12c913f 100644 --- a/src/resolvers/chat/create-message.ts +++ b/src/resolvers/chat/create-message.ts @@ -1,9 +1,6 @@ -import { mongoose } from 'services/mongoose'; +import { Message, Channel } from 'models'; import pubsub from 'services/subscriptions'; -const Message = mongoose.model('Message'); -const Channel = mongoose.model('Channel'); - export default async (_: any, { channelId, text }: { channelId: string, text: string }) => { const channel = await Channel.findById(channelId); diff --git a/src/resolvers/chat/get-channel.ts b/src/resolvers/chat/get-channel.ts index 657bf85..942e0f5 100644 --- a/src/resolvers/chat/get-channel.ts +++ b/src/resolvers/chat/get-channel.ts @@ -1,6 +1,4 @@ -import * as mongoose from 'mongoose'; - -const Channel = mongoose.model('Channel'); +import { Channel } from 'models'; export default (_: any, { channelId }: { channelId: string }) => { return Channel.findById(channelId); diff --git a/src/resolvers/chat/get-channels.ts b/src/resolvers/chat/get-channels.ts index b5c09c9..b0d5873 100644 --- a/src/resolvers/chat/get-channels.ts +++ b/src/resolvers/chat/get-channels.ts @@ -1,6 +1,4 @@ -import * as mongoose from 'mongoose'; - -const Channel = mongoose.model('Channel'); +import { Channel } from 'models'; export default (_: any) => { return Channel.find({}).sort('name'); diff --git a/src/resolvers/chat/get-messages.ts b/src/resolvers/chat/get-messages.ts index c7137aa..11dcdc1 100644 --- a/src/resolvers/chat/get-messages.ts +++ b/src/resolvers/chat/get-messages.ts @@ -1,6 +1,4 @@ -import * as mongoose from 'mongoose'; - -const Message = mongoose.model('Message'); +import { Message } from 'models'; export default (_: any, { channelId }: { channelId: string }) => { return Message.find({ 'channel._id': channelId }).sort('-createdAt'); diff --git a/src/resolvers/user/create-token-by-code.ts b/src/resolvers/user/create-token-by-code.ts index 6bce91b..0f2cad3 100644 --- a/src/resolvers/user/create-token-by-code.ts +++ b/src/resolvers/user/create-token-by-code.ts @@ -1,64 +1,49 @@ -// import Axios from 'axios'; -// import { find, get } from 'lodash'; -// import { mongoose } from 'services/mongoose'; -// import * as GitHubApi from 'github'; -// import settings from 'settings'; +import signUser from './helpers/sign-user'; +import { User } from 'models'; +import { GraphQLContext } from 'services'; +import { githubCreateClient, githubGetPrimaryEmail, githubGetToken, githubGetUser } from 'helpers'; -// const User = mongoose.model('User'); - -export default async (_: any /*{ code }: { code: string }*/) => { - return Promise.reject({ err: new Error('Not implemented yet') }); - - /* - const response = await Axios({ - data: { - client_id: settings.github.clientId, - client_secret: settings.github.clientSecret, - code - }, - - headers: { - 'Accept': 'application/json' - }, +type Input = { + code: string +}; - method: 'post', - url: 'https://github.com/login/oauth/access_token' - }); +export default async (_: any, { code }: Input, context: GraphQLContext) => { + const oauth = await githubGetToken(code); - if (!response.data.access_token) { - throw new Error(get(response.data, 'error_description', 'oauth_receive_error')); + if (!oauth.access_token) { + throw new Error('access_token_error'); } - const client = createGithubClient(); - client.authenticate({ token: response.data.access_token, type: 'oauth' }); - const userResponse = await client.users.get({}); - let emailsResponse; + const client = githubCreateClient(oauth.access_token); + const githubUser = await githubGetUser(client); + const primaryEmail = await githubGetPrimaryEmail(client); + const currentUser = await context.currentUser; - if (!userResponse.data.email) { - emailsResponse = await client.users.getEmails({ page: 1 }); - } - - return User.findOneAndUpdate( - { email: userResponse.data.email || (find(emailsResponse.data, { primary: true, verified: true }) || {email: ''}).email }, + const user = await User.findOneAndUpdate( + { $or: [{ _id: currentUser ? currentUser._id : null }, { 'github.githubId': githubUser.id }, { email: primaryEmail.email }] }, { - avatar: userResponse.data.avatar_url, - - github: { - accessToken: response.data.access_token, - githubId: userResponse.data.id, - name: userResponse.data.name, - scopes: userResponse.data.scopes + $setOnInsert: { + email: primaryEmail.email + }, + + $set: { + github: { + avatar: githubUser.avatar_url, + accessToken: oauth.access_token, + githubId: githubUser.id, + name: githubUser.name, + scope: oauth.scope + } } }, { new: true, upsert: true } ); - */ -}; -/* -function createGithubClient() { - return new GitHubApi({ debug: !settings.isProduction, headers: { 'user-agent': '@flyin/git-chat-server' } }); -} -*/ + if (!user) { + throw new Error('user_find_and_update_error'); + } + + return signUser(user); +}; diff --git a/src/resolvers/user/create-token-by-password.ts b/src/resolvers/user/create-token-by-password.ts index 18e7238..0a5df83 100644 --- a/src/resolvers/user/create-token-by-password.ts +++ b/src/resolvers/user/create-token-by-password.ts @@ -1,8 +1,5 @@ -import * as mongoose from 'mongoose'; +import { User } from 'models'; import signUser from './helpers/sign-user'; -import { UserModel } from 'models/user'; - -const User = mongoose.model('User'); export default async (_: any, { email, password }: { email: string, password: string }) => { const user = await User.findOne({ email }); diff --git a/src/resolvers/user/create-user.ts b/src/resolvers/user/create-user.ts index 5f29460..69fb19a 100644 --- a/src/resolvers/user/create-user.ts +++ b/src/resolvers/user/create-user.ts @@ -1,7 +1,4 @@ -import * as mongoose from 'mongoose'; -import { UserModel } from 'models/user'; - -const User = mongoose.model('User'); +import { User } from 'models'; export default async (_: any, user: { email: string, password: string }) => { return User.create(user); diff --git a/src/resolvers/user/get-user.ts b/src/resolvers/user/get-user.ts index 7457446..68b0ef0 100644 --- a/src/resolvers/user/get-user.ts +++ b/src/resolvers/user/get-user.ts @@ -1,9 +1,7 @@ -import * as mongoose from 'mongoose'; -import { GraphQLContext } from 'services/koa'; +import { User } from 'models'; +import { GraphQLContext } from 'services'; import { UserModel } from 'models/user'; -const User = mongoose.model('User'); - type Input = { userId?: string }; @@ -17,6 +15,10 @@ export default async (_: any, { userId }: Input, context: GraphQLContext) => { return err; } + if (!currentUser) { + throw new Error('Auth required'); + } + if (currentUser.isAdmin && userId) { return User.findById(userId); } diff --git a/src/schemas/user.ts b/src/schemas/user.ts index 72a2501..c1f1663 100644 --- a/src/schemas/user.ts +++ b/src/schemas/user.ts @@ -1,6 +1,7 @@ export default [` type Github { _id: ID! + avatar: String githubId: ID! name: String, scopes: String @@ -8,7 +9,6 @@ type Github { type User { _id: ID! - avatar: String isAdmin: Boolean email: String! github: Github diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..9578609 --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1 @@ +export * from './interfaces'; diff --git a/src/services/interfaces.ts b/src/services/interfaces.ts new file mode 100644 index 0000000..d2a10a3 --- /dev/null +++ b/src/services/interfaces.ts @@ -0,0 +1,5 @@ +import { UserModel } from 'models/user'; + +export interface GraphQLContext { + currentUser: Promise; +} diff --git a/src/services/koa.ts b/src/services/koa.ts index ecd6c4d..8d9b9b9 100644 --- a/src/services/koa.ts +++ b/src/services/koa.ts @@ -1,23 +1,39 @@ +import { IncomingHttpHeaders } from 'http'; +import { get } from 'lodash'; +import * as jwt from 'jsonwebtoken'; import * as Koa from 'koa'; +import * as KoaRouter from 'koa-router'; +import * as koaCors from 'kcors'; +import * as koaBody from 'koa-bodyparser'; +import * as koaLogger from 'koa-logger'; +import { graphiqlKoa, graphqlKoa } from 'graphql-server-koa'; import settings from 'settings'; -import { UserModel } from 'models/user'; - -const KoaRouter = require('koa-router'); -const koaCors = require('kcors'); -const koaBody = require('koa-bodyparser'); -const koaLogger = require('koa-logger'); -const { graphqlKoa, graphiqlKoa } = require('graphql-server-koa'); -const schema = require('../schema'); +import schema from 'schema'; +import { User } from 'models'; const koa = new Koa(); const router = new KoaRouter(); -export interface GraphQLContext { - currentUser: Promise; -} +type JWTPayload = { + userId: string; +}; + +const getRequestUser = (headers: IncomingHttpHeaders, query: any) => { + const token: string = get(headers, 'authorization') || get(query, 'access_token'); + + try { + const payload: JWTPayload = jwt.verify(token, settings.secret) as JWTPayload; + return User.findOne({ _id: payload.userId }); + } catch (err) { + return Promise.resolve(null); + } +}; + +router.post('/', koaBody(), graphqlKoa(async (ctx: Koa.Context) => ({ + context: { + currentUser: getRequestUser(ctx.req.headers, ctx.query) + }, -router.post('/', koaBody(), graphqlKoa(async () => ({ - // context: { currentUser: getRequestUser({ headers: ctx.req.headers, query: ctx.query }) }, schema }))); diff --git a/yarn.lock b/yarn.lock index 1596bb7..49afc19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,15 +45,39 @@ dependencies: "@types/node" "*" +"@types/kcors@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/kcors/-/kcors-2.2.1.tgz#bc622accc6476bb8b6b9e21a084e733139ae9249" + dependencies: + "@types/koa" "*" + "@types/keygrip@*": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.1.tgz#ff540462d2fb4d0a88441ceaf27d287b01c3d878" +"@types/koa-bodyparser@^3.0.23": + version "3.0.23" + resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-3.0.23.tgz#0e719b708d463d1ea31a25971c485c2e4eb6ced4" + dependencies: + "@types/koa" "*" + "@types/koa-compose@*": version "3.2.2" resolved "https://registry.yarnpkg.com/@types/koa-compose/-/koa-compose-3.2.2.tgz#dc106e000bbf92a3ac900f756df47344887ee847" -"@types/koa@^2.0.39": +"@types/koa-logger@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/koa-logger/-/koa-logger-2.0.2.tgz#15538a07c5fbfdecdb9c4c7b59de0d2f8be37bf2" + dependencies: + "@types/koa" "*" + +"@types/koa-router@^7.0.22": + version "7.0.22" + resolved "https://registry.yarnpkg.com/@types/koa-router/-/koa-router-7.0.22.tgz#92b4b533f074036250892fe35bb6329ee9242def" + dependencies: + "@types/koa" "*" + +"@types/koa@*", "@types/koa@^2.0.39": version "2.0.39" resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.0.39.tgz#45ba1249d8849b9b0ff8c1d6d2f80b0838b89ffa" dependencies: