mirror of
https://github.com/Blah-IM/typescript-core.git
synced 2025-05-01 00:31:09 +00:00
feat: Add identity description file URL utilities
Add utilities to work with identity description file URLs, including validation of ID URL format and path construction. Also export these functions in the public API.
This commit is contained in:
parent
2c84f4dee7
commit
6e8d77b11a
5 changed files with 99 additions and 5 deletions
|
@ -1,11 +1,27 @@
|
|||
import {
|
||||
type BlahIdentityDescription,
|
||||
blahIdentityDescriptionSchema,
|
||||
getIdentityDescriptionFileURL,
|
||||
identityDescriptionFilePath,
|
||||
} from "./identityDescription.ts";
|
||||
import { assertTypeMatchesZodSchema } from "../test/utils.ts";
|
||||
import { expect } from "@std/expect";
|
||||
|
||||
Deno.test("type BlahIdentityDescription is accurate", () => {
|
||||
assertTypeMatchesZodSchema<BlahIdentityDescription>(
|
||||
blahIdentityDescriptionSchema,
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("getIdentityDescriptionFileURL", () => {
|
||||
expect(getIdentityDescriptionFileURL("https://lao.sb")).toBe(
|
||||
"https://lao.sb" + identityDescriptionFilePath,
|
||||
);
|
||||
|
||||
expect(getIdentityDescriptionFileURL("https://test.lao.sb")).toBe(
|
||||
"https://test.lao.sb" + identityDescriptionFilePath,
|
||||
);
|
||||
|
||||
expect(() => getIdentityDescriptionFileURL("https://trailing-slash.lao.sb/"))
|
||||
.toThrow();
|
||||
});
|
||||
|
|
|
@ -1,17 +1,42 @@
|
|||
import { z } from "zod";
|
||||
import { blahSignedPayloadSchemaOf } from "../crypto/signedPayload.ts";
|
||||
import { type BlahActKeyRecord, blahActKeyRecordSchema } from "./actKey.ts";
|
||||
import { type BlahProfile, blahProfileSchema } from "./profile.ts";
|
||||
import {
|
||||
type BlahProfile,
|
||||
blahProfileSchema,
|
||||
validateIDURLFormat,
|
||||
} from "./profile.ts";
|
||||
import type { BlahSignedPayload } from "../crypto/mod.ts";
|
||||
|
||||
/** Schema for Blah identity description. */
|
||||
export const blahIdentityDescriptionSchema = z.object({
|
||||
id_key: z.string(),
|
||||
act_keys: z.array(blahSignedPayloadSchemaOf(blahActKeyRecordSchema)).min(1),
|
||||
profile: blahSignedPayloadSchemaOf(blahProfileSchema),
|
||||
});
|
||||
|
||||
/** Type for Blah identity description. */
|
||||
export type BlahIdentityDescription = {
|
||||
id_key: string;
|
||||
act_keys: Array<BlahSignedPayload<BlahActKeyRecord>>;
|
||||
profile: BlahSignedPayload<BlahProfile>;
|
||||
};
|
||||
|
||||
/** Path to the identity description file under a given ID URL. */
|
||||
export const identityDescriptionFilePath = "/.well-known/blah/identity.json";
|
||||
|
||||
/**
|
||||
* Get the full URL to the identity description file for a given ID URL.
|
||||
*
|
||||
* @param idURL - The ID URL to get the identity description file URL for.
|
||||
* @returns The full URL to the identity description file.
|
||||
* @throws Error if the ID URL format is invalid.
|
||||
*/
|
||||
export function getIdentityDescriptionFileURL(
|
||||
idURL: string,
|
||||
): string {
|
||||
if (!validateIDURLFormat(idURL)) throw new Error("Invalid ID URL format");
|
||||
const url = new URL(idURL);
|
||||
url.pathname = identityDescriptionFilePath;
|
||||
return url.toString();
|
||||
}
|
||||
|
|
|
@ -5,17 +5,25 @@ export * from "./identity.ts";
|
|||
import {
|
||||
type BlahProfile,
|
||||
blahProfileSchema as internalBlahProfileSchema,
|
||||
validateIDURLFormat,
|
||||
} from "./profile.ts";
|
||||
const blahProfileSchema: z.ZodType<BlahProfile> = internalBlahProfileSchema;
|
||||
export { type BlahProfile, blahProfileSchema };
|
||||
export { type BlahProfile, blahProfileSchema, validateIDURLFormat };
|
||||
|
||||
import {
|
||||
type BlahIdentityDescription,
|
||||
blahIdentityDescriptionSchema as internalBlahIdentityDescriptionSchema,
|
||||
getIdentityDescriptionFileURL,
|
||||
identityDescriptionFilePath,
|
||||
} from "./identityDescription.ts";
|
||||
const blahIdentityDescriptionSchema: z.ZodType<BlahIdentityDescription> =
|
||||
internalBlahIdentityDescriptionSchema;
|
||||
export { type BlahIdentityDescription, blahIdentityDescriptionSchema };
|
||||
export {
|
||||
type BlahIdentityDescription,
|
||||
blahIdentityDescriptionSchema,
|
||||
getIdentityDescriptionFileURL,
|
||||
identityDescriptionFilePath,
|
||||
};
|
||||
|
||||
import {
|
||||
type BlahActKeyRecord,
|
||||
|
|
|
@ -1,6 +1,36 @@
|
|||
import { type BlahProfile, blahProfileSchema } from "./profile.ts";
|
||||
import {
|
||||
type BlahProfile,
|
||||
blahProfileSchema,
|
||||
validateIDURLFormat,
|
||||
} from "./profile.ts";
|
||||
import { assertTypeMatchesZodSchema } from "../test/utils.ts";
|
||||
import { expect } from "@std/expect";
|
||||
|
||||
Deno.test("type BlahProfile is accurate", () => {
|
||||
assertTypeMatchesZodSchema<BlahProfile>(blahProfileSchema);
|
||||
});
|
||||
|
||||
Deno.test("ID URL format - valid", () => {
|
||||
expect(validateIDURLFormat("https://lao.sb")).toBe(true);
|
||||
expect(validateIDURLFormat("https://test.lao.sb")).toBe(true);
|
||||
expect(validateIDURLFormat("https://🧧.lao.sb")).toBe(true);
|
||||
});
|
||||
|
||||
Deno.test("ID URL format - invalid", () => {
|
||||
// Must be valid URL
|
||||
expect(validateIDURLFormat("lao.sb")).toBe(false);
|
||||
// No trailing slash
|
||||
expect(validateIDURLFormat("https://lao.sb/")).toBe(false);
|
||||
// No search params
|
||||
expect(validateIDURLFormat("https://lao.sb?query=1")).toBe(false);
|
||||
// No fragment
|
||||
expect(validateIDURLFormat("https://lao.sb#fragment")).toBe(false);
|
||||
// No path
|
||||
expect(validateIDURLFormat("https://lao.sb/path")).toBe(false);
|
||||
// No username
|
||||
expect(validateIDURLFormat("https://user@lao.sb")).toBe(false);
|
||||
// No password
|
||||
expect(validateIDURLFormat("https://user:123@lao.sb")).toBe(false);
|
||||
// No non-HTTPS protocol
|
||||
expect(validateIDURLFormat("http://lao.sb")).toBe(false);
|
||||
});
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import z from "zod";
|
||||
|
||||
/** Schema for Blah user profile. */
|
||||
export const blahProfileSchema = z.object({
|
||||
typ: z.literal("profile"),
|
||||
preferred_chat_server_urls: z.array(z.string().url()),
|
||||
id_urls: z.array(z.string().url()).min(1),
|
||||
id_urls: z.array(z.string().refine(validateIDURLFormat)).min(1),
|
||||
name: z.string(),
|
||||
bio: z.string().optional(),
|
||||
});
|
||||
|
||||
/** Type for Blah user profile. */
|
||||
export type BlahProfile = {
|
||||
typ: "profile";
|
||||
preferred_chat_server_urls: string[];
|
||||
|
@ -15,3 +17,16 @@ export type BlahProfile = {
|
|||
name: string;
|
||||
bio?: string;
|
||||
};
|
||||
|
||||
/** Validate the format of an ID URL. */
|
||||
export function validateIDURLFormat(url: string): boolean {
|
||||
const idURL = URL.parse(url);
|
||||
return !!idURL &&
|
||||
idURL.protocol === "https:" &&
|
||||
idURL.pathname === "/" &&
|
||||
!url.endsWith("/") &&
|
||||
!idURL.search &&
|
||||
!idURL.hash &&
|
||||
!idURL.username &&
|
||||
!idURL.password;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue