More details on docs

Changed files
+100 -8
.tangled
src
lib
server
atproto
routes
.tangled/images/oauth-no-scope.png

This is a binary file and will not be displayed.

+2
Dockerfile
··· 1 + #Had problems with pnpm and sqlite, so just using npm. bit longer but gets the job done. 2 + 1 3 FROM node:24-slim AS builder 2 4 WORKDIR /app 3 5
+21
LICENSE.md
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 Bailey Townsend 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+55 -6
README.md
··· 5 5 6 6 ## Features 7 7 - OAuth client configured from `.env` variables 8 - - `DEV=true` allows local development, do not need a public url 9 - - removing `DEV=true` and setting `OAUTH_DOMAIN` is production and requires a public url like [demo.atpoke.xyz](https://demo.atpoke.xyz) 10 - - Setting `OAUTH_JWK` allows for the confidential client that lasts much longer (forever if you refresh the tokens, 180 for indivudal refresh tokens). Can get a value for it from running `node ./bin/gen-jwk.js` 11 8 - Server side sessions and automatic loading of the atproto client from `event.locals.atpAgent` on server side components. 12 9 - Examples on how to create atproto records with [pokes or making a Bluesky post](./src/routes/demo/+page.server.ts) 13 10 - Examples showing how to use [microcosm.blue](https://microcosm.blue/) tooling for an appview like experince without the appview. ··· 15 12 - Find the [handles from the did](./src/routes/demo/+page.svelte) easily with [slingshot](https://slingshot.microcosm.blue/) 16 13 17 14 ## Dev Setup 18 - 1. Copy [.env.example](.env.example) to [.env](.env), .env.example is dev settings 15 + Should work with any package manager if you prefer to use another. But these are directions for [pnpm](https://pnpm.io/) 16 + 17 + 1. Copy [.env.example](.env.example) to [.env](.env), .env.example is the default dev settings 19 18 2. `pnpm install` 20 19 3. May need to run `pnpm approve-builds` for the build scripts for sqlite 21 20 4. `pnpm run dev` or `pnpm run dev:logging` with [pino-pretty](https://github.com/pinojs/pino-pretty) for pretty logging 22 21 23 22 > If you are running locally on a different port than `5173` or something else odd can set `OAUTH_DOMAIN` .env to the domain and port. Just make sure to use either `127.0.0.1` or `[::1]`(ipv6) for oauth to work for local development. 24 23 24 + ## Implementation details 25 + Details on some of the implementation details that you will most likely want to edit when creating your own project. 26 + 27 + ### How to configure OAuth 28 + OAuth *should* Already be figured out for you with everything being configured from the environment variables without having to modify any code. 29 + 30 + > All atproto actions taken for the user happen server side, so we can take advantage of the confidential client and longer session lifetimes. 31 + 32 + ## Types of OAuth clients 33 + - Development local - Set `DEV=true` in `.env` to use a local development client. This is a special client just for development does not require having a public url. As outlined [here](https://atproto.com/specs/oauth#localhost-client-development). This is the default from copying the [.env.example](.env.example) 34 + - Production Public – Remove the `DEV=true`, Set `OAUTH_DOMAIN` to your publicliy accessible domain([demo.atpoke.xyz](https://demo.atpoke.xyz) in `.env` to use a production public client. These have a lower atproto oauth session lifetime which is limited to 2 weeks. 35 + - Production Confidential - Follow `Production Public` and set `OAUTH_JWK` to the value from `node ./bin/gen-jwk.js`. **These are cryptographic signing keys and should be kept secret and private**. These have the longest session lifetime of 180 days for refresh tokens, or indefinitely if refreshed till, pending revocation or jwk rotation. 36 + 37 + Most likely in production you are going to want the Confidential client for the longest lifetime. 38 + The cookie session lifetime is less, so this could expire before the atproto session lifetime. This can be configured at [./src/lib/server/session.ts](./src/lib/server/session.ts), default is 30 days and resets for every logged-in web action. Can read more on atproto clients with [Client type details](https://atproto.com/specs/oauth#types-of-clients) and [session lifetime details](https://atproto.com/specs/oauth#tokens-and-session-lifetime) 39 + 40 + ## OAuth scopes 41 + OAuth scopes are permissions to the user's repo you are requesting. You can read more on them and learn the different ones on [atproto.com/specs/permission](https://atproto.com/specs/permission) 42 + 43 + This demo application only requires the access to the lexicons it needs with default scopes of `atproto repo:app.bsky.feed.post?action=create repo:xyz.atpoke.graph.poke`. This can be changed by setting the `OAUTH_SCOPES` environment variable. For development or full access to a user's repo you are probably looking for `atproto transition:generic` 44 + ![oauth scopes](.tangled/images/oauth-no-scope.png) 45 + 46 + > You may find as you're trying out new scopes that the OAuth screen may not reflect what you've requested. This is because the PDS can cache those. [Docs say this can be anywehre from 15-30mins](https://atproto.com/specs/permission#resolution-and-caching) 47 + 48 + ## OAuth Branding 49 + There are a couple of other odds and ends you can set for OAuth to customize the branding. This mostly shows up on `yourpds.com/account` for now, but it is a standard and more may adpot it. Will be taking the [docs definitions](https://atproto.com/specs/oauth#client-id-metadata-document) for each. 50 + 51 + - `OAUTH_CLIENT_NAME` - (string, optional): human-readable name of the client 52 + - `OAUTH_LOGO_URI` - (string, optional): URL to client logo. Only https: URIs are allowed. 53 + - `OAUTH_TOS_URI` - (string, optional): URL to human-readable terms of service (ToS) for the client. Only https: URIs are allowed. 54 + - `OAUTH_POLICY_URI` - (string, optional): URL to human-readable privacy policy for the client. Only https: URIs are allowed. 55 + 56 + ### Database 57 + This project uses [drizzle ORM](https://orm.drizzle.team/) with the sqlite adapter to make it easy to run locally and get started. This is used for the session store for the server and atproto. This will work for production as well and can build on it as is, but if you want to change out the database layer it should not be too bad since there's not a ton of db queries right now. This is a quick overview of that layer. 58 + 59 + - [./src/lib/server/db/index.ts](./src/lib/server/db/index.ts). This sets up the DB 60 + - [./src/lib/server/db/schema.ts](./src/lib/server/db/schema.ts). This is your database schema. 61 + - [./src/lib/server/cache.ts](./src/lib/server/cache.ts). This is an abstracted key/value cache that the [@atproto/oauth-client-node](https://www.npmjs.com/package/@atproto/oauth-client-node?activeTab=readme) uses to store state and sessions. 62 + - [./src/lib/server/session.ts](./src/lib/server/session.ts). This is the server side session store that is tied to the cookie session. 63 + - A tiny job runs at [./src/hooks.server.ts](./src/hooks.server.ts) that clears the atproto state store, and runs migrations on startup. If you change from a node server adapter may have to change this as well. 64 + - Will most likely need to change out some settings in [drizzle.config.ts](drizzle.config.ts) 65 + - Delete the contents in [drizzle](drizzle) and run `pnpm run db:generate` to generate new migration files for the new adapter. 66 + 67 + 68 + 69 + ### Other production considerations 70 + I did not find a great way to run a "sidecar process" with SvelteKit, and by that I mean a [Jetstream listener](https://docs.bsky.app/blog/jetstream) that runs alongside the SvelteKit application. If you are needing to listen to the firehose or jetstream to get real time records being created, I'd recommend changing out the database layered to something not embedded, then run a separate container (or process) with a node script running in a loop to get that data. [@atcute/jetstream](https://tangled.org/mary.my.id/atcute/tree/trunk/packages/clients/jetstream) is a great way to do this in TypeScript. 71 + For an overview of what that gets you and why you would want that I'd recommend checking out the quick start guide [Statusphere](https://atproto.com/guides/applications) to see how it is used there and why. 72 + 73 + 25 74 ## Production 26 75 27 76 > Sign up for Railway with my referral code [z49xDi](https://railway.com?referralCode=z49xDi) to get $20 in credits, and if you spend anything, I get 15% in credits. ··· 30 79 1. Install the railway cli ([directions here](https://docs.railway.com/guides/cli#installing-the-cli)) 31 80 2. Login with `railway login` 32 81 3. Create a new project with `railway init`, set your project name 33 - 4. Deploy your webapp with `railway up`, this will create a new deployment. This will crash on the first run since we still have some changes to make. This is what uploads your code to railway. That is expected since we don't have a volume and our variables yet. 34 - ![crash](.tangled/images/crash.png) 82 + 4. Deploy your webapp with `railway up`, this will create a new deployment. This will crash on the first run since we still have some changes to make. That is expected since we don't have a volume and our variables yet. This is what actually uploads your code to railway via the [dockerfile](Dockerfile). 83 + ![crash](.tangled/images/crash.png) 35 84 5. `railway service` select the service you deployed earlier, name is most likely the same as the project name. 36 85 6. Run `railway volume add -m /app_data` to create a persistent volume for the sqlite database. 37 86 7. If you do not already have your project dashboard open you can open it with `railway open`, this opens it in a web browser.
+3 -1
src/lib/server/atproto/client.ts
··· 61 61 token_endpoint_auth_method: isConfidential ? 'private_key_jwt' : 'none', 62 62 dpop_bound_access_tokens: true, 63 63 jwks_uri: isConfidential ? `${rootUrl}/.well-known/jwks.json` : undefined, 64 - token_endpoint_auth_signing_alg: isConfidential ? pk?.alg : undefined 64 + token_endpoint_auth_signing_alg: isConfidential ? pk?.alg : undefined, 65 + tos_uri: env.OAUTH_TOS_URI, 66 + policy_uri: env.OAUTH_POLICY_URI, 65 67 }; 66 68 67 69 return new NodeOAuthClient({
+6
src/routes/+layout.svelte
··· 33 33 34 34 <svelte:head> 35 35 <link rel="icon" href={favicon} /> 36 + <title>SvelteKit Atproto Demo</title> 37 + <meta property="og:title" content="SvelteKit Atproto Demo" /> 38 + <meta property="og:description" 39 + content="A demo application with pokes." /> 40 + <meta property="description" 41 + content="A demo application with pokes." /> 36 42 </svelte:head> 37 43 38 44 {#if data.session}
+8 -1
src/routes/+page.svelte
··· 12 12 <div> 13 13 <h1>SvelteKit ATProtocol OAuth template</h1> 14 14 <h3>A build your own ATProto adventure</h3> 15 - <p> It's up to you on what this project should be and what it looks like. This is mostly just a technical preview showing a demo of what the application looks like and how it works. But if you <a href="/login">Login</a> you can poke people or see who poked you, so there's that at least.</p> 15 + <p> It's up to you on what this project should be and what it looks like. Fork the project on <a href="https://tangled.org/baileytownsend.dev/atproto-sveltekit-template">tangled.org</a> to make it your own. This web hosted version is mostly just a technical preview showing a demo of what the application looks like and how it works, not a lot here, or even styling that's up to you, not me. But if you <a href="/login">Login</a> you can poke people or see who poked you, so there's that at least.</p> 16 16 <h3>A bare minimal SvelteKit demo that...</h3> 17 17 <ul> 18 18 <li>Can have local dev oauth with setting the <code>.env</code> variable <code>DEV=true</code></li> ··· 22 22 <li>A persistent session store using <a href="https://orm.drizzle.team/">drizzle</a> with sqlite</li> 23 23 <li>A docker compose and documentation on how to deploy to <a href="https://railway.com/">railway</a></li> 24 24 <li><a href="/demo">An example page using the atproto Agent where you can make a post or poke someone</a> </li> 25 + <li>Uses <a href="https://microcosm.blue" class="link"><span 26 + style="color: rgb(243, 150, 169);">m</span><span style="color: rgb(244, 156, 92);">i</span><span 27 + style="color: rgb(199, 176, 76);">c</span><span style="color: rgb(146, 190, 76);">r</span><span 28 + style="color: rgb(78, 198, 136);">o</span><span style="color: rgb(81, 194, 182);">c</span><span 29 + style="color: rgb(84, 190, 215);">o</span><span style="color: rgb(143, 177, 241);">s</span><span 30 + style="color: rgb(206, 157, 241);">m</span></a> for demoing some AppView-less features you can do, like seeing who has poked you without a backend. 31 + </li> 25 32 <li>Source code can be found on <a href="https://tangled.org/baileytownsend.dev/atproto-sveltekit-template">tangled.org</a> </li> 26 33 </ul> 27 34 <br/>
+5
src/routes/demo/+page.server.ts
··· 30 30 text: rt.text, 31 31 facets: rt.facets, 32 32 createdAt: new Date().toISOString(), 33 + //This sets the language of the post. Want to most likely get it from a locale of the browser 34 + //cheating here and using english since the rest of the documentations in english and this is a demo 35 + langs: [ 36 + 'en' 37 + ] 33 38 }; 34 39 35 40 const result = await agent.com.atproto.repo.createRecord({