@qulib/core 0.4.0 → 0.4.1
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/dist/schemas/config.schema.d.ts +229 -73
- package/dist/schemas/config.schema.d.ts.map +1 -1
- package/dist/schemas/config.schema.js +19 -18
- package/dist/tools/auth-detector.d.ts.map +1 -1
- package/dist/tools/auth-detector.js +205 -26
- package/dist/tools/auth-surface-analyzer.d.ts.map +1 -1
- package/dist/tools/auth-surface-analyzer.js +26 -10
- package/package.json +11 -1
|
@@ -367,67 +367,6 @@ export declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
367
367
|
}>;
|
|
368
368
|
export type HarnessConfig = z.infer<typeof HarnessConfigSchema>;
|
|
369
369
|
export declare function resolveMaxOutputTokensPerLlmCall(config: HarnessConfig): number;
|
|
370
|
-
export declare const DetectedAuthSchema: z.ZodObject<{
|
|
371
|
-
hasAuth: z.ZodBoolean;
|
|
372
|
-
type: z.ZodEnum<["none", "form-login", "oauth", "magic-link", "unknown"]>;
|
|
373
|
-
provider: z.ZodNullable<z.ZodString>;
|
|
374
|
-
loginUrl: z.ZodNullable<z.ZodString>;
|
|
375
|
-
observedSelectors: z.ZodNullable<z.ZodObject<{
|
|
376
|
-
usernameSelector: z.ZodNullable<z.ZodString>;
|
|
377
|
-
passwordSelector: z.ZodNullable<z.ZodString>;
|
|
378
|
-
submitSelector: z.ZodNullable<z.ZodString>;
|
|
379
|
-
}, "strip", z.ZodTypeAny, {
|
|
380
|
-
usernameSelector: string | null;
|
|
381
|
-
passwordSelector: string | null;
|
|
382
|
-
submitSelector: string | null;
|
|
383
|
-
}, {
|
|
384
|
-
usernameSelector: string | null;
|
|
385
|
-
passwordSelector: string | null;
|
|
386
|
-
submitSelector: string | null;
|
|
387
|
-
}>>;
|
|
388
|
-
oauthButtons: z.ZodArray<z.ZodObject<{
|
|
389
|
-
provider: z.ZodString;
|
|
390
|
-
text: z.ZodString;
|
|
391
|
-
}, "strip", z.ZodTypeAny, {
|
|
392
|
-
provider: string;
|
|
393
|
-
text: string;
|
|
394
|
-
}, {
|
|
395
|
-
provider: string;
|
|
396
|
-
text: string;
|
|
397
|
-
}>, "many">;
|
|
398
|
-
recommendation: z.ZodString;
|
|
399
|
-
}, "strip", z.ZodTypeAny, {
|
|
400
|
-
type: "unknown" | "form-login" | "none" | "oauth" | "magic-link";
|
|
401
|
-
loginUrl: string | null;
|
|
402
|
-
hasAuth: boolean;
|
|
403
|
-
provider: string | null;
|
|
404
|
-
observedSelectors: {
|
|
405
|
-
usernameSelector: string | null;
|
|
406
|
-
passwordSelector: string | null;
|
|
407
|
-
submitSelector: string | null;
|
|
408
|
-
} | null;
|
|
409
|
-
oauthButtons: {
|
|
410
|
-
provider: string;
|
|
411
|
-
text: string;
|
|
412
|
-
}[];
|
|
413
|
-
recommendation: string;
|
|
414
|
-
}, {
|
|
415
|
-
type: "unknown" | "form-login" | "none" | "oauth" | "magic-link";
|
|
416
|
-
loginUrl: string | null;
|
|
417
|
-
hasAuth: boolean;
|
|
418
|
-
provider: string | null;
|
|
419
|
-
observedSelectors: {
|
|
420
|
-
usernameSelector: string | null;
|
|
421
|
-
passwordSelector: string | null;
|
|
422
|
-
submitSelector: string | null;
|
|
423
|
-
} | null;
|
|
424
|
-
oauthButtons: {
|
|
425
|
-
provider: string;
|
|
426
|
-
text: string;
|
|
427
|
-
}[];
|
|
428
|
-
recommendation: string;
|
|
429
|
-
}>;
|
|
430
|
-
export type DetectedAuth = z.infer<typeof DetectedAuthSchema>;
|
|
431
370
|
export declare const AuthPathRequirementsSchema: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
432
371
|
method: z.ZodLiteral<"storage-state">;
|
|
433
372
|
instruction: z.ZodString;
|
|
@@ -543,10 +482,10 @@ export declare const AuthPathSchema: z.ZodObject<{
|
|
|
543
482
|
instruction: string;
|
|
544
483
|
}>]>;
|
|
545
484
|
}, "strip", z.ZodTypeAny, {
|
|
546
|
-
type: "unknown" | "form-login" | "oauth" | "
|
|
547
|
-
provider: string | null;
|
|
485
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
548
486
|
label: string;
|
|
549
487
|
id: string;
|
|
488
|
+
provider: string | null;
|
|
550
489
|
source: "built-in" | "user-local" | "heuristic";
|
|
551
490
|
automatable: boolean;
|
|
552
491
|
confidence: "high" | "medium" | "low";
|
|
@@ -566,10 +505,10 @@ export declare const AuthPathSchema: z.ZodObject<{
|
|
|
566
505
|
instruction: string;
|
|
567
506
|
};
|
|
568
507
|
}, {
|
|
569
|
-
type: "unknown" | "form-login" | "oauth" | "
|
|
570
|
-
provider: string | null;
|
|
508
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
571
509
|
label: string;
|
|
572
510
|
id: string;
|
|
511
|
+
provider: string | null;
|
|
573
512
|
source: "built-in" | "user-local" | "heuristic";
|
|
574
513
|
automatable: boolean;
|
|
575
514
|
confidence: "high" | "medium" | "low";
|
|
@@ -589,6 +528,223 @@ export declare const AuthPathSchema: z.ZodObject<{
|
|
|
589
528
|
instruction: string;
|
|
590
529
|
};
|
|
591
530
|
}>;
|
|
531
|
+
export declare const DetectedAuthSchema: z.ZodObject<{
|
|
532
|
+
hasAuth: z.ZodBoolean;
|
|
533
|
+
type: z.ZodEnum<["none", "form-login", "oauth", "magic-link", "unknown"]>;
|
|
534
|
+
provider: z.ZodNullable<z.ZodString>;
|
|
535
|
+
loginUrl: z.ZodNullable<z.ZodString>;
|
|
536
|
+
observedSelectors: z.ZodNullable<z.ZodObject<{
|
|
537
|
+
usernameSelector: z.ZodNullable<z.ZodString>;
|
|
538
|
+
passwordSelector: z.ZodNullable<z.ZodString>;
|
|
539
|
+
submitSelector: z.ZodNullable<z.ZodString>;
|
|
540
|
+
}, "strip", z.ZodTypeAny, {
|
|
541
|
+
usernameSelector: string | null;
|
|
542
|
+
passwordSelector: string | null;
|
|
543
|
+
submitSelector: string | null;
|
|
544
|
+
}, {
|
|
545
|
+
usernameSelector: string | null;
|
|
546
|
+
passwordSelector: string | null;
|
|
547
|
+
submitSelector: string | null;
|
|
548
|
+
}>>;
|
|
549
|
+
oauthButtons: z.ZodArray<z.ZodObject<{
|
|
550
|
+
provider: z.ZodString;
|
|
551
|
+
text: z.ZodString;
|
|
552
|
+
}, "strip", z.ZodTypeAny, {
|
|
553
|
+
text: string;
|
|
554
|
+
provider: string;
|
|
555
|
+
}, {
|
|
556
|
+
text: string;
|
|
557
|
+
provider: string;
|
|
558
|
+
}>, "many">;
|
|
559
|
+
authOptions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
560
|
+
id: z.ZodString;
|
|
561
|
+
label: z.ZodString;
|
|
562
|
+
type: z.ZodEnum<["oauth", "oauth-unknown", "form-login", "form-multi", "magic-link", "unknown"]>;
|
|
563
|
+
provider: z.ZodNullable<z.ZodString>;
|
|
564
|
+
source: z.ZodEnum<["built-in", "user-local", "heuristic"]>;
|
|
565
|
+
automatable: z.ZodBoolean;
|
|
566
|
+
confidence: z.ZodEnum<["high", "medium", "low"]>;
|
|
567
|
+
requirements: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
568
|
+
method: z.ZodLiteral<"storage-state">;
|
|
569
|
+
instruction: z.ZodString;
|
|
570
|
+
}, "strip", z.ZodTypeAny, {
|
|
571
|
+
method: "storage-state";
|
|
572
|
+
instruction: string;
|
|
573
|
+
}, {
|
|
574
|
+
method: "storage-state";
|
|
575
|
+
instruction: string;
|
|
576
|
+
}>, z.ZodObject<{
|
|
577
|
+
method: z.ZodLiteral<"credentials">;
|
|
578
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
579
|
+
name: z.ZodString;
|
|
580
|
+
label: z.ZodString;
|
|
581
|
+
type: z.ZodEnum<["text", "password", "email", "select", "checkbox"]>;
|
|
582
|
+
observedOptions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
583
|
+
}, "strip", z.ZodTypeAny, {
|
|
584
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
585
|
+
name: string;
|
|
586
|
+
label: string;
|
|
587
|
+
observedOptions: string[];
|
|
588
|
+
}, {
|
|
589
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
590
|
+
name: string;
|
|
591
|
+
label: string;
|
|
592
|
+
observedOptions?: string[] | undefined;
|
|
593
|
+
}>, "many">;
|
|
594
|
+
}, "strip", z.ZodTypeAny, {
|
|
595
|
+
method: "credentials";
|
|
596
|
+
fields: {
|
|
597
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
598
|
+
name: string;
|
|
599
|
+
label: string;
|
|
600
|
+
observedOptions: string[];
|
|
601
|
+
}[];
|
|
602
|
+
}, {
|
|
603
|
+
method: "credentials";
|
|
604
|
+
fields: {
|
|
605
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
606
|
+
name: string;
|
|
607
|
+
label: string;
|
|
608
|
+
observedOptions?: string[] | undefined;
|
|
609
|
+
}[];
|
|
610
|
+
}>, z.ZodObject<{
|
|
611
|
+
method: z.ZodLiteral<"unknown">;
|
|
612
|
+
instruction: z.ZodString;
|
|
613
|
+
}, "strip", z.ZodTypeAny, {
|
|
614
|
+
method: "unknown";
|
|
615
|
+
instruction: string;
|
|
616
|
+
}, {
|
|
617
|
+
method: "unknown";
|
|
618
|
+
instruction: string;
|
|
619
|
+
}>]>;
|
|
620
|
+
}, "strip", z.ZodTypeAny, {
|
|
621
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
622
|
+
label: string;
|
|
623
|
+
id: string;
|
|
624
|
+
provider: string | null;
|
|
625
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
626
|
+
automatable: boolean;
|
|
627
|
+
confidence: "high" | "medium" | "low";
|
|
628
|
+
requirements: {
|
|
629
|
+
method: "storage-state";
|
|
630
|
+
instruction: string;
|
|
631
|
+
} | {
|
|
632
|
+
method: "credentials";
|
|
633
|
+
fields: {
|
|
634
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
635
|
+
name: string;
|
|
636
|
+
label: string;
|
|
637
|
+
observedOptions: string[];
|
|
638
|
+
}[];
|
|
639
|
+
} | {
|
|
640
|
+
method: "unknown";
|
|
641
|
+
instruction: string;
|
|
642
|
+
};
|
|
643
|
+
}, {
|
|
644
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
645
|
+
label: string;
|
|
646
|
+
id: string;
|
|
647
|
+
provider: string | null;
|
|
648
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
649
|
+
automatable: boolean;
|
|
650
|
+
confidence: "high" | "medium" | "low";
|
|
651
|
+
requirements: {
|
|
652
|
+
method: "storage-state";
|
|
653
|
+
instruction: string;
|
|
654
|
+
} | {
|
|
655
|
+
method: "credentials";
|
|
656
|
+
fields: {
|
|
657
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
658
|
+
name: string;
|
|
659
|
+
label: string;
|
|
660
|
+
observedOptions?: string[] | undefined;
|
|
661
|
+
}[];
|
|
662
|
+
} | {
|
|
663
|
+
method: "unknown";
|
|
664
|
+
instruction: string;
|
|
665
|
+
};
|
|
666
|
+
}>, "many">>;
|
|
667
|
+
recommendation: z.ZodString;
|
|
668
|
+
}, "strip", z.ZodTypeAny, {
|
|
669
|
+
type: "unknown" | "form-login" | "oauth" | "magic-link" | "none";
|
|
670
|
+
loginUrl: string | null;
|
|
671
|
+
provider: string | null;
|
|
672
|
+
hasAuth: boolean;
|
|
673
|
+
observedSelectors: {
|
|
674
|
+
usernameSelector: string | null;
|
|
675
|
+
passwordSelector: string | null;
|
|
676
|
+
submitSelector: string | null;
|
|
677
|
+
} | null;
|
|
678
|
+
oauthButtons: {
|
|
679
|
+
text: string;
|
|
680
|
+
provider: string;
|
|
681
|
+
}[];
|
|
682
|
+
recommendation: string;
|
|
683
|
+
authOptions?: {
|
|
684
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
685
|
+
label: string;
|
|
686
|
+
id: string;
|
|
687
|
+
provider: string | null;
|
|
688
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
689
|
+
automatable: boolean;
|
|
690
|
+
confidence: "high" | "medium" | "low";
|
|
691
|
+
requirements: {
|
|
692
|
+
method: "storage-state";
|
|
693
|
+
instruction: string;
|
|
694
|
+
} | {
|
|
695
|
+
method: "credentials";
|
|
696
|
+
fields: {
|
|
697
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
698
|
+
name: string;
|
|
699
|
+
label: string;
|
|
700
|
+
observedOptions: string[];
|
|
701
|
+
}[];
|
|
702
|
+
} | {
|
|
703
|
+
method: "unknown";
|
|
704
|
+
instruction: string;
|
|
705
|
+
};
|
|
706
|
+
}[] | undefined;
|
|
707
|
+
}, {
|
|
708
|
+
type: "unknown" | "form-login" | "oauth" | "magic-link" | "none";
|
|
709
|
+
loginUrl: string | null;
|
|
710
|
+
provider: string | null;
|
|
711
|
+
hasAuth: boolean;
|
|
712
|
+
observedSelectors: {
|
|
713
|
+
usernameSelector: string | null;
|
|
714
|
+
passwordSelector: string | null;
|
|
715
|
+
submitSelector: string | null;
|
|
716
|
+
} | null;
|
|
717
|
+
oauthButtons: {
|
|
718
|
+
text: string;
|
|
719
|
+
provider: string;
|
|
720
|
+
}[];
|
|
721
|
+
recommendation: string;
|
|
722
|
+
authOptions?: {
|
|
723
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
724
|
+
label: string;
|
|
725
|
+
id: string;
|
|
726
|
+
provider: string | null;
|
|
727
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
728
|
+
automatable: boolean;
|
|
729
|
+
confidence: "high" | "medium" | "low";
|
|
730
|
+
requirements: {
|
|
731
|
+
method: "storage-state";
|
|
732
|
+
instruction: string;
|
|
733
|
+
} | {
|
|
734
|
+
method: "credentials";
|
|
735
|
+
fields: {
|
|
736
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
737
|
+
name: string;
|
|
738
|
+
label: string;
|
|
739
|
+
observedOptions?: string[] | undefined;
|
|
740
|
+
}[];
|
|
741
|
+
} | {
|
|
742
|
+
method: "unknown";
|
|
743
|
+
instruction: string;
|
|
744
|
+
};
|
|
745
|
+
}[] | undefined;
|
|
746
|
+
}>;
|
|
747
|
+
export type DetectedAuth = z.infer<typeof DetectedAuthSchema>;
|
|
592
748
|
export declare const AuthExplorationSchema: z.ZodObject<{
|
|
593
749
|
url: z.ZodString;
|
|
594
750
|
authRequired: z.ZodBoolean;
|
|
@@ -655,10 +811,10 @@ export declare const AuthExplorationSchema: z.ZodObject<{
|
|
|
655
811
|
instruction: string;
|
|
656
812
|
}>]>;
|
|
657
813
|
}, "strip", z.ZodTypeAny, {
|
|
658
|
-
type: "unknown" | "form-login" | "oauth" | "
|
|
659
|
-
provider: string | null;
|
|
814
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
660
815
|
label: string;
|
|
661
816
|
id: string;
|
|
817
|
+
provider: string | null;
|
|
662
818
|
source: "built-in" | "user-local" | "heuristic";
|
|
663
819
|
automatable: boolean;
|
|
664
820
|
confidence: "high" | "medium" | "low";
|
|
@@ -678,10 +834,10 @@ export declare const AuthExplorationSchema: z.ZodObject<{
|
|
|
678
834
|
instruction: string;
|
|
679
835
|
};
|
|
680
836
|
}, {
|
|
681
|
-
type: "unknown" | "form-login" | "oauth" | "
|
|
682
|
-
provider: string | null;
|
|
837
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
683
838
|
label: string;
|
|
684
839
|
id: string;
|
|
840
|
+
provider: string | null;
|
|
685
841
|
source: "built-in" | "user-local" | "heuristic";
|
|
686
842
|
automatable: boolean;
|
|
687
843
|
confidence: "high" | "medium" | "low";
|
|
@@ -718,10 +874,10 @@ export declare const AuthExplorationSchema: z.ZodObject<{
|
|
|
718
874
|
authRequired: boolean;
|
|
719
875
|
authScope: "none" | "site-wide" | "section-only" | "optional";
|
|
720
876
|
authPaths: {
|
|
721
|
-
type: "unknown" | "form-login" | "oauth" | "
|
|
722
|
-
provider: string | null;
|
|
877
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
723
878
|
label: string;
|
|
724
879
|
id: string;
|
|
880
|
+
provider: string | null;
|
|
725
881
|
source: "built-in" | "user-local" | "heuristic";
|
|
726
882
|
automatable: boolean;
|
|
727
883
|
confidence: "high" | "medium" | "low";
|
|
@@ -752,10 +908,10 @@ export declare const AuthExplorationSchema: z.ZodObject<{
|
|
|
752
908
|
authRequired: boolean;
|
|
753
909
|
authScope: "none" | "site-wide" | "section-only" | "optional";
|
|
754
910
|
authPaths: {
|
|
755
|
-
type: "unknown" | "form-login" | "oauth" | "
|
|
756
|
-
provider: string | null;
|
|
911
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
757
912
|
label: string;
|
|
758
913
|
id: string;
|
|
914
|
+
provider: string | null;
|
|
759
915
|
source: "built-in" | "user-local" | "heuristic";
|
|
760
916
|
automatable: boolean;
|
|
761
917
|
confidence: "high" | "medium" | "low";
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0C9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAE9E;AAED,eAAO,MAAM,kBAAkB
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0C9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAE9E;AAED,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAcrC,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASzB,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoB7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,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"}
|
|
@@ -65,24 +65,6 @@ export const HarnessConfigSchema = z.object({
|
|
|
65
65
|
export function resolveMaxOutputTokensPerLlmCall(config) {
|
|
66
66
|
return config.llmMaxOutputTokensPerCall ?? config.llmTokenBudget;
|
|
67
67
|
}
|
|
68
|
-
export const DetectedAuthSchema = z.object({
|
|
69
|
-
hasAuth: z.boolean(),
|
|
70
|
-
type: z.enum(['none', 'form-login', 'oauth', 'magic-link', 'unknown']),
|
|
71
|
-
provider: z.string().nullable(),
|
|
72
|
-
loginUrl: z.string().nullable(),
|
|
73
|
-
observedSelectors: z
|
|
74
|
-
.object({
|
|
75
|
-
usernameSelector: z.string().nullable(),
|
|
76
|
-
passwordSelector: z.string().nullable(),
|
|
77
|
-
submitSelector: z.string().nullable(),
|
|
78
|
-
})
|
|
79
|
-
.nullable(),
|
|
80
|
-
oauthButtons: z.array(z.object({
|
|
81
|
-
provider: z.string(),
|
|
82
|
-
text: z.string(),
|
|
83
|
-
})),
|
|
84
|
-
recommendation: z.string(),
|
|
85
|
-
});
|
|
86
68
|
export const AuthPathRequirementsSchema = z.discriminatedUnion('method', [
|
|
87
69
|
z.object({ method: z.literal('storage-state'), instruction: z.string() }),
|
|
88
70
|
z.object({
|
|
@@ -106,6 +88,25 @@ export const AuthPathSchema = z.object({
|
|
|
106
88
|
confidence: z.enum(['high', 'medium', 'low']),
|
|
107
89
|
requirements: AuthPathRequirementsSchema,
|
|
108
90
|
});
|
|
91
|
+
export const DetectedAuthSchema = z.object({
|
|
92
|
+
hasAuth: z.boolean(),
|
|
93
|
+
type: z.enum(['none', 'form-login', 'oauth', 'magic-link', 'unknown']),
|
|
94
|
+
provider: z.string().nullable(),
|
|
95
|
+
loginUrl: z.string().nullable(),
|
|
96
|
+
observedSelectors: z
|
|
97
|
+
.object({
|
|
98
|
+
usernameSelector: z.string().nullable(),
|
|
99
|
+
passwordSelector: z.string().nullable(),
|
|
100
|
+
submitSelector: z.string().nullable(),
|
|
101
|
+
})
|
|
102
|
+
.nullable(),
|
|
103
|
+
oauthButtons: z.array(z.object({
|
|
104
|
+
provider: z.string(),
|
|
105
|
+
text: z.string(),
|
|
106
|
+
})),
|
|
107
|
+
authOptions: z.array(AuthPathSchema).optional(),
|
|
108
|
+
recommendation: z.string(),
|
|
109
|
+
});
|
|
109
110
|
export const AuthExplorationSchema = z.object({
|
|
110
111
|
url: z.string(),
|
|
111
112
|
authRequired: z.boolean(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-detector.d.ts","sourceRoot":"","sources":["../../src/tools/auth-detector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"auth-detector.d.ts","sourceRoot":"","sources":["../../src/tools/auth-detector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAsPtE,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,SAAQ,EACjB,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAkJvB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { launchBrowser } from './browser.js';
|
|
2
|
+
import { BUILT_IN_OAUTH_PROVIDERS } from './oauth-providers.js';
|
|
2
3
|
async function waitNetworkIdleBestEffort(page) {
|
|
3
4
|
try {
|
|
4
5
|
await page.waitForLoadState('networkidle', { timeout: 5000 });
|
|
@@ -7,27 +8,20 @@ async function waitNetworkIdleBestEffort(page) {
|
|
|
7
8
|
// best-effort — analytics or polling can prevent networkidle
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
|
-
const
|
|
11
|
-
{ provider: 'github', patterns: [/github/i, /sign in with github/i] },
|
|
12
|
-
{
|
|
13
|
-
provider: 'google',
|
|
14
|
-
patterns: [/google/i, /sign in with google/i, /accounts\.google\.com/i],
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
provider: 'microsoft',
|
|
18
|
-
patterns: [/microsoft/i, /sign in with microsoft/i, /login\.microsoftonline\.com/i],
|
|
19
|
-
},
|
|
20
|
-
{ provider: 'apple', patterns: [/apple/i, /sign in with apple/i] },
|
|
21
|
-
{ provider: 'auth0', patterns: [/auth0/i] },
|
|
22
|
-
{ provider: 'okta', patterns: [/okta/i] },
|
|
23
|
-
];
|
|
11
|
+
const PROVIDER_LABELS = new Set(BUILT_IN_OAUTH_PROVIDERS.map((p) => p.label.toLowerCase()));
|
|
24
12
|
function textLooksLikeOAuthIdpButton(text) {
|
|
25
13
|
const t = text.trim();
|
|
26
14
|
if (t.length === 0 || t.length > 120) {
|
|
27
15
|
return false;
|
|
28
16
|
}
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
if (/\b(sign in with|log in with|continue with|sign up with)\b/i.test(t)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
// Accept single-word / short labels that exactly match a known provider name
|
|
21
|
+
if (PROVIDER_LABELS.has(t.toLowerCase())) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
31
25
|
}
|
|
32
26
|
const MAGIC_LINK_PATTERNS = [
|
|
33
27
|
/email me a (sign[- ]?in )?link/i,
|
|
@@ -53,6 +47,177 @@ async function firstTextInputNameForLogin(page) {
|
|
|
53
47
|
function debugAuth() {
|
|
54
48
|
return process.env.QULIB_DEBUG === '1';
|
|
55
49
|
}
|
|
50
|
+
function slugify(label) {
|
|
51
|
+
const s = label
|
|
52
|
+
.toLowerCase()
|
|
53
|
+
.replace(/\s+/g, '-')
|
|
54
|
+
.replace(/[^a-z0-9-]+/g, '')
|
|
55
|
+
.replace(/^-+|-+$/g, '');
|
|
56
|
+
return s.length > 0 ? s : 'custom';
|
|
57
|
+
}
|
|
58
|
+
function escapeRegExp(s) {
|
|
59
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
60
|
+
}
|
|
61
|
+
async function resolveVisibleFieldLabel(page, el) {
|
|
62
|
+
const id = await el.getAttribute('id');
|
|
63
|
+
if (id) {
|
|
64
|
+
const lt = await page.locator(`label[for="${id.replace(/"/g, '\\"')}"]`).first().textContent().catch(() => null);
|
|
65
|
+
const fromLabel = (lt ?? '').trim();
|
|
66
|
+
if (fromLabel)
|
|
67
|
+
return fromLabel;
|
|
68
|
+
}
|
|
69
|
+
const placeholder = (await el.getAttribute('placeholder'))?.trim();
|
|
70
|
+
if (placeholder)
|
|
71
|
+
return placeholder;
|
|
72
|
+
const aria = (await el.getAttribute('aria-label'))?.trim();
|
|
73
|
+
if (aria)
|
|
74
|
+
return aria;
|
|
75
|
+
const name = (await el.getAttribute('name'))?.trim();
|
|
76
|
+
if (name)
|
|
77
|
+
return name;
|
|
78
|
+
const typ = (await el.getAttribute('type'))?.trim();
|
|
79
|
+
return typ && typ !== 'select' ? typ : 'text';
|
|
80
|
+
}
|
|
81
|
+
async function deriveCredentialFieldName(el) {
|
|
82
|
+
const name = (await el.getAttribute('name'))?.trim();
|
|
83
|
+
if (name)
|
|
84
|
+
return name;
|
|
85
|
+
const placeholder = (await el.getAttribute('placeholder'))?.trim();
|
|
86
|
+
if (placeholder)
|
|
87
|
+
return slugify(placeholder);
|
|
88
|
+
const aria = (await el.getAttribute('aria-label'))?.trim();
|
|
89
|
+
if (aria)
|
|
90
|
+
return slugify(aria);
|
|
91
|
+
const id = (await el.getAttribute('id'))?.trim();
|
|
92
|
+
if (id)
|
|
93
|
+
return slugify(id);
|
|
94
|
+
return 'field';
|
|
95
|
+
}
|
|
96
|
+
async function buildCredentialFieldsFromVisibleForm(page) {
|
|
97
|
+
const fields = [];
|
|
98
|
+
const seen = new Set();
|
|
99
|
+
const loc = page.locator('input[type="text"]:visible, input[type="email"]:visible, input[type="password"]:visible, select:visible');
|
|
100
|
+
const count = await loc.count();
|
|
101
|
+
for (let i = 0; i < count; i++) {
|
|
102
|
+
const el = loc.nth(i);
|
|
103
|
+
const tag = await el.evaluate((node) => node.tagName.toLowerCase()).catch(() => '');
|
|
104
|
+
if (tag === 'select') {
|
|
105
|
+
const name = await deriveCredentialFieldName(el);
|
|
106
|
+
const label = await resolveVisibleFieldLabel(page, el);
|
|
107
|
+
const opts = await el.locator('option').allInnerTexts();
|
|
108
|
+
const observedOptions = opts.map((o) => o.trim()).filter((x) => x.length > 0).slice(0, 20);
|
|
109
|
+
const dedupeKey = `select|${name}|${label}`;
|
|
110
|
+
if (seen.has(dedupeKey))
|
|
111
|
+
continue;
|
|
112
|
+
seen.add(dedupeKey);
|
|
113
|
+
fields.push({ name, label, type: 'select', observedOptions });
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const rawType = ((await el.getAttribute('type')) ?? 'text').toLowerCase();
|
|
117
|
+
if (rawType === 'hidden')
|
|
118
|
+
continue;
|
|
119
|
+
const fieldType = rawType === 'email' ? 'email' : rawType === 'password' ? 'password' : 'text';
|
|
120
|
+
const name = await deriveCredentialFieldName(el);
|
|
121
|
+
const label = await resolveVisibleFieldLabel(page, el);
|
|
122
|
+
const placeholder = (await el.getAttribute('placeholder'))?.trim() ?? '';
|
|
123
|
+
const dedupeKey = `${name}|${placeholder}`;
|
|
124
|
+
if (seen.has(dedupeKey))
|
|
125
|
+
continue;
|
|
126
|
+
seen.add(dedupeKey);
|
|
127
|
+
fields.push({ name, label, type: fieldType, observedOptions: [] });
|
|
128
|
+
}
|
|
129
|
+
return fields;
|
|
130
|
+
}
|
|
131
|
+
function authPathsFromOauthButtons(oauthButtons, loginUrl) {
|
|
132
|
+
return oauthButtons.map((b) => {
|
|
133
|
+
const isUnknown = b.provider === 'unknown';
|
|
134
|
+
const id = isUnknown ? slugify(b.text) : b.provider;
|
|
135
|
+
return {
|
|
136
|
+
id,
|
|
137
|
+
label: b.text,
|
|
138
|
+
type: isUnknown ? 'oauth-unknown' : 'oauth',
|
|
139
|
+
provider: isUnknown ? slugify(b.text) : b.provider,
|
|
140
|
+
source: isUnknown ? 'heuristic' : 'built-in',
|
|
141
|
+
automatable: false,
|
|
142
|
+
confidence: isUnknown ? 'low' : 'high',
|
|
143
|
+
requirements: {
|
|
144
|
+
method: 'storage-state',
|
|
145
|
+
instruction: `Run qulib auth init --base-url ${loginUrl}`,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async function probeClickToRevealForms(page, loginUrl, alreadyMatchedTexts, timeoutMs, progress) {
|
|
151
|
+
const out = [];
|
|
152
|
+
const buttons = page.locator('button');
|
|
153
|
+
const n = await buttons.count();
|
|
154
|
+
const seenLabels = new Set();
|
|
155
|
+
const SUBMIT_RE = /^(sign in|log in|submit|continue|next|cancel|close)$/i;
|
|
156
|
+
let candidateAttempts = 0;
|
|
157
|
+
for (let i = 0; i < n && candidateAttempts < 4; i++) {
|
|
158
|
+
const label = ((await buttons.nth(i).textContent()) ?? '').trim();
|
|
159
|
+
if (!label || label.length > 80)
|
|
160
|
+
continue;
|
|
161
|
+
if (alreadyMatchedTexts.has(label))
|
|
162
|
+
continue;
|
|
163
|
+
if (SUBMIT_RE.test(label))
|
|
164
|
+
continue;
|
|
165
|
+
if (seenLabels.has(label))
|
|
166
|
+
continue;
|
|
167
|
+
seenLabels.add(label);
|
|
168
|
+
candidateAttempts += 1;
|
|
169
|
+
if (debugAuth()) {
|
|
170
|
+
progress?.debug(`detect_auth click-reveal try label="${label.slice(0, 80)}"`);
|
|
171
|
+
}
|
|
172
|
+
let clicked = false;
|
|
173
|
+
try {
|
|
174
|
+
await page.getByRole('button', { name: label, exact: true }).first().click({ timeout: 2000 });
|
|
175
|
+
clicked = true;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
try {
|
|
179
|
+
await page
|
|
180
|
+
.locator('button')
|
|
181
|
+
.filter({ hasText: new RegExp(`^\\s*${escapeRegExp(label)}\\s*$`, 'i') })
|
|
182
|
+
.first()
|
|
183
|
+
.click({ timeout: 2000 });
|
|
184
|
+
clicked = true;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
/* skip */
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (!clicked) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
await page.locator('input[type="password"]').first().waitFor({ state: 'visible', timeout: 2000 });
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
await page.goto(loginUrl, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
|
|
198
|
+
await waitNetworkIdleBestEffort(page);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const fields = await buildCredentialFieldsFromVisibleForm(page);
|
|
202
|
+
const slug = slugify(label);
|
|
203
|
+
out.push({
|
|
204
|
+
id: slug,
|
|
205
|
+
label,
|
|
206
|
+
type: 'form-login',
|
|
207
|
+
provider: slug,
|
|
208
|
+
source: 'heuristic',
|
|
209
|
+
automatable: true,
|
|
210
|
+
confidence: 'medium',
|
|
211
|
+
requirements: {
|
|
212
|
+
method: 'credentials',
|
|
213
|
+
fields,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
await page.goto(loginUrl, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
|
|
217
|
+
await waitNetworkIdleBestEffort(page);
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
56
221
|
export async function detectAuth(url, timeoutMs = 15000, progress) {
|
|
57
222
|
const browser = await launchBrowser();
|
|
58
223
|
try {
|
|
@@ -96,18 +261,27 @@ export async function detectAuth(url, timeoutMs = 15000, progress) {
|
|
|
96
261
|
}
|
|
97
262
|
continue;
|
|
98
263
|
}
|
|
99
|
-
|
|
264
|
+
let matchedAny = false;
|
|
265
|
+
for (const { id, patterns } of BUILT_IN_OAUTH_PROVIDERS) {
|
|
100
266
|
const matched = patterns.some((p) => p.test(trimmed));
|
|
101
267
|
if (debugAuth()) {
|
|
102
|
-
progress?.debug(`detect_auth oauth pattern try provider=${
|
|
268
|
+
progress?.debug(`detect_auth oauth pattern try provider=${id} matched=${matched}`);
|
|
103
269
|
}
|
|
104
270
|
if (matched) {
|
|
105
|
-
if (!oauthButtons.find((b) => b.provider ===
|
|
106
|
-
oauthButtons.push({ provider, text: trimmed.slice(0, 100) });
|
|
271
|
+
if (!oauthButtons.find((b) => b.provider === id)) {
|
|
272
|
+
oauthButtons.push({ provider: id, text: trimmed.slice(0, 100) });
|
|
107
273
|
}
|
|
274
|
+
matchedAny = true;
|
|
108
275
|
}
|
|
109
276
|
}
|
|
277
|
+
// Capture unrecognized SSO-like buttons so they appear in the result
|
|
278
|
+
if (!matchedAny && !oauthButtons.find((b) => b.text === trimmed.slice(0, 100))) {
|
|
279
|
+
oauthButtons.push({ provider: 'unknown', text: trimmed.slice(0, 100) });
|
|
280
|
+
}
|
|
110
281
|
}
|
|
282
|
+
// Only skip buttons already tied to a built-in IdP — leave `unknown` labels probe-able for click-to-reveal forms.
|
|
283
|
+
const skipProbeLabels = new Set(oauthButtons.filter((b) => b.provider !== 'unknown').map((b) => b.text.trim()));
|
|
284
|
+
const clickRevealForms = await probeClickToRevealForms(page, loginUrl, skipProbeLabels, timeoutMs, progress);
|
|
111
285
|
const pageText = await page.locator('body').innerText().catch(() => '');
|
|
112
286
|
const hasMagicLink = MAGIC_LINK_PATTERNS.some((p) => p.test(pageText));
|
|
113
287
|
let type = 'none';
|
|
@@ -117,7 +291,7 @@ export async function detectAuth(url, timeoutMs = 15000, progress) {
|
|
|
117
291
|
if (oauthButtons.length > 0) {
|
|
118
292
|
type = 'oauth';
|
|
119
293
|
provider = oauthButtons[0].provider;
|
|
120
|
-
recommendation = `OAuth detected (${oauthButtons.map((b) => b.provider).join(', ')}). OAuth cannot be automated. Run "qulib auth init --base-url ${
|
|
294
|
+
recommendation = `OAuth detected (${oauthButtons.map((b) => b.provider).join(', ')}). OAuth cannot be automated. Run "qulib auth init --base-url ${loginUrl}" to log in manually once and save a reusable storage state file.`;
|
|
121
295
|
}
|
|
122
296
|
else if (hasFormLogin) {
|
|
123
297
|
type = 'form-login';
|
|
@@ -140,26 +314,31 @@ export async function detectAuth(url, timeoutMs = 15000, progress) {
|
|
|
140
314
|
}
|
|
141
315
|
else if (hasMagicLink) {
|
|
142
316
|
type = 'magic-link';
|
|
143
|
-
recommendation = `Magic link / passwordless auth detected. Qulib cannot complete email-link flows. Run "qulib auth init --base-url ${
|
|
317
|
+
recommendation = `Magic link / passwordless auth detected. Qulib cannot complete email-link flows. Run "qulib auth init --base-url ${loginUrl}" to log in manually once and save a storage state file.`;
|
|
144
318
|
}
|
|
145
319
|
else if (looksLikeLoginPage) {
|
|
146
320
|
type = 'unknown';
|
|
147
|
-
recommendation = `Authentication required but the pattern is unrecognized. Use "qulib auth init --base-url ${
|
|
321
|
+
recommendation = `Authentication required but the pattern is unrecognized. Use "qulib auth init --base-url ${loginUrl}" to capture a storage state by logging in manually.`;
|
|
148
322
|
}
|
|
149
323
|
else {
|
|
150
324
|
type = 'none';
|
|
151
325
|
recommendation = `No authentication required for the entry URL. Qulib can scan anonymously.`;
|
|
152
326
|
}
|
|
327
|
+
if (clickRevealForms.length > 0) {
|
|
328
|
+
recommendation += `\nAutomatable form login detected via: ${clickRevealForms.map((f) => f.label).join(', ')}. Use type="form-login" with the observed selectors in authOptions.`;
|
|
329
|
+
}
|
|
153
330
|
const providerList = oauthButtons.length > 0 ? oauthButtons.map((b) => b.provider).join(', ') : provider ?? 'none';
|
|
154
|
-
const automatable = type === 'form-login';
|
|
331
|
+
const automatable = type === 'form-login' || clickRevealForms.length > 0;
|
|
155
332
|
progress?.info(`Auth detected: ${type} (${providerList}) automatable=${automatable}`);
|
|
333
|
+
const authOptions = [...authPathsFromOauthButtons(oauthButtons, loginUrl), ...clickRevealForms];
|
|
156
334
|
return {
|
|
157
|
-
hasAuth: type !== 'none',
|
|
335
|
+
hasAuth: type !== 'none' || oauthButtons.length > 0 || clickRevealForms.length > 0,
|
|
158
336
|
type,
|
|
159
337
|
provider,
|
|
160
|
-
loginUrl: type === 'none' ? null : loginUrl,
|
|
338
|
+
loginUrl: type === 'none' && oauthButtons.length === 0 && clickRevealForms.length === 0 ? null : loginUrl,
|
|
161
339
|
observedSelectors,
|
|
162
340
|
oauthButtons,
|
|
341
|
+
...(authOptions.length > 0 ? { authOptions } : {}),
|
|
163
342
|
recommendation,
|
|
164
343
|
};
|
|
165
344
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-surface-analyzer.d.ts","sourceRoot":"","sources":["../../src/tools/auth-surface-analyzer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mCAAmC,CAAC;AAW7D,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,YAAY,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"auth-surface-analyzer.d.ts","sourceRoot":"","sources":["../../src/tools/auth-surface-analyzer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mCAAmC,CAAC;AAW7D,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,YAAY,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,EAAE,CAAC,CAwKhB"}
|
|
@@ -110,16 +110,32 @@ export async function analyzeAuthSurfaceGaps(url, detection, timeoutMs) {
|
|
|
110
110
|
const hasEmailLink = await page.getByText(/magic link|email.*link|passwordless/i).count();
|
|
111
111
|
const hasOAuthUi = detection.oauthButtons.length > 0 ||
|
|
112
112
|
(await page.getByText(/sign in with|continue with google|microsoft|github/i).count()) > 0;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
const formLoginFallbacks = (detection.authOptions ?? []).filter((o) => o.type === 'form-login');
|
|
114
|
+
const hasFormLoginFallback = formLoginFallbacks.length > 0;
|
|
115
|
+
if (detection.type === 'oauth' && hasOAuthUi && !hasPassword && !hasEmailLink) {
|
|
116
|
+
if (hasFormLoginFallback) {
|
|
117
|
+
const labels = formLoginFallbacks.map((o) => o.label).join(', ');
|
|
118
|
+
gaps.push({
|
|
119
|
+
id: randomUUID(),
|
|
120
|
+
path: '/',
|
|
121
|
+
severity: 'low',
|
|
122
|
+
category: 'auth-surface',
|
|
123
|
+
reason: `OAuth-primary login with form-login fallback detected via: ${labels}`,
|
|
124
|
+
description: 'A form-based login path exists alongside OAuth. Automate via type="form-login" using the selectors in authOptions.',
|
|
125
|
+
recommendation: `Automatable form option(s): ${labels}. Configure type="form-login" with credentials and selectors from detectedAuth.authOptions.`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
gaps.push({
|
|
130
|
+
id: randomUUID(),
|
|
131
|
+
path: '/',
|
|
132
|
+
severity: 'medium',
|
|
133
|
+
category: 'auth-surface',
|
|
134
|
+
reason: 'OAuth-only entry with no visible password or magic-link fallback.',
|
|
135
|
+
description: 'Users who cannot use a social IdP need another path (email/password, help, or support).',
|
|
136
|
+
recommendation: 'Add a documented fallback (email/password, help desk link, or alternate IdP).',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
123
139
|
}
|
|
124
140
|
const errorSelectors = '[role="alert"], [data-testid*="error" i], .error, .alert-danger, [class*="error" i]';
|
|
125
141
|
const errCount = await page.locator(errorSelectors).count();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qulib/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Qulib — analyze deployed web apps for honest quality gaps (CLI + programmatic API)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Tapesh Nagarwal",
|
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/TapeshN/qulib/issues"
|
|
15
15
|
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"qa",
|
|
18
|
+
"quality",
|
|
19
|
+
"accessibility",
|
|
20
|
+
"gap-analysis",
|
|
21
|
+
"release-confidence",
|
|
22
|
+
"playwright",
|
|
23
|
+
"mcp",
|
|
24
|
+
"ai"
|
|
25
|
+
],
|
|
16
26
|
"publishConfig": {
|
|
17
27
|
"access": "public"
|
|
18
28
|
},
|