@open-mercato/core 0.6.6-develop.6377.1.d26fed7324 → 0.6.6-develop.6381.1.042df302c3

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.
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { locales } from "@open-mercato/shared/lib/i18n/config";
4
+ import { resolveForcedLocale } from "@open-mercato/shared/lib/i18n/locale";
4
5
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
5
6
  import { sanitizeRedirectPath } from "@open-mercato/core/modules/auth/lib/safeRedirect";
6
7
  import { getAppBaseUrl } from "@open-mercato/shared/lib/url";
@@ -17,6 +18,9 @@ const metadata = {
17
18
  };
18
19
  async function POST(req) {
19
20
  const { t } = await resolveTranslations();
21
+ if (resolveForcedLocale(process.env)) {
22
+ return NextResponse.json({ error: t("api.errors.localeForced", "Locale is fixed by configuration") }, { status: 409 });
23
+ }
20
24
  try {
21
25
  const { locale } = await req.json();
22
26
  if (typeof locale !== "string" || !supportedLocales.has(locale)) {
@@ -31,6 +35,9 @@ async function POST(req) {
31
35
  }
32
36
  async function GET(req) {
33
37
  const { t } = await resolveTranslations();
38
+ if (resolveForcedLocale(process.env)) {
39
+ return NextResponse.json({ error: t("api.errors.localeForced", "Locale is fixed by configuration") }, { status: 409 });
40
+ }
34
41
  const url = new URL(req.url);
35
42
  const locale = url.searchParams.get("locale");
36
43
  if (!locale || !supportedLocales.has(locale)) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/auth/api/locale/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { locales, type Locale } from '@open-mercato/shared/lib/i18n/config'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { sanitizeRedirectPath } from '@open-mercato/core/modules/auth/lib/safeRedirect'\nimport { getAppBaseUrl } from '@open-mercato/shared/lib/url'\n\nconst supportedLocales = new Set<Locale>(locales)\nconst localeSchema = z.object({ locale: z.enum(locales as [Locale, ...Locale[]]) })\nconst localeQuerySchema = localeSchema.extend({\n redirect: z.string().optional(),\n})\nconst localeResponseSchema = z.object({ ok: z.boolean() })\nconst localeErrorSchema = z.object({ error: z.string() })\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n try {\n const { locale } = await req.json()\n if (typeof locale !== 'string' || !supportedLocales.has(locale as Locale)) {\n return NextResponse.json({ error: t('api.errors.invalidLocale', 'Invalid locale') }, { status: 400 })\n }\n const res = NextResponse.json({ ok: true })\n res.cookies.set('locale', locale as Locale, { path: '/', maxAge: 60 * 60 * 24 * 365 })\n return res\n } catch {\n return NextResponse.json({ error: t('api.errors.badRequest', 'Bad request') }, { status: 400 })\n }\n}\n\nexport async function GET(req: Request) {\n const { t } = await resolveTranslations()\n const url = new URL(req.url)\n const locale = url.searchParams.get('locale')\n if (!locale || !supportedLocales.has(locale as Locale)) {\n return NextResponse.json({ error: t('api.errors.invalidLocale', 'Invalid locale') }, { status: 400 })\n }\n const baseUrl = getAppBaseUrl(req)\n const safePath = sanitizeRedirectPath(url.searchParams.get('redirect'), baseUrl, '/')\n const res = NextResponse.redirect(new URL(safePath, url.origin))\n res.cookies.set('locale', locale as Locale, { path: '/', maxAge: 60 * 60 * 24 * 365 })\n return res\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Locale preference',\n methods: {\n GET: {\n summary: 'Set locale and redirect',\n description: 'Stores the selected locale in a cookie and redirects to a safe local path.',\n query: localeQuerySchema,\n responses: [\n { status: 302, description: 'Locale cookie set and request redirected' },\n { status: 400, description: 'Invalid locale', schema: localeErrorSchema },\n ],\n },\n POST: {\n summary: 'Set locale',\n description: 'Stores the selected locale in a cookie and returns a JSON success response.',\n requestBody: {\n contentType: 'application/json',\n schema: localeSchema,\n },\n responses: [\n { status: 200, description: 'Locale cookie set', schema: localeResponseSchema },\n { status: 400, description: 'Invalid locale or malformed request body', schema: localeErrorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,eAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AACrC,SAAS,qBAAqB;AAE9B,MAAM,mBAAmB,IAAI,IAAY,OAAO;AAChD,MAAM,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,OAAgC,EAAE,CAAC;AAClF,MAAM,oBAAoB,aAAa,OAAO;AAAA,EAC5C,UAAU,EAAE,OAAO,EAAE,SAAS;AAChC,CAAC;AACD,MAAM,uBAAuB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACzD,MAAM,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAEjD,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM;AAAA,EAC1B,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,IAAI,KAAK;AAClC,QAAI,OAAO,WAAW,YAAY,CAAC,iBAAiB,IAAI,MAAgB,GAAG;AACzE,aAAO,aAAa,KAAK,EAAE,OAAO,EAAE,4BAA4B,gBAAgB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACtG;AACA,UAAM,MAAM,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAC1C,QAAI,QAAQ,IAAI,UAAU,QAAkB,EAAE,MAAM,KAAK,QAAQ,KAAK,KAAK,KAAK,IAAI,CAAC;AACrF,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,OAAO,EAAE,yBAAyB,aAAa,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAChG;AACF;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,SAAS,IAAI,aAAa,IAAI,QAAQ;AAC5C,MAAI,CAAC,UAAU,CAAC,iBAAiB,IAAI,MAAgB,GAAG;AACtD,WAAO,aAAa,KAAK,EAAE,OAAO,EAAE,4BAA4B,gBAAgB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACtG;AACA,QAAM,UAAU,cAAc,GAAG;AACjC,QAAM,WAAW,qBAAqB,IAAI,aAAa,IAAI,UAAU,GAAG,SAAS,GAAG;AACpF,QAAM,MAAM,aAAa,SAAS,IAAI,IAAI,UAAU,IAAI,MAAM,CAAC;AAC/D,MAAI,QAAQ,IAAI,UAAU,QAAkB,EAAE,MAAM,KAAK,QAAQ,KAAK,KAAK,KAAK,IAAI,CAAC;AACrF,SAAO;AACT;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,OAAO;AAAA,MACP,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,2CAA2C;AAAA,QACvE,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,kBAAkB;AAAA,MAC1E;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,qBAAqB;AAAA,QAC9E,EAAE,QAAQ,KAAK,aAAa,4CAA4C,QAAQ,kBAAkB;AAAA,MACpG;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { locales, type Locale } from '@open-mercato/shared/lib/i18n/config'\nimport { resolveForcedLocale } from '@open-mercato/shared/lib/i18n/locale'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { sanitizeRedirectPath } from '@open-mercato/core/modules/auth/lib/safeRedirect'\nimport { getAppBaseUrl } from '@open-mercato/shared/lib/url'\n\nconst supportedLocales = new Set<Locale>(locales)\nconst localeSchema = z.object({ locale: z.enum(locales as [Locale, ...Locale[]]) })\nconst localeQuerySchema = localeSchema.extend({\n redirect: z.string().optional(),\n})\nconst localeResponseSchema = z.object({ ok: z.boolean() })\nconst localeErrorSchema = z.object({ error: z.string() })\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n if (resolveForcedLocale(process.env)) {\n return NextResponse.json({ error: t('api.errors.localeForced', 'Locale is fixed by configuration') }, { status: 409 })\n }\n try {\n const { locale } = await req.json()\n if (typeof locale !== 'string' || !supportedLocales.has(locale as Locale)) {\n return NextResponse.json({ error: t('api.errors.invalidLocale', 'Invalid locale') }, { status: 400 })\n }\n const res = NextResponse.json({ ok: true })\n res.cookies.set('locale', locale as Locale, { path: '/', maxAge: 60 * 60 * 24 * 365 })\n return res\n } catch {\n return NextResponse.json({ error: t('api.errors.badRequest', 'Bad request') }, { status: 400 })\n }\n}\n\nexport async function GET(req: Request) {\n const { t } = await resolveTranslations()\n if (resolveForcedLocale(process.env)) {\n return NextResponse.json({ error: t('api.errors.localeForced', 'Locale is fixed by configuration') }, { status: 409 })\n }\n const url = new URL(req.url)\n const locale = url.searchParams.get('locale')\n if (!locale || !supportedLocales.has(locale as Locale)) {\n return NextResponse.json({ error: t('api.errors.invalidLocale', 'Invalid locale') }, { status: 400 })\n }\n const baseUrl = getAppBaseUrl(req)\n const safePath = sanitizeRedirectPath(url.searchParams.get('redirect'), baseUrl, '/')\n const res = NextResponse.redirect(new URL(safePath, url.origin))\n res.cookies.set('locale', locale as Locale, { path: '/', maxAge: 60 * 60 * 24 * 365 })\n return res\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Locale preference',\n methods: {\n GET: {\n summary: 'Set locale and redirect',\n description: 'Stores the selected locale in a cookie and redirects to a safe local path.',\n query: localeQuerySchema,\n responses: [\n { status: 302, description: 'Locale cookie set and request redirected' },\n { status: 400, description: 'Invalid locale', schema: localeErrorSchema },\n ],\n },\n POST: {\n summary: 'Set locale',\n description: 'Stores the selected locale in a cookie and returns a JSON success response.',\n requestBody: {\n contentType: 'application/json',\n schema: localeSchema,\n },\n responses: [\n { status: 200, description: 'Locale cookie set', schema: localeResponseSchema },\n { status: 400, description: 'Invalid locale or malformed request body', schema: localeErrorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
8
8
  import { useRouter } from "next/navigation";
9
9
  import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
10
10
  import { useT } from "@open-mercato/shared/lib/i18n/context";
11
- import { formatPasswordRequirements, getPasswordPolicy } from "@open-mercato/shared/lib/auth/passwordPolicy";
11
+ import { formatPasswordRequirements, getPasswordPolicy, validatePassword } from "@open-mercato/shared/lib/auth/passwordPolicy";
12
12
  function ResetWithTokenPage({ params }) {
13
13
  const router = useRouter();
14
14
  const t = useT();
@@ -39,6 +39,10 @@ function ResetWithTokenPage({ params }) {
39
39
  setError(t("auth.profile.form.errors.passwordMismatch", "Passwords do not match."));
40
40
  return;
41
41
  }
42
+ if (!validatePassword(password, passwordPolicy).ok) {
43
+ setError(t("auth.profile.form.errors.passwordRequirements", "Password must meet the requirements."));
44
+ return;
45
+ }
42
46
  setSubmitting(true);
43
47
  try {
44
48
  form.set("token", params.token);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/auth/frontend/reset/%5Btoken%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useEffect, useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\n\nexport default function ResetWithTokenPage({ params }: { params: { token: string } }) {\n const router = useRouter()\n const t = useT()\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [clientReady, setClientReady] = useState(false)\n const passwordPolicy = getPasswordPolicy()\n const passwordRequirements = formatPasswordRequirements(passwordPolicy, t)\n const passwordDescription = passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : ''\n\n useEffect(() => {\n setClientReady(true)\n }, [])\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n const form = new FormData(e.currentTarget)\n const password = String(form.get('password') ?? '')\n const confirmPassword = String(form.get('confirmPassword') ?? '')\n\n if (!password) {\n setError(t('auth.profile.form.errors.newPasswordRequired', 'New password is required.'))\n return\n }\n if (!confirmPassword) {\n setError(t('auth.profile.form.errors.confirmPasswordRequired', 'Please confirm the new password.'))\n return\n }\n if (password !== confirmPassword) {\n setError(t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'))\n return\n }\n\n setSubmitting(true)\n try {\n form.set('token', params.token)\n const { ok, result } = await apiCall<{ ok?: boolean; error?: string; redirect?: string }>(\n '/api/auth/reset/confirm',\n { method: 'POST', body: form },\n )\n if (!ok || result?.ok === false) {\n setError(result?.error || t('auth.reset.errors.failed', 'Unable to reset password'))\n return\n }\n router.replace(result?.redirect || '/login')\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.reset.title', 'Set a new password')}</CardTitle>\n <CardDescription>{t('auth.reset.subtitle', 'Choose a strong password for your account.')}</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"grid gap-3\" onSubmit={onSubmit} data-auth-ready={clientReady ? '1' : '0'}>\n {error && <div className=\"text-sm text-status-error-text\">{error}</div>}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.reset.form.password', 'New password')}</Label>\n <PasswordInput id=\"password\" name=\"password\" required minLength={passwordPolicy.minLength} autoComplete=\"new-password\" />\n {passwordDescription ? (\n <p className=\"text-xs text-muted-foreground\">{passwordDescription}</p>\n ) : null}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"confirmPassword\">{t('auth.profile.form.confirmPassword', 'Confirm new password')}</Label>\n <PasswordInput\n id=\"confirmPassword\"\n name=\"confirmPassword\"\n required\n minLength={passwordPolicy.minLength}\n autoComplete=\"new-password\"\n />\n </div>\n <Button type=\"submit\" className=\"mt-2 w-full\" disabled={submitting}>\n {submitting ? t('auth.reset.form.loading', '...') : t('auth.reset.form.submit', 'Update password')}\n </Button>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
- "mappings": ";AAmEQ,SACE,KADF;AAlER,SAAS,MAAM,aAAa,YAAY,WAAW,uBAAuB;AAC1E,SAAS,cAAc;AACvB,SAAS,qBAAqB;AAC9B,SAAS,aAAa;AACtB,SAAS,WAAW,gBAAgB;AACpC,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,4BAA4B,yBAAyB;AAE/C,SAAR,mBAAoC,EAAE,OAAO,GAAkC;AACpF,QAAM,SAAS,UAAU;AACzB,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,iBAAiB,kBAAkB;AACzC,QAAM,uBAAuB,2BAA2B,gBAAgB,CAAC;AACzE,QAAM,sBAAsB,uBACxB,EAAE,mCAAmC,yCAAyC,EAAE,cAAc,qBAAqB,CAAC,IACpH;AAEJ,YAAU,MAAM;AACd,mBAAe,IAAI;AAAA,EACrB,GAAG,CAAC,CAAC;AAEL,iBAAe,SAAS,GAAqC;AAC3D,MAAE,eAAe;AACjB,aAAS,IAAI;AACb,UAAM,OAAO,IAAI,SAAS,EAAE,aAAa;AACzC,UAAM,WAAW,OAAO,KAAK,IAAI,UAAU,KAAK,EAAE;AAClD,UAAM,kBAAkB,OAAO,KAAK,IAAI,iBAAiB,KAAK,EAAE;AAEhE,QAAI,CAAC,UAAU;AACb,eAAS,EAAE,gDAAgD,2BAA2B,CAAC;AACvF;AAAA,IACF;AACA,QAAI,CAAC,iBAAiB;AACpB,eAAS,EAAE,oDAAoD,kCAAkC,CAAC;AAClG;AAAA,IACF;AACA,QAAI,aAAa,iBAAiB;AAChC,eAAS,EAAE,6CAA6C,yBAAyB,CAAC;AAClF;AAAA,IACF;AAEA,kBAAc,IAAI;AAClB,QAAI;AACF,WAAK,IAAI,SAAS,OAAO,KAAK;AAC9B,YAAM,EAAE,IAAI,OAAO,IAAI,MAAM;AAAA,QAC3B;AAAA,QACA,EAAE,QAAQ,QAAQ,MAAM,KAAK;AAAA,MAC/B;AACA,UAAI,CAAC,MAAM,QAAQ,OAAO,OAAO;AAC/B,iBAAS,QAAQ,SAAS,EAAE,4BAA4B,0BAA0B,CAAC;AACnF;AAAA,MACF;AACA,aAAO,QAAQ,QAAQ,YAAY,QAAQ;AAAA,IAC7C,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SACE,oBAAC,SAAI,WAAU,kDACb,+BAAC,QAAK,WAAU,mBACd;AAAA,yBAAC,cACC;AAAA,0BAAC,aAAW,YAAE,oBAAoB,oBAAoB,GAAE;AAAA,MACxD,oBAAC,mBAAiB,YAAE,uBAAuB,4CAA4C,GAAE;AAAA,OAC3F;AAAA,IACA,oBAAC,eACC,+BAAC,UAAK,WAAU,cAAa,UAAoB,mBAAiB,cAAc,MAAM,KACnF;AAAA,eAAS,oBAAC,SAAI,WAAU,kCAAkC,iBAAM;AAAA,MACjE,qBAAC,SAAI,WAAU,cACb;AAAA,4BAAC,SAAM,SAAQ,YAAY,YAAE,4BAA4B,cAAc,GAAE;AAAA,QACzE,oBAAC,iBAAc,IAAG,YAAW,MAAK,YAAW,UAAQ,MAAC,WAAW,eAAe,WAAW,cAAa,gBAAe;AAAA,QACtH,sBACC,oBAAC,OAAE,WAAU,iCAAiC,+BAAoB,IAChE;AAAA,SACN;AAAA,MACA,qBAAC,SAAI,WAAU,cACb;AAAA,4BAAC,SAAM,SAAQ,mBAAmB,YAAE,qCAAqC,sBAAsB,GAAE;AAAA,QACjG;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,UAAQ;AAAA,YACR,WAAW,eAAe;AAAA,YAC1B,cAAa;AAAA;AAAA,QACf;AAAA,SACF;AAAA,MACA,oBAAC,UAAO,MAAK,UAAS,WAAU,eAAc,UAAU,YACrD,uBAAa,EAAE,2BAA2B,KAAK,IAAI,EAAE,0BAA0B,iBAAiB,GACnG;AAAA,OACF,GACF;AAAA,KACF,GACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useEffect, useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatPasswordRequirements, getPasswordPolicy, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'\n\nexport default function ResetWithTokenPage({ params }: { params: { token: string } }) {\n const router = useRouter()\n const t = useT()\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [clientReady, setClientReady] = useState(false)\n const passwordPolicy = getPasswordPolicy()\n const passwordRequirements = formatPasswordRequirements(passwordPolicy, t)\n const passwordDescription = passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : ''\n\n useEffect(() => {\n setClientReady(true)\n }, [])\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n const form = new FormData(e.currentTarget)\n const password = String(form.get('password') ?? '')\n const confirmPassword = String(form.get('confirmPassword') ?? '')\n\n if (!password) {\n setError(t('auth.profile.form.errors.newPasswordRequired', 'New password is required.'))\n return\n }\n if (!confirmPassword) {\n setError(t('auth.profile.form.errors.confirmPasswordRequired', 'Please confirm the new password.'))\n return\n }\n if (password !== confirmPassword) {\n setError(t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'))\n return\n }\n if (!validatePassword(password, passwordPolicy).ok) {\n setError(t('auth.profile.form.errors.passwordRequirements', 'Password must meet the requirements.'))\n return\n }\n\n setSubmitting(true)\n try {\n form.set('token', params.token)\n const { ok, result } = await apiCall<{ ok?: boolean; error?: string; redirect?: string }>(\n '/api/auth/reset/confirm',\n { method: 'POST', body: form },\n )\n if (!ok || result?.ok === false) {\n setError(result?.error || t('auth.reset.errors.failed', 'Unable to reset password'))\n return\n }\n router.replace(result?.redirect || '/login')\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.reset.title', 'Set a new password')}</CardTitle>\n <CardDescription>{t('auth.reset.subtitle', 'Choose a strong password for your account.')}</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"grid gap-3\" onSubmit={onSubmit} data-auth-ready={clientReady ? '1' : '0'}>\n {error && <div className=\"text-sm text-status-error-text\">{error}</div>}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.reset.form.password', 'New password')}</Label>\n <PasswordInput id=\"password\" name=\"password\" required minLength={passwordPolicy.minLength} autoComplete=\"new-password\" />\n {passwordDescription ? (\n <p className=\"text-xs text-muted-foreground\">{passwordDescription}</p>\n ) : null}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"confirmPassword\">{t('auth.profile.form.confirmPassword', 'Confirm new password')}</Label>\n <PasswordInput\n id=\"confirmPassword\"\n name=\"confirmPassword\"\n required\n minLength={passwordPolicy.minLength}\n autoComplete=\"new-password\"\n />\n </div>\n <Button type=\"submit\" className=\"mt-2 w-full\" disabled={submitting}>\n {submitting ? t('auth.reset.form.loading', '...') : t('auth.reset.form.submit', 'Update password')}\n </Button>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6377.1.d26fed7324",
3
+ "version": "0.6.6-develop.6381.1.042df302c3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -246,16 +246,16 @@
246
246
  "zod": "^4.4.3"
247
247
  },
248
248
  "peerDependencies": {
249
- "@open-mercato/ai-assistant": "0.6.6-develop.6377.1.d26fed7324",
250
- "@open-mercato/shared": "0.6.6-develop.6377.1.d26fed7324",
251
- "@open-mercato/ui": "0.6.6-develop.6377.1.d26fed7324",
249
+ "@open-mercato/ai-assistant": "0.6.6-develop.6381.1.042df302c3",
250
+ "@open-mercato/shared": "0.6.6-develop.6381.1.042df302c3",
251
+ "@open-mercato/ui": "0.6.6-develop.6381.1.042df302c3",
252
252
  "react": "^19.0.0",
253
253
  "react-dom": "^19.0.0"
254
254
  },
255
255
  "devDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6377.1.d26fed7324",
257
- "@open-mercato/shared": "0.6.6-develop.6377.1.d26fed7324",
258
- "@open-mercato/ui": "0.6.6-develop.6377.1.d26fed7324",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6381.1.042df302c3",
257
+ "@open-mercato/shared": "0.6.6-develop.6381.1.042df302c3",
258
+ "@open-mercato/ui": "0.6.6-develop.6381.1.042df302c3",
259
259
  "@testing-library/dom": "^10.4.1",
260
260
  "@testing-library/jest-dom": "^6.9.1",
261
261
  "@testing-library/react": "^16.3.1",
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
4
4
  import { locales, type Locale } from '@open-mercato/shared/lib/i18n/config'
5
+ import { resolveForcedLocale } from '@open-mercato/shared/lib/i18n/locale'
5
6
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
6
7
  import { sanitizeRedirectPath } from '@open-mercato/core/modules/auth/lib/safeRedirect'
7
8
  import { getAppBaseUrl } from '@open-mercato/shared/lib/url'
@@ -21,6 +22,9 @@ export const metadata = {
21
22
 
22
23
  export async function POST(req: Request) {
23
24
  const { t } = await resolveTranslations()
25
+ if (resolveForcedLocale(process.env)) {
26
+ return NextResponse.json({ error: t('api.errors.localeForced', 'Locale is fixed by configuration') }, { status: 409 })
27
+ }
24
28
  try {
25
29
  const { locale } = await req.json()
26
30
  if (typeof locale !== 'string' || !supportedLocales.has(locale as Locale)) {
@@ -36,6 +40,9 @@ export async function POST(req: Request) {
36
40
 
37
41
  export async function GET(req: Request) {
38
42
  const { t } = await resolveTranslations()
43
+ if (resolveForcedLocale(process.env)) {
44
+ return NextResponse.json({ error: t('api.errors.localeForced', 'Locale is fixed by configuration') }, { status: 409 })
45
+ }
39
46
  const url = new URL(req.url)
40
47
  const locale = url.searchParams.get('locale')
41
48
  if (!locale || !supportedLocales.has(locale as Locale)) {
@@ -7,7 +7,7 @@ import { useEffect, useState } from 'react'
7
7
  import { useRouter } from 'next/navigation'
8
8
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
9
9
  import { useT } from '@open-mercato/shared/lib/i18n/context'
10
- import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
10
+ import { formatPasswordRequirements, getPasswordPolicy, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'
11
11
 
12
12
  export default function ResetWithTokenPage({ params }: { params: { token: string } }) {
13
13
  const router = useRouter()
@@ -44,6 +44,10 @@ export default function ResetWithTokenPage({ params }: { params: { token: string
44
44
  setError(t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'))
45
45
  return
46
46
  }
47
+ if (!validatePassword(password, passwordPolicy).ok) {
48
+ setError(t('auth.profile.form.errors.passwordRequirements', 'Password must meet the requirements.'))
49
+ return
50
+ }
47
51
 
48
52
  setSubmitting(true)
49
53
  try {