@protobase/client 0.9.0 → 0.10.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.
@@ -35,6 +35,11 @@ export type SetupStatus = {
35
35
  socialProviders: string[];
36
36
  platformSignIn?: PlatformSignIn;
37
37
  staffSignIn?: string;
38
+ /**
39
+ * When people have passkeys made for more than one of the app's domains: those domains, the one new passkeys are made
40
+ * for first. A passkey sign-in asks for one at a time (see `signInWithPasskey`).
41
+ */
42
+ passkeyDomains?: string[];
38
43
  /** With organizations: who may create them, and every role with its label, `membership` for those a member can hold. */
39
44
  organizations?: {
40
45
  create: 'admins' | 'everyone';
package/dist/auth.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { SignInPolicy, SignInPolicyState } from "./sign-in-policy.js";
2
2
  import { AccountSignIn, AuthenticatorSetup, Passkey } from "./account-security.js";
3
3
  import { CurrentOrganization, InvitationPreview, InvitationSent, Organization, OrganizationInvitation, OrganizationMember, OrganizationRole } from "./organizations.js";
4
+ import { ActiveRole } from "./roles.js";
4
5
  import { StaffSignIn } from "./staff-sign-in.js";
5
6
  import { AuthUser, SetupStatus, SignInResult, TwoFactorMethod } from "./auth-types.js";
6
7
  import { AuthError } from "./auth-error.js";
@@ -22,7 +23,7 @@ export declare const tokenExpiry: (token: string) => number;
22
23
  * The browser side of Better Auth: sign in with a password, an emailed code or a passkey (with a second step for an
23
24
  * account with two-factor authentication), the account's passkeys and two-factor authentication (`account`), the
24
25
  * sign-in policy for admins (`signInPolicy`), staff of the operator signing in as people (`staff`), organizations
25
- * (`organizations`), and a short-lived
26
+ * (`organizations`), the role the session acts with (`roles`), and a short-lived
26
27
  * JWT held in memory only (never in storage), fetched with the session cookie and refreshed before it expires.
27
28
  */
28
29
  export declare const createAuthSession: (options?: AuthSessionOptions) => {
@@ -92,6 +93,10 @@ export declare const createAuthSession: (options?: AuthSessionOptions) => {
92
93
  password?: string;
93
94
  }) => Promise<unknown>;
94
95
  };
96
+ roles: {
97
+ current: () => Promise<ActiveRole>;
98
+ switchTo: (role: string) => Promise<void>;
99
+ };
95
100
  signInPolicy: {
96
101
  read: () => Promise<SignInPolicyState>;
97
102
  save: (policy: SignInPolicy) => Promise<SignInPolicyState>;
@@ -109,7 +114,7 @@ export declare const createAuthSession: (options?: AuthSessionOptions) => {
109
114
  signIn: (email: string, password: string) => Promise<SignInResult>;
110
115
  sendSignInCode: (email: string) => Promise<void>;
111
116
  signInWithCode: (email: string, code: string) => Promise<SignInResult>;
112
- signInWithPasskey: () => Promise<AuthUser>;
117
+ signInWithPasskey: (domain?: string) => Promise<AuthUser>;
113
118
  sendTwoFactorCode: () => Promise<void>;
114
119
  verifyTwoFactor: ({ method, code, trustDevice }: {
115
120
  method: TwoFactorMethod | "backup";
package/dist/auth.js CHANGED
@@ -2,6 +2,7 @@ import { AuthError, authError } from "./auth-error.js";
2
2
  import { accountSecurity } from "./account-security.js";
3
3
  import { betterAuthClient } from "./better-auth-client.js";
4
4
  import { organizationsClient } from "./organizations.js";
5
+ import { rolesClient } from "./roles.js";
5
6
  import { signInFlows } from "./sign-in-flows.js";
6
7
  import { signInPolicyClient } from "./sign-in-policy.js";
7
8
  import { staffSignInClient } from "./staff-sign-in.js";
@@ -18,7 +19,7 @@ const tokenExpiry = (token) => {
18
19
  * The browser side of Better Auth: sign in with a password, an emailed code or a passkey (with a second step for an
19
20
  * account with two-factor authentication), the account's passkeys and two-factor authentication (`account`), the
20
21
  * sign-in policy for admins (`signInPolicy`), staff of the operator signing in as people (`staff`), organizations
21
- * (`organizations`), and a short-lived
22
+ * (`organizations`), the role the session acts with (`roles`), and a short-lived
22
23
  * JWT held in memory only (never in storage), fetched with the session cookie and refreshed before it expires.
23
24
  */
24
25
  const createAuthSession = (options = {}) => {
@@ -75,6 +76,7 @@ const createAuthSession = (options = {}) => {
75
76
  socialProviders: body.socialProviders ?? [],
76
77
  ...body.platformSignIn && { platformSignIn: body.platformSignIn },
77
78
  ...body.staffSignIn && { staffSignIn: body.staffSignIn },
79
+ ...body.passkeyDomains && { passkeyDomains: body.passkeyDomains },
78
80
  ...body.organizations && { organizations: body.organizations }
79
81
  };
80
82
  },
@@ -124,6 +126,7 @@ const createAuthSession = (options = {}) => {
124
126
  },
125
127
  account: accountSecurity(client),
126
128
  organizations: organizationsClient(client, () => token({ refresh: true })),
129
+ roles: rolesClient(client, () => token({ refresh: true })),
127
130
  signInPolicy: signInPolicyClient(client),
128
131
  staff: {
129
132
  ...staff,
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ import { ApiError, PreconditionFailedError, ProblemDetails, ProblemError, isApiE
7
7
  import { SignInPolicy, SignInPolicyClient, SignInPolicyState, SignInRule, StaffAccessRule } from "./sign-in-policy.js";
8
8
  import { AccountSecurity, AccountSignIn, AuthenticatorSetup, Passkey, RequiredSetup } from "./account-security.js";
9
9
  import { CurrentOrganization, InvitationPreview, InvitationSent, Organization, OrganizationInvitation, OrganizationMember, OrganizationRole, OrganizationsClient } from "./organizations.js";
10
+ import { ActiveRole, RoleOption, RolesClient } from "./roles.js";
10
11
  import { StaffSignIn, StaffSignInClient } from "./staff-sign-in.js";
11
12
  import { AuthUser, PlatformSignIn, RoleLabel, SetupStatus, SignInMethod, SignInResult, TwoFactorMethod } from "./auth-types.js";
12
13
  import { AuthError } from "./auth-error.js";
@@ -14,4 +15,4 @@ import { AuthSession, AuthSessionOptions, createAuthSession, tokenExpiry } from
14
15
  import { createStaticSession } from "./static-session.js";
15
16
  import { AssistantClient, AssistantClientOptions, AssistantConnection, createAssistantClient } from "./assistant.js";
16
17
  import { RuntimeClient, RuntimeClientOptions, RuntimePolicy, RuntimeStatus, createRuntimeClient } from "./runtime.js";
17
- export { type AccountSecurity, type AccountSignIn, ApiError, type AssistantClient, type AssistantClientOptions, type AssistantConnection, AuthError, type AuthSession, type AuthSessionOptions, type AuthUser, type AuthenticatorSetup, type BatchOp, type BatchRef, type BatchResult, type Client, type ClientOptions, type CurrentOrganization, type Facet, type FilterInput, type Histogram, type HistogramBucket, type InvitationPreview, type InvitationSent, type InvokeOptions, type ListPage, type ListParams, type Meta, type MetaResult, type Organization, type OrganizationInvitation, type OrganizationMember, type OrganizationRole, type OrganizationsClient, type Passkey, type PlatformSignIn, PreconditionFailedError, type ProblemDetails, type ProblemError, type RecordOf, type RecordPermissions, type RequiredSetup, type ResourcePermissions, type ResourceRef, type RoleLabel, type RuntimeClient, type RuntimeClientOptions, type RuntimePolicy, type RuntimeStatus, type Series, type SeriesParams, type SeriesPoint, type SetupStatus, type SignInMethod, type SignInPolicy, type SignInPolicyClient, type SignInPolicyState, type SignInResult, type SignInRule, type StaffAccessRule, type StaffSignIn, type StaffSignInClient, type Stored, type StoredFile, type TwoFactorMethod, type UploadOptions, type UploadProgress, type UploadResult, createAssistantClient, createAuthSession, createClient, createRuntimeClient, createStaticSession, filterText, isApiError, listWire, tokenExpiry };
18
+ export { type AccountSecurity, type AccountSignIn, type ActiveRole, ApiError, type AssistantClient, type AssistantClientOptions, type AssistantConnection, AuthError, type AuthSession, type AuthSessionOptions, type AuthUser, type AuthenticatorSetup, type BatchOp, type BatchRef, type BatchResult, type Client, type ClientOptions, type CurrentOrganization, type Facet, type FilterInput, type Histogram, type HistogramBucket, type InvitationPreview, type InvitationSent, type InvokeOptions, type ListPage, type ListParams, type Meta, type MetaResult, type Organization, type OrganizationInvitation, type OrganizationMember, type OrganizationRole, type OrganizationsClient, type Passkey, type PlatformSignIn, PreconditionFailedError, type ProblemDetails, type ProblemError, type RecordOf, type RecordPermissions, type RequiredSetup, type ResourcePermissions, type ResourceRef, type RoleLabel, type RoleOption, type RolesClient, type RuntimeClient, type RuntimeClientOptions, type RuntimePolicy, type RuntimeStatus, type Series, type SeriesParams, type SeriesPoint, type SetupStatus, type SignInMethod, type SignInPolicy, type SignInPolicyClient, type SignInPolicyState, type SignInResult, type SignInRule, type StaffAccessRule, type StaffSignIn, type StaffSignInClient, type Stored, type StoredFile, type TwoFactorMethod, type UploadOptions, type UploadProgress, type UploadResult, createAssistantClient, createAuthSession, createClient, createRuntimeClient, createStaticSession, filterText, isApiError, listWire, tokenExpiry };
@@ -3,7 +3,7 @@ import { FilterExpr } from "@protobase/schema";
3
3
  export type FilterInput = string | FilterExpr;
4
4
  export type ListParams = {
5
5
  filter?: FilterInput;
6
- /** AIP-132 text, for example `createdAt desc`. */
6
+ /** AIP-132 text, for example `created_at desc`. */
7
7
  orderBy?: string;
8
8
  pageSize?: number;
9
9
  pageToken?: string;
@@ -0,0 +1,26 @@
1
+ import { BetterAuthClient } from "./better-auth-client.js";
2
+ //#region src/roles.d.ts
3
+ /** A role someone can act with: its name and the label to show. */
4
+ export type RoleOption = {
5
+ name: string;
6
+ label: string;
7
+ };
8
+ /**
9
+ * The role the session acts with, the only one its tokens carry (`null` for someone without a role), and the roles the
10
+ * person holds where the session works, to switch to.
11
+ */
12
+ export type ActiveRole = {
13
+ active: string | null;
14
+ roles: RoleOption[];
15
+ };
16
+ /**
17
+ * Switching the role the session acts with. Its tokens carry that role alone, so a switch fetches a new token at once
18
+ * (`refreshToken`), as a switch of organization does. Each call rejects with `AuthError`.
19
+ */
20
+ export declare const rolesClient: (client: BetterAuthClient, refreshToken: () => Promise<unknown>) => {
21
+ current: () => Promise<ActiveRole>;
22
+ /** Acts with another role the person holds from now on, in this session and the next ones. */
23
+ switchTo: (role: string) => Promise<void>;
24
+ };
25
+ export type RolesClient = ReturnType<typeof rolesClient>;
26
+ //#endregion
package/dist/roles.js ADDED
@@ -0,0 +1,24 @@
1
+ import { authError } from "./auth-error.js";
2
+ //#region src/roles.ts
3
+ /**
4
+ * Switching the role the session acts with. Its tokens carry that role alone, so a switch fetches a new token at once
5
+ * (`refreshToken`), as a switch of organization does. Each call rejects with `AuthError`.
6
+ */
7
+ const rolesClient = (client, refreshToken) => ({
8
+ current: async () => {
9
+ const { data, error } = await client.$fetch("/role/current", { method: "GET" });
10
+ if (error) throw authError(error, "Could not read your roles");
11
+ return data;
12
+ },
13
+ /** Acts with another role the person holds from now on, in this session and the next ones. */
14
+ switchTo: async (role) => {
15
+ const { error } = await client.$fetch("/role/switch", {
16
+ method: "POST",
17
+ body: { role }
18
+ });
19
+ if (error) throw authError(error, "Could not switch the role");
20
+ await refreshToken();
21
+ }
22
+ });
23
+ //#endregion
24
+ export { rolesClient };
@@ -1,4 +1,5 @@
1
- import { authError } from "./auth-error.js";
1
+ import { AuthError, authError } from "./auth-error.js";
2
+ import { WebAuthnError, startAuthentication } from "@simplewebauthn/browser";
2
3
  //#region src/sign-in-flows.ts
3
4
  /**
4
5
  * The ways to sign in: a password, an emailed code or a passkey, and the second step after a password or a code for
@@ -41,12 +42,26 @@ const signInFlows = (client, signedIn) => {
41
42
  if (error || !data) throw authError(error, "Sign-in failed");
42
43
  return finish(data);
43
44
  },
44
- /** Asks the browser for one of the person's passkeys for this site. */
45
- signInWithPasskey: async () => {
46
- const { data, error } = await client.signIn.passkey();
47
- if (error || !data) throw authError(error, "Sign-in failed");
45
+ /**
46
+ * Asks the browser for one of the person's passkeys made for `domain`, one of the status's `passkeyDomains`, or for
47
+ * the app's passkey domain when left out. A browser asks for the passkeys of one domain at a time.
48
+ */
49
+ signInWithPasskey: async (domain) => {
50
+ const options = await client.$fetch("/passkey/generate-authenticate-options", {
51
+ method: "GET",
52
+ ...domain && { query: { rpId: domain } }
53
+ });
54
+ if (options.error || !options.data) throw authError(options.error, "Sign-in failed");
55
+ const { clientExtensionResults: _, ...response } = await startAuthentication({ optionsJSON: options.data }).catch((error) => {
56
+ throw new AuthError("Auth cancelled", 400, error instanceof WebAuthnError ? error.code : "AUTH_CANCELLED");
57
+ });
58
+ const verified = await client.$fetch("/passkey/verify-authentication", {
59
+ method: "POST",
60
+ body: { response }
61
+ });
62
+ if (verified.error || !verified.data) throw authError(verified.error, "Sign-in failed");
48
63
  await signedIn();
49
- return data.user;
64
+ return verified.data.user;
50
65
  },
51
66
  /** Emails the code for the second step, to the address of the account signing in. */
52
67
  sendTwoFactorCode: async () => {
@@ -4,7 +4,7 @@ import { AuthSession } from "./auth.js";
4
4
  /**
5
5
  * A session that is already signed in with a token you hold, for stories and scripts (`protobase token <email>`).
6
6
  * It never refreshes, so use a token that outlives the run. Its account has nothing to set up, and it cannot change
7
- * passkeys, two-factor authentication, the sign-in policy or organizations, and is never a staff session.
7
+ * passkeys, two-factor authentication, the sign-in policy, organizations or its role, and is never a staff session.
8
8
  */
9
9
  export declare const createStaticSession: (token: string, user?: AuthUser) => AuthSession;
10
10
  //#endregion
@@ -6,7 +6,7 @@ const unsupported = (what) => async () => {
6
6
  /**
7
7
  * A session that is already signed in with a token you hold, for stories and scripts (`protobase token <email>`).
8
8
  * It never refreshes, so use a token that outlives the run. Its account has nothing to set up, and it cannot change
9
- * passkeys, two-factor authentication, the sign-in policy or organizations, and is never a staff session.
9
+ * passkeys, two-factor authentication, the sign-in policy, organizations or its role, and is never a staff session.
10
10
  */
11
11
  const createStaticSession = (token, user = {
12
12
  id: "token",
@@ -79,6 +79,13 @@ const createStaticSession = (token, user = {
79
79
  stop: unsupported("end a staff session"),
80
80
  log: unsupported("read the staff sign-ins")
81
81
  },
82
+ roles: {
83
+ current: async () => ({
84
+ active: null,
85
+ roles: []
86
+ }),
87
+ switchTo: unsupported("switch roles")
88
+ },
82
89
  organizations: {
83
90
  list: async () => [],
84
91
  current: async () => ({
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { InferRecord, ResourceModel, UserMenuModel, ViewModel } from "@protobase/schema";
1
+ import { BottomBarModel, InferRecord, ResourceModel, UserMenuModel, ViewModel } from "@protobase/schema";
2
2
  import { PageModel } from "@protobase/layout";
3
3
  //#region src/types.d.ts
4
4
  /** A resource by name, or the builder from `resource(...)`, which carries its record type. */
@@ -65,6 +65,7 @@ export type ResourcePermissions = {
65
65
  /**
66
66
  * `assistant`: the assistant backend, for a caller with the `admin` or `ai` role when the app has one.
67
67
  * `runtime`: the runtime updates endpoint, for a caller with the `admin` role when the app has one.
68
+ * `bottomBar`: the tabs on phones, of the bottom bar that names the caller's active role.
68
69
  */
69
70
  export type Meta = {
70
71
  resources: ResourceModel[];
@@ -72,6 +73,7 @@ export type Meta = {
72
73
  pages?: PageModel[];
73
74
  permissions?: Record<string, ResourcePermissions>;
74
75
  userMenu?: UserMenuModel;
76
+ bottomBar?: Omit<BottomBarModel, 'roles'>;
75
77
  assistant?: {
76
78
  url: string;
77
79
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/client",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Typed fetch client for the Protobase REST API.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -30,8 +30,9 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@better-auth/passkey": "^1.7.7",
33
- "@protobase/layout": "0.9.0",
34
- "@protobase/schema": "0.9.0",
33
+ "@protobase/layout": "0.10.0",
34
+ "@protobase/schema": "0.10.0",
35
+ "@simplewebauthn/browser": "^13.3.0",
35
36
  "better-auth": "^1.7.7"
36
37
  }
37
38
  }