@hexclave/next 1.0.64 → 1.0.66

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.
Files changed (50) hide show
  1. package/dist/{admin-app-impl-C74lwnMu.d.ts → admin-app-impl-2gZ3JWa0.d.ts} +2 -2
  2. package/dist/{admin-app-impl-C74lwnMu.d.ts.map → admin-app-impl-2gZ3JWa0.d.ts.map} +1 -1
  3. package/dist/auth-page-YnM_LtbN.d.ts.map +1 -1
  4. package/dist/cli-auth-confirm-BW3PYpKb.d.ts.map +1 -1
  5. package/dist/components/message-cards/no-auth-methods-message-card.d.ts +13 -0
  6. package/dist/components/message-cards/no-auth-methods-message-card.d.ts.map +1 -0
  7. package/dist/components/message-cards/no-auth-methods-message-card.js +75 -0
  8. package/dist/components/message-cards/no-auth-methods-message-card.js.map +1 -0
  9. package/dist/components-page/account-settings/active-sessions/active-sessions-page.js +1 -1
  10. package/dist/components-page/account-settings/api-keys/api-keys-page.js +2 -2
  11. package/dist/components-page/account-settings/teams/team-api-keys-section.js +2 -2
  12. package/dist/components-page/auth-page.js +7 -2
  13. package/dist/components-page/auth-page.js.map +1 -1
  14. package/dist/components-page/cli-auth-confirm.js +7 -1
  15. package/dist/components-page/cli-auth-confirm.js.map +1 -1
  16. package/dist/components-page/cli-auth-confirm.test.js +42 -0
  17. package/dist/components-page/cli-auth-confirm.test.js.map +1 -1
  18. package/dist/esm/components/message-cards/no-auth-methods-message-card.d.ts +13 -0
  19. package/dist/esm/components/message-cards/no-auth-methods-message-card.d.ts.map +1 -0
  20. package/dist/esm/components/message-cards/no-auth-methods-message-card.js +74 -0
  21. package/dist/esm/components/message-cards/no-auth-methods-message-card.js.map +1 -0
  22. package/dist/esm/components-page/account-settings/active-sessions/active-sessions-page.js +1 -1
  23. package/dist/esm/components-page/account-settings/api-keys/api-keys-page.js +2 -2
  24. package/dist/esm/components-page/account-settings/teams/team-api-keys-section.js +2 -2
  25. package/dist/esm/components-page/auth-page.d.ts.map +1 -1
  26. package/dist/esm/components-page/auth-page.js +7 -2
  27. package/dist/esm/components-page/auth-page.js.map +1 -1
  28. package/dist/esm/components-page/cli-auth-confirm.d.ts.map +1 -1
  29. package/dist/esm/components-page/cli-auth-confirm.js +7 -1
  30. package/dist/esm/components-page/cli-auth-confirm.js.map +1 -1
  31. package/dist/esm/components-page/cli-auth-confirm.test.js +42 -0
  32. package/dist/esm/components-page/cli-auth-confirm.test.js.map +1 -1
  33. package/dist/esm/generated/global-css.d.ts +1 -1
  34. package/dist/esm/generated/global-css.js +1 -1
  35. package/dist/esm/generated/global-css.js.map +1 -1
  36. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
  37. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +2 -2
  38. package/dist/generated/global-css.d.ts +1 -1
  39. package/dist/generated/global-css.js +1 -1
  40. package/dist/generated/global-css.js.map +1 -1
  41. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
  42. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -1
  43. package/dist/lib/hexclave-app/apps/implementations/common.js +2 -2
  44. package/dist/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
  45. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  46. package/package.json +4 -4
  47. package/src/components/message-cards/no-auth-methods-message-card.tsx +57 -0
  48. package/src/components-page/auth-page.tsx +18 -1
  49. package/src/components-page/cli-auth-confirm.test.tsx +55 -0
  50. package/src/components-page/cli-auth-confirm.tsx +11 -1
@@ -1,2 +1,2 @@
1
- import { t as _HexclaveAdminAppImplIncomplete } from "../../../../admin-app-impl-C74lwnMu.js";
1
+ import { t as _HexclaveAdminAppImplIncomplete } from "../../../../admin-app-impl-2gZ3JWa0.js";
2
2
  export { _HexclaveAdminAppImplIncomplete };
@@ -1,2 +1,2 @@
1
- import { r as _HexclaveClientAppImplIncomplete } from "../../../../admin-app-impl-C74lwnMu.js";
1
+ import { r as _HexclaveClientAppImplIncomplete } from "../../../../admin-app-impl-2gZ3JWa0.js";
2
2
  export { _HexclaveClientAppImplIncomplete };
@@ -17,8 +17,8 @@ let _hexclave_shared_dist_utils_stores = require("@hexclave/shared/dist/utils/st
17
17
  let ____________generated_env_js = require("../../../../generated/env.js");
18
18
  let ______url_targets_js = require("../../url-targets.js");
19
19
  //#region src/lib/hexclave-app/apps/implementations/common.ts
20
- const clientVersion = "js @hexclave/next@1.0.64";
21
- if ("js @hexclave/next@1.0.64".startsWith("STACK_COMPILE_TIME"));
20
+ const clientVersion = "js @hexclave/next@1.0.66";
21
+ if ("js @hexclave/next@1.0.66".startsWith("STACK_COMPILE_TIME"));
22
22
  const replaceHexclavePortPrefix = (input) => {
23
23
  if (!input) return input;
24
24
  const prefix = ____________generated_env_js.envVars.HEXCLAVE_PORT_PREFIX;
@@ -1,4 +1,4 @@
1
- import { n as _HexclaveServerAppImplIncomplete, r as _HexclaveClientAppImplIncomplete, t as _HexclaveAdminAppImplIncomplete } from "../../../../admin-app-impl-C74lwnMu.js";
1
+ import { n as _HexclaveServerAppImplIncomplete, r as _HexclaveClientAppImplIncomplete, t as _HexclaveAdminAppImplIncomplete } from "../../../../admin-app-impl-2gZ3JWa0.js";
2
2
 
3
3
  //#region src/lib/hexclave-app/apps/implementations/index.d.ts
4
4
  declare const _HexclaveAdminAppImpl: typeof _HexclaveAdminAppImplIncomplete, _HexclaveClientAppImpl: typeof _HexclaveClientAppImplIncomplete, _HexclaveServerAppImpl: typeof _HexclaveServerAppImplIncomplete;
@@ -1,2 +1,2 @@
1
- import { n as _HexclaveServerAppImplIncomplete } from "../../../../admin-app-impl-C74lwnMu.js";
1
+ import { n as _HexclaveServerAppImplIncomplete } from "../../../../admin-app-impl-2gZ3JWa0.js";
2
2
  export { _HexclaveServerAppImplIncomplete };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
3
3
  "name": "@hexclave/next",
4
- "version": "1.0.64",
4
+ "version": "1.0.66",
5
5
  "repository": "https://github.com/hexclave/hexclave",
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.js",
@@ -74,9 +74,9 @@
74
74
  "tailwindcss-animate": "^1.0.7",
75
75
  "rrweb": "^1.1.3",
76
76
  "yup": "^1.7.1",
77
- "@hexclave/sc": "1.0.64",
78
- "@hexclave/shared": "1.0.64",
79
- "@hexclave/ui": "1.0.64"
77
+ "@hexclave/sc": "1.0.66",
78
+ "@hexclave/ui": "1.0.66",
79
+ "@hexclave/shared": "1.0.66"
80
80
  },
81
81
  "peerDependencies": {
82
82
  "@types/react": ">=18.0.0",
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+
4
+ //===========================================
5
+ // THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
6
+ //===========================================
7
+
8
+ import { Typography } from "@hexclave/ui";
9
+ import { KeyRound } from "lucide-react";
10
+ import { useTranslation } from "../../lib/translations";
11
+ import { MaybeFullPage } from "../elements/maybe-full-page";
12
+
13
+ const configSnippet = `auth: {
14
+ password: { allowSignIn: true },
15
+ otp: { allowSignIn: true },
16
+ }`;
17
+
18
+ /**
19
+ * Shown instead of the sign-in/sign-up form when a project has no auth methods at all (no password, no
20
+ * OTP, no passkey, no OAuth provider). Without this, the page would look broken: there is nothing the end
21
+ * user can do, and the developer gets no hint about what went wrong or how to fix it.
22
+ */
23
+ export function NoAuthMethodsMessageCard(props: {
24
+ fullPage?: boolean,
25
+ projectDisplayName?: string,
26
+ }) {
27
+ const { t } = useTranslation();
28
+
29
+ return (
30
+ <MaybeFullPage fullPage={!!props.fullPage}>
31
+ <div className='stack-scope flex flex-col items-stretch' style={{ maxWidth: '380px', flexBasis: '380px', padding: props.fullPage ? '1rem' : 0 }}>
32
+ <div className="flex flex-col items-center text-center">
33
+ <div className="mb-4 flex h-11 w-11 items-center justify-center rounded-2xl border border-black/[0.08] dark:border-white/[0.10]">
34
+ <KeyRound className="h-5 w-5 text-gray-500" />
35
+ </div>
36
+ <Typography type='h2'>{t("Sign-in is not available")}</Typography>
37
+ <Typography type='label' variant='secondary'>
38
+ {props.projectDisplayName == null
39
+ ? t("This app has no authentication methods enabled, so nobody can sign in yet.")
40
+ : t("{projectDisplayName} has no authentication methods enabled, so nobody can sign in yet.", { projectDisplayName: props.projectDisplayName })}
41
+ </Typography>
42
+ </div>
43
+
44
+ <div className="mt-6 rounded-xl border border-black/[0.08] p-4 text-left dark:border-white/[0.10]">
45
+ <Typography type='label'>{t("Are you the developer?")}</Typography>
46
+ <Typography type='label' variant='secondary'>
47
+ {t("Enable at least one sign-in method in your hexclave.config.ts:")}
48
+ </Typography>
49
+ <pre className="mt-2 overflow-x-auto rounded-lg border border-black/[0.06] p-3 font-mono text-xs leading-relaxed dark:border-white/[0.08]">{configSnippet}</pre>
50
+ <Typography type='label' variant='secondary'>
51
+ {t("Or turn one on in the Hexclave dashboard, under Auth Methods.")}
52
+ </Typography>
53
+ </div>
54
+ </div>
55
+ </MaybeFullPage>
56
+ );
57
+ }
@@ -19,6 +19,7 @@ import { SeparatorWithText } from '../components/elements/separator-with-text';
19
19
  import { StyledLink } from '../components/link';
20
20
  import { KnownErrorMessageCard } from '../components/message-cards/known-error-message-card';
21
21
  import { MessageCard } from '../components/message-cards/message-card';
22
+ import { NoAuthMethodsMessageCard } from '../components/message-cards/no-auth-methods-message-card';
22
23
  import { MagicLinkSignIn } from '../components/magic-link-sign-in';
23
24
  import { PredefinedMessageCard } from '../components/message-cards/predefined-message-card';
24
25
  import { OAuthButtonGroup } from '../components/oauth-button-group';
@@ -137,6 +138,18 @@ function Inner(props: Props) {
137
138
  const hasPasskey = (project.config.passkeyEnabled === true && props.type === "sign-in");
138
139
  const enableSeparator = (project.config.credentialEnabled || project.config.magicLinkEnabled) && (hasOAuthProviders || hasPasskey);
139
140
 
141
+ // Note that we check passkeys regardless of `props.type` here (unlike `hasPasskey`); a project with only
142
+ // passkeys enabled can't sign up with them, but it is configured correctly, so the sign-up page should
143
+ // keep pointing at the sign-in page instead of claiming that the project is misconfigured.
144
+ const hasAnyAuthMethod = hasOAuthProviders
145
+ || project.config.passkeyEnabled === true
146
+ || project.config.credentialEnabled
147
+ || project.config.magicLinkEnabled;
148
+
149
+ if (!hasAnyAuthMethod) {
150
+ return <NoAuthMethodsMessageCard fullPage={props.fullPage} projectDisplayName={'displayName' in project ? project.displayName : undefined} />;
151
+ }
152
+
140
153
  return (
141
154
  <MaybeFullPage fullPage={!!props.fullPage}>
142
155
  <div className='stack-scope flex flex-col items-stretch' style={{ maxWidth: '380px', flexBasis: '380px', padding: props.fullPage ? '1rem' : 0 }}>
@@ -191,7 +204,11 @@ function Inner(props: Props) {
191
204
  props.type === 'sign-up' ? <CredentialSignUp noPasswordRepeat={props.noPasswordRepeat} /> : <CredentialSignIn />
192
205
  ) : project.config.magicLinkEnabled ? (
193
206
  <MagicLinkSignIn />
194
- ) : !(hasOAuthProviders || hasPasskey) ? <Typography variant={"destructive"} className="text-center">{t("No authentication method enabled.")}</Typography> : null}
207
+ ) : !(hasOAuthProviders || hasPasskey) ? (
208
+ // Only reachable on the sign-up page of a passkey-only project: the project is configured
209
+ // correctly, there is just no way to create an account with a passkey alone.
210
+ <Typography variant="secondary" className="text-center">{t("New accounts can't be created with the sign-in methods enabled for this app. Sign in instead.")}</Typography>
211
+ ) : null}
195
212
  {props.extraInfo && (
196
213
  <div className={cn('flex flex-col items-center text-center text-sm text-gray-500', {
197
214
  'mt-2': project.config.credentialEnabled || project.config.magicLinkEnabled,
@@ -28,11 +28,13 @@ function createAppTestDouble(options: {
28
28
  signInWithTokens?: (tokens: { accessToken: string, refreshToken: string }) => Promise<void>,
29
29
  redirectToSignIn?: (options: { replace: true }) => Promise<void>,
30
30
  redirectToSignUp?: (options: { replace: true }) => Promise<void>,
31
+ redirectToOnboarding?: (options: { replace: true }) => Promise<void>,
31
32
  }) {
32
33
  const app = {
33
34
  useUser: () => options.user,
34
35
  redirectToSignIn: options.redirectToSignIn ?? vi.fn(async () => {}),
35
36
  redirectToSignUp: options.redirectToSignUp ?? vi.fn(async () => {}),
37
+ redirectToOnboarding: options.redirectToOnboarding ?? vi.fn(async () => {}),
36
38
  [hexclaveAppInternalsSymbol]: {
37
39
  sendRequest: options.sendRequest,
38
40
  signInWithTokens: options.signInWithTokens ?? vi.fn(async () => {}),
@@ -102,6 +104,7 @@ describe("useCliAuthConfirmation", () => {
102
104
  root = null;
103
105
  container = null;
104
106
  vi.restoreAllMocks();
107
+ sessionStorage.clear();
105
108
  window.history.replaceState({}, "", "/");
106
109
  Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", previousActEnvironment);
107
110
  });
@@ -132,6 +135,58 @@ describe("useCliAuthConfirmation", () => {
132
135
  `);
133
136
  });
134
137
 
138
+ it("redirects restricted users to onboarding without completing CLI auth", async () => {
139
+ window.history.replaceState({}, "", "/handler/cli-auth-confirm?login_code=login-code");
140
+ const redirectToOnboarding = vi.fn(async (_options: { replace: true }) => {});
141
+ const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));
142
+ const app = createAppTestDouble({
143
+ user: {
144
+ isRestricted: true,
145
+ isAnonymous: false,
146
+ currentSession: {
147
+ getTokens: vi.fn(async () => ({ refreshToken: "restricted-refresh-token" })),
148
+ },
149
+ },
150
+ sendRequest,
151
+ redirectToOnboarding,
152
+ });
153
+
154
+ await renderWithApp(app);
155
+ await act(async () => {
156
+ getButton("authorize").click();
157
+ });
158
+
159
+ expect(redirectToOnboarding).toHaveBeenCalledWith({ replace: true });
160
+ expect(sessionStorage.getItem("hexclave-cli-auth-confirmed")).toBe("login-code");
161
+ expect(sendRequest).not.toHaveBeenCalled();
162
+ });
163
+
164
+ it("redirects anonymous users to sign-up without completing CLI auth", async () => {
165
+ window.history.replaceState({}, "", "/handler/cli-auth-confirm?login_code=login-code");
166
+ const redirectToSignUp = vi.fn(async (_options: { replace: true }) => {});
167
+ const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));
168
+ const app = createAppTestDouble({
169
+ user: {
170
+ isRestricted: true,
171
+ isAnonymous: true,
172
+ currentSession: {
173
+ getTokens: vi.fn(async () => ({ refreshToken: "anonymous-refresh-token" })),
174
+ },
175
+ },
176
+ sendRequest,
177
+ redirectToSignUp,
178
+ });
179
+
180
+ await renderWithApp(app);
181
+ await act(async () => {
182
+ getButton("authorize").click();
183
+ });
184
+
185
+ expect(redirectToSignUp).toHaveBeenCalledWith({ replace: true });
186
+ expect(sessionStorage.getItem("hexclave-cli-auth-confirmed")).toBe("login-code");
187
+ expect(sendRequest).not.toHaveBeenCalled();
188
+ });
189
+
135
190
  it("ignores duplicate authorize clicks before React re-renders", async () => {
136
191
  window.history.replaceState({}, "", "/handler/cli-auth-confirm?login_code=login-code");
137
192
  const getTokens = vi.fn(async () => ({ refreshToken: "refresh-token" }));
@@ -111,7 +111,7 @@ export function useCliAuthConfirmation(): CliAuthConfirmationState {
111
111
  }, [app, loginCode, user]);
112
112
 
113
113
  useEffect(() => {
114
- if (!confirmed || !user || autoCompleteRef.current) {
114
+ if (!confirmed || !user || user.isRestricted || autoCompleteRef.current) {
115
115
  return;
116
116
  }
117
117
  autoCompleteRef.current = true;
@@ -144,6 +144,16 @@ export function useCliAuthConfirmation(): CliAuthConfirmationState {
144
144
  setError(null);
145
145
  setStatus("authorizing");
146
146
  if (user) {
147
+ if (user.isRestricted) {
148
+ markConfirmed(loginCode);
149
+ setStatus("redirecting");
150
+ await (
151
+ user.isAnonymous
152
+ ? app.redirectToSignUp({ replace: true })
153
+ : app.redirectToOnboarding({ replace: true })
154
+ );
155
+ return;
156
+ }
147
157
  await completeWithCurrentUser();
148
158
  clearConfirmed();
149
159
  setStatus("success");