@stacksjs/defaults 0.74.55 → 0.74.56

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.
@@ -26,12 +26,17 @@ export default new Action({
26
26
  // rather than trusting the bearer token alone (a stolen/leaked
27
27
  // token shouldn't be enough to turn off the second factor it's
28
28
  // meant to help guard against).
29
- const confirmed = await Auth.validate({ email: user.email, password })
29
+ // Bind reconfirmation to the same account and password version while
30
+ // disabling. A concurrent password reset must invalidate this request.
31
+ const confirmed = await Auth.withVerifiedCredentials({ email: user.email, password }, async (verifiedUser) => {
32
+ if (String(verifiedUser.id) !== String(user.id))
33
+ return false
34
+ await disableTwoFactor(user.id as number)
35
+ return true
36
+ })
30
37
  if (!confirmed)
31
38
  return response.unauthorized('Incorrect password')
32
39
 
33
- await disableTwoFactor(user.id as number)
34
-
35
40
  return response.json({ enabled: false })
36
41
  },
37
42
  })
@@ -144,7 +144,34 @@ export function createCsrfCookie(req: Request, minted?: string): string {
144
144
  return `${CSRF_COOKIE_PREFIX}${token}${suffix}`
145
145
  }
146
146
 
147
+ /**
148
+ * Whether a browser could go on to use a CSRF token from this response.
149
+ *
150
+ * A page carries the forms that submit it, and an API answer is what an SPA
151
+ * reads before its next request. A stylesheet, script, font or image is
152
+ * neither, and a cookie on one is worse than useless: a response that sets a
153
+ * cookie is one no shared cache will store, so seeding every static file kept
154
+ * the site's whole asset set out of the CDN (every file came back
155
+ * `cf-cache-status: BYPASS`). A cacheable file carrying a per-visitor token is
156
+ * also the shape of a leak, should any cache in the path store it anyway.
157
+ *
158
+ * No content type at all (a redirect, an empty answer) keeps the old
159
+ * behaviour: it says nothing about what the browser is looking at.
160
+ */
161
+ export function responseMayUseCsrfToken(response: Response): boolean {
162
+ const type = (response.headers.get('content-type') || '').split(';')[0].trim().toLowerCase()
163
+ if (!type)
164
+ return true
165
+ return type === 'text/html'
166
+ || type === 'application/xhtml+xml'
167
+ || type === 'application/json'
168
+ || type.endsWith('+json')
169
+ }
170
+
147
171
  export function seedCsrfCookieIfMissing(req: Request, response: Response, minted?: string, responseHasNoCookies = false): Response {
172
+ if (!responseMayUseCsrfToken(response))
173
+ return response
174
+
148
175
  // A token the router minted before rendering wins over "the header already
149
176
  // has one", because it put that value in the header itself - and the page
150
177
  // has already embedded it in every form it drew. Generating a second token
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.74.55",
5
+ "version": "0.74.56",
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.55",
5
+ "version": "0.74.56",
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.55",
58
+ "@stacksjs/mobile": "^0.74.56",
59
59
  "@stacksjs/sanitizer": "^0.2.113",
60
60
  "ts-qr-codes": "^0.1.8"
61
61
  }