From c942c3c77f0eaddde353ab4aa7a6d1975fa031dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juliano=20C=C3=A9zar=20Chagas=20Tavares?= Date: Thu, 27 Jul 2023 17:41:21 -0300 Subject: [PATCH] Adds Credentials Module (#150) * Add credentials module * Adds changelog * Adds changelog * Make private storage property mandatory --- .changeset/chatty-doors-tap.md | 19 +++++++ examples/ssx-test-app/src/pages/Home.tsx | 18 +++++++ .../ssx-test-app/src/pages/StorageModule.tsx | 34 ++++++++++++- packages/ssx-sdk/src/modules/Credentials.ts | 49 +++++++++++++++++++ .../src/modules/Storage/KeplerStorage.ts | 9 ++++ packages/ssx-sdk/src/modules/index.ts | 1 + packages/ssx-sdk/src/ssx.ts | 24 ++++++++- 7 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 .changeset/chatty-doors-tap.md create mode 100644 packages/ssx-sdk/src/modules/Credentials.ts diff --git a/.changeset/chatty-doors-tap.md b/.changeset/chatty-doors-tap.md new file mode 100644 index 00000000..fcfd7de6 --- /dev/null +++ b/.changeset/chatty-doors-tap.md @@ -0,0 +1,19 @@ +--- +'@spruceid/ssx': minor +--- + +This adds the Credentials Modules, which allows developers to fetch credentials issued on SpruceKit Credential Issuer. +This module requires Storage Module, so you must enable both to make it work. + +```ts +const ssx = SSX({ + modules: { + storage: true, + credentials: true + } +}) + +await ssx.signIn(); + +const { data } = ssx.credentials.list(); +``` diff --git a/examples/ssx-test-app/src/pages/Home.tsx b/examples/ssx-test-app/src/pages/Home.tsx index 5a56d3a3..deb28934 100644 --- a/examples/ssx-test-app/src/pages/Home.tsx +++ b/examples/ssx-test-app/src/pages/Home.tsx @@ -50,6 +50,7 @@ function Home() { const [statement, setStatement] = useState(''); // ssx module config const [storageEnabled, setStorageEnabled] = useState('Off'); + const [credentialsEnabled, setCredentialsEnabled] = useState('Off'); const getSSXConfig = (ssxConfig: Record = {}) => { if (server === 'On') { @@ -112,6 +113,10 @@ function Home() { modules.storage = true; } + if (credentialsEnabled === "On") { + modules.credentials = true; + } + if (modules) { ssxConfig = { ...ssxConfig, @@ -315,6 +320,19 @@ function Home() { /> +
+ + Credentials + +
+ +
+
{ server === 'On' ? diff --git a/examples/ssx-test-app/src/pages/StorageModule.tsx b/examples/ssx-test-app/src/pages/StorageModule.tsx index ded42fe3..9751083f 100644 --- a/examples/ssx-test-app/src/pages/StorageModule.tsx +++ b/examples/ssx-test-app/src/pages/StorageModule.tsx @@ -9,17 +9,24 @@ interface IStorageModule { function StorageModule({ ssx }: IStorageModule) { const [contentList, setContentList] = useState>([]); + const [credentialsList, setCredentialsList] = useState>([]); const [selectedContent, setSelectedContent] = useState(null); const [name, setName] = useState(''); const [text, setText] = useState(''); const [viewingList, setViewingList] = useState(true); + const [allowPost, setAllowPost] = useState(false); useEffect(() => { const getContentList = async () => { const { data } = await ssx.storage.list({ removePrefix: true }); setContentList(data); }; + const getCredentialList = async () => { + const { data } = await ssx.credentials?.list?.({ removePrefix: true }); + setCredentialsList(data); + }; getContentList(); + getCredentialList(); }, [ssx]); const handleShareContent = async (content: string) => { @@ -33,6 +40,7 @@ function StorageModule({ ssx }: IStorageModule) { const handleGetContent = async (content: string) => { const { data } = await ssx.storage.get(content); + setAllowPost(true); setSelectedContent(content); setName(content); setText(data); @@ -75,6 +83,15 @@ function StorageModule({ ssx }: IStorageModule) { setViewingList(false); }; + const handleGetCredential = async (content: string) => { + const { data } = await ssx.credentials.get(content); + setAllowPost(false); + setSelectedContent(content); + setName(content); + setText(data); + setViewingList(false); + }; + return (
@@ -102,13 +119,28 @@ function StorageModule({ ssx }: IStorageModule) {
))} +

Credentials List

+ {credentialsList.map(content => ( +
+ {content} + +
+ ))}
) : (

View/Edit/Post Pane

- + { + allowPost ? + : + null + }
)} diff --git a/packages/ssx-sdk/src/modules/Credentials.ts b/packages/ssx-sdk/src/modules/Credentials.ts new file mode 100644 index 00000000..2e650267 --- /dev/null +++ b/packages/ssx-sdk/src/modules/Credentials.ts @@ -0,0 +1,49 @@ +import { SSXExtension } from "@spruceid/ssx-core/client"; +import { IStorage } from "./Storage"; +import { Response, Request } from './Storage/kepler'; + + +export interface ICredentialsList { + credentialType?: string, + request?: Request, + removePrefix?: boolean +} + +export interface ICredentials extends SSXExtension { + /** + * Retrieves the stored value associated with the specified credential key. + * @param credential - The unique identifier for the stored value. + * @returns A Promise that resolves to the value associated with the given credential key or undefined if the credential key does not exist. + */ + get(credential: string): Promise; + + /** + * Lists all credential keys available. + * @param credentialType - The credential identifier for the stored value. + * @returns A Promise that resolves to an array of strings representing the stored credentials keys. + */ + list(options: ICredentialsList): Promise; + +} +export class Credentials implements ICredentials { + public namespace = 'credentials'; + private prefix: string = 'shared/credentials'; + private storage: IStorage; + + constructor(storage: IStorage) { + this.storage = storage; + } + + public async get(credential: string): Promise { + return this.storage.get(credential, { prefix: this.prefix }); + } + + public async list(options: ICredentialsList): Promise { + const defaultOptions = { + prefix: this.prefix, + }; + const { prefix, credentialType, request, removePrefix } = { ...defaultOptions, ...options }; + return this.storage.list({ prefix, path: credentialType, removePrefix, request }); + } + +} \ No newline at end of file diff --git a/packages/ssx-sdk/src/modules/Storage/KeplerStorage.ts b/packages/ssx-sdk/src/modules/Storage/KeplerStorage.ts index 4f1104ab..3d588d9e 100644 --- a/packages/ssx-sdk/src/modules/Storage/KeplerStorage.ts +++ b/packages/ssx-sdk/src/modules/Storage/KeplerStorage.ts @@ -49,6 +49,7 @@ export class KeplerStorage implements IStorage, IKepler { private autoCreateNewOrbit: boolean; private userAuth: IUserAuthorization; private keplerModule?: any; + private credentialsModule?: boolean; /** The users orbitId. */ public orbitId?: string; @@ -65,6 +66,7 @@ export class KeplerStorage implements IStorage, IKepler { this.userAuth = userAuth; this.hosts = [...(config?.hosts || []), 'https://kepler.spruceid.xyz']; this.prefix = config?.prefix || ''; + this.credentialsModule = config?.credentialsModule; this.autoCreateNewOrbit = config?.autoCreateNewOrbit === undefined ? true @@ -98,6 +100,13 @@ export class KeplerStorage implements IStorage, IKepler { 'del', 'metadata', ]; + if (this.credentialsModule) { + actions[`${this.orbitId}/kv/shared/credentials`] = [ + 'get', + 'list', + 'metadata', + ]; + } return actions; } diff --git a/packages/ssx-sdk/src/modules/index.ts b/packages/ssx-sdk/src/modules/index.ts index eab6a14e..cb3fc043 100644 --- a/packages/ssx-sdk/src/modules/index.ts +++ b/packages/ssx-sdk/src/modules/index.ts @@ -1,2 +1,3 @@ export * from './UserAuthorization'; export * from './Storage'; +export * from './Credentials'; diff --git a/packages/ssx-sdk/src/ssx.ts b/packages/ssx-sdk/src/ssx.ts index 7721bdcc..15876bfd 100644 --- a/packages/ssx-sdk/src/ssx.ts +++ b/packages/ssx-sdk/src/ssx.ts @@ -5,6 +5,8 @@ import { SSXLensProfilesResponse, } from '@spruceid/ssx-core'; import { + Credentials, + ICredentials, IUserAuthorization, KeplerStorage, UserAuthorization, @@ -27,6 +29,7 @@ declare global { */ interface SSXModuleConfig { storage?: boolean | { [key: string]: any }; + credentials?: boolean; } // temporary: will move to ssx-core @@ -68,30 +71,49 @@ export class SSX { /** Storage Module */ public storage: KeplerStorage; + /** Credentials Module */ + public credentials: ICredentials; + constructor(private config: SSXConfig = SSX_DEFAULT_CONFIG) { // TODO: pull out config validation into separate function // TODO: pull out userAuthorization config this.userAuthorization = new UserAuthorization(config); // initialize storage module + // assume credentials is **disabled** if config.credentials is not defined + const credentialsConfig = + config?.modules?.credentials === undefined ? false : config.modules.credentials; + // assume storage module is **disabled** if config.storage is not defined const storageConfig = config?.modules?.storage === undefined ? false : config.modules.storage; if (storageConfig !== false) { if (typeof storageConfig === 'object') { + storageConfig.credentialsModule = credentialsConfig; // Initialize storage with the provided config this.storage = new KeplerStorage(storageConfig, this.userAuthorization); } else { // storage == true or undefined // Initialize storage with default config when no other condition is met this.storage = new KeplerStorage( - { prefix: 'ssx' }, + { prefix: 'ssx', credentialsModule: credentialsConfig }, this.userAuthorization ); } this.extend(this.storage); } + + if (credentialsConfig) { + // Credentials module depends on the storage module. If it isn't enabled + // we won't initialize the credentials module. + if (!storageConfig) { + throw new Error('You must enable the storage module to use the credentials module.') + } else { + this.credentials = new Credentials(this.storage); + this.extend(this.credentials); + } + } } /**