Skip to content

Commit

Permalink
feat: add csrf error message using i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jan 22, 2024
1 parent 9d462f6 commit 49537e7
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 2 deletions.
5 changes: 5 additions & 0 deletions package.json
Expand Up @@ -42,6 +42,7 @@
"@adonisjs/assembler": "^7.1.0",
"@adonisjs/core": "^6.2.1",
"@adonisjs/eslint-config": "^1.2.1",
"@adonisjs/i18n": "^2.0.0",
"@adonisjs/prettier-config": "^1.2.1",
"@adonisjs/session": "^7.1.0",
"@adonisjs/tsconfig": "^1.2.1",
Expand Down Expand Up @@ -75,6 +76,7 @@
"peerDependencies": {
"@adonisjs/core": "^6.2.0",
"@adonisjs/session": "^7.0.0",
"@adonisjs/i18n": "^2.0.0",
"@japa/api-client": "^2.0.2",
"edge.js": "^6.0.1"
},
Expand All @@ -84,6 +86,9 @@
},
"edge.js": {
"optional": true
},
"@adonisjs/i18n": {
"optional": true
}
},
"author": "virk",
Expand Down
18 changes: 16 additions & 2 deletions src/errors.ts
Expand Up @@ -9,18 +9,32 @@

/// <reference types="@adonisjs/session/session_middleware" />

import type { I18n } from '@adonisjs/i18n'
import { Exception } from '@poppinss/utils'
import { HttpContext } from '@adonisjs/core/http'

export const E_BAD_CSRF_TOKEN = class InvalidCSRFToken extends Exception {
code = 'E_BAD_CSRF_TOKEN'
status = 403
message = 'Invalid or expired CSRF token'
identifier = 'errors.E_BAD_CSRF_TOKEN'

async handle(error: InvalidCSRFToken, ctx: HttpContext) {
/**
* Returns the message to be sent in the HTTP response.
* Feel free to override this method and return a custom
* response.
*/
getResponseMessage(error: this, ctx: HttpContext) {
if ('i18n' in ctx) {
return (ctx.i18n as I18n).t(error.identifier, {}, error.message)
}
return error.message
}

async handle(error: this, ctx: HttpContext) {
ctx.session.flashExcept(['_csrf', '_method', 'password', 'password_confirmation'])
ctx.session.flashErrors({
[error.code]: error.message,
[error.code]: this.getResponseMessage(error, ctx),
})
ctx.response.redirect().back()
}
Expand Down
54 changes: 54 additions & 0 deletions tests/csrf.spec.ts
Expand Up @@ -18,6 +18,7 @@ import { SessionMiddlewareFactory } from '@adonisjs/session/factories'
import { setup } from './helpers.js'
import { csrfFactory } from '../src/guards/csrf.js'
import { E_BAD_CSRF_TOKEN } from '../src/errors.js'
import { I18nManagerFactory } from '@adonisjs/i18n/factories'

const tokens = new Tokens()

Expand Down Expand Up @@ -391,4 +392,57 @@ test.group('Csrf', () => {
})
}
})

test('get error message from i18n', async ({ assert }) => {
assert.plan(1)

const app = await setup()
const ctx = new HttpContextFactory().create()
const encrpytion = await app.container.make('encryption')
const middleware = await new SessionMiddlewareFactory().create()

const i18nManager = new I18nManagerFactory()
.merge({
config: {
loaders: [
() => {
return {
async load() {
return {
en: {
'errors.E_BAD_CSRF_TOKEN': 'Session expired',
},
}
},
}
},
],
},
})
.create()

await middleware.handle(ctx, async () => {
ctx.route = { pattern: '/' } as any
ctx.request.request.method = 'PATCH'
await i18nManager.loadTranslations()
ctx.i18n = i18nManager.locale('en')

const secret = await tokens.secret()
const csrfToken = tokens.create(secret)
ctx.request.updateBody({ _csrf: csrfToken })
})

const csrf = csrfFactory({ enabled: true, enableXsrfCookie: false }, encrpytion)
try {
await csrf(ctx)
} catch (error) {
await error.handle(error, ctx)
assert.deepEqual(ctx.session.responseFlashMessages.all(), {
errorsBag: {
E_BAD_CSRF_TOKEN: 'Session expired',
},
input: {},
})
}
})
})

0 comments on commit 49537e7

Please sign in to comment.