@veloxts/auth 0.6.83 → 0.6.84

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @veloxts/auth
2
2
 
3
+ ## 0.6.84
4
+
5
+ ### Patch Changes
6
+
7
+ - - auth: add simplified guard() function with overloads + fluent builder
8
+ - Updated dependencies
9
+ - @veloxts/core@0.6.84
10
+ - @veloxts/router@0.6.84
11
+
3
12
  ## 0.6.83
4
13
 
5
14
  ### Patch Changes
package/dist/guards.d.ts CHANGED
@@ -18,13 +18,83 @@ import type { AuthContext, GuardDefinition, GuardFunction, User } from './types.
18
18
  */
19
19
  export declare function defineGuard<TContext = unknown>(definition: GuardDefinition<TContext>): GuardDefinition<TContext>;
20
20
  /**
21
- * Creates a simple guard from a check function
21
+ * Fluent guard builder for progressive configuration
22
22
  *
23
+ * Allows building guards step-by-step with method chaining.
24
+ * The builder is compatible with GuardLike, so it can be used
25
+ * directly with `.guard()` on procedures.
26
+ *
27
+ * **Note**: This builder uses mutable internal state. Each method
28
+ * modifies the builder and returns the same instance. See
29
+ * `createGuardBuilder` for usage patterns and caveats.
30
+ */
31
+ export interface GuardBuilder<TContext> {
32
+ /** Guard name for error messages (read-only, set via named()) */
33
+ readonly name: string;
34
+ /** Guard check function (read-only) */
35
+ readonly check: GuardFunction<TContext>;
36
+ /** Custom error message (read-only, set via msg()) */
37
+ readonly message: string | undefined;
38
+ /** HTTP status code on failure (read-only, set via status()) */
39
+ readonly statusCode: number;
40
+ /** Set a descriptive name (used in error messages and debugging) */
41
+ named(name: string): GuardBuilder<TContext>;
42
+ /** Set custom error message shown when guard fails */
43
+ msg(message: string): GuardBuilder<TContext>;
44
+ /** Set HTTP status code returned when guard fails (default: 403) */
45
+ status(code: number): GuardBuilder<TContext>;
46
+ }
47
+ /**
48
+ * Resets the guard counter to zero.
49
+ *
50
+ * This is intended for testing purposes only to ensure deterministic
51
+ * guard naming across test runs. Should not be used in production code.
52
+ *
53
+ * @internal
23
54
  * @example
24
55
  * ```typescript
56
+ * import { _resetGuardCounter } from '@veloxts/auth';
57
+ *
58
+ * beforeEach(() => {
59
+ * _resetGuardCounter();
60
+ * });
61
+ * ```
62
+ */
63
+ export declare function _resetGuardCounter(): void;
64
+ /**
65
+ * Creates a guard with simplified syntax
66
+ *
67
+ * Supports three usage patterns with progressive disclosure:
68
+ *
69
+ * @example Simple check function (returns builder for configuration)
70
+ * ```typescript
71
+ * const isVerified = guard((ctx) => ctx.user?.emailVerified === true)
72
+ * .msg('Email verification required');
73
+ * ```
74
+ *
75
+ * @example Check with message (most common - auto-generates name)
76
+ * ```typescript
77
+ * const isVerified = guard(
78
+ * (ctx) => ctx.user?.emailVerified === true,
79
+ * 'Email verification required'
80
+ * );
81
+ * ```
82
+ *
83
+ * @example Named guard (explicit name for debugging)
84
+ * ```typescript
25
85
  * const isActive = guard('isActive', (ctx) => ctx.user?.status === 'active');
26
86
  * ```
87
+ *
88
+ * @example Full fluent configuration
89
+ * ```typescript
90
+ * const isPremium = guard((ctx) => ctx.user?.subscription === 'premium')
91
+ * .named('isPremium')
92
+ * .msg('Premium subscription required')
93
+ * .status(402);
94
+ * ```
27
95
  */
96
+ export declare function guard<TContext = unknown>(check: GuardFunction<TContext>): GuardBuilder<TContext>;
97
+ export declare function guard<TContext = unknown>(check: GuardFunction<TContext>, message: string): GuardDefinition<TContext>;
28
98
  export declare function guard<TContext = unknown>(name: string, check: GuardFunction<TContext>): GuardDefinition<TContext>;
29
99
  /**
30
100
  * Guard that requires authentication
package/dist/guards.js CHANGED
@@ -24,15 +24,131 @@ export function defineGuard(definition) {
24
24
  };
25
25
  }
26
26
  /**
27
- * Creates a simple guard from a check function
27
+ * Counter for generating unique guard names when not provided
28
+ * @internal
29
+ */
30
+ let guardCounter = 0;
31
+ /**
32
+ * Resets the guard counter to zero.
33
+ *
34
+ * This is intended for testing purposes only to ensure deterministic
35
+ * guard naming across test runs. Should not be used in production code.
28
36
  *
37
+ * @internal
29
38
  * @example
30
39
  * ```typescript
31
- * const isActive = guard('isActive', (ctx) => ctx.user?.status === 'active');
40
+ * import { _resetGuardCounter } from '@veloxts/auth';
41
+ *
42
+ * beforeEach(() => {
43
+ * _resetGuardCounter();
44
+ * });
45
+ * ```
46
+ */
47
+ export function _resetGuardCounter() {
48
+ guardCounter = 0;
49
+ }
50
+ /**
51
+ * Attempts to infer a meaningful name from a guard check function
52
+ * @internal
53
+ */
54
+ function inferGuardName(check) {
55
+ // Try to use function name if it exists and isn't generic
56
+ if (check.name && check.name !== 'check' && check.name !== 'anonymous') {
57
+ return check.name;
58
+ }
59
+ // Fall back to generated name
60
+ return `guard_${++guardCounter}`;
61
+ }
62
+ /**
63
+ * Creates a guard builder instance.
64
+ *
65
+ * The builder uses **mutable internal state** with method chaining that returns
66
+ * the same instance. This is intentional for performance and API simplicity:
67
+ *
68
+ * ```typescript
69
+ * // Each method mutates the builder and returns `this`
70
+ * const myGuard = guard((ctx) => ctx.user?.active)
71
+ * .named('isActive') // Mutates name, returns same builder
72
+ * .msg('User inactive') // Mutates message, returns same builder
73
+ * .status(403); // Mutates statusCode, returns same builder
74
+ *
75
+ * // The builder IS the guard definition (implements GuardLike)
76
+ * // No need to call .build() - use directly with .guard()
77
+ * ```
78
+ *
79
+ * **Important**: Because the builder mutates, avoid patterns like:
80
+ * ```typescript
81
+ * // DON'T do this - both variables reference the same mutable builder
82
+ * const base = guard((ctx) => ctx.user != null);
83
+ * const withMsg = base.msg('Auth required');
84
+ * const withOtherMsg = base.msg('Login needed'); // Overwrites previous msg!
85
+ * ```
86
+ *
87
+ * If you need variations, create separate guards:
88
+ * ```typescript
89
+ * const authRequired = guard((ctx) => ctx.user != null, 'Auth required');
90
+ * const loginNeeded = guard((ctx) => ctx.user != null, 'Login needed');
32
91
  * ```
92
+ *
93
+ * @internal
33
94
  */
34
- export function guard(name, check) {
35
- return defineGuard({ name, check });
95
+ function createGuardBuilder(check, initialName) {
96
+ // Mutable internal state - intentionally not exposed directly
97
+ let guardName = initialName;
98
+ let guardMessage;
99
+ let guardStatusCode = 403;
100
+ const builder = {
101
+ // GuardLike compatible properties (getters for current values)
102
+ get name() {
103
+ return guardName;
104
+ },
105
+ get check() {
106
+ return check;
107
+ },
108
+ get message() {
109
+ return guardMessage;
110
+ },
111
+ get statusCode() {
112
+ return guardStatusCode;
113
+ },
114
+ // Builder methods (return self for chaining)
115
+ named(name) {
116
+ guardName = name;
117
+ return builder;
118
+ },
119
+ msg(message) {
120
+ guardMessage = message;
121
+ return builder;
122
+ },
123
+ status(code) {
124
+ guardStatusCode = code;
125
+ return builder;
126
+ },
127
+ };
128
+ return builder;
129
+ }
130
+ // Implementation
131
+ export function guard(nameOrCheck, checkOrMessage) {
132
+ // Overload 3: Legacy (name, check)
133
+ if (typeof nameOrCheck === 'string' && typeof checkOrMessage === 'function') {
134
+ return defineGuard({ name: nameOrCheck, check: checkOrMessage });
135
+ }
136
+ // Overloads 1 & 2: (check) or (check, message)
137
+ if (typeof nameOrCheck === 'function') {
138
+ const check = nameOrCheck;
139
+ const message = typeof checkOrMessage === 'string' ? checkOrMessage : undefined;
140
+ if (message !== undefined) {
141
+ // Overload 2: Simple form with message - return completed guard
142
+ return defineGuard({
143
+ name: inferGuardName(check),
144
+ check,
145
+ message,
146
+ });
147
+ }
148
+ // Overload 1: Return builder for fluent configuration
149
+ return createGuardBuilder(check, inferGuardName(check));
150
+ }
151
+ throw new Error('Invalid guard arguments: expected (check), (check, message), or (name, check)');
36
152
  }
37
153
  // ============================================================================
38
154
  // Built-in Guards
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { createEnhancedTokenStore, DEFAULT_ALLOWED_ROLES, parseUserRoles, } from
18
18
  export type { AuthenticatedContext, InferNarrowedContext, NarrowingGuard, RoleNarrowedContext, } from './guards-narrowing.js';
19
19
  export { authenticatedNarrow, hasRoleNarrow } from './guards-narrowing.js';
20
20
  export { hashPassword, PasswordHasher, passwordHasher, verifyPassword, } from './hash.js';
21
+ export type { GuardBuilder } from './guards.js';
21
22
  export { allOf, anyOf, authenticated, defineGuard, emailVerified, executeGuard, executeGuards, guard, hasAnyPermission, hasPermission, hasRole, not, userCan, } from './guards.js';
22
23
  export { authorize, can, cannot, clearPolicies, createAdminOnlyPolicy, createOwnerOrAdminPolicy, createPolicyBuilder, createReadOnlyPolicy, definePolicy, getPolicy, registerPolicy, } from './policies.js';
23
24
  export { authMiddleware, clearRateLimitStore, rateLimitMiddleware, } from './middleware.js';
package/dist/index.js CHANGED
@@ -23,9 +23,6 @@ export { authenticatedNarrow, hasRoleNarrow } from './guards-narrowing.js';
23
23
  // Password Hashing
24
24
  // ============================================================================
25
25
  export { hashPassword, PasswordHasher, passwordHasher, verifyPassword, } from './hash.js';
26
- // ============================================================================
27
- // Guards
28
- // ============================================================================
29
26
  export {
30
27
  // Combinators
31
28
  allOf, anyOf,
@@ -35,6 +32,7 @@ authenticated,
35
32
  defineGuard, emailVerified,
36
33
  // Execution
37
34
  executeGuard, executeGuards, guard, hasAnyPermission, hasPermission, hasRole, not, userCan, } from './guards.js';
35
+ // NOTE: _resetGuardCounter is available via '@veloxts/auth/testing' for test isolation
38
36
  // ============================================================================
39
37
  // Policies
40
38
  // ============================================================================
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @veloxts/auth/testing - Internal testing utilities
3
+ *
4
+ * This module exports utilities intended for testing purposes only.
5
+ * These are NOT part of the public API and may change without notice.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { _resetGuardCounter } from '@veloxts/auth/testing';
10
+ *
11
+ * beforeEach(() => {
12
+ * _resetGuardCounter();
13
+ * });
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ * @module @veloxts/auth/testing
18
+ */
19
+ export { _resetGuardCounter } from './guards.js';
20
+ export { clearRateLimitStore } from './middleware.js';
21
+ export { clearPolicies } from './policies.js';
22
+ export { clearAuthRateLimitStore, stopAuthRateLimitCleanup } from './rate-limit.js';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @veloxts/auth/testing - Internal testing utilities
3
+ *
4
+ * This module exports utilities intended for testing purposes only.
5
+ * These are NOT part of the public API and may change without notice.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { _resetGuardCounter } from '@veloxts/auth/testing';
10
+ *
11
+ * beforeEach(() => {
12
+ * _resetGuardCounter();
13
+ * });
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ * @module @veloxts/auth/testing
18
+ */
19
+ // Guard testing utilities
20
+ export { _resetGuardCounter } from './guards.js';
21
+ // Rate limit store clearing (for test isolation)
22
+ export { clearRateLimitStore } from './middleware.js';
23
+ // Policy registry clearing (for test isolation)
24
+ export { clearPolicies } from './policies.js';
25
+ export { clearAuthRateLimitStore, stopAuthRateLimitCleanup } from './rate-limit.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/auth",
3
- "version": "0.6.83",
3
+ "version": "0.6.84",
4
4
  "description": "Authentication and authorization system for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,6 +10,10 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "import": "./dist/index.js"
12
12
  },
13
+ "./testing": {
14
+ "types": "./dist/testing.d.ts",
15
+ "import": "./dist/testing.js"
16
+ },
13
17
  "./adapters": {
14
18
  "types": "./dist/adapters/index.d.ts",
15
19
  "import": "./dist/adapters/index.js"
@@ -57,8 +61,8 @@
57
61
  "dependencies": {
58
62
  "@fastify/cookie": "11.0.2",
59
63
  "fastify": "5.6.2",
60
- "@veloxts/core": "0.6.83",
61
- "@veloxts/router": "0.6.83"
64
+ "@veloxts/router": "0.6.84",
65
+ "@veloxts/core": "0.6.84"
62
66
  },
63
67
  "peerDependencies": {
64
68
  "argon2": ">=0.30.0",
@@ -82,8 +86,8 @@
82
86
  "fastify-plugin": "5.1.0",
83
87
  "typescript": "5.9.3",
84
88
  "vitest": "4.0.16",
85
- "@veloxts/validation": "0.6.83",
86
- "@veloxts/testing": "0.6.83"
89
+ "@veloxts/testing": "0.6.84",
90
+ "@veloxts/validation": "0.6.84"
87
91
  },
88
92
  "keywords": [
89
93
  "velox",