@nextsparkjs/core 0.1.0-beta.169 → 0.1.0-beta.170
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/lib/actions/user.actions.d.ts.map +1 -1
- package/dist/lib/actions/user.actions.js +4 -18
- package/dist/lib/api/helpers.d.ts.map +1 -1
- package/dist/lib/api/helpers.js +9 -5
- package/dist/lib/services/user.service.d.ts +16 -0
- package/dist/lib/services/user.service.d.ts.map +1 -1
- package/dist/lib/services/user.service.js +46 -0
- package/dist/lib/utils/cors.d.ts +20 -0
- package/dist/lib/utils/cors.d.ts.map +1 -1
- package/dist/lib/utils/cors.js +21 -0
- package/dist/styles/classes.json +1 -1
- package/dist/templates/app/api/user/delete-account/route.ts +20 -18
- package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +7 -13
- package/package.json +2 -2
- package/templates/app/api/user/delete-account/route.ts +20 -18
- package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +7 -13
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.actions.d.ts","sourceRoot":"","sources":["../../../src/lib/actions/user.actions.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAMzE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CA2DlD;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CAsElD;AAMD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,sBAAsB,CAAC,
|
|
1
|
+
{"version":3,"file":"user.actions.d.ts","sourceRoot":"","sources":["../../../src/lib/actions/user.actions.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAMzE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CA2DlD;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CAsElD;AAMD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAgCrE"}
|
|
@@ -114,27 +114,13 @@ async function deleteAccount() {
|
|
|
114
114
|
return { success: false, error: "Authentication required" };
|
|
115
115
|
}
|
|
116
116
|
const userId = session.user.id;
|
|
117
|
-
|
|
118
|
-
const ownedTeams = await TeamService.getByOwnerId(userId);
|
|
119
|
-
if (ownedTeams.length > 0) {
|
|
120
|
-
return {
|
|
121
|
-
success: false,
|
|
122
|
-
error: "Cannot delete account while owning teams. Transfer ownership or delete teams first."
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
await UserService.deleteAllUserMetas(userId, userId);
|
|
126
|
-
const { mutateWithRLS } = await import("../db.js");
|
|
127
|
-
const result = await mutateWithRLS(
|
|
128
|
-
'DELETE FROM "users" WHERE id = $1',
|
|
129
|
-
[userId],
|
|
130
|
-
userId
|
|
131
|
-
);
|
|
132
|
-
if (result.rowCount === 0) {
|
|
133
|
-
return { success: false, error: "User not found" };
|
|
134
|
-
}
|
|
117
|
+
await UserService.anonymizeAccount(userId);
|
|
135
118
|
revalidatePath("/");
|
|
136
119
|
return { success: true };
|
|
137
120
|
} catch (error) {
|
|
121
|
+
if ((error == null ? void 0 : error.code) === "OWNS_TEAMS") {
|
|
122
|
+
return { success: false, error: error.message };
|
|
123
|
+
}
|
|
138
124
|
console.error("[deleteAccount] Error:", error);
|
|
139
125
|
return {
|
|
140
126
|
success: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/api/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,UAAU,EAAkB,MAAM,QAAQ,CAAC;AAapD,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,OAAO,mBAAmB,EAAE,QAAQ,EAAE,CAAC;IAC9C,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,KAAK,IAAI,GAAG,UAAU,GAAG,WAAW,CAAC;AAErC;;;GAGG;AACH,wBAAsB,8BAA8B,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC;IAClF,IAAI,EAAE,IAAI,CAAC;IACX,iBAAiB,CAAC,EAAE,YAAY,CAAC;CAClC,CAAC,CAoCD;AAgFD;;;GAGG;AACH,wBAAsB,iCAAiC,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC;IACrF,IAAI,EAAE,UAAU,CAAC;IACjB,iBAAiB,CAAC,EAAE,YAAY,CAAC;CAClC,CAAC,CA4BD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAwB3D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAkBjF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAE,MAAY;;;;;;GASzG;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAY,EACpB,OAAO,CAAC,EAAE,OAAO,EACjB,IAAI,CAAC,EAAE,MAAM;;;;;;;;GAWd;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAQA;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM;;;;;;;EAUd;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAAE,EAChD,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,IAEtD,SAAS,WAAW,EAAE,GAAG,MAAM,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC,CA6CvE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGjD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUzD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAezE;AAUD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/api/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,UAAU,EAAkB,MAAM,QAAQ,CAAC;AAapD,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,OAAO,mBAAmB,EAAE,QAAQ,EAAE,CAAC;IAC9C,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,KAAK,IAAI,GAAG,UAAU,GAAG,WAAW,CAAC;AAErC;;;GAGG;AACH,wBAAsB,8BAA8B,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC;IAClF,IAAI,EAAE,IAAI,CAAC;IACX,iBAAiB,CAAC,EAAE,YAAY,CAAC;CAClC,CAAC,CAoCD;AAgFD;;;GAGG;AACH,wBAAsB,iCAAiC,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC;IACrF,IAAI,EAAE,UAAU,CAAC;IACjB,iBAAiB,CAAC,EAAE,YAAY,CAAC;CAClC,CAAC,CA4BD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAwB3D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAkBjF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAE,MAAY;;;;;;GASzG;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAY,EACpB,OAAO,CAAC,EAAE,OAAO,EACjB,IAAI,CAAC,EAAE,MAAM;;;;;;;;GAWd;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAQA;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM;;;;;;;EAUd;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAAE,EAChD,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,IAEtD,SAAS,WAAW,EAAE,GAAG,MAAM,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC,CA6CvE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGjD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUzD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAezE;AAUD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAgDzG;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAG7F;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,EAChC,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,QAAQ,CAAC,CAyCnB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CA0BhE;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CA0BlE;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAClE,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,CAAC,EAAE,EACb,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,EAAE,CAAC,CA6BtD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAClE,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,CAAC,EAAE,EACb,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,OAAO,GACrB,OAAO,CAAC,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;CAAE,CAAC,EAAE,CAAC,CAgExD;AAED;;;GAGG;AACH,wBAAsB,8BAA8B,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAC3E,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,CAAC,EACT,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CA2BlD;AAED;;;GAGG;AACH,wBAAsB,8BAA8B,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAC3E,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,CAAC,EACT,eAAe,EAAE,OAAO,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAiBlD;AAoBD;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA+DjD"}
|
package/dist/lib/api/helpers.js
CHANGED
|
@@ -7,7 +7,7 @@ import { auth } from "../auth.js";
|
|
|
7
7
|
import { MetaService } from "../services/meta.service.js";
|
|
8
8
|
import { ScopeService } from "../services/scope.service.js";
|
|
9
9
|
import { getChildEntities } from "../entities/queries.js";
|
|
10
|
-
import { getCorsOrigins, normalizeOrigin } from "../utils/cors.js";
|
|
10
|
+
import { getCorsOrigins, normalizeOrigin, isOriginAllowed } from "../utils/cors.js";
|
|
11
11
|
async function validateAndAuthenticateRequest(request) {
|
|
12
12
|
var _a;
|
|
13
13
|
const cookieHeader = request.headers.get("cookie");
|
|
@@ -271,8 +271,9 @@ async function addCorsHeaders(response, request) {
|
|
|
271
271
|
allowedOrigin = normalizedOrigin;
|
|
272
272
|
} else {
|
|
273
273
|
const allowedOrigins = getCorsOrigins(config, env);
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
const matched = isOriginAllowed(normalizedOrigin, allowedOrigins);
|
|
275
|
+
if (matched) {
|
|
276
|
+
allowedOrigin = matched;
|
|
276
277
|
}
|
|
277
278
|
}
|
|
278
279
|
}
|
|
@@ -281,6 +282,7 @@ async function addCorsHeaders(response, request) {
|
|
|
281
282
|
allowedOrigin = allowedOrigins[0] || "http://localhost:3000";
|
|
282
283
|
}
|
|
283
284
|
response.headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
285
|
+
response.headers.append("Vary", "Origin");
|
|
284
286
|
response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
|
|
285
287
|
response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key, x-team-id, x-builder-source");
|
|
286
288
|
response.headers.set("Access-Control-Allow-Credentials", "true");
|
|
@@ -309,12 +311,14 @@ async function wrapAuthHandlerWithCors(handler, request) {
|
|
|
309
311
|
allowedOrigin = normalizedOrigin;
|
|
310
312
|
} else {
|
|
311
313
|
const allowedOrigins = getCorsOrigins(config, env);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
+
const matched = isOriginAllowed(normalizedOrigin, allowedOrigins);
|
|
315
|
+
if (matched) {
|
|
316
|
+
allowedOrigin = matched;
|
|
314
317
|
}
|
|
315
318
|
}
|
|
316
319
|
newResponse.headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
317
320
|
newResponse.headers.set("Access-Control-Allow-Credentials", "true");
|
|
321
|
+
newResponse.headers.append("Vary", "Origin");
|
|
318
322
|
}
|
|
319
323
|
return newResponse;
|
|
320
324
|
}
|
|
@@ -189,6 +189,22 @@ export declare class UserService {
|
|
|
189
189
|
* await UserService.deleteAllUserMetas('user-id-123', 'current-user-id')
|
|
190
190
|
*/
|
|
191
191
|
static deleteAllUserMetas(userId: string, currentUserId: string): Promise<void>;
|
|
192
|
+
/**
|
|
193
|
+
* Anonymize (soft-delete) a user account.
|
|
194
|
+
*
|
|
195
|
+
* A hard DELETE of the users row fails under foreign-key constraints (other
|
|
196
|
+
* tables reference the user) and would orphan that history. Anonymizing
|
|
197
|
+
* instead frees the UNIQUE email for re-registration, strips PII, revokes
|
|
198
|
+
* every session and purges stored credentials, while preserving referential
|
|
199
|
+
* integrity. `userId` must come from the caller's session — a user may only
|
|
200
|
+
* anonymize their own account.
|
|
201
|
+
*
|
|
202
|
+
* Throws an Error with `.code === 'OWNS_TEAMS'` when the user still owns
|
|
203
|
+
* teams; the caller must surface that so ownership is transferred first.
|
|
204
|
+
*
|
|
205
|
+
* @param userId - ID of the account to anonymize (own account only)
|
|
206
|
+
*/
|
|
207
|
+
static anonymizeAccount(userId: string): Promise<void>;
|
|
192
208
|
/**
|
|
193
209
|
* Get metadata for multiple users in bulk (solves N+1 query problem)
|
|
194
210
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/user.service.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"user.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/user.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -530,6 +530,52 @@ class UserService {
|
|
|
530
530
|
);
|
|
531
531
|
}
|
|
532
532
|
}
|
|
533
|
+
/**
|
|
534
|
+
* Anonymize (soft-delete) a user account.
|
|
535
|
+
*
|
|
536
|
+
* A hard DELETE of the users row fails under foreign-key constraints (other
|
|
537
|
+
* tables reference the user) and would orphan that history. Anonymizing
|
|
538
|
+
* instead frees the UNIQUE email for re-registration, strips PII, revokes
|
|
539
|
+
* every session and purges stored credentials, while preserving referential
|
|
540
|
+
* integrity. `userId` must come from the caller's session — a user may only
|
|
541
|
+
* anonymize their own account.
|
|
542
|
+
*
|
|
543
|
+
* Throws an Error with `.code === 'OWNS_TEAMS'` when the user still owns
|
|
544
|
+
* teams; the caller must surface that so ownership is transferred first.
|
|
545
|
+
*
|
|
546
|
+
* @param userId - ID of the account to anonymize (own account only)
|
|
547
|
+
*/
|
|
548
|
+
static async anonymizeAccount(userId) {
|
|
549
|
+
if (!userId || userId.trim() === "") {
|
|
550
|
+
throw new Error("User ID is required");
|
|
551
|
+
}
|
|
552
|
+
const { TeamService } = await import("./team.service.js");
|
|
553
|
+
const ownedTeams = await TeamService.getByOwnerId(userId);
|
|
554
|
+
if (ownedTeams.length > 0) {
|
|
555
|
+
const error = new Error(
|
|
556
|
+
"Cannot delete account while owning teams. Transfer ownership or delete teams first."
|
|
557
|
+
);
|
|
558
|
+
error.code = "OWNS_TEAMS";
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
await UserService.deleteAllUserMetas(userId, userId);
|
|
562
|
+
const result = await mutateWithRLS(
|
|
563
|
+
`UPDATE "users"
|
|
564
|
+
SET email = 'deleted+' || id || '@deleted.invalid',
|
|
565
|
+
"emailVerified" = false,
|
|
566
|
+
"firstName" = 'Deleted',
|
|
567
|
+
"lastName" = 'account',
|
|
568
|
+
image = NULL
|
|
569
|
+
WHERE id = $1`,
|
|
570
|
+
[userId],
|
|
571
|
+
userId
|
|
572
|
+
);
|
|
573
|
+
if (result.rowCount === 0) {
|
|
574
|
+
throw new Error("User not found");
|
|
575
|
+
}
|
|
576
|
+
await mutateWithRLS('DELETE FROM "session" WHERE "userId" = $1', [userId], userId, { service: true });
|
|
577
|
+
await mutateWithRLS('DELETE FROM "account" WHERE "userId" = $1', [userId], userId, { service: true });
|
|
578
|
+
}
|
|
533
579
|
/**
|
|
534
580
|
* Get metadata for multiple users in bulk (solves N+1 query problem)
|
|
535
581
|
*
|
package/dist/lib/utils/cors.d.ts
CHANGED
|
@@ -68,4 +68,24 @@ export declare function normalizeCorsEnvironment(env: string): 'development' | '
|
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
70
|
export declare function getCorsOrigins(config: ApplicationConfig, env?: string): string[];
|
|
71
|
+
/**
|
|
72
|
+
* Check whether a request origin is allowed, supporting wildcard-pattern entries
|
|
73
|
+
* in the allow-list. A `*` in an entry matches exactly ONE host label (no dots),
|
|
74
|
+
* so `https://*.example.app` matches `https://tenant.example.app` but NOT
|
|
75
|
+
* `https://a.b.example.app`, `https://example.app`, or look-alikes like
|
|
76
|
+
* `https://evil-example.app`. Entries without `*` are matched exactly.
|
|
77
|
+
*
|
|
78
|
+
* Returns the CONCRETE request origin to echo back (never the pattern or `*`) so
|
|
79
|
+
* credentialed responses — which cannot use `*` for Access-Control-Allow-Origin —
|
|
80
|
+
* send a valid concrete origin; returns null when nothing matches.
|
|
81
|
+
*
|
|
82
|
+
* Why wildcards: multi-tenant apps serve dynamic per-tenant subdomains that can't
|
|
83
|
+
* be enumerated in config. A single `https://*.<apex>` entry lets a session on
|
|
84
|
+
* the apex be recognized cross-subdomain without listing every tenant origin.
|
|
85
|
+
*
|
|
86
|
+
* @param requestOrigin - The (already normalized) incoming Origin to validate.
|
|
87
|
+
* @param allowedOrigins - The allow-list from getCorsOrigins() (may contain patterns).
|
|
88
|
+
* @returns The origin to echo (the requestOrigin on match), or null on no match.
|
|
89
|
+
*/
|
|
90
|
+
export declare function isOriginAllowed(requestOrigin: string, allowedOrigins: string[]): string | null;
|
|
71
91
|
//# sourceMappingURL=cors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/cors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,YAAY,GAAG,SAAS,GAAG,IAAI,CAAA;AAc7E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAetD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,YAAY,CAkBlF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,iBAAiB,EACzB,GAAG,GAAE,MAA8C,GAClD,MAAM,EAAE,CA+CV"}
|
|
1
|
+
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/cors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,YAAY,GAAG,SAAS,GAAG,IAAI,CAAA;AAc7E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAetD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,YAAY,CAkBlF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,iBAAiB,EACzB,GAAG,GAAE,MAA8C,GAClD,MAAM,EAAE,CA+CV;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EAAE,GACvB,MAAM,GAAG,IAAI,CA6Bf"}
|
package/dist/lib/utils/cors.js
CHANGED
|
@@ -57,8 +57,29 @@ function getCorsOrigins(config, env = process.env.NODE_ENV || "development") {
|
|
|
57
57
|
}
|
|
58
58
|
return origins;
|
|
59
59
|
}
|
|
60
|
+
function escapeRegExp(value) {
|
|
61
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
62
|
+
}
|
|
63
|
+
function isOriginAllowed(requestOrigin, allowedOrigins) {
|
|
64
|
+
if (!requestOrigin) return null;
|
|
65
|
+
for (const entry of allowedOrigins) {
|
|
66
|
+
if (!entry) continue;
|
|
67
|
+
if (!entry.includes("*")) {
|
|
68
|
+
if (entry === requestOrigin) return requestOrigin;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!entry.includes("://")) continue;
|
|
72
|
+
const pattern = "^" + escapeRegExp(entry).replace(/(?:\\\*)+/g, "[^.]+") + "$";
|
|
73
|
+
try {
|
|
74
|
+
if (new RegExp(pattern).test(requestOrigin)) return requestOrigin;
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
60
80
|
export {
|
|
61
81
|
getCorsOrigins,
|
|
82
|
+
isOriginAllowed,
|
|
62
83
|
normalizeCorsEnvironment,
|
|
63
84
|
normalizeOrigin
|
|
64
85
|
};
|
package/dist/styles/classes.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { auth } from "@nextsparkjs/core/lib/auth";
|
|
3
|
-
import { mutateWithRLS } from "@nextsparkjs/core/lib/db";
|
|
4
3
|
import { withRateLimitTier } from "@nextsparkjs/core/lib/api/rate-limit";
|
|
4
|
+
import { UserService } from "@nextsparkjs/core/lib/services";
|
|
5
5
|
|
|
6
6
|
export const DELETE = withRateLimitTier(async (req: NextRequest) => {
|
|
7
7
|
try {
|
|
@@ -17,35 +17,37 @@ export const DELETE = withRateLimitTier(async (req: NextRequest) => {
|
|
|
17
17
|
const userId = session.user.id;
|
|
18
18
|
|
|
19
19
|
try {
|
|
20
|
-
//
|
|
20
|
+
// Anonymize the account: scrubs user metadata, frees the UNIQUE email,
|
|
21
|
+
// strips PII, and revokes every session + stored credential. A hard
|
|
22
|
+
// DELETE fails under foreign-key constraints; anonymizing preserves
|
|
23
|
+
// referential integrity. Throws code 'OWNS_TEAMS' if the user still
|
|
24
|
+
// owns teams (ownership must be transferred or those teams deleted first).
|
|
25
|
+
await UserService.anonymizeAccount(userId);
|
|
26
|
+
|
|
27
|
+
// Sessions are already revoked server-side; clear the current session
|
|
28
|
+
// cookie on this device too.
|
|
21
29
|
try {
|
|
22
|
-
await auth.api.signOut({
|
|
23
|
-
headers: req.headers,
|
|
24
|
-
});
|
|
30
|
+
await auth.api.signOut({ headers: req.headers });
|
|
25
31
|
} catch (signOutError) {
|
|
26
|
-
console.warn("Sign out failed (
|
|
32
|
+
console.warn("Sign out after account deletion failed (already revoked):", signOutError);
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
await mutateWithRLS(
|
|
32
|
-
'DELETE FROM "users" WHERE id = $1',
|
|
33
|
-
[userId],
|
|
34
|
-
userId
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
return NextResponse.json({
|
|
38
|
-
message: "Account deleted successfully"
|
|
35
|
+
return NextResponse.json({
|
|
36
|
+
message: "Account deleted successfully",
|
|
39
37
|
});
|
|
40
|
-
|
|
41
38
|
} catch (dbError) {
|
|
39
|
+
if ((dbError as { code?: string })?.code === "OWNS_TEAMS") {
|
|
40
|
+
return NextResponse.json(
|
|
41
|
+
{ error: (dbError as Error).message },
|
|
42
|
+
{ status: 409 }
|
|
43
|
+
);
|
|
44
|
+
}
|
|
42
45
|
console.error("Failed to delete user account:", dbError);
|
|
43
46
|
return NextResponse.json(
|
|
44
47
|
{ error: "Failed to delete account" },
|
|
45
48
|
{ status: 500 }
|
|
46
49
|
);
|
|
47
50
|
}
|
|
48
|
-
|
|
49
51
|
} catch (error) {
|
|
50
52
|
console.error("Delete account error:", error);
|
|
51
53
|
return NextResponse.json(
|
|
@@ -116,21 +116,15 @@ export const GET = withRateLimitTier(withApiLogging(async (
|
|
|
116
116
|
// Get user metadata value using UserService
|
|
117
117
|
const metaValue = await UserService.getUserMeta(id, key, authResult.user.id)
|
|
118
118
|
|
|
119
|
-
// Return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
'META_NOT_FOUND'
|
|
126
|
-
)
|
|
127
|
-
return addCorsHeaders(response)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Return metadata value
|
|
119
|
+
// Return 200 with value:null for an unset-but-valid key. A missing optional
|
|
120
|
+
// meta value is a normal state, not an error — a 404 here forces every
|
|
121
|
+
// client that reads an optional key to swallow a network 4xx (which the
|
|
122
|
+
// browser still logs as a failed request). The key itself was already
|
|
123
|
+
// validated above (INVALID_META_KEY), so reaching here means the key is
|
|
124
|
+
// valid and simply has no stored value yet.
|
|
131
125
|
const response = createApiResponse({
|
|
132
126
|
key,
|
|
133
|
-
value: metaValue,
|
|
127
|
+
value: metaValue ?? null,
|
|
134
128
|
})
|
|
135
129
|
return addCorsHeaders(response)
|
|
136
130
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.170",
|
|
4
4
|
"description": "NextSpark - The complete SaaS framework for Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "NextSpark <hello@nextspark.dev>",
|
|
@@ -469,7 +469,7 @@
|
|
|
469
469
|
"tailwind-merge": "^3.3.1",
|
|
470
470
|
"uuid": "^13.0.0",
|
|
471
471
|
"zod": "^4.1.5",
|
|
472
|
-
"@nextsparkjs/testing": "0.1.0-beta.
|
|
472
|
+
"@nextsparkjs/testing": "0.1.0-beta.170"
|
|
473
473
|
},
|
|
474
474
|
"scripts": {
|
|
475
475
|
"postinstall": "node scripts/postinstall.mjs || true",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { auth } from "@nextsparkjs/core/lib/auth";
|
|
3
|
-
import { mutateWithRLS } from "@nextsparkjs/core/lib/db";
|
|
4
3
|
import { withRateLimitTier } from "@nextsparkjs/core/lib/api/rate-limit";
|
|
4
|
+
import { UserService } from "@nextsparkjs/core/lib/services";
|
|
5
5
|
|
|
6
6
|
export const DELETE = withRateLimitTier(async (req: NextRequest) => {
|
|
7
7
|
try {
|
|
@@ -17,35 +17,37 @@ export const DELETE = withRateLimitTier(async (req: NextRequest) => {
|
|
|
17
17
|
const userId = session.user.id;
|
|
18
18
|
|
|
19
19
|
try {
|
|
20
|
-
//
|
|
20
|
+
// Anonymize the account: scrubs user metadata, frees the UNIQUE email,
|
|
21
|
+
// strips PII, and revokes every session + stored credential. A hard
|
|
22
|
+
// DELETE fails under foreign-key constraints; anonymizing preserves
|
|
23
|
+
// referential integrity. Throws code 'OWNS_TEAMS' if the user still
|
|
24
|
+
// owns teams (ownership must be transferred or those teams deleted first).
|
|
25
|
+
await UserService.anonymizeAccount(userId);
|
|
26
|
+
|
|
27
|
+
// Sessions are already revoked server-side; clear the current session
|
|
28
|
+
// cookie on this device too.
|
|
21
29
|
try {
|
|
22
|
-
await auth.api.signOut({
|
|
23
|
-
headers: req.headers,
|
|
24
|
-
});
|
|
30
|
+
await auth.api.signOut({ headers: req.headers });
|
|
25
31
|
} catch (signOutError) {
|
|
26
|
-
console.warn("Sign out failed (
|
|
32
|
+
console.warn("Sign out after account deletion failed (already revoked):", signOutError);
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
await mutateWithRLS(
|
|
32
|
-
'DELETE FROM "users" WHERE id = $1',
|
|
33
|
-
[userId],
|
|
34
|
-
userId
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
return NextResponse.json({
|
|
38
|
-
message: "Account deleted successfully"
|
|
35
|
+
return NextResponse.json({
|
|
36
|
+
message: "Account deleted successfully",
|
|
39
37
|
});
|
|
40
|
-
|
|
41
38
|
} catch (dbError) {
|
|
39
|
+
if ((dbError as { code?: string })?.code === "OWNS_TEAMS") {
|
|
40
|
+
return NextResponse.json(
|
|
41
|
+
{ error: (dbError as Error).message },
|
|
42
|
+
{ status: 409 }
|
|
43
|
+
);
|
|
44
|
+
}
|
|
42
45
|
console.error("Failed to delete user account:", dbError);
|
|
43
46
|
return NextResponse.json(
|
|
44
47
|
{ error: "Failed to delete account" },
|
|
45
48
|
{ status: 500 }
|
|
46
49
|
);
|
|
47
50
|
}
|
|
48
|
-
|
|
49
51
|
} catch (error) {
|
|
50
52
|
console.error("Delete account error:", error);
|
|
51
53
|
return NextResponse.json(
|
|
@@ -116,21 +116,15 @@ export const GET = withRateLimitTier(withApiLogging(async (
|
|
|
116
116
|
// Get user metadata value using UserService
|
|
117
117
|
const metaValue = await UserService.getUserMeta(id, key, authResult.user.id)
|
|
118
118
|
|
|
119
|
-
// Return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
'META_NOT_FOUND'
|
|
126
|
-
)
|
|
127
|
-
return addCorsHeaders(response)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Return metadata value
|
|
119
|
+
// Return 200 with value:null for an unset-but-valid key. A missing optional
|
|
120
|
+
// meta value is a normal state, not an error — a 404 here forces every
|
|
121
|
+
// client that reads an optional key to swallow a network 4xx (which the
|
|
122
|
+
// browser still logs as a failed request). The key itself was already
|
|
123
|
+
// validated above (INVALID_META_KEY), so reaching here means the key is
|
|
124
|
+
// valid and simply has no stored value yet.
|
|
131
125
|
const response = createApiResponse({
|
|
132
126
|
key,
|
|
133
|
-
value: metaValue,
|
|
127
|
+
value: metaValue ?? null,
|
|
134
128
|
})
|
|
135
129
|
return addCorsHeaders(response)
|
|
136
130
|
} catch (error) {
|