prepare for refresh tokens feature

master
Peter Babič 5 years ago
parent 69233bb7e1
commit 2732ade178
Signed by: peter.babic
GPG Key ID: 4BB075BC1884BA40
  1. 204
      package-lock.json
  2. 3
      package.json
  3. 97
      src/server.spec.ts
  4. 3
      src/server.ts
  5. 38
      src/server/UserResolver.spec.ts
  6. 3
      src/server/UserResolver.ts
  7. 14
      src/server/testing.ts
  8. 8
      src/server/userResolver/ContextInterface.ts
  9. 28
      src/server/userResolver/auth.ts

204
package-lock.json generated

@ -707,6 +707,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz",
"integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==" "integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w=="
}, },
"@types/node-fetch": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.2.tgz",
"integrity": "sha512-djYYKmdNRSBtL1x4CiE9UJb9yZhwtI1VC+UxZD0psNznrUj80ywsxKlEGAE+QL1qvLjPbfb24VosjkYM6W4RSQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/range-parser": { "@types/range-parser": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
@ -772,6 +781,22 @@
"integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==", "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==",
"dev": true "dev": true
}, },
"@types/zen-observable": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz",
"integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==",
"dev": true
},
"@wry/context": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.4.4.tgz",
"integrity": "sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag==",
"dev": true,
"requires": {
"@types/node": ">=6",
"tslib": "^1.9.3"
}
},
"@wry/equality": { "@wry/equality": {
"version": "0.1.9", "version": "0.1.9",
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.9.tgz", "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.9.tgz",
@ -871,6 +896,33 @@
"normalize-path": "^2.1.1" "normalize-path": "^2.1.1"
} }
}, },
"apollo-boost": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/apollo-boost/-/apollo-boost-0.4.4.tgz",
"integrity": "sha512-ASngBvazmp9xNxXfJ2InAzfDwz65o4lswlEPrWoN35scXmCz8Nz4k3CboUXbrcN/G0IExkRf/W7o9Rg0cjEBqg==",
"dev": true,
"requires": {
"apollo-cache": "^1.3.2",
"apollo-cache-inmemory": "^1.6.3",
"apollo-client": "^2.6.4",
"apollo-link": "^1.0.6",
"apollo-link-error": "^1.0.3",
"apollo-link-http": "^1.3.1",
"graphql-tag": "^2.4.2",
"ts-invariant": "^0.4.0",
"tslib": "^1.9.3"
}
},
"apollo-cache": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.2.tgz",
"integrity": "sha512-+KA685AV5ETEJfjZuviRTEImGA11uNBp/MJGnaCvkgr+BYRrGLruVKBv6WvyFod27WEB2sp7SsG8cNBKANhGLg==",
"dev": true,
"requires": {
"apollo-utilities": "^1.3.2",
"tslib": "^1.9.3"
}
},
"apollo-cache-control": { "apollo-cache-control": {
"version": "0.8.4", "version": "0.8.4",
"resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.8.4.tgz", "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.8.4.tgz",
@ -880,6 +932,35 @@
"graphql-extensions": "^0.10.3" "graphql-extensions": "^0.10.3"
} }
}, },
"apollo-cache-inmemory": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.3.tgz",
"integrity": "sha512-S4B/zQNSuYc0M/1Wq8dJDTIO9yRgU0ZwDGnmlqxGGmFombOZb9mLjylewSfQKmjNpciZ7iUIBbJ0mHlPJTzdXg==",
"dev": true,
"requires": {
"apollo-cache": "^1.3.2",
"apollo-utilities": "^1.3.2",
"optimism": "^0.10.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.9.3"
}
},
"apollo-client": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.4.tgz",
"integrity": "sha512-oWOwEOxQ9neHHVZrQhHDbI6bIibp9SHgxaLRVPoGvOFy7OH5XUykZE7hBQAVxq99tQjBzgytaZffQkeWo1B4VQ==",
"dev": true,
"requires": {
"@types/zen-observable": "^0.8.0",
"apollo-cache": "1.3.2",
"apollo-link": "^1.0.0",
"apollo-utilities": "1.3.2",
"symbol-observable": "^1.0.2",
"ts-invariant": "^0.4.0",
"tslib": "^1.9.3",
"zen-observable": "^0.8.0"
}
},
"apollo-datasource": { "apollo-datasource": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.6.3.tgz", "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.6.3.tgz",
@ -941,6 +1022,39 @@
"zen-observable-ts": "^0.8.20" "zen-observable-ts": "^0.8.20"
} }
}, },
"apollo-link-error": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/apollo-link-error/-/apollo-link-error-1.1.12.tgz",
"integrity": "sha512-psNmHyuy3valGikt/XHJfe0pKJnRX19tLLs6P6EHRxg+6q6JMXNVLYPaQBkL0FkwdTCB0cbFJAGRYCBviG8TDA==",
"dev": true,
"requires": {
"apollo-link": "^1.2.13",
"apollo-link-http-common": "^0.2.15",
"tslib": "^1.9.3"
}
},
"apollo-link-http": {
"version": "1.5.16",
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.16.tgz",
"integrity": "sha512-IA3xA/OcrOzINRZEECI6IdhRp/Twom5X5L9jMehfzEo2AXdeRwAMlH5LuvTZHgKD8V1MBnXdM6YXawXkTDSmJw==",
"dev": true,
"requires": {
"apollo-link": "^1.2.13",
"apollo-link-http-common": "^0.2.15",
"tslib": "^1.9.3"
}
},
"apollo-link-http-common": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.15.tgz",
"integrity": "sha512-+Heey4S2IPsPyTf8Ag3PugUupASJMW894iVps6hXbvwtg1aHSNMXUYO5VG7iRHkPzqpuzT4HMBanCTXPjtGzxg==",
"dev": true,
"requires": {
"apollo-link": "^1.2.13",
"ts-invariant": "^0.4.0",
"tslib": "^1.9.3"
}
},
"apollo-server": { "apollo-server": {
"version": "2.9.3", "version": "2.9.3",
"resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-2.9.3.tgz", "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-2.9.3.tgz",
@ -1362,6 +1476,24 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true "dev": true
}, },
"axios": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
"integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
"dev": true,
"requires": {
"follow-redirects": "1.5.10",
"is-buffer": "^2.0.2"
},
"dependencies": {
"is-buffer": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
"dev": true
}
}
},
"babel-jest": { "babel-jest": {
"version": "24.9.0", "version": "24.9.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz",
@ -1713,12 +1845,6 @@
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"dev": true "dev": true
}, },
"class-transformer": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.2.3.tgz",
"integrity": "sha512-qsP+0xoavpOlJHuYsQJsN58HXSl8Jvveo+T37rEvCEeRfMWoytAyR0Ua/YsFgpM6AZYZ/og2PJwArwzJl1aXtQ==",
"dev": true
},
"class-utils": { "class-utils": {
"version": "0.3.6", "version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
@ -2270,15 +2396,6 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
}, },
"encoding": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"dev": true,
"requires": {
"iconv-lite": "~0.4.13"
}
},
"end-of-stream": { "end-of-stream": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
@ -2677,6 +2794,26 @@
"locate-path": "^3.0.0" "locate-path": "^3.0.0"
} }
}, },
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"dev": true,
"requires": {
"debug": "=3.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
}
},
"for-in": { "for-in": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -3836,28 +3973,6 @@
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true "dev": true
}, },
"isomorphic-fetch": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
"dev": true,
"requires": {
"node-fetch": "^1.0.1",
"whatwg-fetch": ">=0.10.0"
},
"dependencies": {
"node-fetch": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
"dev": true,
"requires": {
"encoding": "^0.1.11",
"is-stream": "^1.0.1"
}
}
}
},
"isstream": { "isstream": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@ -5241,6 +5356,15 @@
"wrappy": "1" "wrappy": "1"
} }
}, },
"optimism": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.10.3.tgz",
"integrity": "sha512-9A5pqGoQk49H6Vhjb9kPgAeeECfUDF6aIICbMDL23kDLStBn1MWk3YvcZ4xWF9CsSf6XEgvRLkXy4xof/56vVw==",
"dev": true,
"requires": {
"@wry/context": "^0.4.0"
}
},
"optimist": { "optimist": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
@ -7058,12 +7182,6 @@
"iconv-lite": "0.4.24" "iconv-lite": "0.4.24"
} }
}, },
"whatwg-fetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==",
"dev": true
},
"whatwg-mimetype": { "whatwg-mimetype": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",

@ -31,8 +31,11 @@
"@types/jest": "^24.0.18", "@types/jest": "^24.0.18",
"@types/jsonwebtoken": "^8.3.3", "@types/jsonwebtoken": "^8.3.3",
"@types/node": "^12.7.5", "@types/node": "^12.7.5",
"@types/node-fetch": "^2.5.2",
"apollo-boost": "^0.4.4",
"apollo-server-testing": "^2.9.6", "apollo-server-testing": "^2.9.6",
"jest": "^24.9.0", "jest": "^24.9.0",
"node-fetch": "^2.6.0",
"ts-jest": "^24.1.0", "ts-jest": "^24.1.0",
"ts-node-dev": "^1.0.0-pre.42", "ts-node-dev": "^1.0.0-pre.42",
"typeorm-transactional-cls-hooked": "^0.1.8", "typeorm-transactional-cls-hooked": "^0.1.8",

@ -1,26 +1,85 @@
import { ApolloServer } from "apollo-server-express" import ApolloClient, { gql } from "apollo-boost"
import { createTestClient } from "apollo-server-testing" import fetch from "node-fetch"
import { createConnection, getConnection } from "typeorm" import { createConnection } from "typeorm"
import { createServer } from "./server" import { createServer } from "./server"
import { testingConnectionOptions } from "./server/testing" import {
import auth = require("./server/userResolver/auth") initializeRollbackTransactions,
runInRollbackTransaction,
testingConnectionOptions,
} from "./server/testing"
describe("app should", () => { const port = 4001
it("call the context function on apollo server", async () => {
const spy = jest.spyOn(auth, "contextFunction")
await createConnection(testingConnectionOptions())
const port = 4001 beforeAll(async () => {
const server = (await createServer(port)) as any initializeRollbackTransactions()
const { query } = createTestClient(server) await createConnection(testingConnectionOptions())
await query({ query: "{me{email}}" }) await createServer(port)
})
describe("server should", () => {
it(
"handle auth user me request",
runInRollbackTransaction(async () => {
const uri = `http://localhost:${port}/graphql`
let client = new ApolloClient({ uri, fetch })
const createUserMutation = gql`
mutation {
createUser(email: "email@email.com", password: "password") {
email
}
}
`
await client.mutate({ mutation: createUserMutation })
const loginTokensQuery = gql`
query {
loginTokens(email: "email@email.com", password: "password") {
accessToken
}
}
`
const tokens = await client.query({ query: loginTokensQuery })
const accessToken = tokens.data.loginTokens.accessToken
client = new ApolloClient({
uri,
fetch,
request: operation => {
operation.setContext({
headers: {
authorization: "Bearer " + accessToken,
},
})
},
})
const meQuery = gql`
query {
me {
email
}
}
`
const meResponse = await client.query({ query: meQuery })
const meEmail = meResponse.data.me.email
expect(meEmail).toBe("email@email.com")
})
)
expect(server).toBeInstanceOf(ApolloServer) it(
expect(spy).toHaveBeenCalledTimes(1) "receive no refresh token without auth header",
runInRollbackTransaction(async () => {
const uri = `http://localhost:${port}/refresh_token`
spy.mockRestore() const response = await fetch(uri, { method: "POST" })
const jsonResponse = await response.json()
await server.stop() expect(jsonResponse.data).toBeNull()
await getConnection().close() expect(jsonResponse.errors).not.toBeUndefined()
}) })
)
}) })

@ -13,6 +13,9 @@ export const createServer = async (port: number) => {
}) })
const app = express() const app = express()
app.post("/refresh_token", (_req, res) =>
res.send({ data: null, errors: "Invalid access token" })
)
server.applyMiddleware({ app }) server.applyMiddleware({ app })
app.listen({ port }) app.listen({ port })

@ -1,20 +1,19 @@
import { gql } from "apollo-server" import { gql } from "apollo-server"
import { Request, Response } from "express" import { Request, Response } from "express"
import { createConnection, getConnection } from "typeorm" import { createConnection, getConnection } from "typeorm"
import {
initializeTransactionalContext,
patchTypeORMRepositoryWithBaseRepository,
} from "typeorm-transactional-cls-hooked"
import { callSchema } from "./schema" import { callSchema } from "./schema"
import { runInTransaction, testingConnectionOptions } from "./testing" import {
import { ContextInterface, signAccessToken, verifyAccessToken } from "./userResolver/auth" initializeRollbackTransactions,
runInRollbackTransaction,
testingConnectionOptions,
} from "./testing"
import { signAccessToken, verifyAccessToken } from "./userResolver/auth"
import { ContextInterface } from "./userResolver/ContextInterface"
import { LoginTokens } from "./userResolver/LoginTokens" import { LoginTokens } from "./userResolver/LoginTokens"
import { User } from "./userResolver/User" import { User } from "./userResolver/User"
beforeAll(async () => { beforeAll(async () => {
initializeTransactionalContext() initializeRollbackTransactions()
patchTypeORMRepositoryWithBaseRepository()
await createConnection(testingConnectionOptions()) await createConnection(testingConnectionOptions())
}) })
@ -26,7 +25,7 @@ describe("resolver of user", () => {
describe("createUser mutation should", () => { describe("createUser mutation should", () => {
it( it(
"return email as it creates user with mutation", "return email as it creates user with mutation",
runInTransaction(async () => { runInRollbackTransaction(async () => {
const createUserMutation = gql` const createUserMutation = gql`
mutation { mutation {
createUser(email: "email@email.com", password: "password") { createUser(email: "email@email.com", password: "password") {
@ -48,7 +47,7 @@ describe("resolver of user", () => {
describe("users query should", () => { describe("users query should", () => {
it( it(
"return emails of registered users", "return emails of registered users",
runInTransaction(async () => { runInRollbackTransaction(async () => {
const usersQuery = gql` const usersQuery = gql`
query { query {
users { users {
@ -82,7 +81,7 @@ describe("resolver of user", () => {
it( it(
"return error for non-existent user", "return error for non-existent user",
runInTransaction(async () => { runInRollbackTransaction(async () => {
const response = await callSchema(loginTokensQuery) const response = await callSchema(loginTokensQuery)
expect(response.errors).not.toBeUndefined() expect(response.errors).not.toBeUndefined()
@ -92,7 +91,7 @@ describe("resolver of user", () => {
it( it(
"return error for bad password", "return error for bad password",
runInTransaction(async () => { runInRollbackTransaction(async () => {
await User.create({ await User.create({
email: "email@email.com", email: "email@email.com",
password: "BAD-password", password: "BAD-password",
@ -107,7 +106,7 @@ describe("resolver of user", () => {
it( it(
"return a valid access token with good credentials", "return a valid access token with good credentials",
runInTransaction(async () => { runInRollbackTransaction(async () => {
await User.create({ await User.create({
email: "email@email.com", email: "email@email.com",
password: "good-password", password: "good-password",
@ -115,12 +114,17 @@ describe("resolver of user", () => {
const response = await callSchema(loginTokensQuery) const response = await callSchema(loginTokensQuery)
const accessToken = response.data!.loginTokens.accessToken const accessToken = response.data!.loginTokens.accessToken
const accessTokenPayload = verifyAccessToken(accessToken)
const loginTokens = new LoginTokens() const loginTokens = new LoginTokens()
loginTokens.accessToken = accessToken loginTokens.accessToken = accessToken
const fifteenMinutes = 900
const accessTokenLifetime =
accessTokenPayload.exp! - accessTokenPayload.iat!
expect(accessTokenLifetime).toBe(fifteenMinutes)
expect(accessTokenPayload).toBeTruthy()
expect(response.errors).toBeUndefined() expect(response.errors).toBeUndefined()
expect(response.data).toMatchObject({ loginTokens }) expect(response.data).toMatchObject({ loginTokens })
expect(verifyAccessToken(accessToken)).toBeTruthy()
}) })
) )
}) })
@ -136,7 +140,7 @@ describe("resolver of user", () => {
it( it(
"return an error without a valid jwt token", "return an error without a valid jwt token",
runInTransaction(async () => { runInRollbackTransaction(async () => {
const contextWithInvalidToken = contextWithAuthHeader( const contextWithInvalidToken = contextWithAuthHeader(
"Bearer INVALID-TOKEN" "Bearer INVALID-TOKEN"
) )
@ -149,7 +153,7 @@ describe("resolver of user", () => {
it( it(
"return an user with a valid jwt token", "return an user with a valid jwt token",
runInTransaction(async () => { runInRollbackTransaction(async () => {
const user = await User.create({ const user = await User.create({
email: "email@email.com", email: "email@email.com",
}).save() }).save()

@ -1,6 +1,7 @@
import "reflect-metadata" import "reflect-metadata"
import { Arg, Authorized, Ctx, Mutation, Query } from "type-graphql" import { Arg, Authorized, Ctx, Mutation, Query } from "type-graphql"
import { comparePassword, ContextInterface, signAccessToken } from "./userResolver/auth" import { comparePassword, signAccessToken } from "./userResolver/auth"
import { ContextInterface } from "./userResolver/ContextInterface"
import { LoginTokens } from "./userResolver/LoginTokens" import { LoginTokens } from "./userResolver/LoginTokens"
import { User } from "./userResolver/User" import { User } from "./userResolver/User"

@ -1,5 +1,10 @@
import { ConnectionOptions } from "typeorm" import { ConnectionOptions } from "typeorm"
import { Propagation, Transactional } from "typeorm-transactional-cls-hooked" import {
initializeTransactionalContext,
patchTypeORMRepositoryWithBaseRepository,
Propagation,
Transactional,
} from "typeorm-transactional-cls-hooked"
import { connectionOptions } from "./connection" import { connectionOptions } from "./connection"
export const testingConnectionOptions = () => { export const testingConnectionOptions = () => {
@ -8,6 +13,11 @@ export const testingConnectionOptions = () => {
return { ...connectionOptions(), database } as ConnectionOptions return { ...connectionOptions(), database } as ConnectionOptions
} }
export const initializeRollbackTransactions = () => {
initializeTransactionalContext()
patchTypeORMRepositoryWithBaseRepository()
}
type RunFunction = () => Promise<void> | void type RunFunction = () => Promise<void> | void
class RollbackError extends Error { class RollbackError extends Error {
@ -26,7 +36,7 @@ class TransactionCreator {
} }
} }
export function runInTransaction(func: RunFunction) { export function runInRollbackTransaction(func: RunFunction) {
return async () => { return async () => {
try { try {
await TransactionCreator.run(func) await TransactionCreator.run(func)

@ -0,0 +1,8 @@
import { Request, Response } from "express"
import { ContextPayload } from "./auth"
export interface ContextInterface {
req: Request
res: Response
payload?: ContextPayload
}

@ -1,16 +1,16 @@
import { argon2id, hash, verify as argonVerify } from "argon2" import { argon2id, hash, verify as argonVerify } from "argon2"
import { Request, Response } from "express"
import { sign, verify as jwtVerify } from "jsonwebtoken" import { sign, verify as jwtVerify } from "jsonwebtoken"
import { AuthChecker } from "type-graphql" import { AuthChecker } from "type-graphql"
import { ContextInterface } from "./ContextInterface"
export type Payload = { export type ContextPayload = {
userId: number userId: number
} }
export interface ContextInterface { type AccessTokenPayload = {
req: Request userId: number
res: Response iat: number
payload?: Payload exp?: number
} }
export const hashPassword = async (password: string) => export const hashPassword = async (password: string) =>
@ -19,20 +19,26 @@ export const hashPassword = async (password: string) =>
export const comparePassword = async (hash: string, plain: string) => export const comparePassword = async (hash: string, plain: string) =>
await argonVerify(hash, plain, { type: argon2id }) await argonVerify(hash, plain, { type: argon2id })
export const signAccessToken = (payload: Payload) => { export const signAccessToken = (payload: ContextPayload) => {
return sign(payload, process.env.ACCESS_SECRET!) const accessTokenSecret = process.env.ACCESS_SECRET as string
return sign(payload, accessTokenSecret, {
expiresIn: process.env.ACCESS_EXP,
})
} }
export const verifyAccessToken = (token: string) => { export const verifyAccessToken = (token: string) => {
return jwtVerify(token, process.env.ACCESS_SECRET!) const accessTokenSecret = process.env.ACCESS_SECRET as string
return jwtVerify(token, accessTokenSecret) as AccessTokenPayload
} }
export const customAuthChecker: AuthChecker<ContextInterface> = ({ context }) => { export const customAuthChecker: AuthChecker<ContextInterface> = ({ context }) => {
try { try {
const authHeader = context.req.headers["authorization"] const authHeader = context.req.headers["authorization"]
const accessToken = authHeader!.split(" ")[1] const accessToken = authHeader!.split(" ")[1]
const payload = verifyAccessToken(accessToken) const accessTokenPayload = verifyAccessToken(accessToken)
context.payload = payload as any context.payload = accessTokenPayload as ContextPayload
return true return true
} catch (error) { } catch (error) {

Loading…
Cancel
Save