cauthy-auth 0.19.0 → 0.20.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/dist/react.d.mts CHANGED
@@ -265,6 +265,20 @@ interface AuthMethods {
265
265
  * forms show it themselves.
266
266
  */
267
267
  captchaSiteKey?: string;
268
+ /**
269
+ * Whether the workspace signs some people in through their company's SSO,
270
+ * found by their work address (`signIn.ssoDiscover`, `signIn.sso`).
271
+ */
272
+ sso: boolean;
273
+ }
274
+ /** A company's sign-in (an SSO connection of the workspace). */
275
+ interface SSOConnection {
276
+ /** What `signIn.sso({ connection })` takes. */
277
+ id: string;
278
+ /** The company's name, for "Continue with …". */
279
+ name: string;
280
+ /** The address can sign in no other way: a password, link or provider answers `sso_required`. */
281
+ enforced: boolean;
268
282
  }
269
283
  /**
270
284
  * What holds a passkey, as its authenticator says: a security key (a YubiKey),
@@ -577,6 +591,38 @@ interface AuthClient {
577
591
  social(provider: string, options?: {
578
592
  returnTo?: string;
579
593
  }): void;
594
+ /**
595
+ * Which company's sign-in a work address uses, if any (`found`), or
596
+ * `none`. It says so of the address's domain, never whether the address
597
+ * has an account.
598
+ */
599
+ ssoDiscover(form: {
600
+ email: string;
601
+ }): Promise<AuthResult<'found', {
602
+ connection: SSOConnection;
603
+ }> | AuthResult<'none'>>;
604
+ /**
605
+ * Sends the browser to the person's company (found by `email`, or named
606
+ * by `connection`); it comes back signed in, at `returnTo`. A sign-in the
607
+ * company refused comes back with `?auth_error=`. Any other way of
608
+ * signing in answers `sso_required` for an address its company holds to
609
+ * SSO: send it here then.
610
+ */
611
+ sso(target: {
612
+ email: string;
613
+ } | {
614
+ connection: string;
615
+ }, options?: {
616
+ returnTo?: string;
617
+ }): void;
618
+ /** Where `sso` sends the browser, for a plain link. */
619
+ ssoUrl(target: {
620
+ email: string;
621
+ } | {
622
+ connection: string;
623
+ }, options?: {
624
+ returnTo?: string;
625
+ }): string;
580
626
  /** Where `social` sends the browser, for a plain link. */
581
627
  socialUrl(provider: string, options?: {
582
628
  returnTo?: string;
@@ -723,6 +769,7 @@ interface AuthTexts {
723
769
  linkWith: string;
724
770
  unlink: string;
725
771
  linked: string;
772
+ ssoOnly: string;
726
773
  /** By error code; one missing shows what the server said. */
727
774
  errors: Record<string, string>;
728
775
  }
@@ -872,4 +919,4 @@ declare function useSession(): SessionContext;
872
919
  */
873
920
  declare function useAuth(): AuthClient & Pick<SessionContext, 'session' | 'status' | 'refresh'>;
874
921
 
875
- export { Account, type AccountClient, type Account$1 as AccountDetails, type AccountProps, type AccountSession, type AuthClient, type AuthError, type AuthFormProps, type AuthMethods, type AuthResult, type AuthTexts, CauthyProvider, type CauthyProviderProps, type ConnectedAgent, ForgotPassword, GoogleOneTap, type GoogleOneTapProps, type JsonValue, type LinkedIdentity, type Passkey, type PasskeyKind, type PermissionOf, type Policy, type PublicSession, ResetPassword, type RoleOf, type SecondStep, type SessionContext, type SessionOrganization, type SessionUser, SignIn, SignUp, VerifyEmail, can, canAutofillPasskeys, canUsePasskeys, createAuthClient, definePolicy, useAuth, useSession };
922
+ export { Account, type AccountClient, type Account$1 as AccountDetails, type AccountProps, type AccountSession, type AuthClient, type AuthError, type AuthFormProps, type AuthMethods, type AuthResult, type AuthTexts, CauthyProvider, type CauthyProviderProps, type ConnectedAgent, ForgotPassword, GoogleOneTap, type GoogleOneTapProps, type JsonValue, type LinkedIdentity, type Passkey, type PasskeyKind, type PermissionOf, type Policy, type PublicSession, ResetPassword, type RoleOf, type SSOConnection, type SecondStep, type SessionContext, type SessionOrganization, type SessionUser, SignIn, SignUp, VerifyEmail, can, canAutofillPasskeys, canUsePasskeys, createAuthClient, definePolicy, useAuth, useSession };
package/dist/react.d.ts CHANGED
@@ -265,6 +265,20 @@ interface AuthMethods {
265
265
  * forms show it themselves.
266
266
  */
267
267
  captchaSiteKey?: string;
268
+ /**
269
+ * Whether the workspace signs some people in through their company's SSO,
270
+ * found by their work address (`signIn.ssoDiscover`, `signIn.sso`).
271
+ */
272
+ sso: boolean;
273
+ }
274
+ /** A company's sign-in (an SSO connection of the workspace). */
275
+ interface SSOConnection {
276
+ /** What `signIn.sso({ connection })` takes. */
277
+ id: string;
278
+ /** The company's name, for "Continue with …". */
279
+ name: string;
280
+ /** The address can sign in no other way: a password, link or provider answers `sso_required`. */
281
+ enforced: boolean;
268
282
  }
269
283
  /**
270
284
  * What holds a passkey, as its authenticator says: a security key (a YubiKey),
@@ -577,6 +591,38 @@ interface AuthClient {
577
591
  social(provider: string, options?: {
578
592
  returnTo?: string;
579
593
  }): void;
594
+ /**
595
+ * Which company's sign-in a work address uses, if any (`found`), or
596
+ * `none`. It says so of the address's domain, never whether the address
597
+ * has an account.
598
+ */
599
+ ssoDiscover(form: {
600
+ email: string;
601
+ }): Promise<AuthResult<'found', {
602
+ connection: SSOConnection;
603
+ }> | AuthResult<'none'>>;
604
+ /**
605
+ * Sends the browser to the person's company (found by `email`, or named
606
+ * by `connection`); it comes back signed in, at `returnTo`. A sign-in the
607
+ * company refused comes back with `?auth_error=`. Any other way of
608
+ * signing in answers `sso_required` for an address its company holds to
609
+ * SSO: send it here then.
610
+ */
611
+ sso(target: {
612
+ email: string;
613
+ } | {
614
+ connection: string;
615
+ }, options?: {
616
+ returnTo?: string;
617
+ }): void;
618
+ /** Where `sso` sends the browser, for a plain link. */
619
+ ssoUrl(target: {
620
+ email: string;
621
+ } | {
622
+ connection: string;
623
+ }, options?: {
624
+ returnTo?: string;
625
+ }): string;
580
626
  /** Where `social` sends the browser, for a plain link. */
581
627
  socialUrl(provider: string, options?: {
582
628
  returnTo?: string;
@@ -723,6 +769,7 @@ interface AuthTexts {
723
769
  linkWith: string;
724
770
  unlink: string;
725
771
  linked: string;
772
+ ssoOnly: string;
726
773
  /** By error code; one missing shows what the server said. */
727
774
  errors: Record<string, string>;
728
775
  }
@@ -872,4 +919,4 @@ declare function useSession(): SessionContext;
872
919
  */
873
920
  declare function useAuth(): AuthClient & Pick<SessionContext, 'session' | 'status' | 'refresh'>;
874
921
 
875
- export { Account, type AccountClient, type Account$1 as AccountDetails, type AccountProps, type AccountSession, type AuthClient, type AuthError, type AuthFormProps, type AuthMethods, type AuthResult, type AuthTexts, CauthyProvider, type CauthyProviderProps, type ConnectedAgent, ForgotPassword, GoogleOneTap, type GoogleOneTapProps, type JsonValue, type LinkedIdentity, type Passkey, type PasskeyKind, type PermissionOf, type Policy, type PublicSession, ResetPassword, type RoleOf, type SecondStep, type SessionContext, type SessionOrganization, type SessionUser, SignIn, SignUp, VerifyEmail, can, canAutofillPasskeys, canUsePasskeys, createAuthClient, definePolicy, useAuth, useSession };
922
+ export { Account, type AccountClient, type Account$1 as AccountDetails, type AccountProps, type AccountSession, type AuthClient, type AuthError, type AuthFormProps, type AuthMethods, type AuthResult, type AuthTexts, CauthyProvider, type CauthyProviderProps, type ConnectedAgent, ForgotPassword, GoogleOneTap, type GoogleOneTapProps, type JsonValue, type LinkedIdentity, type Passkey, type PasskeyKind, type PermissionOf, type Policy, type PublicSession, ResetPassword, type RoleOf, type SSOConnection, type SecondStep, type SessionContext, type SessionOrganization, type SessionUser, SignIn, SignUp, VerifyEmail, can, canAutofillPasskeys, canUsePasskeys, createAuthClient, definePolicy, useAuth, useSession };
package/dist/react.js CHANGED
@@ -268,6 +268,11 @@ function createAuthClient(options = {}) {
268
268
  }
269
269
  const linkUrl = (provider, o = {}) => `${base}/me/identities/link/${encodeURIComponent(provider)}${o.returnTo ? `?return_to=${encodeURIComponent(o.returnTo)}` : ""}`;
270
270
  const socialUrl = (provider, o = {}) => `${base}/sign-in/social/${encodeURIComponent(provider)}${o.returnTo ? `?return_to=${encodeURIComponent(o.returnTo)}` : ""}`;
271
+ const ssoUrl = (target, o = {}) => {
272
+ const q = new URLSearchParams("connection" in target ? { connection: target.connection } : { email: target.email.trim() });
273
+ if (o.returnTo) q.set("return_to", o.returnTo);
274
+ return `${base}/sign-in/sso?${q}`;
275
+ };
271
276
  const passkeyUrl = (o = {}) => {
272
277
  const q = new URLSearchParams({ method: "passkey" });
273
278
  if (o.returnTo) q.set("return_to", o.returnTo);
@@ -279,11 +284,11 @@ function createAuthClient(options = {}) {
279
284
  const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
280
285
  if (res.ok) {
281
286
  const m = await res.json();
282
- return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true };
287
+ return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true, sso: m.sso === true };
283
288
  }
284
289
  } catch {
285
290
  }
286
- return { providers: [], signUp: true, magicLink: false, passkey: false, passkeyHere: false, passkeyOnCauthy: false };
291
+ return { providers: [], signUp: true, magicLink: false, passkey: false, passkeyHere: false, passkeyOnCauthy: false, sso: false };
287
292
  }
288
293
  async function ceremony(begin, finish, form, run, more = {}) {
289
294
  const begun = await post(begin, form, null);
@@ -381,6 +386,11 @@ function createAuthClient(options = {}) {
381
386
  socialUrl,
382
387
  social(provider, o) {
383
388
  window.location.assign(socialUrl(provider, o));
389
+ },
390
+ ssoDiscover: (f) => post("/sign-in/sso/discover", { email: f.email.trim() }, checkEmail(f.email)),
391
+ ssoUrl,
392
+ sso(target, o) {
393
+ window.location.assign(ssoUrl(target, o));
384
394
  }
385
395
  },
386
396
  signUp: {
@@ -601,6 +611,7 @@ var TEXTS = {
601
611
  linkWith: "Link",
602
612
  unlink: "Unlink",
603
613
  linked: "The account was linked.",
614
+ ssoOnly: "Your company signs you in to this account.",
604
615
  errors: {
605
616
  required: "This is required.",
606
617
  invalid_email: "That does not look like an email address.",
@@ -629,7 +640,15 @@ var TEXTS = {
629
640
  captcha_failed: "The check that you are not a bot did not pass. Try it again.",
630
641
  passkey_failed: "The passkey could not be used. Try again, or sign in another way.",
631
642
  passkey_exists: "This device already has a passkey for your account.",
632
- passkey_unavailable: "Passkeys cannot be used here."
643
+ passkey_unavailable: "Passkeys cannot be used here.",
644
+ sso_required: "This address signs in through its company.",
645
+ sso_not_found: "No company sign-in answers for this address.",
646
+ sso_failed: "The sign-in with your company could not be completed. Try again.",
647
+ sso_unavailable: "Your company's sign-in cannot be reached right now. Try again in a moment.",
648
+ invalid_assertion: "Your company's answer could not be checked. Try again.",
649
+ provisioning_disabled: "Your company's sign-in worked, but this workspace has no account for you. Ask its admin to add you.",
650
+ connection_inactive: "Your company's sign-in is turned off for this workspace.",
651
+ invalid_grant: "That sign-in expired. Try again."
633
652
  }
634
653
  },
635
654
  es: {
@@ -716,6 +735,7 @@ var TEXTS = {
716
735
  linkWith: "Vincular",
717
736
  unlink: "Desvincular",
718
737
  linked: "La cuenta qued\xF3 vinculada.",
738
+ ssoOnly: "A esta cuenta entras con el inicio de sesi\xF3n de tu empresa.",
719
739
  errors: {
720
740
  required: "Este dato es obligatorio.",
721
741
  invalid_email: "Eso no parece un correo.",
@@ -744,7 +764,15 @@ var TEXTS = {
744
764
  captcha_failed: "La verificaci\xF3n de que no eres un robot no pas\xF3. Int\xE9ntalo de nuevo.",
745
765
  passkey_failed: "No se pudo usar la llave de acceso. Intenta de nuevo o entra de otra forma.",
746
766
  passkey_exists: "Este dispositivo ya tiene una llave de acceso de tu cuenta.",
747
- passkey_unavailable: "Aqu\xED no se pueden usar llaves de acceso."
767
+ passkey_unavailable: "Aqu\xED no se pueden usar llaves de acceso.",
768
+ sso_required: "Este correo entra con el inicio de sesi\xF3n de su empresa.",
769
+ sso_not_found: "Ninguna empresa usa inicio de sesi\xF3n propio con este correo.",
770
+ sso_failed: "No se pudo completar el ingreso con tu empresa. Intenta de nuevo.",
771
+ sso_unavailable: "Ahora no se puede llegar al inicio de sesi\xF3n de tu empresa. Intenta en un momento.",
772
+ invalid_assertion: "No se pudo verificar la respuesta de tu empresa. Intenta de nuevo.",
773
+ provisioning_disabled: "El ingreso con tu empresa funcion\xF3, pero este workspace no tiene una cuenta para ti. P\xEDdele a su admin que te agregue.",
774
+ connection_inactive: "El inicio de sesi\xF3n de tu empresa est\xE1 apagado en este workspace.",
775
+ invalid_grant: "Ese ingreso venci\xF3. Intenta de nuevo."
748
776
  }
749
777
  }
750
778
  };
@@ -937,6 +965,25 @@ function SignIn(props) {
937
965
  };
938
966
  const autofill = (0, import_react.useRef)(null);
939
967
  const [offer, setOffer] = (0, import_react.useState)(0);
968
+ const [company, setCompany] = (0, import_react.useState)(null);
969
+ (0, import_react.useEffect)(() => {
970
+ if (!methods?.sso || checkEmail(email) !== null) return setCompany(null);
971
+ let current = true;
972
+ const t = setTimeout(() => {
973
+ void auth.signIn.ssoDiscover({ email }).then((r) => {
974
+ if (current) setCompany(r.ok && r.status === "found" ? r.connection : null);
975
+ });
976
+ }, 350);
977
+ return () => {
978
+ current = false;
979
+ clearTimeout(t);
980
+ };
981
+ }, [email, methods]);
982
+ const toCompany = () => {
983
+ autofill.current?.abort();
984
+ f.setBusy(true);
985
+ auth.signIn.sso({ email }, { returnTo: props.returnTo });
986
+ };
940
987
  (0, import_react.useEffect)(() => {
941
988
  void auth.methods().then(setMethods);
942
989
  const back = returnedError();
@@ -969,9 +1016,11 @@ function SignIn(props) {
969
1016
  autofill.current?.abort();
970
1017
  f.setBusy(true);
971
1018
  f.setError(null);
1019
+ if (step === "password" && company?.enforced) return toCompany();
972
1020
  const r = step === "code" ? await auth.signIn.mfa({ code }) : step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password, captchaToken: challenge.token });
973
1021
  f.setBusy(false);
974
1022
  if (step === "password") challenge.renew();
1023
+ if (!r.ok && r.error.code === "sso_required") return toCompany();
975
1024
  if (!r.ok) {
976
1025
  setOffer((n) => n + 1);
977
1026
  return f.setError(r.error);
@@ -997,6 +1046,7 @@ function SignIn(props) {
997
1046
  const r = await auth.signIn.magicLink({ email, captchaToken: challenge.token, create: props.createAccounts, returnTo: props.returnTo });
998
1047
  f.setBusy(false);
999
1048
  challenge.renew();
1049
+ if (!r.ok && r.error.code === "sso_required") return toCompany();
1000
1050
  if (!r.ok) return f.setError(r.error);
1001
1051
  setStep("sent");
1002
1052
  };
@@ -1031,23 +1081,31 @@ function SignIn(props) {
1031
1081
  methods?.passkey && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void usePasskey(), children: f.t.usePasskey }),
1032
1082
  ((methods?.providers.length ?? 0) > 0 || methods?.passkey) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("separator"), children: f.t.orWithEmail }),
1033
1083
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: methods?.passkeyHere ? "username webauthn" : "username", required: true, onChange: setEmail }),
1034
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1035
- Field,
1036
- {
1037
- f,
1038
- name: "password",
1039
- type: "password",
1040
- label: f.t.password,
1041
- value: password,
1042
- autoComplete: "current-password",
1043
- required: true,
1044
- onChange: setPassword,
1045
- aside: props.links?.forgotPassword ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.forgotPassword, children: f.t.forgotPassword }) : void 0
1046
- }
1047
- ),
1048
- challenge.node,
1049
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signIn }),
1050
- methods?.magicLink && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendLink(), children: f.t.emailMeALink }),
1084
+ company && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(f.Button, { type: "button", disabled: f.busy, className: f.cx(company.enforced ? "button" : "providerButton"), onClick: toCompany, children: [
1085
+ f.t.continueWith,
1086
+ " ",
1087
+ company.name
1088
+ ] }),
1089
+ company?.enforced && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.ssoOnly }),
1090
+ !company?.enforced && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1091
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1092
+ Field,
1093
+ {
1094
+ f,
1095
+ name: "password",
1096
+ type: "password",
1097
+ label: f.t.password,
1098
+ value: password,
1099
+ autoComplete: "current-password",
1100
+ required: true,
1101
+ onChange: setPassword,
1102
+ aside: props.links?.forgotPassword ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.forgotPassword, children: f.t.forgotPassword }) : void 0
1103
+ }
1104
+ ),
1105
+ challenge.node,
1106
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signIn })
1107
+ ] }),
1108
+ methods?.magicLink && !company?.enforced && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendLink(), children: f.t.emailMeALink }),
1051
1109
  methods?.signUp !== false && props.links?.signUp && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: f.cx("footer"), children: [
1052
1110
  f.t.noAccount,
1053
1111
  " ",