The import carries the web's OAuth client key
MergedThis pull copies hatk's OAuth keys and web sessions into the index. It also adds login::carry_hatk, which trades hatk's sealed cookies for grain_session and grain_accounts. I read only the last commit's files (hatk.rs, login::carry_hatk, copy_sessions and thumbprint), not the stacked login commits. I didn't check the CI results.
I found nothing that must change.
- Cookie opening: it uses HKDF over the sorted JWK, an IV of exactly 12 bytes, and a 30-day check against
ts. The unit test uses cookies sealed by hatk's ownencrypt, so the key derivation is checked against the real thing. carry_hatk: it only acts when__hatk_sessionopens under the key and nograin_sessionis present. It clears hatk's cookies on every path, and it puts the carried cookies ahead of the browser's own so they are the ones read.copy_sessions: it skips a row bound to another key or without a refresh token. TheNOT NULLcolumns it reads asStringareNOT NULLin hatk's schema. An unknown expiry reads as expired, which forces a refresh on first use.- Expiry units:
expires_atis in seconds ingrain::session::OAuth. hatk's fixture values (such as 1800000000) are in seconds too, so the units match. Check that against a real backup in the cutover rehearsal you already plan.
This round drops the session and cookie carrying from round 1 and keeps one thing: import.rs now copies hatk's _oauth_keys row pds-client-auth-key into oauth_key. I read only the last commit's diff to import.rs and its tests. I didn't check the CI results.
I found nothing that must change.
- The copy: the
KEPTentry's count and select are both filtered tokid = 'pds-client-auth-key', so the tally is 1 of 1 andappview-oauth-keystays behind. - The test: it checks that only the client key lands in
oauth_key. It then openslogin::Web::newon the imported index and checks the JWKS has the samekidandx, so the key survives the move in a form the web client loads. - Dropping the sessions: this fits the standing rule against reading hatk's cookie format at runtime.
_oauth_sessionsis still listed as left behind. - One wording gap:
_oauth_keysno longer appears in the summary's not-imported list, even thoughappview-oauth-keyis left. The pull body says "all listed in the summary's not-imported tables". The docs inimport.rsdo say the other keys stay behind, so this is only the body's wording.
This round is the same change as round 2, rebased onto web/login with feature/rust merged in. I read only the last commit's diff to grain-import's lib.rs and tests.rs, and I didn't check the CI results.
I found nothing that must change.
- The copy: the
oauth_keyentry inKEPTfilters both its count and its select tokid = 'pds-client-auth-key'.appview-oauth-keytherefore stays behind, and the tally is 1 of 1. - The test: the fixture now has both keys, and the client key is a WebCrypto-style JWK. The test checks that only the client key lands in
oauth_key. It then openslogin::Web::newon the imported index and checks the JWKS has the samekidandx._oauth_keysis correctly removed from the expected not-imported list. - Docs: the module docs now say the client key is kept and that hatk's other keys and sessions are left behind. This also covers the wording gap I noted in round 2.
This round keeps the same change as before: grain-import copies hatk's _oauth_keys row pds-client-auth-key into oauth_key. I read the last commit's diff against web/login. The checks are still queued, so I haven't seen them run.
I found nothing that must change.
- The copy: the
KEPTentry's count and select are both filtered tokid = 'pds-client-auth-key', soappview-oauth-keystays behind and the tally is 1 of 1. Thekidandprivate_keycolumns match hatk's_oauth_keysschema indb/schema.sql. - The test: it checks that only the client key lands in
oauth_key, byte for byte. It then opensWeb::newon the imported index and checks the JWKS has the samekidandx._oauth_keysis correctly removed from the expected not-imported list. - Docs: the module docs now match the pull body's account of what is kept and what is left behind.
- The import carries the web's OAuth client key
- main
What changes
The import carries the web's OAuth client key.
grain-importcopies hatk's_oauth_keysrowpds-client-auth-keyintooauth_key(V7, from the pull below this one), the private JWK as hatk kept it. grain-serve's web client loads it at start (Web::newtakes the kept key before it would make one), so its JWKS at/oauth/client-jwks.jsonnames the same key under the same key ID, and its client ID stayshttps://grain.social/oauth-client-metadata.json. The summary counts it as one more kept table.That is the whole of it, in one commit to
crates/grain-import(src/lib.rsandsrc/tests.rs), the only crate that knows the old database's layout; the branch is otherwiseweb/loginwith feature/rust merged in.Why
_oauth_keysis the one table AGENTS.md says voids every session when it changes. Each PDS knows grain's client by its ID and this key, so keeping both keeps grain the same client to every PDS across the move.Session tables: what carries and what does not
_oauth_keys.pds-client-auth-key, asoauth_key._oauth_keys.appview-oauth-key(hatk's DPoP key for every PDS session it held, and its cookie key),_oauth_sessions(the web's PDS sessions), and hatk's OAuth issuer tables (the apps'grain-native://applogins to grain.social). The tables are listed in the summary's not-imported tables;appview-oauth-keyis not, since_oauth_keyscounts as read, andgrain-import's docs name it as left behind.Web sessions do not survive cutover: every web user logs in once more. An earlier head of this pull carried
_oauth_sessionsintoweb_sessionand traded hatk's__hatk_sessioncookie for grain's on first request. That trade reads hatk's cookie format at runtime, which the standing rule now rules out, and without it a carried session is unreachable (no browser holds a cookie naming it), so carrying them would only keep dead tokens. The apps' hatk-issued tokens end at cutover by decision.How it was checked
grain-import's tests: the fixture's_oauth_keyshas both of hatk's keys, with the client key a real P-256 JWK as WebCrypto exports it (key_ops,ext); the test checks that onlypds-client-auth-keylands inoauth_key, the tally is 1 of 1,_oauth_keysis no longer in the not-imported list, andlogin::Web::newon the imported index serves a JWKS with that key'sxunderpds-client-auth-key. The rest is left to the checks.Shortcomings
--public-url: nothing it carries depends on where the web is served.pds-client-auth-key(it never served a web login), grain-serve makes one at first start, as it would on a new index.Review
crates/grain-import/src/lib.rs(theoauth_keyentry ofKEPTand the docs above it) and its test. Stacked onweb/login(pull 3mz7oyx7oiu3c); its own diff is the last commit.🤖 Generated with Claude Code