@atproto/lex-password-session 0.0.4 → 0.0.5
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/CHANGELOG.md +12 -0
- package/README.md +187 -167
- package/dist/error.d.ts +47 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +47 -0
- package/dist/error.js.map +1 -1
- package/dist/lexicons/com/atproto/server/createAccount.defs.d.ts +28 -28
- package/dist/lexicons/com/atproto/server/createSession.defs.d.ts +28 -28
- package/dist/lexicons/com/atproto/server/getSession.defs.d.ts +16 -16
- package/dist/lexicons/com/atproto/server/refreshSession.defs.d.ts +20 -20
- package/dist/password-session.d.ts +189 -14
- package/dist/password-session.d.ts.map +1 -1
- package/dist/password-session.js +168 -14
- package/dist/password-session.js.map +1 -1
- package/package.json +6 -6
- package/src/error.ts +47 -0
- package/src/password-session.ts +189 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @atproto/lex-password-session
|
|
2
2
|
|
|
3
|
+
## 0.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#4601](https://github.com/bluesky-social/atproto/pull/4601) [`ed61c62`](https://github.com/bluesky-social/atproto/commit/ed61c62f3161fcde85ee9a93f8ed339c7e06c015) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Fix `exports` field in package.json
|
|
8
|
+
|
|
9
|
+
- [#4601](https://github.com/bluesky-social/atproto/pull/4601) [`ed61c62`](https://github.com/bluesky-social/atproto/commit/ed61c62f3161fcde85ee9a93f8ed339c7e06c015) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add JSDoc
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`7b9a98a`](https://github.com/bluesky-social/atproto/commit/7b9a98a763636c5f66a06da11fe6013f29dd9157), [`7b9a98a`](https://github.com/bluesky-social/atproto/commit/7b9a98a763636c5f66a06da11fe6013f29dd9157), [`ed61c62`](https://github.com/bluesky-social/atproto/commit/ed61c62f3161fcde85ee9a93f8ed339c7e06c015), [`ed61c62`](https://github.com/bluesky-social/atproto/commit/ed61c62f3161fcde85ee9a93f8ed339c7e06c015), [`7b9a98a`](https://github.com/bluesky-social/atproto/commit/7b9a98a763636c5f66a06da11fe6013f29dd9157), [`7b9a98a`](https://github.com/bluesky-social/atproto/commit/7b9a98a763636c5f66a06da11fe6013f29dd9157), [`ed61c62`](https://github.com/bluesky-social/atproto/commit/ed61c62f3161fcde85ee9a93f8ed339c7e06c015), [`ed61c62`](https://github.com/bluesky-social/atproto/commit/ed61c62f3161fcde85ee9a93f8ed339c7e06c015)]:
|
|
12
|
+
- @atproto/lex-schema@0.0.12
|
|
13
|
+
- @atproto/lex-client@0.0.12
|
|
14
|
+
|
|
3
15
|
## 0.0.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# @atproto/lex-password-
|
|
1
|
+
# @atproto/lex-password-session
|
|
2
2
|
|
|
3
|
-
Password-based
|
|
3
|
+
Password-based session authentication for AT Protocol Lexicons. See the [Changelog](./CHANGELOG.md) for version history.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @atproto/lex-password-
|
|
6
|
+
npm install @atproto/lex-password-session
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
- Session management with automatic token refresh
|
|
@@ -17,7 +17,7 @@ npm install @atproto/lex-password-agent
|
|
|
17
17
|
|
|
18
18
|
**What is this?**
|
|
19
19
|
|
|
20
|
-
`@atproto/lex-password-
|
|
20
|
+
`@atproto/lex-password-session` provides a `PasswordSession` class that implements the `Agent` interface from `@atproto/lex-client`. It handles password-based authentication with AT Protocol services, including:
|
|
21
21
|
|
|
22
22
|
1. Creating sessions with username/password credentials
|
|
23
23
|
2. Automatic token refresh when access tokens expire
|
|
@@ -26,28 +26,23 @@ npm install @atproto/lex-password-agent
|
|
|
26
26
|
|
|
27
27
|
```typescript
|
|
28
28
|
import { Client } from '@atproto/lex-client'
|
|
29
|
-
import {
|
|
29
|
+
import { PasswordSession } from '@atproto/lex-password-session'
|
|
30
30
|
import * as app from './lexicons/app.js'
|
|
31
31
|
|
|
32
32
|
// Login with credentials
|
|
33
|
-
const
|
|
33
|
+
const session = await PasswordSession.login({
|
|
34
34
|
service: 'https://bsky.social',
|
|
35
35
|
identifier: 'alice.bsky.social',
|
|
36
36
|
password: 'app-password',
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
onDeleted: (session) => clearStorage(session),
|
|
40
|
-
},
|
|
37
|
+
onUpdated: (data) => saveToStorage(data),
|
|
38
|
+
onDeleted: (data) => clearStorage(data.did),
|
|
41
39
|
})
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const agent = result.value
|
|
46
|
-
const client = new Client(agent)
|
|
41
|
+
const client = new Client(session)
|
|
47
42
|
|
|
48
43
|
// Make authenticated requests
|
|
49
44
|
const profile = await client.call(app.bsky.actor.getProfile, {
|
|
50
|
-
actor:
|
|
45
|
+
actor: session.did,
|
|
51
46
|
})
|
|
52
47
|
```
|
|
53
48
|
|
|
@@ -55,18 +50,19 @@ const profile = await client.call(app.bsky.actor.getProfile, {
|
|
|
55
50
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
56
51
|
|
|
57
52
|
- [Quick Start](#quick-start)
|
|
58
|
-
- [
|
|
53
|
+
- [PasswordSession](#passwordsession)
|
|
59
54
|
- [Login](#login)
|
|
60
55
|
- [Two-Factor Authentication](#two-factor-authentication)
|
|
61
56
|
- [Resume Session](#resume-session)
|
|
62
57
|
- [Logout](#logout)
|
|
63
58
|
- [Static Delete](#static-delete)
|
|
59
|
+
- [Create Account](#create-account)
|
|
64
60
|
- [Session Hooks](#session-hooks)
|
|
65
|
-
- [
|
|
66
|
-
- [
|
|
61
|
+
- [onUpdated](#onupdated)
|
|
62
|
+
- [onUpdateFailure](#onupdatefailure)
|
|
67
63
|
- [onDeleted](#ondeleted)
|
|
68
64
|
- [onDeleteFailure](#ondeletefailure)
|
|
69
|
-
- [Session
|
|
65
|
+
- [Session Data](#session-data)
|
|
70
66
|
- [Error Handling](#error-handling)
|
|
71
67
|
- [Using with Client](#using-with-client)
|
|
72
68
|
- [License](#license)
|
|
@@ -78,91 +74,92 @@ const profile = await client.call(app.bsky.actor.getProfile, {
|
|
|
78
74
|
**1. Install the package**
|
|
79
75
|
|
|
80
76
|
```bash
|
|
81
|
-
npm install @atproto/lex-password-
|
|
77
|
+
npm install @atproto/lex-password-session @atproto/lex-client
|
|
82
78
|
```
|
|
83
79
|
|
|
84
80
|
**2. Login and make requests**
|
|
85
81
|
|
|
86
82
|
```typescript
|
|
87
83
|
import { Client } from '@atproto/lex-client'
|
|
88
|
-
import {
|
|
84
|
+
import { PasswordSession } from '@atproto/lex-password-session'
|
|
89
85
|
|
|
90
|
-
const
|
|
86
|
+
const session = await PasswordSession.login({
|
|
91
87
|
service: 'https://bsky.social',
|
|
92
88
|
identifier: 'your-handle.bsky.social',
|
|
93
89
|
password: 'your-app-password',
|
|
94
90
|
})
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
const agent = result.value
|
|
98
|
-
const client = new Client(agent)
|
|
92
|
+
const client = new Client(session)
|
|
99
93
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
94
|
+
// Make authenticated API calls
|
|
95
|
+
console.log('Logged in as:', session.did)
|
|
103
96
|
```
|
|
104
97
|
|
|
105
|
-
##
|
|
98
|
+
## PasswordSession
|
|
106
99
|
|
|
107
|
-
The `
|
|
100
|
+
The `PasswordSession` class manages password-based authentication sessions.
|
|
108
101
|
|
|
109
102
|
### Login
|
|
110
103
|
|
|
111
104
|
Create a new session with username and password:
|
|
112
105
|
|
|
113
106
|
```typescript
|
|
114
|
-
import {
|
|
107
|
+
import { PasswordSession } from '@atproto/lex-password-session'
|
|
115
108
|
|
|
116
|
-
const
|
|
109
|
+
const session = await PasswordSession.login({
|
|
117
110
|
service: 'https://bsky.social',
|
|
118
111
|
identifier: 'alice.bsky.social', // handle or email
|
|
119
112
|
password: 'app-password',
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
localStorage.removeItem('session')
|
|
127
|
-
},
|
|
113
|
+
onUpdated: (data) => {
|
|
114
|
+
// Persist session for later restoration
|
|
115
|
+
localStorage.setItem('session', JSON.stringify(data))
|
|
116
|
+
},
|
|
117
|
+
onDeleted: () => {
|
|
118
|
+
localStorage.removeItem('session')
|
|
128
119
|
},
|
|
129
120
|
})
|
|
130
121
|
|
|
131
|
-
|
|
132
|
-
const agent = result.value
|
|
133
|
-
console.log('Logged in as:', agent.did)
|
|
134
|
-
} else {
|
|
135
|
-
console.error('Login failed:', result.error, result.message)
|
|
136
|
-
}
|
|
122
|
+
console.log('Logged in as:', session.did)
|
|
137
123
|
```
|
|
138
124
|
|
|
139
|
-
The `login()` method
|
|
140
|
-
|
|
141
|
-
- On success: `{ success: true, value: PasswordAgent }`
|
|
142
|
-
- On expected errors: `XrpcResponseError` with `success: false`
|
|
143
|
-
- On unexpected errors: throws the error
|
|
125
|
+
The `login()` method throws on failure. For expected errors like invalid credentials, an `XrpcResponseError` is thrown. For 2FA requirements, a `LexAuthFactorError` is thrown.
|
|
144
126
|
|
|
145
127
|
### Two-Factor Authentication
|
|
146
128
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const result = await PasswordAgent.login({
|
|
151
|
-
service: 'https://bsky.social',
|
|
152
|
-
identifier: 'alice.bsky.social',
|
|
153
|
-
password: 'app-password',
|
|
154
|
-
})
|
|
129
|
+
> [!CAUTION]
|
|
130
|
+
>
|
|
131
|
+
> Two-factor authentication only applies when using **main account credentials**, which is **strongly discouraged**. Password authentication should be used with [app passwords](https://bsky.app/settings/app-passwords) only because they are designed for programmatic access (bots, scripts, CLI tools). For user-facing applications, use OAuth via [@atproto/oauth-client](../../../oauth/oauth-client) which provides better security and user control.
|
|
155
132
|
|
|
156
|
-
|
|
157
|
-
// Prompt user for 2FA code, then retry
|
|
158
|
-
const code = await prompt2FACode()
|
|
133
|
+
If the account has 2FA enabled, login will throw a `LexAuthFactorError`:
|
|
159
134
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
135
|
+
```typescript
|
|
136
|
+
import {
|
|
137
|
+
PasswordSession,
|
|
138
|
+
LexAuthFactorError,
|
|
139
|
+
} from '@atproto/lex-password-session'
|
|
140
|
+
|
|
141
|
+
async function loginWith2FA(
|
|
142
|
+
identifier: string,
|
|
143
|
+
password: string,
|
|
144
|
+
authFactorToken?: string,
|
|
145
|
+
): Promise<PasswordSession> {
|
|
146
|
+
try {
|
|
147
|
+
return await PasswordSession.login({
|
|
148
|
+
service: 'https://bsky.social',
|
|
149
|
+
identifier,
|
|
150
|
+
password,
|
|
151
|
+
authFactorToken,
|
|
152
|
+
onUpdated: (data) => saveToStorage(data),
|
|
153
|
+
onDeleted: (data) => removeFromStorage(data.did),
|
|
154
|
+
})
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (err instanceof LexAuthFactorError && !authFactorToken) {
|
|
157
|
+
// 2FA required - prompt user for code
|
|
158
|
+
const token = await promptUserFor2FACode(err.message)
|
|
159
|
+
return loginWith2FA(identifier, password, token)
|
|
160
|
+
}
|
|
161
|
+
throw err
|
|
162
|
+
}
|
|
166
163
|
}
|
|
167
164
|
```
|
|
168
165
|
|
|
@@ -171,24 +168,27 @@ if (!result.success && result.error === 'AuthFactorTokenRequired') {
|
|
|
171
168
|
Restore a previously saved session:
|
|
172
169
|
|
|
173
170
|
```typescript
|
|
174
|
-
import {
|
|
171
|
+
import { PasswordSession, SessionData } from '@atproto/lex-password-session'
|
|
175
172
|
|
|
176
173
|
// Load session from storage
|
|
177
|
-
const savedSession:
|
|
174
|
+
const savedSession: SessionData = JSON.parse(localStorage.getItem('session')!)
|
|
178
175
|
|
|
179
176
|
// Resume the session (automatically refreshes tokens)
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
localStorage.removeItem('session')
|
|
187
|
-
},
|
|
177
|
+
const session = await PasswordSession.resume(savedSession, {
|
|
178
|
+
onUpdated: (data) => {
|
|
179
|
+
localStorage.setItem('session', JSON.stringify(data))
|
|
180
|
+
},
|
|
181
|
+
onDeleted: () => {
|
|
182
|
+
localStorage.removeItem('session')
|
|
188
183
|
},
|
|
189
184
|
})
|
|
190
185
|
|
|
191
|
-
console.log('Session resumed for:',
|
|
186
|
+
console.log('Session resumed for:', session.did)
|
|
187
|
+
|
|
188
|
+
// Access session properties
|
|
189
|
+
console.log(session.did) // User's DID
|
|
190
|
+
console.log(session.handle) // User's handle
|
|
191
|
+
console.log(session.destroyed) // false (session is active)
|
|
192
192
|
```
|
|
193
193
|
|
|
194
194
|
> [!NOTE]
|
|
@@ -200,66 +200,87 @@ console.log('Session resumed for:', agent.did)
|
|
|
200
200
|
End the session and notify the server:
|
|
201
201
|
|
|
202
202
|
```typescript
|
|
203
|
-
await
|
|
203
|
+
await session.logout()
|
|
204
204
|
```
|
|
205
205
|
|
|
206
206
|
After logout:
|
|
207
207
|
|
|
208
208
|
- The `onDeleted` hook is called
|
|
209
|
-
- The
|
|
209
|
+
- The session is marked as destroyed (`session.destroyed === true`)
|
|
210
210
|
- Further requests will throw `'Logged out'`
|
|
211
211
|
|
|
212
212
|
### Static Delete
|
|
213
213
|
|
|
214
|
-
Delete a session without creating
|
|
214
|
+
Delete a session without creating a session instance:
|
|
215
215
|
|
|
216
216
|
```typescript
|
|
217
|
-
import {
|
|
217
|
+
import { PasswordSession, SessionData } from '@atproto/lex-password-session'
|
|
218
218
|
|
|
219
|
-
const
|
|
219
|
+
const data: SessionData = JSON.parse(localStorage.getItem('session')!)
|
|
220
220
|
|
|
221
221
|
// Delete the session on the server
|
|
222
|
-
await
|
|
222
|
+
await PasswordSession.delete(data)
|
|
223
223
|
```
|
|
224
224
|
|
|
225
225
|
This is useful for cleanup scenarios where you don't need to make additional requests.
|
|
226
226
|
|
|
227
|
+
### Create Account
|
|
228
|
+
|
|
229
|
+
Create a new account and get an authenticated session:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { PasswordSession } from '@atproto/lex-password-session'
|
|
233
|
+
|
|
234
|
+
const session = await PasswordSession.createAccount(
|
|
235
|
+
{
|
|
236
|
+
handle: 'alice.bsky.social',
|
|
237
|
+
email: 'alice@example.com',
|
|
238
|
+
password: 'secure-password',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
service: 'https://bsky.social',
|
|
242
|
+
onUpdated: (data) => saveToStorage(data),
|
|
243
|
+
onDeleted: (data) => removeFromStorage(data.did),
|
|
244
|
+
},
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
console.log('Account created:', session.did)
|
|
248
|
+
```
|
|
249
|
+
|
|
227
250
|
## Session Hooks
|
|
228
251
|
|
|
229
|
-
Hooks provide callbacks for session lifecycle events. All hooks receive the
|
|
252
|
+
Hooks provide callbacks for session lifecycle events. All hooks receive the session instance as `this` context.
|
|
230
253
|
|
|
231
|
-
###
|
|
254
|
+
### onUpdated
|
|
232
255
|
|
|
233
|
-
Called when
|
|
256
|
+
Called when the session is successfully created or refreshed:
|
|
234
257
|
|
|
235
258
|
```typescript
|
|
236
|
-
const
|
|
259
|
+
const session = await PasswordSession.login({
|
|
237
260
|
service: 'https://bsky.social',
|
|
238
261
|
identifier: 'alice.bsky.social',
|
|
239
262
|
password: 'app-password',
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
saveSession(session)
|
|
247
|
-
},
|
|
263
|
+
onUpdated(data) {
|
|
264
|
+
// `this` is the PasswordSession instance
|
|
265
|
+
console.log('Session updated for:', this.did)
|
|
266
|
+
|
|
267
|
+
// Persist the updated session
|
|
268
|
+
saveSession(data)
|
|
248
269
|
},
|
|
249
270
|
})
|
|
250
271
|
```
|
|
251
272
|
|
|
252
273
|
> [!IMPORTANT]
|
|
253
274
|
>
|
|
254
|
-
> Requests are blocked while `
|
|
275
|
+
> Requests are blocked while `onUpdated` is running. Keep this callback fast to avoid delays.
|
|
255
276
|
|
|
256
|
-
###
|
|
277
|
+
### onUpdateFailure
|
|
257
278
|
|
|
258
279
|
Called when token refresh fails due to transient errors (network issues, server unavailability):
|
|
259
280
|
|
|
260
281
|
```typescript
|
|
261
|
-
|
|
262
|
-
|
|
282
|
+
{
|
|
283
|
+
onUpdateFailure(data, error) {
|
|
263
284
|
console.warn('Token refresh failed:', error.message)
|
|
264
285
|
// Session may still be valid - consider retry logic
|
|
265
286
|
}
|
|
@@ -271,10 +292,10 @@ hooks: {
|
|
|
271
292
|
Called when the session is terminated (logout or server-side invalidation):
|
|
272
293
|
|
|
273
294
|
```typescript
|
|
274
|
-
|
|
275
|
-
onDeleted(
|
|
276
|
-
console.log('Session ended for:',
|
|
277
|
-
clearPersistedSession(
|
|
295
|
+
{
|
|
296
|
+
onDeleted(data) {
|
|
297
|
+
console.log('Session ended for:', data.did)
|
|
298
|
+
clearPersistedSession(data.did)
|
|
278
299
|
redirectToLogin()
|
|
279
300
|
}
|
|
280
301
|
}
|
|
@@ -285,11 +306,11 @@ hooks: {
|
|
|
285
306
|
Called when logout fails due to transient errors:
|
|
286
307
|
|
|
287
308
|
```typescript
|
|
288
|
-
|
|
289
|
-
onDeleteFailure(
|
|
309
|
+
{
|
|
310
|
+
onDeleteFailure(data, error) {
|
|
290
311
|
console.error('Logout failed:', error.message)
|
|
291
312
|
// Consider queuing for retry to avoid orphaned sessions
|
|
292
|
-
queueLogoutRetry(
|
|
313
|
+
queueLogoutRetry(data)
|
|
293
314
|
}
|
|
294
315
|
}
|
|
295
316
|
```
|
|
@@ -298,29 +319,21 @@ hooks: {
|
|
|
298
319
|
>
|
|
299
320
|
> Ignoring delete failures can leave sessions active on the server. Implement retry logic for security-sensitive applications.
|
|
300
321
|
|
|
301
|
-
## Session
|
|
322
|
+
## Session Data
|
|
302
323
|
|
|
303
|
-
The `
|
|
324
|
+
The `SessionData` type contains all data needed to authenticate and restore sessions:
|
|
304
325
|
|
|
305
326
|
```typescript
|
|
306
|
-
type
|
|
327
|
+
type SessionData = {
|
|
307
328
|
// Session credentials and user info from createSession response
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
// ... other fields from createSession
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// When tokens were last refreshed
|
|
320
|
-
refreshedAt: string // ISO 8601 datetime
|
|
321
|
-
|
|
322
|
-
// PDS URL extracted from DID document (for routing requests)
|
|
323
|
-
pdsUrl: string | null
|
|
329
|
+
accessJwt: string
|
|
330
|
+
refreshJwt: string
|
|
331
|
+
did: string
|
|
332
|
+
handle: string
|
|
333
|
+
email?: string
|
|
334
|
+
emailConfirmed?: boolean
|
|
335
|
+
didDoc?: object
|
|
336
|
+
// ... other fields from createSession
|
|
324
337
|
|
|
325
338
|
// Original service URL used for login
|
|
326
339
|
service: string
|
|
@@ -329,28 +342,37 @@ type Session = {
|
|
|
329
342
|
|
|
330
343
|
## Error Handling
|
|
331
344
|
|
|
332
|
-
|
|
345
|
+
The `PasswordSession` class uses exception-based error handling:
|
|
333
346
|
|
|
334
347
|
```typescript
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
348
|
+
import {
|
|
349
|
+
PasswordSession,
|
|
350
|
+
LexAuthFactorError,
|
|
351
|
+
} from '@atproto/lex-password-session'
|
|
352
|
+
import { XrpcResponseError } from '@atproto/lex-client'
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const session = await PasswordSession.login({
|
|
356
|
+
service: 'https://bsky.social',
|
|
357
|
+
identifier: 'alice.bsky.social',
|
|
358
|
+
password: 'wrong-password',
|
|
359
|
+
})
|
|
360
|
+
} catch (err) {
|
|
361
|
+
if (err instanceof LexAuthFactorError) {
|
|
362
|
+
console.error('2FA required')
|
|
363
|
+
} else if (err instanceof XrpcResponseError) {
|
|
364
|
+
switch (err.error) {
|
|
365
|
+
case 'AuthenticationRequired':
|
|
366
|
+
console.error('Invalid credentials')
|
|
367
|
+
break
|
|
368
|
+
case 'AccountTakedown':
|
|
369
|
+
console.error('Account has been suspended')
|
|
370
|
+
break
|
|
371
|
+
default:
|
|
372
|
+
console.error('Login failed:', err.message)
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
throw err
|
|
354
376
|
}
|
|
355
377
|
}
|
|
356
378
|
```
|
|
@@ -367,41 +389,39 @@ Common error codes:
|
|
|
367
389
|
|
|
368
390
|
## Using with Client
|
|
369
391
|
|
|
370
|
-
The `
|
|
392
|
+
The `PasswordSession` implements the `Agent` interface and can be used directly with `Client`:
|
|
371
393
|
|
|
372
394
|
```typescript
|
|
373
395
|
import { Client } from '@atproto/lex-client'
|
|
374
|
-
import {
|
|
396
|
+
import { PasswordSession } from '@atproto/lex-password-session'
|
|
375
397
|
import * as app from './lexicons/app.js'
|
|
376
398
|
|
|
377
|
-
const
|
|
399
|
+
const session = await PasswordSession.login({
|
|
378
400
|
service: 'https://bsky.social',
|
|
379
401
|
identifier: 'alice.bsky.social',
|
|
380
402
|
password: 'app-password',
|
|
381
403
|
})
|
|
382
404
|
|
|
383
|
-
|
|
384
|
-
const client = new Client(result.value)
|
|
405
|
+
const client = new Client(session)
|
|
385
406
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
407
|
+
// The client automatically uses the session for authentication
|
|
408
|
+
const profile = await client.call(app.bsky.actor.getProfile, {
|
|
409
|
+
actor: client.assertDid,
|
|
410
|
+
})
|
|
390
411
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
412
|
+
// Tokens are automatically refreshed when expired
|
|
413
|
+
const timeline = await client.call(app.bsky.feed.getTimeline, {
|
|
414
|
+
limit: 50,
|
|
415
|
+
})
|
|
395
416
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
417
|
+
// Create records
|
|
418
|
+
await client.create(app.bsky.feed.post, {
|
|
419
|
+
text: 'Hello from lex-password-session!',
|
|
420
|
+
createdAt: new Date().toISOString(),
|
|
421
|
+
})
|
|
402
422
|
```
|
|
403
423
|
|
|
404
|
-
The
|
|
424
|
+
The session handles:
|
|
405
425
|
|
|
406
426
|
- Adding `Authorization` headers to requests
|
|
407
427
|
- Detecting expired tokens (401 responses or `ExpiredToken` errors)
|
package/dist/error.d.ts
CHANGED
|
@@ -1,8 +1,55 @@
|
|
|
1
1
|
import { LexError, XrpcFailure } from '@atproto/lex-client';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when two-factor authentication (2FA) is required.
|
|
4
|
+
*
|
|
5
|
+
* This error is thrown by {@link PasswordSession.login} when the server
|
|
6
|
+
* requires an additional authentication factor (e.g., email code). Catch this
|
|
7
|
+
* error to prompt the user for their 2FA code and retry the login with the
|
|
8
|
+
* `authFactorToken` parameter.
|
|
9
|
+
*
|
|
10
|
+
* @example Handling 2FA requirement
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { PasswordSession, LexAuthFactorError } from '@atproto/lex-password-session'
|
|
13
|
+
*
|
|
14
|
+
* try {
|
|
15
|
+
* const session = await PasswordSession.login({
|
|
16
|
+
* service: 'https://bsky.social',
|
|
17
|
+
* identifier: 'alice.bsky.social',
|
|
18
|
+
* password: 'xxxx-xxxx-xxxx-xxxx',
|
|
19
|
+
* })
|
|
20
|
+
* } catch (err) {
|
|
21
|
+
* if (err instanceof LexAuthFactorError) {
|
|
22
|
+
* // Prompt user for 2FA code
|
|
23
|
+
* const token = await promptUser('Enter 2FA code from email:')
|
|
24
|
+
*
|
|
25
|
+
* // Retry with the 2FA token
|
|
26
|
+
* const session = await PasswordSession.login({
|
|
27
|
+
* service: 'https://bsky.social',
|
|
28
|
+
* identifier: 'alice.bsky.social',
|
|
29
|
+
* password: 'xxxx-xxxx-xxxx-xxxx',
|
|
30
|
+
* authFactorToken: token,
|
|
31
|
+
* })
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @extends LexError
|
|
37
|
+
*/
|
|
2
38
|
export declare class LexAuthFactorError extends LexError {
|
|
3
39
|
readonly cause: XrpcFailure;
|
|
4
40
|
name: string;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new LexAuthFactorError.
|
|
43
|
+
*
|
|
44
|
+
* @param cause - The underlying XRPC failure response from the server
|
|
45
|
+
*/
|
|
5
46
|
constructor(cause: XrpcFailure);
|
|
47
|
+
/**
|
|
48
|
+
* Converts this error to an HTTP Response.
|
|
49
|
+
*
|
|
50
|
+
* @returns A 500 Internal Server Error response (2FA errors should not be
|
|
51
|
+
* exposed to end users in server contexts)
|
|
52
|
+
*/
|
|
6
53
|
toResponse(): Response;
|
|
7
54
|
}
|
|
8
55
|
//# sourceMappingURL=error.d.ts.map
|
package/dist/error.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAE3D,qBAAa,kBAAmB,SAAQ,QAAQ;
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBAAa,kBAAmB,SAAQ,QAAQ;IAQlC,QAAQ,CAAC,KAAK,EAAE,WAAW;IAPvC,IAAI,SAAuB;IAE3B;;;;OAIG;gBACkB,KAAK,EAAE,WAAW;IAIvC;;;;;OAKG;IACM,UAAU,IAAI,QAAQ;CAGhC"}
|