@open-mercato/core 0.5.1-develop.2876.8c589daa8f → 0.5.1-develop.2907.745250bbb5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js +13 -2
- package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js.map +2 -2
- package/dist/modules/customer_accounts/api/password/reset-confirm.js +18 -6
- package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/password-change.js +14 -2
- package/dist/modules/customer_accounts/api/portal/password-change.js.map +2 -2
- package/dist/modules/customer_accounts/events.js +1 -0
- package/dist/modules/customer_accounts/events.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerSessionService.js +4 -3
- package/dist/modules/customer_accounts/services/customerSessionService.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerUserService.js +2 -2
- package/dist/modules/customer_accounts/services/customerUserService.js.map +2 -2
- package/package.json +3 -3
- package/src/modules/customer_accounts/api/admin/users/[id]/reset-password.ts +15 -2
- package/src/modules/customer_accounts/api/password/reset-confirm.ts +19 -7
- package/src/modules/customer_accounts/api/portal/password-change.ts +15 -3
- package/src/modules/customer_accounts/events.ts +1 -0
- package/src/modules/customer_accounts/services/customerSessionService.ts +4 -3
- package/src/modules/customer_accounts/services/customerUserService.ts +2 -2
|
@@ -28,12 +28,15 @@ async function POST(req, { params }) {
|
|
|
28
28
|
}
|
|
29
29
|
const customerUserService = container.resolve("customerUserService");
|
|
30
30
|
const customerSessionService = container.resolve("customerSessionService");
|
|
31
|
+
const em = container.resolve("em");
|
|
31
32
|
const user = await customerUserService.findById(params.id, auth.tenantId);
|
|
32
33
|
if (!user) {
|
|
33
34
|
return NextResponse.json({ ok: false, error: "User not found" }, { status: 404 });
|
|
34
35
|
}
|
|
35
|
-
await
|
|
36
|
-
|
|
36
|
+
await em.transactional(async (trx) => {
|
|
37
|
+
await customerUserService.updatePassword(user, parsed.data.newPassword, trx);
|
|
38
|
+
await customerSessionService.revokeAllUserSessions(user.id, trx);
|
|
39
|
+
});
|
|
37
40
|
void emitCustomerAccountsEvent("customer_accounts.password.reset", {
|
|
38
41
|
id: user.id,
|
|
39
42
|
email: user.email,
|
|
@@ -41,6 +44,14 @@ async function POST(req, { params }) {
|
|
|
41
44
|
organizationId: auth.orgId,
|
|
42
45
|
resetBy: auth.sub
|
|
43
46
|
}).catch(() => void 0);
|
|
47
|
+
void emitCustomerAccountsEvent("customer_accounts.password.changed", {
|
|
48
|
+
userId: user.id,
|
|
49
|
+
tenantId: auth.tenantId,
|
|
50
|
+
organizationId: auth.orgId ?? null,
|
|
51
|
+
changedBy: "admin",
|
|
52
|
+
changedById: auth.sub,
|
|
53
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
54
|
+
}).catch(() => void 0);
|
|
44
55
|
return NextResponse.json({ ok: true });
|
|
45
56
|
}
|
|
46
57
|
const successSchema = z.object({ ok: z.literal(true) });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customer_accounts/api/admin/users/%5Bid%5D/reset-password.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { adminResetPasswordSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\n\nexport const metadata = {}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = adminResetPasswordSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })\n }\n\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n const user = await customerUserService.findById(params.id, auth.tenantId!)\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n await customerUserService.updatePassword(user, parsed.data.newPassword)\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { adminResetPasswordSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\n\nexport const metadata = {}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = adminResetPasswordSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })\n }\n\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n const em = container.resolve('em') as EntityManager\n const user = await customerUserService.findById(params.id, auth.tenantId!)\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n await em.transactional(async (trx) => {\n await customerUserService.updatePassword(user, parsed.data.newPassword, trx)\n await customerSessionService.revokeAllUserSessions(user.id, trx)\n })\n\n void emitCustomerAccountsEvent('customer_accounts.password.reset', {\n id: user.id,\n email: user.email,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n resetBy: auth.sub,\n }).catch(() => undefined)\n\n void emitCustomerAccountsEvent('customer_accounts.password.changed', {\n userId: user.id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n changedBy: 'admin',\n changedById: auth.sub,\n at: new Date().toISOString(),\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Reset customer user password (admin)',\n description: 'Allows staff to set a new password for a customer user.',\n tags: ['Customer Accounts Admin'],\n requestBody: { schema: adminResetPasswordSchema },\n responses: [{ status: 200, description: 'Password reset', schema: successSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Reset customer user password (admin)',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { passwordResetConfirmSchema } from "@open-mercato/core/modules/customer_accounts/data/validators";
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
5
|
import { CustomerUser } from "@open-mercato/core/modules/customer_accounts/data/entities";
|
|
6
|
+
import { emitCustomerAccountsEvent } from "@open-mercato/core/modules/customer_accounts/events";
|
|
6
7
|
const metadata = { requireAuth: false };
|
|
7
8
|
async function POST(req) {
|
|
8
9
|
let body;
|
|
@@ -23,17 +24,28 @@ async function POST(req) {
|
|
|
23
24
|
if (!result) {
|
|
24
25
|
return NextResponse.json({ ok: false, error: "Invalid or expired token" }, { status: 400 });
|
|
25
26
|
}
|
|
26
|
-
await customerUserService.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const user = await customerUserService.findById(result.userId, result.tenantId);
|
|
28
|
+
if (!user) {
|
|
29
|
+
return NextResponse.json({ ok: false, error: "Invalid or expired token" }, { status: 400 });
|
|
30
|
+
}
|
|
30
31
|
const em = container.resolve("em");
|
|
32
|
+
await em.transactional(async (trx) => {
|
|
33
|
+
await customerUserService.updatePassword(user, parsed.data.password, trx);
|
|
34
|
+
await customerSessionService.revokeAllUserSessions(user.id, trx);
|
|
35
|
+
});
|
|
31
36
|
await em.nativeUpdate(
|
|
32
37
|
CustomerUser,
|
|
33
|
-
{ id:
|
|
38
|
+
{ id: user.id, emailVerifiedAt: null },
|
|
34
39
|
{ emailVerifiedAt: /* @__PURE__ */ new Date() }
|
|
35
40
|
);
|
|
36
|
-
|
|
41
|
+
void emitCustomerAccountsEvent("customer_accounts.password.changed", {
|
|
42
|
+
userId: user.id,
|
|
43
|
+
tenantId: user.tenantId,
|
|
44
|
+
organizationId: user.organizationId ?? null,
|
|
45
|
+
changedBy: "reset",
|
|
46
|
+
changedById: null,
|
|
47
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
48
|
+
}).catch(() => void 0);
|
|
37
49
|
return NextResponse.json({ ok: true });
|
|
38
50
|
}
|
|
39
51
|
const successSchema = z.object({ ok: z.literal(true) });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customer_accounts/api/password/reset-confirm.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { passwordResetConfirmSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = passwordResetConfirmSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n\n const result = await customerTokenService.verifyPasswordResetToken(parsed.data.token)\n if (!result) {\n return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })\n }\n\n await customerUserService.
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,kCAAkC;AAC3C,SAAS,8BAA8B;AAIvC,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { passwordResetConfirmSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport type { EntityManager } from '@mikro-orm/postgresql'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = passwordResetConfirmSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n\n const result = await customerTokenService.verifyPasswordResetToken(parsed.data.token)\n if (!result) {\n return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })\n }\n\n const user = await customerUserService.findById(result.userId, result.tenantId)\n if (!user) {\n return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })\n }\n\n const em = container.resolve('em') as EntityManager\n await em.transactional(async (trx) => {\n await customerUserService.updatePassword(user, parsed.data.password, trx)\n await customerSessionService.revokeAllUserSessions(user.id, trx)\n })\n\n await em.nativeUpdate(\n CustomerUser,\n { id: user.id, emailVerifiedAt: null },\n { emailVerifiedAt: new Date() },\n )\n\n void emitCustomerAccountsEvent('customer_accounts.password.changed', {\n userId: user.id,\n tenantId: user.tenantId,\n organizationId: user.organizationId ?? null,\n changedBy: 'reset',\n changedById: null,\n at: new Date().toISOString(),\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Confirm customer password reset',\n description: 'Validates the reset token and sets a new password. Revokes all existing sessions.',\n tags: ['Customer Authentication'],\n requestBody: {\n schema: passwordResetConfirmSchema,\n description: 'Password reset confirmation with token and new password.',\n },\n responses: [\n { status: 200, description: 'Password reset successful', schema: successSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid or expired token', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Confirm customer password reset',\n description: 'Handles password reset confirmation for customer accounts.',\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,kCAAkC;AAC3C,SAAS,8BAA8B;AAIvC,SAAS,oBAAoB;AAC7B,SAAS,iCAAiC;AAGnC,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,eAAsB,KAAK,KAAc;AACvC,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxF;AAEA,QAAM,SAAS,2BAA2B,UAAU,IAAI;AACxD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,uBAAuB,UAAU,QAAQ,sBAAsB;AACrE,QAAM,sBAAsB,UAAU,QAAQ,qBAAqB;AACnE,QAAM,yBAAyB,UAAU,QAAQ,wBAAwB;AAEzE,QAAM,SAAS,MAAM,qBAAqB,yBAAyB,OAAO,KAAK,KAAK;AACpF,MAAI,CAAC,QAAQ;AACX,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5F;AAEA,QAAM,OAAO,MAAM,oBAAoB,SAAS,OAAO,QAAQ,OAAO,QAAQ;AAC9E,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5F;AAEA,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,UAAM,oBAAoB,eAAe,MAAM,OAAO,KAAK,UAAU,GAAG;AACxE,UAAM,uBAAuB,sBAAsB,KAAK,IAAI,GAAG;AAAA,EACjE,CAAC;AAED,QAAM,GAAG;AAAA,IACP;AAAA,IACA,EAAE,IAAI,KAAK,IAAI,iBAAiB,KAAK;AAAA,IACrC,EAAE,iBAAiB,oBAAI,KAAK,EAAE;AAAA,EAChC;AAEA,OAAK,0BAA0B,sCAAsC;AAAA,IACnE,QAAQ,KAAK;AAAA,IACb,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK,kBAAkB;AAAA,IACvC,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,EAC7B,CAAC,EAAE,MAAM,MAAM,MAAS;AAExB,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;AACtD,MAAM,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC;AAExE,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,yBAAyB;AAAA,EAChC,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,6BAA6B,QAAQ,cAAc;AAAA,EACjF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,4BAA4B,QAAQ,YAAY;AAAA,EAC9E;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS,EAAE,MAAM,UAAU;AAC7B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { getCustomerAuthFromRequest } from "@open-mercato/core/modules/customer_accounts/lib/customerAuth";
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
5
|
import { passwordChangeSchema } from "@open-mercato/core/modules/customer_accounts/data/validators";
|
|
6
|
+
import { emitCustomerAccountsEvent } from "@open-mercato/core/modules/customer_accounts/events";
|
|
6
7
|
const metadata = { requireAuth: false };
|
|
7
8
|
async function POST(req) {
|
|
8
9
|
const auth = await getCustomerAuthFromRequest(req);
|
|
@@ -22,6 +23,7 @@ async function POST(req) {
|
|
|
22
23
|
const container = await createRequestContainer();
|
|
23
24
|
const customerUserService = container.resolve("customerUserService");
|
|
24
25
|
const customerSessionService = container.resolve("customerSessionService");
|
|
26
|
+
const em = container.resolve("em");
|
|
25
27
|
const user = await customerUserService.findById(auth.sub, auth.tenantId);
|
|
26
28
|
if (!user) {
|
|
27
29
|
return NextResponse.json({ ok: false, error: "User not found" }, { status: 404 });
|
|
@@ -30,8 +32,18 @@ async function POST(req) {
|
|
|
30
32
|
if (!currentValid) {
|
|
31
33
|
return NextResponse.json({ ok: false, error: "Current password is incorrect" }, { status: 400 });
|
|
32
34
|
}
|
|
33
|
-
await
|
|
34
|
-
|
|
35
|
+
await em.transactional(async (trx) => {
|
|
36
|
+
await customerUserService.updatePassword(user, parsed.data.newPassword, trx);
|
|
37
|
+
await customerSessionService.revokeAllUserSessions(user.id, trx);
|
|
38
|
+
});
|
|
39
|
+
void emitCustomerAccountsEvent("customer_accounts.password.changed", {
|
|
40
|
+
userId: user.id,
|
|
41
|
+
tenantId: auth.tenantId,
|
|
42
|
+
organizationId: auth.orgId ?? null,
|
|
43
|
+
changedBy: "self",
|
|
44
|
+
changedById: null,
|
|
45
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
46
|
+
}).catch(() => void 0);
|
|
35
47
|
return NextResponse.json({ ok: true });
|
|
36
48
|
}
|
|
37
49
|
const successSchema = z.object({ ok: z.literal(true) });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customer_accounts/api/portal/password-change.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { passwordChangeSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = passwordChangeSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n\n const user = await customerUserService.findById(auth.sub, auth.tenantId)\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n const currentValid = await customerUserService.verifyPassword(user, parsed.data.currentPassword)\n if (!currentValid) {\n return NextResponse.json({ ok: false, error: 'Current password is incorrect' }, { status: 400 })\n }\n\n await customerUserService.updatePassword(user, parsed.data.newPassword)\n\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { passwordChangeSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = passwordChangeSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n const em = container.resolve('em') as EntityManager\n\n const user = await customerUserService.findById(auth.sub, auth.tenantId)\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n const currentValid = await customerUserService.verifyPassword(user, parsed.data.currentPassword)\n if (!currentValid) {\n return NextResponse.json({ ok: false, error: 'Current password is incorrect' }, { status: 400 })\n }\n\n await em.transactional(async (trx) => {\n await customerUserService.updatePassword(user, parsed.data.newPassword, trx)\n await customerSessionService.revokeAllUserSessions(user.id, trx)\n })\n\n void emitCustomerAccountsEvent('customer_accounts.password.changed', {\n userId: user.id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n changedBy: 'self',\n changedById: null,\n at: new Date().toISOString(),\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Change customer password',\n description: 'Changes the authenticated customer user password after verifying the current password. Revokes all existing sessions.',\n tags: ['Customer Portal'],\n requestBody: { schema: passwordChangeSchema },\n responses: [{ status: 200, description: 'Password changed', schema: successSchema }],\n errors: [\n { status: 400, description: 'Current password incorrect or validation failed', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Change customer password',\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAGlB,SAAS,kCAAkC;AAC3C,SAAS,8BAA8B;AAGvC,SAAS,4BAA4B;AACrC,SAAS,iCAAiC;AAEnC,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,2BAA2B,GAAG;AACjD,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3F;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxF;AAEA,QAAM,SAAS,qBAAqB,UAAU,IAAI;AAClD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,sBAAsB,UAAU,QAAQ,qBAAqB;AACnE,QAAM,yBAAyB,UAAU,QAAQ,wBAAwB;AACzE,QAAM,KAAK,UAAU,QAAQ,IAAI;AAEjC,QAAM,OAAO,MAAM,oBAAoB,SAAS,KAAK,KAAK,KAAK,QAAQ;AACvE,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClF;AAEA,QAAM,eAAe,MAAM,oBAAoB,eAAe,MAAM,OAAO,KAAK,eAAe;AAC/F,MAAI,CAAC,cAAc;AACjB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,gCAAgC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACjG;AAEA,QAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,UAAM,oBAAoB,eAAe,MAAM,OAAO,KAAK,aAAa,GAAG;AAC3E,UAAM,uBAAuB,sBAAsB,KAAK,IAAI,GAAG;AAAA,EACjE,CAAC;AAED,OAAK,0BAA0B,sCAAsC;AAAA,IACnE,QAAQ,KAAK;AAAA,IACb,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK,SAAS;AAAA,IAC9B,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,EAC7B,CAAC,EAAE,MAAM,MAAM,MAAS;AAExB,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;AACtD,MAAM,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC;AAExE,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,iBAAiB;AAAA,EACxB,aAAa,EAAE,QAAQ,qBAAqB;AAAA,EAC5C,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,cAAc,CAAC;AAAA,EACnF,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,mDAAmD,QAAQ,YAAY;AAAA,IACnG,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,YAAY;AAAA,EACvE;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,SAAS,EAAE,MAAM,UAAU;AAC7B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -11,6 +11,7 @@ const events = [
|
|
|
11
11
|
{ id: "customer_accounts.email.verified", label: "Customer Email Verified", category: "lifecycle", portalBroadcast: true },
|
|
12
12
|
{ id: "customer_accounts.password.reset_requested", label: "Customer Password Reset Requested", category: "lifecycle" },
|
|
13
13
|
{ id: "customer_accounts.password.reset", label: "Customer Password Reset", category: "lifecycle", portalBroadcast: true },
|
|
14
|
+
{ id: "customer_accounts.password.changed", label: "Customer Password Changed", category: "lifecycle" },
|
|
14
15
|
{ id: "customer_accounts.role.created", label: "Customer Role Created", entity: "role", category: "crud" },
|
|
15
16
|
{ id: "customer_accounts.role.updated", label: "Customer Role Updated", entity: "role", category: "crud", portalBroadcast: true },
|
|
16
17
|
{ id: "customer_accounts.role.deleted", label: "Customer Role Deleted", entity: "role", category: "crud" },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/customer_accounts/events.ts"],
|
|
4
|
-
"sourcesContent": ["import { createModuleEvents } from '@open-mercato/shared/modules/events'\n\nconst events = [\n { id: 'customer_accounts.user.created', label: 'Customer User Created', entity: 'user', category: 'crud', clientBroadcast: true },\n { id: 'customer_accounts.user.updated', label: 'Customer User Updated', entity: 'user', category: 'crud', portalBroadcast: true },\n { id: 'customer_accounts.user.deleted', label: 'Customer User Deleted', entity: 'user', category: 'crud' },\n { id: 'customer_accounts.user.locked', label: 'Customer User Locked', entity: 'user', category: 'lifecycle', portalBroadcast: true },\n { id: 'customer_accounts.user.unlocked', label: 'Customer User Unlocked', entity: 'user', category: 'lifecycle', portalBroadcast: true },\n { id: 'customer_accounts.login.success', label: 'Customer Login Successful', category: 'lifecycle' },\n { id: 'customer_accounts.login.failed', label: 'Customer Login Failed', category: 'lifecycle' },\n { id: 'customer_accounts.magic_link.requested', label: 'Customer Magic Link Requested', category: 'lifecycle' },\n { id: 'customer_accounts.email.verified', label: 'Customer Email Verified', category: 'lifecycle', portalBroadcast: true },\n { id: 'customer_accounts.password.reset_requested', label: 'Customer Password Reset Requested', category: 'lifecycle' },\n { id: 'customer_accounts.password.reset', label: 'Customer Password Reset', category: 'lifecycle', portalBroadcast: true },\n { id: 'customer_accounts.role.created', label: 'Customer Role Created', entity: 'role', category: 'crud' },\n { id: 'customer_accounts.role.updated', label: 'Customer Role Updated', entity: 'role', category: 'crud', portalBroadcast: true },\n { id: 'customer_accounts.role.deleted', label: 'Customer Role Deleted', entity: 'role', category: 'crud' },\n { id: 'customer_accounts.invitation.accepted', label: 'Customer Invitation Accepted', category: 'lifecycle', clientBroadcast: true },\n { id: 'customer_accounts.magic_link.requested', label: 'Customer Magic Link Requested', category: 'lifecycle' },\n { id: 'customer_accounts.password_reset.requested', label: 'Customer Password Reset Requested', category: 'lifecycle' },\n] as const\n\nexport const eventsConfig = createModuleEvents({\n moduleId: 'customer_accounts',\n events,\n})\n\nexport const emitCustomerAccountsEvent = eventsConfig.emit\n\nexport type CustomerAccountsEventId = typeof events[number]['id']\n\nexport default eventsConfig\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,0BAA0B;AAEnC,MAAM,SAAS;AAAA,EACb,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,QAAQ,iBAAiB,KAAK;AAAA,EAChI,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,QAAQ,iBAAiB,KAAK;AAAA,EAChI,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,OAAO;AAAA,EACzG,EAAE,IAAI,iCAAiC,OAAO,wBAAwB,QAAQ,QAAQ,UAAU,aAAa,iBAAiB,KAAK;AAAA,EACnI,EAAE,IAAI,mCAAmC,OAAO,0BAA0B,QAAQ,QAAQ,UAAU,aAAa,iBAAiB,KAAK;AAAA,EACvI,EAAE,IAAI,mCAAmC,OAAO,6BAA6B,UAAU,YAAY;AAAA,EACnG,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,UAAU,YAAY;AAAA,EAC9F,EAAE,IAAI,0CAA0C,OAAO,iCAAiC,UAAU,YAAY;AAAA,EAC9G,EAAE,IAAI,oCAAoC,OAAO,2BAA2B,UAAU,aAAa,iBAAiB,KAAK;AAAA,EACzH,EAAE,IAAI,8CAA8C,OAAO,qCAAqC,UAAU,YAAY;AAAA,EACtH,EAAE,IAAI,oCAAoC,OAAO,2BAA2B,UAAU,aAAa,iBAAiB,KAAK;AAAA,EACzH,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,OAAO;AAAA,EACzG,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,QAAQ,iBAAiB,KAAK;AAAA,EAChI,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,OAAO;AAAA,EACzG,EAAE,IAAI,yCAAyC,OAAO,gCAAgC,UAAU,aAAa,iBAAiB,KAAK;AAAA,EACnI,EAAE,IAAI,0CAA0C,OAAO,iCAAiC,UAAU,YAAY;AAAA,EAC9G,EAAE,IAAI,8CAA8C,OAAO,qCAAqC,UAAU,YAAY;AACxH;AAEO,MAAM,eAAe,mBAAmB;AAAA,EAC7C,UAAU;AAAA,EACV;AACF,CAAC;AAEM,MAAM,4BAA4B,aAAa;AAItD,IAAO,iBAAQ;",
|
|
4
|
+
"sourcesContent": ["import { createModuleEvents } from '@open-mercato/shared/modules/events'\n\nconst events = [\n { id: 'customer_accounts.user.created', label: 'Customer User Created', entity: 'user', category: 'crud', clientBroadcast: true },\n { id: 'customer_accounts.user.updated', label: 'Customer User Updated', entity: 'user', category: 'crud', portalBroadcast: true },\n { id: 'customer_accounts.user.deleted', label: 'Customer User Deleted', entity: 'user', category: 'crud' },\n { id: 'customer_accounts.user.locked', label: 'Customer User Locked', entity: 'user', category: 'lifecycle', portalBroadcast: true },\n { id: 'customer_accounts.user.unlocked', label: 'Customer User Unlocked', entity: 'user', category: 'lifecycle', portalBroadcast: true },\n { id: 'customer_accounts.login.success', label: 'Customer Login Successful', category: 'lifecycle' },\n { id: 'customer_accounts.login.failed', label: 'Customer Login Failed', category: 'lifecycle' },\n { id: 'customer_accounts.magic_link.requested', label: 'Customer Magic Link Requested', category: 'lifecycle' },\n { id: 'customer_accounts.email.verified', label: 'Customer Email Verified', category: 'lifecycle', portalBroadcast: true },\n { id: 'customer_accounts.password.reset_requested', label: 'Customer Password Reset Requested', category: 'lifecycle' },\n { id: 'customer_accounts.password.reset', label: 'Customer Password Reset', category: 'lifecycle', portalBroadcast: true },\n { id: 'customer_accounts.password.changed', label: 'Customer Password Changed', category: 'lifecycle' },\n { id: 'customer_accounts.role.created', label: 'Customer Role Created', entity: 'role', category: 'crud' },\n { id: 'customer_accounts.role.updated', label: 'Customer Role Updated', entity: 'role', category: 'crud', portalBroadcast: true },\n { id: 'customer_accounts.role.deleted', label: 'Customer Role Deleted', entity: 'role', category: 'crud' },\n { id: 'customer_accounts.invitation.accepted', label: 'Customer Invitation Accepted', category: 'lifecycle', clientBroadcast: true },\n { id: 'customer_accounts.magic_link.requested', label: 'Customer Magic Link Requested', category: 'lifecycle' },\n { id: 'customer_accounts.password_reset.requested', label: 'Customer Password Reset Requested', category: 'lifecycle' },\n] as const\n\nexport const eventsConfig = createModuleEvents({\n moduleId: 'customer_accounts',\n events,\n})\n\nexport const emitCustomerAccountsEvent = eventsConfig.emit\n\nexport type CustomerAccountsEventId = typeof events[number]['id']\n\nexport default eventsConfig\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,0BAA0B;AAEnC,MAAM,SAAS;AAAA,EACb,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,QAAQ,iBAAiB,KAAK;AAAA,EAChI,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,QAAQ,iBAAiB,KAAK;AAAA,EAChI,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,OAAO;AAAA,EACzG,EAAE,IAAI,iCAAiC,OAAO,wBAAwB,QAAQ,QAAQ,UAAU,aAAa,iBAAiB,KAAK;AAAA,EACnI,EAAE,IAAI,mCAAmC,OAAO,0BAA0B,QAAQ,QAAQ,UAAU,aAAa,iBAAiB,KAAK;AAAA,EACvI,EAAE,IAAI,mCAAmC,OAAO,6BAA6B,UAAU,YAAY;AAAA,EACnG,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,UAAU,YAAY;AAAA,EAC9F,EAAE,IAAI,0CAA0C,OAAO,iCAAiC,UAAU,YAAY;AAAA,EAC9G,EAAE,IAAI,oCAAoC,OAAO,2BAA2B,UAAU,aAAa,iBAAiB,KAAK;AAAA,EACzH,EAAE,IAAI,8CAA8C,OAAO,qCAAqC,UAAU,YAAY;AAAA,EACtH,EAAE,IAAI,oCAAoC,OAAO,2BAA2B,UAAU,aAAa,iBAAiB,KAAK;AAAA,EACzH,EAAE,IAAI,sCAAsC,OAAO,6BAA6B,UAAU,YAAY;AAAA,EACtG,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,OAAO;AAAA,EACzG,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,QAAQ,iBAAiB,KAAK;AAAA,EAChI,EAAE,IAAI,kCAAkC,OAAO,yBAAyB,QAAQ,QAAQ,UAAU,OAAO;AAAA,EACzG,EAAE,IAAI,yCAAyC,OAAO,gCAAgC,UAAU,aAAa,iBAAiB,KAAK;AAAA,EACnI,EAAE,IAAI,0CAA0C,OAAO,iCAAiC,UAAU,YAAY;AAAA,EAC9G,EAAE,IAAI,8CAA8C,OAAO,qCAAqC,UAAU,YAAY;AACxH;AAEO,MAAM,eAAe,mBAAmB;AAAA,EAC7C,UAAU;AAAA,EACV;AACF,CAAC;AAEM,MAAM,4BAA4B,aAAa;AAItD,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -109,14 +109,15 @@ class CustomerSessionService {
|
|
|
109
109
|
{ deletedAt: /* @__PURE__ */ new Date() }
|
|
110
110
|
);
|
|
111
111
|
}
|
|
112
|
-
async revokeAllUserSessions(userId) {
|
|
112
|
+
async revokeAllUserSessions(userId, em) {
|
|
113
113
|
const now = /* @__PURE__ */ new Date();
|
|
114
|
-
|
|
114
|
+
const target = em ?? this.em;
|
|
115
|
+
await target.nativeUpdate(
|
|
115
116
|
CustomerUserSession,
|
|
116
117
|
{ user: userId, deletedAt: null },
|
|
117
118
|
{ deletedAt: now }
|
|
118
119
|
);
|
|
119
|
-
await
|
|
120
|
+
await target.nativeUpdate(
|
|
120
121
|
CustomerUser,
|
|
121
122
|
{ id: userId },
|
|
122
123
|
{ sessionsRevokedAt: now }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customer_accounts/services/customerSessionService.ts"],
|
|
4
|
-
"sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerUser, CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { generateSecureToken, hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'\nimport { signAudienceJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nexport const CUSTOMER_JWT_AUDIENCE = 'customer'\nconst CUSTOMER_JWT_TTL_SECONDS = 60 * 60 * 8\n\nconst DEFAULT_SESSION_TTL_DAYS = 30\nconst DEFAULT_MAX_SESSIONS_PER_USER = 5\n\nfunction resolveMaxSessionsPerUser(): number {\n const raw = process.env.MAX_CUSTOMER_SESSIONS_PER_USER\n if (!raw) return DEFAULT_MAX_SESSIONS_PER_USER\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MAX_SESSIONS_PER_USER\n return Math.floor(parsed)\n}\n\nexport class CustomerSessionService {\n constructor(private em: EntityManager) {}\n\n async createSession(\n user: CustomerUser,\n resolvedFeatures: string[],\n ip?: string | null,\n userAgent?: string | null,\n ): Promise<{ rawToken: string; jwt: string; session: CustomerUserSession }> {\n const rawToken = generateSecureToken()\n const tokenHash = hashToken(rawToken)\n const days = Number(process.env.CUSTOMER_SESSION_TTL_DAYS || DEFAULT_SESSION_TTL_DAYS)\n const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000)\n\n await this.enforceSessionCap(user.id, user.tenantId, user.organizationId)\n\n const session = this.em.create(CustomerUserSession, {\n user,\n tokenHash,\n ipAddress: ip || null,\n userAgent: userAgent || null,\n expiresAt,\n lastUsedAt: new Date(),\n createdAt: new Date(),\n } as any) as CustomerUserSession\n await this.em.persist(session).flush()\n\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n\n return { rawToken, jwt, session }\n }\n\n signCustomerJwt(user: CustomerUser, resolvedFeatures: string[], sessionId: string): string {\n return signAudienceJwt(\n CUSTOMER_JWT_AUDIENCE,\n {\n sub: user.id,\n sid: sessionId,\n type: 'customer',\n tenantId: user.tenantId,\n orgId: user.organizationId,\n email: user.email,\n displayName: user.displayName || '',\n customerEntityId: user.customerEntityId || null,\n personEntityId: user.personEntityId || null,\n resolvedFeatures,\n },\n CUSTOMER_JWT_TTL_SECONDS,\n )\n }\n\n async findByToken(rawToken: string, tenantId?: string): Promise<CustomerUserSession | null> {\n const tokenHash = hashToken(rawToken)\n const session = await this.em.findOne(CustomerUserSession, {\n tokenHash,\n deletedAt: null,\n }, { populate: ['user'] })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n const user = session.user as CustomerUser\n if (tenantId && user?.tenantId !== tenantId) return null\n return session\n }\n\n async refreshSession(\n rawToken: string,\n resolvedFeatures: string[],\n ): Promise<{ jwt: string; user: CustomerUser } | null> {\n const session = await this.findByToken(rawToken)\n if (!session) return null\n const user = session.user as CustomerUser\n if (!user || user.deletedAt || !user.isActive) return null\n\n await this.em.nativeUpdate(CustomerUserSession, { id: session.id }, { lastUsedAt: new Date() })\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n return { jwt, user }\n }\n\n async findActiveSessionById(sessionId: string): Promise<CustomerUserSession | null> {\n const session = await this.em.findOne(CustomerUserSession, {\n id: sessionId,\n deletedAt: null,\n })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n return session\n }\n\n async revokeSession(sessionId: string): Promise<void> {\n await this.em.nativeUpdate(CustomerUserSession, { id: sessionId }, { deletedAt: new Date() })\n }\n\n private async enforceSessionCap(\n userId: string,\n tenantId: string,\n organizationId: string,\n ): Promise<void> {\n const cap = resolveMaxSessionsPerUser()\n const existing = await findWithDecryption(\n this.em,\n CustomerUserSession,\n {\n user: userId as any,\n deletedAt: null,\n expiresAt: { $gt: new Date() },\n },\n { orderBy: { createdAt: 'asc' } },\n { tenantId, organizationId },\n )\n const toRevoke = existing.length - (cap - 1)\n if (toRevoke <= 0) return\n const oldestIds = existing.slice(0, toRevoke).map((s) => s.id)\n await this.em.nativeUpdate(\n CustomerUserSession,\n { id: { $in: oldestIds } },\n { deletedAt: new Date() },\n )\n }\n\n async revokeAllUserSessions(userId: string): Promise<void> {\n const now = new Date()\n
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerUser, CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { generateSecureToken, hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'\nimport { signAudienceJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nexport const CUSTOMER_JWT_AUDIENCE = 'customer'\nconst CUSTOMER_JWT_TTL_SECONDS = 60 * 60 * 8\n\nconst DEFAULT_SESSION_TTL_DAYS = 30\nconst DEFAULT_MAX_SESSIONS_PER_USER = 5\n\nfunction resolveMaxSessionsPerUser(): number {\n const raw = process.env.MAX_CUSTOMER_SESSIONS_PER_USER\n if (!raw) return DEFAULT_MAX_SESSIONS_PER_USER\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MAX_SESSIONS_PER_USER\n return Math.floor(parsed)\n}\n\nexport class CustomerSessionService {\n constructor(private em: EntityManager) {}\n\n async createSession(\n user: CustomerUser,\n resolvedFeatures: string[],\n ip?: string | null,\n userAgent?: string | null,\n ): Promise<{ rawToken: string; jwt: string; session: CustomerUserSession }> {\n const rawToken = generateSecureToken()\n const tokenHash = hashToken(rawToken)\n const days = Number(process.env.CUSTOMER_SESSION_TTL_DAYS || DEFAULT_SESSION_TTL_DAYS)\n const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000)\n\n await this.enforceSessionCap(user.id, user.tenantId, user.organizationId)\n\n const session = this.em.create(CustomerUserSession, {\n user,\n tokenHash,\n ipAddress: ip || null,\n userAgent: userAgent || null,\n expiresAt,\n lastUsedAt: new Date(),\n createdAt: new Date(),\n } as any) as CustomerUserSession\n await this.em.persist(session).flush()\n\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n\n return { rawToken, jwt, session }\n }\n\n signCustomerJwt(user: CustomerUser, resolvedFeatures: string[], sessionId: string): string {\n return signAudienceJwt(\n CUSTOMER_JWT_AUDIENCE,\n {\n sub: user.id,\n sid: sessionId,\n type: 'customer',\n tenantId: user.tenantId,\n orgId: user.organizationId,\n email: user.email,\n displayName: user.displayName || '',\n customerEntityId: user.customerEntityId || null,\n personEntityId: user.personEntityId || null,\n resolvedFeatures,\n },\n CUSTOMER_JWT_TTL_SECONDS,\n )\n }\n\n async findByToken(rawToken: string, tenantId?: string): Promise<CustomerUserSession | null> {\n const tokenHash = hashToken(rawToken)\n const session = await this.em.findOne(CustomerUserSession, {\n tokenHash,\n deletedAt: null,\n }, { populate: ['user'] })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n const user = session.user as CustomerUser\n if (tenantId && user?.tenantId !== tenantId) return null\n return session\n }\n\n async refreshSession(\n rawToken: string,\n resolvedFeatures: string[],\n ): Promise<{ jwt: string; user: CustomerUser } | null> {\n const session = await this.findByToken(rawToken)\n if (!session) return null\n const user = session.user as CustomerUser\n if (!user || user.deletedAt || !user.isActive) return null\n\n await this.em.nativeUpdate(CustomerUserSession, { id: session.id }, { lastUsedAt: new Date() })\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n return { jwt, user }\n }\n\n async findActiveSessionById(sessionId: string): Promise<CustomerUserSession | null> {\n const session = await this.em.findOne(CustomerUserSession, {\n id: sessionId,\n deletedAt: null,\n })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n return session\n }\n\n async revokeSession(sessionId: string): Promise<void> {\n await this.em.nativeUpdate(CustomerUserSession, { id: sessionId }, { deletedAt: new Date() })\n }\n\n private async enforceSessionCap(\n userId: string,\n tenantId: string,\n organizationId: string,\n ): Promise<void> {\n const cap = resolveMaxSessionsPerUser()\n const existing = await findWithDecryption(\n this.em,\n CustomerUserSession,\n {\n user: userId as any,\n deletedAt: null,\n expiresAt: { $gt: new Date() },\n },\n { orderBy: { createdAt: 'asc' } },\n { tenantId, organizationId },\n )\n const toRevoke = existing.length - (cap - 1)\n if (toRevoke <= 0) return\n const oldestIds = existing.slice(0, toRevoke).map((s) => s.id)\n await this.em.nativeUpdate(\n CustomerUserSession,\n { id: { $in: oldestIds } },\n { deletedAt: new Date() },\n )\n }\n\n async revokeAllUserSessions(userId: string, em?: EntityManager): Promise<void> {\n const now = new Date()\n const target = em ?? this.em\n await target.nativeUpdate(\n CustomerUserSession,\n { user: userId as any, deletedAt: null },\n { deletedAt: now },\n )\n await target.nativeUpdate(\n CustomerUser,\n { id: userId },\n { sessionsRevokedAt: now },\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -67,9 +67,9 @@ class CustomerUserService {
|
|
|
67
67
|
user.failedLoginAttempts = 0;
|
|
68
68
|
user.lockedUntil = null;
|
|
69
69
|
}
|
|
70
|
-
async updatePassword(user, newPassword) {
|
|
70
|
+
async updatePassword(user, newPassword, em) {
|
|
71
71
|
const passwordHash = await hash(newPassword, BCRYPT_COST);
|
|
72
|
-
await this.em.nativeUpdate(CustomerUser, { id: user.id }, { passwordHash });
|
|
72
|
+
await (em ?? this.em).nativeUpdate(CustomerUser, { id: user.id }, { passwordHash });
|
|
73
73
|
user.passwordHash = passwordHash;
|
|
74
74
|
}
|
|
75
75
|
async updateProfile(user, data) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customer_accounts/services/customerUserService.ts"],
|
|
4
|
-
"sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { hash, compare } from 'bcryptjs'\nimport { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { hashForLookup } from '@open-mercato/shared/lib/encryption/aes'\n\nconst BCRYPT_COST = 10\nconst MAX_FAILED_ATTEMPTS = 5\nconst LOCKOUT_DURATION_MS = 15 * 60 * 1000 // 15 minutes\n\nexport class CustomerUserService {\n constructor(private em: EntityManager) {}\n\n async createUser(\n email: string,\n password: string,\n displayName: string,\n scope: { tenantId: string; organizationId: string },\n ): Promise<CustomerUser> {\n const passwordHash = await hash(password, BCRYPT_COST)\n const emailHash = hashForLookup(email)\n const user = this.em.create(CustomerUser, {\n email: email.toLowerCase().trim(),\n emailHash,\n passwordHash,\n displayName,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isActive: true,\n failedLoginAttempts: 0,\n createdAt: new Date(),\n } as any)\n return user as CustomerUser\n }\n\n async findByEmail(email: string, tenantId: string): Promise<CustomerUser | null> {\n const emailHash = hashForLookup(email)\n return this.em.findOne(CustomerUser, {\n emailHash,\n tenantId,\n deletedAt: null,\n })\n }\n\n async findById(id: string, tenantId: string): Promise<CustomerUser | null> {\n return this.em.findOne(CustomerUser, { id, tenantId, deletedAt: null })\n }\n\n async verifyPassword(user: CustomerUser, password: string): Promise<boolean> {\n if (!user.passwordHash) return false\n return compare(password, user.passwordHash)\n }\n\n async updateLastLoginAt(user: CustomerUser): Promise<void> {\n const now = new Date()\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, { lastLoginAt: now })\n user.lastLoginAt = now\n }\n\n checkLockout(user: CustomerUser): boolean {\n if (!user.lockedUntil) return false\n if (user.lockedUntil.getTime() > Date.now()) return true\n return false\n }\n\n async incrementFailedAttempts(user: CustomerUser): Promise<void> {\n const newCount = (user.failedLoginAttempts || 0) + 1\n const updates: Record<string, unknown> = { failedLoginAttempts: newCount }\n if (newCount >= MAX_FAILED_ATTEMPTS) {\n updates.lockedUntil = new Date(Date.now() + LOCKOUT_DURATION_MS)\n }\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, updates)\n user.failedLoginAttempts = newCount\n if (updates.lockedUntil) user.lockedUntil = updates.lockedUntil as Date\n }\n\n async resetFailedAttempts(user: CustomerUser): Promise<void> {\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, {\n failedLoginAttempts: 0,\n lockedUntil: null,\n })\n user.failedLoginAttempts = 0\n user.lockedUntil = null\n }\n\n async updatePassword(user: CustomerUser, newPassword: string): Promise<void> {\n const passwordHash = await hash(newPassword, BCRYPT_COST)\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, { passwordHash })\n user.passwordHash = passwordHash\n }\n\n async updateProfile(user: CustomerUser, data: { displayName?: string }): Promise<void> {\n const updates: Record<string, unknown> = {}\n if (data.displayName !== undefined) updates.displayName = data.displayName\n if (Object.keys(updates).length === 0) return\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, updates)\n if (data.displayName !== undefined) user.displayName = data.displayName\n }\n\n async softDelete(userId: string): Promise<void> {\n await this.em.nativeUpdate(CustomerUser, { id: userId }, {\n deletedAt: new Date(),\n isActive: false,\n })\n }\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,MAAM,eAAe;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAE9B,MAAM,cAAc;AACpB,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB,KAAK,KAAK;AAE/B,MAAM,oBAAoB;AAAA,EAC/B,YAAoB,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAExC,MAAM,WACJ,OACA,UACA,aACA,OACuB;AACvB,UAAM,eAAe,MAAM,KAAK,UAAU,WAAW;AACrD,UAAM,YAAY,cAAc,KAAK;AACrC,UAAM,OAAO,KAAK,GAAG,OAAO,cAAc;AAAA,MACxC,OAAO,MAAM,YAAY,EAAE,KAAK;AAAA,MAChC;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,UAAU;AAAA,MACV,qBAAqB;AAAA,MACrB,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAQ;AACR,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,OAAe,UAAgD;AAC/E,UAAM,YAAY,cAAc,KAAK;AACrC,WAAO,KAAK,GAAG,QAAQ,cAAc;AAAA,MACnC;AAAA,MACA;AAAA,MACA,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAAS,IAAY,UAAgD;AACzE,WAAO,KAAK,GAAG,QAAQ,cAAc,EAAE,IAAI,UAAU,WAAW,KAAK,CAAC;AAAA,EACxE;AAAA,EAEA,MAAM,eAAe,MAAoB,UAAoC;AAC3E,QAAI,CAAC,KAAK,aAAc,QAAO;AAC/B,WAAO,QAAQ,UAAU,KAAK,YAAY;AAAA,EAC5C;AAAA,EAEA,MAAM,kBAAkB,MAAmC;AACzD,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,KAAK,GAAG,aAAa,cAAc,EAAE,IAAI,KAAK,GAAG,GAAG,EAAE,aAAa,IAAI,CAAC;AAC9E,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,aAAa,MAA6B;AACxC,QAAI,CAAC,KAAK,YAAa,QAAO;AAC9B,QAAI,KAAK,YAAY,QAAQ,IAAI,KAAK,IAAI,EAAG,QAAO;AACpD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,wBAAwB,MAAmC;AAC/D,UAAM,YAAY,KAAK,uBAAuB,KAAK;AACnD,UAAM,UAAmC,EAAE,qBAAqB,SAAS;AACzE,QAAI,YAAY,qBAAqB;AACnC,cAAQ,cAAc,IAAI,KAAK,KAAK,IAAI,IAAI,mBAAmB;AAAA,IACjE;AACA,UAAM,KAAK,GAAG,aAAa,cAAc,EAAE,IAAI,KAAK,GAAG,GAAG,OAAO;AACjE,SAAK,sBAAsB;AAC3B,QAAI,QAAQ,YAAa,MAAK,cAAc,QAAQ;AAAA,EACtD;AAAA,EAEA,MAAM,oBAAoB,MAAmC;AAC3D,UAAM,KAAK,GAAG,aAAa,cAAc,EAAE,IAAI,KAAK,GAAG,GAAG;AAAA,MACxD,qBAAqB;AAAA,MACrB,aAAa;AAAA,IACf,CAAC;AACD,SAAK,sBAAsB;AAC3B,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAM,eAAe,MAAoB,
|
|
4
|
+
"sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { hash, compare } from 'bcryptjs'\nimport { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { hashForLookup } from '@open-mercato/shared/lib/encryption/aes'\n\nconst BCRYPT_COST = 10\nconst MAX_FAILED_ATTEMPTS = 5\nconst LOCKOUT_DURATION_MS = 15 * 60 * 1000 // 15 minutes\n\nexport class CustomerUserService {\n constructor(private em: EntityManager) {}\n\n async createUser(\n email: string,\n password: string,\n displayName: string,\n scope: { tenantId: string; organizationId: string },\n ): Promise<CustomerUser> {\n const passwordHash = await hash(password, BCRYPT_COST)\n const emailHash = hashForLookup(email)\n const user = this.em.create(CustomerUser, {\n email: email.toLowerCase().trim(),\n emailHash,\n passwordHash,\n displayName,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isActive: true,\n failedLoginAttempts: 0,\n createdAt: new Date(),\n } as any)\n return user as CustomerUser\n }\n\n async findByEmail(email: string, tenantId: string): Promise<CustomerUser | null> {\n const emailHash = hashForLookup(email)\n return this.em.findOne(CustomerUser, {\n emailHash,\n tenantId,\n deletedAt: null,\n })\n }\n\n async findById(id: string, tenantId: string): Promise<CustomerUser | null> {\n return this.em.findOne(CustomerUser, { id, tenantId, deletedAt: null })\n }\n\n async verifyPassword(user: CustomerUser, password: string): Promise<boolean> {\n if (!user.passwordHash) return false\n return compare(password, user.passwordHash)\n }\n\n async updateLastLoginAt(user: CustomerUser): Promise<void> {\n const now = new Date()\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, { lastLoginAt: now })\n user.lastLoginAt = now\n }\n\n checkLockout(user: CustomerUser): boolean {\n if (!user.lockedUntil) return false\n if (user.lockedUntil.getTime() > Date.now()) return true\n return false\n }\n\n async incrementFailedAttempts(user: CustomerUser): Promise<void> {\n const newCount = (user.failedLoginAttempts || 0) + 1\n const updates: Record<string, unknown> = { failedLoginAttempts: newCount }\n if (newCount >= MAX_FAILED_ATTEMPTS) {\n updates.lockedUntil = new Date(Date.now() + LOCKOUT_DURATION_MS)\n }\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, updates)\n user.failedLoginAttempts = newCount\n if (updates.lockedUntil) user.lockedUntil = updates.lockedUntil as Date\n }\n\n async resetFailedAttempts(user: CustomerUser): Promise<void> {\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, {\n failedLoginAttempts: 0,\n lockedUntil: null,\n })\n user.failedLoginAttempts = 0\n user.lockedUntil = null\n }\n\n async updatePassword(user: CustomerUser, newPassword: string, em?: EntityManager): Promise<void> {\n const passwordHash = await hash(newPassword, BCRYPT_COST)\n await (em ?? this.em).nativeUpdate(CustomerUser, { id: user.id }, { passwordHash })\n user.passwordHash = passwordHash\n }\n\n async updateProfile(user: CustomerUser, data: { displayName?: string }): Promise<void> {\n const updates: Record<string, unknown> = {}\n if (data.displayName !== undefined) updates.displayName = data.displayName\n if (Object.keys(updates).length === 0) return\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, updates)\n if (data.displayName !== undefined) user.displayName = data.displayName\n }\n\n async softDelete(userId: string): Promise<void> {\n await this.em.nativeUpdate(CustomerUser, { id: userId }, {\n deletedAt: new Date(),\n isActive: false,\n })\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.5.1-develop.
|
|
3
|
+
"version": "0.5.1-develop.2907.745250bbb5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -237,10 +237,10 @@
|
|
|
237
237
|
"ts-pattern": "^5.0.0"
|
|
238
238
|
},
|
|
239
239
|
"peerDependencies": {
|
|
240
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
240
|
+
"@open-mercato/shared": "0.5.1-develop.2907.745250bbb5"
|
|
241
241
|
},
|
|
242
242
|
"devDependencies": {
|
|
243
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
243
|
+
"@open-mercato/shared": "0.5.1-develop.2907.745250bbb5",
|
|
244
244
|
"@testing-library/dom": "^10.4.1",
|
|
245
245
|
"@testing-library/jest-dom": "^6.9.1",
|
|
246
246
|
"@testing-library/react": "^16.3.1",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
4
|
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
5
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
6
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
@@ -38,13 +39,16 @@ export async function POST(req: Request, { params }: { params: { id: string } })
|
|
|
38
39
|
|
|
39
40
|
const customerUserService = container.resolve('customerUserService') as CustomerUserService
|
|
40
41
|
const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService
|
|
42
|
+
const em = container.resolve('em') as EntityManager
|
|
41
43
|
const user = await customerUserService.findById(params.id, auth.tenantId!)
|
|
42
44
|
if (!user) {
|
|
43
45
|
return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
await
|
|
47
|
-
|
|
48
|
+
await em.transactional(async (trx) => {
|
|
49
|
+
await customerUserService.updatePassword(user, parsed.data.newPassword, trx)
|
|
50
|
+
await customerSessionService.revokeAllUserSessions(user.id, trx)
|
|
51
|
+
})
|
|
48
52
|
|
|
49
53
|
void emitCustomerAccountsEvent('customer_accounts.password.reset', {
|
|
50
54
|
id: user.id,
|
|
@@ -54,6 +58,15 @@ export async function POST(req: Request, { params }: { params: { id: string } })
|
|
|
54
58
|
resetBy: auth.sub,
|
|
55
59
|
}).catch(() => undefined)
|
|
56
60
|
|
|
61
|
+
void emitCustomerAccountsEvent('customer_accounts.password.changed', {
|
|
62
|
+
userId: user.id,
|
|
63
|
+
tenantId: auth.tenantId,
|
|
64
|
+
organizationId: auth.orgId ?? null,
|
|
65
|
+
changedBy: 'admin',
|
|
66
|
+
changedById: auth.sub,
|
|
67
|
+
at: new Date().toISOString(),
|
|
68
|
+
}).catch(() => undefined)
|
|
69
|
+
|
|
57
70
|
return NextResponse.json({ ok: true })
|
|
58
71
|
}
|
|
59
72
|
|
|
@@ -7,6 +7,7 @@ import { CustomerUserService } from '@open-mercato/core/modules/customer_account
|
|
|
7
7
|
import { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'
|
|
8
8
|
import { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'
|
|
9
9
|
import { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'
|
|
10
|
+
import { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'
|
|
10
11
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
11
12
|
|
|
12
13
|
export const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }
|
|
@@ -34,20 +35,31 @@ export async function POST(req: Request) {
|
|
|
34
35
|
return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
await customerUserService.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const user = await customerUserService.findById(result.userId, result.tenantId)
|
|
39
|
+
if (!user) {
|
|
40
|
+
return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })
|
|
41
|
+
}
|
|
41
42
|
|
|
42
43
|
const em = container.resolve('em') as EntityManager
|
|
44
|
+
await em.transactional(async (trx) => {
|
|
45
|
+
await customerUserService.updatePassword(user, parsed.data.password, trx)
|
|
46
|
+
await customerSessionService.revokeAllUserSessions(user.id, trx)
|
|
47
|
+
})
|
|
48
|
+
|
|
43
49
|
await em.nativeUpdate(
|
|
44
50
|
CustomerUser,
|
|
45
|
-
{ id:
|
|
51
|
+
{ id: user.id, emailVerifiedAt: null },
|
|
46
52
|
{ emailVerifiedAt: new Date() },
|
|
47
53
|
)
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
void emitCustomerAccountsEvent('customer_accounts.password.changed', {
|
|
56
|
+
userId: user.id,
|
|
57
|
+
tenantId: user.tenantId,
|
|
58
|
+
organizationId: user.organizationId ?? null,
|
|
59
|
+
changedBy: 'reset',
|
|
60
|
+
changedById: null,
|
|
61
|
+
at: new Date().toISOString(),
|
|
62
|
+
}).catch(() => undefined)
|
|
51
63
|
|
|
52
64
|
return NextResponse.json({ ok: true })
|
|
53
65
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
4
|
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
5
|
import { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
5
6
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
7
|
import { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'
|
|
7
8
|
import { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'
|
|
8
9
|
import { passwordChangeSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
|
|
10
|
+
import { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'
|
|
9
11
|
|
|
10
12
|
export const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }
|
|
11
13
|
|
|
@@ -30,6 +32,7 @@ export async function POST(req: Request) {
|
|
|
30
32
|
const container = await createRequestContainer()
|
|
31
33
|
const customerUserService = container.resolve('customerUserService') as CustomerUserService
|
|
32
34
|
const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService
|
|
35
|
+
const em = container.resolve('em') as EntityManager
|
|
33
36
|
|
|
34
37
|
const user = await customerUserService.findById(auth.sub, auth.tenantId)
|
|
35
38
|
if (!user) {
|
|
@@ -41,10 +44,19 @@ export async function POST(req: Request) {
|
|
|
41
44
|
return NextResponse.json({ ok: false, error: 'Current password is incorrect' }, { status: 400 })
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
await
|
|
47
|
+
await em.transactional(async (trx) => {
|
|
48
|
+
await customerUserService.updatePassword(user, parsed.data.newPassword, trx)
|
|
49
|
+
await customerSessionService.revokeAllUserSessions(user.id, trx)
|
|
50
|
+
})
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
void emitCustomerAccountsEvent('customer_accounts.password.changed', {
|
|
53
|
+
userId: user.id,
|
|
54
|
+
tenantId: auth.tenantId,
|
|
55
|
+
organizationId: auth.orgId ?? null,
|
|
56
|
+
changedBy: 'self',
|
|
57
|
+
changedById: null,
|
|
58
|
+
at: new Date().toISOString(),
|
|
59
|
+
}).catch(() => undefined)
|
|
48
60
|
|
|
49
61
|
return NextResponse.json({ ok: true })
|
|
50
62
|
}
|
|
@@ -12,6 +12,7 @@ const events = [
|
|
|
12
12
|
{ id: 'customer_accounts.email.verified', label: 'Customer Email Verified', category: 'lifecycle', portalBroadcast: true },
|
|
13
13
|
{ id: 'customer_accounts.password.reset_requested', label: 'Customer Password Reset Requested', category: 'lifecycle' },
|
|
14
14
|
{ id: 'customer_accounts.password.reset', label: 'Customer Password Reset', category: 'lifecycle', portalBroadcast: true },
|
|
15
|
+
{ id: 'customer_accounts.password.changed', label: 'Customer Password Changed', category: 'lifecycle' },
|
|
15
16
|
{ id: 'customer_accounts.role.created', label: 'Customer Role Created', entity: 'role', category: 'crud' },
|
|
16
17
|
{ id: 'customer_accounts.role.updated', label: 'Customer Role Updated', entity: 'role', category: 'crud', portalBroadcast: true },
|
|
17
18
|
{ id: 'customer_accounts.role.deleted', label: 'Customer Role Deleted', entity: 'role', category: 'crud' },
|
|
@@ -137,14 +137,15 @@ export class CustomerSessionService {
|
|
|
137
137
|
)
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
async revokeAllUserSessions(userId: string): Promise<void> {
|
|
140
|
+
async revokeAllUserSessions(userId: string, em?: EntityManager): Promise<void> {
|
|
141
141
|
const now = new Date()
|
|
142
|
-
|
|
142
|
+
const target = em ?? this.em
|
|
143
|
+
await target.nativeUpdate(
|
|
143
144
|
CustomerUserSession,
|
|
144
145
|
{ user: userId as any, deletedAt: null },
|
|
145
146
|
{ deletedAt: now },
|
|
146
147
|
)
|
|
147
|
-
await
|
|
148
|
+
await target.nativeUpdate(
|
|
148
149
|
CustomerUser,
|
|
149
150
|
{ id: userId },
|
|
150
151
|
{ sessionsRevokedAt: now },
|
|
@@ -82,9 +82,9 @@ export class CustomerUserService {
|
|
|
82
82
|
user.lockedUntil = null
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
async updatePassword(user: CustomerUser, newPassword: string): Promise<void> {
|
|
85
|
+
async updatePassword(user: CustomerUser, newPassword: string, em?: EntityManager): Promise<void> {
|
|
86
86
|
const passwordHash = await hash(newPassword, BCRYPT_COST)
|
|
87
|
-
await this.em.nativeUpdate(CustomerUser, { id: user.id }, { passwordHash })
|
|
87
|
+
await (em ?? this.em).nativeUpdate(CustomerUser, { id: user.id }, { passwordHash })
|
|
88
88
|
user.passwordHash = passwordHash
|
|
89
89
|
}
|
|
90
90
|
|