Skip to content

Commit

Permalink
refactor: code cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Apr 19, 2020
1 parent 1013621 commit ab89c4c
Show file tree
Hide file tree
Showing 18 changed files with 1,562 additions and 787 deletions.
2 changes: 1 addition & 1 deletion adonis-typings/shield.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

declare module '@ioc:Adonis/Addons/Shield' {
import { CookieOptions } from '@poppinss/cookie'
import { CookieOptions } from '@ioc:Adonis/Core/Response'
import { CspOptions as HelmetCspOptions } from 'helmet-csp/dist/lib/types'

/**
Expand Down
2,025 changes: 1,409 additions & 616 deletions package-lock.json

Large diffs are not rendered by default.

31 changes: 16 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,33 @@
"author": "virk",
"license": "MIT",
"devDependencies": {
"@adonisjs/core": "^5.0.0-preview.4",
"@adonisjs/fold": "^6.3.4",
"@adonisjs/mrm-preset": "^2.2.4",
"@adonisjs/session": "^2.3.3",
"@adonisjs/view": "^1.0.13",
"@poppinss/dev-utils": "^1.0.4",
"@adonisjs/core": "^5.0.0-preview-rc-1.2",
"@adonisjs/fold": "^6.3.5",
"@adonisjs/mrm-preset": "^2.3.0",
"@adonisjs/session": "^3.0.1",
"@adonisjs/view": "^2.0.1",
"@poppinss/dev-utils": "^1.0.6",
"@types/csrf": "^1.3.2",
"@types/node": "^13.9.1",
"commitizen": "^4.0.3",
"@types/node": "^13.13.0",
"@types/supertest": "^2.0.8",
"commitizen": "^4.0.4",
"copyfiles": "^2.2.0",
"cz-conventional-changelog": "^3.1.0",
"del-cli": "^3.0.0",
"eslint": "^6.8.0",
"eslint-plugin-adonis": "^1.0.8",
"husky": "^4.2.3",
"eslint-plugin-adonis": "^1.0.9",
"husky": "^4.2.5",
"japa": "^3.0.1",
"mrm": "^2.1.0",
"mrm": "^2.2.1",
"np": "^5.2.1",
"pkg-ok": "^2.3.1",
"ts-node": "^8.6.2",
"supertest": "^4.0.2",
"ts-node": "^8.8.2",
"typescript": "^3.8.3"
},
"dependencies": {
"@poppinss/utils": "^2.1.2",
"@poppinss/utils": "^2.2.4",
"csrf": "^3.1.0",
"helmet-csp": "^2.9.5",
"helmet-csp": "^2.10.0",
"ms": "^2.1.2"
},
"repository": {
Expand Down
14 changes: 8 additions & 6 deletions providers/ShieldProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@

import { IocContract } from '@adonisjs/fold'

/**
* Provider to register shield middleware
*/
export default class ShieldProvider {
constructor (protected container: IocContract) {}

public register () {
this.container.singleton('Adonis/Addons/ShieldMiddleware', () => {
const Config = this.container.use('Adonis/Core/Config')
const shieldConfig = Config.get('shield', {})
const appKey = Config.get('app.appKey')
const viewProvider = this.container.hasBinding('Adonis/Core/View')
const Encryption = this.container.use('Adonis/Core/Encryption')
const View = this.container.hasBinding('Adonis/Core/View')
? this.container.use('Adonis/Core/View')
: undefined

return new (require('../src/ShieldMiddleware').ShieldMiddleware)(
shieldConfig,
appKey,
viewProvider,
Config.get('shield', {}),
Encryption,
View,
)
})
}
Expand Down
7 changes: 5 additions & 2 deletions src/Bindings/Response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
* file that was distributed with this source code.
*/

import crypto from 'crypto'
import { randomString } from '@poppinss/utils'
import { ResponseConstructorContract } from '@ioc:Adonis/Core/Response'

/**
* Sharing CSP nonce with the response
*/
export default function responseBinding (Response: ResponseConstructorContract) {
Response.getter('nonce', () => {
return crypto.randomBytes(16).toString('hex')
return randomString(16)
}, true)
}
8 changes: 5 additions & 3 deletions src/ShieldMiddleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
* file that was distributed with this source code.
*/

import { ViewContract } from '@ioc:Adonis/Core/View'
import { ShieldConfig } from '@ioc:Adonis/Addons/Shield'
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

import * as shield from '../../standalone'
Expand All @@ -21,7 +23,7 @@ export class ShieldMiddleware {
* Actions to be performed
*/
private actions = [
shield.csrfFactory(this.config.csrf || {}, this.appKey, this.viewProvider),
shield.csrfFactory(this.config.csrf || {}, this.encryption, this.viewProvider),
shield.cspFactory(this.config.csp || {}),
shield.dnsPrefetchFactory(this.config.dnsPrefetch || {}),
shield.frameGuardFactory(this.config.xFrame || {}),
Expand All @@ -33,8 +35,8 @@ export class ShieldMiddleware {

constructor (
private config: ShieldConfig,
private appKey: string,
private viewProvider?: any,
private encryption: EncryptionContract,
private viewProvider?: ViewContract,
) {
}

Expand Down
3 changes: 1 addition & 2 deletions src/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
/// <reference path="../adonis-typings/index.ts" />

import helmetCsp from 'helmet-csp'
import { SourceListDirective } from 'helmet-csp/dist/lib/types'

import { CspOptions } from '@ioc:Adonis/Addons/Shield'
import { SourceListDirective } from 'helmet-csp/dist/lib/types'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

import { noop } from './noop'
Expand Down
36 changes: 20 additions & 16 deletions src/csrf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
/// <reference path="../adonis-typings/index.ts" />

import Tokens from 'csrf'
import { unpack } from '@poppinss/cookie'
import { Exception } from '@poppinss/utils'
import { ViewContract } from '@ioc:Adonis/Core/View'
import { CsrfOptions } from '@ioc:Adonis/Addons/Shield'
import { RequestContract } from '@ioc:Adonis/Core/Request'
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

import { noop } from './noop'
Expand Down Expand Up @@ -45,8 +46,8 @@ export class Csrf {

constructor (
private options: CsrfOptions,
private appKey: string,
private viewProvider?: any,
private encryption: EncryptionContract,
private viewProvider?: ViewContract,
) {
}

Expand Down Expand Up @@ -96,8 +97,11 @@ export class Csrf {
}

const encryptedToken = request.header('x-xsrf-token')
const unpackedToken = encryptedToken ? unpack(decodeURIComponent(encryptedToken), this.appKey) : null
return unpackedToken && unpackedToken.signed ? unpackedToken.value : null
if (typeof (encryptedToken) !== 'string' || !encryptedToken) {
return null
}

return this.encryption.decrypt(decodeURIComponent(encryptedToken).slice(2), 'xsrf-token')
}

/**
Expand All @@ -110,19 +114,19 @@ export class Csrf {

ctx.view.share({
csrfToken: ctx.request.csrfToken,
csrfMeta: this.viewProvider.utils.withCtx((viewCtx) => {
return viewCtx.safe(`<meta name='csrf-token' content='${ctx.request.csrfToken}'>`)
}),
csrfField: this.viewProvider.utils.withCtx((viewCtx) => {
return viewCtx.safe(`<input type='hidden' name='_csrf' value='${ctx.request.csrfToken}'>`)
}),
csrfMeta: () => {
return this.viewProvider!.GLOBALS.safe(`<meta name='csrf-token' content='${ctx.request.csrfToken}'>`)
},
csrfField: () => {
return this.viewProvider!.GLOBALS.safe(`<input type='hidden' name='_csrf' value='${ctx.request.csrfToken}'>`)
},
})
}

/**
* Generate a new csrf token using the csrf secret extracted from session.
*/
public generateCsrfToken (csrfSecret: string): string {
private generateCsrfToken (csrfSecret: string): string {
return this.tokens.create(csrfSecret)
}

Expand All @@ -131,7 +135,7 @@ export class Csrf {
* new one. Newly created secret is persisted to session at
* the same time
*/
public async getCsrfSecret (ctx: HttpContextContract): Promise<string> {
private async getCsrfSecret (ctx: HttpContextContract): Promise<string> {
let csrfSecret = ctx.session.get(this.secretSessionKey)

if (!csrfSecret) {
Expand Down Expand Up @@ -173,7 +177,7 @@ export class Csrf {
const cookieOptions = Object.assign({}, this.options.cookieOptions, {
httpOnly: false,
})
ctx.response.cookie('xsrf-token', ctx.request.csrfToken, cookieOptions)
ctx.response.encryptedCookie('xsrf-token', ctx.request.csrfToken, cookieOptions)
}

/**
Expand All @@ -187,11 +191,11 @@ export class Csrf {
* A factory function that returns a new function to enforce CSRF
* protection
*/
export function csrfFactory (options: CsrfOptions, appKey: string, viewProvider?: any) {
export function csrfFactory (options: CsrfOptions, encryption: EncryptionContract, viewProvider?: ViewContract) {
if (!options.enabled) {
return noop
}

const csrfMiddleware = new Csrf(options, appKey, viewProvider)
const csrfMiddleware = new Csrf(options, encryption, viewProvider)
return csrfMiddleware.handle.bind(csrfMiddleware)
}
5 changes: 1 addition & 4 deletions src/frameGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ export function frameGuardFactory (options: XFrameOptions) {
throw new Error('frameGuard: Domain value is required when using action as "ALLOW-FROM"')
}

const result = action === 'ALLOW-FROM'
? `${action} ${options['domain']}`
: action

const result = action === 'ALLOW-FROM' ? `${action} ${options['domain']}` : action
return function frameGuard ({ response }: HttpContextContract) {
response.header('X-Frame-Options', result)
}
Expand Down
76 changes: 23 additions & 53 deletions test-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,81 +10,51 @@
import { join } from 'path'
import { Ioc } from '@adonisjs/fold'
import { IncomingMessage } from 'http'
import { Edge, GLOBALS } from 'edge.js'
import { FakeLogger } from '@adonisjs/logger/build/standalone'
import { Profiler } from '@adonisjs/profiler/build/standalone'
import { SessionConfigContract } from '@ioc:Adonis/Addons/Session'
import { Encryption } from '@adonisjs/encryption/build/standalone'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { HttpContext } from '@adonisjs/http-server/build/standalone'
import ViewProvider from '@adonisjs/view/build/providers/ViewProvider'
import { SessionManager } from '@adonisjs/session/build/src/SessionManager'

const ioc = new Ioc()
const logger = new FakeLogger({ level: 'trace', enabled: false, name: 'adonisjs' })
const profiler = new Profiler(__dirname, logger, {})

const sessionConfig: SessionConfigContract = {
driver: 'cookie',
cookieName: 'adonis-session',
clearWithBrowser: false,
age: '2h',
cookie: {
path: '/',
},
}

export const encryption = new Encryption({ secret: 'verylongrandom32characterssecret' })
export const viewsDir = join(__dirname, 'views')
export const view = new Edge()

/**
* Perform container bindings setup
* Setup
*/
export function setup () {
ioc.bind('Adonis/Core/Env', () => ({
get () {
return true
},
}))

ioc.bind('Adonis/Core/Application', () => ({
viewsPath () {
return viewsDir
},
}))

new ViewProvider(ioc).register()
export async function setup () {
view.mount(viewsDir)
Object.keys(GLOBALS).forEach((key) => view.global(key, GLOBALS[key]))

HttpContext.getter('session', function session () {
const sessionManager = new SessionManager(ioc, sessionConfig)
const sessionManager = new SessionManager(new Ioc(), {
driver: 'cookie',
cookieName: 'adonis-session',
clearWithBrowser: false,
age: '2h',
cookie: {
path: '/',
},
})
return sessionManager.create(this)
}, true)

HttpContext.getter('view', function view () {
return ioc.use('Adonis/Core/View').share({ request: this.request, route: this.route })
HttpContext.getter('view', function () {
return view.share({ request: this.request, route: this.route })
}, true)
}

export function getView () {
return ioc.use('Adonis/Core/View')
}

/**
* Returns HTTP context instance
*/
export function getCtx (
secret: string,
routePath: string = '/',
routeParams = {},
request?: IncomingMessage,
) {
setup()

return HttpContext.create(
routePath,
routeParams,
logger,
profiler.create(''),
{} as any,
request,
undefined,
{ secret } as any,
) as HttpContextContract
export function getCtx (routePath: string = '/', routeParams = {}, req?: IncomingMessage) {
const httpRow = profiler.create('http:request')
return HttpContext
.create(routePath, routeParams, logger, httpRow, encryption, req, undefined, {} as any) as HttpContextContract
}
8 changes: 4 additions & 4 deletions test/csp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { getCtx } from '../test-helpers'
test.group('Csp', () => {
test('return noop function when enabled is false', (assert) => {
const csp = cspFactory({ enabled: false })
const ctx = getCtx('')
const ctx = getCtx()
csp(ctx)

assert.isUndefined(ctx.response.getHeader('Content-Security-Policy'))
Expand All @@ -28,7 +28,7 @@ test.group('Csp', () => {
},
})

const ctx = getCtx('')
const ctx = getCtx()
csp(ctx)

assert.equal(ctx.response.getHeader('Content-Security-Policy'), 'default-src \'self\'')
Expand All @@ -43,7 +43,7 @@ test.group('Csp', () => {
},
})

const ctx = getCtx('')
const ctx = getCtx()
ctx.response.nonce = '1234'

csp(ctx)
Expand All @@ -62,7 +62,7 @@ test.group('Csp', () => {
},
})

const ctx = getCtx('')
const ctx = getCtx()
ctx.response.nonce = '1234'

csp(ctx)
Expand Down

0 comments on commit ab89c4c

Please sign in to comment.