@foir/sdk 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -108,6 +108,29 @@ nobody asking for `@foir/sdk` gets it early. The push that carries the commit to
108
108
 
109
109
  Changes on `dev` that have not been published to npm.
110
110
 
111
+ ## [0.26.0] — 2026-10-19
112
+
113
+ ### Changed
114
+
115
+ - **`session.request` in `@foir/sdk/placement` throws `PlacementRequestFailed`.**
116
+ Its message says each distinct refusal once, where it repeated a read
117
+ refused on a field once per row, and its `errors` keeps every error with its
118
+ `path`. It is still an `Error`, so a caller that reads `message` is
119
+ unaffected.
120
+
121
+ ### Added
122
+
123
+ - **A hosted sign-in can name the organization it is for** (`@foir/sdk/auth`,
124
+ `@foir/sdk/react`): `hostedLoginUrl(config, returnTo, { org })`, a session
125
+ client's `signInUrl(returnTo, { org })` and the provider's
126
+ `hostedSignInUrl(returnTo, { org })`. Use it when one auth client serves an
127
+ address per organization. The login domain's front door passes it on, and
128
+ the sign-in takes that organization's branding, providers and return
129
+ addresses. The platform refuses an organization other than the one the
130
+ domain or app is bound to, one of another project, and one that is not
131
+ active.
132
+ - **`HostedLoginOptions`** (`@foir/sdk/auth`, type).
133
+
111
134
  ## [0.25.0] — 2026-10-18
112
135
 
113
136
  ### Changed
@@ -27,11 +27,22 @@ export interface HostedLoginConfig {
27
27
  */
28
28
  clientId?: string;
29
29
  }
30
+ /** What a sign-in started at the hosted front door is for. */
31
+ export interface HostedLoginOptions {
32
+ /**
33
+ * The organization the person signs in to, when one auth client serves an
34
+ * address per organization. It decides the branding, the pool, and which
35
+ * addresses the person may come back to. The platform refuses an
36
+ * organization other than the one the login domain or app is bound to, one
37
+ * of another project, and one that is not active.
38
+ */
39
+ org?: string;
40
+ }
30
41
  /**
31
- * Build the redirect URL to the hosted front door, forwarding a best-effort
32
- * absolute `returnTo` (honoured if the front door supports it).
42
+ * Build the redirect URL to the hosted front door, forwarding an absolute
43
+ * `returnTo` and, where the app names one, the organization the sign-in is for.
33
44
  */
34
- export declare function hostedLoginUrl(config: HostedLoginConfig, returnTo?: string): string;
45
+ export declare function hostedLoginUrl(config: HostedLoginConfig, returnTo?: string, options?: HostedLoginOptions): string;
35
46
  /**
36
47
  * Build the redirect URL that ENDS the hosted session — the mirror of
37
48
  * `hostedLoginUrl`, and the only thing that actually signs a user out on
@@ -18,7 +18,7 @@ export { FoirAuthDowngradedError, FoirSessionExpiredError } from '../graphql/foi
18
18
  export { AUTH_OPS } from './ops';
19
19
  export type { AuthOpName, AuthOpOptions } from './ops';
20
20
  export { hostedLoginUrl, hostedLogoutUrl, fetchHostedSession, hostedSignInNoticeOf } from './hosted';
21
- export type { HostedLoginConfig, HostedSession, HostedSessionResult, HostedSignInNotice } from './hosted';
21
+ export type { HostedLoginConfig, HostedLoginOptions, HostedSession, HostedSessionResult, HostedSignInNotice } from './hosted';
22
22
  export { createFoirSessionClient } from './session-client';
23
23
  export type { FoirSessionClient, FoirSessionConfig, FoirSessionUser, } from './session-client';
24
24
  export type { AuthResult, ProviderLoginResult, AuthProviderPublic, ProviderLoginInput, ProviderCallbackInput, ProviderOTPVerifyInput, SignupContext, SignupMode, AuthConfig, AuthConfigProvider, DefaultAuthProvider, CurrentUser, ProviderToken, ProviderTokenRefresh, DeleteAccountResult, MyOrganization, MyProfile, MyProfileChange, AuthorizedApp, MySession, MyAddress, MyDataExport, NewPersonalAccessToken, MySignInMethod, MyFactor, StepUp, FactorEnrollment, } from './operations';
@@ -27,6 +27,7 @@
27
27
  * api.foir.dev but a browser cannot reach them there — the cookie would be
28
28
  * cross-site, and that CORS surface deliberately refuses credentials.
29
29
  */
30
+ import type { HostedLoginOptions } from './hosted';
30
31
  export interface FoirSessionConfig {
31
32
  /**
32
33
  * Your verified custom login domain, e.g. `https://login.acme.com`. Must share
@@ -66,8 +67,12 @@ export interface FoirSessionClient {
66
67
  getUser(): Promise<FoirSessionUser | null>;
67
68
  /** Revoke the session server-side and clear the cookie. Idempotent. */
68
69
  signOut(): Promise<void>;
69
- /** Where to send the browser to sign in. */
70
- signInUrl(returnTo?: string): string;
70
+ /**
71
+ * Where to send the browser to sign in. Name the organization the sign-in is
72
+ * for with `options.org` when one auth client serves an address per
73
+ * organization.
74
+ */
75
+ signInUrl(returnTo?: string, options?: HostedLoginOptions): string;
71
76
  /** Observe session changes. Returns an unsubscribe function. */
72
77
  subscribe(listener: (user: FoirSessionUser | null) => void): () => void;
73
78
  /** Cancel the refresh timer. Call on unmount. */
@@ -113,10 +113,11 @@ function createFoirSessionClient(config) {
113
113
  forget();
114
114
  config.onSignOut?.();
115
115
  },
116
- signInUrl(returnTo) {
116
+ signInUrl(returnTo, options = {}) {
117
117
  const dest = new URL(trimSlash(config.loginOrigin));
118
118
  const target = returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
119
119
  if (target && target !== "/") dest.searchParams.set("returnTo", target);
120
+ if (options.org) dest.searchParams.set("org", options.org);
120
121
  return dest.toString();
121
122
  },
122
123
  subscribe(listener) {
package/dist/auth.js CHANGED
@@ -1567,6 +1567,8 @@ var GRANT_TABLE = [
1567
1567
  ["authclients:write", "none", [], []],
1568
1568
  ["domains:read", "none", [], []],
1569
1569
  ["domains:write", "none", [], []],
1570
+ ["loginpages:read", "none", [], []],
1571
+ ["loginpages:write", "none", [], []],
1570
1572
  ["secrets:read", "none", [], []],
1571
1573
  ["secrets:list", "none", [], []],
1572
1574
  ["secrets:put", "none", [], []],
@@ -1604,7 +1606,7 @@ var GRANT_TABLE = [
1604
1606
  ["projects:read", "none", [], []],
1605
1607
  ["projects:write", "none", [], []],
1606
1608
  ["project:stand", "none", [], []],
1607
- ["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "authclients:read", "authclients:write", "domains:read", "domains:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
1609
+ ["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "authclients:read", "authclients:write", "domains:read", "domains:write", "loginpages:read", "loginpages:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
1608
1610
  ["projects:create", "none", [], []],
1609
1611
  ["projects:delete", "none", [], []],
1610
1612
  ["tenants:read", "none", [], []],
@@ -1797,11 +1799,14 @@ async function requireScope(adapter, env, scope) {
1797
1799
  var DEFAULT_COOKIE_NAME = "foir_session";
1798
1800
  var DEFAULT_SESSION_PATH = "/session/token";
1799
1801
  var DEFAULT_END_SESSION_PATH = "/session/end-session";
1800
- function hostedLoginUrl(config, returnTo) {
1802
+ function hostedLoginUrl(config, returnTo, options = {}) {
1801
1803
  const dest = new URL(config.loginOrigin);
1802
1804
  if (returnTo && returnTo !== "/") {
1803
1805
  dest.searchParams.set("returnTo", returnTo);
1804
1806
  }
1807
+ if (options.org) {
1808
+ dest.searchParams.set("org", options.org);
1809
+ }
1805
1810
  return dest.toString();
1806
1811
  }
1807
1812
  function hostedLogoutUrl(config, clientId, returnTo) {
@@ -2017,10 +2022,11 @@ function createFoirSessionClient(config) {
2017
2022
  forget();
2018
2023
  config.onSignOut?.();
2019
2024
  },
2020
- signInUrl(returnTo) {
2025
+ signInUrl(returnTo, options = {}) {
2021
2026
  const dest = new URL(trimSlash(config.loginOrigin));
2022
2027
  const target = returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
2023
2028
  if (target && target !== "/") dest.searchParams.set("returnTo", target);
2029
+ if (options.org) dest.searchParams.set("org", options.org);
2024
2030
  return dest.toString();
2025
2031
  },
2026
2032
  subscribe(listener) {
@@ -113,6 +113,23 @@ export declare function placementFrameAncestors(origins: readonly string[]): str
113
113
  export declare class PlacementRefused extends Error {
114
114
  constructor(reason: string);
115
115
  }
116
+ /** One error of a GraphQL answer, as Foir's API sends it. */
117
+ export interface PlacementRequestError {
118
+ message: string;
119
+ path?: (string | number)[];
120
+ extensions?: Record<string, unknown>;
121
+ }
122
+ /**
123
+ * A request the page made on its token that Foir's API refused or failed.
124
+ * The message says each distinct refusal once: a read refused on a field is
125
+ * answered once per row, and one missing grant over five hundred rows would
126
+ * otherwise read five hundred times. `errors` keeps every one, with its path.
127
+ */
128
+ export declare class PlacementRequestFailed extends Error {
129
+ readonly errors: PlacementRequestError[];
130
+ readonly status: number;
131
+ constructor(errors: PlacementRequestError[], status: number);
132
+ }
116
133
  export interface ConnectPlacementOptions {
117
134
  /** Foir's API, e.g. `https://api.foir.dev`. */
118
135
  apiUrl: string;
package/dist/placement.js CHANGED
@@ -41,6 +41,8 @@ var GRANT_TABLE = [
41
41
  ["authclients:write", "none", [], []],
42
42
  ["domains:read", "none", [], []],
43
43
  ["domains:write", "none", [], []],
44
+ ["loginpages:read", "none", [], []],
45
+ ["loginpages:write", "none", [], []],
44
46
  ["secrets:read", "none", [], []],
45
47
  ["secrets:list", "none", [], []],
46
48
  ["secrets:put", "none", [], []],
@@ -78,7 +80,7 @@ var GRANT_TABLE = [
78
80
  ["projects:read", "none", [], []],
79
81
  ["projects:write", "none", [], []],
80
82
  ["project:stand", "none", [], []],
81
- ["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "authclients:read", "authclients:write", "domains:read", "domains:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
83
+ ["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "authclients:read", "authclients:write", "domains:read", "domains:write", "loginpages:read", "loginpages:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
82
84
  ["projects:create", "none", [], []],
83
85
  ["projects:delete", "none", [], []],
84
86
  ["tenants:read", "none", [], []],
@@ -266,6 +268,17 @@ var PlacementRefused = class extends Error {
266
268
  this.name = "PlacementRefused";
267
269
  }
268
270
  };
271
+ var PlacementRequestFailed = class extends Error {
272
+ errors;
273
+ status;
274
+ constructor(errors, status) {
275
+ const said = [...new Set(errors.map((e) => e.message))];
276
+ super(`[foir] placement request failed: ${said.length > 0 ? said.join("; ") : `HTTP ${status}`}`);
277
+ this.name = "PlacementRequestFailed";
278
+ this.errors = errors;
279
+ this.status = status;
280
+ }
281
+ };
269
282
  function isHostMessage(data) {
270
283
  if (typeof data !== "object" || data === null) return false;
271
284
  const t = data.type;
@@ -325,8 +338,7 @@ function connectPlacement(options) {
325
338
  });
326
339
  const body = await res.json().catch(() => ({}));
327
340
  if (!res.ok || body.errors && body.errors.length > 0) {
328
- const messages = body.errors?.map((e) => e.message).join("; ") || `HTTP ${res.status}`;
329
- throw new Error(`[foir] placement request failed: ${messages}`);
341
+ throw new PlacementRequestFailed(body.errors ?? [], res.status);
330
342
  }
331
343
  return body.data;
332
344
  },
@@ -381,6 +393,7 @@ export {
381
393
  FOIR_CONSOLE_ORIGINS,
382
394
  PLACEMENT_MESSAGE,
383
395
  PlacementRefused,
396
+ PlacementRequestFailed,
384
397
  connectPlacement,
385
398
  placementFrameAncestors
386
399
  };
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { AuthProviderPublic, FoirAccess, FoirUser, FoirSessionConfig, SessionAdapter } from '../auth';
2
+ import type { AuthProviderPublic, FoirAccess, FoirUser, FoirSessionConfig, HostedLoginOptions, SessionAdapter } from '../auth';
3
3
  import type { ProviderCallbackInput, ProviderLoginInput } from '../auth';
4
4
  import type { FoirEnv } from '../graphql/foir-request';
5
5
  /** Where the session hydration stands. */
@@ -53,9 +53,10 @@ export interface FoirContextValue {
53
53
  /**
54
54
  * In `hostedSession` mode, where to send the browser to sign in — sign-in is a
55
55
  * redirect to your Foir login domain, not an in-page form. Null in token mode,
56
- * where you call {@link FoirContextValue.signIn} instead.
56
+ * where you call {@link FoirContextValue.signIn} instead. `options.org` names
57
+ * the organization the sign-in is for.
57
58
  */
58
- hostedSignInUrl: ((returnTo?: string) => string) | null;
59
+ hostedSignInUrl: ((returnTo?: string, options?: HostedLoginOptions) => string) | null;
59
60
  }
60
61
  export interface FoirProviderProps {
61
62
  /** API config: `{ PLATFORM_API_URL, PLATFORM_API_KEY }` (a publishable key). */
@@ -151,5 +152,5 @@ export declare function useFoirAuth(): {
151
152
  refresh: () => Promise<boolean>;
152
153
  getToken: () => Promise<string | null>;
153
154
  getProviders: () => Promise<AuthProviderPublic[]>;
154
- hostedSignInUrl: ((returnTo?: string) => string) | null;
155
+ hostedSignInUrl: ((returnTo?: string, options?: HostedLoginOptions) => string) | null;
155
156
  };
package/dist/react.js CHANGED
@@ -1248,6 +1248,8 @@ var GRANT_TABLE = [
1248
1248
  ["authclients:write", "none", [], []],
1249
1249
  ["domains:read", "none", [], []],
1250
1250
  ["domains:write", "none", [], []],
1251
+ ["loginpages:read", "none", [], []],
1252
+ ["loginpages:write", "none", [], []],
1251
1253
  ["secrets:read", "none", [], []],
1252
1254
  ["secrets:list", "none", [], []],
1253
1255
  ["secrets:put", "none", [], []],
@@ -1285,7 +1287,7 @@ var GRANT_TABLE = [
1285
1287
  ["projects:read", "none", [], []],
1286
1288
  ["projects:write", "none", [], []],
1287
1289
  ["project:stand", "none", [], []],
1288
- ["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "authclients:read", "authclients:write", "domains:read", "domains:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
1290
+ ["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "authclients:read", "authclients:write", "domains:read", "domains:write", "loginpages:read", "loginpages:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
1289
1291
  ["projects:create", "none", [], []],
1290
1292
  ["projects:delete", "none", [], []],
1291
1293
  ["tenants:read", "none", [], []],
@@ -1549,10 +1551,11 @@ function createFoirSessionClient(config) {
1549
1551
  forget();
1550
1552
  config.onSignOut?.();
1551
1553
  },
1552
- signInUrl(returnTo) {
1554
+ signInUrl(returnTo, options = {}) {
1553
1555
  const dest = new URL(trimSlash(config.loginOrigin));
1554
1556
  const target = returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
1555
1557
  if (target && target !== "/") dest.searchParams.set("returnTo", target);
1558
+ if (options.org) dest.searchParams.set("org", options.org);
1556
1559
  return dest.toString();
1557
1560
  },
1558
1561
  subscribe(listener) {
@@ -1779,7 +1782,7 @@ function FoirProvider({
1779
1782
  }, [refresh, loadAccess, accessWasRead]);
1780
1783
  const getProviders = useCallback(() => getAuthProviders(envRef.current), []);
1781
1784
  const hostedSignInUrl = useMemo(
1782
- () => hosted ? (returnTo) => hosted.signInUrl(returnTo) : null,
1785
+ () => hosted ? (returnTo, options) => hosted.signInUrl(returnTo, options) : null,
1783
1786
  [hosted]
1784
1787
  );
1785
1788
  const value = useMemo(
package/dist/server.js CHANGED
@@ -115,6 +115,8 @@ var GRANT_TABLE = [
115
115
  ["authclients:write", "none", [], []],
116
116
  ["domains:read", "none", [], []],
117
117
  ["domains:write", "none", [], []],
118
+ ["loginpages:read", "none", [], []],
119
+ ["loginpages:write", "none", [], []],
118
120
  ["secrets:read", "none", [], []],
119
121
  ["secrets:list", "none", [], []],
120
122
  ["secrets:put", "none", [], []],
@@ -152,7 +154,7 @@ var GRANT_TABLE = [
152
154
  ["projects:read", "none", [], []],
153
155
  ["projects:write", "none", [], []],
154
156
  ["project:stand", "none", [], []],
155
- ["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "authclients:read", "authclients:write", "domains:read", "domains:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
157
+ ["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "authclients:read", "authclients:write", "domains:read", "domains:write", "loginpages:read", "loginpages:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
156
158
  ["projects:create", "none", [], []],
157
159
  ["projects:delete", "none", [], []],
158
160
  ["tenants:read", "none", [], []],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foir/sdk",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Official Foir SDK — editor iframe client + server-side platform helpers",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -168,7 +168,7 @@
168
168
  "blurhash": "^2.0.5",
169
169
  "graphql": "^16.10.0",
170
170
  "hls.js": "^1.6.16",
171
- "jsdom": "^27.0.0",
171
+ "jsdom": "^28.1.0",
172
172
  "react": "^19.2.0",
173
173
  "react-dom": "^19.2.0",
174
174
  "tsup": "^8.5.1",