-
-
Notifications
You must be signed in to change notification settings - Fork 273
feat: added authenticated user storage #8260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dovydas55
wants to merge
49
commits into
main
Choose a base branch
from
MCA-83
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 46 commits
Commits
Show all changes
49 commits
Select commit
Hold shift + click to select a range
c10189d
feat: added authenticated user storage
dovydas55 16163c3
feat: updated changelog
dovydas55 38b17aa
feat: updated changelog
dovydas55 2af31ef
feat: fixing linting errors
dovydas55 782b5ce
feat: fixing linting errors
dovydas55 27911b3
Merge branch 'main' into MCA-83
dovydas55 91701fc
feat: updated API contract
dovydas55 eedd6cc
Merge branch 'MCA-83' of github.com:MetaMask/core into MCA-83
dovydas55 ce5671d
feat: cleaning up
dovydas55 cccf519
Merge branch 'main' of github.com:MetaMask/core into MCA-83
dovydas55 e907cac
feat: added new package authenticated user storage
dovydas55 f79f384
feat: added missing ASU types
dovydas55 55f7144
feat: added auth-engineers team
dovydas55 235b0e5
feat: fixing tests
dovydas55 0f2599a
feat: fixing tests
dovydas55 b7918e5
feat: fixing tests
dovydas55 72e9c2a
feat: fixing tests
dovydas55 acf46c4
Merge branch 'main' of github.com:MetaMask/core into MCA-83
dovydas55 3dbc9af
feat: fixing tests
dovydas55 d534083
Merge branch 'main' of github.com:MetaMask/core into MCA-83
dovydas55 f2dd2d3
feat: added simple README
dovydas55 803f104
feat: added simple README
dovydas55 d415546
feat: added simple README
dovydas55 00707c6
feat: fixing changelog
dovydas55 3800ca8
feat: fixing linter
dovydas55 4d0d255
feat: fixing linter
dovydas55 c70f646
feat: fixing linter
dovydas55 d81b087
Merge branch 'main' into MCA-83
dovydas55 d319cb3
feat: updating docs
dovydas55 35abfa2
Merge branch 'main' into MCA-83
dovydas55 a519017
feat: pull main
dovydas55 ce45739
Merge branch 'MCA-83' of github.com:MetaMask/core into MCA-83
dovydas55 380d4bb
feat: refactor
dovydas55 ec5c1af
feat: refactor
dovydas55 ff3701f
feat: refactor
dovydas55 71771b0
feat: refactor
dovydas55 56099a2
feat: refactor
dovydas55 fa78ff8
feat: refactor
dovydas55 4a42cb3
Merge branch 'main' into MCA-83
dovydas55 2a51ee0
feat: fixing build
dovydas55 c3b4c47
Merge branch 'MCA-83' of github.com:MetaMask/core into MCA-83
dovydas55 3957705
feat: fixing build
dovydas55 ad10d98
feat: fixing build
dovydas55 2d2812d
feat: code review
dovydas55 7f9b2f7
feat: code review
dovydas55 4be6409
feat: code review
dovydas55 b010631
feat: code review
dovydas55 d2db432
feat: code review
dovydas55 2339810
feat: code review
dovydas55 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # Changelog | ||
|
|
||
| All notable changes to this project will be documented in this file. | ||
|
|
||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
|
||
| ## [Unreleased] | ||
|
|
||
| ### Added | ||
|
|
||
| - Initial release ([#8260](https://github.com/MetaMask/core/pull/8260)) | ||
| - `AuthenticatedUserStorageService` class with namespaced domain accessors: `delegations` (list, create, revoke) and `preferences` (getNotifications, putNotifications) | ||
|
|
||
| [Unreleased]: https://github.com/MetaMask/core/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| MIT License | ||
|
|
||
| Copyright (c) 2026 MetaMask | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| # `@metamask/authenticated-user-storage` | ||
|
|
||
| A TypeScript SDK for MetaMask's Authenticated User Storage API. Unlike E2EE user-storage, authenticated user storage holds **structured JSON** scoped to the authenticated user. The server can read and validate the contents, which allows other backend services to consume the data (e.g. delegation execution, notification delivery). | ||
|
|
||
| The SDK currently supports two domains: | ||
|
|
||
| - **Delegations** -- immutable, EIP-712 signed delegation records (list, create, revoke). | ||
| - **Notification Preferences** -- mutable per-user notification settings (get, put). | ||
|
|
||
| ## Installation | ||
|
|
||
| `yarn add @metamask/authenticated-user-storage` | ||
|
|
||
| or | ||
|
|
||
| `npm install @metamask/authenticated-user-storage` | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Creating a service | ||
|
|
||
| `AuthenticatedUserStorageService` extends `BaseDataService` and requires a messenger, an environment, and an access-token callback: | ||
|
|
||
| - **`messenger`** -- a namespaced messenger for registering actions and events. | ||
| - **`env`** -- selects the backend environment (`DEV`, `UAT`, or `PRD`). | ||
| - **`getAccessToken`** -- an async callback that returns a valid JWT access token for the current user. | ||
|
|
||
| ```typescript | ||
| import { Messenger } from '@metamask/messenger'; | ||
| import { AuthenticatedUserStorageService } from '@metamask/authenticated-user-storage'; | ||
| import type { | ||
| AuthenticatedUserStorageMessenger, | ||
| AuthenticatedUserStorageActions, | ||
| AuthenticatedUserStorageEvents, | ||
| } from '@metamask/authenticated-user-storage'; | ||
|
|
||
| // Create the messenger | ||
| const messenger = new Messenger< | ||
| 'AuthenticatedUserStorageService', | ||
| AuthenticatedUserStorageActions, | ||
| AuthenticatedUserStorageEvents | ||
| >({ | ||
| namespace: 'AuthenticatedUserStorageService', | ||
| parent: rootMessenger, | ||
| }); | ||
|
|
||
| // Instantiate the service | ||
| const service = new AuthenticatedUserStorageService({ | ||
| messenger, | ||
| environment: 'prod', | ||
| getAccessToken: () => | ||
| rootMessenger.call('AuthenticationController:getBearerToken'), | ||
| }); | ||
| ``` | ||
|
|
||
| The `environment` option selects the backend environment: | ||
|
|
||
| | Value | Server | | ||
| | -------- | ------------------------------------- | | ||
| | `'dev'` | `user-storage.dev-api.cx.metamask.io` | | ||
| | `'uat'` | `user-storage.uat-api.cx.metamask.io` | | ||
| | `'prod'` | `user-storage.api.cx.metamask.io` | | ||
|
|
||
| ### Calling methods via the messenger | ||
|
|
||
| Once instantiated, all service methods are available as messenger actions. This allows any consumer with access to the messenger to call them without needing a direct reference to the service instance: | ||
|
|
||
| ```typescript | ||
| const delegations = await rootMessenger.call( | ||
| 'AuthenticatedUserStorageService:listDelegations', | ||
| ); | ||
| ``` | ||
|
|
||
| ### Delegations | ||
|
|
||
| Delegations are immutable once stored. They can only be revoked (deleted), not updated. | ||
|
|
||
| ```typescript | ||
| import type { DelegationSubmission } from '@metamask/authenticated-user-storage'; | ||
|
|
||
| // List all delegations for the authenticated user | ||
| const delegations = await service.listDelegations(); | ||
|
|
||
| // Submit a new signed delegation | ||
| const submission: DelegationSubmission = { | ||
| signedDelegation: { ... }, | ||
| metadata: { ... }, | ||
| }; | ||
| await service.createDelegation(submission, 'extension'); | ||
|
|
||
| // Revoke a delegation by its hash | ||
| await service.revokeDelegation('0xdae6d1...'); | ||
| ``` | ||
|
|
||
| ### Notification preferences | ||
|
|
||
| Preferences are mutable. The first call creates the record; subsequent calls update it. | ||
|
|
||
| ```typescript | ||
| import type { NotificationPreferences } from '@metamask/authenticated-user-storage'; | ||
|
|
||
| // Retrieve current preferences (returns null if none have been set) | ||
| const prefs = await service.getNotificationPreferences(); | ||
|
|
||
| // Create or update preferences | ||
| const updated: NotificationPreferences = { | ||
| walletActivity: { ... }, | ||
| marketing: { ... }, | ||
| perps: { ... }, | ||
| socialAI: { ... }, | ||
| }; | ||
| await service.putNotificationPreferences(updated, 'extension'); | ||
| ``` | ||
|
|
||
| ## Response validation | ||
|
|
||
| All API responses are validated at runtime using [`@metamask/superstruct`](https://github.com/MetaMask/superstruct) schemas before being returned to callers. If the server returns data that doesn't match the expected shape, the SDK throws with details about the structural mismatch rather than silently returning malformed data. | ||
|
|
||
| ## Error handling | ||
|
|
||
| HTTP errors are represented as `HttpError` from `@metamask/controller-utils`. All errors are encouraged to bubble up to the caller. The service policy provided by `BaseDataService` automatically retries transient failures before propagating the error. | ||
|
|
||
| ```typescript | ||
| import { HttpError } from '@metamask/controller-utils'; | ||
|
|
||
| try { | ||
| await service.createDelegation(submission); | ||
| } catch (error) { | ||
| if (error instanceof HttpError) { | ||
| console.error(error.message); | ||
| // e.g. "Failed to create delegation: 409" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Contributing | ||
|
|
||
| This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| /* | ||
| * For a detailed explanation regarding each configuration property and type check, visit: | ||
| * https://jestjs.io/docs/configuration | ||
| */ | ||
|
|
||
| const merge = require('deepmerge'); | ||
| const path = require('path'); | ||
|
|
||
| const baseConfig = require('../../jest.config.packages'); | ||
|
|
||
| const displayName = path.basename(__dirname); | ||
|
|
||
| module.exports = merge(baseConfig, { | ||
| // The display name when running multiple projects | ||
| displayName, | ||
|
|
||
| // An object that configures minimum threshold enforcement for coverage results | ||
| coverageThreshold: { | ||
| global: { | ||
| branches: 100, | ||
| functions: 100, | ||
| lines: 100, | ||
| statements: 100, | ||
| }, | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| { | ||
| "name": "@metamask/authenticated-user-storage", | ||
| "version": "0.0.0", | ||
| "description": "SDK for authenticated (non-encrypted) user storage endpoints", | ||
| "keywords": [ | ||
| "MetaMask", | ||
| "Ethereum" | ||
| ], | ||
| "homepage": "https://github.com/MetaMask/core/tree/main/packages/authenticated-user-storage#readme", | ||
| "bugs": { | ||
| "url": "https://github.com/MetaMask/core/issues" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/MetaMask/core.git" | ||
| }, | ||
| "license": "MIT", | ||
| "sideEffects": false, | ||
| "exports": { | ||
| ".": { | ||
| "import": { | ||
| "types": "./dist/index.d.mts", | ||
| "default": "./dist/index.mjs" | ||
| }, | ||
| "require": { | ||
| "types": "./dist/index.d.cts", | ||
| "default": "./dist/index.cjs" | ||
| } | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "main": "./dist/index.cjs", | ||
| "types": "./dist/index.d.cts", | ||
| "files": [ | ||
| "dist/" | ||
| ], | ||
| "scripts": { | ||
| "build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references", | ||
| "build:all": "ts-bridge --project tsconfig.build.json --verbose --clean", | ||
| "build:docs": "typedoc", | ||
| "changelog:update": "../../scripts/update-changelog.sh @metamask/authenticated-user-storage", | ||
| "changelog:validate": "../../scripts/validate-changelog.sh @metamask/authenticated-user-storage", | ||
| "messenger-action-types:check": "tsx ../../packages/messenger-cli/src/cli.ts --check", | ||
| "messenger-action-types:generate": "tsx ../../packages/messenger-cli/src/cli.ts --generate", | ||
| "since-latest-release": "../../scripts/since-latest-release.sh", | ||
| "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", | ||
| "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", | ||
| "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose", | ||
| "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch" | ||
| }, | ||
| "dependencies": { | ||
| "@metamask/base-data-service": "^0.1.1", | ||
| "@metamask/controller-utils": "^11.20.0", | ||
| "@metamask/messenger": "^1.1.1", | ||
| "@metamask/superstruct": "^3.1.0", | ||
| "@metamask/utils": "^11.9.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@metamask/auto-changelog": "^3.4.4", | ||
| "@ts-bridge/cli": "^0.6.4", | ||
| "@types/jest": "^29.5.14", | ||
| "deepmerge": "^4.2.2", | ||
| "jest": "^29.7.0", | ||
| "nock": "^13.3.1", | ||
| "ts-jest": "^29.2.5", | ||
| "tsx": "^4.20.5", | ||
| "typedoc": "^0.25.13", | ||
| "typedoc-plugin-missing-exports": "^2.0.0", | ||
| "typescript": "~5.3.3" | ||
| }, | ||
| "engines": { | ||
| "node": "^18.18 || >=20" | ||
| }, | ||
| "publishConfig": { | ||
| "access": "public", | ||
| "registry": "https://registry.npmjs.org/" | ||
| } | ||
| } |
69 changes: 69 additions & 0 deletions
69
packages/authenticated-user-storage/src/authenticated-user-storage-method-action-types.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| /** | ||
| * This file is auto generated. | ||
| * Do not edit manually. | ||
| */ | ||
|
|
||
| import type { AuthenticatedUserStorageService } from './authenticated-user-storage'; | ||
|
|
||
| /** | ||
| * Returns all delegation records belonging to the authenticated user. | ||
| * | ||
| * @returns An array of delegation records, or an empty array if none exist. | ||
| */ | ||
| export type AuthenticatedUserStorageListDelegationsAction = { | ||
| type: `AuthenticatedUserStorageService:listDelegations`; | ||
| handler: AuthenticatedUserStorageService['listDelegations']; | ||
| }; | ||
|
|
||
| /** | ||
| * Stores a signed delegation record for the authenticated user. | ||
| * | ||
| * @param submission - The signed delegation and its metadata. | ||
| * @param clientType - Optional client type header. | ||
| */ | ||
| export type AuthenticatedUserStorageCreateDelegationAction = { | ||
| type: `AuthenticatedUserStorageService:createDelegation`; | ||
| handler: AuthenticatedUserStorageService['createDelegation']; | ||
| }; | ||
|
|
||
| /** | ||
| * Revokes (deletes) a delegation record. | ||
| * | ||
| * @param delegationHash - The unique hash identifying the delegation. | ||
| */ | ||
| export type AuthenticatedUserStorageRevokeDelegationAction = { | ||
| type: `AuthenticatedUserStorageService:revokeDelegation`; | ||
| handler: AuthenticatedUserStorageService['revokeDelegation']; | ||
| }; | ||
|
|
||
| /** | ||
| * Returns the notification preferences for the authenticated user. | ||
| * | ||
| * @returns The notification preferences object, or `null` if none have been | ||
| * set (404). | ||
| */ | ||
| export type AuthenticatedUserStorageGetNotificationPreferencesAction = { | ||
| type: `AuthenticatedUserStorageService:getNotificationPreferences`; | ||
| handler: AuthenticatedUserStorageService['getNotificationPreferences']; | ||
| }; | ||
|
|
||
| /** | ||
| * Creates or updates the notification preferences for the authenticated user. | ||
| * | ||
| * @param prefs - The full notification preferences object. | ||
| * @param clientType - Optional client type header. | ||
| */ | ||
| export type AuthenticatedUserStoragePutNotificationPreferencesAction = { | ||
| type: `AuthenticatedUserStorageService:putNotificationPreferences`; | ||
| handler: AuthenticatedUserStorageService['putNotificationPreferences']; | ||
| }; | ||
|
|
||
| /** | ||
| * Union of all AuthenticatedUserStorage action types. | ||
| */ | ||
| export type AuthenticatedUserStorageMethodActions = | ||
| | AuthenticatedUserStorageListDelegationsAction | ||
| | AuthenticatedUserStorageCreateDelegationAction | ||
| | AuthenticatedUserStorageRevokeDelegationAction | ||
| | AuthenticatedUserStorageGetNotificationPreferencesAction | ||
| | AuthenticatedUserStoragePutNotificationPreferencesAction; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.