@gatemux/dsh-gatemux-login 1.0.1 → 1.0.3

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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/account.js +44 -7
  3. package/package.json +15 -15
package/README.md CHANGED
@@ -22,8 +22,8 @@ keep another default.
22
22
 
23
23
  ## Requirements
24
24
 
25
- - DeepSeek Harness desktop app, `0.1.7.x`.
26
- The plugin declares `@deepseek-ai/dsh-*` peer dependencies pinned to that line.
25
+ - DeepSeek Harness desktop app, `0.1.7.x` or `0.2.x`.
26
+ The plugin declares `@deepseek-ai/dsh-*` peer dependencies across both lines.
27
27
  A different DSH release is rejected at install time; installing anyway needs a
28
28
  version exemption, which risks crashes and data loss.
29
29
  - A GateMux account.
@@ -105,7 +105,7 @@ module or the patch is skipped.
105
105
  | `ACCOUNT_QUOTA_EXCEEDED` | Your GateMux balance or spend cap is exhausted. Top up. |
106
106
  | `ip_not_allowed` (HTTP 403) | Your network is not in the GateMux application's trusted egress IP allowlist. Contact the plugin operator. Re-authorizing will not fix it. |
107
107
  | `scope_denied`, `model_not_allowed` (HTTP 403) | The application registration does not permit this request. Contact the plugin operator. |
108
- | Install refused for peer dependencies | Your DSH version is outside `0.1.7.x`. |
108
+ | Install refused for peer dependencies | Your DSH version is outside `0.1.7.x` and `0.2.x`. |
109
109
 
110
110
  403 errors are reported as authorization faults, not retried: they are
111
111
  configuration problems on the application's side.
package/account.js CHANGED
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto'
23
+ import { platform, release } from 'node:os'
23
24
  import { promises as streamPromises } from 'node:stream'
24
25
 
25
26
  import { Service } from '@deepseek-ai/cordis'
@@ -68,6 +69,16 @@ function parseGrant(payload, issuer) {
68
69
  return { version: 1, accessToken, refreshToken, expiresAt, issuer }
69
70
  }
70
71
 
72
+ /**
73
+ * Read a stored record as a grant this deployment may use.
74
+ * @param record - record at this provider's key, or undefined when none is stored.
75
+ * @param issuer - origin a grant must carry to be usable here.
76
+ * @returns the parsed grant, or null when no record is stored or it is unusable.
77
+ */
78
+ function usableGrant(record, issuer) {
79
+ return record !== undefined && record.kind === 'grant' ? parseGrant(record.payload, issuer) : null
80
+ }
81
+
71
82
  /** Accept a bare HTTPS origin, rejecting paths, credentials, and non-HTTPS schemes. */
72
83
  function originOf(value) {
73
84
  let url
@@ -182,22 +193,27 @@ export class GateMuxAccount extends DeepSeekAccount {
182
193
  /** Discard a stored grant issued by another environment. */
183
194
  async [Service.init]() {
184
195
  const record = await this.ctx.credentials.readRecord(KEY)
185
- if (record === undefined) return
186
- const grant = record.kind === 'grant' ? parseGrant(record.payload, this.issuer) : null
187
- if (grant !== null) return
196
+ if (record === undefined || usableGrant(record, this.issuer) !== null) return
188
197
  await this.ctx.credentials.deleteRecord(KEY)
189
198
  this.logger.warn('stored grant discarded', { reason: 'unreadable-or-foreign-issuer' })
190
199
  }
191
200
 
192
201
  async getState() {
193
202
  const record = await this.ctx.credentials.readRecord(KEY)
194
- if (record !== undefined && (record.kind !== 'grant' || parseGrant(record.payload, this.issuer) === null)) {
195
- throw new GateMuxAuthError('stored grant is unreadable', 'storage')
203
+ const grant = usableGrant(record, this.issuer)
204
+ // A record this deployment cannot use reads as absent instead of raising: every
205
+ // caller turns a throw into a failure with no diagnosis. `startSignIn` returns
206
+ // this outside its own catch, so a raise there rejects the start call and the
207
+ // dialog reports a sign-in failure for a record the person never supplied, and
208
+ // `watch` ends the account stream. The record is left in place for a sign-in to
209
+ // overwrite; discarding it here could delete a grant committed since the read.
210
+ if (record !== undefined && grant === null) {
211
+ this.logger.warn('stored grant is unreadable', { reason: 'unreadable-or-foreign-issuer' })
196
212
  }
197
213
  const attempt = this.attempt?.view ?? null
198
214
  return {
199
- status: record === undefined ? 'signed-out' : 'credential-stored',
200
- attempt: record === undefined && attempt?.phase === 'succeeded' ? null : attempt,
215
+ status: grant === null ? 'signed-out' : 'credential-stored',
216
+ attempt: grant === null && attempt?.phase === 'succeeded' ? null : attempt,
201
217
  links: {
202
218
  usageUrl: `${this.consoleOrigin}/app/usage`,
203
219
  topUpUrl: `${this.consoleOrigin}/app/billing`,
@@ -378,6 +394,27 @@ export class GateMuxAccount extends DeepSeekAccount {
378
394
  return null
379
395
  }
380
396
 
397
+ /**
398
+ * Read the signed-in account ID without a request and without credentials.
399
+ *
400
+ * GateMux registers no device, so `deviceId` is omitted rather than invented.
401
+ * The ID comes from the last successful profile read for this same grant; a
402
+ * caller that reads it before that profile read gets the OS version alone.
403
+ *
404
+ * @returns the Node platform version, plus the account ID while a profile for the stored grant is cached.
405
+ */
406
+ async getDeviceIdentity() {
407
+ const lifetime = this.detailsLifetime
408
+ const grant = await this.readGrant(lifetime)
409
+ const profile = grant !== null && this.lastProfile?.token === grant.accessToken
410
+ ? this.lastProfile.profile
411
+ : null
412
+ return {
413
+ ...(profile?.id == null ? {} : { userId: profile.id }),
414
+ osVersion: `${platform()} ${release()}`,
415
+ }
416
+ }
417
+
381
418
  /**
382
419
  * Run one authorization attempt: PKCE, browser callback, code exchange, commit.
383
420
  * @param session - the attempt's cancellation signal and commit channel.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gatemux/dsh-gatemux-login",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "description": "Sign in with GateMux for DeepSeek Harness: users pay from their own GateMux balance.",
6
6
  "publishConfig": {
@@ -29,22 +29,22 @@
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@deepseek-ai/cordis": "^4.0.4",
32
- "@deepseek-ai/dsh-authorization": "^0.1.7-rc.2",
33
- "@deepseek-ai/dsh-credentials": "^0.1.7-rc.2",
34
- "@deepseek-ai/dsh-deepseek-account": "^0.1.7-rc.2",
35
- "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2",
36
- "@deepseek-ai/dsh-launch-environment": "^0.1.7-rc.2",
37
- "@deepseek-ai/dsh-llm": "^0.1.7-rc.2",
38
- "@deepseek-ai/dsh-llm-deepseek": "^0.1.7-rc.2"
32
+ "@deepseek-ai/dsh-authorization": "^0.1.7-rc.2 || ^0.2.0-rc.2",
33
+ "@deepseek-ai/dsh-credentials": "^0.1.7-rc.2 || ^0.2.0-rc.2",
34
+ "@deepseek-ai/dsh-deepseek-account": "^0.1.7-rc.2 || ^0.2.0-rc.2",
35
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 || ^0.2.0-rc.2",
36
+ "@deepseek-ai/dsh-launch-environment": "^0.1.7-rc.2 || ^0.2.0-rc.2",
37
+ "@deepseek-ai/dsh-llm": "^0.1.7-rc.2 || ^0.2.0-rc.2",
38
+ "@deepseek-ai/dsh-llm-deepseek": "^0.1.7-rc.2 || ^0.2.0-rc.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@deepseek-ai/cordis": "^4.0.4",
42
- "@deepseek-ai/dsh-authorization": "^0.1.7-rc.2",
43
- "@deepseek-ai/dsh-credentials": "^0.1.7-rc.2",
44
- "@deepseek-ai/dsh-deepseek-account": "^0.1.7-rc.2",
45
- "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2",
46
- "@deepseek-ai/dsh-launch-environment": "^0.1.7-rc.2",
47
- "@deepseek-ai/dsh-llm": "^0.1.7-rc.2",
48
- "@deepseek-ai/dsh-llm-deepseek": "^0.1.7-rc.2"
42
+ "@deepseek-ai/dsh-authorization": "^0.1.7-rc.2 || ^0.2.0-rc.2",
43
+ "@deepseek-ai/dsh-credentials": "^0.1.7-rc.2 || ^0.2.0-rc.2",
44
+ "@deepseek-ai/dsh-deepseek-account": "^0.1.7-rc.2 || ^0.2.0-rc.2",
45
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 || ^0.2.0-rc.2",
46
+ "@deepseek-ai/dsh-launch-environment": "^0.1.7-rc.2 || ^0.2.0-rc.2",
47
+ "@deepseek-ai/dsh-llm": "^0.1.7-rc.2 || ^0.2.0-rc.2",
48
+ "@deepseek-ai/dsh-llm-deepseek": "^0.1.7-rc.2 || ^0.2.0-rc.2"
49
49
  }
50
50
  }