Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/src/content/docs/developer-guides/session-keys.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,14 @@ const synapse = Synapse.create({

`Synapse.create()` validates that the session key has all four FWSS permissions (`DefaultFwssPermissions`) and that none are expired. This means the session key's expirations must be populated before construction, either by passing `expirations` to `fromSecp256k1()`, or by calling `sessionKey.syncExpirations()` after login.

#### All-or-nothing in `@filoz/synapse-sdk`

`@filoz/synapse-sdk` is the high-level "golden path" entry point and deliberately takes an all-or-nothing stance on session key permissions: `Synapse.create()` will throw if any of `CreateDataSet`, `AddPieces`, `SchedulePieceRemovals`, or `TerminateService` is missing or expired. This keeps the API surface predictable — every operation the high-level SDK exposes will work once construction succeeds.

If you want a narrower scope (for example, only `CreateDataSet` + `AddPieces` for an upload-only client), drop down to [`@filoz/synapse-core`](/developer-guides/synapse-core/) directly. The core package's `SessionKey` and SP-client functions check permissions per operation, so you can authorize the minimum set you need and call those operations directly without going through `Synapse.create()`.

The error thrown by `Synapse.create()` names the specific permissions that are missing or expired so you can decide whether to extend the session key's authorizations or switch to `@filoz/synapse-core` for that flow.

### Revoke the session key

When done, the root wallet can revoke permissions:
Expand Down
12 changes: 12 additions & 0 deletions packages/synapse-core/src/session-key/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ export const DefaultFwssPermissions = [
TerminateServicePermission,
]

/**
* Human-readable EIP-712 primary type names for each FWSS permission hash.
* Useful for building error messages and debug output that reference
* permissions by name instead of opaque bytes32 hashes.
*/
export const PermissionNames: Record<Hex, string> = {
[CreateDataSetPermission]: 'CreateDataSet',
[AddPiecesPermission]: 'AddPieces',
[SchedulePieceRemovalsPermission]: 'SchedulePieceRemovals',
[TerminateServicePermission]: 'TerminateService',
}

export type Permission =
| CreateDataSetPermission
| AddPiecesPermission
Expand Down
25 changes: 21 additions & 4 deletions packages/synapse-sdk/src/synapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,27 @@ export class Synapse {
throw new Error('Transport must be a custom transport. See https://viem.sh/docs/clients/transports/custom.')
}

if (options.sessionKey != null && !options.sessionKey.hasPermissions(SessionKey.DefaultFwssPermissions)) {
throw new Error(
'Session key does not have the required permissions. Please login and sync expirations with the session key first.'
)
if (options.sessionKey != null) {
const sessionKey = options.sessionKey
const missing = SessionKey.DefaultFwssPermissions.filter(
(permission) => !sessionKey.hasPermission(permission)
).map((permission) => {
const name = SessionKey.PermissionNames[permission] ?? permission
const expiry = sessionKey.expirations[permission]
if (expiry == null || expiry === 0n) {
return `${name} (not authorized)`
}
return `${name} (expired at ${new Date(Number(expiry) * 1000).toISOString()})`
})
if (missing.length > 0) {
throw new Error(
`Session key is missing required FWSS permissions: ${missing.join(', ')}. ` +
'Synapse.create requires every permission in SessionKey.DefaultFwssPermissions to be authorized and unexpired. ' +
'Authorize the session key for all of them (SessionKey.login) and refresh local state (sessionKey.syncExpirations), ' +
'or drop down to @filoz/synapse-core to operate with a custom permission scope. ' +
'See https://docs.filecoin.cloud/developer-guides/session-keys/ for details.'
)
}
}

return new Synapse({
Expand Down
50 changes: 50 additions & 0 deletions packages/synapse-sdk/src/test/session-keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,5 +206,55 @@ describe('Synapse', () => {

await context.deletePiece({ piece: result.pieceCid })
})

describe('Synapse.create permission validation', () => {
it('should throw an informative error listing not-authorized and expired permissions', () => {
const now = BigInt(Math.floor(Date.now() / 1000))
const sessionKey = SessionKey.fromSecp256k1({
chain: calibration,
privateKey: Mocks.PRIVATE_KEYS.key2,
root: client.account,
expirations: {
[SessionKey.CreateDataSetPermission]: now + 3600n,
[SessionKey.AddPiecesPermission]: 0n,
[SessionKey.SchedulePieceRemovalsPermission]: now - 3600n,
[SessionKey.TerminateServicePermission]: now + 3600n,
},
})

try {
Synapse.create({ chain: calibration, account, source: null, sessionKey })
assert.fail('expected Synapse.create to throw')
} catch (error) {
assert.instanceOf(error, Error)
assert.match(error.message, /Session key is missing required FWSS permissions/)
assert.include(error.message, 'AddPieces (not authorized)')
assert.include(error.message, 'SchedulePieceRemovals (expired at ')
assert.notInclude(error.message, 'CreateDataSet (')
assert.notInclude(error.message, 'TerminateService (')
assert.include(error.message, '@filoz/synapse-core')
assert.include(error.message, 'https://docs.filecoin.cloud/developer-guides/session-keys/')
}
})

it('should not throw when all FWSS permissions are valid', () => {
server.use(Mocks.JSONRPC(Mocks.presets.basic))
const now = BigInt(Math.floor(Date.now() / 1000))
const sessionKey = SessionKey.fromSecp256k1({
chain: calibration,
privateKey: Mocks.PRIVATE_KEYS.key2,
root: client.account,
expirations: {
[SessionKey.CreateDataSetPermission]: now + 3600n,
[SessionKey.AddPiecesPermission]: now + 3600n,
[SessionKey.SchedulePieceRemovalsPermission]: now + 3600n,
[SessionKey.TerminateServicePermission]: now + 3600n,
},
})

const synapse = Synapse.create({ chain: calibration, account, source: null, sessionKey })
assert.exists(synapse)
})
})
})
})
Loading