@agimon-ai/po-identity 0.0.0-stage → 0.0.2-alpha.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/LICENSE +149 -0
- package/README.md +267 -2
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/apiContracts.cjs +280 -0
- package/dist/apiContracts.d.mts +202 -0
- package/dist/apiContracts.mjs +273 -0
- package/dist/extensions/server.cjs +97 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.mjs +91 -0
- package/dist/index.cjs +14 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +7 -0
- package/dist/services/appleTokens/index.cjs +68 -0
- package/dist/services/appleTokens/index.d.mts +16 -0
- package/dist/services/appleTokens/index.mjs +68 -0
- package/dist/services/emailSender/index.cjs +22 -0
- package/dist/services/emailSender/index.d.mts +16 -0
- package/dist/services/emailSender/index.mjs +22 -0
- package/dist/services/identityConfiguration/index.cjs +51 -0
- package/dist/services/identityConfiguration/index.d.mts +39 -0
- package/dist/services/identityConfiguration/index.mjs +51 -0
- package/dist/services/identityHome/index.cjs +1236 -0
- package/dist/services/identityHome/index.d.mts +121 -0
- package/dist/services/identityHome/index.mjs +1235 -0
- package/dist/services/identityHome/roster.cjs +274 -0
- package/dist/services/identityHome/roster.mjs +272 -0
- package/dist/services/sqliteAdapter/index.cjs +138 -0
- package/dist/services/sqliteAdapter/index.d.mts +8 -0
- package/dist/services/sqliteAdapter/index.mjs +137 -0
- package/package.json +85 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
License text copyright © 2017 MariaDB Corporation Ab, All Rights Reserved.
|
|
2
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
3
|
+
|
|
4
|
+
Parameters
|
|
5
|
+
|
|
6
|
+
Licensor: Agimon Pty Ltd
|
|
7
|
+
Licensed Work: Po, the software in the Po source repository and the
|
|
8
|
+
packages released from it, in each version released by
|
|
9
|
+
the Licensor.
|
|
10
|
+
The Licensed Work is (c) 2026 Agimon Pty Ltd.
|
|
11
|
+
Material identified as third-party in the "Third-party
|
|
12
|
+
components" section at the end of this file or in a
|
|
13
|
+
NOTICE file, and any package or directory whose own
|
|
14
|
+
license file grants a different license, is not part of
|
|
15
|
+
the Licensed Work and remains under its own license.
|
|
16
|
+
Additional Use Grant: You may make production use of the Licensed Work if
|
|
17
|
+
your use is Eligible Use. "Eligible Use" is production use that falls
|
|
18
|
+
within at least one of the following:
|
|
19
|
+
|
|
20
|
+
(a) Personal Use. Use by an individual for their own personal purposes,
|
|
21
|
+
and not on behalf of, or for the benefit of, an employer, client, or
|
|
22
|
+
other Organization. Use by an individual in the course of employment
|
|
23
|
+
or engagement by an Organization is use by that Organization.
|
|
24
|
+
|
|
25
|
+
(b) Small Organization Use. Use by or on behalf of an Organization whose
|
|
26
|
+
Annual Revenue is less than USD 1,000,000.
|
|
27
|
+
|
|
28
|
+
(c) Non-profit, Education and Research Use. Use, regardless of Annual
|
|
29
|
+
Revenue, by (i) an Organization registered or recognized as a
|
|
30
|
+
non-profit or charitable organization under the laws of its
|
|
31
|
+
jurisdiction, for its non-profit purposes; (ii) an accredited school,
|
|
32
|
+
college, or university, for teaching, learning, or academic research;
|
|
33
|
+
or (iii) a publicly funded research institution, for its research.
|
|
34
|
+
|
|
35
|
+
(d) Service Provider Use. Use by a consultant, contractor, agency, or other
|
|
36
|
+
service provider, regardless of its own Annual Revenue, solely to
|
|
37
|
+
build, deploy, host, operate, or support the Licensed Work for a client
|
|
38
|
+
whose own use would be Eligible Use under (a), (b), or (c). A service
|
|
39
|
+
provider's use for its own purposes is Eligible Use only if the service
|
|
40
|
+
provider itself qualifies under (b) or (c).
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Hosted services. Offering the Licensed Work to third parties as a hosted
|
|
44
|
+
or managed service whose primary value is the Licensed Work is Eligible
|
|
45
|
+
Use only if each such third party's own use would be Eligible Use.
|
|
46
|
+
|
|
47
|
+
Definitions. "Organization" means any company, partnership, sole
|
|
48
|
+
proprietorship, trust, association, government body, or other entity, and
|
|
49
|
+
includes an individual acting in a business or professional capacity.
|
|
50
|
+
"Affiliate" means any entity that directly or indirectly controls, is
|
|
51
|
+
controlled by, or is under common control with an Organization, where
|
|
52
|
+
"control" means ownership of more than 50% of the voting interests of an
|
|
53
|
+
entity or the power to direct its management and policies by contract or
|
|
54
|
+
otherwise. "Annual Revenue" means the total gross revenue of an
|
|
55
|
+
Organization and all of its Affiliates combined, from all sources and
|
|
56
|
+
whether or not related to the Licensed Work, for its most recently
|
|
57
|
+
completed fiscal year, or, if it has not completed a fiscal year, for the
|
|
58
|
+
twelve months before the use (or since its formation, if shorter),
|
|
59
|
+
converted into US dollars at the average exchange rate for that period.
|
|
60
|
+
Proceeds of equity or debt financing are not revenue.
|
|
61
|
+
|
|
62
|
+
Threshold. If an Organization stops qualifying under (b), its existing
|
|
63
|
+
production use remains Eligible Use for 90 days after it stops
|
|
64
|
+
qualifying, after which it must obtain a commercial license or stop
|
|
65
|
+
production use. Use that was Eligible Use when it occurred never requires
|
|
66
|
+
a commercial license after the fact.
|
|
67
|
+
Change Date: Four years from the date a version of the Licensed Work
|
|
68
|
+
is first publicly released by the Licensor.
|
|
69
|
+
Change License: Apache License, Version 2.0
|
|
70
|
+
|
|
71
|
+
For information about alternative licensing arrangements for the Licensed
|
|
72
|
+
Work, please visit: https://agimon.ai/licensing
|
|
73
|
+
|
|
74
|
+
Notice
|
|
75
|
+
|
|
76
|
+
The Business Source License (this document, or the "License") is not an Open
|
|
77
|
+
Source license. However, the Licensed Work will eventually be made available
|
|
78
|
+
under an Open Source License, as stated in this License.
|
|
79
|
+
|
|
80
|
+
-----------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
Business Source License 1.1
|
|
83
|
+
|
|
84
|
+
Terms
|
|
85
|
+
|
|
86
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
87
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
88
|
+
Licensor may make an Additional Use Grant, above, permitting limited
|
|
89
|
+
production use.
|
|
90
|
+
|
|
91
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
92
|
+
available distribution of a specific version of the Licensed Work under this
|
|
93
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
94
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
95
|
+
above terminate.
|
|
96
|
+
|
|
97
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
98
|
+
currently in effect as described in this License, you must purchase a
|
|
99
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
100
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
101
|
+
|
|
102
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
103
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
104
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
105
|
+
for each version of the Licensed Work released by Licensor.
|
|
106
|
+
|
|
107
|
+
You must conspicuously display this License on each original or modified copy
|
|
108
|
+
of the Licensed Work. If you receive the Licensed Work in original or
|
|
109
|
+
modified form from a third party, the terms and conditions set forth in this
|
|
110
|
+
License apply to your use of that work.
|
|
111
|
+
|
|
112
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
113
|
+
terminate your rights under this License for the current and all other
|
|
114
|
+
versions of the Licensed Work.
|
|
115
|
+
|
|
116
|
+
This License does not grant you any right in any trademark or logo of
|
|
117
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
118
|
+
Licensor as expressly required by this License).
|
|
119
|
+
|
|
120
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
121
|
+
AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
122
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
123
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
124
|
+
TITLE.
|
|
125
|
+
|
|
126
|
+
MariaDB hereby grants you permission to use this License’s text to license
|
|
127
|
+
your works, and to refer to it using the trademark “Business Source License”,
|
|
128
|
+
as long as you comply with the Covenants of Licensor below.
|
|
129
|
+
|
|
130
|
+
Covenants of Licensor
|
|
131
|
+
|
|
132
|
+
In consideration of the right to use this License’s text and the “Business
|
|
133
|
+
Source License” name and trademark, Licensor covenants to MariaDB, and to all
|
|
134
|
+
other recipients of the licensed work to be provided by Licensor:
|
|
135
|
+
|
|
136
|
+
1. To specify as the Change License the GPL Version 2.0 or any later version,
|
|
137
|
+
or a license that is compatible with GPL Version 2.0 or a later version,
|
|
138
|
+
where “compatible” means that software provided under the Change License can
|
|
139
|
+
be included in a program with software provided under GPL Version 2.0 or a
|
|
140
|
+
later version. Licensor may specify additional Change Licenses without
|
|
141
|
+
limitation.
|
|
142
|
+
|
|
143
|
+
2. To either: (a) specify an additional grant of rights to use that does not
|
|
144
|
+
impose any additional restriction on the right granted in this License, as
|
|
145
|
+
the Additional Use Grant; or (b) insert the text “None”.
|
|
146
|
+
|
|
147
|
+
3. To specify a Change Date.
|
|
148
|
+
|
|
149
|
+
4. Not to modify this License in any other way.
|
package/README.md
CHANGED
|
@@ -1,3 +1,268 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Po identity
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Selectable global identity home for Node and SQLite-backed Durable Objects. Better Auth and its
|
|
4
|
+
Expo, SSO and passkey integrations are pinned to 1.7.12. Pairing-only hosts do not compose this package.
|
|
5
|
+
No organization is created at signup. `personalOrg(subject)` provisions the personal organization,
|
|
6
|
+
owner membership and unique subject marker in one transaction.
|
|
7
|
+
|
|
8
|
+
## Composition
|
|
9
|
+
|
|
10
|
+
Node's global plugin consumes only the server environment explicitly admitted by the CLI, once at
|
|
11
|
+
composition. It injects `po/sqlite` and `po/fs`. It does not use a session's `po/config`: that would let
|
|
12
|
+
one session configure an issuer shared by every session. This was the coordinator-approved phase-1
|
|
13
|
+
configuration decision. Configuration changes require restart. A future global config service can
|
|
14
|
+
provide hot reload. The Worker uses the same `identityConfiguration(env)` function.
|
|
15
|
+
|
|
16
|
+
Required environment keys, with no defaults:
|
|
17
|
+
|
|
18
|
+
- `PO_IDENTITY_ISSUER`, `PO_IDENTITY_JWT_KEY_WRAP` (at least 32 characters).
|
|
19
|
+
- `PO_UNSENDBOX_ENDPOINT`, `PO_UNSENDBOX_ORGANIZATION_ID`, `PO_UNSENDBOX_API_KEY`, `PO_UNSENDBOX_FROM`.
|
|
20
|
+
|
|
21
|
+
Optional OAuth configuration: `PO_APPLE_CLIENT_ID`, `PO_APPLE_CLIENT_SECRET`, `PO_APPLE_BUNDLE_ID`,
|
|
22
|
+
`PO_GOOGLE_CLIENT_ID`, `PO_GOOGLE_CLIENT_SECRET`, `PO_MICROSOFT_CLIENT_ID`,
|
|
23
|
+
`PO_MICROSOFT_CLIENT_SECRET`, `PO_GITHUB_CLIENT_ID`, `PO_GITHUB_CLIENT_SECRET`.
|
|
24
|
+
An enabled provider requires its secret. Apple's client secret is a server-held Apple client-secret
|
|
25
|
+
JWT, never an app credential. Operators must renew it before expiry. Native code exchange uses the
|
|
26
|
+
bundle ID when configured, rather than silently exchanging a native code as a web Services ID.
|
|
27
|
+
|
|
28
|
+
`PO_IDENTITY_WEB_RP_ID` enables web account passkeys against the configured issuer origin. Native
|
|
29
|
+
account passkeys remain disabled. Expo trusts `po://`; native scheme/capability changes belong
|
|
30
|
+
to phase 2. SSO includes OIDC and SAML, with no unsolicited-login override.
|
|
31
|
+
|
|
32
|
+
Node stores `identity.sqlite` under the admitted Po home, respecting `PO_HOME`. The Worker binds
|
|
33
|
+
`PO_IDENTITY` to one `PoIdentityObject` per configured issuer. Secrets are operator bindings, not
|
|
34
|
+
checked-in values. Hosts must serve the issuer through trusted HTTPS in production.
|
|
35
|
+
|
|
36
|
+
## Issuer endpoints
|
|
37
|
+
|
|
38
|
+
All paths are under `/api/auth/`. Node mounts them only when the global identity API is composed.
|
|
39
|
+
The Worker branches to the identity object before its legacy attach-token gate. Other routes keep
|
|
40
|
+
their existing authorization. Better Auth's session-only `/token` and built-in deletion endpoints
|
|
41
|
+
are disabled: they must not bypass device binding or the deletion outbox.
|
|
42
|
+
|
|
43
|
+
- Better Auth: email OTP, magic link, configured social providers, SSO and optional web passkeys.
|
|
44
|
+
- `GET jwks`: public signing keys. Private keys and OAuth tokens are wrapped at rest.
|
|
45
|
+
- `POST personal-org`: authenticated lazy provisioning.
|
|
46
|
+
- `POST devices/enroll`: authenticated first-device TOFU with proof of the new private key.
|
|
47
|
+
A durable enrollment marker forbids another TOFU after enrollment or revocation.
|
|
48
|
+
Returns a 256-bit recovery code once; only a salted PBKDF2-SHA-256 hash (600,000 iterations)
|
|
49
|
+
is stored. Optional `assurance: "hardware" | "software"` is signed and stored as self-reported.
|
|
50
|
+
Software enrollment is allowed; App Attest and Play Integrity are future attestation upgrades.
|
|
51
|
+
Later devices need a current trusted-device C10 `approval` or an owned hub's `hubEnrollment`.
|
|
52
|
+
- `POST identity-token`: session plus fresh enrolled-device proof, current person and membership.
|
|
53
|
+
C1 claims bind the device JWK thumbprint and network audience, with a 600-second lifetime.
|
|
54
|
+
- `POST delegated-token`: fresh calling-host proof plus enrolled-device-signed C5 consent. It needs
|
|
55
|
+
no person session. The composition must supply `callingHostKey(networkId, hostId)` from current
|
|
56
|
+
roster authority. Without that port it fails closed, not to a caller-advertised key. C2 binds
|
|
57
|
+
the host key, host ID and grant ID; expiry is the smaller of one hour and grant expiry.
|
|
58
|
+
- `POST device-key`: cookie-free lookup with `IdentityDeviceKeyRequest` from `apiContracts`.
|
|
59
|
+
Send `{proof}` signed with `po/host-request/v1` over `{issuer, hostId, keyId, issuedAt,
|
|
60
|
+
expiresAt, nonce}` by the hub key previously admitted through `hubs/claim`. Lifetime is at
|
|
61
|
+
most 60 seconds, with the same conservative 30-second skew deadline. Nonces (16 to 128
|
|
62
|
+
base64url characters) are durably single-use per host, including failed key lookups.
|
|
63
|
+
Returns `{keyId, issuer, subject, publicKey, revoked, revocationEpoch, trust, organizationId}`
|
|
64
|
+
from the registry,
|
|
65
|
+
never from caller-advertised keys. Only the claiming person's keys or keys of current
|
|
66
|
+
members of a hub's claimed organization where its claimant is still a member are visible. Unknown, out-of-scope, malformed,
|
|
67
|
+
stale, replayed, unenrolled and revoked-host requests all return the same no-store 404.
|
|
68
|
+
Migration 3 preserves legacy owner-only hub scope; fresh valid claims record org scopes,
|
|
69
|
+
including multiple organizations per hub.
|
|
70
|
+
The registry's `identity_hub.revoked` flag refuses lookup and further enrollment/claims.
|
|
71
|
+
- `GET devices`: same-person device list, public account keys, trust, self-reported assurance,
|
|
72
|
+
current person `revocationEpoch`, the durable `hasEnrolled` marker and any pending recovery.
|
|
73
|
+
- `POST devices/approval/request`: signed-in new-key `approval-request` proof creates an opaque
|
|
74
|
+
16-character, five-minute code. `GET devices/approval?code=...` is same-person lookup/polling;
|
|
75
|
+
`POST devices/approval` admits the trusted device's signed C10 envelope. The new device then
|
|
76
|
+
calls `devices/enroll` with a fresh new-key proof and that approval. Final admission is durable
|
|
77
|
+
and single-use. Both devices must be signed in; no email address enters the approval.
|
|
78
|
+
- `POST hubs/claim`: current enrolled-device C10 claim plus the nested QR host attestation records
|
|
79
|
+
the person's hub key. The hub must independently enforce its own local pin; the issuer never
|
|
80
|
+
authorizes on its behalf. Advertised unregistered hub keys cannot approve enrollment.
|
|
81
|
+
- `POST devices/recovery/code`: `{proof, recoveryCode}` with action `recover-code` enrolls at once
|
|
82
|
+
and consumes the code. `POST devices/recovery/start`: `{proof}` with action `recover-start`
|
|
83
|
+
persists a 72-hour due time. It durably queues a start email with a cancel link and one notice
|
|
84
|
+
per trusted device through `notifyTrustedDevice`. Missing or failed delivery ports leave jobs
|
|
85
|
+
pending. The email token is hashed in the pending row and wrapped in the delivery outbox.
|
|
86
|
+
- `POST devices/recovery/cancel`: `{cancelToken}` from email, or `{proof}` signed by a trusted
|
|
87
|
+
same-person device or the requesting new device. Action `cancel-recovery` binds
|
|
88
|
+
`targetDeviceId` to the recovery ID. The email GET only displays confirmation, avoiding
|
|
89
|
+
mutation by link scanners. Completion durably queues a second email.
|
|
90
|
+
- `POST devices/recovery/admin-approve`: current enrolled org owner/admin proof, action
|
|
91
|
+
`admin-approve`, `org` the limiting organization and `targetDeviceId` the pending recovery ID.
|
|
92
|
+
The signed-in pending device and admin must both belong to that organization. The resulting
|
|
93
|
+
device can mint only for that org and cannot approve unrestricted devices or claim hubs.
|
|
94
|
+
- `POST devices/revoke`: enrolled-device proof and a same-person target. Revocation immediately
|
|
95
|
+
stops new mints, including C2 grants signed by that device.
|
|
96
|
+
- `GET revocations?after=<sequence>`: public cursor feed, no addresses or credentials, no cache.
|
|
97
|
+
Enrolled hosts can fetch it without a person cookie or host proof.
|
|
98
|
+
- `POST delete-account`: refuses a sole company owner with other members, deletes person-owned
|
|
99
|
+
identity and device records, emits revocation, and durably queues personal-org/cloud-workspace
|
|
100
|
+
purge and Apple revocation before removing credentials.
|
|
101
|
+
|
|
102
|
+
Proofs use `identityProofFields(issuer, subject, action, proof)` with the tag
|
|
103
|
+
`po/identity-mint/v1` and P-256 SHA-256 P1363 base64url signatures. Actions include `enroll`, `mint`, `delegate`, `revoke`, `approval-request`, `recover-code`,
|
|
104
|
+
`recover-start`, `cancel-recovery` and `admin-approve`. The helper binds timestamps, nonce, org, device ID, optional enrollment
|
|
105
|
+
key, optional revocation target and the exact serialized grant. Proof times are milliseconds;
|
|
106
|
+
JWT dates are seconds. Proof lifetime is at most 60 seconds. Future issue time is at most 30
|
|
107
|
+
seconds; acceptance ends at expiry minus 30 seconds, inclusive. When supplied, assurance appends `["assurance", value]` to the signed fields; omitted assurance
|
|
108
|
+
keeps phase-1 signed bytes unchanged. Nonces are admitted durably in
|
|
109
|
+
transactions before tokens are released. Signing is followed by a current-authority recheck.
|
|
110
|
+
A destination still needs phase-3 JWT validation, C3/C4 proofs, durable nonce admission and local
|
|
111
|
+
authorization. Issuance is not workspace permission.
|
|
112
|
+
|
|
113
|
+
Native Apple sign-in sends `authorizationCode` beside `idToken: { token, nonce }` to
|
|
114
|
+
`sign-in/social`. Missing code or nonce is refused. The issuer verifies Apple's JWKS, issuer,
|
|
115
|
+
audience, time and nonce, exchanges the one-use code, verifies the exchanged subject and stores
|
|
116
|
+
an encrypted issuer/subject/provider-bound refresh credential before returning a session.
|
|
117
|
+
Deletion retains that ciphertext through Apple outages and retries via the outbox. No refresh
|
|
118
|
+
token is returned to the app. Provider response bodies and addresses are never logged.
|
|
119
|
+
|
|
120
|
+
## Persistence and limits
|
|
121
|
+
|
|
122
|
+
The SQLite adapter is the Better Auth spike adapter moved into runtime source, with literal predicates,
|
|
123
|
+
one-row versus many-row mutations, transaction-local executors, counts and versioned migration
|
|
124
|
+
admission. Initial schema creation is tied to the pinned plugin versions. Plugin/schema upgrades
|
|
125
|
+
need explicit new migrations; this is not a general Better Auth migration engine or a completed
|
|
126
|
+
upstream adapter conformance certification. The Node/workerd rollback and concurrency tests
|
|
127
|
+
now import the real package adapter.
|
|
128
|
+
|
|
129
|
+
Node drains the identity outbox on start and periodically. Recovery uses the injected
|
|
130
|
+
`scheduleRecovery` port and a Node scheduler. The identity DO's single alarm multiplexes recovery
|
|
131
|
+
due times and outbox retries; both deadlines are restored from SQLite after reconstruction.
|
|
132
|
+
`runDueRecovery` uses the injected clock and atomically completes only due, uncancelled requests.
|
|
133
|
+
|
|
134
|
+
Every completed recovery increments the person's epoch atomically with device insertion and a
|
|
135
|
+
feed row carrying `epoch`. The recovered device stores that enrollment epoch. SQLite refuses
|
|
136
|
+
non-increasing epoch updates. Other enrolled devices stay trusted, but C5 `revocationEpoch` must
|
|
137
|
+
exactly equal the current epoch to mint C2. Missing grant epochs mean zero, deliberately voiding
|
|
138
|
+
all legacy grants after the first recovery. C1/C2 JWTs carry `revocation_epoch`.
|
|
139
|
+
|
|
140
|
+
Phase 3 still needs destination JWT/feed checks. A pre-recovery identity token can remain valid
|
|
141
|
+
until its own expiry, at most ten minutes, at a destination that has not fetched the feed.
|
|
142
|
+
Delegated tokens retain their existing at-most-one-hour expiry. Propagation is not bounded here.
|
|
143
|
+
|
|
144
|
+
Hub policy is exported as `createHubDeviceTrust` from core's remote-access store. Owner claims,
|
|
145
|
+
account pins and replay admission persist in `hub-trust.json`. Approval requires a device already
|
|
146
|
+
pinned here and verified current issuer device authority. Recovered keys need a fresh QR pin in
|
|
147
|
+
the current recovery epoch. Issuer enrollment alone is never owner-level authority. Phase 3 must
|
|
148
|
+
compose this gate into federation control routes; it is intentionally not wired into legacy
|
|
149
|
+
layer-2 native registration, whose host-scoped keys differ from the account device key.
|
|
150
|
+
|
|
151
|
+
### Phase-4 provisioning API
|
|
152
|
+
|
|
153
|
+
`ensurePersonalOrg(subject, 'cloud-workspace')` atomically creates the personal org, owner membership,
|
|
154
|
+
unique `(issuer, subject)` marker, first workspace allocation and materialization outbox item.
|
|
155
|
+
Concurrent calls and retries return the same `{orgId, workspaceId}`. Slug collisions retry inside
|
|
156
|
+
that transaction (up to 16 attempts). Existing subject-only markers migrate without replacing orgs.
|
|
157
|
+
`PersonalOrgTrigger` reserves `own-hub-link` for phase 5; it is currently refused.
|
|
158
|
+
|
|
159
|
+
The authenticated `POST personal-org` accepts `{trigger: "cloud-workspace"}` and returns
|
|
160
|
+
`{organizationId, orgId, workspaceId}` for bootstrap before device enrollment and token minting.
|
|
161
|
+
Legacy `{}` and `personalOrg(subject)` remain org-only compatibility calls. Signup still creates no org.
|
|
162
|
+
|
|
163
|
+
The trusted internal API also exposes `allocateWorkspace(subject, orgId)` (a new allocation per call),
|
|
164
|
+
`listOrgWorkspaces(subject, orgId)` and `isWorkspaceAllocatedToOrg(subject, orgId, workspaceId)`.
|
|
165
|
+
All require a current account and org membership. The last method returns false for unallocated ids.
|
|
166
|
+
The Worker must authenticate the subject before calling these methods; they are not bearer endpoints.
|
|
167
|
+
`orgRole` and `federationAuthority` expose live membership and device/epoch authority to its verifier.
|
|
168
|
+
The latter fails closed on issuer, membership, device revocation and restricted-org mismatches.
|
|
169
|
+
|
|
170
|
+
`listProvisioningOutbox()` returns typed `{id, kind: "materialize-workspace", payload: {orgId, workspaceId}}`
|
|
171
|
+
items only. `ackProvisioningOutbox(id)` is idempotent and cannot ack recovery, purge or Apple jobs.
|
|
172
|
+
Alternatively the host supplies `materializeWorkspace(payload)` and calls `drainOutbox()` (also used
|
|
173
|
+
by `runDueRecovery`). Missing consumers and failed deliveries stay pending. Materialization is an
|
|
174
|
+
at-least-once, separate cross-object step: its consumer must be idempotent. Hosts restore retry
|
|
175
|
+
alarms from persisted pending jobs on boot and schedule after allocation. Identity commits never
|
|
176
|
+
claim cross-object atomicity. Deletion cancels personal-org allocation/materialization records and
|
|
177
|
+
queues the existing purge job. Host-side purge and in-flight materialization need coordinated
|
|
178
|
+
idempotent handling; production alarm delivery and full cloud deletion remain Worker verification.
|
|
179
|
+
|
|
180
|
+
Types are exported from the package root and `apiContracts`. No dependency changes are required.
|
|
181
|
+
Registry migration 4 adds provisioning tables and repairs the monotonic epoch update trigger when
|
|
182
|
+
opening older v2 databases that did not have it. SQLite upgrade tests preserve v2 devices, hubs and
|
|
183
|
+
pending outbox data. The purge consumer is a host port. Until wired, purge jobs remain pending durably.
|
|
184
|
+
No purge SLA, roster cadence or emergency JWKS-cache bound is invented here.
|
|
185
|
+
Calling-host roster authority and destination authorization remain separate phases. Apple exchange/revocation tests use fake HTTP providers. Real providers, native
|
|
186
|
+
Apple return, deployment, production quotas and provider key rotation remain unverified.
|
|
187
|
+
|
|
188
|
+
## Phase-1 local evidence
|
|
189
|
+
|
|
190
|
+
Final checks in the multihost worktree:
|
|
191
|
+
|
|
192
|
+
```sh
|
|
193
|
+
NX_DAEMON=false pnpm nx run-many -p @agimon-ai/po-identity @agimon-ai/po-cloudflare @agimon-ai/po-core @agimon-ai/po-web-security @agimon-ai/po -t lint,typecheck,build --parallel=1 --output-style=static
|
|
194
|
+
NX_DAEMON=false pnpm nx run @agimon-ai/po-cloudflare:test --maxWorkers=1 --output-style=static
|
|
195
|
+
NX_DAEMON=false pnpm nx run-many -p @agimon-ai/po-identity @agimon-ai/po-core @agimon-ai/po-web-security @agimon-ai/po -t test --parallel=1 --output-style=static
|
|
196
|
+
pnpm --filter @agimon-ai/po-cloudflare exec wrangler deploy --dry-run --outdir .wrangler/identity-bundle
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
All listed Nx targets passed. Full test counts: identity 11, Cloudflare 64, core 1,981, security 126,
|
|
200
|
+
CLI 962 passed with one existing skipped test. A parallel run hit the already documented isolate
|
|
201
|
+
memory-test duration bound; the isolated rerun and full single-worker Cloudflare suite passed.
|
|
202
|
+
No isolate code, assertions or time bounds were changed. Worktree-local vibe-lint preflight,
|
|
203
|
+
including `po-edge-portable`, and `git diff --check` passed.
|
|
204
|
+
|
|
205
|
+
Wrangler 4.150.0 reported total upload **13,553.00 KiB raw, 3,129.00 KiB gzip**. The main emitted
|
|
206
|
+
`worker.js` was **13,375,137 raw bytes, 2,969,373 gzip bytes**; total upload also includes WASM.
|
|
207
|
+
This is the composed Po Worker, including identity and SAML, not the smaller Better Auth and SAML spikes.
|
|
208
|
+
It exceeds a 3 MiB compressed free-worker ceiling. Paid-plan limits, deployment, CPU/startup
|
|
209
|
+
budgets and actual production-provider behavior were not verified. Dry-run artifacts remain ignored.
|
|
210
|
+
|
|
211
|
+
## Phase-2 verification boundary
|
|
212
|
+
|
|
213
|
+
Focused protocol tests cover signed approval chains, forgery/expiry/replay refusal, owned-hub
|
|
214
|
+
attestation admission, one-use recovery codes, software assurance, org-only admin recovery,
|
|
215
|
+
monotonic epochs, stale/invalid grant epochs, email and device cancellation, exact 72-hour
|
|
216
|
+
completion and restart. A shared fixture runs the real KDF and signature/envelope helpers on
|
|
217
|
+
both Node SQLite and workerd DO SQLite, with only delivery, scheduling and time injected.
|
|
218
|
+
The hub-store tests cover account-association round trips, durable claims and approval chains,
|
|
219
|
+
and recovered-device owner denial until QR. A diagnostic spy checks no recovery address is logged.
|
|
220
|
+
|
|
221
|
+
Real email deliverability, the native push adapter, production DO alarm delivery across eviction,
|
|
222
|
+
production issuer-to-hub authority synchronization and end-to-end native QR attestation remain
|
|
223
|
+
unverified. No dependency or lockfile changes are needed by this protocol implementation.
|
|
224
|
+
|
|
225
|
+
## Network roster issuer (phase 5)
|
|
226
|
+
|
|
227
|
+
- `GET /api/auth/roster-metadata` publishes `{issuer, rosterPublicKey}`. The key is base64url DER
|
|
228
|
+
P-256 SPKI, not PEM. Claim-time discovery must fetch it from the already trusted issuer and pin
|
|
229
|
+
it locally, never trust a key in a roster response. The dedicated private roster key is persisted
|
|
230
|
+
encrypted with `jwtKeyWrap`. No route exposes it. Rotation currently requires every hub to re-pin;
|
|
231
|
+
the upgrade path is signed key-transition records.
|
|
232
|
+
- `POST /api/networks/own-hub-link` returns `{networkId}` and lazily creates one personal org per
|
|
233
|
+
person in the existing transaction. It does not allocate a cloud workspace.
|
|
234
|
+
- `GET /api/networks/:networkId/roster` returns an issuer-signed C6 envelope. Current member identity
|
|
235
|
+
tokens (ES256, issuer and network audience, live membership, device and recovery epoch) or existing
|
|
236
|
+
signed-in sessions are accepted. Host polling may instead send `x-po-roster-proof`: a JSON
|
|
237
|
+
`po/host-request/v1` envelope signed by the current host key, with `{networkId, hostId,
|
|
238
|
+
destination: issuer, method: 'GET', path: exactPathname, nonce, issuedAt, expiresAt}`. Envelope freshness
|
|
239
|
+
and durable single-use nonces apply; removed or evicted hosts cannot poll.
|
|
240
|
+
- `POST /api/networks/:networkId/hosts` accepts `{entry}` from the entry owner's current membership;
|
|
241
|
+
`POST /api/networks/:networkId/changes` accepts `{change}` from an owner or admin. A change names
|
|
242
|
+
the next epoch. Before any epoch or eviction write, the issuer verifies host entries against the
|
|
243
|
+
submitting owner's enrolled, non-revoked device, and eviction/voter changes against a current
|
|
244
|
+
org owner's enrolled, non-revoked device. Durable records verify as of issuance, with at most
|
|
245
|
+
30 seconds of future skew. Term changes require a current voter host's own key and verify at
|
|
246
|
+
the current time. These enrollment checks do not replace `applyRoster` with independently pinned keys.
|
|
247
|
+
Host entries, changes and all Better Auth membership writes advance the epoch transactionally.
|
|
248
|
+
Membership removal permanently marks that person's host IDs unavailable.
|
|
249
|
+
- Composition APIs on `createIdentityHome()` are `networkRoster(subject, networkId)`,
|
|
250
|
+
`registerRosterHost(subject, networkId, entry)`, `registerRosterChange(subject, networkId, change)`,
|
|
251
|
+
`rosterIssuerPublicKey()`, `hostKey(networkId, hostId)`, `rosterPins(subject, networkId)`, and
|
|
252
|
+
`registerCloudRosterHost(subject, networkId, {hostId, publicKey, endpoints?})`. The cloud helper
|
|
253
|
+
signs with the issuer and uses reserved device key ID `issuer-roster`; identical calls preserve
|
|
254
|
+
the original owner and epoch. `rosterPins` supplies `{hostId, issuer, subject, keyId, publicKey}`
|
|
255
|
+
records for Po Cloud only. Its trust ceiling is issuer-enrolled devices, not local hub pins.
|
|
256
|
+
|
|
257
|
+
Snapshots retain all evictions, the latest voter change per live host, and the current epoch's term
|
|
258
|
+
changes. The issuer orders these records and may withhold changes. A new joiner skips an eviction
|
|
259
|
+
it cannot verify only if the target host is absent from both its local state and the snapshot.
|
|
260
|
+
It then lacks that tombstone, so a malicious issuer could later re-include an old device-signed host
|
|
261
|
+
entry. Independent hub pins remain mandatory. Compaction requires a signed checkpoint. Roster authorization does not replace C9 quorum commitment, and this issuer
|
|
262
|
+
does not claim Byzantine safety or availability in small networks.
|
|
263
|
+
|
|
264
|
+
Durable device host entries and owner voter/eviction decisions verify as of issuance, including on
|
|
265
|
+
catch-up after their declared expiry. The fresh issuer snapshot supplies freshness. Term diagnostics
|
|
266
|
+
still verify at the current time. Local pin revocation or rotation removes the old key's authority,
|
|
267
|
+
so its old records stop verifying. A compromised still-pinned owner key can backdate records until
|
|
268
|
+
it is unpinned; the issuer cannot correct a host's local trust decision.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
exports.__toESM = __toESM;
|