Skip to content

Commit 8077f3e

Browse files
committed
Release v1.12.1
Origin-SHA: 2309e696a4208c0d227fbb928967a1fdedfdcbfd
1 parent a988502 commit 8077f3e

4 files changed

Lines changed: 123 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# @opensea/cli
22

3+
## 1.12.1
4+
5+
### Patch Changes
6+
7+
- 71ae9ee: Keep OAuth scope status aligned with the OpenAPI scope catalog when the token endpoint omits its `scope` field.
8+
- Updated dependencies [71ae9ee]
9+
- @opensea/sdk@11.4.1
10+
311
## 1.12.0
412

513
### Minor Changes

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opensea/cli",
3-
"version": "1.12.0",
3+
"version": "1.12.1",
44
"type": "module",
55
"description": "OpenSea CLI - Query the OpenSea API from the command line or programmatically",
66
"main": "dist/index.js",
@@ -24,7 +24,7 @@
2424
},
2525
"dependencies": {
2626
"@opensea/api-types": "^0.8.0",
27-
"@opensea/sdk": "^11.4.0",
27+
"@opensea/sdk": "^11.4.1",
2828
"@opensea/wallet-adapters": "^0.3.2",
2929
"commander": "^14.0.3",
3030
"zod": "^4.3.6"

src/auth/store.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
22
import { homedir } from "node:os"
33
import { join } from "node:path"
4+
import { extractOpenSeaScopes } from "@opensea/sdk"
45

56
/**
67
* Shape of a persisted auth token entry.
@@ -58,6 +59,12 @@ function writeStore(store: AuthStore): void {
5859
}
5960
}
6061

62+
function withDerivedScopes(token: StoredToken): StoredToken {
63+
if (token.scopes.length > 0) return token
64+
const scopes = extractOpenSeaScopes(token.accessToken)
65+
return scopes.length > 0 ? { ...token, scopes } : token
66+
}
67+
6168
/**
6269
* Save a token to the persistent store, keyed by wallet address.
6370
*/
@@ -74,23 +81,25 @@ export function saveToken(token: StoredToken): void {
7481
export function loadCurrentToken(): StoredToken | undefined {
7582
const store = readStore()
7683
if (!store.currentAddress) return undefined
77-
return store.tokens[store.currentAddress]
84+
const token = store.tokens[store.currentAddress]
85+
return token ? withDerivedScopes(token) : undefined
7886
}
7987

8088
/**
8189
* Load a specific token by wallet address.
8290
*/
8391
export function loadToken(address: string): StoredToken | undefined {
8492
const store = readStore()
85-
return store.tokens[address.toLowerCase()]
93+
const token = store.tokens[address.toLowerCase()]
94+
return token ? withDerivedScopes(token) : undefined
8695
}
8796

8897
/**
8998
* List all stored tokens.
9099
*/
91100
export function listTokens(): StoredToken[] {
92101
const store = readStore()
93-
return Object.values(store.tokens)
102+
return Object.values(store.tokens).map(withDerivedScopes)
94103
}
95104

96105
/**

test/auth/store.test.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { rmSync } from "node:fs"
2+
import { afterEach, describe, expect, test, vi } from "vitest"
3+
4+
const { testHome } = vi.hoisted(() => ({
5+
testHome: "/tmp/opensea-cli-auth-store-test",
6+
}))
7+
8+
vi.mock("node:os", () => ({ homedir: () => testHome }))
9+
10+
import {
11+
listTokens,
12+
loadCurrentToken,
13+
loadToken,
14+
saveToken,
15+
} from "../../src/auth/store.js"
16+
17+
function jwt(payload: Record<string, unknown>): string {
18+
const encoded = Buffer.from(JSON.stringify(payload))
19+
.toString("base64url")
20+
.replace(/=+$/, "")
21+
return `header.${encoded}.signature`
22+
}
23+
24+
afterEach(() => {
25+
rmSync(testHome, { recursive: true, force: true })
26+
})
27+
28+
describe("auth store", () => {
29+
test("derives missing stored scopes from the OpenSea JWT claim", () => {
30+
const token = {
31+
accessToken: jwt({
32+
opensea_scopes:
33+
"write:orders read:eligibility read:rewards unknown:scope",
34+
}),
35+
refreshToken: "refresh",
36+
expiresAt: "2030-01-01T00:00:00.000Z",
37+
scopes: [],
38+
address: "0xAbC",
39+
}
40+
saveToken(token)
41+
42+
expect(loadCurrentToken()?.scopes).toEqual([
43+
"read:eligibility",
44+
"write:orders",
45+
])
46+
expect(loadToken("0xabc")?.scopes).toEqual([
47+
"read:eligibility",
48+
"write:orders",
49+
])
50+
expect(listTokens()[0]?.scopes).toEqual([
51+
"read:eligibility",
52+
"write:orders",
53+
])
54+
})
55+
56+
test("preserves a non-empty stored scope list", () => {
57+
saveToken({
58+
accessToken: jwt({ opensea_scopes: "write:orders" }),
59+
refreshToken: "refresh",
60+
expiresAt: "2030-01-01T00:00:00.000Z",
61+
scopes: ["read:favorites"],
62+
address: "0xabc",
63+
})
64+
65+
expect(loadCurrentToken()?.scopes).toEqual(["read:favorites"])
66+
})
67+
68+
test("lists mixed stored and derived scope states", () => {
69+
saveToken({
70+
accessToken: jwt({ opensea_scopes: "write:orders" }),
71+
refreshToken: "refresh-1",
72+
expiresAt: "2030-01-01T00:00:00.000Z",
73+
scopes: [],
74+
address: "0xabc",
75+
})
76+
saveToken({
77+
accessToken: jwt({ opensea_scopes: "write:orders" }),
78+
refreshToken: "refresh-2",
79+
expiresAt: "2030-01-01T00:00:00.000Z",
80+
scopes: ["read:favorites"],
81+
address: "0xdef",
82+
})
83+
84+
expect(listTokens().map(token => token.scopes)).toEqual([
85+
["write:orders"],
86+
["read:favorites"],
87+
])
88+
})
89+
90+
test("leaves scopes empty for a malformed access token", () => {
91+
saveToken({
92+
accessToken: "not-a-jwt",
93+
refreshToken: "refresh",
94+
expiresAt: "2030-01-01T00:00:00.000Z",
95+
scopes: [],
96+
address: "0xabc",
97+
})
98+
99+
expect(loadCurrentToken()?.scopes).toEqual([])
100+
})
101+
})

0 commit comments

Comments
 (0)