0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-24 19:26:56 +01:00

refactor(utils/basic-auth): Moved Internal function to utils (#3359)

* refactor(utils/basic-auth):  Split the code into utils

* remove:  dependency on HonoRequest
This commit is contained in:
sugar 2024-09-03 16:45:31 +09:00 committed by GitHub
parent fa1954001c
commit 1db161e343
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 85 additions and 25 deletions

View File

@ -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 =
| {
@ -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)) {

View File

@ -0,0 +1,57 @@
import { HonoRequest } from '../request'
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
src/utils/basic-auth.ts Normal file
View File

@ -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] }
}