@alteran/astro 0.1.7 → 0.1.8

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 CHANGED
@@ -185,18 +185,45 @@ Set these secrets for each environment using `wrangler secret put <NAME> --env <
185
185
 
186
186
  **Generate secrets:**
187
187
  ```bash
188
- # Generate signing key
188
+ # One-shot bootstrap (recommended)
189
+ # Generates all required secrets and prints wrangler commands
190
+ bun run scripts/setup-secrets.ts --env production --did did:web:example.com --handle user.example.com
191
+
192
+ # Or generate only the repo signing key
189
193
  bun run scripts/generate-signing-key.ts
190
194
 
191
- # Set secrets (example for production)
195
+ # After generation, set secrets (example for production)
192
196
  wrangler secret put PDS_DID --env production
193
197
  wrangler secret put PDS_HANDLE --env production
194
198
  wrangler secret put USER_PASSWORD --env production
195
199
  wrangler secret put ACCESS_TOKEN_SECRET --env production
196
200
  wrangler secret put REFRESH_TOKEN_SECRET --env production
197
201
  wrangler secret put REPO_SIGNING_KEY --env production
202
+ # Optional: publish public key for DID document
203
+ wrangler secret put REPO_SIGNING_PUBLIC_KEY --env production
198
204
  ```
199
205
 
206
+ ### Using Cloudflare Secret Store (optional)
207
+
208
+ Instead of Wrangler Secrets, you may bind secrets from Cloudflare Secret Store. This repo now supports both. Bind each secret you want to source from Secret Store via `secrets_store_secrets` in `wrangler.jsonc`:
209
+
210
+ ```jsonc
211
+ {
212
+ // ...
213
+ "secrets_store_secrets": [
214
+ { "binding": "USER_PASSWORD", "secret_name": "user_password", "store_id": "<your-store-id>" },
215
+ { "binding": "ACCESS_TOKEN_SECRET", "secret_name": "access_token_secret", "store_id": "<your-store-id>" },
216
+ { "binding": "REFRESH_TOKEN_SECRET", "secret_name": "refresh_token_secret", "store_id": "<your-store-id>" },
217
+ { "binding": "PDS_DID", "secret_name": "pds_did", "store_id": "<your-store-id>" },
218
+ { "binding": "PDS_HANDLE", "secret_name": "pds_handle", "store_id": "<your-store-id>" }
219
+ ]
220
+ }
221
+ ```
222
+
223
+ Notes:
224
+ - Bindings can use the same names as the existing env vars. Only one source should be configured per secret (Wrangler Secret OR Secret Store binding).
225
+ - At runtime, the worker resolves Secret Store bindings via `await env.<BINDING>.get()` and passes them to the app as plain strings.
226
+
200
227
  ### Optional Configuration
201
228
 
202
229
  These can be set as environment variables in [`wrangler.jsonc`](wrangler.jsonc:1) or as secrets:
@@ -326,8 +353,12 @@ This PDS now implements full AT Protocol core compliance with:
326
353
 
327
354
  ## Setup Instructions
328
355
 
329
- ### 1. Generate Signing Key
356
+ ### 1. Generate Secrets
330
357
  ```bash
358
+ # Recommended: bootstrap all secrets (prints wrangler commands)
359
+ bun run scripts/setup-secrets.ts --env production --did did:web:example.com --handle user.example.com
360
+
361
+ # Alternative: generate only the repo signing key
331
362
  bun run scripts/generate-signing-key.ts
332
363
  ```
333
364
 
@@ -341,6 +372,8 @@ wrangler secret put PDS_HANDLE # Your handle
341
372
  wrangler secret put USER_PASSWORD # Login password
342
373
  wrangler secret put ACCESS_TOKEN_SECRET
343
374
  wrangler secret put REFRESH_TOKEN_SECRET
375
+ # Optional: publish raw public key for DID document
376
+ wrangler secret put REPO_SIGNING_PUBLIC_KEY
344
377
  ```
345
378
 
346
379
  **For Local Development (.dev.vars):**
@@ -348,6 +381,8 @@ wrangler secret put REFRESH_TOKEN_SECRET
348
381
  PDS_DID=did:plc:your-did-here
349
382
  PDS_HANDLE=your-handle.bsky.social
350
383
  REPO_SIGNING_KEY=<base64-key-from-step-1>
384
+ # Optional: publish raw 32-byte public key in did.json
385
+ REPO_SIGNING_PUBLIC_KEY=<base64-raw-public-key>
351
386
  USER_PASSWORD=your-password
352
387
  ACCESS_TOKEN_SECRET=your-access-secret
353
388
  REFRESH_TOKEN_SECRET=your-refresh-secret
@@ -0,0 +1,18 @@
1
+ CREATE TABLE `account_state` (
2
+ `did` text PRIMARY KEY NOT NULL,
3
+ `active` integer DEFAULT false NOT NULL,
4
+ `created_at` integer NOT NULL
5
+ );
6
+ --> statement-breakpoint
7
+ PRAGMA foreign_keys=OFF;--> statement-breakpoint
8
+ CREATE TABLE `__new_blob_usage` (
9
+ `record_uri` text NOT NULL,
10
+ `key` text NOT NULL,
11
+ PRIMARY KEY(`record_uri`, `key`)
12
+ );
13
+ --> statement-breakpoint
14
+ INSERT INTO `__new_blob_usage`("record_uri", "key") SELECT "record_uri", "key" FROM `blob_usage`;--> statement-breakpoint
15
+ DROP TABLE `blob_usage`;--> statement-breakpoint
16
+ ALTER TABLE `__new_blob_usage` RENAME TO `blob_usage`;--> statement-breakpoint
17
+ PRAGMA foreign_keys=ON;--> statement-breakpoint
18
+ CREATE INDEX `blob_usage_record_uri_idx` ON `blob_usage` (`record_uri`);
@@ -0,0 +1,429 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "cfdb4a96-9b33-41d2-9733-a44d61806a55",
5
+ "prevId": "cb70b8e1-d043-4b92-928a-ac332366391c",
6
+ "tables": {
7
+ "account_state": {
8
+ "name": "account_state",
9
+ "columns": {
10
+ "did": {
11
+ "name": "did",
12
+ "type": "text",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": false
16
+ },
17
+ "active": {
18
+ "name": "active",
19
+ "type": "integer",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false,
23
+ "default": false
24
+ },
25
+ "created_at": {
26
+ "name": "created_at",
27
+ "type": "integer",
28
+ "primaryKey": false,
29
+ "notNull": true,
30
+ "autoincrement": false
31
+ }
32
+ },
33
+ "indexes": {},
34
+ "foreignKeys": {},
35
+ "compositePrimaryKeys": {},
36
+ "uniqueConstraints": {},
37
+ "checkConstraints": {}
38
+ },
39
+ "blob_quota": {
40
+ "name": "blob_quota",
41
+ "columns": {
42
+ "did": {
43
+ "name": "did",
44
+ "type": "text",
45
+ "primaryKey": true,
46
+ "notNull": true,
47
+ "autoincrement": false
48
+ },
49
+ "total_bytes": {
50
+ "name": "total_bytes",
51
+ "type": "integer",
52
+ "primaryKey": false,
53
+ "notNull": true,
54
+ "autoincrement": false,
55
+ "default": 0
56
+ },
57
+ "blob_count": {
58
+ "name": "blob_count",
59
+ "type": "integer",
60
+ "primaryKey": false,
61
+ "notNull": true,
62
+ "autoincrement": false,
63
+ "default": 0
64
+ },
65
+ "updated_at": {
66
+ "name": "updated_at",
67
+ "type": "integer",
68
+ "primaryKey": false,
69
+ "notNull": true,
70
+ "autoincrement": false
71
+ }
72
+ },
73
+ "indexes": {},
74
+ "foreignKeys": {},
75
+ "compositePrimaryKeys": {},
76
+ "uniqueConstraints": {},
77
+ "checkConstraints": {}
78
+ },
79
+ "blob": {
80
+ "name": "blob",
81
+ "columns": {
82
+ "cid": {
83
+ "name": "cid",
84
+ "type": "text",
85
+ "primaryKey": true,
86
+ "notNull": true,
87
+ "autoincrement": false
88
+ },
89
+ "did": {
90
+ "name": "did",
91
+ "type": "text",
92
+ "primaryKey": false,
93
+ "notNull": true,
94
+ "autoincrement": false
95
+ },
96
+ "key": {
97
+ "name": "key",
98
+ "type": "text",
99
+ "primaryKey": false,
100
+ "notNull": true,
101
+ "autoincrement": false
102
+ },
103
+ "mime": {
104
+ "name": "mime",
105
+ "type": "text",
106
+ "primaryKey": false,
107
+ "notNull": true,
108
+ "autoincrement": false
109
+ },
110
+ "size": {
111
+ "name": "size",
112
+ "type": "integer",
113
+ "primaryKey": false,
114
+ "notNull": true,
115
+ "autoincrement": false
116
+ }
117
+ },
118
+ "indexes": {},
119
+ "foreignKeys": {},
120
+ "compositePrimaryKeys": {},
121
+ "uniqueConstraints": {},
122
+ "checkConstraints": {}
123
+ },
124
+ "blob_usage": {
125
+ "name": "blob_usage",
126
+ "columns": {
127
+ "record_uri": {
128
+ "name": "record_uri",
129
+ "type": "text",
130
+ "primaryKey": false,
131
+ "notNull": true,
132
+ "autoincrement": false
133
+ },
134
+ "key": {
135
+ "name": "key",
136
+ "type": "text",
137
+ "primaryKey": false,
138
+ "notNull": true,
139
+ "autoincrement": false
140
+ }
141
+ },
142
+ "indexes": {
143
+ "blob_usage_record_uri_idx": {
144
+ "name": "blob_usage_record_uri_idx",
145
+ "columns": [
146
+ "record_uri"
147
+ ],
148
+ "isUnique": false
149
+ }
150
+ },
151
+ "foreignKeys": {},
152
+ "compositePrimaryKeys": {
153
+ "blob_usage_record_uri_key_pk": {
154
+ "columns": [
155
+ "record_uri",
156
+ "key"
157
+ ],
158
+ "name": "blob_usage_record_uri_key_pk"
159
+ }
160
+ },
161
+ "uniqueConstraints": {},
162
+ "checkConstraints": {}
163
+ },
164
+ "blockstore": {
165
+ "name": "blockstore",
166
+ "columns": {
167
+ "cid": {
168
+ "name": "cid",
169
+ "type": "text",
170
+ "primaryKey": true,
171
+ "notNull": true,
172
+ "autoincrement": false
173
+ },
174
+ "bytes": {
175
+ "name": "bytes",
176
+ "type": "text",
177
+ "primaryKey": false,
178
+ "notNull": false,
179
+ "autoincrement": false
180
+ }
181
+ },
182
+ "indexes": {},
183
+ "foreignKeys": {},
184
+ "compositePrimaryKeys": {},
185
+ "uniqueConstraints": {},
186
+ "checkConstraints": {}
187
+ },
188
+ "commit_log": {
189
+ "name": "commit_log",
190
+ "columns": {
191
+ "seq": {
192
+ "name": "seq",
193
+ "type": "integer",
194
+ "primaryKey": true,
195
+ "notNull": true,
196
+ "autoincrement": false
197
+ },
198
+ "cid": {
199
+ "name": "cid",
200
+ "type": "text",
201
+ "primaryKey": false,
202
+ "notNull": true,
203
+ "autoincrement": false
204
+ },
205
+ "rev": {
206
+ "name": "rev",
207
+ "type": "text",
208
+ "primaryKey": false,
209
+ "notNull": true,
210
+ "autoincrement": false
211
+ },
212
+ "data": {
213
+ "name": "data",
214
+ "type": "text",
215
+ "primaryKey": false,
216
+ "notNull": true,
217
+ "autoincrement": false
218
+ },
219
+ "sig": {
220
+ "name": "sig",
221
+ "type": "text",
222
+ "primaryKey": false,
223
+ "notNull": true,
224
+ "autoincrement": false
225
+ },
226
+ "ts": {
227
+ "name": "ts",
228
+ "type": "integer",
229
+ "primaryKey": false,
230
+ "notNull": true,
231
+ "autoincrement": false
232
+ }
233
+ },
234
+ "indexes": {
235
+ "commit_log_seq_idx": {
236
+ "name": "commit_log_seq_idx",
237
+ "columns": [
238
+ "seq"
239
+ ],
240
+ "isUnique": false
241
+ }
242
+ },
243
+ "foreignKeys": {},
244
+ "compositePrimaryKeys": {},
245
+ "uniqueConstraints": {},
246
+ "checkConstraints": {}
247
+ },
248
+ "login_attempts": {
249
+ "name": "login_attempts",
250
+ "columns": {
251
+ "ip": {
252
+ "name": "ip",
253
+ "type": "text",
254
+ "primaryKey": true,
255
+ "notNull": true,
256
+ "autoincrement": false
257
+ },
258
+ "attempts": {
259
+ "name": "attempts",
260
+ "type": "integer",
261
+ "primaryKey": false,
262
+ "notNull": true,
263
+ "autoincrement": false,
264
+ "default": 0
265
+ },
266
+ "locked_until": {
267
+ "name": "locked_until",
268
+ "type": "integer",
269
+ "primaryKey": false,
270
+ "notNull": false,
271
+ "autoincrement": false
272
+ },
273
+ "last_attempt": {
274
+ "name": "last_attempt",
275
+ "type": "integer",
276
+ "primaryKey": false,
277
+ "notNull": true,
278
+ "autoincrement": false
279
+ }
280
+ },
281
+ "indexes": {},
282
+ "foreignKeys": {},
283
+ "compositePrimaryKeys": {},
284
+ "uniqueConstraints": {},
285
+ "checkConstraints": {}
286
+ },
287
+ "record": {
288
+ "name": "record",
289
+ "columns": {
290
+ "uri": {
291
+ "name": "uri",
292
+ "type": "text",
293
+ "primaryKey": true,
294
+ "notNull": true,
295
+ "autoincrement": false
296
+ },
297
+ "did": {
298
+ "name": "did",
299
+ "type": "text",
300
+ "primaryKey": false,
301
+ "notNull": true,
302
+ "autoincrement": false
303
+ },
304
+ "cid": {
305
+ "name": "cid",
306
+ "type": "text",
307
+ "primaryKey": false,
308
+ "notNull": true,
309
+ "autoincrement": false
310
+ },
311
+ "json": {
312
+ "name": "json",
313
+ "type": "text",
314
+ "primaryKey": false,
315
+ "notNull": true,
316
+ "autoincrement": false
317
+ },
318
+ "created_at": {
319
+ "name": "created_at",
320
+ "type": "integer",
321
+ "primaryKey": false,
322
+ "notNull": false,
323
+ "autoincrement": false,
324
+ "default": 0
325
+ }
326
+ },
327
+ "indexes": {
328
+ "record_did_idx": {
329
+ "name": "record_did_idx",
330
+ "columns": [
331
+ "did"
332
+ ],
333
+ "isUnique": false
334
+ },
335
+ "record_cid_idx": {
336
+ "name": "record_cid_idx",
337
+ "columns": [
338
+ "cid"
339
+ ],
340
+ "isUnique": false
341
+ }
342
+ },
343
+ "foreignKeys": {},
344
+ "compositePrimaryKeys": {},
345
+ "uniqueConstraints": {},
346
+ "checkConstraints": {}
347
+ },
348
+ "repo_root": {
349
+ "name": "repo_root",
350
+ "columns": {
351
+ "did": {
352
+ "name": "did",
353
+ "type": "text",
354
+ "primaryKey": true,
355
+ "notNull": true,
356
+ "autoincrement": false
357
+ },
358
+ "commit_cid": {
359
+ "name": "commit_cid",
360
+ "type": "text",
361
+ "primaryKey": false,
362
+ "notNull": true,
363
+ "autoincrement": false
364
+ },
365
+ "rev": {
366
+ "name": "rev",
367
+ "type": "integer",
368
+ "primaryKey": false,
369
+ "notNull": true,
370
+ "autoincrement": false
371
+ }
372
+ },
373
+ "indexes": {},
374
+ "foreignKeys": {},
375
+ "compositePrimaryKeys": {},
376
+ "uniqueConstraints": {},
377
+ "checkConstraints": {}
378
+ },
379
+ "token_revocation": {
380
+ "name": "token_revocation",
381
+ "columns": {
382
+ "jti": {
383
+ "name": "jti",
384
+ "type": "text",
385
+ "primaryKey": true,
386
+ "notNull": true,
387
+ "autoincrement": false
388
+ },
389
+ "exp": {
390
+ "name": "exp",
391
+ "type": "integer",
392
+ "primaryKey": false,
393
+ "notNull": true,
394
+ "autoincrement": false
395
+ },
396
+ "revoked_at": {
397
+ "name": "revoked_at",
398
+ "type": "integer",
399
+ "primaryKey": false,
400
+ "notNull": true,
401
+ "autoincrement": false
402
+ }
403
+ },
404
+ "indexes": {
405
+ "token_revocation_exp_idx": {
406
+ "name": "token_revocation_exp_idx",
407
+ "columns": [
408
+ "exp"
409
+ ],
410
+ "isUnique": false
411
+ }
412
+ },
413
+ "foreignKeys": {},
414
+ "compositePrimaryKeys": {},
415
+ "uniqueConstraints": {},
416
+ "checkConstraints": {}
417
+ }
418
+ },
419
+ "views": {},
420
+ "enums": {},
421
+ "_meta": {
422
+ "schemas": {},
423
+ "tables": {},
424
+ "columns": {}
425
+ },
426
+ "internal": {
427
+ "indexes": {}
428
+ }
429
+ }
@@ -36,6 +36,13 @@
36
36
  "when": 1759448068564,
37
37
  "tag": "0004_skinny_domino",
38
38
  "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "6",
43
+ "when": 1759515076154,
44
+ "tag": "0005_odd_bishop",
45
+ "breakpoints": true
39
46
  }
40
47
  ]
41
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alteran/astro",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Astro integration for running a Cloudflare-hosted Bluesky PDS with Alteran.",
5
5
  "module": "index.js",
6
6
  "types": "index.d.ts",
@@ -37,7 +37,8 @@
37
37
  "db:apply": "wrangler d1 migrations apply pds",
38
38
  "db:apply:local": "bunx wrangler d1 migrations apply pds --local",
39
39
  "db:apply:local:direct": "wrangler d1 migrations apply pds --local",
40
- "db:reset:local": "rm -rf .wrangler/state && rm -rf drizzle && bun run db:generate && bun run db:apply:local"
40
+ "db:reset:local": "rm -rf .wrangler/state && rm -rf drizzle && bun run db:generate && bun run db:apply:local",
41
+ "secrets:setup": "bun run scripts/setup-secrets.ts"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@astrojs/cloudflare": "^12.6.9",
package/src/db/dal.ts CHANGED
@@ -95,3 +95,36 @@ export async function checkBlobQuota(env: Env, did: string, additionalBytes: num
95
95
 
96
96
  return (quota.total_bytes + additionalBytes) <= maxBytes;
97
97
  }
98
+
99
+ // Account state management for migration support
100
+ export async function getAccountState(env: Env, did: string) {
101
+ const db = getDb(env);
102
+ const { account_state } = await import('./schema');
103
+ const state = await db.select().from(account_state).where(eq(account_state.did, did)).get();
104
+ return state ?? null;
105
+ }
106
+
107
+ export async function createAccountState(env: Env, did: string, active: boolean = false) {
108
+ const db = getDb(env);
109
+ const { account_state } = await import('./schema');
110
+ await db.insert(account_state).values({
111
+ did,
112
+ active,
113
+ created_at: Date.now(),
114
+ }).run();
115
+ }
116
+
117
+ export async function setAccountActive(env: Env, did: string, active: boolean) {
118
+ const db = getDb(env);
119
+ const { account_state } = await import('./schema');
120
+ await db.update(account_state)
121
+ .set({ active })
122
+ .where(eq(account_state.did, did))
123
+ .run();
124
+ }
125
+
126
+ export async function isAccountActive(env: Env, did: string): Promise<boolean> {
127
+ const state = await getAccountState(env, did);
128
+ // If no account state exists, assume active (backward compatibility)
129
+ return state?.active ?? true;
130
+ }
package/src/db/schema.ts CHANGED
@@ -85,5 +85,12 @@ export const blob_quota = sqliteTable('blob_quota', {
85
85
  updated_at: integer('updated_at').notNull(),
86
86
  });
87
87
 
88
+ // Account state for migration support (single-user PDS)
89
+ export const account_state = sqliteTable('account_state', {
90
+ did: text('did').primaryKey().notNull(),
91
+ active: integer('active', { mode: 'boolean' }).notNull().default(false),
92
+ created_at: integer('created_at').notNull(),
93
+ });
94
+
88
95
  export type RecordRow = typeof record.$inferSelect;
89
96
  export type NewRecordRow = typeof record.$inferInsert;