@arkstack/auth 0.12.2 → 0.12.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/index.d.ts +1 -2
- package/dist/index.js +0 -2
- package/package.json +4 -4
- package/dist/index.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -209,5 +209,4 @@ declare class AuthenticationException extends Exception {
|
|
|
209
209
|
errors(): Record<string, any> | undefined;
|
|
210
210
|
}
|
|
211
211
|
//#endregion
|
|
212
|
-
export { Auth, AuthContract, AuthenticationException, DeviceAgentPayload, IssuedSmsCode, PersonalAccessToken, Session, SessionDevice, SessionDeviceInfo, SmsCodePurpose, TwoFactor, TwoFactorMethod, TwoFactorSetup, TwoFactorStatus, User, UserTwoFactor, auth };
|
|
213
|
-
//# sourceMappingURL=index.d.ts.map
|
|
212
|
+
export { Auth, AuthContract, AuthenticationException, DeviceAgentPayload, IssuedSmsCode, PersonalAccessToken, Session, SessionDevice, SessionDeviceInfo, SmsCodePurpose, TwoFactor, TwoFactorMethod, TwoFactorSetup, TwoFactorStatus, User, UserTwoFactor, auth };
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkstack/auth",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.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",
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
"jose": "^6.2.3",
|
|
39
39
|
"otpauth": "^9.5.1",
|
|
40
40
|
"ua-parser-js": "^2.0.9",
|
|
41
|
-
"@arkstack/common": "^0.12.
|
|
42
|
-
"@arkstack/http": "^0.12.
|
|
41
|
+
"@arkstack/common": "^0.12.4",
|
|
42
|
+
"@arkstack/http": "^0.12.4"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@h3ravel/support": "^0.15.11",
|
|
46
|
-
"@arkstack/database": "^0.12.
|
|
46
|
+
"@arkstack/database": "^0.12.4"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsdown --config-loader unrun",
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#request","#response","#errors","HttpSession","#user"],"sources":["../src/Contracts/AuthContract.ts","../src/Exceptions/AuthenticationException.ts","../src/Session.ts","../src/SessionDevice.ts","../src/Auth.ts","../src/utils.ts","../src/TwoFactor.ts","../src/Contracts/PersonalAccessToken.ts","../src/Contracts/User.ts","../src/Contracts/UserTwoFactor.ts"],"sourcesContent":["import { Session } from '../Session'\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 session (): Session\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 { Session as HttpSession } from '@arkstack/http'\nimport { getModel } from '@arkstack/common'\n\n/**\n * Represents an authenticated user session.\n *\n * @author 3m1n3nc3\n */\nexport class Session extends HttpSession {\n constructor(private auth: AuthContract, current: HttpSession | undefined = (globalThis as any).session?.()) {\n super(current instanceof HttpSession ? current : undefined)\n }\n\n /**\n * Destroy the current session\n * \n * @returns\n */\n override async destroy () {\n const pat = await this.token()\n\n if (pat) {\n await this.auth.logout(pat)\n }\n\n await super.destroy()\n\n return this\n }\n\n /**\n * Get the current auth session 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\n return await Model.query().where({ token }).first()\n }\n}\n","import { type Request } from '@arkstack/http'\nimport { DeviceAgentPayload, 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.readDeviceAgent(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 readDeviceAgent (req?: Request): DeviceAgentPayload | null {\n const value = req?.header('x-device-agent')\n\n if (typeof value !== 'string' || value.length < 1) {\n return null\n }\n\n try {\n const parsed: DeviceAgentPayload = 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 { Session } from './Session'\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 session () {\n return new Session(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 [session, ...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: session.id }).update(payload)\n\n session.token = payload.token\n session.name = payload.name\n session.userId = payload.userId as never\n session.deviceInfo = payload.deviceInfo\n session.lastUsedAt = payload.lastUsedAt\n\n return session\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 { Auth } from './Auth'\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 * \n * @returns — A new instance of the Auth class.\n */\nexport const auth = (secret?: string | undefined) => Auth.make(secret)","import { Encryption, Hash, env, getModel } from '@arkstack/common'\nimport type { IssuedSmsCode, SmsCodePurpose, TwoFactorMethod, TwoFactorSetup, TwoFactorStatus } from './types/TwoFactor'\n\nimport { Secret } from 'otpauth'\nimport { User } from './Contracts/User'\nimport { UserTwoFactor } from './Contracts/UserTwoFactor'\nimport { randomBytes } from 'node:crypto'\n\ntype TwoFactorUser = User & {\n phone?: string | null\n}\n\n\nexport class TwoFactor {\n static smsCodeTtlMinutes: number = Number(env('TWO_FACTOR_SMS_TTL_MINUTES', 10)) || 10\n\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 /** \n * Build the account label used inside the OTP URI. \n * \n * @param user \n * @returns \n */\n static getLabel (user: User) {\n return user.email || `${env('APP_NAME', 'Arkstack')}:${user.id}`\n }\n\n /** \n * Create the per-user TOTP instance for setup and verification. \n * \n * @param user \n * @param secret \n * @returns \n */\n static getTotp (user: User, secret: string) {\n return Hash.totp(secret, this.getLabel(user), env('APP_NAME', 'Arkstack'))\n }\n\n /** \n * Generate a new shared secret for authenticator-based 2FA. \n * \n * @returns The generated secret in base32 format.\n */\n static generateSecret (size = 20) {\n return new Secret({ size }).base32\n }\n\n /** \n * Build the setup payload returned to the client.\n * \n * @param user The user for whom the setup is being created.\n * @param secret Optional existing secret to use for the setup.\n * @returns An object containing the secret and the OTPAuth URL.\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 /** \n * Verify a 6-digit authenticator code for a user.\n * \n * @param user The user for whom the code is being verified.\n * @param secret The secret used to generate the code.\n * @param code The 6-digit code to verify.\n * @returns True if the code is valid, false otherwise.\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 /** \n * Read the setup secret stored for a user.\n * \n * @param userId The ID of the user.\n * @returns The stored secret, or null if not found.\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 /** \n * Store the setup secret for a user.\n * \n * @param userId The ID of the user.\n * @param secret The secret to store.\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 /** \n * Read the timestamp indicating whether 2FA is enabled.\n * \n * @param userId The ID of the user.\n * @returns The timestamp when 2FA was enabled, or null if not enabled.\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 /** \n * Persist the timestamp marking 2FA as enabled.\n * \n * @param userId The ID of the user.\n * @param enabledAt The timestamp to store.\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 /** \n * Remove all persisted 2FA state for a user.\n * \n * @param userId The ID of the user.\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 /** \n * Generate one-time recovery codes shown when 2FA is enabled.\n * \n * @returns An array of recovery codes.\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 // return Array.from({ length: 8 }, () => {\n // const left = Math.random().toString(36).slice(2, 6).toUpperCase()\n // const right = Math.random().toString(36).slice(2, 6).toUpperCase()\n\n // return `${left}-${right}`\n // })\n }\n\n /** \n * Hash recovery codes before persisting them.\n * \n * @param codes An array of recovery codes to hash.\n * @returns An array of hashed recovery codes.\n */\n static async hashBackupCodes (codes: string[]) {\n return await Promise.all(codes.map(async code => await Hash.make(code)))\n }\n\n /** \n * Read stored recovery-code hashes for a user.\n * \n * @param userId The ID of the user.\n * @returns An array of recovery-code hashes.\n */\n static async readRecoveryCodeHashes (userId: User['id']) {\n const record = await this.getRecord(userId)\n\n return record?.recoveryCodeHashes ?? []\n }\n\n /** \n * Persist recovery-code hashes on the user's dedicated 2FA record.\n * \n * @param userId \n * @param hashes \n */\n static async writeRecoveryCodeHashes (userId: User['id'], hashes: string[]) {\n await this.upsert(userId, { recoveryCodeHashes: hashes })\n }\n\n /** \n * Consume a valid recovery code and invalidate it immediately.\n * \n * @param userId The ID of the user.\n * @param recoveryCode The recovery code to consume.\n * @returns True if the recovery code was valid and consumed, false otherwise.\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 /** \n * Return the public 2FA status payload for a user.\n * \n * @param userId The ID of the user.\n * @returns An object containing the 2FA status and recovery codes remaining.\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 /**\n * Issue a new SMS code for the given user and send it via SMS for the specified purpose.\n * \n * @param user \n * @param purpose \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() + TwoFactor.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 /**\n * Verify a submitted SMS code for a user and purpose, consuming the code if valid.\n * \n * @param userId \n * @param code \n * @param purpose \n * @returns \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":"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"}
|