@arkstack/auth 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.d.ts CHANGED
@@ -1,9 +1,19 @@
1
- import type { User } from '.'
2
- import type { Auth } from '.'
1
+ import type { User } from '@arkstack/auth'
2
+ import type { Auth } from '@arkstack/auth'
3
+
4
+ declare module 'node:http' {
5
+ interface IncomingMessage {
6
+ user?: User | undefined;
7
+ auth?: Auth | undefined;
8
+ authUser?: User | undefined;
9
+ authToken?: string | undefined;
10
+ }
11
+ }
3
12
 
4
13
  declare module 'clear-router/types/h3' {
5
14
  interface HttpRequest {
6
15
  user?: User | undefined;
16
+ auth?: CurrentSession | undefined;
7
17
  authUser?: User | undefined;
8
18
  authToken?: string | undefined;
9
19
  }
@@ -12,7 +22,7 @@ declare module 'clear-router/types/h3' {
12
22
  declare module 'clear-router' {
13
23
  interface HttpRequests {
14
24
  user?: User | undefined;
15
- auth?: CurrentSession | undefined;
25
+ auth?: Auth | undefined;
16
26
  authUser?: User | undefined;
17
27
  authToken?: string | undefined;
18
28
  }
package/dist/index.d.ts CHANGED
@@ -5,11 +5,11 @@ import { Model } from "@arkstack/database";
5
5
 
6
6
  //#region src/Contracts/PersonalAccessToken.d.ts
7
7
  declare abstract class PersonalAccessToken extends Model {
8
- id: number;
8
+ [key: string]: any;
9
9
  name: string;
10
10
  token: string;
11
11
  abilities: string[];
12
- userId: number;
12
+ userId: never;
13
13
  createdAt: Date;
14
14
  expiresAt: Date | null;
15
15
  lastUsedAt: Date | null;
@@ -26,7 +26,7 @@ declare class CurrentSession {
26
26
  //#endregion
27
27
  //#region src/Contracts/User.d.ts
28
28
  declare abstract class User extends Model {
29
- id: number;
29
+ [key: string]: any;
30
30
  email: string;
31
31
  name: string;
32
32
  password: string;
@@ -496,7 +496,7 @@ declare class TwoFactor {
496
496
  //#endregion
497
497
  //#region src/Contracts/UserTwoFactor.d.ts
498
498
  declare abstract class UserTwoFactor extends Model {
499
- id: number | string;
499
+ [key: string]: any;
500
500
  userId: User['id'];
501
501
  method: TwoFactorMethod | null;
502
502
  secretCiphertext: string | null;
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 { 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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"}
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 [key: string]: any\n declare name: string\n declare token: string\n declare abilities: string[]\n declare userId: never\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 [key: string]: any\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 [key: string]: any\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":";;;;;;;;;;;;;AAWA,IAAsB,eAAtB,MAAmC;;;ACPnC,IAAa,0BAAb,cAA6C,UAAU;CACnD;CACA;CACA;CACA,aAAqB;CACrB;CAEA,YACI,UAAkB,yBAClB,KAMF;EACE,MAAM,QAAQ;EACd,KAAK,OAAO;EACZ,KAAK,aAAa,KAAK,UAAU;EACjC,IAAI,KAAK;GACL,KAAKA,WAAW,QAAQ,KAAK,IAAI,IAAI;GACrC,KAAKC,YAAY,SAAS,KAAK,IAAI,IAAI;GACvC,KAAKC,UAAU,IAAI;;EAGvB,KAAUD;EACV,KAAUD;;CAGd,SAA2C;EACvC,OAAO,KAAKE;;;;;;;;;;;;;;;;ACnBpB,IAAa,iBAAb,MAA4B;CACJ;CAApB,YAAY,MAA4B;EAApB,KAAA,OAAA;;;;;;CAMpB,MAAM,UAAW;EACb,MAAM,MAAM,MAAM,KAAK,OAAO;EAE9B,IAAI,KACA,MAAM,KAAK,KAAK,OAAO,IAAI;;;;;;;CASnC,MAAM,QAA8C;EAChD,IAAI,CAAC,KAAK,KAAK,YAAY,EACvB,OAAO;EAGX,MAAM,QAAQ,KAAK,KAAK,YAAY,CAAE,aAAa;EAEnD,IAAI,CAAC,OACD,OAAO;EAMX,OAAO,OAFW,MADE,SAAqC,sBAAsB,EACvD,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,OAAO;;;;;AC3ChE,IAAa,gBAAb,MAA2B;CACvB,OAAwB,uBAAuB;EAC3C;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACH;;;;;;;CAQD,OAAO,YAAa,KAAkC;EAClD,MAAM,YAAY,KAAK,cAAc,IAAI;EACzC,MAAM,KAAK,IAAI,SAAS,aAAa,KAAA,EAAU,CAAC,WAAW;EAC3D,MAAM,KAAK,KAAK,cAAc,IAAI;EAElC,OAAO;GACH,SAAS,KAAK,WAAW,GAAG,QAAQ,KAAK,IAAI,KAAK,cAAc,UAAU;GAC1E,IAAI,IAAI,MAAM,KAAK,WAAW,GAAG,GAAG,KAAK,IAAI,KAAK,SAAS,UAAU;GACrE,WAAW,IAAI,aAAa,KAAK,WAAW,GAAG,GAAG,QAAQ;GAC1D,YAAY,IAAI,cAAc,KAAK,oBAAoB,GAAG,OAAO,KAAK,IAAI,KAAK,iBAAiB,UAAU;GAC1G,YAAY,IAAI,cAAc;GAC9B,cAAc,IAAI,gBAAgB,KAAK,WAAW,GAAG,OAAO,OAAO;GACnE,OAAO,IAAI,SAAS,KAAK,WAAW,GAAG,OAAO,MAAM;GACpD,UAAU,IAAI,YAAY;GAC1B,WAAW,KAAK,gBAAgB,IAAI;GACpC;GACH;;;;;;;;CASL,OAAO,eAAgB,YAA6C;EAChE,MAAM,aAAa,KAAK,WAAW,YAAY,WAAW;EAC1D,MAAM,eAAe,KAAK,WAAW,YAAY,aAAa;EAC9D,MAAM,QAAQ,KAAK,WAAW,YAAY,MAAM;EAChD,MAAM,UAAU,KAAK,WAAW,YAAY,QAAQ;EACpD,MAAM,KAAK,KAAK,WAAW,YAAY,GAAG;EAC1C,MAAM,aAAa,KAAK,WAAW,YAAY,WAAW;EAE1D,IAAI,gBAAgB,OAChB,OAAO,MAAM,WAAW,aAAa,GAAG,QAAQ,GAAG,aAAa,GAAG;EAGvE,IAAI,OACA,OAAO;EAGX,IAAI,YACA,OAAO;EAGX,IAAI,WAAW,IACX,OAAO,GAAG,QAAQ,MAAM;EAG5B,IAAI,MAAM,cAAc,eAAe,WACnC,OAAO,GAAG,GAAG,GAAG;EAGpB,IAAI,SACA,OAAO;EAGX,OAAO;;;;;;;;;CAUX,OAAO,aAAc,YAA6C;EAC9D,MAAM,QAAQ,KAAK,qBACd,KAAK,UAAU,CAAC,OAAO,KAAK,WAAW,aAAa,OAAO,EAAE,aAAa,CAAC,CAAU,CACrF,QAAQ,GAAG,WAAW,CAAC,CAAC,MAAM;EAEnC,IAAI,MAAM,SAAS,GAGf,OAF0B,KAAK,WAAW,YAAY,UAAU,EAAE,aAAa,IAEnD;EAGhC,OAAO,MAAM,KAAK,CAAC,OAAO,WAAW,GAAG,MAAM,GAAG,QAAQ,CAAC,KAAK,IAAI;;;;;;;;;CAUvE,OAAO,QAAS,MAAuC,OAAwC;EAC3F,MAAM,UAAU,KAAK,aAAa,KAAK;EACvC,MAAM,WAAW,KAAK,aAAa,MAAM;EAEzC,IAAI,CAAC,WAAW,CAAC,UACb,OAAO;EAGX,OAAO,YAAY;;;;;;;;CASvB,OAAe,cAAe,KAAe;EACzC,MAAM,YAAY,KAAK,OAAO,aAAa;EAE3C,OAAO,OAAO,cAAc,WAAW,YAAY;;;;;;;;CASvD,OAAe,WAAY,OAAgB;EACvC,OAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;;;;;;;;;CAUnE,OAAe,cAAe,KAAwC;EAClE,MAAM,QAAQ,KAAK,OAAO,eAAe;EAEzC,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAC5C,OAAO;EAGX,IAAI;GACA,MAAM,SAA2B,KAAK,MAAM,MAAM;GAElD,OAAO;IACH,YAAY,KAAK,WAAW,OAAO,WAAW,IAAI,KAAA;IAClD,cAAc,KAAK,WAAW,OAAO,aAAa,IAAI,KAAA;IACtD,OAAO,KAAK,WAAW,OAAO,MAAM,IAAI,KAAA;IACxC,UAAU,KAAK,WAAW,OAAO,SAAS,IAAI,KAAA;IAC9C,IAAI,KAAK,WAAW,OAAO,GAAG,IAAI,KAAA;IAClC,WAAW,KAAK,WAAW,OAAO,UAAU,IAAI,KAAA;IAChD,YAAY,KAAK,oBAAoB,OAAO,WAAW,IAAI,KAAA;IAC9D;UACG;GACJ,OAAO;;;CAIf,OAAe,oBAAqB,OAAwD;EACxF,IAAI,UAAU,YAAY,UAAU,YAAY,UAAU,aAAa,UAAU,SAAS,UAAU,WAChG,OAAO;EAGX,OAAO;;;;;;;;CASX,OAAe,gBAAiB,KAAe;EAC3C,MAAM,YAAY,KAAK,OAAO,kBAAkB;EAEhD,IAAI,OAAO,cAAc,YAAY,UAAU,SAAS,GACpD,OAAO,UAAU,MAAM,IAAI,CAAC,GAAG,MAAM;EAGzC,OAAO,KAAK,MAAM;;;;;;;;CAStB,OAAe,cAAe,WAA0B;EACpD,IAAI,CAAC,WAAW,OAAO;EACvB,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO;EACrC,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO;EACrC,IAAI,oBAAoB,KAAK,UAAU,EAAE,OAAO;EAChD,IAAI,YAAY,KAAK,UAAU,IAAI,CAAC,SAAS,KAAK,UAAU,EAAE,OAAO;EACrE,IAAI,aAAa,KAAK,UAAU,EAAE,OAAO;EACzC,IAAI,YAAY,KAAK,UAAU,IAAI,CAAC,YAAY,KAAK,UAAU,EAAE,OAAO;EACxE,IAAI,oBAAoB,KAAK,UAAU,EAAE,OAAO;EAChD,IAAI,YAAY,KAAK,UAAU,EAAE,OAAO;EACxC,IAAI,UAAU,KAAK,UAAU,EAAE,OAAO;EAEtC,OAAO;;;;;;;;CASX,OAAe,SAAU,WAA0B;EAC/C,IAAI,CAAC,WAAW,OAAO;EACvB,IAAI,oBAAoB,KAAK,UAAU,EAAE,OAAO;EAChD,IAAI,WAAW,KAAK,UAAU,EAAE,OAAO;EACvC,IAAI,sBAAsB,KAAK,UAAU,EAAE,OAAO;EAClD,IAAI,cAAc,KAAK,UAAU,EAAE,OAAO;EAC1C,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO;EAErC,OAAO;;;;;;;;CASX,OAAe,iBAAkB,WAA2D;EACxF,IAAI,CAAC,WAAW,OAAO;EACvB,IAAI,oBAAoB,KAAK,UAAU,EAAE,OAAO;EAChD,IAAI,eAAe,KAAK,UAAU,EAAE,OAAO;EAC3C,IAAI,yBAAyB,KAAK,UAAU,EAAE,OAAO;EAErD,OAAO;;;;;;;;;;;ACvOf,IAAa,OAAb,MAAa,aAAa,aAAa;CACnC,OAAiB;CACjB;CACA,QAAqB;CAErB,YAAY,QAAiB,KAA2C;EACpE,OAAO;EACP,KAAK,MAAM,QAAQ,KAAW,IAAI;EAClC,KAAK,mBAAmB;;;;;;;;;CAU5B,OAAO,KAAM,QAAiB;EAC1B,OAAO,IAAI,KAAK,OAAO;;;;;;;;CAS3B,OAAO,WAAY,KAA0C;EACzD,KAAK,MAAM,QAAQ,KAAW,IAAI;EAElC,OAAO;;;;;;;;CASX,WAAY,KAA0C;EAClD,KAAK,QAAQ,QAAQ,KAAW,IAAI;EAEpC,OAAO;;;;;;;;CASX,aAAyC;EACrC,OAAO,KAAK;;;;;;;CAQhB,OAAqB;EACjB,OAAO,KAAKC;;;;;;;;;CAUhB,MAAM,OAAQ,OAAe,UAAoC;EAC7D,MAAM,OAAO,OAAO,MAAM,SAAsB,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,OAAO;EAEzF,OAAO,CAAC,CAAC,QAAQ,MAAM,KAAK,OAAO,UAAU,KAAK,SAAS;;;;;;;;;CAU/D,MAAM,QAAS,OAAe,UAAiC;EAC3D,MAAM,OAAO,OAAO,MAAM,SAAsB,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,OAAO;EAEzF,IAAI,CAAC,MACD,MAAM,IAAI,wBAAwB,0BAA0B;GAAE,KAAK,KAAK;GAAK,QAAQ;GAAK,QAAQ,EAAE,OAAO,CAAC,0CAA0C,EAAE;GAAE,CAAC;EAK/J,IAAI,CAAC,MAFiB,KAAK,OAAO,UAAU,KAAK,SAAS,EAGtD,MAAM,IAAI,wBAAwB,uBAAuB;GAAE,KAAK,KAAK;GAAK,QAAQ;GAAK,QAAQ,EAAE,UAAU,CAAC,mBAAmB,EAAE;GAAE,CAAC;EAGxI,KAAK,KAAK,QAAQ,KAAK;EAEvB,KAAKA,QAAQ;EAEb,OAAO;;;;;;;;;CAUX,MAAM,MAAO,OAAe,UAAgD;EACxE,MAAM,OAAO,MAAM,KAAK,QAAQ,OAAO,SAAS;EAEhD,OAAO,MAAM,KAAK,OAAO,KAAK;;;;;;;;;;;CAYlC,MAAM,qBAAsB,MAAY,SAAiB,YAAoB,OAAwB;EACjG,OAAO,MAAM,KAAK,UAAU;GACxB,KAAK,KAAK,GAAG,UAAU;GACvB,OAAO,KAAK;GACZ;GACH,EAAE,UAAU;;;;;;;;;;CAWjB,MAAM,wBAAyB,OAAe,SAAgC;EAC1E,MAAM,UAAU,MAAM,KAAK,UAAU,MAAM;EAE3C,IAAI,CAAC,WAAW,QAAQ,YAAY,WAAW,CAAC,QAAQ,KACpD,MAAM,IAAI,wBACN,yCACA;GAAE,KAAK,KAAK;GAAK,QAAQ;GAAK,CACjC;EAGL,MAAM,OAAO,OAAO,MAAM,SAAsB,OAAO,EAAE,OAAO,CAAC,KAAK,QAAQ,IAAI;EAElF,IAAI,CAAC,MACD,MAAM,IAAI,wBACN,0BACA;GAAE,KAAK,KAAK;GAAK,QAAQ;GAAK,CACjC;EAGL,KAAK,KAAK,QAAQ,KAAK;EAEvB,KAAKA,QAAQ;EAEb,OAAO;;;;;;;;CASX,MAAM,OAAQ,OAAqD;EAC/D,IAAI,CAAC,KAAKA,SAAS,CAAC,OAChB;EAGJ,IAAI,OACA,IAAI,OAAO,UAAU,UAGjB,OAAM,MAFmB,SAAqC,sBAAsB,EAEnE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ;OAElD,MAAM,MAAM,QAAQ;OAKxB,OAAM,MAFmB,SAAqC,sBAAsB,EAEnE,OAAO,CAAC,MAAM,EAAE,QAAQ,KAAKA,MAAO,IAAI,CAAC,CAAC,QAAQ;EAGvE,KAAKA,QAAQ;;;;;;;CAQjB,MAAM,QAA2B;EAC7B,OAAO,CAAC,CAAC,KAAKA;;;;;;;CAQlB,iBAAkB;EACd,OAAO,IAAI,eAAe,KAAK;;;;;;;;CASnC,MAAM,OAAQ,MAA0C;EACpD,MAAM,UAAsB;GACxB,KAAK,KAAK,GAAG,UAAU;GACvB,OAAO,KAAK;GACf;EAED,KAAK,KAAK,QAAQ,KAAK;EAEvB,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ;EAC3C,MAAM,aAAa,cAAc,YAAY,KAAK,IAAI;EAEtD,MAAM,MAAM,MAAM,KAAK,kBAAkB,MAAM,OAAO,WAAW;EAEjE,IAAI,kBAAkB,QAAQ,KAAK;EAEnC,OAAO;;;;;;;;;;CAWX,MAAc,kBAAmB,MAAY,OAAe,YAA4C;EACpG,MAAM,aAAa,MAAM,SAAqC,sBAAsB;EACpF,MAAM,YAAY,cAAc,aAAa,WAAW;EACxD,MAAM,UAAU;GACZ,WAAW,EAAE;GACb;GACA,MAAM,cAAc,eAAe,WAAW;GAC9C,QAAQ,KAAK;GACb,4BAAY,IAAI,MAAM;GACzB;EASD,IAAI,CAAC,WACD,OAAO,MAAM,WAAW,OAAO,CAAC,OAAO,QAAQ;EAGnD,QAAQ,aAAa;EAGrB,MAAM,oBADoB,MAAM,WAAW,OAAO,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE,KAC5C,CACpC,QAAQ,YAAY,cAAc,QAAQ,QAAQ,YAAY,WAAW,CAAC,CAC1E,MAAM,MAAM,UAAU;GACnB,MAAM,YAAY,KAAK,cAAc,KAAK,WAAW,SAAS;GAG9D,QAFmB,MAAM,cAAc,MAAM,WAAW,SAExC,GAAG;IACrB;EAEN,IAAI,iBAAiB,SAAS,GAC1B,OAAO,MAAM,WAAW,OAAO,CAAC,OAAO,QAAQ;EAGnD,MAAM,CAAC,gBAAgB,GAAG,qBAAqB;EAE/C,IAAI,kBAAkB,SAAS,GAC3B,MAAM,QAAQ,IAAI,kBAAkB,IAAI,OAAO,YAAY,MAAM,QAAQ,QAAQ,CAAC,CAAC;EAGvF,MAAM,WAAW,OAAO,CAAC,MAAM,EAAE,IAAI,eAAe,IAAI,CAAC,CAAC,OAAO,QAAQ;EAEzE,eAAe,QAAQ,QAAQ;EAC/B,eAAe,OAAO,QAAQ;EAC9B,eAAe,SAAS,QAAQ;EAChC,eAAe,aAAa,QAAQ;EACpC,eAAe,aAAa,QAAQ;EAEpC,OAAO;;;;;;;;CASX,MAAM,eAAgB,OAA8B;EAChD,MAAM,UAAU,MAAM,KAAK,UAAU,MAAM;EAE3C,IAAI,CAAC,SACD,MAAM,IAAI,wBACN,8BACA;GAAE,KAAK,KAAK;GAAK,QAAQ;GAAK,CACjC;EAIL,MAAM,MAAM,OAAM,MADO,SAAqC,sBAAsB,EACvD,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,OAAO;EAE7D,IAAI,CAAC,KACD,MAAM,IAAI,wBACN,mCACA;GAAE,KAAK,KAAK;GAAK,QAAQ;GAAK,CACjC;EAGL,MAAM,OAAO,OAAO,MAAM,SAAsB,OAAO,EAAE,OAAO,CAAC,KAAK,QAAQ,IAAK;EAEnF,IAAI,CAAC,MACD,MAAM,IAAI,wBACN,0BACA;GAAE,KAAK,KAAK;GAAK,QAAQ;GAAK,CACjC;EAGL,KAAK,KAAK,QAAQ,KAAK;EAEvB,KAAU,aAAa,IAAI,CAAC,OAAO,UAAU;GACzC,IAAI,IAAI,WAAW,KAAK,eACpB,QAAQ,MAAM,qCAAqC,MAAM;IAE/D;EAEF,KAAKA,QAAQ;EAEb,OAAO;;;;;;;;CASX,MAAc,UAAW,SAAqB,YAAoB,IAAI,kBAAkB,KAAK,EAAmB;EAO5G,OAAO,MANW,IAAI,QAAQ,QAAQ,CACjC,mBAAmB,EAAE,KAAK,SAAS,CAAC,CACpC,aAAa,CACb,kBAAkB,UAAU,CAC5B,KAAK,IAAI,aAAa,CAAC,OAAO,KAAK,WAAW,CAAC,CAAC;;;;;;;;CAWzD,MAAc,UAAW,OAA2C;EAChE,IAAI;GACA,MAAM,EAAE,YAAY,MAAM,UAAU,OAAO,IAAI,aAAa,CAAC,OAAO,KAAK,WAAW,CAAC,CAAC;GAEtF,OAAO;UACH;GACJ,OAAO;;;CAIf,YAA6B;EACzB,OAAO,KAAK,oBAAoB,IAAI,cAAc,iBAAiB;;;;;;;;;CAUvE,MAAc,aAAc,KAA0B;EAClD,MAAM,sBAAM,IAAI,MAAM;EACtB,MAAM,oBAAoB,cAAc,YAAY,KAAK,IAAI;EAC7D,MAAM,yBAAyB,CAAC,IAAI,cAAe,IAAI,SAAS,GAAG,IAAI,WAAW,SAAS,GAAI,MAAS;EACxG,MAAM,gBAAgB,CAAC,CAAC,IAAI;EAC5B,MAAM,qBAAqB,cAAc,eAAe,kBAAkB;EAC1E,MAAM,oBAAoB,cAAc,eAAe,IAAI,WAAW;EACtE,MAAM,0BAA0B,CAAC,iBAAiB,sBAAsB;EAExE,IAAI,CAAC,0BAA0B,CAAC,yBAC5B;EAGJ,MAAM,UAIF,EACA,YAAY,KACf;EAED,IAAI,yBAAyB;GACzB,QAAQ,aAAa;GACrB,QAAQ,OAAO;;EAKnB,OAAM,MAFmB,SAAqC,sBAAsB,EAEnE,OAAO,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC,OAAO,QAAQ;EAE9D,IAAI,aAAa;EAEjB,IAAI,QAAQ,eAAe,KAAA,GACvB,IAAI,aAAa,QAAQ;EAG7B,IAAI,QAAQ,SAAS,KAAA,GACjB,IAAI,OAAO,QAAQ;;;;;ACpb/B,MAAM,iBAAiB;AACvB,MAAM,gBAAgB,IAAI,YAAY,WAAW;AACjD,MAAM,0BAA0B,OAAO,IAAI,8BAA8B,GAAG,CAAC,IAAI;AAEjF,IAAa,YAAb,MAAuB;CACnB,aAAqB,WAAY;EAC7B,OAAO,MAAM,SAA+B,gBAAgB;;CAGhE,aAAqB,UAAW,QAAoB;EAGhD,OAAO,OAAM,MAFO,KAAK,UAAU,EAEhB,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,OAAO;;CAGxD,aAAqB,OACjB,QACA,YAIF;EAGE,OAAM,MAFc,KAAK,UAAU,EAEvB,OAAO,CAAC,eAAe,EAAE,QAAQ,EAAE,WAAW;;CAG9D,OAAO,gBAAiB,QAAgD;EACpE,IAAI,WAAW,mBAAmB,WAAW,OACzC,OAAO;EAGX,OAAO;;CAGX,OAAO,UAAW,OAAuB;EACrC,IAAI,CAAC,OACD,OAAO;EAGX,MAAM,aAAa,MAAM,QAAQ,QAAQ,GAAG;EAE5C,IAAI,WAAW,UAAU,GACrB,OAAO;EAGX,OAAO,GAAG,IAAI,OAAO,KAAK,IAAI,WAAW,SAAS,GAAG,EAAE,CAAC,GAAG,WAAW,MAAM,GAAG;;CAGnF,OAAO,SAAU,MAAY;EACzB,OAAO,KAAK,SAAS,GAAG,SAAS,CAAC,GAAG,KAAK;;CAG9C,OAAO,QAAS,MAAY,QAAgB;EACxC,OAAO,KAAK,KAAK,QAAQ,KAAK,SAAS,KAAK,EAAE,SAAS,CAAC;;CAG5D,OAAO,eAAgB,OAAO,IAAI;EAC9B,MAAM,QAAQ,YAAY,KAAK;EAC/B,IAAI,SAAS;EAEb,KAAK,MAAM,QAAQ,OACf,UAAU,eAAe,OAAO;EAGpC,OAAO;;CAGX,OAAO,YAAa,MAAY,QAAiC;EAC7D,MAAM,iBAAiB,UAAU,KAAK,gBAAgB;EAGtD,OAAO;GACH,QAAQ;GACR,YAJS,KAAK,QAAQ,MAAM,eAIZ,CAAC,UAAU;GAC9B;;CAGL,OAAO,WAAY,MAAY,QAAgB,MAAc;EACzD,OAAO,KAAK,QAAQ,MAAM,OAAO,CAAC,SAAS;GAAE,OAAO;GAAM,QAAQ;GAAG,CAAC,KAAK;;CAG/E,aAAa,UAAW,QAAoB;EACxC,MAAM,SAAS,MAAM,KAAK,UAAU,OAAO;EAE3C,OAAO,KAAK,gBAAgB,QAAQ,OAAO;;CAG/C,aAAa,UAAW,QAAoB,QAAyB;EACjE,MAAM,KAAK,OAAO,QAAQ,EAAE,QAAQ,CAAC;;CAGzC,aAAa,UAAW,QAAoB;EACxC,MAAM,SAAS,MAAM,KAAK,UAAU,OAAO;EAE3C,OAAO,QAAQ,mBAAmB,WAAW,QAAQ,OAAO,iBAAiB,GAAG;;CAGpF,aAAa,UAAW,QAAoB,QAAgB;EACxD,MAAM,KAAK,OAAO,QAAQ,EAAE,kBAAkB,WAAW,QAAQ,OAAO,EAAE,CAAC;;CAG/E,aAAa,YAAa,QAAoB;EAC1C,MAAM,KAAK,OAAO,QAAQ,EAAE,kBAAkB,MAAM,CAAC;;CAGzD,aAAa,aAAc,QAAoB;EAG3C,QAAO,MAFc,KAAK,UAAU,OAAO,GAE5B,WAAW,aAAa,IAAI;;CAG/C,aAAa,aAAc,QAAoB,4BAA2B,IAAI,MAAM,EAAE;EAClF,MAAM,KAAK,OAAO,QAAQ,EACtB,WAAW,OAAO,cAAc,WAAW,IAAI,KAAK,UAAU,GAAG,WACpE,CAAC;;CAGN,aAAa,MAAO,QAAoB;EAGpC,OAAM,MAFc,KAAK,UAAU,EAEvB,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ;;CAGlD,OAAO,oBAAqB,QAAQ,GAAG;EACnC,OAAO,MAAM,KAAK,EAAE,QAAQ,OAAO,QAAQ;GAIvC,OAAO,GAHM,YAAY,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC,aAG1C,CAAC,GAFD,YAAY,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC,aAElC;IACzB;;CAGN,aAAa,gBAAiB,OAAiB;EAC3C,OAAO,MAAM,QAAQ,IAAI,MAAM,IAAI,OAAM,SAAQ,MAAM,KAAK,KAAK,KAAK,CAAC,CAAC;;CAG5E,aAAa,uBAAwB,QAAoB;EAGrD,QAAO,MAFc,KAAK,UAAU,OAAO,GAE5B,sBAAsB,EAAE;;CAG3C,aAAa,wBAAyB,QAAoB,QAAkB;EACxE,MAAM,KAAK,OAAO,QAAQ,EAAE,oBAAoB,QAAQ,CAAC;;CAG7D,aAAa,oBAAqB,QAAoB,cAAsB;EACxE,MAAM,SAAS,MAAM,KAAK,uBAAuB,OAAO;EAExD,KAAK,MAAM,CAAC,OAAO,SAAS,OAAO,SAAS,EACxC,IAAI,MAAM,KAAK,OAAO,cAAc,KAAK,EAAE;GACvC,MAAM,KAAK,wBACP,QACA,OAAO,QAAQ,GAAG,iBAAiB,iBAAiB,MAAM,CAC7D;GAED,OAAO;;EAIf,OAAO;;CAGX,aAAa,WAAY,QAA8C;EACnE,MAAM,SAAS,MAAM,KAAK,UAAU,OAAO;EAC3C,MAAM,YAAY,QAAQ,WAAW,aAAa,IAAI;EACtD,MAAM,gBAAgB,QAAQ,sBAAsB,EAAE;EAEtD,OAAO;GACH,SAAS,CAAC,CAAC;GACX;GACA,QAAQ,KAAK,gBAAgB,QAAQ,OAAO;GAC5C,wBAAwB,cAAc;GACzC;;CAGL,OAAO,gBAAiB;EACpB,OAAO,KAAK,MAAM,MAAS,KAAK,QAAQ,GAAG,IAAO,CAAC,UAAU;;CAGjE,aAAa,aAAc,MAAY,SAAiD;EACpF,IAAI,CAAE,KAAuB,OACzB,MAAM,IAAI,MAAM,6DAA6D;EAGjF,MAAM,OAAO,KAAK,eAAe;EACjC,MAAM,cAAc,MAAM,KAAK,KAAK,KAAK;EACzC,MAAM,YAAY,IAAI,KAAK,KAAK,KAAK,GAAG,mBAAmB,GAAG,KAAK,IAAK;EAExE,MAAM,KAAK,OAAO,KAAK,IAAI;GACvB;GACA,kBAAkB;GAClB,gBAAgB;GACnB,CAAC;EAEF,OAAO;GACH;GACA;GACA;GACH;;CAGL,aAAa,aAAc,QAAoB;EAC3C,MAAM,KAAK,OAAO,QAAQ;GACtB,aAAa;GACb,kBAAkB;GAClB,gBAAgB;GACnB,CAAC;;CAGN,aAAa,cAAe,QAAoB,MAAc,SAAyB;EACnF,MAAM,SAAS,MAAM,KAAK,UAAU,OAAO;EAE3C,IAAI,CAAC,QAAQ,eAAe,CAAC,OAAO,oBAAoB,OAAO,mBAAmB,SAC9E,OAAO;EAGX,IAAI,OAAO,iBAAiB,SAAS,GAAG,KAAK,KAAK,EAAE;GAChD,MAAM,KAAK,aAAa,OAAO;GAE/B,OAAO;;EAGX,MAAM,UAAU,MAAM,KAAK,OAAO,MAAM,OAAO,YAAY;EAE3D,IAAI,SACA,MAAM,KAAK,aAAa,OAAO;EAGnC,OAAO;;;;;AC/Of,IAAsB,sBAAtB,cAAkD,MAAM;;;ACAxD,IAAsB,OAAtB,cAAmC,MAAM;CAQrC,OAAiB,QAA6B;;;;ACLlD,IAAsB,gBAAtB,cAA4C,MAAM;CAa9C,OAA0B,QAA6B;CAEvD,QAAkB,EACd,oBAAoB,QACvB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/auth",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "type": "module",
5
5
  "description": "Authentication module for Arkstack, providing core authentication and identity features.",
6
6
  "homepage": "https://arkstack.toneflix.net/guide/auth",
@@ -37,13 +37,13 @@
37
37
  "dependencies": {
38
38
  "jose": "^6.2.3",
39
39
  "ua-parser-js": "^2.0.9",
40
- "@arkstack/common": "^0.5.2",
41
- "@arkstack/http": "^0.5.2"
40
+ "@arkstack/common": "^0.5.4",
41
+ "@arkstack/http": "^0.5.4"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@h3ravel/support": "^0.15.11",
45
- "@arkstack/database": "^0.5.2",
46
- "@arkstack/notifications": "^0.5.2"
45
+ "@arkstack/database": "^0.5.4",
46
+ "@arkstack/notifications": "^0.5.4"
47
47
  },
48
48
  "inlinedDependencies": {
49
49
  "otpauth": "9.5.1"