@arkstack/auth 0.3.15

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Toneflix Technologies Limited
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # @arkstack/auth
2
+
3
+ Authentication package for Arkstack applications.
4
+
5
+ `@arkstack/auth` provides the framework-neutral auth service used by Arkstack runtime drivers. It supports credential verification, JWT-backed personal access tokens, temporary purpose-bound tokens, current-session lookup, and auth-specific exceptions.
6
+
7
+ ## Usage
8
+
9
+ ```ts
10
+ import { Auth } from '@arkstack/auth';
11
+
12
+ const auth = Auth.make();
13
+ const token = await auth.login(email, password);
14
+ const user = await auth.authorizeToken(token.token);
15
+ ```
16
+
17
+ Auth resolves your app's `User` and `PersonalAccessToken` models with `getModel()` from `@arkstack/common`.
18
+
19
+ Driver middleware lives in the runtime packages:
20
+
21
+ ```ts
22
+ import { auth } from '@arkstack/driver-express/middlewares';
23
+ import { auth as h3Auth } from '@arkstack/driver-h3/middlewares';
24
+ ```
25
+
26
+ See the documentation Authentication guide for the full setup.
@@ -0,0 +1,135 @@
1
+ import { Exception } from "@arkstack/common";
2
+ import { Request, RequestSource, Response, ResponseSource } from "@arkstack/http";
3
+ import { Model } from "arkormx";
4
+
5
+ //#region src/Contracts/PersonalAccessToken.d.ts
6
+ declare abstract class PersonalAccessToken extends Model {
7
+ id: number;
8
+ name: string;
9
+ token: string;
10
+ abilities: string[];
11
+ userId: number;
12
+ createdAt: Date;
13
+ expiresAt: Date | null;
14
+ lastUsedAt: Date | null;
15
+ deviceInfo: Record<string, unknown> | null;
16
+ }
17
+ //#endregion
18
+ //#region src/CurrentSession.d.ts
19
+ declare class CurrentSession {
20
+ private auth;
21
+ constructor(auth: AuthContract);
22
+ destroy(): Promise<void>;
23
+ token(): Promise<PersonalAccessToken | null>;
24
+ }
25
+ //#endregion
26
+ //#region src/Contracts/User.d.ts
27
+ declare abstract class User extends Model {
28
+ id: number;
29
+ name: string;
30
+ email: string;
31
+ password: string;
32
+ protected static table?: string | undefined;
33
+ }
34
+ //#endregion
35
+ //#region src/Contracts/AuthContract.d.ts
36
+ declare abstract class AuthContract {
37
+ abstract setRequest(req: Request<User> | RequestSource<User>): this;
38
+ abstract getRequest(): Request<User> | undefined;
39
+ abstract user(): User | null;
40
+ abstract verify(email: string, password: string): Promise<boolean>;
41
+ abstract attempt(email: string, password: string): Promise<User>;
42
+ abstract login(email: string, password: string): Promise<PersonalAccessToken>;
43
+ abstract createTemporaryToken(user: User, purpose: string, expiresIn?: string): Promise<string>;
44
+ abstract authorizeTemporaryToken(token: string, purpose: string): Promise<User>;
45
+ abstract logout(token?: string | PersonalAccessToken): Promise<void>;
46
+ abstract check(): Promise<boolean>;
47
+ abstract currentSession(): CurrentSession;
48
+ abstract create(user: User): Promise<PersonalAccessToken>;
49
+ abstract authorizeToken(token: string): Promise<User>;
50
+ }
51
+ //#endregion
52
+ //#region src/Auth.d.ts
53
+ declare class Auth extends AuthContract {
54
+ #private;
55
+ protected static req?: Request<User>;
56
+ private configuredSecret?;
57
+ constructor(secret?: string, req?: Request<User> | RequestSource<User>);
58
+ static make(secret?: string): Auth;
59
+ static setRequest(req: Request<User> | RequestSource<User>): typeof Auth;
60
+ setRequest(req: Request<User> | RequestSource<User>): this;
61
+ getRequest(): Request<User> | undefined;
62
+ user(): User | null;
63
+ verify(email: string, password: string): Promise<boolean>;
64
+ attempt(email: string, password: string): Promise<User>;
65
+ login(email: string, password: string): Promise<PersonalAccessToken>;
66
+ createTemporaryToken(user: User, purpose: string, expiresIn?: string): Promise<string>;
67
+ authorizeTemporaryToken(token: string, purpose: string): Promise<User>;
68
+ logout(token?: string | PersonalAccessToken): Promise<void>;
69
+ check(): Promise<boolean>;
70
+ currentSession(): CurrentSession;
71
+ create(user: User): Promise<PersonalAccessToken>;
72
+ private upsertDeviceToken;
73
+ authorizeToken(token: string): Promise<User>;
74
+ private createJWT;
75
+ private verifyJWT;
76
+ private getSecret;
77
+ private touchSession;
78
+ }
79
+ //#endregion
80
+ //#region src/types/Session.d.ts
81
+ interface SessionDeviceInfo extends Record<string, unknown> {
82
+ browser: string | null;
83
+ os: string | null;
84
+ osVersion: string | null;
85
+ deviceType: 'mobile' | 'tablet' | 'desktop' | 'bot' | 'unknown';
86
+ deviceName: string | null;
87
+ manufacturer: string | null;
88
+ model: string | null;
89
+ platform: string | null;
90
+ ipAddress: string | null;
91
+ userAgent: string | null;
92
+ }
93
+ type AuthAgentPayload = {
94
+ deviceName?: string;
95
+ manufacturer?: string;
96
+ model?: string;
97
+ platform?: string;
98
+ os?: string;
99
+ osVersion?: string;
100
+ deviceType?: SessionDeviceInfo['deviceType'];
101
+ };
102
+ //#endregion
103
+ //#region src/SessionDevice.d.ts
104
+ declare class SessionDevice {
105
+ private static readonly uniqueIdentityFields;
106
+ static fromRequest(req?: Request): SessionDeviceInfo;
107
+ static getDisplayName(deviceInfo?: Record<string, unknown> | null): string;
108
+ static getUniqueKey(deviceInfo?: Record<string, unknown> | null): string | null;
109
+ static matches(left?: Record<string, unknown> | null, right?: Record<string, unknown> | null): boolean;
110
+ private static readUserAgent;
111
+ private static readString;
112
+ private static readAuthAgent;
113
+ private static normalizeDeviceType;
114
+ private static detectIpAddress;
115
+ private static detectBrowser;
116
+ private static detectOs;
117
+ private static detectDeviceType;
118
+ }
119
+ //#endregion
120
+ //#region src/Exceptions/AuthenticationException.d.ts
121
+ declare class AuthenticationException extends Exception {
122
+ #private;
123
+ statusCode: number;
124
+ name: string;
125
+ constructor(message?: string, ctx?: {
126
+ req?: Request | RequestSource;
127
+ res?: Response | ResponseSource;
128
+ status?: number;
129
+ errors?: Record<string, any>;
130
+ });
131
+ errors(): Record<string, any> | undefined;
132
+ }
133
+ //#endregion
134
+ export { Auth, AuthAgentPayload, AuthContract, AuthenticationException, CurrentSession, PersonalAccessToken, SessionDevice, SessionDeviceInfo, User };
135
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,594 @@
1
+ import { Exception, Hash, env, getModel } from "@arkstack/common";
2
+ import { SignJWT, jwtVerify } from "jose";
3
+ import { Request, Response } from "@arkstack/http";
4
+ import { UAParser } from "ua-parser-js";
5
+ import { Model } from "arkormx";
6
+
7
+ //#region src/Contracts/AuthContract.ts
8
+ /**
9
+ * The Auth class provides methods for user authentication, including verifying
10
+ * credentials, logging in, logging out, and managing personal access tokens.
11
+ *
12
+ * @author Legacy (3m1n3nc3)
13
+ */
14
+ var AuthContract = class {};
15
+
16
+ //#endregion
17
+ //#region src/Exceptions/AuthenticationException.ts
18
+ var AuthenticationException = class extends Exception {
19
+ #errors;
20
+ #request;
21
+ #response;
22
+ statusCode = 401;
23
+ name;
24
+ constructor(message = "Authentication failed", ctx) {
25
+ super(message);
26
+ this.name = "AuthenticationException";
27
+ this.statusCode = ctx?.status ?? 401;
28
+ if (ctx) {
29
+ this.#request = Request.from(ctx.req);
30
+ this.#response = Response.from(ctx.res);
31
+ this.#errors = ctx.errors;
32
+ }
33
+ this.#response;
34
+ this.#request;
35
+ }
36
+ errors() {
37
+ return this.#errors;
38
+ }
39
+ };
40
+
41
+ //#endregion
42
+ //#region src/CurrentSession.ts
43
+ /**
44
+ * The CurrentSession class represents the current authentication session and provides
45
+ * methods to manage it, such as destroying the session (logging out) and retrieving
46
+ * the current personal access token. It is used internally by the Auth class to
47
+ * handle session-specific operations.
48
+ *
49
+ * @author Legacy (3m1n3nc3)
50
+ * @since 1.0.0
51
+ * @version 1.0.0
52
+ * @see Auth
53
+ */
54
+ var CurrentSession = class {
55
+ constructor(auth) {
56
+ this.auth = auth;
57
+ }
58
+ /**
59
+ * Destroy the current session's personal access token, effectively
60
+ * logging out the user from this session.
61
+ */
62
+ async destroy() {
63
+ const pat = await this.token();
64
+ if (pat) await this.auth.logout(pat);
65
+ }
66
+ /**
67
+ * Get the current session's personal access token
68
+ *
69
+ * @returns
70
+ */
71
+ async token() {
72
+ if (!this.auth.getRequest()) return null;
73
+ const token = this.auth.getRequest().bearerToken();
74
+ if (!token) return null;
75
+ return await (await getModel("PersonalAccessToken")).query().where({ token }).first();
76
+ }
77
+ };
78
+
79
+ //#endregion
80
+ //#region src/SessionDevice.ts
81
+ var SessionDevice = class {
82
+ static uniqueIdentityFields = [
83
+ "deviceName",
84
+ "manufacturer",
85
+ "model",
86
+ "platform",
87
+ "os",
88
+ "osVersion",
89
+ "browser",
90
+ "deviceType"
91
+ ];
92
+ /**
93
+ * Extracts device information from the incoming request to build a SessionDeviceInfo object.
94
+ *
95
+ * @param req The incoming HTTP request object.
96
+ * @returns A SessionDeviceInfo object containing information about the client's device.
97
+ */
98
+ static fromRequest(req) {
99
+ const userAgent = this.readUserAgent(req);
100
+ const ua = new UAParser(userAgent ?? void 0).getResult();
101
+ const ca = this.readAuthAgent(req);
102
+ return {
103
+ browser: this.readString(ua.browser.name) ?? this.detectBrowser(userAgent),
104
+ os: ca?.os ?? this.readString(ua.os.name) ?? this.detectOs(userAgent),
105
+ osVersion: ca?.osVersion ?? this.readString(ua.os.version),
106
+ deviceType: ca?.deviceType ?? this.normalizeDeviceType(ua.device.type) ?? this.detectDeviceType(userAgent),
107
+ deviceName: ca?.deviceName ?? null,
108
+ manufacturer: ca?.manufacturer ?? this.readString(ua.device.vendor),
109
+ model: ca?.model ?? this.readString(ua.device.model),
110
+ platform: ca?.platform ?? null,
111
+ ipAddress: this.detectIpAddress(req),
112
+ userAgent
113
+ };
114
+ }
115
+ /**
116
+ * Generates a human-readable display name for the device based on available information.
117
+ *
118
+ * @param deviceInfo A record containing device information.
119
+ * @returns A string representing the display name of the device.
120
+ */
121
+ static getDisplayName(deviceInfo) {
122
+ const deviceName = this.readString(deviceInfo?.deviceName);
123
+ const manufacturer = this.readString(deviceInfo?.manufacturer);
124
+ const model = this.readString(deviceInfo?.model);
125
+ const browser = this.readString(deviceInfo?.browser);
126
+ const os = this.readString(deviceInfo?.os);
127
+ const deviceType = this.readString(deviceInfo?.deviceType);
128
+ if (manufacturer && model) return model.startsWith(manufacturer) ? model : `${manufacturer} ${model}`;
129
+ if (model) return model;
130
+ if (deviceName) return deviceName;
131
+ if (browser && os) return `${browser} on ${os}`;
132
+ if (os && deviceType && deviceType !== "unknown") return `${os} ${deviceType}`;
133
+ if (browser) return browser;
134
+ return "Unknown device";
135
+ }
136
+ /**
137
+ * Builds a stable device key for matching previously issued sessions to the
138
+ * current request device.
139
+ *
140
+ * @param deviceInfo A record containing device information.
141
+ * @returns A normalized device key or null when there is not enough signal.
142
+ */
143
+ static getUniqueKey(deviceInfo) {
144
+ const parts = this.uniqueIdentityFields.map((field) => [field, this.readString(deviceInfo?.[field])?.toLowerCase()]).filter(([, value]) => !!value);
145
+ if (parts.length < 2) return this.readString(deviceInfo?.userAgent)?.toLowerCase() ?? null;
146
+ return parts.map(([field, value]) => `${field}:${value}`).join("|");
147
+ }
148
+ /**
149
+ * Determines whether two device payloads represent the same device.
150
+ *
151
+ * @param left The first device payload.
152
+ * @param right The second device payload.
153
+ * @returns True when both payloads resolve to the same device key.
154
+ */
155
+ static matches(left, right) {
156
+ const leftKey = this.getUniqueKey(left);
157
+ const rightKey = this.getUniqueKey(right);
158
+ if (!leftKey || !rightKey) return false;
159
+ return leftKey === rightKey;
160
+ }
161
+ /**
162
+ * Safely reads the user agent string from the request headers.
163
+ *
164
+ * @param req
165
+ * @returns
166
+ */
167
+ static readUserAgent(req) {
168
+ const userAgent = req?.header("user-agent");
169
+ return typeof userAgent === "string" ? userAgent : null;
170
+ }
171
+ /**
172
+ * Safely reads a string value, ensuring it's a non-empty string or returns null.
173
+ *
174
+ * @param value
175
+ * @returns
176
+ */
177
+ static readString(value) {
178
+ return typeof value === "string" && value.length > 0 ? value : null;
179
+ }
180
+ /**
181
+ * Reads a specific device-related header from the request
182
+ *
183
+ * @param req
184
+ * @param headerName
185
+ * @returns
186
+ */
187
+ static readAuthAgent(req) {
188
+ const value = req?.header("x-auth-agent");
189
+ if (typeof value !== "string" || value.length < 1) return null;
190
+ try {
191
+ const parsed = JSON.parse(value);
192
+ return {
193
+ deviceName: this.readString(parsed.deviceName) ?? void 0,
194
+ manufacturer: this.readString(parsed.manufacturer) ?? void 0,
195
+ model: this.readString(parsed.model) ?? void 0,
196
+ platform: this.readString(parsed.platform) ?? void 0,
197
+ os: this.readString(parsed.os) ?? void 0,
198
+ osVersion: this.readString(parsed.osVersion) ?? void 0,
199
+ deviceType: this.normalizeDeviceType(parsed.deviceType) ?? void 0
200
+ };
201
+ } catch {
202
+ return null;
203
+ }
204
+ }
205
+ static normalizeDeviceType(value) {
206
+ if (value === "mobile" || value === "tablet" || value === "desktop" || value === "bot" || value === "unknown") return value;
207
+ return null;
208
+ }
209
+ /**
210
+ * Detects the client's IP address from the request, considering common headers set by proxies.
211
+ *
212
+ * @param req
213
+ * @returns
214
+ */
215
+ static detectIpAddress(req) {
216
+ const forwarded = req?.header("x-forwarded-for");
217
+ if (typeof forwarded === "string" && forwarded.length > 0) return forwarded.split(",")[0].trim();
218
+ return req?.ip ?? null;
219
+ }
220
+ /**
221
+ * Detects the browser from the user agent string.
222
+ *
223
+ * @param userAgent
224
+ * @returns
225
+ */
226
+ static detectBrowser(userAgent) {
227
+ if (!userAgent) return null;
228
+ if (/Edg\//i.test(userAgent)) return "Edge";
229
+ if (/OPR\//i.test(userAgent)) return "Opera";
230
+ if (/SamsungBrowser\//i.test(userAgent)) return "Samsung Internet";
231
+ if (/Chrome\//i.test(userAgent) && !/Edg\//i.test(userAgent)) return "Chrome";
232
+ if (/Firefox\//i.test(userAgent)) return "Firefox";
233
+ if (/Safari\//i.test(userAgent) && !/Chrome\//i.test(userAgent)) return "Safari";
234
+ if (/PostmanRuntime\//i.test(userAgent)) return "Postman";
235
+ if (/okhttp\//i.test(userAgent)) return "OkHttp";
236
+ if (/curl\//i.test(userAgent)) return "cURL";
237
+ return null;
238
+ }
239
+ /**
240
+ * Detects the operating system from the user agent string.
241
+ *
242
+ * @param userAgent
243
+ * @returns
244
+ */
245
+ static detectOs(userAgent) {
246
+ if (!userAgent) return null;
247
+ if (/iPhone|iPad|iPod/i.test(userAgent)) return "iOS";
248
+ if (/Android/i.test(userAgent)) return "Android";
249
+ if (/Mac OS X|Macintosh/i.test(userAgent)) return "macOS";
250
+ if (/Windows NT/i.test(userAgent)) return "Windows";
251
+ if (/Linux/i.test(userAgent)) return "Linux";
252
+ return null;
253
+ }
254
+ /**
255
+ * Detects the device type from the user agent string.
256
+ *
257
+ * @param userAgent
258
+ * @returns
259
+ */
260
+ static detectDeviceType(userAgent) {
261
+ if (!userAgent) return "unknown";
262
+ if (/bot|spider|crawl/i.test(userAgent)) return "bot";
263
+ if (/iPad|Tablet/i.test(userAgent)) return "tablet";
264
+ if (/Mobile|iPhone|Android/i.test(userAgent)) return "mobile";
265
+ return "desktop";
266
+ }
267
+ };
268
+
269
+ //#endregion
270
+ //#region src/Auth.ts
271
+ /**
272
+ * The Auth class provides methods for user authentication, including verifying
273
+ * credentials, logging in, logging out, and managing personal access tokens.
274
+ *
275
+ * @author Legacy (3m1n3nc3)
276
+ */
277
+ var Auth = class Auth extends AuthContract {
278
+ static req;
279
+ configuredSecret;
280
+ #user = null;
281
+ constructor(secret, req) {
282
+ super();
283
+ Auth.req = Request.from(req);
284
+ this.configuredSecret = secret;
285
+ }
286
+ /**
287
+ * Create a new instance of the Auth class with an optional secret for JWT
288
+ * signing and verification.
289
+ *
290
+ * @param secret The secret key used for signing and verifying JWTs.
291
+ * @returns A new instance of the Auth class.
292
+ */
293
+ static make(secret) {
294
+ return new Auth(secret);
295
+ }
296
+ /**
297
+ * Set the current HTTP request instance being processed.
298
+ *
299
+ * @param req The HTTP request instance to be set.
300
+ * @returns The Auth class itself for method chaining.
301
+ */
302
+ static setRequest(req) {
303
+ this.req = Request.from(req);
304
+ return this;
305
+ }
306
+ /**
307
+ * Set the current HTTP request instance being processed.
308
+ *
309
+ * @param req The HTTP request instance to be set.
310
+ * @returns The Auth instance itself for method chaining.
311
+ */
312
+ setRequest(req) {
313
+ Auth.req ??= Request.from(req);
314
+ return this;
315
+ }
316
+ /**
317
+ * Get the current HTTP request instance being processed, which may contain
318
+ * user information and other request-specific data relevant to authentication operations.
319
+ *
320
+ * @returns The current HTTP request instance or undefined if not set.
321
+ */
322
+ getRequest() {
323
+ return Auth.req;
324
+ }
325
+ /**
326
+ * Get the currently authenticated user
327
+ *
328
+ * @returns The currently authenticated user or null if not authenticated.
329
+ */
330
+ user() {
331
+ return this.#user;
332
+ }
333
+ /**
334
+ * Verify user credentials
335
+ *
336
+ * @param email The email address of the user.
337
+ * @param password The password of the user.
338
+ * @returns A boolean indicating whether the credentials are valid.
339
+ */
340
+ async verify(email, password) {
341
+ const user = await (await getModel("User")).query().where({ email }).first();
342
+ return !!user && await Hash.verify(password, user.password);
343
+ }
344
+ /**
345
+ * Attempt to authenticate a user with the given email and password.
346
+ *
347
+ * @param email
348
+ * @param password
349
+ * @returns
350
+ */
351
+ async attempt(email, password) {
352
+ const user = await (await getModel("User")).query().where({ email }).first();
353
+ if (!user) throw new AuthenticationException("User account not found", {
354
+ req: Auth.req,
355
+ status: 422,
356
+ errors: { email: ["No account found for this email address"] }
357
+ });
358
+ if (!await Hash.verify(password, user.password)) throw new AuthenticationException("Invalid credentials", {
359
+ req: Auth.req,
360
+ status: 422,
361
+ errors: { password: ["Invalid password"] }
362
+ });
363
+ Auth.req?.setUser(user);
364
+ this.#user = user;
365
+ return user;
366
+ }
367
+ /**
368
+ * Login a user and create a personal access token
369
+ *
370
+ * @param email
371
+ * @param password
372
+ * @returns
373
+ */
374
+ async login(email, password) {
375
+ const user = await this.attempt(email, password);
376
+ return await this.create(user);
377
+ }
378
+ /**
379
+ * Create a temporary token for a user with a specific purpose, such as
380
+ * two-factor authentication.
381
+ *
382
+ * @param user
383
+ * @param purpose
384
+ * @param expiresIn
385
+ * @returns
386
+ */
387
+ async createTemporaryToken(user, purpose, expiresIn = "10m") {
388
+ return await this.createJWT({
389
+ sub: user.id.toString(),
390
+ email: user.email,
391
+ purpose
392
+ }, expiresIn);
393
+ }
394
+ /**
395
+ * Authorize a temporary token and return the associated user if the token is
396
+ * valid and matches the expected purpose.
397
+ *
398
+ * @param token
399
+ * @param purpose
400
+ * @returns
401
+ */
402
+ async authorizeTemporaryToken(token, purpose) {
403
+ const payload = await this.verifyJWT(token);
404
+ if (!payload || payload.purpose !== purpose || !payload.sub) throw new AuthenticationException("Invalid or expired two-factor session", {
405
+ req: Auth.req,
406
+ status: 401
407
+ });
408
+ const user = await (await getModel("User")).query().find(payload.sub);
409
+ if (!user) throw new AuthenticationException("User account not found", {
410
+ req: Auth.req,
411
+ status: 401
412
+ });
413
+ Auth.req?.setUser(user);
414
+ this.#user = user;
415
+ return user;
416
+ }
417
+ /**
418
+ * Logout the currently authenticated user and delete all their personal access tokens
419
+ *
420
+ * @param token
421
+ * @returns
422
+ */
423
+ async logout(token) {
424
+ if (!this.#user && !token) return;
425
+ if (token) if (typeof token === "string") await (await getModel("PersonalAccessToken")).query().where({ token }).delete();
426
+ else await token.delete();
427
+ else await (await getModel("PersonalAccessToken")).query().where({ userId: this.#user.id }).delete();
428
+ this.#user = null;
429
+ }
430
+ /**
431
+ * Check if the user is authenticated
432
+ *
433
+ * @returns
434
+ */
435
+ async check() {
436
+ return !!this.#user;
437
+ }
438
+ /**
439
+ * Get the current session's personal access token
440
+ *
441
+ * @returns
442
+ */
443
+ currentSession() {
444
+ return new CurrentSession(this);
445
+ }
446
+ /**
447
+ * Create a personal access token for a user
448
+ *
449
+ * @param user
450
+ * @returns
451
+ */
452
+ async create(user) {
453
+ const payload = {
454
+ sub: user.id.toString(),
455
+ email: user.email
456
+ };
457
+ Auth.req?.setUser(user);
458
+ const token = await this.createJWT(payload);
459
+ const deviceInfo = SessionDevice.fromRequest(Auth.req);
460
+ const pat = await this.upsertDeviceToken(user, token, deviceInfo);
461
+ await pat.load(["user"]);
462
+ return pat;
463
+ }
464
+ /**
465
+ * Create or replace the personal access token for the same user and device
466
+ * while keeping a single active session record for that device.
467
+ *
468
+ * @param user The authenticated user.
469
+ * @param token The new bearer token to persist.
470
+ * @param deviceInfo The current request's device information.
471
+ */
472
+ async upsertDeviceToken(user, token, deviceInfo) {
473
+ const TokenModel = await getModel("PersonalAccessToken");
474
+ const deviceKey = SessionDevice.getUniqueKey(deviceInfo);
475
+ const payload = {
476
+ abilities: [],
477
+ token,
478
+ name: SessionDevice.getDisplayName(deviceInfo),
479
+ userId: user.id,
480
+ lastUsedAt: /* @__PURE__ */ new Date()
481
+ };
482
+ if (!deviceKey) return await TokenModel.query().create(payload);
483
+ payload.deviceInfo = deviceInfo;
484
+ const matchingSessions = (await TokenModel.query().where({ userId: user.id }).get()).all().filter((session) => SessionDevice.matches(session.deviceInfo, deviceInfo)).sort((left, right) => {
485
+ const leftTime = (left.lastUsedAt ?? left.createdAt).getTime();
486
+ return (right.lastUsedAt ?? right.createdAt).getTime() - leftTime;
487
+ });
488
+ if (matchingSessions.length < 1) return await TokenModel.query().create(payload);
489
+ const [currentSession, ...duplicateSessions] = matchingSessions;
490
+ if (duplicateSessions.length > 0) await Promise.all(duplicateSessions.map(async (session) => await session.delete()));
491
+ await TokenModel.query().where({ id: currentSession.id }).update(payload);
492
+ currentSession.token = payload.token;
493
+ currentSession.name = payload.name;
494
+ currentSession.userId = payload.userId;
495
+ currentSession.deviceInfo = payload.deviceInfo;
496
+ currentSession.lastUsedAt = payload.lastUsedAt;
497
+ return currentSession;
498
+ }
499
+ /**
500
+ * Authorize a token and return the associated user
501
+ *
502
+ * @param token
503
+ * @returns
504
+ */
505
+ async authorizeToken(token) {
506
+ const payload = await this.verifyJWT(token);
507
+ if (!payload) throw new AuthenticationException("Invalid or expired session", {
508
+ req: Auth.req,
509
+ status: 401
510
+ });
511
+ const pat = await (await getModel("PersonalAccessToken")).query().where({ token }).first();
512
+ if (!pat) throw new AuthenticationException("Invalid or expired access token", {
513
+ req: Auth.req,
514
+ status: 401
515
+ });
516
+ const user = await (await getModel("User")).query().find(payload.sub);
517
+ if (!user) throw new AuthenticationException("User account not found", {
518
+ req: Auth.req,
519
+ status: 401
520
+ });
521
+ Auth.req?.setUser(user);
522
+ this.touchSession(pat).catch((error) => {
523
+ if (env("NODE_ENV") === "development") console.error("Failed to update session activity", error);
524
+ });
525
+ this.#user = user;
526
+ return user;
527
+ }
528
+ /**
529
+ * Create a JWT token
530
+ *
531
+ * @param payload
532
+ * @returns
533
+ */
534
+ async createJWT(payload, expiresIn = env("JWT_EXPIRES_IN", "1h")) {
535
+ return await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(expiresIn).sign(new TextEncoder().encode(this.getSecret()));
536
+ }
537
+ /**
538
+ * Verify a JWT token
539
+ *
540
+ * @param token
541
+ * @returns
542
+ */
543
+ async verifyJWT(token) {
544
+ try {
545
+ const { payload } = await jwtVerify(token, new TextEncoder().encode(this.getSecret()));
546
+ return payload;
547
+ } catch {
548
+ return null;
549
+ }
550
+ }
551
+ getSecret() {
552
+ return this.configuredSecret ?? env("JWT_SECRET", "default_secret");
553
+ }
554
+ /**
555
+ * Update the last used timestamp and device information of a personal
556
+ * access token to keep the session active and reflect the latest device details.
557
+ *
558
+ * @param pat The personal access token to update.
559
+ * @returns A promise that resolves when the update is complete.
560
+ */
561
+ async touchSession(pat) {
562
+ const now = /* @__PURE__ */ new Date();
563
+ const currentDeviceInfo = SessionDevice.fromRequest(Auth.req);
564
+ const shouldUpdateLastUsedAt = !pat.lastUsedAt || now.getTime() - pat.lastUsedAt.getTime() > 300 * 1e3;
565
+ const hasDeviceInfo = !!pat.deviceInfo;
566
+ const currentDisplayName = SessionDevice.getDisplayName(currentDeviceInfo);
567
+ const storedDisplayName = SessionDevice.getDisplayName(pat.deviceInfo);
568
+ const shouldRefreshDeviceInfo = !hasDeviceInfo || storedDisplayName !== currentDisplayName;
569
+ if (!shouldUpdateLastUsedAt && !shouldRefreshDeviceInfo) return;
570
+ const payload = { lastUsedAt: now };
571
+ if (shouldRefreshDeviceInfo) {
572
+ payload.deviceInfo = currentDeviceInfo;
573
+ payload.name = currentDisplayName;
574
+ }
575
+ await (await getModel("PersonalAccessToken")).query().where({ id: pat.id }).update(payload);
576
+ pat.lastUsedAt = now;
577
+ if (payload.deviceInfo !== void 0) pat.deviceInfo = payload.deviceInfo;
578
+ if (payload.name !== void 0) pat.name = payload.name;
579
+ }
580
+ };
581
+
582
+ //#endregion
583
+ //#region src/Contracts/PersonalAccessToken.ts
584
+ var PersonalAccessToken = class extends Model {};
585
+
586
+ //#endregion
587
+ //#region src/Contracts/User.ts
588
+ var User = class extends Model {
589
+ static table = "users";
590
+ };
591
+
592
+ //#endregion
593
+ export { Auth, AuthContract, AuthenticationException, CurrentSession, PersonalAccessToken, SessionDevice, User };
594
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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/Contracts/PersonalAccessToken.ts","../src/Contracts/User.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 await pat.load(['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 { Model } from 'arkormx'\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 'arkormx'\n\nexport abstract class User extends Model {\n declare id: number\n declare name: string\n declare email: string\n declare password: string\n\n protected static table?: string | undefined = 'users'\n}"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@arkstack/auth",
3
+ "version": "0.3.15",
4
+ "type": "module",
5
+ "description": "Authentication package for Arkstack applications, providing utilities for user authentication, password hashing, and two-factor authentication.",
6
+ "homepage": "https://arkstack.toneflix.net",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/arkstack-hq/arkstack.git",
10
+ "directory": "packages/auth"
11
+ },
12
+ "keywords": [
13
+ "authentication",
14
+ "password",
15
+ "hashing",
16
+ "two-factor",
17
+ "2fa",
18
+ "auth",
19
+ "utilities",
20
+ "helpers",
21
+ "logging",
22
+ "configuration",
23
+ "error-handling",
24
+ "validation",
25
+ "arkstack"
26
+ ],
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "exports": {
34
+ ".": "./dist/index.js",
35
+ "./package.json": "./package.json"
36
+ },
37
+ "dependencies": {
38
+ "jose": "^6.2.3",
39
+ "ua-parser-js": "^2.0.9",
40
+ "@arkstack/common": "^0.3.15",
41
+ "@arkstack/http": "^0.3.15"
42
+ },
43
+ "peerDependencies": {
44
+ "@h3ravel/support": "^0.15.11",
45
+ "arkormx": "^2.0.6"
46
+ },
47
+ "scripts": {
48
+ "build": "tsdown --config-loader unconfig",
49
+ "version:patch": "pnpm version patch"
50
+ }
51
+ }