@starci/hfs 5.2.0 → 5.4.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/CHANGELOG.md +43 -0
- package/README.md +2 -1
- package/cache/hfs-family.d.mts +2 -3
- package/cache/hfs-family.mjs +4 -9
- package/cache/secret-records.d.mts +7 -0
- package/cache/secret-records.mjs +12 -0
- package/cache/session.mjs +4 -2
- package/codemod/account-doors.d.mts +36 -0
- package/codemod/account-doors.mjs +134 -0
- package/codemod/app-prettier.d.mts +5 -0
- package/codemod/app-prettier.mjs +27 -0
- package/codemod/explicit-surface.mjs +1 -25
- package/codemod/main.mjs +35 -1
- package/codemod/module-definition.d.mts +59 -0
- package/codemod/module-definition.mjs +257 -0
- package/codemod/quality-binding.d.mts +33 -0
- package/codemod/quality-binding.mjs +38 -0
- package/codemod/secrets-cache.d.mts +35 -0
- package/codemod/secrets-cache.mjs +70 -0
- package/codemod/sops-rules.d.mts +34 -0
- package/codemod/sops-rules.mjs +69 -0
- package/codemod/theme-tokens.mjs +48 -2
- package/lint/run.d.mts +6 -0
- package/lint/run.mjs +63 -10
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +43 -10
- package/runtime/knowledge/hfs/fe-stack.yaml +3 -3
- package/runtime/knowledge/hfs/rules.yaml +13 -9
- package/runtime/knowledge/hfs/slots.yaml +105 -12
- package/runtime/modules/kernel/failure-codes.yaml +5 -4
- package/runtime/scripts/hfs/architecture/config.d.mts +4 -0
- package/runtime/scripts/hfs/architecture/config.mjs +25 -1
- package/runtime/scripts/hfs/architecture/module-per-transport.d.mts +1 -1
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +6 -1
- package/runtime/scripts/hfs/architecture/root-packages.d.mts +26 -0
- package/runtime/scripts/hfs/architecture/root-packages.mjs +78 -0
- package/runtime/scripts/hfs/architecture/type-context.d.mts +4 -1
- package/runtime/scripts/hfs/architecture/type-context.mjs +30 -0
- package/runtime/scripts/hfs/architecture/typescript.d.mts +7 -1
- package/runtime/scripts/hfs/architecture/typescript.mjs +21 -1
- package/runtime/scripts/hfs/explicit-surface-rule.mjs +1 -1
- package/runtime/scripts/hfs/rules/database-access.mjs +39 -1
- package/runtime/scripts/hfs/rules/monorepo.d.mts +4 -0
- package/runtime/scripts/hfs/rules/monorepo.mjs +10 -5
- package/runtime/scripts/hfs/rules/secrets.mjs +11 -0
- package/runtime/scripts/hfs/secret-errors.d.mts +3 -0
- package/runtime/scripts/hfs/secret-errors.mjs +8 -0
- package/runtime/scripts/hfs/secret-init.d.mts +6 -0
- package/runtime/scripts/hfs/secret-init.mjs +23 -0
- package/runtime/scripts/hfs/secret-sops.mjs +9 -1
- package/runtime/scripts/hfs/secret-standard.d.mts +20 -0
- package/runtime/scripts/hfs/secret-standard.mjs +106 -0
- package/runtime/scripts/hfs/secret.mjs +5 -1
- package/runtime/scripts/hfs/view.d.mts +23 -0
- package/runtime/scripts/hfs/view.mjs +68 -0
- package/runtime/scripts/lib/declaration-surface.d.mts +4 -0
- package/runtime/scripts/lib/declaration-surface.mjs +55 -1
- package/runtime/scripts/lib/secret-records.d.mts +13 -0
- package/runtime/scripts/lib/secret-records.mjs +83 -0
- package/scaffold/add-inbox-lite.mjs +2 -2
- package/scaffold/add-queue-lite.mjs +1 -3
- package/scaffold/app.d.mts +2 -2
- package/scaffold/app.mjs +10 -12
- package/scaffold/lite-exports.mjs +1 -15
- package/scripts/sync-bundles.d.mts +8 -0
- package/stack/images/openclaw-runtime/Dockerfile +18 -0
- package/stack/images/openclaw-runtime/entrypoint.sh +23 -0
- package/stack/images/openclaw-runtime/openclaw-init.mjs +101 -0
- package/stack/images/openclaw-runtime/openclaw-relay.mjs +25 -0
- package/stack/local-up.mjs +3 -0
- package/stack/runtime-layers.d.mts +21 -0
- package/stack/runtime-layers.mjs +67 -0
- package/sync/compile-guard.d.mts +36 -0
- package/sync/compile-guard.mjs +189 -0
- package/sync/hygiene.mjs +3 -1
- package/sync/index.d.mts +11 -1
- package/sync/index.mjs +61 -10
- package/templates/app/auth-doors.sql.partial +115 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -1
- package/templates/app/package-scripts/core.json +1 -1
- package/templates/app/package-scripts/package.json +1 -1
- package/templates/app/package-scripts-lite/package.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +9 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +3 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +19 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +3 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +5 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +4 -0
- package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +27 -5
- package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +33 -0
- package/templates/be/auth-lite/src/features/api/auth/application/confirm.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/confirm.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/forgot.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/forgot.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/resend.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/resend.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/reset.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/reset.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/signup.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/signup.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +3 -10
- package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +6 -1
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +3 -10
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +56 -1
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/forgot.request.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/resend.request.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/reset.request.ts +18 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/signup.request.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/token.request.ts +11 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +30 -30
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +7 -16
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +113 -49
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +42 -53
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.log-events.ts +6 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.spec.ts +233 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.ts +176 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-sign-in.log-events.ts +6 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/account-doors.port.ts +98 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-email-token.entity.ts +34 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +4 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +3 -10
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +6 -3
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +6 -1
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +17 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +6 -0
- package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +29 -33
- package/templates/be/inbox/module-definition.ts.tpl +2 -9
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +2 -11
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +3 -9
- package/templates/be/platform/graphql/graphql.module-definition.ts +2 -9
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +2 -9
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +3 -13
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +4 -11
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +4 -9
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +5 -11
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +4 -11
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +2 -9
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +3 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +3 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +3 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity-mail.messages.ts +65 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/index.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.config.ts +33 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.decorators.ts +14 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module-definition.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module.ts +27 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.options.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +2 -9
- package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/rate-limit.guard.spec.ts +11 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +11 -0
- package/templates/be/table/api/feature.module-definition.ts.tpl +3 -9
- package/templates/be/table/api/graphql.module-definition.ts.tpl +3 -9
- package/templates/be/table/module-definition.ts.tpl +2 -9
- package/templates/fe/auth-lite/apps/web/src/modules/auth/email-doors.ts +16 -0
- package/templates/fe/auth-lite/apps/web/src/modules/auth/index.ts +5 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +30 -67
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +6 -21
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +10 -16
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/confirm/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/forgot/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/resend/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/reset/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/signup/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +5 -9
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/component.tsx +51 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/index.tsx +84 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppConfirmPage/index.tsx +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppForgotPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +11 -3
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +4 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResendPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResetPage/index.tsx +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppSignupPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/emailAuth.shared.ts +12 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/index.ts +2 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/useEmailDoor.ts +32 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +50 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +50 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +7 -30
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +13 -14
- package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +5 -0
- package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +4 -17
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +0 -38
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +0 -54
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactElement } from "react"
|
|
2
|
+
import type { Metadata } from "next"
|
|
3
|
+
import { Page } from "@starci/grammar/common"
|
|
4
|
+
import { getTranslations } from "next-intl/server"
|
|
5
|
+
import { EmailDoorForm } from "@/components/blocks/EmailDoorForm"
|
|
6
|
+
|
|
7
|
+
/** The document title of the reset page. */
|
|
8
|
+
export const appResetMetadata = async (): Promise<Metadata> => {
|
|
9
|
+
const t = await getTranslations("app.emailAuth.reset")
|
|
10
|
+
return { title: t("title") }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The query of the page: the link of the mail carries its token there. */
|
|
14
|
+
type AppResetPageProps = { readonly searchParams: Promise<{ readonly token?: string | readonly string[] }> }
|
|
15
|
+
|
|
16
|
+
/** The reset door of the lite sign-in, drawn as one Grammar Page (R325). */
|
|
17
|
+
export const AppResetPage = async (props: AppResetPageProps): Promise<ReactElement> => {
|
|
18
|
+
const { token } = await props.searchParams
|
|
19
|
+
const t = await getTranslations("app.emailAuth.reset")
|
|
20
|
+
return (
|
|
21
|
+
<Page header={{ title: t("title") }} measure="reading">
|
|
22
|
+
<EmailDoorForm door="reset" token={typeof token === "string" ? token : token?.[0]} />
|
|
23
|
+
</Page>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactElement } from "react"
|
|
2
|
+
import type { Metadata } from "next"
|
|
3
|
+
import { Page } from "@starci/grammar/common"
|
|
4
|
+
import { getTranslations } from "next-intl/server"
|
|
5
|
+
import { EmailDoorForm } from "@/components/blocks/EmailDoorForm"
|
|
6
|
+
|
|
7
|
+
/** The document title of the signup page. */
|
|
8
|
+
export const appSignupMetadata = async (): Promise<Metadata> => {
|
|
9
|
+
const t = await getTranslations("app.emailAuth.signup")
|
|
10
|
+
return { title: t("title") }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The signup door of the lite sign-in, drawn as one Grammar Page (R325). */
|
|
14
|
+
export const AppSignupPage = async (_props: Record<string, never>): Promise<ReactElement> => {
|
|
15
|
+
const t = await getTranslations("app.emailAuth.signup")
|
|
16
|
+
return (
|
|
17
|
+
<Page header={{ title: t("title") }} measure="reading">
|
|
18
|
+
<EmailDoorForm door="signup" />
|
|
19
|
+
</Page>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AuthFailure } from "@/modules/auth"
|
|
2
|
+
|
|
3
|
+
/** Why an email door refused: input the back end rejected, a weak password, a dead link, too many tries or the back end unreachable. */
|
|
4
|
+
export type EmailDoorFailure = "invalid" | "password" | "link" | "tooMany" | "unavailable"
|
|
5
|
+
|
|
6
|
+
/** The failure a door's wire code and kind stand for. */
|
|
7
|
+
export const failureOf = (failure: AuthFailure): EmailDoorFailure => {
|
|
8
|
+
if (failure.code === "IDENTITY_PASSWORD_INVALID") return "password"
|
|
9
|
+
if (failure.code === "IDENTITY_RESET_TOKEN_INVALID") return "link"
|
|
10
|
+
if (failure.code === "IDENTITY_TOO_MANY_ATTEMPTS" || failure.code === "429") return "tooMany"
|
|
11
|
+
return failure.kind === "invalid" ? "invalid" : "unavailable"
|
|
12
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useState, useTransition } from "react"
|
|
4
|
+
import { sendEmailDoor } from "@/modules/auth"
|
|
5
|
+
import type { EmailDoorName } from "@/modules/auth"
|
|
6
|
+
import { failureOf } from "./emailAuth.shared"
|
|
7
|
+
import type { EmailDoorFailure } from "./emailAuth.shared"
|
|
8
|
+
|
|
9
|
+
export type { EmailDoorFailure } from "./emailAuth.shared"
|
|
10
|
+
|
|
11
|
+
/** What an email door hook answers: whether a request runs, whether the back end accepted it, why it refused, the call itself and `reject`. */
|
|
12
|
+
export interface EmailDoor {
|
|
13
|
+
readonly failure: EmailDoorFailure | undefined
|
|
14
|
+
readonly done: boolean
|
|
15
|
+
readonly pending: boolean
|
|
16
|
+
readonly submit: (body: Readonly<Record<string, string | undefined>>) => void
|
|
17
|
+
readonly reject: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Calls one email door of the back end through modules/auth; a success sets `done`, a refusal its failure. */
|
|
21
|
+
export const useEmailDoor = (door: EmailDoorName): EmailDoor => {
|
|
22
|
+
const [failure, setFailure] = useState<EmailDoorFailure>()
|
|
23
|
+
const [done, setDone] = useState(false)
|
|
24
|
+
const [pending, startTransition] = useTransition()
|
|
25
|
+
const submit = (body: Readonly<Record<string, string | undefined>>): void =>
|
|
26
|
+
startTransition(async () => {
|
|
27
|
+
const failed = await sendEmailDoor(door, body)
|
|
28
|
+
setFailure(failed === undefined ? undefined : failureOf(failed))
|
|
29
|
+
setDone(failed === undefined)
|
|
30
|
+
})
|
|
31
|
+
return { failure, done, pending, submit, reject: (): void => setFailure("invalid") }
|
|
32
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/** The locale served at the unprefixed path
|
|
2
|
-
export { DEFAULT_LOCALE,
|
|
1
|
+
/** The web app's description of its languages and the locale served at the unprefixed path (the global error boundary). */
|
|
2
|
+
export { DEFAULT_LOCALE, i18n } from "./routing"
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
"brand": "{{project}}"
|
|
6
6
|
},
|
|
7
7
|
"home": {
|
|
8
|
-
"title": "Home"
|
|
8
|
+
"title": "Home",
|
|
9
|
+
"signup": "Create an account",
|
|
10
|
+
"forgot": "Forgot your password?"
|
|
9
11
|
},
|
|
10
12
|
"signIn": {
|
|
11
13
|
"title": "Sign in",
|
|
@@ -52,6 +54,53 @@
|
|
|
52
54
|
"title": "Getting started",
|
|
53
55
|
"description": "Sign in to use the application. You can close this guide and open it again at any time.",
|
|
54
56
|
"close": "Close"
|
|
57
|
+
},
|
|
58
|
+
"emailAuth": {
|
|
59
|
+
"email": "Email",
|
|
60
|
+
"password": "Password",
|
|
61
|
+
"newPassword": "New password",
|
|
62
|
+
"toSignIn": "Back to sign in",
|
|
63
|
+
"toResend": "Send the confirmation again",
|
|
64
|
+
"toForgot": "Request a new reset link",
|
|
65
|
+
"toSignup": "Create an account",
|
|
66
|
+
"toForgotPassword": "Forgot your password?",
|
|
67
|
+
"signup": {
|
|
68
|
+
"title": "Create an account",
|
|
69
|
+
"description": "Choose a password of at least 8 characters. We email a link to confirm your address.",
|
|
70
|
+
"submit": "Create account",
|
|
71
|
+
"done": "Check your inbox: we sent a link to confirm your email."
|
|
72
|
+
},
|
|
73
|
+
"resend": {
|
|
74
|
+
"title": "Confirm your email again",
|
|
75
|
+
"description": "Enter your address and we email a new confirmation link if the account is waiting for one.",
|
|
76
|
+
"submit": "Send the link",
|
|
77
|
+
"done": "If that address is waiting for confirmation, a new link is on its way."
|
|
78
|
+
},
|
|
79
|
+
"forgot": {
|
|
80
|
+
"title": "Forgot your password",
|
|
81
|
+
"description": "Enter your address and we email a link to choose a new password.",
|
|
82
|
+
"submit": "Send the link",
|
|
83
|
+
"done": "If that address has an account, a reset link is on its way."
|
|
84
|
+
},
|
|
85
|
+
"reset": {
|
|
86
|
+
"title": "Choose a new password",
|
|
87
|
+
"description": "The link works once. Every device is signed out when the password changes.",
|
|
88
|
+
"submit": "Save password",
|
|
89
|
+
"done": "Your password is changed. Sign in with the new one."
|
|
90
|
+
},
|
|
91
|
+
"confirm": {
|
|
92
|
+
"title": "Confirm your email",
|
|
93
|
+
"description": "Press the button to confirm your email address.",
|
|
94
|
+
"submit": "Confirm email",
|
|
95
|
+
"done": "Your email is confirmed. You can sign in."
|
|
96
|
+
},
|
|
97
|
+
"errors": {
|
|
98
|
+
"invalid": "Enter a valid value in every field.",
|
|
99
|
+
"password": "The password needs at least 8 characters and at most 72 bytes.",
|
|
100
|
+
"link": "The link is invalid or has expired. Request a new one.",
|
|
101
|
+
"tooMany": "Too many attempts. Try again later.",
|
|
102
|
+
"unavailable": "This is unavailable. Try again shortly."
|
|
103
|
+
}
|
|
55
104
|
}
|
|
56
105
|
}
|
|
57
106
|
}
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
"brand": "{{project}}"
|
|
6
6
|
},
|
|
7
7
|
"home": {
|
|
8
|
-
"title": "Trang chủ"
|
|
8
|
+
"title": "Trang chủ",
|
|
9
|
+
"signup": "Tạo tài khoản",
|
|
10
|
+
"forgot": "Quên mật khẩu?"
|
|
9
11
|
},
|
|
10
12
|
"signIn": {
|
|
11
13
|
"title": "Đăng nhập",
|
|
@@ -52,6 +54,53 @@
|
|
|
52
54
|
"title": "Bắt đầu",
|
|
53
55
|
"description": "Đăng nhập để sử dụng ứng dụng. Bạn có thể đóng hướng dẫn này và mở lại bất cứ lúc nào.",
|
|
54
56
|
"close": "Đóng"
|
|
57
|
+
},
|
|
58
|
+
"emailAuth": {
|
|
59
|
+
"email": "Email",
|
|
60
|
+
"password": "Mật khẩu",
|
|
61
|
+
"newPassword": "Mật khẩu mới",
|
|
62
|
+
"toSignIn": "Quay lại đăng nhập",
|
|
63
|
+
"toResend": "Gửi lại thư xác nhận",
|
|
64
|
+
"toForgot": "Yêu cầu liên kết đặt lại mới",
|
|
65
|
+
"toSignup": "Tạo tài khoản",
|
|
66
|
+
"toForgotPassword": "Quên mật khẩu?",
|
|
67
|
+
"signup": {
|
|
68
|
+
"title": "Tạo tài khoản",
|
|
69
|
+
"description": "Chọn mật khẩu ít nhất 8 ký tự. Chúng tôi gửi email một liên kết để xác nhận địa chỉ của bạn.",
|
|
70
|
+
"submit": "Tạo tài khoản",
|
|
71
|
+
"done": "Hãy kiểm tra hộp thư: chúng tôi đã gửi liên kết xác nhận email."
|
|
72
|
+
},
|
|
73
|
+
"resend": {
|
|
74
|
+
"title": "Xác nhận email lại",
|
|
75
|
+
"description": "Nhập địa chỉ của bạn, chúng tôi gửi liên kết xác nhận mới nếu tài khoản đang chờ xác nhận.",
|
|
76
|
+
"submit": "Gửi liên kết",
|
|
77
|
+
"done": "Nếu địa chỉ đó đang chờ xác nhận, một liên kết mới đang được gửi."
|
|
78
|
+
},
|
|
79
|
+
"forgot": {
|
|
80
|
+
"title": "Quên mật khẩu",
|
|
81
|
+
"description": "Nhập địa chỉ của bạn, chúng tôi gửi email một liên kết để chọn mật khẩu mới.",
|
|
82
|
+
"submit": "Gửi liên kết",
|
|
83
|
+
"done": "Nếu địa chỉ đó có tài khoản, một liên kết đặt lại đang được gửi."
|
|
84
|
+
},
|
|
85
|
+
"reset": {
|
|
86
|
+
"title": "Chọn mật khẩu mới",
|
|
87
|
+
"description": "Liên kết chỉ dùng được một lần. Mọi thiết bị sẽ bị đăng xuất khi đổi mật khẩu.",
|
|
88
|
+
"submit": "Lưu mật khẩu",
|
|
89
|
+
"done": "Mật khẩu đã được đổi. Hãy đăng nhập bằng mật khẩu mới."
|
|
90
|
+
},
|
|
91
|
+
"confirm": {
|
|
92
|
+
"title": "Xác nhận email",
|
|
93
|
+
"description": "Nhấn nút để xác nhận địa chỉ email của bạn.",
|
|
94
|
+
"submit": "Xác nhận email",
|
|
95
|
+
"done": "Email đã được xác nhận. Bạn có thể đăng nhập."
|
|
96
|
+
},
|
|
97
|
+
"errors": {
|
|
98
|
+
"invalid": "Hãy nhập giá trị hợp lệ ở mọi ô.",
|
|
99
|
+
"password": "Mật khẩu cần ít nhất 8 ký tự và tối đa 72 byte.",
|
|
100
|
+
"link": "Liên kết không hợp lệ hoặc đã hết hạn. Hãy yêu cầu liên kết mới.",
|
|
101
|
+
"tooMany": "Quá nhiều lần thử. Hãy thử lại sau.",
|
|
102
|
+
"unavailable": "Chưa thể thực hiện. Hãy thử lại sau."
|
|
103
|
+
}
|
|
55
104
|
}
|
|
56
105
|
}
|
|
57
106
|
}
|
|
@@ -1,35 +1,12 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import { getRequestConfig } from "next-intl/server"
|
|
5
|
-
import { headers } from "next/headers"
|
|
6
|
-
import { PRODUCT_TIME_ZONE, routing } from "./routing"
|
|
7
|
-
|
|
8
|
-
/** The message tree a catalog module exports as its default: the module is read as `unknown` and checked, never trusted as `any`. */
|
|
9
|
-
const isMessages = (value: unknown): value is AbstractIntlMessages => typeof value === "object" && value !== null
|
|
10
|
-
|
|
11
|
-
const messagesOf = (loaded: unknown): AbstractIntlMessages => {
|
|
12
|
-
const messages = typeof loaded === "object" && loaded !== null && "default" in loaded ? loaded.default : undefined
|
|
13
|
-
if (!isMessages(messages)) throw new TypeError("A message catalog module must export its messages as default")
|
|
14
|
-
return messages
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** The header the next-intl middleware sets on the request with the locale it negotiated. */
|
|
18
|
-
const LOCALE_HEADER = "x-next-intl-locale"
|
|
1
|
+
import { createRequestConfig } from "@starci/next-i18n/request"
|
|
2
|
+
import type { I18nRequestConfig } from "@starci/next-i18n/request"
|
|
3
|
+
import { i18n } from "./routing"
|
|
19
4
|
|
|
20
5
|
/**
|
|
21
|
-
* The request config next-intl's plugin loads
|
|
22
|
-
*
|
|
6
|
+
* The request config next-intl's plugin loads: the app's own catalogs over the shared stack. The locale is the one the proxy
|
|
7
|
+
* negotiated (a route the proxy does not negotiate reads the cookie, then the default); `next/root-params` can only be imported
|
|
8
|
+
* inside the app directory, so no route reader is passed.
|
|
23
9
|
*/
|
|
24
|
-
const requestConfig:
|
|
25
|
-
const requested = (await headers()).get(LOCALE_HEADER)
|
|
26
|
-
const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale
|
|
27
|
-
return {
|
|
28
|
-
locale,
|
|
29
|
-
timeZone: PRODUCT_TIME_ZONE,
|
|
30
|
-
messages: messagesOf(await import(`./messages/${locale}.json`)),
|
|
31
|
-
getMessageFallback: ({ namespace, key }) => `web:${namespace ?? ""}.${key}`,
|
|
32
|
-
}
|
|
33
|
-
})
|
|
10
|
+
const requestConfig: I18nRequestConfig = createRequestConfig(i18n, { app: "web", loadMessages: (locale) => import(`./messages/${locale}.json`) })
|
|
34
11
|
|
|
35
12
|
export default requestConfig
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
+
import { defineI18n } from "@starci/next-i18n"
|
|
2
|
+
import type { I18n } from "@starci/next-i18n"
|
|
3
|
+
|
|
1
4
|
const LOCALES = ["vi", "en"] as const
|
|
2
5
|
|
|
3
|
-
/** The
|
|
4
|
-
export
|
|
6
|
+
/** The description of the web app's languages: the locales and the `as-needed` prefix mode that `defineI18n` types. */
|
|
7
|
+
export type WebI18n = I18n<typeof LOCALES, "as-needed">
|
|
5
8
|
|
|
6
|
-
/** The
|
|
7
|
-
export const
|
|
9
|
+
/** The locale served at the unprefixed path. */
|
|
10
|
+
export const DEFAULT_LOCALE: WebI18n["defaultLocale"] = "vi"
|
|
8
11
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
locales: LOCALES,
|
|
16
|
-
defaultLocale: DEFAULT_LOCALE,
|
|
17
|
-
localePrefix: "as-needed" as const,
|
|
18
|
-
}
|
|
12
|
+
/**
|
|
13
|
+
* The one description of the web app's languages, read by the proxy, the request config and the locale layout: vi default, a prefix
|
|
14
|
+
* only when the locale is not the default, and a fixed time zone so the server and the hydrated client format the same instant the
|
|
15
|
+
* same way. The stack that reads it is `@starci/next-i18n`'s (FE-I18N-1).
|
|
16
|
+
*/
|
|
17
|
+
export const i18n: WebI18n = defineI18n({ locales: LOCALES, defaultLocale: DEFAULT_LOCALE, timeZone: "Asia/Ho_Chi_Minh" })
|
|
@@ -1,25 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import { routing } from "@/modules/i18n"
|
|
5
|
-
|
|
6
|
-
/** The paths outside locale routing: the liveness route and the framework's own assets. */
|
|
7
|
-
const UNNEGOTIATED_ROOTS: ReadonlySet<string> = new Set(["health", "_next", "_vercel"])
|
|
8
|
-
const negotiate = createMiddleware(routing)
|
|
9
|
-
|
|
10
|
-
const firstSegment = (pathname: string): string => pathname.split("/")[1] ?? ""
|
|
11
|
-
|
|
12
|
-
const isUnnegotiated = (pathname: string): boolean => {
|
|
13
|
-
const last = pathname.split("/").at(-1) ?? ""
|
|
14
|
-
return UNNEGOTIATED_ROOTS.has(firstSegment(pathname)) || (last.includes(".") && !last.endsWith("."))
|
|
15
|
-
}
|
|
1
|
+
import { createProxy } from "@starci/next-i18n/proxy"
|
|
2
|
+
import type { LocaleProxy } from "@starci/next-i18n/proxy"
|
|
3
|
+
import { i18n } from "@/modules/i18n"
|
|
16
4
|
|
|
17
5
|
/**
|
|
18
6
|
* Locale routing only. The session lives in the browser (modules/auth) and the back end verifies the access token of
|
|
19
7
|
* every call, so the proxy neither reads nor refreshes it.
|
|
20
8
|
*/
|
|
21
|
-
export const proxy = (
|
|
22
|
-
isUnnegotiated(request.nextUrl.pathname) ? NextResponse.next() : negotiate(request)
|
|
9
|
+
export const proxy: LocaleProxy = createProxy(i18n)
|
|
23
10
|
|
|
24
11
|
/** Routes that pass through locale negotiation. */
|
|
25
12
|
export const config = {
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// Managed by starci app sync (slot be.identity.sign-in); do not edit: run "npx starci app sync --execute".
|
|
2
|
-
import { Column, Entity, PrimaryColumn } from "typeorm"
|
|
3
|
-
|
|
4
|
-
@Entity({ schema: "auth", name: "identities" })
|
|
5
|
-
/** One external identity of an account (a Google subject), mapped from auth.identities of db/bootstrap.sql. */
|
|
6
|
-
export class AuthIdentityEntity {
|
|
7
|
-
/** The row id. */
|
|
8
|
-
@PrimaryColumn({ name: "id", type: "uuid" })
|
|
9
|
-
id!: string
|
|
10
|
-
|
|
11
|
-
/** The provider's id of the person (the Google subject). */
|
|
12
|
-
@Column({ name: "provider_id", type: "text" })
|
|
13
|
-
providerId!: string
|
|
14
|
-
|
|
15
|
-
/** The account the identity belongs to. */
|
|
16
|
-
@Column({ name: "user_id", type: "uuid" })
|
|
17
|
-
userId!: string
|
|
18
|
-
|
|
19
|
-
/** What the provider told us, as JSON. */
|
|
20
|
-
@Column({ name: "identity_data", type: "jsonb" })
|
|
21
|
-
identityData!: Record<string, string | boolean | null>
|
|
22
|
-
|
|
23
|
-
/** The provider name. */
|
|
24
|
-
@Column({ name: "provider", type: "text" })
|
|
25
|
-
provider!: string
|
|
26
|
-
|
|
27
|
-
/** The last sign-in through this identity, or null. */
|
|
28
|
-
@Column({ name: "last_sign_in_at", type: "timestamptz", nullable: true })
|
|
29
|
-
lastSignInAt!: Date | null
|
|
30
|
-
|
|
31
|
-
/** When the identity was linked. */
|
|
32
|
-
@Column({ name: "created_at", type: "timestamptz", nullable: true })
|
|
33
|
-
createdAt!: Date | null
|
|
34
|
-
|
|
35
|
-
/** When the identity last changed. */
|
|
36
|
-
@Column({ name: "updated_at", type: "timestamptz", nullable: true })
|
|
37
|
-
updatedAt!: Date | null
|
|
38
|
-
}
|
package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// Managed by starci app sync (slot be.identity.sign-in); do not edit: run "npx starci app sync --execute".
|
|
2
|
-
import { Column, Entity, PrimaryColumn } from "typeorm"
|
|
3
|
-
|
|
4
|
-
@Entity({ schema: "auth", name: "users" })
|
|
5
|
-
/** One person who can sign in, mapped from auth.users of db/bootstrap.sql (the GoTrue column set; only the columns sign-in reads or writes). */
|
|
6
|
-
export class AuthUserEntity {
|
|
7
|
-
/** The account id. */
|
|
8
|
-
@PrimaryColumn({ name: "id", type: "uuid" })
|
|
9
|
-
id!: string
|
|
10
|
-
|
|
11
|
-
/** The audience the row carries. */
|
|
12
|
-
@Column({ name: "aud", type: "varchar", length: 255, nullable: true })
|
|
13
|
-
aud!: string | null
|
|
14
|
-
|
|
15
|
-
/** The database role the row carries. */
|
|
16
|
-
@Column({ name: "role", type: "varchar", length: 255, nullable: true })
|
|
17
|
-
role!: string | null
|
|
18
|
-
|
|
19
|
-
/** The sign-in address, lower-cased. */
|
|
20
|
-
@Column({ name: "email", type: "varchar", length: 255, nullable: true })
|
|
21
|
-
email!: string | null
|
|
22
|
-
|
|
23
|
-
/** The bcrypt hash of the password, or null for an account that signs in with Google only. */
|
|
24
|
-
@Column({ name: "encrypted_password", type: "varchar", length: 255, nullable: true })
|
|
25
|
-
encryptedPassword!: string | null
|
|
26
|
-
|
|
27
|
-
/** When the mailbox was confirmed, or null. */
|
|
28
|
-
@Column({ name: "email_confirmed_at", type: "timestamptz", nullable: true })
|
|
29
|
-
emailConfirmedAt!: Date | null
|
|
30
|
-
|
|
31
|
-
/** The last sign-in, or null. */
|
|
32
|
-
@Column({ name: "last_sign_in_at", type: "timestamptz", nullable: true })
|
|
33
|
-
lastSignInAt!: Date | null
|
|
34
|
-
|
|
35
|
-
/** Until when the account is banned, or null. */
|
|
36
|
-
@Column({ name: "banned_until", type: "timestamptz", nullable: true })
|
|
37
|
-
bannedUntil!: Date | null
|
|
38
|
-
|
|
39
|
-
/** When the account was deleted, or null. */
|
|
40
|
-
@Column({ name: "deleted_at", type: "timestamptz", nullable: true })
|
|
41
|
-
deletedAt!: Date | null
|
|
42
|
-
|
|
43
|
-
/** True for an anonymous account, which never signs in with an address. */
|
|
44
|
-
@Column({ name: "is_anonymous", type: "boolean" })
|
|
45
|
-
isAnonymous!: boolean
|
|
46
|
-
|
|
47
|
-
/** When the account was created. */
|
|
48
|
-
@Column({ name: "created_at", type: "timestamptz", nullable: true })
|
|
49
|
-
createdAt!: Date | null
|
|
50
|
-
|
|
51
|
-
/** When the account last changed. */
|
|
52
|
-
@Column({ name: "updated_at", type: "timestamptz", nullable: true })
|
|
53
|
-
updatedAt!: Date | null
|
|
54
|
-
}
|