about summary refs log tree commit diff
path: root/kittybox-rs/companion-lite/src/indieauth.ts
diff options
context:
space:
mode:
Diffstat (limited to 'kittybox-rs/companion-lite/src/indieauth.ts')
-rw-r--r--kittybox-rs/companion-lite/src/indieauth.ts113
1 files changed, 113 insertions, 0 deletions
diff --git a/kittybox-rs/companion-lite/src/indieauth.ts b/kittybox-rs/companion-lite/src/indieauth.ts
new file mode 100644
index 0000000..40facab
--- /dev/null
+++ b/kittybox-rs/companion-lite/src/indieauth.ts
@@ -0,0 +1,113 @@
+// @ts-ignore
+import { mf2 } from "https://esm.sh/microformats-parser@1.4.1?pin=v96"
+import { MF2 } from "./micropub_api.js"
+import base64 from "./base64.js"
+  /*
+  const { mf2 }: {
+    mf2: (html: string, options: {
+      baseUrl: string,
+      experimental?: { lang?: boolean, textContent?: boolean }
+    }) => {
+      items: MF2[],
+      rels: {[key: string]: string[]},
+      "rel-urls": {[key: string]: { rels: string[], text?: string }}
+    }
+  } =
+    // @ts-ignore
+    await import("https://esm.sh/microformats-parser@1.4.1?pin=v96");
+  */
+
+interface IndieauthMetadata {
+  authorization_endpoint: string,
+  token_endpoint: string,
+  issuer: string,
+  introspection_endpoint?: string,
+  introspection_endpoint_auth_methods_supported?: ("Bearer")[],
+  revocation_endpoint?: string,
+  revocation_endpoint_auth_methods_supported?: ["none"],
+  scopes_supported?: string[],
+  response_types_supported: ["code"],
+  grant_types_supported: ("authorization_code" | "refresh_token")[]
+  code_challenge_methods_supported: ("S256")[]
+  authorization_response_iss_parameter_supported: true,
+  userinfo_endpoint?: string
+}
+
+interface MF2ParsedData {
+  items: MF2[],
+  rels: {[key: string]: string[]},
+  "rel-urls": {[key: string]: { rels: string[], text?: string }}
+}
+
+export interface IndiewebEndpoints {
+  authorization_endpoint: URL,
+  token_endpoint: URL,
+  userinfo_endpoint: URL | null,
+  revocation_endpoint: URL | null,
+  micropub: URL,
+  
+}
+
+export function create_verifier() {
+  const array = new Uint8Array(64)
+  crypto.getRandomValues(array)
+
+  return array.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')
+}
+
+export async function create_challenge(verifier: string): Promise<string> {
+  return await crypto.subtle.digest('SHA-256', Uint8Array.from(verifier, c => c.charCodeAt(0)))
+    .then((buf) => base64.encode(new Uint8Array(buf)))
+    .then(s => {
+      return s
+        .replaceAll("+", "-")
+        .replaceAll("/", "_")
+        .replaceAll(/=$/g, "")
+    })
+}
+
+export async function discover_endpoints(me: URL): Promise<IndiewebEndpoints | null> {
+  const response = await fetch(me);
+  const data: MF2ParsedData = mf2(await response.text(), { baseUrl: me.toString() });
+  let endpoints: Partial<IndiewebEndpoints> = {};
+  if ("micropub" in data.rels) {
+    endpoints.micropub = new URL(data.rels.micropub[0])
+  } else {
+    return null
+  }
+  if ("indieauth_metadata" in data.rels) {
+    const metadata_response = await fetch(data.rels.indieauth_metadata[0], {
+      headers: {
+        "Accept": "application/json"
+      }
+    });
+
+    const metadata = await metadata_response.json() as IndieauthMetadata;
+    endpoints.authorization_endpoint = new URL(metadata.authorization_endpoint)
+    endpoints.token_endpoint = new URL(metadata.token_endpoint)
+    if (metadata.userinfo_endpoint != null) {
+      endpoints.userinfo_endpoint = new URL(metadata.userinfo_endpoint)
+    } else {
+      endpoints.userinfo_endpoint = null
+    }
+    if (metadata.revocation_endpoint != null) {
+      endpoints.revocation_endpoint = new URL(metadata.revocation_endpoint)
+    } else {
+      endpoints.revocation_endpoint = null
+    }
+
+    return endpoints as IndiewebEndpoints
+  } else if (
+    "authorization_endpoint" in data.rels
+      && "token_endpoint" in data.rels
+  ) {
+    endpoints.authorization_endpoint = new URL(data.rels.authorization_endpoint[0])
+    endpoints.token_endpoint = new URL(data.rels.token_endpoint[0])
+    endpoints.userinfo_endpoint = null
+    endpoints.revocation_endpoint = null
+
+    return endpoints as IndiewebEndpoints
+  } else {
+    return null
+  }
+}