@awsmbiz/create-inbis-app 0.0.0-stage → 0.1.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 +28 -2
- package/dist/cli.mjs +850 -0
- package/dist/cli.mjs.map +1 -0
- package/package.json +43 -3
- package/template/.dev.vars.example +4 -0
- package/template/README.md +115 -0
- package/template/e2e/accessibility.spec.ts +33 -0
- package/template/e2e/axe.ts +47 -0
- package/template/e2e/shell.spec.ts +33 -0
- package/template/e2e/tsconfig.json +8 -0
- package/template/eslint.config.js +7 -0
- package/template/migrations/0001_app_initial.sql +8 -0
- package/template/modules/README.md +6 -0
- package/template/modules/access-control/files/src/access-control-routes.ts +36 -0
- package/template/modules/access-control/files/src/access-control.ts +46 -0
- package/template/modules/access-control/files/test/access-control.test.ts +78 -0
- package/template/modules/access-control/module.json +54 -0
- package/template/modules/access-control/wiring/admin/admin.imports.tsx +1 -0
- package/template/modules/access-control/wiring/admin/admin.nav.tsx +2 -0
- package/template/modules/access-control/wiring/admin/admin.pages.tsx +4 -0
- package/template/modules/access-control/wiring/admin/admin.userSections.tsx +1 -0
- package/template/modules/access-control/wiring/admin/readme.modules.md +3 -0
- package/template/modules/access-control/wiring/admin/web.messages.ts +4 -0
- package/template/modules/access-control/wiring/audit/audit.catalogues.imports.ts +1 -0
- package/template/modules/access-control/wiring/audit/audit.catalogues.ts +1 -0
- package/template/modules/access-control/wiring/audit/readme.modules.md +4 -0
- package/template/modules/access-control/wiring/audit/web.messages.ts +6 -0
- package/template/modules/access-control/wiring/d1.jsonc +8 -0
- package/template/modules/access-control/wiring/env.bindings.ts +2 -0
- package/template/modules/access-control/wiring/readme.modules.md +20 -0
- package/template/modules/access-control/wiring/test.env.ts +1 -0
- package/template/modules/access-control/wiring/test.migrate.ts +1 -0
- package/template/modules/access-control/wiring/vitest.bindings.ts +1 -0
- package/template/modules/access-control/wiring/worker.imports.ts +1 -0
- package/template/modules/access-control/wiring/worker.routes.ts +2 -0
- package/template/modules/access-control/with-admin/e2e/access-roles.spec.ts +51 -0
- package/template/modules/access-control/with-admin/web/src/admin-roles.tsx +38 -0
- package/template/modules/access-control/with-audit/test/access-control-audit.test.ts +25 -0
- package/template/modules/admin/files/e2e/admin-accessibility.spec.ts +86 -0
- package/template/modules/admin/files/e2e/admin-area.spec.ts +109 -0
- package/template/modules/admin/files/e2e/admin-helpers.ts +39 -0
- package/template/modules/admin/files/src/admin.ts +46 -0
- package/template/modules/admin/files/test/admin.test.ts +47 -0
- package/template/modules/admin/files/web/src/admin-area.tsx +140 -0
- package/template/modules/admin/files/web/src/admin-client.ts +49 -0
- package/template/modules/admin/files/web/src/admin-example.tsx +62 -0
- package/template/modules/admin/files/web/src/admin-gate.tsx +112 -0
- package/template/modules/admin/module.json +50 -0
- package/template/modules/admin/wiring/account.sections.tsx +16 -0
- package/template/modules/admin/wiring/audit/admin.authAudit.ts +2 -0
- package/template/modules/admin/wiring/audit/admin.imports.tsx +1 -0
- package/template/modules/admin/wiring/audit/admin.nav.tsx +1 -0
- package/template/modules/admin/wiring/audit/admin.pages.tsx +1 -0
- package/template/modules/admin/wiring/audit/admin.server.imports.ts +3 -0
- package/template/modules/admin/wiring/audit/admin.stats.ts +3 -0
- package/template/modules/admin/wiring/audit/admin.userSections.tsx +1 -0
- package/template/modules/admin/wiring/audit/readme.modules.md +4 -0
- package/template/modules/admin/wiring/audit/web.messages.ts +1 -0
- package/template/modules/admin/wiring/readme.modules.md +26 -0
- package/template/modules/admin/wiring/web.messages.ts +22 -0
- package/template/modules/admin/wiring/web.routes.imports.tsx +1 -0
- package/template/modules/admin/wiring/web.routes.tsx +2 -0
- package/template/modules/admin/wiring/web.styles.css +1 -0
- package/template/modules/admin/wiring/worker.imports.ts +1 -0
- package/template/modules/admin/wiring/worker.routes.ts +2 -0
- package/template/modules/admin/with-audit/e2e/admin-activity.spec.ts +37 -0
- package/template/modules/admin/with-audit/test/admin-audit.test.ts +37 -0
- package/template/modules/admin/with-audit/web/src/admin-activity.tsx +21 -0
- package/template/modules/audit/files/src/audit-actions.ts +20 -0
- package/template/modules/audit/files/src/audit-log.ts +33 -0
- package/template/modules/audit/files/test/audit.test.ts +47 -0
- package/template/modules/audit/module.json +36 -0
- package/template/modules/audit/wiring/audit.imports.ts +1 -0
- package/template/modules/audit/wiring/audit.sink.ts +2 -0
- package/template/modules/audit/wiring/auth/audit.catalogues.imports.ts +1 -0
- package/template/modules/audit/wiring/auth/audit.catalogues.ts +1 -0
- package/template/modules/audit/wiring/auth/readme.modules.md +7 -0
- package/template/modules/audit/wiring/auth/web.messages.ts +26 -0
- package/template/modules/audit/wiring/auth/web.routes.imports.tsx +1 -0
- package/template/modules/audit/wiring/auth/web.routes.tsx +1 -0
- package/template/modules/audit/wiring/auth/worker.imports.ts +1 -0
- package/template/modules/audit/wiring/auth/worker.routes.ts +2 -0
- package/template/modules/audit/wiring/d1.jsonc +8 -0
- package/template/modules/audit/wiring/env.bindings.ts +4 -0
- package/template/modules/audit/wiring/index.imports.ts +1 -0
- package/template/modules/audit/wiring/index.setup.ts +2 -0
- package/template/modules/audit/wiring/readme.modules.md +16 -0
- package/template/modules/audit/wiring/test.env.ts +1 -0
- package/template/modules/audit/wiring/test.migrate.ts +1 -0
- package/template/modules/audit/wiring/vars.deployed.jsonc +2 -0
- package/template/modules/audit/wiring/vars.dev.jsonc +2 -0
- package/template/modules/audit/wiring/vitest.bindings.ts +1 -0
- package/template/modules/audit/with-auth/e2e/my-activity.spec.ts +89 -0
- package/template/modules/audit/with-auth/src/audit-routes.ts +21 -0
- package/template/modules/audit/with-auth/test/audit-routes.test.ts +17 -0
- package/template/modules/audit/with-auth/web/src/my-activity.tsx +168 -0
- package/template/modules/auth/files/e2e/auth-accessibility.spec.ts +68 -0
- package/template/modules/auth/files/e2e/auth-bootstrap-admin.spec.ts +58 -0
- package/template/modules/auth/files/e2e/auth-helpers.ts +74 -0
- package/template/modules/auth/files/e2e/auth-invitations.spec.ts +51 -0
- package/template/modules/auth/files/e2e/auth-phone.spec.ts +41 -0
- package/template/modules/auth/files/e2e/auth-seeded.ts +97 -0
- package/template/modules/auth/files/e2e/auth-sign-in.spec.ts +57 -0
- package/template/modules/auth/files/e2e/auth-two-factor.spec.ts +44 -0
- package/template/modules/auth/files/scripts/seed-user.mjs +64 -0
- package/template/modules/auth/files/src/auth-choices.ts +33 -0
- package/template/modules/auth/files/src/auth-config.ts +55 -0
- package/template/modules/auth/files/src/auth.ts +68 -0
- package/template/modules/auth/files/src/local-test-mode.ts +49 -0
- package/template/modules/auth/files/src/notifications.ts +94 -0
- package/template/modules/auth/files/test/auth-fixtures.ts +135 -0
- package/template/modules/auth/files/test/auth.test.ts +210 -0
- package/template/modules/auth/files/test/totp.ts +49 -0
- package/template/modules/auth/files/test/turnstile-fake.ts +42 -0
- package/template/modules/auth/files/web/src/account-menu.tsx +25 -0
- package/template/modules/auth/files/web/src/account.tsx +87 -0
- package/template/modules/auth/files/web/src/auth-client.ts +60 -0
- package/template/modules/auth/files/web/src/auth-pages.tsx +152 -0
- package/template/modules/auth/module.json +56 -0
- package/template/modules/auth/wiring/bindings.deployed.jsonc +2 -0
- package/template/modules/auth/wiring/d1.jsonc +7 -0
- package/template/modules/auth/wiring/devvars.txt +12 -0
- package/template/modules/auth/wiring/env.bindings.ts +26 -0
- package/template/modules/auth/wiring/index.deps.ts +1 -0
- package/template/modules/auth/wiring/index.imports.ts +4 -0
- package/template/modules/auth/wiring/index.setup.ts +7 -0
- package/template/modules/auth/wiring/playwright.imports.ts +1 -0
- package/template/modules/auth/wiring/playwright.servers.ts +12 -0
- package/template/modules/auth/wiring/playwright.setup.ts +2 -0
- package/template/modules/auth/wiring/playwright.vars.ts +2 -0
- package/template/modules/auth/wiring/readme.modules.md +30 -0
- package/template/modules/auth/wiring/readme.provisioning.md +11 -0
- package/template/modules/auth/wiring/secrets.jsonc +2 -0
- package/template/modules/auth/wiring/test.env.ts +1 -0
- package/template/modules/auth/wiring/test.migrate.ts +1 -0
- package/template/modules/auth/wiring/vars.dev.jsonc +19 -0
- package/template/modules/auth/wiring/vars.production.jsonc +13 -0
- package/template/modules/auth/wiring/vars.staging.jsonc +13 -0
- package/template/modules/auth/wiring/vitest.bindings.ts +6 -0
- package/template/modules/auth/wiring/web.header.tsx +1 -0
- package/template/modules/auth/wiring/web.layout.imports.tsx +1 -0
- package/template/modules/auth/wiring/web.messages.ts +17 -0
- package/template/modules/auth/wiring/web.routes.imports.tsx +12 -0
- package/template/modules/auth/wiring/web.routes.tsx +11 -0
- package/template/modules/auth/wiring/web.styles.css +1 -0
- package/template/modules/auth/wiring/worker.deps.ts +2 -0
- package/template/modules/auth/wiring/worker.imports.ts +1 -0
- package/template/modules/auth/wiring/worker.routes.ts +2 -0
- package/template/modules/example/files/e2e/example-notes.spec.ts +39 -0
- package/template/modules/example/files/migrations/0002_example_notes.sql +12 -0
- package/template/modules/example/files/src/example-audit-actions.ts +12 -0
- package/template/modules/example/files/src/example-notes.ts +136 -0
- package/template/modules/example/files/test/example-notes-fixtures.ts +30 -0
- package/template/modules/example/files/test/example-notes.test.ts +52 -0
- package/template/modules/example/files/web/src/example-notes.tsx +176 -0
- package/template/modules/example/module.json +61 -0
- package/template/modules/example/wiring/access-control/access.permissions.ts +3 -0
- package/template/modules/example/wiring/access-control/access.roles.ts +2 -0
- package/template/modules/example/wiring/access-control/example.e2e.imports.ts +2 -0
- package/template/modules/example/wiring/access-control/example.e2e.setup.ts +18 -0
- package/template/modules/example/wiring/access-control/example.imports.ts +3 -0
- package/template/modules/example/wiring/access-control/example.policy.ts +10 -0
- package/template/modules/example/wiring/access-control/example.test.caller.ts +17 -0
- package/template/modules/example/wiring/access-control/example.test.imports.ts +1 -0
- package/template/modules/example/wiring/access-control/readme.modules.md +5 -0
- package/template/modules/example/wiring/admin/admin.imports.tsx +1 -0
- package/template/modules/example/wiring/admin/admin.nav.tsx +2 -0
- package/template/modules/example/wiring/admin/admin.pages.tsx +2 -0
- package/template/modules/example/wiring/admin/readme.modules.md +4 -0
- package/template/modules/example/wiring/admin/web.messages.ts +11 -0
- package/template/modules/example/wiring/admin/worker.imports.ts +1 -0
- package/template/modules/example/wiring/admin/worker.routes.ts +2 -0
- package/template/modules/example/wiring/audit/audit.catalogues.imports.ts +1 -0
- package/template/modules/example/wiring/audit/audit.catalogues.ts +2 -0
- package/template/modules/example/wiring/audit/readme.modules.md +4 -0
- package/template/modules/example/wiring/readme.modules.md +16 -0
- package/template/modules/example/wiring/web.messages.ts +19 -0
- package/template/modules/example/wiring/web.routes.imports.tsx +1 -0
- package/template/modules/example/wiring/web.routes.tsx +2 -0
- package/template/modules/example/wiring/worker.imports.ts +1 -0
- package/template/modules/example/wiring/worker.routes.ts +2 -0
- package/template/modules/example/with-access-control/test/example-notes-access.test.ts +51 -0
- package/template/modules/example/with-admin/e2e/example-notes-admin.spec.ts +55 -0
- package/template/modules/example/with-admin/src/example-notes-admin.ts +62 -0
- package/template/modules/example/with-admin/test/example-notes-admin.test.ts +42 -0
- package/template/modules/example/with-admin/web/src/admin-notes.tsx +99 -0
- package/template/modules/example/with-audit/test/example-notes-audit.test.ts +22 -0
- package/template/package.json +57 -0
- package/template/playwright.config.ts +38 -0
- package/template/renovate.json +12 -0
- package/template/src/app.ts +46 -0
- package/template/src/audit-catalogues.ts +9 -0
- package/template/src/audit-sink.ts +21 -0
- package/template/src/env.d.ts +21 -0
- package/template/src/index.ts +18 -0
- package/template/src/v1.ts +24 -0
- package/template/standalone/.github/workflows/ci.yml +21 -0
- package/template/standalone/.github/workflows/deploy.yml +32 -0
- package/template/standalone/.nvmrc +1 -0
- package/template/standalone/_gitignore +9 -0
- package/template/standalone/pnpm-workspace.yaml +11 -0
- package/template/template.json +56 -0
- package/template/test/env.d.ts +11 -0
- package/template/test/migrate.ts +11 -0
- package/template/test/migrations.test.ts +22 -0
- package/template/test/worker.test.ts +65 -0
- package/template/tsconfig.json +7 -0
- package/template/vitest.config.ts +24 -0
- package/template/web/index.html +12 -0
- package/template/web/src/app.tsx +44 -0
- package/template/web/src/home.tsx +51 -0
- package/template/web/src/layout.tsx +48 -0
- package/template/web/src/main.tsx +16 -0
- package/template/web/src/messages.ts +38 -0
- package/template/web/src/navigation.tsx +34 -0
- package/template/web/src/route-focus.ts +49 -0
- package/template/web/src/routes.tsx +38 -0
- package/template/web/src/styles.css +11 -0
- package/template/web/src/system-theme.ts +16 -0
- package/template/web/tsconfig.json +7 -0
- package/template/web/vite.config.ts +15 -0
- package/template/wrangler.jsonc +96 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useMe } from "./auth-client.js";
|
|
2
|
+
import { t } from "./messages.js";
|
|
3
|
+
import { Link, type Navigate } from "./navigation.js";
|
|
4
|
+
|
|
5
|
+
/** The header's account link: "Sign in" for visitors, the user's name (to the account page) once signed in. */
|
|
6
|
+
export function AccountMenu({ path, navigate }: { path: string; navigate: Navigate }) {
|
|
7
|
+
const [state] = useMe();
|
|
8
|
+
const className =
|
|
9
|
+
"text-sm text-muted-foreground underline-offset-4 hover:text-foreground hover:underline aria-[current=page]:font-medium aria-[current=page]:text-foreground";
|
|
10
|
+
if (state.status === "loading") return null;
|
|
11
|
+
if (state.status === "signed-out") {
|
|
12
|
+
return (
|
|
13
|
+
<Link href={`/sign-in?next=${encodeURIComponent(path)}`} navigate={navigate} className={className}>
|
|
14
|
+
{t("account.signIn")}
|
|
15
|
+
</Link>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
const label = state.status === "signed-in" ? state.me.user.name : t("account.title");
|
|
19
|
+
return (
|
|
20
|
+
<Link href="/account" navigate={navigate} aria-current={path === "/account" ? "page" : undefined} className={className}>
|
|
21
|
+
<span className="sr-only">{t("account.title")}: </span>
|
|
22
|
+
{label}
|
|
23
|
+
</Link>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { StepUpScreen, usePublicAuthConfig } from "@awsmbiz/inbis-auth-ui";
|
|
2
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from "@awsmbiz/inbis-ui";
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { client, identityOf, useMe } from "./auth-client.js";
|
|
5
|
+
import { t } from "./messages.js";
|
|
6
|
+
import { Link } from "./navigation.js";
|
|
7
|
+
import type { RouteContext } from "./routes.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The signed-in user's account page: who is signed in, and links to two-factor
|
|
11
|
+
* authentication and sessions. Signed-out visitors are sent to sign-in and come back.
|
|
12
|
+
* A page of your own that needs a session follows the same pattern (`useMe`).
|
|
13
|
+
*/
|
|
14
|
+
export function AccountPage({ navigate }: RouteContext) {
|
|
15
|
+
const [state, refresh] = useMe();
|
|
16
|
+
const publicConfig = usePublicAuthConfig(client);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (state.status === "signed-out") navigate(`/sign-in?next=${encodeURIComponent("/account")}`, { replace: true });
|
|
20
|
+
}, [state, navigate]);
|
|
21
|
+
|
|
22
|
+
if (state.status === "requirement") {
|
|
23
|
+
// The session needs a second factor (or another sign-in method) before it may be used.
|
|
24
|
+
return (
|
|
25
|
+
<StepUpScreen
|
|
26
|
+
client={client}
|
|
27
|
+
requirement={state.requirement}
|
|
28
|
+
onSatisfied={refresh}
|
|
29
|
+
onSignInAgain={() => {
|
|
30
|
+
void client.signOut().then(() => {
|
|
31
|
+
navigate("/sign-in?next=%2Faccount");
|
|
32
|
+
});
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (state.status !== "signed-in") return <p className="text-sm text-muted-foreground">{t("common.loading")}</p>;
|
|
38
|
+
|
|
39
|
+
const { me } = state;
|
|
40
|
+
const twoFactor = publicConfig.status === "ready" && publicConfig.config.twoFactor !== undefined;
|
|
41
|
+
const linkClass = "text-sm font-medium text-primary underline underline-offset-4";
|
|
42
|
+
return (
|
|
43
|
+
<div className="flex flex-col gap-6">
|
|
44
|
+
<div className="flex flex-col gap-2">
|
|
45
|
+
<h1 className="text-2xl font-semibold tracking-tight">{t("account.heading")}</h1>
|
|
46
|
+
<p className="text-muted-foreground">{t("account.signedInAs", { name: me.user.name, identity: identityOf(me) })}</p>
|
|
47
|
+
</div>
|
|
48
|
+
<Card>
|
|
49
|
+
<CardHeader>
|
|
50
|
+
<CardTitle>
|
|
51
|
+
<h2>{t("account.security")}</h2>
|
|
52
|
+
</CardTitle>
|
|
53
|
+
<CardDescription>{t("account.securityDescription")}</CardDescription>
|
|
54
|
+
</CardHeader>
|
|
55
|
+
<CardContent>
|
|
56
|
+
<ul className="flex flex-col gap-2">
|
|
57
|
+
{twoFactor ? (
|
|
58
|
+
<li>
|
|
59
|
+
<Link href="/account/security" navigate={navigate} className={linkClass}>
|
|
60
|
+
{t("account.twoFactor")}
|
|
61
|
+
</Link>
|
|
62
|
+
</li>
|
|
63
|
+
) : null}
|
|
64
|
+
<li>
|
|
65
|
+
<Link href="/account/sessions" navigate={navigate} className={linkClass}>
|
|
66
|
+
{t("account.sessions")}
|
|
67
|
+
</Link>
|
|
68
|
+
</li>
|
|
69
|
+
</ul>
|
|
70
|
+
</CardContent>
|
|
71
|
+
</Card>
|
|
72
|
+
{/* inbis:wire account.sections */}
|
|
73
|
+
<div>
|
|
74
|
+
<Button
|
|
75
|
+
variant="outline"
|
|
76
|
+
onClick={() => {
|
|
77
|
+
void client.signOut().then(() => {
|
|
78
|
+
navigate("/sign-in");
|
|
79
|
+
});
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{t("account.signOut")}
|
|
83
|
+
</Button>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { accessRequirementOf, createInbisAuthClient, type AccessRequirement } from "@awsmbiz/inbis-auth/client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
// Same origin as the Worker serving this SPA.
|
|
5
|
+
export const client = createInbisAuthClient();
|
|
6
|
+
|
|
7
|
+
/** Where to go after signing in: `?next=` when it is a path on this site, else home. */
|
|
8
|
+
export function nextPath(search: string): string {
|
|
9
|
+
const next = new URLSearchParams(search).get("next");
|
|
10
|
+
return next?.startsWith("/") === true && !next.startsWith("//") ? next : "/";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The signed-in user, as `GET /api/me` returns it. */
|
|
14
|
+
export interface Me {
|
|
15
|
+
user: { id: string; name: string; email: string; phoneNumber: string | null; emailIsPlaceholder: boolean; role: "admin" | "user" };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type MeState =
|
|
19
|
+
| { status: "loading" }
|
|
20
|
+
| { status: "signed-out" }
|
|
21
|
+
/** Signed in, but the session needs a second factor or a different sign-in first. */
|
|
22
|
+
| { status: "requirement"; requirement: AccessRequirement }
|
|
23
|
+
| { status: "signed-in"; me: Me };
|
|
24
|
+
|
|
25
|
+
/** Asks `GET /api/me` who is signed in. `refresh` asks again (after signing in or out). */
|
|
26
|
+
export function useMe(): [MeState, () => void] {
|
|
27
|
+
const [state, setState] = useState<MeState>({ status: "loading" });
|
|
28
|
+
const [attempt, setAttempt] = useState(0);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
let active = true;
|
|
31
|
+
void fetch("/api/me", { credentials: "include" })
|
|
32
|
+
.then(async (response): Promise<MeState> => {
|
|
33
|
+
if (response.status === 401) return { status: "signed-out" };
|
|
34
|
+
const requirement = await accessRequirementOf(response.clone());
|
|
35
|
+
if (requirement !== null) return { status: "requirement", requirement };
|
|
36
|
+
if (!response.ok) return { status: "signed-out" };
|
|
37
|
+
return { status: "signed-in", me: (await response.json()) as Me };
|
|
38
|
+
})
|
|
39
|
+
.then((next) => {
|
|
40
|
+
if (active) setState(next);
|
|
41
|
+
})
|
|
42
|
+
.catch(() => {
|
|
43
|
+
if (active) setState({ status: "signed-out" });
|
|
44
|
+
});
|
|
45
|
+
return () => {
|
|
46
|
+
active = false;
|
|
47
|
+
};
|
|
48
|
+
}, [attempt]);
|
|
49
|
+
return [
|
|
50
|
+
state,
|
|
51
|
+
() => {
|
|
52
|
+
setAttempt((n) => n + 1);
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** How to show who is signed in: a phone-only user's email is a placeholder. */
|
|
58
|
+
export function identityOf(me: Me): string {
|
|
59
|
+
return (me.user.emailIsPlaceholder ? me.user.phoneNumber : me.user.email) ?? "";
|
|
60
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AcceptInvitationScreen,
|
|
3
|
+
ForgotPasswordScreen,
|
|
4
|
+
PhoneSignInScreen,
|
|
5
|
+
ResetPasswordScreen,
|
|
6
|
+
SessionsScreen,
|
|
7
|
+
SignInScreen,
|
|
8
|
+
SignUpScreen,
|
|
9
|
+
TwoFactorSettingsScreen,
|
|
10
|
+
VerifyEmailScreen,
|
|
11
|
+
} from "@awsmbiz/inbis-auth-ui";
|
|
12
|
+
import { client, nextPath } from "./auth-client.js";
|
|
13
|
+
import type { RouteContext } from "./routes.js";
|
|
14
|
+
|
|
15
|
+
// The sign-in screens of @awsmbiz/inbis-auth-ui, each a full page of its own (bare
|
|
16
|
+
// routes in routes.tsx). Each screen reads the server's public auth config, so it only
|
|
17
|
+
// offers what the app has switched on (sign-up when registration is open, phone sign-in,
|
|
18
|
+
// password reset, 2FA).
|
|
19
|
+
|
|
20
|
+
/** Signs out, then opens sign-in, coming back to `next` afterwards. */
|
|
21
|
+
function signInAgain({ navigate }: RouteContext, next: string) {
|
|
22
|
+
void client.signOut().then(() => {
|
|
23
|
+
navigate(`/sign-in?next=${encodeURIComponent(next)}`);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function SignInPage({ navigate, search }: RouteContext) {
|
|
28
|
+
return (
|
|
29
|
+
<SignInScreen
|
|
30
|
+
client={client}
|
|
31
|
+
onSignedIn={() => {
|
|
32
|
+
navigate(nextPath(search));
|
|
33
|
+
}}
|
|
34
|
+
onCreateAccount={() => {
|
|
35
|
+
navigate("/sign-up");
|
|
36
|
+
}}
|
|
37
|
+
onForgotPassword={() => {
|
|
38
|
+
navigate("/forgot-password");
|
|
39
|
+
}}
|
|
40
|
+
onVerifyEmail={() => {
|
|
41
|
+
navigate("/verify-email");
|
|
42
|
+
}}
|
|
43
|
+
onPhoneSignIn={() => {
|
|
44
|
+
navigate(`/sign-in/phone${search}`);
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function PhoneSignInPage({ navigate, search }: RouteContext) {
|
|
51
|
+
return (
|
|
52
|
+
<PhoneSignInScreen
|
|
53
|
+
client={client}
|
|
54
|
+
onSignedIn={() => {
|
|
55
|
+
navigate(nextPath(search));
|
|
56
|
+
}}
|
|
57
|
+
onSignIn={() => {
|
|
58
|
+
navigate(`/sign-in${search}`);
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function SignUpPage({ navigate }: RouteContext) {
|
|
65
|
+
return (
|
|
66
|
+
<SignUpScreen
|
|
67
|
+
client={client}
|
|
68
|
+
// Email verification is required: the new account signs in after verifying.
|
|
69
|
+
callbackURL="/verify-email?verified=1"
|
|
70
|
+
onSignedUp={() => {
|
|
71
|
+
navigate("/verify-email?sent=1");
|
|
72
|
+
}}
|
|
73
|
+
onSignIn={() => {
|
|
74
|
+
navigate("/sign-in");
|
|
75
|
+
}}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function ForgotPasswordPage({ navigate }: RouteContext) {
|
|
81
|
+
return (
|
|
82
|
+
<ForgotPasswordScreen
|
|
83
|
+
client={client}
|
|
84
|
+
onSignIn={() => {
|
|
85
|
+
navigate("/sign-in");
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function ResetPasswordPage({ navigate }: RouteContext) {
|
|
92
|
+
return (
|
|
93
|
+
<ResetPasswordScreen
|
|
94
|
+
client={client}
|
|
95
|
+
onSignIn={() => {
|
|
96
|
+
navigate("/sign-in");
|
|
97
|
+
}}
|
|
98
|
+
onRequestNewLink={() => {
|
|
99
|
+
navigate("/forgot-password");
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function VerifyEmailPage({ navigate, search }: RouteContext) {
|
|
106
|
+
return (
|
|
107
|
+
<VerifyEmailScreen
|
|
108
|
+
client={client}
|
|
109
|
+
search={search}
|
|
110
|
+
onSignIn={() => {
|
|
111
|
+
navigate("/sign-in");
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Invitation links (`registration.invitations.acceptPath`, default /accept-invitation). */
|
|
118
|
+
export function AcceptInvitationPage({ navigate }: RouteContext) {
|
|
119
|
+
return (
|
|
120
|
+
<AcceptInvitationScreen
|
|
121
|
+
client={client}
|
|
122
|
+
onSignIn={() => {
|
|
123
|
+
navigate("/sign-in");
|
|
124
|
+
}}
|
|
125
|
+
onSignedIn={() => {
|
|
126
|
+
navigate("/account");
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function SecurityPage(context: RouteContext) {
|
|
133
|
+
return (
|
|
134
|
+
<TwoFactorSettingsScreen
|
|
135
|
+
client={client}
|
|
136
|
+
onReauthenticate={() => {
|
|
137
|
+
signInAgain(context, "/account/security");
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function SessionsPage(context: RouteContext) {
|
|
144
|
+
return (
|
|
145
|
+
<SessionsScreen
|
|
146
|
+
client={client}
|
|
147
|
+
onReauthenticate={() => {
|
|
148
|
+
signInAgain(context, "/account/sessions");
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auth",
|
|
3
|
+
"description": "Sign-in: email/password, phone OTP, 2FA, invitations, registration modes, bootstrap-admin, auth emails/SMS and the auth-ui screens.",
|
|
4
|
+
"order": 10,
|
|
5
|
+
"requires": [],
|
|
6
|
+
"bindings": {
|
|
7
|
+
"AUTH_DB": "the auth tables (@awsmbiz/inbis-auth migrations, same database)"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@awsmbiz/inbis-auth": "workspace:*",
|
|
11
|
+
"@awsmbiz/inbis-auth-ui": "workspace:*",
|
|
12
|
+
"@awsmbiz/inbis-notifications": "workspace:*"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"better-auth": "1.7.14"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"db:seed": "node scripts/seed-user.mjs",
|
|
19
|
+
"auth:bootstrap-admin": "inbis-auth bootstrap-admin"
|
|
20
|
+
},
|
|
21
|
+
"points": {
|
|
22
|
+
"account.sections": {
|
|
23
|
+
"file": "web/src/account.tsx",
|
|
24
|
+
"description": "JSX after the account page's Security card (e.g. a Card); `me` (the signed-in user), `navigate` and `linkClass` are in scope."
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"wiring": {
|
|
28
|
+
"worker.imports": "wiring/worker.imports.ts",
|
|
29
|
+
"worker.deps": "wiring/worker.deps.ts",
|
|
30
|
+
"worker.routes": "wiring/worker.routes.ts",
|
|
31
|
+
"index.imports": "wiring/index.imports.ts",
|
|
32
|
+
"index.setup": "wiring/index.setup.ts",
|
|
33
|
+
"index.deps": "wiring/index.deps.ts",
|
|
34
|
+
"env.bindings": "wiring/env.bindings.ts",
|
|
35
|
+
"wrangler.vars": { "dev": "wiring/vars.dev.jsonc", "staging": "wiring/vars.staging.jsonc", "production": "wiring/vars.production.jsonc" },
|
|
36
|
+
"wrangler.secrets": "wiring/secrets.jsonc",
|
|
37
|
+
"wrangler.bindings": { "staging": "wiring/bindings.deployed.jsonc", "production": "wiring/bindings.deployed.jsonc" },
|
|
38
|
+
"wrangler.d1": "wiring/d1.jsonc",
|
|
39
|
+
"devvars": "wiring/devvars.txt",
|
|
40
|
+
"test.env": "wiring/test.env.ts",
|
|
41
|
+
"test.migrate": "wiring/test.migrate.ts",
|
|
42
|
+
"vitest.bindings": "wiring/vitest.bindings.ts",
|
|
43
|
+
"playwright.imports": "wiring/playwright.imports.ts",
|
|
44
|
+
"playwright.setup": "wiring/playwright.setup.ts",
|
|
45
|
+
"playwright.vars": "wiring/playwright.vars.ts",
|
|
46
|
+
"playwright.servers": "wiring/playwright.servers.ts",
|
|
47
|
+
"web.routes.imports": "wiring/web.routes.imports.tsx",
|
|
48
|
+
"web.routes": "wiring/web.routes.tsx",
|
|
49
|
+
"web.layout.imports": "wiring/web.layout.imports.tsx",
|
|
50
|
+
"web.header": "wiring/web.header.tsx",
|
|
51
|
+
"web.messages": "wiring/web.messages.ts",
|
|
52
|
+
"web.styles": "wiring/web.styles.css",
|
|
53
|
+
"readme.modules": "wiring/readme.modules.md",
|
|
54
|
+
"readme.provisioning": "wiring/readme.provisioning.md"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Auth: signs sessions and encrypts 2FA secrets. Any random string of 32+ characters,
|
|
2
|
+
# e.g. `openssl rand -base64 32`.
|
|
3
|
+
AUTH_SECRET=change-me-to-a-random-string-of-32-plus-chars
|
|
4
|
+
# Turnstile secret key. Locally, Cloudflare's testing secret that accepts any token
|
|
5
|
+
# (pairs with the testing TURNSTILE_SITE_KEY below).
|
|
6
|
+
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA
|
|
7
|
+
# Local-only overrides, so a deployed environment's config also starts on this machine
|
|
8
|
+
# (`wrangler dev --env staging`, e.g. the deploy workflow's dry-run smoke test): log
|
|
9
|
+
# transports instead of the communication service, and Cloudflare's Turnstile testing site key.
|
|
10
|
+
EMAIL_TRANSPORT=log
|
|
11
|
+
SMS_TRANSPORT=log
|
|
12
|
+
TURNSTILE_SITE_KEY=1x00000000000000000000AA
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The auth tables (@awsmbiz/inbis-auth migrations); same database as DB. */
|
|
2
|
+
AUTH_DB: D1Database;
|
|
3
|
+
/** Public origin of this Worker (auth links, cookies; https outside localhost). */
|
|
4
|
+
AUTH_BASE_URL: string;
|
|
5
|
+
/** Secret: signs sessions and encrypts 2FA secrets (32+ random characters). */
|
|
6
|
+
AUTH_SECRET: string;
|
|
7
|
+
/** Secret: the Turnstile widget's secret key. */
|
|
8
|
+
TURNSTILE_SECRET_KEY: string;
|
|
9
|
+
/** The Turnstile widget's public site key. */
|
|
10
|
+
TURNSTILE_SITE_KEY: string;
|
|
11
|
+
/** Sender of auth emails, `Name <address>`, on a domain onboarded to the communication service. */
|
|
12
|
+
EMAIL_FROM: string;
|
|
13
|
+
/** "log" (local) or "communication-service". */
|
|
14
|
+
EMAIL_TRANSPORT: string;
|
|
15
|
+
/** "log" (local) or "communication-service". */
|
|
16
|
+
SMS_TRANSPORT: string;
|
|
17
|
+
/** The app's ID in the communication service. */
|
|
18
|
+
APP_ID: string;
|
|
19
|
+
/** SMS sender header and the registered template IDs (communication-service SMS). */
|
|
20
|
+
SMS_HEADER: string;
|
|
21
|
+
SMS_OTP_TEMPLATE_ID: string;
|
|
22
|
+
SMS_INVITATION_TEMPLATE_ID: string;
|
|
23
|
+
/** "true": local test mode (src/local-test-mode.ts); ignored off local hosts. */
|
|
24
|
+
LOCAL_TEST_MODE?: string;
|
|
25
|
+
/** The shared communication service (deployed environments only). */
|
|
26
|
+
COMMUNICATION_SERVICE?: Fetcher;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
auth: { authConfig, ...notifications, ...(localTest ? { fetch: stubbedFetch((input, init) => fetch(input, init)) } : {}) },
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const authConfig = appAuthConfig(env);
|
|
2
|
+
validateAuthConfig(authConfig);
|
|
3
|
+
const notifications = appNotifications(env, authConfig);
|
|
4
|
+
// Playwright runs (LOCAL_TEST_MODE, local hosts only): no calls to Turnstile or Pwned Passwords.
|
|
5
|
+
// Better Auth's captcha plugin calls the global fetch, so that is wrapped too.
|
|
6
|
+
const localTest = localTestMode(env);
|
|
7
|
+
if (localTest) globalThis.fetch = stubbedFetch(globalThis.fetch.bind(globalThis));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { authDevVars, authSeedCommands, BOOTSTRAP_PORT, BOOTSTRAP_STATE, BOOTSTRAP_URL } from "./e2e/auth-seeded.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Auth: a fresh installation with no users, for the first administrator
|
|
2
|
+
// (e2e/auth-bootstrap-admin.spec.ts runs `inbis-auth bootstrap-admin` against it).
|
|
3
|
+
{
|
|
4
|
+
command: [
|
|
5
|
+
`rm -rf ${BOOTSTRAP_STATE}`,
|
|
6
|
+
`inbis-db migrate --local --persist-to ${BOOTSTRAP_STATE}`,
|
|
7
|
+
`wrangler dev --port ${BOOTSTRAP_PORT} --persist-to ${BOOTSTRAP_STATE} ${authDevVars(BOOTSTRAP_URL)}`,
|
|
8
|
+
].join(" && "),
|
|
9
|
+
url: `${BOOTSTRAP_URL}/health`,
|
|
10
|
+
reuseExistingServer: false,
|
|
11
|
+
timeout: 120_000,
|
|
12
|
+
},
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## Sign-in (auth module)
|
|
2
|
+
|
|
3
|
+
`@awsmbiz/inbis-auth` (Better Auth on D1), `@awsmbiz/inbis-auth-ui` (the screens) and
|
|
4
|
+
`@awsmbiz/inbis-notifications` (auth emails and SMS).
|
|
5
|
+
|
|
6
|
+
- **Choices** — `src/auth-choices.ts`: the sign-in methods, the registration mode and the
|
|
7
|
+
phone country allow/block lists chosen when the app was created. Change them there;
|
|
8
|
+
`src/auth-config.ts` turns them into the auth config (every other option is the
|
|
9
|
+
platform default; see the `@awsmbiz/inbis-auth` README). Registration modes: `open`
|
|
10
|
+
(anyone signs up), `invite-only` (administrators create accounts or invite),
|
|
11
|
+
`admin-only` (administrators create accounts; invitations are refused), `closed`
|
|
12
|
+
(only the first administrator). Phone countries are calling-code prefixes; there is
|
|
13
|
+
no default.
|
|
14
|
+
- **Routes** — `src/auth.ts` mounts `/api/auth/*`, `GET /api/me` and, locally, the outboxes.
|
|
15
|
+
Protect your own routes with `requireAuth()` from `@awsmbiz/inbis-auth/hono`.
|
|
16
|
+
- **Screens** — `/sign-in`, `/sign-in/phone`, `/sign-up`, `/forgot-password`,
|
|
17
|
+
`/reset-password`, `/verify-email`, `/accept-invitation`, `/account` (with
|
|
18
|
+
`/account/security` for 2FA and `/account/sessions`). The header links to sign-in or
|
|
19
|
+
the account page.
|
|
20
|
+
- **Email and SMS** — locally (`EMAIL_TRANSPORT`/`SMS_TRANSPORT` = `log`) nothing is sent:
|
|
21
|
+
messages are printed and kept in a dev outbox, `GET /api/dev/outbox` and
|
|
22
|
+
`GET /api/dev/sms-outbox` (local hosts only). Deployed environments send through the
|
|
23
|
+
shared communication service (see Provisioning).
|
|
24
|
+
- **First administrator** — `pnpm auth:bootstrap-admin --email you@example.com --url <app URL>`
|
|
25
|
+
prints a one-time invitation link (add `--remote --env production` for a deployed
|
|
26
|
+
database, `--send-email` to email it). Administrators set up 2FA on first use.
|
|
27
|
+
- **Local users** — `pnpm db:seed [--email a@b.test] [--password '...'] [--role admin]`
|
|
28
|
+
adds a verified email + password user to the local database.
|
|
29
|
+
- **Tests** — `test/auth.test.ts` (Workers) and `e2e/auth-*.spec.ts` (Playwright + axe)
|
|
30
|
+
read `src/auth-choices.ts` and skip the journeys the app has switched off.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Sign-in (auth module):
|
|
2
|
+
- `AUTH_SECRET`: 32+ random characters (`openssl rand -base64 32`); `TURNSTILE_SECRET_KEY`:
|
|
3
|
+
the secret of a [Turnstile widget](https://developers.cloudflare.com/turnstile/get-started/)
|
|
4
|
+
for the app's domains, whose site key goes in `TURNSTILE_SITE_KEY` in `wrangler.jsonc`.
|
|
5
|
+
- Email and SMS go through the shared communication service (`biz-cloud-communication-service`,
|
|
6
|
+
bound as `COMMUNICATION_SERVICE` in each deployed environment). Register the app with it
|
|
7
|
+
under `APP_ID` and onboard the sender domain of `EMAIL_FROM`. With phone sign-in on, register
|
|
8
|
+
the SMS texts in `src/notifications.ts` (`SMS_TEXTS`) and set `SMS_HEADER`,
|
|
9
|
+
`SMS_OTP_TEMPLATE_ID` and `SMS_INVITATION_TEMPLATE_ID`; startup fails while they are blank.
|
|
10
|
+
- After migrating and deploying (step 4), create the first administrator:
|
|
11
|
+
`pnpm auth:bootstrap-admin --email you@example.com --url https://<domain> --remote --env <env>`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AUTH_MIGRATIONS: D1Migration[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
await applyD1Migrations(env.AUTH_DB, env.AUTH_MIGRATIONS, "_inbis_auth_migrations");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Auth: public origin of this Worker; https gives production cookies (Secure, __Secure- prefix).
|
|
2
|
+
"AUTH_BASE_URL": "http://localhost:8787",
|
|
3
|
+
// Sender of auth emails (verification, password reset, invitations).
|
|
4
|
+
"EMAIL_FROM": "React Hono App <no-reply@react-hono-app.example.com>",
|
|
5
|
+
// "log": print emails/SMS and keep a local outbox (GET /api/dev/outbox, /api/dev/sms-outbox;
|
|
6
|
+
// local hosts only). Deployed environments use "communication-service".
|
|
7
|
+
"EMAIL_TRANSPORT": "log",
|
|
8
|
+
"SMS_TRANSPORT": "log",
|
|
9
|
+
// The app's ID in the communication service, and the SMS sender header and template IDs
|
|
10
|
+
// (communication-service SMS only; see README). Unused locally.
|
|
11
|
+
"APP_ID": "",
|
|
12
|
+
"SMS_HEADER": "",
|
|
13
|
+
"SMS_OTP_TEMPLATE_ID": "",
|
|
14
|
+
"SMS_INVITATION_TEMPLATE_ID": "",
|
|
15
|
+
// Cloudflare's Turnstile testing site key (always passes), paired with the testing secret
|
|
16
|
+
// in .dev.vars.example: https://developers.cloudflare.com/turnstile/troubleshooting/testing/
|
|
17
|
+
"TURNSTILE_SITE_KEY": "1x00000000000000000000AA",
|
|
18
|
+
// "true": local test mode (src/local-test-mode.ts); the Playwright suite sets it.
|
|
19
|
+
"LOCAL_TEST_MODE": "",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Auth: this environment's public origin.
|
|
2
|
+
"AUTH_BASE_URL": "https://react-hono-app.example.com",
|
|
3
|
+
"EMAIL_FROM": "React Hono App <no-reply@react-hono-app.example.com>",
|
|
4
|
+
"EMAIL_TRANSPORT": "communication-service",
|
|
5
|
+
"SMS_TRANSPORT": "communication-service",
|
|
6
|
+
// Registered with the communication service before the first send (see README).
|
|
7
|
+
"APP_ID": "react-hono-app",
|
|
8
|
+
// From the app's SMS registration. Startup fails while these are blank and phone sign-in is on.
|
|
9
|
+
"SMS_HEADER": "",
|
|
10
|
+
"SMS_OTP_TEMPLATE_ID": "",
|
|
11
|
+
"SMS_INVITATION_TEMPLATE_ID": "",
|
|
12
|
+
// The deployed Turnstile widget's site key (its secret: TURNSTILE_SECRET_KEY). Startup fails while blank.
|
|
13
|
+
"TURNSTILE_SITE_KEY": "",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Auth: this environment's public origin.
|
|
2
|
+
"AUTH_BASE_URL": "https://staging.react-hono-app.example.com",
|
|
3
|
+
"EMAIL_FROM": "React Hono App <no-reply@react-hono-app.example.com>",
|
|
4
|
+
"EMAIL_TRANSPORT": "communication-service",
|
|
5
|
+
"SMS_TRANSPORT": "communication-service",
|
|
6
|
+
// Registered with the communication service before the first send (see README).
|
|
7
|
+
"APP_ID": "react-hono-app",
|
|
8
|
+
// From the app's SMS registration. Startup fails while these are blank and phone sign-in is on.
|
|
9
|
+
"SMS_HEADER": "",
|
|
10
|
+
"SMS_OTP_TEMPLATE_ID": "",
|
|
11
|
+
"SMS_INVITATION_TEMPLATE_ID": "",
|
|
12
|
+
// The deployed Turnstile widget's site key (its secret: TURNSTILE_SECRET_KEY). Startup fails while blank.
|
|
13
|
+
"TURNSTILE_SITE_KEY": "",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Auth: a production-like https origin (Secure cookies), test-only secrets and
|
|
2
|
+
// Cloudflare's Turnstile testing secret (siteverify is faked in test/auth-fixtures.ts).
|
|
3
|
+
AUTH_BASE_URL: "https://app.example.test",
|
|
4
|
+
AUTH_SECRET: "test-only-secret-0123456789abcdefghijklmnop",
|
|
5
|
+
TURNSTILE_SECRET_KEY: "1x0000000000000000000000000000000AA",
|
|
6
|
+
AUTH_MIGRATIONS: await readD1Migrations("./node_modules/@awsmbiz/inbis-auth/migrations"),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<AccountMenu path={path} navigate={navigate} />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { AccountMenu } from "./account-menu.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Sign-in (auth module). The screens' own text comes from @awsmbiz/inbis-auth-ui.
|
|
2
|
+
"auth.signIn.title": "Sign in",
|
|
3
|
+
"auth.phoneSignIn.title": "Sign in with your phone",
|
|
4
|
+
"auth.signUp.title": "Create an account",
|
|
5
|
+
"auth.forgotPassword.title": "Forgot password",
|
|
6
|
+
"auth.resetPassword.title": "Reset password",
|
|
7
|
+
"auth.verifyEmail.title": "Verify your email",
|
|
8
|
+
"auth.acceptInvitation.title": "Accept your invitation",
|
|
9
|
+
"account.title": "Account",
|
|
10
|
+
"account.heading": "Your account",
|
|
11
|
+
"account.signedInAs": "Signed in as {name} ({identity}).",
|
|
12
|
+
"account.security": "Security",
|
|
13
|
+
"account.securityDescription": "Protect your account and see where you are signed in.",
|
|
14
|
+
"account.twoFactor": "Two-factor authentication",
|
|
15
|
+
"account.sessions": "Signed-in devices",
|
|
16
|
+
"account.signIn": "Sign in",
|
|
17
|
+
"account.signOut": "Sign out",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Sign-in (auth module): full-page screens, then the signed-in user's pages.
|
|
2
|
+
{ path: "/sign-in", title: "auth.signIn.title", bare: true, render: (c) => <SignInPage {...c} /> },
|
|
3
|
+
{ path: "/sign-in/phone", title: "auth.phoneSignIn.title", bare: true, render: (c) => <PhoneSignInPage {...c} /> },
|
|
4
|
+
{ path: "/sign-up", title: "auth.signUp.title", bare: true, render: (c) => <SignUpPage {...c} /> },
|
|
5
|
+
{ path: "/forgot-password", title: "auth.forgotPassword.title", bare: true, render: (c) => <ForgotPasswordPage {...c} /> },
|
|
6
|
+
{ path: "/reset-password", title: "auth.resetPassword.title", bare: true, render: (c) => <ResetPasswordPage {...c} /> },
|
|
7
|
+
{ path: "/verify-email", title: "auth.verifyEmail.title", bare: true, render: (c) => <VerifyEmailPage {...c} /> },
|
|
8
|
+
{ path: "/accept-invitation", title: "auth.acceptInvitation.title", bare: true, render: (c) => <AcceptInvitationPage {...c} /> },
|
|
9
|
+
{ path: "/account/security", title: "account.twoFactor", bare: true, render: (c) => <SecurityPage {...c} /> },
|
|
10
|
+
{ path: "/account/sessions", title: "account.sessions", bare: true, render: (c) => <SessionsPage {...c} /> },
|
|
11
|
+
{ path: "/account", title: "account.title", render: (c) => <AccountPage {...c} /> },
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "@awsmbiz/inbis-auth-ui/styles.css";
|