@stacksjs/defaults 0.74.52 → 0.74.55

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.
@@ -258,7 +258,7 @@ interface RbacStore { findRoleByName, createRole, deleteRole, getAllRoles, findP
258
258
  - `SessionAuth.logout(sessionId): void`
259
259
  - `SessionAuth.user(sessionId): Promise<UserModel | undefined>`
260
260
  - `SessionAuth.check(sessionId): boolean`
261
- - `SessionAuth.refresh(sessionId, ttlMs?): boolean`
261
+ - `SessionAuth.refresh(sessionId, ttlMs?): boolean`, rejects non-positive or non-finite TTLs without changing the session
262
262
 
263
263
  Internal: in-memory Map with 10k session limit, 5-minute eviction interval, timing-safe password comparison with dummy bcrypt hash.
264
264
 
@@ -86,6 +86,12 @@ const users = await db.selectFrom('users').where('active', '=', true).get()
86
86
 
87
87
  `initializeDbConfig(config)` can be called to update the backing config at runtime.
88
88
 
89
+ For reads which cannot tolerate replication lag, use `db.primary.selectFrom(...)`.
90
+ It stays on the primary when automatic replica routing is enabled, and uses the
91
+ active transaction connection inside a transaction. It does not mark the request
92
+ as a writer or change routing for unrelated reads. Session authentication uses
93
+ this handle so a revoked session cannot authenticate from a stale replica.
94
+
89
95
  ## SQL Template Tag (types.ts)
90
96
 
91
97
  ```typescript
@@ -1,6 +1,5 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { Auth, authCookie, createTwoFactorChallenge, getTwoFactorState } from '@stacksjs/auth'
3
- import { User } from '@stacksjs/orm'
4
3
  import { response } from '@stacksjs/router'
5
4
  import { schema } from '@stacksjs/validation'
6
5
  import { PASSWORD_MAX_LENGTH, PASSWORD_PRESENCE_MESSAGE } from '../../password-policy'
@@ -30,28 +29,27 @@ export default new Action({
30
29
  const email = request.get('email')
31
30
  const password = request.get('password')
32
31
 
33
- // Verify credentials WITHOUT minting tokens yet — if the account
34
- // has TOTP 2FA enabled, no token pack should exist until the code
35
- // is also verified (VerifyTwoFactorLoginAction mints the real
36
- // pack via Auth.loginUsingId, same as the non-2FA path below).
37
- const isValid = await Auth.attempt({ email, password })
38
- if (!isValid)
32
+ // Verify once, then keep the observed password version locked while
33
+ // choosing and issuing the challenge or token. A completed password reset
34
+ // cannot be followed by this old in-flight login minting fresh access.
35
+ const decision = await Auth.withVerifiedCredentials({ email, password }, async (authedUser) => {
36
+ const { enabled: twoFactorEnabled } = await getTwoFactorState(authedUser.id as number)
37
+ if (twoFactorEnabled) {
38
+ return { kind: 'challenge' as const, token: await createTwoFactorChallenge(authedUser.id as number) }
39
+ }
40
+ return { kind: 'login' as const, result: await Auth.loginUsingId(authedUser.id as number) }
41
+ })
42
+ if (!decision)
39
43
  return response.unauthorized('Incorrect email or password')
40
44
 
41
- const authedUser = await User.where('email', '=', email).first()
42
- if (!authedUser)
43
- return response.unauthorized('Incorrect email or password')
44
-
45
- const { enabled: twoFactorEnabled } = await getTwoFactorState(authedUser.id as number)
46
- if (twoFactorEnabled) {
47
- const challengeToken = await createTwoFactorChallenge(authedUser.id as number)
45
+ if (decision.kind === 'challenge') {
48
46
  return response.json({
49
47
  requires_two_factor: true,
50
- challenge_token: challengeToken,
48
+ challenge_token: decision.token,
51
49
  })
52
50
  }
53
51
 
54
- const result = await Auth.loginUsingId(authedUser.id as number)
52
+ const result = decision.result
55
53
  if (!result)
56
54
  return response.unauthorized('Incorrect email or password')
57
55
 
@@ -1,7 +1,7 @@
1
1
  import { timingSafeEqual } from 'node:crypto'
2
- import { HttpError } from '@stacksjs/error-handling'
2
+ import { HttpError } from '@stacksjs/error-handling/http-error'
3
3
  import type { EnhancedRequest } from '@stacksjs/router'
4
- import { Middleware } from '@stacksjs/router'
4
+ import { Middleware } from '@stacksjs/router/middleware'
5
5
 
6
6
  /**
7
7
  * CSRF Protection Middleware (default-on for unsafe methods)
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.74.52",
5
+ "version": "0.74.55",
6
6
  "description": "A modern Stacks development environment.",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/chrisbbreuer",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.52",
5
+ "version": "0.74.55",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "@iconify-json/f7": "^1.2.2",
57
57
  "@iconify-json/hugeicons": "^1.2.27",
58
- "@stacksjs/mobile": "^0.74.52",
58
+ "@stacksjs/mobile": "^0.74.55",
59
59
  "@stacksjs/sanitizer": "^0.2.113",
60
60
  "ts-qr-codes": "^0.1.8"
61
61
  }