@stacksjs/defaults 0.70.261 → 0.70.263

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.
@@ -46,7 +46,17 @@ export default new Action({
46
46
  to: user?.email,
47
47
  })
48
48
 
49
+ // Same OAuth2-compatible payload LoginAction returns, so a client can
50
+ // code against one shape. Registering used to hand back only `token`,
51
+ // which left brand-new accounts with no refresh token to exchange — they
52
+ // were signed out an hour into their first session while every other
53
+ // user refreshed normally (stacksjs/stacks#2212). The legacy `token`
54
+ // alias stays for backward compatibility.
49
55
  return response.json({
56
+ access_token: result.token,
57
+ refresh_token: result.refreshToken,
58
+ token_type: 'Bearer',
59
+ expires_in: result.expiresIn,
50
60
  token: result.token,
51
61
  user: {
52
62
  id: user?.id,
@@ -1,10 +1,21 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { RateLimiter } from '@stacksjs/auth'
3
+ import { log } from '@stacksjs/logging'
3
4
  import { User } from '@stacksjs/orm'
4
5
  import { job } from '@stacksjs/queue'
5
6
  import { response } from '@stacksjs/router'
6
7
  import { schema } from '@stacksjs/validation'
7
8
 
9
+ /**
10
+ * The one thing this endpoint ever says.
11
+ *
12
+ * Held in a constant because the security property is that EVERY path returns
13
+ * it byte for byte — unknown address, known address, and a mail dispatch that
14
+ * blew up. The moment one branch returns something else, the endpoint becomes
15
+ * an account-existence oracle again (stacksjs/stacks#2214).
16
+ */
17
+ const NEUTRAL_RESPONSE = 'If an account exists for that email address, a password reset link has been sent.'
18
+
8
19
  export default new Action({
9
20
  name: 'SendPasswordResetEmailAction',
10
21
  description: 'Send Password Reset Email',
@@ -15,52 +26,45 @@ export default new Action({
15
26
  },
16
27
  },
17
28
  async handle(request) {
18
- console.log('[Action] SendPasswordResetEmailAction.handle() called')
19
-
20
29
  const email = request.get('email')
21
- console.log('[Action] Email from request:', email)
22
30
 
23
- if (!email) {
24
- console.log('[Action] No email provided, returning 422')
31
+ if (!email)
25
32
  return response.error('Email is required', 422)
26
- }
27
33
 
28
- // Rate limit password reset requests by email
34
+ // Rate limiting is per-email, so it does not slow enumeration ACROSS
35
+ // addresses at all — the shape of the attack this endpoint invites. It
36
+ // stays because it still limits hammering one mailbox, but it is not the
37
+ // control that protects existence; the uniform response below is.
29
38
  const rateLimitKey = `password_reset:${email.toLowerCase()}`
30
- if (await RateLimiter.isRateLimited(rateLimitKey)) {
31
- console.log('[Action] Rate limited for:', rateLimitKey)
39
+ if (await RateLimiter.isRateLimited(rateLimitKey))
32
40
  return response.error('Too many password reset attempts. Please try again later.', 429)
33
- }
34
41
 
35
- // Record the attempt
36
42
  await RateLimiter.recordFailedAttempt(rateLimitKey)
37
- console.log('[Action] Recorded rate limit attempt')
38
43
 
39
- // Check if user exists
40
- console.log('[Action] Looking up user by email...')
41
44
  const user = await User.where('email', email).first()
42
45
 
43
- if (!user) {
44
- console.log('[Action] User not found for email:', email)
45
- return response.error('No account found with this email address.', 404)
46
- }
47
- console.log('[Action] User found:', user.id)
48
-
49
- // Dispatch password reset email job to queue (with 10 second delay)
50
- console.log('[Action] About to dispatch SendPasswordResetEmailJob with 10s delay...')
51
- try {
52
- await job('SendPasswordResetEmailJob', { email })
53
- .onQueue('emails')
54
- .dispatch()
55
- console.log('[Action] Job dispatched successfully with 10s delay!')
56
- }
57
- catch (error) {
58
- console.error('[Action] Failed to dispatch email job for', email)
59
- console.error('[Action] Error:', error)
60
- return response.error('Failed to send password reset email. Please try again later.', 500)
46
+ // No early return for a missing user. It used to answer
47
+ // `404 "No account found with this email address."`, which let anyone test
48
+ // an arbitrary list of addresses for membership against an unauthenticated
49
+ // endpoint. The sibling `PasswordResetAction` already refuses to leak the
50
+ // same fact, so this was an inconsistency between two files in one
51
+ // directory rather than a disagreement about the threat.
52
+ if (user) {
53
+ try {
54
+ await job('SendPasswordResetEmailJob', { email })
55
+ .onQueue('emails')
56
+ .dispatch()
57
+ }
58
+ catch (error) {
59
+ // Swallowed DELIBERATELY, and this is the subtle half. Returning a 500
60
+ // here would reintroduce the oracle whenever the mailer is degraded:
61
+ // an unknown address never reaches dispatch, so it can never 5xx, and
62
+ // "neutral message vs. send failure" becomes the tell. The operator
63
+ // gets the failure in the logs; the caller gets what everyone gets.
64
+ log.error('[password-reset] failed to dispatch reset email', error)
65
+ }
61
66
  }
62
67
 
63
- console.log('[Action] Returning success response')
64
- return response.success('Password reset link has been sent to your email.')
68
+ return response.success(NEUTRAL_RESPONSE)
65
69
  },
66
70
  })
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.70.261",
5
+ "version": "0.70.263",
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.70.261",
5
+ "version": "0.70.263",
6
6
  "description": "The complete managed Stacks application scaffold, including runtime defaults, AI guidance, editor metadata, and npm-backed project support files.",
7
7
  "author": "Chris Breuer",
8
8
  "license": "MIT",