Skip to content

Commit

Permalink
refactor(utils/basic-auth): Moved Internal function to utils (#3359)
Browse files Browse the repository at this point in the history
* refactor(utils/basic-auth):  Split the code into utils

* remove:  dependency on HonoRequest
  • Loading branch information
sugar-cat7 authored Sep 3, 2024
1 parent fa19540 commit 1db161e
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 25 deletions.
27 changes: 2 additions & 25 deletions src/middleware/basic-auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,9 @@

import type { Context } from '../../context'
import { HTTPException } from '../../http-exception'
import type { HonoRequest } from '../../request'
import type { MiddlewareHandler } from '../../types'
import { auth } from '../../utils/basic-auth'
import { timingSafeEqual } from '../../utils/buffer'
import { decodeBase64 } from '../../utils/encode'

const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/
const USER_PASS_REGEXP = /^([^:]*):(.*)$/
const utf8Decoder = new TextDecoder()
const auth = (req: HonoRequest) => {
const match = CREDENTIALS_REGEXP.exec(req.header('Authorization') || '')
if (!match) {
return undefined
}

let userPass = undefined
// If an invalid string is passed to atob(), it throws a `DOMException`.
try {
userPass = USER_PASS_REGEXP.exec(utf8Decoder.decode(decodeBase64(match[1])))
} catch {} // Do nothing

if (!userPass) {
return undefined
}

return { username: userPass[1], password: userPass[2] }
}

type BasicAuthOptions =
| {
Expand Down Expand Up @@ -98,7 +75,7 @@ export const basicAuth = (
}

return async function basicAuth(ctx, next) {
const requestUser = auth(ctx.req)
const requestUser = auth(ctx.req.raw)
if (requestUser) {
if (verifyUserInOptions) {
if (await options.verifyUser(requestUser.username, requestUser.password, ctx)) {
Expand Down
57 changes: 57 additions & 0 deletions src/utils/basic-auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { HonoRequest } from '../request'

Check warning on line 1 in src/utils/basic-auth.test.ts

View workflow job for this annotation

GitHub Actions / Main

'HonoRequest' is defined but never used
import { auth } from './basic-auth'

describe('auth', () => {
it('auth() - not include Authorization Header', () => {
const res = auth(new Request('http://localhost/auth'))
expect(res).toBeUndefined()
})

it('auth() - invalid Authorization Header format', () => {
const res = auth(
new Request('http://localhost/auth', {
headers: { Authorization: 'InvalidAuthHeader' },
})
)
expect(res).toBeUndefined()
})

it('auth() - invalid Base64 string in Authorization Header', () => {
const res = auth(
new Request('http://localhost/auth', {
headers: { Authorization: 'Basic InvalidBase64' },
})
)
expect(res).toBeUndefined()
})

it('auth() - valid Authorization Header', () => {
const validBase64 = btoa('username:password')
const res = auth(
new Request('http://localhost/auth', {
headers: { Authorization: `Basic ${validBase64}` },
})
)
expect(res).toEqual({ username: 'username', password: 'password' })
})

it('auth() - empty username', () => {
const validBase64 = btoa(':password')
const res = auth(
new Request('http://localhost/auth', {
headers: { Authorization: `Basic ${validBase64}` },
})
)
expect(res).toEqual({ username: '', password: 'password' })
})

it('auth() - empty password', () => {
const validBase64 = btoa('username:')
const res = auth(
new Request('http://localhost/auth', {
headers: { Authorization: `Basic ${validBase64}` },
})
)
expect(res).toEqual({ username: 'username', password: '' })
})
})
26 changes: 26 additions & 0 deletions src/utils/basic-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { decodeBase64 } from './encode'

const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/
const USER_PASS_REGEXP = /^([^:]*):(.*)$/
const utf8Decoder = new TextDecoder()

export type Auth = (req: Request) => { username: string; password: string } | undefined

export const auth: Auth = (req: Request) => {
const match = CREDENTIALS_REGEXP.exec(req.headers.get('Authorization') || '')
if (!match) {
return undefined
}

let userPass = undefined
// If an invalid string is passed to atob(), it throws a `DOMException`.
try {
userPass = USER_PASS_REGEXP.exec(utf8Decoder.decode(decodeBase64(match[1])))
} catch {} // Do nothing

if (!userPass) {
return undefined
}

return { username: userPass[1], password: userPass[2] }
}

0 comments on commit 1db161e

Please sign in to comment.