@loomup/astro 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.d.ts CHANGED
@@ -8,6 +8,8 @@ export type LoomupAuthEndpointContext = {
8
8
  export type LoomupAuthHandlerOptions = CreateServerClientOptions & {
9
9
  /** Catch-all Astro parameter name. Default: `loomup`. */
10
10
  param?: string;
11
+ /** Exact application callback URL allowlisted in `$auth.redirect_urls`. */
12
+ oauthCallbackUrl?: string;
11
13
  };
12
14
  /**
13
15
  * Create one Astro catch-all endpoint for login, logout, session hydration,
package/dist/auth.js CHANGED
@@ -1,12 +1,20 @@
1
1
  /** Same-origin Astro auth endpoint for Loomup-backed applications. */
2
2
  import { LoomupError } from "@loomup/client";
3
- import { createServerClient, } from "./server.js";
3
+ import { readTokens, writeTokens } from "./cookies.js";
4
+ import { createServerClient, resolveServerUrl, } from "./server.js";
5
+ const OAUTH_VERIFIER_COOKIE = "loomup-oauth-verifier";
6
+ const OAUTH_RETURN_COOKIE = "loomup-oauth-return";
4
7
  function response(data, status = 200) {
5
8
  return Response.json(data, {
6
9
  status,
7
10
  headers: { "Cache-Control": "private, no-store" },
8
11
  });
9
12
  }
13
+ function localErrorRedirect(returnTo, error) {
14
+ const destination = new URL(returnTo, "http://loomup.local");
15
+ destination.searchParams.set("error", error);
16
+ return `${destination.pathname}${destination.search}${destination.hash}`;
17
+ }
10
18
  async function jsonBody(request) {
11
19
  try {
12
20
  const value = await request.json();
@@ -24,8 +32,180 @@ function assertSameOrigin(request) {
24
32
  throw new LoomupError("cross-origin auth mutation rejected", "forbidden", 403);
25
33
  }
26
34
  }
27
- function publicSession(user, accessToken) {
28
- return { data: { user, access_token: accessToken } };
35
+ function publicSession(user) {
36
+ return { data: { user } };
37
+ }
38
+ function joinUrl(base, path) {
39
+ return `${base.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
40
+ }
41
+ function upstreamCookie(headers, name) {
42
+ const extended = headers;
43
+ const values = typeof extended.getSetCookie === "function"
44
+ ? extended.getSetCookie()
45
+ : typeof extended.getAll === "function"
46
+ ? extended.getAll("Set-Cookie")
47
+ : [headers.get("Set-Cookie") ?? ""];
48
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
49
+ const pattern = new RegExp(`(?:^|,\\s*)${escaped}=([^;]*)`);
50
+ for (const value of values) {
51
+ const match = pattern.exec(value);
52
+ if (match?.[1])
53
+ return match[1].replace(/^"|"$/g, "");
54
+ }
55
+ return undefined;
56
+ }
57
+ async function upstreamRequest(baseUrl, method, path, body, accessToken, serviceKey) {
58
+ const headers = { Accept: "application/json" };
59
+ if (body !== undefined)
60
+ headers["Content-Type"] = "application/json";
61
+ if (accessToken || serviceKey)
62
+ headers.Authorization = `Bearer ${accessToken ?? serviceKey}`;
63
+ const upstream = await fetch(joinUrl(baseUrl, path), {
64
+ method,
65
+ headers,
66
+ body: body === undefined ? undefined : JSON.stringify(body),
67
+ });
68
+ const text = await upstream.text();
69
+ let payload;
70
+ try {
71
+ payload = text ? JSON.parse(text) : null;
72
+ }
73
+ catch {
74
+ payload = null;
75
+ }
76
+ const envelope = payload;
77
+ if (!upstream.ok) {
78
+ const message = envelope?.error?.message ?? envelope?.message ?? text ?? upstream.statusText;
79
+ const code = envelope?.error?.code;
80
+ throw new LoomupError(String(message || upstream.statusText), typeof code === "string" ? code : undefined, upstream.status);
81
+ }
82
+ if (!envelope || !("data" in envelope)) {
83
+ throw new LoomupError("invalid response from Loomup", "invalid_response", 502);
84
+ }
85
+ return { data: envelope.data, response: upstream };
86
+ }
87
+ async function authExchange(baseUrl, cookies, options, path, body, currentRefresh) {
88
+ const { data, response: upstream } = await upstreamRequest(baseUrl, "POST", path, body, undefined, options.client?.serviceKey);
89
+ const accessToken = typeof data.access_token === "string"
90
+ ? data.access_token
91
+ : upstreamCookie(upstream.headers, "loomup_access");
92
+ const refreshToken = typeof data.refresh_token === "string"
93
+ ? data.refresh_token
94
+ : upstreamCookie(upstream.headers, "loomup_refresh") ?? currentRefresh;
95
+ if (!accessToken || !refreshToken) {
96
+ throw new LoomupError("Loomup auth response did not include a complete session", "invalid_response", 502);
97
+ }
98
+ writeTokens(cookies, {
99
+ access_token: accessToken,
100
+ refresh_token: refreshToken,
101
+ expires_in: typeof data.expires_in === "number" ? data.expires_in : undefined,
102
+ }, options.cookies);
103
+ return { accessToken, user: data.user };
104
+ }
105
+ async function userForAccess(baseUrl, accessToken) {
106
+ const { data } = await upstreamRequest(baseUrl, "GET", "/auth/me", undefined, accessToken);
107
+ return data;
108
+ }
109
+ async function sessionFromCookies(baseUrl, cookies, options) {
110
+ const tokens = readTokens(cookies, options.cookies?.names);
111
+ if (tokens.access) {
112
+ try {
113
+ return { accessToken: tokens.access, user: await userForAccess(baseUrl, tokens.access) };
114
+ }
115
+ catch (error) {
116
+ if (!(error instanceof LoomupError) || error.status !== 401 || !tokens.refresh)
117
+ throw error;
118
+ }
119
+ }
120
+ if (!tokens.refresh) {
121
+ throw new LoomupError("authentication required", "unauthorized", 401);
122
+ }
123
+ const session = await authExchange(baseUrl, cookies, options, "/auth/refresh", { refresh_token: tokens.refresh }, tokens.refresh);
124
+ return {
125
+ accessToken: session.accessToken,
126
+ user: session.user ?? (await userForAccess(baseUrl, session.accessToken)),
127
+ };
128
+ }
129
+ const REQUEST_HEADERS = [
130
+ "accept",
131
+ "content-type",
132
+ "idempotency-key",
133
+ "if-match",
134
+ "if-none-match",
135
+ "if-modified-since",
136
+ "range",
137
+ "x-loomup-upsert",
138
+ ];
139
+ const RESPONSE_HEADERS = [
140
+ "accept-ranges",
141
+ "cache-control",
142
+ "content-disposition",
143
+ "content-range",
144
+ "content-type",
145
+ "etag",
146
+ "last-modified",
147
+ ];
148
+ function proxyPath(action) {
149
+ const path = action.slice("data".length).replace(/^\/+/, "");
150
+ if (!path)
151
+ throw new LoomupError("missing Loomup API path", "not_found", 404);
152
+ const segments = path.split("/");
153
+ if (segments.some((segment) => segment === "." || segment === ".." || segment.includes("\\"))) {
154
+ throw new LoomupError("invalid Loomup API path", "invalid_input", 400);
155
+ }
156
+ if (path.startsWith("auth/") && path !== "auth/me") {
157
+ throw new LoomupError("use the Astro auth endpoint", "forbidden", 403);
158
+ }
159
+ if (path.startsWith("account/")) {
160
+ throw new LoomupError("use the Astro account endpoint", "forbidden", 403);
161
+ }
162
+ return `/${path}`;
163
+ }
164
+ async function proxyToLoomup(context, options, baseUrl, action) {
165
+ const method = context.request.method.toUpperCase();
166
+ if (!["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"].includes(method)) {
167
+ return response({ error: { code: "method_not_allowed" } }, 405);
168
+ }
169
+ if (method !== "GET" && method !== "HEAD")
170
+ assertSameOrigin(context.request);
171
+ let tokens = readTokens(context.cookies, options.cookies?.names);
172
+ if (!tokens.access) {
173
+ if (!tokens.refresh) {
174
+ throw new LoomupError("authentication required", "unauthorized", 401);
175
+ }
176
+ await authExchange(baseUrl, context.cookies, options, "/auth/refresh", { refresh_token: tokens.refresh }, tokens.refresh);
177
+ tokens = readTokens(context.cookies, options.cookies?.names);
178
+ }
179
+ if (!tokens.access) {
180
+ throw new LoomupError("authentication required", "unauthorized", 401);
181
+ }
182
+ const requestHeaders = new Headers();
183
+ for (const name of REQUEST_HEADERS) {
184
+ const value = context.request.headers.get(name);
185
+ if (value)
186
+ requestHeaders.set(name, value);
187
+ }
188
+ requestHeaders.set("Authorization", `Bearer ${tokens.access}`);
189
+ const incomingUrl = new URL(context.request.url);
190
+ const targetUrl = `${joinUrl(baseUrl, proxyPath(action))}${incomingUrl.search}`;
191
+ const upstream = await fetch(targetUrl, {
192
+ method,
193
+ headers: requestHeaders,
194
+ body: method === "GET" || method === "HEAD" ? undefined : context.request.body,
195
+ redirect: "manual",
196
+ });
197
+ const responseHeaders = new Headers();
198
+ for (const name of RESPONSE_HEADERS) {
199
+ const value = upstream.headers.get(name);
200
+ if (value)
201
+ responseHeaders.set(name, value);
202
+ }
203
+ responseHeaders.set("Cache-Control", upstream.headers.get("Cache-Control") ?? "private, no-store");
204
+ return new Response(upstream.body, {
205
+ status: upstream.status,
206
+ statusText: upstream.statusText,
207
+ headers: responseHeaders,
208
+ });
29
209
  }
30
210
  /**
31
211
  * Create one Astro catch-all endpoint for login, logout, session hydration,
@@ -43,41 +223,105 @@ export function createLoomupAuthHandler(options = {}) {
43
223
  const param = options.param ?? "loomup";
44
224
  return async (context) => {
45
225
  const action = context.params?.[param]?.replace(/^\/+|\/+$/g, "") ?? "";
226
+ const url = resolveServerUrl(options.url);
46
227
  const client = createServerClient(context.cookies, options);
47
228
  try {
229
+ if (action === "data" || action.startsWith("data/")) {
230
+ return await proxyToLoomup(context, options, url, action);
231
+ }
48
232
  switch (action) {
233
+ case "oauth/start": {
234
+ assertSameOrigin(context.request);
235
+ if (!options.oauthCallbackUrl) {
236
+ throw new LoomupError("oauthCallbackUrl is required", "oauth_misconfigured", 500);
237
+ }
238
+ const body = await jsonBody(context.request);
239
+ const provider = String(body.provider ?? "");
240
+ if (!["google", "apple", "github"].includes(provider)) {
241
+ throw new LoomupError("supported OAuth provider required", "invalid_input", 400);
242
+ }
243
+ const requestedReturn = String(body.returnTo ?? "/");
244
+ const returnTo = requestedReturn.startsWith("/") && !requestedReturn.startsWith("//")
245
+ ? requestedReturn
246
+ : "/";
247
+ const loomup = (await import("@loomup/client")).createClient({
248
+ url,
249
+ serviceKey: options.client?.serviceKey,
250
+ });
251
+ const authorization = await loomup.auth.authorizeOAuth({
252
+ provider: provider,
253
+ redirectTo: options.oauthCallbackUrl,
254
+ });
255
+ const secure = options.cookies?.secure ?? process.env.NODE_ENV === "production";
256
+ const cookieOptions = { path: "/", httpOnly: true, sameSite: "lax", secure, maxAge: 600 };
257
+ context.cookies.set(OAUTH_VERIFIER_COOKIE, authorization.code_verifier, cookieOptions);
258
+ context.cookies.set(OAUTH_RETURN_COOKIE, encodeURIComponent(returnTo), cookieOptions);
259
+ return new Response(null, { status: 302, headers: { Location: authorization.authorization_url } });
260
+ }
261
+ case "oauth/callback": {
262
+ const callback = new URL(context.request.url);
263
+ const code = callback.searchParams.get("code");
264
+ const providerError = callback.searchParams.get("error");
265
+ const verifier = context.cookies.get(OAUTH_VERIFIER_COOKIE)?.value;
266
+ const encodedReturn = context.cookies.get(OAUTH_RETURN_COOKIE)?.value;
267
+ let returnTo = "/";
268
+ try {
269
+ const candidate = decodeURIComponent(encodedReturn ?? "/");
270
+ if (candidate.startsWith("/") && !candidate.startsWith("//"))
271
+ returnTo = candidate;
272
+ }
273
+ catch { /* default */ }
274
+ if (providerError && verifier) {
275
+ context.cookies.delete(OAUTH_VERIFIER_COOKIE, { path: "/" });
276
+ context.cookies.delete(OAUTH_RETURN_COOKIE, { path: "/" });
277
+ return new Response(null, { status: 302, headers: { Location: localErrorRedirect(returnTo, providerError) } });
278
+ }
279
+ if (!code || !verifier) {
280
+ throw new LoomupError("OAuth callback is incomplete", "oauth_flow_expired", 400);
281
+ }
282
+ await authExchange(url, context.cookies, options, "/auth/oauth/exchange", {
283
+ code,
284
+ code_verifier: verifier,
285
+ });
286
+ context.cookies.delete(OAUTH_VERIFIER_COOKIE, { path: "/" });
287
+ context.cookies.delete(OAUTH_RETURN_COOKIE, { path: "/" });
288
+ return new Response(null, { status: 302, headers: { Location: returnTo } });
289
+ }
49
290
  case "session": {
50
291
  if (context.request.method !== "GET") {
51
292
  return response({ error: { code: "method_not_allowed" } }, 405);
52
293
  }
53
- const user = await client.auth.me();
54
- return response(publicSession(user, client.accessToken));
294
+ const session = await sessionFromCookies(url, context.cookies, options);
295
+ return response(publicSession(session.user));
55
296
  }
56
297
  case "refresh": {
57
298
  assertSameOrigin(context.request);
58
- const tokens = await client.auth.refresh();
59
- const user = tokens.user ?? (await client.auth.me());
60
- return response(publicSession(user, tokens.access_token));
299
+ const refreshToken = readTokens(context.cookies, options.cookies?.names).refresh;
300
+ if (!refreshToken)
301
+ throw new LoomupError("no refresh token", "no_refresh", 401);
302
+ const session = await authExchange(url, context.cookies, options, "/auth/refresh", { refresh_token: refreshToken }, refreshToken);
303
+ const user = session.user ?? (await userForAccess(url, session.accessToken));
304
+ return response(publicSession(user));
61
305
  }
62
306
  case "login": {
63
307
  assertSameOrigin(context.request);
64
308
  const body = await jsonBody(context.request);
65
- const tokens = await client.auth.signIn({
309
+ const session = await authExchange(url, context.cookies, options, "/auth/login", {
66
310
  email: String(body.email ?? ""),
67
311
  password: String(body.password ?? ""),
68
312
  });
69
- const user = tokens.user ?? (await client.auth.me());
70
- return response(publicSession(user, tokens.access_token));
313
+ const user = session.user ?? (await userForAccess(url, session.accessToken));
314
+ return response(publicSession(user));
71
315
  }
72
316
  case "register": {
73
317
  assertSameOrigin(context.request);
74
318
  const body = await jsonBody(context.request);
75
- const tokens = await client.auth.signUp({
319
+ const session = await authExchange(url, context.cookies, options, "/auth/register", {
76
320
  email: String(body.email ?? ""),
77
321
  password: String(body.password ?? ""),
78
322
  });
79
- const user = tokens.user ?? (await client.auth.me());
80
- return response(publicSession(user, tokens.access_token), 201);
323
+ const user = session.user ?? (await userForAccess(url, session.accessToken));
324
+ return response(publicSession(user), 201);
81
325
  }
82
326
  case "logout": {
83
327
  assertSameOrigin(context.request);
@@ -89,15 +333,15 @@ export function createLoomupAuthHandler(options = {}) {
89
333
  const body = await jsonBody(context.request);
90
334
  const currentPassword = String(body.currentPassword ?? "");
91
335
  const newPassword = String(body.newPassword ?? "");
92
- const user = await client.auth.me();
93
- await client.request("POST", "/account/api/change-password", {
94
- current_password: currentPassword,
95
- new_password: newPassword,
96
- });
336
+ const current = await sessionFromCookies(url, context.cookies, options);
337
+ await upstreamRequest(url, "POST", "/account/api/change-password", { current_password: currentPassword, new_password: newPassword }, current.accessToken);
97
338
  // The core revokes every refresh session on password change. Issue a
98
339
  // fresh current session so the browser does not fail on its next 401.
99
- const tokens = await client.auth.signIn({ email: user.email, password: newPassword });
100
- return response(publicSession(tokens.user ?? user, tokens.access_token));
340
+ const session = await authExchange(url, context.cookies, options, "/auth/login", {
341
+ email: current.user.email,
342
+ password: newPassword,
343
+ });
344
+ return response(publicSession(session.user ?? current.user));
101
345
  }
102
346
  case "password-reset/request": {
103
347
  assertSameOrigin(context.request);
package/dist/client.d.ts CHANGED
@@ -33,9 +33,11 @@ export declare function createBrowserClient<TMap extends DefaultTableMap = Defau
33
33
  }, TUpdateMap extends DefaultUpdateMap = {
34
34
  [K in keyof TMap]: Partial<TMap[K]> & Record<string, unknown>;
35
35
  }>(options?: CreateBrowserClientOptions): LoomupClient<TMap, TInsertMap, TUpdateMap>;
36
- export type CreateAuthenticatedProjectOptions = CreateBrowserClientOptions & {
36
+ export type CreateAuthenticatedProjectOptions = {
37
37
  /** Same-origin catch-all endpoint. Default: `/api/loomup`. */
38
38
  authEndpoint?: string;
39
+ /** Same-origin data gateway. Default: `<authEndpoint>/data`. */
40
+ dataEndpoint?: string;
39
41
  fetch?: typeof fetch;
40
42
  };
41
43
  export type AuthenticatedProject<TMap, TInsertMap, TUpdateMap> = {
@@ -44,8 +46,8 @@ export type AuthenticatedProject<TMap, TInsertMap, TUpdateMap> = {
44
46
  signOut(): Promise<void>;
45
47
  };
46
48
  /**
47
- * Hydrate the typed `db.issues` project client without exposing the refresh
48
- * token to JavaScript. Access-token renewal goes through the Astro endpoint.
49
+ * Hydrate the typed `db.issues` project client through Astro's same-origin
50
+ * data gateway. Loomup's URL and both session tokens remain server-only.
49
51
  */
50
52
  export declare function createAuthenticatedProject<TMap = DefaultTableMap, TInsertMap = {
51
53
  [K in keyof TMap]: Partial<TMap[K]> & Record<string, unknown>;
package/dist/client.js CHANGED
@@ -65,23 +65,24 @@ async function authRequest(fetchImpl, endpoint, action, init) {
65
65
  return payload.data ?? {};
66
66
  }
67
67
  /**
68
- * Hydrate the typed `db.issues` project client without exposing the refresh
69
- * token to JavaScript. Access-token renewal goes through the Astro endpoint.
68
+ * Hydrate the typed `db.issues` project client through Astro's same-origin
69
+ * data gateway. Loomup's URL and both session tokens remain server-only.
70
70
  */
71
71
  export async function createAuthenticatedProject(options = {}) {
72
72
  const fetchImpl = options.fetch ?? globalThis.fetch;
73
73
  const endpoint = options.authEndpoint ?? "/api/loomup";
74
74
  const session = await authRequest(fetchImpl, endpoint, "session", { method: "GET" });
75
- if (!session.user || !session.access_token) {
75
+ if (!session.user) {
76
76
  throw new LoomupError("authenticated session required", "unauthorized", 401);
77
77
  }
78
78
  const db = createProject({
79
- url: resolveBrowserUrl(options.url),
80
- token: session.access_token,
81
- WebSocketImpl: options.WebSocketImpl,
79
+ url: options.dataEndpoint ?? `${endpoint.replace(/\/$/, "")}/data`,
82
80
  accessTokenProvider: async () => {
83
- const refreshed = await authRequest(fetchImpl, endpoint, "refresh", { method: "POST" });
84
- return refreshed.access_token;
81
+ await authRequest(fetchImpl, endpoint, "refresh", { method: "POST" });
82
+ // The core client requires a truthy retry signal. This marker is sent
83
+ // only to the same-origin gateway, which replaces Authorization with
84
+ // the server-held access token.
85
+ return "server-session";
85
86
  },
86
87
  });
87
88
  return {
package/dist/server.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Server-side Loomup client for Astro (SSR frontmatter, endpoints, middleware).
3
3
  * Persists access/refresh tokens in httpOnly cookies.
4
4
  */
5
- import { LoomupClient, type AuthTokens, type CreateClientOptions, type DefaultTableMap, type LoomupProject } from "@loomup/client";
5
+ import { LoomupClient, type AuthTokens, type AuthSignUpResult, type CreateClientOptions, type DefaultTableMap, type LoomupProject, type OAuthAuthorizeInput, type OAuthExchangeInput } from "@loomup/client";
6
6
  import { type CookieOptions, type CookieStore } from "./cookies.js";
7
7
  export type { CookieNames, CookieOptions, CookieStore, CookieWriteOptions, } from "./cookies.js";
8
8
  export { DEFAULT_ACCESS_COOKIE, DEFAULT_REFRESH_COOKIE, clearTokens, readTokens, resolveCookieNames, writeTokens, } from "./cookies.js";
@@ -25,6 +25,7 @@ export type CreateServerClientOptions = {
25
25
  */
26
26
  client?: Omit<CreateClientOptions, "url" | "token" | "refreshToken">;
27
27
  };
28
+ export declare function resolveServerUrl(explicit?: string): string;
28
29
  /**
29
30
  * Cookie-backed Loomup client for Astro SSR.
30
31
  * Extends the core client so `.from()`, `.request()`, etc. stay identical.
@@ -47,12 +48,13 @@ export declare class ServerLoomupClient<TMap = DefaultTableMap, TInsertMap = {
47
48
  signUp(creds: {
48
49
  email: string;
49
50
  password: string;
50
- }): Promise<AuthTokens>;
51
+ }): Promise<AuthSignUpResult>;
51
52
  signIn(creds: {
52
53
  email: string;
53
54
  password: string;
54
55
  }): Promise<AuthTokens>;
55
56
  refresh(): Promise<AuthTokens>;
57
+ exchangeOAuthCode(input: OAuthExchangeInput): Promise<AuthTokens>;
56
58
  signOut(): Promise<void>;
57
59
  setToken(token: string | undefined): void;
58
60
  setRefreshToken(token: string | undefined): void;
@@ -64,11 +66,11 @@ export declare class ServerLoomupClient<TMap = DefaultTableMap, TInsertMap = {
64
66
  signUp: (creds: {
65
67
  email: string;
66
68
  password: string;
67
- }) => Promise<AuthTokens>;
69
+ }) => Promise<AuthSignUpResult>;
68
70
  register: (creds: {
69
71
  email: string;
70
72
  password: string;
71
- }) => Promise<AuthTokens>;
73
+ }) => Promise<AuthSignUpResult>;
72
74
  signIn: (creds: {
73
75
  email: string;
74
76
  password: string;
@@ -77,6 +79,20 @@ export declare class ServerLoomupClient<TMap = DefaultTableMap, TInsertMap = {
77
79
  email: string;
78
80
  password: string;
79
81
  }) => Promise<AuthTokens>;
82
+ oauthProviders: () => Promise<import("@loomup/client").OAuthProviderInfo[]>;
83
+ authorizeOAuth: (input: OAuthAuthorizeInput) => Promise<import("@loomup/client").OAuthAuthorization>;
84
+ exchangeOAuthCode: (input: OAuthExchangeInput) => Promise<AuthTokens>;
85
+ resendVerification: (email: string) => Promise<import("@loomup/client").AuthActionResult>;
86
+ confirmVerification: (token: string) => Promise<AuthTokens>;
87
+ requestPasswordReset: (email: string) => Promise<import("@loomup/client").AuthActionResult>;
88
+ confirmPasswordReset: (input: {
89
+ token: string;
90
+ password: string;
91
+ }) => Promise<import("@loomup/client").AuthActionResult>;
92
+ acceptInvitation: (input: {
93
+ token: string;
94
+ password: string;
95
+ }) => Promise<AuthTokens>;
80
96
  signOut: () => Promise<void>;
81
97
  logout: () => Promise<void>;
82
98
  me: () => Promise<import("@loomup/client").User>;
package/dist/server.js CHANGED
@@ -7,7 +7,7 @@ import { asCookieStore, clearTokens, readTokens, writeTokens, } from "./cookies.
7
7
  export { DEFAULT_ACCESS_COOKIE, DEFAULT_REFRESH_COOKIE, clearTokens, readTokens, resolveCookieNames, writeTokens, } from "./cookies.js";
8
8
  export { LoomupError, createClient, StorageBucket, encodeObjectPath, normalizeStorageUpload, } from "@loomup/client";
9
9
  export { fileAndPathFromFormData, uploadFromFormData, storageDownloadResponse, } from "./objectStorage.js";
10
- function resolveServerUrl(explicit) {
10
+ export function resolveServerUrl(explicit) {
11
11
  if (explicit)
12
12
  return explicit;
13
13
  if (typeof process !== "undefined") {
@@ -43,7 +43,8 @@ export class ServerLoomupClient extends LoomupClient {
43
43
  }
44
44
  async signUp(creds) {
45
45
  const data = await super.signUp(creds);
46
- this.persistFromTokens(data);
46
+ if ("access_token" in data)
47
+ this.persistFromTokens(data);
47
48
  return data;
48
49
  }
49
50
  async signIn(creds) {
@@ -56,6 +57,11 @@ export class ServerLoomupClient extends LoomupClient {
56
57
  this.persistFromTokens(data);
57
58
  return data;
58
59
  }
60
+ async exchangeOAuthCode(input) {
61
+ const data = await super.exchangeOAuthCode(input);
62
+ this.persistFromTokens(data);
63
+ return data;
64
+ }
59
65
  async signOut() {
60
66
  await super.signOut();
61
67
  this.clearCookieTokens();
@@ -87,6 +93,14 @@ export class ServerLoomupClient extends LoomupClient {
87
93
  register: (creds) => this.signUp(creds),
88
94
  signIn: (creds) => this.signIn(creds),
89
95
  login: (creds) => this.signIn(creds),
96
+ oauthProviders: () => this.oauthProviders(),
97
+ authorizeOAuth: (input) => this.authorizeOAuth(input),
98
+ exchangeOAuthCode: (input) => this.exchangeOAuthCode(input),
99
+ resendVerification: (email) => this.resendEmailVerification(email),
100
+ confirmVerification: (token) => this.confirmEmailVerification(token),
101
+ requestPasswordReset: (email) => this.requestPasswordReset(email),
102
+ confirmPasswordReset: (input) => this.confirmPasswordReset(input),
103
+ acceptInvitation: (input) => this.acceptInvitation(input),
90
104
  signOut: () => this.signOut(),
91
105
  logout: () => this.signOut(),
92
106
  me: () => this.me(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomup/astro",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Astro integration and SSR helpers for Loomup Realtime",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",