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.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +25 -0
- package/CLAUDE.md +1 -0
- package/package.json +1 -1
- package/repo-docs/backend/runtime-and-flavors.md +1 -1
- package/vona/docker-compose-original/config/nginx/conf.d/nginx.conf +2 -2
- 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 +11 -5
- package/vona/packages-vona/vona-core/src/lib/core/context.ts +1 -1
- 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/src/service/account.ts +1 -1
- package/vona/src/suite/a-home/modules/home-user/test/accountPasswordReset.test.ts +19 -8
- package/vona/src/suite/a-home/modules/home-user/test/accountPasswordSet.test.ts +17 -8
- 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/src/lib/utils.ts +42 -0
- package/vona/src/suite-vendor/a-auth/modules/a-auth/src/service/auth.ts +60 -2
- package/vona/src/suite-vendor/a-auth/modules/a-auth/test/authCallbackRedirect.test.ts +44 -0
- package/vona/src/suite-vendor/a-auth/modules/a-auth/test/oauthRequestContext.test.ts +148 -0
- package/vona/src/suite-vendor/a-auth/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-socket/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-socket/src/bean/socketConnection.cors.ts +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/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/src/bean/middlewareSystem.cors.ts +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/zova/pnpm-lock.yaml +16 -16
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
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
|
@@ -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
|
|
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 $
|
|
15
|
+
proxy_set_header X-Forwarded-Host $host;
|
|
16
16
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
17
|
-
proxy_set_header 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";
|
|
@@ -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,22 +72,28 @@ export class VonaApplication extends KoaApplication {
|
|
|
72
72
|
if (req) {
|
|
73
73
|
request = req;
|
|
74
74
|
} else {
|
|
75
|
-
const
|
|
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':
|
|
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
|
|
85
|
-
protocol
|
|
86
|
-
secure:
|
|
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
|
-
|
|
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
|
},
|
|
@@ -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.
|
|
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('
|
|
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
|
|
259
|
-
|
|
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,
|
|
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
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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('
|
|
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
|
|
464
|
-
|
|
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,
|
|
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
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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> {
|
|
@@ -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
|
+
});
|
|
@@ -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.
|
|
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;
|
|
@@ -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');
|
|
@@ -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(
|
package/zova/pnpm-lock.yaml
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|