@kingironman2011/better-auth-bsky 0.2.0

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KingIronMan2011
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.
package/README.md ADDED
@@ -0,0 +1,238 @@
1
+ # @kingironman2011/better-auth-bsky
2
+
3
+ A [better-auth](https://better-auth.com) plugin that adds ATProto / Bluesky OAuth 2.1 authentication using [`@atcute/oauth-node-client`](https://github.com/mary-ext/atcute). Supports DPoP, PAR, and PKCE — the standard way to authenticate ATProto/Bluesky users without app passwords.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @kingironman2011/better-auth-bsky better-auth @atcute/oauth-node-client
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Server
14
+
15
+ ```typescript
16
+ import { betterAuth } from "better-auth";
17
+ import { atproto } from "@kingironman2011/better-auth-bsky";
18
+
19
+ export const auth = betterAuth({
20
+ // ... your config
21
+ plugins: [
22
+ atproto({
23
+ clientName: "My App",
24
+ }),
25
+ ],
26
+ });
27
+ ```
28
+
29
+ ### Client
30
+
31
+ ```typescript
32
+ import { createAuthClient } from "better-auth/client";
33
+ import { atprotoClient } from "@kingironman2011/better-auth-bsky/client";
34
+
35
+ const client = createAuthClient({
36
+ plugins: [atprotoClient()],
37
+ });
38
+
39
+ // Sign in — returns { url, redirect: true }
40
+ const { data } = await client.signIn.atproto({
41
+ handle: "user.bsky.social",
42
+ callbackURL: "/dashboard",
43
+ });
44
+ window.location.href = data.url;
45
+
46
+ // Check ATProto session
47
+ const session = await client.atproto.getSession();
48
+
49
+ // Restore ATProto session (lightweight token refresh check)
50
+ const status = await client.atproto.restore();
51
+
52
+ // Sign out (revokes ATProto OAuth session)
53
+ await client.atproto.signOut();
54
+ ```
55
+
56
+ ## Configuration
57
+
58
+ ```typescript
59
+ atproto({
60
+ // Required
61
+ clientName: "My App",
62
+
63
+ // Optional — app identity shown during authorization
64
+ clientUri: "https://myapp.com",
65
+ logoUri: "https://myapp.com/logo.png",
66
+ tosUri: "https://myapp.com/tos",
67
+ policyUri: "https://myapp.com/privacy",
68
+
69
+ // Optional — OAuth scopes (default: "atproto")
70
+ // Accepts a string or array of scope strings (e.g. from @atcute/oauth-types scope builders)
71
+ // The base "atproto" scope is always included automatically
72
+ scope: "atproto",
73
+
74
+ // Optional — private keys for confidential client mode
75
+ // If omitted, runs as a public client (shorter token lifetime)
76
+ keyset: [privateJwk],
77
+
78
+ // Optional — block new user creation (existing users can still sign in)
79
+ disableSignUp: false,
80
+
81
+ // Optional — custom profile-to-user field mapping
82
+ // Called during sign-in to populate user name, email, and image
83
+ mapProfileToUser: (profile) => ({
84
+ name: profile.displayName || profile.handle,
85
+ image: profile.avatar,
86
+ }),
87
+
88
+ // Optional — override endpoint paths
89
+ clientMetadataPath: "/oauth-client-metadata.json", // default
90
+ jwksPath: "/.well-known/jwks.json", // default
91
+ callbackPath: "/atproto/callback", // default
92
+ signInPath: "/sign-in/atproto", // default
93
+ });
94
+ ```
95
+
96
+ ## Public vs Confidential Client
97
+
98
+ The plugin auto-detects the client type based on whether `keyset` is provided.
99
+
100
+ | | Public | Confidential |
101
+ | ---------------------------- | ------------------- | ---------------------- |
102
+ | Config | No `keyset` | `keyset: [privateJwk]` |
103
+ | `token_endpoint_auth_method` | `none` | `private_key_jwt` |
104
+ | Max session lifetime | 14 days | 180 days |
105
+ | JWKS endpoint | Not served | Serves public keys |
106
+ | Loopback support | Yes (auto-detected) | Yes (dev only) |
107
+
108
+ ### Generating a keypair
109
+
110
+ ```typescript
111
+ import { generateAtprotoKeypair } from "@kingironman2011/better-auth-bsky";
112
+
113
+ const privateJwk = await generateAtprotoKeypair();
114
+ // Store securely — do NOT commit to version control
115
+ ```
116
+
117
+ ## Endpoints
118
+
119
+ All paths are relative to better-auth's `basePath`.
120
+
121
+ | Method | Path | Purpose |
122
+ | ------ | ----------------------------- | ------------------------------------------------------------- |
123
+ | GET | `/oauth-client-metadata.json` | OAuth client metadata document |
124
+ | GET | `/.well-known/jwks.json` | Public JWKS (confidential mode only) |
125
+ | POST | `/sign-in/atproto` | Start OAuth flow (`{ handle, callbackURL? }`) |
126
+ | GET | `/atproto/callback` | OAuth callback (code exchange, profile sync, user management) |
127
+ | GET | `/atproto/session` | Current user's ATProto info (DID, handle, PDS) |
128
+ | POST | `/atproto/restore` | Lightweight session check with token refresh |
129
+ | POST | `/atproto/sign-out` | Revoke ATProto OAuth session |
130
+
131
+ Additionally, `getAtprotoClient` is a server-only endpoint (not exposed over HTTP). It returns an authenticated `@atcute/client` `Client` and `OAuthSession` for making XRPC calls on behalf of a user:
132
+
133
+ ```typescript
134
+ const { client, session } = await auth.api.getAtprotoClient({
135
+ body: { did: "did:plc:abc123" },
136
+ // or: { userId: "user-id" }
137
+ });
138
+ ```
139
+
140
+ ### Rate Limiting
141
+
142
+ The plugin applies rate limits to sensitive endpoints:
143
+
144
+ - `/sign-in/atproto`: 5 requests per 60 seconds
145
+ - `/atproto/callback`: 10 requests per 60 seconds
146
+
147
+ ## Database Schema
148
+
149
+ The plugin extends the `user` table and adds two new tables via better-auth's migration system.
150
+
151
+ **`user` table extensions:**
152
+
153
+ | Column | Type | Notes |
154
+ | --------------- | ------ | -------------------------------- |
155
+ | `atprotoDid` | string | Unique, the user's permanent DID |
156
+ | `atprotoHandle` | string | Current ATProto handle |
157
+
158
+ **`atprotoSession`** — persists OAuth sessions for `@atcute/oauth-node-client`:
159
+
160
+ | Column | Type | Notes |
161
+ | ------------- | ------ | ----------------------------------------- |
162
+ | `id` | string | PK |
163
+ | `did` | string | Unique, the user's DID |
164
+ | `sessionData` | string | JSON blob (DPoP key, tokens, auth method) |
165
+ | `userId` | string | FK to `user.id` (cascade delete) |
166
+ | `handle` | string | ATProto handle (can change) |
167
+ | `pdsUrl` | string | User's PDS endpoint |
168
+ | `updatedAt` | date | |
169
+
170
+ **`atprotoState`** — temporary OAuth authorization states (~10min TTL):
171
+
172
+ | Column | Type | Notes |
173
+ | ----------- | ------ | ------------------------------------------- |
174
+ | `id` | string | PK |
175
+ | `stateKey` | string | Unique, the OAuth state parameter |
176
+ | `stateData` | string | JSON blob (DPoP key, PKCE verifier, issuer) |
177
+ | `expiresAt` | number | Unix timestamp |
178
+
179
+ ## How it Works
180
+
181
+ 1. **Sign-in**: Client POSTs handle to `/sign-in/atproto`. The plugin resolves the handle to a DID, discovers the user's PDS and authorization server, generates PKCE + DPoP keys, sends a PAR request, and returns the authorization URL.
182
+
183
+ 2. **Authorization**: User authorizes at their PDS authorization server (e.g. bsky.social).
184
+
185
+ 3. **Callback**: PDS redirects back to `/atproto/callback`. The plugin exchanges the code for tokens (with DPoP proof), fetches the user's profile (display name, avatar, etc.), then either finds an existing user, links to a currently-logged-in user, or creates a new user. Profile fields are synced to the user record, the ATProto session is persisted, a better-auth session cookie is set, and the user is redirected to the `callbackURL`.
186
+
187
+ 4. **Account linking**: If a user is already signed in via another method and completes the ATProto OAuth flow, their ATProto account is linked to their existing user. This respects better-auth's `account.accountLinking.enabled` configuration.
188
+
189
+ 5. **Session restoration**: On server restart, `oauthClient.restore(did)` rehydrates sessions from the database and handles token refresh automatically. The `/atproto/restore` endpoint exposes this to the client.
190
+
191
+ 6. **Authenticated API calls**: Use `auth.api.getAtprotoClient({ body: { did } })` server-side to get an authenticated `@atcute/client` `Client` for making XRPC calls on behalf of a user.
192
+
193
+ ## Identity Mapping
194
+
195
+ - **DID** is the permanent identifier, stored as `account.accountId` with `account.providerId = "atproto"`, and on the user record as `atprotoDid`
196
+ - **Email** uses a deterministic placeholder: `{did}@atproto.invalid` (RFC 2606 reserved TLD). Override via `mapProfileToUser` if you have access to the user's email
197
+ - **Handle** is tracked on both `atprotoSession.handle` and `user.atprotoHandle`, and updated on each sign-in
198
+ - **Profile** data (display name, avatar, banner, bio) is fetched on sign-in and mapped to user fields via `mapProfileToUser`
199
+
200
+ ## Exports
201
+
202
+ ### `@kingironman2011/better-auth-bsky` (main)
203
+
204
+ | Export | Type | Description |
205
+ | --------------------------- | -------- | ------------------------------------------------- |
206
+ | `atproto` | function | Server plugin factory |
207
+ | `atprotoClient` | function | Client plugin factory |
208
+ | `generateAtprotoKeypair` | function | Generate ES256 keypair for confidential mode |
209
+ | `extractPublicJwk` | function | Extract public JWK from a private JWK |
210
+ | `fetchAtprotoProfilePublic` | function | Fetch a profile via the Bluesky public API |
211
+ | `atprotoPlaceholderEmail` | function | Generate deterministic placeholder email from DID |
212
+ | `DbSessionStore` | class | Database-backed OAuth session store |
213
+ | `DbStateStore` | class | Database-backed OAuth state store |
214
+ | `atprotoSchema` | object | Database schema definition for migrations |
215
+ | `AtprotoPluginOptions` | type | Plugin configuration options |
216
+ | `AtprotoProfile` | type | Profile data shape from ATProto |
217
+
218
+ ### `@kingironman2011/better-auth-bsky/client`
219
+
220
+ | Export | Type | Description |
221
+ | --------------- | -------- | --------------------- |
222
+ | `atprotoClient` | function | Client plugin factory |
223
+
224
+ ## Development
225
+
226
+ ```bash
227
+ pnpm install
228
+ pnpm run build # build with tsdown
229
+ pnpm run test # vitest run
230
+ pnpm run test:watch # vitest watch
231
+ pnpm run lint # lint + typecheck + fmt check
232
+ pnpm run demo # interactive demo with Cloudflare tunnel
233
+ pnpm run demo:local # demo on localhost only (no tunnel)
234
+ ```
235
+
236
+ ## License
237
+
238
+ [MIT](LICENSE.md)
@@ -0,0 +1,63 @@
1
+ import { t as atproto } from "./server-DO9pjTl1.js";
2
+
3
+ //#region src/client.d.ts
4
+ declare const atprotoClient: () => {
5
+ id: "atproto";
6
+ $InferServerPlugin: ReturnType<typeof atproto>;
7
+ getActions: ($fetch: import("better-auth/client").BetterFetch) => {
8
+ signIn: {
9
+ atproto: (data: {
10
+ handle: string;
11
+ callbackURL?: string;
12
+ }) => Promise<{
13
+ data: unknown;
14
+ error: null;
15
+ } | {
16
+ data: null;
17
+ error: {
18
+ message?: string | undefined;
19
+ status: number;
20
+ statusText: string;
21
+ };
22
+ }>;
23
+ };
24
+ atproto: {
25
+ getSession: () => Promise<{
26
+ data: unknown;
27
+ error: null;
28
+ } | {
29
+ data: null;
30
+ error: {
31
+ message?: string | undefined;
32
+ status: number;
33
+ statusText: string;
34
+ };
35
+ }>;
36
+ restore: () => Promise<{
37
+ data: unknown;
38
+ error: null;
39
+ } | {
40
+ data: null;
41
+ error: {
42
+ message?: string | undefined;
43
+ status: number;
44
+ statusText: string;
45
+ };
46
+ }>;
47
+ signOut: () => Promise<{
48
+ data: unknown;
49
+ error: null;
50
+ } | {
51
+ data: null;
52
+ error: {
53
+ message?: string | undefined;
54
+ status: number;
55
+ statusText: string;
56
+ };
57
+ }>;
58
+ };
59
+ };
60
+ };
61
+ //#endregion
62
+ export { atprotoClient };
63
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"mappings":";;;cAGa,aAAA;;sBAIiB,UAAA,QAAkB,OAAA;;;;QAGhB,MAAA;QAAgB,WAAA;MAAA,MAAsB,OAAA"}
package/dist/client.js ADDED
@@ -0,0 +1,22 @@
1
+ //#region src/client.ts
2
+ const atprotoClient = () => ({
3
+ id: "atproto",
4
+ $InferServerPlugin: {},
5
+ getActions: ($fetch) => ({
6
+ signIn: { atproto: async (data) => {
7
+ return $fetch("/sign-in/atproto", {
8
+ method: "POST",
9
+ body: data
10
+ });
11
+ } },
12
+ atproto: {
13
+ getSession: async () => $fetch("/atproto/session", { method: "GET" }),
14
+ restore: async () => $fetch("/atproto/restore", { method: "POST" }),
15
+ signOut: async () => $fetch("/atproto/sign-out", { method: "POST" })
16
+ }
17
+ })
18
+ });
19
+ //#endregion
20
+ export { atprotoClient };
21
+
22
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type { BetterAuthClientPlugin } from \"better-auth/client\";\nimport type { atproto } from \"./server.js\";\n\nexport const atprotoClient = () =>\n ({\n id: \"atproto\",\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- required by better-auth plugin inference\n $InferServerPlugin: {} as ReturnType<typeof atproto>,\n getActions: ($fetch) => ({\n signIn: {\n atproto: async (data: { handle: string; callbackURL?: string }) => {\n return $fetch(\"/sign-in/atproto\", {\n method: \"POST\",\n body: data,\n });\n },\n },\n atproto: {\n getSession: async () => $fetch(\"/atproto/session\", { method: \"GET\" }),\n restore: async () => $fetch(\"/atproto/restore\", { method: \"POST\" }),\n signOut: async () => $fetch(\"/atproto/sign-out\", { method: \"POST\" }),\n },\n }),\n }) satisfies BetterAuthClientPlugin;\n"],"mappings":";AAGA,MAAa,uBACV;CACC,IAAI;CAEJ,oBAAoB,CAAC;CACrB,aAAa,YAAY;EACvB,QAAQ,EACN,SAAS,OAAO,SAAmD;GACjE,OAAO,OAAO,oBAAoB;IAChC,QAAQ;IACR,MAAM;GACR,CAAC;EACH,EACF;EACA,SAAS;GACP,YAAY,YAAY,OAAO,oBAAoB,EAAE,QAAQ,MAAM,CAAC;GACpE,SAAS,YAAY,OAAO,oBAAoB,EAAE,QAAQ,OAAO,CAAC;GAClE,SAAS,YAAY,OAAO,qBAAqB,EAAE,QAAQ,OAAO,CAAC;EACrE;CACF;AACF"}
@@ -0,0 +1,79 @@
1
+ import { a as AtprotoProfile, i as AtprotoPluginOptions, n as atprotoPlaceholderEmail, o as atprotoSchema, r as fetchAtprotoProfilePublic, t as atproto } from "./server-DO9pjTl1.js";
2
+ import { atprotoClient } from "./client.js";
3
+ import { ClientAssertionPrivateJwk, SessionStore, StateStore, StoredSession, StoredState } from "@atcute/oauth-node-client";
4
+ import { PublicJwk } from "@atcute/oauth-crypto";
5
+ import { Did } from "@atcute/lexicons";
6
+
7
+ //#region src/key-utils.d.ts
8
+ /**
9
+ * Generates an ES256 keypair for ATProto confidential client authentication.
10
+ * The returned JWK includes private key material and should be stored securely.
11
+ */
12
+ declare function generateAtprotoKeypair(kid?: string): Promise<ClientAssertionPrivateJwk>;
13
+ /**
14
+ * Extracts the public portion of a JWK by stripping private key fields.
15
+ * Safe to serve at the JWKS endpoint.
16
+ */
17
+ declare function extractPublicJwk(privateJwk: ClientAssertionPrivateJwk): PublicJwk;
18
+ //#endregion
19
+ //#region src/stores.d.ts
20
+ /**
21
+ * A generic better-auth adapter interface matching the subset we need.
22
+ * This avoids importing better-auth's full type tree.
23
+ */
24
+ interface DbAdapter {
25
+ findOne: <T>(data: {
26
+ model: string;
27
+ where: {
28
+ field: string;
29
+ value: unknown;
30
+ }[];
31
+ }) => Promise<T | null>;
32
+ create: <T>(data: {
33
+ model: string;
34
+ data: Record<string, unknown>;
35
+ }) => Promise<T>;
36
+ update: <T>(data: {
37
+ model: string;
38
+ where: {
39
+ field: string;
40
+ value: unknown;
41
+ }[];
42
+ update: Record<string, unknown>;
43
+ }) => Promise<T | null>;
44
+ delete: (data: {
45
+ model: string;
46
+ where: {
47
+ field: string;
48
+ value: unknown;
49
+ }[];
50
+ }) => Promise<void>;
51
+ deleteMany: (data: {
52
+ model: string;
53
+ where: {
54
+ field: string;
55
+ value: unknown;
56
+ }[];
57
+ }) => Promise<number>;
58
+ }
59
+ /** Database-backed session store for @atcute/oauth-node-client. */
60
+ declare class DbSessionStore implements SessionStore {
61
+ private adapter;
62
+ constructor(adapter: DbAdapter);
63
+ get(did: Did): Promise<StoredSession | undefined>;
64
+ set(did: Did, session: StoredSession): Promise<void>;
65
+ delete(did: Did): Promise<void>;
66
+ clear(): Promise<void>;
67
+ }
68
+ /** Database-backed state store for @atcute/oauth-node-client. */
69
+ declare class DbStateStore implements StateStore {
70
+ private adapter;
71
+ constructor(adapter: DbAdapter);
72
+ get(stateKey: string): Promise<StoredState | undefined>;
73
+ set(stateKey: string, state: StoredState): Promise<void>;
74
+ delete(stateKey: string): Promise<void>;
75
+ clear(): Promise<void>;
76
+ }
77
+ //#endregion
78
+ export { type AtprotoPluginOptions, type AtprotoProfile, DbSessionStore, DbStateStore, atproto, atprotoClient, atprotoPlaceholderEmail, atprotoSchema, extractPublicJwk, fetchAtprotoProfilePublic, generateAtprotoKeypair };
79
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/key-utils.ts","../src/stores.ts"],"mappings":";;;;;;;;;;;iBAasB,sBAAA,CACpB,GAAA,YACC,OAAO,CAAC,yBAAA;AAFX;;;;AAAA,iBAUgB,gBAAA,CACd,UAAA,EAAY,yBAAA,GACX,SAAS;;;;;;;UCbK,SAAA;EACf,OAAA,MAAa,IAAA;IACX,KAAA;IACA,KAAA;MAAS,KAAA;MAAe,KAAA;IAAA;EAAA,MACpB,OAAA,CAAQ,CAAA;EACd,MAAA,MAAY,IAAA;IACV,KAAA;IACA,IAAA,EAAM,MAAA;EAAA,MACF,OAAA,CAAQ,CAAA;EACd,MAAA,MAAY,IAAA;IACV,KAAA;IACA,KAAA;MAAS,KAAA;MAAe,KAAA;IAAA;IACxB,MAAA,EAAQ,MAAA;EAAA,MACJ,OAAA,CAAQ,CAAA;EACd,MAAA,GAAS,IAAA;IACP,KAAA;IACA,KAAA;MAAS,KAAA;MAAe,KAAA;IAAA;EAAA,MACpB,OAAA;EACN,UAAA,GAAa,IAAA;IACX,KAAA;IACA,KAAA;MAAS,KAAA;MAAe,KAAA;IAAA;EAAA,MACpB,OAAA;AAAA;;cAIK,cAAA,YAA0B,YAAA;EAAA,QACjB,OAAA;cAAA,OAAA,EAAS,SAAA;EAEvB,GAAA,CAAI,GAAA,EAAK,GAAA,GAAM,OAAA,CAAQ,aAAA;EAYvB,GAAA,CAAI,GAAA,EAAK,GAAA,EAAK,OAAA,EAAS,aAAA,GAAgB,OAAA;EA2BvC,MAAA,CAAO,GAAA,EAAK,GAAA,GAAM,OAAA;EAOlB,KAAA,IAAS,OAAA;AAAA;;cAUJ,YAAA,YAAwB,UAAA;EAAA,QACf,OAAA;cAAA,OAAA,EAAS,SAAA;EAEvB,GAAA,CAAI,QAAA,WAAmB,OAAA,CAAQ,WAAA;EAkB/B,GAAA,CAAI,QAAA,UAAkB,KAAA,EAAO,WAAA,GAAc,OAAA;EAY3C,MAAA,CAAO,QAAA,WAAmB,OAAA;EAO1B,KAAA,IAAS,OAAA;AAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ import { a as DbStateStore, i as DbSessionStore, n as atprotoPlaceholderEmail, o as atprotoSchema, r as fetchAtprotoProfilePublic, t as atproto } from "./server-DS4UMolW.js";
2
+ import { atprotoClient } from "./client.js";
3
+ import { generateClientAssertionKey } from "@atcute/oauth-node-client";
4
+ //#region src/key-utils.ts
5
+ /** Private key fields to strip when extracting a public JWK. */
6
+ const PRIVATE_KEY_FIELDS = /* @__PURE__ */ new Set([
7
+ "d",
8
+ "p",
9
+ "q",
10
+ "dp",
11
+ "dq",
12
+ "qi"
13
+ ]);
14
+ /**
15
+ * Generates an ES256 keypair for ATProto confidential client authentication.
16
+ * The returned JWK includes private key material and should be stored securely.
17
+ */
18
+ async function generateAtprotoKeypair(kid) {
19
+ return generateClientAssertionKey(kid ?? "atproto-key", "ES256");
20
+ }
21
+ /**
22
+ * Extracts the public portion of a JWK by stripping private key fields.
23
+ * Safe to serve at the JWKS endpoint.
24
+ */
25
+ function extractPublicJwk(privateJwk) {
26
+ const entries = Object.entries(privateJwk).filter(([key]) => !PRIVATE_KEY_FIELDS.has(key));
27
+ return Object.fromEntries(entries);
28
+ }
29
+ //#endregion
30
+ export { DbSessionStore, DbStateStore, atproto, atprotoClient, atprotoPlaceholderEmail, atprotoSchema, extractPublicJwk, fetchAtprotoProfilePublic, generateAtprotoKeypair };
31
+
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/key-utils.ts"],"sourcesContent":["import type { PublicJwk } from \"@atcute/oauth-crypto\";\nimport {\n type ClientAssertionPrivateJwk,\n generateClientAssertionKey,\n} from \"@atcute/oauth-node-client\";\n\n/** Private key fields to strip when extracting a public JWK. */\nconst PRIVATE_KEY_FIELDS = new Set([\"d\", \"p\", \"q\", \"dp\", \"dq\", \"qi\"]);\n\n/**\n * Generates an ES256 keypair for ATProto confidential client authentication.\n * The returned JWK includes private key material and should be stored securely.\n */\nexport async function generateAtprotoKeypair(\n kid?: string,\n): Promise<ClientAssertionPrivateJwk> {\n return generateClientAssertionKey(kid ?? \"atproto-key\", \"ES256\");\n}\n\n/**\n * Extracts the public portion of a JWK by stripping private key fields.\n * Safe to serve at the JWKS endpoint.\n */\nexport function extractPublicJwk(\n privateJwk: ClientAssertionPrivateJwk,\n): PublicJwk {\n const entries = Object.entries(privateJwk).filter(\n ([key]) => !PRIVATE_KEY_FIELDS.has(key),\n );\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Object.fromEntries loses type info\n return Object.fromEntries(entries) as PublicJwk;\n}\n"],"mappings":";;;;;AAOA,MAAM,qCAAqB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAM;CAAM;AAAI,CAAC;;;;;AAMpE,eAAsB,uBACpB,KACoC;CACpC,OAAO,2BAA2B,OAAO,eAAe,OAAO;AACjE;;;;;AAMA,SAAgB,iBACd,YACW;CACX,MAAM,UAAU,OAAO,QAAQ,UAAU,CAAC,CAAC,QACxC,CAAC,SAAS,CAAC,mBAAmB,IAAI,GAAG,CACxC;CAEA,OAAO,OAAO,YAAY,OAAO;AACnC"}