Skip to content

Commit

Permalink
feat: basic auth middleware supports overriding hashFunction (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
yusukebe authored Mar 8, 2022
1 parent ccf27fb commit 7a31de5
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 10 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,13 @@
],
"devDependencies": {
"@cloudflare/workers-types": "^3.3.0",
"@types/crypto-js": "^4.1.1",
"@types/jest": "^27.4.0",
"@types/mustache": "^4.1.2",
"@types/node": "^17.0.8",
"@typescript-eslint/eslint-plugin": "^5.9.0",
"@typescript-eslint/parser": "^5.9.0",
"crypto-js": "^4.1.1",
"eslint": "^7.26.0",
"eslint-config-prettier": "^8.3.0",
"eslint-define-config": "^1.2.1",
Expand All @@ -103,4 +105,4 @@
"engines": {
"node": ">=11.0.0"
}
}
}
25 changes: 23 additions & 2 deletions src/middleware/basic-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

## Usage

index.js:

```js
import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
Expand All @@ -24,3 +22,26 @@ app.get('/auth/page', (c) => {

app.fire()
```

For Fastly Compute@Edge, polyfill `crypto` or use `crypto-js`.

Install:

```
npm i crypto-js
```

Override `hashFunction`:

```js
import { SHA256 } from 'crypto-js'

app.use(
'/auth/*',
basicAuth({
username: 'hono',
password: 'acoolproject',
hashFunction: (d: string) => SHA256(d).toString(), // <---
})
)
```
22 changes: 22 additions & 0 deletions src/middleware/basic-auth/basic-auth.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Hono } from '../../hono'
import { basicAuth } from './basic-auth'
import { SHA256 } from 'crypto-js'

describe('Basic Auth by Middleware', () => {
const crypto = global.crypto
Expand Down Expand Up @@ -52,9 +53,19 @@ describe('Basic Auth by Middleware', () => {
)
)

app.use(
'/auth-override-func/*',
basicAuth({
username: username,
password: password,
hashFunction: (data: string) => SHA256(data).toString(),
})
)

app.get('/auth/*', () => new Response('auth'))
app.get('/auth-unicode/*', () => new Response('auth'))
app.get('/auth-multi/*', () => new Response('auth'))
app.get('/auth-override-func/*', () => new Response('auth'))

it('Unauthorized', async () => {
const req = new Request('http://localhost/auth/a')
Expand Down Expand Up @@ -104,4 +115,15 @@ describe('Basic Auth by Middleware', () => {
expect(res.status).toBe(200)
expect(await res.text()).toBe('auth')
})

it('should authorize with sha256 function override', async () => {
const credential = Buffer.from(username + ':' + password).toString('base64')

const req = new Request('http://localhost/auth-override-func/a')
req.headers.set('Authorization', `Basic ${credential}`)
const res = await app.dispatch(req)
expect(res).not.toBeNull()
expect(res.status).toBe(200)
expect(await res.text()).toBe('auth')
})
})
15 changes: 11 additions & 4 deletions src/middleware/basic-auth/basic-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const auth = (req: Request) => {
}

export const basicAuth = (
options: { username: string; password: string; realm?: string },
options: { username: string; password: string; realm?: string; hashFunction?: Function },
...users: { username: string; password: string }[]
) => {
if (!options) {
Expand All @@ -43,16 +43,23 @@ export const basicAuth = (
if (!options.realm) {
options.realm = 'Secure Area'
}

users.unshift({ username: options.username, password: options.password })

return async (ctx: Context, next: Function) => {
const requestUser = auth(ctx.req)

if (requestUser) {
for (const user of users) {
const usernameEqual = await timingSafeEqual(user.username, requestUser.username)
const passwordEqual = await timingSafeEqual(user.password, requestUser.password)
const usernameEqual = await timingSafeEqual(
user.username,
requestUser.username,
options.hashFunction
)
const passwordEqual = await timingSafeEqual(
user.password,
requestUser.password,
options.hashFunction
)
if (usernameEqual && passwordEqual) {
// Authorized OK
return next()
Expand Down
2 changes: 2 additions & 0 deletions src/utils/buffer.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { timingSafeEqual } from './buffer'
import { SHA256 as sha256CryptoJS } from 'crypto-js'

describe('buffer', () => {
it('positive', async () => {
Expand All @@ -13,6 +14,7 @@ describe('buffer', () => {
expect(await timingSafeEqual(undefined, undefined)).toBe(true)
expect(await timingSafeEqual(true, true)).toBe(true)
expect(await timingSafeEqual(false, false)).toBe(true)
expect(await timingSafeEqual(true, true, (d: string) => sha256CryptoJS(d).toString()))
})

it('negative', async () => {
Expand Down
10 changes: 7 additions & 3 deletions src/utils/buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ export const equal = (a: ArrayBuffer, b: ArrayBuffer) => {

export const timingSafeEqual = async (
a: string | object | boolean,
b: string | object | boolean
b: string | object | boolean,
hashFunction?: Function
) => {
const sa = await sha256(a)
const sb = await sha256(b)
if (!hashFunction) {
hashFunction = sha256
}
const sa = await hashFunction(a)
const sb = await hashFunction(b)
return sa === sb && a === b
}
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,11 @@
dependencies:
"@babel/types" "^7.3.0"

"@types/crypto-js@^4.1.1":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d"
integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==

"@types/graceful-fs@^4.1.2":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
Expand Down Expand Up @@ -1299,6 +1304,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"

crypto-js@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==

cssom@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
Expand Down

0 comments on commit 7a31de5

Please sign in to comment.