cabloy 5.1.188 → 5.1.190

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.
Files changed (49) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/CLAUDE.md +1 -0
  4. package/package.json +1 -1
  5. package/repo-docs/backend/runtime-and-flavors.md +1 -1
  6. package/vona/docker-compose-original/config/nginx/conf.d/nginx.conf +2 -2
  7. package/vona/packages-cli/cli/package.json +1 -1
  8. package/vona/packages-cli/cli-set-api/package.json +1 -1
  9. package/vona/packages-cli/cli-set-api/toolsIsolate/test.ts +2 -2
  10. package/vona/packages-vona/vona/package.json +1 -1
  11. package/vona/packages-vona/vona-core/package.json +1 -1
  12. package/vona/packages-vona/vona-core/src/lib/core/application.ts +11 -5
  13. package/vona/packages-vona/vona-core/src/lib/core/context.ts +1 -1
  14. package/vona/packages-vona/vona-core/src/lib/utils/util.ts +7 -2
  15. package/vona/packages-vona/vona-mock/package.json +1 -1
  16. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.ts +4 -4
  17. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productContent.test.ts +10 -3
  18. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +5 -3
  19. package/vona/src/suite/a-home/modules/home-index/src/bean/meta.printTip.ts +1 -1
  20. package/vona/src/suite/a-home/modules/home-user/src/service/account.ts +1 -1
  21. package/vona/src/suite/a-home/modules/home-user/test/accountPasswordReset.test.ts +19 -8
  22. package/vona/src/suite/a-home/modules/home-user/test/accountPasswordSet.test.ts +17 -8
  23. package/vona/src/suite-vendor/a-auth/modules/a-auth/package.json +1 -1
  24. package/vona/src/suite-vendor/a-auth/modules/a-auth/src/controller/passport.ts +4 -2
  25. package/vona/src/suite-vendor/a-auth/modules/a-auth/src/lib/utils.ts +42 -0
  26. package/vona/src/suite-vendor/a-auth/modules/a-auth/src/service/auth.ts +60 -2
  27. package/vona/src/suite-vendor/a-auth/modules/a-auth/test/authCallbackRedirect.test.ts +44 -0
  28. package/vona/src/suite-vendor/a-auth/modules/a-auth/test/oauthRequestContext.test.ts +148 -0
  29. package/vona/src/suite-vendor/a-auth/package.json +1 -1
  30. package/vona/src/suite-vendor/a-cabloy/modules/a-socket/package.json +1 -1
  31. package/vona/src/suite-vendor/a-cabloy/modules/a-socket/src/bean/socketConnection.cors.ts +1 -1
  32. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
  33. package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
  34. package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
  35. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCallback.ts +1 -1
  36. package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
  37. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
  38. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
  39. package/vona/src/suite-vendor/a-pay/package.json +5 -5
  40. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  41. package/vona/src/suite-vendor/a-vona/modules/a-core/src/bean/bean.core.ts +0 -16
  42. package/vona/src/suite-vendor/a-vona/modules/a-core/src/bean/meta.runtime.ts +2 -2
  43. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
  44. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.betterSqlite3.ts +1 -0
  45. package/vona/src/suite-vendor/a-vona/modules/a-security/package.json +1 -1
  46. package/vona/src/suite-vendor/a-vona/modules/a-security/src/bean/middlewareSystem.cors.ts +1 -1
  47. package/vona/src/suite-vendor/a-vona/modules/a-security/test/origin.test.ts +12 -2
  48. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  49. package/zova/pnpm-lock.yaml +16 -16
package/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.188
1
+ 5.1.190
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.190
4
+
5
+ ### Features
6
+
7
+ - Update application and authentication capabilities.
8
+
9
+ ### Bug Fixes
10
+
11
+ - Correct test coverage and authentication request handling.
12
+
13
+ ### Improvements
14
+
15
+ - Simplify `bean.core` by removing obsolete methods.
16
+ - Update application, Passport, OAuth request context, and project documentation.
17
+
18
+ ## 5.1.189
19
+
20
+ ### Features
21
+
22
+ - Add anonymous context creation support.
23
+ - Add home user functionality.
24
+ - Add application host utility access through the context.
25
+ - Add authentication enhancements.
26
+ - Update core feature behavior.
27
+
3
28
  ## 5.1.188
4
29
 
5
30
  ### Bug Fixes
package/CLAUDE.md CHANGED
@@ -88,6 +88,7 @@ Before inventing a custom implementation path:
88
88
  - For replay-safe transient failures, use `@Core.retryable(...)` with an explicit `errorCodes` allowlist. It retries the downstream AOP suffix, so place it closest to the method when it must wrap and retry a `@Core.transaction(...)` boundary; use `ownerOnly: true` for dual-role leaves that must not retry inside a caller-owned transaction; do not retry external side effects or infer retryability from isolation level.
89
89
  - Backend tests that use scoped Vona state must own an `app.bean.executor.mockCtx(...)` boundary; every intentionally competing operation must run in a separate `mockCtx(...)`.
90
90
  - Do not set `TEST_CONCURRENCY=false` for normal `npm run test` or CI runs: an unset value defaults to concurrent execution. For stateful `node:test` suites, declare `{ concurrency: false }` explicitly. Prove business contention by explicitly launching competing operations and asserting their combined durable outcome, never through runner parallelism or scheduling.
91
+ - Unit tests must not temporarily mutate process-global origin or proxy configuration, including `app.config.server.serve.protocol`, `app.config.server.serve.host`, and `app.proxy`. Model request-origin cases with an explicit `req` fixture, and use the established `AppUtil` loopback fallback for requestless local URLs. Do not introduce a broad origin lock to serialize such tests; retain narrowly scoped locks only for genuinely shared domain state.
91
92
  - Unit tests must delete every test-owned persisted resource in `finally`, using precise owned identities and reverse dependency order.
92
93
  - Shared durable test or local-development fixtures must be created through the owning module's `meta.version.ts` `seed()` hook and treated as read-only by tests; the managed seed path starts from a newly recreated database rather than repeating against one database.
93
94
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabloy",
3
- "version": "5.1.188",
3
+ "version": "5.1.190",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [
@@ -133,7 +133,7 @@ Use this page as the runtime-facing overview, then inspect the current app confi
133
133
 
134
134
  When configured, `SERVER_SERVE_PROTOCOL` and `SERVER_SERVE_HOST` define Vona's canonical public origin for backend APIs and backend-hosted SSR sites. If either serve value is not configured, Vona resolves the effective protocol and host from the current proxy-aware request context; background work that needs a stable backend public origin should configure both serve values explicitly.
135
135
 
136
- A browser consumer can be deployed on a different origin and pathname. Account password-set and password-reset delivery therefore does not derive its destination from `SERVER_SERVE_*`, SSR Site metadata, `publicPath`, `siteId`, `Referer`, or browser-selected mount data: Zova supplies the complete token-free absolute public page URL, and Vona validates only HTTP(S), absence of userinfo/query/fragment, and an exact trusted origin. `checkOriginExact` permits the exact current origin when protocol, host, and effective port all match; request or proxy Host values are not a general cross-origin allowlist and cannot authorize lookalike, suffix, alternate-scheme, or alternate-port destinations. Other production consumer origins must exactly match an explicit `a-security:cors` `whiteList` entry. In `dev/test`, exact credential-link authorization additionally accepts different ports only when both API and consumer hostnames are loopback (`localhost`, `127.0.0.1`, or `::1`). Vona preserves the frontend pathname and alone adds the raw token as the `token` URL query parameter. Query transport reaches the initial request; Referrer-Policy and Vona, edge, or APM query-log redaction are intentionally outside this flow's current scope. Normal CORS defaults to an empty `whiteList`; configure `CORS_WHITE_LIST` with the permitted browser origins, or explicitly configure `a-security:cors`. In `dev/test`, loopback requests may cross ports; elsewhere, cross-origin CORS requires an allowed entry. Normal CORS wildcard and suffix matching remain available when configured but are not credential-link authorization.
136
+ A browser consumer can be deployed on a different origin and pathname. Account password-set and password-reset delivery does not derive its destination from SSR Site metadata, `publicPath`, `siteId`, `Referer`, or browser-selected mount data: Zova supplies the complete token-free absolute public page URL, and Vona validates only HTTP(S), absence of userinfo/query/fragment, and an exact trusted origin. Production origin checks supply `app.util.host` as their same-host authority: when configured, `SERVER_SERVE_HOST` is the canonical host; otherwise Vona falls back to the current proxy-aware request host. `checkOriginExact` permits an origin whose protocol, canonical/fallback host, and effective port match; request or proxy Host values are not a general cross-origin allowlist and cannot authorize lookalike, suffix, alternate-scheme, or alternate-port destinations when a canonical host is configured. Other production consumer origins must exactly match an explicit `a-security:cors` `whiteList` entry. This change supplies the canonical host only; `checkOriginExact` continues to obtain the protocol from the active request context. In `dev/test`, exact credential-link authorization additionally accepts different ports only when both API and consumer hostnames are loopback (`localhost`, `127.0.0.1`, or `::1`). Vona preserves the frontend pathname and alone adds the raw token as the `token` URL query parameter. Query transport reaches the initial request; Referrer-Policy and Vona, edge, or APM query-log redaction are intentionally outside this flow's current scope. Normal CORS defaults to an empty `whiteList`; configure `CORS_WHITE_LIST` with the permitted browser origins, or explicitly configure `a-security:cors`. In `dev/test`, loopback requests may cross ports; elsewhere, cross-origin CORS requires an allowed entry. Normal CORS wildcard and suffix matching remain available when configured but are not credential-link authorization.
137
137
 
138
138
  ## Configure browser CORS origins
139
139
 
@@ -12,9 +12,9 @@ server {
12
12
  proxy_http_version 1.1;
13
13
  proxy_set_header X-Real-IP $remote_addr;
14
14
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
15
- proxy_set_header X-Forwarded-Host $http_host;
15
+ proxy_set_header X-Forwarded-Host $host;
16
16
  proxy_set_header X-Forwarded-Proto $scheme;
17
- proxy_set_header Host $http_host;
17
+ proxy_set_header Host $host;
18
18
  proxy_set_header X-NginX-Proxy true;
19
19
  proxy_set_header Upgrade $http_upgrade;
20
20
  proxy_set_header Connection "upgrade";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli",
3
- "version": "1.1.154",
3
+ "version": "1.1.156",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli-set-api",
3
- "version": "1.1.152",
3
+ "version": "1.1.154",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli-set-api",
6
6
  "keywords": [
@@ -99,8 +99,8 @@ async function testRun(projectPath: string, coverage: boolean, patterns: string[
99
99
  .on('test:coverage', data => {
100
100
  outputCoverageReport(data.summary.totals);
101
101
  })
102
- .on('test:pass', t => {
103
- if (t.name === '---done---') {
102
+ .on('test:pass', data => {
103
+ if (data.nesting === 0 && data.name === '---done---') {
104
104
  void closeApplicationOnce();
105
105
  }
106
106
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona",
3
- "version": "5.1.99",
3
+ "version": "5.1.101",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-core",
3
- "version": "5.1.40",
3
+ "version": "5.1.42",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona",
6
6
  "keywords": [
@@ -72,22 +72,28 @@ export class VonaApplication extends KoaApplication {
72
72
  if (req) {
73
73
  request = req;
74
74
  } else {
75
- const host = `localhost:${this.meta.env.SERVER_LISTEN_PORT}`;
75
+ const protocol = 'http';
76
+ const port = this.meta.env.SERVER_LISTEN_PORT;
77
+ const host = port ? `localhost:${port}` : 'localhost';
78
+ const hostname = new URL(`${protocol}://${host}`).hostname;
76
79
  request = {
77
80
  headers: {
78
81
  'host': host,
79
- 'x-forwarded-for': host,
82
+ 'x-forwarded-for': '127.0.0.1',
83
+ 'x-forwarded-host': host,
84
+ 'x-forwarded-proto': protocol,
80
85
  },
81
86
  query: {},
82
87
  querystring: '',
83
88
  host,
84
- hostname: 'localhost',
85
- protocol: 'http',
86
- secure: 'false',
89
+ hostname,
90
+ protocol,
91
+ secure: false,
87
92
  method: 'POST',
88
93
  url: '',
89
94
  path: '',
90
95
  socket: {
96
+ encrypted: false,
91
97
  remoteAddress: '127.0.0.1',
92
98
  remotePort: 7001,
93
99
  },
@@ -127,7 +127,7 @@ export const contextBase: ContextBase = {
127
127
  typeof statusOrOptions === 'number' ? { status: statusOrOptions } : (statusOrOptions ?? {});
128
128
  // checkOrigin
129
129
  if (!url.startsWith('/') && !options.trustedExternal) {
130
- const origin = cast(self.app.bean).security.checkOrigin(url, self.host);
130
+ const origin = cast(self.app.bean).security.checkOrigin(url, self.app.util.host);
131
131
  if (!origin) self.app.throw(403);
132
132
  }
133
133
  // throw
@@ -61,12 +61,17 @@ export class AppUtil extends BeanSimple {
61
61
 
62
62
  get protocol() {
63
63
  const config = this.ctx ? this.ctx.config : this.app.config;
64
- return config.server.serve.protocol || this.ctx?.protocol;
64
+ return config.server.serve.protocol || this.ctx?.protocol || 'http';
65
65
  }
66
66
 
67
67
  get host() {
68
68
  const config = this.ctx ? this.ctx.config : this.app.config;
69
- return config.server.serve.host || this.ctx?.host;
69
+ let host = config.server.serve.host || this.ctx?.host;
70
+ if (!host) {
71
+ const port = this.app.meta.env.SERVER_LISTEN_PORT;
72
+ host = port ? `localhost:${port}` : 'localhost';
73
+ }
74
+ return host;
70
75
  }
71
76
 
72
77
  getAbsoluteUrl(path?: string) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-mock",
3
- "version": "6.1.40",
3
+ "version": "6.1.42",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona mock",
6
6
  "keywords": [
@@ -17,11 +17,11 @@ import type { EntityProduct } from '../entity/product.tsx';
17
17
  import type { ModelProduct } from '../model/product.ts';
18
18
 
19
19
  const serializationRetryOptions = {
20
- retries: 3,
21
- factor: 1,
20
+ retries: 6,
21
+ factor: 2,
22
22
  minTimeout: 10,
23
- maxTimeout: 10,
24
- randomize: false,
23
+ maxTimeout: 160,
24
+ randomize: true,
25
25
  errorCodes: [
26
26
  '40001',
27
27
  'ER_LOCK_DEADLOCK',
@@ -243,7 +243,7 @@ describe('productContent.test.ts', { concurrency: false }, () => {
243
243
  return product.id;
244
244
  });
245
245
 
246
- await Promise.all(
246
+ const results = await Promise.allSettled(
247
247
  markdowns.map(descriptionMarkdown =>
248
248
  runInMockCtx(async () => {
249
249
  await app.scope('commerce-catalog').service.product.update(productId, {
@@ -252,10 +252,17 @@ describe('productContent.test.ts', { concurrency: false }, () => {
252
252
  }),
253
253
  ),
254
254
  );
255
+ assert.deepEqual(
256
+ results.map(result => result.status),
257
+ ['fulfilled', 'fulfilled'],
258
+ );
255
259
 
256
260
  await runInMockCtx(async () => {
257
- const stored = await app.scope('commerce-catalog').model.productContent.get({ productId });
258
- assert.ok(stored);
261
+ const storeds = await app.scope('commerce-catalog').model.productContent.select({
262
+ where: { productId },
263
+ });
264
+ assert.equal(storeds.length, 1);
265
+ const stored = storeds[0]!;
259
266
  assert.ok(markdowns.includes(stored.descriptionMarkdown!));
260
267
  assert.equal(
261
268
  stored.descriptionHtml,
@@ -23,7 +23,9 @@ import 'vona-module-a-ssr';
23
23
  input.candidate.clientName,
24
24
  );
25
25
  const server = _ctx.app.config.server.serve;
26
- const hasPublicServer = !!(server.protocol && server.host);
26
+ const hasCallbackOrigin = _ctx.app.meta.isTest
27
+ ? !!(_ctx.app.util.protocol && _ctx.app.util.host)
28
+ : !!(server.protocol && server.host);
27
29
  if (input.candidate.key === 'paypal') {
28
30
  const options = clientOptions as IPayProviderPaypalClientOptions;
29
31
  return !!(
@@ -31,12 +33,12 @@ import 'vona-module-a-ssr';
31
33
  options.secretCredential.clientSecret &&
32
34
  options.webhookId &&
33
35
  options.merchantReference &&
34
- hasPublicServer
36
+ hasCallbackOrigin
35
37
  );
36
38
  }
37
39
  if (input.candidate.key === 'stripe') {
38
40
  const options = clientOptions as IPayProviderStripeClientOptions;
39
- return !!(options.secretCredential && options.secretWebhook && hasPublicServer);
41
+ return !!(options.secretCredential && options.secretWebhook && hasCallbackOrigin);
40
42
  }
41
43
  return false;
42
44
  },
@@ -8,7 +8,7 @@ export class MetaPrintTip extends BeanBase implements IMetaPrintTipExecute {
8
8
  async execute(): Promise<TypeMetaPrintTipResult> {
9
9
  return {
10
10
  title: 'home',
11
- message: this.bean.core.getAbsoluteUrl(),
11
+ message: this.app.util.getAbsoluteUrl(),
12
12
  };
13
13
  }
14
14
  }
@@ -601,7 +601,7 @@ export class ServiceAccount extends BeanBase {
601
601
  url.password ||
602
602
  url.search ||
603
603
  url.hash ||
604
- this.bean.security.checkOriginExact(url.origin, this.ctx.host) !== url.origin
604
+ this.bean.security.checkOriginExact(url.origin, this.app.util.host) !== url.origin
605
605
  ) {
606
606
  this.app.throw(503, `account ${purpose} consumer URL is invalid`);
607
607
  }
@@ -248,20 +248,29 @@ describe('accountPasswordReset.test.ts', { concurrency: false }, () => {
248
248
  }
249
249
  });
250
250
 
251
- it('allows an exact same-origin consumer without a whitelist entry', async () => {
251
+ it('uses the fallback origin for an exact same-origin consumer without a whitelist entry', async () => {
252
252
  let fixture: IAccountPasswordResetFixture | undefined;
253
253
  let restoreConfig: (() => void) | undefined;
254
254
  try {
255
255
  fixture = await createFixture({ simpleAuth: true });
256
256
  restoreConfig = await configurePasswordReset([]);
257
257
 
258
- const sameOriginConsumerUrl = await app.bean.executor.mockCtx(async () => {
259
- return `${app.ctx.protocol}://${app.ctx.host}/home/user/password-reset`;
260
- });
258
+ const port = app.meta.env.SERVER_LISTEN_PORT;
259
+ const fallbackOrigin = `http://${port ? `localhost:${port}` : 'localhost'}`;
260
+ const sameOriginConsumerUrl = `${fallbackOrigin}/home/user/password-reset`;
261
261
  const issued = await issuePasswordResetLink(fixture, sameOriginConsumerUrl);
262
- assert.equal(issued.origin, new URL(sameOriginConsumerUrl).origin);
262
+ assert.equal(issued.origin, fallbackOrigin);
263
263
  assert.equal(issued.path, '/home/user/password-reset');
264
264
  assert.equal(issued.search, `?token=${issued.token}`);
265
+
266
+ await clearPasswordResetState(fixture.userId);
267
+ await clearPasswordResetRecipientCooldown(fixture.email);
268
+ await requestPasswordReset(
269
+ fixture.email,
270
+ 'https://request.example.test/home/user/password-reset',
271
+ );
272
+ await assertNoPasswordResetState(fixture.userId);
273
+ await assertNoPasswordResetRecipientCooldown(fixture.email);
265
274
  } finally {
266
275
  restoreConfig?.();
267
276
  if (fixture) await removeFixture(fixture);
@@ -706,9 +715,11 @@ async function assertNoPasswordResetRecipientCooldown(email: string) {
706
715
  }
707
716
 
708
717
  async function clearPasswordResetState(userId: string): Promise<void> {
709
- const digest = await app.scope('home-user').cacheRedis.passwordResetCurrent.get(userId as any);
710
- if (digest) await app.scope('home-user').cacheRedis.passwordReset.del(digest);
711
- await app.scope('home-user').cacheRedis.passwordResetCurrent.del(userId as any);
718
+ await app.bean.executor.mockCtx(async () => {
719
+ const digest = await app.scope('home-user').cacheRedis.passwordResetCurrent.get(userId as any);
720
+ if (digest) await app.scope('home-user').cacheRedis.passwordReset.del(digest);
721
+ await app.scope('home-user').cacheRedis.passwordResetCurrent.del(userId as any);
722
+ });
712
723
  }
713
724
 
714
725
  async function assertNoPasswordResetState(userId: string, digest?: string): Promise<void> {
@@ -453,20 +453,27 @@ describe('accountPasswordSet.test.ts', { concurrency: false, sequential: true },
453
453
  }
454
454
  });
455
455
 
456
- it('allows an exact same-origin consumer without a whitelist entry', async () => {
456
+ it('uses the fallback origin for an exact same-origin consumer without a whitelist entry', async () => {
457
457
  let fixture: IAccountPasswordSetFixture | undefined;
458
458
  let restoreConfig: (() => void) | undefined;
459
459
  try {
460
460
  fixture = await createFixture();
461
461
  restoreConfig = await configurePasswordSet([]);
462
462
 
463
- const sameOriginConsumerUrl = await app.bean.executor.mockCtx(async () => {
464
- return `${app.ctx.protocol}://${app.ctx.host}/home/user/password-set`;
465
- });
463
+ const port = app.meta.env.SERVER_LISTEN_PORT;
464
+ const fallbackOrigin = `http://${port ? `localhost:${port}` : 'localhost'}`;
465
+ const sameOriginConsumerUrl = `${fallbackOrigin}/home/user/password-set`;
466
466
  const issued = await issuePasswordSetLink(fixture, sameOriginConsumerUrl);
467
- assert.equal(issued.origin, new URL(sameOriginConsumerUrl).origin);
467
+ assert.equal(issued.origin, fallbackOrigin);
468
468
  assert.equal(issued.path, '/home/user/password-set');
469
469
  assert.equal(issued.search, `?token=${issued.token}`);
470
+
471
+ await clearPasswordSetState(fixture.userId);
472
+ await issuePasswordSetLinkRejected(
473
+ fixture,
474
+ 'https://request.example.test/home/user/password-set',
475
+ );
476
+ await assertNoPasswordSetState(fixture.userId);
470
477
  } finally {
471
478
  restoreConfig?.();
472
479
  if (fixture) await removeFixture(fixture);
@@ -784,9 +791,11 @@ async function getCacheTtl(cacheName: 'passwordSet' | 'passwordSetCurrent', key:
784
791
  }
785
792
 
786
793
  async function clearPasswordSetState(userId: string): Promise<void> {
787
- const digest = await app.scope('home-user').cacheRedis.passwordSetCurrent.get(userId);
788
- if (digest) await app.scope('home-user').cacheRedis.passwordSet.del(digest);
789
- await app.scope('home-user').cacheRedis.passwordSetCurrent.del(userId);
794
+ await app.bean.executor.mockCtx(async () => {
795
+ const digest = await app.scope('home-user').cacheRedis.passwordSetCurrent.get(userId);
796
+ if (digest) await app.scope('home-user').cacheRedis.passwordSet.del(digest);
797
+ await app.scope('home-user').cacheRedis.passwordSetCurrent.del(userId);
798
+ });
790
799
  }
791
800
 
792
801
  async function assertNoPasswordSetState(userId: string, digest?: string): Promise<void> {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-auth",
3
- "version": "5.1.23",
3
+ "version": "5.1.25",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -15,7 +15,7 @@ export class ControllerPassport extends BeanBase {
15
15
  @Aspect.guardGlobal('a-user:passport', { public: true })
16
16
  @Api.exclude()
17
17
  async callback() {
18
- const code = this.ctx.query.code as string;
18
+ // const code = this.ctx.query.code as string;
19
19
  const stateQuery = this.ctx.query.state as string;
20
20
  if (!stateQuery) this.app.throw(403);
21
21
  const strategyState: IAuthenticateStrategyState = (await this.bean.jwt
@@ -23,7 +23,7 @@ export class ControllerPassport extends BeanBase {
23
23
  .verify(stateQuery)) as unknown as IAuthenticateStrategyState;
24
24
  return await this.bean.executor.newCtx(
25
25
  async () => {
26
- this.ctx.request.query = { code, state: stateQuery };
26
+ // this.ctx.request.query = { code, state: stateQuery };
27
27
  return await this.scope.service.auth.authCallback(strategyState);
28
28
  },
29
29
  {
@@ -31,6 +31,8 @@ export class ControllerPassport extends BeanBase {
31
31
  tz: strategyState.tz,
32
32
  instanceName: strategyState.instanceName,
33
33
  instance: true,
34
+ req: this.ctx.req,
35
+ res: this.ctx.res,
34
36
  },
35
37
  );
36
38
  }
@@ -7,3 +7,45 @@ export function getStrategyOauth2Profile<T = IAuthUserProfile>(
7
7
  ): T {
8
8
  return args[2];
9
9
  }
10
+
11
+ export type TypeOauthRedirectKind =
12
+ | 'absolute'
13
+ | 'invalid'
14
+ | 'missing'
15
+ | 'non_http'
16
+ | 'protocol_relative'
17
+ | 'relative';
18
+
19
+ export interface IOauthRedirectDiagnostic {
20
+ redirectKind: TypeOauthRedirectKind;
21
+ redirectOrigin?: string;
22
+ redirectProtocol?: string;
23
+ redirectHost?: string;
24
+ redirectHostname?: string;
25
+ redirectPort?: string;
26
+ }
27
+
28
+ export function getOauthRedirectDiagnostic(redirect: string): IOauthRedirectDiagnostic {
29
+ if (redirect.startsWith('//')) return { redirectKind: 'protocol_relative' };
30
+ if (redirect.startsWith('/')) return { redirectKind: 'relative' };
31
+
32
+ let url: URL;
33
+ try {
34
+ url = new URL(redirect);
35
+ } catch {
36
+ return { redirectKind: 'invalid' };
37
+ }
38
+
39
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
40
+ return { redirectKind: 'non_http', redirectProtocol: url.protocol };
41
+ }
42
+
43
+ return {
44
+ redirectKind: 'absolute',
45
+ redirectOrigin: url.origin,
46
+ redirectProtocol: url.protocol,
47
+ redirectHost: url.host,
48
+ redirectHostname: url.hostname,
49
+ redirectPort: url.port || undefined,
50
+ };
51
+ }
@@ -20,6 +20,7 @@ import type {
20
20
  } from '../types/authProvider.ts';
21
21
 
22
22
  import { StrategyMock } from '../lib/strategyMock.ts';
23
+ import { getOauthRedirectDiagnostic } from '../lib/utils.ts';
23
24
 
24
25
  @Service()
25
26
  export class ServiceAuth extends BeanBase {
@@ -247,10 +248,27 @@ export class ServiceAuth extends BeanBase {
247
248
  if (clientOptions.mockUsername) {
248
249
  return resolve(jwt);
249
250
  }
251
+ // redirect
252
+ if (!strategyState.redirect) {
253
+ this._logOauthRedirectRejected(entityAuthProvider, strategyState, 'redirect_missing');
254
+ return reject(new Error('redirect not specified'));
255
+ }
256
+ if (!strategyState.redirect.startsWith('/')) {
257
+ const redirectAllowed = !!this.bean.security.checkOrigin(
258
+ strategyState.redirect,
259
+ this.app.util.host,
260
+ );
261
+ if (!redirectAllowed) {
262
+ this._logOauthRedirectRejected(
263
+ entityAuthProvider,
264
+ strategyState,
265
+ 'origin_disallowed',
266
+ );
267
+ return this.app.throw(403);
268
+ }
269
+ }
250
270
  // code
251
271
  const code = await this.bean.passport.createOauthCodeFromOauthAuthToken(jwt.accessToken);
252
- // redirect
253
- if (!strategyState.redirect) return reject(new Error('redirect not specified'));
254
272
  const redirectUrl = combineQueries(strategyState.redirect, {
255
273
  [this.scope.config.oauthCodeField]: code,
256
274
  });
@@ -266,6 +284,46 @@ export class ServiceAuth extends BeanBase {
266
284
  });
267
285
  }
268
286
 
287
+ private _logOauthRedirectRejected(
288
+ entityAuthProvider: EntityAuthProvider,
289
+ strategyState: IAuthenticateStrategyState,
290
+ reason: 'origin_disallowed' | 'redirect_missing',
291
+ ) {
292
+ const redirectDiagnostic = strategyState.redirect
293
+ ? getOauthRedirectDiagnostic(strategyState.redirect)
294
+ : { redirectKind: 'missing' as const };
295
+ const onionCors = this.bean.onion.middlewareSystem.getOnionSlice('a-security:cors');
296
+ const whiteList = onionCors?.beanOptions.options?.whiteList;
297
+ const whiteListEntries =
298
+ typeof whiteList === 'string' ? whiteList.split(',').filter(Boolean) : whiteList;
299
+ this.$logger.warn('OAuth callback final redirect rejected', {
300
+ event: 'auth.oauth_final_redirect_rejected',
301
+ stage: 'redirect_validation',
302
+ outcome: 'rejected',
303
+ reason,
304
+ authProviderId: entityAuthProvider.id,
305
+ providerName: entityAuthProvider.providerName,
306
+ clientName: entityAuthProvider.clientName,
307
+ intention: strategyState.intention ?? 'login',
308
+ instanceName: strategyState.instanceName,
309
+ mockMode: !!strategyState.clientOptions?.mockUsername,
310
+ requestHost: this.app.util.host,
311
+ requestProtocol: this.app.util.protocol,
312
+ serveHost: this.ctx.config.server.serve.host || null,
313
+ serveProtocol: this.ctx.config.server.serve.protocol || null,
314
+ proxyEnabled: this.app.proxy,
315
+ hostMatches:
316
+ redirectDiagnostic.redirectHost === undefined
317
+ ? undefined
318
+ : redirectDiagnostic.redirectHost === this.app.util.host,
319
+ whiteListConfigured: Array.isArray(whiteList) ? whiteList.length > 0 : !!whiteList,
320
+ whiteListKind: Array.isArray(whiteList) ? 'array' : typeof whiteList,
321
+ whiteListEntryCount: Array.isArray(whiteListEntries) ? whiteListEntries.length : 0,
322
+ whiteListWildcard: whiteList === '*',
323
+ ...redirectDiagnostic,
324
+ });
325
+ }
326
+
269
327
  public async getStrategyConstructable(
270
328
  beanAuthProvider: IAuthProviderVerify & IAuthProviderStrategy,
271
329
  clientOptions: IAuthProviderClientOptions,
@@ -0,0 +1,44 @@
1
+ import assert from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+
4
+ import { getOauthRedirectDiagnostic } from '../src/lib/utils.ts';
5
+
6
+ describe('authCallbackRedirect.test.ts', () => {
7
+ it('keeps absolute redirect diagnostics free of sensitive URL components', () => {
8
+ const code = 'oauth-authorization-code-sentinel';
9
+ const state = 'oauth-state-sentinel';
10
+ const token = 'oauth-token-sentinel';
11
+ const redirect = `https://user:password@app.example.test:8443/auth/callback?code=${code}&state=${state}&token=${token}#fragment-sentinel`;
12
+
13
+ const diagnostic = getOauthRedirectDiagnostic(redirect);
14
+ const serialized = JSON.stringify(diagnostic);
15
+
16
+ assert.deepEqual(diagnostic, {
17
+ redirectKind: 'absolute',
18
+ redirectOrigin: 'https://app.example.test:8443',
19
+ redirectProtocol: 'https:',
20
+ redirectHost: 'app.example.test:8443',
21
+ redirectHostname: 'app.example.test',
22
+ redirectPort: '8443',
23
+ });
24
+ for (const value of [code, state, token, 'user', 'password', 'callback', 'fragment-sentinel']) {
25
+ assert.equal(serialized.includes(value), false);
26
+ }
27
+ });
28
+
29
+ it('classifies redirect shapes without retaining raw values', () => {
30
+ assert.deepEqual(getOauthRedirectDiagnostic('/home/base/authCallback?token=secret'), {
31
+ redirectKind: 'relative',
32
+ });
33
+ assert.deepEqual(getOauthRedirectDiagnostic('//app.example.test/authCallback?token=secret'), {
34
+ redirectKind: 'protocol_relative',
35
+ });
36
+ assert.deepEqual(getOauthRedirectDiagnostic('https://[invalid'), {
37
+ redirectKind: 'invalid',
38
+ });
39
+ assert.deepEqual(getOauthRedirectDiagnostic('javascript:alert(secret)'), {
40
+ redirectKind: 'non_http',
41
+ redirectProtocol: 'javascript:',
42
+ });
43
+ });
44
+ });
@@ -0,0 +1,148 @@
1
+ import assert from 'node:assert';
2
+ import { after, before, describe, it } from 'node:test';
3
+ import { acquireTestLock, app } from 'vona-mock';
4
+
5
+ describe('oauthRequestContext.test.ts', { concurrency: false }, () => {
6
+ let release: () => void;
7
+
8
+ before(async () => {
9
+ release = await acquireTestLock('a-security');
10
+ });
11
+
12
+ after(() => {
13
+ release();
14
+ });
15
+
16
+ it('passes the callback request through an isolated context without rewriting it', async () => {
17
+ await app.bean.executor.mockCtx(async () => {
18
+ const req = {
19
+ method: 'GET',
20
+ url: '/api/auth/passport/callback?a=1&b=2',
21
+ headers: {
22
+ 'host': 'demo.example.test',
23
+ 'user-agent': 'oauth-callback-test',
24
+ },
25
+ httpVersionMajor: 1,
26
+ socket: {
27
+ encrypted: true,
28
+ remoteAddress: '192.0.2.10',
29
+ remotePort: 443,
30
+ },
31
+ };
32
+ const originalUrl = req.url;
33
+ const originalHeaders = { ...req.headers };
34
+ const outerContext = app.ctx;
35
+ const result = await app.bean.executor.newCtx(
36
+ async () => {
37
+ return {
38
+ context: app.ctx,
39
+ request: app.ctx.req,
40
+ method: app.ctx.method,
41
+ host: app.ctx.host,
42
+ protocol: app.ctx.protocol,
43
+ secure: app.ctx.secure,
44
+ query: app.ctx.request.query,
45
+ userAgent: app.ctx.get('user-agent'),
46
+ };
47
+ },
48
+ {
49
+ instanceName: outerContext.instanceName!,
50
+ req,
51
+ },
52
+ );
53
+
54
+ assert.notEqual(result.context, outerContext);
55
+ assert.equal(result.request, req);
56
+ assert.equal(result.method, 'GET');
57
+ assert.equal(result.host, 'demo.example.test');
58
+ assert.equal(result.protocol, 'https');
59
+ assert.equal(result.secure, true);
60
+ assert.deepEqual(result.query, { a: '1', b: '2' });
61
+ assert.equal(result.userAgent, 'oauth-callback-test');
62
+ assert.equal(req.url, originalUrl);
63
+ assert.deepEqual(req.headers, originalHeaders);
64
+ assert.equal(app.ctx, outerContext);
65
+ });
66
+ });
67
+
68
+ it('logs rejected redirects against the effective request origin', async () => {
69
+ const req = {
70
+ method: 'GET',
71
+ url: '/api/auth/passport/callback',
72
+ headers: {
73
+ host: 'edge.example.test',
74
+ },
75
+ httpVersionMajor: 1,
76
+ socket: {
77
+ encrypted: true,
78
+ remoteAddress: '192.0.2.10',
79
+ remotePort: 443,
80
+ },
81
+ };
82
+ await app.bean.executor.mockCtx(
83
+ async () => {
84
+ assert.equal(app.ctx.host, 'edge.example.test');
85
+ assert.equal(app.ctx.protocol, 'https');
86
+ assert.equal(app.util.host, 'edge.example.test');
87
+ assert.equal(app.util.protocol, 'https');
88
+
89
+ const service = app.scope('a-auth').service.auth;
90
+ const logger = (service as any).$logger;
91
+ const warnPrevious = logger.warn;
92
+ const warnings: unknown[][] = [];
93
+ logger.warn = (...args: unknown[]) => warnings.push(args);
94
+ try {
95
+ (service as any)._logOauthRedirectRejected(
96
+ {
97
+ id: -1,
98
+ providerName: 'auth-oauth:oauth',
99
+ clientName: 'github',
100
+ },
101
+ {
102
+ authProviderId: -1,
103
+ instanceName: app.ctx.instanceName!,
104
+ locale: app.ctx.locale,
105
+ redirect: 'https://redirect.example.test/oauth/callback',
106
+ },
107
+ 'origin_disallowed',
108
+ );
109
+ } finally {
110
+ logger.warn = warnPrevious;
111
+ }
112
+
113
+ assert.equal(warnings.length, 1);
114
+ assert.equal(warnings[0][0], 'OAuth callback final redirect rejected');
115
+ const diagnostic = warnings[0][1] as Record<string, unknown>;
116
+ assert.deepEqual(
117
+ {
118
+ event: diagnostic.event,
119
+ stage: diagnostic.stage,
120
+ outcome: diagnostic.outcome,
121
+ reason: diagnostic.reason,
122
+ requestHost: diagnostic.requestHost,
123
+ requestProtocol: diagnostic.requestProtocol,
124
+ serveHost: diagnostic.serveHost,
125
+ serveProtocol: diagnostic.serveProtocol,
126
+ proxyEnabled: diagnostic.proxyEnabled,
127
+ redirectHost: diagnostic.redirectHost,
128
+ hostMatches: diagnostic.hostMatches,
129
+ },
130
+ {
131
+ event: 'auth.oauth_final_redirect_rejected',
132
+ stage: 'redirect_validation',
133
+ outcome: 'rejected',
134
+ reason: 'origin_disallowed',
135
+ requestHost: 'edge.example.test',
136
+ requestProtocol: 'https',
137
+ serveHost: null,
138
+ serveProtocol: null,
139
+ proxyEnabled: false,
140
+ redirectHost: 'redirect.example.test',
141
+ hostMatches: false,
142
+ },
143
+ );
144
+ },
145
+ { req },
146
+ );
147
+ });
148
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-auth",
3
- "version": "5.1.27",
3
+ "version": "5.1.29",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-socket",
3
- "version": "5.1.16",
3
+ "version": "5.1.17",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -14,7 +14,7 @@ export interface ISocketConnectionOptionsCors extends IDecoratorSocketConnection
14
14
  export class SocketConnectionCors extends BeanBase implements ISocketConnectionExecute {
15
15
  async enter(ws: WebSocket, _options: ISocketConnectionOptionsCors, next: Next): Promise<void> {
16
16
  // cors
17
- const origin = this.bean.security.checkOrigin(this.ctx.get('origin'), this.ctx.host);
17
+ const origin = this.bean.security.checkOrigin(this.ctx.get('origin'), this.app.util.host);
18
18
  if (!origin) {
19
19
  ws.terminate();
20
20
  return;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-ssr",
3
- "version": "5.1.32",
3
+ "version": "5.1.33",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-cabloy",
3
- "version": "5.1.56",
3
+ "version": "5.1.57",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-pay",
3
- "version": "5.0.8",
3
+ "version": "5.0.9",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -88,7 +88,7 @@ export class ServicePaymentCallback extends BeanBase {
88
88
 
89
89
  private _assertTrustedOrigin() {
90
90
  const { protocol, host } = this.app.config.server.serve;
91
- if (!protocol || !host) {
91
+ if (!this.app.meta.isTest && (!protocol || !host)) {
92
92
  this.app.throw(503, 'payment callbacks require configured server serve protocol and host');
93
93
  }
94
94
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-pay-mock",
3
- "version": "5.0.8",
3
+ "version": "5.0.9",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-pay-paypal",
3
- "version": "5.0.8",
3
+ "version": "5.0.9",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-pay-stripe",
3
- "version": "5.0.8",
3
+ "version": "5.0.9",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "vona-suite-a-pay",
3
- "version": "5.0.8",
3
+ "version": "5.0.9",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "files": [],
7
7
  "type": "module",
8
8
  "dependencies": {
9
- "vona-module-a-pay": "^5.0.8",
10
- "vona-module-pay-mock": "^5.0.8",
11
- "vona-module-pay-paypal": "^5.0.8",
12
- "vona-module-pay-stripe": "^5.0.8"
9
+ "vona-module-a-pay": "^5.0.9",
10
+ "vona-module-pay-mock": "^5.0.9",
11
+ "vona-module-pay-paypal": "^5.0.9",
12
+ "vona-module-pay-stripe": "^5.0.9"
13
13
  },
14
14
  "title": "a-pay"
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-core",
3
- "version": "5.1.54",
3
+ "version": "5.1.56",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -5,23 +5,7 @@ import type { TypeContextFetch } from '../types/fetch.ts';
5
5
 
6
6
  @Bean()
7
7
  export class BeanCore extends BeanBase {
8
- get protocol() {
9
- return this.app.util.protocol;
10
- }
11
-
12
- get host() {
13
- return this.app.util.host;
14
- }
15
-
16
8
  get fetch(): TypeContextFetch {
17
9
  return this.ctx.state.fetch ?? globalThis.fetch;
18
10
  }
19
-
20
- getAbsoluteUrl(path?: string) {
21
- return this.app.util.getAbsoluteUrl(path);
22
- }
23
-
24
- getAbsoluteUrlByApiPath(path: string) {
25
- return this.app.util.getAbsoluteUrlByApiPath(path);
26
- }
27
11
  }
@@ -12,8 +12,8 @@ export interface TypeMetaRuntimeResult {
12
12
  export class MetaRuntime extends BeanBase implements IMetaRuntimeExecute {
13
13
  async execute(): Promise<TypeMetaRuntimeResult> {
14
14
  return {
15
- protocol: this.bean.core.protocol,
16
- host: this.bean.core.host,
15
+ protocol: this.app.util.protocol,
16
+ host: this.app.util.host,
17
17
  };
18
18
  }
19
19
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-ormdialect",
3
- "version": "5.1.17",
3
+ "version": "5.1.18",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -98,6 +98,7 @@ export class DatabaseDialectBetterSqlite3 extends ServiceDatabaseDialectBase {
98
98
  }
99
99
 
100
100
  async function sqlite3_afterCreate(conn) {
101
+ conn.pragma('busy_timeout = 5000');
101
102
  const pragma: { journal_mode: string }[] = conn.pragma('journal_mode');
102
103
  if (pragma[0]?.journal_mode?.toLocaleLowerCase() !== 'wal') {
103
104
  conn.pragma('journal_mode = wal');
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-security",
3
- "version": "5.1.13",
3
+ "version": "5.1.15",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -52,5 +52,5 @@ export class MiddlewareSystemCors extends BeanBase implements IMiddlewareSystemE
52
52
  }
53
53
 
54
54
  function _corsOrigin(ctx: VonaContext) {
55
- return ctx.app.bean.security.checkOrigin(ctx.get('origin'), ctx.host);
55
+ return ctx.app.bean.security.checkOrigin(ctx.get('origin'), ctx.app.util.host);
56
56
  }
@@ -1,8 +1,18 @@
1
1
  import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { app } from 'vona-mock';
2
+ import { after, before, describe, it } from 'node:test';
3
+ import { acquireTestLock, app } from 'vona-mock';
4
4
 
5
5
  describe('origin.test.ts', { concurrency: false }, () => {
6
+ let release: () => void;
7
+
8
+ before(async () => {
9
+ release = await acquireTestLock('a-security');
10
+ });
11
+
12
+ after(() => {
13
+ release();
14
+ });
15
+
6
16
  it('keeps normal CORS separate from exact credential-link authorization', async () => {
7
17
  await withWhiteList('*', async () => {
8
18
  assert.equal(
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-vona",
3
- "version": "5.1.90",
3
+ "version": "5.1.92",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",
@@ -77,7 +77,7 @@ importers:
77
77
  version: 10.6.0(postcss@8.5.28)
78
78
  axios:
79
79
  specifier: ^1.18.1
80
- version: 1.20.0
80
+ version: 1.20.0(supports-color@10.2.2)
81
81
  compression:
82
82
  specifier: ^1.8.1
83
83
  version: 1.8.2
@@ -369,7 +369,7 @@ importers:
369
369
  devDependencies:
370
370
  '@cabloy/cli':
371
371
  specifier: ^3.1.29
372
- version: 3.1.29(vue@3.5.42(typescript@5.9.3))
372
+ version: 3.1.29(supports-color@10.2.2)(vue@3.5.42(typescript@5.9.3))
373
373
  '@cabloy/lint':
374
374
  specifier: ^5.1.35
375
375
  version: 5.1.35(@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.10.0(jiti@2.7.0))(supports-color@10.2.2)(typescript@5.9.3))(eslint@10.10.0(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.70.0(typescript@5.9.3))(@typescript-eslint/utils@8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.10.0(jiti@2.7.0))(oxfmt@0.62.0)(oxlint@1.82.0)(supports-color@10.2.2)(typescript@5.9.3)(vue-eslint-parser@10.4.1(eslint@10.10.0(jiti@2.7.0)))
@@ -444,7 +444,7 @@ importers:
444
444
  dependencies:
445
445
  '@cabloy/cli':
446
446
  specifier: ^3.1.29
447
- version: 3.1.29(vue@3.5.42(typescript@5.9.3))
447
+ version: 3.1.29(supports-color@10.2.2)(vue@3.5.42(typescript@5.9.3))
448
448
  '@cabloy/process-helper':
449
449
  specifier: ^3.1.8
450
450
  version: 3.1.8
@@ -455,7 +455,7 @@ importers:
455
455
  specifier: ^7.6.2
456
456
  version: 7.8.5
457
457
  zova-cli-set-front:
458
- specifier: ^1.2.108
458
+ specifier: ^1.2.109
459
459
  version: link:../cli-set-front
460
460
  devDependencies:
461
461
  clean-package:
@@ -478,7 +478,7 @@ importers:
478
478
  version: 7.29.7(@babel/core@7.29.7)
479
479
  '@cabloy/cli':
480
480
  specifier: ^3.1.29
481
- version: 3.1.29(vue@3.5.42(typescript@5.9.3))
481
+ version: 3.1.29(supports-color@10.2.2)(vue@3.5.42(typescript@5.9.3))
482
482
  '@cabloy/extend':
483
483
  specifier: ^3.2.8
484
484
  version: 3.2.8
@@ -564,7 +564,7 @@ importers:
564
564
  specifier: ^1.1.17
565
565
  version: link:../../packages-utils/zova-openapi
566
566
  zova-vite:
567
- specifier: ^1.1.46
567
+ specifier: ^1.1.47
568
568
  version: link:../../packages-utils/zova-vite
569
569
  devDependencies:
570
570
  clean-package:
@@ -1204,7 +1204,7 @@ importers:
1204
1204
  dependencies:
1205
1205
  axios:
1206
1206
  specifier: ^1.18.1
1207
- version: 1.20.0
1207
+ version: 1.20.0(supports-color@10.2.2)
1208
1208
  devDependencies:
1209
1209
  clean-package:
1210
1210
  specifier: ^2.2.0
@@ -1477,7 +1477,7 @@ importers:
1477
1477
  devDependencies:
1478
1478
  '@cabloy/cli':
1479
1479
  specifier: ^3.1.29
1480
- version: 3.1.29(vue@3.5.42(typescript@5.9.3))
1480
+ version: 3.1.29(supports-color@10.2.2)(vue@3.5.42(typescript@5.9.3))
1481
1481
  '@types/luxon':
1482
1482
  specifier: ^3.7.1
1483
1483
  version: 3.7.5
@@ -8851,7 +8851,7 @@ snapshots:
8851
8851
 
8852
8852
  '@bufbuild/protobuf@2.15.0': {}
8853
8853
 
8854
- '@cabloy/cli@3.1.29(vue@3.5.42(typescript@5.9.3))':
8854
+ '@cabloy/cli@3.1.29(supports-color@10.2.2)(vue@3.5.42(typescript@5.9.3))':
8855
8855
  dependencies:
8856
8856
  '@babel/parser': 7.29.8
8857
8857
  '@cabloy/module-glob': 5.3.18
@@ -8860,7 +8860,7 @@ snapshots:
8860
8860
  '@cabloy/utils': 2.1.27
8861
8861
  '@cabloy/word-utils': link:packages-utils/word-utils
8862
8862
  '@npmcli/config': 10.12.0
8863
- '@zhennann/common-bin': 4.0.1
8863
+ '@zhennann/common-bin': 4.0.1(supports-color@10.2.2)
8864
8864
  '@zhennann/ejs': 3.0.1
8865
8865
  boxen: 4.2.0
8866
8866
  chalk: 3.0.0
@@ -11048,7 +11048,7 @@ snapshots:
11048
11048
 
11049
11049
  '@vue/shared@3.5.13': {}
11050
11050
 
11051
- '@zhennann/common-bin@4.0.1':
11051
+ '@zhennann/common-bin@4.0.1(supports-color@10.2.2)':
11052
11052
  dependencies:
11053
11053
  chalk: 4.1.2
11054
11054
  change-case: 4.1.2
@@ -11082,7 +11082,7 @@ snapshots:
11082
11082
 
11083
11083
  acorn@8.18.0: {}
11084
11084
 
11085
- agent-base@6.0.2:
11085
+ agent-base@6.0.2(supports-color@10.2.2):
11086
11086
  dependencies:
11087
11087
  debug: 4.4.3(supports-color@10.2.2)
11088
11088
  transitivePeerDependencies:
@@ -11190,11 +11190,11 @@ snapshots:
11190
11190
  dependencies:
11191
11191
  possible-typed-array-names: 1.1.0
11192
11192
 
11193
- axios@1.20.0:
11193
+ axios@1.20.0(supports-color@10.2.2):
11194
11194
  dependencies:
11195
11195
  follow-redirects: 1.16.0
11196
11196
  form-data: 4.0.6
11197
- https-proxy-agent: 5.0.1
11197
+ https-proxy-agent: 5.0.1(supports-color@10.2.2)
11198
11198
  proxy-from-env: 2.1.0
11199
11199
  transitivePeerDependencies:
11200
11200
  - debug
@@ -12589,9 +12589,9 @@ snapshots:
12589
12589
  statuses: 2.0.2
12590
12590
  toidentifier: 1.0.1
12591
12591
 
12592
- https-proxy-agent@5.0.1:
12592
+ https-proxy-agent@5.0.1(supports-color@10.2.2):
12593
12593
  dependencies:
12594
- agent-base: 6.0.2
12594
+ agent-base: 6.0.2(supports-color@10.2.2)
12595
12595
  debug: 4.4.3(supports-color@10.2.2)
12596
12596
  transitivePeerDependencies:
12597
12597
  - supports-color