Schedule posts to Bluesky with Cloudflare workers. skyscheduler.work
cf tool bsky-tool cloudflare bluesky schedule bsky service social-media cloudflare-workers

fix up reset passwords

also just make it look better

+15 -35
+1 -1
assets/js/reset.js
··· 1 1 function handleResetLoad() { 2 2 if (resetToken = new URLSearchParams(window.location.search).get("token")) { 3 3 const resetTokenField = document.getElementById("resetToken"); 4 - const submitButton = document.getElementById("submitButton"); 4 + const submitButton = document.querySelector('button[type="submit"]'); 5 5 if (resetTokenField && submitButton) { 6 6 resetTokenField.value = encodeURI(resetToken); 7 7 submitButton.removeAttribute("disabled");
+4 -4
src/auth/index.ts
··· 5 5 import { drizzle, DrizzleD1Database } from "drizzle-orm/d1"; 6 6 import { schema } from "../db"; 7 7 import { BSKY_MAX_USERNAME_LENGTH, BSKY_MIN_USERNAME_LENGTH } from "../limits"; 8 - import { APP_NAME } from "../siteinfo"; 8 + import { APP_NAME, SITE_URL } from "../siteinfo"; 9 9 import { Bindings } from "../types"; 10 10 import { lookupBskyHandle } from "../utils/bskyApi"; 11 11 import { createDMWithUser } from "../utils/bskyMsg"; 12 12 13 - function createPasswordResetMessage(url: string) { 13 + function createPasswordResetMessage(url: string, token: string) { 14 14 return `Your ${APP_NAME} password reset url is: 15 - ${url} 15 + ${SITE_URL}/reset-password/${token} 16 16 17 17 This URL will expire in about an hour. 18 18 ··· 71 71 const userName = (user as any).username; 72 72 const bskyUserId = await lookupBskyHandle(userName); 73 73 if (bskyUserId !== null) { 74 - const response = await createDMWithUser(env!, bskyUserId, createPasswordResetMessage(url)); 74 + const response = await createDMWithUser(env!, bskyUserId, createPasswordResetMessage(url, token)); 75 75 if (!response) 76 76 throw new Error("FAILED_MESSAGE"); 77 77 } else {
+1 -16
src/endpoints/openapi.tsx
··· 5 5 import { ContextVariables } from "../auth"; 6 6 import { Bindings } from "../types"; 7 7 import { AccountDeleteSchema, AccountForgotSchema } from "../validation/accountForgotDeleteSchema"; 8 - import { 9 - AccountResetSchema, PasswordResetCheckCallbackParam, 10 - PasswordResetTokenParam 11 - } from "../validation/accountResetSchema"; 8 + import { AccountResetSchema } from "../validation/accountResetSchema"; 12 9 import { AccountUpdateSchema } from "../validation/accountUpdateSchema"; 13 10 import { LoginSchema } from "../validation/loginSchema"; 14 11 import { FileDeleteSchema } from "../validation/mediaSchema"; ··· 423 420 } 424 421 } 425 422 }), validator("param", CheckFileSchema)); 426 - 427 - openapiRoutes.get("/api/auth/reset-password/:id", describeRoute({ 428 - description: "resets a password", 429 - responses: { 430 - 200: { 431 - description: "valid token, redirect to reset" 432 - }, 433 - 404: { 434 - description: "reset token is invalid" 435 - } 436 - } 437 - }), validator("param", PasswordResetTokenParam), validator("query", PasswordResetCheckCallbackParam));
+8
src/index.tsx
··· 113 113 // Reset Password route 114 114 app.get("/reset", redirectToDashIfLogin, (c) => c.html(<ResetPassword />)); 115 115 116 + // Reset Password Confirm route 117 + app.get("/reset-password/:id", (c) => { 118 + // Alternatively you can just URL rewrite this in cloudflare and it'll look 119 + // 100x times better. 120 + const { id } = c.req.param(); 121 + return c.redirect(`/api/auth/reset-password/${id}?callbackURL=%2Freset`); 122 + }); 123 + 116 124 // Startup Application 117 125 app.get("/setup", async (c) => await setupAccounts(c)); 118 126
+1 -14
src/validation/accountResetSchema.ts
··· 13 13 .max(MAX_DASHBOARD_PASS, "confirm password too long") 14 14 .nonempty("confirm password cannot be empty") 15 15 .nonoptional(), 16 - }).refine((schema) => schema.confirmPassword === schema.password, "Passwords do not match"); 17 - 18 - // encoded strings 19 - const uriComponent = z.codec(z.string(), z.string(), { 20 - decode: (encodedString) => decodeURIComponent(encodedString), 21 - encode: (decodedString) => encodeURIComponent(decodedString), 22 - }); 23 - export const PasswordResetCheckCallbackParam = z.object({ 24 - callbackURL: z.literal(z.encode(uriComponent, "/reset")) 25 - }); 26 - 27 - export const PasswordResetTokenParam = z.object({ 28 - id: z.string().min(20).max(64) 29 - }); 16 + }).refine((schema) => schema.confirmPassword === schema.password, "Passwords do not match");