@qulib/core 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/cli/index.js +47 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/schemas/config.schema.d.ts +357 -0
- package/dist/schemas/config.schema.d.ts.map +1 -1
- package/dist/schemas/config.schema.js +37 -0
- package/dist/schemas/gap-analysis.schema.d.ts +18 -18
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +1 -1
- package/dist/schemas/repo-analysis.schema.d.ts +6 -6
- package/dist/tools/auth-explorer.d.ts +3 -0
- package/dist/tools/auth-explorer.d.ts.map +1 -0
- package/dist/tools/auth-explorer.js +324 -0
- package/dist/tools/oauth-providers.d.ts +7 -0
- package/dist/tools/oauth-providers.d.ts.map +1 -0
- package/dist/tools/oauth-providers.js +21 -0
- package/dist/tools/user-providers.d.ts +15 -0
- package/dist/tools/user-providers.d.ts.map +1 -0
- package/dist/tools/user-providers.js +62 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,20 @@ qulib analyze --url https://app.example.com --auth-storage-state ./qulib-storage
|
|
|
55
55
|
|
|
56
56
|
The storage state is just a JSON file of cookies and localStorage — keep it private, treat it like a credential.
|
|
57
57
|
|
|
58
|
+
### Multi-path auth exploration (`explore-auth`)
|
|
59
|
+
|
|
60
|
+
For unfamiliar apps (especially enterprise SSO with several buttons), run **`qulib explore-auth --url <url>`** before `analyze`. The JSON lists every detected path (built-in OAuth names like Google/Clever, **heuristic** unknown buttons such as tenant-specific SSO labels, password forms, and magic-link copy) plus **`suggestedAgentBehavior`** for the agent.
|
|
61
|
+
|
|
62
|
+
Unknown SSO buttons include **`unrecognizedButtons`** with a hint. Teach this machine to recognize a label next time:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
qulib auth providers add --id scholastic-sync --label "Scholastic Sync" --pattern "scholastic sync"
|
|
66
|
+
qulib auth providers list
|
|
67
|
+
qulib auth providers remove --id scholastic-sync
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Patterns live in **`~/.qulib/providers.json`** (per user, not in the repo). Built-in public platforms stay in qulib’s curated list; tenant-specific names are never shipped as built-ins.
|
|
71
|
+
|
|
58
72
|
### Auth detection
|
|
59
73
|
|
|
60
74
|
To check what auth pattern a site uses before configuring anything:
|
package/dist/cli/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { z } from 'zod';
|
|
|
6
6
|
import { HarnessConfigSchema } from '../schemas/config.schema.js';
|
|
7
7
|
import { analyzeApp } from '../analyze.js';
|
|
8
8
|
import { detectAuth } from '../tools/auth-detector.js';
|
|
9
|
+
import { exploreAuth } from '../tools/auth-explorer.js';
|
|
9
10
|
const program = new Command();
|
|
10
11
|
const AnalyzeUrlSchema = z.string().url();
|
|
11
12
|
const FormLoginCliSchema = z.object({
|
|
@@ -166,6 +167,15 @@ program
|
|
|
166
167
|
submitSelector: options.submitSelector,
|
|
167
168
|
});
|
|
168
169
|
});
|
|
170
|
+
program
|
|
171
|
+
.command('explore-auth')
|
|
172
|
+
.description('Explore all sign-in paths (OAuth, forms, magic link) for agent-driven setup before analyze')
|
|
173
|
+
.requiredOption('--url <url>', 'URL of the app or login page')
|
|
174
|
+
.option('--timeout <ms>', 'Navigation timeout in ms', '20000')
|
|
175
|
+
.action(async (options) => {
|
|
176
|
+
const result = await exploreAuth(options.url, parseInt(options.timeout, 10));
|
|
177
|
+
console.log(JSON.stringify(result, null, 2));
|
|
178
|
+
});
|
|
169
179
|
program
|
|
170
180
|
.command('detect-auth')
|
|
171
181
|
.description('Detect the authentication pattern used by a deployed web app')
|
|
@@ -176,6 +186,43 @@ program
|
|
|
176
186
|
console.log(JSON.stringify(result, null, 2));
|
|
177
187
|
});
|
|
178
188
|
const authCmd = program.command('auth').description('Authentication helpers for scans');
|
|
189
|
+
const providersCmd = authCmd
|
|
190
|
+
.command('providers')
|
|
191
|
+
.description('User-local OAuth/SSO button patterns (~/.qulib/providers.json)');
|
|
192
|
+
providersCmd
|
|
193
|
+
.command('list')
|
|
194
|
+
.description('List user-local providers registered on this machine')
|
|
195
|
+
.action(async () => {
|
|
196
|
+
const { listUserProviders } = await import('../tools/user-providers.js');
|
|
197
|
+
const providers = listUserProviders();
|
|
198
|
+
console.log(JSON.stringify(providers, null, 2));
|
|
199
|
+
});
|
|
200
|
+
providersCmd
|
|
201
|
+
.command('add')
|
|
202
|
+
.description('Register a custom provider pattern (case-insensitive regex source)')
|
|
203
|
+
.requiredOption('--id <id>', 'Stable id (kebab-case), e.g. scholastic-sync')
|
|
204
|
+
.requiredOption('--label <label>', 'Human-readable label')
|
|
205
|
+
.requiredOption('--pattern <regex>', 'Regex source, e.g. scholastic sync')
|
|
206
|
+
.action(async (opts) => {
|
|
207
|
+
try {
|
|
208
|
+
new RegExp(opts.pattern, 'i');
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
throw new Error(`Invalid regex pattern: ${opts.pattern}`);
|
|
212
|
+
}
|
|
213
|
+
const { addUserProvider } = await import('../tools/user-providers.js');
|
|
214
|
+
addUserProvider({ id: opts.id, label: opts.label, pattern: opts.pattern });
|
|
215
|
+
console.log(`[qulib] Added provider "${opts.label}" (id: ${opts.id}) to ~/.qulib/providers.json`);
|
|
216
|
+
});
|
|
217
|
+
providersCmd
|
|
218
|
+
.command('remove')
|
|
219
|
+
.description('Remove a user-local provider by id')
|
|
220
|
+
.requiredOption('--id <id>', 'Provider id to remove')
|
|
221
|
+
.action(async (opts) => {
|
|
222
|
+
const { removeUserProvider } = await import('../tools/user-providers.js');
|
|
223
|
+
const removed = removeUserProvider(opts.id);
|
|
224
|
+
console.log(removed ? `[qulib] Removed "${opts.id}"` : `[qulib] No provider with id "${opts.id}" found`);
|
|
225
|
+
});
|
|
179
226
|
authCmd
|
|
180
227
|
.command('init')
|
|
181
228
|
.description('Open a browser, let the user log in manually, save the storage state to a file for reuse')
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { analyzeApp } from './analyze.js';
|
|
2
2
|
export { detectAuth } from './tools/auth-detector.js';
|
|
3
|
+
export { exploreAuth } from './tools/auth-explorer.js';
|
|
4
|
+
export { addUserProvider, removeUserProvider, listUserProviders } from './tools/user-providers.js';
|
|
3
5
|
export type { AnalyzeOptions, AnalyzeResult } from './analyze.js';
|
|
4
|
-
export type { HarnessConfig, AuthConfig, RouteInventory, GapAnalysis, RepoAnalysis, DetectedAuth, } from './schemas/index.js';
|
|
6
|
+
export type { HarnessConfig, AuthConfig, RouteInventory, GapAnalysis, RepoAnalysis, DetectedAuth, AuthExploration, AuthPath, AuthPathRequirements, } from './schemas/index.js';
|
|
5
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClE,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnG,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClE,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,oBAAoB,GACrB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -384,5 +384,362 @@ export declare const DetectedAuthSchema: z.ZodObject<{
|
|
|
384
384
|
recommendation: string;
|
|
385
385
|
}>;
|
|
386
386
|
export type DetectedAuth = z.infer<typeof DetectedAuthSchema>;
|
|
387
|
+
export declare const AuthPathRequirementsSchema: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
388
|
+
method: z.ZodLiteral<"storage-state">;
|
|
389
|
+
instruction: z.ZodString;
|
|
390
|
+
}, "strip", z.ZodTypeAny, {
|
|
391
|
+
method: "storage-state";
|
|
392
|
+
instruction: string;
|
|
393
|
+
}, {
|
|
394
|
+
method: "storage-state";
|
|
395
|
+
instruction: string;
|
|
396
|
+
}>, z.ZodObject<{
|
|
397
|
+
method: z.ZodLiteral<"credentials">;
|
|
398
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
399
|
+
name: z.ZodString;
|
|
400
|
+
label: z.ZodString;
|
|
401
|
+
type: z.ZodEnum<["text", "password", "email", "select", "checkbox"]>;
|
|
402
|
+
observedOptions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
403
|
+
}, "strip", z.ZodTypeAny, {
|
|
404
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
405
|
+
name: string;
|
|
406
|
+
label: string;
|
|
407
|
+
observedOptions: string[];
|
|
408
|
+
}, {
|
|
409
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
410
|
+
name: string;
|
|
411
|
+
label: string;
|
|
412
|
+
observedOptions?: string[] | undefined;
|
|
413
|
+
}>, "many">;
|
|
414
|
+
}, "strip", z.ZodTypeAny, {
|
|
415
|
+
method: "credentials";
|
|
416
|
+
fields: {
|
|
417
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
418
|
+
name: string;
|
|
419
|
+
label: string;
|
|
420
|
+
observedOptions: string[];
|
|
421
|
+
}[];
|
|
422
|
+
}, {
|
|
423
|
+
method: "credentials";
|
|
424
|
+
fields: {
|
|
425
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
426
|
+
name: string;
|
|
427
|
+
label: string;
|
|
428
|
+
observedOptions?: string[] | undefined;
|
|
429
|
+
}[];
|
|
430
|
+
}>, z.ZodObject<{
|
|
431
|
+
method: z.ZodLiteral<"unknown">;
|
|
432
|
+
instruction: z.ZodString;
|
|
433
|
+
}, "strip", z.ZodTypeAny, {
|
|
434
|
+
method: "unknown";
|
|
435
|
+
instruction: string;
|
|
436
|
+
}, {
|
|
437
|
+
method: "unknown";
|
|
438
|
+
instruction: string;
|
|
439
|
+
}>]>;
|
|
440
|
+
export declare const AuthPathSchema: z.ZodObject<{
|
|
441
|
+
id: z.ZodString;
|
|
442
|
+
label: z.ZodString;
|
|
443
|
+
type: z.ZodEnum<["oauth", "oauth-unknown", "form-login", "form-multi", "magic-link", "unknown"]>;
|
|
444
|
+
provider: z.ZodNullable<z.ZodString>;
|
|
445
|
+
source: z.ZodEnum<["built-in", "user-local", "heuristic"]>;
|
|
446
|
+
automatable: z.ZodBoolean;
|
|
447
|
+
confidence: z.ZodEnum<["high", "medium", "low"]>;
|
|
448
|
+
requirements: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
449
|
+
method: z.ZodLiteral<"storage-state">;
|
|
450
|
+
instruction: z.ZodString;
|
|
451
|
+
}, "strip", z.ZodTypeAny, {
|
|
452
|
+
method: "storage-state";
|
|
453
|
+
instruction: string;
|
|
454
|
+
}, {
|
|
455
|
+
method: "storage-state";
|
|
456
|
+
instruction: string;
|
|
457
|
+
}>, z.ZodObject<{
|
|
458
|
+
method: z.ZodLiteral<"credentials">;
|
|
459
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
460
|
+
name: z.ZodString;
|
|
461
|
+
label: z.ZodString;
|
|
462
|
+
type: z.ZodEnum<["text", "password", "email", "select", "checkbox"]>;
|
|
463
|
+
observedOptions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
464
|
+
}, "strip", z.ZodTypeAny, {
|
|
465
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
466
|
+
name: string;
|
|
467
|
+
label: string;
|
|
468
|
+
observedOptions: string[];
|
|
469
|
+
}, {
|
|
470
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
471
|
+
name: string;
|
|
472
|
+
label: string;
|
|
473
|
+
observedOptions?: string[] | undefined;
|
|
474
|
+
}>, "many">;
|
|
475
|
+
}, "strip", z.ZodTypeAny, {
|
|
476
|
+
method: "credentials";
|
|
477
|
+
fields: {
|
|
478
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
479
|
+
name: string;
|
|
480
|
+
label: string;
|
|
481
|
+
observedOptions: string[];
|
|
482
|
+
}[];
|
|
483
|
+
}, {
|
|
484
|
+
method: "credentials";
|
|
485
|
+
fields: {
|
|
486
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
487
|
+
name: string;
|
|
488
|
+
label: string;
|
|
489
|
+
observedOptions?: string[] | undefined;
|
|
490
|
+
}[];
|
|
491
|
+
}>, z.ZodObject<{
|
|
492
|
+
method: z.ZodLiteral<"unknown">;
|
|
493
|
+
instruction: z.ZodString;
|
|
494
|
+
}, "strip", z.ZodTypeAny, {
|
|
495
|
+
method: "unknown";
|
|
496
|
+
instruction: string;
|
|
497
|
+
}, {
|
|
498
|
+
method: "unknown";
|
|
499
|
+
instruction: string;
|
|
500
|
+
}>]>;
|
|
501
|
+
}, "strip", z.ZodTypeAny, {
|
|
502
|
+
type: "unknown" | "form-login" | "oauth" | "magic-link" | "oauth-unknown" | "form-multi";
|
|
503
|
+
provider: string | null;
|
|
504
|
+
label: string;
|
|
505
|
+
id: string;
|
|
506
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
507
|
+
automatable: boolean;
|
|
508
|
+
confidence: "high" | "medium" | "low";
|
|
509
|
+
requirements: {
|
|
510
|
+
method: "storage-state";
|
|
511
|
+
instruction: string;
|
|
512
|
+
} | {
|
|
513
|
+
method: "credentials";
|
|
514
|
+
fields: {
|
|
515
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
516
|
+
name: string;
|
|
517
|
+
label: string;
|
|
518
|
+
observedOptions: string[];
|
|
519
|
+
}[];
|
|
520
|
+
} | {
|
|
521
|
+
method: "unknown";
|
|
522
|
+
instruction: string;
|
|
523
|
+
};
|
|
524
|
+
}, {
|
|
525
|
+
type: "unknown" | "form-login" | "oauth" | "magic-link" | "oauth-unknown" | "form-multi";
|
|
526
|
+
provider: string | null;
|
|
527
|
+
label: string;
|
|
528
|
+
id: string;
|
|
529
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
530
|
+
automatable: boolean;
|
|
531
|
+
confidence: "high" | "medium" | "low";
|
|
532
|
+
requirements: {
|
|
533
|
+
method: "storage-state";
|
|
534
|
+
instruction: string;
|
|
535
|
+
} | {
|
|
536
|
+
method: "credentials";
|
|
537
|
+
fields: {
|
|
538
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
539
|
+
name: string;
|
|
540
|
+
label: string;
|
|
541
|
+
observedOptions?: string[] | undefined;
|
|
542
|
+
}[];
|
|
543
|
+
} | {
|
|
544
|
+
method: "unknown";
|
|
545
|
+
instruction: string;
|
|
546
|
+
};
|
|
547
|
+
}>;
|
|
548
|
+
export declare const AuthExplorationSchema: z.ZodObject<{
|
|
549
|
+
url: z.ZodString;
|
|
550
|
+
authRequired: z.ZodBoolean;
|
|
551
|
+
authScope: z.ZodEnum<["site-wide", "section-only", "optional", "none"]>;
|
|
552
|
+
authPaths: z.ZodArray<z.ZodObject<{
|
|
553
|
+
id: z.ZodString;
|
|
554
|
+
label: z.ZodString;
|
|
555
|
+
type: z.ZodEnum<["oauth", "oauth-unknown", "form-login", "form-multi", "magic-link", "unknown"]>;
|
|
556
|
+
provider: z.ZodNullable<z.ZodString>;
|
|
557
|
+
source: z.ZodEnum<["built-in", "user-local", "heuristic"]>;
|
|
558
|
+
automatable: z.ZodBoolean;
|
|
559
|
+
confidence: z.ZodEnum<["high", "medium", "low"]>;
|
|
560
|
+
requirements: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
561
|
+
method: z.ZodLiteral<"storage-state">;
|
|
562
|
+
instruction: z.ZodString;
|
|
563
|
+
}, "strip", z.ZodTypeAny, {
|
|
564
|
+
method: "storage-state";
|
|
565
|
+
instruction: string;
|
|
566
|
+
}, {
|
|
567
|
+
method: "storage-state";
|
|
568
|
+
instruction: string;
|
|
569
|
+
}>, z.ZodObject<{
|
|
570
|
+
method: z.ZodLiteral<"credentials">;
|
|
571
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
572
|
+
name: z.ZodString;
|
|
573
|
+
label: z.ZodString;
|
|
574
|
+
type: z.ZodEnum<["text", "password", "email", "select", "checkbox"]>;
|
|
575
|
+
observedOptions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
576
|
+
}, "strip", z.ZodTypeAny, {
|
|
577
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
578
|
+
name: string;
|
|
579
|
+
label: string;
|
|
580
|
+
observedOptions: string[];
|
|
581
|
+
}, {
|
|
582
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
583
|
+
name: string;
|
|
584
|
+
label: string;
|
|
585
|
+
observedOptions?: string[] | undefined;
|
|
586
|
+
}>, "many">;
|
|
587
|
+
}, "strip", z.ZodTypeAny, {
|
|
588
|
+
method: "credentials";
|
|
589
|
+
fields: {
|
|
590
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
591
|
+
name: string;
|
|
592
|
+
label: string;
|
|
593
|
+
observedOptions: string[];
|
|
594
|
+
}[];
|
|
595
|
+
}, {
|
|
596
|
+
method: "credentials";
|
|
597
|
+
fields: {
|
|
598
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
599
|
+
name: string;
|
|
600
|
+
label: string;
|
|
601
|
+
observedOptions?: string[] | undefined;
|
|
602
|
+
}[];
|
|
603
|
+
}>, z.ZodObject<{
|
|
604
|
+
method: z.ZodLiteral<"unknown">;
|
|
605
|
+
instruction: z.ZodString;
|
|
606
|
+
}, "strip", z.ZodTypeAny, {
|
|
607
|
+
method: "unknown";
|
|
608
|
+
instruction: string;
|
|
609
|
+
}, {
|
|
610
|
+
method: "unknown";
|
|
611
|
+
instruction: string;
|
|
612
|
+
}>]>;
|
|
613
|
+
}, "strip", z.ZodTypeAny, {
|
|
614
|
+
type: "unknown" | "form-login" | "oauth" | "magic-link" | "oauth-unknown" | "form-multi";
|
|
615
|
+
provider: string | null;
|
|
616
|
+
label: string;
|
|
617
|
+
id: string;
|
|
618
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
619
|
+
automatable: boolean;
|
|
620
|
+
confidence: "high" | "medium" | "low";
|
|
621
|
+
requirements: {
|
|
622
|
+
method: "storage-state";
|
|
623
|
+
instruction: string;
|
|
624
|
+
} | {
|
|
625
|
+
method: "credentials";
|
|
626
|
+
fields: {
|
|
627
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
628
|
+
name: string;
|
|
629
|
+
label: string;
|
|
630
|
+
observedOptions: string[];
|
|
631
|
+
}[];
|
|
632
|
+
} | {
|
|
633
|
+
method: "unknown";
|
|
634
|
+
instruction: string;
|
|
635
|
+
};
|
|
636
|
+
}, {
|
|
637
|
+
type: "unknown" | "form-login" | "oauth" | "magic-link" | "oauth-unknown" | "form-multi";
|
|
638
|
+
provider: string | null;
|
|
639
|
+
label: string;
|
|
640
|
+
id: string;
|
|
641
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
642
|
+
automatable: boolean;
|
|
643
|
+
confidence: "high" | "medium" | "low";
|
|
644
|
+
requirements: {
|
|
645
|
+
method: "storage-state";
|
|
646
|
+
instruction: string;
|
|
647
|
+
} | {
|
|
648
|
+
method: "credentials";
|
|
649
|
+
fields: {
|
|
650
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
651
|
+
name: string;
|
|
652
|
+
label: string;
|
|
653
|
+
observedOptions?: string[] | undefined;
|
|
654
|
+
}[];
|
|
655
|
+
} | {
|
|
656
|
+
method: "unknown";
|
|
657
|
+
instruction: string;
|
|
658
|
+
};
|
|
659
|
+
}>, "many">;
|
|
660
|
+
observedAt: z.ZodString;
|
|
661
|
+
suggestedAgentBehavior: z.ZodString;
|
|
662
|
+
unrecognizedButtons: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
663
|
+
label: z.ZodString;
|
|
664
|
+
hint: z.ZodString;
|
|
665
|
+
}, "strip", z.ZodTypeAny, {
|
|
666
|
+
label: string;
|
|
667
|
+
hint: string;
|
|
668
|
+
}, {
|
|
669
|
+
label: string;
|
|
670
|
+
hint: string;
|
|
671
|
+
}>, "many">>;
|
|
672
|
+
}, "strip", z.ZodTypeAny, {
|
|
673
|
+
url: string;
|
|
674
|
+
authRequired: boolean;
|
|
675
|
+
authScope: "none" | "site-wide" | "section-only" | "optional";
|
|
676
|
+
authPaths: {
|
|
677
|
+
type: "unknown" | "form-login" | "oauth" | "magic-link" | "oauth-unknown" | "form-multi";
|
|
678
|
+
provider: string | null;
|
|
679
|
+
label: string;
|
|
680
|
+
id: string;
|
|
681
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
682
|
+
automatable: boolean;
|
|
683
|
+
confidence: "high" | "medium" | "low";
|
|
684
|
+
requirements: {
|
|
685
|
+
method: "storage-state";
|
|
686
|
+
instruction: string;
|
|
687
|
+
} | {
|
|
688
|
+
method: "credentials";
|
|
689
|
+
fields: {
|
|
690
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
691
|
+
name: string;
|
|
692
|
+
label: string;
|
|
693
|
+
observedOptions: string[];
|
|
694
|
+
}[];
|
|
695
|
+
} | {
|
|
696
|
+
method: "unknown";
|
|
697
|
+
instruction: string;
|
|
698
|
+
};
|
|
699
|
+
}[];
|
|
700
|
+
observedAt: string;
|
|
701
|
+
suggestedAgentBehavior: string;
|
|
702
|
+
unrecognizedButtons: {
|
|
703
|
+
label: string;
|
|
704
|
+
hint: string;
|
|
705
|
+
}[];
|
|
706
|
+
}, {
|
|
707
|
+
url: string;
|
|
708
|
+
authRequired: boolean;
|
|
709
|
+
authScope: "none" | "site-wide" | "section-only" | "optional";
|
|
710
|
+
authPaths: {
|
|
711
|
+
type: "unknown" | "form-login" | "oauth" | "magic-link" | "oauth-unknown" | "form-multi";
|
|
712
|
+
provider: string | null;
|
|
713
|
+
label: string;
|
|
714
|
+
id: string;
|
|
715
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
716
|
+
automatable: boolean;
|
|
717
|
+
confidence: "high" | "medium" | "low";
|
|
718
|
+
requirements: {
|
|
719
|
+
method: "storage-state";
|
|
720
|
+
instruction: string;
|
|
721
|
+
} | {
|
|
722
|
+
method: "credentials";
|
|
723
|
+
fields: {
|
|
724
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
725
|
+
name: string;
|
|
726
|
+
label: string;
|
|
727
|
+
observedOptions?: string[] | undefined;
|
|
728
|
+
}[];
|
|
729
|
+
} | {
|
|
730
|
+
method: "unknown";
|
|
731
|
+
instruction: string;
|
|
732
|
+
};
|
|
733
|
+
}[];
|
|
734
|
+
observedAt: string;
|
|
735
|
+
suggestedAgentBehavior: string;
|
|
736
|
+
unrecognizedButtons?: {
|
|
737
|
+
label: string;
|
|
738
|
+
hint: string;
|
|
739
|
+
}[] | undefined;
|
|
740
|
+
}>;
|
|
741
|
+
export type AuthPathRequirements = z.infer<typeof AuthPathRequirementsSchema>;
|
|
742
|
+
export type AuthPath = z.infer<typeof AuthPathSchema>;
|
|
743
|
+
export type AuthExploration = z.infer<typeof AuthExplorationSchema>;
|
|
387
744
|
export {};
|
|
388
745
|
//# sourceMappingURL=config.schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/config.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;AACpD,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,aAAa,GAAG,mBAAmB,GAAG,KAAK,GAAG,eAAe,CAAC;AAEvG,QAAA,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBvB,CAAC;AAEH,QAAA,MAAM,sBAAsB;;;;;;;;;EAG1B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAA8E,CAAC;AAE5G,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACtE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC5E,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAe9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"config.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/config.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;AACpD,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,aAAa,GAAG,mBAAmB,GAAG,KAAK,GAAG,eAAe,CAAC;AAEvG,QAAA,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBvB,CAAC;AAEH,QAAA,MAAM,sBAAsB;;;;;;;;;EAG1B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAA8E,CAAC;AAE5G,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACtE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC5E,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAe9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAcrC,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASzB,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAehC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC9E,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
|
@@ -55,3 +55,40 @@ export const DetectedAuthSchema = z.object({
|
|
|
55
55
|
})),
|
|
56
56
|
recommendation: z.string(),
|
|
57
57
|
});
|
|
58
|
+
export const AuthPathRequirementsSchema = z.discriminatedUnion('method', [
|
|
59
|
+
z.object({ method: z.literal('storage-state'), instruction: z.string() }),
|
|
60
|
+
z.object({
|
|
61
|
+
method: z.literal('credentials'),
|
|
62
|
+
fields: z.array(z.object({
|
|
63
|
+
name: z.string(),
|
|
64
|
+
label: z.string(),
|
|
65
|
+
type: z.enum(['text', 'password', 'email', 'select', 'checkbox']),
|
|
66
|
+
observedOptions: z.array(z.string()).default([]),
|
|
67
|
+
})),
|
|
68
|
+
}),
|
|
69
|
+
z.object({ method: z.literal('unknown'), instruction: z.string() }),
|
|
70
|
+
]);
|
|
71
|
+
export const AuthPathSchema = z.object({
|
|
72
|
+
id: z.string(),
|
|
73
|
+
label: z.string(),
|
|
74
|
+
type: z.enum(['oauth', 'oauth-unknown', 'form-login', 'form-multi', 'magic-link', 'unknown']),
|
|
75
|
+
provider: z.string().nullable(),
|
|
76
|
+
source: z.enum(['built-in', 'user-local', 'heuristic']),
|
|
77
|
+
automatable: z.boolean(),
|
|
78
|
+
confidence: z.enum(['high', 'medium', 'low']),
|
|
79
|
+
requirements: AuthPathRequirementsSchema,
|
|
80
|
+
});
|
|
81
|
+
export const AuthExplorationSchema = z.object({
|
|
82
|
+
url: z.string(),
|
|
83
|
+
authRequired: z.boolean(),
|
|
84
|
+
authScope: z.enum(['site-wide', 'section-only', 'optional', 'none']),
|
|
85
|
+
authPaths: z.array(AuthPathSchema),
|
|
86
|
+
observedAt: z.string(),
|
|
87
|
+
suggestedAgentBehavior: z.string(),
|
|
88
|
+
unrecognizedButtons: z
|
|
89
|
+
.array(z.object({
|
|
90
|
+
label: z.string(),
|
|
91
|
+
hint: z.string(),
|
|
92
|
+
}))
|
|
93
|
+
.default([]),
|
|
94
|
+
});
|
|
@@ -23,13 +23,13 @@ export declare const FrameworkRecommendationSchema: z.ZodObject<{
|
|
|
23
23
|
reason: z.ZodString;
|
|
24
24
|
confidence: z.ZodEnum<["high", "medium", "low"]>;
|
|
25
25
|
}, "strip", z.ZodTypeAny, {
|
|
26
|
+
confidence: "high" | "medium" | "low";
|
|
26
27
|
reason: string;
|
|
27
28
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
28
|
-
confidence: "high" | "medium" | "low";
|
|
29
29
|
}, {
|
|
30
|
+
confidence: "high" | "medium" | "low";
|
|
30
31
|
reason: string;
|
|
31
32
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
32
|
-
confidence: "high" | "medium" | "low";
|
|
33
33
|
}>;
|
|
34
34
|
export declare const TestStepSchema: z.ZodObject<{
|
|
35
35
|
action: z.ZodEnum<["navigate", "click", "type", "assert-visible", "assert-hidden", "assert-text", "assert-disabled", "assert-count", "wait", "api-call"]>;
|
|
@@ -75,13 +75,13 @@ export declare const NeutralScenarioSchema: z.ZodObject<{
|
|
|
75
75
|
reason: z.ZodString;
|
|
76
76
|
confidence: z.ZodEnum<["high", "medium", "low"]>;
|
|
77
77
|
}, "strip", z.ZodTypeAny, {
|
|
78
|
+
confidence: "high" | "medium" | "low";
|
|
78
79
|
reason: string;
|
|
79
80
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
80
|
-
confidence: "high" | "medium" | "low";
|
|
81
81
|
}, {
|
|
82
|
+
confidence: "high" | "medium" | "low";
|
|
82
83
|
reason: string;
|
|
83
84
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
84
|
-
confidence: "high" | "medium" | "low";
|
|
85
85
|
}>, "many">;
|
|
86
86
|
sourceGapIds: z.ZodArray<z.ZodString, "many">;
|
|
87
87
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -97,9 +97,9 @@ export declare const NeutralScenarioSchema: z.ZodObject<{
|
|
|
97
97
|
}[];
|
|
98
98
|
tags: string[];
|
|
99
99
|
recommendations: {
|
|
100
|
+
confidence: "high" | "medium" | "low";
|
|
100
101
|
reason: string;
|
|
101
102
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
102
|
-
confidence: "high" | "medium" | "low";
|
|
103
103
|
}[];
|
|
104
104
|
sourceGapIds: string[];
|
|
105
105
|
targetComponent?: string | undefined;
|
|
@@ -116,9 +116,9 @@ export declare const NeutralScenarioSchema: z.ZodObject<{
|
|
|
116
116
|
}[];
|
|
117
117
|
tags: string[];
|
|
118
118
|
recommendations: {
|
|
119
|
+
confidence: "high" | "medium" | "low";
|
|
119
120
|
reason: string;
|
|
120
121
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
121
|
-
confidence: "high" | "medium" | "low";
|
|
122
122
|
}[];
|
|
123
123
|
sourceGapIds: string[];
|
|
124
124
|
targetComponent?: string | undefined;
|
|
@@ -132,17 +132,17 @@ export declare const GeneratedTestSchema: z.ZodObject<{
|
|
|
132
132
|
outputPath: z.ZodString;
|
|
133
133
|
}, "strip", z.ZodTypeAny, {
|
|
134
134
|
code: string;
|
|
135
|
+
source: "llm" | "template";
|
|
135
136
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
136
137
|
scenarioId: string;
|
|
137
138
|
filename: string;
|
|
138
|
-
source: "llm" | "template";
|
|
139
139
|
outputPath: string;
|
|
140
140
|
}, {
|
|
141
141
|
code: string;
|
|
142
|
+
source: "llm" | "template";
|
|
142
143
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
143
144
|
scenarioId: string;
|
|
144
145
|
filename: string;
|
|
145
|
-
source: "llm" | "template";
|
|
146
146
|
outputPath: string;
|
|
147
147
|
}>;
|
|
148
148
|
export declare const GapAnalysisSchema: z.ZodObject<{
|
|
@@ -199,13 +199,13 @@ export declare const GapAnalysisSchema: z.ZodObject<{
|
|
|
199
199
|
reason: z.ZodString;
|
|
200
200
|
confidence: z.ZodEnum<["high", "medium", "low"]>;
|
|
201
201
|
}, "strip", z.ZodTypeAny, {
|
|
202
|
+
confidence: "high" | "medium" | "low";
|
|
202
203
|
reason: string;
|
|
203
204
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
204
|
-
confidence: "high" | "medium" | "low";
|
|
205
205
|
}, {
|
|
206
|
+
confidence: "high" | "medium" | "low";
|
|
206
207
|
reason: string;
|
|
207
208
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
208
|
-
confidence: "high" | "medium" | "low";
|
|
209
209
|
}>, "many">;
|
|
210
210
|
sourceGapIds: z.ZodArray<z.ZodString, "many">;
|
|
211
211
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -221,9 +221,9 @@ export declare const GapAnalysisSchema: z.ZodObject<{
|
|
|
221
221
|
}[];
|
|
222
222
|
tags: string[];
|
|
223
223
|
recommendations: {
|
|
224
|
+
confidence: "high" | "medium" | "low";
|
|
224
225
|
reason: string;
|
|
225
226
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
226
|
-
confidence: "high" | "medium" | "low";
|
|
227
227
|
}[];
|
|
228
228
|
sourceGapIds: string[];
|
|
229
229
|
targetComponent?: string | undefined;
|
|
@@ -240,9 +240,9 @@ export declare const GapAnalysisSchema: z.ZodObject<{
|
|
|
240
240
|
}[];
|
|
241
241
|
tags: string[];
|
|
242
242
|
recommendations: {
|
|
243
|
+
confidence: "high" | "medium" | "low";
|
|
243
244
|
reason: string;
|
|
244
245
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
245
|
-
confidence: "high" | "medium" | "low";
|
|
246
246
|
}[];
|
|
247
247
|
sourceGapIds: string[];
|
|
248
248
|
targetComponent?: string | undefined;
|
|
@@ -256,17 +256,17 @@ export declare const GapAnalysisSchema: z.ZodObject<{
|
|
|
256
256
|
outputPath: z.ZodString;
|
|
257
257
|
}, "strip", z.ZodTypeAny, {
|
|
258
258
|
code: string;
|
|
259
|
+
source: "llm" | "template";
|
|
259
260
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
260
261
|
scenarioId: string;
|
|
261
262
|
filename: string;
|
|
262
|
-
source: "llm" | "template";
|
|
263
263
|
outputPath: string;
|
|
264
264
|
}, {
|
|
265
265
|
code: string;
|
|
266
|
+
source: "llm" | "template";
|
|
266
267
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
267
268
|
scenarioId: string;
|
|
268
269
|
filename: string;
|
|
269
|
-
source: "llm" | "template";
|
|
270
270
|
outputPath: string;
|
|
271
271
|
}>, "many">;
|
|
272
272
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -295,19 +295,19 @@ export declare const GapAnalysisSchema: z.ZodObject<{
|
|
|
295
295
|
}[];
|
|
296
296
|
tags: string[];
|
|
297
297
|
recommendations: {
|
|
298
|
+
confidence: "high" | "medium" | "low";
|
|
298
299
|
reason: string;
|
|
299
300
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
300
|
-
confidence: "high" | "medium" | "low";
|
|
301
301
|
}[];
|
|
302
302
|
sourceGapIds: string[];
|
|
303
303
|
targetComponent?: string | undefined;
|
|
304
304
|
}[];
|
|
305
305
|
generatedTests: {
|
|
306
306
|
code: string;
|
|
307
|
+
source: "llm" | "template";
|
|
307
308
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
308
309
|
scenarioId: string;
|
|
309
310
|
filename: string;
|
|
310
|
-
source: "llm" | "template";
|
|
311
311
|
outputPath: string;
|
|
312
312
|
}[];
|
|
313
313
|
coverageWarning?: "auth-required" | "budget-exceeded" | "low-coverage" | "navigation-failures" | undefined;
|
|
@@ -337,19 +337,19 @@ export declare const GapAnalysisSchema: z.ZodObject<{
|
|
|
337
337
|
}[];
|
|
338
338
|
tags: string[];
|
|
339
339
|
recommendations: {
|
|
340
|
+
confidence: "high" | "medium" | "low";
|
|
340
341
|
reason: string;
|
|
341
342
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
342
|
-
confidence: "high" | "medium" | "low";
|
|
343
343
|
}[];
|
|
344
344
|
sourceGapIds: string[];
|
|
345
345
|
targetComponent?: string | undefined;
|
|
346
346
|
}[];
|
|
347
347
|
generatedTests: {
|
|
348
348
|
code: string;
|
|
349
|
+
source: "llm" | "template";
|
|
349
350
|
adapter: "playwright" | "cypress-e2e" | "cypress-component" | "api" | "accessibility";
|
|
350
351
|
scenarioId: string;
|
|
351
352
|
filename: string;
|
|
352
|
-
source: "llm" | "template";
|
|
353
353
|
outputPath: string;
|
|
354
354
|
}[];
|
|
355
355
|
coverageWarning?: "auth-required" | "budget-exceeded" | "low-coverage" | "navigation-failures" | undefined;
|
package/dist/schemas/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { HarnessConfigSchema, AuthConfigSchema, DetectedAuthSchema, type ExplorerType, type AdapterType, type FormLoginAuthConfig, type StorageStateAuthConfig, type AuthConfig, type HarnessConfig, type DetectedAuth, } from './config.schema.js';
|
|
1
|
+
export { HarnessConfigSchema, AuthConfigSchema, DetectedAuthSchema, AuthPathRequirementsSchema, AuthPathSchema, AuthExplorationSchema, type ExplorerType, type AdapterType, type FormLoginAuthConfig, type StorageStateAuthConfig, type AuthConfig, type HarnessConfig, type DetectedAuth, type AuthPathRequirements, type AuthPath, type AuthExploration, } from './config.schema.js';
|
|
2
2
|
export { DecisionLogEntrySchema, type DecisionLogEntry, } from './decision-log.schema.js';
|
|
3
3
|
export { RouteInventorySchema, RouteSchema, A11yViolationSchema, BrokenLinkSchema, type RouteInventory, type Route, } from './route-inventory.schema.js';
|
|
4
4
|
export { GapAnalysisSchema, GapSchema, NeutralScenarioSchema, GeneratedTestSchema, TestStepSchema, FrameworkRecommendationSchema, type GapAnalysis, type Gap, type NeutralScenario, type GeneratedTest, type TestStep, type FrameworkRecommendation, } from './gap-analysis.schema.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,0BAA0B,EAC1B,cAAc,EACd,qBAAqB,EACrB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,QAAQ,EACb,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,sBAAsB,EACtB,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,KAAK,GACX,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,iBAAiB,EACjB,SAAS,EACT,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,6BAA6B,EAC7B,KAAK,WAAW,EAChB,KAAK,GAAG,EACR,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,uBAAuB,GAC7B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,KAAK,YAAY,GAClB,MAAM,2BAA2B,CAAC"}
|
package/dist/schemas/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { HarnessConfigSchema, AuthConfigSchema, DetectedAuthSchema, } from './config.schema.js';
|
|
1
|
+
export { HarnessConfigSchema, AuthConfigSchema, DetectedAuthSchema, AuthPathRequirementsSchema, AuthPathSchema, AuthExplorationSchema, } from './config.schema.js';
|
|
2
2
|
export { DecisionLogEntrySchema, } from './decision-log.schema.js';
|
|
3
3
|
export { RouteInventorySchema, RouteSchema, A11yViolationSchema, BrokenLinkSchema, } from './route-inventory.schema.js';
|
|
4
4
|
export { GapAnalysisSchema, GapSchema, NeutralScenarioSchema, GeneratedTestSchema, TestStepSchema, FrameworkRecommendationSchema, } from './gap-analysis.schema.js';
|
|
@@ -5,12 +5,12 @@ export declare const RepoRouteSchema: z.ZodObject<{
|
|
|
5
5
|
method: z.ZodEnum<["GET", "POST", "PUT", "DELETE", "PATCH", "unknown"]>;
|
|
6
6
|
}, "strip", z.ZodTypeAny, {
|
|
7
7
|
path: string;
|
|
8
|
-
file: string;
|
|
9
8
|
method: "unknown" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
9
|
+
file: string;
|
|
10
10
|
}, {
|
|
11
11
|
path: string;
|
|
12
|
-
file: string;
|
|
13
12
|
method: "unknown" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
13
|
+
file: string;
|
|
14
14
|
}>;
|
|
15
15
|
export declare const TestFileSchema: z.ZodObject<{
|
|
16
16
|
file: z.ZodString;
|
|
@@ -62,12 +62,12 @@ export declare const RepoAnalysisSchema: z.ZodObject<{
|
|
|
62
62
|
method: z.ZodEnum<["GET", "POST", "PUT", "DELETE", "PATCH", "unknown"]>;
|
|
63
63
|
}, "strip", z.ZodTypeAny, {
|
|
64
64
|
path: string;
|
|
65
|
-
file: string;
|
|
66
65
|
method: "unknown" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
66
|
+
file: string;
|
|
67
67
|
}, {
|
|
68
68
|
path: string;
|
|
69
|
-
file: string;
|
|
70
69
|
method: "unknown" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
70
|
+
file: string;
|
|
71
71
|
}>, "many">;
|
|
72
72
|
testFiles: z.ZodArray<z.ZodObject<{
|
|
73
73
|
file: z.ZodString;
|
|
@@ -115,8 +115,8 @@ export declare const RepoAnalysisSchema: z.ZodObject<{
|
|
|
115
115
|
scannedAt: string;
|
|
116
116
|
routes: {
|
|
117
117
|
path: string;
|
|
118
|
-
file: string;
|
|
119
118
|
method: "unknown" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
119
|
+
file: string;
|
|
120
120
|
}[];
|
|
121
121
|
repoPath: string;
|
|
122
122
|
testFiles: {
|
|
@@ -139,8 +139,8 @@ export declare const RepoAnalysisSchema: z.ZodObject<{
|
|
|
139
139
|
scannedAt: string;
|
|
140
140
|
routes: {
|
|
141
141
|
path: string;
|
|
142
|
-
file: string;
|
|
143
142
|
method: "unknown" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
143
|
+
file: string;
|
|
144
144
|
}[];
|
|
145
145
|
repoPath: string;
|
|
146
146
|
testFiles: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-explorer.d.ts","sourceRoot":"","sources":["../../src/tools/auth-explorer.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,eAAe,EAGrB,MAAM,6BAA6B,CAAC;AA6MrC,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,CA2J1F"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { AuthExplorationSchema, } from '../schemas/config.schema.js';
|
|
2
|
+
import { launchBrowser } from './browser.js';
|
|
3
|
+
import { BUILT_IN_OAUTH_PROVIDERS } from './oauth-providers.js';
|
|
4
|
+
import { loadUserProviders } from './user-providers.js';
|
|
5
|
+
const MAGIC_LINK_PATTERNS = [
|
|
6
|
+
/email me a (sign[- ]?in )?link/i,
|
|
7
|
+
/sign in with email/i,
|
|
8
|
+
/passwordless/i,
|
|
9
|
+
/we'll send you a link/i,
|
|
10
|
+
];
|
|
11
|
+
async function waitNetworkIdleBestEffort(page) {
|
|
12
|
+
try {
|
|
13
|
+
await page.waitForLoadState('networkidle', { timeout: 5000 });
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// best-effort
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function textLooksLikeOAuthIdpButton(text) {
|
|
20
|
+
const t = text.trim();
|
|
21
|
+
if (t.length === 0 || t.length > 120) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return (/\b(sign in with|log in with|continue with|sign up with)\b/i.test(t) ||
|
|
25
|
+
/^(github|google|microsoft|apple)$/i.test(t));
|
|
26
|
+
}
|
|
27
|
+
function slugifyLabel(text) {
|
|
28
|
+
const s = text
|
|
29
|
+
.trim()
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.replace(/\s+/g, '-')
|
|
32
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
33
|
+
.slice(0, 48);
|
|
34
|
+
return s.length > 0 ? s : 'unknown';
|
|
35
|
+
}
|
|
36
|
+
function onLoginishPage(url) {
|
|
37
|
+
return /login|sign[- ]?in|auth|sso|oauth/i.test(new URL(url).pathname + new URL(url).hostname);
|
|
38
|
+
}
|
|
39
|
+
function isHeuristicUnknownSso(text, loginish) {
|
|
40
|
+
const t = text.trim();
|
|
41
|
+
if (!loginish || t.length < 3 || t.length > 80) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (/^(submit|cancel|back|close|next|skip|help|faq)$/i.test(t)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (/\b(sign in with|log in with|continue with)\b/i.test(t)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (/\b(sync|sso|portal|workspace|federation)\b/i.test(t)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
function storageRequirement() {
|
|
56
|
+
return {
|
|
57
|
+
method: 'storage-state',
|
|
58
|
+
instruction: 'OAuth and most SSO flows cannot be scripted. Run `qulib auth init --base-url <app-url>` on this machine, then pass the saved storage state JSON to `analyze` or MCP `analyze_app` as `auth: { type: "storage-state", path: "..." }`.',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function collectVisibleControlTexts(page) {
|
|
62
|
+
return page.evaluate(() => {
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
const out = [];
|
|
65
|
+
const nodes = document.querySelectorAll('button, a[href], [role="button"]');
|
|
66
|
+
for (const el of nodes) {
|
|
67
|
+
const t = (el.textContent ?? '').trim().replace(/\s+/g, ' ');
|
|
68
|
+
if (!t || t.length > 120) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const style = window.getComputedStyle(el);
|
|
72
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (!seen.has(t)) {
|
|
76
|
+
seen.add(t);
|
|
77
|
+
out.push(t);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function buildAllProviders() {
|
|
84
|
+
const builtIn = BUILT_IN_OAUTH_PROVIDERS.map((p) => ({ ...p, source: 'built-in' }));
|
|
85
|
+
const user = loadUserProviders().map((p) => ({ ...p, source: 'user-local' }));
|
|
86
|
+
return [...builtIn, ...user];
|
|
87
|
+
}
|
|
88
|
+
function matchProvider(text, p) {
|
|
89
|
+
return p.patterns.some((re) => re.test(text));
|
|
90
|
+
}
|
|
91
|
+
function oauthConfidence(source, loginish) {
|
|
92
|
+
if (source === 'user-local') {
|
|
93
|
+
return 'high';
|
|
94
|
+
}
|
|
95
|
+
if (source === 'built-in' && loginish) {
|
|
96
|
+
return 'high';
|
|
97
|
+
}
|
|
98
|
+
if (source === 'built-in') {
|
|
99
|
+
return 'medium';
|
|
100
|
+
}
|
|
101
|
+
return 'low';
|
|
102
|
+
}
|
|
103
|
+
async function buildFormPaths(page) {
|
|
104
|
+
const passwordCount = await page.locator('input[type="password"]').count();
|
|
105
|
+
if (passwordCount === 0) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
const formType = passwordCount > 1 ? 'form-multi' : 'form-login';
|
|
109
|
+
const fields = await page.evaluate(() => {
|
|
110
|
+
const pwd = document.querySelector('input[type="password"]');
|
|
111
|
+
if (!pwd) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const form = pwd.closest('form') ?? document.body;
|
|
115
|
+
const out = [];
|
|
116
|
+
const inputs = form.querySelectorAll('input, select, textarea');
|
|
117
|
+
for (const el of inputs) {
|
|
118
|
+
if (!(el instanceof HTMLElement)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const tag = el.tagName.toLowerCase();
|
|
122
|
+
if (tag === 'input') {
|
|
123
|
+
const inp = el;
|
|
124
|
+
const t = (inp.type || 'text').toLowerCase();
|
|
125
|
+
if (['hidden', 'submit', 'button', 'image', 'reset'].includes(t)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
let fieldType = 'text';
|
|
129
|
+
if (t === 'password') {
|
|
130
|
+
fieldType = 'password';
|
|
131
|
+
}
|
|
132
|
+
else if (t === 'email') {
|
|
133
|
+
fieldType = 'email';
|
|
134
|
+
}
|
|
135
|
+
else if (t === 'checkbox') {
|
|
136
|
+
fieldType = 'checkbox';
|
|
137
|
+
}
|
|
138
|
+
const id = inp.id;
|
|
139
|
+
let label = inp.getAttribute('aria-label') ?? inp.placeholder ?? inp.name ?? fieldType;
|
|
140
|
+
if (id) {
|
|
141
|
+
const lab = document.querySelector(`label[for="${CSS.escape(id)}"]`);
|
|
142
|
+
if (lab?.textContent) {
|
|
143
|
+
label = lab.textContent.trim();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
out.push({
|
|
147
|
+
name: inp.name || inp.id || fieldType,
|
|
148
|
+
label: label.slice(0, 120),
|
|
149
|
+
type: fieldType,
|
|
150
|
+
observedOptions: [],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else if (tag === 'select') {
|
|
154
|
+
const sel = el;
|
|
155
|
+
const opts = Array.from(sel.options).map((o) => o.text.trim()).filter(Boolean);
|
|
156
|
+
out.push({
|
|
157
|
+
name: sel.name || sel.id || 'select',
|
|
158
|
+
label: (sel.getAttribute('aria-label') ?? sel.name ?? 'select').slice(0, 120),
|
|
159
|
+
type: 'select',
|
|
160
|
+
observedOptions: opts.slice(0, 50),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
});
|
|
166
|
+
const requirements = fields.length > 0
|
|
167
|
+
? { method: 'credentials', fields }
|
|
168
|
+
: {
|
|
169
|
+
method: 'unknown',
|
|
170
|
+
instruction: 'A password field exists but field metadata could not be read. Inspect the page in devtools and configure form-login selectors manually, or use `qulib auth init`.',
|
|
171
|
+
};
|
|
172
|
+
return [
|
|
173
|
+
{
|
|
174
|
+
id: formType === 'form-multi' ? 'form-multi' : 'form-login',
|
|
175
|
+
label: formType === 'form-multi' ? 'Multi-field sign-in form' : 'Username / password form',
|
|
176
|
+
type: formType,
|
|
177
|
+
provider: null,
|
|
178
|
+
source: 'heuristic',
|
|
179
|
+
automatable: requirements.method === 'credentials',
|
|
180
|
+
confidence: requirements.method === 'credentials' ? 'medium' : 'low',
|
|
181
|
+
requirements,
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
export async function exploreAuth(url, timeoutMs = 20000) {
|
|
186
|
+
const browser = await launchBrowser();
|
|
187
|
+
try {
|
|
188
|
+
const context = await browser.newContext();
|
|
189
|
+
const page = await context.newPage();
|
|
190
|
+
await page.goto(url, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
|
|
191
|
+
await waitNetworkIdleBestEffort(page);
|
|
192
|
+
const loginishAfterFirst = /login|sign[- ]?in|auth/i.test(page.url()) || (await page.locator('input[type="password"]').count()) > 0;
|
|
193
|
+
if (!loginishAfterFirst) {
|
|
194
|
+
const loginLink = page.locator('a').filter({ hasText: /^(log ?in|sign ?in|sign in)$/i }).first();
|
|
195
|
+
if ((await loginLink.count()) > 0) {
|
|
196
|
+
const href = await loginLink.getAttribute('href');
|
|
197
|
+
if (href) {
|
|
198
|
+
const next = new URL(href, url).toString();
|
|
199
|
+
await page.goto(next, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
|
|
200
|
+
await waitNetworkIdleBestEffort(page);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const finalUrl = page.url();
|
|
205
|
+
const loginish = onLoginishPage(finalUrl) || (await page.locator('input[type="password"]').count()) > 0;
|
|
206
|
+
const allProviders = buildAllProviders();
|
|
207
|
+
const texts = await collectVisibleControlTexts(page);
|
|
208
|
+
const consumed = new Set();
|
|
209
|
+
const authPaths = [];
|
|
210
|
+
const unrecognizedButtons = [];
|
|
211
|
+
for (const rawText of texts) {
|
|
212
|
+
const text = rawText.trim();
|
|
213
|
+
if (!text) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
let matched = null;
|
|
217
|
+
for (const p of allProviders) {
|
|
218
|
+
if (!matchProvider(text, p)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (p.source === 'built-in' && !(textLooksLikeOAuthIdpButton(text) || loginish)) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
matched = { p, gate: textLooksLikeOAuthIdpButton(text) || loginish };
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
if (matched) {
|
|
228
|
+
const { p, gate } = matched;
|
|
229
|
+
const id = `oauth:${p.id}`;
|
|
230
|
+
if (consumed.has(id)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
consumed.add(id);
|
|
234
|
+
authPaths.push({
|
|
235
|
+
id,
|
|
236
|
+
label: p.label,
|
|
237
|
+
type: 'oauth',
|
|
238
|
+
provider: p.id,
|
|
239
|
+
source: p.source,
|
|
240
|
+
automatable: false,
|
|
241
|
+
confidence: oauthConfidence(p.source, loginish || gate),
|
|
242
|
+
requirements: storageRequirement(),
|
|
243
|
+
});
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (isHeuristicUnknownSso(text, loginish)) {
|
|
247
|
+
const slug = slugifyLabel(text);
|
|
248
|
+
const id = `oauth-unknown:${slug}`;
|
|
249
|
+
if (consumed.has(id)) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
consumed.add(id);
|
|
253
|
+
authPaths.push({
|
|
254
|
+
id,
|
|
255
|
+
label: text.slice(0, 100),
|
|
256
|
+
type: 'oauth-unknown',
|
|
257
|
+
provider: null,
|
|
258
|
+
source: 'heuristic',
|
|
259
|
+
automatable: false,
|
|
260
|
+
confidence: 'low',
|
|
261
|
+
requirements: storageRequirement(),
|
|
262
|
+
});
|
|
263
|
+
const safePattern = text.slice(0, 48).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
264
|
+
unrecognizedButtons.push({
|
|
265
|
+
label: text.slice(0, 100),
|
|
266
|
+
hint: `If this is your org SSO, register it: qulib auth providers add --id "${slug}" --label "${text.replace(/"/g, '\\"').slice(0, 80)}" --pattern "${safePattern}"`,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const pageText = await page.locator('body').innerText().catch(() => '');
|
|
271
|
+
if (MAGIC_LINK_PATTERNS.some((re) => re.test(pageText))) {
|
|
272
|
+
authPaths.push({
|
|
273
|
+
id: 'magic-link',
|
|
274
|
+
label: 'Magic link / passwordless',
|
|
275
|
+
type: 'magic-link',
|
|
276
|
+
provider: null,
|
|
277
|
+
source: 'heuristic',
|
|
278
|
+
automatable: false,
|
|
279
|
+
confidence: 'medium',
|
|
280
|
+
requirements: {
|
|
281
|
+
method: 'storage-state',
|
|
282
|
+
instruction: 'Magic-link flows need a human in the loop. Use `qulib auth init --base-url <app-url>` and complete email or provider steps in the opened browser, then reuse the saved storage state for scans.',
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
authPaths.push(...(await buildFormPaths(page)));
|
|
287
|
+
const authRequired = authPaths.length > 0;
|
|
288
|
+
let authScope = 'none';
|
|
289
|
+
if (authRequired) {
|
|
290
|
+
if (loginish) {
|
|
291
|
+
authScope = 'site-wide';
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
authScope = /login|signin|auth/i.test(new URL(finalUrl).pathname) ? 'site-wide' : 'optional';
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const suggestedParts = [];
|
|
298
|
+
if (authPaths.some((p) => p.type === 'oauth' || p.type === 'oauth-unknown')) {
|
|
299
|
+
suggestedParts.push('For OAuth or unrecognized SSO buttons, collect a Playwright storage state with `qulib auth init` before calling `analyze_app`.');
|
|
300
|
+
}
|
|
301
|
+
if (authPaths.some((p) => p.type === 'form-login' || p.type === 'form-multi')) {
|
|
302
|
+
suggestedParts.push('For password forms, gather username/password and stable selectors (or use storage state if MFA applies).');
|
|
303
|
+
}
|
|
304
|
+
if (authPaths.some((p) => p.type === 'magic-link')) {
|
|
305
|
+
suggestedParts.push('For magic-link, use `qulib auth init` after the user completes email delivery.');
|
|
306
|
+
}
|
|
307
|
+
if (!authRequired) {
|
|
308
|
+
suggestedParts.push('No sign-in surface detected at this URL; you can run `analyze_app` without auth unless gated deeper in the app.');
|
|
309
|
+
}
|
|
310
|
+
const exploration = {
|
|
311
|
+
url: finalUrl,
|
|
312
|
+
authRequired,
|
|
313
|
+
authScope,
|
|
314
|
+
authPaths,
|
|
315
|
+
observedAt: new Date().toISOString(),
|
|
316
|
+
suggestedAgentBehavior: suggestedParts.join(' '),
|
|
317
|
+
unrecognizedButtons,
|
|
318
|
+
};
|
|
319
|
+
return AuthExplorationSchema.parse(exploration);
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
await browser.close();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-providers.d.ts","sourceRoot":"","sources":["../../src/tools/oauth-providers.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,eAAO,MAAM,wBAAwB,EAAE,aAAa,EAsBnD,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const BUILT_IN_OAUTH_PROVIDERS = [
|
|
2
|
+
{ id: 'github', label: 'GitHub', patterns: [/\bgithub\b/i] },
|
|
3
|
+
{ id: 'google', label: 'Google', patterns: [/\bgoogle\b/i, /accounts\.google\.com/i] },
|
|
4
|
+
{ id: 'microsoft', label: 'Microsoft', patterns: [/microsoft/i, /login\.microsoftonline\.com/i] },
|
|
5
|
+
{ id: 'apple', label: 'Apple', patterns: [/sign in with apple/i, /\bapple id\b/i] },
|
|
6
|
+
{ id: 'facebook', label: 'Facebook', patterns: [/facebook/i] },
|
|
7
|
+
{ id: 'twitter', label: 'Twitter/X', patterns: [/twitter\.com/i, /\bsign in with x\b/i] },
|
|
8
|
+
{ id: 'linkedin', label: 'LinkedIn', patterns: [/linkedin/i] },
|
|
9
|
+
{ id: 'auth0', label: 'Auth0', patterns: [/auth0/i] },
|
|
10
|
+
{ id: 'okta', label: 'Okta', patterns: [/\bokta\b/i] },
|
|
11
|
+
{ id: 'onelogin', label: 'OneLogin', patterns: [/onelogin/i] },
|
|
12
|
+
{ id: 'duo', label: 'Duo Security', patterns: [/duo security/i] },
|
|
13
|
+
{ id: 'ping', label: 'Ping Identity', patterns: [/pingidentity/i, /pingone/i] },
|
|
14
|
+
{ id: 'workday', label: 'Workday', patterns: [/workday/i] },
|
|
15
|
+
{ id: 'saml', label: 'SAML SSO', patterns: [/\bsaml\b/i] },
|
|
16
|
+
{ id: 'clever', label: 'Clever', patterns: [/\bclever\b/i, /clever\.com/i] },
|
|
17
|
+
{ id: 'classlink', label: 'ClassLink', patterns: [/classlink/i] },
|
|
18
|
+
{ id: 'schoology', label: 'Schoology', patterns: [/schoology/i] },
|
|
19
|
+
{ id: 'canvas', label: 'Canvas (Instructure)', patterns: [/\bcanvas lms\b/i, /instructure/i] },
|
|
20
|
+
{ id: 'blackboard', label: 'Blackboard', patterns: [/blackboard/i] },
|
|
21
|
+
];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OAuthProvider } from './oauth-providers.js';
|
|
2
|
+
export interface SerializedProvider {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
patterns: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function loadUserProviders(): OAuthProvider[];
|
|
8
|
+
export declare function addUserProvider(input: {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
pattern: string;
|
|
12
|
+
}): void;
|
|
13
|
+
export declare function removeUserProvider(id: string): boolean;
|
|
14
|
+
export declare function listUserProviders(): SerializedProvider[];
|
|
15
|
+
//# sourceMappingURL=user-providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-providers.d.ts","sourceRoot":"","sources":["../../src/tools/user-providers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,iBAAiB,IAAI,aAAa,EAAE,CAOnD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAc3F;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAStD;AAED,wBAAgB,iBAAiB,IAAI,kBAAkB,EAAE,CAExD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
const USER_PROVIDERS_PATH = join(homedir(), '.qulib', 'providers.json');
|
|
5
|
+
export function loadUserProviders() {
|
|
6
|
+
const raw = loadSerialized();
|
|
7
|
+
return raw.map((p) => ({
|
|
8
|
+
id: p.id,
|
|
9
|
+
label: p.label,
|
|
10
|
+
patterns: p.patterns.map((src) => new RegExp(src, 'i')),
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
export function addUserProvider(input) {
|
|
14
|
+
const existing = loadSerialized();
|
|
15
|
+
const idx = existing.findIndex((p) => p.id === input.id);
|
|
16
|
+
if (idx >= 0) {
|
|
17
|
+
const p = existing[idx];
|
|
18
|
+
if (!p.patterns.includes(input.pattern)) {
|
|
19
|
+
p.patterns.push(input.pattern);
|
|
20
|
+
}
|
|
21
|
+
p.label = input.label;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
existing.push({ id: input.id, label: input.label, patterns: [input.pattern] });
|
|
25
|
+
}
|
|
26
|
+
ensureDir();
|
|
27
|
+
writeFileSync(USER_PROVIDERS_PATH, JSON.stringify(existing, null, 2), 'utf-8');
|
|
28
|
+
}
|
|
29
|
+
export function removeUserProvider(id) {
|
|
30
|
+
const existing = loadSerialized();
|
|
31
|
+
const filtered = existing.filter((p) => p.id !== id);
|
|
32
|
+
if (filtered.length === existing.length) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
ensureDir();
|
|
36
|
+
writeFileSync(USER_PROVIDERS_PATH, JSON.stringify(filtered, null, 2), 'utf-8');
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
export function listUserProviders() {
|
|
40
|
+
return loadSerialized();
|
|
41
|
+
}
|
|
42
|
+
function loadSerialized() {
|
|
43
|
+
if (!existsSync(USER_PROVIDERS_PATH)) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(readFileSync(USER_PROVIDERS_PATH, 'utf-8'));
|
|
48
|
+
if (!Array.isArray(parsed)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function ensureDir() {
|
|
58
|
+
const dir = dirname(USER_PROVIDERS_PATH);
|
|
59
|
+
if (!existsSync(dir)) {
|
|
60
|
+
mkdirSync(dir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
}
|