mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-30 06:02:00 +02:00
Merge main into proxy branch (formatting and docs)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@yaak/auth-ntlm",
|
||||
"displayName": "NTLM Authentication",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Authenticate requests using NTLM authentication",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mountain-loop/yaak.git",
|
||||
"directory": "plugins/auth-ntlm"
|
||||
},
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"test": "vitest --run tests"
|
||||
"test": "vp test --run tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"httpntlm": "^1.8.13"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { PluginDefinition } from '@yaakapp/api';
|
||||
import type { PluginDefinition } from "@yaakapp/api";
|
||||
|
||||
import { ntlm } from 'httpntlm';
|
||||
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(','))
|
||||
.filter((h) => h.name.toLowerCase() === "www-authenticate")
|
||||
.flatMap((h) => h.value.split(","))
|
||||
.map((v) => v.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
@@ -14,40 +14,40 @@ function extractNtlmChallenge(headers: Array<{ name: string; value: string }>):
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
authentication: {
|
||||
name: 'windows',
|
||||
label: 'NTLM Auth',
|
||||
shortLabel: 'NTLM',
|
||||
name: "windows",
|
||||
label: "NTLM Auth",
|
||||
shortLabel: "NTLM",
|
||||
args: [
|
||||
{
|
||||
type: 'banner',
|
||||
color: 'info',
|
||||
type: "banner",
|
||||
color: "info",
|
||||
inputs: [
|
||||
{
|
||||
type: 'markdown',
|
||||
type: "markdown",
|
||||
content:
|
||||
'NTLM is still in beta. Please submit any issues to [Feedback](https://yaak.app/feedback).',
|
||||
"NTLM is still in beta. Please submit any issues to [Feedback](https://yaak.app/feedback).",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
label: 'Username',
|
||||
type: "text",
|
||||
name: "username",
|
||||
label: "Username",
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
type: "text",
|
||||
name: "password",
|
||||
label: "Password",
|
||||
optional: true,
|
||||
password: true,
|
||||
},
|
||||
{
|
||||
type: 'accordion',
|
||||
label: 'Advanced',
|
||||
type: "accordion",
|
||||
label: "Advanced",
|
||||
inputs: [
|
||||
{ name: 'domain', label: 'Domain', type: 'text', optional: true },
|
||||
{ name: 'workstation', label: 'Workstation', type: 'text', optional: true },
|
||||
{ name: "domain", label: "Domain", type: "text", optional: true },
|
||||
{ name: "workstation", label: "Workstation", type: "text", optional: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -72,15 +72,15 @@ export const plugin: PluginDefinition = {
|
||||
method,
|
||||
url,
|
||||
headers: [
|
||||
{ name: 'Authorization', value: type1 },
|
||||
{ name: 'Connection', value: 'keep-alive' },
|
||||
{ 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');
|
||||
throw new Error("Unable to find NTLM challenge in WWW-Authenticate response headers");
|
||||
}
|
||||
|
||||
const type2 = ntlm.parseType2Message(ntlmChallenge, (err: Error | null) => {
|
||||
@@ -88,7 +88,7 @@ export const plugin: PluginDefinition = {
|
||||
});
|
||||
const type3 = ntlm.createType3Message(type2, options);
|
||||
|
||||
return { setHeaders: [{ name: 'Authorization', value: type3 }] };
|
||||
return { setHeaders: [{ name: "Authorization", value: type3 }] };
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
2
plugins/auth-ntlm/src/modules.d.ts
vendored
2
plugins/auth-ntlm/src/modules.d.ts
vendored
@@ -1 +1 @@
|
||||
declare module 'httpntlm';
|
||||
declare module "httpntlm";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import type { Context } from "@yaakapp/api";
|
||||
import { beforeEach, describe, expect, test, vi } from "vite-plus/test";
|
||||
|
||||
const ntlmMock = vi.hoisted(() => ({
|
||||
createType1Message: vi.fn(),
|
||||
@@ -7,25 +7,26 @@ const ntlmMock = vi.hoisted(() => ({
|
||||
createType3Message: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('httpntlm', () => ({ ntlm: ntlmMock }));
|
||||
vi.mock("httpntlm", () => ({ ntlm: ntlmMock }));
|
||||
|
||||
import { plugin } from '../src';
|
||||
import { plugin } from "../src";
|
||||
|
||||
describe('auth-ntlm', () => {
|
||||
describe("auth-ntlm", () => {
|
||||
beforeEach(() => {
|
||||
ntlmMock.createType1Message.mockReset();
|
||||
ntlmMock.parseType2Message.mockReset();
|
||||
ntlmMock.createType3Message.mockReset();
|
||||
ntlmMock.createType1Message.mockReturnValue('NTLM TYPE1');
|
||||
ntlmMock.createType1Message.mockReturnValue("NTLM TYPE1");
|
||||
// oxlint-disable-next-line no-explicit-any
|
||||
ntlmMock.parseType2Message.mockReturnValue({} as any);
|
||||
ntlmMock.createType3Message.mockReturnValue('NTLM TYPE3');
|
||||
ntlmMock.createType3Message.mockReturnValue("NTLM TYPE3");
|
||||
});
|
||||
|
||||
test('uses NTLM challenge when Negotiate and NTLM headers are separate', async () => {
|
||||
test("uses NTLM challenge when Negotiate and NTLM headers are separate", async () => {
|
||||
const send = vi.fn().mockResolvedValue({
|
||||
headers: [
|
||||
{ name: 'WWW-Authenticate', value: 'Negotiate' },
|
||||
{ name: 'WWW-Authenticate', value: 'NTLM TlRMTVNTUAACAAAAAA==' },
|
||||
{ name: "WWW-Authenticate", value: "Negotiate" },
|
||||
{ name: "WWW-Authenticate", value: "NTLM TlRMTVNTUAACAAAAAA==" },
|
||||
],
|
||||
});
|
||||
const ctx = { httpRequest: { send } } as unknown as Context;
|
||||
@@ -33,41 +34,41 @@ describe('auth-ntlm', () => {
|
||||
const result = await plugin.authentication?.onApply(ctx, {
|
||||
values: {},
|
||||
headers: [],
|
||||
url: 'https://example.local/resource',
|
||||
method: 'GET',
|
||||
contextId: 'ctx',
|
||||
url: "https://example.local/resource",
|
||||
method: "GET",
|
||||
contextId: "ctx",
|
||||
});
|
||||
|
||||
expect(ntlmMock.parseType2Message).toHaveBeenCalledWith(
|
||||
'NTLM TlRMTVNTUAACAAAAAA==',
|
||||
"NTLM TlRMTVNTUAACAAAAAA==",
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(result).toEqual({ setHeaders: [{ name: 'Authorization', value: 'NTLM TYPE3' }] });
|
||||
expect(result).toEqual({ setHeaders: [{ name: "Authorization", value: "NTLM TYPE3" }] });
|
||||
});
|
||||
|
||||
test('uses NTLM challenge when auth schemes are comma-separated in one header', async () => {
|
||||
test("uses NTLM challenge when auth schemes are comma-separated in one header", async () => {
|
||||
const send = vi.fn().mockResolvedValue({
|
||||
headers: [{ name: 'www-authenticate', value: 'Negotiate, NTLM TlRMTVNTUAACAAAAAA==' }],
|
||||
headers: [{ name: "www-authenticate", value: "Negotiate, NTLM TlRMTVNTUAACAAAAAA==" }],
|
||||
});
|
||||
const ctx = { httpRequest: { send } } as unknown as Context;
|
||||
|
||||
await plugin.authentication?.onApply(ctx, {
|
||||
values: {},
|
||||
headers: [],
|
||||
url: 'https://example.local/resource',
|
||||
method: 'GET',
|
||||
contextId: 'ctx',
|
||||
url: "https://example.local/resource",
|
||||
method: "GET",
|
||||
contextId: "ctx",
|
||||
});
|
||||
|
||||
expect(ntlmMock.parseType2Message).toHaveBeenCalledWith(
|
||||
'NTLM TlRMTVNTUAACAAAAAA==',
|
||||
"NTLM TlRMTVNTUAACAAAAAA==",
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
test('throws a clear error when NTLM challenge is missing', async () => {
|
||||
test("throws a clear error when NTLM challenge is missing", async () => {
|
||||
const send = vi.fn().mockResolvedValue({
|
||||
headers: [{ name: 'WWW-Authenticate', value: 'Negotiate' }],
|
||||
headers: [{ name: "WWW-Authenticate", value: "Negotiate" }],
|
||||
});
|
||||
const ctx = { httpRequest: { send } } as unknown as Context;
|
||||
|
||||
@@ -75,10 +76,10 @@ describe('auth-ntlm', () => {
|
||||
plugin.authentication?.onApply(ctx, {
|
||||
values: {},
|
||||
headers: [],
|
||||
url: 'https://example.local/resource',
|
||||
method: 'GET',
|
||||
contextId: 'ctx',
|
||||
url: "https://example.local/resource",
|
||||
method: "GET",
|
||||
contextId: "ctx",
|
||||
}),
|
||||
).rejects.toThrow('Unable to find NTLM challenge in WWW-Authenticate response headers');
|
||||
).rejects.toThrow("Unable to find NTLM challenge in WWW-Authenticate response headers");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user