Schedule posts to Bluesky with Cloudflare workers. skyscheduler.work
cf tool bsky-tool cloudflare bluesky schedule bsky service social-media cloudflare-workers
at main 161 lines 4.7 kB view raw
1import { betterAuth, Session } from "better-auth"; 2import { withCloudflare } from "better-auth-cloudflare"; 3import { drizzleAdapter } from "better-auth/adapters/drizzle"; 4import { username } from "better-auth/plugins"; 5import { drizzle, DrizzleD1Database } from "drizzle-orm/d1"; 6import { schema } from "../db"; 7import { BSKY_MAX_USERNAME_LENGTH, BSKY_MIN_USERNAME_LENGTH } from "../limits"; 8import { APP_NAME } from "../siteinfo"; 9import { Bindings } from "../types"; 10import { lookupBskyHandle } from "../utils/bsky/bskyApi"; 11import { createDMWithUser } from "../utils/bsky/bskyMessage"; 12import { createPasswordResetMessage } from "../utils/messages/accountReset"; 13 14// Single auth configuration that handles both CLI and runtime scenarios 15function createAuth(env?: Bindings, cf?: IncomingRequestCfProperties) { 16 // Use actual DB for runtime, empty object for CLI 17 const db = env ? drizzle(env.DB, { schema, logger: false }) : ({} as any); 18 return betterAuth({ 19 disabledPaths: [ 20 "/sign-in/email", 21 "/sign-in/social", 22 "/change-email", 23 "/set-password", 24 "/link-social-account", 25 "/unlink-account", 26 "/account-info", 27 "/refresh-token", 28 "/get-access-token", 29 "/verify-email", 30 "/send-verification-email", 31 "/revoke-other-sessions", 32 "/revoke-session", 33 "/link-social", 34 "/list-accounts", 35 "/list-sessions", 36 "/cloudflare/geolocation", 37 "/is-username-available", 38 "/delete-user/callback", 39 "/change-password", 40 "/is-username-available" 41 ], 42 ...withCloudflare( 43 { 44 autoDetectIpAddress: false, 45 geolocationTracking: false, 46 cf: cf || {}, 47 d1: env 48 ? { 49 db, 50 options: { 51 usePlural: true, 52 debugLogs: false, 53 }, 54 } 55 : undefined, 56 kv: env?.KV, 57 }, 58 { 59 emailAndPassword: { 60 enabled: true, 61 requireEmailVerification: false, 62 sendResetPassword: async ({user, url, token}, request) => { 63 const userName = (user as any).username; 64 const bskyUserId = await lookupBskyHandle(userName); 65 if (bskyUserId !== null) { 66 const response = await createDMWithUser(env!, bskyUserId, createPasswordResetMessage(url, token)); 67 if (!response) 68 throw new Error("FAILED_MESSAGE"); 69 } else { 70 console.error(`Unable to look up bsky username for user ${userName}, got null`); 71 throw new Error("NO_LOOKUP"); 72 } 73 }, 74 }, 75 plugins: [ 76 username({ 77 // We validate all of our usernames ahead of time 78 // do not use the validator in betterauth but instead our own ZOD system 79 usernameValidator: (username) => { 80 return true; 81 }, 82 displayUsernameValidator: (displayUsername) => { 83 return true; 84 }, 85 /* we do our own normalization in the zod schemas */ 86 usernameNormalization: false, 87 displayUsernameNormalization: false, 88 minUsernameLength: BSKY_MIN_USERNAME_LENGTH, 89 maxUsernameLength: BSKY_MAX_USERNAME_LENGTH 90 }) 91 ], 92 rateLimit: { 93 enabled: true, 94 window: 60, 95 max: 100, 96 "*": { 97 window: 60, 98 max: 100, 99 }, 100 }, 101 } 102 ), 103 appName: APP_NAME, 104 secret: env?.BETTER_AUTH_SECRET, 105 baseURL: (env?.BETTER_AUTH_URL === "*") ? undefined : env?.BETTER_AUTH_URL, 106 user: { 107 additionalFields: { 108 bskyAppPass: { 109 type: "string", 110 required: true 111 }, 112 pds: { 113 type: "string", 114 defaultValue: "https://bsky.social", 115 required: true 116 } 117 }, 118 changeEmail: { 119 enabled: false 120 }, 121 deleteUser: { 122 enabled: false, 123 } 124 }, 125 account: { 126 accountLinking: { 127 enabled: false 128 }, 129 }, 130 telemetry: { 131 enabled: false 132 }, 133 logger: { 134 disabled: true 135 }, 136 // Only add database adapter for CLI schema generation 137 ...(env ? {} : { 138 database: drizzleAdapter({} as D1Database, { 139 provider: "sqlite", 140 usePlural: true, 141 debugLogs: false, 142 }), 143 }), 144 }); 145} 146 147// Export for CLI schema generation 148export const auth = createAuth(); 149 150// Export for variable types 151type ContextVariables = { 152 auth: ReturnType<typeof createAuth>; 153 userId: string; 154 isAdmin: boolean; 155 session: Session; 156 db: DrizzleD1Database; 157 pds: string; 158}; 159 160// Export for runtime usage 161export { ContextVariables, createAuth };