@chfischerx/puttry 0.1.0

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.
@@ -0,0 +1,4879 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __commonJS = (cb, mod) => function __require() {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ };
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
33
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
34
+
35
+ // src/server/logger.ts
36
+ var logger_exports = {};
37
+ __export(logger_exports, {
38
+ default: () => logger_default
39
+ });
40
+ import winston from "winston";
41
+ function getLogger() {
42
+ if (logger) return logger;
43
+ const level = process.env.VERBOSE === "1" || process.env.VERBOSE === "true" ? "debug" : "info";
44
+ const fmt = printf(
45
+ ({ level: level2, message, timestamp: timestamp2 }) => `${timestamp2} [${level2}] ${message}`
46
+ );
47
+ logger = winston.createLogger({
48
+ level,
49
+ format: combine(timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), fmt),
50
+ transports: [new winston.transports.Console()]
51
+ });
52
+ if (level === "debug") {
53
+ logger.debug("[logger] Debug level logging ENABLED");
54
+ }
55
+ return logger;
56
+ }
57
+ var combine, timestamp, printf, logger, logger_default;
58
+ var init_logger = __esm({
59
+ "src/server/logger.ts"() {
60
+ ({ combine, timestamp, printf } = winston.format);
61
+ logger = null;
62
+ logger_default = new Proxy(
63
+ {},
64
+ {
65
+ get(_target, prop) {
66
+ return getLogger()[prop];
67
+ }
68
+ }
69
+ );
70
+ }
71
+ });
72
+
73
+ // src/lib/password-gen.ts
74
+ function generateXkcdPassword(wordCount = 4) {
75
+ const selected = [];
76
+ for (let i = 0; i < wordCount; i++) {
77
+ const idx = Math.floor(Math.random() * WORDS.length);
78
+ selected.push(WORDS[idx]);
79
+ }
80
+ const digit = Math.floor(Math.random() * 10);
81
+ return `${selected.join("-")}-${digit}`;
82
+ }
83
+ function generateRandomPassword(length = 16) {
84
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
85
+ const bytes = new Uint8Array(length);
86
+ crypto.getRandomValues(bytes);
87
+ let password = "";
88
+ for (let i = 0; i < length; i++) {
89
+ password += chars[bytes[i] % chars.length];
90
+ }
91
+ return password;
92
+ }
93
+ var WORDS;
94
+ var init_password_gen = __esm({
95
+ "src/lib/password-gen.ts"() {
96
+ WORDS = [
97
+ "apple",
98
+ "baked",
99
+ "beach",
100
+ "bench",
101
+ "bikes",
102
+ "birds",
103
+ "black",
104
+ "blade",
105
+ "blank",
106
+ "blend",
107
+ "block",
108
+ "blood",
109
+ "blown",
110
+ "board",
111
+ "boats",
112
+ "books",
113
+ "boots",
114
+ "bound",
115
+ "boxes",
116
+ "bread",
117
+ "break",
118
+ "brick",
119
+ "bride",
120
+ "brief",
121
+ "bring",
122
+ "brink",
123
+ "broke",
124
+ "brown",
125
+ "build",
126
+ "built",
127
+ "cable",
128
+ "calls",
129
+ "cards",
130
+ "cargo",
131
+ "carol",
132
+ "carry",
133
+ "cases",
134
+ "catch",
135
+ "cause",
136
+ "caves",
137
+ "chain",
138
+ "chair",
139
+ "charm",
140
+ "chart",
141
+ "chase",
142
+ "cheap",
143
+ "check",
144
+ "chess",
145
+ "chest",
146
+ "chief",
147
+ "child",
148
+ "china",
149
+ "chose",
150
+ "claim",
151
+ "class",
152
+ "clean",
153
+ "clear",
154
+ "click",
155
+ "cliff",
156
+ "climb",
157
+ "clock",
158
+ "close",
159
+ "cloud",
160
+ "coach",
161
+ "coast",
162
+ "codes",
163
+ "coins",
164
+ "comet",
165
+ "comic",
166
+ "coral",
167
+ "cords",
168
+ "cores",
169
+ "craft",
170
+ "crash",
171
+ "cream",
172
+ "creek",
173
+ "crime",
174
+ "crops",
175
+ "cross",
176
+ "crowd",
177
+ "crown",
178
+ "crude",
179
+ "curve",
180
+ "cycle",
181
+ "daily",
182
+ "dance",
183
+ "darts",
184
+ "dated",
185
+ "deals",
186
+ "death",
187
+ "decks",
188
+ "delay",
189
+ "delta",
190
+ "dense",
191
+ "depth",
192
+ "derby",
193
+ "dials",
194
+ "diary",
195
+ "diced",
196
+ "diner",
197
+ "dirty",
198
+ "disco",
199
+ "ditch",
200
+ "diver",
201
+ "dodge",
202
+ "donor",
203
+ "doors",
204
+ "doubt",
205
+ "dough",
206
+ "draft",
207
+ "drain",
208
+ "drake",
209
+ "drank",
210
+ "drawn",
211
+ "dread",
212
+ "dream",
213
+ "dress",
214
+ "dried",
215
+ "drift",
216
+ "drill",
217
+ "drink",
218
+ "drive",
219
+ "drown",
220
+ "drugs",
221
+ "drums",
222
+ "drunk",
223
+ "ducks",
224
+ "dunes",
225
+ "eager",
226
+ "early",
227
+ "earth",
228
+ "easel",
229
+ "eaten",
230
+ "eater",
231
+ "edges",
232
+ "eight",
233
+ "elite",
234
+ "empty",
235
+ "endow",
236
+ "enemy",
237
+ "enjoy",
238
+ "enter",
239
+ "entry",
240
+ "epoch",
241
+ "equal",
242
+ "error",
243
+ "essay",
244
+ "ethos",
245
+ "event",
246
+ "every",
247
+ "exact",
248
+ "excel",
249
+ "exist",
250
+ "extra",
251
+ "fable",
252
+ "faced",
253
+ "facts",
254
+ "faded",
255
+ "fails",
256
+ "fairy",
257
+ "faith",
258
+ "falls",
259
+ "false",
260
+ "famed",
261
+ "fancy",
262
+ "farms",
263
+ "fatal",
264
+ "fault",
265
+ "fauna",
266
+ "favor",
267
+ "feast",
268
+ "feats",
269
+ "feeds",
270
+ "feels",
271
+ "fence",
272
+ "ferry",
273
+ "fetch",
274
+ "fever",
275
+ "fewer",
276
+ "fiber",
277
+ "field",
278
+ "fiend",
279
+ "fiery",
280
+ "fifth",
281
+ "fifty",
282
+ "fight",
283
+ "filed",
284
+ "files",
285
+ "fills",
286
+ "films",
287
+ "final",
288
+ "finds",
289
+ "fined",
290
+ "finer",
291
+ "fires",
292
+ "first",
293
+ "fists",
294
+ "fixed",
295
+ "flags",
296
+ "flame",
297
+ "flank",
298
+ "flaps",
299
+ "flash",
300
+ "flask",
301
+ "flats",
302
+ "flaws",
303
+ "fleas",
304
+ "fleet",
305
+ "flesh",
306
+ "flies",
307
+ "flint",
308
+ "float",
309
+ "flock",
310
+ "flood",
311
+ "floor",
312
+ "flour",
313
+ "flows",
314
+ "fluid",
315
+ "flush",
316
+ "foams",
317
+ "focal",
318
+ "focus",
319
+ "foggy",
320
+ "folds",
321
+ "folks",
322
+ "fonts",
323
+ "foods",
324
+ "fools",
325
+ "force",
326
+ "forge",
327
+ "forms",
328
+ "forth",
329
+ "forty",
330
+ "forum",
331
+ "fouls",
332
+ "found",
333
+ "fount",
334
+ "frame",
335
+ "frank",
336
+ "fraud",
337
+ "freak",
338
+ "fresh",
339
+ "fried",
340
+ "fries",
341
+ "frock",
342
+ "front",
343
+ "frost",
344
+ "frown",
345
+ "froze",
346
+ "fruit",
347
+ "fuels",
348
+ "fully",
349
+ "funds",
350
+ "fungi",
351
+ "funky",
352
+ "funny",
353
+ "fuzzy",
354
+ "gains",
355
+ "games",
356
+ "gangs",
357
+ "gates",
358
+ "gauge",
359
+ "gazed",
360
+ "gears",
361
+ "genus",
362
+ "ghost",
363
+ "giant",
364
+ "gifts",
365
+ "girls",
366
+ "given",
367
+ "gives",
368
+ "gland",
369
+ "glare",
370
+ "glass",
371
+ "glaze",
372
+ "gleam",
373
+ "glean",
374
+ "glide",
375
+ "glint",
376
+ "globe",
377
+ "gloom",
378
+ "glory",
379
+ "gloss",
380
+ "glove",
381
+ "glows",
382
+ "glued",
383
+ "gnome",
384
+ "goals",
385
+ "goats",
386
+ "going",
387
+ "golds",
388
+ "golfs",
389
+ "goose",
390
+ "gorge",
391
+ "gowns",
392
+ "grace",
393
+ "grade",
394
+ "graft",
395
+ "grain",
396
+ "grand",
397
+ "grant",
398
+ "grape",
399
+ "graph",
400
+ "grasp",
401
+ "grass",
402
+ "grate",
403
+ "grave",
404
+ "gravy",
405
+ "graze",
406
+ "great",
407
+ "greed",
408
+ "greek",
409
+ "green",
410
+ "greet",
411
+ "grief",
412
+ "grill",
413
+ "grime",
414
+ "grind",
415
+ "grins",
416
+ "gripe",
417
+ "grips",
418
+ "grist",
419
+ "grits",
420
+ "groan",
421
+ "groom",
422
+ "grope",
423
+ "gross",
424
+ "group",
425
+ "grove",
426
+ "growl",
427
+ "grown",
428
+ "grows",
429
+ "guard",
430
+ "guess",
431
+ "guest",
432
+ "guide",
433
+ "guild",
434
+ "guilt",
435
+ "guise",
436
+ "gulfs",
437
+ "gulps",
438
+ "gummy",
439
+ "gusts",
440
+ "gutsy",
441
+ "habit",
442
+ "hails",
443
+ "hairs",
444
+ "halts",
445
+ "halve",
446
+ "hands",
447
+ "handy",
448
+ "hangs",
449
+ "happy",
450
+ "hardy",
451
+ "harem",
452
+ "harks",
453
+ "harms",
454
+ "harps",
455
+ "harsh",
456
+ "haste",
457
+ "hasty",
458
+ "hatch",
459
+ "hated",
460
+ "hater",
461
+ "hauls",
462
+ "haunt",
463
+ "haven",
464
+ "hawks",
465
+ "heads",
466
+ "heals",
467
+ "heaps",
468
+ "heard",
469
+ "hears",
470
+ "heart",
471
+ "heats",
472
+ "heavy",
473
+ "hedge",
474
+ "heeds",
475
+ "heels",
476
+ "heirs",
477
+ "heist",
478
+ "hello",
479
+ "helps",
480
+ "hence",
481
+ "herbs",
482
+ "herds",
483
+ "heron",
484
+ "hides",
485
+ "highs",
486
+ "hiked",
487
+ "hiker",
488
+ "hikes",
489
+ "hills",
490
+ "hinds",
491
+ "hinge",
492
+ "hints",
493
+ "hippo",
494
+ "hired",
495
+ "hires",
496
+ "hitch",
497
+ "hoard",
498
+ "hoary",
499
+ "hobby",
500
+ "hoist",
501
+ "holds",
502
+ "holes",
503
+ "holly",
504
+ "homes",
505
+ "honed",
506
+ "hones",
507
+ "honey",
508
+ "honks",
509
+ "hoods",
510
+ "hoofs",
511
+ "hooks",
512
+ "hoops",
513
+ "hoots",
514
+ "hoped",
515
+ "hopes",
516
+ "horns",
517
+ "horse",
518
+ "hosed",
519
+ "hoses",
520
+ "hosts",
521
+ "hotly",
522
+ "hound",
523
+ "hours",
524
+ "house",
525
+ "hovel",
526
+ "hover",
527
+ "howdy",
528
+ "howls",
529
+ "hubby",
530
+ "huffs",
531
+ "hulks",
532
+ "hulls",
533
+ "human",
534
+ "humid",
535
+ "humor",
536
+ "humps",
537
+ "hunks",
538
+ "hunts",
539
+ "hurls",
540
+ "hurry",
541
+ "hurts",
542
+ "husks",
543
+ "husky",
544
+ "hutch",
545
+ "hydro",
546
+ "hyena",
547
+ "hyper",
548
+ "icing",
549
+ "icons",
550
+ "ideal",
551
+ "ideas",
552
+ "idiom",
553
+ "idiot",
554
+ "idles",
555
+ "idols",
556
+ "igloo",
557
+ "image",
558
+ "imbue",
559
+ "imply",
560
+ "inbox",
561
+ "incur",
562
+ "index",
563
+ "inept",
564
+ "inert",
565
+ "infer",
566
+ "infos",
567
+ "ingot",
568
+ "inlet",
569
+ "inner",
570
+ "input",
571
+ "inset",
572
+ "inter",
573
+ "intro",
574
+ "irons",
575
+ "irony",
576
+ "issue",
577
+ "items",
578
+ "itchy",
579
+ "ivory",
580
+ "jacks",
581
+ "jails",
582
+ "james",
583
+ "jamps",
584
+ "japan",
585
+ "jared",
586
+ "jarred",
587
+ "jawed",
588
+ "jazzy",
589
+ "jeans",
590
+ "jeeps",
591
+ "jeers",
592
+ "jello",
593
+ "jelly",
594
+ "jenny",
595
+ "jerks",
596
+ "jerry",
597
+ "jesse",
598
+ "jests",
599
+ "jetty",
600
+ "jewel",
601
+ "jiffy",
602
+ "jihad",
603
+ "jimmy",
604
+ "jingo",
605
+ "jinks",
606
+ "jived",
607
+ "jiver",
608
+ "jives",
609
+ "jocks",
610
+ "joeys",
611
+ "joins",
612
+ "joint",
613
+ "joist",
614
+ "joked",
615
+ "joker",
616
+ "jokes",
617
+ "jolly",
618
+ "jonah",
619
+ "jones",
620
+ "joust",
621
+ "jowls",
622
+ "joyed",
623
+ "judge",
624
+ "juice",
625
+ "juicy",
626
+ "jumbo",
627
+ "jumps",
628
+ "jumpy",
629
+ "junco",
630
+ "junks",
631
+ "junky",
632
+ "juror",
633
+ "kails",
634
+ "karma",
635
+ "kayak",
636
+ "keels",
637
+ "keend",
638
+ "keens",
639
+ "keeps",
640
+ "kefir",
641
+ "keira",
642
+ "keith",
643
+ "kelly",
644
+ "kelps",
645
+ "kendo",
646
+ "kenny",
647
+ "kenya",
648
+ "kerns",
649
+ "kevin",
650
+ "khaki",
651
+ "kicks",
652
+ "kills",
653
+ "kilns",
654
+ "kilos",
655
+ "kilts",
656
+ "kinds",
657
+ "kinks",
658
+ "kirks",
659
+ "kites",
660
+ "kiths",
661
+ "kitty",
662
+ "knack",
663
+ "knave",
664
+ "knead",
665
+ "kneel",
666
+ "knelt",
667
+ "knife",
668
+ "knits",
669
+ "knobs",
670
+ "knock",
671
+ "knoll",
672
+ "knots",
673
+ "known",
674
+ "knows",
675
+ "koala",
676
+ "kraft",
677
+ "krill",
678
+ "label",
679
+ "labor",
680
+ "laced",
681
+ "laces",
682
+ "lacks",
683
+ "lager",
684
+ "lakes",
685
+ "lambs",
686
+ "lamed",
687
+ "lames",
688
+ "lamps",
689
+ "lance",
690
+ "lands",
691
+ "lanes",
692
+ "lanky",
693
+ "lapse",
694
+ "larch",
695
+ "lards",
696
+ "large",
697
+ "larks",
698
+ "laser",
699
+ "lasso",
700
+ "latch",
701
+ "laths",
702
+ "lathe",
703
+ "latte",
704
+ "lauds",
705
+ "laugh",
706
+ "laura",
707
+ "laved",
708
+ "laves",
709
+ "lawns",
710
+ "layer",
711
+ "layed",
712
+ "layne",
713
+ "leads",
714
+ "leafs",
715
+ "leafy",
716
+ "leaks",
717
+ "leaky",
718
+ "leans",
719
+ "leant",
720
+ "leaps",
721
+ "learn",
722
+ "lease",
723
+ "leash",
724
+ "least",
725
+ "leapt",
726
+ "leave",
727
+ "ledge",
728
+ "leech",
729
+ "leeds",
730
+ "leeks",
731
+ "leers",
732
+ "leery",
733
+ "lefts",
734
+ "lefty",
735
+ "legal",
736
+ "legit",
737
+ "legos",
738
+ "lemon",
739
+ "lemur",
740
+ "lends",
741
+ "lenis",
742
+ "lenos",
743
+ "leper",
744
+ "lepus",
745
+ "level",
746
+ "lever",
747
+ "lewis",
748
+ "libra",
749
+ "licks",
750
+ "lidos",
751
+ "liege",
752
+ "liens",
753
+ "lifes",
754
+ "lifts",
755
+ "light",
756
+ "likes",
757
+ "lilac",
758
+ "limbs",
759
+ "limey",
760
+ "limit",
761
+ "limns",
762
+ "limos",
763
+ "linas",
764
+ "lined",
765
+ "linen",
766
+ "liner",
767
+ "lines",
768
+ "lingo",
769
+ "lings",
770
+ "links",
771
+ "linos",
772
+ "lints",
773
+ "linty",
774
+ "lions",
775
+ "lipid",
776
+ "lisps",
777
+ "lists",
778
+ "litas",
779
+ "lithe",
780
+ "lived",
781
+ "liven",
782
+ "liver",
783
+ "lives",
784
+ "livre",
785
+ "loads",
786
+ "loafs",
787
+ "loams",
788
+ "loamy",
789
+ "loans",
790
+ "loath",
791
+ "lobby",
792
+ "lobed",
793
+ "lobes",
794
+ "lobos",
795
+ "local",
796
+ "lochs",
797
+ "locks",
798
+ "locus",
799
+ "loden",
800
+ "lodes",
801
+ "lodge",
802
+ "lofts",
803
+ "lofty",
804
+ "logan",
805
+ "logas",
806
+ "logic",
807
+ "logos",
808
+ "logue",
809
+ "loins",
810
+ "loire",
811
+ "loked",
812
+ "loken",
813
+ "loken",
814
+ "lokey",
815
+ "lolas",
816
+ "lolly",
817
+ "loman",
818
+ "lonas",
819
+ "loned",
820
+ "loner",
821
+ "lones",
822
+ "longa",
823
+ "longe",
824
+ "longs",
825
+ "loona",
826
+ "loons",
827
+ "loony",
828
+ "loops",
829
+ "loopy",
830
+ "loose",
831
+ "loosh",
832
+ "loots",
833
+ "looty",
834
+ "loped",
835
+ "loper",
836
+ "lopes",
837
+ "lopey",
838
+ "lopht",
839
+ "lopia",
840
+ "lopin",
841
+ "lopko",
842
+ "lopod",
843
+ "loppa",
844
+ "loppy",
845
+ "lopsy",
846
+ "loral",
847
+ "loran",
848
+ "loras",
849
+ "lorch",
850
+ "lords",
851
+ "lordy",
852
+ "lorem",
853
+ "lorer",
854
+ "lores",
855
+ "loret",
856
+ "lorey",
857
+ "loria",
858
+ "lorik",
859
+ "loris",
860
+ "lorna",
861
+ "lorne",
862
+ "lorns",
863
+ "lorny",
864
+ "loro",
865
+ "loron",
866
+ "loros",
867
+ "lorox",
868
+ "lorry",
869
+ "lorsa",
870
+ "lorsi",
871
+ "lorsy",
872
+ "lorta",
873
+ "lorto",
874
+ "lorty",
875
+ "lorus",
876
+ "lorva",
877
+ "lorve",
878
+ "lorvy",
879
+ "lorza",
880
+ "losah",
881
+ "losak",
882
+ "losar",
883
+ "losba",
884
+ "losca",
885
+ "losda",
886
+ "losde",
887
+ "losdy",
888
+ "losea",
889
+ "loseb",
890
+ "losec",
891
+ "losed",
892
+ "losee",
893
+ "losel",
894
+ "losem",
895
+ "losen",
896
+ "loser",
897
+ "loses",
898
+ "loset",
899
+ "losev",
900
+ "losew",
901
+ "losex",
902
+ "losey",
903
+ "losez",
904
+ "losfa",
905
+ "losfd",
906
+ "losfe",
907
+ "losfh",
908
+ "losfi",
909
+ "losfk",
910
+ "losfl",
911
+ "losfm",
912
+ "losfn",
913
+ "losfo",
914
+ "losfp",
915
+ "losfq",
916
+ "losfr",
917
+ "losfs",
918
+ "losft",
919
+ "losfu",
920
+ "losfv",
921
+ "losfw",
922
+ "losfx",
923
+ "losfy",
924
+ "losfz"
925
+ ];
926
+ }
927
+ });
928
+
929
+ // src/server/auth-state.ts
930
+ var auth_state_exports = {};
931
+ __export(auth_state_exports, {
932
+ cleanupAuthState: () => cleanupAuthState,
933
+ clear2FAState: () => clear2FAState,
934
+ get2FAState: () => get2FAState,
935
+ getSessionPassword: () => getSessionPassword,
936
+ initAuthState: () => initAuthState,
937
+ on2FAStateChange: () => on2FAStateChange,
938
+ onPasswordRotated: () => onPasswordRotated,
939
+ rotateSessionPassword: () => rotateSessionPassword,
940
+ save2FAState: () => save2FAState
941
+ });
942
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, watchFile, unlinkSync } from "node:fs";
943
+ import { join } from "node:path";
944
+ import { homedir } from "node:os";
945
+ import { EventEmitter } from "node:events";
946
+ async function initAuthState() {
947
+ await authState.init();
948
+ }
949
+ function getSessionPassword() {
950
+ return authState.getSessionPassword();
951
+ }
952
+ function rotateSessionPassword() {
953
+ return authState.rotateSessionPassword();
954
+ }
955
+ function get2FAState() {
956
+ return authState.get2FAState();
957
+ }
958
+ function save2FAState(state) {
959
+ authState.save2FAState(state);
960
+ }
961
+ function clear2FAState() {
962
+ authState.clear2FAState();
963
+ }
964
+ function onPasswordRotated(callback) {
965
+ authState.onPasswordRotated(callback);
966
+ }
967
+ function on2FAStateChange(callback) {
968
+ authState.on2FAStateChange(callback);
969
+ }
970
+ function cleanupAuthState() {
971
+ authState.cleanup();
972
+ }
973
+ var STATE_DIR, SESSION_PASSWORD_PATH, TOTP_STATE_PATH, AuthState, authState;
974
+ var init_auth_state = __esm({
975
+ "src/server/auth-state.ts"() {
976
+ init_password_gen();
977
+ init_logger();
978
+ STATE_DIR = join(homedir(), ".puttry");
979
+ SESSION_PASSWORD_PATH = join(STATE_DIR, "session-password.txt");
980
+ TOTP_STATE_PATH = join(STATE_DIR, "2fa-state.json");
981
+ AuthState = class extends EventEmitter {
982
+ sessionPassword = null;
983
+ totpState = null;
984
+ watchers = [];
985
+ async init() {
986
+ mkdirSync(STATE_DIR, { recursive: true });
987
+ this.loadSessionPassword();
988
+ this.loadTotpState();
989
+ this.watchFiles();
990
+ }
991
+ loadSessionPassword() {
992
+ if (existsSync(SESSION_PASSWORD_PATH)) {
993
+ try {
994
+ this.sessionPassword = readFileSync(SESSION_PASSWORD_PATH, "utf-8").trim();
995
+ logger_default.info(`[auth-state] Session password loaded from disk`);
996
+ } catch (err) {
997
+ logger_default.error(`[auth-state] Failed to load session password: ${err instanceof Error ? err.message : err}`);
998
+ this.generateNewSessionPassword();
999
+ }
1000
+ } else {
1001
+ this.generateNewSessionPassword();
1002
+ }
1003
+ }
1004
+ generateNewSessionPassword() {
1005
+ const type = (process.env.SESSION_PASSWORD_TYPE || "xkcd").toLowerCase();
1006
+ const length = parseInt(process.env.SESSION_PASSWORD_LENGTH || "4", 10);
1007
+ if (type === "random") {
1008
+ this.sessionPassword = generateRandomPassword(length);
1009
+ } else {
1010
+ this.sessionPassword = generateXkcdPassword(length);
1011
+ }
1012
+ this.persistSessionPassword();
1013
+ logger_default.info(`[auth-state] Generated new session password`);
1014
+ }
1015
+ persistSessionPassword() {
1016
+ if (!this.sessionPassword) return;
1017
+ try {
1018
+ writeFileSync(SESSION_PASSWORD_PATH, this.sessionPassword, { mode: 384 });
1019
+ } catch (err) {
1020
+ logger_default.error(`[auth-state] Failed to save session password: ${err instanceof Error ? err.message : err}`);
1021
+ }
1022
+ }
1023
+ loadTotpState() {
1024
+ if (existsSync(TOTP_STATE_PATH)) {
1025
+ try {
1026
+ const data = readFileSync(TOTP_STATE_PATH, "utf-8");
1027
+ this.totpState = JSON.parse(data);
1028
+ logger_default.info(`[auth-state] TOTP state loaded from ${TOTP_STATE_PATH}: verified=${this.totpState?.verified}`);
1029
+ } catch (err) {
1030
+ logger_default.error(`[auth-state] Failed to load TOTP state from ${TOTP_STATE_PATH}: ${err instanceof Error ? err.message : err}`);
1031
+ this.totpState = null;
1032
+ }
1033
+ } else {
1034
+ logger_default.info(`[auth-state] TOTP state file not found at ${TOTP_STATE_PATH}`);
1035
+ }
1036
+ }
1037
+ watchFiles() {
1038
+ watchFile(SESSION_PASSWORD_PATH, () => {
1039
+ const oldPassword = this.sessionPassword;
1040
+ this.loadSessionPassword();
1041
+ if (oldPassword !== this.sessionPassword) {
1042
+ logger_default.info(`[auth-state] Session password changed externally`);
1043
+ this.emit("passwordRotated");
1044
+ }
1045
+ });
1046
+ watchFile(TOTP_STATE_PATH, () => {
1047
+ const oldState = this.totpState;
1048
+ this.loadTotpState();
1049
+ if (JSON.stringify(oldState) !== JSON.stringify(this.totpState)) {
1050
+ logger_default.info(`[auth-state] TOTP state changed externally`);
1051
+ this.emit("2faChanged");
1052
+ }
1053
+ });
1054
+ }
1055
+ getSessionPassword() {
1056
+ if (!this.sessionPassword) {
1057
+ this.generateNewSessionPassword();
1058
+ }
1059
+ return this.sessionPassword;
1060
+ }
1061
+ rotateSessionPassword() {
1062
+ this.generateNewSessionPassword();
1063
+ this.emit("passwordRotated");
1064
+ return this.sessionPassword;
1065
+ }
1066
+ get2FAState() {
1067
+ this.loadTotpState();
1068
+ logger_default.info(`[auth-state] get2FAState() returning: ${this.totpState ? `verified=${this.totpState.verified}` : "null"}`);
1069
+ return this.totpState;
1070
+ }
1071
+ save2FAState(state) {
1072
+ this.totpState = state;
1073
+ try {
1074
+ mkdirSync(STATE_DIR, { recursive: true });
1075
+ writeFileSync(TOTP_STATE_PATH, JSON.stringify(state, null, 2), { mode: 384 });
1076
+ this.emit("2faChanged");
1077
+ logger_default.info(`[auth-state] TOTP state saved`);
1078
+ } catch (err) {
1079
+ logger_default.error(`[auth-state] Failed to save TOTP state: ${err instanceof Error ? err.message : err}`);
1080
+ }
1081
+ }
1082
+ clear2FAState() {
1083
+ this.totpState = null;
1084
+ try {
1085
+ if (existsSync(TOTP_STATE_PATH)) {
1086
+ unlinkSync(TOTP_STATE_PATH);
1087
+ }
1088
+ this.emit("2faChanged");
1089
+ logger_default.info(`[auth-state] TOTP state cleared`);
1090
+ } catch (err) {
1091
+ logger_default.error(`[auth-state] Failed to clear TOTP state: ${err instanceof Error ? err.message : err}`);
1092
+ }
1093
+ }
1094
+ on2FAStateChange(callback) {
1095
+ this.on("2faChanged", callback);
1096
+ }
1097
+ onPasswordRotated(callback) {
1098
+ this.on("passwordRotated", callback);
1099
+ }
1100
+ cleanup() {
1101
+ this.watchers.forEach((w) => clearTimeout(w));
1102
+ this.removeAllListeners();
1103
+ }
1104
+ };
1105
+ authState = new AuthState();
1106
+ }
1107
+ });
1108
+
1109
+ // src/server/totp-helper.ts
1110
+ var totp_helper_exports = {};
1111
+ __export(totp_helper_exports, {
1112
+ generateQRCode: () => generateQRCode,
1113
+ generateTotpSecret: () => generateTotpSecret,
1114
+ verifyTotp: () => verifyTotp
1115
+ });
1116
+ import { verifySync, generateSecret, generateURI } from "otplib";
1117
+ import QRCode from "qrcode";
1118
+ async function generateTotpSecret(_email) {
1119
+ return generateSecret();
1120
+ }
1121
+ async function generateQRCode(secret, email, appName = "PuTTrY") {
1122
+ const keyUri = generateURI({
1123
+ secret,
1124
+ label: email,
1125
+ issuer: appName
1126
+ });
1127
+ const dataUrl = await QRCode.toDataURL(keyUri);
1128
+ return {
1129
+ dataUrl,
1130
+ manualEntryKey: secret
1131
+ };
1132
+ }
1133
+ async function verifyTotp(secret, token) {
1134
+ try {
1135
+ const result = verifySync({ token, secret });
1136
+ return result?.valid ?? false;
1137
+ } catch {
1138
+ return false;
1139
+ }
1140
+ }
1141
+ var init_totp_helper = __esm({
1142
+ "src/server/totp-helper.ts"() {
1143
+ }
1144
+ });
1145
+
1146
+ // node_modules/ip-address/dist/common.js
1147
+ var require_common = __commonJS({
1148
+ "node_modules/ip-address/dist/common.js"(exports) {
1149
+ "use strict";
1150
+ Object.defineProperty(exports, "__esModule", { value: true });
1151
+ exports.isInSubnet = isInSubnet;
1152
+ exports.isCorrect = isCorrect;
1153
+ exports.numberToPaddedHex = numberToPaddedHex;
1154
+ exports.stringToPaddedHex = stringToPaddedHex;
1155
+ exports.testBit = testBit;
1156
+ function isInSubnet(address) {
1157
+ if (this.subnetMask < address.subnetMask) {
1158
+ return false;
1159
+ }
1160
+ if (this.mask(address.subnetMask) === address.mask()) {
1161
+ return true;
1162
+ }
1163
+ return false;
1164
+ }
1165
+ function isCorrect(defaultBits) {
1166
+ return function() {
1167
+ if (this.addressMinusSuffix !== this.correctForm()) {
1168
+ return false;
1169
+ }
1170
+ if (this.subnetMask === defaultBits && !this.parsedSubnet) {
1171
+ return true;
1172
+ }
1173
+ return this.parsedSubnet === String(this.subnetMask);
1174
+ };
1175
+ }
1176
+ function numberToPaddedHex(number) {
1177
+ return number.toString(16).padStart(2, "0");
1178
+ }
1179
+ function stringToPaddedHex(numberString) {
1180
+ return numberToPaddedHex(parseInt(numberString, 10));
1181
+ }
1182
+ function testBit(binaryValue, position) {
1183
+ const { length } = binaryValue;
1184
+ if (position > length) {
1185
+ return false;
1186
+ }
1187
+ const positionInString = length - position;
1188
+ return binaryValue.substring(positionInString, positionInString + 1) === "1";
1189
+ }
1190
+ }
1191
+ });
1192
+
1193
+ // node_modules/ip-address/dist/v4/constants.js
1194
+ var require_constants = __commonJS({
1195
+ "node_modules/ip-address/dist/v4/constants.js"(exports) {
1196
+ "use strict";
1197
+ Object.defineProperty(exports, "__esModule", { value: true });
1198
+ exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0;
1199
+ exports.BITS = 32;
1200
+ exports.GROUPS = 4;
1201
+ exports.RE_ADDRESS = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/g;
1202
+ exports.RE_SUBNET_STRING = /\/\d{1,2}$/;
1203
+ }
1204
+ });
1205
+
1206
+ // node_modules/ip-address/dist/address-error.js
1207
+ var require_address_error = __commonJS({
1208
+ "node_modules/ip-address/dist/address-error.js"(exports) {
1209
+ "use strict";
1210
+ Object.defineProperty(exports, "__esModule", { value: true });
1211
+ exports.AddressError = void 0;
1212
+ var AddressError = class extends Error {
1213
+ constructor(message, parseMessage) {
1214
+ super(message);
1215
+ this.name = "AddressError";
1216
+ this.parseMessage = parseMessage;
1217
+ }
1218
+ };
1219
+ exports.AddressError = AddressError;
1220
+ }
1221
+ });
1222
+
1223
+ // node_modules/ip-address/dist/ipv4.js
1224
+ var require_ipv4 = __commonJS({
1225
+ "node_modules/ip-address/dist/ipv4.js"(exports) {
1226
+ "use strict";
1227
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
1228
+ if (k2 === void 0) k2 = k;
1229
+ var desc = Object.getOwnPropertyDescriptor(m, k);
1230
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1231
+ desc = { enumerable: true, get: function() {
1232
+ return m[k];
1233
+ } };
1234
+ }
1235
+ Object.defineProperty(o, k2, desc);
1236
+ }) : (function(o, m, k, k2) {
1237
+ if (k2 === void 0) k2 = k;
1238
+ o[k2] = m[k];
1239
+ }));
1240
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
1241
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
1242
+ }) : function(o, v) {
1243
+ o["default"] = v;
1244
+ });
1245
+ var __importStar = exports && exports.__importStar || function(mod) {
1246
+ if (mod && mod.__esModule) return mod;
1247
+ var result = {};
1248
+ if (mod != null) {
1249
+ for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
1250
+ }
1251
+ __setModuleDefault(result, mod);
1252
+ return result;
1253
+ };
1254
+ Object.defineProperty(exports, "__esModule", { value: true });
1255
+ exports.Address4 = void 0;
1256
+ var common = __importStar(require_common());
1257
+ var constants = __importStar(require_constants());
1258
+ var address_error_1 = require_address_error();
1259
+ var Address4 = class _Address4 {
1260
+ constructor(address) {
1261
+ this.groups = constants.GROUPS;
1262
+ this.parsedAddress = [];
1263
+ this.parsedSubnet = "";
1264
+ this.subnet = "/32";
1265
+ this.subnetMask = 32;
1266
+ this.v4 = true;
1267
+ this.isCorrect = common.isCorrect(constants.BITS);
1268
+ this.isInSubnet = common.isInSubnet;
1269
+ this.address = address;
1270
+ const subnet = constants.RE_SUBNET_STRING.exec(address);
1271
+ if (subnet) {
1272
+ this.parsedSubnet = subnet[0].replace("/", "");
1273
+ this.subnetMask = parseInt(this.parsedSubnet, 10);
1274
+ this.subnet = `/${this.subnetMask}`;
1275
+ if (this.subnetMask < 0 || this.subnetMask > constants.BITS) {
1276
+ throw new address_error_1.AddressError("Invalid subnet mask.");
1277
+ }
1278
+ address = address.replace(constants.RE_SUBNET_STRING, "");
1279
+ }
1280
+ this.addressMinusSuffix = address;
1281
+ this.parsedAddress = this.parse(address);
1282
+ }
1283
+ static isValid(address) {
1284
+ try {
1285
+ new _Address4(address);
1286
+ return true;
1287
+ } catch (e) {
1288
+ return false;
1289
+ }
1290
+ }
1291
+ /*
1292
+ * Parses a v4 address
1293
+ */
1294
+ parse(address) {
1295
+ const groups = address.split(".");
1296
+ if (!address.match(constants.RE_ADDRESS)) {
1297
+ throw new address_error_1.AddressError("Invalid IPv4 address.");
1298
+ }
1299
+ return groups;
1300
+ }
1301
+ /**
1302
+ * Returns the correct form of an address
1303
+ * @memberof Address4
1304
+ * @instance
1305
+ * @returns {String}
1306
+ */
1307
+ correctForm() {
1308
+ return this.parsedAddress.map((part) => parseInt(part, 10)).join(".");
1309
+ }
1310
+ /**
1311
+ * Converts a hex string to an IPv4 address object
1312
+ * @memberof Address4
1313
+ * @static
1314
+ * @param {string} hex - a hex string to convert
1315
+ * @returns {Address4}
1316
+ */
1317
+ static fromHex(hex) {
1318
+ const padded = hex.replace(/:/g, "").padStart(8, "0");
1319
+ const groups = [];
1320
+ let i;
1321
+ for (i = 0; i < 8; i += 2) {
1322
+ const h = padded.slice(i, i + 2);
1323
+ groups.push(parseInt(h, 16));
1324
+ }
1325
+ return new _Address4(groups.join("."));
1326
+ }
1327
+ /**
1328
+ * Converts an integer into a IPv4 address object
1329
+ * @memberof Address4
1330
+ * @static
1331
+ * @param {integer} integer - a number to convert
1332
+ * @returns {Address4}
1333
+ */
1334
+ static fromInteger(integer) {
1335
+ return _Address4.fromHex(integer.toString(16));
1336
+ }
1337
+ /**
1338
+ * Return an address from in-addr.arpa form
1339
+ * @memberof Address4
1340
+ * @static
1341
+ * @param {string} arpaFormAddress - an 'in-addr.arpa' form ipv4 address
1342
+ * @returns {Adress4}
1343
+ * @example
1344
+ * var address = Address4.fromArpa(42.2.0.192.in-addr.arpa.)
1345
+ * address.correctForm(); // '192.0.2.42'
1346
+ */
1347
+ static fromArpa(arpaFormAddress) {
1348
+ const leader = arpaFormAddress.replace(/(\.in-addr\.arpa)?\.$/, "");
1349
+ const address = leader.split(".").reverse().join(".");
1350
+ return new _Address4(address);
1351
+ }
1352
+ /**
1353
+ * Converts an IPv4 address object to a hex string
1354
+ * @memberof Address4
1355
+ * @instance
1356
+ * @returns {String}
1357
+ */
1358
+ toHex() {
1359
+ return this.parsedAddress.map((part) => common.stringToPaddedHex(part)).join(":");
1360
+ }
1361
+ /**
1362
+ * Converts an IPv4 address object to an array of bytes
1363
+ * @memberof Address4
1364
+ * @instance
1365
+ * @returns {Array}
1366
+ */
1367
+ toArray() {
1368
+ return this.parsedAddress.map((part) => parseInt(part, 10));
1369
+ }
1370
+ /**
1371
+ * Converts an IPv4 address object to an IPv6 address group
1372
+ * @memberof Address4
1373
+ * @instance
1374
+ * @returns {String}
1375
+ */
1376
+ toGroup6() {
1377
+ const output = [];
1378
+ let i;
1379
+ for (i = 0; i < constants.GROUPS; i += 2) {
1380
+ output.push(`${common.stringToPaddedHex(this.parsedAddress[i])}${common.stringToPaddedHex(this.parsedAddress[i + 1])}`);
1381
+ }
1382
+ return output.join(":");
1383
+ }
1384
+ /**
1385
+ * Returns the address as a `bigint`
1386
+ * @memberof Address4
1387
+ * @instance
1388
+ * @returns {bigint}
1389
+ */
1390
+ bigInt() {
1391
+ return BigInt(`0x${this.parsedAddress.map((n) => common.stringToPaddedHex(n)).join("")}`);
1392
+ }
1393
+ /**
1394
+ * Helper function getting start address.
1395
+ * @memberof Address4
1396
+ * @instance
1397
+ * @returns {bigint}
1398
+ */
1399
+ _startAddress() {
1400
+ return BigInt(`0b${this.mask() + "0".repeat(constants.BITS - this.subnetMask)}`);
1401
+ }
1402
+ /**
1403
+ * The first address in the range given by this address' subnet.
1404
+ * Often referred to as the Network Address.
1405
+ * @memberof Address4
1406
+ * @instance
1407
+ * @returns {Address4}
1408
+ */
1409
+ startAddress() {
1410
+ return _Address4.fromBigInt(this._startAddress());
1411
+ }
1412
+ /**
1413
+ * The first host address in the range given by this address's subnet ie
1414
+ * the first address after the Network Address
1415
+ * @memberof Address4
1416
+ * @instance
1417
+ * @returns {Address4}
1418
+ */
1419
+ startAddressExclusive() {
1420
+ const adjust = BigInt("1");
1421
+ return _Address4.fromBigInt(this._startAddress() + adjust);
1422
+ }
1423
+ /**
1424
+ * Helper function getting end address.
1425
+ * @memberof Address4
1426
+ * @instance
1427
+ * @returns {bigint}
1428
+ */
1429
+ _endAddress() {
1430
+ return BigInt(`0b${this.mask() + "1".repeat(constants.BITS - this.subnetMask)}`);
1431
+ }
1432
+ /**
1433
+ * The last address in the range given by this address' subnet
1434
+ * Often referred to as the Broadcast
1435
+ * @memberof Address4
1436
+ * @instance
1437
+ * @returns {Address4}
1438
+ */
1439
+ endAddress() {
1440
+ return _Address4.fromBigInt(this._endAddress());
1441
+ }
1442
+ /**
1443
+ * The last host address in the range given by this address's subnet ie
1444
+ * the last address prior to the Broadcast Address
1445
+ * @memberof Address4
1446
+ * @instance
1447
+ * @returns {Address4}
1448
+ */
1449
+ endAddressExclusive() {
1450
+ const adjust = BigInt("1");
1451
+ return _Address4.fromBigInt(this._endAddress() - adjust);
1452
+ }
1453
+ /**
1454
+ * Converts a BigInt to a v4 address object
1455
+ * @memberof Address4
1456
+ * @static
1457
+ * @param {bigint} bigInt - a BigInt to convert
1458
+ * @returns {Address4}
1459
+ */
1460
+ static fromBigInt(bigInt) {
1461
+ return _Address4.fromHex(bigInt.toString(16));
1462
+ }
1463
+ /**
1464
+ * Convert a byte array to an Address4 object
1465
+ * @memberof Address4
1466
+ * @static
1467
+ * @param {Array<number>} bytes - an array of 4 bytes (0-255)
1468
+ * @returns {Address4}
1469
+ */
1470
+ static fromByteArray(bytes) {
1471
+ if (bytes.length !== 4) {
1472
+ throw new address_error_1.AddressError("IPv4 addresses require exactly 4 bytes");
1473
+ }
1474
+ for (let i = 0; i < bytes.length; i++) {
1475
+ if (!Number.isInteger(bytes[i]) || bytes[i] < 0 || bytes[i] > 255) {
1476
+ throw new address_error_1.AddressError("All bytes must be integers between 0 and 255");
1477
+ }
1478
+ }
1479
+ return this.fromUnsignedByteArray(bytes);
1480
+ }
1481
+ /**
1482
+ * Convert an unsigned byte array to an Address4 object
1483
+ * @memberof Address4
1484
+ * @static
1485
+ * @param {Array<number>} bytes - an array of 4 unsigned bytes (0-255)
1486
+ * @returns {Address4}
1487
+ */
1488
+ static fromUnsignedByteArray(bytes) {
1489
+ if (bytes.length !== 4) {
1490
+ throw new address_error_1.AddressError("IPv4 addresses require exactly 4 bytes");
1491
+ }
1492
+ const address = bytes.join(".");
1493
+ return new _Address4(address);
1494
+ }
1495
+ /**
1496
+ * Returns the first n bits of the address, defaulting to the
1497
+ * subnet mask
1498
+ * @memberof Address4
1499
+ * @instance
1500
+ * @returns {String}
1501
+ */
1502
+ mask(mask) {
1503
+ if (mask === void 0) {
1504
+ mask = this.subnetMask;
1505
+ }
1506
+ return this.getBitsBase2(0, mask);
1507
+ }
1508
+ /**
1509
+ * Returns the bits in the given range as a base-2 string
1510
+ * @memberof Address4
1511
+ * @instance
1512
+ * @returns {string}
1513
+ */
1514
+ getBitsBase2(start, end) {
1515
+ return this.binaryZeroPad().slice(start, end);
1516
+ }
1517
+ /**
1518
+ * Return the reversed ip6.arpa form of the address
1519
+ * @memberof Address4
1520
+ * @param {Object} options
1521
+ * @param {boolean} options.omitSuffix - omit the "in-addr.arpa" suffix
1522
+ * @instance
1523
+ * @returns {String}
1524
+ */
1525
+ reverseForm(options) {
1526
+ if (!options) {
1527
+ options = {};
1528
+ }
1529
+ const reversed = this.correctForm().split(".").reverse().join(".");
1530
+ if (options.omitSuffix) {
1531
+ return reversed;
1532
+ }
1533
+ return `${reversed}.in-addr.arpa.`;
1534
+ }
1535
+ /**
1536
+ * Returns true if the given address is a multicast address
1537
+ * @memberof Address4
1538
+ * @instance
1539
+ * @returns {boolean}
1540
+ */
1541
+ isMulticast() {
1542
+ return this.isInSubnet(new _Address4("224.0.0.0/4"));
1543
+ }
1544
+ /**
1545
+ * Returns a zero-padded base-2 string representation of the address
1546
+ * @memberof Address4
1547
+ * @instance
1548
+ * @returns {string}
1549
+ */
1550
+ binaryZeroPad() {
1551
+ return this.bigInt().toString(2).padStart(constants.BITS, "0");
1552
+ }
1553
+ /**
1554
+ * Groups an IPv4 address for inclusion at the end of an IPv6 address
1555
+ * @returns {String}
1556
+ */
1557
+ groupForV6() {
1558
+ const segments = this.parsedAddress;
1559
+ return this.address.replace(constants.RE_ADDRESS, `<span class="hover-group group-v4 group-6">${segments.slice(0, 2).join(".")}</span>.<span class="hover-group group-v4 group-7">${segments.slice(2, 4).join(".")}</span>`);
1560
+ }
1561
+ };
1562
+ exports.Address4 = Address4;
1563
+ }
1564
+ });
1565
+
1566
+ // node_modules/ip-address/dist/v6/constants.js
1567
+ var require_constants2 = __commonJS({
1568
+ "node_modules/ip-address/dist/v6/constants.js"(exports) {
1569
+ "use strict";
1570
+ Object.defineProperty(exports, "__esModule", { value: true });
1571
+ exports.RE_URL_WITH_PORT = exports.RE_URL = exports.RE_ZONE_STRING = exports.RE_SUBNET_STRING = exports.RE_BAD_ADDRESS = exports.RE_BAD_CHARACTERS = exports.TYPES = exports.SCOPES = exports.GROUPS = exports.BITS = void 0;
1572
+ exports.BITS = 128;
1573
+ exports.GROUPS = 8;
1574
+ exports.SCOPES = {
1575
+ 0: "Reserved",
1576
+ 1: "Interface local",
1577
+ 2: "Link local",
1578
+ 4: "Admin local",
1579
+ 5: "Site local",
1580
+ 8: "Organization local",
1581
+ 14: "Global",
1582
+ 15: "Reserved"
1583
+ };
1584
+ exports.TYPES = {
1585
+ "ff01::1/128": "Multicast (All nodes on this interface)",
1586
+ "ff01::2/128": "Multicast (All routers on this interface)",
1587
+ "ff02::1/128": "Multicast (All nodes on this link)",
1588
+ "ff02::2/128": "Multicast (All routers on this link)",
1589
+ "ff05::2/128": "Multicast (All routers in this site)",
1590
+ "ff02::5/128": "Multicast (OSPFv3 AllSPF routers)",
1591
+ "ff02::6/128": "Multicast (OSPFv3 AllDR routers)",
1592
+ "ff02::9/128": "Multicast (RIP routers)",
1593
+ "ff02::a/128": "Multicast (EIGRP routers)",
1594
+ "ff02::d/128": "Multicast (PIM routers)",
1595
+ "ff02::16/128": "Multicast (MLDv2 reports)",
1596
+ "ff01::fb/128": "Multicast (mDNSv6)",
1597
+ "ff02::fb/128": "Multicast (mDNSv6)",
1598
+ "ff05::fb/128": "Multicast (mDNSv6)",
1599
+ "ff02::1:2/128": "Multicast (All DHCP servers and relay agents on this link)",
1600
+ "ff05::1:2/128": "Multicast (All DHCP servers and relay agents in this site)",
1601
+ "ff02::1:3/128": "Multicast (All DHCP servers on this link)",
1602
+ "ff05::1:3/128": "Multicast (All DHCP servers in this site)",
1603
+ "::/128": "Unspecified",
1604
+ "::1/128": "Loopback",
1605
+ "ff00::/8": "Multicast",
1606
+ "fe80::/10": "Link-local unicast"
1607
+ };
1608
+ exports.RE_BAD_CHARACTERS = /([^0-9a-f:/%])/gi;
1609
+ exports.RE_BAD_ADDRESS = /([0-9a-f]{5,}|:{3,}|[^:]:$|^:[^:]|\/$)/gi;
1610
+ exports.RE_SUBNET_STRING = /\/\d{1,3}(?=%|$)/;
1611
+ exports.RE_ZONE_STRING = /%.*$/;
1612
+ exports.RE_URL = /^\[{0,1}([0-9a-f:]+)\]{0,1}/;
1613
+ exports.RE_URL_WITH_PORT = /\[([0-9a-f:]+)\]:([0-9]{1,5})/;
1614
+ }
1615
+ });
1616
+
1617
+ // node_modules/ip-address/dist/v6/helpers.js
1618
+ var require_helpers = __commonJS({
1619
+ "node_modules/ip-address/dist/v6/helpers.js"(exports) {
1620
+ "use strict";
1621
+ Object.defineProperty(exports, "__esModule", { value: true });
1622
+ exports.spanAllZeroes = spanAllZeroes;
1623
+ exports.spanAll = spanAll;
1624
+ exports.spanLeadingZeroes = spanLeadingZeroes;
1625
+ exports.simpleGroup = simpleGroup;
1626
+ function spanAllZeroes(s) {
1627
+ return s.replace(/(0+)/g, '<span class="zero">$1</span>');
1628
+ }
1629
+ function spanAll(s, offset = 0) {
1630
+ const letters = s.split("");
1631
+ return letters.map((n, i) => `<span class="digit value-${n} position-${i + offset}">${spanAllZeroes(n)}</span>`).join("");
1632
+ }
1633
+ function spanLeadingZeroesSimple(group) {
1634
+ return group.replace(/^(0+)/, '<span class="zero">$1</span>');
1635
+ }
1636
+ function spanLeadingZeroes(address) {
1637
+ const groups = address.split(":");
1638
+ return groups.map((g) => spanLeadingZeroesSimple(g)).join(":");
1639
+ }
1640
+ function simpleGroup(addressString, offset = 0) {
1641
+ const groups = addressString.split(":");
1642
+ return groups.map((g, i) => {
1643
+ if (/group-v4/.test(g)) {
1644
+ return g;
1645
+ }
1646
+ return `<span class="hover-group group-${i + offset}">${spanLeadingZeroesSimple(g)}</span>`;
1647
+ });
1648
+ }
1649
+ }
1650
+ });
1651
+
1652
+ // node_modules/ip-address/dist/v6/regular-expressions.js
1653
+ var require_regular_expressions = __commonJS({
1654
+ "node_modules/ip-address/dist/v6/regular-expressions.js"(exports) {
1655
+ "use strict";
1656
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
1657
+ if (k2 === void 0) k2 = k;
1658
+ var desc = Object.getOwnPropertyDescriptor(m, k);
1659
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1660
+ desc = { enumerable: true, get: function() {
1661
+ return m[k];
1662
+ } };
1663
+ }
1664
+ Object.defineProperty(o, k2, desc);
1665
+ }) : (function(o, m, k, k2) {
1666
+ if (k2 === void 0) k2 = k;
1667
+ o[k2] = m[k];
1668
+ }));
1669
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
1670
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
1671
+ }) : function(o, v) {
1672
+ o["default"] = v;
1673
+ });
1674
+ var __importStar = exports && exports.__importStar || function(mod) {
1675
+ if (mod && mod.__esModule) return mod;
1676
+ var result = {};
1677
+ if (mod != null) {
1678
+ for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
1679
+ }
1680
+ __setModuleDefault(result, mod);
1681
+ return result;
1682
+ };
1683
+ Object.defineProperty(exports, "__esModule", { value: true });
1684
+ exports.ADDRESS_BOUNDARY = void 0;
1685
+ exports.groupPossibilities = groupPossibilities;
1686
+ exports.padGroup = padGroup;
1687
+ exports.simpleRegularExpression = simpleRegularExpression;
1688
+ exports.possibleElisions = possibleElisions;
1689
+ var v6 = __importStar(require_constants2());
1690
+ function groupPossibilities(possibilities) {
1691
+ return `(${possibilities.join("|")})`;
1692
+ }
1693
+ function padGroup(group) {
1694
+ if (group.length < 4) {
1695
+ return `0{0,${4 - group.length}}${group}`;
1696
+ }
1697
+ return group;
1698
+ }
1699
+ exports.ADDRESS_BOUNDARY = "[^A-Fa-f0-9:]";
1700
+ function simpleRegularExpression(groups) {
1701
+ const zeroIndexes = [];
1702
+ groups.forEach((group, i) => {
1703
+ const groupInteger = parseInt(group, 16);
1704
+ if (groupInteger === 0) {
1705
+ zeroIndexes.push(i);
1706
+ }
1707
+ });
1708
+ const possibilities = zeroIndexes.map((zeroIndex) => groups.map((group, i) => {
1709
+ if (i === zeroIndex) {
1710
+ const elision = i === 0 || i === v6.GROUPS - 1 ? ":" : "";
1711
+ return groupPossibilities([padGroup(group), elision]);
1712
+ }
1713
+ return padGroup(group);
1714
+ }).join(":"));
1715
+ possibilities.push(groups.map(padGroup).join(":"));
1716
+ return groupPossibilities(possibilities);
1717
+ }
1718
+ function possibleElisions(elidedGroups, moreLeft, moreRight) {
1719
+ const left = moreLeft ? "" : ":";
1720
+ const right = moreRight ? "" : ":";
1721
+ const possibilities = [];
1722
+ if (!moreLeft && !moreRight) {
1723
+ possibilities.push("::");
1724
+ }
1725
+ if (moreLeft && moreRight) {
1726
+ possibilities.push("");
1727
+ }
1728
+ if (moreRight && !moreLeft || !moreRight && moreLeft) {
1729
+ possibilities.push(":");
1730
+ }
1731
+ possibilities.push(`${left}(:0{1,4}){1,${elidedGroups - 1}}`);
1732
+ possibilities.push(`(0{1,4}:){1,${elidedGroups - 1}}${right}`);
1733
+ possibilities.push(`(0{1,4}:){${elidedGroups - 1}}0{1,4}`);
1734
+ for (let groups = 1; groups < elidedGroups - 1; groups++) {
1735
+ for (let position = 1; position < elidedGroups - groups; position++) {
1736
+ possibilities.push(`(0{1,4}:){${position}}:(0{1,4}:){${elidedGroups - position - groups - 1}}0{1,4}`);
1737
+ }
1738
+ }
1739
+ return groupPossibilities(possibilities);
1740
+ }
1741
+ }
1742
+ });
1743
+
1744
+ // node_modules/ip-address/dist/ipv6.js
1745
+ var require_ipv6 = __commonJS({
1746
+ "node_modules/ip-address/dist/ipv6.js"(exports) {
1747
+ "use strict";
1748
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
1749
+ if (k2 === void 0) k2 = k;
1750
+ var desc = Object.getOwnPropertyDescriptor(m, k);
1751
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1752
+ desc = { enumerable: true, get: function() {
1753
+ return m[k];
1754
+ } };
1755
+ }
1756
+ Object.defineProperty(o, k2, desc);
1757
+ }) : (function(o, m, k, k2) {
1758
+ if (k2 === void 0) k2 = k;
1759
+ o[k2] = m[k];
1760
+ }));
1761
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
1762
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
1763
+ }) : function(o, v) {
1764
+ o["default"] = v;
1765
+ });
1766
+ var __importStar = exports && exports.__importStar || function(mod) {
1767
+ if (mod && mod.__esModule) return mod;
1768
+ var result = {};
1769
+ if (mod != null) {
1770
+ for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
1771
+ }
1772
+ __setModuleDefault(result, mod);
1773
+ return result;
1774
+ };
1775
+ Object.defineProperty(exports, "__esModule", { value: true });
1776
+ exports.Address6 = void 0;
1777
+ var common = __importStar(require_common());
1778
+ var constants4 = __importStar(require_constants());
1779
+ var constants6 = __importStar(require_constants2());
1780
+ var helpers = __importStar(require_helpers());
1781
+ var ipv4_1 = require_ipv4();
1782
+ var regular_expressions_1 = require_regular_expressions();
1783
+ var address_error_1 = require_address_error();
1784
+ var common_1 = require_common();
1785
+ function assert(condition) {
1786
+ if (!condition) {
1787
+ throw new Error("Assertion failed.");
1788
+ }
1789
+ }
1790
+ function addCommas(number) {
1791
+ const r = /(\d+)(\d{3})/;
1792
+ while (r.test(number)) {
1793
+ number = number.replace(r, "$1,$2");
1794
+ }
1795
+ return number;
1796
+ }
1797
+ function spanLeadingZeroes4(n) {
1798
+ n = n.replace(/^(0{1,})([1-9]+)$/, '<span class="parse-error">$1</span>$2');
1799
+ n = n.replace(/^(0{1,})(0)$/, '<span class="parse-error">$1</span>$2');
1800
+ return n;
1801
+ }
1802
+ function compact(address, slice) {
1803
+ const s1 = [];
1804
+ const s2 = [];
1805
+ let i;
1806
+ for (i = 0; i < address.length; i++) {
1807
+ if (i < slice[0]) {
1808
+ s1.push(address[i]);
1809
+ } else if (i > slice[1]) {
1810
+ s2.push(address[i]);
1811
+ }
1812
+ }
1813
+ return s1.concat(["compact"]).concat(s2);
1814
+ }
1815
+ function paddedHex(octet) {
1816
+ return parseInt(octet, 16).toString(16).padStart(4, "0");
1817
+ }
1818
+ function unsignByte(b) {
1819
+ return b & 255;
1820
+ }
1821
+ var Address62 = class _Address6 {
1822
+ constructor(address, optionalGroups) {
1823
+ this.addressMinusSuffix = "";
1824
+ this.parsedSubnet = "";
1825
+ this.subnet = "/128";
1826
+ this.subnetMask = 128;
1827
+ this.v4 = false;
1828
+ this.zone = "";
1829
+ this.isInSubnet = common.isInSubnet;
1830
+ this.isCorrect = common.isCorrect(constants6.BITS);
1831
+ if (optionalGroups === void 0) {
1832
+ this.groups = constants6.GROUPS;
1833
+ } else {
1834
+ this.groups = optionalGroups;
1835
+ }
1836
+ this.address = address;
1837
+ const subnet = constants6.RE_SUBNET_STRING.exec(address);
1838
+ if (subnet) {
1839
+ this.parsedSubnet = subnet[0].replace("/", "");
1840
+ this.subnetMask = parseInt(this.parsedSubnet, 10);
1841
+ this.subnet = `/${this.subnetMask}`;
1842
+ if (Number.isNaN(this.subnetMask) || this.subnetMask < 0 || this.subnetMask > constants6.BITS) {
1843
+ throw new address_error_1.AddressError("Invalid subnet mask.");
1844
+ }
1845
+ address = address.replace(constants6.RE_SUBNET_STRING, "");
1846
+ } else if (/\//.test(address)) {
1847
+ throw new address_error_1.AddressError("Invalid subnet mask.");
1848
+ }
1849
+ const zone = constants6.RE_ZONE_STRING.exec(address);
1850
+ if (zone) {
1851
+ this.zone = zone[0];
1852
+ address = address.replace(constants6.RE_ZONE_STRING, "");
1853
+ }
1854
+ this.addressMinusSuffix = address;
1855
+ this.parsedAddress = this.parse(this.addressMinusSuffix);
1856
+ }
1857
+ static isValid(address) {
1858
+ try {
1859
+ new _Address6(address);
1860
+ return true;
1861
+ } catch (e) {
1862
+ return false;
1863
+ }
1864
+ }
1865
+ /**
1866
+ * Convert a BigInt to a v6 address object
1867
+ * @memberof Address6
1868
+ * @static
1869
+ * @param {bigint} bigInt - a BigInt to convert
1870
+ * @returns {Address6}
1871
+ * @example
1872
+ * var bigInt = BigInt('1000000000000');
1873
+ * var address = Address6.fromBigInt(bigInt);
1874
+ * address.correctForm(); // '::e8:d4a5:1000'
1875
+ */
1876
+ static fromBigInt(bigInt) {
1877
+ const hex = bigInt.toString(16).padStart(32, "0");
1878
+ const groups = [];
1879
+ let i;
1880
+ for (i = 0; i < constants6.GROUPS; i++) {
1881
+ groups.push(hex.slice(i * 4, (i + 1) * 4));
1882
+ }
1883
+ return new _Address6(groups.join(":"));
1884
+ }
1885
+ /**
1886
+ * Convert a URL (with optional port number) to an address object
1887
+ * @memberof Address6
1888
+ * @static
1889
+ * @param {string} url - a URL with optional port number
1890
+ * @example
1891
+ * var addressAndPort = Address6.fromURL('http://[ffff::]:8080/foo/');
1892
+ * addressAndPort.address.correctForm(); // 'ffff::'
1893
+ * addressAndPort.port; // 8080
1894
+ */
1895
+ static fromURL(url) {
1896
+ let host;
1897
+ let port = null;
1898
+ let result;
1899
+ if (url.indexOf("[") !== -1 && url.indexOf("]:") !== -1) {
1900
+ result = constants6.RE_URL_WITH_PORT.exec(url);
1901
+ if (result === null) {
1902
+ return {
1903
+ error: "failed to parse address with port",
1904
+ address: null,
1905
+ port: null
1906
+ };
1907
+ }
1908
+ host = result[1];
1909
+ port = result[2];
1910
+ } else if (url.indexOf("/") !== -1) {
1911
+ url = url.replace(/^[a-z0-9]+:\/\//, "");
1912
+ result = constants6.RE_URL.exec(url);
1913
+ if (result === null) {
1914
+ return {
1915
+ error: "failed to parse address from URL",
1916
+ address: null,
1917
+ port: null
1918
+ };
1919
+ }
1920
+ host = result[1];
1921
+ } else {
1922
+ host = url;
1923
+ }
1924
+ if (port) {
1925
+ port = parseInt(port, 10);
1926
+ if (port < 0 || port > 65536) {
1927
+ port = null;
1928
+ }
1929
+ } else {
1930
+ port = null;
1931
+ }
1932
+ return {
1933
+ address: new _Address6(host),
1934
+ port
1935
+ };
1936
+ }
1937
+ /**
1938
+ * Create an IPv6-mapped address given an IPv4 address
1939
+ * @memberof Address6
1940
+ * @static
1941
+ * @param {string} address - An IPv4 address string
1942
+ * @returns {Address6}
1943
+ * @example
1944
+ * var address = Address6.fromAddress4('192.168.0.1');
1945
+ * address.correctForm(); // '::ffff:c0a8:1'
1946
+ * address.to4in6(); // '::ffff:192.168.0.1'
1947
+ */
1948
+ static fromAddress4(address) {
1949
+ const address4 = new ipv4_1.Address4(address);
1950
+ const mask6 = constants6.BITS - (constants4.BITS - address4.subnetMask);
1951
+ return new _Address6(`::ffff:${address4.correctForm()}/${mask6}`);
1952
+ }
1953
+ /**
1954
+ * Return an address from ip6.arpa form
1955
+ * @memberof Address6
1956
+ * @static
1957
+ * @param {string} arpaFormAddress - an 'ip6.arpa' form address
1958
+ * @returns {Adress6}
1959
+ * @example
1960
+ * var address = Address6.fromArpa(e.f.f.f.3.c.2.6.f.f.f.e.6.6.8.e.1.0.6.7.9.4.e.c.0.0.0.0.1.0.0.2.ip6.arpa.)
1961
+ * address.correctForm(); // '2001:0:ce49:7601:e866:efff:62c3:fffe'
1962
+ */
1963
+ static fromArpa(arpaFormAddress) {
1964
+ let address = arpaFormAddress.replace(/(\.ip6\.arpa)?\.$/, "");
1965
+ const semicolonAmount = 7;
1966
+ if (address.length !== 63) {
1967
+ throw new address_error_1.AddressError("Invalid 'ip6.arpa' form.");
1968
+ }
1969
+ const parts = address.split(".").reverse();
1970
+ for (let i = semicolonAmount; i > 0; i--) {
1971
+ const insertIndex = i * 4;
1972
+ parts.splice(insertIndex, 0, ":");
1973
+ }
1974
+ address = parts.join("");
1975
+ return new _Address6(address);
1976
+ }
1977
+ /**
1978
+ * Return the Microsoft UNC transcription of the address
1979
+ * @memberof Address6
1980
+ * @instance
1981
+ * @returns {String} the Microsoft UNC transcription of the address
1982
+ */
1983
+ microsoftTranscription() {
1984
+ return `${this.correctForm().replace(/:/g, "-")}.ipv6-literal.net`;
1985
+ }
1986
+ /**
1987
+ * Return the first n bits of the address, defaulting to the subnet mask
1988
+ * @memberof Address6
1989
+ * @instance
1990
+ * @param {number} [mask=subnet] - the number of bits to mask
1991
+ * @returns {String} the first n bits of the address as a string
1992
+ */
1993
+ mask(mask = this.subnetMask) {
1994
+ return this.getBitsBase2(0, mask);
1995
+ }
1996
+ /**
1997
+ * Return the number of possible subnets of a given size in the address
1998
+ * @memberof Address6
1999
+ * @instance
2000
+ * @param {number} [subnetSize=128] - the subnet size
2001
+ * @returns {String}
2002
+ */
2003
+ // TODO: probably useful to have a numeric version of this too
2004
+ possibleSubnets(subnetSize = 128) {
2005
+ const availableBits = constants6.BITS - this.subnetMask;
2006
+ const subnetBits = Math.abs(subnetSize - constants6.BITS);
2007
+ const subnetPowers = availableBits - subnetBits;
2008
+ if (subnetPowers < 0) {
2009
+ return "0";
2010
+ }
2011
+ return addCommas((BigInt("2") ** BigInt(subnetPowers)).toString(10));
2012
+ }
2013
+ /**
2014
+ * Helper function getting start address.
2015
+ * @memberof Address6
2016
+ * @instance
2017
+ * @returns {bigint}
2018
+ */
2019
+ _startAddress() {
2020
+ return BigInt(`0b${this.mask() + "0".repeat(constants6.BITS - this.subnetMask)}`);
2021
+ }
2022
+ /**
2023
+ * The first address in the range given by this address' subnet
2024
+ * Often referred to as the Network Address.
2025
+ * @memberof Address6
2026
+ * @instance
2027
+ * @returns {Address6}
2028
+ */
2029
+ startAddress() {
2030
+ return _Address6.fromBigInt(this._startAddress());
2031
+ }
2032
+ /**
2033
+ * The first host address in the range given by this address's subnet ie
2034
+ * the first address after the Network Address
2035
+ * @memberof Address6
2036
+ * @instance
2037
+ * @returns {Address6}
2038
+ */
2039
+ startAddressExclusive() {
2040
+ const adjust = BigInt("1");
2041
+ return _Address6.fromBigInt(this._startAddress() + adjust);
2042
+ }
2043
+ /**
2044
+ * Helper function getting end address.
2045
+ * @memberof Address6
2046
+ * @instance
2047
+ * @returns {bigint}
2048
+ */
2049
+ _endAddress() {
2050
+ return BigInt(`0b${this.mask() + "1".repeat(constants6.BITS - this.subnetMask)}`);
2051
+ }
2052
+ /**
2053
+ * The last address in the range given by this address' subnet
2054
+ * Often referred to as the Broadcast
2055
+ * @memberof Address6
2056
+ * @instance
2057
+ * @returns {Address6}
2058
+ */
2059
+ endAddress() {
2060
+ return _Address6.fromBigInt(this._endAddress());
2061
+ }
2062
+ /**
2063
+ * The last host address in the range given by this address's subnet ie
2064
+ * the last address prior to the Broadcast Address
2065
+ * @memberof Address6
2066
+ * @instance
2067
+ * @returns {Address6}
2068
+ */
2069
+ endAddressExclusive() {
2070
+ const adjust = BigInt("1");
2071
+ return _Address6.fromBigInt(this._endAddress() - adjust);
2072
+ }
2073
+ /**
2074
+ * Return the scope of the address
2075
+ * @memberof Address6
2076
+ * @instance
2077
+ * @returns {String}
2078
+ */
2079
+ getScope() {
2080
+ let scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)];
2081
+ if (this.getType() === "Global unicast" && scope !== "Link local") {
2082
+ scope = "Global";
2083
+ }
2084
+ return scope || "Unknown";
2085
+ }
2086
+ /**
2087
+ * Return the type of the address
2088
+ * @memberof Address6
2089
+ * @instance
2090
+ * @returns {String}
2091
+ */
2092
+ getType() {
2093
+ for (const subnet of Object.keys(constants6.TYPES)) {
2094
+ if (this.isInSubnet(new _Address6(subnet))) {
2095
+ return constants6.TYPES[subnet];
2096
+ }
2097
+ }
2098
+ return "Global unicast";
2099
+ }
2100
+ /**
2101
+ * Return the bits in the given range as a BigInt
2102
+ * @memberof Address6
2103
+ * @instance
2104
+ * @returns {bigint}
2105
+ */
2106
+ getBits(start, end) {
2107
+ return BigInt(`0b${this.getBitsBase2(start, end)}`);
2108
+ }
2109
+ /**
2110
+ * Return the bits in the given range as a base-2 string
2111
+ * @memberof Address6
2112
+ * @instance
2113
+ * @returns {String}
2114
+ */
2115
+ getBitsBase2(start, end) {
2116
+ return this.binaryZeroPad().slice(start, end);
2117
+ }
2118
+ /**
2119
+ * Return the bits in the given range as a base-16 string
2120
+ * @memberof Address6
2121
+ * @instance
2122
+ * @returns {String}
2123
+ */
2124
+ getBitsBase16(start, end) {
2125
+ const length = end - start;
2126
+ if (length % 4 !== 0) {
2127
+ throw new Error("Length of bits to retrieve must be divisible by four");
2128
+ }
2129
+ return this.getBits(start, end).toString(16).padStart(length / 4, "0");
2130
+ }
2131
+ /**
2132
+ * Return the bits that are set past the subnet mask length
2133
+ * @memberof Address6
2134
+ * @instance
2135
+ * @returns {String}
2136
+ */
2137
+ getBitsPastSubnet() {
2138
+ return this.getBitsBase2(this.subnetMask, constants6.BITS);
2139
+ }
2140
+ /**
2141
+ * Return the reversed ip6.arpa form of the address
2142
+ * @memberof Address6
2143
+ * @param {Object} options
2144
+ * @param {boolean} options.omitSuffix - omit the "ip6.arpa" suffix
2145
+ * @instance
2146
+ * @returns {String}
2147
+ */
2148
+ reverseForm(options) {
2149
+ if (!options) {
2150
+ options = {};
2151
+ }
2152
+ const characters = Math.floor(this.subnetMask / 4);
2153
+ const reversed = this.canonicalForm().replace(/:/g, "").split("").slice(0, characters).reverse().join(".");
2154
+ if (characters > 0) {
2155
+ if (options.omitSuffix) {
2156
+ return reversed;
2157
+ }
2158
+ return `${reversed}.ip6.arpa.`;
2159
+ }
2160
+ if (options.omitSuffix) {
2161
+ return "";
2162
+ }
2163
+ return "ip6.arpa.";
2164
+ }
2165
+ /**
2166
+ * Return the correct form of the address
2167
+ * @memberof Address6
2168
+ * @instance
2169
+ * @returns {String}
2170
+ */
2171
+ correctForm() {
2172
+ let i;
2173
+ let groups = [];
2174
+ let zeroCounter = 0;
2175
+ const zeroes = [];
2176
+ for (i = 0; i < this.parsedAddress.length; i++) {
2177
+ const value = parseInt(this.parsedAddress[i], 16);
2178
+ if (value === 0) {
2179
+ zeroCounter++;
2180
+ }
2181
+ if (value !== 0 && zeroCounter > 0) {
2182
+ if (zeroCounter > 1) {
2183
+ zeroes.push([i - zeroCounter, i - 1]);
2184
+ }
2185
+ zeroCounter = 0;
2186
+ }
2187
+ }
2188
+ if (zeroCounter > 1) {
2189
+ zeroes.push([this.parsedAddress.length - zeroCounter, this.parsedAddress.length - 1]);
2190
+ }
2191
+ const zeroLengths = zeroes.map((n) => n[1] - n[0] + 1);
2192
+ if (zeroes.length > 0) {
2193
+ const index = zeroLengths.indexOf(Math.max(...zeroLengths));
2194
+ groups = compact(this.parsedAddress, zeroes[index]);
2195
+ } else {
2196
+ groups = this.parsedAddress;
2197
+ }
2198
+ for (i = 0; i < groups.length; i++) {
2199
+ if (groups[i] !== "compact") {
2200
+ groups[i] = parseInt(groups[i], 16).toString(16);
2201
+ }
2202
+ }
2203
+ let correct = groups.join(":");
2204
+ correct = correct.replace(/^compact$/, "::");
2205
+ correct = correct.replace(/(^compact)|(compact$)/, ":");
2206
+ correct = correct.replace(/compact/, "");
2207
+ return correct;
2208
+ }
2209
+ /**
2210
+ * Return a zero-padded base-2 string representation of the address
2211
+ * @memberof Address6
2212
+ * @instance
2213
+ * @returns {String}
2214
+ * @example
2215
+ * var address = new Address6('2001:4860:4001:803::1011');
2216
+ * address.binaryZeroPad();
2217
+ * // '0010000000000001010010000110000001000000000000010000100000000011
2218
+ * // 0000000000000000000000000000000000000000000000000001000000010001'
2219
+ */
2220
+ binaryZeroPad() {
2221
+ return this.bigInt().toString(2).padStart(constants6.BITS, "0");
2222
+ }
2223
+ // TODO: Improve the semantics of this helper function
2224
+ parse4in6(address) {
2225
+ const groups = address.split(":");
2226
+ const lastGroup = groups.slice(-1)[0];
2227
+ const address4 = lastGroup.match(constants4.RE_ADDRESS);
2228
+ if (address4) {
2229
+ this.parsedAddress4 = address4[0];
2230
+ this.address4 = new ipv4_1.Address4(this.parsedAddress4);
2231
+ for (let i = 0; i < this.address4.groups; i++) {
2232
+ if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) {
2233
+ throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", address.replace(constants4.RE_ADDRESS, this.address4.parsedAddress.map(spanLeadingZeroes4).join(".")));
2234
+ }
2235
+ }
2236
+ this.v4 = true;
2237
+ groups[groups.length - 1] = this.address4.toGroup6();
2238
+ address = groups.join(":");
2239
+ }
2240
+ return address;
2241
+ }
2242
+ // TODO: Make private?
2243
+ parse(address) {
2244
+ address = this.parse4in6(address);
2245
+ const badCharacters = address.match(constants6.RE_BAD_CHARACTERS);
2246
+ if (badCharacters) {
2247
+ throw new address_error_1.AddressError(`Bad character${badCharacters.length > 1 ? "s" : ""} detected in address: ${badCharacters.join("")}`, address.replace(constants6.RE_BAD_CHARACTERS, '<span class="parse-error">$1</span>'));
2248
+ }
2249
+ const badAddress = address.match(constants6.RE_BAD_ADDRESS);
2250
+ if (badAddress) {
2251
+ throw new address_error_1.AddressError(`Address failed regex: ${badAddress.join("")}`, address.replace(constants6.RE_BAD_ADDRESS, '<span class="parse-error">$1</span>'));
2252
+ }
2253
+ let groups = [];
2254
+ const halves = address.split("::");
2255
+ if (halves.length === 2) {
2256
+ let first = halves[0].split(":");
2257
+ let last = halves[1].split(":");
2258
+ if (first.length === 1 && first[0] === "") {
2259
+ first = [];
2260
+ }
2261
+ if (last.length === 1 && last[0] === "") {
2262
+ last = [];
2263
+ }
2264
+ const remaining = this.groups - (first.length + last.length);
2265
+ if (!remaining) {
2266
+ throw new address_error_1.AddressError("Error parsing groups");
2267
+ }
2268
+ this.elidedGroups = remaining;
2269
+ this.elisionBegin = first.length;
2270
+ this.elisionEnd = first.length + this.elidedGroups;
2271
+ groups = groups.concat(first);
2272
+ for (let i = 0; i < remaining; i++) {
2273
+ groups.push("0");
2274
+ }
2275
+ groups = groups.concat(last);
2276
+ } else if (halves.length === 1) {
2277
+ groups = address.split(":");
2278
+ this.elidedGroups = 0;
2279
+ } else {
2280
+ throw new address_error_1.AddressError("Too many :: groups found");
2281
+ }
2282
+ groups = groups.map((group) => parseInt(group, 16).toString(16));
2283
+ if (groups.length !== this.groups) {
2284
+ throw new address_error_1.AddressError("Incorrect number of groups found");
2285
+ }
2286
+ return groups;
2287
+ }
2288
+ /**
2289
+ * Return the canonical form of the address
2290
+ * @memberof Address6
2291
+ * @instance
2292
+ * @returns {String}
2293
+ */
2294
+ canonicalForm() {
2295
+ return this.parsedAddress.map(paddedHex).join(":");
2296
+ }
2297
+ /**
2298
+ * Return the decimal form of the address
2299
+ * @memberof Address6
2300
+ * @instance
2301
+ * @returns {String}
2302
+ */
2303
+ decimal() {
2304
+ return this.parsedAddress.map((n) => parseInt(n, 16).toString(10).padStart(5, "0")).join(":");
2305
+ }
2306
+ /**
2307
+ * Return the address as a BigInt
2308
+ * @memberof Address6
2309
+ * @instance
2310
+ * @returns {bigint}
2311
+ */
2312
+ bigInt() {
2313
+ return BigInt(`0x${this.parsedAddress.map(paddedHex).join("")}`);
2314
+ }
2315
+ /**
2316
+ * Return the last two groups of this address as an IPv4 address string
2317
+ * @memberof Address6
2318
+ * @instance
2319
+ * @returns {Address4}
2320
+ * @example
2321
+ * var address = new Address6('2001:4860:4001::1825:bf11');
2322
+ * address.to4().correctForm(); // '24.37.191.17'
2323
+ */
2324
+ to4() {
2325
+ const binary = this.binaryZeroPad().split("");
2326
+ return ipv4_1.Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join("")}`).toString(16));
2327
+ }
2328
+ /**
2329
+ * Return the v4-in-v6 form of the address
2330
+ * @memberof Address6
2331
+ * @instance
2332
+ * @returns {String}
2333
+ */
2334
+ to4in6() {
2335
+ const address4 = this.to4();
2336
+ const address6 = new _Address6(this.parsedAddress.slice(0, 6).join(":"), 6);
2337
+ const correct = address6.correctForm();
2338
+ let infix = "";
2339
+ if (!/:$/.test(correct)) {
2340
+ infix = ":";
2341
+ }
2342
+ return correct + infix + address4.address;
2343
+ }
2344
+ /**
2345
+ * Return an object containing the Teredo properties of the address
2346
+ * @memberof Address6
2347
+ * @instance
2348
+ * @returns {Object}
2349
+ */
2350
+ inspectTeredo() {
2351
+ const prefix = this.getBitsBase16(0, 32);
2352
+ const bitsForUdpPort = this.getBits(80, 96);
2353
+ const udpPort = (bitsForUdpPort ^ BigInt("0xffff")).toString();
2354
+ const server4 = ipv4_1.Address4.fromHex(this.getBitsBase16(32, 64));
2355
+ const bitsForClient4 = this.getBits(96, 128);
2356
+ const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt("0xffffffff")).toString(16));
2357
+ const flagsBase2 = this.getBitsBase2(64, 80);
2358
+ const coneNat = (0, common_1.testBit)(flagsBase2, 15);
2359
+ const reserved = (0, common_1.testBit)(flagsBase2, 14);
2360
+ const groupIndividual = (0, common_1.testBit)(flagsBase2, 8);
2361
+ const universalLocal = (0, common_1.testBit)(flagsBase2, 9);
2362
+ const nonce = BigInt(`0b${flagsBase2.slice(2, 6) + flagsBase2.slice(8, 16)}`).toString(10);
2363
+ return {
2364
+ prefix: `${prefix.slice(0, 4)}:${prefix.slice(4, 8)}`,
2365
+ server4: server4.address,
2366
+ client4: client4.address,
2367
+ flags: flagsBase2,
2368
+ coneNat,
2369
+ microsoft: {
2370
+ reserved,
2371
+ universalLocal,
2372
+ groupIndividual,
2373
+ nonce
2374
+ },
2375
+ udpPort
2376
+ };
2377
+ }
2378
+ /**
2379
+ * Return an object containing the 6to4 properties of the address
2380
+ * @memberof Address6
2381
+ * @instance
2382
+ * @returns {Object}
2383
+ */
2384
+ inspect6to4() {
2385
+ const prefix = this.getBitsBase16(0, 16);
2386
+ const gateway = ipv4_1.Address4.fromHex(this.getBitsBase16(16, 48));
2387
+ return {
2388
+ prefix: prefix.slice(0, 4),
2389
+ gateway: gateway.address
2390
+ };
2391
+ }
2392
+ /**
2393
+ * Return a v6 6to4 address from a v6 v4inv6 address
2394
+ * @memberof Address6
2395
+ * @instance
2396
+ * @returns {Address6}
2397
+ */
2398
+ to6to4() {
2399
+ if (!this.is4()) {
2400
+ return null;
2401
+ }
2402
+ const addr6to4 = [
2403
+ "2002",
2404
+ this.getBitsBase16(96, 112),
2405
+ this.getBitsBase16(112, 128),
2406
+ "",
2407
+ "/16"
2408
+ ].join(":");
2409
+ return new _Address6(addr6to4);
2410
+ }
2411
+ /**
2412
+ * Return a byte array
2413
+ * @memberof Address6
2414
+ * @instance
2415
+ * @returns {Array}
2416
+ */
2417
+ toByteArray() {
2418
+ const valueWithoutPadding = this.bigInt().toString(16);
2419
+ const leadingPad = "0".repeat(valueWithoutPadding.length % 2);
2420
+ const value = `${leadingPad}${valueWithoutPadding}`;
2421
+ const bytes = [];
2422
+ for (let i = 0, length = value.length; i < length; i += 2) {
2423
+ bytes.push(parseInt(value.substring(i, i + 2), 16));
2424
+ }
2425
+ return bytes;
2426
+ }
2427
+ /**
2428
+ * Return an unsigned byte array
2429
+ * @memberof Address6
2430
+ * @instance
2431
+ * @returns {Array}
2432
+ */
2433
+ toUnsignedByteArray() {
2434
+ return this.toByteArray().map(unsignByte);
2435
+ }
2436
+ /**
2437
+ * Convert a byte array to an Address6 object
2438
+ * @memberof Address6
2439
+ * @static
2440
+ * @returns {Address6}
2441
+ */
2442
+ static fromByteArray(bytes) {
2443
+ return this.fromUnsignedByteArray(bytes.map(unsignByte));
2444
+ }
2445
+ /**
2446
+ * Convert an unsigned byte array to an Address6 object
2447
+ * @memberof Address6
2448
+ * @static
2449
+ * @returns {Address6}
2450
+ */
2451
+ static fromUnsignedByteArray(bytes) {
2452
+ const BYTE_MAX = BigInt("256");
2453
+ let result = BigInt("0");
2454
+ let multiplier = BigInt("1");
2455
+ for (let i = bytes.length - 1; i >= 0; i--) {
2456
+ result += multiplier * BigInt(bytes[i].toString(10));
2457
+ multiplier *= BYTE_MAX;
2458
+ }
2459
+ return _Address6.fromBigInt(result);
2460
+ }
2461
+ /**
2462
+ * Returns true if the address is in the canonical form, false otherwise
2463
+ * @memberof Address6
2464
+ * @instance
2465
+ * @returns {boolean}
2466
+ */
2467
+ isCanonical() {
2468
+ return this.addressMinusSuffix === this.canonicalForm();
2469
+ }
2470
+ /**
2471
+ * Returns true if the address is a link local address, false otherwise
2472
+ * @memberof Address6
2473
+ * @instance
2474
+ * @returns {boolean}
2475
+ */
2476
+ isLinkLocal() {
2477
+ if (this.getBitsBase2(0, 64) === "1111111010000000000000000000000000000000000000000000000000000000") {
2478
+ return true;
2479
+ }
2480
+ return false;
2481
+ }
2482
+ /**
2483
+ * Returns true if the address is a multicast address, false otherwise
2484
+ * @memberof Address6
2485
+ * @instance
2486
+ * @returns {boolean}
2487
+ */
2488
+ isMulticast() {
2489
+ return this.getType() === "Multicast";
2490
+ }
2491
+ /**
2492
+ * Returns true if the address is a v4-in-v6 address, false otherwise
2493
+ * @memberof Address6
2494
+ * @instance
2495
+ * @returns {boolean}
2496
+ */
2497
+ is4() {
2498
+ return this.v4;
2499
+ }
2500
+ /**
2501
+ * Returns true if the address is a Teredo address, false otherwise
2502
+ * @memberof Address6
2503
+ * @instance
2504
+ * @returns {boolean}
2505
+ */
2506
+ isTeredo() {
2507
+ return this.isInSubnet(new _Address6("2001::/32"));
2508
+ }
2509
+ /**
2510
+ * Returns true if the address is a 6to4 address, false otherwise
2511
+ * @memberof Address6
2512
+ * @instance
2513
+ * @returns {boolean}
2514
+ */
2515
+ is6to4() {
2516
+ return this.isInSubnet(new _Address6("2002::/16"));
2517
+ }
2518
+ /**
2519
+ * Returns true if the address is a loopback address, false otherwise
2520
+ * @memberof Address6
2521
+ * @instance
2522
+ * @returns {boolean}
2523
+ */
2524
+ isLoopback() {
2525
+ return this.getType() === "Loopback";
2526
+ }
2527
+ // #endregion
2528
+ // #region HTML
2529
+ /**
2530
+ * @returns {String} the address in link form with a default port of 80
2531
+ */
2532
+ href(optionalPort) {
2533
+ if (optionalPort === void 0) {
2534
+ optionalPort = "";
2535
+ } else {
2536
+ optionalPort = `:${optionalPort}`;
2537
+ }
2538
+ return `http://[${this.correctForm()}]${optionalPort}/`;
2539
+ }
2540
+ /**
2541
+ * @returns {String} a link suitable for conveying the address via a URL hash
2542
+ */
2543
+ link(options) {
2544
+ if (!options) {
2545
+ options = {};
2546
+ }
2547
+ if (options.className === void 0) {
2548
+ options.className = "";
2549
+ }
2550
+ if (options.prefix === void 0) {
2551
+ options.prefix = "/#address=";
2552
+ }
2553
+ if (options.v4 === void 0) {
2554
+ options.v4 = false;
2555
+ }
2556
+ let formFunction = this.correctForm;
2557
+ if (options.v4) {
2558
+ formFunction = this.to4in6;
2559
+ }
2560
+ const form = formFunction.call(this);
2561
+ if (options.className) {
2562
+ return `<a href="${options.prefix}${form}" class="${options.className}">${form}</a>`;
2563
+ }
2564
+ return `<a href="${options.prefix}${form}">${form}</a>`;
2565
+ }
2566
+ /**
2567
+ * Groups an address
2568
+ * @returns {String}
2569
+ */
2570
+ group() {
2571
+ if (this.elidedGroups === 0) {
2572
+ return helpers.simpleGroup(this.address).join(":");
2573
+ }
2574
+ assert(typeof this.elidedGroups === "number");
2575
+ assert(typeof this.elisionBegin === "number");
2576
+ const output = [];
2577
+ const [left, right] = this.address.split("::");
2578
+ if (left.length) {
2579
+ output.push(...helpers.simpleGroup(left));
2580
+ } else {
2581
+ output.push("");
2582
+ }
2583
+ const classes = ["hover-group"];
2584
+ for (let i = this.elisionBegin; i < this.elisionBegin + this.elidedGroups; i++) {
2585
+ classes.push(`group-${i}`);
2586
+ }
2587
+ output.push(`<span class="${classes.join(" ")}"></span>`);
2588
+ if (right.length) {
2589
+ output.push(...helpers.simpleGroup(right, this.elisionEnd));
2590
+ } else {
2591
+ output.push("");
2592
+ }
2593
+ if (this.is4()) {
2594
+ assert(this.address4 instanceof ipv4_1.Address4);
2595
+ output.pop();
2596
+ output.push(this.address4.groupForV6());
2597
+ }
2598
+ return output.join(":");
2599
+ }
2600
+ // #endregion
2601
+ // #region Regular expressions
2602
+ /**
2603
+ * Generate a regular expression string that can be used to find or validate
2604
+ * all variations of this address
2605
+ * @memberof Address6
2606
+ * @instance
2607
+ * @param {boolean} substringSearch
2608
+ * @returns {string}
2609
+ */
2610
+ regularExpressionString(substringSearch = false) {
2611
+ let output = [];
2612
+ const address6 = new _Address6(this.correctForm());
2613
+ if (address6.elidedGroups === 0) {
2614
+ output.push((0, regular_expressions_1.simpleRegularExpression)(address6.parsedAddress));
2615
+ } else if (address6.elidedGroups === constants6.GROUPS) {
2616
+ output.push((0, regular_expressions_1.possibleElisions)(constants6.GROUPS));
2617
+ } else {
2618
+ const halves = address6.address.split("::");
2619
+ if (halves[0].length) {
2620
+ output.push((0, regular_expressions_1.simpleRegularExpression)(halves[0].split(":")));
2621
+ }
2622
+ assert(typeof address6.elidedGroups === "number");
2623
+ output.push((0, regular_expressions_1.possibleElisions)(address6.elidedGroups, halves[0].length !== 0, halves[1].length !== 0));
2624
+ if (halves[1].length) {
2625
+ output.push((0, regular_expressions_1.simpleRegularExpression)(halves[1].split(":")));
2626
+ }
2627
+ output = [output.join(":")];
2628
+ }
2629
+ if (!substringSearch) {
2630
+ output = [
2631
+ "(?=^|",
2632
+ regular_expressions_1.ADDRESS_BOUNDARY,
2633
+ "|[^\\w\\:])(",
2634
+ ...output,
2635
+ ")(?=[^\\w\\:]|",
2636
+ regular_expressions_1.ADDRESS_BOUNDARY,
2637
+ "|$)"
2638
+ ];
2639
+ }
2640
+ return output.join("");
2641
+ }
2642
+ /**
2643
+ * Generate a regular expression that can be used to find or validate all
2644
+ * variations of this address.
2645
+ * @memberof Address6
2646
+ * @instance
2647
+ * @param {boolean} substringSearch
2648
+ * @returns {RegExp}
2649
+ */
2650
+ regularExpression(substringSearch = false) {
2651
+ return new RegExp(this.regularExpressionString(substringSearch), "i");
2652
+ }
2653
+ };
2654
+ exports.Address6 = Address62;
2655
+ }
2656
+ });
2657
+
2658
+ // node_modules/ip-address/dist/ip-address.js
2659
+ var require_ip_address = __commonJS({
2660
+ "node_modules/ip-address/dist/ip-address.js"(exports) {
2661
+ "use strict";
2662
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
2663
+ if (k2 === void 0) k2 = k;
2664
+ var desc = Object.getOwnPropertyDescriptor(m, k);
2665
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
2666
+ desc = { enumerable: true, get: function() {
2667
+ return m[k];
2668
+ } };
2669
+ }
2670
+ Object.defineProperty(o, k2, desc);
2671
+ }) : (function(o, m, k, k2) {
2672
+ if (k2 === void 0) k2 = k;
2673
+ o[k2] = m[k];
2674
+ }));
2675
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
2676
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
2677
+ }) : function(o, v) {
2678
+ o["default"] = v;
2679
+ });
2680
+ var __importStar = exports && exports.__importStar || function(mod) {
2681
+ if (mod && mod.__esModule) return mod;
2682
+ var result = {};
2683
+ if (mod != null) {
2684
+ for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
2685
+ }
2686
+ __setModuleDefault(result, mod);
2687
+ return result;
2688
+ };
2689
+ Object.defineProperty(exports, "__esModule", { value: true });
2690
+ exports.v6 = exports.AddressError = exports.Address6 = exports.Address4 = void 0;
2691
+ var ipv4_1 = require_ipv4();
2692
+ Object.defineProperty(exports, "Address4", { enumerable: true, get: function() {
2693
+ return ipv4_1.Address4;
2694
+ } });
2695
+ var ipv6_1 = require_ipv6();
2696
+ Object.defineProperty(exports, "Address6", { enumerable: true, get: function() {
2697
+ return ipv6_1.Address6;
2698
+ } });
2699
+ var address_error_1 = require_address_error();
2700
+ Object.defineProperty(exports, "AddressError", { enumerable: true, get: function() {
2701
+ return address_error_1.AddressError;
2702
+ } });
2703
+ var helpers = __importStar(require_helpers());
2704
+ exports.v6 = { helpers };
2705
+ }
2706
+ });
2707
+
2708
+ // node_modules/express-rate-limit/dist/index.mjs
2709
+ import { isIPv6 } from "node:net";
2710
+ import { isIPv6 as isIPv62 } from "node:net";
2711
+ import { Buffer as Buffer2 } from "node:buffer";
2712
+ import { createHash } from "node:crypto";
2713
+ import { isIP } from "node:net";
2714
+ function ipKeyGenerator(ip, ipv6Subnet = 56) {
2715
+ if (isIPv6(ip)) {
2716
+ const address = new import_ip_address.Address6(ip);
2717
+ if (address.is4()) return address.to4().correctForm();
2718
+ if (ipv6Subnet) {
2719
+ const subnet = new import_ip_address.Address6(`${ip}/${ipv6Subnet}`);
2720
+ return `${subnet.startAddress().correctForm()}/${ipv6Subnet}`;
2721
+ }
2722
+ }
2723
+ return ip;
2724
+ }
2725
+ var import_ip_address, MemoryStore, SUPPORTED_DRAFT_VERSIONS, getResetSeconds, getPartitionKey, setLegacyHeaders, setDraft6Headers, setDraft7Headers, setDraft8Headers, setRetryAfterHeader, omitUndefinedProperties, ValidationError, ChangeWarning, usedStores, singleCountKeys, validations, getValidations, isLegacyStore, promisifyStore, getOptionsFromConfig, parseOptions, handleAsyncErrors, rateLimit, rate_limit_default;
2726
+ var init_dist = __esm({
2727
+ "node_modules/express-rate-limit/dist/index.mjs"() {
2728
+ import_ip_address = __toESM(require_ip_address(), 1);
2729
+ MemoryStore = class {
2730
+ constructor(validations2) {
2731
+ this.validations = validations2;
2732
+ this.previous = /* @__PURE__ */ new Map();
2733
+ this.current = /* @__PURE__ */ new Map();
2734
+ this.localKeys = true;
2735
+ }
2736
+ /**
2737
+ * Method that initializes the store.
2738
+ *
2739
+ * @param options {Options} - The options used to setup the middleware.
2740
+ */
2741
+ init(options) {
2742
+ this.windowMs = options.windowMs;
2743
+ this.validations?.windowMs(this.windowMs);
2744
+ if (this.interval) clearInterval(this.interval);
2745
+ this.interval = setInterval(() => {
2746
+ this.clearExpired();
2747
+ }, this.windowMs);
2748
+ this.interval.unref?.();
2749
+ }
2750
+ /**
2751
+ * Method to fetch a client's hit count and reset time.
2752
+ *
2753
+ * @param key {string} - The identifier for a client.
2754
+ *
2755
+ * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
2756
+ *
2757
+ * @public
2758
+ */
2759
+ async get(key) {
2760
+ return this.current.get(key) ?? this.previous.get(key);
2761
+ }
2762
+ /**
2763
+ * Method to increment a client's hit counter.
2764
+ *
2765
+ * @param key {string} - The identifier for a client.
2766
+ *
2767
+ * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
2768
+ *
2769
+ * @public
2770
+ */
2771
+ async increment(key) {
2772
+ const client = this.getClient(key);
2773
+ const now = Date.now();
2774
+ if (client.resetTime.getTime() <= now) {
2775
+ this.resetClient(client, now);
2776
+ }
2777
+ client.totalHits++;
2778
+ return client;
2779
+ }
2780
+ /**
2781
+ * Method to decrement a client's hit counter.
2782
+ *
2783
+ * @param key {string} - The identifier for a client.
2784
+ *
2785
+ * @public
2786
+ */
2787
+ async decrement(key) {
2788
+ const client = this.getClient(key);
2789
+ if (client.totalHits > 0) client.totalHits--;
2790
+ }
2791
+ /**
2792
+ * Method to reset a client's hit counter.
2793
+ *
2794
+ * @param key {string} - The identifier for a client.
2795
+ *
2796
+ * @public
2797
+ */
2798
+ async resetKey(key) {
2799
+ this.current.delete(key);
2800
+ this.previous.delete(key);
2801
+ }
2802
+ /**
2803
+ * Method to reset everyone's hit counter.
2804
+ *
2805
+ * @public
2806
+ */
2807
+ async resetAll() {
2808
+ this.current.clear();
2809
+ this.previous.clear();
2810
+ }
2811
+ /**
2812
+ * Method to stop the timer (if currently running) and prevent any memory
2813
+ * leaks.
2814
+ *
2815
+ * @public
2816
+ */
2817
+ shutdown() {
2818
+ clearInterval(this.interval);
2819
+ void this.resetAll();
2820
+ }
2821
+ /**
2822
+ * Recycles a client by setting its hit count to zero, and reset time to
2823
+ * `windowMs` milliseconds from now.
2824
+ *
2825
+ * NOT to be confused with `#resetKey()`, which removes a client from both the
2826
+ * `current` and `previous` maps.
2827
+ *
2828
+ * @param client {Client} - The client to recycle.
2829
+ * @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
2830
+ *
2831
+ * @return {Client} - The modified client that was passed in, to allow for chaining.
2832
+ */
2833
+ resetClient(client, now = Date.now()) {
2834
+ client.totalHits = 0;
2835
+ client.resetTime.setTime(now + this.windowMs);
2836
+ return client;
2837
+ }
2838
+ /**
2839
+ * Retrieves or creates a client, given a key. Also ensures that the client being
2840
+ * returned is in the `current` map.
2841
+ *
2842
+ * @param key {string} - The key under which the client is (or is to be) stored.
2843
+ *
2844
+ * @returns {Client} - The requested client.
2845
+ */
2846
+ getClient(key) {
2847
+ if (this.current.has(key)) return this.current.get(key);
2848
+ let client;
2849
+ if (this.previous.has(key)) {
2850
+ client = this.previous.get(key);
2851
+ this.previous.delete(key);
2852
+ } else {
2853
+ client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
2854
+ this.resetClient(client);
2855
+ }
2856
+ this.current.set(key, client);
2857
+ return client;
2858
+ }
2859
+ /**
2860
+ * Move current clients to previous, create a new map for current.
2861
+ *
2862
+ * This function is called every `windowMs`.
2863
+ */
2864
+ clearExpired() {
2865
+ this.previous = this.current;
2866
+ this.current = /* @__PURE__ */ new Map();
2867
+ }
2868
+ };
2869
+ SUPPORTED_DRAFT_VERSIONS = [
2870
+ "draft-6",
2871
+ "draft-7",
2872
+ "draft-8"
2873
+ ];
2874
+ getResetSeconds = (windowMs, resetTime) => {
2875
+ let resetSeconds;
2876
+ if (resetTime) {
2877
+ const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
2878
+ resetSeconds = Math.max(0, deltaSeconds);
2879
+ } else {
2880
+ resetSeconds = Math.ceil(windowMs / 1e3);
2881
+ }
2882
+ return resetSeconds;
2883
+ };
2884
+ getPartitionKey = (key) => {
2885
+ const hash = createHash("sha256");
2886
+ hash.update(key);
2887
+ const partitionKey = hash.digest("hex").slice(0, 12);
2888
+ return Buffer2.from(partitionKey).toString("base64");
2889
+ };
2890
+ setLegacyHeaders = (response, info) => {
2891
+ if (response.headersSent) return;
2892
+ response.setHeader("X-RateLimit-Limit", info.limit.toString());
2893
+ response.setHeader("X-RateLimit-Remaining", info.remaining.toString());
2894
+ if (info.resetTime instanceof Date) {
2895
+ response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
2896
+ response.setHeader(
2897
+ "X-RateLimit-Reset",
2898
+ Math.ceil(info.resetTime.getTime() / 1e3).toString()
2899
+ );
2900
+ }
2901
+ };
2902
+ setDraft6Headers = (response, info, windowMs) => {
2903
+ if (response.headersSent) return;
2904
+ const windowSeconds = Math.ceil(windowMs / 1e3);
2905
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
2906
+ response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
2907
+ response.setHeader("RateLimit-Limit", info.limit.toString());
2908
+ response.setHeader("RateLimit-Remaining", info.remaining.toString());
2909
+ if (typeof resetSeconds === "number")
2910
+ response.setHeader("RateLimit-Reset", resetSeconds.toString());
2911
+ };
2912
+ setDraft7Headers = (response, info, windowMs) => {
2913
+ if (response.headersSent) return;
2914
+ const windowSeconds = Math.ceil(windowMs / 1e3);
2915
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
2916
+ response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
2917
+ response.setHeader(
2918
+ "RateLimit",
2919
+ `limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
2920
+ );
2921
+ };
2922
+ setDraft8Headers = (response, info, windowMs, name, key) => {
2923
+ if (response.headersSent) return;
2924
+ const windowSeconds = Math.ceil(windowMs / 1e3);
2925
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
2926
+ const partitionKey = getPartitionKey(key);
2927
+ const header = `r=${info.remaining}; t=${resetSeconds}`;
2928
+ const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
2929
+ response.append("RateLimit", `"${name}"; ${header}`);
2930
+ response.append("RateLimit-Policy", `"${name}"; ${policy}`);
2931
+ };
2932
+ setRetryAfterHeader = (response, info, windowMs) => {
2933
+ if (response.headersSent) return;
2934
+ const resetSeconds = getResetSeconds(windowMs, info.resetTime);
2935
+ response.setHeader("Retry-After", resetSeconds.toString());
2936
+ };
2937
+ omitUndefinedProperties = (passedOptions) => {
2938
+ const omittedOptions = {};
2939
+ for (const k of Object.keys(passedOptions)) {
2940
+ const key = k;
2941
+ if (passedOptions[key] !== void 0) {
2942
+ omittedOptions[key] = passedOptions[key];
2943
+ }
2944
+ }
2945
+ return omittedOptions;
2946
+ };
2947
+ ValidationError = class extends Error {
2948
+ /**
2949
+ * The code must be a string, in snake case and all capital, that starts with
2950
+ * the substring `ERR_ERL_`.
2951
+ *
2952
+ * The message must be a string, starting with an uppercase character,
2953
+ * describing the issue in detail.
2954
+ */
2955
+ constructor(code, message) {
2956
+ const url = `https://express-rate-limit.github.io/${code}/`;
2957
+ super(`${message} See ${url} for more information.`);
2958
+ this.name = this.constructor.name;
2959
+ this.code = code;
2960
+ this.help = url;
2961
+ }
2962
+ };
2963
+ ChangeWarning = class extends ValidationError {
2964
+ };
2965
+ usedStores = /* @__PURE__ */ new Set();
2966
+ singleCountKeys = /* @__PURE__ */ new WeakMap();
2967
+ validations = {
2968
+ enabled: {
2969
+ default: true
2970
+ },
2971
+ // Should be EnabledValidations type, but that's a circular reference
2972
+ disable() {
2973
+ for (const k of Object.keys(this.enabled)) this.enabled[k] = false;
2974
+ },
2975
+ /**
2976
+ * Checks whether the IP address is valid, and that it does not have a port
2977
+ * number in it.
2978
+ *
2979
+ * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
2980
+ *
2981
+ * @param ip {string | undefined} - The IP address provided by Express as request.ip.
2982
+ *
2983
+ * @returns {void}
2984
+ */
2985
+ ip(ip) {
2986
+ if (ip === void 0) {
2987
+ throw new ValidationError(
2988
+ "ERR_ERL_UNDEFINED_IP_ADDRESS",
2989
+ `An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
2990
+ );
2991
+ }
2992
+ if (!isIP(ip)) {
2993
+ throw new ValidationError(
2994
+ "ERR_ERL_INVALID_IP_ADDRESS",
2995
+ `An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
2996
+ );
2997
+ }
2998
+ },
2999
+ /**
3000
+ * Makes sure the trust proxy setting is not set to `true`.
3001
+ *
3002
+ * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
3003
+ *
3004
+ * @param request {Request} - The Express request object.
3005
+ *
3006
+ * @returns {void}
3007
+ */
3008
+ trustProxy(request) {
3009
+ if (request.app.get("trust proxy") === true) {
3010
+ throw new ValidationError(
3011
+ "ERR_ERL_PERMISSIVE_TRUST_PROXY",
3012
+ `The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
3013
+ );
3014
+ }
3015
+ },
3016
+ /**
3017
+ * Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
3018
+ * header is present.
3019
+ *
3020
+ * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
3021
+ *
3022
+ * @param request {Request} - The Express request object.
3023
+ *
3024
+ * @returns {void}
3025
+ */
3026
+ xForwardedForHeader(request) {
3027
+ if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
3028
+ throw new ValidationError(
3029
+ "ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
3030
+ `The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
3031
+ );
3032
+ }
3033
+ },
3034
+ /**
3035
+ * Alert the user if the Forwarded header is set (standardized version of X-Forwarded-For - not supported by express as of version 5.1.0)
3036
+ *
3037
+ * @param request {Request} - The Express request object.
3038
+ *
3039
+ * @returns {void}
3040
+ */
3041
+ forwardedHeader(request) {
3042
+ if (request.headers.forwarded && request.ip === request.socket?.remoteAddress) {
3043
+ throw new ValidationError(
3044
+ "ERR_ERL_FORWARDED_HEADER",
3045
+ `The 'Forwarded' header (standardized X-Forwarded-For) is set but currently being ignored. Add a custom keyGenerator to use a value from this header.`
3046
+ );
3047
+ }
3048
+ },
3049
+ /**
3050
+ * Ensures totalHits value from store is a positive integer.
3051
+ *
3052
+ * @param hits {any} - The `totalHits` returned by the store.
3053
+ */
3054
+ positiveHits(hits) {
3055
+ if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
3056
+ throw new ValidationError(
3057
+ "ERR_ERL_INVALID_HITS",
3058
+ `The totalHits value returned from the store must be a positive integer, got ${hits}`
3059
+ );
3060
+ }
3061
+ },
3062
+ /**
3063
+ * Ensures a single store instance is not used with multiple express-rate-limit instances
3064
+ */
3065
+ unsharedStore(store) {
3066
+ if (usedStores.has(store)) {
3067
+ const maybeUniquePrefix = store?.localKeys ? "" : " (with a unique prefix)";
3068
+ throw new ValidationError(
3069
+ "ERR_ERL_STORE_REUSE",
3070
+ `A Store instance must not be shared across multiple rate limiters. Create a new instance of ${store.constructor.name}${maybeUniquePrefix} for each limiter instead.`
3071
+ );
3072
+ }
3073
+ usedStores.add(store);
3074
+ },
3075
+ /**
3076
+ * Ensures a given key is incremented only once per request.
3077
+ *
3078
+ * @param request {Request} - The Express request object.
3079
+ * @param store {Store} - The store class.
3080
+ * @param key {string} - The key used to store the client's hit count.
3081
+ *
3082
+ * @returns {void}
3083
+ */
3084
+ singleCount(request, store, key) {
3085
+ let storeKeys = singleCountKeys.get(request);
3086
+ if (!storeKeys) {
3087
+ storeKeys = /* @__PURE__ */ new Map();
3088
+ singleCountKeys.set(request, storeKeys);
3089
+ }
3090
+ const storeKey = store.localKeys ? store : store.constructor.name;
3091
+ let keys = storeKeys.get(storeKey);
3092
+ if (!keys) {
3093
+ keys = [];
3094
+ storeKeys.set(storeKey, keys);
3095
+ }
3096
+ const prefixedKey = `${store.prefix ?? ""}${key}`;
3097
+ if (keys.includes(prefixedKey)) {
3098
+ throw new ValidationError(
3099
+ "ERR_ERL_DOUBLE_COUNT",
3100
+ `The hit count for ${key} was incremented more than once for a single request.`
3101
+ );
3102
+ }
3103
+ keys.push(prefixedKey);
3104
+ },
3105
+ /**
3106
+ * Warns the user that the behaviour for `max: 0` / `limit: 0` is
3107
+ * changing in the next major release.
3108
+ *
3109
+ * @param limit {number} - The maximum number of hits per client.
3110
+ *
3111
+ * @returns {void}
3112
+ */
3113
+ limit(limit) {
3114
+ if (limit === 0) {
3115
+ throw new ChangeWarning(
3116
+ "WRN_ERL_MAX_ZERO",
3117
+ "Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7"
3118
+ );
3119
+ }
3120
+ },
3121
+ /**
3122
+ * Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
3123
+ * and will be removed in the next major release.
3124
+ *
3125
+ * @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
3126
+ *
3127
+ * @returns {void}
3128
+ */
3129
+ draftPolliHeaders(draft_polli_ratelimit_headers) {
3130
+ if (draft_polli_ratelimit_headers) {
3131
+ throw new ChangeWarning(
3132
+ "WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
3133
+ `The draft_polli_ratelimit_headers configuration option is deprecated and has been removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
3134
+ );
3135
+ }
3136
+ },
3137
+ /**
3138
+ * Warns the user that the `onLimitReached` option is deprecated and
3139
+ * will be removed in the next major release.
3140
+ *
3141
+ * @param onLimitReached {any | undefined} - The maximum number of hits per client.
3142
+ *
3143
+ * @returns {void}
3144
+ */
3145
+ onLimitReached(onLimitReached) {
3146
+ if (onLimitReached) {
3147
+ throw new ChangeWarning(
3148
+ "WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
3149
+ "The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7."
3150
+ );
3151
+ }
3152
+ },
3153
+ /**
3154
+ * Warns the user when an invalid/unsupported version of the draft spec is passed.
3155
+ *
3156
+ * @param version {any | undefined} - The version passed by the user.
3157
+ *
3158
+ * @returns {void}
3159
+ */
3160
+ headersDraftVersion(version) {
3161
+ if (typeof version !== "string" || // @ts-expect-error This is fine. If version is not in the array, it will just return false.
3162
+ !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
3163
+ const versionString = SUPPORTED_DRAFT_VERSIONS.join(", ");
3164
+ throw new ValidationError(
3165
+ "ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",
3166
+ `standardHeaders: only the following versions of the IETF draft specification are supported: ${versionString}.`
3167
+ );
3168
+ }
3169
+ },
3170
+ /**
3171
+ * Warns the user when the selected headers option requires a reset time but
3172
+ * the store does not provide one.
3173
+ *
3174
+ * @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
3175
+ *
3176
+ * @returns {void}
3177
+ */
3178
+ headersResetTime(resetTime) {
3179
+ if (!resetTime) {
3180
+ throw new ValidationError(
3181
+ "ERR_ERL_HEADERS_NO_RESET",
3182
+ `standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.`
3183
+ );
3184
+ }
3185
+ },
3186
+ knownOptions(passedOptions) {
3187
+ if (!passedOptions) return;
3188
+ const optionsMap = {
3189
+ windowMs: true,
3190
+ limit: true,
3191
+ message: true,
3192
+ statusCode: true,
3193
+ legacyHeaders: true,
3194
+ standardHeaders: true,
3195
+ identifier: true,
3196
+ requestPropertyName: true,
3197
+ skipFailedRequests: true,
3198
+ skipSuccessfulRequests: true,
3199
+ keyGenerator: true,
3200
+ ipv6Subnet: true,
3201
+ handler: true,
3202
+ skip: true,
3203
+ requestWasSuccessful: true,
3204
+ store: true,
3205
+ validate: true,
3206
+ headers: true,
3207
+ max: true,
3208
+ passOnStoreError: true
3209
+ };
3210
+ const validOptions = Object.keys(optionsMap).concat(
3211
+ "draft_polli_ratelimit_headers",
3212
+ // not a valid option anymore, but we have a more specific check for this one, so don't warn for it here
3213
+ // from express-slow-down - https://github.com/express-rate-limit/express-slow-down/blob/main/source/types.ts#L65
3214
+ "delayAfter",
3215
+ "delayMs",
3216
+ "maxDelayMs"
3217
+ );
3218
+ for (const key of Object.keys(passedOptions)) {
3219
+ if (!validOptions.includes(key)) {
3220
+ throw new ValidationError(
3221
+ "ERR_ERL_UNKNOWN_OPTION",
3222
+ `Unexpected configuration option: ${key}`
3223
+ // todo: suggest a valid option with a short levenstein distance?
3224
+ );
3225
+ }
3226
+ }
3227
+ },
3228
+ /**
3229
+ * Checks the options.validate setting to ensure that only recognized
3230
+ * validations are enabled or disabled.
3231
+ *
3232
+ * If any unrecognized values are found, an error is logged that
3233
+ * includes the list of supported validations.
3234
+ */
3235
+ validationsConfig() {
3236
+ const supportedValidations = Object.keys(this).filter(
3237
+ (k) => !["enabled", "disable"].includes(k)
3238
+ );
3239
+ supportedValidations.push("default");
3240
+ for (const key of Object.keys(this.enabled)) {
3241
+ if (!supportedValidations.includes(key)) {
3242
+ throw new ValidationError(
3243
+ "ERR_ERL_UNKNOWN_VALIDATION",
3244
+ `options.validate.${key} is not recognized. Supported validate options are: ${supportedValidations.join(
3245
+ ", "
3246
+ )}.`
3247
+ );
3248
+ }
3249
+ }
3250
+ },
3251
+ /**
3252
+ * Checks to see if the instance was created inside of a request handler,
3253
+ * which would prevent it from working correctly, with the default memory
3254
+ * store (or any other store with localKeys.)
3255
+ */
3256
+ creationStack(store) {
3257
+ const { stack } = new Error(
3258
+ "express-rate-limit validation check (set options.validate.creationStack=false to disable)"
3259
+ );
3260
+ if (stack?.includes("Layer.handle [as handle_request]") || // express v4
3261
+ stack?.includes("Layer.handleRequest")) {
3262
+ if (!store.localKeys) {
3263
+ throw new ValidationError(
3264
+ "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
3265
+ "express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."
3266
+ );
3267
+ }
3268
+ throw new ValidationError(
3269
+ "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
3270
+ "express-rate-limit instance should be created at app initialization, not when responding to a request."
3271
+ );
3272
+ }
3273
+ },
3274
+ ipv6Subnet(ipv6Subnet) {
3275
+ if (ipv6Subnet === false) {
3276
+ return;
3277
+ }
3278
+ if (!Number.isInteger(ipv6Subnet) || ipv6Subnet < 32 || ipv6Subnet > 64) {
3279
+ throw new ValidationError(
3280
+ "ERR_ERL_IPV6_SUBNET",
3281
+ `Unexpected ipv6Subnet value: ${ipv6Subnet}. Expected an integer between 32 and 64 (usually 48-64).`
3282
+ );
3283
+ }
3284
+ },
3285
+ ipv6SubnetOrKeyGenerator(options) {
3286
+ if (options.ipv6Subnet !== void 0 && options.keyGenerator) {
3287
+ throw new ValidationError(
3288
+ "ERR_ERL_IPV6SUBNET_OR_KEYGENERATOR",
3289
+ `Incompatible options: the 'ipv6Subnet' option is ignored when a custom 'keyGenerator' function is also set.`
3290
+ );
3291
+ }
3292
+ },
3293
+ keyGeneratorIpFallback(keyGenerator) {
3294
+ if (!keyGenerator) {
3295
+ return;
3296
+ }
3297
+ const src = keyGenerator.toString();
3298
+ if ((src.includes("req.ip") || src.includes("request.ip")) && !src.includes("ipKeyGenerator")) {
3299
+ throw new ValidationError(
3300
+ "ERR_ERL_KEY_GEN_IPV6",
3301
+ "Custom keyGenerator appears to use request IP without calling the ipKeyGenerator helper function for IPv6 addresses. This could allow IPv6 users to bypass limits."
3302
+ );
3303
+ }
3304
+ },
3305
+ /**
3306
+ * Checks to see if the window duration is greater than 2^32 - 1. This is only
3307
+ * called by the default MemoryStore, since it uses Node's setInterval method.
3308
+ *
3309
+ * See https://nodejs.org/api/timers.html#setintervalcallback-delay-args.
3310
+ */
3311
+ windowMs(windowMs) {
3312
+ const SET_TIMEOUT_MAX = 2 ** 31 - 1;
3313
+ if (typeof windowMs !== "number" || Number.isNaN(windowMs) || windowMs < 1 || windowMs > SET_TIMEOUT_MAX) {
3314
+ throw new ValidationError(
3315
+ "ERR_ERL_WINDOW_MS",
3316
+ `Invalid windowMs value: ${windowMs}${typeof windowMs !== "number" ? ` (${typeof windowMs})` : ""}, must be a number between 1 and ${SET_TIMEOUT_MAX} when using the default MemoryStore`
3317
+ );
3318
+ }
3319
+ }
3320
+ };
3321
+ getValidations = (_enabled) => {
3322
+ let enabled;
3323
+ if (typeof _enabled === "boolean") {
3324
+ enabled = {
3325
+ default: _enabled
3326
+ };
3327
+ } else {
3328
+ enabled = {
3329
+ default: true,
3330
+ ..._enabled
3331
+ };
3332
+ }
3333
+ const wrappedValidations = { enabled };
3334
+ for (const [name, validation] of Object.entries(validations)) {
3335
+ if (typeof validation === "function")
3336
+ wrappedValidations[name] = (...args) => {
3337
+ if (!(enabled[name] ?? enabled.default)) {
3338
+ return;
3339
+ }
3340
+ try {
3341
+ ;
3342
+ validation.apply(
3343
+ wrappedValidations,
3344
+ args
3345
+ );
3346
+ } catch (error) {
3347
+ if (error instanceof ChangeWarning) console.warn(error);
3348
+ else console.error(error);
3349
+ }
3350
+ };
3351
+ }
3352
+ return wrappedValidations;
3353
+ };
3354
+ isLegacyStore = (store) => (
3355
+ // Check that `incr` exists but `increment` does not - store authors might want
3356
+ // to keep both around for backwards compatibility.
3357
+ typeof store.incr === "function" && typeof store.increment !== "function"
3358
+ );
3359
+ promisifyStore = (passedStore) => {
3360
+ if (!isLegacyStore(passedStore)) {
3361
+ return passedStore;
3362
+ }
3363
+ const legacyStore = passedStore;
3364
+ class PromisifiedStore {
3365
+ async increment(key) {
3366
+ return new Promise((resolve, reject) => {
3367
+ legacyStore.incr(
3368
+ key,
3369
+ (error, totalHits, resetTime) => {
3370
+ if (error) reject(error);
3371
+ resolve({ totalHits, resetTime });
3372
+ }
3373
+ );
3374
+ });
3375
+ }
3376
+ async decrement(key) {
3377
+ return legacyStore.decrement(key);
3378
+ }
3379
+ async resetKey(key) {
3380
+ return legacyStore.resetKey(key);
3381
+ }
3382
+ /* istanbul ignore next */
3383
+ async resetAll() {
3384
+ if (typeof legacyStore.resetAll === "function")
3385
+ return legacyStore.resetAll();
3386
+ }
3387
+ }
3388
+ return new PromisifiedStore();
3389
+ };
3390
+ getOptionsFromConfig = (config3) => {
3391
+ const { validations: validations2, ...directlyPassableEntries } = config3;
3392
+ return {
3393
+ ...directlyPassableEntries,
3394
+ validate: validations2.enabled
3395
+ };
3396
+ };
3397
+ parseOptions = (passedOptions) => {
3398
+ const notUndefinedOptions = omitUndefinedProperties(passedOptions);
3399
+ const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
3400
+ validations2.validationsConfig();
3401
+ validations2.knownOptions(passedOptions);
3402
+ validations2.draftPolliHeaders(
3403
+ // @ts-expect-error see the note above.
3404
+ notUndefinedOptions.draft_polli_ratelimit_headers
3405
+ );
3406
+ validations2.onLimitReached(notUndefinedOptions.onLimitReached);
3407
+ if (notUndefinedOptions.ipv6Subnet !== void 0 && typeof notUndefinedOptions.ipv6Subnet !== "function") {
3408
+ validations2.ipv6Subnet(notUndefinedOptions.ipv6Subnet);
3409
+ }
3410
+ validations2.keyGeneratorIpFallback(notUndefinedOptions.keyGenerator);
3411
+ validations2.ipv6SubnetOrKeyGenerator(notUndefinedOptions);
3412
+ let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
3413
+ if (standardHeaders === true) standardHeaders = "draft-6";
3414
+ const config3 = {
3415
+ windowMs: 60 * 1e3,
3416
+ limit: passedOptions.max ?? 5,
3417
+ // `max` is deprecated, but support it anyways.
3418
+ message: "Too many requests, please try again later.",
3419
+ statusCode: 429,
3420
+ legacyHeaders: passedOptions.headers ?? true,
3421
+ identifier(request, _response) {
3422
+ let duration = "";
3423
+ const property = config3.requestPropertyName;
3424
+ const { limit } = request[property];
3425
+ const seconds = config3.windowMs / 1e3;
3426
+ const minutes = config3.windowMs / (1e3 * 60);
3427
+ const hours = config3.windowMs / (1e3 * 60 * 60);
3428
+ const days = config3.windowMs / (1e3 * 60 * 60 * 24);
3429
+ if (seconds < 60) duration = `${seconds}sec`;
3430
+ else if (minutes < 60) duration = `${minutes}min`;
3431
+ else if (hours < 24) duration = `${hours}hr${hours > 1 ? "s" : ""}`;
3432
+ else duration = `${days}day${days > 1 ? "s" : ""}`;
3433
+ return `${limit}-in-${duration}`;
3434
+ },
3435
+ requestPropertyName: "rateLimit",
3436
+ skipFailedRequests: false,
3437
+ skipSuccessfulRequests: false,
3438
+ requestWasSuccessful: (_request, response) => response.statusCode < 400,
3439
+ skip: (_request, _response) => false,
3440
+ async keyGenerator(request, response) {
3441
+ validations2.ip(request.ip);
3442
+ validations2.trustProxy(request);
3443
+ validations2.xForwardedForHeader(request);
3444
+ validations2.forwardedHeader(request);
3445
+ const ip = request.ip;
3446
+ let subnet = 56;
3447
+ if (isIPv62(ip)) {
3448
+ subnet = typeof config3.ipv6Subnet === "function" ? await config3.ipv6Subnet(request, response) : config3.ipv6Subnet;
3449
+ if (typeof config3.ipv6Subnet === "function")
3450
+ validations2.ipv6Subnet(subnet);
3451
+ }
3452
+ return ipKeyGenerator(ip, subnet);
3453
+ },
3454
+ ipv6Subnet: 56,
3455
+ async handler(request, response, _next, _optionsUsed) {
3456
+ response.status(config3.statusCode);
3457
+ const message = typeof config3.message === "function" ? await config3.message(
3458
+ request,
3459
+ response
3460
+ ) : config3.message;
3461
+ if (!response.writableEnded) response.send(message);
3462
+ },
3463
+ passOnStoreError: false,
3464
+ // Allow the default options to be overridden by the passed options.
3465
+ ...notUndefinedOptions,
3466
+ // `standardHeaders` is resolved into a draft version above, use that.
3467
+ standardHeaders,
3468
+ // Note that this field is declared after the user's options are spread in,
3469
+ // so that this field doesn't get overridden with an un-promisified store!
3470
+ store: promisifyStore(
3471
+ notUndefinedOptions.store ?? new MemoryStore(validations2)
3472
+ ),
3473
+ // Print an error to the console if a few known misconfigurations are detected.
3474
+ validations: validations2
3475
+ };
3476
+ if (typeof config3.store.increment !== "function" || typeof config3.store.decrement !== "function" || typeof config3.store.resetKey !== "function" || config3.store.resetAll !== void 0 && typeof config3.store.resetAll !== "function" || config3.store.init !== void 0 && typeof config3.store.init !== "function") {
3477
+ throw new TypeError(
3478
+ "An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
3479
+ );
3480
+ }
3481
+ return config3;
3482
+ };
3483
+ handleAsyncErrors = (fn) => async (request, response, next) => {
3484
+ try {
3485
+ await Promise.resolve(fn(request, response, next)).catch(next);
3486
+ } catch (error) {
3487
+ next(error);
3488
+ }
3489
+ };
3490
+ rateLimit = (passedOptions) => {
3491
+ const config3 = parseOptions(passedOptions ?? {});
3492
+ const options = getOptionsFromConfig(config3);
3493
+ config3.validations.creationStack(config3.store);
3494
+ config3.validations.unsharedStore(config3.store);
3495
+ if (typeof config3.store.init === "function") config3.store.init(options);
3496
+ const middleware = handleAsyncErrors(
3497
+ async (request, response, next) => {
3498
+ const skip = await config3.skip(request, response);
3499
+ if (skip) {
3500
+ next();
3501
+ return;
3502
+ }
3503
+ const augmentedRequest = request;
3504
+ const key = await config3.keyGenerator(request, response);
3505
+ let totalHits = 0;
3506
+ let resetTime;
3507
+ try {
3508
+ const incrementResult = await config3.store.increment(key);
3509
+ totalHits = incrementResult.totalHits;
3510
+ resetTime = incrementResult.resetTime;
3511
+ } catch (error) {
3512
+ if (config3.passOnStoreError) {
3513
+ console.error(
3514
+ "express-rate-limit: error from store, allowing request without rate-limiting.",
3515
+ error
3516
+ );
3517
+ next();
3518
+ return;
3519
+ }
3520
+ throw error;
3521
+ }
3522
+ config3.validations.positiveHits(totalHits);
3523
+ config3.validations.singleCount(request, config3.store, key);
3524
+ const retrieveLimit = typeof config3.limit === "function" ? config3.limit(request, response) : config3.limit;
3525
+ const limit = await retrieveLimit;
3526
+ config3.validations.limit(limit);
3527
+ const info = {
3528
+ limit,
3529
+ used: totalHits,
3530
+ remaining: Math.max(limit - totalHits, 0),
3531
+ resetTime,
3532
+ key
3533
+ };
3534
+ Object.defineProperty(info, "current", {
3535
+ configurable: false,
3536
+ enumerable: false,
3537
+ value: totalHits
3538
+ });
3539
+ augmentedRequest[config3.requestPropertyName] = info;
3540
+ if (config3.legacyHeaders && !response.headersSent) {
3541
+ setLegacyHeaders(response, info);
3542
+ }
3543
+ if (config3.standardHeaders && !response.headersSent) {
3544
+ switch (config3.standardHeaders) {
3545
+ case "draft-6": {
3546
+ setDraft6Headers(response, info, config3.windowMs);
3547
+ break;
3548
+ }
3549
+ case "draft-7": {
3550
+ config3.validations.headersResetTime(info.resetTime);
3551
+ setDraft7Headers(response, info, config3.windowMs);
3552
+ break;
3553
+ }
3554
+ case "draft-8": {
3555
+ const retrieveName = typeof config3.identifier === "function" ? config3.identifier(request, response) : config3.identifier;
3556
+ const name = await retrieveName;
3557
+ config3.validations.headersResetTime(info.resetTime);
3558
+ setDraft8Headers(response, info, config3.windowMs, name, key);
3559
+ break;
3560
+ }
3561
+ default: {
3562
+ config3.validations.headersDraftVersion(config3.standardHeaders);
3563
+ break;
3564
+ }
3565
+ }
3566
+ }
3567
+ if (config3.skipFailedRequests || config3.skipSuccessfulRequests) {
3568
+ let decremented = false;
3569
+ const decrementKey = async () => {
3570
+ if (!decremented) {
3571
+ await config3.store.decrement(key);
3572
+ decremented = true;
3573
+ }
3574
+ };
3575
+ if (config3.skipFailedRequests) {
3576
+ response.on("finish", async () => {
3577
+ if (!await config3.requestWasSuccessful(request, response))
3578
+ await decrementKey();
3579
+ });
3580
+ response.on("close", async () => {
3581
+ if (!response.writableEnded) await decrementKey();
3582
+ });
3583
+ response.on("error", async () => {
3584
+ await decrementKey();
3585
+ });
3586
+ }
3587
+ if (config3.skipSuccessfulRequests) {
3588
+ response.on("finish", async () => {
3589
+ if (await config3.requestWasSuccessful(request, response))
3590
+ await decrementKey();
3591
+ });
3592
+ }
3593
+ }
3594
+ config3.validations.disable();
3595
+ if (totalHits > limit) {
3596
+ if (config3.legacyHeaders || config3.standardHeaders) {
3597
+ setRetryAfterHeader(response, info, config3.windowMs);
3598
+ }
3599
+ config3.handler(request, response, next, options);
3600
+ return;
3601
+ }
3602
+ next();
3603
+ }
3604
+ );
3605
+ const getThrowFn = () => {
3606
+ throw new Error("The current store does not support the get/getKey method");
3607
+ };
3608
+ middleware.resetKey = config3.store.resetKey.bind(config3.store);
3609
+ middleware.getKey = typeof config3.store.get === "function" ? config3.store.get.bind(config3.store) : getThrowFn;
3610
+ return middleware;
3611
+ };
3612
+ rate_limit_default = rateLimit;
3613
+ }
3614
+ });
3615
+
3616
+ // src/server/settings-api.ts
3617
+ var settings_api_exports = {};
3618
+ __export(settings_api_exports, {
3619
+ SETTINGS_REGISTRY: () => SETTINGS_REGISTRY,
3620
+ config: () => config,
3621
+ getEnvFilePath: () => getEnvFilePath,
3622
+ initializeConfig: () => initializeConfig,
3623
+ parseEnvFile: () => parseEnvFile,
3624
+ updateSetting: () => updateSetting,
3625
+ writeEnvFile: () => writeEnvFile
3626
+ });
3627
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
3628
+ import { join as join2 } from "node:path";
3629
+ import { homedir as homedir2 } from "node:os";
3630
+ function getEnvFilePath() {
3631
+ const localPath = join2(import.meta.dirname, "../../.env.local");
3632
+ if (existsSync2(localPath)) {
3633
+ return localPath;
3634
+ }
3635
+ return join2(homedir2(), ".puttry", ".env");
3636
+ }
3637
+ function parseEnvFile(filePath) {
3638
+ if (!existsSync2(filePath)) {
3639
+ return {};
3640
+ }
3641
+ const content = readFileSync2(filePath, "utf-8");
3642
+ const result = {};
3643
+ for (const line of content.split("\n")) {
3644
+ const trimmed = line.trim();
3645
+ if (trimmed && !trimmed.startsWith("#")) {
3646
+ const [key, ...valueParts] = trimmed.split("=");
3647
+ if (key) {
3648
+ result[key] = valueParts.join("=");
3649
+ }
3650
+ }
3651
+ }
3652
+ return result;
3653
+ }
3654
+ function writeEnvFile(filePath, data) {
3655
+ const lines = Object.entries(data).map(([key, value]) => `${key}=${value}`).join("\n");
3656
+ writeFileSync2(filePath, lines + "\n", "utf-8");
3657
+ }
3658
+ function initializeConfig() {
3659
+ config.AUTH_DISABLED = process.env.AUTH_DISABLED === "1" || process.env.AUTH_DISABLED === "true";
3660
+ config.SHOW_AUTH_DISABLED_WARNING = process.env.SHOW_AUTH_DISABLED_WARNING === "1" || process.env.SHOW_AUTH_DISABLED_WARNING === "true";
3661
+ config.TOTP_ENABLED = process.env.TOTP_ENABLED === "1" || process.env.TOTP_ENABLED === "true";
3662
+ config.SESSION_PASSWORD_TYPE = process.env.SESSION_PASSWORD_TYPE ?? "xkcd";
3663
+ config.SESSION_PASSWORD_LENGTH = Number(process.env.SESSION_PASSWORD_LENGTH ?? 4);
3664
+ config.LOG_SESSION_PASSWORD = process.env.LOG_SESSION_PASSWORD !== "0";
3665
+ config.PASSKEY_RP_ORIGIN = process.env.PASSKEY_RP_ORIGIN ?? "";
3666
+ config.RATE_LIMIT_GLOBAL_MAX = Number(process.env.RATE_LIMIT_GLOBAL_MAX ?? 500);
3667
+ config.RATE_LIMIT_SESSION_PASSWORD_MAX = Number(process.env.RATE_LIMIT_SESSION_PASSWORD_MAX ?? 10);
3668
+ config.SCROLLBACK_LINES = Number(process.env.SCROLLBACK_LINES ?? 1e4);
3669
+ }
3670
+ function updateSetting(key, value) {
3671
+ if (!(key in config)) {
3672
+ return { success: false, error: `Unknown setting: ${key}` };
3673
+ }
3674
+ const configKey = key;
3675
+ const metadata = SETTINGS_REGISTRY[configKey];
3676
+ if (!metadata) {
3677
+ return { success: false, error: `Setting not registered: ${key}` };
3678
+ }
3679
+ let convertedValue = value;
3680
+ if (metadata.type === "boolean") {
3681
+ convertedValue = value === "true" || value === "1" ? true : false;
3682
+ } else if (metadata.type === "number") {
3683
+ convertedValue = Number(value);
3684
+ if (isNaN(convertedValue)) {
3685
+ return { success: false, error: `Invalid number for ${key}` };
3686
+ }
3687
+ } else if (metadata.type === "enum" && "values" in metadata) {
3688
+ const validValues = metadata.values;
3689
+ if (!validValues.includes(value)) {
3690
+ return { success: false, error: `Invalid value for ${key}. Must be one of: ${validValues.join(", ")}` };
3691
+ }
3692
+ convertedValue = value;
3693
+ }
3694
+ ;
3695
+ config[configKey] = convertedValue;
3696
+ if (metadata.type === "boolean") {
3697
+ process.env[configKey] = convertedValue ? "1" : "0";
3698
+ } else {
3699
+ process.env[configKey] = String(convertedValue);
3700
+ }
3701
+ try {
3702
+ const envPath = getEnvFilePath();
3703
+ const envData = parseEnvFile(envPath);
3704
+ envData[configKey] = process.env[configKey];
3705
+ writeEnvFile(envPath, envData);
3706
+ logger_default.info(`[settings] Updated ${configKey} = ${process.env[configKey]}`);
3707
+ } catch (err) {
3708
+ logger_default.error(`[settings] Failed to persist ${configKey}:`, err);
3709
+ return { success: false, error: `Failed to persist setting: ${err}` };
3710
+ }
3711
+ return {
3712
+ success: true,
3713
+ requiresRestart: false,
3714
+ note: metadata.note
3715
+ };
3716
+ }
3717
+ var config, SETTINGS_REGISTRY;
3718
+ var init_settings_api = __esm({
3719
+ "src/server/settings-api.ts"() {
3720
+ init_logger();
3721
+ config = {
3722
+ AUTH_DISABLED: false,
3723
+ SHOW_AUTH_DISABLED_WARNING: false,
3724
+ TOTP_ENABLED: false,
3725
+ SESSION_PASSWORD_TYPE: "xkcd",
3726
+ SESSION_PASSWORD_LENGTH: 4,
3727
+ LOG_SESSION_PASSWORD: true,
3728
+ PASSKEY_RP_ORIGIN: "",
3729
+ RATE_LIMIT_GLOBAL_MAX: 500,
3730
+ RATE_LIMIT_SESSION_PASSWORD_MAX: 10,
3731
+ SCROLLBACK_LINES: 1e4
3732
+ };
3733
+ SETTINGS_REGISTRY = {
3734
+ AUTH_DISABLED: { type: "boolean", live: true, requiresRestart: false, note: "Users must reload" },
3735
+ SHOW_AUTH_DISABLED_WARNING: { type: "boolean", live: true, requiresRestart: false },
3736
+ TOTP_ENABLED: { type: "boolean", live: true, requiresRestart: false, note: "Affects next login attempt" },
3737
+ SESSION_PASSWORD_TYPE: { type: "enum", values: ["xkcd", "random"], live: true, requiresRestart: false },
3738
+ SESSION_PASSWORD_LENGTH: { type: "number", live: true, requiresRestart: false },
3739
+ LOG_SESSION_PASSWORD: { type: "boolean", live: true, requiresRestart: false },
3740
+ PASSKEY_RP_ORIGIN: { type: "string", live: true, requiresRestart: false },
3741
+ RATE_LIMIT_GLOBAL_MAX: { type: "number", live: true, requiresRestart: false },
3742
+ RATE_LIMIT_SESSION_PASSWORD_MAX: { type: "number", live: true, requiresRestart: false },
3743
+ SCROLLBACK_LINES: { type: "number", live: true, requiresRestart: false, note: "Affects new sessions" }
3744
+ };
3745
+ }
3746
+ });
3747
+
3748
+ // src/server/rate-limit.ts
3749
+ var rate_limit_exports = {};
3750
+ __export(rate_limit_exports, {
3751
+ globalLimiter: () => globalLimiter,
3752
+ sessionPasswordLimiter: () => sessionPasswordLimiter
3753
+ });
3754
+ function isAuthenticated(req) {
3755
+ const cookieHeader = req.headers.cookie;
3756
+ if (!cookieHeader) return false;
3757
+ return /(?:^|;\s*)_wt_session=/.test(cookieHeader) || /(?:^|;\s*)_wt_temp=/.test(cookieHeader);
3758
+ }
3759
+ var globalLimiter, sessionPasswordLimiter;
3760
+ var init_rate_limit = __esm({
3761
+ "src/server/rate-limit.ts"() {
3762
+ init_dist();
3763
+ init_settings_api();
3764
+ globalLimiter = rate_limit_default({
3765
+ windowMs: 15 * 60 * 1e3,
3766
+ // 15 minutes
3767
+ max: (_req, _res) => config.RATE_LIMIT_GLOBAL_MAX,
3768
+ message: { error: "Too many requests" },
3769
+ standardHeaders: false,
3770
+ // Disable `RateLimit-*` headers
3771
+ skip: (req, res) => {
3772
+ if (res.statusCode < 400) return true;
3773
+ if (isAuthenticated(req)) return true;
3774
+ return false;
3775
+ }
3776
+ });
3777
+ sessionPasswordLimiter = rate_limit_default({
3778
+ windowMs: 60 * 60 * 1e3,
3779
+ // 1 hour
3780
+ max: (_req, _res) => config.RATE_LIMIT_SESSION_PASSWORD_MAX,
3781
+ message: { error: "Too many requests" },
3782
+ standardHeaders: false,
3783
+ handler: (req, res) => {
3784
+ console.log(`[rate-limit] Rate limit exceeded for ${req.ip}`);
3785
+ res.status(429).json({ error: "Too many requests" });
3786
+ }
3787
+ });
3788
+ }
3789
+ });
3790
+
3791
+ // src/server/sync-bus.ts
3792
+ var sync_bus_exports = {};
3793
+ __export(sync_bus_exports, {
3794
+ addSyncClient: () => addSyncClient,
3795
+ broadcastSync: () => broadcastSync
3796
+ });
3797
+ import { WebSocket } from "ws";
3798
+ function addSyncClient(ws) {
3799
+ syncClients.add(ws);
3800
+ ws.on("close", () => syncClients.delete(ws));
3801
+ ws.on("error", () => syncClients.delete(ws));
3802
+ }
3803
+ function broadcastSync(event) {
3804
+ const data = JSON.stringify(event);
3805
+ for (const client of syncClients) {
3806
+ if (client.readyState === WebSocket.OPEN) client.send(data);
3807
+ }
3808
+ }
3809
+ var syncClients;
3810
+ var init_sync_bus = __esm({
3811
+ "src/server/sync-bus.ts"() {
3812
+ syncClients = /* @__PURE__ */ new Set();
3813
+ }
3814
+ });
3815
+
3816
+ // src/server/pty-manager.ts
3817
+ var pty_manager_exports = {};
3818
+ __export(pty_manager_exports, {
3819
+ attachWebSocket: () => attachWebSocket,
3820
+ cleanupAll: () => cleanupAll,
3821
+ createSession: () => createSession,
3822
+ getAllSessions: () => getAllSessions,
3823
+ getSession: () => getSession,
3824
+ getSessionProcessInfo: () => getSessionProcessInfo,
3825
+ killSession: () => killSession,
3826
+ onSyncClientConnect: () => onSyncClientConnect,
3827
+ onSyncClientDisconnect: () => onSyncClientDisconnect,
3828
+ renameSession: () => renameSession
3829
+ });
3830
+ import { spawn } from "node-pty";
3831
+ import { randomUUID } from "node:crypto";
3832
+ import { execSync } from "node:child_process";
3833
+ function countNewlines(str) {
3834
+ let count = 0;
3835
+ for (let i = 0; i < str.length; i++) {
3836
+ if (str[i] === "\n") count++;
3837
+ }
3838
+ return count;
3839
+ }
3840
+ function createSession(cols, rows, creatorClientId) {
3841
+ const id = randomUUID();
3842
+ const label = `bash ${sessions.size + 1}`;
3843
+ const pty = spawn(process.env.SHELL || "bash", [], {
3844
+ name: "xterm-256color",
3845
+ cols,
3846
+ rows,
3847
+ cwd: process.env.HOME
3848
+ });
3849
+ const session = {
3850
+ id,
3851
+ label,
3852
+ pty,
3853
+ clients: /* @__PURE__ */ new Set(),
3854
+ clientIds: /* @__PURE__ */ new Map(),
3855
+ cols,
3856
+ rows,
3857
+ createdAt: /* @__PURE__ */ new Date(),
3858
+ outputBuffer: "",
3859
+ outputBufferLines: 0,
3860
+ inputLockClientId: creatorClientId ?? null
3861
+ };
3862
+ pty.onData((data) => {
3863
+ session.outputBuffer += data;
3864
+ session.outputBufferLines += countNewlines(data);
3865
+ while (session.outputBufferLines > config.SCROLLBACK_LINES) {
3866
+ const idx = session.outputBuffer.indexOf("\n");
3867
+ if (idx === -1) {
3868
+ session.outputBuffer = "";
3869
+ session.outputBufferLines = 0;
3870
+ break;
3871
+ }
3872
+ session.outputBuffer = session.outputBuffer.slice(idx + 1);
3873
+ session.outputBufferLines--;
3874
+ }
3875
+ for (const client of session.clients) {
3876
+ if (client.readyState === 1) {
3877
+ client.send(data);
3878
+ }
3879
+ }
3880
+ const existingTimer = dataActivityDebounceTimers.get(id);
3881
+ if (!existingTimer) {
3882
+ broadcastSync({ type: "data-activity", sessionId: id });
3883
+ const timer = setTimeout(() => {
3884
+ dataActivityDebounceTimers.delete(id);
3885
+ }, 300);
3886
+ dataActivityDebounceTimers.set(id, timer);
3887
+ }
3888
+ });
3889
+ pty.onExit(() => {
3890
+ logger_default.info(`[pty-manager] PTY ${id} exited`);
3891
+ for (const client of session.clients) {
3892
+ if (client.readyState === 1) {
3893
+ client.send(JSON.stringify({ type: "exit" }));
3894
+ }
3895
+ }
3896
+ session.clients.clear();
3897
+ sessions.delete(id);
3898
+ broadcastSync({ type: "session-deleted", sessionId: id });
3899
+ });
3900
+ sessions.set(id, session);
3901
+ logger_default.info(`[pty-manager] Created session ${id} (${label})`);
3902
+ broadcastSync({ type: "session-created", session: { id, label, createdAt: session.createdAt, cols, rows } });
3903
+ return session;
3904
+ }
3905
+ function getSession(id) {
3906
+ return sessions.get(id);
3907
+ }
3908
+ function getAllSessions() {
3909
+ return Array.from(sessions.values()).map((s) => ({
3910
+ id: s.id,
3911
+ label: s.label,
3912
+ pty: s.pty,
3913
+ clients: s.clients,
3914
+ clientIds: s.clientIds,
3915
+ cols: s.cols,
3916
+ rows: s.rows,
3917
+ createdAt: s.createdAt,
3918
+ outputBuffer: s.outputBuffer,
3919
+ outputBufferLines: s.outputBufferLines,
3920
+ inputLockClientId: s.inputLockClientId
3921
+ }));
3922
+ }
3923
+ function attachWebSocket(id, ws, clientId = "") {
3924
+ const session = sessions.get(id);
3925
+ if (!session) {
3926
+ logger_default.warn(`[pty-manager] Cannot attach WebSocket: session ${id} not found`);
3927
+ return false;
3928
+ }
3929
+ if (session.outputBuffer.length > 0) {
3930
+ ws.send(session.outputBuffer);
3931
+ }
3932
+ session.clients.add(ws);
3933
+ session.clientIds.set(ws, clientId);
3934
+ ws.on("message", (data) => {
3935
+ try {
3936
+ const message = JSON.parse(data.toString());
3937
+ if (message.type === "resize") {
3938
+ session.pty.resize(message.cols, message.rows);
3939
+ session.cols = message.cols;
3940
+ session.rows = message.rows;
3941
+ } else if (message.type === "acquire-lock") {
3942
+ const force = !!message.force;
3943
+ const wasHeld = session.inputLockClientId;
3944
+ if (force || !session.inputLockClientId || session.inputLockClientId === clientId) {
3945
+ session.inputLockClientId = clientId;
3946
+ broadcastSync({ type: "input-lock-acquired", sessionId: id, clientId });
3947
+ logger_default.info(`[pty-manager] Lock acquired for session ${id} by ${clientId} (force=${force}, wasHeld=${wasHeld})`);
3948
+ } else {
3949
+ logger_default.info(`[pty-manager] Lock denied for session ${id} by ${clientId} (held by ${session.inputLockClientId})`);
3950
+ }
3951
+ } else if (message.type === "release-lock") {
3952
+ if (session.inputLockClientId === clientId) {
3953
+ session.inputLockClientId = null;
3954
+ broadcastSync({ type: "input-lock-released", sessionId: id });
3955
+ }
3956
+ }
3957
+ } catch {
3958
+ if (session.inputLockClientId === clientId) {
3959
+ session.pty.write(data.toString());
3960
+ }
3961
+ }
3962
+ });
3963
+ ws.on("close", () => {
3964
+ logger_default.info(`[pty-manager] WebSocket closed for session ${id}`);
3965
+ session.clients.delete(ws);
3966
+ session.clientIds.delete(ws);
3967
+ });
3968
+ ws.on("error", (err) => {
3969
+ logger_default.error(`[pty-manager] WebSocket error for session ${id}: ${err.message}`);
3970
+ session.clients.delete(ws);
3971
+ });
3972
+ logger_default.info(`[pty-manager] WebSocket attached to session ${id}`);
3973
+ return true;
3974
+ }
3975
+ function renameSession(id, newLabel) {
3976
+ const session = sessions.get(id);
3977
+ if (!session) return false;
3978
+ session.label = newLabel;
3979
+ logger_default.info(`[pty-manager] Renamed session ${id} to "${newLabel}"`);
3980
+ broadcastSync({ type: "session-renamed", sessionId: id, label: newLabel });
3981
+ return true;
3982
+ }
3983
+ function killSession(id) {
3984
+ const session = sessions.get(id);
3985
+ if (!session) return false;
3986
+ for (const client of session.clients) {
3987
+ if (client.readyState === 1) {
3988
+ client.send(JSON.stringify({ type: "exit" }));
3989
+ client.close();
3990
+ }
3991
+ }
3992
+ session.clients.clear();
3993
+ const releaseTimer = lockReleaseTimers.get(id);
3994
+ if (releaseTimer) {
3995
+ clearTimeout(releaseTimer);
3996
+ lockReleaseTimers.delete(id);
3997
+ }
3998
+ try {
3999
+ session.pty.kill();
4000
+ } catch {
4001
+ }
4002
+ sessions.delete(id);
4003
+ logger_default.info(`[pty-manager] Killed session ${id}`);
4004
+ broadcastSync({ type: "session-deleted", sessionId: id });
4005
+ return true;
4006
+ }
4007
+ function onSyncClientConnect(clientId) {
4008
+ let cancelledCount = 0;
4009
+ for (const [sessionId, timer] of lockReleaseTimers) {
4010
+ const session = sessions.get(sessionId);
4011
+ if (session && session.inputLockClientId === clientId) {
4012
+ clearTimeout(timer);
4013
+ lockReleaseTimers.delete(sessionId);
4014
+ cancelledCount++;
4015
+ logger_default.info(`[pty-manager] Cancelled pending lock release for session ${sessionId} (sync reconnect by ${clientId})`);
4016
+ }
4017
+ }
4018
+ logger_default.info(`[pty-manager] Sync client connected: ${clientId} (cancelled ${cancelledCount} timers)`);
4019
+ }
4020
+ function onSyncClientDisconnect(clientId) {
4021
+ let timerCount = 0;
4022
+ for (const [id, session] of sessions) {
4023
+ if (session.inputLockClientId === clientId) {
4024
+ const existing = lockReleaseTimers.get(id);
4025
+ if (existing) clearTimeout(existing);
4026
+ const timer = setTimeout(() => {
4027
+ lockReleaseTimers.delete(id);
4028
+ if (session.inputLockClientId === clientId) {
4029
+ session.inputLockClientId = null;
4030
+ broadcastSync({ type: "input-lock-released", sessionId: id });
4031
+ logger_default.info(`[pty-manager] Lock released for session ${id} (sync timeout for ${clientId})`);
4032
+ }
4033
+ }, 2e3);
4034
+ lockReleaseTimers.set(id, timer);
4035
+ timerCount++;
4036
+ logger_default.info(`[pty-manager] Started 2s grace timer for session ${id} (lock held by ${clientId})`);
4037
+ }
4038
+ }
4039
+ logger_default.info(`[pty-manager] Sync client disconnected: ${clientId} (started ${timerCount} grace timers)`);
4040
+ }
4041
+ function cleanupAll() {
4042
+ for (const [id] of sessions) {
4043
+ killSession(id);
4044
+ }
4045
+ logger_default.info(`[pty-manager] Cleaned up all sessions`);
4046
+ }
4047
+ function getSessionProcessInfo(id) {
4048
+ const session = sessions.get(id);
4049
+ if (!session) return null;
4050
+ try {
4051
+ const pid = session.pty.pid;
4052
+ if (!pid) return null;
4053
+ const psOutput = execSync(`ps -p ${pid} -o rss=,pcpu= 2>/dev/null || echo ""`, {
4054
+ encoding: "utf-8"
4055
+ }).trim();
4056
+ let memory = "N/A";
4057
+ let memoryBytes = 0;
4058
+ let cpu = 0;
4059
+ if (psOutput) {
4060
+ const [rssStr, cpuStr] = psOutput.split(/\s+/);
4061
+ const rssKb = parseInt(rssStr, 10);
4062
+ memoryBytes = rssKb * 1024;
4063
+ memory = formatBytes(memoryBytes);
4064
+ cpu = parseFloat(cpuStr) || 0;
4065
+ }
4066
+ const createdAt = session.createdAt.getTime();
4067
+ const now = Date.now();
4068
+ const uptimeSeconds = Math.floor((now - createdAt) / 1e3);
4069
+ const uptime = formatUptime(uptimeSeconds);
4070
+ return {
4071
+ pid,
4072
+ memory,
4073
+ memoryBytes,
4074
+ cpu,
4075
+ startedAt: session.createdAt.toISOString(),
4076
+ uptime,
4077
+ uptimeSeconds
4078
+ };
4079
+ } catch (err) {
4080
+ logger_default.warn(`[pty-manager] Failed to get process info for session ${id}: ${err instanceof Error ? err.message : err}`);
4081
+ return null;
4082
+ }
4083
+ }
4084
+ function formatBytes(bytes) {
4085
+ if (bytes === 0) return "0 B";
4086
+ const k = 1024;
4087
+ const sizes = ["B", "KB", "MB", "GB"];
4088
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
4089
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
4090
+ }
4091
+ function formatUptime(seconds) {
4092
+ const days = Math.floor(seconds / 86400);
4093
+ const hours = Math.floor(seconds % 86400 / 3600);
4094
+ const minutes = Math.floor(seconds % 3600 / 60);
4095
+ const secs = seconds % 60;
4096
+ const parts = [];
4097
+ if (days > 0) parts.push(`${days}d`);
4098
+ if (hours > 0) parts.push(`${hours}h`);
4099
+ if (minutes > 0) parts.push(`${minutes}m`);
4100
+ if (secs > 0 || parts.length === 0) parts.push(`${secs}s`);
4101
+ return parts.join(" ");
4102
+ }
4103
+ var sessions, dataActivityDebounceTimers, lockReleaseTimers;
4104
+ var init_pty_manager = __esm({
4105
+ "src/server/pty-manager.ts"() {
4106
+ init_logger();
4107
+ init_sync_bus();
4108
+ init_settings_api();
4109
+ sessions = /* @__PURE__ */ new Map();
4110
+ dataActivityDebounceTimers = /* @__PURE__ */ new Map();
4111
+ lockReleaseTimers = /* @__PURE__ */ new Map();
4112
+ }
4113
+ });
4114
+
4115
+ // src/server/terminal-routes.ts
4116
+ var terminal_routes_exports = {};
4117
+ __export(terminal_routes_exports, {
4118
+ createTerminalRouter: () => createTerminalRouter
4119
+ });
4120
+ import { Router } from "express";
4121
+ function createTerminalRouter() {
4122
+ const router = Router();
4123
+ router.get("/", (_req, res) => {
4124
+ try {
4125
+ const sessions2 = getAllSessions().map((s) => ({
4126
+ id: s.id,
4127
+ label: s.label,
4128
+ createdAt: s.createdAt,
4129
+ cols: s.cols,
4130
+ rows: s.rows
4131
+ }));
4132
+ res.json(sessions2);
4133
+ } catch (err) {
4134
+ logger_default.error(`[terminal-routes] Failed to list sessions: ${err instanceof Error ? err.message : err}`);
4135
+ res.status(500).json({ error: "Failed to list sessions" });
4136
+ }
4137
+ });
4138
+ router.post("/", (req, res) => {
4139
+ try {
4140
+ const { clientId } = req.body ?? {};
4141
+ const cols = 80;
4142
+ const rows = 24;
4143
+ const session = createSession(cols, rows, clientId ?? null);
4144
+ res.status(201).json({
4145
+ id: session.id,
4146
+ label: session.label,
4147
+ createdAt: session.createdAt,
4148
+ cols: session.cols,
4149
+ rows: session.rows
4150
+ });
4151
+ } catch (err) {
4152
+ logger_default.error(`[terminal-routes] Failed to create session: ${err instanceof Error ? err.message : err}`);
4153
+ res.status(500).json({ error: "Failed to create session" });
4154
+ }
4155
+ });
4156
+ router.patch("/:id", (req, res) => {
4157
+ try {
4158
+ const { id } = req.params;
4159
+ const { label } = req.body;
4160
+ if (!label || typeof label !== "string" || !label.trim()) {
4161
+ res.status(400).json({ error: "Label is required and must be a non-empty string" });
4162
+ return;
4163
+ }
4164
+ const session = getSession(id);
4165
+ if (!session) {
4166
+ res.status(404).json({ error: "Session not found" });
4167
+ return;
4168
+ }
4169
+ if (!renameSession(id, label.trim())) {
4170
+ res.status(500).json({ error: "Failed to rename session" });
4171
+ return;
4172
+ }
4173
+ res.json({
4174
+ id: session.id,
4175
+ label: label.trim(),
4176
+ createdAt: session.createdAt,
4177
+ cols: session.cols,
4178
+ rows: session.rows
4179
+ });
4180
+ } catch (err) {
4181
+ logger_default.error(`[terminal-routes] Failed to rename session: ${err instanceof Error ? err.message : err}`);
4182
+ res.status(500).json({ error: "Failed to rename session" });
4183
+ }
4184
+ });
4185
+ router.get("/:id/info", (req, res) => {
4186
+ try {
4187
+ const { id } = req.params;
4188
+ const session = getSession(id);
4189
+ if (!session) {
4190
+ res.status(404).json({ error: "Session not found" });
4191
+ return;
4192
+ }
4193
+ const processInfo = getSessionProcessInfo(id);
4194
+ if (!processInfo) {
4195
+ res.status(500).json({ error: "Failed to get process info" });
4196
+ return;
4197
+ }
4198
+ res.json(processInfo);
4199
+ } catch (err) {
4200
+ logger_default.error(`[terminal-routes] Failed to get process info: ${err instanceof Error ? err.message : err}`);
4201
+ res.status(500).json({ error: "Failed to get process info" });
4202
+ }
4203
+ });
4204
+ router.delete("/:id", (req, res) => {
4205
+ try {
4206
+ const { id } = req.params;
4207
+ const session = getSession(id);
4208
+ if (!session) {
4209
+ res.status(404).json({ error: "Session not found" });
4210
+ return;
4211
+ }
4212
+ if (!killSession(id)) {
4213
+ res.status(500).json({ error: "Failed to kill session" });
4214
+ return;
4215
+ }
4216
+ res.json({ success: true });
4217
+ } catch (err) {
4218
+ logger_default.error(`[terminal-routes] Failed to kill session: ${err instanceof Error ? err.message : err}`);
4219
+ res.status(500).json({ error: "Failed to kill session" });
4220
+ }
4221
+ });
4222
+ return router;
4223
+ }
4224
+ var init_terminal_routes = __esm({
4225
+ "src/server/terminal-routes.ts"() {
4226
+ init_pty_manager();
4227
+ init_logger();
4228
+ }
4229
+ });
4230
+
4231
+ // src/server/passkey-state.ts
4232
+ var passkey_state_exports = {};
4233
+ __export(passkey_state_exports, {
4234
+ clearPasskeys: () => clearPasskeys,
4235
+ deletePasskey: () => deletePasskey,
4236
+ getPasskeyById: () => getPasskeyById,
4237
+ getPasskeys: () => getPasskeys,
4238
+ savePasskey: () => savePasskey
4239
+ });
4240
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "node:fs";
4241
+ import { join as join3 } from "node:path";
4242
+ import { homedir as homedir3 } from "node:os";
4243
+ function loadPasskeys() {
4244
+ if (existsSync3(PASSKEYS_PATH)) {
4245
+ try {
4246
+ const data = readFileSync3(PASSKEYS_PATH, "utf-8");
4247
+ const passkeys = JSON.parse(data);
4248
+ logger_default.info(`[passkey-state] Loaded ${passkeys.length} passkeys from disk`);
4249
+ return passkeys;
4250
+ } catch (err) {
4251
+ logger_default.error(`[passkey-state] Failed to load passkeys: ${err instanceof Error ? err.message : err}`);
4252
+ return [];
4253
+ }
4254
+ }
4255
+ return [];
4256
+ }
4257
+ function savePasskeys(passkeys) {
4258
+ try {
4259
+ mkdirSync2(STATE_DIR2, { recursive: true });
4260
+ writeFileSync3(PASSKEYS_PATH, JSON.stringify(passkeys, null, 2), { mode: 384 });
4261
+ logger_default.info(`[passkey-state] Saved ${passkeys.length} passkeys to disk`);
4262
+ } catch (err) {
4263
+ logger_default.error(`[passkey-state] Failed to save passkeys: ${err instanceof Error ? err.message : err}`);
4264
+ }
4265
+ }
4266
+ function getPasskeys() {
4267
+ return loadPasskeys();
4268
+ }
4269
+ function savePasskey(cred) {
4270
+ const passkeys = loadPasskeys();
4271
+ const existingIndex = passkeys.findIndex((p) => p.id === cred.id);
4272
+ if (existingIndex >= 0) {
4273
+ passkeys[existingIndex] = cred;
4274
+ } else {
4275
+ passkeys.push(cred);
4276
+ }
4277
+ savePasskeys(passkeys);
4278
+ }
4279
+ function deletePasskey(id) {
4280
+ const passkeys = loadPasskeys();
4281
+ const filtered = passkeys.filter((p) => p.id !== id);
4282
+ savePasskeys(filtered);
4283
+ logger_default.info(`[passkey-state] Deleted passkey ${id.slice(0, 8)}...`);
4284
+ }
4285
+ function getPasskeyById(id) {
4286
+ const passkeys = loadPasskeys();
4287
+ return passkeys.find((p) => p.id === id) ?? null;
4288
+ }
4289
+ function clearPasskeys() {
4290
+ try {
4291
+ if (existsSync3(PASSKEYS_PATH)) {
4292
+ unlinkSync2(PASSKEYS_PATH);
4293
+ }
4294
+ logger_default.info(`[passkey-state] All passkeys cleared`);
4295
+ } catch (err) {
4296
+ logger_default.error(`[passkey-state] Failed to clear passkeys: ${err instanceof Error ? err.message : err}`);
4297
+ }
4298
+ }
4299
+ var STATE_DIR2, PASSKEYS_PATH;
4300
+ var init_passkey_state = __esm({
4301
+ "src/server/passkey-state.ts"() {
4302
+ init_logger();
4303
+ STATE_DIR2 = join3(homedir3(), ".puttry");
4304
+ PASSKEYS_PATH = join3(STATE_DIR2, "passkeys.json");
4305
+ }
4306
+ });
4307
+
4308
+ // src/server/server.ts
4309
+ import express from "express";
4310
+ import { createServer } from "node:http";
4311
+ import { WebSocketServer } from "ws";
4312
+ import path from "node:path";
4313
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
4314
+ import { join as join4 } from "node:path";
4315
+ import { homedir as homedir4, hostname, userInfo } from "node:os";
4316
+ import { randomUUID as randomUUID2 } from "node:crypto";
4317
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "node:fs";
4318
+ var envPaths = [
4319
+ join4(import.meta.dirname, "../../.env.local"),
4320
+ join4(homedir4(), ".puttry", ".env")
4321
+ ];
4322
+ console.log("[startup] Loading env files from:", envPaths);
4323
+ for (const envPath of envPaths) {
4324
+ console.log(`[startup] Checking ${envPath}...`);
4325
+ if (existsSync4(envPath)) {
4326
+ console.log(`[startup] Found ${envPath}, loading...`);
4327
+ try {
4328
+ const envContent = readFileSync4(envPath, "utf-8");
4329
+ const lines = envContent.split("\n");
4330
+ for (const line of lines) {
4331
+ const trimmed = line.trim();
4332
+ if (trimmed && !trimmed.startsWith("#")) {
4333
+ const [key, ...valueParts] = trimmed.split("=");
4334
+ const value = valueParts.join("=");
4335
+ if (key && !process.env[key]) {
4336
+ console.log(`[startup] Set ${key}=${value}`);
4337
+ process.env[key] = value;
4338
+ } else if (key && process.env[key]) {
4339
+ console.log(`[startup] Skipped ${key} (already set to ${process.env[key]})`);
4340
+ }
4341
+ }
4342
+ }
4343
+ } catch (err) {
4344
+ console.error(`Failed to load .env from ${envPath}`, err);
4345
+ }
4346
+ } else {
4347
+ console.log(`[startup] Not found: ${envPath}`);
4348
+ }
4349
+ }
4350
+ var logger2 = (await Promise.resolve().then(() => (init_logger(), logger_exports))).default;
4351
+ var { initAuthState: initAuthState2, getSessionPassword: getSessionPassword2, get2FAState: get2FAState2, save2FAState: save2FAState2 } = await Promise.resolve().then(() => (init_auth_state(), auth_state_exports));
4352
+ var { verifyTotp: verifyTotp2, generateQRCode: generateQRCode2 } = await Promise.resolve().then(() => (init_totp_helper(), totp_helper_exports));
4353
+ var { globalLimiter: globalLimiter2, sessionPasswordLimiter: sessionPasswordLimiter2 } = await Promise.resolve().then(() => (init_rate_limit(), rate_limit_exports));
4354
+ var { createTerminalRouter: createTerminalRouter2 } = await Promise.resolve().then(() => (init_terminal_routes(), terminal_routes_exports));
4355
+ var { getSession: getSession2, attachWebSocket: attachWebSocket2, cleanupAll: cleanupAll2, getAllSessions: getAllSessions2, onSyncClientConnect: onSyncClientConnect2, onSyncClientDisconnect: onSyncClientDisconnect2 } = await Promise.resolve().then(() => (init_pty_manager(), pty_manager_exports));
4356
+ var { addSyncClient: addSyncClient2 } = await Promise.resolve().then(() => (init_sync_bus(), sync_bus_exports));
4357
+ var { getPasskeys: getPasskeys2, savePasskey: savePasskey2, deletePasskey: deletePasskey2, getPasskeyById: getPasskeyById2 } = await Promise.resolve().then(() => (init_passkey_state(), passkey_state_exports));
4358
+ var { config: config2, initializeConfig: initializeConfig2, updateSetting: updateSetting2 } = await Promise.resolve().then(() => (init_settings_api(), settings_api_exports));
4359
+ var {
4360
+ generateRegistrationOptions,
4361
+ verifyRegistrationResponse,
4362
+ generateAuthenticationOptions,
4363
+ verifyAuthenticationResponse
4364
+ } = await import("@simplewebauthn/server");
4365
+ initializeConfig2();
4366
+ await initAuthState2();
4367
+ var PID_DIR = join4(homedir4(), ".puttry");
4368
+ var PID_PATH = join4(PID_DIR, "server.pid");
4369
+ mkdirSync3(PID_DIR, { recursive: true });
4370
+ writeFileSync4(PID_PATH, String(process.pid), "utf-8");
4371
+ logger2.info(`[startup] PID file written to ${PID_PATH}`);
4372
+ var PORT = Number(process.env.PORT ?? 5174);
4373
+ var HOST = process.env.HOST ?? "0.0.0.0";
4374
+ var distPath = path.join(import.meta.dirname, "../dist");
4375
+ var RP_NAME = "PuTTrY";
4376
+ function getPasskeyOrigin() {
4377
+ return process.env.PASSKEY_RP_ORIGIN ?? `http://localhost:${PORT}`;
4378
+ }
4379
+ function getPasskeyRpId() {
4380
+ try {
4381
+ return new URL(getPasskeyOrigin()).hostname;
4382
+ } catch {
4383
+ return "localhost";
4384
+ }
4385
+ }
4386
+ function storeChallengeForSession(req, challenge) {
4387
+ const token = parseBrowserSessionToken(req) ?? parseTempSessionToken(req);
4388
+ if (!token) return;
4389
+ pendingChallenges.set(token, { challenge, expiresAt: Date.now() + 5 * 60 * 1e3 });
4390
+ setTimeout(() => pendingChallenges.delete(token), 5 * 60 * 1e3);
4391
+ }
4392
+ function getChallengeForSession(req) {
4393
+ const token = parseBrowserSessionToken(req) ?? parseTempSessionToken(req);
4394
+ if (!token) return null;
4395
+ const entry = pendingChallenges.get(token);
4396
+ if (!entry || entry.expiresAt < Date.now()) {
4397
+ pendingChallenges.delete(token);
4398
+ return null;
4399
+ }
4400
+ pendingChallenges.delete(token);
4401
+ return entry.challenge;
4402
+ }
4403
+ logger2.info(`[startup] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
4404
+ logger2.info(`[startup] Environment Configuration`);
4405
+ logger2.info(`[startup] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
4406
+ logger2.info(`[startup] PORT = ${process.env.PORT ?? "5174 (default)"}`);
4407
+ logger2.info(`[startup] HOST = ${process.env.HOST ?? "0.0.0.0 (default)"}`);
4408
+ logger2.info(`[startup] AUTH_DISABLED = ${process.env.AUTH_DISABLED ?? "0 (default - auth enabled)"}`);
4409
+ logger2.info(`[startup] SHOW_AUTH_DISABLED_WARNING = ${process.env.SHOW_AUTH_DISABLED_WARNING ?? "0 (default - warning hidden)"}`);
4410
+ logger2.info(`[startup] SESSION_PASSWORD_TYPE = ${process.env.SESSION_PASSWORD_TYPE ?? "xkcd (default)"}`);
4411
+ logger2.info(`[startup] SESSION_PASSWORD_LENGTH = ${process.env.SESSION_PASSWORD_LENGTH ?? "4 (default)"}`);
4412
+ logger2.info(`[startup] TOTP_ENABLED = ${process.env.TOTP_ENABLED ?? "0 (default - disabled)"}`);
4413
+ logger2.info(`[startup] LOG_SESSION_PASSWORD = ${process.env.LOG_SESSION_PASSWORD ?? "1 (default - enabled)"}`);
4414
+ logger2.info(`[startup] PASSKEY_RP_ORIGIN = ${process.env.PASSKEY_RP_ORIGIN ?? getPasskeyOrigin() + " (default)"}`);
4415
+ logger2.info(`[startup] RATE_LIMIT_GLOBAL_MAX = ${process.env.RATE_LIMIT_GLOBAL_MAX ?? "500 (default)"}`);
4416
+ logger2.info(`[startup] RATE_LIMIT_SESSION_PASSWORD_MAX = ${process.env.RATE_LIMIT_SESSION_PASSWORD_MAX ?? "10 (default)"}`);
4417
+ logger2.info(`[startup] SCROLLBACK_LINES = ${process.env.SCROLLBACK_LINES ?? "10000 (default)"}`);
4418
+ logger2.info(`[startup] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
4419
+ logger2.info("");
4420
+ var activeSessions = /* @__PURE__ */ new Set();
4421
+ var pendingTotpSessions = /* @__PURE__ */ new Map();
4422
+ var pendingChallenges = /* @__PURE__ */ new Map();
4423
+ function parseBrowserSessionToken(req) {
4424
+ const cookieHeader = req.headers.cookie;
4425
+ if (!cookieHeader) return null;
4426
+ const match = cookieHeader.match(/(?:^|;\s*)_wt_session=([^;]+)/);
4427
+ return match ? match[1] : null;
4428
+ }
4429
+ function parseTempSessionToken(req) {
4430
+ const cookieHeader = req.headers.cookie;
4431
+ if (!cookieHeader) return null;
4432
+ const match = cookieHeader.match(/(?:^|;\s*)_wt_temp=([^;]+)/);
4433
+ return match ? match[1] : null;
4434
+ }
4435
+ function createBrowserSession() {
4436
+ const token = randomUUID2();
4437
+ activeSessions.add(token);
4438
+ return {
4439
+ token,
4440
+ setCookieHeader: `_wt_session=${token}; HttpOnly; SameSite=Lax; Path=/`
4441
+ };
4442
+ }
4443
+ function createTempSession() {
4444
+ const token = randomUUID2();
4445
+ pendingTotpSessions.set(token, { createdAt: Date.now() });
4446
+ setTimeout(() => pendingTotpSessions.delete(token), 5 * 60 * 1e3);
4447
+ return {
4448
+ token,
4449
+ setCookieHeader: `_wt_temp=${token}; HttpOnly; SameSite=Lax; Path=/; Max-Age=300`
4450
+ };
4451
+ }
4452
+ function clearBrowserSessionCookie() {
4453
+ return `_wt_session=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`;
4454
+ }
4455
+ function clearTempSessionCookie() {
4456
+ return `_wt_temp=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`;
4457
+ }
4458
+ var app = express();
4459
+ var httpServer = createServer(app);
4460
+ var wss = new WebSocketServer({ noServer: true });
4461
+ var syncWss = new WebSocketServer({ noServer: true });
4462
+ app.use(express.json());
4463
+ if (process.env.LOG_SESSION_PASSWORD !== "0") {
4464
+ const { getSessionPassword: getSessionPassword3 } = await Promise.resolve().then(() => (init_auth_state(), auth_state_exports));
4465
+ const password = getSessionPassword3();
4466
+ logger2.info(`[startup] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
4467
+ logger2.info(`[startup] Session Password`);
4468
+ logger2.info(`[startup] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
4469
+ logger2.info(`[startup] Password: ${password}`);
4470
+ logger2.info(`[startup] URL: http://localhost:${PORT}`);
4471
+ logger2.info(`[startup] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
4472
+ logger2.info("");
4473
+ }
4474
+ function requireAuth(req, res, next) {
4475
+ if (config2.AUTH_DISABLED) {
4476
+ next();
4477
+ return;
4478
+ }
4479
+ const browserToken = parseBrowserSessionToken(req);
4480
+ if (browserToken && activeSessions.has(browserToken)) {
4481
+ next();
4482
+ } else {
4483
+ res.status(401).json({ error: "Not authenticated" });
4484
+ }
4485
+ }
4486
+ function requireAuthOrTempSession(req, res, next) {
4487
+ if (config2.AUTH_DISABLED) {
4488
+ next();
4489
+ return;
4490
+ }
4491
+ const browserToken = parseBrowserSessionToken(req);
4492
+ const tempToken = parseTempSessionToken(req);
4493
+ logger2.info(`[auth] Checking temp/browser session: browserToken=${!!browserToken}, tempToken=${!!tempToken}, pendingCount=${pendingTotpSessions.size}`);
4494
+ if (browserToken) logger2.info(`[auth] Browser token found, in activeSessions: ${activeSessions.has(browserToken)}`);
4495
+ if (tempToken) logger2.info(`[auth] Temp token found, in pendingTotpSessions: ${pendingTotpSessions.has(tempToken)}`);
4496
+ if (browserToken && activeSessions.has(browserToken) || tempToken && pendingTotpSessions.has(tempToken)) {
4497
+ next();
4498
+ } else {
4499
+ res.status(401).json({ error: "Not authenticated" });
4500
+ }
4501
+ }
4502
+ app.use("/api/auth/login", sessionPasswordLimiter2);
4503
+ app.use(globalLimiter2);
4504
+ app.get("/api/auth-status", (req, res) => {
4505
+ if (config2.AUTH_DISABLED) {
4506
+ res.json({ authenticated: true, authDisabled: true, showAuthDisabledWarning: config2.SHOW_AUTH_DISABLED_WARNING });
4507
+ return;
4508
+ }
4509
+ const browserToken = parseBrowserSessionToken(req);
4510
+ const authenticated = !!browserToken && activeSessions.has(browserToken);
4511
+ res.json({ authenticated, authDisabled: false, showAuthDisabledWarning: false });
4512
+ });
4513
+ app.post("/api/auth/login", (req, res) => {
4514
+ if (config2.AUTH_DISABLED) {
4515
+ const session = createBrowserSession();
4516
+ res.set("Set-Cookie", session.setCookieHeader);
4517
+ res.json({ authenticated: true, requiresTOTP: false });
4518
+ return;
4519
+ }
4520
+ const { password } = req.body;
4521
+ const currentPassword = getSessionPassword2();
4522
+ if (!password || password !== currentPassword) {
4523
+ res.status(401).json({ error: "Invalid password" });
4524
+ return;
4525
+ }
4526
+ const totpState = get2FAState2();
4527
+ const totpEnabled = config2.TOTP_ENABLED;
4528
+ const totpActive = totpEnabled && totpState?.verified === true;
4529
+ const passkeysActive = getPasskeys2().length > 0;
4530
+ if (totpEnabled && !totpActive && !passkeysActive) {
4531
+ const tempSession2 = createTempSession();
4532
+ logger2.info(`[auth] Creating temp session for TOTP setup: ${tempSession2.token.slice(0, 8)}...`);
4533
+ res.set("Set-Cookie", tempSession2.setCookieHeader);
4534
+ res.json({ authenticated: false, requiresTOTP: true, totpMode: "setup" });
4535
+ return;
4536
+ }
4537
+ if (!totpActive && !passkeysActive) {
4538
+ const session = createBrowserSession();
4539
+ res.set("Set-Cookie", session.setCookieHeader);
4540
+ res.json({ authenticated: true, requiresTOTP: false });
4541
+ return;
4542
+ }
4543
+ const tempSession = createTempSession();
4544
+ logger2.info(`[auth] Creating temp session for 2FA verification: ${tempSession.token.slice(0, 8)}...`);
4545
+ res.set("Set-Cookie", tempSession.setCookieHeader);
4546
+ if (totpActive && passkeysActive) {
4547
+ res.json({ authenticated: false, requiresTOTP: true, totpMode: "verify", requiresPasskey: true, canChoose: true });
4548
+ return;
4549
+ }
4550
+ if (totpActive) {
4551
+ res.json({ authenticated: false, requiresTOTP: true, totpMode: "verify" });
4552
+ return;
4553
+ }
4554
+ res.json({ authenticated: false, requiresPasskey: true });
4555
+ });
4556
+ app.delete("/api/auth", (req, res) => {
4557
+ const browserToken = parseBrowserSessionToken(req);
4558
+ if (browserToken) {
4559
+ activeSessions.delete(browserToken);
4560
+ pendingChallenges.delete(browserToken);
4561
+ }
4562
+ const tempToken = parseTempSessionToken(req);
4563
+ if (tempToken) {
4564
+ pendingTotpSessions.delete(tempToken);
4565
+ pendingChallenges.delete(tempToken);
4566
+ }
4567
+ res.set("Set-Cookie", [clearBrowserSessionCookie(), clearTempSessionCookie()]);
4568
+ res.json({ cleared: true });
4569
+ });
4570
+ app.get("/api/auth/2fa/qr", requireAuthOrTempSession, async (_req, res) => {
4571
+ try {
4572
+ const totpState = get2FAState2();
4573
+ let secret = totpState?.secret;
4574
+ if (!secret) {
4575
+ const { generateTotpSecret: generateTotpSecret2 } = await Promise.resolve().then(() => (init_totp_helper(), totp_helper_exports));
4576
+ secret = await generateTotpSecret2("user");
4577
+ }
4578
+ const accountLabel = `${userInfo().username}@${hostname()}`;
4579
+ const { dataUrl, manualEntryKey } = await generateQRCode2(secret, accountLabel, "PuTTrY");
4580
+ res.json({ dataUrl, manualEntryKey });
4581
+ } catch (err) {
4582
+ logger2.error(`[auth] QR generation failed: ${err instanceof Error ? err.message : err}`);
4583
+ res.status(500).json({ error: "Failed to generate QR code" });
4584
+ }
4585
+ });
4586
+ app.post("/api/auth/2fa/setup", requireAuthOrTempSession, async (req, res) => {
4587
+ const { secret, code } = req.body;
4588
+ if (!secret || !code) {
4589
+ res.status(400).json({ error: "Missing secret or code" });
4590
+ return;
4591
+ }
4592
+ if (!await verifyTotp2(secret, code)) {
4593
+ res.status(400).json({ error: "Invalid verification code" });
4594
+ return;
4595
+ }
4596
+ save2FAState2({
4597
+ secret,
4598
+ verified: true,
4599
+ setupAt: (/* @__PURE__ */ new Date()).toISOString()
4600
+ });
4601
+ const tempToken = parseTempSessionToken(req);
4602
+ if (tempToken) {
4603
+ pendingTotpSessions.delete(tempToken);
4604
+ }
4605
+ const session = createBrowserSession();
4606
+ res.set("Set-Cookie", [session.setCookieHeader, clearTempSessionCookie()]);
4607
+ res.json({ success: true, authenticated: true });
4608
+ });
4609
+ app.post("/api/auth/2fa/verify", async (req, res) => {
4610
+ const tempToken = parseTempSessionToken(req);
4611
+ if (!tempToken || !pendingTotpSessions.has(tempToken)) {
4612
+ res.status(401).json({ error: "No pending authentication" });
4613
+ return;
4614
+ }
4615
+ const { code } = req.body;
4616
+ if (!code) {
4617
+ res.status(400).json({ error: "Missing code" });
4618
+ return;
4619
+ }
4620
+ const totpState = get2FAState2();
4621
+ if (!totpState || !totpState.verified || !totpState.secret) {
4622
+ res.status(500).json({ error: "2FA not properly configured" });
4623
+ return;
4624
+ }
4625
+ try {
4626
+ const valid = await verifyTotp2(totpState.secret, code);
4627
+ if (!valid) {
4628
+ res.status(401).json({ error: "Invalid verification code" });
4629
+ return;
4630
+ }
4631
+ pendingTotpSessions.delete(tempToken);
4632
+ const session = createBrowserSession();
4633
+ res.set("Set-Cookie", [session.setCookieHeader, clearTempSessionCookie()]);
4634
+ res.json({ authenticated: true });
4635
+ } catch (err) {
4636
+ logger2.error(`[auth] TOTP verification error: ${err instanceof Error ? err.message : err}`);
4637
+ res.status(500).json({ error: "Verification failed" });
4638
+ }
4639
+ });
4640
+ app.get("/api/auth/passkey/register/options", requireAuth, async (req, res) => {
4641
+ try {
4642
+ const options = await generateRegistrationOptions({
4643
+ rpName: RP_NAME,
4644
+ rpID: getPasskeyRpId(),
4645
+ userID: Buffer.from("user"),
4646
+ userName: "user",
4647
+ userDisplayName: "PuTTrY User",
4648
+ attestationType: "direct",
4649
+ authenticatorSelection: {
4650
+ authenticatorAttachment: "platform",
4651
+ residentKey: "preferred"
4652
+ }
4653
+ });
4654
+ storeChallengeForSession(req, options.challenge);
4655
+ res.json(options);
4656
+ } catch (err) {
4657
+ logger2.error(`[auth] Passkey registration options failed: ${err instanceof Error ? err.message : err}`);
4658
+ res.status(500).json({ error: "Failed to generate registration options" });
4659
+ }
4660
+ });
4661
+ app.post("/api/auth/passkey/register/verify", requireAuth, async (req, res) => {
4662
+ try {
4663
+ const { response: credentialResponse, name } = req.body;
4664
+ if (!credentialResponse || !name) {
4665
+ res.status(400).json({ error: "Missing credential response or name" });
4666
+ return;
4667
+ }
4668
+ const challenge = getChallengeForSession(req);
4669
+ if (!challenge) {
4670
+ res.status(400).json({ error: "No pending registration challenge" });
4671
+ return;
4672
+ }
4673
+ const verification = await verifyRegistrationResponse({
4674
+ response: credentialResponse,
4675
+ expectedChallenge: challenge,
4676
+ expectedOrigin: getPasskeyOrigin(),
4677
+ expectedRPID: getPasskeyRpId()
4678
+ });
4679
+ if (!verification.verified || !verification.registrationInfo) {
4680
+ res.status(400).json({ error: "Registration verification failed" });
4681
+ return;
4682
+ }
4683
+ const { credential } = verification.registrationInfo;
4684
+ savePasskey2({
4685
+ id: credential.id,
4686
+ name,
4687
+ publicKey: Buffer.from(credential.publicKey).toString("base64"),
4688
+ counter: credential.counter,
4689
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
4690
+ transports: credential.transports ?? []
4691
+ });
4692
+ res.json({ verified: true });
4693
+ } catch (err) {
4694
+ logger2.error(`[auth] Passkey verification failed: ${err instanceof Error ? err.message : err}`);
4695
+ res.status(500).json({ error: "Verification failed" });
4696
+ }
4697
+ });
4698
+ app.post("/api/auth/passkey/auth/options", requireAuthOrTempSession, async (req, res) => {
4699
+ try {
4700
+ const passkeys = getPasskeys2();
4701
+ const options = await generateAuthenticationOptions({
4702
+ rpID: getPasskeyRpId(),
4703
+ allowCredentials: passkeys.map((pk) => ({
4704
+ id: pk.id,
4705
+ transports: pk.transports
4706
+ }))
4707
+ });
4708
+ storeChallengeForSession(req, options.challenge);
4709
+ res.json(options);
4710
+ } catch (err) {
4711
+ logger2.error(`[auth] Passkey auth options failed: ${err instanceof Error ? err.message : err}`);
4712
+ res.status(500).json({ error: "Failed to generate auth options" });
4713
+ }
4714
+ });
4715
+ app.post("/api/auth/passkey/auth/verify", requireAuthOrTempSession, async (req, res) => {
4716
+ try {
4717
+ const { response: assertionResponse } = req.body;
4718
+ if (!assertionResponse) {
4719
+ res.status(400).json({ error: "Missing assertion response" });
4720
+ return;
4721
+ }
4722
+ const challenge = getChallengeForSession(req);
4723
+ if (!challenge) {
4724
+ res.status(400).json({ error: "No pending auth challenge" });
4725
+ return;
4726
+ }
4727
+ const credentialId = Buffer.from(assertionResponse.id, "base64url").toString("base64url");
4728
+ const passkey = getPasskeyById2(credentialId);
4729
+ if (!passkey) {
4730
+ res.status(400).json({ error: "Passkey not found" });
4731
+ return;
4732
+ }
4733
+ const verification = await verifyAuthenticationResponse({
4734
+ response: assertionResponse,
4735
+ expectedChallenge: challenge,
4736
+ expectedOrigin: getPasskeyOrigin(),
4737
+ expectedRPID: getPasskeyRpId(),
4738
+ credential: {
4739
+ id: passkey.id,
4740
+ publicKey: new Uint8Array(Buffer.from(passkey.publicKey, "base64")),
4741
+ counter: passkey.counter,
4742
+ transports: passkey.transports
4743
+ }
4744
+ });
4745
+ if (!verification.verified) {
4746
+ res.status(400).json({ error: "Authentication verification failed" });
4747
+ return;
4748
+ }
4749
+ savePasskey2({
4750
+ ...passkey,
4751
+ counter: verification.authenticationInfo.newCounter
4752
+ });
4753
+ const tempToken = parseTempSessionToken(req);
4754
+ if (tempToken) {
4755
+ pendingTotpSessions.delete(tempToken);
4756
+ }
4757
+ const session = createBrowserSession();
4758
+ res.set("Set-Cookie", [session.setCookieHeader, clearTempSessionCookie()]);
4759
+ res.json({ authenticated: true });
4760
+ } catch (err) {
4761
+ logger2.error(`[auth] Passkey verification failed: ${err instanceof Error ? err.message : err}`);
4762
+ res.status(500).json({ error: "Verification failed" });
4763
+ }
4764
+ });
4765
+ app.get("/api/auth/passkeys", requireAuth, (_req, res) => {
4766
+ const passkeys = getPasskeys2();
4767
+ const result = passkeys.map((pk) => ({
4768
+ id: pk.id,
4769
+ name: pk.name,
4770
+ counter: pk.counter,
4771
+ registeredAt: pk.registeredAt,
4772
+ transports: pk.transports
4773
+ }));
4774
+ res.json(result);
4775
+ });
4776
+ app.delete("/api/auth/passkey/:id", requireAuth, (req, res) => {
4777
+ const { id } = req.params;
4778
+ deletePasskey2(id);
4779
+ res.json({ deleted: true });
4780
+ });
4781
+ app.get("/api/auth/session-password", requireAuth, (_req, res) => {
4782
+ const { getSessionPassword: getSessionPassword3 } = (init_auth_state(), __toCommonJS(auth_state_exports));
4783
+ const password = getSessionPassword3();
4784
+ res.json({ password });
4785
+ });
4786
+ app.post("/api/auth/session-password/rotate", requireAuth, (_req, res) => {
4787
+ const { rotateSessionPassword: rotateSessionPassword2 } = (init_auth_state(), __toCommonJS(auth_state_exports));
4788
+ const newPassword = rotateSessionPassword2();
4789
+ logger2.info(`[auth] Session password rotated`);
4790
+ res.json({ password: newPassword });
4791
+ });
4792
+ app.get("/api/config", requireAuth, (_req, res) => {
4793
+ res.json({ scrollbackLines: config2.SCROLLBACK_LINES });
4794
+ });
4795
+ app.get("/api/settings", requireAuth, (_req, res) => {
4796
+ res.json(config2);
4797
+ });
4798
+ app.post("/api/settings", requireAuth, (req, res) => {
4799
+ const { key, value } = req.body;
4800
+ const result = updateSetting2(key, value);
4801
+ if (!result.success) {
4802
+ res.status(400).json(result);
4803
+ return;
4804
+ }
4805
+ res.json(result);
4806
+ });
4807
+ app.use("/api/sessions", requireAuth, createTerminalRouter2());
4808
+ app.use(express.static(distPath));
4809
+ app.use((_req, res) => {
4810
+ res.sendFile(path.join(distPath, "index.html"));
4811
+ });
4812
+ httpServer.on("upgrade", (req, socket, head) => {
4813
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
4814
+ const pathname = url.pathname;
4815
+ if (pathname === "/sync") {
4816
+ if (!config2.AUTH_DISABLED) {
4817
+ const browserToken = parseBrowserSessionToken(req);
4818
+ if (!browserToken || !activeSessions.has(browserToken)) {
4819
+ socket.destroy();
4820
+ return;
4821
+ }
4822
+ }
4823
+ syncWss.handleUpgrade(req, socket, head, (ws) => {
4824
+ const clientId = url.searchParams.get("clientId") || "";
4825
+ const snapshot = getAllSessions2().map((s) => ({ id: s.id, label: s.label, createdAt: s.createdAt, cols: s.cols, rows: s.rows, inputLockClientId: s.inputLockClientId ?? null }));
4826
+ ws.send(JSON.stringify({ type: "snapshot", sessions: snapshot }));
4827
+ addSyncClient2(ws);
4828
+ onSyncClientConnect2(clientId);
4829
+ ws.on("close", () => onSyncClientDisconnect2(clientId));
4830
+ logger2.info(`[ws] Sync client connected: ${clientId}`);
4831
+ });
4832
+ return;
4833
+ }
4834
+ if (!pathname.startsWith("/terminal/")) {
4835
+ socket.destroy();
4836
+ return;
4837
+ }
4838
+ const sessionId = pathname.slice("/terminal/".length);
4839
+ if (!config2.AUTH_DISABLED) {
4840
+ const browserToken = parseBrowserSessionToken(req);
4841
+ if (!browserToken || !activeSessions.has(browserToken)) {
4842
+ socket.write("HTTP/1.1 401 Unauthorized\r\nContent-Type: text/plain\r\n\r\nUnauthorized");
4843
+ socket.destroy();
4844
+ return;
4845
+ }
4846
+ }
4847
+ const session = getSession2(sessionId);
4848
+ if (!session) {
4849
+ socket.write("HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n\r\nSession not found");
4850
+ socket.destroy();
4851
+ return;
4852
+ }
4853
+ wss.handleUpgrade(req, socket, head, (ws) => {
4854
+ const clientId = url.searchParams.get("clientId") ?? "";
4855
+ attachWebSocket2(sessionId, ws, clientId);
4856
+ logger2.info(`[ws] Client connected to session ${sessionId}`);
4857
+ });
4858
+ });
4859
+ httpServer.listen(PORT, HOST, () => {
4860
+ logger2.info(`[server] listening on http://${HOST}:${PORT}`);
4861
+ });
4862
+ process.on("SIGTERM", () => {
4863
+ logger2.info("[server] SIGTERM received, cleaning up...");
4864
+ cleanupAll2();
4865
+ try {
4866
+ unlinkSync3(PID_PATH);
4867
+ } catch {
4868
+ }
4869
+ process.exit(0);
4870
+ });
4871
+ process.on("SIGINT", () => {
4872
+ logger2.info("[server] SIGINT received, cleaning up...");
4873
+ cleanupAll2();
4874
+ try {
4875
+ unlinkSync3(PID_PATH);
4876
+ } catch {
4877
+ }
4878
+ process.exit(0);
4879
+ });