nest-digest-auth
Version:
Digest Authentication wrapper using Axios, compatible with NestJS
86 lines (73 loc) • 2.42 kB
text/typescript
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');
}
}
}