@atproto/lex-password-session 0.0.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.
Files changed (97) hide show
  1. package/README.md +413 -0
  2. package/dist/error.d.ts +8 -0
  3. package/dist/error.d.ts.map +1 -0
  4. package/dist/error.js +14 -0
  5. package/dist/error.js.map +1 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +6 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/lexicons/com/atproto/server/createAccount.d.ts +3 -0
  11. package/dist/lexicons/com/atproto/server/createAccount.d.ts.map +1 -0
  12. package/dist/lexicons/com/atproto/server/createAccount.defs.d.ts +57 -0
  13. package/dist/lexicons/com/atproto/server/createAccount.defs.d.ts.map +1 -0
  14. package/dist/lexicons/com/atproto/server/createAccount.defs.js +43 -0
  15. package/dist/lexicons/com/atproto/server/createAccount.defs.js.map +1 -0
  16. package/dist/lexicons/com/atproto/server/createAccount.js +10 -0
  17. package/dist/lexicons/com/atproto/server/createAccount.js.map +1 -0
  18. package/dist/lexicons/com/atproto/server/createSession.d.ts +3 -0
  19. package/dist/lexicons/com/atproto/server/createSession.d.ts.map +1 -0
  20. package/dist/lexicons/com/atproto/server/createSession.defs.d.ts +53 -0
  21. package/dist/lexicons/com/atproto/server/createSession.defs.d.ts.map +1 -0
  22. package/dist/lexicons/com/atproto/server/createSession.defs.js +35 -0
  23. package/dist/lexicons/com/atproto/server/createSession.defs.js.map +1 -0
  24. package/dist/lexicons/com/atproto/server/createSession.js +10 -0
  25. package/dist/lexicons/com/atproto/server/createSession.js.map +1 -0
  26. package/dist/lexicons/com/atproto/server/deleteSession.d.ts +3 -0
  27. package/dist/lexicons/com/atproto/server/deleteSession.d.ts.map +1 -0
  28. package/dist/lexicons/com/atproto/server/deleteSession.defs.d.ts +13 -0
  29. package/dist/lexicons/com/atproto/server/deleteSession.defs.d.ts.map +1 -0
  30. package/dist/lexicons/com/atproto/server/deleteSession.defs.js +19 -0
  31. package/dist/lexicons/com/atproto/server/deleteSession.defs.js.map +1 -0
  32. package/dist/lexicons/com/atproto/server/deleteSession.js +10 -0
  33. package/dist/lexicons/com/atproto/server/deleteSession.js.map +1 -0
  34. package/dist/lexicons/com/atproto/server/getSession.d.ts +3 -0
  35. package/dist/lexicons/com/atproto/server/getSession.d.ts.map +1 -0
  36. package/dist/lexicons/com/atproto/server/getSession.defs.d.ts +37 -0
  37. package/dist/lexicons/com/atproto/server/getSession.defs.d.ts.map +1 -0
  38. package/dist/lexicons/com/atproto/server/getSession.defs.js +27 -0
  39. package/dist/lexicons/com/atproto/server/getSession.defs.js.map +1 -0
  40. package/dist/lexicons/com/atproto/server/getSession.js +10 -0
  41. package/dist/lexicons/com/atproto/server/getSession.js.map +1 -0
  42. package/dist/lexicons/com/atproto/server/refreshSession.d.ts +3 -0
  43. package/dist/lexicons/com/atproto/server/refreshSession.d.ts.map +1 -0
  44. package/dist/lexicons/com/atproto/server/refreshSession.defs.d.ts +43 -0
  45. package/dist/lexicons/com/atproto/server/refreshSession.defs.d.ts.map +1 -0
  46. package/dist/lexicons/com/atproto/server/refreshSession.defs.js +30 -0
  47. package/dist/lexicons/com/atproto/server/refreshSession.defs.js.map +1 -0
  48. package/dist/lexicons/com/atproto/server/refreshSession.js +10 -0
  49. package/dist/lexicons/com/atproto/server/refreshSession.js.map +1 -0
  50. package/dist/lexicons/com/atproto/server.d.ts +6 -0
  51. package/dist/lexicons/com/atproto/server.d.ts.map +1 -0
  52. package/dist/lexicons/com/atproto/server.js +13 -0
  53. package/dist/lexicons/com/atproto/server.js.map +1 -0
  54. package/dist/lexicons/com/atproto.d.ts +2 -0
  55. package/dist/lexicons/com/atproto.d.ts.map +1 -0
  56. package/dist/lexicons/com/atproto.js +9 -0
  57. package/dist/lexicons/com/atproto.js.map +1 -0
  58. package/dist/lexicons/com.d.ts +2 -0
  59. package/dist/lexicons/com.d.ts.map +1 -0
  60. package/dist/lexicons/com.js +9 -0
  61. package/dist/lexicons/com.js.map +1 -0
  62. package/dist/lexicons/index.d.ts +2 -0
  63. package/dist/lexicons/index.d.ts.map +1 -0
  64. package/dist/lexicons/index.js +9 -0
  65. package/dist/lexicons/index.js.map +1 -0
  66. package/dist/password-session.d.ts +127 -0
  67. package/dist/password-session.d.ts.map +1 -0
  68. package/dist/password-session.js +242 -0
  69. package/dist/password-session.js.map +1 -0
  70. package/dist/util.d.ts +5 -0
  71. package/dist/util.d.ts.map +1 -0
  72. package/dist/util.js +46 -0
  73. package/dist/util.js.map +1 -0
  74. package/package.json +52 -0
  75. package/src/error.ts +14 -0
  76. package/src/index.ts +2 -0
  77. package/src/lexicons/com/atproto/server/createAccount.defs.ts +56 -0
  78. package/src/lexicons/com/atproto/server/createAccount.ts +6 -0
  79. package/src/lexicons/com/atproto/server/createSession.defs.ts +48 -0
  80. package/src/lexicons/com/atproto/server/createSession.ts +6 -0
  81. package/src/lexicons/com/atproto/server/deleteSession.defs.ts +32 -0
  82. package/src/lexicons/com/atproto/server/deleteSession.ts +6 -0
  83. package/src/lexicons/com/atproto/server/getSession.defs.ts +36 -0
  84. package/src/lexicons/com/atproto/server/getSession.ts +6 -0
  85. package/src/lexicons/com/atproto/server/refreshSession.defs.ts +43 -0
  86. package/src/lexicons/com/atproto/server/refreshSession.ts +6 -0
  87. package/src/lexicons/com/atproto/server.ts +9 -0
  88. package/src/lexicons/com/atproto.ts +5 -0
  89. package/src/lexicons/com.ts +5 -0
  90. package/src/lexicons/index.ts +5 -0
  91. package/src/password-session-utils.test.ts +177 -0
  92. package/src/password-session.test.ts +416 -0
  93. package/src/password-session.ts +404 -0
  94. package/src/util.ts +61 -0
  95. package/tsconfig.build.json +12 -0
  96. package/tsconfig.json +7 -0
  97. package/tsconfig.tests.json +9 -0
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PasswordSession = void 0;
4
+ const lex_client_1 = require("@atproto/lex-client");
5
+ const error_js_1 = require("./error.js");
6
+ const index_js_1 = require("./lexicons/index.js");
7
+ const util_js_1 = require("./util.js");
8
+ class PasswordSession {
9
+ options;
10
+ /**
11
+ * Internal {@link Agent} used for session management towards the
12
+ * authentication service only.
13
+ */
14
+ #serviceAgent;
15
+ #sessionData;
16
+ #sessionPromise;
17
+ constructor(sessionData, options) {
18
+ this.options = options;
19
+ this.#serviceAgent = (0, lex_client_1.buildAgent)({
20
+ service: sessionData.service,
21
+ fetch: options.fetch,
22
+ });
23
+ this.#sessionData = sessionData;
24
+ this.#sessionPromise = Promise.resolve(this.#sessionData);
25
+ }
26
+ get did() {
27
+ return this.session.did;
28
+ }
29
+ get handle() {
30
+ return this.session.handle;
31
+ }
32
+ get session() {
33
+ if (this.#sessionData)
34
+ return this.#sessionData;
35
+ throw new lex_client_1.LexRpcError('AuthenticationRequired', 'Logged out');
36
+ }
37
+ get destroyed() {
38
+ return this.#sessionData === null;
39
+ }
40
+ async fetchHandler(path, init) {
41
+ const headers = new Headers(init.headers);
42
+ if (headers.has('authorization')) {
43
+ throw new TypeError("Unexpected 'authorization' header set");
44
+ }
45
+ const sessionPromise = this.#sessionPromise;
46
+ const sessionData = await sessionPromise;
47
+ const fetch = this.options.fetch ?? globalThis.fetch;
48
+ headers.set('authorization', `Bearer ${sessionData.accessJwt}`);
49
+ const initialRes = await fetch(fetchUrl(sessionData, path), {
50
+ ...init,
51
+ headers,
52
+ });
53
+ const refreshNeeded = initialRes.status === 401 ||
54
+ (initialRes.status === 400 &&
55
+ (await (0, util_js_1.extractLexRpcErrorCode)(initialRes)) === 'ExpiredToken');
56
+ if (!refreshNeeded) {
57
+ return initialRes;
58
+ }
59
+ // Refresh session (unless it was already refreshed in the meantime)
60
+ const newSessionPromise = this.#sessionPromise === sessionPromise
61
+ ? this.refresh()
62
+ : this.#sessionPromise;
63
+ // Error should have been propagated through hooks
64
+ const newSessionData = await newSessionPromise.catch((_err) => null);
65
+ if (!newSessionData) {
66
+ return initialRes;
67
+ }
68
+ // refresh silently failed, no point in retrying.
69
+ if (newSessionData.accessJwt === sessionData.accessJwt) {
70
+ return initialRes;
71
+ }
72
+ if (init?.signal?.aborted) {
73
+ return initialRes;
74
+ }
75
+ // The stream was already consumed. We cannot retry the request. A solution
76
+ // would be to tee() the input stream but that would bufferize the entire
77
+ // stream in memory which can lead to memory starvation. Instead, we will
78
+ // return the original response and let the calling code handle retries.
79
+ if (ReadableStream && init?.body instanceof ReadableStream) {
80
+ return initialRes;
81
+ }
82
+ // Make sure the initial request is cancelled to avoid leaking resources
83
+ // (NodeJS 👀): https://undici.nodejs.org/#/?id=garbage-collection
84
+ if (!initialRes.bodyUsed) {
85
+ await initialRes.body?.cancel();
86
+ }
87
+ // Finally, retry the request with the new access token
88
+ headers.set('authorization', `Bearer ${newSessionData.accessJwt}`);
89
+ return fetch(fetchUrl(newSessionData, path), { ...init, headers });
90
+ }
91
+ async refresh() {
92
+ this.#sessionPromise = this.#sessionPromise.then(async (sessionData) => {
93
+ const response = await (0, lex_client_1.xrpcSafe)(this.#serviceAgent, index_js_1.com.atproto.server.refreshSession.main, { headers: { Authorization: `Bearer ${sessionData.refreshJwt}` } });
94
+ if (!response.success && response.matchesSchema()) {
95
+ // Expected errors that indicate the session is no longer valid
96
+ await this.options.onDeleted.call(this, sessionData);
97
+ // Update the session promise to a rejected state
98
+ this.#sessionData = null;
99
+ throw response;
100
+ }
101
+ if (!response.success) {
102
+ // We failed to refresh the token, assume the session might still be
103
+ // valid by returning the existing session.
104
+ await this.options.onUpdateFailure?.call(this, sessionData, response);
105
+ return sessionData;
106
+ }
107
+ const data = response.body;
108
+ // Historically, refreshSession did not return all the fields from
109
+ // getSession. In particular, emailConfirmed and didDoc were missing.
110
+ // Similarly, some servers might not return the didDoc in refreshSession.
111
+ // We fetch them via getSession if missing, allowing to ensure that we are
112
+ // always talking with the right PDS.
113
+ if (data.emailConfirmed == null || data.didDoc == null) {
114
+ const extraData = await (0, lex_client_1.xrpcSafe)(this.#serviceAgent, index_js_1.com.atproto.server.getSession.main, { headers: { Authorization: `Bearer ${data.accessJwt}` } });
115
+ if (extraData.success && extraData.body.did === data.did) {
116
+ Object.assign(data, extraData.body);
117
+ }
118
+ }
119
+ const newSession = {
120
+ ...data,
121
+ service: sessionData.service,
122
+ };
123
+ await this.options.onUpdated.call(this, newSession);
124
+ return (this.#sessionData = newSession);
125
+ });
126
+ return this.#sessionPromise;
127
+ }
128
+ async logout() {
129
+ let reason = null;
130
+ this.#sessionPromise = this.#sessionPromise.then(async (sessionData) => {
131
+ const result = await (0, lex_client_1.xrpcSafe)(this.#serviceAgent, index_js_1.com.atproto.server.deleteSession.main, { headers: { Authorization: `Bearer ${sessionData.refreshJwt}` } });
132
+ if (result.success || result.matchesSchema()) {
133
+ await this.options.onDeleted.call(this, sessionData);
134
+ // Update the session promise to a rejected state
135
+ this.#sessionData = null;
136
+ throw new lex_client_1.LexRpcError('AuthenticationRequired', 'Logged out');
137
+ }
138
+ else {
139
+ // Capture the reason for the failure to re-throw in the outer promise
140
+ reason = result;
141
+ // An unknown/unexpected error occurred (network, server down, etc)
142
+ await this.options.onDeleteFailure?.call(this, sessionData, result);
143
+ // Keep the session in an active state
144
+ return sessionData;
145
+ }
146
+ });
147
+ return this.#sessionPromise.then((_session) => {
148
+ // If the promise above resolved, then logout failed. Re-throw the
149
+ // reason captured earlier.
150
+ throw reason;
151
+ }, (_err) => {
152
+ // Successful logout
153
+ });
154
+ }
155
+ /**
156
+ * @note It is **not** recommended to use {@link PasswordSession} with main
157
+ * account credentials. Instead, it is strongly advised to use OAuth based
158
+ * authentication for main username/password credentials and use
159
+ * {@link PasswordSession} with an app-password, for bots, scripts, or similar
160
+ * use-cases.
161
+ *
162
+ * @throws If unable to create a session. In particular, if the server
163
+ * requires a 2FA token, a {@link LexRpcResponseError} with the
164
+ * `AuthFactorTokenRequired` error code will be thrown.
165
+ *
166
+ *
167
+ * @example Handling 2FA errors
168
+ *
169
+ * ```ts
170
+ * try {
171
+ * const session = await PasswordSession.create({
172
+ * service: 'https://example.com',
173
+ * identifier: 'alice',
174
+ * password: 'correct horse battery staple',
175
+ * })
176
+ * } catch (err) {
177
+ * if (err instanceof LexRpcResponseError && err.error === 'AuthFactorTokenRequired') {
178
+ * // Prompt user for 2FA token and re-attempt session creation
179
+ * }
180
+ * }
181
+ * ```
182
+ */
183
+ static async create({ service, identifier, password, allowTakendown, authFactorToken, ...options }) {
184
+ const xrpcAgent = (0, lex_client_1.buildAgent)({
185
+ service,
186
+ fetch: options.fetch,
187
+ });
188
+ const response = await (0, lex_client_1.xrpcSafe)(xrpcAgent, index_js_1.com.atproto.server.createSession.main, { body: { identifier, password, allowTakendown, authFactorToken } });
189
+ if (!response.success) {
190
+ if (response.error === 'AuthFactorTokenRequired') {
191
+ throw new error_js_1.LexAuthFactorError(response);
192
+ }
193
+ throw response.reason;
194
+ }
195
+ const data = {
196
+ ...response.body,
197
+ service: String(service),
198
+ };
199
+ const agent = new PasswordSession(data, options);
200
+ await options.onUpdated.call(agent, data);
201
+ return agent;
202
+ }
203
+ /**
204
+ * Resume an existing session, ensuring it is still valid by refreshing it.
205
+ * Any error thrown here indicates that the session is definitely no longer
206
+ * valid. Network errors will be propagated through the
207
+ * {@link PasswordSessionOptions.onUpdateFailure} hook, and not re-thrown
208
+ * here. This means that a resolved promise does not necessarily indicate a
209
+ * valid session, only that it's refresh did not definitively fail.
210
+ *
211
+ * This is the same as calling {@link PasswordSession.refresh} after
212
+ * constructing the {@link PasswordSession} manually.
213
+ *
214
+ * @throws If, and only if, the session is definitely no longer valid.
215
+ */
216
+ static async resume(data, options) {
217
+ const agent = new PasswordSession(data, options);
218
+ await agent.refresh();
219
+ return agent;
220
+ }
221
+ /**
222
+ * Delete a session without having to {@link resume resume()} it first, or
223
+ * provide hooks.
224
+ *
225
+ * @throws In case of unexpected error (network issue, server down, etc)
226
+ * meaning that the session may still be valid.
227
+ */
228
+ static async delete(data, options) {
229
+ const agent = new PasswordSession(data, {
230
+ ...options,
231
+ onUpdated: options?.onUpdated ?? util_js_1.noop,
232
+ onDeleted: options?.onDeleted ?? util_js_1.noop,
233
+ });
234
+ await agent.logout();
235
+ }
236
+ }
237
+ exports.PasswordSession = PasswordSession;
238
+ function fetchUrl(sessionData, path) {
239
+ const pdsUrl = (0, util_js_1.extractPdsUrl)(sessionData.didDoc);
240
+ return new URL(path, pdsUrl ?? sessionData.service);
241
+ }
242
+ //# sourceMappingURL=password-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-session.js","sourceRoot":"","sources":["../src/password-session.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Agent,\n LexRpcError,\n LexRpcFailure,\n buildAgent,\n xrpcSafe,\n} from '@atproto/lex-client'\nimport { LexAuthFactorError } from './error.js'\nimport { com } from './lexicons/index.js'\nimport { extractLexRpcErrorCode, extractPdsUrl, noop } from './util.js'\n\nexport type RefreshFailure = LexRpcFailure<\n typeof com.atproto.server.refreshSession.main\n>\n\nexport type DeleteFailure = LexRpcFailure<\n typeof com.atproto.server.deleteSession.main\n>\n\nexport type SessionData = com.atproto.server.createSession.OutputBody & {\n service: string\n}\n\nexport type PasswordSessionOptions = {\n /**\n * Custom fetch implementation to use for network requests\n */\n fetch?: typeof globalThis.fetch\n\n /**\n * Called whenever the session is successfully created/refreshed, and new\n * credentials have been obtained. Use this hook to persist the updated\n * session information.\n *\n * If this callback returns a promise, this function will never be called\n * again (on the same process) until the promise resolves.\n *\n * @note this function **must** not throw\n */\n onUpdated: (this: PasswordSession, data: SessionData) => void | Promise<void>\n\n /**\n * Called whenever the session update fails due to an expected error, such as\n * a network issue or server unavailability. This function can be used to log\n * the error or notify the user, but should not assume that the session is\n * invalid.\n *\n * @note this function **must** not throw\n */\n onUpdateFailure?: (\n this: PasswordSession,\n data: SessionData,\n err: RefreshFailure,\n ) => void | Promise<void>\n\n /**\n * Called whenever the session is deleted, either due to an explicit logout or\n * because the refresh operation indicated that the session is no longer\n * valid. Use this hook to clean up any persisted session information and\n * update the application state accordingly.\n *\n * @note this function **must** not throw\n */\n onDeleted: (this: PasswordSession, data: SessionData) => void | Promise<void>\n\n /**\n * Called whenever a session deletion fails due to an unexpected error, such\n * as a network issue or server unavailability. This function can be used to\n * log the error or notify the user. When this function is called, the session\n * might still be valid on the server. It is up to the implementation to\n * decide whether to retry the deletion or keep the session active. Ignoring\n * these errors is not recommended as it can lead to orphaned sessions on the\n * server, or security issues if the user believes they have logged out when a\n * bad actor is still using the session. The implementation should consider\n * keeping track of failed deletions and retrying them later, until they\n * succeed.\n *\n * @note this function **must** not throw\n */\n onDeleteFailure?: (\n this: PasswordSession,\n data: SessionData,\n err: DeleteFailure,\n ) => void | Promise<void>\n}\n\nexport class PasswordSession implements Agent {\n /**\n * Internal {@link Agent} used for session management towards the\n * authentication service only.\n */\n #serviceAgent: Agent\n\n #sessionData: null | SessionData\n #sessionPromise: Promise<SessionData>\n\n constructor(\n sessionData: SessionData,\n protected readonly options: PasswordSessionOptions,\n ) {\n this.#serviceAgent = buildAgent({\n service: sessionData.service,\n fetch: options.fetch,\n })\n\n this.#sessionData = sessionData\n this.#sessionPromise = Promise.resolve(this.#sessionData)\n }\n\n get did() {\n return this.session.did\n }\n\n get handle() {\n return this.session.handle\n }\n\n get session() {\n if (this.#sessionData) return this.#sessionData\n throw new LexRpcError('AuthenticationRequired', 'Logged out')\n }\n\n get destroyed(): boolean {\n return this.#sessionData === null\n }\n\n async fetchHandler(path: string, init: RequestInit): Promise<Response> {\n const headers = new Headers(init.headers)\n if (headers.has('authorization')) {\n throw new TypeError(\"Unexpected 'authorization' header set\")\n }\n\n const sessionPromise = this.#sessionPromise\n const sessionData = await sessionPromise\n\n const fetch = this.options.fetch ?? globalThis.fetch\n\n headers.set('authorization', `Bearer ${sessionData.accessJwt}`)\n const initialRes = await fetch(fetchUrl(sessionData, path), {\n ...init,\n headers,\n })\n\n const refreshNeeded =\n initialRes.status === 401 ||\n (initialRes.status === 400 &&\n (await extractLexRpcErrorCode(initialRes)) === 'ExpiredToken')\n\n if (!refreshNeeded) {\n return initialRes\n }\n\n // Refresh session (unless it was already refreshed in the meantime)\n const newSessionPromise =\n this.#sessionPromise === sessionPromise\n ? this.refresh()\n : this.#sessionPromise\n\n // Error should have been propagated through hooks\n const newSessionData = await newSessionPromise.catch((_err) => null)\n if (!newSessionData) {\n return initialRes\n }\n\n // refresh silently failed, no point in retrying.\n if (newSessionData.accessJwt === sessionData.accessJwt) {\n return initialRes\n }\n\n if (init?.signal?.aborted) {\n return initialRes\n }\n\n // The stream was already consumed. We cannot retry the request. A solution\n // would be to tee() the input stream but that would bufferize the entire\n // stream in memory which can lead to memory starvation. Instead, we will\n // return the original response and let the calling code handle retries.\n if (ReadableStream && init?.body instanceof ReadableStream) {\n return initialRes\n }\n\n // Make sure the initial request is cancelled to avoid leaking resources\n // (NodeJS 👀): https://undici.nodejs.org/#/?id=garbage-collection\n if (!initialRes.bodyUsed) {\n await initialRes.body?.cancel()\n }\n\n // Finally, retry the request with the new access token\n headers.set('authorization', `Bearer ${newSessionData.accessJwt}`)\n return fetch(fetchUrl(newSessionData, path), { ...init, headers })\n }\n\n async refresh(): Promise<SessionData> {\n this.#sessionPromise = this.#sessionPromise.then(async (sessionData) => {\n const response = await xrpcSafe(\n this.#serviceAgent,\n com.atproto.server.refreshSession.main,\n { headers: { Authorization: `Bearer ${sessionData.refreshJwt}` } },\n )\n\n if (!response.success && response.matchesSchema()) {\n // Expected errors that indicate the session is no longer valid\n await this.options.onDeleted.call(this, sessionData)\n\n // Update the session promise to a rejected state\n this.#sessionData = null\n throw response\n }\n\n if (!response.success) {\n // We failed to refresh the token, assume the session might still be\n // valid by returning the existing session.\n await this.options.onUpdateFailure?.call(this, sessionData, response)\n\n return sessionData\n }\n\n const data = response.body\n\n // Historically, refreshSession did not return all the fields from\n // getSession. In particular, emailConfirmed and didDoc were missing.\n // Similarly, some servers might not return the didDoc in refreshSession.\n // We fetch them via getSession if missing, allowing to ensure that we are\n // always talking with the right PDS.\n if (data.emailConfirmed == null || data.didDoc == null) {\n const extraData = await xrpcSafe(\n this.#serviceAgent,\n com.atproto.server.getSession.main,\n { headers: { Authorization: `Bearer ${data.accessJwt}` } },\n )\n if (extraData.success && extraData.body.did === data.did) {\n Object.assign(data, extraData.body)\n }\n }\n\n const newSession: SessionData = {\n ...data,\n service: sessionData.service,\n }\n\n await this.options.onUpdated.call(this, newSession)\n\n return (this.#sessionData = newSession)\n })\n\n return this.#sessionPromise\n }\n\n async logout(): Promise<void> {\n let reason: DeleteFailure | null = null\n\n this.#sessionPromise = this.#sessionPromise.then(async (sessionData) => {\n const result = await xrpcSafe(\n this.#serviceAgent,\n com.atproto.server.deleteSession.main,\n { headers: { Authorization: `Bearer ${sessionData.refreshJwt}` } },\n )\n\n if (result.success || result.matchesSchema()) {\n await this.options.onDeleted.call(this, sessionData)\n\n // Update the session promise to a rejected state\n this.#sessionData = null\n throw new LexRpcError('AuthenticationRequired', 'Logged out')\n } else {\n // Capture the reason for the failure to re-throw in the outer promise\n reason = result\n\n // An unknown/unexpected error occurred (network, server down, etc)\n await this.options.onDeleteFailure?.call(this, sessionData, result)\n\n // Keep the session in an active state\n return sessionData\n }\n })\n\n return this.#sessionPromise.then(\n (_session) => {\n // If the promise above resolved, then logout failed. Re-throw the\n // reason captured earlier.\n throw reason!\n },\n (_err) => {\n // Successful logout\n },\n )\n }\n\n /**\n * @note It is **not** recommended to use {@link PasswordSession} with main\n * account credentials. Instead, it is strongly advised to use OAuth based\n * authentication for main username/password credentials and use\n * {@link PasswordSession} with an app-password, for bots, scripts, or similar\n * use-cases.\n *\n * @throws If unable to create a session. In particular, if the server\n * requires a 2FA token, a {@link LexRpcResponseError} with the\n * `AuthFactorTokenRequired` error code will be thrown.\n *\n *\n * @example Handling 2FA errors\n *\n * ```ts\n * try {\n * const session = await PasswordSession.create({\n * service: 'https://example.com',\n * identifier: 'alice',\n * password: 'correct horse battery staple',\n * })\n * } catch (err) {\n * if (err instanceof LexRpcResponseError && err.error === 'AuthFactorTokenRequired') {\n * // Prompt user for 2FA token and re-attempt session creation\n * }\n * }\n * ```\n */\n static async create({\n service,\n identifier,\n password,\n allowTakendown,\n authFactorToken,\n ...options\n }: PasswordSessionOptions & {\n service: string | URL\n identifier: string\n password: string\n allowTakendown?: boolean\n authFactorToken?: string\n }): Promise<PasswordSession> {\n const xrpcAgent = buildAgent({\n service,\n fetch: options.fetch,\n })\n\n const response = await xrpcSafe(\n xrpcAgent,\n com.atproto.server.createSession.main,\n { body: { identifier, password, allowTakendown, authFactorToken } },\n )\n\n if (!response.success) {\n if (response.error === 'AuthFactorTokenRequired') {\n throw new LexAuthFactorError(response)\n }\n throw response.reason\n }\n\n const data: SessionData = {\n ...response.body,\n service: String(service),\n }\n\n const agent = new PasswordSession(data, options)\n await options.onUpdated.call(agent, data)\n return agent\n }\n\n /**\n * Resume an existing session, ensuring it is still valid by refreshing it.\n * Any error thrown here indicates that the session is definitely no longer\n * valid. Network errors will be propagated through the\n * {@link PasswordSessionOptions.onUpdateFailure} hook, and not re-thrown\n * here. This means that a resolved promise does not necessarily indicate a\n * valid session, only that it's refresh did not definitively fail.\n *\n * This is the same as calling {@link PasswordSession.refresh} after\n * constructing the {@link PasswordSession} manually.\n *\n * @throws If, and only if, the session is definitely no longer valid.\n */\n static async resume(\n data: SessionData,\n options: PasswordSessionOptions,\n ): Promise<PasswordSession> {\n const agent = new PasswordSession(data, options)\n await agent.refresh()\n return agent\n }\n\n /**\n * Delete a session without having to {@link resume resume()} it first, or\n * provide hooks.\n *\n * @throws In case of unexpected error (network issue, server down, etc)\n * meaning that the session may still be valid.\n */\n static async delete(\n data: SessionData,\n options?: Partial<PasswordSessionOptions>,\n ): Promise<void> {\n const agent = new PasswordSession(data, {\n ...options,\n onUpdated: options?.onUpdated ?? noop,\n onDeleted: options?.onDeleted ?? noop,\n })\n await agent.logout()\n }\n}\n\nfunction fetchUrl(sessionData: SessionData, path: string): URL {\n const pdsUrl = extractPdsUrl(sessionData.didDoc)\n return new URL(path, pdsUrl ?? sessionData.service)\n}\n"]}
package/dist/util.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { LexMap } from '@atproto/lex-client';
2
+ export declare const noop: () => void;
3
+ export declare function extractLexRpcErrorCode(response: Response): Promise<string | null>;
4
+ export declare function extractPdsUrl(didDoc?: LexMap): string | null;
5
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAY,MAAM,qBAAqB,CAAA;AAGtD,eAAO,MAAM,IAAI,YAAW,CAAA;AAE5B,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAKxB;AA4BD,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM5D"}
package/dist/util.js ADDED
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noop = void 0;
4
+ exports.extractLexRpcErrorCode = extractLexRpcErrorCode;
5
+ exports.extractPdsUrl = extractPdsUrl;
6
+ const lex_schema_1 = require("@atproto/lex-schema");
7
+ const noop = () => { };
8
+ exports.noop = noop;
9
+ async function extractLexRpcErrorCode(response) {
10
+ const json = await peekJson(response, 10 * 1024); // Avoid reading large bodies
11
+ if (json === undefined)
12
+ return null;
13
+ if (!lex_schema_1.l.lexErrorData.matches(json))
14
+ return null;
15
+ return json.error;
16
+ }
17
+ async function peekJson(response, maxSize = Infinity) {
18
+ const type = extractType(response);
19
+ if (type !== 'application/json')
20
+ return undefined;
21
+ const length = extractLength(response);
22
+ if (length != null && length > maxSize)
23
+ return undefined;
24
+ try {
25
+ return (await response.clone().json());
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ }
31
+ function extractLength({ headers }) {
32
+ return headers.get('Content-Length')
33
+ ? Number(headers.get('Content-Length'))
34
+ : undefined;
35
+ }
36
+ function extractType({ headers }) {
37
+ return headers.get('Content-Type')?.split(';')[0]?.trim().toLowerCase();
38
+ }
39
+ function extractPdsUrl(didDoc) {
40
+ const pdsService = ifArray(didDoc?.service)?.find((service) => ifString(service?.id)?.endsWith('#atproto_pds'));
41
+ const pdsEndpoint = ifString(pdsService?.serviceEndpoint);
42
+ return pdsEndpoint && URL.canParse(pdsEndpoint) ? pdsEndpoint : null;
43
+ }
44
+ const ifString = (v) => (typeof v === 'string' ? v : undefined);
45
+ const ifArray = (v) => (Array.isArray(v) ? v : undefined);
46
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAKA,wDAOC;AA4BD,sCAMC;AA7CD,oDAAuC;AAEhC,MAAM,IAAI,GAAG,GAAG,EAAE,GAAE,CAAC,CAAA;AAAf,QAAA,IAAI,QAAW;AAErB,KAAK,UAAU,sBAAsB,CAC1C,QAAkB;IAElB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,EAAE,GAAG,IAAI,CAAC,CAAA,CAAC,6BAA6B;IAC9E,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACnC,IAAI,CAAC,cAAC,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAC9C,OAAO,IAAI,CAAC,KAAK,CAAA;AACnB,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,QAAkB,EAClB,OAAO,GAAG,QAAQ;IAElB,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;IAClC,IAAI,IAAI,KAAK,kBAAkB;QAAE,OAAO,SAAS,CAAA;IACjD,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IACtC,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,GAAG,OAAO;QAAE,OAAO,SAAS,CAAA;IAExD,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAsB,CAAA;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,EAAE,OAAO,EAAY;IAC1C,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;QAClC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACvC,CAAC,CAAC,SAAS,CAAA;AACf,CAAC;AAED,SAAS,WAAW,CAAC,EAAE,OAAO,EAAY;IACxC,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;AACzE,CAAC;AAED,SAAgB,aAAa,CAAC,MAAe;IAC3C,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAC5D,QAAQ,CAAE,OAAe,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CACzD,CAAA;IACD,MAAM,WAAW,GAAG,QAAQ,CAAE,UAAkB,EAAE,eAAe,CAAC,CAAA;IAClE,OAAO,WAAW,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAA;AACtE,CAAC;AAED,MAAM,QAAQ,GAAG,CAAI,CAAI,EAAE,EAAE,CAC3B,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAIvB,CAAA;AAEjB,MAAM,OAAO,GAAG,CAAI,CAAI,EAAE,EAAE,CAC1B,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAIlB,CAAA","sourcesContent":["import { LexMap, LexValue } from '@atproto/lex-client'\nimport { l } from '@atproto/lex-schema'\n\nexport const noop = () => {}\n\nexport async function extractLexRpcErrorCode(\n response: Response,\n): Promise<string | null> {\n const json = await peekJson(response, 10 * 1024) // Avoid reading large bodies\n if (json === undefined) return null\n if (!l.lexErrorData.matches(json)) return null\n return json.error\n}\n\nasync function peekJson(\n response: Response,\n maxSize = Infinity,\n): Promise<undefined | LexValue> {\n const type = extractType(response)\n if (type !== 'application/json') return undefined\n const length = extractLength(response)\n if (length != null && length > maxSize) return undefined\n\n try {\n return (await response.clone().json()) as Promise<LexValue>\n } catch {\n return undefined\n }\n}\n\nfunction extractLength({ headers }: Response) {\n return headers.get('Content-Length')\n ? Number(headers.get('Content-Length'))\n : undefined\n}\n\nfunction extractType({ headers }: Response) {\n return headers.get('Content-Type')?.split(';')[0]?.trim().toLowerCase()\n}\n\nexport function extractPdsUrl(didDoc?: LexMap): string | null {\n const pdsService = ifArray(didDoc?.service)?.find((service) =>\n ifString((service as any)?.id)?.endsWith('#atproto_pds'),\n )\n const pdsEndpoint = ifString((pdsService as any)?.serviceEndpoint)\n return pdsEndpoint && URL.canParse(pdsEndpoint) ? pdsEndpoint : null\n}\n\nconst ifString = <T>(v: T) =>\n (typeof v === 'string' ? v : undefined) as unknown extends T\n ? undefined | string\n : T extends string\n ? string\n : undefined\n\nconst ifArray = <T>(v: T) =>\n (Array.isArray(v) ? v : undefined) as unknown extends T\n ? undefined | unknown[]\n : T extends unknown[]\n ? Extract<T, unknown[]>\n : undefined\n"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@atproto/lex-password-session",
3
+ "version": "0.0.0",
4
+ "license": "MIT",
5
+ "description": "Password based client authentication for AT Lexicons",
6
+ "keywords": [
7
+ "atproto",
8
+ "lexicon",
9
+ "utilities"
10
+ ],
11
+ "homepage": "https://atproto.com",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/bluesky-social/atproto",
15
+ "directory": "packages/lex/lex-password-session"
16
+ },
17
+ "files": [
18
+ "./src",
19
+ "./tsconfig.build.json",
20
+ "./tsconfig.tests.json",
21
+ "./tsconfig.json",
22
+ "./dist",
23
+ "./CHANGELOG.md"
24
+ ],
25
+ "sideEffects": false,
26
+ "type": "commonjs",
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "browser": "./dist/index.js",
33
+ "import": "./dist/index.js",
34
+ "require": "./dist/index.js"
35
+ }
36
+ },
37
+ "dependencies": {
38
+ "@atproto/lex-client": "workspace:*",
39
+ "@atproto/lex-schema": "workspace:*",
40
+ "tslib": "^2.8.1"
41
+ },
42
+ "devDependencies": {
43
+ "@atproto/lex-builder": "workspace:*",
44
+ "@atproto/lex-server": "workspace:*",
45
+ "vitest": "^4.0.16"
46
+ },
47
+ "scripts": {
48
+ "prebuild": "node ./scripts/lex-build.mjs",
49
+ "build": "tsc --build tsconfig.build.json",
50
+ "test": "vitest run"
51
+ }
52
+ }
package/src/error.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { LexError, LexRpcResponseError } from '@atproto/lex-client'
2
+ import { com } from './lexicons'
3
+
4
+ export class LexAuthFactorError extends LexError {
5
+ name = 'LexAuthFactorError'
6
+
7
+ constructor(
8
+ readonly response: LexRpcResponseError<
9
+ typeof com.atproto.server.createSession.main
10
+ >,
11
+ ) {
12
+ super(response.error, response.message, { cause: response.reason })
13
+ }
14
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './error.js'
2
+ export * from './password-session.js'
@@ -0,0 +1,56 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ import { l } from '@atproto/lex-schema'
6
+
7
+ const $nsid = 'com.atproto.server.createAccount'
8
+
9
+ export { $nsid }
10
+
11
+ /** Create an account. Implemented by PDS. */
12
+ const main =
13
+ /*#__PURE__*/
14
+ l.procedure(
15
+ $nsid,
16
+ /*#__PURE__*/ l.params(),
17
+ /*#__PURE__*/ l.jsonPayload({
18
+ email: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
19
+ handle: /*#__PURE__*/ l.string({ format: 'handle' }),
20
+ did: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string({ format: 'did' })),
21
+ inviteCode: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
22
+ verificationCode: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
23
+ verificationPhone: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
24
+ password: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
25
+ recoveryKey: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
26
+ plcOp: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.unknownObject()),
27
+ }),
28
+ /*#__PURE__*/ l.jsonPayload({
29
+ accessJwt: /*#__PURE__*/ l.string(),
30
+ refreshJwt: /*#__PURE__*/ l.string(),
31
+ handle: /*#__PURE__*/ l.string({ format: 'handle' }),
32
+ did: /*#__PURE__*/ l.string({ format: 'did' }),
33
+ didDoc: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.unknownObject()),
34
+ }),
35
+ [
36
+ 'InvalidHandle',
37
+ 'InvalidPassword',
38
+ 'InvalidInviteCode',
39
+ 'HandleNotAvailable',
40
+ 'UnsupportedDomain',
41
+ 'UnresolvableDid',
42
+ 'IncompatibleDidDoc',
43
+ ],
44
+ )
45
+ export { main }
46
+
47
+ export type Params = l.InferMethodParams<typeof main>
48
+ export type Input = l.InferMethodInput<typeof main>
49
+ export type InputBody = l.InferMethodInputBody<typeof main>
50
+ export type Output = l.InferMethodOutput<typeof main>
51
+ export type OutputBody = l.InferMethodOutputBody<typeof main>
52
+
53
+ export const $lxm = /*#__PURE__*/ main.nsid,
54
+ $params = /*#__PURE__*/ main.parameters,
55
+ $input = /*#__PURE__*/ main.input,
56
+ $output = /*#__PURE__*/ main.output
@@ -0,0 +1,6 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ export * from './createAccount.defs.js'
6
+ export * as $defs from './createAccount.defs.js'
@@ -0,0 +1,48 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ import { l } from '@atproto/lex-schema'
6
+
7
+ const $nsid = 'com.atproto.server.createSession'
8
+
9
+ export { $nsid }
10
+
11
+ /** Create an authentication session. */
12
+ const main =
13
+ /*#__PURE__*/
14
+ l.procedure(
15
+ $nsid,
16
+ /*#__PURE__*/ l.params(),
17
+ /*#__PURE__*/ l.jsonPayload({
18
+ identifier: /*#__PURE__*/ l.string(),
19
+ password: /*#__PURE__*/ l.string(),
20
+ authFactorToken: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
21
+ allowTakendown: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
22
+ }),
23
+ /*#__PURE__*/ l.jsonPayload({
24
+ accessJwt: /*#__PURE__*/ l.string(),
25
+ refreshJwt: /*#__PURE__*/ l.string(),
26
+ handle: /*#__PURE__*/ l.string({ format: 'handle' }),
27
+ did: /*#__PURE__*/ l.string({ format: 'did' }),
28
+ didDoc: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.unknownObject()),
29
+ email: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
30
+ emailConfirmed: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
31
+ emailAuthFactor: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
32
+ active: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
33
+ status: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
34
+ }),
35
+ ['AccountTakedown', 'AuthFactorTokenRequired'],
36
+ )
37
+ export { main }
38
+
39
+ export type Params = l.InferMethodParams<typeof main>
40
+ export type Input = l.InferMethodInput<typeof main>
41
+ export type InputBody = l.InferMethodInputBody<typeof main>
42
+ export type Output = l.InferMethodOutput<typeof main>
43
+ export type OutputBody = l.InferMethodOutputBody<typeof main>
44
+
45
+ export const $lxm = /*#__PURE__*/ main.nsid,
46
+ $params = /*#__PURE__*/ main.parameters,
47
+ $input = /*#__PURE__*/ main.input,
48
+ $output = /*#__PURE__*/ main.output
@@ -0,0 +1,6 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ export * from './createSession.defs.js'
6
+ export * as $defs from './createSession.defs.js'
@@ -0,0 +1,32 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ import { l } from '@atproto/lex-schema'
6
+
7
+ const $nsid = 'com.atproto.server.deleteSession'
8
+
9
+ export { $nsid }
10
+
11
+ /** Delete the current session. Requires auth using the 'refreshJwt' (not the 'accessJwt'). */
12
+ const main =
13
+ /*#__PURE__*/
14
+ l.procedure(
15
+ $nsid,
16
+ /*#__PURE__*/ l.params(),
17
+ /*#__PURE__*/ l.payload(),
18
+ /*#__PURE__*/ l.payload(),
19
+ ['InvalidToken', 'ExpiredToken'],
20
+ )
21
+ export { main }
22
+
23
+ export type Params = l.InferMethodParams<typeof main>
24
+ export type Input = l.InferMethodInput<typeof main>
25
+ export type InputBody = l.InferMethodInputBody<typeof main>
26
+ export type Output = l.InferMethodOutput<typeof main>
27
+ export type OutputBody = l.InferMethodOutputBody<typeof main>
28
+
29
+ export const $lxm = /*#__PURE__*/ main.nsid,
30
+ $params = /*#__PURE__*/ main.parameters,
31
+ $input = /*#__PURE__*/ main.input,
32
+ $output = /*#__PURE__*/ main.output
@@ -0,0 +1,6 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ export * from './deleteSession.defs.js'
6
+ export * as $defs from './deleteSession.defs.js'
@@ -0,0 +1,36 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ import { l } from '@atproto/lex-schema'
6
+
7
+ const $nsid = 'com.atproto.server.getSession'
8
+
9
+ export { $nsid }
10
+
11
+ /** Get information about the current auth session. Requires auth. */
12
+ const main =
13
+ /*#__PURE__*/
14
+ l.query(
15
+ $nsid,
16
+ /*#__PURE__*/ l.params(),
17
+ /*#__PURE__*/ l.jsonPayload({
18
+ handle: /*#__PURE__*/ l.string({ format: 'handle' }),
19
+ did: /*#__PURE__*/ l.string({ format: 'did' }),
20
+ didDoc: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.unknownObject()),
21
+ email: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
22
+ emailConfirmed: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
23
+ emailAuthFactor: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
24
+ active: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
25
+ status: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
26
+ }),
27
+ )
28
+ export { main }
29
+
30
+ export type Params = l.InferMethodParams<typeof main>
31
+ export type Output = l.InferMethodOutput<typeof main>
32
+ export type OutputBody = l.InferMethodOutputBody<typeof main>
33
+
34
+ export const $lxm = /*#__PURE__*/ main.nsid,
35
+ $params = main.parameters,
36
+ $output = main.output
@@ -0,0 +1,6 @@
1
+ /*
2
+ * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
3
+ */
4
+
5
+ export * from './getSession.defs.js'
6
+ export * as $defs from './getSession.defs.js'