Add GPG key
This commit is contained in:
parent
37fba2f602
commit
790ba15725
9 changed files with 361 additions and 116 deletions
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
import { readFile } from "node:fs/promises";
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import { IconEnvelope, IconBriefcase, IconLink, IconCommentDots, IconSSH } from "../components/icons";
|
||||
import { IconEnvelope, IconBriefcase, IconKey, IconLink, IconCommentDots, IconSSH } from "../components/icons";
|
||||
import {
|
||||
IconBluesky,
|
||||
IconCodeberg,
|
||||
|
|
@ -33,6 +34,9 @@ import {
|
|||
IconX,
|
||||
IconYouTube,
|
||||
} from "../components/icons/brands";
|
||||
|
||||
const gpgKey = await readFile("./public/gpg.pub", { encoding: "utf-8" });
|
||||
const sshKey = await readFile("./public/ssh.pub", { encoding: "utf-8" });
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
|
|
@ -301,6 +305,23 @@ import {
|
|||
<p class="sr-only select-none">badmanners.vore@gmail.com</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
id="gpg"
|
||||
class="u-key text-link group block w-full py-2 transition-colors motion-reduce:transition-none"
|
||||
href="/gpg.pub"
|
||||
aria-label="GPG public key"
|
||||
data-clipboard={gpgKey}
|
||||
data-noun="GPG key"
|
||||
>
|
||||
<IconKey
|
||||
height="1.75rem"
|
||||
width="1.75rem"
|
||||
class="inline transition-transform group-hover:scale-150 group-focus:scale-150 motion-reduce:transition-none motion-reduce:group-hover:scale-100 motion-reduce:group-focus:scale-100"
|
||||
/>
|
||||
<p class="sr-only select-none">GPG public key</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
id="inkbunny"
|
||||
|
|
@ -378,7 +399,9 @@ import {
|
|||
width="1.75rem"
|
||||
class="inline transition-transform group-hover:scale-150 group-focus:scale-150 motion-reduce:transition-none motion-reduce:group-hover:scale-100 motion-reduce:group-focus:scale-100"
|
||||
/>
|
||||
<p class="sr-only select-none"><span class="p-uid">aspe:keyoxide.org:UWYBVFCBFXTVUF2U6FS6AYJHLU</span> on Keyoxide</p>
|
||||
<p class="sr-only select-none">
|
||||
<span class="p-uid">aspe:keyoxide.org:UWYBVFCBFXTVUF2U6FS6AYJHLU</span> on Keyoxide
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
|
@ -499,7 +522,7 @@ import {
|
|||
class="u-key text-link group block w-full py-2 transition-colors motion-reduce:transition-none"
|
||||
href="/ssh.pub"
|
||||
aria-label="SSH public key"
|
||||
data-clipboard="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ3QAZd3E95gxef2kiXppWa/xhcwBtnKMZJaW6s4d7Tm Bad Manners <me@badmanners.xyz>"
|
||||
data-clipboard={sshKey}
|
||||
data-noun="SSH key"
|
||||
>
|
||||
<IconSSH
|
||||
|
|
|
|||
|
|
@ -3,71 +3,88 @@ import { readFile } from "node:fs/promises";
|
|||
import { parse } from "toml";
|
||||
|
||||
/**
|
||||
* Verify attributions and copyright according to the [Creative Commons recommended practices](https://wiki.creativecommons.org/wiki/Recommended_practices_for_attribution)
|
||||
* Makes sure the copyright follows the TASL format. T = title (or description), A = author, S = source, L = license.
|
||||
* @param copyright
|
||||
*/
|
||||
function validateTASL(copyright: any) {
|
||||
const title = copyright.title ?? copyright.description;
|
||||
if (typeof title !== "string" || !title) {
|
||||
throw new Error(`Missing "title" and/or "description" for attribution (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
// Author must be a valid string or object or list
|
||||
const authors = [copyright.author].flat();
|
||||
if (authors.length === 0) {
|
||||
throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
authors.forEach((author) => {
|
||||
if (!author) {
|
||||
throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
if (typeof author !== "object" && typeof author !== "string") {
|
||||
throw new Error(
|
||||
`Invalid "${typeof author}" type for "author"${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
if (typeof author === "object" && !(author.name || author.url)) {
|
||||
throw new Error(
|
||||
`Missing both name and URL for "author" ${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
});
|
||||
// Source must be a valid string or list of strings
|
||||
const sources = [copyright.source].flat();
|
||||
if (sources.length === 0) {
|
||||
throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
sources.forEach((source) => {
|
||||
if (!source) {
|
||||
throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
if (typeof source !== "object" && typeof source !== "string") {
|
||||
throw new Error(
|
||||
`Invalid "${typeof source}" type for "source"${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
if (typeof source === "object" && !source.url) {
|
||||
throw new Error(
|
||||
`Missing URL for "source" ${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
});
|
||||
// License must be a valid string or object or list
|
||||
const licenses = [copyright.license].flat();
|
||||
if (licenses.length === 0) {
|
||||
throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
licenses.forEach((license) => {
|
||||
if (!license) {
|
||||
throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
if (typeof license !== "object" && typeof license !== "string") {
|
||||
throw new Error(
|
||||
`Invalid "${typeof license}" type for "license"${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
if (typeof license === "object" && !(license.name || license.url)) {
|
||||
throw new Error(
|
||||
`Missing both name and URL for "license" ${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies attributions and copyright according to the [Creative Commons recommended practices](https://wiki.creativecommons.org/wiki/Recommended_practices_for_attribution)
|
||||
* @param copyright Unparsed TOML copyright information.
|
||||
*/
|
||||
function verifyAttributions(licenses: string) {
|
||||
const { copyright, attributions } = parse(licenses);
|
||||
// Make sure each copyright and attribution follows the TASL format.
|
||||
// - T: title (or description)
|
||||
// - A: author
|
||||
// - S: source
|
||||
// - L: license
|
||||
// - other fields that have custom validation: type, notes, items
|
||||
// Make sure each copyright and attribution follows the TASL format,
|
||||
// and that other fields (type, notes, items) pass their custom validation.
|
||||
[copyright, attributions].flat().forEach((value) => {
|
||||
// Title or description must be a valid string
|
||||
const title = value.title ?? value.description;
|
||||
if (typeof title !== "string" || !title) {
|
||||
throw new Error(`Missing "title" and/or "description" for attribution (${JSON.stringify(value)})`);
|
||||
}
|
||||
// Author must be a valid string or object or list
|
||||
const authors = [value.author].flat();
|
||||
if (authors.length === 0) {
|
||||
throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
authors.forEach((author) => {
|
||||
if (!author) {
|
||||
throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof author !== "object" && typeof author !== "string") {
|
||||
throw new Error(`Invalid "${typeof author}" type for "author"${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof author === "object" && !(author.name || author.url)) {
|
||||
throw new Error(`Missing both name and URL for "author" ${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
});
|
||||
// Source must be a valid string or list of strings
|
||||
const sources = [value.source].flat();
|
||||
if (sources.length === 0) {
|
||||
throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
sources.forEach((source) => {
|
||||
if (!source) {
|
||||
throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof source !== "object" && typeof source !== "string") {
|
||||
throw new Error(`Invalid "${typeof source}" type for "source"${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof source === "object" && !(source.url)) {
|
||||
throw new Error(`Missing URL for "source" ${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
});
|
||||
// License must be a valid string or object or list
|
||||
const licenses = [value.license].flat();
|
||||
if (licenses.length === 0) {
|
||||
throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
licenses.forEach((license) => {
|
||||
if (!license) {
|
||||
throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof license !== "object" && typeof license !== "string") {
|
||||
throw new Error(`Invalid "${typeof license}" type for "license"${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof license === "object" && !(license.name || license.url)) {
|
||||
throw new Error(`Missing both name and URL for "license" ${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
});
|
||||
// Validate TASL
|
||||
validateTASL(value);
|
||||
const title = copyright.title ?? copyright.description;
|
||||
// Validate extra optional fields
|
||||
// 1. Type must be a valid string
|
||||
if (typeof value.type !== "string") {
|
||||
|
|
@ -80,13 +97,17 @@ function verifyAttributions(licenses: string) {
|
|||
if ("items" in value) {
|
||||
const items = value.items;
|
||||
if (!Array.isArray(items)) {
|
||||
throw new Error(`Invalid non-array "items" ${JSON.stringify(items)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
throw new Error(
|
||||
`Invalid non-array "items" ${JSON.stringify(items)} for attribution "${title}" (${JSON.stringify(value)})`,
|
||||
);
|
||||
}
|
||||
items.forEach((item) => {
|
||||
if (!item) {
|
||||
throw new Error(`Invalid item ${JSON.stringify} in "items" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
throw new Error(
|
||||
`Invalid item ${JSON.stringify} in "items" for attribution "${title}" (${JSON.stringify(value)})`,
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
// 3. Type must be a valid string
|
||||
if ("notes" in value) {
|
||||
|
|
@ -106,18 +127,33 @@ function verifyAttributions(licenses: string) {
|
|||
if ("additional" in copyright) {
|
||||
const additionals = copyright.additional;
|
||||
if (!Array.isArray(additionals)) {
|
||||
throw new Error(`Invalid non-array "additional" ${JSON.stringify(additionals)} for copyright (${JSON.stringify(copyright)})`);
|
||||
throw new Error(
|
||||
`Invalid non-array "additional" ${JSON.stringify(additionals)} for copyright (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
additionals.forEach((additional) => {
|
||||
if (typeof additional.notes !== "string" || !additional.notes) {
|
||||
throw new Error(`Invalid "notes" for additional copyright (${JSON.stringify(additional)})`);
|
||||
}
|
||||
})
|
||||
if (typeof additional.type !== "string" || !additional.type) {
|
||||
throw new Error(`Invalid "type" for additional copyright (${JSON.stringify(additional)})`);
|
||||
}
|
||||
// Check TASL + date if title or description is present
|
||||
if (additional.title || additional.description) {
|
||||
validateTASL(additional);
|
||||
if (typeof additional.date !== "string") {
|
||||
throw new Error(`Invalid "date" for additional (${JSON.stringify(additional)})`);
|
||||
}
|
||||
if (!additional.date) {
|
||||
throw new Error(`Missing "date" for additional (${JSON.stringify(additional)})`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const GET: APIRoute = async () => {
|
||||
const licenses = await readFile("./src/data/licenses.toml", { encoding: "utf-8" });
|
||||
verifyAttributions(licenses);
|
||||
return new Response(licenses, { headers: { "Content-Type": "application/toml; charset=utf-8" } })
|
||||
return new Response(licenses, { headers: { "Content-Type": "application/toml; charset=utf-8" } });
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue