UNPKG

nest-digest-auth

Version:

Digest Authentication wrapper using Axios, compatible with NestJS

86 lines (73 loc) 2.42 kB
import axios, { AxiosRequestHeaders, Method } from 'axios'; import * as crypto from 'crypto'; export class DigestAuthService { private md5(data: string): string { return crypto.createHash('md5').update(data).digest('hex'); } private parseAuthHeader(authHeader: string): Record<string, string> { const authParams: Record<string, string> = {}; const regex = /(\w+)=["]?([^",]+)["]?/g; let match: RegExpExecArray | null; while ((match = regex.exec(authHeader)) !== null) { authParams[match[1]] = match[2]; } return authParams; } async digestRequest( options: { method: Method; url: string; headers?: AxiosRequestHeaders; }, data: any, user: string, pass: string, ): Promise<any> { try { const initialRes = await axios({ method: options.method, url: options.url, headers: options.headers || {}, validateStatus: () => true, }); if ( initialRes.status !== 401 || !initialRes.headers['www-authenticate'] ) { throw new Error( `Expected 401 Unauthorized, got ${initialRes.status}`, ); } const authDetails = this.parseAuthHeader( initialRes.headers['www-authenticate'], ); const realm = authDetails.realm; const nonce = authDetails.nonce; const qop = authDetails.qop || 'auth'; const cnonce = this.md5(Date.now().toString()); const nc = '00000001'; const method = options.method.toUpperCase(); const urlObj = new URL(options.url); const path = urlObj.pathname + urlObj.search; const ha1 = this.md5(`${user}:${realm}:${pass}`); const ha2 = this.md5(`${method}:${path}`); const response = this.md5( `${ha1}:${nonce}:${nc}:${cnonce}:${qop}:${ha2}`, ); const authHeader = `Digest username="${user}", realm="${realm}", nonce="${nonce}", uri="${path}", algorithm=MD5, response="${response}", qop=${qop}, nc=${nc}, cnonce="${cnonce}"`; const finalRes = await axios({ method, url: options.url, headers: { ...(options.headers || {}), Authorization: authHeader, 'Content-Type': 'application/json', }, data, }); return finalRes.data; } catch (error: any) { throw new Error(error?.message || 'Digest request failed'); } } }