cabloy 5.1.189 → 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 (36) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/CLAUDE.md +1 -0
  4. package/package.json +1 -1
  5. package/vona/packages-cli/cli/package.json +1 -1
  6. package/vona/packages-cli/cli-set-api/package.json +1 -1
  7. package/vona/packages-cli/cli-set-api/toolsIsolate/test.ts +2 -2
  8. package/vona/packages-vona/vona/package.json +1 -1
  9. package/vona/packages-vona/vona-core/package.json +1 -1
  10. package/vona/packages-vona/vona-core/src/lib/core/application.ts +5 -6
  11. package/vona/packages-vona/vona-core/src/lib/utils/util.ts +7 -2
  12. package/vona/packages-vona/vona-mock/package.json +1 -1
  13. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.ts +4 -4
  14. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productContent.test.ts +10 -3
  15. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +5 -3
  16. package/vona/src/suite/a-home/modules/home-index/src/bean/meta.printTip.ts +1 -1
  17. package/vona/src/suite/a-home/modules/home-user/test/accountPasswordReset.test.ts +9 -17
  18. package/vona/src/suite/a-home/modules/home-user/test/accountPasswordSet.test.ts +9 -17
  19. package/vona/src/suite-vendor/a-auth/modules/a-auth/package.json +1 -1
  20. package/vona/src/suite-vendor/a-auth/modules/a-auth/src/controller/passport.ts +4 -2
  21. package/vona/src/suite-vendor/a-auth/modules/a-auth/test/oauthRequestContext.test.ts +123 -146
  22. package/vona/src/suite-vendor/a-auth/package.json +1 -1
  23. package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
  24. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCallback.ts +1 -1
  25. package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
  26. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
  27. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
  28. package/vona/src/suite-vendor/a-pay/package.json +5 -5
  29. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  30. package/vona/src/suite-vendor/a-vona/modules/a-core/src/bean/bean.core.ts +0 -16
  31. package/vona/src/suite-vendor/a-vona/modules/a-core/src/bean/meta.runtime.ts +2 -2
  32. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
  33. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.betterSqlite3.ts +1 -0
  34. package/vona/src/suite-vendor/a-vona/modules/a-security/package.json +1 -1
  35. package/vona/src/suite-vendor/a-vona/modules/a-security/test/origin.test.ts +12 -2
  36. package/vona/src/suite-vendor/a-vona/package.json +1 -1
package/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.189
1
+ 5.1.190
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
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
+
3
18
  ## 5.1.189
4
19
 
5
20
  ### Features
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.189",
3
+ "version": "5.1.190",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli",
3
- "version": "1.1.155",
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.153",
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.100",
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.41",
3
+ "version": "5.1.42",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona",
6
6
  "keywords": [
@@ -72,10 +72,9 @@ export class VonaApplication extends KoaApplication {
72
72
  if (req) {
73
73
  request = req;
74
74
  } else {
75
- const port = this.config?.server.listen.port ?? this.meta.env.SERVER_LISTEN_PORT;
76
- const hostFallback = port ? `localhost:${port}` : 'localhost';
77
- const protocol = (this.config ? this.util.protocol : undefined) || 'http';
78
- const host = (this.config ? this.util.host : undefined) || hostFallback;
75
+ const protocol = 'http';
76
+ const port = this.meta.env.SERVER_LISTEN_PORT;
77
+ const host = port ? `localhost:${port}` : 'localhost';
79
78
  const hostname = new URL(`${protocol}://${host}`).hostname;
80
79
  request = {
81
80
  headers: {
@@ -89,12 +88,12 @@ export class VonaApplication extends KoaApplication {
89
88
  host,
90
89
  hostname,
91
90
  protocol,
92
- secure: protocol === 'https',
91
+ secure: false,
93
92
  method: 'POST',
94
93
  url: '',
95
94
  path: '',
96
95
  socket: {
97
- encrypted: protocol === 'https',
96
+ encrypted: false,
98
97
  remoteAddress: '127.0.0.1',
99
98
  remotePort: 7001,
100
99
  },
@@ -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.41",
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
  }
@@ -248,38 +248,30 @@ describe('accountPasswordReset.test.ts', { concurrency: false }, () => {
248
248
  }
249
249
  });
250
250
 
251
- it('uses the canonical host for 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
- let serve: typeof app.config.server.serve | undefined;
255
- let servePrevious: { protocol?: string; host?: string } | undefined;
256
254
  try {
257
255
  fixture = await createFixture({ simpleAuth: true });
258
256
  restoreConfig = await configurePasswordReset([]);
259
- await app.bean.executor.mockCtx(async () => {
260
- serve = app.ctx.config.server.serve;
261
- servePrevious = { ...serve };
262
- (serve as any).host = 'canonical.example.test';
263
- });
264
257
 
265
- const sameOriginConsumerUrl = await app.bean.executor.mockCtx(async () => {
266
- return `${app.ctx.protocol}://${app.util.host}/home/user/password-reset`;
267
- });
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`;
268
261
  const issued = await issuePasswordResetLink(fixture, sameOriginConsumerUrl);
269
- assert.equal(issued.origin, new URL(sameOriginConsumerUrl).origin);
262
+ assert.equal(issued.origin, fallbackOrigin);
270
263
  assert.equal(issued.path, '/home/user/password-reset');
271
264
  assert.equal(issued.search, `?token=${issued.token}`);
272
265
 
273
266
  await clearPasswordResetState(fixture.userId);
274
267
  await clearPasswordResetRecipientCooldown(fixture.email);
275
- const requestOriginConsumerUrl = await app.bean.executor.mockCtx(async () => {
276
- return `${app.ctx.protocol}://${app.ctx.host}/home/user/password-reset`;
277
- });
278
- await requestPasswordReset(fixture.email, requestOriginConsumerUrl);
268
+ await requestPasswordReset(
269
+ fixture.email,
270
+ 'https://request.example.test/home/user/password-reset',
271
+ );
279
272
  await assertNoPasswordResetState(fixture.userId);
280
273
  await assertNoPasswordResetRecipientCooldown(fixture.email);
281
274
  } finally {
282
- if (serve && servePrevious) Object.assign(serve, servePrevious);
283
275
  restoreConfig?.();
284
276
  if (fixture) await removeFixture(fixture);
285
277
  }
@@ -453,36 +453,28 @@ describe('accountPasswordSet.test.ts', { concurrency: false, sequential: true },
453
453
  }
454
454
  });
455
455
 
456
- it('uses the canonical host for 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
- let serve: typeof app.config.server.serve | undefined;
460
- let servePrevious: { protocol?: string; host?: string } | undefined;
461
459
  try {
462
460
  fixture = await createFixture();
463
461
  restoreConfig = await configurePasswordSet([]);
464
- await app.bean.executor.mockCtx(async () => {
465
- serve = app.ctx.config.server.serve;
466
- servePrevious = { ...serve };
467
- (serve as any).host = 'canonical.example.test';
468
- });
469
462
 
470
- const sameOriginConsumerUrl = await app.bean.executor.mockCtx(async () => {
471
- return `${app.ctx.protocol}://${app.util.host}/home/user/password-set`;
472
- });
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`;
473
466
  const issued = await issuePasswordSetLink(fixture, sameOriginConsumerUrl);
474
- assert.equal(issued.origin, new URL(sameOriginConsumerUrl).origin);
467
+ assert.equal(issued.origin, fallbackOrigin);
475
468
  assert.equal(issued.path, '/home/user/password-set');
476
469
  assert.equal(issued.search, `?token=${issued.token}`);
477
470
 
478
471
  await clearPasswordSetState(fixture.userId);
479
- const requestOriginConsumerUrl = await app.bean.executor.mockCtx(async () => {
480
- return `${app.ctx.protocol}://${app.ctx.host}/home/user/password-set`;
481
- });
482
- await issuePasswordSetLinkRejected(fixture, requestOriginConsumerUrl);
472
+ await issuePasswordSetLinkRejected(
473
+ fixture,
474
+ 'https://request.example.test/home/user/password-set',
475
+ );
483
476
  await assertNoPasswordSetState(fixture.userId);
484
477
  } finally {
485
- if (serve && servePrevious) Object.assign(serve, servePrevious);
486
478
  restoreConfig?.();
487
479
  if (fixture) await removeFixture(fixture);
488
480
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-auth",
3
- "version": "5.1.24",
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
  }
@@ -3,169 +3,146 @@ import { after, before, describe, it } from 'node:test';
3
3
  import { acquireTestLock, app } from 'vona-mock';
4
4
 
5
5
  describe('oauthRequestContext.test.ts', { concurrency: false }, () => {
6
- const releases: Array<() => void> = [];
6
+ let release: () => void;
7
7
 
8
8
  before(async () => {
9
- releases.push(await acquireTestLock('a-security'));
9
+ release = await acquireTestLock('a-security');
10
10
  });
11
11
 
12
12
  after(() => {
13
- for (const release of releases.reverse()) release();
13
+ release();
14
14
  });
15
+
15
16
  it('passes the callback request through an isolated context without rewriting it', async () => {
16
17
  await app.bean.executor.mockCtx(async () => {
17
- const proxyPrevious = app.proxy;
18
- app.proxy = true;
19
- try {
20
- const req = {
21
- method: 'GET',
22
- url: '/api/auth/passport/callback?a=1&b=2',
23
- headers: {
24
- 'host': 'origin.internal.test',
25
- 'x-forwarded-host': 'demo.example.test',
26
- 'x-forwarded-proto': 'https',
27
- 'user-agent': 'oauth-callback-test',
28
- },
29
- httpVersionMajor: 1,
30
- socket: {
31
- remoteAddress: '192.0.2.10',
32
- remotePort: 443,
33
- },
34
- };
35
- const originalUrl = req.url;
36
- const originalHeaders = { ...req.headers };
37
- const outerContext = app.ctx;
38
- const result = await app.bean.executor.newCtx(
39
- async () => {
40
- return {
41
- context: app.ctx,
42
- request: app.ctx.req,
43
- method: app.ctx.method,
44
- host: app.ctx.host,
45
- protocol: app.ctx.protocol,
46
- secure: app.ctx.secure,
47
- query: app.ctx.request.query,
48
- userAgent: app.ctx.get('user-agent'),
49
- };
50
- },
51
- {
52
- instanceName: outerContext.instanceName!,
53
- req,
54
- },
55
- );
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
+ );
56
53
 
57
- assert.notEqual(result.context, outerContext);
58
- assert.equal(result.request, req);
59
- assert.equal(result.method, 'GET');
60
- assert.equal(result.host, 'demo.example.test');
61
- assert.equal(result.protocol, 'https');
62
- assert.equal(result.secure, true);
63
- assert.deepEqual(result.query, { a: '1', b: '2' });
64
- assert.equal(result.userAgent, 'oauth-callback-test');
65
- assert.equal(req.url, originalUrl);
66
- assert.deepEqual(req.headers, originalHeaders);
67
- assert.equal(app.ctx, outerContext);
68
- } finally {
69
- app.proxy = proxyPrevious;
70
- }
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);
71
65
  });
72
66
  });
73
67
 
74
- it('logs rejected redirects against the canonical server origin', async () => {
75
- await app.bean.executor.mockCtx(async () => {
76
- const proxyPrevious = app.proxy;
77
- const serve = app.ctx.config.server.serve;
78
- const servePrevious = { ...serve };
79
- app.proxy = true;
80
- (serve as any).host = 'canonical.example.test';
81
- (serve as any).protocol = 'http';
82
- try {
83
- const req = {
84
- method: 'GET',
85
- url: '/api/auth/passport/callback',
86
- headers: {
87
- 'host': 'origin.internal.test',
88
- 'x-forwarded-host': 'edge.example.test',
89
- 'x-forwarded-proto': 'https',
90
- },
91
- httpVersionMajor: 1,
92
- socket: {
93
- remoteAddress: '192.0.2.10',
94
- remotePort: 443,
95
- },
96
- };
97
- await app.bean.executor.newCtx(
98
- async () => {
99
- assert.equal(app.ctx.host, 'edge.example.test');
100
- assert.equal(app.ctx.protocol, 'https');
101
- assert.equal(app.util.host, 'canonical.example.test');
102
- assert.equal(app.util.protocol, 'http');
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');
103
88
 
104
- const service = app.scope('a-auth').service.auth;
105
- const logger = (service as any).$logger;
106
- const warnPrevious = logger.warn;
107
- const warnings: unknown[][] = [];
108
- logger.warn = (...args: unknown[]) => warnings.push(args);
109
- try {
110
- (service as any)._logOauthRedirectRejected(
111
- {
112
- id: -1,
113
- providerName: 'auth-oauth:oauth',
114
- clientName: 'github',
115
- },
116
- {
117
- authProviderId: -1,
118
- instanceName: app.ctx.instanceName!,
119
- locale: app.ctx.locale,
120
- redirect: 'https://edge.example.test/oauth/callback',
121
- },
122
- 'origin_disallowed',
123
- );
124
- } finally {
125
- logger.warn = warnPrevious;
126
- }
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
+ }
127
112
 
128
- assert.equal(warnings.length, 1);
129
- assert.equal(warnings[0][0], 'OAuth callback final redirect rejected');
130
- const diagnostic = warnings[0][1] as Record<string, unknown>;
131
- assert.deepEqual(
132
- {
133
- event: diagnostic.event,
134
- stage: diagnostic.stage,
135
- outcome: diagnostic.outcome,
136
- reason: diagnostic.reason,
137
- requestHost: diagnostic.requestHost,
138
- requestProtocol: diagnostic.requestProtocol,
139
- serveHost: diagnostic.serveHost,
140
- serveProtocol: diagnostic.serveProtocol,
141
- proxyEnabled: diagnostic.proxyEnabled,
142
- redirectHost: diagnostic.redirectHost,
143
- hostMatches: diagnostic.hostMatches,
144
- },
145
- {
146
- event: 'auth.oauth_final_redirect_rejected',
147
- stage: 'redirect_validation',
148
- outcome: 'rejected',
149
- reason: 'origin_disallowed',
150
- requestHost: 'canonical.example.test',
151
- requestProtocol: 'http',
152
- serveHost: 'canonical.example.test',
153
- serveProtocol: 'http',
154
- proxyEnabled: true,
155
- redirectHost: 'edge.example.test',
156
- hostMatches: false,
157
- },
158
- );
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,
159
129
  },
160
130
  {
161
- instanceName: app.ctx.instanceName!,
162
- req,
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,
163
142
  },
164
143
  );
165
- } finally {
166
- app.proxy = proxyPrevious;
167
- Object.assign(serve, servePrevious);
168
- }
169
- });
144
+ },
145
+ { req },
146
+ );
170
147
  });
171
148
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-auth",
3
- "version": "5.1.28",
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-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.55",
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.14",
3
+ "version": "5.1.15",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -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.91",
3
+ "version": "5.1.92",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",