@withstudiocms/auth-kit 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ export declare const usernames: Set<string>;
2
+ export default usernames;
3
+ export declare const isReservedUsername: (name: string) => boolean;
@@ -0,0 +1,555 @@
1
+ const usernames = /* @__PURE__ */ new Set([
2
+ ".git",
3
+ ".htaccess",
4
+ ".htpasswd",
5
+ ".well-known",
6
+ "400",
7
+ "401",
8
+ "403",
9
+ "404",
10
+ "405",
11
+ "406",
12
+ "407",
13
+ "408",
14
+ "409",
15
+ "410",
16
+ "411",
17
+ "412",
18
+ "413",
19
+ "414",
20
+ "415",
21
+ "416",
22
+ "417",
23
+ "421",
24
+ "422",
25
+ "423",
26
+ "424",
27
+ "426",
28
+ "428",
29
+ "429",
30
+ "431",
31
+ "500",
32
+ "501",
33
+ "502",
34
+ "503",
35
+ "504",
36
+ "505",
37
+ "506",
38
+ "507",
39
+ "508",
40
+ "509",
41
+ "510",
42
+ "511",
43
+ "_domainkey",
44
+ "about",
45
+ "about-us",
46
+ "abuse",
47
+ "access",
48
+ "account",
49
+ "accounts",
50
+ "ad",
51
+ "add",
52
+ "admin",
53
+ "administration",
54
+ "administrator",
55
+ "ads",
56
+ "ads.txt",
57
+ "advertise",
58
+ "advertising",
59
+ "aes128-ctr",
60
+ "aes128-gcm",
61
+ "aes192-ctr",
62
+ "aes256-ctr",
63
+ "aes256-gcm",
64
+ "affiliate",
65
+ "affiliates",
66
+ "ajax",
67
+ "alert",
68
+ "alerts",
69
+ "alpha",
70
+ "amp",
71
+ "analytics",
72
+ "api",
73
+ "app",
74
+ "app-ads.txt",
75
+ "apps",
76
+ "asc",
77
+ "assets",
78
+ "atom",
79
+ "auth",
80
+ "authentication",
81
+ "authorize",
82
+ "autoconfig",
83
+ "autodiscover",
84
+ "avatar",
85
+ "backup",
86
+ "banner",
87
+ "banners",
88
+ "bbs",
89
+ "beta",
90
+ "billing",
91
+ "billings",
92
+ "blog",
93
+ "blogs",
94
+ "board",
95
+ "bookmark",
96
+ "bookmarks",
97
+ "broadcasthost",
98
+ "business",
99
+ "buy",
100
+ "cache",
101
+ "calendar",
102
+ "campaign",
103
+ "captcha",
104
+ "careers",
105
+ "cart",
106
+ "cas",
107
+ "categories",
108
+ "category",
109
+ "cdn",
110
+ "cgi",
111
+ "cgi-bin",
112
+ "chacha20-poly1305",
113
+ "change",
114
+ "channel",
115
+ "channels",
116
+ "chart",
117
+ "chat",
118
+ "checkout",
119
+ "clear",
120
+ "client",
121
+ "close",
122
+ "cloud",
123
+ "cms",
124
+ "com",
125
+ "comment",
126
+ "comments",
127
+ "community",
128
+ "compare",
129
+ "compose",
130
+ "config",
131
+ "connect",
132
+ "contact",
133
+ "contest",
134
+ "cookies",
135
+ "copy",
136
+ "copyright",
137
+ "count",
138
+ "cp",
139
+ "cpanel",
140
+ "create",
141
+ "crossdomain.xml",
142
+ "css",
143
+ "curve25519-sha256",
144
+ "customer",
145
+ "customers",
146
+ "customize",
147
+ "dashboard",
148
+ "db",
149
+ "deals",
150
+ "debug",
151
+ "delete",
152
+ "desc",
153
+ "destroy",
154
+ "dev",
155
+ "developer",
156
+ "developers",
157
+ "diffie-hellman-group-exchange-sha256",
158
+ "diffie-hellman-group14-sha1",
159
+ "disconnect",
160
+ "discuss",
161
+ "dns",
162
+ "dns0",
163
+ "dns1",
164
+ "dns2",
165
+ "dns3",
166
+ "dns4",
167
+ "docs",
168
+ "documentation",
169
+ "domain",
170
+ "download",
171
+ "downloads",
172
+ "downvote",
173
+ "draft",
174
+ "drop",
175
+ "ecdh-sha2-nistp256",
176
+ "ecdh-sha2-nistp384",
177
+ "ecdh-sha2-nistp521",
178
+ "edit",
179
+ "editor",
180
+ "email",
181
+ "enterprise",
182
+ "error",
183
+ "errors",
184
+ "event",
185
+ "events",
186
+ "example",
187
+ "exception",
188
+ "exit",
189
+ "explore",
190
+ "export",
191
+ "extensions",
192
+ "false",
193
+ "family",
194
+ "faq",
195
+ "faqs",
196
+ "favicon.ico",
197
+ "features",
198
+ "feed",
199
+ "feedback",
200
+ "feeds",
201
+ "file",
202
+ "files",
203
+ "filter",
204
+ "follow",
205
+ "follower",
206
+ "followers",
207
+ "following",
208
+ "fonts",
209
+ "forgot",
210
+ "forgot-password",
211
+ "forgotpassword",
212
+ "form",
213
+ "forms",
214
+ "forum",
215
+ "forums",
216
+ "friend",
217
+ "friends",
218
+ "ftp",
219
+ "get",
220
+ "git",
221
+ "go",
222
+ "graphql",
223
+ "group",
224
+ "groups",
225
+ "guest",
226
+ "guidelines",
227
+ "guides",
228
+ "head",
229
+ "header",
230
+ "help",
231
+ "hide",
232
+ "hmac-sha",
233
+ "hmac-sha1",
234
+ "hmac-sha1-etm",
235
+ "hmac-sha2-256",
236
+ "hmac-sha2-256-etm",
237
+ "hmac-sha2-512",
238
+ "hmac-sha2-512-etm",
239
+ "home",
240
+ "host",
241
+ "hosting",
242
+ "hostmaster",
243
+ "htpasswd",
244
+ "http",
245
+ "httpd",
246
+ "https",
247
+ "humans.txt",
248
+ "icons",
249
+ "images",
250
+ "imap",
251
+ "img",
252
+ "import",
253
+ "index",
254
+ "info",
255
+ "insert",
256
+ "investors",
257
+ "invitations",
258
+ "invite",
259
+ "invites",
260
+ "invoice",
261
+ "is",
262
+ "isatap",
263
+ "issues",
264
+ "it",
265
+ "jobs",
266
+ "join",
267
+ "js",
268
+ "json",
269
+ "keybase.txt",
270
+ "learn",
271
+ "legal",
272
+ "license",
273
+ "licensing",
274
+ "like",
275
+ "limit",
276
+ "live",
277
+ "load",
278
+ "local",
279
+ "localdomain",
280
+ "localhost",
281
+ "lock",
282
+ "login",
283
+ "logout",
284
+ "lost-password",
285
+ "m",
286
+ "mail",
287
+ "mail0",
288
+ "mail1",
289
+ "mail2",
290
+ "mail3",
291
+ "mail4",
292
+ "mail5",
293
+ "mail6",
294
+ "mail7",
295
+ "mail8",
296
+ "mail9",
297
+ "mailer-daemon",
298
+ "mailerdaemon",
299
+ "map",
300
+ "marketing",
301
+ "marketplace",
302
+ "master",
303
+ "me",
304
+ "media",
305
+ "member",
306
+ "members",
307
+ "message",
308
+ "messages",
309
+ "metrics",
310
+ "mis",
311
+ "mobile",
312
+ "moderator",
313
+ "modify",
314
+ "more",
315
+ "mx",
316
+ "mx1",
317
+ "my",
318
+ "net",
319
+ "network",
320
+ "new",
321
+ "news",
322
+ "newsletter",
323
+ "newsletters",
324
+ "next",
325
+ "nil",
326
+ "no-reply",
327
+ "nobody",
328
+ "noc",
329
+ "none",
330
+ "noreply",
331
+ "notification",
332
+ "notifications",
333
+ "ns",
334
+ "ns0",
335
+ "ns1",
336
+ "ns2",
337
+ "ns3",
338
+ "ns4",
339
+ "ns5",
340
+ "ns6",
341
+ "ns7",
342
+ "ns8",
343
+ "ns9",
344
+ "null",
345
+ "oauth",
346
+ "oauth2",
347
+ "offer",
348
+ "offers",
349
+ "online",
350
+ "openid",
351
+ "order",
352
+ "orders",
353
+ "overview",
354
+ "owa",
355
+ "owner",
356
+ "page",
357
+ "pages",
358
+ "partners",
359
+ "passwd",
360
+ "password",
361
+ "pay",
362
+ "payment",
363
+ "payments",
364
+ "paypal",
365
+ "photo",
366
+ "photos",
367
+ "pixel",
368
+ "plans",
369
+ "plugins",
370
+ "policies",
371
+ "policy",
372
+ "pop",
373
+ "pop3",
374
+ "popular",
375
+ "portal",
376
+ "portfolio",
377
+ "post",
378
+ "postfix",
379
+ "postmaster",
380
+ "poweruser",
381
+ "preferences",
382
+ "premium",
383
+ "press",
384
+ "previous",
385
+ "pricing",
386
+ "print",
387
+ "privacy",
388
+ "privacy-policy",
389
+ "private",
390
+ "prod",
391
+ "product",
392
+ "production",
393
+ "profile",
394
+ "profiles",
395
+ "project",
396
+ "projects",
397
+ "promo",
398
+ "public",
399
+ "purchase",
400
+ "put",
401
+ "quota",
402
+ "redirect",
403
+ "reduce",
404
+ "refund",
405
+ "refunds",
406
+ "register",
407
+ "registration",
408
+ "remove",
409
+ "replies",
410
+ "reply",
411
+ "report",
412
+ "request",
413
+ "request-password",
414
+ "reset",
415
+ "reset-password",
416
+ "response",
417
+ "return",
418
+ "returns",
419
+ "review",
420
+ "reviews",
421
+ "robots.txt",
422
+ "root",
423
+ "rootuser",
424
+ "rsa-sha2-2",
425
+ "rsa-sha2-512",
426
+ "rss",
427
+ "rules",
428
+ "sales",
429
+ "save",
430
+ "script",
431
+ "sdk",
432
+ "search",
433
+ "secure",
434
+ "security",
435
+ "select",
436
+ "services",
437
+ "session",
438
+ "sessions",
439
+ "settings",
440
+ "setup",
441
+ "share",
442
+ "shift",
443
+ "shop",
444
+ "signin",
445
+ "signup",
446
+ "site",
447
+ "sitemap",
448
+ "sites",
449
+ "smtp",
450
+ "sort",
451
+ "source",
452
+ "sql",
453
+ "ssh",
454
+ "ssh-rsa",
455
+ "ssl",
456
+ "ssladmin",
457
+ "ssladministrator",
458
+ "sslwebmaster",
459
+ "stage",
460
+ "staging",
461
+ "stat",
462
+ "static",
463
+ "statistics",
464
+ "stats",
465
+ "status",
466
+ "store",
467
+ "style",
468
+ "styles",
469
+ "stylesheet",
470
+ "stylesheets",
471
+ "subdomain",
472
+ "subscribe",
473
+ "sudo",
474
+ "super",
475
+ "superuser",
476
+ "support",
477
+ "survey",
478
+ "sync",
479
+ "sysadmin",
480
+ "sysadmin",
481
+ "system",
482
+ "tablet",
483
+ "tag",
484
+ "tags",
485
+ "team",
486
+ "telnet",
487
+ "terms",
488
+ "terms-of-use",
489
+ "test",
490
+ "testimonials",
491
+ "theme",
492
+ "themes",
493
+ "today",
494
+ "tools",
495
+ "topic",
496
+ "topics",
497
+ "tour",
498
+ "training",
499
+ "translate",
500
+ "translations",
501
+ "trending",
502
+ "trial",
503
+ "true",
504
+ "umac-128",
505
+ "umac-128-etm",
506
+ "umac-64",
507
+ "umac-64-etm",
508
+ "undefined",
509
+ "unfollow",
510
+ "unlike",
511
+ "unsubscribe",
512
+ "update",
513
+ "upgrade",
514
+ "usenet",
515
+ "user",
516
+ "username",
517
+ "users",
518
+ "uucp",
519
+ "var",
520
+ "verify",
521
+ "video",
522
+ "view",
523
+ "void",
524
+ "vote",
525
+ "vpn",
526
+ "webmail",
527
+ "webmaster",
528
+ "website",
529
+ "widget",
530
+ "widgets",
531
+ "wiki",
532
+ "wpad",
533
+ "write",
534
+ "www",
535
+ "www-data",
536
+ "www1",
537
+ "www2",
538
+ "www3",
539
+ "www4",
540
+ "you",
541
+ "yourname",
542
+ "yourusername",
543
+ "zlib"
544
+ ]);
545
+ var usernames_default = usernames;
546
+ const canonicalizeUsername = (name) => (name ?? "").trim().normalize("NFKC").toLowerCase();
547
+ const isReservedUsername = (name) => {
548
+ const normalized = canonicalizeUsername(name);
549
+ return usernames.has(normalized);
550
+ };
551
+ export {
552
+ usernames_default as default,
553
+ isReservedUsername,
554
+ usernames
555
+ };
@@ -0,0 +1,63 @@
1
+ import { Effect, Platform } from '@withstudiocms/effect';
2
+ import { PasswordError } from '../errors.js';
3
+ /**
4
+ * Compares two strings in constant time to prevent timing attacks.
5
+ *
6
+ * This function ensures that the comparison time is independent of the
7
+ * input strings' content, making it resistant to timing attacks that
8
+ * could reveal information about the strings.
9
+ *
10
+ * @param a - The first string to compare.
11
+ * @param b - The second string to compare.
12
+ * @returns `true` if the strings are equal, `false` otherwise.
13
+ * @private
14
+ */
15
+ export declare const constantTimeEqual: (a: string, b: string) => boolean;
16
+ /**
17
+ * The generation prefix for the secure password format.
18
+ * This is used to identify the version of the password hashing scheme.
19
+ */
20
+ export declare const PASS_GEN1_0_PREFIX = "gen1.0";
21
+ /**
22
+ * Builds a secure password hash from the generation, salt, and hash.
23
+ *
24
+ * The format of the secure password is: `gen1.0:salt:hash`.
25
+ * If any of the components are invalid, a PasswordError is thrown.
26
+ *
27
+ * @param generation - The generation identifier (e.g., 'gen1.0').
28
+ * @param salt - The salt used in the hashing process.
29
+ * @param hash - The hashed password.
30
+ * @returns A string representing the secure password.
31
+ */
32
+ export declare const buildSecurePassword: (args_0: {
33
+ generation: string;
34
+ salt: string;
35
+ hash: string;
36
+ }) => Effect.Effect.AsEffect<Effect.Effect<string, never, never>>;
37
+ /**
38
+ * Breaks down a secure password hash into its components.
39
+ *
40
+ * The hash is expected to be in the format: `gen1.0:salt:hash`.
41
+ * If the hash does not match this format, or if it uses an unsupported generation,
42
+ * a PasswordError is thrown.
43
+ *
44
+ * @param hash - The secure password hash to break down.
45
+ * @returns An object containing the generation, salt, and hash value.
46
+ */
47
+ export declare const breakSecurePassword: (hash: string) => Effect.Effect.AsEffect<Effect.Effect<{
48
+ generation: string;
49
+ salt: string;
50
+ hash: string;
51
+ }, PasswordError, never>>;
52
+ /**
53
+ * @private Internal function for the `verifyPasswordStrength` function
54
+ */
55
+ export declare const verifyPasswordLength: (pass: string) => Effect.Effect.AsEffect<Effect.Effect<"Password must be between 6 and 255 characters long." | undefined, PasswordError, never>>;
56
+ /**
57
+ * @private Internal function for the `verifyPasswordStrength` function
58
+ */
59
+ export declare const verifySafe: (pass: string) => Effect.Effect<string | undefined, import("../errors.js").CheckIfUnsafeError, never>;
60
+ /**
61
+ * @private Internal function for the `verifyPasswordStrength` function
62
+ */
63
+ export declare const checkPwnedDB: (pass: string) => Effect.Effect<string | undefined, Platform.HttpClientError.ResponseError, never>;
@@ -0,0 +1,91 @@
1
+ import { sha1 } from "@oslojs/crypto/sha1";
2
+ import { encodeHexLowerCase } from "@oslojs/encoding";
3
+ import { Effect, Platform } from "@withstudiocms/effect";
4
+ import { PasswordError, usePasswordError } from "../errors.js";
5
+ import { CheckIfUnsafe } from "./unsafeCheck.js";
6
+ const constantTimeEqual = (a, b) => {
7
+ const len = Math.max(a.length, b.length);
8
+ let result = a.length ^ b.length;
9
+ for (let i = 0; i < len; i++) {
10
+ const ca = i < a.length ? a.charCodeAt(i) : 0;
11
+ const cb = i < b.length ? b.charCodeAt(i) : 0;
12
+ result |= ca ^ cb;
13
+ }
14
+ return result === 0;
15
+ };
16
+ const PASS_GEN1_0_PREFIX = "gen1.0";
17
+ const buildSecurePassword = Effect.fn(
18
+ ({ generation, hash, salt }) => Effect.succeed(`${generation}:${salt}:${hash}`)
19
+ );
20
+ const breakSecurePassword = Effect.fn(
21
+ (hash) => usePasswordError(() => {
22
+ const parts = hash.split(":");
23
+ if (parts.length !== 3) {
24
+ throw new PasswordError({
25
+ cause: 'Invalid secure password format. Expected "gen1.0:salt:hash".'
26
+ });
27
+ }
28
+ const [generation, salt, hashValue] = parts;
29
+ if (generation !== PASS_GEN1_0_PREFIX) {
30
+ throw new PasswordError({
31
+ cause: "Legacy password hashes are not supported. Please reset any legacy passwords."
32
+ });
33
+ }
34
+ if (!salt || !hashValue) {
35
+ throw new PasswordError({
36
+ cause: "Invalid secure password format: missing salt or hash."
37
+ });
38
+ }
39
+ return { generation, salt, hash: hashValue };
40
+ })
41
+ );
42
+ const verifyPasswordLength = Effect.fn(
43
+ (pass) => usePasswordError(() => {
44
+ if (pass.length < 6 || pass.length > 255) {
45
+ return "Password must be between 6 and 255 characters long.";
46
+ }
47
+ return void 0;
48
+ })
49
+ );
50
+ const verifySafe = (pass) => Effect.gen(function* () {
51
+ const check = yield* CheckIfUnsafe;
52
+ const isUnsafe = yield* check.password(pass);
53
+ if (isUnsafe) {
54
+ return "Password must not be a commonly known unsafe password (admin, root, etc.)";
55
+ }
56
+ return void 0;
57
+ }).pipe(Effect.provide(CheckIfUnsafe.Default));
58
+ const checkPwnedDB = (pass) => Effect.gen(function* () {
59
+ const http = yield* Platform.HttpClient.HttpClient;
60
+ const encodedData = new TextEncoder().encode(pass);
61
+ const sha1Hash = sha1(encodedData);
62
+ const hashHex = encodeHexLowerCase(sha1Hash);
63
+ const hashPrefix = hashHex.slice(0, 5);
64
+ const response = yield* http.get(`https://api.pwnedpasswords.com/range/${hashPrefix}`).pipe(
65
+ Effect.catchTags({
66
+ RequestError: () => Effect.succeed({ text: Effect.succeed(""), status: 500 }),
67
+ ResponseError: () => Effect.succeed({ text: Effect.succeed(""), status: 500 })
68
+ })
69
+ );
70
+ if (response.status >= 400) {
71
+ return void 0;
72
+ }
73
+ const data = yield* response.text;
74
+ const lines = data.split("\n");
75
+ for (const line of lines) {
76
+ const hashSuffix = line.slice(0, 35).toLowerCase();
77
+ if (hashHex === hashPrefix + hashSuffix) {
78
+ return 'Password must not be in the <a href="https://haveibeenpwned.com/Passwords" target="_blank">pwned password database</a>.';
79
+ }
80
+ }
81
+ return void 0;
82
+ }).pipe(Effect.provide(Platform.FetchHttpClient.layer));
83
+ export {
84
+ PASS_GEN1_0_PREFIX,
85
+ breakSecurePassword,
86
+ buildSecurePassword,
87
+ checkPwnedDB,
88
+ constantTimeEqual,
89
+ verifyPasswordLength,
90
+ verifySafe
91
+ };