import type { PluginDefinition } from '@yaakapp/api'; import { ntlm } from 'httpntlm'; function extractNtlmChallenge(headers: Array<{ name: string; value: string }>): string | null { const authValues = headers .filter((h) => h.name.toLowerCase() === 'www-authenticate') .flatMap((h) => h.value.split(',')) .map((v) => v.trim()) .filter(Boolean); return authValues.find((v) => /^NTLM\s+\S+/i.test(v)) ?? null; } export const plugin: PluginDefinition = { authentication: { name: 'windows', label: 'NTLM Auth', shortLabel: 'NTLM', args: [ { type: 'banner', color: 'info', inputs: [ { type: 'markdown', content: 'NTLM is still in beta. Please submit any issues to [Feedback](https://yaak.app/feedback).', }, ], }, { type: 'text', name: 'username', label: 'Username', optional: true, }, { type: 'text', name: 'password', label: 'Password', optional: true, password: true, }, { type: 'accordion', label: 'Advanced', inputs: [ { name: 'domain', label: 'Domain', type: 'text', optional: true }, { name: 'workstation', label: 'Workstation', type: 'text', optional: true }, ], }, ], async onApply(ctx, { values, method, url }) { const username = values.username ? String(values.username) : undefined; const password = values.password ? String(values.password) : undefined; const domain = values.domain ? String(values.domain) : undefined; const workstation = values.workstation ? String(values.workstation) : undefined; const options = { url, username, password, workstation, domain, }; const type1 = ntlm.createType1Message(options); const negotiateResponse = await ctx.httpRequest.send({ httpRequest: { method, url, headers: [ { name: 'Authorization', value: type1 }, { name: 'Connection', value: 'keep-alive' }, ], }, }); const ntlmChallenge = extractNtlmChallenge(negotiateResponse.headers); if (ntlmChallenge == null) { throw new Error('Unable to find NTLM challenge in WWW-Authenticate response headers'); } const type2 = ntlm.parseType2Message(ntlmChallenge, (err: Error | null) => { if (err != null) throw err; }); const type3 = ntlm.createType3Message(type2, options); return { setHeaders: [{ name: 'Authorization', value: type3 }] }; }, }, };