Add support for cap, a pow captcha, and implement on contact form
This commit is contained in:
parent
22c2bc1492
commit
bdf4f9a051
6 changed files with 172 additions and 8 deletions
85
src/lib/cap.ts
Normal file
85
src/lib/cap.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import Cap, { type ChallengeData } from "@cap.js/server";
|
||||
import { db, eq, and, gt, lte, Cap_Challenges, Cap_Tokens } from "astro:db";
|
||||
|
||||
const cap = new Cap({
|
||||
storage: {
|
||||
challenges: {
|
||||
store: async (token: string, challengeData: ChallengeData) => {
|
||||
const expires = challengeData.expires;
|
||||
const data = challengeData.challenge;
|
||||
await db
|
||||
.insert(Cap_Challenges)
|
||||
.values({ token: token, data: data, expires: expires })
|
||||
.onConflictDoUpdate({
|
||||
target: Cap_Challenges.token,
|
||||
set: { data: data, expires: expires },
|
||||
});
|
||||
},
|
||||
read: async (token) => {
|
||||
const result = await db
|
||||
.select({
|
||||
challenge: Cap_Challenges.data,
|
||||
expires: Cap_Challenges.expires,
|
||||
})
|
||||
.from(Cap_Challenges)
|
||||
.where(
|
||||
and(
|
||||
eq(Cap_Challenges.token, token),
|
||||
gt(Cap_Challenges.expires, Date.now()),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
const data = result[0] as ChallengeData;
|
||||
|
||||
return result ? data : null;
|
||||
},
|
||||
|
||||
delete: async (token) => {
|
||||
await db.delete(Cap_Challenges).where(eq(Cap_Challenges.token, token));
|
||||
},
|
||||
|
||||
deleteExpired: async () => {
|
||||
await db
|
||||
.delete(Cap_Challenges)
|
||||
.where(lte(Cap_Challenges.expires, Date.now()));
|
||||
},
|
||||
},
|
||||
tokens: {
|
||||
store: async (tokenKey, expires) => {
|
||||
await db
|
||||
.insert(Cap_Tokens)
|
||||
.values({ key: tokenKey, expires: expires })
|
||||
.onConflictDoUpdate({
|
||||
target: Cap_Tokens.key,
|
||||
set: { expires: expires },
|
||||
});
|
||||
},
|
||||
|
||||
get: async (tokenKey) => {
|
||||
const result = await db
|
||||
.select({ expires: Cap_Tokens.expires })
|
||||
.from(Cap_Tokens)
|
||||
.where(
|
||||
and(
|
||||
eq(Cap_Tokens.key, tokenKey),
|
||||
gt(Cap_Tokens.expires, Date.now()),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
return result ? result[0].expires : null;
|
||||
},
|
||||
|
||||
delete: async (tokenKey) => {
|
||||
await db.delete(Cap_Tokens).where(eq(Cap_Tokens.key, tokenKey));
|
||||
},
|
||||
|
||||
deleteExpired: async () => {
|
||||
await db.delete(Cap_Tokens).where(lte(Cap_Tokens.expires, Date.now()));
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default cap;
|
||||
13
src/pages/cap/challenge.ts
Normal file
13
src/pages/cap/challenge.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import type { APIRoute } from "astro";
|
||||
import cap from "@lib/cap";
|
||||
export const prerender = false;
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
return new Response(JSON.stringify(await cap.createChallenge()), {
|
||||
status: 200,
|
||||
});
|
||||
} catch {
|
||||
return new Response(JSON.stringify({ success: false }), { status: 400 });
|
||||
}
|
||||
};
|
||||
14
src/pages/cap/redeem.ts
Normal file
14
src/pages/cap/redeem.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import type { APIRoute } from "astro";
|
||||
import cap from "@lib/cap";
|
||||
export const prerender = false;
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
const { token, solutions } = await request.json();
|
||||
if (!token || !solutions) {
|
||||
return new Response(JSON.stringify({ success: false }), { status: 400 });
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify(await cap.redeemChallenge({ token, solutions })),
|
||||
{ status: 200 },
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
import Layout from "@layouts/BaseLayout.astro";
|
||||
import SmsClient from "@lib/SmsGatewayClient.ts";
|
||||
import CapServer from "@lib/cap";
|
||||
export const prerender = false;
|
||||
|
||||
const errors = { name: "", phone: "", msg: "", form: "" };
|
||||
|
|
@ -9,9 +10,14 @@ if (Astro.request.method === "POST") {
|
|||
try {
|
||||
const data = await Astro.request.formData();
|
||||
const name = data.get("name")?.toString();
|
||||
const capToken = data.get("cap-token")?.toString();
|
||||
const phone = data.get("phone")?.toString();
|
||||
const msg = data.get("msg")?.toString();
|
||||
|
||||
if (typeof capToken !== "string" || !(await CapServer.validateToken(capToken)).success) {
|
||||
throw new Error("invalid cap token");
|
||||
}
|
||||
|
||||
if (typeof name !== "string" || name.length < 1) {
|
||||
errors.name += "Please enter a name. ";
|
||||
}
|
||||
|
|
@ -38,7 +44,29 @@ if (Astro.request.method === "POST") {
|
|||
}
|
||||
}
|
||||
---
|
||||
|
||||
<script>import CapWidget from "@cap.js/widget";</script>
|
||||
<style>
|
||||
cap-widget {
|
||||
--cap-background: #fdfdfd;
|
||||
--cap-border-color: var(--border-color);
|
||||
--cap-border-radius: 4px;
|
||||
--cap-widget-height: 100%;
|
||||
--cap-widget-width: 100%;
|
||||
--cap-widget-padding: 10px 20px;
|
||||
--cap-gap: 14px;
|
||||
--cap-color: #212121;
|
||||
--cap-checkbox-size: 25px;
|
||||
--cap-checkbox-border: 1px solid #aaaaaad1;
|
||||
--cap-checkbox-border-radius: 4px;
|
||||
--cap-checkbox-background: #fafafa91;
|
||||
--cap-checkbox-margin: 2px;
|
||||
--cap-font: system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco",
|
||||
"Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif;
|
||||
--cap-spinner-color: #000;
|
||||
--cap-spinner-background-color: #eee;
|
||||
--cap-spinner-thickness: 5px;
|
||||
}
|
||||
</style>
|
||||
<Layout>
|
||||
<title slot="head">Contact</title>
|
||||
<style>
|
||||
|
|
@ -48,10 +76,10 @@ if (Astro.request.method === "POST") {
|
|||
width: 100%;
|
||||
margin: 0 auto;
|
||||
grid-template-areas:
|
||||
"header header "
|
||||
"name phone"
|
||||
"msg msg"
|
||||
" . submit";
|
||||
"header header header header "
|
||||
"name name phone phone"
|
||||
"msg msg msg msg"
|
||||
"captcha captcha submit submit";
|
||||
}
|
||||
div {
|
||||
grid-area: header;
|
||||
|
|
@ -65,6 +93,10 @@ if (Astro.request.method === "POST") {
|
|||
label[for="msg"] {
|
||||
grid-area: msg;
|
||||
}
|
||||
label[for="captcha"] {
|
||||
grid-area: captcha;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
button#submit {
|
||||
grid-area: submit;
|
||||
}
|
||||
|
|
@ -91,8 +123,8 @@ if (Astro.request.method === "POST") {
|
|||
<textarea id="msg" name="msg" placeholder="I think badblocks rocks!"></textarea>
|
||||
{errors.msg && <p>{errors.msg}</p>}
|
||||
</label>
|
||||
<label for="cap">
|
||||
<cap-widget data-cap-api-endpoint="<your cap endpoint>"></cap-widget>
|
||||
<label for="captcha">
|
||||
<cap-widget id="captcha" data-cap-api-endpoint="/cap/"></cap-widget>
|
||||
</label>
|
||||
<button id="submit" type="submit">Submit</button>
|
||||
</form> || <p>Your message has been sent successfully!</p>}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue