@oxyhq/core 12.4.0 → 12.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "12.4.0",
3
+ "version": "12.4.1",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -159,6 +159,26 @@ describe('refreshPersistedSession — arm 1 (device-secret mint)', () => {
159
159
  expect(await store.load()).toEqual(STORED);
160
160
  expect(signInWithSharedIdentity).not.toHaveBeenCalled();
161
161
  });
162
+
163
+ it('KEEPS the store on an UNRECOGNIZED 401 (proxy / middleware / deploy-window) — never wipes the credential on an ambiguous 401', async () => {
164
+ const store = createMemoryAuthStateStore();
165
+ await store.save(STORED);
166
+ const signInWithSharedIdentity = jest.fn(async () => null);
167
+ const { oxy } = makeOxy({
168
+ mintFromDeviceSecret: async () => {
169
+ // A 401 whose body is NEITHER `invalid_device_secret` NOR `no_active_session`
170
+ // — an auth-layer / proxy / starting-instance 401 common during a deploy
171
+ // window. It is NOT proof the secret diverged, so the durable device
172
+ // credential must survive (treated as transient) and a later attempt self-heals.
173
+ throw Object.assign(new Error('Unauthorized'), { status: 401 });
174
+ },
175
+ signInWithSharedIdentity,
176
+ });
177
+
178
+ expect(await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: true })).toBeNull();
179
+ expect(await store.load()).toEqual(STORED);
180
+ expect(signInWithSharedIdentity).not.toHaveBeenCalled();
181
+ });
162
182
  });
163
183
 
164
184
  describe('refreshPersistedSession — arm 2 (native shared-key fallback)', () => {
@@ -136,9 +136,18 @@ export async function refreshDeviceSecretArm(deps: {
136
136
  // Structural read (not `instanceof Error`): the thrown value can be a
137
137
  // plain ApiError-shaped object or come from another realm.
138
138
  const message = (error as { message?: unknown })?.message;
139
- return typeof message === 'string' && message.includes('no_active_session')
140
- ? { status: 'no-session' }
141
- : { status: 'invalid-secret' };
139
+ const body = typeof message === 'string' ? message : '';
140
+ // ONLY the server's explicit `invalid_device_secret` proves the presented
141
+ // secret is bad and may clear the durable device credential. `no_active_session`
142
+ // is an authoritative signed-out. ANY OTHER 401 — a middleware/CSRF/proxy 401,
143
+ // an ALB/starting-instance 401, a CORS error page, etc., all common during a
144
+ // deploy/restart window — is NOT proof the secret diverged: treat it as
145
+ // transient and KEEP the credential so a later attempt self-heals. Wiping the
146
+ // credential on an ambiguous 401 is what logged users out on every deploy,
147
+ // ecosystem-wide.
148
+ if (body.includes('invalid_device_secret')) return { status: 'invalid-secret' };
149
+ if (body.includes('no_active_session')) return { status: 'no-session' };
150
+ return { status: 'transient' };
142
151
  }
143
152
  return { status: 'transient' };
144
153
  }