diff --git a/KeriAuth.BrowserExtension.sln b/KeriAuth.BrowserExtension.sln index 723f9e4..53ad186 100644 --- a/KeriAuth.BrowserExtension.sln +++ b/KeriAuth.BrowserExtension.sln @@ -10,9 +10,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig KERIAuthArchitecture.jpg = KERIAuthArchitecture.jpg README.md = README.md + PAGE-CS-MESSAGES.md = PAGE-CS-MESSAGES.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeriAuth.BrowserExtension.Tests", "KeriAuth.BrowserExtension.Tests\KeriAuth.BrowserExtension.Tests.csproj", "{3DA9AB88-7EFD-460F-A919-3D8BCE0EA95D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeriAuth.BrowserExtension.Tests", "KeriAuth.BrowserExtension.Tests\KeriAuth.BrowserExtension.Tests.csproj", "{3DA9AB88-7EFD-460F-A919-3D8BCE0EA95D}" ProjectSection(ProjectDependencies) = postProject {118B1C27-1F19-4566-849E-D2EA618A6988} = {118B1C27-1F19-4566-849E-D2EA618A6988} EndProjectSection diff --git a/KeriAuth.BrowserExtension/KeriAuth.BrowserExtension.csproj b/KeriAuth.BrowserExtension/KeriAuth.BrowserExtension.csproj index 0c45a5e..9f05b1d 100644 --- a/KeriAuth.BrowserExtension/KeriAuth.BrowserExtension.csproj +++ b/KeriAuth.BrowserExtension/KeriAuth.BrowserExtension.csproj @@ -61,7 +61,7 @@ - + diff --git a/KeriAuth.BrowserExtension/Models/ApiRequest.cs b/KeriAuth.BrowserExtension/Models/ApiRequest.cs new file mode 100644 index 0000000..aa7fa32 --- /dev/null +++ b/KeriAuth.BrowserExtension/Models/ApiRequest.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace KeriAuth.BrowserExtension.Models +{ + public record ApiRequest + { + [JsonPropertyName("url")] + public string Url { get; init; } = string.Empty; + + [JsonPropertyName("method")] + public string Method { get; init; } = "GET"; + + // Default constructor + public ApiRequest() { } + + // Constructor with parameters (if needed) + public ApiRequest(string url, string method) + { + Url = url; + Method = method; + } + } +} diff --git a/KeriAuth.BrowserExtension/Services/SignifyService/SignifyClientService.cs b/KeriAuth.BrowserExtension/Services/SignifyService/SignifyClientService.cs index acb7643..93289ea 100644 --- a/KeriAuth.BrowserExtension/Services/SignifyService/SignifyClientService.cs +++ b/KeriAuth.BrowserExtension/Services/SignifyService/SignifyClientService.cs @@ -352,21 +352,9 @@ async Task> ISignifyClientService.SignRequestHeader(string origin logger.LogInformation("SignRequestHeader: jsonInputHeaders: `{i}`", jsonInputHeaders); logger.LogInformation("SignRequestHeader: invoke params: origin: `{o}` rurl: `{r}` method: `{m}` jsonInputHeaders: `{i}` prefix: `{p}`", origin, rurl, method, jsonInputHeaders, prefix); - - // tmp test - //var aidJson = await Signify_ts_shim.GetAID(prefix); - //logger.LogWarning("SignRequestHeader: aidJson: {s}", aidJson); - // end tmp test - - - // TODO consider timeout, e.g. await TimeoutHelper.WithTimeout(... - - - var signedHeadersAsJsonBase64 = await Signify_ts_shim.GetSignedHeadersWithJsonHeaders(origin, rurl, method, jsonInputHeaders, prefix); logger.LogInformation("SignRequestHeader: signedHeadersAsJsonBase64: {s}", signedHeadersAsJsonBase64); - // Step 1: Decode the Base64 string byte[] jsonBytes = Convert.FromBase64String(signedHeadersAsJsonBase64); string jsonString = Encoding.UTF8.GetString(jsonBytes); @@ -380,7 +368,6 @@ async Task> ISignifyClientService.SignRequestHeader(string origin string json = JsonConvert.SerializeObject(jsonObject, Formatting.Indented); logger.LogInformation("SignRequestHeader: signedHeaders as json-like: {p}", json); - return Result.Ok(json); } catch (JSException e) diff --git a/KeriAuth.BrowserExtension/Services/StorageService.cs b/KeriAuth.BrowserExtension/Services/StorageService.cs index b648d88..85c4111 100644 --- a/KeriAuth.BrowserExtension/Services/StorageService.cs +++ b/KeriAuth.BrowserExtension/Services/StorageService.cs @@ -337,7 +337,7 @@ public async Task NotifyStorageChanged(Dictionary - // Debug.Assert(GetOriginUri() is not null); - // await getWebsiteConfig(GetOriginUri()!); - // StateHasChanged(); - // } - async Task updateIsCredentialRemembered(bool isChecked) { IsCredentialRemembered = isChecked; @@ -226,10 +216,6 @@ string jsonString = decodedMsg2.ToString(); // Convert StringValues to string - - - - // Parse the JSON string // TODO write exception handling around the following JsonDocument jsonDocument = JsonDocument.Parse(jsonString); @@ -240,7 +226,6 @@ RequestId = requestId.ToString(); return; - } else { @@ -321,7 +306,6 @@ this.StateHasChanged(); } - List> credentials = new(); private async Task getCredentials() @@ -347,57 +331,51 @@ } } - // TODO move into Models directory file - public record ApiRequest - { - [JsonPropertyName("url")] - public string Url { get; init; } - - [JsonPropertyName("method")] - public string Method { get; init; } - } - /* / SignRequest() */ async Task SignRequest(SignInMode RequestedSignInMode, string requestId2, string? SelectedName, string payloadJson) { - logger.LogInformation("SignRequest: payloadJson: {d}", payloadJson); ApiRequest? request = JsonSerializer.Deserialize(payloadJson); if (request is null) { - logger.LogError("Error in SignRequest: {0}", "Invalid payloadJson"); + logger.LogError("SignRequest: Error: {0}", "Invalid payloadJson"); return; } logger.LogInformation("SignRequest: request: {d}", request.ToString()); - // headers is intentionally empty for now - Dictionary inputHeadersDict = new Dictionary(); + var inputHeadersDict = new Dictionary(); - var replyMessageDataRes = await signifyClientService.SignRequestHeader(OriginStr, request.Url, request.Method, inputHeadersDict, SelectedName!); - if (replyMessageDataRes.IsFailed || replyMessageDataRes.Value is null) + var signedRequestResultJsonRes = await signifyClientService.SignRequestHeader(OriginStr, request.Url, request.Method, inputHeadersDict, SelectedName!); + if (signedRequestResultJsonRes.IsFailed || signedRequestResultJsonRes.Value is null) { - logger.LogError("Error in SignRequest: {0}", replyMessageDataRes.Errors); + logger.LogError("SignRequest: Error: {0}", signedRequestResultJsonRes.Errors); return; } else { - var replyMessageData = replyMessageDataRes.Value; - logger.LogInformation("replyMessageData: {r}", replyMessageData); + var signedRequestResultJson = signedRequestResultJsonRes.Value; + logger.LogInformation("SignRequest: replyMessageData: {r}", signedRequestResultJson); - var tmp = JsonSerializer.Deserialize(replyMessageData); - if (tmp is null) + var signedRequestResult = JsonSerializer.Deserialize(signedRequestResultJson); + if (signedRequestResult is null) { - // TODO: return an error in ReplyMessageData instead - throw new Exception("cannot deserialize replyMessageData"); + var errMsg = "KeriAuth could not deserialize signedRequestResult"; + logger.LogError("SignRequest error: {e}", errMsg); + var replyMessageDataErr = new ReplyMessageData("/signify/reply", null, requestId2, null, errMsg); + await appSwMessagingService.SendToServiceWorkerAsync(replyMessageDataErr); } else { - // var tmp = new SignedHeadersResult("1", "2", "3", "4"); - var tmp2 = new ReplyMessageData("/signify/reply", tmp, requestId2, null, "KeriAuth"); - await appSwMessagingService.SendToServiceWorkerAsync(tmp2); + var replyMessageData = new ReplyMessageData( + "/signify/reply", + signedRequestResult, + requestId2, + null, + "KeriAuth"); + await appSwMessagingService.SendToServiceWorkerAsync(replyMessageData); // TODO close commented out to help with debugging // UIHelper.CloseWindow(); } @@ -406,12 +384,14 @@ async Task Cancel() { - var cr = new ReplyMessageData( + var replyMessageData = new ReplyMessageData( "/KeriAuth/signify/replyCancel", new AuthorizeResult(null, null), - requestId: RequestId + requestId: RequestId, + null, + "KeriAuth" ); - await appSwMessagingService.SendToServiceWorkerAsync(cr); + await appSwMessagingService.SendToServiceWorkerAsync(replyMessageData); // Close blazor application UIHelper.CloseWindow(); } @@ -422,6 +402,7 @@ ; } + // TODO remove or create helper? static bool IsJsonValid(string jsonString) { try diff --git a/KeriAuth.BrowserExtension/package-lock.json b/KeriAuth.BrowserExtension/package-lock.json index 548a014..59b3b10 100644 --- a/KeriAuth.BrowserExtension/package-lock.json +++ b/KeriAuth.BrowserExtension/package-lock.json @@ -27,7 +27,7 @@ "ecdsa-secp256r1": "^1.3.3", "esbuild": "^0.19.12", "esbuild-plugin-alias": "^0.2.1", - "eslint": "^9.5.0", + "eslint": "^9.12.0", "path": "^0.12.7", "polaris-web": "github:WebOfTrust/polaris-web", "signify-ts": "github:WebOfTrust/signify-ts", @@ -2012,7 +2012,6 @@ "version": "0.3.0", "resolved": "git+ssh://git@github.com/WebOfTrust/signify-ts.git#1b747f85e17a5adeae859436cc535478d2aec8f7", "dev": true, - "license": "Apache-2.0", "workspaces": [ "examples/*" ], diff --git a/KeriAuth.BrowserExtension/package.json b/KeriAuth.BrowserExtension/package.json index 260fd3f..fc92ba9 100644 --- a/KeriAuth.BrowserExtension/package.json +++ b/KeriAuth.BrowserExtension/package.json @@ -28,7 +28,7 @@ "ecdsa-secp256r1": "^1.3.3", "esbuild": "^0.19.12", "esbuild-plugin-alias": "^0.2.1", - "eslint": "^9.5.0", + "eslint": "^9.12.0", "path": "^0.12.7", "signify-ts": "github:WebOfTrust/signify-ts", "polaris-web": "github:WebOfTrust/polaris-web", diff --git a/KeriAuth.BrowserExtension/wwwroot/scripts/es6/ExCsInterfaces.ts b/KeriAuth.BrowserExtension/wwwroot/scripts/es6/ExCsInterfaces.ts index 2f1f6c3..9b45866 100644 --- a/KeriAuth.BrowserExtension/wwwroot/scripts/es6/ExCsInterfaces.ts +++ b/KeriAuth.BrowserExtension/wwwroot/scripts/es6/ExCsInterfaces.ts @@ -112,6 +112,7 @@ export const CsToPageMsgIndicator = "KeriAuthCs"; export interface KeriAuthMessageData extends MessageData { source: typeof CsToPageMsgIndicator; + rurl?: string; } // Signing related types from signify-browser-extension config/types.ts. Here because we don't want dependencies on signify-browser-extension, diff --git a/KeriAuth.BrowserExtension/wwwroot/scripts/es6/service-worker.ts b/KeriAuth.BrowserExtension/wwwroot/scripts/es6/service-worker.ts index 7260278..3f90f00 100644 --- a/KeriAuth.BrowserExtension/wwwroot/scripts/es6/service-worker.ts +++ b/KeriAuth.BrowserExtension/wwwroot/scripts/es6/service-worker.ts @@ -190,15 +190,16 @@ async function isWindowOpen(windowId: number): Promise { }); } -function serializeAndEncode(obj: object): string { +function serializeAndEncode(obj: any): string { + // TODO assumes the payload obj is simple const jsonString: string = JSON.stringify(obj); const encodedString: string = encodeURIComponent(jsonString); return encodedString; } -function handleSignRequest(msg: any, csTabPort: chrome.runtime.Port) { +function handleSignRequest(payload: any, csTabPort: chrome.runtime.Port) { // ICsSwMsgSignRequest - console.log("SW handleSignRequest: ", msg); + console.log("SW handleSignRequest: ", payload); // TODO EE! temporary placeholder. Should request user to sign request @@ -208,33 +209,17 @@ function handleSignRequest(msg: any, csTabPort: chrome.runtime.Port) { const tabId = Number(csTabPort.sender.tab.id); //chrome.action.setBadgeText({ text: "3", tabId: tabId }); //chrome.action.setBadgeTextColor({ color: '#FF0000', tabId: tabId }); - // TODO Could alternately implement the msg passing via messaging versus the URL + // TODO Could alternately implement the payload passing via messaging versus the URL // TODO should start a timer so the webpage doesn't need to wait forever for a response from the user? Then return an error. // TODO EE! add msgRequestId const jsonOrigin = JSON.stringify(csTabPort.sender.origin); - console.log("SW handleSignRequest: tabId: ", tabId, "message value: ", msg, "origin: ", jsonOrigin); + console.log("SW handleSignRequest: tabId: ", tabId, "payload value: ", payload, "origin: ", jsonOrigin); - const encodedMsg = serializeAndEncode(msg); + const encodedMsg = serializeAndEncode(payload); try { useActionPopup(tabId, [{ key: "message", value: encodedMsg }, { key: "origin", value: jsonOrigin }, { key: "popupType", value: "SignRequest" }]); - //const credObject = JSON.parse(msg.payload.credential.rawJson); - //const expiry = Math.floor((new Date().getTime() + 30 * 60 * 1000) / 1000); - //const authorizeResultCredential = { credential: { raw: credObject, cesr: msg.payload.credential.cesr }, expiry: expiry }; - - // const authorizeResult = { - // type: SwCsMsgType.REPLY, - // payload: { - // "signature": "indexed=\"?0\";signify=\"0BCUmN5EAYdT3okIb8yEIG9sVepXwlQQSqcuZd7wQYEFDzVNkIPwYUX679lYNHS1YCdSPATJGTHfdTLTHjZoPO8F\"", - // "signature-input": "signify=(\"@method\" \"@path\" \"signify-resource\" \"signify-timestamp\");created=1727719612;keyid=\"BJF5YenWeqMPGU2iL2hsn9D8PSGXRDXSZbM7Znvh1XvI\";alg=\"ed25519\"", - // "signify-resource": "EO0KSgpgvjNFoc8KoFfb0qgjbrVieMVbBhNit7ZtEue3", - // "signify-timestamp": "2024-10-01T18:06:52.193000+00:00" - // }, - // requestId: msg.requestId, - // }; - // console.log("TMP SW from App: SignDataResult?? authorizeResult", authorizeResult); - // csTabPort.postMessage(authorizeResult); } catch (error) { console.error("SW handleSignRequest: error invoking useActionPopup: ", error); @@ -395,7 +380,8 @@ async function handleMessageFromApp(message: any, appPort: chrome.runtime.Port, const authorizeResult = { type: SwCsMsgType.REPLY, requestId: message.requestId, - payload: authorizeResultCredential + payload: authorizeResultCredential, + rurl: "" // TODO rurl should not be fixed }; console.log("SW from App: authorizeResult", authorizeResult); cSConnection.port.postMessage(authorizeResult); diff --git a/KeriAuth.BrowserExtension/wwwroot/scripts/esbuild/ContentScript.ts b/KeriAuth.BrowserExtension/wwwroot/scripts/esbuild/ContentScript.ts index df179ec..a0181ce 100644 --- a/KeriAuth.BrowserExtension/wwwroot/scripts/esbuild/ContentScript.ts +++ b/KeriAuth.BrowserExtension/wwwroot/scripts/esbuild/ContentScript.ts @@ -81,7 +81,8 @@ function handleMessageFromServiceWorker(message: MessageData, port: chr requestId: message.requestId, payload: message.payload, error: message.error, - source: CsToPageMsgIndicator + source: CsToPageMsgIndicator, + rurl: "" // TODO rurl should not be fixed } postMessageToPage>(msg); break; @@ -154,6 +155,15 @@ function handleWindowMessage(event: MessageEvent, portWithSw: chrome. case CsSwMsgType.SELECT_AUTHORIZE_CREDENTIAL: case CsSwMsgType.SELECT_AUTHORIZE_AID: try { + if (event.data.payload?.headers) { + console.log("KeriAuthCs from page payload headers: "); + for (const key in event.data.payload.headers) { + if (event.data.payload.headers.hasOwnProperty(key)) { + const value = event.data.payload.headers[key]; + console.log(` ${key}: ${value}`); + } + } + } portWithSw.postMessage(event.data); } catch (error) { console.error("KeriAuthCs to SW: error sending message {event.data} {e}:", event.data, error); @@ -166,24 +176,24 @@ function handleWindowMessage(event: MessageEvent, portWithSw: chrome. break; case "/signify/get-session-info": - console.log(`KeriAuthCs from page: ${event.data.type} sessions not yet implemented`); + console.log(`KeriAuthCs from page: ${event.data.type} sessions not implemented`); // TODO implement sessions postMessageToPage({ type: "/signify/reply", error: { code: 501, message: "KERIAuth sessions not supported" }, requestId: event?.data?.requestId, - rurl: "", + rurl: "", // TODO rurl should not be fixed source: CsToPageMsgIndicator }); break; case "/signify/clear-session": - console.log(`KeriAuthCs from page: ${event.data.type} sessions not yet implemented`); + console.log(`KeriAuthCs from page: ${event.data.type} sessions not implemented`); // TODO implement sessions postMessageToPage({ type: "/signify/reply", error: { code: 501, message: "KERIAuth sessions not supported" }, requestId: event?.data?.requestId, - rurl: "", + rurl: "", // TODO rurl should not be fixed source: CsToPageMsgIndicator }); break; @@ -191,7 +201,7 @@ function handleWindowMessage(event: MessageEvent, portWithSw: chrome. case CsSwMsgType.VENDOR_INFO: case CsSwMsgType.FETCH_RESOURCE: default: - console.error("KeriAuthCs from page: handler not yet implemented for:", event.data); + console.error("KeriAuthCs from page: handler not implemented for:", event.data); break; } } catch (error) { diff --git a/KeriAuth.BrowserExtension/wwwroot/scripts/esbuild/signify_ts_shim.ts b/KeriAuth.BrowserExtension/wwwroot/scripts/esbuild/signify_ts_shim.ts index 7938028..bb4c37a 100644 --- a/KeriAuth.BrowserExtension/wwwroot/scripts/esbuild/signify_ts_shim.ts +++ b/KeriAuth.BrowserExtension/wwwroot/scripts/esbuild/signify_ts_shim.ts @@ -76,6 +76,9 @@ export const connect = async (agentUrl: string, passcode: string): Promise => { + // TODO not that headers won't be printable this way: console.log("getSignedHeaders: params: ", origin, " ", rurl, " ", method, " ", headers, " ", aidName); // in case the client is not connected, try to connect @@ -207,7 +211,6 @@ const getSignedHeaders = async ( validateClient(); //} - const client: SignifyClient = _client!; //const session = await sessionService.get({ tabId, origin }); @@ -216,14 +219,8 @@ const getSignedHeaders = async ( // throw new Error("Session not found"); //} try { - - // temporary test - //const aid = await getAID(aidName); - //console.log("getSignedHeaders: aid: ", aid); - // end temporary test - - - console.log("getSignedHeaders: createSignedRequest args:", aidName, rurl, method, new Headers()); // headers); // TODO ignoring param + // TODO not that headers won't be printable this way: + console.log("getSignedHeaders: createSignedRequest args:", aidName, rurl, method, headers); const signedRequest: Request = await client.createSignedRequest(aidName, rurl, { method, headers, @@ -231,7 +228,6 @@ const getSignedHeaders = async ( //resetTimeoutAlarm(); console.log("getSignedHeaders: signedRequest:", signedRequest); - // Log each header for better visibility if (signedRequest.headers) { console.log("getSignedHeaders: signedRequest.headers details:"); @@ -256,7 +252,6 @@ const getSignedHeaders = async ( } catch (error) { console.error("getSignedHeaders: Error occurred:", error); throw error; - // return JSON.stringify({ error: error.message }); } }; @@ -301,6 +296,7 @@ export const getSignedHeadersWithJsonHeaders = async ( try { console.log("getSignedHeadersWithJsonHeaders: ", origin, " ", rurl, " ", method, " ", headersJson, " ", aidName); const initialHeaders: Headers = parseHeaders(headersJson); + // TODO to confirm headers parsing, iterate and print these, but expect these to be {} at current stage of development. console.log("getSignedHeadersWithJsonHeaders initialHeaders: ", initialHeaders); // Call the original getSignedHeaders function with the parsed initialHeaders diff --git a/PAGE-CS-MESSAGES.md b/PAGE-CS-MESSAGES.md new file mode 100644 index 0000000..3c3b436 --- /dev/null +++ b/PAGE-CS-MESSAGES.md @@ -0,0 +1,20 @@ +# KERI Auth Browser Extension +# Page <-> Content Script Messages +Summary of requests from web page to content script, and expected replies back + +| Page -> Content Script Request | Request Arg Type | Content Script -> Page Reply | Reply Type | Comments | +| ------------- | +| | | signify/reply | MessageData\ | | +| | | signify-extension | {type: 'signify-extension', data: { string: extensionId } } | | +| signify/configure-vendor | ConfigureVendorArgs | n/a | void | | +| signify/sign-request | SignRequestArgs | signify/reply | MessageData\ | | +| signify/authorize | AuthorizeArgs | signify/reply | MessageData\ | | +| signify/sign-data | SignDataArgs | signify/reply | MessageData\ | | +| signify-extension-client | ExtensionClientOptions | signify/reply | MessageData\ | | +| signify/authorize/aid | | signify/reply | MessageData\ | | +| signify/authorize/credential | | signify/reply | MessageData\ | | + +### References +- [KERI Auth Content Script](https://github.com/KERIAuth/keriauth-blazor-wasm/blob/main/KeriAuth.BrowserExtension/wwwroot/scripts/esbuild/ContentScript.ts) +- [Signify-browser-extension event-types](https://github.com/WebOfTrust/signify-browser-extension/blob/main/src/config/event-types.ts) +- [Polaris-web Page Messages](https://github.com/WebOfTrust/polaris-web/src/client.ts) \ No newline at end of file diff --git a/README.md b/README.md index 76413c6..dba09cf 100644 --- a/README.md +++ b/README.md @@ -74,17 +74,21 @@ Figure: KERI Auth Browser Extension Architecture ([source](https://docs.google.c * Persists configuration via chrome.storage.local. * Communicates with KERIA agent via signify-ts library. -### Content Script +### Web Page and Content Script Interaction +#### Content Script * With the user’s permission this script is injected into the active web page after the user initiates the action. * Runs in an isolated JavaScript context * Handles messages to/from the website via a JavaScript API (polaris-web) that the web page also implements. * Handles messages to/from the service-worker. -### Web Page +#### Web Page * Provided by a website owner, leveraging JavaScript interfaces on the Content Script, which follows the Signify protocol (polaris-web). * Interacts with extension via content script to get user's choice of Identifier (KERI AID) and Credential (ACDC). * Responsible for validating and determining acceptance of presented Identifier and Credential. May use other services for validation of the identifier's key-state, credential schema, and issuer's root of trust. +#### JavaScript API +The messages between the Content Script and Web Page are documented [here](PAGE-CS-MESSAGES.md). + ### Blazor WASM App Components #### Single Page App