+9
astro.config.mjs
+9
astro.config.mjs
···
1
1
import { defineConfig } from "astro/config";
2
2
import tailwindcss from "@tailwindcss/vite";
3
+
import node from "@astrojs/node";
3
4
4
5
export default defineConfig({
6
+
output: "server",
7
+
adapter: node({
8
+
mode: "standalone",
9
+
}),
10
+
server: {
11
+
host: "127.0.0.1",
12
+
port: 4321,
13
+
},
5
14
vite: {
6
15
plugins: [tailwindcss()],
7
16
},
+528
-14
package-lock.json
+528
-14
package-lock.json
···
8
8
"name": "astro-atproto-starter",
9
9
"version": "0.0.1",
10
10
"dependencies": {
11
+
"@astrojs/node": "^9.5.0",
11
12
"@astrojs/tailwind": "^6.0.2",
13
+
"@atproto/api": "^0.17.4",
14
+
"@atproto/oauth-client-node": "^0.3.10",
12
15
"@tailwindcss/vite": "^4.1.16",
13
16
"astro": "^5.15.1",
14
17
"daisyui": "^5.3.9",
18
+
"dotenv": "^17.2.3",
15
19
"tailwindcss": "^4.1.16"
16
20
}
17
21
},
···
56
60
"vfile": "^6.0.3"
57
61
}
58
62
},
63
+
"node_modules/@astrojs/node": {
64
+
"version": "9.5.0",
65
+
"resolved": "https://registry.npmjs.org/@astrojs/node/-/node-9.5.0.tgz",
66
+
"integrity": "sha512-x1whLIatmCefaqJA8FjfI+P6FStF+bqmmrib0OUGM1M3cZhAXKLgPx6UF2AzQ3JgpXgCWYM24MHtraPvZhhyLQ==",
67
+
"license": "MIT",
68
+
"dependencies": {
69
+
"@astrojs/internal-helpers": "0.7.4",
70
+
"send": "^1.2.0",
71
+
"server-destroy": "^1.0.1"
72
+
},
73
+
"peerDependencies": {
74
+
"astro": "^5.14.3"
75
+
}
76
+
},
59
77
"node_modules/@astrojs/prism": {
60
78
"version": "3.3.0",
61
79
"resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz",
···
99
117
},
100
118
"engines": {
101
119
"node": "18.20.8 || ^20.3.0 || >=22.0.0"
120
+
}
121
+
},
122
+
"node_modules/@atproto-labs/did-resolver": {
123
+
"version": "0.2.2",
124
+
"resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.2.2.tgz",
125
+
"integrity": "sha512-ca2B7xR43tVoQ8XxBvha58DXwIH8cIyKQl6lpOKGkPUrJuFoO4iCLlDiSDi2Ueh+yE1rMDPP/qveHdajgDX3WQ==",
126
+
"license": "MIT",
127
+
"dependencies": {
128
+
"@atproto-labs/fetch": "0.2.3",
129
+
"@atproto-labs/pipe": "0.1.1",
130
+
"@atproto-labs/simple-store": "0.3.0",
131
+
"@atproto-labs/simple-store-memory": "0.1.4",
132
+
"@atproto/did": "0.2.1",
133
+
"zod": "^3.23.8"
134
+
}
135
+
},
136
+
"node_modules/@atproto-labs/fetch": {
137
+
"version": "0.2.3",
138
+
"resolved": "https://registry.npmjs.org/@atproto-labs/fetch/-/fetch-0.2.3.tgz",
139
+
"integrity": "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==",
140
+
"license": "MIT",
141
+
"dependencies": {
142
+
"@atproto-labs/pipe": "0.1.1"
143
+
}
144
+
},
145
+
"node_modules/@atproto-labs/fetch-node": {
146
+
"version": "0.2.0",
147
+
"resolved": "https://registry.npmjs.org/@atproto-labs/fetch-node/-/fetch-node-0.2.0.tgz",
148
+
"integrity": "sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q==",
149
+
"license": "MIT",
150
+
"dependencies": {
151
+
"@atproto-labs/fetch": "0.2.3",
152
+
"@atproto-labs/pipe": "0.1.1",
153
+
"ipaddr.js": "^2.1.0",
154
+
"undici": "^6.14.1"
155
+
},
156
+
"engines": {
157
+
"node": ">=18.7.0"
158
+
}
159
+
},
160
+
"node_modules/@atproto-labs/handle-resolver": {
161
+
"version": "0.3.2",
162
+
"resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.3.2.tgz",
163
+
"integrity": "sha512-KIerCzh3qb+zZoqWbIvTlvBY0XPq0r56kwViaJY/LTe/3oPO2JaqlYKS/F4dByWBhHK6YoUOJ0sWrh6PMJl40A==",
164
+
"license": "MIT",
165
+
"dependencies": {
166
+
"@atproto-labs/simple-store": "0.3.0",
167
+
"@atproto-labs/simple-store-memory": "0.1.4",
168
+
"@atproto/did": "0.2.1",
169
+
"zod": "^3.23.8"
170
+
}
171
+
},
172
+
"node_modules/@atproto-labs/handle-resolver-node": {
173
+
"version": "0.1.21",
174
+
"resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver-node/-/handle-resolver-node-0.1.21.tgz",
175
+
"integrity": "sha512-fuJy5Px5pGF3lJX/ATdurbT8tbmaFWtf+PPxAQDFy7ot2no3t+iaAgymhyxYymrssOuWs6BwOP8tyF3VrfdwtQ==",
176
+
"license": "MIT",
177
+
"dependencies": {
178
+
"@atproto-labs/fetch-node": "0.2.0",
179
+
"@atproto-labs/handle-resolver": "0.3.2",
180
+
"@atproto/did": "0.2.1"
181
+
},
182
+
"engines": {
183
+
"node": ">=18.7.0"
184
+
}
185
+
},
186
+
"node_modules/@atproto-labs/identity-resolver": {
187
+
"version": "0.3.2",
188
+
"resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.3.2.tgz",
189
+
"integrity": "sha512-MYxO9pe0WsFyi5HFdKAwqIqHfiF2kBPoVhAIuH/4PYHzGr799ED47xLhNMxR3ZUYrJm5+TQzWXypGZ0Btw1Ffw==",
190
+
"license": "MIT",
191
+
"dependencies": {
192
+
"@atproto-labs/did-resolver": "0.2.2",
193
+
"@atproto-labs/handle-resolver": "0.3.2"
194
+
}
195
+
},
196
+
"node_modules/@atproto-labs/pipe": {
197
+
"version": "0.1.1",
198
+
"resolved": "https://registry.npmjs.org/@atproto-labs/pipe/-/pipe-0.1.1.tgz",
199
+
"integrity": "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==",
200
+
"license": "MIT"
201
+
},
202
+
"node_modules/@atproto-labs/simple-store": {
203
+
"version": "0.3.0",
204
+
"resolved": "https://registry.npmjs.org/@atproto-labs/simple-store/-/simple-store-0.3.0.tgz",
205
+
"integrity": "sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ==",
206
+
"license": "MIT"
207
+
},
208
+
"node_modules/@atproto-labs/simple-store-memory": {
209
+
"version": "0.1.4",
210
+
"resolved": "https://registry.npmjs.org/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.4.tgz",
211
+
"integrity": "sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw==",
212
+
"license": "MIT",
213
+
"dependencies": {
214
+
"@atproto-labs/simple-store": "0.3.0",
215
+
"lru-cache": "^10.2.0"
216
+
}
217
+
},
218
+
"node_modules/@atproto/api": {
219
+
"version": "0.17.4",
220
+
"resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.17.4.tgz",
221
+
"integrity": "sha512-MRa0WdxyDiGF7fVKd/2ldvonsHQjsaLUOGw/PHrZ7J01lqlw/jaXLS25FNNYzjPGmGpnIyDCIg4Uucd/OblI9w==",
222
+
"license": "MIT",
223
+
"dependencies": {
224
+
"@atproto/common-web": "^0.4.3",
225
+
"@atproto/lexicon": "^0.5.1",
226
+
"@atproto/syntax": "^0.4.1",
227
+
"@atproto/xrpc": "^0.7.5",
228
+
"await-lock": "^2.2.2",
229
+
"multiformats": "^9.9.0",
230
+
"tlds": "^1.234.0",
231
+
"zod": "^3.23.8"
232
+
}
233
+
},
234
+
"node_modules/@atproto/common-web": {
235
+
"version": "0.4.3",
236
+
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.3.tgz",
237
+
"integrity": "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg==",
238
+
"license": "MIT",
239
+
"dependencies": {
240
+
"graphemer": "^1.4.0",
241
+
"multiformats": "^9.9.0",
242
+
"uint8arrays": "3.0.0",
243
+
"zod": "^3.23.8"
244
+
}
245
+
},
246
+
"node_modules/@atproto/did": {
247
+
"version": "0.2.1",
248
+
"resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.2.1.tgz",
249
+
"integrity": "sha512-1i5BTU2GnBaaeYWhxUOnuEKFVq9euT5+dQPFabHpa927BlJ54PmLGyBBaOI7/NbLmN5HWwBa18SBkMpg3jGZRA==",
250
+
"license": "MIT",
251
+
"dependencies": {
252
+
"zod": "^3.23.8"
253
+
}
254
+
},
255
+
"node_modules/@atproto/jwk": {
256
+
"version": "0.6.0",
257
+
"resolved": "https://registry.npmjs.org/@atproto/jwk/-/jwk-0.6.0.tgz",
258
+
"integrity": "sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw==",
259
+
"license": "MIT",
260
+
"dependencies": {
261
+
"multiformats": "^9.9.0",
262
+
"zod": "^3.23.8"
263
+
}
264
+
},
265
+
"node_modules/@atproto/jwk-jose": {
266
+
"version": "0.1.11",
267
+
"resolved": "https://registry.npmjs.org/@atproto/jwk-jose/-/jwk-jose-0.1.11.tgz",
268
+
"integrity": "sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q==",
269
+
"license": "MIT",
270
+
"dependencies": {
271
+
"@atproto/jwk": "0.6.0",
272
+
"jose": "^5.2.0"
273
+
}
274
+
},
275
+
"node_modules/@atproto/jwk-webcrypto": {
276
+
"version": "0.2.0",
277
+
"resolved": "https://registry.npmjs.org/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.2.0.tgz",
278
+
"integrity": "sha512-UmgRrrEAkWvxwhlwe30UmDOdTEFidlIzBC7C3cCbeJMcBN1x8B3KH+crXrsTqfWQBG58mXgt8wgSK3Kxs2LhFg==",
279
+
"license": "MIT",
280
+
"dependencies": {
281
+
"@atproto/jwk": "0.6.0",
282
+
"@atproto/jwk-jose": "0.1.11",
283
+
"zod": "^3.23.8"
284
+
}
285
+
},
286
+
"node_modules/@atproto/lexicon": {
287
+
"version": "0.5.1",
288
+
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.5.1.tgz",
289
+
"integrity": "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A==",
290
+
"license": "MIT",
291
+
"dependencies": {
292
+
"@atproto/common-web": "^0.4.3",
293
+
"@atproto/syntax": "^0.4.1",
294
+
"iso-datestring-validator": "^2.2.2",
295
+
"multiformats": "^9.9.0",
296
+
"zod": "^3.23.8"
297
+
}
298
+
},
299
+
"node_modules/@atproto/oauth-client": {
300
+
"version": "0.5.8",
301
+
"resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.5.8.tgz",
302
+
"integrity": "sha512-7YEym6d97+Dd73qGdkQTXi5La8xvCQxwRUDzzlR/NVAARa9a4YP7MCmqBJVeP2anT0By+DSAPyPDLTsxcjIcCg==",
303
+
"license": "MIT",
304
+
"dependencies": {
305
+
"@atproto-labs/did-resolver": "0.2.2",
306
+
"@atproto-labs/fetch": "0.2.3",
307
+
"@atproto-labs/handle-resolver": "0.3.2",
308
+
"@atproto-labs/identity-resolver": "0.3.2",
309
+
"@atproto-labs/simple-store": "0.3.0",
310
+
"@atproto-labs/simple-store-memory": "0.1.4",
311
+
"@atproto/did": "0.2.1",
312
+
"@atproto/jwk": "0.6.0",
313
+
"@atproto/oauth-types": "0.5.0",
314
+
"@atproto/xrpc": "0.7.5",
315
+
"core-js": "^3",
316
+
"multiformats": "^9.9.0",
317
+
"zod": "^3.23.8"
318
+
}
319
+
},
320
+
"node_modules/@atproto/oauth-client-node": {
321
+
"version": "0.3.10",
322
+
"resolved": "https://registry.npmjs.org/@atproto/oauth-client-node/-/oauth-client-node-0.3.10.tgz",
323
+
"integrity": "sha512-6khKlJqu1Ed5rt3rzcTD5hymB6JUjKdOHWYXwiphw4inkAIo6GxLCighI4eGOqZorYk2j8ueeTNB6KsgH0kcRw==",
324
+
"license": "MIT",
325
+
"dependencies": {
326
+
"@atproto-labs/did-resolver": "0.2.2",
327
+
"@atproto-labs/handle-resolver-node": "0.1.21",
328
+
"@atproto-labs/simple-store": "0.3.0",
329
+
"@atproto/did": "0.2.1",
330
+
"@atproto/jwk": "0.6.0",
331
+
"@atproto/jwk-jose": "0.1.11",
332
+
"@atproto/jwk-webcrypto": "0.2.0",
333
+
"@atproto/oauth-client": "0.5.8",
334
+
"@atproto/oauth-types": "0.5.0"
335
+
},
336
+
"engines": {
337
+
"node": ">=18.7.0"
338
+
}
339
+
},
340
+
"node_modules/@atproto/oauth-types": {
341
+
"version": "0.5.0",
342
+
"resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.5.0.tgz",
343
+
"integrity": "sha512-33xz7HcXhbl+XRqbIMVu3GE02iK1nKe2oMWENASsfZEYbCz2b9ZOarOFuwi7g4LKqpGowGp0iRKsQHFcq4SDaQ==",
344
+
"license": "MIT",
345
+
"dependencies": {
346
+
"@atproto/did": "0.2.1",
347
+
"@atproto/jwk": "0.6.0",
348
+
"zod": "^3.23.8"
349
+
}
350
+
},
351
+
"node_modules/@atproto/syntax": {
352
+
"version": "0.4.1",
353
+
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.1.tgz",
354
+
"integrity": "sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw==",
355
+
"license": "MIT"
356
+
},
357
+
"node_modules/@atproto/xrpc": {
358
+
"version": "0.7.5",
359
+
"resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.5.tgz",
360
+
"integrity": "sha512-MUYNn5d2hv8yVegRL0ccHvTHAVj5JSnW07bkbiaz96UH45lvYNRVwt44z+yYVnb0/mvBzyD3/ZQ55TRGt7fHkA==",
361
+
"license": "MIT",
362
+
"dependencies": {
363
+
"@atproto/lexicon": "^0.5.1",
364
+
"zod": "^3.23.8"
102
365
}
103
366
},
104
367
"node_modules/@babel/helper-string-parser": {
···
2047
2310
"postcss": "^8.1.0"
2048
2311
}
2049
2312
},
2313
+
"node_modules/await-lock": {
2314
+
"version": "2.2.2",
2315
+
"resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz",
2316
+
"integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==",
2317
+
"license": "MIT"
2318
+
},
2050
2319
"node_modules/axobject-query": {
2051
2320
"version": "4.1.0",
2052
2321
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
···
2340
2609
"integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==",
2341
2610
"license": "MIT"
2342
2611
},
2612
+
"node_modules/core-js": {
2613
+
"version": "3.46.0",
2614
+
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz",
2615
+
"integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==",
2616
+
"hasInstallScript": true,
2617
+
"license": "MIT",
2618
+
"funding": {
2619
+
"type": "opencollective",
2620
+
"url": "https://opencollective.com/core-js"
2621
+
}
2622
+
},
2343
2623
"node_modules/crossws": {
2344
2624
"version": "0.3.5",
2345
2625
"resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz",
···
2419
2699
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
2420
2700
"license": "MIT"
2421
2701
},
2702
+
"node_modules/depd": {
2703
+
"version": "2.0.0",
2704
+
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
2705
+
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
2706
+
"license": "MIT",
2707
+
"engines": {
2708
+
"node": ">= 0.8"
2709
+
}
2710
+
},
2422
2711
"node_modules/dequal": {
2423
2712
"version": "2.0.3",
2424
2713
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
···
2495
2784
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
2496
2785
"license": "MIT"
2497
2786
},
2787
+
"node_modules/dotenv": {
2788
+
"version": "17.2.3",
2789
+
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
2790
+
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
2791
+
"license": "BSD-2-Clause",
2792
+
"engines": {
2793
+
"node": ">=12"
2794
+
},
2795
+
"funding": {
2796
+
"url": "https://dotenvx.com"
2797
+
}
2798
+
},
2498
2799
"node_modules/dset": {
2499
2800
"version": "3.1.4",
2500
2801
"resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz",
···
2504
2805
"node": ">=4"
2505
2806
}
2506
2807
},
2808
+
"node_modules/ee-first": {
2809
+
"version": "1.1.1",
2810
+
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
2811
+
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
2812
+
"license": "MIT"
2813
+
},
2507
2814
"node_modules/electron-to-chromium": {
2508
2815
"version": "1.5.240",
2509
2816
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz",
···
2515
2822
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
2516
2823
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
2517
2824
"license": "MIT"
2825
+
},
2826
+
"node_modules/encodeurl": {
2827
+
"version": "2.0.0",
2828
+
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
2829
+
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
2830
+
"license": "MIT",
2831
+
"engines": {
2832
+
"node": ">= 0.8"
2833
+
}
2518
2834
},
2519
2835
"node_modules/enhanced-resolve": {
2520
2836
"version": "5.18.3",
···
2597
2913
"node": ">=6"
2598
2914
}
2599
2915
},
2916
+
"node_modules/escape-html": {
2917
+
"version": "1.0.3",
2918
+
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
2919
+
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
2920
+
"license": "MIT"
2921
+
},
2600
2922
"node_modules/escape-string-regexp": {
2601
2923
"version": "5.0.0",
2602
2924
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
···
2616
2938
"license": "MIT",
2617
2939
"dependencies": {
2618
2940
"@types/estree": "^1.0.0"
2941
+
}
2942
+
},
2943
+
"node_modules/etag": {
2944
+
"version": "1.8.1",
2945
+
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
2946
+
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
2947
+
"license": "MIT",
2948
+
"engines": {
2949
+
"node": ">= 0.6"
2619
2950
}
2620
2951
},
2621
2952
"node_modules/eventemitter3": {
···
2702
3033
"url": "https://github.com/sponsors/rawify"
2703
3034
}
2704
3035
},
3036
+
"node_modules/fresh": {
3037
+
"version": "2.0.0",
3038
+
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
3039
+
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
3040
+
"license": "MIT",
3041
+
"engines": {
3042
+
"node": ">= 0.8"
3043
+
}
3044
+
},
2705
3045
"node_modules/fsevents": {
2706
3046
"version": "2.3.3",
2707
3047
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
···
2739
3079
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
2740
3080
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
2741
3081
"license": "ISC"
3082
+
},
3083
+
"node_modules/graphemer": {
3084
+
"version": "1.4.0",
3085
+
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
3086
+
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
3087
+
"license": "MIT"
2742
3088
},
2743
3089
"node_modules/h3": {
2744
3090
"version": "1.15.4",
···
2966
3312
"integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
2967
3313
"license": "BSD-2-Clause"
2968
3314
},
3315
+
"node_modules/http-errors": {
3316
+
"version": "2.0.0",
3317
+
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
3318
+
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
3319
+
"license": "MIT",
3320
+
"dependencies": {
3321
+
"depd": "2.0.0",
3322
+
"inherits": "2.0.4",
3323
+
"setprototypeof": "1.2.0",
3324
+
"statuses": "2.0.1",
3325
+
"toidentifier": "1.0.1"
3326
+
},
3327
+
"engines": {
3328
+
"node": ">= 0.8"
3329
+
}
3330
+
},
3331
+
"node_modules/http-errors/node_modules/statuses": {
3332
+
"version": "2.0.1",
3333
+
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
3334
+
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
3335
+
"license": "MIT",
3336
+
"engines": {
3337
+
"node": ">= 0.8"
3338
+
}
3339
+
},
2969
3340
"node_modules/import-meta-resolve": {
2970
3341
"version": "4.2.0",
2971
3342
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
···
2974
3345
"funding": {
2975
3346
"type": "github",
2976
3347
"url": "https://github.com/sponsors/wooorm"
3348
+
}
3349
+
},
3350
+
"node_modules/inherits": {
3351
+
"version": "2.0.4",
3352
+
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
3353
+
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
3354
+
"license": "ISC"
3355
+
},
3356
+
"node_modules/ipaddr.js": {
3357
+
"version": "2.2.0",
3358
+
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
3359
+
"integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
3360
+
"license": "MIT",
3361
+
"engines": {
3362
+
"node": ">= 10"
2977
3363
}
2978
3364
},
2979
3365
"node_modules/iron-webcrypto": {
···
3054
3440
"url": "https://github.com/sponsors/sindresorhus"
3055
3441
}
3056
3442
},
3443
+
"node_modules/iso-datestring-validator": {
3444
+
"version": "2.2.2",
3445
+
"resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz",
3446
+
"integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==",
3447
+
"license": "MIT"
3448
+
},
3057
3449
"node_modules/jiti": {
3058
3450
"version": "2.6.1",
3059
3451
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
···
3061
3453
"license": "MIT",
3062
3454
"bin": {
3063
3455
"jiti": "lib/jiti-cli.mjs"
3456
+
}
3457
+
},
3458
+
"node_modules/jose": {
3459
+
"version": "5.10.0",
3460
+
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
3461
+
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
3462
+
"license": "MIT",
3463
+
"funding": {
3464
+
"url": "https://github.com/sponsors/panva"
3064
3465
}
3065
3466
},
3066
3467
"node_modules/js-yaml": {
···
4185
4586
],
4186
4587
"license": "MIT"
4187
4588
},
4589
+
"node_modules/mime-db": {
4590
+
"version": "1.54.0",
4591
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
4592
+
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
4593
+
"license": "MIT",
4594
+
"engines": {
4595
+
"node": ">= 0.6"
4596
+
}
4597
+
},
4598
+
"node_modules/mime-types": {
4599
+
"version": "3.0.1",
4600
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
4601
+
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
4602
+
"license": "MIT",
4603
+
"dependencies": {
4604
+
"mime-db": "^1.54.0"
4605
+
},
4606
+
"engines": {
4607
+
"node": ">= 0.6"
4608
+
}
4609
+
},
4188
4610
"node_modules/mrmime": {
4189
4611
"version": "2.0.1",
4190
4612
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
···
4199
4621
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
4200
4622
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
4201
4623
"license": "MIT"
4624
+
},
4625
+
"node_modules/multiformats": {
4626
+
"version": "9.9.0",
4627
+
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
4628
+
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
4629
+
"license": "(Apache-2.0 AND MIT)"
4202
4630
},
4203
4631
"node_modules/nanoid": {
4204
4632
"version": "3.3.11",
···
4292
4720
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
4293
4721
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
4294
4722
"license": "MIT"
4723
+
},
4724
+
"node_modules/on-finished": {
4725
+
"version": "2.4.1",
4726
+
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
4727
+
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
4728
+
"license": "MIT",
4729
+
"dependencies": {
4730
+
"ee-first": "1.1.1"
4731
+
},
4732
+
"engines": {
4733
+
"node": ">= 0.8"
4734
+
}
4295
4735
},
4296
4736
"node_modules/oniguruma-parser": {
4297
4737
"version": "0.12.1",
···
4520
4960
"integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==",
4521
4961
"license": "MIT"
4522
4962
},
4963
+
"node_modules/range-parser": {
4964
+
"version": "1.2.1",
4965
+
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
4966
+
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
4967
+
"license": "MIT",
4968
+
"engines": {
4969
+
"node": ">= 0.6"
4970
+
}
4971
+
},
4523
4972
"node_modules/readdirp": {
4524
4973
"version": "4.1.2",
4525
4974
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
···
4819
5268
"node": ">=10"
4820
5269
}
4821
5270
},
5271
+
"node_modules/send": {
5272
+
"version": "1.2.0",
5273
+
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
5274
+
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
5275
+
"license": "MIT",
5276
+
"dependencies": {
5277
+
"debug": "^4.3.5",
5278
+
"encodeurl": "^2.0.0",
5279
+
"escape-html": "^1.0.3",
5280
+
"etag": "^1.8.1",
5281
+
"fresh": "^2.0.0",
5282
+
"http-errors": "^2.0.0",
5283
+
"mime-types": "^3.0.1",
5284
+
"ms": "^2.1.3",
5285
+
"on-finished": "^2.4.1",
5286
+
"range-parser": "^1.2.1",
5287
+
"statuses": "^2.0.1"
5288
+
},
5289
+
"engines": {
5290
+
"node": ">= 18"
5291
+
}
5292
+
},
5293
+
"node_modules/server-destroy": {
5294
+
"version": "1.0.1",
5295
+
"resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
5296
+
"integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==",
5297
+
"license": "ISC"
5298
+
},
5299
+
"node_modules/setprototypeof": {
5300
+
"version": "1.2.0",
5301
+
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
5302
+
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
5303
+
"license": "ISC"
5304
+
},
4822
5305
"node_modules/sharp": {
4823
5306
"version": "0.34.4",
4824
5307
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz",
···
4913
5396
"funding": {
4914
5397
"type": "github",
4915
5398
"url": "https://github.com/sponsors/wooorm"
5399
+
}
5400
+
},
5401
+
"node_modules/statuses": {
5402
+
"version": "2.0.2",
5403
+
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
5404
+
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
5405
+
"license": "MIT",
5406
+
"engines": {
5407
+
"node": ">= 0.8"
4916
5408
}
4917
5409
},
4918
5410
"node_modules/string-width": {
···
5008
5500
"url": "https://github.com/sponsors/SuperchupuDev"
5009
5501
}
5010
5502
},
5503
+
"node_modules/tlds": {
5504
+
"version": "1.261.0",
5505
+
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz",
5506
+
"integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==",
5507
+
"license": "MIT",
5508
+
"bin": {
5509
+
"tlds": "bin.js"
5510
+
}
5511
+
},
5512
+
"node_modules/toidentifier": {
5513
+
"version": "1.0.1",
5514
+
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
5515
+
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
5516
+
"license": "MIT",
5517
+
"engines": {
5518
+
"node": ">=0.6"
5519
+
}
5520
+
},
5011
5521
"node_modules/trim-lines": {
5012
5522
"version": "3.0.1",
5013
5523
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
···
5066
5576
"url": "https://github.com/sponsors/sindresorhus"
5067
5577
}
5068
5578
},
5069
-
"node_modules/typescript": {
5070
-
"version": "5.9.3",
5071
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
5072
-
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
5073
-
"license": "Apache-2.0",
5074
-
"peer": true,
5075
-
"bin": {
5076
-
"tsc": "bin/tsc",
5077
-
"tsserver": "bin/tsserver"
5078
-
},
5079
-
"engines": {
5080
-
"node": ">=14.17"
5081
-
}
5082
-
},
5083
5579
"node_modules/ufo": {
5084
5580
"version": "1.6.1",
5085
5581
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
5086
5582
"integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
5087
5583
"license": "MIT"
5088
5584
},
5585
+
"node_modules/uint8arrays": {
5586
+
"version": "3.0.0",
5587
+
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz",
5588
+
"integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
5589
+
"license": "MIT",
5590
+
"dependencies": {
5591
+
"multiformats": "^9.4.2"
5592
+
}
5593
+
},
5089
5594
"node_modules/ultrahtml": {
5090
5595
"version": "1.6.0",
5091
5596
"resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz",
···
5097
5602
"resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
5098
5603
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==",
5099
5604
"license": "MIT"
5605
+
},
5606
+
"node_modules/undici": {
5607
+
"version": "6.22.0",
5608
+
"resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz",
5609
+
"integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==",
5610
+
"license": "MIT",
5611
+
"engines": {
5612
+
"node": ">=18.17"
5613
+
}
5100
5614
},
5101
5615
"node_modules/undici-types": {
5102
5616
"version": "7.16.0",
+4
package.json
+4
package.json
···
9
9
"astro": "astro"
10
10
},
11
11
"dependencies": {
12
+
"@astrojs/node": "^9.5.0",
12
13
"@astrojs/tailwind": "^6.0.2",
14
+
"@atproto/api": "^0.17.4",
15
+
"@atproto/oauth-client-node": "^0.3.10",
13
16
"@tailwindcss/vite": "^4.1.16",
14
17
"astro": "^5.15.1",
15
18
"daisyui": "^5.3.9",
19
+
"dotenv": "^17.2.3",
16
20
"tailwindcss": "^4.1.16"
17
21
}
18
22
}
+17
src/lib/context.ts
+17
src/lib/context.ts
···
1
+
import { NodeOAuthClient } from '@atproto/oauth-client-node'
2
+
import { createOAuthClient } from './oauth'
3
+
4
+
export type AppContext = {
5
+
oauthClient: NodeOAuthClient
6
+
}
7
+
8
+
let _ctx: AppContext | null = null
9
+
10
+
export async function getAppContext(): Promise<AppContext> {
11
+
if (_ctx) return _ctx
12
+
13
+
const oauthClient = await createOAuthClient()
14
+
15
+
_ctx = { oauthClient }
16
+
return _ctx
17
+
}
+4
src/lib/env.ts
+4
src/lib/env.ts
+21
src/lib/oauth.ts
+21
src/lib/oauth.ts
···
1
+
import {
2
+
atprotoLoopbackClientMetadata,
3
+
NodeOAuthClient,
4
+
} from "@atproto/oauth-client-node";
5
+
import { env } from "./env";
6
+
import { SessionStore, StateStore } from "./storage";
7
+
8
+
export async function createOAuthClient() {
9
+
const clientMetadata = atprotoLoopbackClientMetadata(
10
+
`http://localhost?${new URLSearchParams([
11
+
["redirect_uri", `http://127.0.0.1:${env.PORT}/api/oauth/callback`],
12
+
["scope", `atproto transition:generic`],
13
+
])}`,
14
+
);
15
+
16
+
return new NodeOAuthClient({
17
+
clientMetadata,
18
+
stateStore: new StateStore(),
19
+
sessionStore: new SessionStore(),
20
+
});
21
+
}
+61
src/lib/session.ts
+61
src/lib/session.ts
···
1
+
import type { AstroCookies } from 'astro'
2
+
3
+
export type SessionData = { did?: string }
4
+
5
+
const COOKIE_NAME = 'sid'
6
+
7
+
// Simple cookie-based session using Web Crypto API
8
+
export class Session {
9
+
private data: SessionData = {}
10
+
private cookies: AstroCookies
11
+
12
+
constructor(cookies: AstroCookies, data: SessionData = {}) {
13
+
this.cookies = cookies
14
+
this.data = data
15
+
}
16
+
17
+
get did() {
18
+
return this.data.did
19
+
}
20
+
21
+
set did(value: string | undefined) {
22
+
this.data.did = value
23
+
}
24
+
25
+
async save() {
26
+
const jsonData = JSON.stringify(this.data)
27
+
// For simplicity, we'll just base64 encode the data
28
+
// In production, you'd want proper encryption
29
+
const encoded = btoa(jsonData)
30
+
31
+
this.cookies.set(COOKIE_NAME, encoded, {
32
+
httpOnly: true,
33
+
secure: false,
34
+
sameSite: 'lax',
35
+
path: '/',
36
+
maxAge: 60 * 60 * 24 * 30, // 30 days
37
+
})
38
+
}
39
+
40
+
destroy() {
41
+
this.data = {}
42
+
this.cookies.delete(COOKIE_NAME, { path: '/' })
43
+
}
44
+
}
45
+
46
+
export function getSession(cookies: AstroCookies): Session {
47
+
const cookie = cookies.get(COOKIE_NAME)
48
+
49
+
if (!cookie?.value) {
50
+
return new Session(cookies)
51
+
}
52
+
53
+
try {
54
+
const decoded = atob(cookie.value)
55
+
const data = JSON.parse(decoded) as SessionData
56
+
return new Session(cookies, data)
57
+
} catch (err) {
58
+
console.warn('Failed to decode session:', err)
59
+
return new Session(cookies)
60
+
}
61
+
}
+41
src/lib/storage.ts
+41
src/lib/storage.ts
···
1
+
import type {
2
+
NodeSavedSession,
3
+
NodeSavedSessionStore,
4
+
NodeSavedState,
5
+
NodeSavedStateStore,
6
+
} from '@atproto/oauth-client-node'
7
+
8
+
// In-memory storage for OAuth state and sessions
9
+
// For production, you'd want to use a proper database or distributed cache
10
+
11
+
export class StateStore implements NodeSavedStateStore {
12
+
private store = new Map<string, NodeSavedState>()
13
+
14
+
async get(key: string): Promise<NodeSavedState | undefined> {
15
+
return this.store.get(key)
16
+
}
17
+
18
+
async set(key: string, val: NodeSavedState) {
19
+
this.store.set(key, val)
20
+
}
21
+
22
+
async del(key: string) {
23
+
this.store.delete(key)
24
+
}
25
+
}
26
+
27
+
export class SessionStore implements NodeSavedSessionStore {
28
+
private store = new Map<string, NodeSavedSession>()
29
+
30
+
async get(key: string): Promise<NodeSavedSession | undefined> {
31
+
return this.store.get(key)
32
+
}
33
+
34
+
async set(key: string, val: NodeSavedSession) {
35
+
this.store.set(key, val)
36
+
}
37
+
38
+
async del(key: string) {
39
+
this.store.delete(key)
40
+
}
41
+
}
+24
src/pages/api/login.ts
+24
src/pages/api/login.ts
···
1
+
import type { APIRoute } from 'astro'
2
+
import { getAppContext } from '../../lib/context'
3
+
4
+
export const POST: APIRoute = async ({ request, redirect }) => {
5
+
try {
6
+
const ctx = await getAppContext()
7
+
const formData = await request.formData()
8
+
const handle = formData.get('handle')
9
+
10
+
if (!handle || typeof handle !== 'string') {
11
+
return new Response('Invalid handle', { status: 400 })
12
+
}
13
+
14
+
const url = await ctx.oauthClient.authorize(handle, {
15
+
scope: 'atproto transition:generic',
16
+
})
17
+
18
+
return redirect(url.toString())
19
+
} catch (err) {
20
+
console.error('OAuth authorize failed:', err)
21
+
const error = err instanceof Error ? err.message : 'unexpected error'
22
+
return new Response(`Login failed: ${error}`, { status: 500 })
23
+
}
24
+
}
+26
src/pages/api/logout.ts
+26
src/pages/api/logout.ts
···
1
+
import type { APIRoute } from 'astro'
2
+
import { getAppContext } from '../../lib/context'
3
+
import { getSession } from '../../lib/session'
4
+
5
+
export const POST: APIRoute = async (context) => {
6
+
try {
7
+
const ctx = await getAppContext()
8
+
const session = getSession(context.cookies)
9
+
10
+
if (session.did) {
11
+
try {
12
+
const oauthSession = await ctx.oauthClient.restore(session.did)
13
+
if (oauthSession) await oauthSession.signOut()
14
+
} catch (err) {
15
+
console.warn('Failed to revoke credentials:', err)
16
+
}
17
+
}
18
+
19
+
session.destroy()
20
+
21
+
return context.redirect('/')
22
+
} catch (err) {
23
+
console.error('Logout failed:', err)
24
+
return new Response('Logout failed', { status: 500 })
25
+
}
26
+
}
+31
src/pages/api/oauth/callback.ts
+31
src/pages/api/oauth/callback.ts
···
1
+
import type { APIRoute } from 'astro'
2
+
import { getAppContext } from '../../../lib/context'
3
+
import { getSession } from '../../../lib/session'
4
+
5
+
export const GET: APIRoute = async (context) => {
6
+
try {
7
+
const ctx = await getAppContext()
8
+
const url = new URL(context.request.url)
9
+
const params = new URLSearchParams(url.search)
10
+
11
+
const session = getSession(context.cookies)
12
+
13
+
if (session.did) {
14
+
try {
15
+
const oauthSession = await ctx.oauthClient.restore(session.did)
16
+
if (oauthSession) await oauthSession.signOut()
17
+
} catch (err) {
18
+
console.warn('OAuth restore failed during callback:', err)
19
+
}
20
+
}
21
+
22
+
const oauth = await ctx.oauthClient.callback(params)
23
+
session.did = oauth.session.did
24
+
await session.save()
25
+
26
+
return context.redirect('/')
27
+
} catch (err) {
28
+
console.error('OAuth callback failed:', err)
29
+
return context.redirect('/?error=login_failed')
30
+
}
31
+
}
+74
-14
src/pages/index.astro
+74
-14
src/pages/index.astro
···
1
1
---
2
2
import "../styles.css";
3
+
import { getSession } from "../lib/session";
4
+
import { getAppContext } from "../lib/context";
5
+
import { Agent } from "@atproto/api";
6
+
7
+
const session = getSession(Astro.cookies);
8
+
const ctx = await getAppContext();
9
+
10
+
let agent: Agent | null = null;
11
+
let profile: any = null;
12
+
13
+
if (session.did) {
14
+
try {
15
+
const oauthSession = await ctx.oauthClient.restore(session.did);
16
+
if (oauthSession) {
17
+
agent = new Agent(oauthSession);
18
+
19
+
try {
20
+
const profileResponse = await agent.com.atproto.repo.getRecord({
21
+
repo: agent.assertDid,
22
+
collection: "app.bsky.actor.profile",
23
+
rkey: "self",
24
+
});
25
+
profile = profileResponse.data;
26
+
} catch (err) {
27
+
console.warn("Failed to fetch profile:", err);
28
+
}
29
+
}
30
+
} catch (err) {
31
+
console.warn("OAuth restore failed:", err);
32
+
session.destroy();
33
+
}
34
+
}
35
+
36
+
const displayName = profile?.value?.displayName || agent?.assertDid || "User";
37
+
const handle = agent?.assertDid || "";
3
38
---
4
39
5
40
<html lang="en" data-theme="dracula">
···
14
49
<div class="min-h-screen flex items-center justify-center">
15
50
<div class="card w-96 bg-base-100 shadow-xl">
16
51
<div class="card-body">
17
-
<h2 class="card-title justify-center mb-6">Login</h2>
18
-
<form>
19
-
<div class="join join-vertical w-full">
20
-
<input
21
-
type="text"
22
-
placeholder="Username"
23
-
class="input input-bordered join-item"
24
-
name="username"
25
-
/>
26
-
<button type="submit" class="btn btn-primary join-item">
27
-
Login
28
-
</button>
29
-
</div>
30
-
</form>
52
+
{
53
+
agent ? (
54
+
<>
55
+
<h2 class="card-title justify-center mb-4">Welcome!</h2>
56
+
<div class="space-y-4">
57
+
<div class="text-center">
58
+
<p class="text-lg font-semibold">{displayName}</p>
59
+
<p class="text-sm opacity-70">{handle}</p>
60
+
</div>
61
+
<form method="POST" action="/api/logout" class="w-full">
62
+
<button type="submit" class="btn btn-error w-full">
63
+
Logout
64
+
</button>
65
+
</form>
66
+
</div>
67
+
</>
68
+
) : (
69
+
<>
70
+
<h2 class="card-title justify-center mb-6">ATProto Login</h2>
71
+
<form method="POST" action="/api/login">
72
+
<div class="join join-vertical w-full">
73
+
<input
74
+
type="text"
75
+
placeholder="Enter your handle (e.g. alice.bsky.social)"
76
+
class="input input-bordered join-item"
77
+
name="handle"
78
+
required
79
+
/>
80
+
<button
81
+
type="submit"
82
+
class="btn btn-primary btn-wide join-item"
83
+
>
84
+
Login
85
+
</button>
86
+
</div>
87
+
</form>
88
+
</>
89
+
)
90
+
}
31
91
</div>
32
92
</div>
33
93
</div>