astro-altcha 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # `astro-altcha`
2
+
3
+ This is an [Astro integration](https://docs.astro.build/en/guides/integrations-guide/) that sets up altcha for Astro.
4
+
5
+ ## Usage
6
+
7
+ ### Prerequisites
8
+
9
+ TODO:
10
+
11
+ ### Installation
12
+
13
+ Install the integration **automatically** using the Astro CLI:
14
+
15
+ ```bash
16
+ pnpm astro add astro-altcha
17
+ ```
18
+
19
+ ```bash
20
+ npx astro add astro-altcha
21
+ ```
22
+
23
+ ```bash
24
+ yarn astro add astro-altcha
25
+ ```
26
+
27
+ Or install it **manually**:
28
+
29
+ 1. Install the required dependencies
30
+
31
+ ```bash
32
+ pnpm add astro-altcha
33
+ ```
34
+
35
+ ```bash
36
+ npm install astro-altcha
37
+ ```
38
+
39
+ ```bash
40
+ yarn add astro-altcha
41
+ ```
42
+
43
+ 2. Add the integration to your astro config
44
+
45
+ ```diff
46
+ +import altcha from "astro-altcha";
47
+
48
+ export default defineConfig({
49
+ integrations: [
50
+ + altcha(),
51
+ ],
52
+ });
53
+ ```
54
+
55
+ ### Configuration
56
+
57
+ TODO:configuration
58
+
59
+ ## Contributing
60
+
61
+ This package is structured as a monorepo:
62
+
63
+ - `playground` contains code for testing the package
64
+ - `package` contains the actual package
65
+
66
+ Install dependencies using pnpm:
67
+
68
+ ```bash
69
+ pnpm i --frozen-lockfile
70
+ ```
71
+
72
+ Start the playground and package watcher:
73
+
74
+ ```bash
75
+ pnpm dev
76
+ ```
77
+
78
+ You can now edit files in `package`. Please note that making changes to those files may require restarting the playground dev server.
79
+
80
+ ## Licensing
81
+
82
+ [MIT Licensed](https://github.com/TODO:/blob/main/LICENSE). Made with ❤️ by [TODO:](https://github.com/TODO:).
83
+
84
+ ## Acknowledgements
85
+
86
+ - Created using [astro-integration-template](https://github.com/florian-lefebvre/astro-integration-template).
87
+ - TODO:
@@ -0,0 +1,6 @@
1
+ import { Algorithm, ResolvedAstroAltchaConfig } from "../types/index.js";
2
+ import { DeriveKeyFunction } from "altcha/lib";
3
+ import { APIRoute } from "astro";
4
+ //#region src/api/altcha-challenge.d.ts
5
+ export declare function createChallengeHandler(hmacSecret: string, config: ResolvedAstroAltchaConfig<Algorithm>, deriveKeyFunc: DeriveKeyFunction): APIRoute;
6
+ //#endregion
@@ -0,0 +1,20 @@
1
+ import { createChallenge } from "altcha-lib";
2
+ import { deriveHmacKeySecret } from "altcha-lib/frameworks/shared";
3
+
4
+ //#region src/api/altcha-challenge.ts
5
+ function createChallengeHandler(hmacSecret, config, deriveKeyFunc) {
6
+ return (async (context) => {
7
+ if (context.cache.enabled) context.cache.set(false);
8
+ const challenge = await createChallenge({
9
+ ...config.challengeOptions,
10
+ expiresAt: Date.now() * 1e3 + config.challengeExpiresAfterMinutes * 60,
11
+ deriveKey: deriveKeyFunc,
12
+ hmacSignatureSecret: hmacSecret,
13
+ hmacKeySignatureSecret: await deriveHmacKeySecret(hmacSecret)
14
+ });
15
+ return Response.json(challenge);
16
+ });
17
+ }
18
+
19
+ //#endregion
20
+ export { createChallengeHandler };
@@ -0,0 +1,26 @@
1
+ import { DeriveKeyFunction } from "altcha-lib";
2
+ import { z } from "astro/zod";
3
+ import { MiddlewareHandler } from "astro";
4
+ //#region src/api/altcha-middleware.d.ts
5
+ declare const altchaPayloadSchema: z.ZodObject<{
6
+ challenge: z.ZodObject<{
7
+ parameters: z.ZodObject<{
8
+ algorithm: z.ZodString;
9
+ cost: z.ZodNumber;
10
+ keyLength: z.ZodNumber;
11
+ keyPrefix: z.ZodString;
12
+ keySignature: z.ZodString;
13
+ nonce: z.ZodString;
14
+ salt: z.ZodString;
15
+ }, z.core.$strip>;
16
+ signature: z.ZodString;
17
+ }, z.core.$strip>;
18
+ solution: z.ZodObject<{
19
+ counter: z.ZodNumber;
20
+ derivedKey: z.ZodString;
21
+ time: z.ZodNumber;
22
+ }, z.core.$strip>;
23
+ }, z.core.$strip>;
24
+ export type AltchaPayload = z.infer<typeof altchaPayloadSchema>;
25
+ export declare function createAltchaMiddlewareHandler(hmacSecret: string, deriveKeyFunc: DeriveKeyFunction): MiddlewareHandler;
26
+ //#endregion
@@ -0,0 +1,60 @@
1
+ import { CappedMap } from "altcha-lib";
2
+ import { deriveHmacKeySecret, verify } from "altcha-lib/frameworks/shared";
3
+ import { z } from "astro/zod";
4
+ import { base64ToString } from "uint8array-extras";
5
+
6
+ //#region src/api/altcha-middleware.ts
7
+ const altchaPayloadSchema = z.object({
8
+ challenge: z.object({
9
+ parameters: z.object({
10
+ algorithm: z.string(),
11
+ cost: z.number(),
12
+ keyLength: z.number(),
13
+ keyPrefix: z.string(),
14
+ keySignature: z.string(),
15
+ nonce: z.string(),
16
+ salt: z.string()
17
+ }),
18
+ signature: z.string()
19
+ }),
20
+ solution: z.object({
21
+ counter: z.number(),
22
+ derivedKey: z.string(),
23
+ time: z.number()
24
+ })
25
+ });
26
+ const store = new CappedMap({ maxSize: 499 });
27
+ const keyRegexp = /.*astro-altcha.*/;
28
+ function createAltchaMiddlewareHandler(hmacSecret, deriveKeyFunc) {
29
+ return async (context, next) => {
30
+ if (context.request.method !== "POST") return next();
31
+ if (!context.request.headers.get("content-type")?.includes("multipart/form-data") && !context.request.headers.get("content-type")?.includes("application/x-www-form-urlencoded")) return next();
32
+ const body = Object.fromEntries((await context.request.clone().formData()).entries());
33
+ const key = Object.keys(body).find((key) => keyRegexp.test(key));
34
+ if (!key) return next();
35
+ const altchaBase64 = body?.[key]?.toString();
36
+ if (!altchaBase64) return next();
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(base64ToString(altchaBase64));
40
+ } catch (e) {
41
+ context.logger?.error(`Error parsing altcha payload: ${e}`);
42
+ return next();
43
+ }
44
+ const { success, data: altchaPayload, error } = altchaPayloadSchema.safeParse(parsed);
45
+ if (!success) {
46
+ context.logger?.error(`Error parsing altcha payload: ${z.prettifyError(error)}`);
47
+ return next();
48
+ }
49
+ const result = await verify(altchaPayload, deriveKeyFunc, hmacSecret, await deriveHmacKeySecret(hmacSecret), store);
50
+ if (result.error) {
51
+ context.logger?.error(`Error verifying altcha payload: ${result.error}`);
52
+ return next();
53
+ }
54
+ context.locals.altcha = result.verification;
55
+ return next();
56
+ };
57
+ }
58
+
59
+ //#endregion
60
+ export { createAltchaMiddlewareHandler };
@@ -0,0 +1,3 @@
1
+ //#region src/api/challenge.d.ts
2
+ export declare const GET: import("astro").APIRoute;
3
+ //#endregion
@@ -0,0 +1,12 @@
1
+ import { createChallengeHandler } from "./altcha-challenge.js";
2
+ import { pickDeriveKeyFunc } from "./pickDeriveKey.js";
3
+ import { getSecret } from "astro:env/server";
4
+ import { config } from "astro-altcha:config";
5
+
6
+ //#region src/api/challenge.ts
7
+ const ALTCHA_HMAC_SIGNATURE = getSecret("ALTCHA_HMAC_SIGNATURE");
8
+ if (!ALTCHA_HMAC_SIGNATURE) throw new Error("ALTCHA_HMAC_SIGNATURE not set");
9
+ const GET = createChallengeHandler(ALTCHA_HMAC_SIGNATURE, config, await pickDeriveKeyFunc(config.challengeOptions.algorithm));
10
+
11
+ //#endregion
12
+ export { GET };
@@ -0,0 +1,3 @@
1
+ //#region src/api/middleware.d.ts
2
+ export declare const onRequest: any;
3
+ //#endregion
@@ -0,0 +1,14 @@
1
+ import { createAltchaMiddlewareHandler } from "./altcha-middleware.js";
2
+ import { pickDeriveKeyFunc } from "./pickDeriveKey.js";
3
+ import { getSecret } from "astro:env/server";
4
+ import { config } from "astro-altcha:config";
5
+ import { defineMiddleware } from "astro:middleware";
6
+
7
+ //#region src/api/middleware.ts
8
+ const ALTCHA_HMAC_SIGNATURE = getSecret("ALTCHA_HMAC_SIGNATURE");
9
+ if (!ALTCHA_HMAC_SIGNATURE) throw new Error("ALTCHA_HMAC_SIGNATURE not set");
10
+ const altchaMiddleware = defineMiddleware(createAltchaMiddlewareHandler(ALTCHA_HMAC_SIGNATURE, await pickDeriveKeyFunc(config.challengeOptions.algorithm)));
11
+ const onRequest = altchaMiddleware;
12
+
13
+ //#endregion
14
+ export { onRequest };
@@ -0,0 +1,4 @@
1
+ import { Algorithm } from "../types/index.js";
2
+ //#region src/api/pickDeriveKey.d.ts
3
+ export declare function pickDeriveKeyFunc(algorithm: Algorithm): Promise<typeof import("altcha-lib/algorithms/pbkdf2").deriveKey>;
4
+ //#endregion
@@ -0,0 +1,16 @@
1
+ //#region src/api/pickDeriveKey.ts
2
+ async function pickDeriveKeyFunc(algorithm) {
3
+ switch (algorithm) {
4
+ case "PBKDF2/SHA-256":
5
+ case "PBKDF2/SHA-384":
6
+ case "PBKDF2/SHA-512": return await import("altcha-lib/algorithms/pbkdf2").then((m) => m.deriveKey);
7
+ case "Argon2id": return await import("altcha-lib/algorithms/argon2id").then((m) => m.deriveKey);
8
+ case "Scrypt": return await import("altcha-lib/algorithms/scrypt").then((m) => m.deriveKey);
9
+ case "SHA-256":
10
+ case "SHA-384":
11
+ case "SHA-512": return await import("altcha-lib/algorithms/sha").then((m) => m.deriveKey);
12
+ }
13
+ }
14
+
15
+ //#endregion
16
+ export { pickDeriveKeyFunc };
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,21 @@
1
+ //#region src/components/altcha-form-script.ts
2
+ const altchaWidgets = document.querySelectorAll("altcha-widget");
3
+ for (const widget of altchaWidgets) {
4
+ if (widget.getAttribute("data-astro-altcha") !== "astro-altcha-form-widget") continue;
5
+ const form = widget.closest("form");
6
+ if (!form) throw new Error("Widget must be placed inside a form element");
7
+ const config = form.querySelector("script");
8
+ if (config?.getAttribute("data-astro-altcha") !== "config") continue;
9
+ const { hydrationStrategy, debug } = JSON.parse(config.innerText);
10
+ if (hydrationStrategy === "onload") await loadAltcha(form, widget, debug);
11
+ else form.addEventListener("focusin", () => loadAltcha(form, widget, debug), { once: true });
12
+ }
13
+ async function loadAltcha(form, widget, debug) {
14
+ await import("altcha");
15
+ widget.configure({ debug });
16
+ const configureEvent = new CustomEvent("astro-altcha:init", { detail: { widget } });
17
+ form.dispatchEvent(configureEvent);
18
+ }
19
+
20
+ //#endregion
21
+ export { };
@@ -0,0 +1,3 @@
1
+ import AltchaForm from "./AltchaForm.astro";
2
+ import AltchaObfuscationWidget from "./AltchaObfuscationWidget.astro";
3
+ export { AltchaForm, AltchaObfuscationWidget };
@@ -0,0 +1,4 @@
1
+ import AltchaForm from "./AltchaForm.astro";
2
+ import AltchaObfuscationWidget from "./AltchaObfuscationWidget.astro";
3
+
4
+ export { AltchaForm, AltchaObfuscationWidget };
@@ -0,0 +1,2 @@
1
+ import { integration } from "./integration.js";
2
+ export { integration as default };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import { integration } from "./integration.js";
2
+
3
+ //#region src/index.ts
4
+ var src_default = integration;
5
+
6
+ //#endregion
7
+ export { src_default as default };
@@ -0,0 +1,39 @@
1
+ import { Algorithm, AltchaWidgetName, AstroAltchaConfig } from "./types/index.js";
2
+ import { AstroIntegration } from "astro";
3
+ //#region src/integration.d.ts
4
+ export declare function integration(options?: AstroAltchaConfig): AstroIntegration;
5
+ export declare function mergeDefaultConfig(options?: AstroAltchaConfig): {
6
+ readonly challengeOptions: {
7
+ readonly algorithm: "PBKDF2/SHA-256";
8
+ readonly cost: number;
9
+ readonly counter: number;
10
+ } | {
11
+ readonly algorithm: Exclude<Algorithm, "Argon2id" | "Scrypt">;
12
+ readonly keyLength?: number;
13
+ readonly keyPrefix?: string;
14
+ counter: number;
15
+ readonly counterMode?: "uint32" | "string";
16
+ readonly data?: Record<string, string | number | boolean | null>;
17
+ readonly hmacAlgorithm?: import("altcha-lib").HmacAlgorithm;
18
+ readonly keyPrefixLength?: number;
19
+ readonly memoryCost?: number;
20
+ readonly parallelism?: number;
21
+ cost: number;
22
+ } | {
23
+ readonly algorithm: "Argon2id" | "Scrypt";
24
+ readonly keyLength?: number;
25
+ readonly keyPrefix?: string;
26
+ counter: number;
27
+ readonly counterMode?: "uint32" | "string";
28
+ readonly data?: Record<string, string | number | boolean | null>;
29
+ readonly hmacAlgorithm?: import("altcha-lib").HmacAlgorithm;
30
+ readonly keyPrefixLength?: number;
31
+ readonly memoryCost: number;
32
+ readonly parallelism?: number;
33
+ cost: number;
34
+ };
35
+ readonly defaultName: AltchaWidgetName;
36
+ readonly challengePath: string;
37
+ readonly challengeExpiresAfterMinutes: number;
38
+ };
39
+ //#endregion
@@ -0,0 +1,85 @@
1
+ import plugin from "./vite-plugin.js";
2
+ import { randomInt } from "altcha-lib";
3
+ import { envField } from "astro/config";
4
+
5
+ //#region src/integration.ts
6
+ function integration(options = {}) {
7
+ const config = mergeDefaultConfig(options);
8
+ return {
9
+ name: "astro-altcha",
10
+ hooks: {
11
+ "astro:config:setup": ({ injectRoute, updateConfig, addMiddleware }) => {
12
+ updateConfig({
13
+ vite: { plugins: [plugin(config)] },
14
+ env: { schema: { ALTCHA_HMAC_SIGNATURE: envField.string({
15
+ context: "server",
16
+ access: "secret"
17
+ }) } }
18
+ });
19
+ injectRoute({
20
+ pattern: config.challengePath,
21
+ entrypoint: new URL("./api/challenge.js", import.meta.url),
22
+ prerender: false
23
+ });
24
+ addMiddleware({
25
+ order: "pre",
26
+ entrypoint: new URL("./api/middleware.js", import.meta.url)
27
+ });
28
+ },
29
+ "astro:config:done": ({ injectTypes }) => {
30
+ injectTypes({
31
+ filename: "astro-altcha-events.d.ts",
32
+ content: `import type { WidgetAttributes, WidgetMethods } from "astro-altcha/types/altcha"
33
+
34
+ interface CustomEventMap {
35
+ "astro-altcha:init": {
36
+ widget: WidgetAttributes & WidgetMethods & HTMLElement;
37
+ }
38
+ }
39
+
40
+ declare global {
41
+ interface HTMLFormElement {
42
+ addEventListener<K extends keyof CustomEventMap>(
43
+ type: K,
44
+ listener: (this: EventTarget, ev: CustomEvent<CustomEventMap[K]>) => void,
45
+ ): void;
46
+
47
+ dispatchEvent<K extends keyof CustomEventMap>(
48
+ ev: CustomEvent<CustomEventMap[K]>,
49
+ ): boolean;
50
+ }
51
+ };
52
+
53
+ export {};
54
+ `
55
+ });
56
+ injectTypes({
57
+ filename: "astro-altcha-locals.d.ts",
58
+ content: `declare namespace App {
59
+ interface Locals {
60
+ altcha: import("astro-altcha/types/altcha").VerifySolutionResult | import("astro-altcha/types/altcha-lib").VerifyServerResult | null;
61
+ }
62
+ }
63
+ `
64
+ });
65
+ }
66
+ }
67
+ };
68
+ }
69
+ function mergeDefaultConfig(options = {}) {
70
+ return {
71
+ defaultName: "astro-altcha",
72
+ challengePath: "/api/altcha/challenge",
73
+ challengeExpiresAfterMinutes: 20,
74
+ ...options,
75
+ challengeOptions: {
76
+ algorithm: "PBKDF2/SHA-256",
77
+ cost: 5e3,
78
+ counter: randomInt(5e3, 1e4),
79
+ ...options.challengeOptions
80
+ }
81
+ };
82
+ }
83
+
84
+ //#endregion
85
+ export { integration, mergeDefaultConfig };
@@ -0,0 +1 @@
1
+ export * from "altcha-lib/types";
@@ -0,0 +1,3 @@
1
+ export * from "altcha-lib/types"
2
+
3
+ export { };
@@ -0,0 +1 @@
1
+ export * from "altcha/types";
@@ -0,0 +1,3 @@
1
+ export * from "altcha/types"
2
+
3
+ export { };
@@ -0,0 +1,65 @@
1
+ import { CreateChallengeOptions } from "altcha-lib/types";
2
+ import { WidgetAttributes } from "altcha/types";
3
+ //#region src/types/index.d.ts
4
+ export type AltchaWidgetName = `${string}astro-altcha${string}`;
5
+ export type Algorithm = `PBKDF2/${Digest}` | "Argon2id" | "Scrypt" | Digest;
6
+ type Digest = `SHA-${"256" | "384" | 512}`;
7
+ export type AstroAltchaConfig = {
8
+ /**
9
+ * Default altcha-widget name. Must contain the string `astro-altcha`.
10
+ *
11
+ * Default: `astro-altcha`
12
+ */
13
+ defaultName?: AltchaWidgetName;
14
+ /**
15
+ * Path of the challenge API endpoint.
16
+ *
17
+ * Default: `/api/altcha/challenge`
18
+ */
19
+ challengePath?: string;
20
+ /**
21
+ * Challenge options for [Challange creation](https://altcha.org/docs/integration/server/#challenge-generation)
22
+ *
23
+ * Default:
24
+ * ```
25
+ * algorithm: "PBKDF2/SHA-256"
26
+ * cost: 5_000
27
+ * counter: randomInt(5_000, 10_000)
28
+ * ```
29
+ */
30
+ challengeOptions?: WithOptional<AltchaChallengeOptions<Algorithm>, "cost">;
31
+ /**
32
+ * Challenge expiration in minutes
33
+ *
34
+ * Default: 20 min
35
+ */
36
+ challengeExpiresAfterMinutes?: number;
37
+ };
38
+ export type ResolvedAstroAltchaConfig<Algo extends Algorithm> = Required<AstroAltchaConfig> & {
39
+ challengeOptions: AltchaChallengeOptions<Algo>;
40
+ };
41
+ type WithOptional<T, K extends keyof T> = T extends unknown ? Omit<T, K> & Partial<Pick<T, K>> : never;
42
+ type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P]; };
43
+ type OmitCreateChallengeOptions = Omit<CreateChallengeOptions, "hmacSignatureSecret" | "hmacKeySignatureSecret" | "deriveKey" | "expiresAt" | "algorithm">;
44
+ export type AltchaChallengeOptions<Algo extends Algorithm> = Algo extends "Argon2id" | "Scrypt" ? WithRequired<OmitCreateChallengeOptions, "memoryCost"> & {
45
+ algorithm: "Argon2id" | "Scrypt";
46
+ } : OmitCreateChallengeOptions & {
47
+ algorithm: Exclude<Algorithm, "Argon2id" | "Scrypt">;
48
+ };
49
+ export type AstroAltchaHydrationStrategy = "onfocus" | "onload";
50
+ export type AltchaFormWidgetProps = {
51
+ /** When the altcha-widget should be hydrated.
52
+ *
53
+ * `onfocus` - when the form is first focused
54
+ *
55
+ * `onload` - when the page first loads
56
+ */
57
+ hydrationStrategy?: "onfocus" | "onload";
58
+ debug?: boolean;
59
+ } & Omit<WidgetAttributes, "name">;
60
+ export type AltchaFormProps = {
61
+ widget?: AltchaFormWidgetProps & {
62
+ name?: AltchaWidgetName;
63
+ };
64
+ } & astroHTML.JSX.FormHTMLAttributes;
65
+ //#endregion
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,6 @@
1
+ import { Algorithm, ResolvedAstroAltchaConfig } from "./types/index.js";
2
+ import { AstroConfig } from "astro";
3
+ //#region src/vite-plugin.d.ts
4
+ declare const plugin: (config: ResolvedAstroAltchaConfig<Algorithm>) => NonNullable<AstroConfig["vite"]["plugins"]>[number];
5
+ //#endregion
6
+ export { plugin as default };
@@ -0,0 +1,18 @@
1
+ //#region src/vite-plugin.ts
2
+ const virtualModuleId = "astro-altcha:config";
3
+ const resolvedVirtualModuleId = `\0${virtualModuleId}`;
4
+ const plugin = (config) => {
5
+ return {
6
+ name: "vite-plugin-altcha-config",
7
+ resolveId(id) {
8
+ if (id === virtualModuleId) return resolvedVirtualModuleId;
9
+ },
10
+ load(id) {
11
+ if (id === resolvedVirtualModuleId) return `export const config =
12
+ ${JSON.stringify(config)}`;
13
+ }
14
+ };
15
+ };
16
+
17
+ //#endregion
18
+ export { plugin as default };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "astro-altcha",
3
+ "version": "0.1.0",
4
+ "description": "Altcha integration for Astro",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./types": {
13
+ "types": "./dist/types.d.ts"
14
+ },
15
+ "./types/altcha": {
16
+ "types": "./dist/types/altcha.d.ts"
17
+ },
18
+ "./types/altcha-lib": {
19
+ "types": "./dist/types/altcha-lib.d.ts"
20
+ },
21
+ "./components": "./src/components/index.ts",
22
+ "./components/*": "./src/components/*"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src/components"
27
+ ],
28
+ "keywords": [
29
+ "astro-integration",
30
+ "astro-component",
31
+ "withastro",
32
+ "astro",
33
+ "altcha",
34
+ "astro-altcha"
35
+ ],
36
+ "author": "Lukas Trombach",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://codeberg.org/Trombach/astro-altcha"
40
+ },
41
+ "bugs": "https://codeberg.org/Trombach/astro-altcha/issues",
42
+ "homepage": "https://codeberg.org/Trombach/astro-altcha",
43
+ "license": "MIT",
44
+ "peerDependencies": {
45
+ "astro": "^6.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@astrojs/check": "0.9.10",
49
+ "tsdown": "0.23.0"
50
+ },
51
+ "dependencies": {
52
+ "altcha": "^3.2.3",
53
+ "altcha-lib": "^2.5.0",
54
+ "uint8array-extras": "^1.5.0"
55
+ },
56
+ "scripts": {
57
+ "dev": "tsdown --watch",
58
+ "build": "tsdown",
59
+ "ts:check": "pnpm astro check",
60
+ "test": "node --experimental-test-snapshots --experimental-test-module-mocks --test 'test/**/*.test.ts'",
61
+ "test:ci": "node --experimental-test-snapshots --experimental-test-module-mocks --experimental-test-coverage --test 'test/**/*.test.ts'",
62
+ "test:watch": "node --experimental-test-snapshots --experimental-test-module-mocks --watch --test 'test/**/*.test.ts'",
63
+ "test:update-snapshots": "node --test-update-snapshots --experimental-test-snapshots --experimental-test-module-mocks --test 'test/**/*.test.ts'"
64
+ }
65
+ }
@@ -0,0 +1,35 @@
1
+ ---
2
+ import { config } from "astro-altcha:config";
3
+ import type { AltchaFormProps } from "../types/index.js";
4
+
5
+ type Props = AltchaFormProps;
6
+
7
+ const {
8
+ widget: {
9
+ name = config.defaultName,
10
+ hydrationStrategy = "onload",
11
+ debug,
12
+ challenge = config.challengePath,
13
+ ...widgetProps
14
+ } = {},
15
+ ...formProps
16
+ } = Astro.props;
17
+ ---
18
+
19
+ <form {...formProps}>
20
+ <slot />
21
+ <altcha-widget
22
+ data-astro-altcha="astro-altcha-form-widget"
23
+ {name}
24
+ {challenge}
25
+ {...widgetProps}
26
+ ></altcha-widget>
27
+ <script
28
+ is:inline
29
+ data-astro-altcha="config"
30
+ type="application/json"
31
+ set:html={JSON.stringify({ hydrationStrategy, debug})}
32
+ />
33
+ </form>
34
+
35
+ <script src="./altcha-form-script.ts" />
@@ -0,0 +1,21 @@
1
+ ---
2
+ import type { WidgetAttributes } from "altcha/types";
3
+ import { obfuscate } from "altcha-lib/obfuscation";
4
+
5
+ type Props = {
6
+ data: string;
7
+ } & WidgetAttributes;
8
+
9
+ const { data, display = "floating" } = Astro.props;
10
+ const dataObfuscated = await obfuscate(data);
11
+ ---
12
+
13
+ <altcha-widget data-obfuscated={dataObfuscated} {display}>
14
+ <slot name="reveal">
15
+ <button type="button">Click to reveal</button>
16
+ </slot>
17
+ </altcha-widget>
18
+
19
+ <script>
20
+ import "altcha/plugins/obfuscation";
21
+ </script>
@@ -0,0 +1,47 @@
1
+ import type { WidgetAttributes, WidgetMethods } from "altcha/types";
2
+ import type { AstroAltchaHydrationStrategy } from "../types/index.js";
3
+
4
+ const altchaWidgets = document.querySelectorAll("altcha-widget");
5
+
6
+ for (const widget of altchaWidgets) {
7
+ if (widget.getAttribute("data-astro-altcha") !== "astro-altcha-form-widget") {
8
+ continue;
9
+ }
10
+
11
+ const form = widget.closest("form");
12
+ if (!form) {
13
+ throw new Error("Widget must be placed inside a form element");
14
+ }
15
+
16
+ const config = form.querySelector("script");
17
+ if (config?.getAttribute("data-astro-altcha") !== "config") continue;
18
+
19
+ const {
20
+ hydrationStrategy,
21
+ debug,
22
+ }: {
23
+ hydrationStrategy: AstroAltchaHydrationStrategy;
24
+ debug: boolean;
25
+ } = JSON.parse(config.innerText);
26
+ if (hydrationStrategy === "onload") {
27
+ await loadAltcha(form, widget, debug);
28
+ } else {
29
+ form.addEventListener("focusin", () => loadAltcha(form, widget, debug), {
30
+ once: true,
31
+ });
32
+ }
33
+ }
34
+
35
+ async function loadAltcha(
36
+ form: HTMLFormElement,
37
+ widget: WidgetAttributes & WidgetMethods & HTMLElement,
38
+ debug: boolean,
39
+ ) {
40
+ await import("altcha");
41
+ widget.configure({ debug });
42
+
43
+ const configureEvent = new CustomEvent("astro-altcha:init", {
44
+ detail: { widget },
45
+ });
46
+ form.dispatchEvent(configureEvent);
47
+ }
@@ -0,0 +1,2 @@
1
+ export { default as AltchaForm } from "./AltchaForm.astro";
2
+ export { default as AltchaObfuscationWidget } from "./AltchaObfuscationWidget.astro";