@arkstack/auth 0.4.3 → 0.5.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @arkstack/auth
2
2
 
3
- Authentication package for Arkstack applications.
3
+ Authentication module for Arkstack, providing core authentication and identity features.
4
4
 
5
5
  `@arkstack/auth` provides the framework-neutral auth service used by Arkstack runtime drivers. It supports credential verification, JWT-backed personal access tokens, temporary purpose-bound tokens, current-session lookup, two-factor authentication helpers, and auth-specific exceptions.
6
6
 
@@ -29,7 +29,10 @@ if (TwoFactor.verifyCode(user, setup.secret, code)) {
29
29
 
30
30
  await TwoFactor.setMethod(user.id, 'authenticator');
31
31
  await TwoFactor.setEnabledAt(user.id);
32
- await TwoFactor.writeRecoveryCodeHashes(user.id, await TwoFactor.hashBackupCodes(recoveryCodes));
32
+ await TwoFactor.writeRecoveryCodeHashes(
33
+ user.id,
34
+ await TwoFactor.hashBackupCodes(recoveryCodes),
35
+ );
33
36
  }
34
37
  ```
35
38
 
package/dist/index.d.ts CHANGED
@@ -1,64 +1,8 @@
1
1
  /// <reference path="./app.d.ts" />
2
2
  import { Exception } from "@arkstack/common";
3
3
  import { Request, RequestSource, Response, ResponseSource } from "@arkstack/http";
4
- import { Model } from "arkormx";
5
- import { BelongsToRelation, HasManyRelation, HasOneRelation } from "arkormx/relationship";
6
- import { UserNotification } from "@arkstack/notifications";
7
- import * as otpauth from "otpauth";
4
+ import { Model } from "@arkstack/database";
8
5
 
9
- //#region src/types/TwoFactor.d.ts
10
- type TwoFactorMethod = 'authenticator' | 'sms';
11
- type SmsCodePurpose = 'setup' | 'login';
12
- type TwoFactorSetup = {
13
- secret: string;
14
- otpauthUrl: string;
15
- };
16
- type TwoFactorStatus = {
17
- enabled: boolean;
18
- enabledAt: string | null;
19
- method: TwoFactorMethod | null;
20
- recoveryCodesRemaining: number;
21
- };
22
- type IssuedSmsCode = {
23
- code: string;
24
- expiresAt: Date;
25
- purpose: SmsCodePurpose;
26
- };
27
- //#endregion
28
- //#region src/Contracts/UserTwoFactor.d.ts
29
- declare abstract class UserTwoFactor extends Model {
30
- id: number | string;
31
- userId: User['id'];
32
- method: TwoFactorMethod | null;
33
- secretCiphertext: string | null;
34
- smsCodeHash: string | null;
35
- smsCodeExpiresAt: Date | null;
36
- smsCodePurpose: SmsCodePurpose | null;
37
- enabledAt: Date | null;
38
- recoveryCodeHashes: string[] | null;
39
- createdAt: Date;
40
- updatedAt: Date;
41
- protected static table?: string | undefined;
42
- protected casts: {
43
- readonly recoveryCodeHashes: "json";
44
- };
45
- abstract user(): BelongsToRelation<this, User>;
46
- }
47
- //#endregion
48
- //#region src/Contracts/User.d.ts
49
- declare abstract class User extends Model {
50
- id: number;
51
- email: string;
52
- name: string;
53
- password: string;
54
- createdAt: Date;
55
- updatedAt: Date;
56
- protected static table?: string | undefined;
57
- abstract personalAccessTokens(): HasManyRelation<this, PersonalAccessToken>;
58
- abstract twoFactor(): HasOneRelation<this, UserTwoFactor>;
59
- abstract notifications(): HasManyRelation<this, UserNotification>;
60
- }
61
- //#endregion
62
6
  //#region src/Contracts/PersonalAccessToken.d.ts
63
7
  declare abstract class PersonalAccessToken extends Model {
64
8
  id: number;
@@ -70,7 +14,6 @@ declare abstract class PersonalAccessToken extends Model {
70
14
  expiresAt: Date | null;
71
15
  lastUsedAt: Date | null;
72
16
  deviceInfo: Record<string, unknown> | null;
73
- abstract user(): BelongsToRelation<this, User>;
74
17
  }
75
18
  //#endregion
76
19
  //#region src/CurrentSession.d.ts
@@ -81,6 +24,17 @@ declare class CurrentSession {
81
24
  token(): Promise<PersonalAccessToken | null>;
82
25
  }
83
26
  //#endregion
27
+ //#region src/Contracts/User.d.ts
28
+ declare abstract class User extends Model {
29
+ id: number;
30
+ email: string;
31
+ name: string;
32
+ password: string;
33
+ createdAt: Date;
34
+ updatedAt: Date;
35
+ protected static table?: string | undefined;
36
+ }
37
+ //#endregion
84
38
  //#region src/Contracts/AuthContract.d.ts
85
39
  declare abstract class AuthContract {
86
40
  abstract setRequest(req: Request<User> | RequestSource<User>): this;
@@ -166,6 +120,348 @@ declare class SessionDevice {
166
120
  private static detectDeviceType;
167
121
  }
168
122
  //#endregion
123
+ //#region ../../node_modules/.pnpm/otpauth@9.5.1/node_modules/otpauth/dist/otpauth.d.ts
124
+ /**
125
+ * OTP secret key.
126
+ */
127
+ declare class Secret {
128
+ /**
129
+ * Converts a Latin-1 string to a Secret object.
130
+ * @param {string} str Latin-1 string.
131
+ * @returns {Secret} Secret object.
132
+ */
133
+ static fromLatin1(str: string): Secret;
134
+ /**
135
+ * Converts an UTF-8 string to a Secret object.
136
+ * @param {string} str UTF-8 string.
137
+ * @returns {Secret} Secret object.
138
+ */
139
+ static fromUTF8(str: string): Secret;
140
+ /**
141
+ * Converts a base32 string to a Secret object.
142
+ * @param {string} str Base32 string.
143
+ * @returns {Secret} Secret object.
144
+ */
145
+ static fromBase32(str: string): Secret;
146
+ /**
147
+ * Converts a hexadecimal string to a Secret object.
148
+ * @param {string} str Hexadecimal string.
149
+ * @returns {Secret} Secret object.
150
+ */
151
+ static fromHex(str: string): Secret;
152
+ /**
153
+ * Creates a secret key object.
154
+ * @param {Object} [config] Configuration options.
155
+ * @param {ArrayBufferLike} [config.buffer] Secret key buffer.
156
+ * @param {number} [config.size=20] Number of random bytes to generate, ignored if 'buffer' is provided.
157
+ */
158
+ constructor({
159
+ buffer,
160
+ size
161
+ }?: {
162
+ buffer?: ArrayBufferLike | undefined;
163
+ size?: number | undefined;
164
+ });
165
+ /**
166
+ * Secret key.
167
+ * @type {Uint8Array}
168
+ * @readonly
169
+ */
170
+ readonly bytes: Uint8Array;
171
+ /**
172
+ * Secret key buffer.
173
+ * @deprecated For backward compatibility, the "bytes" property should be used instead.
174
+ * @type {ArrayBufferLike}
175
+ */
176
+ get buffer(): ArrayBufferLike;
177
+ /**
178
+ * Latin-1 string representation of secret key.
179
+ * @type {string}
180
+ */
181
+ get latin1(): string;
182
+ /**
183
+ * UTF-8 string representation of secret key.
184
+ * @type {string}
185
+ */
186
+ get utf8(): string;
187
+ /**
188
+ * Base32 string representation of secret key.
189
+ * @type {string}
190
+ */
191
+ get base32(): string;
192
+ /**
193
+ * Hexadecimal string representation of secret key.
194
+ * @type {string}
195
+ */
196
+ get hex(): string;
197
+ }
198
+ /**
199
+ * HOTP: An HMAC-based One-time Password Algorithm.
200
+ * @see [RFC 4226](https://datatracker.ietf.org/doc/html/rfc4226)
201
+ */
202
+ /**
203
+ * TOTP: Time-Based One-Time Password Algorithm.
204
+ * @see [RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238)
205
+ */
206
+ declare class TOTP {
207
+ /**
208
+ * Default configuration.
209
+ * @type {{
210
+ * issuer: string,
211
+ * label: string,
212
+ * issuerInLabel: boolean,
213
+ * algorithm: string,
214
+ * digits: number,
215
+ * period: number
216
+ * window: number
217
+ * }}
218
+ */
219
+ static get defaults(): {
220
+ issuer: string;
221
+ label: string;
222
+ issuerInLabel: boolean;
223
+ algorithm: string;
224
+ digits: number;
225
+ period: number;
226
+ window: number;
227
+ };
228
+ /**
229
+ * Calculates the counter. i.e. the number of periods since timestamp 0.
230
+ * @param {Object} [config] Configuration options.
231
+ * @param {number} [config.period=30] Token time-step duration.
232
+ * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
233
+ * @returns {number} Counter.
234
+ */
235
+ static counter({
236
+ period,
237
+ timestamp
238
+ }?: {
239
+ period?: number | undefined;
240
+ timestamp?: number | undefined;
241
+ }): number;
242
+ /**
243
+ * Calculates the remaining time in milliseconds until the next token is generated.
244
+ * @param {Object} [config] Configuration options.
245
+ * @param {number} [config.period=30] Token time-step duration.
246
+ * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
247
+ * @returns {number} counter.
248
+ */
249
+ static remaining({
250
+ period,
251
+ timestamp
252
+ }?: {
253
+ period?: number | undefined;
254
+ timestamp?: number | undefined;
255
+ }): number;
256
+ /**
257
+ * Generates a TOTP token.
258
+ * @param {Object} config Configuration options.
259
+ * @param {Secret} config.secret Secret key.
260
+ * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
261
+ * @param {number} [config.digits=6] Token length.
262
+ * @param {number} [config.period=30] Token time-step duration.
263
+ * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
264
+ * @param {(algorithm: string, key: Uint8Array, message: Uint8Array) => Uint8Array} [config.hmac] Custom HMAC function.
265
+ * @returns {string} Token.
266
+ */
267
+ static generate({
268
+ secret,
269
+ algorithm,
270
+ digits,
271
+ period,
272
+ timestamp,
273
+ hmac
274
+ }: {
275
+ secret: Secret;
276
+ algorithm?: string | undefined;
277
+ digits?: number | undefined;
278
+ period?: number | undefined;
279
+ timestamp?: number | undefined;
280
+ hmac?: ((algorithm: string, key: Uint8Array, message: Uint8Array) => Uint8Array) | undefined;
281
+ }): string;
282
+ /**
283
+ * Validates a TOTP token.
284
+ * @param {Object} config Configuration options.
285
+ * @param {string} config.token Token value.
286
+ * @param {Secret} config.secret Secret key.
287
+ * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
288
+ * @param {number} [config.digits=6] Token length.
289
+ * @param {number} [config.period=30] Token time-step duration.
290
+ * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
291
+ * @param {number} [config.window=1] Window of counter values to test.
292
+ * @param {(algorithm: string, key: Uint8Array, message: Uint8Array) => Uint8Array} [config.hmac] Custom HMAC function.
293
+ * @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid.
294
+ */
295
+ static validate({
296
+ token,
297
+ secret,
298
+ algorithm,
299
+ digits,
300
+ period,
301
+ timestamp,
302
+ window,
303
+ hmac
304
+ }: {
305
+ token: string;
306
+ secret: Secret;
307
+ algorithm?: string | undefined;
308
+ digits?: number | undefined;
309
+ period?: number | undefined;
310
+ timestamp?: number | undefined;
311
+ window?: number | undefined;
312
+ hmac?: ((algorithm: string, key: Uint8Array, message: Uint8Array) => Uint8Array) | undefined;
313
+ }): number | null;
314
+ /**
315
+ * Creates a TOTP object.
316
+ * @param {Object} [config] Configuration options.
317
+ * @param {string} [config.issuer=''] Account provider.
318
+ * @param {string} [config.label='OTPAuth'] Account label.
319
+ * @param {boolean} [config.issuerInLabel=true] Include issuer prefix in label.
320
+ * @param {Secret|string} [config.secret=Secret] Secret key.
321
+ * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
322
+ * @param {number} [config.digits=6] Token length.
323
+ * @param {number} [config.period=30] Token time-step duration.
324
+ * @param {(algorithm: string, key: Uint8Array, message: Uint8Array) => Uint8Array} [config.hmac] Custom HMAC function.
325
+ */
326
+ constructor({
327
+ issuer,
328
+ label,
329
+ issuerInLabel,
330
+ secret,
331
+ algorithm,
332
+ digits,
333
+ period,
334
+ hmac
335
+ }?: {
336
+ issuer?: string | undefined;
337
+ label?: string | undefined;
338
+ issuerInLabel?: boolean | undefined;
339
+ secret?: string | Secret | undefined;
340
+ algorithm?: string | undefined;
341
+ digits?: number | undefined;
342
+ period?: number | undefined;
343
+ hmac?: ((algorithm: string, key: Uint8Array, message: Uint8Array) => Uint8Array) | undefined;
344
+ });
345
+ /**
346
+ * Account provider.
347
+ * @type {string}
348
+ */
349
+ issuer: string;
350
+ /**
351
+ * Account label.
352
+ * @type {string}
353
+ */
354
+ label: string;
355
+ /**
356
+ * Include issuer prefix in label.
357
+ * @type {boolean}
358
+ */
359
+ issuerInLabel: boolean;
360
+ /**
361
+ * Secret key.
362
+ * @type {Secret}
363
+ */
364
+ secret: Secret;
365
+ /**
366
+ * HMAC hashing algorithm.
367
+ * @type {string}
368
+ */
369
+ algorithm: string;
370
+ /**
371
+ * Token length.
372
+ * @type {number}
373
+ */
374
+ digits: number;
375
+ /**
376
+ * Token time-step duration.
377
+ * @type {number}
378
+ */
379
+ period: number;
380
+ /**
381
+ * Custom HMAC function.
382
+ * @type {((algorithm: string, key: Uint8Array, message: Uint8Array) => Uint8Array)|undefined}
383
+ */
384
+ hmac: ((algorithm: string, key: Uint8Array, message: Uint8Array) => Uint8Array) | undefined;
385
+ /**
386
+ * Calculates the counter. i.e. the number of periods since timestamp 0.
387
+ * @param {Object} [config] Configuration options.
388
+ * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
389
+ * @returns {number} Counter.
390
+ */
391
+ counter({
392
+ timestamp
393
+ }?: {
394
+ timestamp?: number | undefined;
395
+ }): number;
396
+ /**
397
+ * Calculates the remaining time in milliseconds until the next token is generated.
398
+ * @param {Object} [config] Configuration options.
399
+ * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
400
+ * @returns {number} counter.
401
+ */
402
+ remaining({
403
+ timestamp
404
+ }?: {
405
+ timestamp?: number | undefined;
406
+ }): number;
407
+ /**
408
+ * Generates a TOTP token.
409
+ * @param {Object} [config] Configuration options.
410
+ * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
411
+ * @returns {string} Token.
412
+ */
413
+ generate({
414
+ timestamp
415
+ }?: {
416
+ timestamp?: number | undefined;
417
+ }): string;
418
+ /**
419
+ * Validates a TOTP token.
420
+ * @param {Object} config Configuration options.
421
+ * @param {string} config.token Token value.
422
+ * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
423
+ * @param {number} [config.window=1] Window of counter values to test.
424
+ * @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid.
425
+ */
426
+ validate({
427
+ token,
428
+ timestamp,
429
+ window
430
+ }: {
431
+ token: string;
432
+ timestamp?: number | undefined;
433
+ window?: number | undefined;
434
+ }): number | null;
435
+ /**
436
+ * Returns a Google Authenticator key URI.
437
+ * @returns {string} URI.
438
+ */
439
+ toString(): string;
440
+ }
441
+ /**
442
+ * HOTP/TOTP object/string conversion.
443
+ * @see [Key URI Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
444
+ */
445
+ //#endregion
446
+ //#region src/types/TwoFactor.d.ts
447
+ type TwoFactorMethod = 'authenticator' | 'sms';
448
+ type SmsCodePurpose = 'setup' | 'login';
449
+ type TwoFactorSetup = {
450
+ secret: string;
451
+ otpauthUrl: string;
452
+ };
453
+ type TwoFactorStatus = {
454
+ enabled: boolean;
455
+ enabledAt: string | null;
456
+ method: TwoFactorMethod | null;
457
+ recoveryCodesRemaining: number;
458
+ };
459
+ type IssuedSmsCode = {
460
+ code: string;
461
+ expiresAt: Date;
462
+ purpose: SmsCodePurpose;
463
+ };
464
+ //#endregion
169
465
  //#region src/TwoFactor.d.ts
170
466
  declare class TwoFactor {
171
467
  private static getModel;
@@ -174,7 +470,7 @@ declare class TwoFactor {
174
470
  static normalizeMethod(method?: string | null): TwoFactorMethod | null;
175
471
  static maskPhone(phone?: string | null): string | null;
176
472
  static getLabel(user: User): string;
177
- static getTotp(user: User, secret: string): otpauth.TOTP;
473
+ static getTotp(user: User, secret: string): TOTP;
178
474
  static generateSecret(size?: number): string;
179
475
  static createSetup(user: User, secret?: string): TwoFactorSetup;
180
476
  static verifyCode(user: User, secret: string, code: string): boolean;
@@ -198,6 +494,25 @@ declare class TwoFactor {
198
494
  static verifySmsCode(userId: User['id'], code: string, purpose: SmsCodePurpose): Promise<boolean>;
199
495
  }
200
496
  //#endregion
497
+ //#region src/Contracts/UserTwoFactor.d.ts
498
+ declare abstract class UserTwoFactor extends Model {
499
+ id: number | string;
500
+ userId: User['id'];
501
+ method: TwoFactorMethod | null;
502
+ secretCiphertext: string | null;
503
+ smsCodeHash: string | null;
504
+ smsCodeExpiresAt: Date | null;
505
+ smsCodePurpose: SmsCodePurpose | null;
506
+ enabledAt: Date | null;
507
+ recoveryCodeHashes: string[] | null;
508
+ createdAt: Date;
509
+ updatedAt: Date;
510
+ protected static table?: string | undefined;
511
+ protected casts: {
512
+ readonly recoveryCodeHashes: "json";
513
+ };
514
+ }
515
+ //#endregion
201
516
  //#region src/Exceptions/AuthenticationException.d.ts
202
517
  declare class AuthenticationException extends Exception {
203
518
  #private;
package/dist/index.js CHANGED
@@ -3,8 +3,7 @@ import { SignJWT, jwtVerify } from "jose";
3
3
  import { Request, Response } from "@arkstack/http";
4
4
  import { UAParser } from "ua-parser-js";
5
5
  import { randomBytes } from "node:crypto";
6
- import { Model } from "arkormx";
7
-
6
+ import { Model } from "@arkstack/database";
8
7
  //#region src/Contracts/AuthContract.ts
9
8
  /**
10
9
  * The Auth class provides methods for user authentication, including verifying
@@ -13,7 +12,6 @@ import { Model } from "arkormx";
13
12
  * @author Legacy (3m1n3nc3)
14
13
  */
15
14
  var AuthContract = class {};
16
-
17
15
  //#endregion
18
16
  //#region src/Exceptions/AuthenticationException.ts
19
17
  var AuthenticationException = class extends Exception {
@@ -38,7 +36,6 @@ var AuthenticationException = class extends Exception {
38
36
  return this.#errors;
39
37
  }
40
38
  };
41
-
42
39
  //#endregion
43
40
  //#region src/CurrentSession.ts
44
41
  /**
@@ -53,6 +50,7 @@ var AuthenticationException = class extends Exception {
53
50
  * @see Auth
54
51
  */
55
52
  var CurrentSession = class {
53
+ auth;
56
54
  constructor(auth) {
57
55
  this.auth = auth;
58
56
  }
@@ -76,7 +74,6 @@ var CurrentSession = class {
76
74
  return await (await getModel("PersonalAccessToken")).query().where({ token }).first();
77
75
  }
78
76
  };
79
-
80
77
  //#endregion
81
78
  //#region src/SessionDevice.ts
82
79
  var SessionDevice = class {
@@ -266,7 +263,6 @@ var SessionDevice = class {
266
263
  return "desktop";
267
264
  }
268
265
  };
269
-
270
266
  //#endregion
271
267
  //#region src/Auth.ts
272
268
  /**
@@ -579,7 +575,6 @@ var Auth = class Auth extends AuthContract {
579
575
  if (payload.name !== void 0) pat.name = payload.name;
580
576
  }
581
577
  };
582
-
583
578
  //#endregion
584
579
  //#region src/TwoFactor.ts
585
580
  const secretAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
@@ -727,24 +722,21 @@ var TwoFactor = class {
727
722
  return isValid;
728
723
  }
729
724
  };
730
-
731
725
  //#endregion
732
726
  //#region src/Contracts/PersonalAccessToken.ts
733
727
  var PersonalAccessToken = class extends Model {};
734
-
735
728
  //#endregion
736
729
  //#region src/Contracts/User.ts
737
730
  var User = class extends Model {
738
731
  static table = "users";
739
732
  };
740
-
741
733
  //#endregion
742
734
  //#region src/Contracts/UserTwoFactor.ts
743
735
  var UserTwoFactor = class extends Model {
744
736
  static table = "user_two_factors";
745
737
  casts = { recoveryCodeHashes: "json" };
746
738
  };
747
-
748
739
  //#endregion
749
740
  export { Auth, AuthContract, AuthenticationException, CurrentSession, PersonalAccessToken, SessionDevice, TwoFactor, User, UserTwoFactor };
741
+
750
742
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["#request","#response","#errors","#user"],"sources":["../src/Contracts/AuthContract.ts","../src/Exceptions/AuthenticationException.ts","../src/CurrentSession.ts","../src/SessionDevice.ts","../src/Auth.ts","../src/TwoFactor.ts","../src/Contracts/PersonalAccessToken.ts","../src/Contracts/User.ts","../src/Contracts/UserTwoFactor.ts"],"sourcesContent":["import { CurrentSession } from '../CurrentSession'\nimport { PersonalAccessToken } from './PersonalAccessToken'\nimport { Request, type RequestSource } from '@arkstack/http'\nimport { User } from './User'\n\n/**\n * The Auth class provides methods for user authentication, including verifying \n * credentials, logging in, logging out, and managing personal access tokens. \n * \n * @author Legacy (3m1n3nc3)\n */\nexport abstract class AuthContract {\n /**\n * Set the current HTTP request instance being processed.\n * \n * @param req The HTTP request instance to be set.\n * @returns The Auth instance itself for method chaining.\n */\n abstract setRequest (req: Request<User> | RequestSource<User>): this\n\n /**\n * Get the current HTTP request instance being processed, which may contain\n * user information and other request-specific data relevant to authentication operations.\n * \n * @returns The current HTTP request instance or undefined if not set.\n */\n abstract getRequest (): Request<User> | undefined\n\n /**\n * Get the currently authenticated user\n * \n * @returns The currently authenticated user or null if not authenticated.\n */\n abstract user (): User | null\n\n /**\n * Verify user credentials\n * \n * @param email The email address of the user.\n * @param password The password of the user.\n * @returns A boolean indicating whether the credentials are valid.\n */\n abstract verify (email: string, password: string): Promise<boolean>\n\n /**\n * Attempt to authenticate a user with the given email and password.\n * \n * @param email \n * @param password \n * @returns \n */\n abstract attempt (email: string, password: string): Promise<User>\n\n /**\n * Login a user and create a personal access token\n * \n * @param email \n * @param password \n * @returns \n */\n abstract login (email: string, password: string): Promise<PersonalAccessToken>\n\n /**\n * Create a temporary token for a user with a specific purpose, such as\n * two-factor authentication.\n * \n * @param user \n * @param purpose \n * @param expiresIn \n * @returns \n */\n abstract createTemporaryToken (user: User, purpose: string, expiresIn?: string): Promise<string>\n\n /**\n * Authorize a temporary token and return the associated user if the token is \n * valid and matches the expected purpose.\n * \n * @param token \n * @param purpose \n * @returns \n */\n abstract authorizeTemporaryToken (token: string, purpose: string): Promise<User>\n\n /**\n * Logout the currently authenticated user and delete all their personal access tokens\n * \n * @param token \n * @returns \n */\n abstract logout (token?: string | PersonalAccessToken): Promise<void>\n\n /**\n * Check if the user is authenticated\n * \n * @returns \n */\n abstract check (): Promise<boolean>\n\n /**\n * Get the current session's personal access token\n * \n * @returns \n */\n abstract currentSession (): CurrentSession\n\n /**\n * Create a personal access token for a user\n * \n * @param user \n * @returns \n */\n abstract create (user: User): Promise<PersonalAccessToken>\n\n /**\n * Authorize a token and return the associated user\n * \n * @param token \n * @returns \n */\n abstract authorizeToken (token: string): Promise<User>\n}\n","import { Request, Response, type RequestSource, type ResponseSource } from '@arkstack/http'\n\nimport { Exception } from '@arkstack/common'\n\nexport class AuthenticationException extends Exception {\n #errors?: Record<string, any>\n #request?: Request\n #response?: Response\n statusCode: number = 401\n name: string\n\n constructor(\n message: string = 'Authentication failed',\n ctx?: {\n req?: Request | RequestSource,\n res?: Response | ResponseSource,\n status?: number,\n errors?: Record<string, any>\n }\n ) {\n super(message)\n this.name = 'AuthenticationException'\n this.statusCode = ctx?.status ?? 401\n if (ctx) {\n this.#request = Request.from(ctx.req)\n this.#response = Response.from(ctx.res)\n this.#errors = ctx.errors\n }\n\n void this.#response\n void this.#request\n }\n\n errors (): Record<string, any> | undefined {\n return this.#errors\n }\n}\n","import { AuthContract } from './Contracts/AuthContract'\nimport { PersonalAccessToken } from './Contracts/PersonalAccessToken'\nimport { getModel } from '@arkstack/common'\n\n/**\n * The CurrentSession class represents the current authentication session and provides \n * methods to manage it, such as destroying the session (logging out) and retrieving \n * the current personal access token. It is used internally by the Auth class to \n * handle session-specific operations.\n * \n * @author Legacy (3m1n3nc3)\n * @since 1.0.0\n * @version 1.0.0\n * @see Auth\n */\nexport class CurrentSession {\n constructor(private auth: AuthContract) { }\n\n /**\n * Destroy the current session's personal access token, effectively \n * logging out the user from this session.\n */\n async destroy () {\n const pat = await this.token()\n\n if (pat) {\n await this.auth.logout(pat)\n }\n }\n\n /**\n * Get the current session's personal access token\n * \n * @returns \n */\n async token (): Promise<PersonalAccessToken | null> {\n if (!this.auth.getRequest()) {\n return null\n }\n\n const token = this.auth.getRequest()!.bearerToken()\n\n if (!token) {\n return null\n }\n\n const Model = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n const pat = await Model.query().where({ token }).first()\n\n return pat\n }\n}\n","import { type Request } from '@arkstack/http'\nimport { AuthAgentPayload, SessionDeviceInfo } from './types/Session'\nimport { UAParser } from 'ua-parser-js'\n\nexport class SessionDevice {\n private static readonly uniqueIdentityFields = [\n 'deviceName',\n 'manufacturer',\n 'model',\n 'platform',\n 'os',\n 'osVersion',\n 'browser',\n 'deviceType',\n ] as const\n\n /**\n * Extracts device information from the incoming request to build a SessionDeviceInfo object.\n * \n * @param req The incoming HTTP request object.\n * @returns A SessionDeviceInfo object containing information about the client's device.\n */\n static fromRequest (req?: Request): SessionDeviceInfo {\n const userAgent = this.readUserAgent(req)\n const ua = new UAParser(userAgent ?? undefined).getResult()\n const ca = this.readAuthAgent(req)\n\n return {\n browser: this.readString(ua.browser.name) ?? this.detectBrowser(userAgent),\n os: ca?.os ?? this.readString(ua.os.name) ?? this.detectOs(userAgent),\n osVersion: ca?.osVersion ?? this.readString(ua.os.version),\n deviceType: ca?.deviceType ?? this.normalizeDeviceType(ua.device.type) ?? this.detectDeviceType(userAgent),\n deviceName: ca?.deviceName ?? null,\n manufacturer: ca?.manufacturer ?? this.readString(ua.device.vendor),\n model: ca?.model ?? this.readString(ua.device.model),\n platform: ca?.platform ?? null,\n ipAddress: this.detectIpAddress(req),\n userAgent,\n }\n }\n\n /**\n * Generates a human-readable display name for the device based on available information.\n * \n * @param deviceInfo A record containing device information.\n * @returns A string representing the display name of the device.\n */\n static getDisplayName (deviceInfo?: Record<string, unknown> | null) {\n const deviceName = this.readString(deviceInfo?.deviceName)\n const manufacturer = this.readString(deviceInfo?.manufacturer)\n const model = this.readString(deviceInfo?.model)\n const browser = this.readString(deviceInfo?.browser)\n const os = this.readString(deviceInfo?.os)\n const deviceType = this.readString(deviceInfo?.deviceType)\n\n if (manufacturer && model) {\n return model.startsWith(manufacturer) ? model : `${manufacturer} ${model}`\n }\n\n if (model) {\n return model\n }\n\n if (deviceName) {\n return deviceName\n }\n\n if (browser && os) {\n return `${browser} on ${os}`\n }\n\n if (os && deviceType && deviceType !== 'unknown') {\n return `${os} ${deviceType}`\n }\n\n if (browser) {\n return browser\n }\n\n return 'Unknown device'\n }\n\n /**\n * Builds a stable device key for matching previously issued sessions to the\n * current request device.\n *\n * @param deviceInfo A record containing device information.\n * @returns A normalized device key or null when there is not enough signal.\n */\n static getUniqueKey (deviceInfo?: Record<string, unknown> | null) {\n const parts = this.uniqueIdentityFields\n .map((field) => [field, this.readString(deviceInfo?.[field])?.toLowerCase()] as const)\n .filter(([, value]) => !!value)\n\n if (parts.length < 2) {\n const fallbackUserAgent = this.readString(deviceInfo?.userAgent)?.toLowerCase()\n\n return fallbackUserAgent ?? null\n }\n\n return parts.map(([field, value]) => `${field}:${value}`).join('|')\n }\n\n /**\n * Determines whether two device payloads represent the same device.\n *\n * @param left The first device payload.\n * @param right The second device payload.\n * @returns True when both payloads resolve to the same device key.\n */\n static matches (left?: Record<string, unknown> | null, right?: Record<string, unknown> | null) {\n const leftKey = this.getUniqueKey(left)\n const rightKey = this.getUniqueKey(right)\n\n if (!leftKey || !rightKey) {\n return false\n }\n\n return leftKey === rightKey\n }\n\n /**\n * Safely reads the user agent string from the request headers.\n * \n * @param req \n * @returns \n */\n private static readUserAgent (req?: Request) {\n const userAgent = req?.header('user-agent')\n\n return typeof userAgent === 'string' ? userAgent : null\n }\n\n /**\n * Safely reads a string value, ensuring it's a non-empty string or returns null.\n * \n * @param value \n * @returns \n */\n private static readString (value: unknown) {\n return typeof value === 'string' && value.length > 0 ? value : null\n }\n\n /**\n * Reads a specific device-related header from the request\n * \n * @param req \n * @param headerName \n * @returns \n */\n private static readAuthAgent (req?: Request): AuthAgentPayload | null {\n const value = req?.header('x-auth-agent')\n\n if (typeof value !== 'string' || value.length < 1) {\n return null\n }\n\n try {\n const parsed: AuthAgentPayload = JSON.parse(value)\n\n return {\n deviceName: this.readString(parsed.deviceName) ?? undefined,\n manufacturer: this.readString(parsed.manufacturer) ?? undefined,\n model: this.readString(parsed.model) ?? undefined,\n platform: this.readString(parsed.platform) ?? undefined,\n os: this.readString(parsed.os) ?? undefined,\n osVersion: this.readString(parsed.osVersion) ?? undefined,\n deviceType: this.normalizeDeviceType(parsed.deviceType) ?? undefined,\n }\n } catch {\n return null\n }\n }\n\n private static normalizeDeviceType (value: unknown): SessionDeviceInfo['deviceType'] | null {\n if (value === 'mobile' || value === 'tablet' || value === 'desktop' || value === 'bot' || value === 'unknown') {\n return value\n }\n\n return null\n }\n\n /**\n * Detects the client's IP address from the request, considering common headers set by proxies.\n * \n * @param req \n * @returns \n */\n private static detectIpAddress (req?: Request) {\n const forwarded = req?.header('x-forwarded-for')\n\n if (typeof forwarded === 'string' && forwarded.length > 0) {\n return forwarded.split(',')[0].trim()\n }\n\n return req?.ip ?? null\n }\n\n /**\n * Detects the browser from the user agent string.\n * \n * @param userAgent \n * @returns \n */\n private static detectBrowser (userAgent: string | null) {\n if (!userAgent) return null\n if (/Edg\\//i.test(userAgent)) return 'Edge'\n if (/OPR\\//i.test(userAgent)) return 'Opera'\n if (/SamsungBrowser\\//i.test(userAgent)) return 'Samsung Internet'\n if (/Chrome\\//i.test(userAgent) && !/Edg\\//i.test(userAgent)) return 'Chrome'\n if (/Firefox\\//i.test(userAgent)) return 'Firefox'\n if (/Safari\\//i.test(userAgent) && !/Chrome\\//i.test(userAgent)) return 'Safari'\n if (/PostmanRuntime\\//i.test(userAgent)) return 'Postman'\n if (/okhttp\\//i.test(userAgent)) return 'OkHttp'\n if (/curl\\//i.test(userAgent)) return 'cURL'\n\n return null\n }\n\n /**\n * Detects the operating system from the user agent string.\n * \n * @param userAgent \n * @returns \n */\n private static detectOs (userAgent: string | null) {\n if (!userAgent) return null\n if (/iPhone|iPad|iPod/i.test(userAgent)) return 'iOS'\n if (/Android/i.test(userAgent)) return 'Android'\n if (/Mac OS X|Macintosh/i.test(userAgent)) return 'macOS'\n if (/Windows NT/i.test(userAgent)) return 'Windows'\n if (/Linux/i.test(userAgent)) return 'Linux'\n\n return null\n }\n\n /**\n * Detects the device type from the user agent string.\n * \n * @param userAgent \n * @returns \n */\n private static detectDeviceType (userAgent: string | null): SessionDeviceInfo['deviceType'] {\n if (!userAgent) return 'unknown'\n if (/bot|spider|crawl/i.test(userAgent)) return 'bot'\n if (/iPad|Tablet/i.test(userAgent)) return 'tablet'\n if (/Mobile|iPhone|Android/i.test(userAgent)) return 'mobile'\n\n return 'desktop'\n }\n}\n","import { Hash, env, getModel } from '@arkstack/common'\nimport { JWTPayload, SignJWT, jwtVerify } from 'jose'\n\nimport { AuthContract } from './Contracts/AuthContract'\nimport { AuthenticationException } from './Exceptions/AuthenticationException'\nimport { CurrentSession } from './CurrentSession'\nimport { PersonalAccessToken } from './Contracts/PersonalAccessToken'\nimport { Request, type RequestSource } from '@arkstack/http'\nimport { SessionDevice } from './SessionDevice'\nimport { User } from './Contracts/User'\n\n/**\n * The Auth class provides methods for user authentication, including verifying \n * credentials, logging in, logging out, and managing personal access tokens. \n * \n * @author Legacy (3m1n3nc3)\n */\nexport class Auth extends AuthContract {\n protected static req?: Request<User>\n private configuredSecret?: string\n #user: User | null = null\n\n constructor(secret?: string, req?: Request<User> | RequestSource<User>) {\n super()\n Auth.req = Request.from<User>(req)\n this.configuredSecret = secret\n }\n\n /**\n * Create a new instance of the Auth class with an optional secret for JWT \n * signing and verification.\n * \n * @param secret The secret key used for signing and verifying JWTs.\n * @returns A new instance of the Auth class.\n */\n static make (secret?: string) {\n return new Auth(secret)\n }\n\n /**\n * Set the current HTTP request instance being processed.\n * \n * @param req The HTTP request instance to be set.\n * @returns The Auth class itself for method chaining.\n */\n static setRequest (req: Request<User> | RequestSource<User>) {\n this.req = Request.from<User>(req)\n\n return this\n }\n\n /**\n * Set the current HTTP request instance being processed.\n * \n * @param req The HTTP request instance to be set.\n * @returns The Auth instance itself for method chaining.\n */\n setRequest (req: Request<User> | RequestSource<User>) {\n Auth.req ??= Request.from<User>(req)\n\n return this\n }\n\n /**\n * Get the current HTTP request instance being processed, which may contain\n * user information and other request-specific data relevant to authentication operations.\n * \n * @returns The current HTTP request instance or undefined if not set.\n */\n getRequest (): Request<User> | undefined {\n return Auth.req\n }\n\n /**\n * Get the currently authenticated user\n * \n * @returns The currently authenticated user or null if not authenticated.\n */\n user (): User | null {\n return this.#user\n }\n\n /**\n * Verify user credentials\n * \n * @param email The email address of the user.\n * @param password The password of the user.\n * @returns A boolean indicating whether the credentials are valid.\n */\n async verify (email: string, password: string): Promise<boolean> {\n const user = await (await getModel<typeof User>('User')).query().where({ email }).first()\n\n return !!user && await Hash.verify(password, user.password)\n }\n\n /**\n * Attempt to authenticate a user with the given email and password.\n * \n * @param email \n * @param password \n * @returns \n */\n async attempt (email: string, password: string): Promise<User> {\n const user = await (await getModel<typeof User>('User')).query().where({ email }).first()\n\n if (!user) {\n throw new AuthenticationException('User account not found', { req: Auth.req, status: 422, errors: { email: ['No account found for this email address'] } })\n }\n\n const isValid = await Hash.verify(password, user.password)\n\n if (!isValid) {\n throw new AuthenticationException('Invalid credentials', { req: Auth.req, status: 422, errors: { password: ['Invalid password'] } })\n }\n\n Auth.req?.setUser(user)\n\n this.#user = user\n\n return user\n }\n\n /**\n * Login a user and create a personal access token\n * \n * @param email \n * @param password \n * @returns \n */\n async login (email: string, password: string): Promise<PersonalAccessToken> {\n const user = await this.attempt(email, password)\n\n return await this.create(user)\n }\n\n /**\n * Create a temporary token for a user with a specific purpose, such as\n * two-factor authentication.\n * \n * @param user \n * @param purpose \n * @param expiresIn \n * @returns \n */\n async createTemporaryToken (user: User, purpose: string, expiresIn: string = '10m'): Promise<string> {\n return await this.createJWT({\n sub: user.id.toString(),\n email: user.email,\n purpose,\n }, expiresIn)\n }\n\n /**\n * Authorize a temporary token and return the associated user if the token is \n * valid and matches the expected purpose.\n * \n * @param token \n * @param purpose \n * @returns \n */\n async authorizeTemporaryToken (token: string, purpose: string): Promise<User> {\n const payload = await this.verifyJWT(token)\n\n if (!payload || payload.purpose !== purpose || !payload.sub) {\n throw new AuthenticationException(\n 'Invalid or expired two-factor session',\n { req: Auth.req, status: 401 }\n )\n }\n\n const user = await (await getModel<typeof User>('User')).query().find(payload.sub)\n\n if (!user) {\n throw new AuthenticationException(\n 'User account not found',\n { req: Auth.req, status: 401 }\n )\n }\n\n Auth.req?.setUser(user)\n\n this.#user = user\n\n return user\n }\n\n /**\n * Logout the currently authenticated user and delete all their personal access tokens\n * \n * @param token \n * @returns \n */\n async logout (token?: string | PersonalAccessToken): Promise<void> {\n if (!this.#user && !token) {\n return\n }\n\n if (token) {\n if (typeof token === 'string') {\n const TokenModel = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n\n await TokenModel.query().where({ token }).delete()\n } else {\n await token.delete()\n }\n } else {\n const TokenModel = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n\n await TokenModel.query().where({ userId: this.#user!.id }).delete()\n }\n\n this.#user = null\n }\n\n /**\n * Check if the user is authenticated\n * \n * @returns \n */\n async check (): Promise<boolean> {\n return !!this.#user\n }\n\n /**\n * Get the current session's personal access token\n * \n * @returns \n */\n currentSession () {\n return new CurrentSession(this)\n }\n\n /**\n * Create a personal access token for a user\n * \n * @param user \n * @returns \n */\n async create (user: User): Promise<PersonalAccessToken> {\n const payload: JWTPayload = {\n sub: user.id.toString(),\n email: user.email,\n }\n\n Auth.req?.setUser(user)\n\n const token = await this.createJWT(payload)\n const deviceInfo = SessionDevice.fromRequest(Auth.req)\n\n const pat = await this.upsertDeviceToken(user, token, deviceInfo)\n\n pat.setLoadedRelation('user', user)\n\n return pat\n }\n\n /**\n * Create or replace the personal access token for the same user and device\n * while keeping a single active session record for that device.\n *\n * @param user The authenticated user.\n * @param token The new bearer token to persist.\n * @param deviceInfo The current request's device information.\n */\n private async upsertDeviceToken (user: User, token: string, deviceInfo: Record<string, unknown> | null) {\n const TokenModel = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n const deviceKey = SessionDevice.getUniqueKey(deviceInfo)\n const payload = {\n abilities: [],\n token,\n name: SessionDevice.getDisplayName(deviceInfo),\n userId: user.id,\n lastUsedAt: new Date(),\n } as {\n abilities: string[]\n token: string\n name: string\n userId: User['id']\n deviceInfo?: Record<string, unknown> | null\n lastUsedAt: Date\n }\n\n if (!deviceKey) {\n return await TokenModel.query().create(payload)\n }\n\n payload.deviceInfo = deviceInfo\n\n const existingSessions = (await TokenModel.query().where({ userId: user.id }).get()).all()\n const matchingSessions = existingSessions\n .filter((session) => SessionDevice.matches(session.deviceInfo, deviceInfo))\n .sort((left, right) => {\n const leftTime = (left.lastUsedAt ?? left.createdAt).getTime()\n const rightTime = (right.lastUsedAt ?? right.createdAt).getTime()\n\n return rightTime - leftTime\n })\n\n if (matchingSessions.length < 1) {\n return await TokenModel.query().create(payload)\n }\n\n const [currentSession, ...duplicateSessions] = matchingSessions\n\n if (duplicateSessions.length > 0) {\n await Promise.all(duplicateSessions.map(async (session) => await session.delete()))\n }\n\n await TokenModel.query().where({ id: currentSession.id }).update(payload)\n\n currentSession.token = payload.token\n currentSession.name = payload.name\n currentSession.userId = payload.userId\n currentSession.deviceInfo = payload.deviceInfo\n currentSession.lastUsedAt = payload.lastUsedAt\n\n return currentSession\n }\n\n /**\n * Authorize a token and return the associated user\n * \n * @param token \n * @returns \n */\n async authorizeToken (token: string): Promise<User> {\n const payload = await this.verifyJWT(token)\n\n if (!payload) {\n throw new AuthenticationException(\n 'Invalid or expired session',\n { req: Auth.req, status: 401 }\n )\n }\n\n const TokenModel = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n const pat = await TokenModel.query().where({ token }).first()\n\n if (!pat) {\n throw new AuthenticationException(\n 'Invalid or expired access token',\n { req: Auth.req, status: 401 }\n )\n }\n\n const user = await (await getModel<typeof User>('User')).query().find(payload.sub!)\n\n if (!user) {\n throw new AuthenticationException(\n 'User account not found',\n { req: Auth.req, status: 401 }\n )\n }\n\n Auth.req?.setUser(user)\n\n void this.touchSession(pat).catch((error) => {\n if (env('NODE_ENV') === 'development') {\n console.error('Failed to update session activity', error)\n }\n })\n\n this.#user = user\n\n return user\n }\n\n /**\n * Create a JWT token\n * \n * @param payload \n * @returns \n */\n private async createJWT (payload: JWTPayload, expiresIn: string = env('JWT_EXPIRES_IN', '1h')): Promise<string> {\n const jwt = await new SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(expiresIn)\n .sign(new TextEncoder().encode(this.getSecret()))\n\n return jwt\n }\n\n /**\n * Verify a JWT token\n * \n * @param token \n * @returns \n */\n private async verifyJWT (token: string): Promise<JWTPayload | null> {\n try {\n const { payload } = await jwtVerify(token, new TextEncoder().encode(this.getSecret()))\n\n return payload\n } catch {\n return null\n }\n }\n\n private getSecret (): string {\n return this.configuredSecret ?? env('JWT_SECRET', 'default_secret')\n }\n\n /**\n * Update the last used timestamp and device information of a personal \n * access token to keep the session active and reflect the latest device details.\n * \n * @param pat The personal access token to update.\n * @returns A promise that resolves when the update is complete.\n */\n private async touchSession (pat: PersonalAccessToken) {\n const now = new Date()\n const currentDeviceInfo = SessionDevice.fromRequest(Auth.req)\n const shouldUpdateLastUsedAt = !pat.lastUsedAt || (now.getTime() - pat.lastUsedAt.getTime()) > 5 * 60 * 1000\n const hasDeviceInfo = !!pat.deviceInfo\n const currentDisplayName = SessionDevice.getDisplayName(currentDeviceInfo)\n const storedDisplayName = SessionDevice.getDisplayName(pat.deviceInfo)\n const shouldRefreshDeviceInfo = !hasDeviceInfo || storedDisplayName !== currentDisplayName\n\n if (!shouldUpdateLastUsedAt && !shouldRefreshDeviceInfo) {\n return\n }\n\n const payload: {\n lastUsedAt: Date\n deviceInfo?: Record<string, unknown> | null\n name?: string\n } = {\n lastUsedAt: now,\n }\n\n if (shouldRefreshDeviceInfo) {\n payload.deviceInfo = currentDeviceInfo\n payload.name = currentDisplayName\n }\n\n const TokenModel = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n\n await TokenModel.query().where({ id: pat.id }).update(payload)\n\n pat.lastUsedAt = now\n\n if (payload.deviceInfo !== undefined) {\n pat.deviceInfo = payload.deviceInfo\n }\n\n if (payload.name !== undefined) {\n pat.name = payload.name\n }\n }\n}\n","import { Encryption, Hash, env, getModel } from '@arkstack/common'\nimport { randomBytes } from 'node:crypto'\n\nimport { User } from './Contracts/User'\nimport { UserTwoFactor } from './Contracts/UserTwoFactor'\nimport type { IssuedSmsCode, SmsCodePurpose, TwoFactorMethod, TwoFactorSetup, TwoFactorStatus } from './types/TwoFactor'\n\ntype TwoFactorUser = User & {\n phone?: string | null\n}\n\nconst secretAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\nconst appName = () => env('APP_NAME', 'Arkstack')\nconst smsCodeTtlMinutes = () => Number(env('TWO_FACTOR_SMS_TTL_MINUTES', 10)) || 10\n\nexport class TwoFactor {\n private static async getModel () {\n return await getModel<typeof UserTwoFactor>('UserTwoFactor')\n }\n\n private static async getRecord (userId: User['id']) {\n const Model = await this.getModel()\n\n return await Model.query().where({ userId }).first()\n }\n\n private static async upsert (\n userId: User['id'],\n attributes: Partial<Pick<UserTwoFactor,\n | 'method' | 'secretCiphertext' | 'smsCodeHash' | 'smsCodeExpiresAt'\n | 'smsCodePurpose' | 'enabledAt' | 'recoveryCodeHashes'\n >>\n ) {\n const Model = await this.getModel()\n\n await Model.query().updateOrInsert({ userId }, attributes)\n }\n\n static normalizeMethod (method?: string | null): TwoFactorMethod | null {\n if (method === 'authenticator' || method === 'sms') {\n return method\n }\n\n return null\n }\n\n static maskPhone (phone?: string | null) {\n if (!phone) {\n return null\n }\n\n const normalized = phone.replace(/\\s+/g, '')\n\n if (normalized.length <= 4) {\n return normalized\n }\n\n return `${'*'.repeat(Math.max(normalized.length - 4, 2))}${normalized.slice(-4)}`\n }\n\n static getLabel (user: User) {\n return user.email || `${appName()}:${user.id}`\n }\n\n static getTotp (user: User, secret: string) {\n return Hash.totp(secret, this.getLabel(user), appName())\n }\n\n static generateSecret (size = 20) {\n const bytes = randomBytes(size)\n let secret = ''\n\n for (const byte of bytes) {\n secret += secretAlphabet[byte % secretAlphabet.length]\n }\n\n return secret\n }\n\n static createSetup (user: User, secret?: string): TwoFactorSetup {\n const resolvedSecret = secret ?? this.generateSecret()\n const totp = this.getTotp(user, resolvedSecret)\n\n return {\n secret: resolvedSecret,\n otpauthUrl: totp.toString(),\n }\n }\n\n static verifyCode (user: User, secret: string, code: string) {\n return this.getTotp(user, secret).validate({ token: code, window: 1 }) !== null\n }\n\n static async getMethod (userId: User['id']) {\n const record = await this.getRecord(userId)\n\n return this.normalizeMethod(record?.method)\n }\n\n static async setMethod (userId: User['id'], method: TwoFactorMethod) {\n await this.upsert(userId, { method })\n }\n\n static async getSecret (userId: User['id']) {\n const record = await this.getRecord(userId)\n\n return record?.secretCiphertext ? Encryption.decrypt(record.secretCiphertext) : null\n }\n\n static async setSecret (userId: User['id'], secret: string) {\n await this.upsert(userId, { secretCiphertext: Encryption.encrypt(secret) })\n }\n\n static async clearSecret (userId: User['id']) {\n await this.upsert(userId, { secretCiphertext: null })\n }\n\n static async getEnabledAt (userId: User['id']) {\n const record = await this.getRecord(userId)\n\n return record?.enabledAt?.toISOString() ?? null\n }\n\n static async setEnabledAt (userId: User['id'], enabledAt: string | Date = new Date()) {\n await this.upsert(userId, {\n enabledAt: typeof enabledAt === 'string' ? new Date(enabledAt) : enabledAt,\n })\n }\n\n static async clear (userId: User['id']) {\n const Model = await this.getModel()\n\n await Model.query().where({ userId }).delete()\n }\n\n static generateBackupCodes (count = 8) {\n return Array.from({ length: count }, () => {\n const left = randomBytes(3).toString('hex').slice(0, 4).toUpperCase()\n const right = randomBytes(3).toString('hex').slice(0, 4).toUpperCase()\n\n return `${left}-${right}`\n })\n }\n\n static async hashBackupCodes (codes: string[]) {\n return await Promise.all(codes.map(async code => await Hash.make(code)))\n }\n\n static async readRecoveryCodeHashes (userId: User['id']) {\n const record = await this.getRecord(userId)\n\n return record?.recoveryCodeHashes ?? []\n }\n\n static async writeRecoveryCodeHashes (userId: User['id'], hashes: string[]) {\n await this.upsert(userId, { recoveryCodeHashes: hashes })\n }\n\n static async consumeRecoveryCode (userId: User['id'], recoveryCode: string) {\n const hashes = await this.readRecoveryCodeHashes(userId)\n\n for (const [index, hash] of hashes.entries()) {\n if (await Hash.verify(recoveryCode, hash)) {\n await this.writeRecoveryCodeHashes(\n userId,\n hashes.filter((_, currentIndex) => currentIndex !== index),\n )\n\n return true\n }\n }\n\n return false\n }\n\n static async readStatus (userId: User['id']): Promise<TwoFactorStatus> {\n const record = await this.getRecord(userId)\n const enabledAt = record?.enabledAt?.toISOString() ?? null\n const recoveryCodes = record?.recoveryCodeHashes ?? []\n\n return {\n enabled: !!enabledAt,\n enabledAt,\n method: this.normalizeMethod(record?.method),\n recoveryCodesRemaining: recoveryCodes.length,\n }\n }\n\n static createSmsCode () {\n return Math.floor(100000 + Math.random() * 900000).toString()\n }\n\n static async issueSmsCode (user: User, purpose: SmsCodePurpose): Promise<IssuedSmsCode> {\n if (!(user as TwoFactorUser).phone) {\n throw new Error('A phone number is required to issue a two-factor SMS code.')\n }\n\n const code = this.createSmsCode()\n const smsCodeHash = await Hash.make(code)\n const expiresAt = new Date(Date.now() + smsCodeTtlMinutes() * 60 * 1000)\n\n await this.upsert(user.id, {\n smsCodeHash,\n smsCodeExpiresAt: expiresAt,\n smsCodePurpose: purpose,\n })\n\n return {\n code,\n expiresAt,\n purpose,\n }\n }\n\n static async clearSmsCode (userId: User['id']) {\n await this.upsert(userId, {\n smsCodeHash: null,\n smsCodeExpiresAt: null,\n smsCodePurpose: null,\n })\n }\n\n static async verifySmsCode (userId: User['id'], code: string, purpose: SmsCodePurpose) {\n const record = await this.getRecord(userId)\n\n if (!record?.smsCodeHash || !record.smsCodeExpiresAt || record.smsCodePurpose !== purpose) {\n return false\n }\n\n if (record.smsCodeExpiresAt.getTime() < Date.now()) {\n await this.clearSmsCode(userId)\n\n return false\n }\n\n const isValid = await Hash.verify(code, record.smsCodeHash)\n\n if (isValid) {\n await this.clearSmsCode(userId)\n }\n\n return isValid\n }\n}\n","import { BelongsToRelation } from 'arkormx/relationship'\nimport { Model } from 'arkormx'\nimport { User } from './User'\n\nexport abstract class PersonalAccessToken extends Model {\n declare id: number\n declare name: string\n declare token: string\n declare abilities: string[]\n declare userId: number\n declare createdAt: Date\n declare expiresAt: Date | null\n declare lastUsedAt: Date | null\n declare deviceInfo: Record<string, unknown> | null\n\n abstract user (): BelongsToRelation<this, User>\n}\n","import { HasManyRelation, HasOneRelation } from 'arkormx/relationship'\n\nimport { Model } from 'arkormx'\nimport { PersonalAccessToken } from './PersonalAccessToken'\nimport { UserNotification } from '@arkstack/notifications'\nimport { UserTwoFactor } from './UserTwoFactor'\n\nexport abstract class User extends Model {\n declare id: number\n declare email: string\n declare name: string\n declare password: string\n declare createdAt: Date\n declare updatedAt: Date\n\n protected static table?: string | undefined = 'users'\n\n abstract personalAccessTokens (): HasManyRelation<this, PersonalAccessToken>\n\n abstract twoFactor (): HasOneRelation<this, UserTwoFactor>\n\n abstract notifications (): HasManyRelation<this, UserNotification>\n}","import { Model } from 'arkormx'\n\nimport type { SmsCodePurpose, TwoFactorMethod } from '../types/TwoFactor'\nimport type { User } from './User'\nimport { BelongsToRelation } from 'arkormx/relationship'\n\nexport abstract class UserTwoFactor extends Model {\n declare id: number | string\n declare userId: User['id']\n declare method: TwoFactorMethod | null\n declare secretCiphertext: string | null\n declare smsCodeHash: string | null\n declare smsCodeExpiresAt: Date | null\n declare smsCodePurpose: SmsCodePurpose | null\n declare enabledAt: Date | null\n declare recoveryCodeHashes: string[] | null\n declare createdAt: Date\n declare updatedAt: Date\n\n protected static override table?: string | undefined = 'user_two_factors'\n\n protected casts = {\n recoveryCodeHashes: 'json',\n } as const\n\n abstract user (): BelongsToRelation<this, User>\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["#request","#response","#errors","#user"],"sources":["../src/Contracts/AuthContract.ts","../src/Exceptions/AuthenticationException.ts","../src/CurrentSession.ts","../src/SessionDevice.ts","../src/Auth.ts","../src/TwoFactor.ts","../src/Contracts/PersonalAccessToken.ts","../src/Contracts/User.ts","../src/Contracts/UserTwoFactor.ts"],"sourcesContent":["import { CurrentSession } from '../CurrentSession'\nimport { PersonalAccessToken } from './PersonalAccessToken'\nimport { Request, type RequestSource } from '@arkstack/http'\nimport { User } from './User'\n\n/**\n * The Auth class provides methods for user authentication, including verifying \n * credentials, logging in, logging out, and managing personal access tokens. \n * \n * @author Legacy (3m1n3nc3)\n */\nexport abstract class AuthContract {\n /**\n * Set the current HTTP request instance being processed.\n * \n * @param req The HTTP request instance to be set.\n * @returns The Auth instance itself for method chaining.\n */\n abstract setRequest (req: Request<User> | RequestSource<User>): this\n\n /**\n * Get the current HTTP request instance being processed, which may contain\n * user information and other request-specific data relevant to authentication operations.\n * \n * @returns The current HTTP request instance or undefined if not set.\n */\n abstract getRequest (): Request<User> | undefined\n\n /**\n * Get the currently authenticated user\n * \n * @returns The currently authenticated user or null if not authenticated.\n */\n abstract user (): User | null\n\n /**\n * Verify user credentials\n * \n * @param email The email address of the user.\n * @param password The password of the user.\n * @returns A boolean indicating whether the credentials are valid.\n */\n abstract verify (email: string, password: string): Promise<boolean>\n\n /**\n * Attempt to authenticate a user with the given email and password.\n * \n * @param email \n * @param password \n * @returns \n */\n abstract attempt (email: string, password: string): Promise<User>\n\n /**\n * Login a user and create a personal access token\n * \n * @param email \n * @param password \n * @returns \n */\n abstract login (email: string, password: string): Promise<PersonalAccessToken>\n\n /**\n * Create a temporary token for a user with a specific purpose, such as\n * two-factor authentication.\n * \n * @param user \n * @param purpose \n * @param expiresIn \n * @returns \n */\n abstract createTemporaryToken (user: User, purpose: string, expiresIn?: string): Promise<string>\n\n /**\n * Authorize a temporary token and return the associated user if the token is \n * valid and matches the expected purpose.\n * \n * @param token \n * @param purpose \n * @returns \n */\n abstract authorizeTemporaryToken (token: string, purpose: string): Promise<User>\n\n /**\n * Logout the currently authenticated user and delete all their personal access tokens\n * \n * @param token \n * @returns \n */\n abstract logout (token?: string | PersonalAccessToken): Promise<void>\n\n /**\n * Check if the user is authenticated\n * \n * @returns \n */\n abstract check (): Promise<boolean>\n\n /**\n * Get the current session's personal access token\n * \n * @returns \n */\n abstract currentSession (): CurrentSession\n\n /**\n * Create a personal access token for a user\n * \n * @param user \n * @returns \n */\n abstract create (user: User): Promise<PersonalAccessToken>\n\n /**\n * Authorize a token and return the associated user\n * \n * @param token \n * @returns \n */\n abstract authorizeToken (token: string): Promise<User>\n}\n","import { Request, Response, type RequestSource, type ResponseSource } from '@arkstack/http'\n\nimport { Exception } from '@arkstack/common'\n\nexport class AuthenticationException extends Exception {\n #errors?: Record<string, any>\n #request?: Request\n #response?: Response\n statusCode: number = 401\n name: string\n\n constructor(\n message: string = 'Authentication failed',\n ctx?: {\n req?: Request | RequestSource,\n res?: Response | ResponseSource,\n status?: number,\n errors?: Record<string, any>\n }\n ) {\n super(message)\n this.name = 'AuthenticationException'\n this.statusCode = ctx?.status ?? 401\n if (ctx) {\n this.#request = Request.from(ctx.req)\n this.#response = Response.from(ctx.res)\n this.#errors = ctx.errors\n }\n\n void this.#response\n void this.#request\n }\n\n errors (): Record<string, any> | undefined {\n return this.#errors\n }\n}\n","import { AuthContract } from './Contracts/AuthContract'\nimport { PersonalAccessToken } from './Contracts/PersonalAccessToken'\nimport { getModel } from '@arkstack/common'\n\n/**\n * The CurrentSession class represents the current authentication session and provides \n * methods to manage it, such as destroying the session (logging out) and retrieving \n * the current personal access token. It is used internally by the Auth class to \n * handle session-specific operations.\n * \n * @author Legacy (3m1n3nc3)\n * @since 1.0.0\n * @version 1.0.0\n * @see Auth\n */\nexport class CurrentSession {\n constructor(private auth: AuthContract) { }\n\n /**\n * Destroy the current session's personal access token, effectively \n * logging out the user from this session.\n */\n async destroy () {\n const pat = await this.token()\n\n if (pat) {\n await this.auth.logout(pat)\n }\n }\n\n /**\n * Get the current session's personal access token\n * \n * @returns \n */\n async token (): Promise<PersonalAccessToken | null> {\n if (!this.auth.getRequest()) {\n return null\n }\n\n const token = this.auth.getRequest()!.bearerToken()\n\n if (!token) {\n return null\n }\n\n const Model = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n const pat = await Model.query().where({ token }).first()\n\n return pat\n }\n}\n","import { type Request } from '@arkstack/http'\nimport { AuthAgentPayload, SessionDeviceInfo } from './types/Session'\nimport { UAParser } from 'ua-parser-js'\n\nexport class SessionDevice {\n private static readonly uniqueIdentityFields = [\n 'deviceName',\n 'manufacturer',\n 'model',\n 'platform',\n 'os',\n 'osVersion',\n 'browser',\n 'deviceType',\n ] as const\n\n /**\n * Extracts device information from the incoming request to build a SessionDeviceInfo object.\n * \n * @param req The incoming HTTP request object.\n * @returns A SessionDeviceInfo object containing information about the client's device.\n */\n static fromRequest (req?: Request): SessionDeviceInfo {\n const userAgent = this.readUserAgent(req)\n const ua = new UAParser(userAgent ?? undefined).getResult()\n const ca = this.readAuthAgent(req)\n\n return {\n browser: this.readString(ua.browser.name) ?? this.detectBrowser(userAgent),\n os: ca?.os ?? this.readString(ua.os.name) ?? this.detectOs(userAgent),\n osVersion: ca?.osVersion ?? this.readString(ua.os.version),\n deviceType: ca?.deviceType ?? this.normalizeDeviceType(ua.device.type) ?? this.detectDeviceType(userAgent),\n deviceName: ca?.deviceName ?? null,\n manufacturer: ca?.manufacturer ?? this.readString(ua.device.vendor),\n model: ca?.model ?? this.readString(ua.device.model),\n platform: ca?.platform ?? null,\n ipAddress: this.detectIpAddress(req),\n userAgent,\n }\n }\n\n /**\n * Generates a human-readable display name for the device based on available information.\n * \n * @param deviceInfo A record containing device information.\n * @returns A string representing the display name of the device.\n */\n static getDisplayName (deviceInfo?: Record<string, unknown> | null) {\n const deviceName = this.readString(deviceInfo?.deviceName)\n const manufacturer = this.readString(deviceInfo?.manufacturer)\n const model = this.readString(deviceInfo?.model)\n const browser = this.readString(deviceInfo?.browser)\n const os = this.readString(deviceInfo?.os)\n const deviceType = this.readString(deviceInfo?.deviceType)\n\n if (manufacturer && model) {\n return model.startsWith(manufacturer) ? model : `${manufacturer} ${model}`\n }\n\n if (model) {\n return model\n }\n\n if (deviceName) {\n return deviceName\n }\n\n if (browser && os) {\n return `${browser} on ${os}`\n }\n\n if (os && deviceType && deviceType !== 'unknown') {\n return `${os} ${deviceType}`\n }\n\n if (browser) {\n return browser\n }\n\n return 'Unknown device'\n }\n\n /**\n * Builds a stable device key for matching previously issued sessions to the\n * current request device.\n *\n * @param deviceInfo A record containing device information.\n * @returns A normalized device key or null when there is not enough signal.\n */\n static getUniqueKey (deviceInfo?: Record<string, unknown> | null) {\n const parts = this.uniqueIdentityFields\n .map((field) => [field, this.readString(deviceInfo?.[field])?.toLowerCase()] as const)\n .filter(([, value]) => !!value)\n\n if (parts.length < 2) {\n const fallbackUserAgent = this.readString(deviceInfo?.userAgent)?.toLowerCase()\n\n return fallbackUserAgent ?? null\n }\n\n return parts.map(([field, value]) => `${field}:${value}`).join('|')\n }\n\n /**\n * Determines whether two device payloads represent the same device.\n *\n * @param left The first device payload.\n * @param right The second device payload.\n * @returns True when both payloads resolve to the same device key.\n */\n static matches (left?: Record<string, unknown> | null, right?: Record<string, unknown> | null) {\n const leftKey = this.getUniqueKey(left)\n const rightKey = this.getUniqueKey(right)\n\n if (!leftKey || !rightKey) {\n return false\n }\n\n return leftKey === rightKey\n }\n\n /**\n * Safely reads the user agent string from the request headers.\n * \n * @param req \n * @returns \n */\n private static readUserAgent (req?: Request) {\n const userAgent = req?.header('user-agent')\n\n return typeof userAgent === 'string' ? userAgent : null\n }\n\n /**\n * Safely reads a string value, ensuring it's a non-empty string or returns null.\n * \n * @param value \n * @returns \n */\n private static readString (value: unknown) {\n return typeof value === 'string' && value.length > 0 ? value : null\n }\n\n /**\n * Reads a specific device-related header from the request\n * \n * @param req \n * @param headerName \n * @returns \n */\n private static readAuthAgent (req?: Request): AuthAgentPayload | null {\n const value = req?.header('x-auth-agent')\n\n if (typeof value !== 'string' || value.length < 1) {\n return null\n }\n\n try {\n const parsed: AuthAgentPayload = JSON.parse(value)\n\n return {\n deviceName: this.readString(parsed.deviceName) ?? undefined,\n manufacturer: this.readString(parsed.manufacturer) ?? undefined,\n model: this.readString(parsed.model) ?? undefined,\n platform: this.readString(parsed.platform) ?? undefined,\n os: this.readString(parsed.os) ?? undefined,\n osVersion: this.readString(parsed.osVersion) ?? undefined,\n deviceType: this.normalizeDeviceType(parsed.deviceType) ?? undefined,\n }\n } catch {\n return null\n }\n }\n\n private static normalizeDeviceType (value: unknown): SessionDeviceInfo['deviceType'] | null {\n if (value === 'mobile' || value === 'tablet' || value === 'desktop' || value === 'bot' || value === 'unknown') {\n return value\n }\n\n return null\n }\n\n /**\n * Detects the client's IP address from the request, considering common headers set by proxies.\n * \n * @param req \n * @returns \n */\n private static detectIpAddress (req?: Request) {\n const forwarded = req?.header('x-forwarded-for')\n\n if (typeof forwarded === 'string' && forwarded.length > 0) {\n return forwarded.split(',')[0].trim()\n }\n\n return req?.ip ?? null\n }\n\n /**\n * Detects the browser from the user agent string.\n * \n * @param userAgent \n * @returns \n */\n private static detectBrowser (userAgent: string | null) {\n if (!userAgent) return null\n if (/Edg\\//i.test(userAgent)) return 'Edge'\n if (/OPR\\//i.test(userAgent)) return 'Opera'\n if (/SamsungBrowser\\//i.test(userAgent)) return 'Samsung Internet'\n if (/Chrome\\//i.test(userAgent) && !/Edg\\//i.test(userAgent)) return 'Chrome'\n if (/Firefox\\//i.test(userAgent)) return 'Firefox'\n if (/Safari\\//i.test(userAgent) && !/Chrome\\//i.test(userAgent)) return 'Safari'\n if (/PostmanRuntime\\//i.test(userAgent)) return 'Postman'\n if (/okhttp\\//i.test(userAgent)) return 'OkHttp'\n if (/curl\\//i.test(userAgent)) return 'cURL'\n\n return null\n }\n\n /**\n * Detects the operating system from the user agent string.\n * \n * @param userAgent \n * @returns \n */\n private static detectOs (userAgent: string | null) {\n if (!userAgent) return null\n if (/iPhone|iPad|iPod/i.test(userAgent)) return 'iOS'\n if (/Android/i.test(userAgent)) return 'Android'\n if (/Mac OS X|Macintosh/i.test(userAgent)) return 'macOS'\n if (/Windows NT/i.test(userAgent)) return 'Windows'\n if (/Linux/i.test(userAgent)) return 'Linux'\n\n return null\n }\n\n /**\n * Detects the device type from the user agent string.\n * \n * @param userAgent \n * @returns \n */\n private static detectDeviceType (userAgent: string | null): SessionDeviceInfo['deviceType'] {\n if (!userAgent) return 'unknown'\n if (/bot|spider|crawl/i.test(userAgent)) return 'bot'\n if (/iPad|Tablet/i.test(userAgent)) return 'tablet'\n if (/Mobile|iPhone|Android/i.test(userAgent)) return 'mobile'\n\n return 'desktop'\n }\n}\n","import { Hash, env, getModel } from '@arkstack/common'\nimport { JWTPayload, SignJWT, jwtVerify } from 'jose'\n\nimport { AuthContract } from './Contracts/AuthContract'\nimport { AuthenticationException } from './Exceptions/AuthenticationException'\nimport { CurrentSession } from './CurrentSession'\nimport { PersonalAccessToken } from './Contracts/PersonalAccessToken'\nimport { Request, type RequestSource } from '@arkstack/http'\nimport { SessionDevice } from './SessionDevice'\nimport { User } from './Contracts/User'\n\n/**\n * The Auth class provides methods for user authentication, including verifying \n * credentials, logging in, logging out, and managing personal access tokens. \n * \n * @author Legacy (3m1n3nc3)\n */\nexport class Auth extends AuthContract {\n protected static req?: Request<User>\n private configuredSecret?: string\n #user: User | null = null\n\n constructor(secret?: string, req?: Request<User> | RequestSource<User>) {\n super()\n Auth.req = Request.from<User>(req)\n this.configuredSecret = secret\n }\n\n /**\n * Create a new instance of the Auth class with an optional secret for JWT \n * signing and verification.\n * \n * @param secret The secret key used for signing and verifying JWTs.\n * @returns A new instance of the Auth class.\n */\n static make (secret?: string) {\n return new Auth(secret)\n }\n\n /**\n * Set the current HTTP request instance being processed.\n * \n * @param req The HTTP request instance to be set.\n * @returns The Auth class itself for method chaining.\n */\n static setRequest (req: Request<User> | RequestSource<User>) {\n this.req = Request.from<User>(req)\n\n return this\n }\n\n /**\n * Set the current HTTP request instance being processed.\n * \n * @param req The HTTP request instance to be set.\n * @returns The Auth instance itself for method chaining.\n */\n setRequest (req: Request<User> | RequestSource<User>) {\n Auth.req ??= Request.from<User>(req)\n\n return this\n }\n\n /**\n * Get the current HTTP request instance being processed, which may contain\n * user information and other request-specific data relevant to authentication operations.\n * \n * @returns The current HTTP request instance or undefined if not set.\n */\n getRequest (): Request<User> | undefined {\n return Auth.req\n }\n\n /**\n * Get the currently authenticated user\n * \n * @returns The currently authenticated user or null if not authenticated.\n */\n user (): User | null {\n return this.#user\n }\n\n /**\n * Verify user credentials\n * \n * @param email The email address of the user.\n * @param password The password of the user.\n * @returns A boolean indicating whether the credentials are valid.\n */\n async verify (email: string, password: string): Promise<boolean> {\n const user = await (await getModel<typeof User>('User')).query().where({ email }).first()\n\n return !!user && await Hash.verify(password, user.password)\n }\n\n /**\n * Attempt to authenticate a user with the given email and password.\n * \n * @param email \n * @param password \n * @returns \n */\n async attempt (email: string, password: string): Promise<User> {\n const user = await (await getModel<typeof User>('User')).query().where({ email }).first()\n\n if (!user) {\n throw new AuthenticationException('User account not found', { req: Auth.req, status: 422, errors: { email: ['No account found for this email address'] } })\n }\n\n const isValid = await Hash.verify(password, user.password)\n\n if (!isValid) {\n throw new AuthenticationException('Invalid credentials', { req: Auth.req, status: 422, errors: { password: ['Invalid password'] } })\n }\n\n Auth.req?.setUser(user)\n\n this.#user = user\n\n return user\n }\n\n /**\n * Login a user and create a personal access token\n * \n * @param email \n * @param password \n * @returns \n */\n async login (email: string, password: string): Promise<PersonalAccessToken> {\n const user = await this.attempt(email, password)\n\n return await this.create(user)\n }\n\n /**\n * Create a temporary token for a user with a specific purpose, such as\n * two-factor authentication.\n * \n * @param user \n * @param purpose \n * @param expiresIn \n * @returns \n */\n async createTemporaryToken (user: User, purpose: string, expiresIn: string = '10m'): Promise<string> {\n return await this.createJWT({\n sub: user.id.toString(),\n email: user.email,\n purpose,\n }, expiresIn)\n }\n\n /**\n * Authorize a temporary token and return the associated user if the token is \n * valid and matches the expected purpose.\n * \n * @param token \n * @param purpose \n * @returns \n */\n async authorizeTemporaryToken (token: string, purpose: string): Promise<User> {\n const payload = await this.verifyJWT(token)\n\n if (!payload || payload.purpose !== purpose || !payload.sub) {\n throw new AuthenticationException(\n 'Invalid or expired two-factor session',\n { req: Auth.req, status: 401 }\n )\n }\n\n const user = await (await getModel<typeof User>('User')).query().find(payload.sub)\n\n if (!user) {\n throw new AuthenticationException(\n 'User account not found',\n { req: Auth.req, status: 401 }\n )\n }\n\n Auth.req?.setUser(user)\n\n this.#user = user\n\n return user\n }\n\n /**\n * Logout the currently authenticated user and delete all their personal access tokens\n * \n * @param token \n * @returns \n */\n async logout (token?: string | PersonalAccessToken): Promise<void> {\n if (!this.#user && !token) {\n return\n }\n\n if (token) {\n if (typeof token === 'string') {\n const TokenModel = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n\n await TokenModel.query().where({ token }).delete()\n } else {\n await token.delete()\n }\n } else {\n const TokenModel = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n\n await TokenModel.query().where({ userId: this.#user!.id }).delete()\n }\n\n this.#user = null\n }\n\n /**\n * Check if the user is authenticated\n * \n * @returns \n */\n async check (): Promise<boolean> {\n return !!this.#user\n }\n\n /**\n * Get the current session's personal access token\n * \n * @returns \n */\n currentSession () {\n return new CurrentSession(this)\n }\n\n /**\n * Create a personal access token for a user\n * \n * @param user \n * @returns \n */\n async create (user: User): Promise<PersonalAccessToken> {\n const payload: JWTPayload = {\n sub: user.id.toString(),\n email: user.email,\n }\n\n Auth.req?.setUser(user)\n\n const token = await this.createJWT(payload)\n const deviceInfo = SessionDevice.fromRequest(Auth.req)\n\n const pat = await this.upsertDeviceToken(user, token, deviceInfo)\n\n pat.setLoadedRelation('user', user)\n\n return pat\n }\n\n /**\n * Create or replace the personal access token for the same user and device\n * while keeping a single active session record for that device.\n *\n * @param user The authenticated user.\n * @param token The new bearer token to persist.\n * @param deviceInfo The current request's device information.\n */\n private async upsertDeviceToken (user: User, token: string, deviceInfo: Record<string, unknown> | null) {\n const TokenModel = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n const deviceKey = SessionDevice.getUniqueKey(deviceInfo)\n const payload = {\n abilities: [],\n token,\n name: SessionDevice.getDisplayName(deviceInfo),\n userId: user.id,\n lastUsedAt: new Date(),\n } as {\n abilities: string[]\n token: string\n name: string\n userId: User['id']\n deviceInfo?: Record<string, unknown> | null\n lastUsedAt: Date\n }\n\n if (!deviceKey) {\n return await TokenModel.query().create(payload)\n }\n\n payload.deviceInfo = deviceInfo\n\n const existingSessions = (await TokenModel.query().where({ userId: user.id }).get()).all()\n const matchingSessions = existingSessions\n .filter((session) => SessionDevice.matches(session.deviceInfo, deviceInfo))\n .sort((left, right) => {\n const leftTime = (left.lastUsedAt ?? left.createdAt).getTime()\n const rightTime = (right.lastUsedAt ?? right.createdAt).getTime()\n\n return rightTime - leftTime\n })\n\n if (matchingSessions.length < 1) {\n return await TokenModel.query().create(payload)\n }\n\n const [currentSession, ...duplicateSessions] = matchingSessions\n\n if (duplicateSessions.length > 0) {\n await Promise.all(duplicateSessions.map(async (session) => await session.delete()))\n }\n\n await TokenModel.query().where({ id: currentSession.id }).update(payload)\n\n currentSession.token = payload.token\n currentSession.name = payload.name\n currentSession.userId = payload.userId\n currentSession.deviceInfo = payload.deviceInfo\n currentSession.lastUsedAt = payload.lastUsedAt\n\n return currentSession\n }\n\n /**\n * Authorize a token and return the associated user\n * \n * @param token \n * @returns \n */\n async authorizeToken (token: string): Promise<User> {\n const payload = await this.verifyJWT(token)\n\n if (!payload) {\n throw new AuthenticationException(\n 'Invalid or expired session',\n { req: Auth.req, status: 401 }\n )\n }\n\n const TokenModel = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n const pat = await TokenModel.query().where({ token }).first()\n\n if (!pat) {\n throw new AuthenticationException(\n 'Invalid or expired access token',\n { req: Auth.req, status: 401 }\n )\n }\n\n const user = await (await getModel<typeof User>('User')).query().find(payload.sub!)\n\n if (!user) {\n throw new AuthenticationException(\n 'User account not found',\n { req: Auth.req, status: 401 }\n )\n }\n\n Auth.req?.setUser(user)\n\n void this.touchSession(pat).catch((error) => {\n if (env('NODE_ENV') === 'development') {\n console.error('Failed to update session activity', error)\n }\n })\n\n this.#user = user\n\n return user\n }\n\n /**\n * Create a JWT token\n * \n * @param payload \n * @returns \n */\n private async createJWT (payload: JWTPayload, expiresIn: string = env('JWT_EXPIRES_IN', '1h')): Promise<string> {\n const jwt = await new SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(expiresIn)\n .sign(new TextEncoder().encode(this.getSecret()))\n\n return jwt\n }\n\n /**\n * Verify a JWT token\n * \n * @param token \n * @returns \n */\n private async verifyJWT (token: string): Promise<JWTPayload | null> {\n try {\n const { payload } = await jwtVerify(token, new TextEncoder().encode(this.getSecret()))\n\n return payload\n } catch {\n return null\n }\n }\n\n private getSecret (): string {\n return this.configuredSecret ?? env('JWT_SECRET', 'default_secret')\n }\n\n /**\n * Update the last used timestamp and device information of a personal \n * access token to keep the session active and reflect the latest device details.\n * \n * @param pat The personal access token to update.\n * @returns A promise that resolves when the update is complete.\n */\n private async touchSession (pat: PersonalAccessToken) {\n const now = new Date()\n const currentDeviceInfo = SessionDevice.fromRequest(Auth.req)\n const shouldUpdateLastUsedAt = !pat.lastUsedAt || (now.getTime() - pat.lastUsedAt.getTime()) > 5 * 60 * 1000\n const hasDeviceInfo = !!pat.deviceInfo\n const currentDisplayName = SessionDevice.getDisplayName(currentDeviceInfo)\n const storedDisplayName = SessionDevice.getDisplayName(pat.deviceInfo)\n const shouldRefreshDeviceInfo = !hasDeviceInfo || storedDisplayName !== currentDisplayName\n\n if (!shouldUpdateLastUsedAt && !shouldRefreshDeviceInfo) {\n return\n }\n\n const payload: {\n lastUsedAt: Date\n deviceInfo?: Record<string, unknown> | null\n name?: string\n } = {\n lastUsedAt: now,\n }\n\n if (shouldRefreshDeviceInfo) {\n payload.deviceInfo = currentDeviceInfo\n payload.name = currentDisplayName\n }\n\n const TokenModel = await getModel<typeof PersonalAccessToken>('PersonalAccessToken')\n\n await TokenModel.query().where({ id: pat.id }).update(payload)\n\n pat.lastUsedAt = now\n\n if (payload.deviceInfo !== undefined) {\n pat.deviceInfo = payload.deviceInfo\n }\n\n if (payload.name !== undefined) {\n pat.name = payload.name\n }\n }\n}\n","import { Encryption, Hash, env, getModel } from '@arkstack/common'\nimport { randomBytes } from 'node:crypto'\n\nimport { User } from './Contracts/User'\nimport { UserTwoFactor } from './Contracts/UserTwoFactor'\nimport type { IssuedSmsCode, SmsCodePurpose, TwoFactorMethod, TwoFactorSetup, TwoFactorStatus } from './types/TwoFactor'\n\ntype TwoFactorUser = User & {\n phone?: string | null\n}\n\nconst secretAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\nconst appName = () => env('APP_NAME', 'Arkstack')\nconst smsCodeTtlMinutes = () => Number(env('TWO_FACTOR_SMS_TTL_MINUTES', 10)) || 10\n\nexport class TwoFactor {\n private static async getModel () {\n return await getModel<typeof UserTwoFactor>('UserTwoFactor')\n }\n\n private static async getRecord (userId: User['id']) {\n const Model = await this.getModel()\n\n return await Model.query().where({ userId }).first()\n }\n\n private static async upsert (\n userId: User['id'],\n attributes: Partial<Pick<UserTwoFactor,\n | 'method' | 'secretCiphertext' | 'smsCodeHash' | 'smsCodeExpiresAt'\n | 'smsCodePurpose' | 'enabledAt' | 'recoveryCodeHashes'\n >>\n ) {\n const Model = await this.getModel()\n\n await Model.query().updateOrInsert({ userId }, attributes)\n }\n\n static normalizeMethod (method?: string | null): TwoFactorMethod | null {\n if (method === 'authenticator' || method === 'sms') {\n return method\n }\n\n return null\n }\n\n static maskPhone (phone?: string | null) {\n if (!phone) {\n return null\n }\n\n const normalized = phone.replace(/\\s+/g, '')\n\n if (normalized.length <= 4) {\n return normalized\n }\n\n return `${'*'.repeat(Math.max(normalized.length - 4, 2))}${normalized.slice(-4)}`\n }\n\n static getLabel (user: User) {\n return user.email || `${appName()}:${user.id}`\n }\n\n static getTotp (user: User, secret: string) {\n return Hash.totp(secret, this.getLabel(user), appName())\n }\n\n static generateSecret (size = 20) {\n const bytes = randomBytes(size)\n let secret = ''\n\n for (const byte of bytes) {\n secret += secretAlphabet[byte % secretAlphabet.length]\n }\n\n return secret\n }\n\n static createSetup (user: User, secret?: string): TwoFactorSetup {\n const resolvedSecret = secret ?? this.generateSecret()\n const totp = this.getTotp(user, resolvedSecret)\n\n return {\n secret: resolvedSecret,\n otpauthUrl: totp.toString(),\n }\n }\n\n static verifyCode (user: User, secret: string, code: string) {\n return this.getTotp(user, secret).validate({ token: code, window: 1 }) !== null\n }\n\n static async getMethod (userId: User['id']) {\n const record = await this.getRecord(userId)\n\n return this.normalizeMethod(record?.method)\n }\n\n static async setMethod (userId: User['id'], method: TwoFactorMethod) {\n await this.upsert(userId, { method })\n }\n\n static async getSecret (userId: User['id']) {\n const record = await this.getRecord(userId)\n\n return record?.secretCiphertext ? Encryption.decrypt(record.secretCiphertext) : null\n }\n\n static async setSecret (userId: User['id'], secret: string) {\n await this.upsert(userId, { secretCiphertext: Encryption.encrypt(secret) })\n }\n\n static async clearSecret (userId: User['id']) {\n await this.upsert(userId, { secretCiphertext: null })\n }\n\n static async getEnabledAt (userId: User['id']) {\n const record = await this.getRecord(userId)\n\n return record?.enabledAt?.toISOString() ?? null\n }\n\n static async setEnabledAt (userId: User['id'], enabledAt: string | Date = new Date()) {\n await this.upsert(userId, {\n enabledAt: typeof enabledAt === 'string' ? new Date(enabledAt) : enabledAt,\n })\n }\n\n static async clear (userId: User['id']) {\n const Model = await this.getModel()\n\n await Model.query().where({ userId }).delete()\n }\n\n static generateBackupCodes (count = 8) {\n return Array.from({ length: count }, () => {\n const left = randomBytes(3).toString('hex').slice(0, 4).toUpperCase()\n const right = randomBytes(3).toString('hex').slice(0, 4).toUpperCase()\n\n return `${left}-${right}`\n })\n }\n\n static async hashBackupCodes (codes: string[]) {\n return await Promise.all(codes.map(async code => await Hash.make(code)))\n }\n\n static async readRecoveryCodeHashes (userId: User['id']) {\n const record = await this.getRecord(userId)\n\n return record?.recoveryCodeHashes ?? []\n }\n\n static async writeRecoveryCodeHashes (userId: User['id'], hashes: string[]) {\n await this.upsert(userId, { recoveryCodeHashes: hashes })\n }\n\n static async consumeRecoveryCode (userId: User['id'], recoveryCode: string) {\n const hashes = await this.readRecoveryCodeHashes(userId)\n\n for (const [index, hash] of hashes.entries()) {\n if (await Hash.verify(recoveryCode, hash)) {\n await this.writeRecoveryCodeHashes(\n userId,\n hashes.filter((_, currentIndex) => currentIndex !== index),\n )\n\n return true\n }\n }\n\n return false\n }\n\n static async readStatus (userId: User['id']): Promise<TwoFactorStatus> {\n const record = await this.getRecord(userId)\n const enabledAt = record?.enabledAt?.toISOString() ?? null\n const recoveryCodes = record?.recoveryCodeHashes ?? []\n\n return {\n enabled: !!enabledAt,\n enabledAt,\n method: this.normalizeMethod(record?.method),\n recoveryCodesRemaining: recoveryCodes.length,\n }\n }\n\n static createSmsCode () {\n return Math.floor(100000 + Math.random() * 900000).toString()\n }\n\n static async issueSmsCode (user: User, purpose: SmsCodePurpose): Promise<IssuedSmsCode> {\n if (!(user as TwoFactorUser).phone) {\n throw new Error('A phone number is required to issue a two-factor SMS code.')\n }\n\n const code = this.createSmsCode()\n const smsCodeHash = await Hash.make(code)\n const expiresAt = new Date(Date.now() + smsCodeTtlMinutes() * 60 * 1000)\n\n await this.upsert(user.id, {\n smsCodeHash,\n smsCodeExpiresAt: expiresAt,\n smsCodePurpose: purpose,\n })\n\n return {\n code,\n expiresAt,\n purpose,\n }\n }\n\n static async clearSmsCode (userId: User['id']) {\n await this.upsert(userId, {\n smsCodeHash: null,\n smsCodeExpiresAt: null,\n smsCodePurpose: null,\n })\n }\n\n static async verifySmsCode (userId: User['id'], code: string, purpose: SmsCodePurpose) {\n const record = await this.getRecord(userId)\n\n if (!record?.smsCodeHash || !record.smsCodeExpiresAt || record.smsCodePurpose !== purpose) {\n return false\n }\n\n if (record.smsCodeExpiresAt.getTime() < Date.now()) {\n await this.clearSmsCode(userId)\n\n return false\n }\n\n const isValid = await Hash.verify(code, record.smsCodeHash)\n\n if (isValid) {\n await this.clearSmsCode(userId)\n }\n\n return isValid\n }\n}\n","import { Model } from '@arkstack/database'\n\nexport abstract class PersonalAccessToken extends Model {\n declare id: number\n declare name: string\n declare token: string\n declare abilities: string[]\n declare userId: number\n declare createdAt: Date\n declare expiresAt: Date | null\n declare lastUsedAt: Date | null\n declare deviceInfo: Record<string, unknown> | null\n}\n","import { Model } from '@arkstack/database'\n\nexport abstract class User extends Model {\n declare id: number\n declare email: string\n declare name: string\n declare password: string\n declare createdAt: Date\n declare updatedAt: Date\n\n protected static table?: string | undefined = 'users'\n}","import { Model } from '@arkstack/database'\n\nimport type { SmsCodePurpose, TwoFactorMethod } from '../types/TwoFactor'\nimport type { User } from './User'\n\nexport abstract class UserTwoFactor extends Model {\n declare id: number | string\n declare userId: User['id']\n declare method: TwoFactorMethod | null\n declare secretCiphertext: string | null\n declare smsCodeHash: string | null\n declare smsCodeExpiresAt: Date | null\n declare smsCodePurpose: SmsCodePurpose | null\n declare enabledAt: Date | null\n declare recoveryCodeHashes: string[] | null\n declare createdAt: Date\n declare updatedAt: Date\n\n protected static override table?: string | undefined = 'user_two_factors'\n\n protected casts = {\n recoveryCodeHashes: 'json',\n } as const\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@arkstack/auth",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
- "description": "Authentication package for Arkstack applications, providing utilities for user authentication, password hashing, and two-factor authentication.",
5
+ "description": "Authentication module for Arkstack, providing core authentication and identity features.",
6
6
  "homepage": "https://arkstack.toneflix.net/guide/auth",
7
7
  "repository": {
8
8
  "type": "git",
@@ -37,16 +37,19 @@
37
37
  "dependencies": {
38
38
  "jose": "^6.2.3",
39
39
  "ua-parser-js": "^2.0.9",
40
- "@arkstack/common": "^0.4.3",
41
- "@arkstack/http": "^0.4.3"
40
+ "@arkstack/common": "^0.5.0",
41
+ "@arkstack/http": "^0.5.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@h3ravel/support": "^0.15.11",
45
- "arkormx": "^2.0.7",
46
- "@arkstack/notifications": "^0.4.3"
45
+ "@arkstack/database": "^0.5.0",
46
+ "@arkstack/notifications": "^0.5.0"
47
+ },
48
+ "inlinedDependencies": {
49
+ "otpauth": "9.5.1"
47
50
  },
48
51
  "scripts": {
49
- "build": "tsdown --config-loader unconfig",
52
+ "build": "tsdown --config-loader unrun",
50
53
  "version:patch": "pnpm version patch"
51
54
  }
52
55
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types/TwoFactor.ts","../src/Contracts/UserTwoFactor.ts","../src/Contracts/User.ts","../src/Contracts/PersonalAccessToken.ts","../src/CurrentSession.ts","../src/Contracts/AuthContract.ts","../src/Auth.ts","../src/types/Session.ts","../src/SessionDevice.ts","../src/TwoFactor.ts","../src/Exceptions/AuthenticationException.ts"],"mappings":";;;;;;;;KAAY,eAAA;AAAA,KAEA,cAAA;AAAA,KAEA,cAAA;EACR,MAAA;EACA,UAAA;AAAA;AAAA,KAGQ,eAAA;EACR,OAAA;EACA,SAAA;EACA,MAAA,EAAQ,eAAA;EACR,sBAAA;AAAA;AAAA,KAGQ,aAAA;EACR,IAAA;EACA,SAAA,EAAW,IAAA;EACX,OAAA,EAAS,cAAA;AAAA;;;uBCbS,aAAA,SAAsB,KAAA;EAChC,EAAA;EACA,MAAA,EAAQ,IAAA;EACR,MAAA,EAAQ,eAAA;EACR,gBAAA;EACA,WAAA;EACA,gBAAA,EAAkB,IAAA;EAClB,cAAA,EAAgB,cAAA;EAChB,SAAA,EAAW,IAAA;EACX,kBAAA;EACA,SAAA,EAAW,IAAA;EACX,SAAA,EAAW,IAAA;EAAA,iBAEO,KAAA;EAAA,UAEhB,KAAA;IAAA;;WAID,IAAA,CAAA,GAAS,iBAAA,OAAwB,IAAA;AAAA;;;uBClBxB,IAAA,SAAa,KAAA;EACvB,EAAA;EACA,KAAA;EACA,IAAA;EACA,QAAA;EACA,SAAA,EAAW,IAAA;EACX,SAAA,EAAW,IAAA;EAAA,iBAEF,KAAA;EAAA,SAER,oBAAA,CAAA,GAAyB,eAAA,OAAsB,mBAAA;EAAA,SAE/C,SAAA,CAAA,GAAc,cAAA,OAAqB,aAAA;EAAA,SAEnC,aAAA,CAAA,GAAkB,eAAA,OAAsB,gBAAA;AAAA;;;uBCjB/B,mBAAA,SAA4B,KAAA;EACtC,EAAA;EACA,IAAA;EACA,KAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,EAAW,IAAA;EACX,SAAA,EAAW,IAAA;EACX,UAAA,EAAY,IAAA;EACZ,UAAA,EAAY,MAAA;EAAA,SAEX,IAAA,CAAA,GAAS,iBAAA,OAAwB,IAAA;AAAA;;;cCAjC,cAAA;EAAA,QACW,IAAA;cAAA,IAAA,EAAM,YAAA;EAMpB,OAAA,CAAA,GAAO,OAAA;EAaP,KAAA,CAAA,GAAU,OAAA,CAAQ,mBAAA;AAAA;;;uBCxBN,YAAA;EAAA,SAOT,UAAA,CAAY,GAAA,EAAK,OAAA,CAAQ,IAAA,IAAQ,aAAA,CAAc,IAAA;EAAA,SAQ/C,UAAA,CAAA,GAAe,OAAA,CAAQ,IAAA;EAAA,SAOvB,IAAA,CAAA,GAAS,IAAA;EAAA,SAST,MAAA,CAAQ,KAAA,UAAe,QAAA,WAAmB,OAAA;EAAA,SAS1C,OAAA,CAAS,KAAA,UAAe,QAAA,WAAmB,OAAA,CAAQ,IAAA;EAAA,SASnD,KAAA,CAAO,KAAA,UAAe,QAAA,WAAmB,OAAA,CAAQ,mBAAA;EAAA,SAWjD,oBAAA,CAAsB,IAAA,EAAM,IAAA,EAAM,OAAA,UAAiB,SAAA,YAAqB,OAAA;EAAA,SAUxE,uBAAA,CAAyB,KAAA,UAAe,OAAA,WAAkB,OAAA,CAAQ,IAAA;EAAA,SAQlE,MAAA,CAAQ,KAAA,YAAiB,mBAAA,GAAsB,OAAA;EAAA,SAO/C,KAAA,CAAA,GAAU,OAAA;EAAA,SAOV,cAAA,CAAA,GAAmB,cAAA;EAAA,SAQnB,MAAA,CAAQ,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,mBAAA;EAAA,SAQ7B,cAAA,CAAgB,KAAA,WAAgB,OAAA,CAAQ,IAAA;AAAA;;;cCtGxC,IAAA,SAAa,YAAA;EAAA;mBACL,GAAA,GAAM,OAAA,CAAQ,IAAA;EAAA,QACvB,gBAAA;cAGI,MAAA,WAAiB,GAAA,GAAM,OAAA,CAAQ,IAAA,IAAQ,aAAA,CAAc,IAAA;EAAA,OAa1D,IAAA,CAAM,MAAA,YAAe,IAAA;EAAA,OAUrB,UAAA,CAAY,GAAA,EAAK,OAAA,CAAQ,IAAA,IAAQ,aAAA,CAAc,IAAA,WAAK,IAAA;EAY3D,UAAA,CAAY,GAAA,EAAK,OAAA,CAAQ,IAAA,IAAQ,aAAA,CAAc,IAAA;EAY/C,UAAA,CAAA,GAAe,OAAA,CAAQ,IAAA;EASvB,IAAA,CAAA,GAAS,IAAA;EAWH,MAAA,CAAQ,KAAA,UAAe,QAAA,WAAmB,OAAA;EAa1C,OAAA,CAAS,KAAA,UAAe,QAAA,WAAmB,OAAA,CAAQ,IAAA;EA2BnD,KAAA,CAAO,KAAA,UAAe,QAAA,WAAmB,OAAA,CAAQ,mBAAA;EAejD,oBAAA,CAAsB,IAAA,EAAM,IAAA,EAAM,OAAA,UAAiB,SAAA,YAA4B,OAAA;EAgB/E,uBAAA,CAAyB,KAAA,UAAe,OAAA,WAAkB,OAAA,CAAQ,IAAA;EAgClE,MAAA,CAAQ,KAAA,YAAiB,mBAAA,GAAsB,OAAA;EA2B/C,KAAA,CAAA,GAAU,OAAA;EAShB,cAAA,CAAA,GAAc,cAAA;EAUR,MAAA,CAAQ,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,mBAAA;EAAA,QA0BrB,iBAAA;EA6DR,cAAA,CAAgB,KAAA,WAAgB,OAAA,CAAQ,IAAA;EAAA,QAgDhC,SAAA;EAAA,QAgBA,SAAA;EAAA,QAUN,SAAA;EAAA,QAWM,YAAA;AAAA;;;UCzZD,iBAAA,SAA0B,MAAA;EACvC,OAAA;EACA,EAAA;EACA,SAAA;EACA,UAAA;EACA,UAAA;EACA,YAAA;EACA,KAAA;EACA,QAAA;EACA,SAAA;EACA,SAAA;AAAA;AAAA,KAGQ,gBAAA;EACR,UAAA;EACA,YAAA;EACA,KAAA;EACA,QAAA;EACA,EAAA;EACA,SAAA;EACA,UAAA,GAAa,iBAAA;AAAA;;;cCjBJ,aAAA;EAAA,wBACe,oBAAA;EAAA,OAiBjB,WAAA,CAAa,GAAA,GAAM,OAAA,GAAU,iBAAA;EAAA,OAyB7B,cAAA,CAAgB,UAAA,GAAa,MAAA;EAAA,OA0C7B,YAAA,CAAc,UAAA,GAAa,MAAA;EAAA,OAqB3B,OAAA,CAAS,IAAA,GAAO,MAAA,0BAAgC,KAAA,GAAQ,MAAA;EAAA,eAiBhD,aAAA;EAAA,eAYA,UAAA;EAAA,eAWA,aAAA;EAAA,eAwBA,mBAAA;EAAA,eAcA,eAAA;EAAA,eAgBA,aAAA;EAAA,eAqBA,QAAA;EAAA,eAiBA,gBAAA;AAAA;;;cCnON,SAAA;EAAA,eACY,QAAA;EAAA,eAIA,SAAA;EAAA,eAMA,MAAA;EAAA,OAYd,eAAA,CAAiB,MAAA,mBAAyB,eAAA;EAAA,OAQ1C,SAAA,CAAW,KAAA;EAAA,OAcX,QAAA,CAAU,IAAA,EAAM,IAAA;EAAA,OAIhB,OAAA,CAAS,IAAA,EAAM,IAAA,EAAM,MAAA,WAAF,OAAA,CAAgB,IAAA;EAAA,OAInC,cAAA,CAAgB,IAAA;EAAA,OAWhB,WAAA,CAAa,IAAA,EAAM,IAAA,EAAM,MAAA,YAAkB,cAAA;EAAA,OAU3C,UAAA,CAAY,IAAA,EAAM,IAAA,EAAM,MAAA,UAAgB,IAAA;EAAA,OAIlC,SAAA,CAAW,MAAA,EAAQ,IAAA,SAAU,OAAA,CAAA,eAAA;EAAA,OAM7B,SAAA,CAAW,MAAA,EAAQ,IAAA,QAAY,MAAA,EAAQ,eAAA,GAAe,OAAA;EAAA,OAItD,SAAA,CAAW,MAAA,EAAQ,IAAA,SAAU,OAAA;EAAA,OAM7B,SAAA,CAAW,MAAA,EAAQ,IAAA,QAAY,MAAA,WAAc,OAAA;EAAA,OAI7C,WAAA,CAAa,MAAA,EAAQ,IAAA,SAAU,OAAA;EAAA,OAI/B,YAAA,CAAc,MAAA,EAAQ,IAAA,SAAU,OAAA;EAAA,OAMhC,YAAA,CAAc,MAAA,EAAQ,IAAA,QAAY,SAAA,YAAoB,IAAA,GAAiB,OAAA;EAAA,OAMvE,KAAA,CAAO,MAAA,EAAQ,IAAA,SAAU,OAAA;EAAA,OAM/B,mBAAA,CAAqB,KAAA;EAAA,OASf,eAAA,CAAiB,KAAA,aAAe,OAAA;EAAA,OAIhC,sBAAA,CAAwB,MAAA,EAAQ,IAAA,SAAU,OAAA;EAAA,OAM1C,uBAAA,CAAyB,MAAA,EAAQ,IAAA,QAAY,MAAA,aAAgB,OAAA;EAAA,OAI7D,mBAAA,CAAqB,MAAA,EAAQ,IAAA,QAAY,YAAA,WAAoB,OAAA;EAAA,OAiB7D,UAAA,CAAY,MAAA,EAAQ,IAAA,SAAa,OAAA,CAAQ,eAAA;EAAA,OAa/C,aAAA,CAAA;EAAA,OAIM,YAAA,CAAc,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAAA,OAsB5D,YAAA,CAAc,MAAA,EAAQ,IAAA,SAAU,OAAA;EAAA,OAQhC,aAAA,CAAe,MAAA,EAAQ,IAAA,QAAY,IAAA,UAAc,OAAA,EAAS,cAAA,GAAc,OAAA;AAAA;;;cC1N5E,uBAAA,SAAgC,SAAA;EAAA;EAIzC,UAAA;EACA,IAAA;cAGI,OAAA,WACA,GAAA;IACI,GAAA,GAAM,OAAA,GAAU,aAAA;IAChB,GAAA,GAAM,QAAA,GAAW,cAAA;IACjB,MAAA;IACA,MAAA,GAAS,MAAA;EAAA;EAgBjB,MAAA,CAAA,GAAW,MAAA;AAAA"}