@getstrata/core 0.5.100 → 0.7.3

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +67 -32
  2. package/README.md +16 -35
  3. package/dist/core/auth/abilityCatalog.d.ts +2 -2
  4. package/dist/core/auth/basicAuthGuard.d.ts +9 -0
  5. package/dist/core/auth/guard.d.ts +6 -0
  6. package/dist/core/auth/jwt.d.ts +19 -0
  7. package/dist/core/auth/jwtGuard.d.ts +14 -0
  8. package/dist/core/auth/tokenAbilityChecker.d.ts +5 -0
  9. package/dist/core/cache/tags.d.ts +6 -0
  10. package/dist/core/contracts/authUserDirectory.d.ts +4 -0
  11. package/dist/core/database/baseRepository.d.ts +5 -1
  12. package/dist/core/database/dialect.d.ts +18 -0
  13. package/dist/core/database/factory.d.ts +1 -0
  14. package/dist/core/database/index.d.ts +11 -3
  15. package/dist/core/database/model.d.ts +21 -2
  16. package/dist/core/database/mysqlConnection.d.ts +12 -0
  17. package/dist/core/database/namedConnections.d.ts +15 -0
  18. package/dist/core/database/relationQuery.d.ts +22 -3
  19. package/dist/core/database/relationships.d.ts +22 -2
  20. package/dist/core/database/repositoryQuery.d.ts +5 -1
  21. package/dist/core/database/sqliteConnection.d.ts +7 -0
  22. package/dist/core/http/loginThrottleMiddleware.d.ts +5 -2
  23. package/dist/core/http/resources.d.ts +2 -2
  24. package/dist/core/http/response.d.ts +2 -1
  25. package/dist/core/http/statelessAuth.d.ts +8 -0
  26. package/dist/core/http/throttleResponse.d.ts +2 -0
  27. package/dist/core/runtime/frontendMode.d.ts +10 -2
  28. package/dist/entries/auth/basicAuthGuard.js +137 -0
  29. package/dist/entries/auth/jwt.js +135 -0
  30. package/dist/entries/auth/jwtGuard.js +203 -0
  31. package/dist/entries/auth/sessionGuard.js +3 -21
  32. package/dist/entries/auth/tokenAbilityChecker.js +24 -0
  33. package/dist/entries/cache/tags.js +7 -1
  34. package/dist/entries/database/connectionContext.js +1 -0
  35. package/dist/entries/database/dialect.js +1 -0
  36. package/dist/entries/database/factory.js +5 -4
  37. package/dist/entries/database/model.js +189 -33
  38. package/dist/entries/database/mysqlConnection.js +35 -0
  39. package/dist/entries/database/namedConnections.js +1 -0
  40. package/dist/entries/database/query.js +28 -15
  41. package/dist/entries/database/relationships.js +43 -6
  42. package/dist/entries/database/repositoryQuery.js +142 -73
  43. package/dist/entries/database/schema.js +28 -15
  44. package/dist/entries/database/sqliteConnection.js +34 -0
  45. package/dist/entries/facades.js +1 -1
  46. package/dist/entries/http/contentNegotiation.js +5 -2
  47. package/dist/entries/http/csrfMiddleware.js +45 -0
  48. package/dist/entries/http/loginThrottleMiddleware.js +246 -7
  49. package/dist/entries/http/memoryThrottleMiddleware.js +208 -6
  50. package/dist/entries/http/requireAbilityMiddleware.js +35 -11
  51. package/dist/entries/http/requirePasswordConfirmMiddleware.js +5 -2
  52. package/dist/entries/http/requireVerifiedMiddleware.js +5 -2
  53. package/dist/entries/http/requireWebAuthMiddleware.js +12 -3
  54. package/dist/entries/http/resources.js +4 -1
  55. package/dist/entries/http/response.js +46 -12
  56. package/dist/entries/http/statelessAuth.js +48 -0
  57. package/dist/entries/http/throttleMiddleware.js +208 -6
  58. package/dist/entries/http/webErrorResponse.js +35 -11
  59. package/dist/entries/http/webFormRequest.js +5 -2
  60. package/dist/entries/mail/mailer.js +1 -1
  61. package/dist/entries/openapi/generator.js +48 -5
  62. package/dist/entries/runtime/frontendMode.js +39 -10
  63. package/dist/framework/public-api.d.ts +11 -1
  64. package/dist/index.js +1068 -250
  65. package/package.json +56 -5
@@ -0,0 +1,203 @@
1
+ // @bun
2
+ // ../../src/core/http/statelessAuth.ts
3
+ function authorizationScheme(request) {
4
+ const header = request.headers.get("authorization")?.trim() ?? "";
5
+ const scheme = header.split(/\s+/, 1)[0];
6
+ return scheme ? scheme.toLowerCase() : "";
7
+ }
8
+ function requestUsesHeaderCredentials(request) {
9
+ const scheme = authorizationScheme(request);
10
+ return scheme === "bearer" || scheme === "basic";
11
+ }
12
+ function readBearerToken(request) {
13
+ const header = request.headers.get("authorization")?.trim() ?? "";
14
+ if (!header.toLowerCase().startsWith("bearer ")) {
15
+ return null;
16
+ }
17
+ const token = header.slice("Bearer ".length).trim();
18
+ return token.length > 0 ? token : null;
19
+ }
20
+ function readBasicCredentials(request) {
21
+ const header = request.headers.get("authorization")?.trim() ?? "";
22
+ if (!header.toLowerCase().startsWith("basic ")) {
23
+ return null;
24
+ }
25
+ const encoded = header.slice("Basic ".length).trim();
26
+ if (!encoded) {
27
+ return null;
28
+ }
29
+ try {
30
+ const decoded = Buffer.from(encoded, "base64").toString("utf8");
31
+ const separator = decoded.indexOf(":");
32
+ if (separator < 0) {
33
+ return null;
34
+ }
35
+ return {
36
+ username: decoded.slice(0, separator),
37
+ password: decoded.slice(separator + 1)
38
+ };
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ // ../../src/core/auth/jwt.ts
45
+ import { createHmac, timingSafeEqual } from "crypto";
46
+
47
+ // ../../src/core/runtime/appKeyPrefix.ts
48
+ function appKeyPrefix() {
49
+ return process.env.APP_KEY_PREFIX?.trim() || "strata";
50
+ }
51
+ function appCookieName(kind) {
52
+ return `${appKeyPrefix()}_${kind}`;
53
+ }
54
+ function appDevSecret(kind) {
55
+ return `${appKeyPrefix()}-dev-${kind}`;
56
+ }
57
+ function namespacedRedisKey(kind) {
58
+ return `${appKeyPrefix()}:${kind}`;
59
+ }
60
+ function smtpEhloHost() {
61
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
62
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
63
+ return safe || "strata.local";
64
+ }
65
+ function siemEventType() {
66
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
67
+ }
68
+ function appUserAgent() {
69
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
70
+ }
71
+ function otelServiceName() {
72
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
73
+ }
74
+ function webhookSignatureHeader() {
75
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
76
+ }
77
+ function appDisplayName() {
78
+ return process.env.APP_NAME?.trim() || "Strata";
79
+ }
80
+ function appEnv() {
81
+ return process.env.APP_ENV?.trim() || "local";
82
+ }
83
+ function appUrl() {
84
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
85
+ }
86
+ function apiPrefix() {
87
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
88
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
89
+ const trimmed = withSlash.replace(/\/+$/, "");
90
+ return trimmed || "/api/v1";
91
+ }
92
+ function sdkClientClassName() {
93
+ const override = process.env.APP_SDK_CLASS?.trim();
94
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
95
+ return override;
96
+ }
97
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
98
+ return fromName ? `${fromName}Client` : "AppClient";
99
+ }
100
+
101
+ // ../../src/core/auth/jwt.ts
102
+ function resolveJwtSecret(secret) {
103
+ return secret?.trim() || process.env.JWT_SECRET?.trim() || process.env.SESSION_SECRET?.trim() || appDevSecret("jwt-secret");
104
+ }
105
+ function jwtTtlSeconds(override) {
106
+ if (typeof override === "number" && Number.isInteger(override) && override > 0) {
107
+ return override;
108
+ }
109
+ const parsed = Number.parseInt(process.env.JWT_TTL_SECONDS ?? "", 10);
110
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : 3600;
111
+ }
112
+ function encodeJson(value) {
113
+ return Buffer.from(JSON.stringify(value)).toString("base64url");
114
+ }
115
+ function decodeJson(value) {
116
+ try {
117
+ return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+ function signPart(headerAndPayload, secret) {
123
+ return createHmac("sha256", secret).update(headerAndPayload).digest("base64url");
124
+ }
125
+ function signaturesMatch(left, right) {
126
+ const leftBuffer = Buffer.from(left);
127
+ const rightBuffer = Buffer.from(right);
128
+ if (leftBuffer.length !== rightBuffer.length) {
129
+ return false;
130
+ }
131
+ return timingSafeEqual(leftBuffer, rightBuffer);
132
+ }
133
+ function signJwt(payload, options = {}) {
134
+ const now = Math.floor(Date.now() / 1000);
135
+ const body = {
136
+ ...payload,
137
+ iat: now,
138
+ exp: now + jwtTtlSeconds(options.ttlSeconds)
139
+ };
140
+ const header = encodeJson({ alg: "HS256", typ: "JWT" });
141
+ const data = encodeJson(body);
142
+ const unsigned = `${header}.${data}`;
143
+ const signature = signPart(unsigned, resolveJwtSecret(options.secret));
144
+ return `${unsigned}.${signature}`;
145
+ }
146
+ function verifyJwt(token, secret) {
147
+ const parts = token.split(".");
148
+ if (parts.length !== 3) {
149
+ return null;
150
+ }
151
+ const [header, data, signature] = parts;
152
+ if (!header || !data || !signature) {
153
+ return null;
154
+ }
155
+ const expected = signPart(`${header}.${data}`, resolveJwtSecret(secret));
156
+ if (!signaturesMatch(signature, expected)) {
157
+ return null;
158
+ }
159
+ const parsedHeader = decodeJson(header);
160
+ if (parsedHeader?.alg !== "HS256") {
161
+ return null;
162
+ }
163
+ const payload = decodeJson(data);
164
+ if (!payload || payload.sub === undefined || payload.sub === null) {
165
+ return null;
166
+ }
167
+ if (typeof payload.exp === "number" && payload.exp * 1000 <= Date.now()) {
168
+ return null;
169
+ }
170
+ return payload;
171
+ }
172
+
173
+ // ../../src/core/auth/jwtGuard.ts
174
+ function authUserFromJwt(payload) {
175
+ return {
176
+ id: payload.sub,
177
+ ...payload.role ? { role: String(payload.role) } : {},
178
+ ...Array.isArray(payload.abilities) ? { abilities: payload.abilities.map(String) } : {},
179
+ ...payload.emailVerifiedAt !== undefined ? { emailVerifiedAt: payload.emailVerifiedAt } : {}
180
+ };
181
+ }
182
+
183
+ class JwtGuard {
184
+ options;
185
+ constructor(options = {}) {
186
+ this.options = options;
187
+ }
188
+ resolve(request) {
189
+ const token = readBearerToken(request);
190
+ if (token?.split(".").length !== 3) {
191
+ return null;
192
+ }
193
+ const payload = verifyJwt(token, this.options.secret);
194
+ if (!payload) {
195
+ return null;
196
+ }
197
+ return authUserFromJwt(payload);
198
+ }
199
+ }
200
+ export {
201
+ JwtGuard,
202
+ authUserFromJwt
203
+ };
@@ -46,35 +46,17 @@ function resolveAuthUserDirectory(container) {
46
46
 
47
47
  // ../../src/core/auth/abilityCatalog.ts
48
48
  var MEMBER_ABILITIES = [
49
- "organizations:read",
50
- "organizations:create",
51
- "projects:read",
52
- "projects:create",
53
- "tasks:read",
54
- "tasks:create",
55
- "comments:read",
56
- "comments:create",
57
- "attachments:read",
58
- "attachments:create",
49
+ "profile:read",
59
50
  "auth:tokens:read",
60
51
  "auth:tokens:write",
61
52
  "auth:tokens:delete"
62
53
  ];
63
54
  var ADMIN_ABILITIES = [
64
55
  ...MEMBER_ABILITIES,
65
- "organizations:create",
66
- "organizations:update",
67
- "organizations:delete",
68
- "projects:update",
69
- "projects:delete",
70
- "tasks:update",
71
- "tasks:delete",
72
- "comments:update",
73
- "comments:delete",
74
- "attachments:delete",
75
56
  "webhooks:read",
76
57
  "webhooks:write",
77
- "audit:read"
58
+ "audit:read",
59
+ "audit:export"
78
60
  ];
79
61
  var PLATFORM_ADMIN_ABILITIES = ["*"];
80
62
  function resolveAbilitiesForRole(role) {
@@ -0,0 +1,24 @@
1
+ // @bun
2
+ // ../../src/core/auth/tokenAbilityChecker.ts
3
+ import { ForbiddenError } from "@getstrata/core/errors/http";
4
+ function tokenCan(user, ability) {
5
+ if (!user) {
6
+ return false;
7
+ }
8
+ const abilities = user.abilities ?? [];
9
+ return abilities.includes("*") || abilities.includes(ability);
10
+ }
11
+ function createTokenAbilityChecker() {
12
+ return {
13
+ tokenCan,
14
+ requireAbility(user, ability) {
15
+ if (!tokenCan(user, ability)) {
16
+ throw new ForbiddenError(`Missing ability: ${ability}`);
17
+ }
18
+ }
19
+ };
20
+ }
21
+ export {
22
+ createTokenAbilityChecker,
23
+ tokenCan
24
+ };
@@ -6,7 +6,13 @@ var CACHE_TAGS = {
6
6
  tasks: "tasks",
7
7
  comments: "comments",
8
8
  attachments: "attachments",
9
- reports: "reports"
9
+ reports: "reports",
10
+ users: "users",
11
+ departments: "departments",
12
+ positions: "positions",
13
+ applications: "applications",
14
+ careers: "careers",
15
+ offers: "offers"
10
16
  };
11
17
  export {
12
18
  CACHE_TAGS
@@ -0,0 +1 @@
1
+ export * from "../../index.js";
@@ -0,0 +1 @@
1
+ export * from "../../index.js";
@@ -151,10 +151,11 @@ class Factory {
151
151
  return made;
152
152
  }
153
153
  async createOne(overrides = {}) {
154
- const created = await this.persist(this.insertable(this.makeOne(overrides)));
155
- for (const child of this.children) {
156
- await child.factory.for(created, child.foreignKey).create();
157
- }
154
+ return this.persistCreated(this.makeOne(overrides));
155
+ }
156
+ async persistCreated(record) {
157
+ const created = await this.persist(this.insertable(record));
158
+ await Promise.all(this.children.map((child) => child.factory.for(created, child.foreignKey).create()));
158
159
  for (const callback of this.afterCreatingCallbacks) {
159
160
  await callback(created);
160
161
  }