@pawabase/client 0.0.0-stage → 0.1.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 (243) hide show
  1. package/README.md +313 -2
  2. package/dist/cjs/admin.d.ts +187 -0
  3. package/dist/cjs/admin.d.ts.map +1 -0
  4. package/dist/cjs/admin.js +235 -0
  5. package/dist/cjs/admin.js.map +1 -0
  6. package/dist/cjs/auth.d.ts +448 -0
  7. package/dist/cjs/auth.d.ts.map +1 -0
  8. package/dist/cjs/auth.js +1016 -0
  9. package/dist/cjs/auth.js.map +1 -0
  10. package/dist/cjs/client.d.ts +103 -0
  11. package/dist/cjs/client.d.ts.map +1 -0
  12. package/dist/cjs/client.js +144 -0
  13. package/dist/cjs/client.js.map +1 -0
  14. package/dist/cjs/data.d.ts +220 -0
  15. package/dist/cjs/data.d.ts.map +1 -0
  16. package/dist/cjs/data.js +490 -0
  17. package/dist/cjs/data.js.map +1 -0
  18. package/dist/cjs/errors.d.ts +167 -0
  19. package/dist/cjs/errors.d.ts.map +1 -0
  20. package/dist/cjs/errors.js +316 -0
  21. package/dist/cjs/errors.js.map +1 -0
  22. package/dist/cjs/files.d.ts +80 -0
  23. package/dist/cjs/files.d.ts.map +1 -0
  24. package/dist/cjs/files.js +167 -0
  25. package/dist/cjs/files.js.map +1 -0
  26. package/dist/cjs/functions.d.ts +66 -0
  27. package/dist/cjs/functions.d.ts.map +1 -0
  28. package/dist/cjs/functions.js +109 -0
  29. package/dist/cjs/functions.js.map +1 -0
  30. package/dist/cjs/http.d.ts +101 -0
  31. package/dist/cjs/http.d.ts.map +1 -0
  32. package/dist/cjs/http.js +249 -0
  33. package/dist/cjs/http.js.map +1 -0
  34. package/dist/cjs/index.d.ts +22 -0
  35. package/dist/cjs/index.d.ts.map +1 -0
  36. package/dist/cjs/index.js +45 -0
  37. package/dist/cjs/index.js.map +1 -0
  38. package/dist/cjs/manage/access.d.ts +136 -0
  39. package/dist/cjs/manage/access.d.ts.map +1 -0
  40. package/dist/cjs/manage/access.js +162 -0
  41. package/dist/cjs/manage/access.js.map +1 -0
  42. package/dist/cjs/manage/automation.d.ts +106 -0
  43. package/dist/cjs/manage/automation.d.ts.map +1 -0
  44. package/dist/cjs/manage/automation.js +144 -0
  45. package/dist/cjs/manage/automation.js.map +1 -0
  46. package/dist/cjs/manage/base.d.ts +52 -0
  47. package/dist/cjs/manage/base.d.ts.map +1 -0
  48. package/dist/cjs/manage/base.js +69 -0
  49. package/dist/cjs/manage/base.js.map +1 -0
  50. package/dist/cjs/manage/data.d.ts +100 -0
  51. package/dist/cjs/manage/data.d.ts.map +1 -0
  52. package/dist/cjs/manage/data.js +141 -0
  53. package/dist/cjs/manage/data.js.map +1 -0
  54. package/dist/cjs/manage/files.d.ts +45 -0
  55. package/dist/cjs/manage/files.d.ts.map +1 -0
  56. package/dist/cjs/manage/files.js +73 -0
  57. package/dist/cjs/manage/files.js.map +1 -0
  58. package/dist/cjs/manage/functions.d.ts +48 -0
  59. package/dist/cjs/manage/functions.d.ts.map +1 -0
  60. package/dist/cjs/manage/functions.js +61 -0
  61. package/dist/cjs/manage/functions.js.map +1 -0
  62. package/dist/cjs/manage/index.d.ts +76 -0
  63. package/dist/cjs/manage/index.d.ts.map +1 -0
  64. package/dist/cjs/manage/index.js +134 -0
  65. package/dist/cjs/manage/index.js.map +1 -0
  66. package/dist/cjs/manage/lifecycle.d.ts +128 -0
  67. package/dist/cjs/manage/lifecycle.d.ts.map +1 -0
  68. package/dist/cjs/manage/lifecycle.js +167 -0
  69. package/dist/cjs/manage/lifecycle.js.map +1 -0
  70. package/dist/cjs/manage/mail.d.ts +16 -0
  71. package/dist/cjs/manage/mail.d.ts.map +1 -0
  72. package/dist/cjs/manage/mail.js +24 -0
  73. package/dist/cjs/manage/mail.js.map +1 -0
  74. package/dist/cjs/manage/observe.d.ts +149 -0
  75. package/dist/cjs/manage/observe.d.ts.map +1 -0
  76. package/dist/cjs/manage/observe.js +168 -0
  77. package/dist/cjs/manage/observe.js.map +1 -0
  78. package/dist/cjs/manage/realtime.d.ts +52 -0
  79. package/dist/cjs/manage/realtime.d.ts.map +1 -0
  80. package/dist/cjs/manage/realtime.js +71 -0
  81. package/dist/cjs/manage/realtime.js.map +1 -0
  82. package/dist/cjs/package.json +1 -0
  83. package/dist/cjs/realtime.d.ts +207 -0
  84. package/dist/cjs/realtime.d.ts.map +1 -0
  85. package/dist/cjs/realtime.js +704 -0
  86. package/dist/cjs/realtime.js.map +1 -0
  87. package/dist/cjs/session-store.d.ts +16 -0
  88. package/dist/cjs/session-store.d.ts.map +1 -0
  89. package/dist/cjs/session-store.js +37 -0
  90. package/dist/cjs/session-store.js.map +1 -0
  91. package/dist/cjs/typegen.d.ts +26 -0
  92. package/dist/cjs/typegen.d.ts.map +1 -0
  93. package/dist/cjs/typegen.js +167 -0
  94. package/dist/cjs/typegen.js.map +1 -0
  95. package/dist/cjs/types.d.ts +178 -0
  96. package/dist/cjs/types.d.ts.map +1 -0
  97. package/dist/cjs/types.js +4 -0
  98. package/dist/cjs/types.js.map +1 -0
  99. package/dist/cjs/util.d.ts +27 -0
  100. package/dist/cjs/util.d.ts.map +1 -0
  101. package/dist/cjs/util.js +115 -0
  102. package/dist/cjs/util.js.map +1 -0
  103. package/dist/cjs/version.d.ts +2 -0
  104. package/dist/cjs/version.d.ts.map +1 -0
  105. package/dist/cjs/version.js +5 -0
  106. package/dist/cjs/version.js.map +1 -0
  107. package/dist/esm/admin.d.ts +187 -0
  108. package/dist/esm/admin.d.ts.map +1 -0
  109. package/dist/esm/admin.js +222 -0
  110. package/dist/esm/admin.js.map +1 -0
  111. package/dist/esm/auth.d.ts +448 -0
  112. package/dist/esm/auth.d.ts.map +1 -0
  113. package/dist/esm/auth.js +1012 -0
  114. package/dist/esm/auth.js.map +1 -0
  115. package/dist/esm/cli/gen-types.d.ts +2 -0
  116. package/dist/esm/cli/gen-types.d.ts.map +1 -0
  117. package/dist/esm/cli/gen-types.js +100 -0
  118. package/dist/esm/cli/gen-types.js.map +1 -0
  119. package/dist/esm/client.d.ts +103 -0
  120. package/dist/esm/client.d.ts.map +1 -0
  121. package/dist/esm/client.js +139 -0
  122. package/dist/esm/client.js.map +1 -0
  123. package/dist/esm/data.d.ts +220 -0
  124. package/dist/esm/data.d.ts.map +1 -0
  125. package/dist/esm/data.js +485 -0
  126. package/dist/esm/data.js.map +1 -0
  127. package/dist/esm/errors.d.ts +167 -0
  128. package/dist/esm/errors.d.ts.map +1 -0
  129. package/dist/esm/errors.js +293 -0
  130. package/dist/esm/errors.js.map +1 -0
  131. package/dist/esm/files.d.ts +80 -0
  132. package/dist/esm/files.d.ts.map +1 -0
  133. package/dist/esm/files.js +162 -0
  134. package/dist/esm/files.js.map +1 -0
  135. package/dist/esm/functions.d.ts +66 -0
  136. package/dist/esm/functions.d.ts.map +1 -0
  137. package/dist/esm/functions.js +103 -0
  138. package/dist/esm/functions.js.map +1 -0
  139. package/dist/esm/http.d.ts +101 -0
  140. package/dist/esm/http.d.ts.map +1 -0
  141. package/dist/esm/http.js +245 -0
  142. package/dist/esm/http.js.map +1 -0
  143. package/dist/esm/index.d.ts +22 -0
  144. package/dist/esm/index.d.ts.map +1 -0
  145. package/dist/esm/index.js +12 -0
  146. package/dist/esm/index.js.map +1 -0
  147. package/dist/esm/manage/access.d.ts +136 -0
  148. package/dist/esm/manage/access.d.ts.map +1 -0
  149. package/dist/esm/manage/access.js +155 -0
  150. package/dist/esm/manage/access.js.map +1 -0
  151. package/dist/esm/manage/automation.d.ts +106 -0
  152. package/dist/esm/manage/automation.d.ts.map +1 -0
  153. package/dist/esm/manage/automation.js +134 -0
  154. package/dist/esm/manage/automation.js.map +1 -0
  155. package/dist/esm/manage/base.d.ts +52 -0
  156. package/dist/esm/manage/base.d.ts.map +1 -0
  157. package/dist/esm/manage/base.js +63 -0
  158. package/dist/esm/manage/base.js.map +1 -0
  159. package/dist/esm/manage/data.d.ts +100 -0
  160. package/dist/esm/manage/data.d.ts.map +1 -0
  161. package/dist/esm/manage/data.js +130 -0
  162. package/dist/esm/manage/data.js.map +1 -0
  163. package/dist/esm/manage/files.d.ts +45 -0
  164. package/dist/esm/manage/files.d.ts.map +1 -0
  165. package/dist/esm/manage/files.js +68 -0
  166. package/dist/esm/manage/files.js.map +1 -0
  167. package/dist/esm/manage/functions.d.ts +48 -0
  168. package/dist/esm/manage/functions.d.ts.map +1 -0
  169. package/dist/esm/manage/functions.js +57 -0
  170. package/dist/esm/manage/functions.js.map +1 -0
  171. package/dist/esm/manage/index.d.ts +76 -0
  172. package/dist/esm/manage/index.d.ts.map +1 -0
  173. package/dist/esm/manage/index.js +115 -0
  174. package/dist/esm/manage/index.js.map +1 -0
  175. package/dist/esm/manage/lifecycle.d.ts +128 -0
  176. package/dist/esm/manage/lifecycle.d.ts.map +1 -0
  177. package/dist/esm/manage/lifecycle.js +161 -0
  178. package/dist/esm/manage/lifecycle.js.map +1 -0
  179. package/dist/esm/manage/mail.d.ts +16 -0
  180. package/dist/esm/manage/mail.d.ts.map +1 -0
  181. package/dist/esm/manage/mail.js +19 -0
  182. package/dist/esm/manage/mail.js.map +1 -0
  183. package/dist/esm/manage/observe.d.ts +149 -0
  184. package/dist/esm/manage/observe.d.ts.map +1 -0
  185. package/dist/esm/manage/observe.js +161 -0
  186. package/dist/esm/manage/observe.js.map +1 -0
  187. package/dist/esm/manage/realtime.d.ts +52 -0
  188. package/dist/esm/manage/realtime.d.ts.map +1 -0
  189. package/dist/esm/manage/realtime.js +66 -0
  190. package/dist/esm/manage/realtime.js.map +1 -0
  191. package/dist/esm/package.json +1 -0
  192. package/dist/esm/realtime.d.ts +207 -0
  193. package/dist/esm/realtime.d.ts.map +1 -0
  194. package/dist/esm/realtime.js +699 -0
  195. package/dist/esm/realtime.js.map +1 -0
  196. package/dist/esm/session-store.d.ts +16 -0
  197. package/dist/esm/session-store.d.ts.map +1 -0
  198. package/dist/esm/session-store.js +32 -0
  199. package/dist/esm/session-store.js.map +1 -0
  200. package/dist/esm/typegen.d.ts +26 -0
  201. package/dist/esm/typegen.d.ts.map +1 -0
  202. package/dist/esm/typegen.js +163 -0
  203. package/dist/esm/typegen.js.map +1 -0
  204. package/dist/esm/types.d.ts +178 -0
  205. package/dist/esm/types.d.ts.map +1 -0
  206. package/dist/esm/types.js +3 -0
  207. package/dist/esm/types.js.map +1 -0
  208. package/dist/esm/util.d.ts +27 -0
  209. package/dist/esm/util.d.ts.map +1 -0
  210. package/dist/esm/util.js +102 -0
  211. package/dist/esm/util.js.map +1 -0
  212. package/dist/esm/version.d.ts +2 -0
  213. package/dist/esm/version.d.ts.map +1 -0
  214. package/dist/esm/version.js +2 -0
  215. package/dist/esm/version.js.map +1 -0
  216. package/package.json +84 -4
  217. package/src/admin.ts +311 -0
  218. package/src/auth.ts +1180 -0
  219. package/src/cli/gen-types.ts +105 -0
  220. package/src/client.ts +189 -0
  221. package/src/data.ts +576 -0
  222. package/src/errors.ts +330 -0
  223. package/src/files.ts +208 -0
  224. package/src/functions.ts +131 -0
  225. package/src/http.ts +354 -0
  226. package/src/index.ts +56 -0
  227. package/src/manage/access.ts +206 -0
  228. package/src/manage/automation.ts +162 -0
  229. package/src/manage/base.ts +83 -0
  230. package/src/manage/data.ts +159 -0
  231. package/src/manage/files.ts +79 -0
  232. package/src/manage/functions.ts +69 -0
  233. package/src/manage/index.ts +132 -0
  234. package/src/manage/lifecycle.ts +208 -0
  235. package/src/manage/mail.ts +22 -0
  236. package/src/manage/observe.ts +226 -0
  237. package/src/manage/realtime.ts +95 -0
  238. package/src/realtime.ts +765 -0
  239. package/src/session-store.ts +36 -0
  240. package/src/typegen.ts +192 -0
  241. package/src/types.ts +195 -0
  242. package/src/util.ts +114 -0
  243. package/src/version.ts +1 -0
@@ -0,0 +1,1016 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthClient = void 0;
4
+ const errors_js_1 = require("./errors.js");
5
+ const session_store_js_1 = require("./session-store.js");
6
+ const util_js_1 = require("./util.js");
7
+ const LOCK_PREFIX = "pawabase-auth-refresh:";
8
+ const FATAL_REFRESH = new Set([400, 401, 403]);
9
+ function storageKeyFor(baseUrl, apiKey) {
10
+ return `pawabase.auth.${baseUrl.replace(/^https?:\/\//, "")}.${apiKey.slice(0, 14)}`;
11
+ }
12
+ function toSession(wire, user, fallback) {
13
+ const issued = (0, util_js_1.nowSeconds)();
14
+ const resolved = user ?? wire.user ?? fallback?.user;
15
+ if (!resolved)
16
+ throw new errors_js_1.PawabaseError("The server returned a session without a user.");
17
+ return {
18
+ access_token: wire.access_token,
19
+ refresh_token: wire.refresh_token,
20
+ token_type: "bearer",
21
+ expires_in: wire.expires_in,
22
+ expires_at: wire.expires_at ?? issued + wire.expires_in,
23
+ ...(wire.session_id ? { session_id: wire.session_id } : {}),
24
+ org: wire.org ?? null,
25
+ org_role: wire.org_role ?? null,
26
+ user: resolved,
27
+ };
28
+ }
29
+ function validSession(value) {
30
+ return ((0, util_js_1.isRecord)(value) &&
31
+ typeof value["access_token"] === "string" &&
32
+ typeof value["refresh_token"] === "string" &&
33
+ typeof value["expires_at"] === "number" &&
34
+ (0, util_js_1.isRecord)(value["user"]));
35
+ }
36
+ /**
37
+ * Sign-in, session lifecycle and everything about the signed-in person.
38
+ *
39
+ * The session is kept in storage, refreshed before it expires, and refreshed only
40
+ * once at a time: Akountz rotates refresh tokens and treats a reused one as theft,
41
+ * so two refreshes racing (two requests, or two tabs) would sign the user out.
42
+ * Concurrent callers share one refresh; across tabs the Web Locks API serialises it.
43
+ */
44
+ class AuthClient {
45
+ http;
46
+ options;
47
+ session = null;
48
+ listeners = new Set();
49
+ storage;
50
+ persist;
51
+ storageKey;
52
+ autoRefresh;
53
+ margin;
54
+ timer = null;
55
+ inflight = null;
56
+ ready;
57
+ disposers = [];
58
+ disposed = false;
59
+ mfa;
60
+ sessions;
61
+ orgs;
62
+ /** Browsers and apps the user has signed in from. */
63
+ devices;
64
+ /** Keys a user makes for themselves to call the API as themselves. */
65
+ apiKeys;
66
+ /** Passkeys (WebAuthn): register one, sign in with one. */
67
+ passkeys;
68
+ constructor(http, options = {}) {
69
+ this.http = http;
70
+ this.options = options;
71
+ const browser = (0, session_store_js_1.browserStorage)();
72
+ this.persist = options.persistSession ?? true;
73
+ this.storage = options.storage ?? (this.persist && browser ? browser : new session_store_js_1.MemoryStorage());
74
+ this.storageKey = options.storageKey ?? storageKeyFor(http.config.baseUrl, http.config.apiKey);
75
+ this.autoRefresh = options.autoRefreshToken ?? true;
76
+ this.margin = options.refreshMarginSeconds ?? 60;
77
+ this.mfa = new AuthMfa(http, () => this.refreshSession());
78
+ this.sessions = new AuthSessions(http);
79
+ this.orgs = new AuthOrgs(http);
80
+ this.devices = new AuthDevices(http);
81
+ this.apiKeys = new AuthApiKeys(http);
82
+ this.passkeys = new AuthPasskeys(http, (wire) => this.adopt(wire));
83
+ this.ready = this.initialize();
84
+ this.ready.catch(() => undefined);
85
+ }
86
+ // ── lifecycle ─────────────────────────────────────────────────────────
87
+ async initialize() {
88
+ try {
89
+ const raw = await this.storage.getItem(this.storageKey);
90
+ if (raw) {
91
+ const parsed = JSON.parse(raw);
92
+ if (validSession(parsed))
93
+ this.session = parsed;
94
+ else
95
+ await this.storage.removeItem(this.storageKey);
96
+ }
97
+ }
98
+ catch {
99
+ await Promise.resolve(this.storage.removeItem(this.storageKey)).catch(() => undefined);
100
+ }
101
+ this.watchOtherTabs();
102
+ const inUrl = (this.options.detectSessionInUrl ?? typeof window !== "undefined") && typeof window !== "undefined";
103
+ if (inUrl && /(?:^#|&)(access_token|error|mfa_required|linked)=/.test(window.location.hash)) {
104
+ const result = await this.getSessionFromUrl().catch(() => ({ status: "none" }));
105
+ if (result.status === "signed_in")
106
+ return;
107
+ }
108
+ if (this.session)
109
+ this.schedule();
110
+ }
111
+ /** Resolves once the stored session has been read. Everything else awaits it for you. */
112
+ async initialized() {
113
+ await this.ready;
114
+ }
115
+ /** Stop timers and listeners. Call when discarding a client (tests, hot reload). */
116
+ dispose() {
117
+ this.disposed = true;
118
+ if (this.timer)
119
+ clearTimeout(this.timer);
120
+ this.timer = null;
121
+ for (const dispose of this.disposers)
122
+ dispose();
123
+ this.disposers = [];
124
+ this.listeners.clear();
125
+ }
126
+ /**
127
+ * Be told about sign-ins, sign-outs and refreshes (also from other tabs).
128
+ * The listener is called once, soon, with `INITIAL_SESSION`.
129
+ */
130
+ onAuthStateChange(listener) {
131
+ this.listeners.add(listener);
132
+ void this.ready.then(() => {
133
+ if (this.listeners.has(listener))
134
+ this.call(listener, "INITIAL_SESSION", this.session);
135
+ });
136
+ return { unsubscribe: () => void this.listeners.delete(listener) };
137
+ }
138
+ emit(event) {
139
+ for (const listener of [...this.listeners])
140
+ this.call(listener, event, this.session);
141
+ }
142
+ call(listener, event, session) {
143
+ try {
144
+ const result = listener(event, session);
145
+ if (result instanceof Promise)
146
+ result.catch(() => undefined);
147
+ }
148
+ catch {
149
+ /* a listener must not break auth */
150
+ }
151
+ }
152
+ // ── session access ────────────────────────────────────────────────────
153
+ /**
154
+ * The current session, refreshed first when it is about to expire. `null` when signed out.
155
+ * Throws only if the token has already expired and refreshing failed for a reason that
156
+ * may pass (offline, a server error).
157
+ */
158
+ async getSession() {
159
+ await this.ready;
160
+ const session = this.session;
161
+ if (!session)
162
+ return null;
163
+ if (!this.expiring(session))
164
+ return session;
165
+ try {
166
+ return await this.refreshSession();
167
+ }
168
+ catch (error) {
169
+ if (!this.session)
170
+ return null; // the refresh token was refused: signed out
171
+ if (session.expires_at > (0, util_js_1.nowSeconds)())
172
+ return session; // still valid for now
173
+ throw error;
174
+ }
175
+ }
176
+ /** The access token to send as a bearer, or `null`. */
177
+ async getAccessToken() {
178
+ return (await this.getSession())?.access_token ?? null;
179
+ }
180
+ /** The signed-in user as last known, without a request. */
181
+ async getCachedUser() {
182
+ await this.ready;
183
+ return this.session?.user ?? null;
184
+ }
185
+ /** Called by the transport after a `401`: refresh once, unless someone already did. */
186
+ async onUnauthorized(failed) {
187
+ await this.ready;
188
+ if (!this.session)
189
+ return null;
190
+ if (this.session.access_token !== failed)
191
+ return this.session.access_token;
192
+ try {
193
+ return (await this.refreshSession()).access_token;
194
+ }
195
+ catch {
196
+ return null;
197
+ }
198
+ }
199
+ expiring(session) {
200
+ return session.expires_at - this.margin <= (0, util_js_1.nowSeconds)();
201
+ }
202
+ /**
203
+ * Adopt a session you obtained elsewhere (a server-side rendered page's cookies,
204
+ * a mobile deep link). Both tokens are required; the user is fetched with them.
205
+ */
206
+ async setSession(tokens) {
207
+ await this.ready;
208
+ const claims = (0, util_js_1.decodeJwt)(tokens.access_token);
209
+ const exp = typeof claims?.["exp"] === "number" ? claims["exp"] : (0, util_js_1.nowSeconds)() + 300;
210
+ const user = await this.http.request({
211
+ path: "/auth/v1/user",
212
+ accessToken: tokens.access_token,
213
+ });
214
+ const session = toSession({
215
+ access_token: tokens.access_token,
216
+ refresh_token: tokens.refresh_token,
217
+ token_type: "bearer",
218
+ expires_in: Math.max(0, exp - (0, util_js_1.nowSeconds)()),
219
+ expires_at: exp,
220
+ org: typeof claims?.["org"] === "string" ? claims["org"] : null,
221
+ org_role: typeof claims?.["org_role"] === "string" ? claims["org_role"] : null,
222
+ }, user);
223
+ await this.store(session, "SIGNED_IN");
224
+ return session;
225
+ }
226
+ async store(session, event) {
227
+ this.session = session;
228
+ try {
229
+ if (session)
230
+ await this.storage.setItem(this.storageKey, JSON.stringify(session));
231
+ else
232
+ await this.storage.removeItem(this.storageKey);
233
+ }
234
+ catch {
235
+ /* storage can be full or blocked; the in-memory session still works */
236
+ }
237
+ if (session)
238
+ this.schedule();
239
+ else if (this.timer) {
240
+ clearTimeout(this.timer);
241
+ this.timer = null;
242
+ }
243
+ this.emit(event);
244
+ }
245
+ schedule() {
246
+ if (!this.autoRefresh || this.disposed || !this.session)
247
+ return;
248
+ if (this.timer)
249
+ clearTimeout(this.timer);
250
+ const wait = Math.max(1, this.session.expires_at - this.margin - (0, util_js_1.nowSeconds)());
251
+ // setTimeout caps at ~24.8 days; tokens last minutes, but be safe.
252
+ const timer = setTimeout(() => {
253
+ void this.refreshSession().catch(() => {
254
+ if (this.session && !this.disposed) {
255
+ this.timer = setTimeout(() => this.schedule(), 10_000);
256
+ this.timer.unref?.();
257
+ }
258
+ });
259
+ }, Math.min(wait * 1000, 2 ** 31 - 1));
260
+ timer.unref?.();
261
+ this.timer = timer;
262
+ }
263
+ watchOtherTabs() {
264
+ if (typeof window === "undefined" || !this.persist)
265
+ return;
266
+ const onStorage = (event) => {
267
+ if (event.key !== this.storageKey)
268
+ return;
269
+ void (async () => {
270
+ const before = this.session;
271
+ const raw = event.newValue;
272
+ let next = null;
273
+ try {
274
+ const parsed = raw ? JSON.parse(raw) : null;
275
+ next = validSession(parsed) ? parsed : null;
276
+ }
277
+ catch {
278
+ next = null;
279
+ }
280
+ this.session = next;
281
+ if (next)
282
+ this.schedule();
283
+ this.emit(!next ? "SIGNED_OUT" : !before ? "SIGNED_IN" : "TOKEN_REFRESHED");
284
+ })();
285
+ };
286
+ const onVisible = () => {
287
+ if (document.visibilityState === "visible" && this.session && this.expiring(this.session)) {
288
+ void this.refreshSession().catch(() => undefined);
289
+ }
290
+ };
291
+ window.addEventListener("storage", onStorage);
292
+ document.addEventListener("visibilitychange", onVisible);
293
+ this.disposers.push(() => {
294
+ window.removeEventListener("storage", onStorage);
295
+ document.removeEventListener("visibilitychange", onVisible);
296
+ });
297
+ }
298
+ // ── refresh ───────────────────────────────────────────────────────────
299
+ /** Exchange the refresh token for a new session. Concurrent calls share one exchange. */
300
+ refreshSession(options = {}) {
301
+ if (this.inflight && !("org" in options))
302
+ return this.inflight;
303
+ const run = this.exclusive(() => this.doRefresh(options));
304
+ const tracked = run.finally(() => {
305
+ if (this.inflight === tracked)
306
+ this.inflight = null;
307
+ });
308
+ this.inflight = tracked;
309
+ return tracked;
310
+ }
311
+ async exclusive(fn) {
312
+ const locks = globalThis
313
+ .navigator?.locks;
314
+ if (locks?.request && this.persist && this.storage !== undefined && typeof window !== "undefined") {
315
+ return locks.request(`${LOCK_PREFIX}${this.storageKey}`, fn);
316
+ }
317
+ return fn();
318
+ }
319
+ async doRefresh(options) {
320
+ await this.ready;
321
+ let current = this.session;
322
+ // Another tab may have refreshed while we waited for the lock.
323
+ if (this.persist) {
324
+ try {
325
+ const raw = await this.storage.getItem(this.storageKey);
326
+ const parsed = raw ? JSON.parse(raw) : null;
327
+ if (validSession(parsed) && parsed.refresh_token !== current?.refresh_token) {
328
+ this.session = parsed;
329
+ current = parsed;
330
+ if (!this.expiring(parsed) && !("org" in options)) {
331
+ this.schedule();
332
+ this.emit("TOKEN_REFRESHED");
333
+ return parsed;
334
+ }
335
+ }
336
+ }
337
+ catch {
338
+ /* fall through to a normal refresh */
339
+ }
340
+ }
341
+ if (!current)
342
+ throw new errors_js_1.AuthenticationError({ status: 401, code: "no_session", message: "Not signed in." });
343
+ try {
344
+ const wire = await this.http.request({
345
+ method: "POST",
346
+ path: "/auth/v1/token",
347
+ body: { grant_type: "refresh_token", refresh_token: current.refresh_token, ...("org" in options ? { org: options.org } : {}) },
348
+ auth: "none",
349
+ });
350
+ const next = toSession(wire, undefined, current);
351
+ await this.store(next, "TOKEN_REFRESHED");
352
+ return next;
353
+ }
354
+ catch (error) {
355
+ if (error instanceof errors_js_1.ApiError && FATAL_REFRESH.has(error.status)) {
356
+ await this.store(null, "SIGNED_OUT");
357
+ }
358
+ throw error;
359
+ }
360
+ }
361
+ // ── sign-in ───────────────────────────────────────────────────────────
362
+ /** What this environment offers: sign-up, magic links, MFA, providers, password rules. */
363
+ settings() {
364
+ return this.http.request({ path: "/auth/v1/settings", auth: "none" });
365
+ }
366
+ /**
367
+ * Create an account. When the project requires email confirmation the result has no
368
+ * session (`verificationRequired` is `true`); otherwise the user is signed in.
369
+ */
370
+ async signUp(input) {
371
+ const wire = await this.http.request({
372
+ method: "POST",
373
+ path: "/auth/v1/signup",
374
+ body: {
375
+ email: input.email,
376
+ password: input.password,
377
+ ...(input.username ? { username: input.username } : {}),
378
+ ...(input.name ? { name: input.name } : {}),
379
+ ...(input.data ? { data: input.data } : {}),
380
+ ...(input.redirectTo ? { redirect_to: input.redirectTo } : {}),
381
+ },
382
+ auth: "none",
383
+ });
384
+ if (wire.verification_required || !wire.access_token) {
385
+ return { user: wire.user, session: null, verificationRequired: true };
386
+ }
387
+ const session = toSession(wire, undefined);
388
+ await this.store(session, "SIGNED_IN");
389
+ return { user: session.user, session, verificationRequired: false };
390
+ }
391
+ /**
392
+ * Sign in with email and password. When the account has MFA the result is
393
+ * `{ status: "mfa_required" }`; call its `verify(code)` to finish.
394
+ */
395
+ async signInWithPassword(input) {
396
+ const wire = await this.http.request({
397
+ method: "POST",
398
+ path: "/auth/v1/token",
399
+ body: {
400
+ grant_type: "password",
401
+ email: input.email,
402
+ password: input.password,
403
+ ...(input.org ? { org: input.org } : {}),
404
+ },
405
+ auth: "none",
406
+ });
407
+ if ("mfa_required" in wire && wire.mfa_required) {
408
+ return this.challenge(wire.mfa_token, wire.factors, input.org);
409
+ }
410
+ const session = toSession(wire, undefined);
411
+ await this.store(session, "SIGNED_IN");
412
+ return { status: "signed_in", session };
413
+ }
414
+ challenge(mfaToken, factors, org) {
415
+ return {
416
+ status: "mfa_required",
417
+ mfaToken,
418
+ factors,
419
+ verify: (code) => this.verifyMfa({ mfaToken, code, ...(org ? { org } : {}) }),
420
+ };
421
+ }
422
+ /** Finish an MFA sign-in with an authenticator code or a recovery code. */
423
+ async verifyMfa(input) {
424
+ const wire = await this.http.request({
425
+ method: "POST",
426
+ path: "/auth/v1/token",
427
+ body: {
428
+ grant_type: "mfa",
429
+ mfa_token: input.mfaToken,
430
+ code: input.code,
431
+ ...(input.org ? { org: input.org } : {}),
432
+ },
433
+ auth: "none",
434
+ });
435
+ const session = toSession(wire, undefined);
436
+ await this.store(session, "SIGNED_IN");
437
+ return session;
438
+ }
439
+ /** Email a one-time sign-in link. Always resolves the same way, so it does not reveal whether an account exists. */
440
+ async signInWithMagicLink(input) {
441
+ await this.http.request({
442
+ method: "POST",
443
+ path: "/auth/v1/magic-link",
444
+ body: {
445
+ email: input.email,
446
+ ...(input.redirectTo ? { redirect_to: input.redirectTo } : {}),
447
+ ...(input.createUser !== undefined ? { create_user: input.createUser } : {}),
448
+ },
449
+ auth: "none",
450
+ });
451
+ }
452
+ /** Exchange the token from a magic-link email for a session. */
453
+ async verifyMagicLink(token) {
454
+ const wire = await this.http.request({ method: "POST", path: "/auth/v1/magic-link/verify", body: { token }, auth: "none" });
455
+ if ("mfa_required" in wire && wire.mfa_required)
456
+ return this.challenge(wire.mfa_token, wire.factors);
457
+ const session = toSession(wire, undefined);
458
+ await this.store(session, "SIGNED_IN");
459
+ return { status: "signed_in", session };
460
+ }
461
+ /** Keep a session the server just issued and announce it. */
462
+ async adopt(wire) {
463
+ await this.ready;
464
+ const session = toSession(wire, undefined);
465
+ await this.store(session, "SIGNED_IN");
466
+ return session;
467
+ }
468
+ /**
469
+ * Start a guest session: an account with no email or password, for someone who has not signed up yet. Turn it into a real account with
470
+ * {@link upgradeAnonymous}, keeping everything the guest created. The environment must allow guest sign-in.
471
+ */
472
+ async signInAnonymously() {
473
+ const wire = await this.http.request({ method: "POST", path: "/auth/v1/anonymous", body: {}, auth: "none" });
474
+ return this.adopt(wire);
475
+ }
476
+ /** Turn the signed-in guest into a full account. A verification email is sent. */
477
+ async upgradeAnonymous(input) {
478
+ await this.ready;
479
+ const result = await this.http.request({
480
+ method: "POST",
481
+ path: "/auth/v1/anonymous/upgrade",
482
+ body: {
483
+ email: input.email,
484
+ ...(input.password ? { password: input.password } : {}),
485
+ ...(input.name ? { name: input.name } : {}),
486
+ ...(input.redirectTo ? { redirect_to: input.redirectTo } : {}),
487
+ },
488
+ });
489
+ if (this.session) {
490
+ this.session = { ...this.session, user: result.user };
491
+ await Promise.resolve(this.storage.setItem(this.storageKey, JSON.stringify(this.session))).catch(() => undefined);
492
+ }
493
+ this.emit("USER_UPDATED");
494
+ return result;
495
+ }
496
+ /** Email a short sign-in code. The answer is the same whether or not the address has an account. */
497
+ async sendEmailCode(input) {
498
+ await this.http.request({ method: "POST", path: "/auth/v1/email-code", body: { email: input.email, create_user: input.createUser ?? true }, auth: "none" });
499
+ }
500
+ /** Sign in with the code from {@link sendEmailCode}. An account with a second factor answers `mfa_required`. */
501
+ async verifyEmailCode(input) {
502
+ const wire = await this.http.request({ method: "POST", path: "/auth/v1/email-code/verify", body: { email: input.email, code: input.code }, auth: "none" });
503
+ if ("mfa_required" in wire && wire.mfa_required)
504
+ return this.challenge(wire.mfa_token, wire.factors);
505
+ const session = await this.adopt(wire);
506
+ return { status: "signed_in", session };
507
+ }
508
+ /** The public keys that verify integration tokens ({@link issueToken}), for a server that checks them. */
509
+ async jwks() {
510
+ const { environment } = this.scope();
511
+ return this.http.request({ path: `/auth/v1/token-keys/${encodeURIComponent(environment)}/jwks`, auth: "none" });
512
+ }
513
+ /**
514
+ * Exchange the signed-in session for a short-lived token for another service, shaped by a token profile you defined in Studio.
515
+ */
516
+ issueToken(profile) {
517
+ return this.http.request({ method: "POST", path: "/auth/v1/token-profiles/issue", body: { profile } });
518
+ }
519
+ /** Where to send the browser for a social sign-in. Needs `environment` in the client options (or a signed-in session, which names it). */
520
+ signInWithOAuthUrl(provider, options = {}) {
521
+ const { environment } = this.scope();
522
+ if ((0, util_js_1.isSecretKey)(this.http.config.apiKey)) {
523
+ throw new errors_js_1.ConfigError("Refusing to put a secret key in a URL: use a publishable key for social sign-in.");
524
+ }
525
+ const query = (0, util_js_1.queryString)({ redirect_to: options.redirectTo, apikey: this.http.config.apiKey });
526
+ return this.http.url(`/auth/v1/authorize/${encodeURIComponent(environment)}/${encodeURIComponent(provider)}`) + query;
527
+ }
528
+ /** Trade the `code` a sign-in redirect carried for a session (pass the verifier of a PKCE `code_challenge` if the sign-in began with one). */
529
+ async exchangeCode(code, codeVerifier) {
530
+ const wire = await this.http.request({
531
+ method: "POST",
532
+ path: "/auth/v1/token/exchange",
533
+ body: { code, ...(codeVerifier ? { code_verifier: codeVerifier } : {}) },
534
+ auth: "none",
535
+ });
536
+ const session = toSession(wire, undefined);
537
+ await this.store(session, "SIGNED_IN");
538
+ return session;
539
+ }
540
+ /** Redirect the browser to a provider. After the round trip, {@link getSessionFromUrl} (run automatically) picks the session up. */
541
+ signInWithOAuth(provider, options = {}) {
542
+ if (typeof window === "undefined") {
543
+ throw new errors_js_1.ConfigError("signInWithOAuth redirects a browser. On a server, use signInWithOAuthUrl().");
544
+ }
545
+ window.location.assign(this.signInWithOAuthUrl(provider, options));
546
+ }
547
+ scope() {
548
+ const claims = this.session ? (0, util_js_1.decodeJwt)(this.session.access_token) : null;
549
+ const environment = this.options.environment ?? (typeof claims?.["env"] === "string" ? claims["env"] : undefined);
550
+ if (!environment) {
551
+ throw new errors_js_1.ConfigError("Social sign-in needs the environment: pass `environment` to createClient().");
552
+ }
553
+ return { environment };
554
+ }
555
+ /**
556
+ * Read what a social sign-in left in the URL fragment (`#access_token=…`), store the
557
+ * session, and clean the URL. Called automatically in browsers; call it yourself in
558
+ * a mobile deep-link handler.
559
+ */
560
+ async getSessionFromUrl(url) {
561
+ const target = url ?? (typeof window !== "undefined" ? window.location.href : "");
562
+ const hash = target.includes("#") ? target.slice(target.indexOf("#") + 1) : "";
563
+ if (!hash)
564
+ return { status: "none" };
565
+ const params = new URLSearchParams(hash);
566
+ const clean = () => {
567
+ if (url === undefined && typeof window !== "undefined" && window.history?.replaceState) {
568
+ window.history.replaceState(null, "", window.location.pathname + window.location.search);
569
+ }
570
+ };
571
+ const error = params.get("error");
572
+ if (error) {
573
+ clean();
574
+ return { status: "error", error };
575
+ }
576
+ const linked = params.get("linked");
577
+ if (linked) {
578
+ clean();
579
+ return { status: "linked", provider: linked };
580
+ }
581
+ if (params.get("mfa_required") && params.get("mfa_token")) {
582
+ clean();
583
+ const mfaToken = params.get("mfa_token");
584
+ return { status: "mfa_required", mfaToken, verify: (code) => this.verifyMfa({ mfaToken, code }) };
585
+ }
586
+ const access = params.get("access_token");
587
+ const refresh = params.get("refresh_token");
588
+ if (!access || !refresh)
589
+ return { status: "none" };
590
+ clean();
591
+ const session = await this.setSession({ access_token: access, refresh_token: refresh });
592
+ return { status: "signed_in", session };
593
+ }
594
+ // ── session end ───────────────────────────────────────────────────────
595
+ /**
596
+ * Sign out. `local` (default) ends this session, `others` every other one, `global` all of them.
597
+ * The local session is always cleared, even when the server cannot be reached.
598
+ */
599
+ async signOut(options = {}) {
600
+ await this.ready;
601
+ const scope = options.scope ?? "local";
602
+ let failure = null;
603
+ if (this.session) {
604
+ try {
605
+ await this.http.request({ method: "POST", path: "/auth/v1/logout", body: { scope } });
606
+ }
607
+ catch (error) {
608
+ // An expired or revoked token already means "signed out" to the server.
609
+ if (!(error instanceof errors_js_1.AuthenticationError))
610
+ failure = error;
611
+ }
612
+ }
613
+ if (scope !== "others")
614
+ await this.store(null, "SIGNED_OUT");
615
+ if (failure)
616
+ throw failure;
617
+ }
618
+ // ── the signed-in user ────────────────────────────────────────────────
619
+ /** The user, fresh from the server (and the session's cached copy is updated). */
620
+ async getUser() {
621
+ await this.ready;
622
+ const user = await this.http.request({ path: "/auth/v1/user" });
623
+ if (this.session) {
624
+ this.session = { ...this.session, user };
625
+ await Promise.resolve(this.storage.setItem(this.storageKey, JSON.stringify(this.session))).catch(() => undefined);
626
+ }
627
+ return user;
628
+ }
629
+ /** Update the profile, password or (with confirmation) email. */
630
+ async updateUser(input) {
631
+ await this.ready;
632
+ await this.http.request({ method: "PATCH", path: "/auth/v1/user", body: input });
633
+ const user = await this.getUser();
634
+ this.emit("USER_UPDATED");
635
+ return user;
636
+ }
637
+ /** Send the confirmation email again. Always resolves the same way. */
638
+ async resendVerification(input) {
639
+ await this.http.request({
640
+ method: "POST",
641
+ path: "/auth/v1/verify/request",
642
+ body: { email: input.email, ...(input.redirectTo ? { redirect_to: input.redirectTo } : {}) },
643
+ auth: "none",
644
+ });
645
+ }
646
+ /** Confirm an address with the token from the email. Signs the user in. */
647
+ async verifyEmail(token) {
648
+ const wire = await this.http.request({
649
+ method: "POST",
650
+ path: "/auth/v1/verify",
651
+ body: { token },
652
+ auth: "none",
653
+ });
654
+ const session = toSession(wire, undefined);
655
+ await this.store(session, "SIGNED_IN");
656
+ return session;
657
+ }
658
+ /** Confirm a new address with the token from the email. */
659
+ async confirmEmailChange(token) {
660
+ await this.http.request({ method: "POST", path: "/auth/v1/email/confirm", body: { token }, auth: "none" });
661
+ if (this.session)
662
+ await this.getUser().catch(() => undefined);
663
+ }
664
+ /** Email a password reset link. Always resolves the same way. */
665
+ async requestPasswordReset(input) {
666
+ await this.http.request({
667
+ method: "POST",
668
+ path: "/auth/v1/recover",
669
+ body: { email: input.email, ...(input.redirectTo ? { redirect_to: input.redirectTo } : {}) },
670
+ auth: "none",
671
+ });
672
+ }
673
+ /** Set a new password with the token from the reset email. Every other session is ended and this one is signed in. */
674
+ async resetPassword(input) {
675
+ const wire = await this.http.request({
676
+ method: "POST",
677
+ path: "/auth/v1/recover/confirm",
678
+ body: { token: input.token, password: input.password },
679
+ auth: "none",
680
+ });
681
+ const session = toSession(wire, undefined);
682
+ await this.store(session, "SIGNED_IN");
683
+ return session;
684
+ }
685
+ // ── identities and organization context ───────────────────────────────
686
+ /** A URL that links a provider to the signed-in account when the browser visits it. */
687
+ async linkIdentityUrl(provider, options = {}) {
688
+ const result = await this.http.request({
689
+ method: "POST",
690
+ path: "/auth/v1/identities/link",
691
+ query: { provider, redirect_to: options.redirectTo },
692
+ });
693
+ return result.url;
694
+ }
695
+ async unlinkIdentity(identityId) {
696
+ await this.http.request({ method: "DELETE", path: `/auth/v1/identities/${identityId}` });
697
+ if (this.session)
698
+ await this.getUser().catch(() => undefined);
699
+ }
700
+ async identities() {
701
+ return (await this.getUser()).identities;
702
+ }
703
+ /** Issue tokens for another organization the user belongs to (or `null` for none). */
704
+ switchOrg(slug) {
705
+ return this.refreshSession({ org: slug });
706
+ }
707
+ /** Wait `ms` (abortable). Handy in tests. */
708
+ pause(ms) {
709
+ return (0, util_js_1.sleep)(ms);
710
+ }
711
+ }
712
+ exports.AuthClient = AuthClient;
713
+ class AuthMfa {
714
+ http;
715
+ refresh;
716
+ constructor(http, refresh) {
717
+ this.http = http;
718
+ this.refresh = refresh;
719
+ }
720
+ status() {
721
+ return this.http.request({ path: "/auth/v1/mfa" });
722
+ }
723
+ /** Start enrolling an authenticator app. Show `otpauth_uri` as a QR code, then call {@link verifyEnrollment}. */
724
+ enroll() {
725
+ return this.http.request({ method: "POST", path: "/auth/v1/mfa/totp/enroll" });
726
+ }
727
+ /** Confirm enrolment with a first code. Returns one-time recovery codes: show them once. */
728
+ async verifyEnrollment(code) {
729
+ const result = await this.http.request({
730
+ method: "POST",
731
+ path: "/auth/v1/mfa/totp/verify",
732
+ body: { code },
733
+ });
734
+ await this.refresh().catch(() => undefined); // the session is now aal2
735
+ return result;
736
+ }
737
+ /** Start using emailed codes as a factor (a code is sent to the account's address). */
738
+ enrollEmail() {
739
+ return this.http.request({ method: "POST", path: "/auth/v1/mfa/email/enroll" });
740
+ }
741
+ /** Send the emailed code for a sign-in that is waiting on MFA. */
742
+ challenge(mfaToken) {
743
+ return this.http.request({ method: "POST", path: "/auth/v1/mfa/challenge", body: { mfa_token: mfaToken }, auth: "none" });
744
+ }
745
+ /** When the environment requires MFA, a sign-in is held until a factor is set up: start it (`totp` or `email`). */
746
+ startSetup(input) {
747
+ return this.http.request({ method: "POST", path: "/auth/v1/mfa/setup/start", body: { mfa_token: input.mfaToken, method: input.method }, auth: "none" });
748
+ }
749
+ /** Finish the required setup with a first code: the response carries the session and the recovery codes. */
750
+ completeSetup(input) {
751
+ return this.http.request({
752
+ method: "POST",
753
+ path: "/auth/v1/mfa/setup/complete",
754
+ body: { mfa_token: input.mfaToken, code: input.code, ...(input.org ? { org: input.org } : {}) },
755
+ auth: "none",
756
+ });
757
+ }
758
+ disable(code) {
759
+ return this.http.request({ method: "POST", path: "/auth/v1/mfa/disable", body: { code } });
760
+ }
761
+ /** Replace the recovery codes. Needs a current authenticator code. */
762
+ regenerateRecoveryCodes(code) {
763
+ return this.http.request({ method: "POST", path: "/auth/v1/mfa/recovery-codes", body: { code } });
764
+ }
765
+ }
766
+ const b64uToBytes = (value) => {
767
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
768
+ const raw = atob(padded);
769
+ const bytes = new Uint8Array(raw.length);
770
+ for (let i = 0; i < raw.length; i += 1)
771
+ bytes[i] = raw.charCodeAt(i);
772
+ return bytes.buffer;
773
+ };
774
+ const bytesToB64u = (buffer) => {
775
+ const bytes = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
776
+ let binary = "";
777
+ for (const byte of bytes)
778
+ binary += String.fromCharCode(byte);
779
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
780
+ };
781
+ function webauthn() {
782
+ if (typeof navigator === "undefined" || !navigator.credentials || typeof PublicKeyCredential === "undefined") {
783
+ throw new errors_js_1.ConfigError("Passkeys need a browser that supports WebAuthn. On a server, use `requestRegistration` / `requestSignIn` and carry the credential yourself.");
784
+ }
785
+ return navigator.credentials;
786
+ }
787
+ /**
788
+ * Passkeys. In a browser, {@link register} and {@link signIn} do the whole ceremony. Anywhere else (a native app, a test), use the
789
+ * `request…` and `finish…` pairs: hand `options` to the platform's WebAuthn, and send what it returns (already encoded as the
790
+ * server expects) back with the `ticket`.
791
+ */
792
+ class AuthPasskeys {
793
+ http;
794
+ adopt;
795
+ constructor(http, adopt) {
796
+ this.http = http;
797
+ this.adopt = adopt;
798
+ }
799
+ async list() {
800
+ const body = await this.http.request({ path: "/auth/v1/passkeys" });
801
+ return body.data;
802
+ }
803
+ async remove(passkeyId) {
804
+ await this.http.request({ method: "DELETE", path: `/auth/v1/passkeys/${encodeURIComponent(passkeyId)}` });
805
+ }
806
+ /** Begin adding a passkey to the signed-in account. */
807
+ requestRegistration() {
808
+ return this.http.request({ method: "POST", path: "/auth/v1/passkeys/register/options", body: {} });
809
+ }
810
+ /** Finish adding a passkey with the credential the device made. */
811
+ finishRegistration(input) {
812
+ return this.http.request({
813
+ method: "POST",
814
+ path: "/auth/v1/passkeys/register/verify",
815
+ body: { ticket: input.ticket, credential: input.credential, ...(input.name ? { name: input.name } : {}) },
816
+ });
817
+ }
818
+ /** Begin signing in with a passkey. With an email, only that account's passkeys are offered. */
819
+ requestSignIn(input = {}) {
820
+ return this.http.request({ method: "POST", path: "/auth/v1/passkeys/authenticate/options", body: input.email ? { email: input.email } : {}, auth: "none" });
821
+ }
822
+ /** Finish signing in with the credential the device made. */
823
+ async finishSignIn(input) {
824
+ const wire = await this.http.request({
825
+ method: "POST",
826
+ path: "/auth/v1/passkeys/authenticate/verify",
827
+ body: { ticket: input.ticket, credential: input.credential },
828
+ auth: "none",
829
+ });
830
+ return this.adopt(wire);
831
+ }
832
+ /** In a browser: ask the device to make a passkey and add it to the signed-in account. */
833
+ async register(input = {}) {
834
+ const credentials = webauthn();
835
+ const { ticket, options } = await this.requestRegistration();
836
+ const publicKey = {
837
+ ...options,
838
+ challenge: b64uToBytes(String(options.challenge)),
839
+ user: { ...options.user, id: new TextEncoder().encode(String(options.user.id)) },
840
+ excludeCredentials: (options.excludeCredentials ?? []).map((c) => ({ ...c, id: b64uToBytes(c.id) })),
841
+ };
842
+ const made = (await credentials.create({ publicKey }));
843
+ if (!made)
844
+ throw new errors_js_1.PawabaseError("No passkey was made.");
845
+ const response = made.response;
846
+ const result = await this.finishRegistration({
847
+ ticket,
848
+ ...(input.name ? { name: input.name } : {}),
849
+ credential: {
850
+ id: made.id,
851
+ rawId: bytesToB64u(made.rawId),
852
+ type: made.type,
853
+ response: {
854
+ clientDataJSON: bytesToB64u(response.clientDataJSON),
855
+ attestationObject: bytesToB64u(response.attestationObject),
856
+ transports: typeof response.getTransports === "function" ? response.getTransports() : [],
857
+ },
858
+ clientExtensionResults: made.getClientExtensionResults(),
859
+ },
860
+ });
861
+ return result.passkey;
862
+ }
863
+ /** In a browser: ask the device for a passkey and sign in with it. */
864
+ async signIn(input = {}) {
865
+ const credentials = webauthn();
866
+ const { ticket, options } = await this.requestSignIn(input);
867
+ const publicKey = {
868
+ ...options,
869
+ challenge: b64uToBytes(String(options.challenge)),
870
+ allowCredentials: (options.allowCredentials ?? []).map((c) => ({ ...c, id: b64uToBytes(c.id) })),
871
+ };
872
+ const got = (await credentials.get({ publicKey }));
873
+ if (!got)
874
+ throw new errors_js_1.PawabaseError("No passkey was chosen.");
875
+ const response = got.response;
876
+ return this.finishSignIn({
877
+ ticket,
878
+ credential: {
879
+ id: got.id,
880
+ rawId: bytesToB64u(got.rawId),
881
+ type: got.type,
882
+ response: {
883
+ clientDataJSON: bytesToB64u(response.clientDataJSON),
884
+ authenticatorData: bytesToB64u(response.authenticatorData),
885
+ signature: bytesToB64u(response.signature),
886
+ userHandle: response.userHandle ? bytesToB64u(response.userHandle) : null,
887
+ },
888
+ clientExtensionResults: got.getClientExtensionResults(),
889
+ },
890
+ });
891
+ }
892
+ }
893
+ class AuthSessions {
894
+ http;
895
+ constructor(http) {
896
+ this.http = http;
897
+ }
898
+ async list() {
899
+ return (await this.http.request({ path: "/auth/v1/sessions" })).data;
900
+ }
901
+ /** Sign one session out (a lost phone, another browser). */
902
+ async revoke(sessionId) {
903
+ await this.http.request({ method: "DELETE", path: `/auth/v1/sessions/${encodeURIComponent(sessionId)}` });
904
+ }
905
+ /** Recent sign-in activity for the account. */
906
+ async history() {
907
+ return (await this.http.request({ path: "/auth/v1/history" })).data;
908
+ }
909
+ }
910
+ class AuthDevices {
911
+ http;
912
+ constructor(http) {
913
+ this.http = http;
914
+ }
915
+ async list() {
916
+ const body = await this.http.request({ path: "/auth/v1/user/devices" });
917
+ return body.data ?? [];
918
+ }
919
+ async forget(deviceId) {
920
+ await this.http.request({ method: "DELETE", path: `/auth/v1/user/devices/${encodeURIComponent(deviceId)}` });
921
+ }
922
+ }
923
+ class AuthApiKeys {
924
+ http;
925
+ constructor(http) {
926
+ this.http = http;
927
+ }
928
+ async list() {
929
+ const body = await this.http.request({ path: "/auth/v1/user/api-keys" });
930
+ return body.data ?? [];
931
+ }
932
+ /** Make a key. The secret is in the answer once and never again. */
933
+ create(input) {
934
+ return this.http.request({
935
+ method: "POST",
936
+ path: "/auth/v1/user/api-keys",
937
+ body: { name: input.name, scopes: input.scopes ?? [], ...(input.expiresInDays ? { expires_in_days: input.expiresInDays } : {}), ...(input.org ? { org: input.org } : {}) },
938
+ });
939
+ }
940
+ async revoke(keyId) {
941
+ await this.http.request({ method: "DELETE", path: `/auth/v1/user/api-keys/${encodeURIComponent(keyId)}` });
942
+ }
943
+ }
944
+ class AuthOrgs {
945
+ http;
946
+ constructor(http) {
947
+ this.http = http;
948
+ }
949
+ base(slug) {
950
+ return `/auth/v1/orgs/${encodeURIComponent(slug)}`;
951
+ }
952
+ async list() {
953
+ return (await this.http.request({ path: "/auth/v1/orgs" })).data;
954
+ }
955
+ create(input) {
956
+ return this.http.request({ method: "POST", path: "/auth/v1/orgs", body: input });
957
+ }
958
+ get(slug) {
959
+ return this.http.request({ path: this.base(slug) });
960
+ }
961
+ update(slug, input) {
962
+ return this.http.request({ method: "PATCH", path: this.base(slug), body: input });
963
+ }
964
+ async delete(slug) {
965
+ await this.http.request({ method: "DELETE", path: this.base(slug) });
966
+ }
967
+ async members(slug) {
968
+ return (await this.http.request({ path: `${this.base(slug)}/members` })).data;
969
+ }
970
+ setMemberRole(slug, userId, role) {
971
+ return this.http.request({ method: "PUT", path: `${this.base(slug)}/members/${encodeURIComponent(userId)}`, body: { role } });
972
+ }
973
+ async removeMember(slug, userId) {
974
+ await this.http.request({ method: "DELETE", path: `${this.base(slug)}/members/${encodeURIComponent(userId)}` });
975
+ }
976
+ invite(slug, input) {
977
+ return this.http.request({
978
+ method: "POST",
979
+ path: `${this.base(slug)}/invitations`,
980
+ body: {
981
+ email: input.email,
982
+ role: input.role ?? "member",
983
+ ...(input.redirectTo ? { redirect_to: input.redirectTo } : {}),
984
+ },
985
+ });
986
+ }
987
+ async invitations(slug) {
988
+ return (await this.http.request({ path: `${this.base(slug)}/invitations` })).data;
989
+ }
990
+ async revokeInvitation(slug, invitationId) {
991
+ await this.http.request({ method: "DELETE", path: `${this.base(slug)}/invitations/${invitationId}` });
992
+ }
993
+ /** Accept an invitation with the token from the email. The user must be signed in. */
994
+ acceptInvitation(token) {
995
+ return this.http.request({ method: "POST", path: "/auth/v1/invitations/accept", body: { token } });
996
+ }
997
+ async teams(slug) {
998
+ return (await this.http.request({ path: `${this.base(slug)}/teams` })).data;
999
+ }
1000
+ createTeam(slug, input) {
1001
+ return this.http.request({ method: "POST", path: `${this.base(slug)}/teams`, body: input });
1002
+ }
1003
+ async addTeamMember(slug, team, userId) {
1004
+ await this.http.request({
1005
+ method: "PUT",
1006
+ path: `${this.base(slug)}/teams/${encodeURIComponent(team)}/members/${encodeURIComponent(userId)}`,
1007
+ });
1008
+ }
1009
+ async removeTeamMember(slug, team, userId) {
1010
+ await this.http.request({
1011
+ method: "DELETE",
1012
+ path: `${this.base(slug)}/teams/${encodeURIComponent(team)}/members/${encodeURIComponent(userId)}`,
1013
+ });
1014
+ }
1015
+ }
1016
+ //# sourceMappingURL=auth.js.map