Skip to content

Commit

Permalink
fix(auth): add JWT refresh tokens to rest api
Browse files Browse the repository at this point in the history
Add refresh tokens and delete refresh tokens endpoints to rest api from the auth package
  • Loading branch information
Frantz Kati committed Nov 21, 2020
1 parent 034f575 commit 0e553e5
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 14 deletions.
4 changes: 4 additions & 0 deletions examples/blog/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ module.exports = tensei()
.teams()
.apiPath('auth')
.rolesAndPermissions()
.jwt({
expiresIn: 60,
refreshTokenExpiresIn: 60 * 2,
})
.social('github', {
key: process.env.GITHUB_KEY,
secret: process.env.GITHUB_SECRET,
Expand Down
4 changes: 3 additions & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"main": "./build/index.js",
"license": "MIT",
"files": [
"build/"
"build/",
"auth.d.ts",
"auth.config.d.ts"
],
"types": "./build/index.d.ts",
"devDependencies": {
Expand Down
33 changes: 29 additions & 4 deletions packages/auth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Bcrypt from 'bcryptjs'
import Jwt from 'jsonwebtoken'
import Randomstring from 'randomstring'
import { validateAll } from 'indicative/validator'
import { Request, Response, NextFunction } from 'express'
import { Request, Response, NextFunction, response } from 'express'
import {
plugin,
resource,
Expand Down Expand Up @@ -808,8 +808,10 @@ class Auth {
route(`Get authenticated ${name}`)
.path(this.getApiPath('me'))
.get()
.handle(async (request, response) =>
response.formatter.ok(request.user)
.handle(async ({ user }, { formatter: { ok, unauthorized } }) =>
user && ! user.public ? ok(user) : unauthorized({
message: 'Unauthorized.'
})
),
route(`Resend Verification email`)
.path(this.getApiPath('verification/resend'))
Expand All @@ -834,6 +836,28 @@ class Auth {
response.formatter.ok(
await this.socialAuth(request as any, 'register')
)
),
route('Refresh Token')
.path(this.getApiPath('refresh-token'))
.post()
.handle(async (request, { formatter: { ok, unauthorized } }) => {
try {
return ok(
await this.handleRefreshTokens(request as any)
)
} catch (error) {
return unauthorized({
message: error.message || 'Invalid refresh token.'
})
}
}),
route('Remove refresh Token')
.path(this.getApiPath('refresh-token'))
.delete()
.handle(async (request, response) =>
response.formatter.ok(
await this.removeRefreshTokens(request as any)
)
)
]
}
Expand Down Expand Up @@ -978,8 +1002,9 @@ class Auth {
) as JwtPayload
} catch (error) {}

if (!tokenPayload || !tokenPayload.refresh)
if (!tokenPayload || !tokenPayload.refresh) {
throw ctx.authenticationError('Invalid refresh token.')
}

const user: any = await ctx.manager.findOne(
this.resources.user.data.pascalCaseName,
Expand Down
3 changes: 2 additions & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"license": "MIT",
"types": "./typings/index.d.ts",
"files": [
"build/"
"build/",
"typings/"
],
"devDependencies": {
"@babel/core": "^7.11.4",
Expand Down
8 changes: 6 additions & 2 deletions packages/common/typings/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,15 @@ declare module '@tensei/common/config' {
TContext = GraphQLPluginContext,
TArgs = any
> = IMiddleware<TSource, TContext, TArgs>
type MiddlewareGenerator = (
type MiddlewareGenerator<
TSource = any,
TContext = GraphQLPluginContext,
TArgs = any
> = (
graphQlQueries: GraphQlQueryContract[],
typeDefs: ITypedef[],
schema: GraphQLSchema
) => IMiddleware
) => IMiddleware<TSource, TContext, TArgs>
export interface Config {
databaseClient: any
schemas: any
Expand Down
2 changes: 2 additions & 0 deletions packages/core/Tensei.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ export class Tensei implements TenseiContract {

this.app.use(CookieParser())

this.app.disable('x-powered-by')

const rootStorage = (this.storageConfig.disks?.local?.config as any)
.root
const publicPath = (this.storageConfig.disks?.local?.config as any)
Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"license": "MIT",
"files": [
"build/",
"typings/"
"typings/",
"core.d.ts"
],
"devDependencies": {
"@types/bcryptjs": "^2.4.2",
Expand Down
12 changes: 12 additions & 0 deletions packages/rest/rest.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Request } from 'express'

declare global {
namespace Express {
export interface Request {
authenticationError: (message?: string) => unknown
forbiddenError: (message?: string) => unknown
validationError: (message?: string) => unknown
userInputError: (message?: string) => unknown
}
}
}
51 changes: 46 additions & 5 deletions packages/rest/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Request, Response, request } from 'express'
import { Request, Response } from 'express'
import qs from 'qs'
import {
FindOptions,
Expand All @@ -15,7 +15,7 @@ class Rest {
return `/${apiPath}/${path}`
}

public getPageMetaFromFindOptions(
private getPageMetaFromFindOptions(
total: number,
findOptions: FindOptions<any>
) {
Expand All @@ -31,7 +31,7 @@ class Rest {
}
}

public parseQueryToFindOptions(query: any, resource: ResourceContract) {
private parseQueryToFindOptions(query: any, resource: ResourceContract) {
let findOptions: FindOptions<any> = {}

if (query.page && query.page !== '-1') {
Expand Down Expand Up @@ -69,7 +69,7 @@ class Rest {
return findOptions
}

public parseQueryToWhereOptions(query: any) {
private parseQueryToWhereOptions(query: any) {
let whereOptions: FilterQuery<any> = {}

if (query.where) {
Expand Down Expand Up @@ -101,7 +101,7 @@ class Rest {
return whereOptions
}

extendRoutes(
private extendRoutes(
resources: ResourceContract[],
getApiPath: (path: string) => string
) {
Expand Down Expand Up @@ -299,6 +299,46 @@ class Rest {
.afterDatabaseSetup(
async ({ extendRoutes, resources, apiPath, app }) => {
app.use(responseEnhancer())

app.use((request, response, next) => {
// @ts-ignore
request.req = request

return next()
})

app.use((request, response, next) => {
request.authenticationError = (
message: string = 'Unauthenticated.'
) => ({
status: 401,
message
})

request.forbiddenError = (
message: string = 'Forbidden.'
) => ({
status: 400,
message
})

request.validationError = (
message: string = 'Validation failed.'
) => ({
status: 422,
message
})

request.userInputError = (
message: string = 'Validation failed.'
) => ({
status: 422,
message
})

return next()
})

extendRoutes(
this.extendRoutes(resources, (path: string) =>
this.getApiPath(apiPath, path)
Expand All @@ -317,6 +357,7 @@ class Rest {
}
)
})

routes.forEach(route => {
;(app as any)[route.config.type.toLowerCase()](
route.config.path,
Expand Down

0 comments on commit 0e553e5

Please sign in to comment.