arcane-os 0.24.1 → 0.25.0
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/CHANGELOG.md +13 -0
- package/README.md +6 -2
- package/docs/reference/cli.md +11 -2
- package/docs/reference/mail.md +45 -8
- package/docs/reviews/mail-server-purpose-review.md +60 -0
- package/package.json +1 -1
- package/src/mail-server.mjs +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.25.0
|
|
4
|
+
|
|
5
|
+
- Automatically skip configured mail subscription verification when the actual
|
|
6
|
+
requester connection IP equals the server-side IP of that connection. This
|
|
7
|
+
lets services sharing that IP and local development send without a subscription
|
|
8
|
+
key. Other requester IPs, including other intranet machines, still use the
|
|
9
|
+
configured verification callback.
|
|
10
|
+
- Use native connection addresses for HTTP/1 and HTTP/2, with no environment
|
|
11
|
+
setting, domain lookup, forwarded-header interpretation, or special loopback rule.
|
|
12
|
+
Preserve CORS, report handling, provider delivery, and existing caller APIs.
|
|
13
|
+
- Document the IP rule and its purpose gates; update focused test source for
|
|
14
|
+
the exemption and the remaining subscription-verification paths.
|
|
15
|
+
|
|
3
16
|
## 0.24.1
|
|
4
17
|
|
|
5
18
|
- Add a mail CLI parameter table with command scope, purpose, and defaults.
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.
|
|
22
|
+
This checkout defines the `0.25.0` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into application
|
|
24
24
|
artifacts.
|
|
25
25
|
|
|
@@ -31,7 +31,11 @@ Existing root credential, profile, and TLS settings remain supported. Browser
|
|
|
31
31
|
mail defaults to `/v1/mail` on the current domain, and multiple applications can
|
|
32
32
|
share one server with explicit allowed origins. Subscription verification is disabled
|
|
33
33
|
until a `verifySubscription` callback is configured; that callback receives the
|
|
34
|
-
application name and bearer subscription key before
|
|
34
|
+
application name and bearer subscription key before provider attempts from other
|
|
35
|
+
requester IPs. Requests whose actual connection source IP equals the server-side
|
|
36
|
+
IP of that connection automatically skip subscription verification, including
|
|
37
|
+
local development. Other intranet machines still need verification. The exception
|
|
38
|
+
requires no environment setting or domain lookup.
|
|
35
39
|
See the [method and action gate report](docs/reviews/mail-server-purpose-review.md)
|
|
36
40
|
for the removals, retained responsibilities, and naming decisions.
|
|
37
41
|
|
package/docs/reference/cli.md
CHANGED
|
@@ -985,7 +985,16 @@ application with `Authorization: Bearer <subscription_key>`.
|
|
|
985
985
|
Subscription verification is disabled when no callback is configured. The
|
|
986
986
|
programmatic `createToolchain().mail({action: 'serve', ...})` path accepts
|
|
987
987
|
`verifySubscription({appName, subscriptionKey, signal})`; supplying that callback
|
|
988
|
-
enables verification
|
|
988
|
+
enables verification for requests whose actual connection source IP differs
|
|
989
|
+
from the server-side IP of that connection. The gateway compares the native
|
|
990
|
+
`request.socket.remoteAddress` with `request.socket.localAddress`. A nonempty,
|
|
991
|
+
equal requester IP skips subscription verification and needs neither a bearer
|
|
992
|
+
key nor an application header for that check. No environment setting or domain
|
|
993
|
+
lookup is involved. Local development qualifies under the same rule; another
|
|
994
|
+
machine on an intranet still needs verification. Missing connection metadata
|
|
995
|
+
does not qualify. CORS and the remaining mail requirements still apply.
|
|
996
|
+
|
|
997
|
+
For requests requiring verification, the callback must resolve to `true`
|
|
989
998
|
to accept the request. An invalid subscription receives 401; verifier service
|
|
990
999
|
failure receives retryable 503; cancellation stops verification before sending.
|
|
991
1000
|
The callback connects the actual TWiN Stripe endpoint when its contract is
|
|
@@ -1016,7 +1025,7 @@ deadline. `mail.bodyTimeoutMs` selects the optional request-body deadline.
|
|
|
1016
1025
|
After binding, `server.ready` reports lifecycle fields such as
|
|
1017
1026
|
protocol, optional app label, bind address, port, URL, and `callerAuthentication`
|
|
1018
1027
|
(`none` or `subscription`). Human output states whether verification is disabled
|
|
1019
|
-
or configured.
|
|
1028
|
+
or configured; configured verification includes the automatic same-IP exception.
|
|
1020
1029
|
The command owns the server until its lifecycle ends or `SIGINT`/`SIGTERM`
|
|
1021
1030
|
cancels it. The server's Resend credential remains outside results and events;
|
|
1022
1031
|
per-request observer events preserve the complete delivery, report, provider
|
package/docs/reference/mail.md
CHANGED
|
@@ -115,7 +115,8 @@ An absent key, `null`, or an empty string omits the Authorization header; the
|
|
|
115
115
|
transport does not block initial setup because a key is missing. Explicit
|
|
116
116
|
`subscriptionKey: null` or `subscriptionKey: ''` also skips User lookup.
|
|
117
117
|
A supplied value of another type is rejected. A gateway with subscription
|
|
118
|
-
verification configured owns rejection of requests without a usable key
|
|
118
|
+
verification configured owns rejection of requests without a usable key,
|
|
119
|
+
subject to its [automatic same-IP exception](#automatic-same-ip-exception).
|
|
119
120
|
|
|
120
121
|
For a caller-owned key and shared endpoint:
|
|
121
122
|
|
|
@@ -586,10 +587,40 @@ Subscription verification is disabled during initial setup when
|
|
|
586
587
|
subscription key. Starting the ordinary CLI gateway uses this mode. This does
|
|
587
588
|
not claim that a subscription was checked.
|
|
588
589
|
|
|
590
|
+
### Automatic same-IP exception
|
|
591
|
+
|
|
592
|
+
The gateway automatically skips subscription verification when the incoming
|
|
593
|
+
connection's nonempty `request.socket.remoteAddress` equals
|
|
594
|
+
`request.socket.localAddress`. This also applies when a verifier is configured
|
|
595
|
+
and the request has no subscription key or supplies an invalid one. The
|
|
596
|
+
verification callback is not called for that request.
|
|
597
|
+
|
|
598
|
+
No `.arcane.env.json` setting or domain allowlist is needed. The comparison uses
|
|
599
|
+
the actual source and destination IP addresses exposed by Node's request socket
|
|
600
|
+
for HTTP/1.1 and HTTP/2. It adds no DNS lookup, configuration read, cache, or
|
|
601
|
+
interface scan. `Origin`, `Host`, `Forwarded`, and `X-Forwarded-For` do not
|
|
602
|
+
establish the connection's IP equality.
|
|
603
|
+
|
|
604
|
+
| Caller connection | Subscription behavior with a verifier configured |
|
|
605
|
+
| --- | --- |
|
|
606
|
+
| The verification service connects to mail using the same source and destination IP | Skips subscription checking, allowing its service-generated emails without a subscriber key. |
|
|
607
|
+
| A local development request has equal source and destination IPs, including an ordinary loopback connection | Uses the same automatic exception. There is no special loopback rule. |
|
|
608
|
+
| Another machine on the same intranet connects from a different IP | Uses the configured subscription verifier. Sharing a network or subnet supplies no exception. |
|
|
609
|
+
| Two services on one machine connect through different interface IPs | Uses the configured subscription verifier when the actual addresses differ. Machine identity alone supplies no exception. |
|
|
610
|
+
| The request has no usable socket source address | Uses the configured subscription verifier; absent addresses do not count as equal. |
|
|
611
|
+
|
|
612
|
+
The exception skips only subscription checking. Existing CORS, route, method,
|
|
613
|
+
idempotency-key, report, recipient, and provider handling still apply. The gateway
|
|
614
|
+
continues to require its Resend provider credential and reports acceptance only
|
|
615
|
+
after the provider accepts the email.
|
|
616
|
+
|
|
617
|
+
### Verification callback contract
|
|
618
|
+
|
|
589
619
|
The hosting process enables verification by supplying the programmatic
|
|
590
620
|
`verifySubscription` function through
|
|
591
621
|
`createToolchain().mail({action:'serve', verifySubscription, ...options})`.
|
|
592
|
-
The server then reports `callerAuthentication: 'subscription'
|
|
622
|
+
The server then reports `callerAuthentication: 'subscription'`, identifying the
|
|
623
|
+
configured mode while retaining the same-IP exception above. The callback
|
|
593
624
|
contract is:
|
|
594
625
|
|
|
595
626
|
```javascript
|
|
@@ -602,13 +633,15 @@ The gateway also treats any other returned value as invalid. `appName` is the
|
|
|
602
633
|
exact incoming `X-Mail-App` value, `subscriptionKey` is the incoming Bearer
|
|
603
634
|
key, and `signal` follows the request lifecycle. These control fields stay
|
|
604
635
|
separate from the mail report and Resend payload. The verifier runs for each
|
|
605
|
-
POST request before any provider attempt; results
|
|
636
|
+
POST request outside the same-IP exception before any provider attempt; results
|
|
637
|
+
are not cached.
|
|
606
638
|
|
|
607
639
|
`X-Mail-App` identifies the originating app so the verifier can select that
|
|
608
640
|
app's subscription account, such as the appropriate Stripe account for `BOSS`
|
|
609
641
|
or `TWiN`. It is separate from the gateway's optional `--app` event label.
|
|
610
642
|
The missing-header and Bearer errors below apply only when `verifySubscription`
|
|
611
|
-
is configured
|
|
643
|
+
is configured and the same-IP exception does not apply. Ordinary `mail serve`
|
|
644
|
+
does not require either header.
|
|
612
645
|
|
|
613
646
|
| Configured-verifier outcome | Gateway response |
|
|
614
647
|
| --- | --- |
|
|
@@ -654,10 +687,14 @@ error report, and trigger another mail event. Subscriber failures cannot change
|
|
|
654
687
|
an already committed mail result, though global capture can report them as new
|
|
655
688
|
errors. This is a source-level possibility, not evidence of a deployed loop.
|
|
656
689
|
|
|
657
|
-
For a verification service
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
690
|
+
For a verification service's own emails sent through the same IP, the gateway
|
|
691
|
+
skips its subscription callback and proceeds to provider delivery. That removes
|
|
692
|
+
the circular dependency `mail -> verify subscription -> send mail -> verify
|
|
693
|
+
subscription` for this connection path. Connections from a different IP still
|
|
694
|
+
use the configured callback, whose owning service must preserve a verification
|
|
695
|
+
path that can return without recursively depending on another verification.
|
|
696
|
+
The exception does not change the separate browser event-subscriber behavior
|
|
697
|
+
described above.
|
|
661
698
|
|
|
662
699
|
## Gateway request lifecycle
|
|
663
700
|
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
[Roshi's Codex PRIME] The gateway review and its portable credential follow-up apply the same three purpose gates. The gateway removals preserve complete delivery outcomes, configured CORS and recipient policy, and the optional subscription verifier. Mail credentials now belong in the user-selected `.arcane.env.json`, with one portable Node implementation for Windows, Linux, and macOS and a compatible Node host as the Android adaptation boundary. The original gateway inventory below uses baseline `f055e05`; the credential follow-up reviews the Windows storage implementation present at SDK `0.18.0`. Commit, package, publication, and actual platform execution outcomes belong to the delivery record rather than to this source-review report.
|
|
2
2
|
|
|
3
|
+
The [same-IP subscription exception follow-up](#same-ip-subscription-exception-follow-up)
|
|
4
|
+
records the later user-selected change to when the optional verifier runs. The
|
|
5
|
+
earlier inventories retain their stated historical scope; current verification
|
|
6
|
+
skips requests whose actual connection source and destination IPs are equal.
|
|
7
|
+
|
|
3
8
|
| Decision | Behavior | Why it matters | Source status at this review |
|
|
4
9
|
| --- | --- | --- | --- |
|
|
5
10
|
| Remove | Loopback-only admission, manual header reconstruction, address/origin normalization, local payload grammar, manual content-length scan, dead queue knobs | These restrict supported use, rewrite supplied values, or repeat work already owned elsewhere. | Removed from the inspected source; naming-only changes carry no runtime claim. |
|
|
@@ -541,3 +546,58 @@ global error handler; it is not an automatic server response to an ordinary
|
|
|
541
546
|
mail failure. This is a static source-path finding, not an observed production
|
|
542
547
|
incident or an executed reproduction. This documentation clarification changes
|
|
543
548
|
no error, subscription, retry, or delivery behavior.
|
|
549
|
+
|
|
550
|
+
## Same-IP subscription exception follow-up
|
|
551
|
+
|
|
552
|
+
The user selected an automatic same-IP exception so the verification service can
|
|
553
|
+
send its own emails without a subscriber key. This supersedes the proposed
|
|
554
|
+
domain exception in `.arcane.env.json`; neither an environment setting nor a
|
|
555
|
+
domain allowlist is needed. The reusable decision belongs in the SDK's existing
|
|
556
|
+
`handleMailRequest`, immediately at its subscription-verification branch.
|
|
557
|
+
Application-owned messages, sender choices, subscription accounts, and the
|
|
558
|
+
verification callback remain with their existing owners.
|
|
559
|
+
|
|
560
|
+
The condition is a nonempty `request.socket.remoteAddress` equal to
|
|
561
|
+
`request.socket.localAddress`. Matching requests skip `verifyMailSubscription`
|
|
562
|
+
and the configured `verifySubscription` callback, including when the request
|
|
563
|
+
has no subscriber key or supplies an invalid one. Requests with different or
|
|
564
|
+
unavailable socket addresses retain the configured verification path. Omitting
|
|
565
|
+
the callback continues to select the existing no-verification setup.
|
|
566
|
+
|
|
567
|
+
| Method or action | Gate 1: Do we care? | Gate 2: Why is it worth the work? | Gate 3: Can we remove it without losing the required result? | Decision and concrete effect |
|
|
568
|
+
| --- | --- | --- | --- | --- |
|
|
569
|
+
| Automatic source/destination IP equality at `handleMailRequest` | Yes. The verifier needs to send service mail without a subscriber key. | It identifies the exact connection case the user selected using already-available socket fields. | No. Removing the exception would restore the verifier's dependency on subscription checking for its own mail. | Keep one inline equality decision with a nonempty source address. No new helper or configuration owner. |
|
|
570
|
+
| Subscription checking for a matching connection | The subscription-verification capability matters; this particular call does not. | Requiring it for the verifier's own email adds an unnecessary dependency and can create a circular service call. | Yes. The user expressly permits these senders regardless of their subscription. | Skip the callback and its asynchronous wait for matching requests. A callback that contacts the verification service makes no such request on this path. |
|
|
571
|
+
| Proposed `.arcane.env.json` domain exception, DNS resolution, address cache, and interface enumeration | No. The selected rule is actual IP equality and requires no setup. | These would introduce configuration, I/O, retained state, or a broader machine/domain interpretation absent from the selected outcome. | Yes. Native request socket fields already supply both values. | Add none. Secret files, named provider profiles, and current configuration loading remain unchanged. |
|
|
572
|
+
| `Origin`, `Host`, `Forwarded`, or `X-Forwarded-For` as evidence of matching connection IPs | No. These request fields do not define the native socket endpoints. | Reading them for this exception would change its meaning and duplicate unrelated routing or CORS concerns. | Yes. The actual socket exposes the relevant addresses directly. | Keep header handling at its existing owner; none of these headers selects the exception. |
|
|
573
|
+
| Special loopback, intranet, subnet, or same-machine rules | No. The selected condition is equal IPs. | A larger address policy would grant a different exception and add work. | Yes. Equal local-development addresses already use the ordinary equality path. | Add none. Another intranet machine with a different IP still uses configured verification. Services on one machine using different interface IPs may also require it. |
|
|
574
|
+
| CORS, routing, methods, idempotency, report/recipient handling, provider result, and cancellation | Yes. These continue to determine the actual mail outcome. | Skipping subscription checking does not change the requested delivery or lifecycle contracts. | No. Their retained behavior supports complete delivery and honest results. | Preserve these owners and their current ordering. Provider acceptance remains separate from the subscription decision. |
|
|
575
|
+
|
|
576
|
+
The operation graph adds one synchronous connection-address comparison at the
|
|
577
|
+
existing request owner. A matching request avoids subscription header checking,
|
|
578
|
+
callback invocation, and its asynchronous verification wait, then uses the
|
|
579
|
+
ordinary provider path. There is no new file read, DNS request, timer, polling,
|
|
580
|
+
cache, helper, shared-state lock, startup barrier, or per-platform process.
|
|
581
|
+
Requests continue independently under the existing handler lifecycle. These are
|
|
582
|
+
source-level operation changes, not measured timing improvements.
|
|
583
|
+
|
|
584
|
+
The selected `node-http-server` raw-request hook supplies the native Node request.
|
|
585
|
+
HTTP/1.1 and HTTP/2 both expose the needed address properties; the same Node
|
|
586
|
+
contract applies on Windows, Linux, and macOS, with Android requiring a compatible
|
|
587
|
+
Node host. The local socket address is the actual destination used by the
|
|
588
|
+
connection, including when the listener binds `0.0.0.0`.
|
|
589
|
+
[Node socket addresses](https://nodejs.org/api/net.html#socketlocaladdress) and
|
|
590
|
+
[HTTP/2 request sockets](https://nodejs.org/api/http2.html#requestsocket)
|
|
591
|
+
document these properties.
|
|
592
|
+
|
|
593
|
+
For equal connection IPs, the verification service's own mail proceeds without
|
|
594
|
+
calling that service again. This removes its circular subscription dependency
|
|
595
|
+
on this path. It does not claim that arbitrary callback code or a repeatedly
|
|
596
|
+
failing browser mail-event subscriber cannot create a separate feedback loop.
|
|
597
|
+
The existing error and retry distinctions remain documented in the
|
|
598
|
+
[mail reference](../reference/mail.md#error-reports-and-retries).
|
|
599
|
+
|
|
600
|
+
This follow-up records the selected behavior and source review. No local test,
|
|
601
|
+
check, build, server launch, live mail send, or platform execution was performed
|
|
602
|
+
by this documentation author. Release and runtime evidence remain with the
|
|
603
|
+
corresponding operation's owner.
|
package/package.json
CHANGED
package/src/mail-server.mjs
CHANGED
|
@@ -899,8 +899,16 @@ function createConfiguredMailHandler(configuration){
|
|
|
899
899
|
if(request.method!=='POST'){
|
|
900
900
|
throw new MailGatewayFault('mail_method_not_allowed',{statusCode:405});
|
|
901
901
|
}
|
|
902
|
-
if(configuration.verifySubscription){
|
|
903
|
-
|
|
902
|
+
if (configuration.verifySubscription) {
|
|
903
|
+
const requesterIp = request.socket?.remoteAddress;
|
|
904
|
+
const serverIp = request.socket?.localAddress;
|
|
905
|
+
if (!requesterIp || requesterIp !== serverIp) {
|
|
906
|
+
appId = await verifyMailSubscription(
|
|
907
|
+
request,
|
|
908
|
+
configuration,
|
|
909
|
+
requestController.signal
|
|
910
|
+
);
|
|
911
|
+
}
|
|
904
912
|
}
|
|
905
913
|
idempotencyKey=requireRequestHeader(request,'idempotency-key');
|
|
906
914
|
const requestText=await readRequestBodyText(request,{
|