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.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +15 -0
- package/CLAUDE.md +1 -0
- package/package.json +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/toolsIsolate/test.ts +2 -2
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/core/application.ts +5 -6
- package/vona/packages-vona/vona-core/src/lib/utils/util.ts +7 -2
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.ts +4 -4
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productContent.test.ts +10 -3
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +5 -3
- package/vona/src/suite/a-home/modules/home-index/src/bean/meta.printTip.ts +1 -1
- package/vona/src/suite/a-home/modules/home-user/test/accountPasswordReset.test.ts +9 -17
- package/vona/src/suite/a-home/modules/home-user/test/accountPasswordSet.test.ts +9 -17
- package/vona/src/suite-vendor/a-auth/modules/a-auth/package.json +1 -1
- package/vona/src/suite-vendor/a-auth/modules/a-auth/src/controller/passport.ts +4 -2
- package/vona/src/suite-vendor/a-auth/modules/a-auth/test/oauthRequestContext.test.ts +123 -146
- package/vona/src/suite-vendor/a-auth/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCallback.ts +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/package.json +5 -5
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-core/src/bean/bean.core.ts +0 -16
- package/vona/src/suite-vendor/a-vona/modules/a-core/src/bean/meta.runtime.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.betterSqlite3.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-security/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-security/test/origin.test.ts +12 -2
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
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
|
@@ -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',
|
|
103
|
-
if (
|
|
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.
|
|
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": [
|
|
@@ -72,10 +72,9 @@ export class VonaApplication extends KoaApplication {
|
|
|
72
72
|
if (req) {
|
|
73
73
|
request = req;
|
|
74
74
|
} else {
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
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:
|
|
91
|
+
secure: false,
|
|
93
92
|
method: 'POST',
|
|
94
93
|
url: '',
|
|
95
94
|
path: '',
|
|
96
95
|
socket: {
|
|
97
|
-
encrypted:
|
|
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
|
-
|
|
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) {
|
|
@@ -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:
|
|
21
|
-
factor:
|
|
20
|
+
retries: 6,
|
|
21
|
+
factor: 2,
|
|
22
22
|
minTimeout: 10,
|
|
23
|
-
maxTimeout:
|
|
24
|
-
randomize:
|
|
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.
|
|
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
|
|
258
|
-
|
|
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,
|
package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 &&
|
|
41
|
+
return !!(options.secretCredential && options.secretWebhook && hasCallbackOrigin);
|
|
40
42
|
}
|
|
41
43
|
return false;
|
|
42
44
|
},
|
|
@@ -248,38 +248,30 @@ describe('accountPasswordReset.test.ts', { concurrency: false }, () => {
|
|
|
248
248
|
}
|
|
249
249
|
});
|
|
250
250
|
|
|
251
|
-
it('uses the
|
|
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
|
|
266
|
-
|
|
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,
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|
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
|
|
471
|
-
|
|
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,
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
6
|
+
let release: () => void;
|
|
7
7
|
|
|
8
8
|
before(async () => {
|
|
9
|
-
|
|
9
|
+
release = await acquireTestLock('a-security');
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
after(() => {
|
|
13
|
-
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
});
|
|
144
|
+
},
|
|
145
|
+
{ req },
|
|
146
|
+
);
|
|
170
147
|
});
|
|
171
148
|
});
|
|
@@ -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,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-suite-a-pay",
|
|
3
|
-
"version": "5.0.
|
|
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.
|
|
10
|
-
"vona-module-pay-mock": "^5.0.
|
|
11
|
-
"vona-module-pay-paypal": "^5.0.
|
|
12
|
-
"vona-module-pay-stripe": "^5.0.
|
|
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
|
}
|
|
@@ -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.
|
|
16
|
-
host: this.
|
|
15
|
+
protocol: this.app.util.protocol,
|
|
16
|
+
host: this.app.util.host,
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
}
|
package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.betterSqlite3.ts
CHANGED
|
@@ -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,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(
|