Skip to content

Commit

Permalink
refactor(utils/basic-auth): Split the code into utils
Browse files Browse the repository at this point in the history
  • Loading branch information
sugar-cat7 committed Sep 2, 2024
1 parent bdaaa7f commit 24bd2dd
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 24 deletions.
25 changes: 1 addition & 24 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
63 changes: 63 additions & 0 deletions src/utils/basic-auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { HonoRequest } from '../request'
import { auth } from './basic-auth'

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

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

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

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

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

it('auth() - empty password', () => {
const validBase64 = btoa('username:')
const req = new HonoRequest(
new Request('http://localhost/auth', {
headers: { Authorization: `Basic ${validBase64}` },
})
)
const res = auth(req)
expect(res).toEqual({ username: 'username', password: '' })
})
})
27 changes: 27 additions & 0 deletions src/utils/basic-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { HonoRequest } from '../request'
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: HonoRequest) => { username: string; password: string } | undefined

export const auth: 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] }
}

0 comments on commit 24bd2dd

Please sign in to comment.