From 58c7997ee39934733c3b00e431c4e46e7502774f Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 19 Aug 2024 13:41:50 -0400 Subject: [PATCH] docs: finish passkey guides, liquid extension and linking --- docs/.astro/types.d.ts | 103 +-- docs/astro.config.mjs | 18 + docs/package-lock.json | 597 +++++++++++++++++- docs/package.json | 5 +- docs/public/logo.png | Bin 0 -> 56646 bytes docs/src/components/QrCodeDemo.tsx | 58 ++ .../docs/clients/android/authentication.mdx | 24 +- .../docs/clients/android/introduction.mdx | 4 +- .../{get-passkey.md => authenticate.md} | 4 +- .../{create-passkey.md => register.md} | 4 +- .../docs/clients/android/registration.mdx | 27 +- .../docs/clients/browser/authentication.md | 74 ++- .../docs/clients/browser/registration.mdx | 105 ++- .../docs/guides/Passkey/authentication.mdx | 140 ++-- .../content/docs/guides/Passkey/extension.mdx | 105 +++ .../docs/guides/Passkey/registration.mdx | 139 ++-- .../docs/guides/Peer to Peer/answer.mdx | 12 +- .../docs/guides/Peer to Peer/exchange.mdx | 63 +- .../docs/guides/Peer to Peer/offer.mdx | 8 +- docs/src/content/docs/guides/components.mdx | 10 +- docs/src/content/docs/guides/concepts.md | 42 -- docs/src/content/docs/guides/concepts.mdx | 85 +++ .../content/docs/guides/getting-started.mdx | 16 +- docs/src/content/docs/guides/linking.mdx | 79 +++ docs/src/content/docs/guides/qr-code.md | 10 - docs/src/pages/demo.astro | 7 + docs/tsconfig.json | 3 +- 27 files changed, 1405 insertions(+), 337 deletions(-) create mode 100644 docs/public/logo.png create mode 100644 docs/src/components/QrCodeDemo.tsx rename docs/src/content/docs/clients/android/provider-service/{get-passkey.md => authenticate.md} (69%) rename docs/src/content/docs/clients/android/provider-service/{create-passkey.md => register.md} (75%) create mode 100644 docs/src/content/docs/guides/Passkey/extension.mdx delete mode 100644 docs/src/content/docs/guides/concepts.md create mode 100644 docs/src/content/docs/guides/concepts.mdx create mode 100644 docs/src/content/docs/guides/linking.mdx delete mode 100644 docs/src/content/docs/guides/qr-code.md create mode 100644 docs/src/pages/demo.astro diff --git a/docs/.astro/types.d.ts b/docs/.astro/types.d.ts index 83eef58..c268a41 100644 --- a/docs/.astro/types.d.ts +++ b/docs/.astro/types.d.ts @@ -141,217 +141,224 @@ declare module 'astro:content' { slug: "architecture"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; "clients/android/answer.mdx": { id: "clients/android/answer.mdx"; slug: "clients/android/answer"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "clients/android/authentication.mdx": { id: "clients/android/authentication.mdx"; slug: "clients/android/authentication"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "clients/android/introduction.mdx": { id: "clients/android/introduction.mdx"; slug: "clients/android/introduction"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "clients/android/offer.mdx": { id: "clients/android/offer.mdx"; slug: "clients/android/offer"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; -"clients/android/provider-service/create-passkey.md": { - id: "clients/android/provider-service/create-passkey.md"; - slug: "clients/android/provider-service/create-passkey"; +"clients/android/provider-service/authenticate.md": { + id: "clients/android/provider-service/authenticate.md"; + slug: "clients/android/provider-service/authenticate"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> -} & { render(): Render[".md"] }; -"clients/android/provider-service/get-passkey.md": { - id: "clients/android/provider-service/get-passkey.md"; - slug: "clients/android/provider-service/get-passkey"; - body: string; - collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; "clients/android/provider-service/introduction.mdx": { id: "clients/android/provider-service/introduction.mdx"; slug: "clients/android/provider-service/introduction"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; +"clients/android/provider-service/register.md": { + id: "clients/android/provider-service/register.md"; + slug: "clients/android/provider-service/register"; + body: string; + collection: "docs"; + data: any +} & { render(): Render[".md"] }; "clients/android/registration.mdx": { id: "clients/android/registration.mdx"; slug: "clients/android/registration"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "clients/browser/answer.mdx": { id: "clients/browser/answer.mdx"; slug: "clients/browser/answer"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "clients/browser/authentication.md": { id: "clients/browser/authentication.md"; slug: "clients/browser/authentication"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; "clients/browser/example.md": { id: "clients/browser/example.md"; slug: "clients/browser/example"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; "clients/browser/introduction.mdx": { id: "clients/browser/introduction.mdx"; slug: "clients/browser/introduction"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "clients/browser/offer.mdx": { id: "clients/browser/offer.mdx"; slug: "clients/browser/offer"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "clients/browser/registration.mdx": { id: "clients/browser/registration.mdx"; slug: "clients/browser/registration"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "guides/Passkey/authentication.mdx": { id: "guides/Passkey/authentication.mdx"; slug: "guides/passkey/authentication"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any +} & { render(): Render[".mdx"] }; +"guides/Passkey/extension.mdx": { + id: "guides/Passkey/extension.mdx"; + slug: "guides/passkey/extension"; + body: string; + collection: "docs"; + data: any } & { render(): Render[".mdx"] }; "guides/Passkey/registration.mdx": { id: "guides/Passkey/registration.mdx"; slug: "guides/passkey/registration"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "guides/Peer to Peer/answer.mdx": { id: "guides/Peer to Peer/answer.mdx"; slug: "guides/peer-to-peer/answer"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "guides/Peer to Peer/exchange.mdx": { id: "guides/Peer to Peer/exchange.mdx"; slug: "guides/peer-to-peer/exchange"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "guides/Peer to Peer/offer.mdx": { id: "guides/Peer to Peer/offer.mdx"; slug: "guides/peer-to-peer/offer"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; "guides/components.mdx": { id: "guides/components.mdx"; slug: "guides/components"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; -"guides/concepts.md": { - id: "guides/concepts.md"; +"guides/concepts.mdx": { + id: "guides/concepts.mdx"; slug: "guides/concepts"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> -} & { render(): Render[".md"] }; + data: any +} & { render(): Render[".mdx"] }; "guides/getting-started.mdx": { id: "guides/getting-started.mdx"; slug: "guides/getting-started"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".mdx"] }; -"guides/qr-code.md": { - id: "guides/qr-code.md"; - slug: "guides/qr-code"; +"guides/linking.mdx": { + id: "guides/linking.mdx"; + slug: "guides/linking"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> -} & { render(): Render[".md"] }; + data: any +} & { render(): Render[".mdx"] }; "introduction.md": { id: "introduction.md"; slug: "introduction"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; "server/environment-variables.md": { id: "server/environment-variables.md"; slug: "server/environment-variables"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; "server/integrations.md": { id: "server/integrations.md"; slug: "server/integrations"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; "server/introduction.md": { id: "server/introduction.md"; slug: "server/introduction"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; "server/running-locally.md": { id: "server/running-locally.md"; slug: "server/running-locally"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; "server/tmp.md": { id: "server/tmp.md"; slug: "server/tmp"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; "server/tmp2.md": { id: "server/tmp2.md"; slug: "server/tmp2"; body: string; collection: "docs"; - data: InferEntrySchema<"docs"> + data: any } & { render(): Render[".md"] }; }; @@ -363,5 +370,5 @@ declare module 'astro:content' { type AnyEntryMap = ContentEntryMap & DataEntryMap; - export type ContentConfig = typeof import("../src/content/config.js"); + export type ContentConfig = never; } diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 9c593d0..389d998 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -7,8 +7,15 @@ import starlightOpenAPI, { openAPISidebarGroups } from "starlight-openapi"; import react from "@astrojs/react"; import mdx from "@astrojs/mdx"; +import basicSsl from '@vitejs/plugin-basic-ssl' // https://astro.build/config export default defineConfig({ + vite: { + plugins: [basicSsl()], + server: { + https: true, + }, + }, site: 'https://liquidauth.com', markdown: { rehypePlugins: [rehypeMermaid] @@ -22,6 +29,17 @@ export default defineConfig({ logo: { src: "./public/logo.svg" }, + head:[ + { + tag: 'script', + attrs: { + // Tweaks to the script URL or attributes can be made here. + src: 'https://unpkg.com/htmx.org@1.9.6', + integrity: "sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni", + crossorigin: "anonymous", + }, + }, + ], editLink: { baseUrl: 'https://github.com/algorandfoundation/liquid-auth/edit/develop/docs/', }, diff --git a/docs/package-lock.json b/docs/package-lock.json index 5ee10ef..08561c0 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,15 +8,17 @@ "name": "test-starlight", "version": "0.0.1", "dependencies": { + "@algorandfoundation/liquid-client": "github:algorandfoundation/liquid-auth-js", "@astrojs/check": "^0.5.10", "@astrojs/mdx": "^3.1.2", - "@astrojs/react": "^3.6.0", + "@astrojs/react": "^3.6.2", "@astrojs/starlight": "^0.24.2", "@astrojs/tailwind": "^5.1.0", "@fontsource/poppins": "^5.0.13", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "astro": "^4.3.5", + "qr-code-styling": "github:awesome-algorand/qr-code-styling", "react": "^18.3.1", "react-dom": "^18.3.1", "rehype-mermaid": "^2.1.0", @@ -29,10 +31,37 @@ "typescript": "^5.4.5" }, "devDependencies": { + "@vitejs/plugin-basic-ssl": "^1.1.0", "socket.io-client": "^4.7.5", "tweetnacl": "^1.0.3" } }, + "node_modules/@algorandfoundation/liquid-client": { + "version": "0.0.1", + "resolved": "git+ssh://git@github.com/algorandfoundation/liquid-auth-js.git#b787150ff490f7a4c2e28864821c2be76e31d294", + "hasInstallScript": true, + "license": "AGPL-3.0", + "dependencies": { + "canvas": "^2.11.2", + "eventemitter3": "^5.0.1", + "qr-code-styling": "*", + "socket.io-client": "^4.7.5", + "tweetnacl": "^1.0.3", + "uuid": "^10.0.0" + } + }, + "node_modules/@algorandfoundation/liquid-client/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -358,9 +387,9 @@ } }, "node_modules/@astrojs/react": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-3.6.0.tgz", - "integrity": "sha512-YGLxy5jCU9xKG/HAvYsWMcvrQVIhqVe0Sda3Z5UtP32rfXeG6B9J1xQvnx+kRSFTpIrj+7AwPSDSehLbCHJ56w==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-3.6.2.tgz", + "integrity": "sha512-fK29lYI7zK/KG4ZBy956x4dmauZcZ18osFkuyGa8r3gmmCQa2NZ9XNu9WaVYEUm0j89f4Gii4tbxLoyM8nk2MA==", "dependencies": { "@vitejs/plugin-react": "^4.3.1", "ultrahtml": "^1.5.3" @@ -1823,6 +1852,25 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@mdx-js/mdx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz", @@ -2282,8 +2330,7 @@ "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "dev": true + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, "node_modules/@types/acorn": { "version": "4.0.6", @@ -2466,6 +2513,18 @@ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", + "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "dev": true, + "engines": { + "node": ">=14.6.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", @@ -2584,6 +2643,11 @@ "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.16.tgz", "integrity": "sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg==" }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/acorn": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", @@ -2603,6 +2667,17 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", @@ -2715,6 +2790,24 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -3197,6 +3290,52 @@ } ] }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/canvas/node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/canvas/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/canvas/node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -3488,6 +3627,14 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/color/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3526,6 +3673,16 @@ "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -4095,6 +4252,11 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -4229,7 +4391,6 @@ "version": "6.5.4", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", - "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", @@ -4242,7 +4403,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", - "dev": true, "engines": { "node": ">=10.0.0" } @@ -4569,6 +4729,38 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4590,6 +4782,68 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -4730,6 +4984,11 @@ "node": ">=4" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -5124,6 +5383,18 @@ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", @@ -5171,6 +5442,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -5733,6 +6014,28 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", @@ -7395,6 +7698,45 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -7436,6 +7778,11 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nan": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==" + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -7486,6 +7833,25 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -7496,6 +7862,20 @@ "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -7542,6 +7922,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -7794,6 +8186,14 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -8249,6 +8649,19 @@ "node": ">=6" } }, + "node_modules/qr-code-styling": { + "version": "1.5.1", + "resolved": "git+ssh://git@github.com/awesome-algorand/qr-code-styling.git#f279d28f55e300270e31a034d6ade67f5a373598", + "license": "MIT", + "dependencies": { + "qrcode-generator": "^1.4.3" + } + }, + "node_modules/qrcode-generator": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz", + "integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9023,6 +9436,61 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", @@ -9179,6 +9647,11 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/sharp": { "version": "0.32.6", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", @@ -9323,7 +9796,6 @@ "version": "4.7.5", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", - "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -9338,7 +9810,6 @@ "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -9694,6 +10165,22 @@ "node": ">=10" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", @@ -9717,6 +10204,27 @@ "streamx": "^2.15.0" } }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -9755,6 +10263,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -9825,8 +10338,7 @@ "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, "node_modules/type-fest": { "version": "2.19.0", @@ -10452,6 +10964,20 @@ "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz", "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==" }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -10486,6 +11012,51 @@ "node": ">=4" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/widest-line": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", @@ -10662,7 +11233,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, "engines": { "node": ">=10.0.0" }, @@ -10683,7 +11253,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", - "dev": true, "engines": { "node": ">=0.4.0" } diff --git a/docs/package.json b/docs/package.json index 4c8701e..44b5fe4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -10,15 +10,17 @@ "astro": "astro" }, "dependencies": { + "@algorandfoundation/liquid-client": "github:algorandfoundation/liquid-auth-js", "@astrojs/check": "^0.5.10", "@astrojs/mdx": "^3.1.2", - "@astrojs/react": "^3.6.0", + "@astrojs/react": "^3.6.2", "@astrojs/starlight": "^0.24.2", "@astrojs/tailwind": "^5.1.0", "@fontsource/poppins": "^5.0.13", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "astro": "^4.3.5", + "qr-code-styling": "github:awesome-algorand/qr-code-styling", "react": "^18.3.1", "react-dom": "^18.3.1", "rehype-mermaid": "^2.1.0", @@ -31,6 +33,7 @@ "typescript": "^5.4.5" }, "devDependencies": { + "@vitejs/plugin-basic-ssl": "^1.1.0", "socket.io-client": "^4.7.5", "tweetnacl": "^1.0.3" } diff --git a/docs/public/logo.png b/docs/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7e7d46f31ac285b392469a7198a8a307e48d183a GIT binary patch literal 56646 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%0DZ0q7wsK7MEb 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 literal 0 HcmV?d00001 diff --git a/docs/src/components/QrCodeDemo.tsx b/docs/src/components/QrCodeDemo.tsx new file mode 100644 index 0000000..47638a8 --- /dev/null +++ b/docs/src/components/QrCodeDemo.tsx @@ -0,0 +1,58 @@ +import {SignalClient} from "@algorandfoundation/liquid-client"; +import { useEffect, useState } from "react"; + +const url = "liquid-auth.onrender.com" + +const INITIAL = "Initializing ๐Ÿš€" +const PEER_CONNECTED = "Peer connected ๐ŸŽ‰" +const LINK_REQUEST = "Link Requested ๐Ÿšš" +const WAITING = "Waiting for Link โŒ›" +const LINKED = "Linked ๐Ÿ”—" +const CLOSED = "Closed ๐Ÿšช" + +export function QrCodeDemo(){ + const [client] = useState(new SignalClient(url)); + const [requestId, setRequestId] = useState(SignalClient.generateRequestId()); + const [qrCodeUrl, setQrCodeUrl] = useState(""); + const [status, setStatus] = useState(INITIAL); + + // @ts-expect-error, ignoring the private field + client.qrCodeOptions.image = "/logo.png" + + useEffect(() => { + setStatus(LINK_REQUEST) + client.on("link-message", () => { + setStatus(LINKED) + }) + client.peer(requestId, "offer").then(() => { + setStatus(PEER_CONNECTED) + }) + client.qrCode().then((url) => { + setQrCodeUrl(url); + setStatus(WAITING) + }) + return ()=>{ + client.close() + setStatus(CLOSED) + } + }, [requestId]); + + return
+
{status}
+ {qrCodeUrl && PEER_CONNECTED && +
+ QR Code for Liquid Auth + Algorand QRCode +
+ } + {status === PEER_CONNECTED && } + +
+} diff --git a/docs/src/content/docs/clients/android/authentication.mdx b/docs/src/content/docs/clients/android/authentication.mdx index 2ff5b15..14d04f3 100644 --- a/docs/src/content/docs/clients/android/authentication.mdx +++ b/docs/src/content/docs/clients/android/authentication.mdx @@ -14,7 +14,7 @@ val httpClient = OkHttpClient.Builder() val assertionApi = AssertionApi(httpClient) ``` -## User Agent +## ๐Ÿ‘ค User Agent The User-Agent string is used to authenticate the device. @@ -24,7 +24,7 @@ val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} " + "(Android ${Build.VERSION.RELEASE}; ${Build.MODEL}; ${Build.BRAND})" ``` -## Options +## ๐Ÿงฎ Options Fetch assertion options from the service. ```kotlin @@ -35,7 +35,7 @@ val response = assertionApi.postAssertionOptions( ).await() ``` -## Retrieving +## ๐ŸŽ‰ Retrieving There are several ways to handle the retrieval of an existing Passkey. We recommend @@ -105,14 +105,28 @@ using the [FIDO2ApiClient](https://developers.google.com/identity/fido/android/n -## Response +## ๐Ÿ” Liquid Extension + +The liquid extension is optional for authentication requests. + +This is useful when you already have a passkey +and want to peer with a device that is not signed in. ```kotlin -// MainActivity.kt val liquidExtJSON = JSONObject() +// Optionally authenticate a remote peer liquidExtJSON.put("requestId", requestId) +``` + + +## ๐Ÿšš Response + +```kotlin +// MainActivity.kt + + val response = assertionApi.postAssertionResponse( origin, // Origin Server userAgent, // Required for checking the authenticator fingerprint diff --git a/docs/src/content/docs/clients/android/introduction.mdx b/docs/src/content/docs/clients/android/introduction.mdx index f47df2a..8ac15cb 100644 --- a/docs/src/content/docs/clients/android/introduction.mdx +++ b/docs/src/content/docs/clients/android/introduction.mdx @@ -42,7 +42,7 @@ using the SHA256 fingerprint and application name in the [environment configurat Add jitpack as a repository - ```kotlin + ```kotlin add={6} add={7} add={8} dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { @@ -56,7 +56,7 @@ Add jitpack as a repository ``` - ```groovy + ```groovy add={6} dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { diff --git a/docs/src/content/docs/clients/android/provider-service/get-passkey.md b/docs/src/content/docs/clients/android/provider-service/authenticate.md similarity index 69% rename from docs/src/content/docs/clients/android/provider-service/get-passkey.md rename to docs/src/content/docs/clients/android/provider-service/authenticate.md index fca6c4f..a9f70d7 100644 --- a/docs/src/content/docs/clients/android/provider-service/get-passkey.md +++ b/docs/src/content/docs/clients/android/provider-service/authenticate.md @@ -1,5 +1,5 @@ --- -title: "Android: Get Service Passkey" +title: "Android 14: Authenticate" --- ```kotlin @@ -8,6 +8,6 @@ val request = PendingIntentHandler.retrieveProviderCreateCredentialRequest(inten if (request.callingRequest is CreatePublicKeyCredentialRequest) { val result = viewModel.processCreatePasskey(this@CreatePasskeyActivity, request) } else { - binding.createPasskeyMessage.text = resources.getString(R.string.get_passkey_error) + val text = resources.getString(R.string.get_passkey_error) } ``` diff --git a/docs/src/content/docs/clients/android/provider-service/create-passkey.md b/docs/src/content/docs/clients/android/provider-service/register.md similarity index 75% rename from docs/src/content/docs/clients/android/provider-service/create-passkey.md rename to docs/src/content/docs/clients/android/provider-service/register.md index 9175cbc..ffe295b 100644 --- a/docs/src/content/docs/clients/android/provider-service/create-passkey.md +++ b/docs/src/content/docs/clients/android/provider-service/register.md @@ -1,5 +1,5 @@ --- -title: "Android: Create Service Passkey" +title: "Android 14: Register" --- @@ -13,6 +13,6 @@ if (request != null) { finish() } } else { - binding.getPasskeyMessage.text = resources.getString(R.string.get_passkey_error) + val text = resources.getString(R.string.get_passkey_error) } ``` diff --git a/docs/src/content/docs/clients/android/registration.mdx b/docs/src/content/docs/clients/android/registration.mdx index 82b09b3..4cad405 100644 --- a/docs/src/content/docs/clients/android/registration.mdx +++ b/docs/src/content/docs/clients/android/registration.mdx @@ -6,6 +6,8 @@ import {Aside, Tabs, TabItem } from "@astrojs/starlight/components"; Register a [Passkey](../../guides/concepts#passkeys) with the [Service](../../server/introduction) and attest the knowledge of a KeyPair. +This is done by creating a new Passkey with the [liquid extension](/guides/passkey/extension) enabled. +The extension is used to sign the challenge with an additional KeyPair as well as authenticate a remote peer. Get started by initializing the AttestationApi with an OkHttpClient and CookieJar. ```kotlin @@ -15,7 +17,7 @@ val httpClient = OkHttpClient.Builder() val attestationApi = AttestationApi(httpClient) ``` -## User Agent +## ๐Ÿ‘ค User Agent The User-Agent string is used to authenticate the device. @@ -25,9 +27,10 @@ val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} " + "(Android ${Build.VERSION.RELEASE}; ${Build.MODEL}; ${Build.BRAND})" ``` -## Options +## ๐Ÿงฎ Options Fetch attestation options from the service. +The remote client should present a [Deep Link](/guides/linking/#deep-link) which contains the Origin. ```kotlin // Create Options for FIDO2 Server val options = JSONObject() @@ -47,7 +50,7 @@ val response = attestationApi.postAttestationOptions( ) ``` -## Creating +## โœจ Creating There are several ways to handle the creation of a new Passkey. We recommend @@ -124,17 +127,29 @@ attestationIntentLauncher.launch( -## Response +## ๐Ÿ” Liquid Extension + +Sign the challenge with an additional KeyPair and optionally authenticate a remote peer. ```kotlin // Create the Liquid Extension JSON val liquidExtJSON = JSONObject() +// The type of signature and public key, this is also used +// to determine the type of encoding for the user.id liquidExtJSON.put("type", "algorand") -liquidExtJSON.put("requestId", msg.requestId) +// The address of the account liquidExtJSON.put("address", account.address.toString()) -liquidExtJSON.put("signature", Base64.encodeBase64URLSafeString(signature!!)) +// The signature of the challenge, signed by the account +liquidExtJSON.put("signature", Base64.encodeBase64URLSafeString(signature)) +// Optionally authenticate a remote peer +liquidExtJSON.put("requestId", "") +// Optional device name liquidExtJSON.put("device", Build.MODEL) +``` +## ๐Ÿšš Response + +```kotlin val response = attestationApi.postAttestationResponse( origin, // Origin Server "User-Agent-String", // Required for checking the authenticator fingerprint diff --git a/docs/src/content/docs/clients/browser/authentication.md b/docs/src/content/docs/clients/browser/authentication.md index 563aeb0..47e87b8 100644 --- a/docs/src/content/docs/clients/browser/authentication.md +++ b/docs/src/content/docs/clients/browser/authentication.md @@ -1,8 +1,12 @@ --- title: 'Browser: Authentication' +sidebar: + badge: + text: "TODO" + variant: danger --- -Authenticate an existing [Passkey](../../guides/concepts#passkeys) with the [Service](../../server/introduction). +Authenticate an existing [Passkey](/guides/concepts/#passkeys) with the [Service](/guides/server/introduction). ### Who is this for? @@ -33,3 +37,71 @@ await assertion( {requestId: 12345} // Optional requestId to link ) ``` + +## Manual + +If you want to manually handle the process of creating a passkey, you can use the following methods and preforming +the three steps of the process. + +### ๐Ÿงฎ Options + +Manually fetching the `Options` from the service. + +```typescript +import {fetchAssertionRequestOptions} from '@algorandfoundation/liquid-client/assertion' + +const encodedOptions = await fetchAssertionRequestOptions("https://my-liquid-service.com", "") +``` + +### ๐ŸŽ‰ Retrieving + +Decode the options and fetch the Passkey. + +```typescript +import {fromBase64Url} from "@algorandfoundation/liquid-client/encoding"; +const options = { ...encodedOptions }; +// Challenge from the service +options.challenge = fromBase64Url(options.challenge); +// Decode any known credentials +if (options.allowCredentials) { + for (const cred of options.allowCredentials) { + cred.id = fromBase64Url(cred.id); + } +} +const credential = navigator.credentials.get({ + publicKey: options +}) +``` + +### ๐Ÿ” Liquid Extension + +Optionally, Authenticate a remote user with the Liquid Extension. + +```typescript +credential.clientExtensionResults = { + // Optionally authenticate a remote peer + requestId: "" +} +``` + +### ๐Ÿšš Response + +Encode and submit the passkey to the service. + +```typescript +import {fetchAssertionResponse} from '@algorandfoundation/liquid-client/assertion' +import {toBase64URL} from '@algorandfoundation/liquid-client/encoding' + +const result = await fetchAssertionResponse("https://my-liquid-service.com", { + id: credential.id, + type: credential.type, + rawId: toBase64URL(credential.rawId), + clientExtensionResults: credential.clientExtensionResults, + response: Object.keys(AuthenticatorAssertionResponse.prototype).reduce((prev, curr) => { + prev[curr] = toBase64URL(credential.response[curr]); + return prev; + }, { + clientDataJSON: toBase64URL(credential.response.clientDataJSON), + }), +}) +``` diff --git a/docs/src/content/docs/clients/browser/registration.mdx b/docs/src/content/docs/clients/browser/registration.mdx index be2a8f1..55ba822 100644 --- a/docs/src/content/docs/clients/browser/registration.mdx +++ b/docs/src/content/docs/clients/browser/registration.mdx @@ -12,7 +12,10 @@ Register a [Passkey](../../guides/concepts#passkeys) with the [Service](../../se ## Client -Creating a passkey and registering it with the service using an instance of the `SignalClient` +Creating a passkey and registering it with the service using an instance of the `SignalClient`. + +`attestation` is a convenience method that handles the entire process of creating a passkey and registering it with the service. +The caller must provide a callback that will be called when a challenge is received from the service. ```typescript // browser.client.ts @@ -26,11 +29,11 @@ await client.attestation( // The type of signature and public key type: 'algorand', // The address of the account - address: account.addr, + address: address, // The signature of the challenge, signed by the account - signature: toBase64URL(nacl.sign.detached(challenge, account.sk)), + signature: toBase64URL(nacl.sign.detached(challenge, seceretKey)), // Optionally authenticate a remote peer - requestId: 12345, + requestId: "", // Optional device name device: 'Demo Web Wallet' }) @@ -40,11 +43,13 @@ await client.attestation( ## Stateless -Creating a passkey without using the `SignalClient` +Using the `attestation` method to create a passkey without using the `SignalClient` ```typescript +import * as nacl from 'tweetnacl' import {attestation} from '@algorandfoundation/liquid-client/attestation' import {toBase64URL} from '@algorandfoundation/liquid-client/encoding' + await attestation( "https://my-liquid-service.com", // Callback when a challenge is received, return a signed challenge @@ -52,9 +57,9 @@ await attestation( // The type of signature and public key type: 'algorand', // The address of the account - address: account.addr, + address: address, // The signature of the challenge, signed by the account - signature: toBase64URL(nacl.sign.detached(challenge, account.sk)), + signature: toBase64URL(nacl.sign.detached(challenge, seceretKey)), // Optionally authenticate a remote peer requestId: 12345, // Optional device name @@ -62,3 +67,89 @@ await attestation( }) ) ``` + +## Manual + +If you want to manually handle the process of creating a passkey, you can use the following methods and preforming +the three steps of the process. + +### ๐Ÿงฎ Options + +Manually fetching the PublicKeyCredentialCreationOptions from the service. + +```typescript +import {fetchAttestationRequest} from '@algorandfoundation/liquid-client/attestation' + +const encodedOptions = await fetchAttestationRequest("https://my-liquid-service.com") +``` + +### โœจ Creating + +Decode the options and create a new passkey. + +```typescript +import {decodeAddress, fromBase64Url} from "@algorandfoundation/liquid-client/encoding"; +const options = { ...encodedOptions }; + // Uint8Array of the user's id, is set as the encoded address for this type of key + options.user.id = decodeAddress(address); + // Must be string that is equal to the id bytes using the appropriate encoding + options.user.name = address; + // Friendly name to display for the user + options.user.displayName = "Hello World"; + // Challenge from the service + options.challenge = fromBase64Url(options.challenge); + + // Decode any known credentials + if (options.excludeCredentials) { + for (const cred of options.excludeCredentials) { + cred.id = fromBase64Url(cred.id); + } +} +const credential = navigator.credentials.create({ + publicKey: options +}) + +``` + +### ๐Ÿ” Liquid Extension + +Sign the challenge with an additional KeyPair. + +```typescript +import * as nacl from 'tweetnacl' +import {toBase64URL} from '@algorandfoundation/liquid-client/encoding' + +credential.clientExtensionResults = { + // The type of signature and public key, this is also used + // to determine the type of encoding for the user.id + type: 'algorand', + // The address of the account + address: address, + // The signature of the challenge, signed by the account + signature: toBase64URL(nacl.sign.detached(options.challenge, seceretKey)), + // Optionally authenticate a remote peer + requestId: "", + // Optional device name + device: 'Demo Web Wallet' +} +``` + +### ๐Ÿšš Response + +Encode and submit the passkey to the service. + +```typescript +import {fetchAttestationResponse} from '@algorandfoundation/liquid-client/attestation' +import {toBase64URL} from '@algorandfoundation/liquid-client/encoding' + +const result = await fetchAttestationResponse("https://my-liquid-service.com", { + id: credential.id, + rawId: toBase64URL(credential.rawId), + type: credential.type, + response: { + clientDataJSON: toBase64URL(response.clientDataJSON), + attestationObject: toBase64URL(response.attestationObject), + }, + clientExtensionResults: credential.clientExtensionResults + }) +``` diff --git a/docs/src/content/docs/guides/Passkey/authentication.mdx b/docs/src/content/docs/guides/Passkey/authentication.mdx index 13ba917..b822f75 100644 --- a/docs/src/content/docs/guides/Passkey/authentication.mdx +++ b/docs/src/content/docs/guides/Passkey/authentication.mdx @@ -1,11 +1,8 @@ --- -title: Authentication Guide +title: "Passkey: Authentication Guide" sidebar: order: 11 label: Authentication - badge: - text: "WIP" - variant: caution tags: - Passkey - Registration @@ -13,7 +10,7 @@ tags: - Browser - Server --- -import { Tabs, TabItem, Steps, Aside} from '@astrojs/starlight/components'; +import { Steps, LinkCard, CardGrid, Aside } from "@astrojs/starlight/components"; Authenticating with a previously created [Passkey](../../concepts#passkeys) is a three-step process. @@ -23,101 +20,74 @@ Authenticating with a previously created [Passkey](../../concepts#passkeys) is a 3. Post the **Assertion Response** from an authenticator to the [Server](../../../server/introduction). -## โš™๏ธ Options +### Who is this for? -[PublicKeyCredentialRequestOptions](https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options) are used +- **Wallets/Credential Managers** that want to adopt Liquid Auth. +- **Ecosystems** that want to leverage `Liquid Auth` for their networks. + +## ๐Ÿงฎ Options + + + + +PublicKeyCredentialRequestOptions are used to retrieve existing Passkey and are generated by the service. The URI for this request is [/assertion/request/:credId](../../../server/api/operations/assertioncontroller_request) using the `POST` method. - - - ```bash - export CRED_ID="AWwK7bcWvbQ4plHok9mOIzuMdDKmLTKog2mgqhe2X48C40ISsvt7xK9CJmjjWbWF7EnNVGxDPb51WHGfN1ac1_w" - curl -X POST \ - --header "Content-Type: application/json" \ - https://my-liquid-auth-service.com/attestation/request/$CRED_ID - ``` - - - ```typescript - //app.js - // TODO - ``` - See the [Browser](../../../clients/browser/registration) registration guide for more details. - - - ```kotlin - //app.kt - import foundation.algorand.auth.fido2.* - // TODO - ``` - See the [Android](../../../clients/android/registration) registration guide for more details. - - + + + + ## ๐ŸŽ‰ Retrieving Getting the existing Passkey from an authenticator can vary depending on the platform. Most platforms will accept the **Request Options** -and return an [AuthenticatorAssertionResponse](https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse). - - - - - ```typescript - //app.ts - const response: AuthenticatorAssertionResponse = await navigator.credentials.get({ - publicKey: options - }) - ``` - See the [Browser](../../../clients/browser/authentication) authentication guide for more details. - - - ```kotlin - //app.kt - // Retrieve the PublicKeyCredential from the - // FIDO2Client or CredentialManager - val credential = PublicKeyCredential() - ``` - See the [Android](../../../clients/android/authentication) authentication guide for more details. - - +and return an AuthenticatorAssertionResponse. + + + + + + ## ๐Ÿšš Response + + Registering a new Passkey with the service. Prove the authenticity of the Passkey by sending the **Attestation Response** to the service. The URI for this request is [/assertion/response](../../../server/api/operations/assertioncontroller_response) using the `POST` method. - - - ```bash - # TODO - curl -X POST \ - --header "Content-Type: application/json" \ - --data '{"clientDataJSON": "", "extension"}' \ - https://my-liquid-auth-service.com/attestation/response - ``` - - - ```typescript - //app.ts - // TODO - ``` - See the [Browser](../../../clients/browser/authentication) authentication guide for more details. - - - ```kotlin - //app.kt - val response = attestationApi.postAssertionResponse( - origin, // Origin Server - "User-Agent-String", // Required for checking the authenticator fingerprint - credential // PublicKeyCredential - liquidExtension // Liquid Extension - ) - ``` - See the [Android](../../../clients/android/authentication) authentication guide for more details. - - + + + + + diff --git a/docs/src/content/docs/guides/Passkey/extension.mdx b/docs/src/content/docs/guides/Passkey/extension.mdx new file mode 100644 index 0000000..6bdb3b1 --- /dev/null +++ b/docs/src/content/docs/guides/Passkey/extension.mdx @@ -0,0 +1,105 @@ +--- +title: "Passkey: Liquid Extension Guide" +sidebar: + label: "Extension" +--- + +import { Aside, LinkCard } from "@astrojs/starlight/components"; + + +This is a [WebAuthn extension](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/WebAuthn_extensions) that allows the service to verify the authenticity of a `KeyPair` using a `Passkey`. +The extension is used during the [Registration](/guides/passkey/registration) and [Authentication](/guides/passkey/authentication) of Passkeys. + +It is the heart of operations for the `Liquid` service and is required for registration and peer-to-peer connections. + +### Who is this for? + +- **Wallets/Credential Managers** that want to adopt Liquid Auth. +- **Ecosystems** that want to leverage the `Liquid Extension` for their networks. + + + +## ๐Ÿงฎ Options + +The service will expect the following options to be passed with the `PublicKeyCredentialCreationOptions` and `PublicKeyCredentialRequestOptions`: + +```javascript +const body = { + //...options + "extensions": { + "liquid": true + } + } +``` + +This tells the service that the request is using the Liquid extension and expects to receive a `Passkey` with additional information. + + +## ๐Ÿงช Handling Passkey + +As of this writing, currently no Authenticators support the `liquid` extension natively. The extension must be handled by the integrators + +If you are developing an Authenticator, you will need to handle the `liquid` extension in the `PublicKeyCredentialCreationOptions` and `PublicKeyCredentialRequestOptions`. + +Feel free to edit this document if you have an Authenticator that supports the `liquid` extension. + + +## ๐Ÿšš Response + +The response message should include the extension results in the `liquid` key if it was enabled in the `Options`. +It is mandatory to include the extension when registering new credentials with the Service. + + +### โœจ Registration + + + +The reference implementation of the extension currently only supports the `algorand` type. +It allows associating an Algorand address with the Passkey. + +```javascript +const response = { + //...response from authenticator + "clientExtensionResults": { + "liquid": { + // Required for the extension + "type": "algorand", + // The address of the account in Algorand Encoding + "address": "2SPDE6XLJNXFTOO7OIGNRNKSEDOHJWVD3HBSEAPHONZQ4IQEYOGYTP6LXA", + // Signature of the challenge that was produced by the Service + "signature": "QY31mdH8AwpJ9p4pCXBO2iA5WdU-BjG52xEtJNuSJNHJIaJ10uzqk3FdR0fvYVfb_rzXTuWn4k1PFFeg-vpEDw", + // Optional RequestId to authenticate a remote peer + "requestId": "019097ff-bb8c-7514-a0c6-5209d2405a4a", + // Optional name for the device + "device": "Pixel 8 Pro" + } + } +} +``` + + + +### ๐ŸŽ‰ Authentication + +When using a previously registered Passkey, the client does not require the liquid extension. +The service has attested to the additional keypair which represents an account and can rely on the WebAuthn standard for authentication. + + +Although the extension is not required, it is useful for establishing a Peer-to-Peer connection. + + +```javascript +const response = { + //...response from authenticator + "clientExtensionResults": { + "liquid": { + // Optional RequestId to authenticate a remote peer + "requestId": "019097ff-bb8c-7514-a0c6-5209d2405a4a" + } + } +} +``` diff --git a/docs/src/content/docs/guides/Passkey/registration.mdx b/docs/src/content/docs/guides/Passkey/registration.mdx index 05705cf..a8a610a 100644 --- a/docs/src/content/docs/guides/Passkey/registration.mdx +++ b/docs/src/content/docs/guides/Passkey/registration.mdx @@ -1,10 +1,7 @@ --- -title: Registration Guide +title: "Passkey: Registration Guide" sidebar: order: 10 - badge: - text: "WIP" - variant: caution label: Registration tags: - Passkey @@ -14,117 +11,79 @@ tags: - Browser - Server --- -import { Tabs, TabItem, Steps, Aside} from '@astrojs/starlight/components'; +import { Steps, Aside, CardGrid, LinkCard} from '@astrojs/starlight/components'; Registering a [Passkey](../../concepts#passkeys) is a three-step process. 1. Fetch the **Creation Options** from the [Server](../../../server/introduction). - 2. Create a new [Passkey](../../concepts#passkeys)() using the **Creation Options**. + 2. Create a new [Passkey](../../concepts#passkeys) using the **Creation Options**. 3. Post the **Attestation Response** from an authenticator to the [Server](../../../server/introduction). -## โš™๏ธ Options +### Who is this for? + +- **Wallets/Credential Managers** that want to adopt Liquid Auth. +- **Ecosystems** that want to leverage `Liquid Auth` for their networks. + +## ๐Ÿงฎ Options -[PublicKeyCredentialCreationOptions](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialCreationOptions) are used to create a new Passkey and are generated by the service. +PublicKeyCredentialCreationOptions are used to create a new Passkey and are generated by the service. This allows the service to define what options are required for registration. The URI for this request is [/attestation/request](../../../server/api/operations/attestationcontroller_request) using the `POST` method. - - - ```bash - curl -X POST \ - --header "Content-Type: application/json" \ - --data '{"extensions": {"liquid": true}}' \ - https://my-liquid-auth-service.com/attestation/request - ``` - - - ```typescript - //app.js - import { fetchAttestationRequest } from '@algorandfoundation/liquid-client/attestation' - const options = await fetchAttestationRequest("https://my-liquid-auth-service.com") - ``` - See the [Browser](../../../clients/browser/registration) registration guide for more details. - - - ```kotlin - //app.kt - import foundation.algorand.auth.fido2.* - // TODO - ``` - See the [Android](../../../clients/android/registration) registration guide for more details. - - + + + + + ## โœจ Creating Generating a new Passkey from an authenticator can vary depending on the platform. -Most platforms will accept the **Creation Options** and return an [AuthenticatorAttestationResponse](https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAttestationResponse). - - - - - ```typescript - //app.ts - const response: AuthenticatorAttestationResponse = await navigator.credentials.create({ - publicKey: options - }) - ``` - See the [Browser](../../../clients/browser/registration) registration guide for more details. - - - ```kotlin - //app.kt - // Create a new PublicKeyCredential using - // FIDO2Client or CredentialManager - val credential = PublicKeyCredential() - ``` - See the [Android](../../../clients/android/registration) registration guide for more details. - - +Most platforms will accept the **Creation Options** and return an AuthenticatorAttestationResponse. + + + + + ## ๐Ÿšš Response - + Registering a new Passkey with the service. Prove the authenticity of the Passkey by sending the **Attestation Response** to the service. The URI for this request is [/attestation/response](../../../server/api/operations/attestationcontroller_response) using the `POST` method. - - - ```bash - # TODO - curl -X POST \ - --header "Content-Type: application/json" \ - --data '{"attestationResponse": "", "clientDataJSON": "", "extension"}' \ - https://my-liquid-auth-service.com/attestation/response - ``` - - - ```typescript - //app.ts - import { fetchAttestationResponse } from '@algorandfoundation/liquid-client/attestation' - // TODO - ``` - See the [Browser](../../../clients/browser/registration) registration guide for more details. - - - ```kotlin - //app.kt - val response = attestationApi.postAttestationResponse( - origin, // Origin Server - "User-Agent-String", // Required for checking the authenticator fingerprint - credential // PublicKeyCredential - liquidExtension // Liquid Extension - ) - ``` - See the [Android](../../../clients/android/registration) registration guide for more details. - - + + + + + diff --git a/docs/src/content/docs/guides/Peer to Peer/answer.mdx b/docs/src/content/docs/guides/Peer to Peer/answer.mdx index 2e03694..520cfba 100644 --- a/docs/src/content/docs/guides/Peer to Peer/answer.mdx +++ b/docs/src/content/docs/guides/Peer to Peer/answer.mdx @@ -1,5 +1,5 @@ --- -title: "Answer Guide" +title: "Peer: Answer Guide" sidebar: order: 21 label: "Answer" @@ -9,9 +9,15 @@ import { LinkCard, CardGrid } from "@astrojs/starlight/components"; An **Answer** is a type of session that is initiated by a client and is used to respond to an **Offer** from another client. +The Answer client is responsible for presenting the [Deep Link](/guides/linking/#deep-link) to the Offer client and waiting for the response. -This is usually a dApp that wants to peer with a wallet client with an Offer. -The Answer client is responsible for presenting the QRCode to the Offer client. +### Who is this for? + +- **dApps/Wallets** that want to peer with Wallets in the ecosystem. +- **Wallets** that want to integrate deeply into `Liquid Auth` and tailor the experience. + + +### Additional Links >A: Present QR Code B->>A: Scan QR Code A-->>B: Receive Origin and RequestId - B->>C: Offer to Origin - C->>A: Offer to Client - A->>C: Answer to Origin - C->>B: Answer to Client + B->>C: Authenticate/Register + C->>C: Validate Signatures + C->>C: Join Clients to Room + C-->>B: Ok Response + B->>C: Emit Offer to Origin + C-->>A: Emit Offer to Client + A->>C: Emit Answer to Origin + C-->>B: Emit Answer to Client +``` + +## ICE Candidates + +The clients exchange ICE candidates to establish a connection. +The ICE candidates are used to determine the best path for the data to travel between the clients. +The clients exchange ICE candidates over the signaling server via WebSockets. + +```mermaid +sequenceDiagram + participant A as Answer Client + participant C as Liquid Service + participant B as Offer Client + A->>A: Gather ICE Candidates + A->>C: Send ICE Candidates + C->>B: Send ICE Candidates + B->>B: Gather ICE Candidates + B->>C: Send ICE Candidates + C->>A: Send ICE Candidates +``` + +### Candidate Discovery + +STUN and TURN servers are used as a fallback when a local connection cannot be established on the LAN. +The STUN server is used to discover the client's public IP address, +while the TURN server is used to relay data if a direct connection to public IP cannot be established. + +The following diagram shows the exchange of STUN/TURN candidates with a client. + +```mermaid +sequenceDiagram + participant A as Peer A + participant B as STUN Service + participant C as TURN Service + A->>A: Get Local Candidates + A->>B: Get STUN Candidates + B-->>A: Send STUN Candidates + A->>C: Get TURN Relay Candidates + C-->>A: Send TURN Relay Candidates ``` diff --git a/docs/src/content/docs/guides/Peer to Peer/offer.mdx b/docs/src/content/docs/guides/Peer to Peer/offer.mdx index 66fb91c..4aa819d 100644 --- a/docs/src/content/docs/guides/Peer to Peer/offer.mdx +++ b/docs/src/content/docs/guides/Peer to Peer/offer.mdx @@ -1,5 +1,5 @@ --- -title: "Offer Guide" +title: "Peer: Offer Guide" sidebar: order: 20 label: "Offer" @@ -11,7 +11,11 @@ import {Aside, CardGrid, LinkCard} from '@astrojs/starlight/components'; An **Offer** is a type of session that is initiated by a client and is used to create a P2P connection with an **Answer** client. -This is usually a wallet that wants to peer with a dApp client that can produce an Answer. +### Who is this for? + +- **Wallets/Credential Managers** that want to peer with dApps clients. + +### Additional Links You can find more information in the Server guide The responsibilities of the service are limited to authentication and brokering connections between peers. -Authentication is handled using [Passkeys](../concepts#passkeys) with a custom `Liquid Extension` +Authentication is handled using [Passkeys](../concepts#-passkeys) with a custom `Liquid Extension` which attests an additional KeyPair (ie Algorand Account). -Once a client is authenticated, the service can be used to broker a [Peer to Peer](../concepts#peer-to-peer) connection. +Once a client is authenticated, the service can be used to broker a [Peer to Peer](../concepts#-peer-to-peer) connection. #### Who should use this service? @@ -29,9 +29,9 @@ Not only is this generally more secure and decentralized, it also reduces the lo -### Clients +### ๐Ÿ’ป Clients - + The **Liquid Auth Clients** are libraries that provide a simple way to interact with the service and other peers. Clients use the service to authenticate and broker a connection between peers. diff --git a/docs/src/content/docs/guides/concepts.md b/docs/src/content/docs/guides/concepts.md deleted file mode 100644 index 07a7523..0000000 --- a/docs/src/content/docs/guides/concepts.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Key Concepts -sidebar: - order: 1 - badge: - text: "WIP" - variant: caution ---- - -There are a few key concepts that are important to understand when working with Liquid Auth. -The main concepts are `Linking`, `Passkeys`, and `Peer-to-Peer`. -See the full details in the [Architecture Reference](../../architecture). - -## Linking - -A link will authorize a remote client to access the service. This is done by generating a `RequestId` by the remote device and waiting -for a device to attest a Passkey. We recommend displaying a QR code with a [liquid deep-link]() to the user to scan with their device. - -See how to implement Linking in the [Android]() and [Browser]() client documentation - -## Passkeys - -Passkeys are also known as FIDO2/WebAuthn [PublicKeyCredential](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential). -This KeyPair is used to register or authenticate a user and is generated by an authenticator device. -Passkeys/Authenticators must also support the `Liquid Extension` which is used to attest a `KeyPair` not controlled by the authenticator. - -## Peer-to-Peer - -[WebRTC](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API) is used for establishing a peer-to-peer connection between two clients. - -#### Offer - -Session Description Protocol (SDP) message sent from the client to the server. The offer contains information about the client's media capabilities and information about the datachannel. - -#### Answer - -Session Description Protocol (SDP) message sent back to a client who created the Offer. -An Answer client can then use this information to generate an answer, which is sent back to the offer client. - -#### Candidate - -ICE Candidate is a network address that can be used to communicate with the peer. diff --git a/docs/src/content/docs/guides/concepts.mdx b/docs/src/content/docs/guides/concepts.mdx new file mode 100644 index 0000000..f4b3078 --- /dev/null +++ b/docs/src/content/docs/guides/concepts.mdx @@ -0,0 +1,85 @@ +--- +title: Key Concepts +sidebar: + order: 1 +--- + +import {LinkCard, CardGrid} from '@astrojs/starlight/components'; + +There are a few key concepts that are important to understand when working with Liquid Auth. +The main concepts are `Linking`, `Passkeys`, and `Peer-to-Peer`. +See the full details in the [Architecture Reference](../../architecture). + +## ๐Ÿ”— Linking + +A link will authorize a remote client to access the service. +This is done by generating a `RequestId` and waiting for a device to attest a Passkey. + + + +## ๐Ÿ”’ Passkeys + +Passkeys are also known as FIDO2/WebAuthn [PublicKeyCredential](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential). +This KeyPair is used to register or authenticate a user and is generated by an authenticator device. + + + + + + + +### FIDO2 Extension + +Authenticators must also support the `Liquid Extension` which is used to attest a `KeyPair` not controlled by the authenticator. + +This extension attaches an additional signature to the credential to associate the `KeyPair` with the `Passkey`. +The extension also includes an optional `requestId` which is used to link the `KeyPair` to a specific session. +When two clients are linked, they can establish a peer-to-peer connection. + + + + +## ๐Ÿ”„ Peer-to-Peer + +[WebRTC](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API) is used for establishing a peer-to-peer connection between two clients. + +#### Offer + +Session Description Protocol (SDP) message sent from the client to the server. The offer contains information about the client's media capabilities and information about the datachannel. + + + +#### Answer + +Session Description Protocol (SDP) message sent back to a client who created the Offer. +An Answer client can then use this information to generate an answer, which is sent back to the offer client. + + + +#### Candidate + +ICE Candidate is a network address that can be used to communicate with the peer. diff --git a/docs/src/content/docs/guides/getting-started.mdx b/docs/src/content/docs/guides/getting-started.mdx index 9b24322..9b795f1 100644 --- a/docs/src/content/docs/guides/getting-started.mdx +++ b/docs/src/content/docs/guides/getting-started.mdx @@ -4,12 +4,24 @@ sidebar: order: 0 pagefind: false --- -import { LinkCard, CardGrid, Aside } from '@astrojs/starlight/components'; +import { LinkCard, CardGrid } from '@astrojs/starlight/components'; Liquid Auth is composed of two main components, the **Liquid Auth Server** and the **Liquid Auth Clients**. They are used together to provide authentication and signaling services for decentralized applications (dApps). -## Quick Links + +## ๐Ÿš€ Use Case + +The service should be provided by the **dApp** and the **Wallets** should act as clients. +The **Wallet** will receive the `Origin` and `RequestId` from the `dApp` and will use it +to authenticate with the service provided as the `Origin`. + +#### ๐Ÿ“ Requirements + +- **Wallets** should implement the **Offer** `SignalClient` and `WebAuthn Extension` to establish a peer-to-peer connection with the **dApp**. +- **dApps** should implement the **Answer** `SignalClient` and host the Service. + +## ๐Ÿ”— Quick Links + We recommend following the Peer-to-Peer guides to understand how to establish a connection between clients + + +A link will authorize a remote client to access the service. +This is done by generating a `requestId` and waiting for a device to attest a [Passkey](/guides/concepts/#-passkeys). + +A link event can only be acknowledged by the service when the remote client has successfully authenticated. +The linking processs is backed by a [Deep Link](#deep-link) and a [QR Code](#qr-code). +The `SignalClient` is responsible for generating the deep link and presenting to another client. + +The remote client will handle the `Deep Link` by submitting a `Passkey` with the [Liquid Extension](/guides/passkey/extension) to the origin service. +Once the service has validated the linking request, the client will be able to communicate with the service and establish a peer-to-peer connection. + + +### Who is this for? + +- **dApps/Wallets** that want to integrate deeply into `Liquid Auth`. + + +## Deep Link + +Liquid uses a custom deep link to handle linking between devices. + +The format is as follows: + +``` +liquid:///?requestId= +``` + +This link will be used to generate a QR code for the user to scan with their device. + +#### Origin + +The origin is the server that will handle the linking request. + + +#### Request ID + +The request ID is a UUID generated by a client to identify the linking request. + +## QR Code + +We recommend displaying the deep link as a QR code for the user to scan with their device. +Try it out by downloading the [demo Android](https://github.com/algorandfoundation/liquid-auth-android/releases) application and scanning the QRCode below. + +
Loading
+ + +### Diagram + +This diagram illustrates the linking process between a website and a wallet. + +```mermaid +sequenceDiagram + participant Website as Answer Client + participant Server + participant Wallet as Offer Client + Website->>Server: Subscribe to 'wss:link' + Website-->>Website: Display QR Connect Request ID + Wallet->>Website: Scan QR Code + Server-->>Wallet: Get Challenge/Options + Wallet->>Server: Register/Authenticate + Server-->>Server: Validate Signatures + Server-->>Website: HTTPOnly Session + Server->>Wallet: Ok Response + HTTPOnly Session + Server->>Website: Emit to `wss:link` client +``` diff --git a/docs/src/content/docs/guides/qr-code.md b/docs/src/content/docs/guides/qr-code.md deleted file mode 100644 index f4f1c57..0000000 --- a/docs/src/content/docs/guides/qr-code.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: "QR Code Guide" -sidebar: - label: "QR Code" - badge: - text: "TODO" - variant: danger -next: false ---- - diff --git a/docs/src/pages/demo.astro b/docs/src/pages/demo.astro new file mode 100644 index 0000000..8faadcf --- /dev/null +++ b/docs/src/pages/demo.astro @@ -0,0 +1,7 @@ +--- +import {QrCodeDemo} from '../components/QrCodeDemo'; +export const partial = true; +--- + + + diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 69b4fc1..b984f06 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -3,6 +3,7 @@ "exclude": ["./clients/**/*"], "compilerOptions": { "jsx": "react-jsx", - "jsxImportSource": "react" + "jsxImportSource": "react", + "allowSyntheticDefaultImports": true } }