@protobase/ui 0.46.7 → 0.46.9
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/account/change-email-dialog.js +1 -1
- package/dist/account/two-factor-setup.js +1 -1
- package/dist/app/confirm-sign-in-messages.js +22 -0
- package/dist/app/confirm-sign-in.js +88 -0
- package/dist/app/sensitive-field.js +10 -2
- package/dist/auth/auth-messages.js +6 -3
- package/dist/auth/auth-provider.js +3 -1
- package/dist/auth/code-form.js +1 -1
- package/dist/auth/reset-password-page.js +2 -1
- package/dist/auth/two-factor-form.js +1 -1
- package/dist/index.js +1 -1
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-axeIZ-10.js +9 -0
- package/dist/shell/assets/{index-Cjh9OH3F.js → index-1Ue_dZiA.js} +2 -2
- package/dist/shell/assets/{records-map-DFiu2MjE.js → records-map-DPA8Y3C8.js} +2 -2
- package/dist/shell/assets/shared-protobase-ui-dWIf89ff.js +2 -0
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/shell/protobase.sw.js +2 -2
- package/dist/shell-source-maps/assets/app-axeIZ-10.js.map +1 -0
- package/dist/shell-source-maps/assets/{index-Cjh9OH3F.js.map → index-1Ue_dZiA.js.map} +1 -1
- package/dist/shell-source-maps/assets/{records-map-DFiu2MjE.js.map → records-map-DPA8Y3C8.js.map} +1 -1
- package/dist/shell-source-maps/assets/{shared-protobase-ui-CLCawVYs.js.map → shared-protobase-ui-dWIf89ff.js.map} +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-BClUNT0O.js +0 -9
- package/dist/shell/assets/shared-protobase-ui-CLCawVYs.js +0 -2
- package/dist/shell-source-maps/assets/app-BClUNT0O.js.map +0 -1
|
@@ -2,9 +2,9 @@ import { text } from "./messages.js";
|
|
|
2
2
|
import { Button } from "../primitives/button.js";
|
|
3
3
|
import { Dialog } from "../primitives/dialog.js";
|
|
4
4
|
import { Input } from "../primitives/input.js";
|
|
5
|
+
import { OtpInput } from "../primitives/otp-input.js";
|
|
5
6
|
import { SectionError } from "./account-section.js";
|
|
6
7
|
import { authMessage } from "../auth/auth-messages.js";
|
|
7
|
-
import { OtpInput } from "../primitives/otp-input.js";
|
|
8
8
|
import { useState } from "react";
|
|
9
9
|
import { AuthError } from "@protobase/client";
|
|
10
10
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -2,8 +2,8 @@ import { text } from "./messages.js";
|
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { Button } from "../primitives/button.js";
|
|
4
4
|
import { Input } from "../primitives/input.js";
|
|
5
|
-
import { authMessage } from "../auth/auth-messages.js";
|
|
6
5
|
import { OtpInput } from "../primitives/otp-input.js";
|
|
6
|
+
import { authMessage } from "../auth/auth-messages.js";
|
|
7
7
|
import { BackupCodes } from "./backup-codes.js";
|
|
8
8
|
import { TotpQr, totpKey } from "./totp-qr.js";
|
|
9
9
|
import { useState } from "react";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineMessages } from "../locale/messages.js";
|
|
2
|
+
//#region src/app/confirm-sign-in-messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
confirmTitle: "Confirm it's you",
|
|
6
|
+
confirmWhy: (minutes) => `Showing this needs a passkey or your authenticator app in the last ${minutes} minutes.`,
|
|
7
|
+
usePasskey: "Use a passkey",
|
|
8
|
+
authenticatorCode: "Code from your authenticator app",
|
|
9
|
+
confirm: "Confirm",
|
|
10
|
+
nothingToConfirmWith: "Add a passkey or an authenticator app on your account page first."
|
|
11
|
+
},
|
|
12
|
+
nl: {
|
|
13
|
+
confirmTitle: "Bevestig dat jij het bent",
|
|
14
|
+
confirmWhy: (minutes) => `Dit tonen vraagt in de laatste ${minutes} minuten een passkey of je authenticator-app.`,
|
|
15
|
+
usePasskey: "Passkey gebruiken",
|
|
16
|
+
authenticatorCode: "Code uit je authenticator-app",
|
|
17
|
+
confirm: "Bevestigen",
|
|
18
|
+
nothingToConfirmWith: "Voeg eerst een passkey of een authenticator-app toe op je accountpagina."
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
//#endregion
|
|
22
|
+
export { text };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useOptionalAuth } from "../auth/auth-provider.js";
|
|
2
|
+
import { Button } from "../primitives/button.js";
|
|
3
|
+
import { OtpInput } from "../primitives/otp-input.js";
|
|
4
|
+
import { text } from "./confirm-sign-in-messages.js";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
8
|
+
//#region src/app/confirm-sign-in.tsx
|
|
9
|
+
/**
|
|
10
|
+
* Asks the person to confirm it's them, with a passkey or their authenticator app, after the server refused something
|
|
11
|
+
* that needs a recent strong sign-in (`strong-sign-in-required`). It offers only what the account has; a passkey signs
|
|
12
|
+
* the browser in anew, a code confirms the session it has.
|
|
13
|
+
*/
|
|
14
|
+
const ConfirmSignIn = ({ minutes, onConfirmed }) => {
|
|
15
|
+
const auth = useOptionalAuth();
|
|
16
|
+
const [code, setCode] = useState("");
|
|
17
|
+
const methods = useQuery({
|
|
18
|
+
queryKey: ["account-sign-in-methods"],
|
|
19
|
+
queryFn: () => auth.session.account.signInMethods(),
|
|
20
|
+
enabled: Boolean(auth)
|
|
21
|
+
});
|
|
22
|
+
const passkey = useMutation({
|
|
23
|
+
mutationFn: () => auth.signInWithPasskey(),
|
|
24
|
+
onSuccess: onConfirmed
|
|
25
|
+
});
|
|
26
|
+
const authenticator = useMutation({
|
|
27
|
+
mutationFn: (entered) => auth.verifyTwoFactor({
|
|
28
|
+
method: "totp",
|
|
29
|
+
code: entered
|
|
30
|
+
}),
|
|
31
|
+
onSuccess: onConfirmed
|
|
32
|
+
});
|
|
33
|
+
const account = methods.data?.account;
|
|
34
|
+
const error = passkey.error ?? authenticator.error ?? methods.error;
|
|
35
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
36
|
+
"aria-label": text("confirmTitle"),
|
|
37
|
+
className: "mt-2 flex flex-col gap-2 rounded-md border border-border bg-surface p-3",
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ jsx("p", {
|
|
40
|
+
className: "font-medium",
|
|
41
|
+
children: text("confirmTitle")
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ jsx("p", {
|
|
44
|
+
className: "text-muted-foreground",
|
|
45
|
+
children: text("confirmWhy", minutes)
|
|
46
|
+
}),
|
|
47
|
+
account && account.passkeys > 0 && /* @__PURE__ */ jsx(Button, {
|
|
48
|
+
variant: "primary",
|
|
49
|
+
size: "sm",
|
|
50
|
+
className: "self-start",
|
|
51
|
+
loading: passkey.isPending,
|
|
52
|
+
onClick: () => passkey.mutate(),
|
|
53
|
+
children: text("usePasskey")
|
|
54
|
+
}),
|
|
55
|
+
account?.authenticatorApp && /* @__PURE__ */ jsxs("form", {
|
|
56
|
+
className: "flex flex-wrap items-end gap-2",
|
|
57
|
+
onSubmit: (event) => {
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
authenticator.mutate(code.trim());
|
|
60
|
+
},
|
|
61
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
62
|
+
className: "flex flex-col gap-1.5",
|
|
63
|
+
children: [text("authenticatorCode"), /* @__PURE__ */ jsx(OtpInput, {
|
|
64
|
+
name: "code",
|
|
65
|
+
required: true,
|
|
66
|
+
value: code,
|
|
67
|
+
invalid: authenticator.isError,
|
|
68
|
+
onChange: setCode,
|
|
69
|
+
onComplete: (entered) => authenticator.mutate(entered)
|
|
70
|
+
})]
|
|
71
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
72
|
+
type: "submit",
|
|
73
|
+
size: "sm",
|
|
74
|
+
loading: authenticator.isPending,
|
|
75
|
+
children: text("confirm")
|
|
76
|
+
})]
|
|
77
|
+
}),
|
|
78
|
+
account && account.passkeys === 0 && !account.authenticatorApp && /* @__PURE__ */ jsx("p", { children: text("nothingToConfirmWith") }),
|
|
79
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
80
|
+
role: "alert",
|
|
81
|
+
className: "font-medium text-danger-text",
|
|
82
|
+
children: error.message
|
|
83
|
+
})
|
|
84
|
+
]
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
export { ConfirmSignIn };
|
|
@@ -6,15 +6,19 @@ import { displayValue } from "../live/display-value.js";
|
|
|
6
6
|
import { text } from "./messages.js";
|
|
7
7
|
import { toDraft } from "../live/field-values.js";
|
|
8
8
|
import { ErrorBanner } from "./error-banner.js";
|
|
9
|
+
import { ConfirmSignIn } from "./confirm-sign-in.js";
|
|
9
10
|
import { useState } from "react";
|
|
10
11
|
import { Eye, EyeOff } from "lucide-react";
|
|
12
|
+
import { isApiError } from "@protobase/client";
|
|
11
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
14
|
import { useMutation } from "@tanstack/react-query";
|
|
13
15
|
//#region src/app/sensitive-field.tsx
|
|
14
16
|
const mask = "••••••••••";
|
|
17
|
+
const strongSignInMinutes = (error) => isApiError(error) && error.slug === "strong-sign-in-required" ? error.problem.minutes ?? 15 : void 0;
|
|
15
18
|
/**
|
|
16
19
|
* A value hidden like a password. Showing a stored one asks the server for it, once per page visit; a write-only one has
|
|
17
|
-
* none to show, so its eye only unmasks what was typed.
|
|
20
|
+
* none to show, so its eye only unmasks what was typed. When the server wants a recent strong sign-in first, the field
|
|
21
|
+
* asks the person to confirm it's them, then shows the value.
|
|
18
22
|
*/
|
|
19
23
|
const SensitiveField = ({ field, hints, value, reveal, locked, invalid, placeholder, onChange }) => {
|
|
20
24
|
const [shown, setShown] = useState(false);
|
|
@@ -38,7 +42,11 @@ const SensitiveField = ({ field, hints, value, reveal, locked, invalid, placehol
|
|
|
38
42
|
onClick: toggle,
|
|
39
43
|
children: !stored.isPending && (shown ? /* @__PURE__ */ jsx(EyeOff, { className: "size-3.5" }) : /* @__PURE__ */ jsx(Eye, { className: "size-3.5" }))
|
|
40
44
|
});
|
|
41
|
-
const
|
|
45
|
+
const minutes = strongSignInMinutes(stored.error);
|
|
46
|
+
const error = minutes !== void 0 ? /* @__PURE__ */ jsx(ConfirmSignIn, {
|
|
47
|
+
minutes,
|
|
48
|
+
onConfirmed: () => stored.mutate()
|
|
49
|
+
}) : stored.error && /* @__PURE__ */ jsx("div", {
|
|
42
50
|
className: "mt-2",
|
|
43
51
|
children: /* @__PURE__ */ jsx(ErrorBanner, {
|
|
44
52
|
title: text("couldNotShow", label),
|
|
@@ -26,7 +26,8 @@ const text = defineMessages({
|
|
|
26
26
|
staffReasonRequired: "Say why you sign in as this person, in at least 10 characters.",
|
|
27
27
|
staffForbidden: "Staff sign-in is turned off for this app.",
|
|
28
28
|
untrustedCallback: "This page is not on an address the server trusts.",
|
|
29
|
-
staffFailed: "The staff sign-in did not go through. Try again."
|
|
29
|
+
staffFailed: "The staff sign-in did not go through. Try again.",
|
|
30
|
+
passwordLeaked: "This password appears in a known data breach. Choose a different one."
|
|
30
31
|
},
|
|
31
32
|
nl: {
|
|
32
33
|
tooManyRequests: "Te veel pogingen. Wacht een minuut en probeer het opnieuw.",
|
|
@@ -53,7 +54,8 @@ const text = defineMessages({
|
|
|
53
54
|
staffReasonRequired: "Zeg in minstens 10 tekens waarom je als deze persoon inlogt.",
|
|
54
55
|
staffForbidden: "Inloggen als medewerker staat uit voor deze app.",
|
|
55
56
|
untrustedCallback: "Deze pagina staat niet op een adres dat de server vertrouwt.",
|
|
56
|
-
staffFailed: "De inlog als medewerker lukte niet. Probeer het opnieuw."
|
|
57
|
+
staffFailed: "De inlog als medewerker lukte niet. Probeer het opnieuw.",
|
|
58
|
+
passwordLeaked: "Dit wachtwoord komt voor in een bekend datalek. Kies een ander wachtwoord."
|
|
57
59
|
}
|
|
58
60
|
});
|
|
59
61
|
const providerText = defineMessages({
|
|
@@ -94,7 +96,8 @@ const messages = {
|
|
|
94
96
|
AUTHENTICATION_FAILED: "passkeyFailed",
|
|
95
97
|
PREVIOUSLY_REGISTERED: "passkeyAdded",
|
|
96
98
|
ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED: "passkeyAdded",
|
|
97
|
-
SESSION_NOT_FRESH: "notFresh"
|
|
99
|
+
SESSION_NOT_FRESH: "notFresh",
|
|
100
|
+
PASSWORD_COMPROMISED: "passwordLeaked"
|
|
98
101
|
};
|
|
99
102
|
const lookup = (ids, code) => {
|
|
100
103
|
const id = code ? ids[code] : void 0;
|
|
@@ -12,6 +12,8 @@ const useAuth = () => {
|
|
|
12
12
|
if (!api) throw new Error("useAuth must be used inside AuthProvider");
|
|
13
13
|
return api;
|
|
14
14
|
};
|
|
15
|
+
/** The auth state and steps inside `AuthProvider`, `null` outside it, for a part that also works without one. */
|
|
16
|
+
const useOptionalAuth = () => useContext(AuthContext);
|
|
15
17
|
/**
|
|
16
18
|
* Finds out whether the server has a login and who is signed in, and keeps that state for the app. Without a `session`,
|
|
17
19
|
* it makes one on `origin`, the page's own by default; in the iOS or Android app, with its `nativeBridge`, the session
|
|
@@ -216,4 +218,4 @@ const AuthProvider = ({ session: given, origin, nativeBridge, children }) => {
|
|
|
216
218
|
});
|
|
217
219
|
};
|
|
218
220
|
//#endregion
|
|
219
|
-
export { AuthProvider, useAuth };
|
|
221
|
+
export { AuthProvider, useAuth, useOptionalAuth };
|
package/dist/auth/code-form.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { text } from "./messages.js";
|
|
2
2
|
import { Button } from "../primitives/button.js";
|
|
3
|
-
import { AuthLayout } from "./auth-layout.js";
|
|
4
3
|
import { OtpInput } from "../primitives/otp-input.js";
|
|
4
|
+
import { AuthLayout } from "./auth-layout.js";
|
|
5
5
|
import { ResendCodeButton } from "./resend-code-button.js";
|
|
6
6
|
import { useEffect, useRef, useState } from "react";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { text } from "./messages.js";
|
|
2
2
|
import { useAuth } from "./auth-provider.js";
|
|
3
|
+
import { authMessage } from "./auth-messages.js";
|
|
3
4
|
import { ResetPasswordForm } from "./reset-password-form.js";
|
|
4
5
|
import { useState } from "react";
|
|
5
6
|
import { AuthError } from "@protobase/client";
|
|
@@ -10,7 +11,7 @@ const resetPasswordMessage = (error) => {
|
|
|
10
11
|
if (error.status === 429) return text("tooManyAttempts");
|
|
11
12
|
if (error.code === "PASSWORD_TOO_SHORT") return text("useAtLeast", 12);
|
|
12
13
|
if (error.code === "PASSWORD_TOO_LONG") return text("passwordTooLong");
|
|
13
|
-
return error
|
|
14
|
+
return authMessage(error);
|
|
14
15
|
};
|
|
15
16
|
/** Sets a new password with the token of an emailed reset link. */
|
|
16
17
|
const ResetPasswordPage = ({ token, onDone, onRequestNewLink, onBack }) => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { text } from "./messages.js";
|
|
2
2
|
import { Button } from "../primitives/button.js";
|
|
3
3
|
import { Input } from "../primitives/input.js";
|
|
4
|
+
import { OtpInput } from "../primitives/otp-input.js";
|
|
4
5
|
import { Checkbox } from "../primitives/checkbox.js";
|
|
5
6
|
import { AuthLayout } from "./auth-layout.js";
|
|
6
|
-
import { OtpInput } from "../primitives/otp-input.js";
|
|
7
7
|
import { useState } from "react";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
//#region src/auth/two-factor-form.tsx
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { AppLink, useAppAddress } from "./app/use-app-address.js";
|
|
|
12
12
|
import { Segmented } from "./primitives/segmented.js";
|
|
13
13
|
import { Textarea } from "./primitives/textarea.js";
|
|
14
14
|
import { useList } from "./data/use-list.js";
|
|
15
|
+
import { OtpInput } from "./primitives/otp-input.js";
|
|
15
16
|
import { definePlugin, defineUi } from "./app/pages/project-ui.js";
|
|
16
17
|
import { Markdown } from "./markdown/markdown.js";
|
|
17
18
|
import { ActionInputError } from "./app/pages/action-inputs/input-errors.js";
|
|
@@ -22,7 +23,6 @@ import { SignInPrompt } from "./auth/sign-in-prompt.js";
|
|
|
22
23
|
import { useListDisplay } from "./app/displays/display-context.js";
|
|
23
24
|
import { MenuSelect } from "./primitives/menu-select.js";
|
|
24
25
|
import { AuthLayout } from "./auth/auth-layout.js";
|
|
25
|
-
import { OtpInput } from "./primitives/otp-input.js";
|
|
26
26
|
import { allowDelayMs, isFramed, useArmedAfterFocus, useGuardedPress } from "./connect/interaction-guard.js";
|
|
27
27
|
import { ResendCodeButton } from "./auth/resend-code-button.js";
|
|
28
28
|
import { CodeForm } from "./auth/code-form.js";
|
package/dist/package.js
CHANGED