UNPKG

@inrupt/solid-client

Version:

Make your web apps work with Solid Pods.

177 lines (165 loc) 6.14 kB
// Copyright Inrupt Inc. // // 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 SOFTWARE. // import { security } from "../constants"; import type { Iri, IriString, Jwk, Jwks, SolidDataset, ThingPersisted, UrlString, WebId, WithResourceInfo, } from "../interfaces"; import { overwriteFile } from "../resource/file"; import { getSourceUrl } from "../resource/resource"; import { getSolidDataset } from "../resource/solidDataset"; import { getUrl } from "../thing/get"; import { setIri } from "../thing/set"; import { getThing, setThing } from "../thing/thing"; function getProfileFromProfileDoc( profileDataset: SolidDataset, webId: WebId, ): ThingPersisted { const profile = getThing(profileDataset, webId); if (profile === null) { throw new Error( `Profile document [${getSourceUrl( profileDataset, )}] does not include WebID [${webId}]`, ); } return profile; } /** * Set a JWKS IRI associated with a WebID in a profile document. * * @param profileDocument The profile document dataset. * @param webId The WebID associated with the profile document. * @param jwksIri The JWKS IRI to be set. * @returns A modified copy of the profile document, with the JWKS IRI set. * @since 1.12.0 */ export function setProfileJwks<Dataset extends SolidDataset>( profileDocument: Dataset, webId: WebId, jwksIri: Iri | IriString, ): Dataset { return setThing( profileDocument, setIri( getProfileFromProfileDoc(profileDocument, webId), security.publicKey, jwksIri, ), ); } /** * Look for a JWKS IRI optionally advertized from a profile document. * * @param profileDocument The profile document. * @param webId The WebID featured in the profile document. * @returns The JWKS IRI associated with the WebID, if any. * @since 1.12.0 */ export function getProfileJwksIri( profileDocument: SolidDataset, webId: WebId, ): UrlString | null { return getUrl( getProfileFromProfileDoc(profileDocument, webId), security.publicKey, ); } const isJwks = (jwksDocument: Jwks | unknown): jwksDocument is Jwks => { return typeof (jwksDocument as Jwks).keys !== "undefined"; }; /** * Fetch a JWKS at a given IRI, and add the given JWK to the obtained key set. * * @param jwk The JWK to add to the set. * @param jwksIri The IRI where the key set should be looked up. * @param options @param options Optional parameter `options.fetch`: An alternative `fetch` function to make the HTTP request, compatible with the browser-native [fetch API](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/fetch#parameters). * @returns Promise resolving to a JWKS where the given key has been added. * @since 1.12.0 */ export async function addJwkToJwks( jwk: Jwk, jwksIri: IriString, options?: { fetch?: typeof fetch }, ): Promise<Jwks> { const jwksResponse = await (options?.fetch ?? fetch)(jwksIri); if (!jwksResponse.ok) { throw new Error( `Fetching [${jwksIri}] returned an error: ${jwksResponse.status} ${jwksResponse.statusText}`, ); } try { const jwksDocument = await jwksResponse.json(); if (!isJwks(jwksDocument)) { throw new Error( `[${jwksIri}] does not dereference to a valid JWKS: ${JSON.stringify( jwksDocument, )}`, ); } return { keys: [...jwksDocument.keys, jwk], }; } catch (e) { throw new Error(`Parsing the document at [${jwksIri}] failed: ${e}`); } } /** * Adds a public key to the JWKS listed in the profile associated to the given WebID. * Retrieves the profile document for the specified WebID and looks up the associated * JWKS. Having added the given key to the JWKS, this function overwrites the * previous JWKS so that the new version is saved. This assumes the JWKS is hosted * at a read-write IRI, such as in a Solid Pod. * * @param publicKey The public key value to set. * @param webId The WebID whose profile document references the key set to which we wish to add the specified public key. * @param options Optional parameter `options.fetch`: An alternative `fetch` function to make the HTTP request, compatible with the browser-native [fetch API](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/fetch#parameters). * @since 1.12.0 */ export async function addPublicKeyToProfileJwks( publicKey: Jwk, webId: WebId, options: { fetch?: typeof fetch } = {}, ): Promise<Blob & WithResourceInfo> { const profileDataset = await getSolidDataset(webId, options); if (profileDataset === null) { throw new Error( `The profile document associated with WebID [${webId}] could not be retrieved.`, ); } const jwksIri = getProfileJwksIri(profileDataset, webId); if (jwksIri === null) { throw new Error( `No key set is declared for the property [${security.publicKey}] in the profile of [${webId}]`, ); } const updatedJwks = await addJwkToJwks(publicKey, jwksIri, options); return overwriteFile(jwksIri, new Blob([JSON.stringify(updatedJwks)]), { contentType: "application/json", fetch: options.fetch, }); }