cauthy-auth 0.32.0 → 0.34.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/README.md CHANGED
@@ -90,12 +90,13 @@ import { OrganizationMembers } from 'cauthy-auth/react'
90
90
  organizationId={org.id}
91
91
  roles={(role) => role !== 'billing'} // the roles offered when adding or changing one
92
92
  hide={['archive', 'delete']} // never offered: 'archive', 'delete', 'rename', 'leave'
93
+ roleLabel={(c) => t(`roles.${c.role}`)} // a role's name wherever one shows. Default: the catalog's
93
94
  onSaved={(e) => toast(e.kind)} // member.added, member.role_changed, member.removed, member.left,
94
95
  /> // member.invited_again, organization.renamed, .archived, .deleted
95
96
 
96
97
  const { organizations } = useAuth()
97
98
  const [org] = await organizations.list() // org.can: { readMembers, manageMembers, removeMembers, update, archive, delete }
98
- await organizations.add(org.id, { email: 'ana@example.com', role: 'reader' })
99
+ await organizations.add(org.id, { email: 'ana@example.com', role: 'reader' }) // already a member: error member_exists, role unchanged
99
100
  await organizations.setRole(org.id, userId, 'admin')
100
101
  await organizations.remove(org.id, userId) // anybody may remove themselves: leave
101
102
  await organizations.rename(org.id, 'Acme Books') // r.organization
@@ -173,15 +174,25 @@ The ready-made forms wear the look set for your application in Cauthy's dashboar
173
174
  import 'cauthy-auth/styles.css'
174
175
  ```
175
176
 
176
- Or draw them with your own `components` and `classNames`, and pass `unstyled`. Every part the forms draw can be yours; a part not given is drawn as before. With every part given and `unstyled`, the forms add only structural wrappers (`div`, `form`, `section`, lists):
177
+ Or draw them with your own `components` and `classNames`, and pass `unstyled`. Every part the forms draw can be yours; a part not given is drawn as before. With every part given and `unstyled`, the forms add no element of their own but lists:
177
178
 
178
179
  ```tsx
179
- <SignIn unstyled components={{ Button, Input, Checkbox, Select, Field, Text, Badge, Link, Divider, Alert, Confirm }} />
180
+ <SignIn unstyled components={{ Form, Button, Input, Checkbox, Select, Field, Text, Badge, Link, Divider, Alert, Confirm }} />
180
181
  ```
181
182
 
183
+ `Form` is every element the forms put around their parts: each form and each of its steps, and each part of `<Account />` and `<OrganizationMembers />`. Give it your design system's stack and the gaps between fields and buttons are yours, with no CSS on the page:
184
+
185
+ ```tsx
186
+ const Form: AuthComponents['Form'] = ({ as, children, ...rest }) =>
187
+ as === 'form' ? <Stack as="form" gap={3} {...rest}>{children}</Stack> : <Stack gap={3} {...rest}>{children}</Stack>
188
+ ```
189
+
190
+ `Button` is told what kind of button it is: `primary` sends the form; `secondary` is any other way (a provider, a passkey, a sign-in link, SSO, cancel, send again); `danger` removes, leaves, deletes, disconnects, signs out everywhere or turns the second step off. `loading` is true on the one button whose action is under way; every button is `disabled` meanwhile.
191
+
182
192
  | Part | Is given |
183
193
  | --- | --- |
184
- | `Button` | `type`, `disabled`, `className`, `onClick`, `children` |
194
+ | `Form` | `as` (`form` or `div`), `onSubmit`, `noValidate`, `aria-busy`, `className`, `section`, `children`. Render a `<form>` for `as="form"`, for Enter to send it. `section` names each part of `<Account />` (`profile`, `passkeys`… as in `AccountSection`) and of `<OrganizationMembers />` (`members`, `add-member`, `organization`, `rename`), to draw each as a card |
195
+ | `Button` | `type`, `variant` (`primary`, `secondary`, `danger`), `loading`, `disabled`, `className`, `onClick`, `children` |
185
196
  | `Input` | `id`, `name`, `type`, `value`, `autoComplete`, `required`, `className`, `aria-invalid`, `aria-describedby`, `onChange(e)` |
186
197
  | `Checkbox` | `id`, `name`, `checked`, `required`, `disabled`, `className`, `onChange(checked)`, `children` (its label) |
187
198
  | `Select` | `id`, `name`, `value`, `options: { value, label }[]`, `disabled`, `className`, `aria-label`, `onChange(value)` |
@@ -193,6 +204,14 @@ Or draw them with your own `components` and `classNames`, and pass `unstyled`. E
193
204
  | `Alert` | `tone` (`danger`, `info`, `success`), `children`. Announce it: `role="alert"` or `role="status"` |
194
205
  | `Confirm` | A function, `({ title, body, confirmLabel, cancelLabel, danger }) => Promise<boolean>`: your own dialog before removing a member, leaving, archiving or deleting. Without it, the question shows inline |
195
206
 
207
+ A form checks every field before it sends anything: each one wrong shows its error at once, in the order they are drawn, and the first takes the focus. `<SignUp />` draws its fields once the workspace has said what it asks (a company's name, the legal boxes), so nothing moves as they come.
208
+
209
+ What Cauthy mails from these forms (a sign-in link, a new password, a confirmation) goes in the page's language (`<html lang>`). Give `<CauthyProvider locale>` your application's own instead, or a function for a person who can change it:
210
+
211
+ ```tsx
212
+ <CauthyProvider locale={() => i18n.language}>
213
+ ```
214
+
196
215
  `texts` changes any sentence. Its keys are stable: `email`, `password`, `name`, `code`, `signIn`, `signUp`, `continueWith`, `orWithEmail`, `forgotPassword`, `noAccount`, `createAccount`, `company`, `iAccept`, `and`, `termsOfService`, `privacyPolicy`, `haveAccount`, `sendLink`, `linkSent`, `codeHelp`, `codeHelpPhone`, `codeHelpEmail`, `sendEmailAgain`, `sendCodeSms`, `sendCodeWhatsapp`, `codeSent`, `pushSend`, `pushPick`, `pushWaiting`, `phone`, `phoneHelp`, `phoneOn`, `phoneOff`, `addPhone`, `removePhone`, `bySms`, `byWhatsapp`, `channel`, `verify`, `checkEmail`, `backToSignIn`, `emailMeALink`, `usePasskey`, `codeFromMail`, `signInLinkSent`, `newPassword`, `savePassword`, `passwordChanged`, `confirming`, `emailConfirmed`, `invitedTo`, `invitedToOrganization`, `invitedAs`, `choosePassword`, `acceptInvite`, `openingInvitation`, `invitationInvalid`, `profile`, `save`, `saved`, `currentPassword`, `changePassword`, `passwordChangedHere`, `secondStep`, `secondStepOn`, `secondStepOff`, `turnOn`, `turnOff`, `addToApp`, `openInApp`, `recoveryCodes`, `newRecoveryCodes`, `keepCodes`, `passkeys`, `managePasskeys`, `passkeysHelp`, `noPasskeys`, `passkeyName`, `addPasskey` (unused), `addPasskeyDevice`, `addSecurityKey`, `passkeyKinds`, `removePasskey`, `passkeyAdded`, `passkeyUnnamed`, `sessions`, `signOutEverywhere`, `agents`, `agentsHelp`, `disconnect`, `anAgent`, `unknownDevice`, `emailAddress`, `newEmail`, `changeEmail`, `emailSent`, `linkedAccounts`, `noLinkedAccounts`, `linkWith`, `unlink`, `linked`, `ssoOnly`, `members`, `membersHidden`, `organizationMissing`, `memberYou`, `memberInvited`, `memberManager`, `memberRole`, `memberAdd`, `memberAdded`, `memberRemove`, `memberRemoveConfirm`, `memberLeave`, `memberLeaveConfirm`, `memberInviteAgain`, `memberInvitationSent`, `organization`, `organizationRename`, `organizationRenamed`, `organizationArchive`, `organizationArchiveConfirm`, `organizationArchived`, `organizationDelete`, `organizationDeleteConfirm`, `organizationDeleted`, `yes`, `cancel`, `termsChanged`, `termsChangedHelp`, `acceptTerms`, and `errors` by error code. Built in: English and Spanish (`locale`). The dashboard can start the look from your stylesheet (shadcn/ui and Tailwind, Stackmyth, or variables of your own); `npx cauthy-auth theme --css src/app/globals.css` reads it the same way from a terminal.
197
216
 
198
217
  Connect your application to Cauthy in one step, from Cauthy's dashboard ("Connect a System") or from a terminal. In the application's folder, `init` reads its framework (Next.js, TanStack Start, React Router or Remix, SvelteKit, Vite + React, Express, Hono, Fastify), makes the client, writes every variable to its env file, `CAUTHY_SECRET` included, and lays down the route and a sign-in page, never over a file that is there:
@@ -209,6 +228,8 @@ const org = await auth.system.organizations.create({ name: 'Acme' })
209
228
  await auth.system.members.add(org.id, { role: 'owner', email: 'ana@acme.com' })
210
229
  ```
211
230
 
231
+ `members.add` only adds: a person who already belongs keeps their role, and it throws `CauthyApiError` with `code: 'member_exists'` (409). Change a role with `members.setRole`, or pass `onlyNew: false` to set the role of whoever is already there. `<OrganizationMembers />` and `organizations.add` never change a member's role either: they answer `member_exists` (the text is `errors.member_exists`).
232
+
212
233
  Its invitations open your `pages.acceptInvite` when the application knows its public origin (`baseURL`, `CAUTHY_BASE_URL`); otherwise Cauthy's own page.
213
234
 
214
235
  A backend that is handed a Cauthy access token checks it with Cauthy's published keys: no secret, no call per request.
@@ -1,4 +1,4 @@
1
- import { J as JsonValue, S as Session, t as SessionStatus, s as SessionOrganization, f as CheckQuestion, k as OrganizationRef, g as CheckResult, d as CauthySystem, q as PublicSession, c as CauthyOptions } from './system-BvvoNs2y.js';
1
+ import { J as JsonValue, S as Session, t as SessionStatus, s as SessionOrganization, f as CheckQuestion, k as OrganizationRef, g as CheckResult, d as CauthySystem, q as PublicSession, c as CauthyOptions } from './system-Bz4haPrI.js';
2
2
  import { JWTVerifyGetKey } from 'jose';
3
3
 
4
4
  /** Something that happened in Cauthy, as a subscriber is told. Moments are UTC. */
@@ -1,4 +1,4 @@
1
- import { J as JsonValue, S as Session, t as SessionStatus, s as SessionOrganization, f as CheckQuestion, k as OrganizationRef, g as CheckResult, d as CauthySystem, q as PublicSession, c as CauthyOptions } from './system-BrrJsrFW.mjs';
1
+ import { J as JsonValue, S as Session, t as SessionStatus, s as SessionOrganization, f as CheckQuestion, k as OrganizationRef, g as CheckResult, d as CauthySystem, q as PublicSession, c as CauthyOptions } from './system-DUGV040y.mjs';
2
2
  import { JWTVerifyGetKey } from 'jose';
3
3
 
4
4
  /** Something that happened in Cauthy, as a subscriber is told. Moments are UTC. */
package/dist/cli.js CHANGED
@@ -1084,7 +1084,7 @@ function createSystem(given = {}) {
1084
1084
  return toMember(await data(
1085
1085
  "POST",
1086
1086
  `/v1/organizations/${id(orgId)}/members`,
1087
- { role: m.role, user_id: m.userId, email: m.email, name: m.name, locale: m.locale, client_id: clientId },
1087
+ { role: m.role, user_id: m.userId, email: m.email, name: m.name, locale: m.locale, client_id: clientId, only_new: m.onlyNew !== false },
1088
1088
  inviteHeaders
1089
1089
  ));
1090
1090
  },
package/dist/client.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CauthyTheme } from './theme.mjs';
2
- import { q as PublicSession, f as CheckQuestion, k as OrganizationRef, g as CheckResult } from './system-BrrJsrFW.mjs';
3
- export { h as CheckRule } from './system-BrrJsrFW.mjs';
2
+ import { q as PublicSession, f as CheckQuestion, k as OrganizationRef, g as CheckResult } from './system-DUGV040y.mjs';
3
+ export { h as CheckRule } from './system-DUGV040y.mjs';
4
4
  import { A as AuthField, b as PasswordPolicy } from './validate-sEXG82po.mjs';
5
5
  export { D as DEFAULT_PASSWORD_POLICY, F as FieldProblem, M as MIN_PASSWORD, P as PasswordClass, a as PasswordLocale, c as PasswordReason, d as checkCode, e as checkEmail, f as checkName, g as checkNewPassword, h as checkPassword, i as describePasswordPolicy, j as explainPasswordProblems, p as passwordPolicyOf, k as passwordProblems } from './validate-sEXG82po.mjs';
6
6
  import 'jose';
@@ -291,7 +291,11 @@ interface OrganizationsClient {
291
291
  members(organizationId: string): Promise<OrganizationPerson[] | null>;
292
292
  /** The roles a member may be given, or null when the person may not see them. */
293
293
  roles(organizationId: string): Promise<OrganizationRoleChoice[] | null>;
294
- /** Adds a person by their address; an address with no account gets one and its invitation. */
294
+ /**
295
+ * Adds a person by their address; an address with no account gets one and
296
+ * its invitation. It only adds: a person who already belongs keeps their
297
+ * role and the result is the error `member_exists` (change it with `setRole`).
298
+ */
295
299
  add(organizationId: string, form: {
296
300
  email: string;
297
301
  role: string;
package/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CauthyTheme } from './theme.js';
2
- import { q as PublicSession, f as CheckQuestion, k as OrganizationRef, g as CheckResult } from './system-BvvoNs2y.js';
3
- export { h as CheckRule } from './system-BvvoNs2y.js';
2
+ import { q as PublicSession, f as CheckQuestion, k as OrganizationRef, g as CheckResult } from './system-Bz4haPrI.js';
3
+ export { h as CheckRule } from './system-Bz4haPrI.js';
4
4
  import { A as AuthField, b as PasswordPolicy } from './validate-sEXG82po.js';
5
5
  export { D as DEFAULT_PASSWORD_POLICY, F as FieldProblem, M as MIN_PASSWORD, P as PasswordClass, a as PasswordLocale, c as PasswordReason, d as checkCode, e as checkEmail, f as checkName, g as checkNewPassword, h as checkPassword, i as describePasswordPolicy, j as explainPasswordProblems, p as passwordPolicyOf, k as passwordProblems } from './validate-sEXG82po.js';
6
6
  import 'jose';
@@ -291,7 +291,11 @@ interface OrganizationsClient {
291
291
  members(organizationId: string): Promise<OrganizationPerson[] | null>;
292
292
  /** The roles a member may be given, or null when the person may not see them. */
293
293
  roles(organizationId: string): Promise<OrganizationRoleChoice[] | null>;
294
- /** Adds a person by their address; an address with no account gets one and its invitation. */
294
+ /**
295
+ * Adds a person by their address; an address with no account gets one and
296
+ * its invitation. It only adds: a person who already belongs keeps their
297
+ * role and the result is the error `member_exists` (change it with `setRole`).
298
+ */
295
299
  add(organizationId: string, form: {
296
300
  email: string;
297
301
  role: string;