Skip to content

Commit

Permalink
feat(config): add config.auth param to provide basic auth credentials (
Browse files Browse the repository at this point in the history
  • Loading branch information
zZeepo committed May 1, 2023
1 parent fbb19fa commit bdb89ec
Show file tree
Hide file tree
Showing 8 changed files with 45 additions and 7 deletions.
7 changes: 7 additions & 0 deletions docs/content/3.api/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ Where to emit lighthouse reports and the runtime client.

Display the loggers' debug messages.

### auth

- **Type:** `false|{ username: string, password: string }`
- **Default:** `false`

Optional basic auth credentials

### hooks

- **Type:** `NestedHooks<UnlighthouseHooks>`
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const validateHost = async (resolvedConfig: ResolvedUserConfig) => {
if (resolvedConfig.site) {
// test HTTP response from site
logger.debug(`Testing Site \`${resolvedConfig.site}\` is valid.`)
const { valid, response, error, redirected, redirectUrl } = await fetchUrlRaw(resolvedConfig.site)
const { valid, response, error, redirected, redirectUrl } = await fetchUrlRaw(resolvedConfig.site, resolvedConfig)
if (!valid) {
// something is wrong with the site, bail
if (response?.status)
Expand Down
2 changes: 2 additions & 0 deletions packages/core/lighthouse.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ declare module 'lighthouse' {
precomputedLanternData?: PrecomputedLanternData | null
/** The budget.json object for LightWallet. */
budgets?: Array<Budget> | null
/** Optional extra headers */
extraHeaders?: { [key: string]: string }
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/puppeteer/tasks/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const extractHtmlPayload: (page: Page, route: string) => Promise<{ succes

// if we don't need to execute any javascript we can do a less expensive fetch of the URL
if (resolvedConfig.scanner.skipJavascript) {
const { valid, response, redirected, redirectUrl } = await fetchUrlRaw(route)
const { valid, response, redirected, redirectUrl } = await fetchUrlRaw(route, resolvedConfig)
if (!valid || !response)
return { success: false, message: `Invalid response from URL ${route} code: ${response?.status || '404'}.` }

Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/puppeteer/tasks/lighthouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ export const runLighthouseTask: PuppeteerTask = async (props) => {
// ignore csp errors
await page.setBypassCSP(true)

if (resolvedConfig.auth) {
page.authenticate(resolvedConfig.auth)
}

// Wait for Lighthouse to open url, then allow hook to run
browser.on('targetchanged', async (target) => {
const page = await target.page()
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/resolveConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ export const resolveUserConfig: (userConfig: UserConfig) => Promise<ResolvedUser
}
}

if (config.auth) {
config.lighthouseOptions.extraHeaders = config.lighthouseOptions.extraHeaders || {}
if (!config.lighthouseOptions.extraHeaders['Authorization']) {
const credentials = `${config.auth.username}:${config.auth.password}`
config.lighthouseOptions.extraHeaders["Authorization"] = 'Basic ' + Buffer.from(credentials).toString("base64")
}
}

if (config.client?.columns) {
// filter out any columns for categories we're not showing
config.client.columns = pick(config.client.columns, ['overview', ...config.lighthouseOptions.onlyCategories as UnlighthouseTabs[]])
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ export interface ResolvedUserConfig {
* @default true
*/
cache: boolean
/**
* Optional basic auth credentials
*
* @default false
*/
auth: false | { username: string, password: string }
/**
* Load the configuration from a custom config file.
* By default, it attempts to load configuration from `unlighthouse.config.ts`.
Expand Down
21 changes: 16 additions & 5 deletions packages/core/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { ensureDirSync } from 'fs-extra'
import sanitize from 'sanitize-filename'
import slugify from 'slugify'
import { hasProtocol, joinURL, withLeadingSlash, withTrailingSlash, withoutLeadingSlash, withoutTrailingSlash } from 'ufo'
import type { AxiosResponse } from 'axios'
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import axios from 'axios'
import type { NormalisedRoute, UnlighthouseRouteReport } from './types'
import type { NormalisedRoute, ResolvedUserConfig, UnlighthouseRouteReport } from './types'
import { useUnlighthouse } from './unlighthouse'

export const ReportArtifacts = {
Expand Down Expand Up @@ -118,16 +118,27 @@ export const formatBytes = (bytes: number, decimals = 2) => {
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`
}

export async function fetchUrlRaw(url: string): Promise<{ error?: any; redirected?: boolean; redirectUrl?: string; valid: boolean; response?: AxiosResponse }> {
export async function fetchUrlRaw(url: string, resolvedConfig: ResolvedUserConfig): Promise<{ error?: any; redirected?: boolean; redirectUrl?: string; valid: boolean; response?: AxiosResponse }> {
const axiosOptions: AxiosRequestConfig = {}
if (resolvedConfig.auth) {
axiosOptions.auth = resolvedConfig.auth
}

try {
const response = await axios.get(url, {
// allow all SSL's
httpsAgent: new https.Agent({
rejectUnauthorized: false,
}),
...axiosOptions,
})
const redirected = response.request.res.responseUrl && response.request.res.responseUrl !== url
const redirectUrl = response.request.res.responseUrl
let responseUrl = response.request.res.responseUrl
if (responseUrl && axiosOptions.auth) {
// remove auth credentials from url (e.g. https://user:passwd@domain.de)
responseUrl = responseUrl.replace(/(?<=https?:\/\/)(.+@)/g, '')
}
const redirected = responseUrl && responseUrl !== url;
const redirectUrl = responseUrl;
if (response.status < 200 || (response.status >= 300 && !redirected)) {
return {
valid: false,
Expand Down

0 comments on commit bdb89ec

Please sign in to comment.