@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
@@ -1,5 +1,5 @@
1
1
  import type BaseRepository from "./baseRepository.ts";
2
- import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation } from "./relationships.ts";
2
+ import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasManyThroughRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation } from "./relationships.ts";
3
3
  import type { RepositoryQuery } from "./repositoryQuery.ts";
4
4
  import type { QueryOptions, QueryWhere } from "./types.ts";
5
5
  type RelatedRecord = {
@@ -17,7 +17,7 @@ interface RelationHost<TEntity extends object, PrimaryKey extends keyof TEntity
17
17
  get<K extends keyof TEntity>(key: K): TEntity[K];
18
18
  getRepository(): BaseRepository<TEntity, PrimaryKey>;
19
19
  }
20
- type RelationKind = "hasMany" | "hasOne" | "belongsTo" | "belongsToMany" | "morphMany" | "morphOne" | "morphTo";
20
+ type RelationKind = "hasMany" | "hasOne" | "belongsTo" | "belongsToMany" | "hasManyThrough" | "morphMany" | "morphOne" | "morphTo";
21
21
  type ExistsClause = {
22
22
  sql: string;
23
23
  params: unknown[];
@@ -159,6 +159,25 @@ declare class MorphToRelationQuery<TChild extends object, ChildKey extends keyof
159
159
  get(): Promise<RelatedRecord | null>;
160
160
  then(onfulfilled?: ((value: RelatedRecord | null) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
161
161
  }
162
+ declare class HasManyThroughRelationQuery<TParent extends object, ParentKey extends keyof TParent & string, TFar extends object, FarKey extends keyof TFar & string> {
163
+ private readonly parent;
164
+ private readonly related;
165
+ readonly relation: HasManyThroughRelation<TParent, TFar, ParentKey, string, string, keyof TFar & string>;
166
+ readonly kind: RelationKind;
167
+ private extraWhere;
168
+ private extraOptions;
169
+ constructor(parent: RelationHost<TParent, ParentKey>, related: RelatedModelClass<TFar, FarKey>, relation: HasManyThroughRelation<TParent, TFar, ParentKey, string, string, keyof TFar & string>);
170
+ where(where: QueryWhere<TFar>): this;
171
+ orderBy(orderBy: QueryOptions<TFar>["orderBy"]): this;
172
+ limit(limit: number): this;
173
+ applyEagerLoad(query: RepositoryQuery<TParent, ParentKey>, alias: string): void;
174
+ hydrateEager(row: Record<string, unknown>, alias: string): unknown;
175
+ toExistsClause(parentTable: string): ExistsClause;
176
+ get(): Promise<RelatedRecord[]>;
177
+ first(): Promise<RelatedRecord | null>;
178
+ count(): Promise<number>;
179
+ then(onfulfilled?: ((value: RelatedRecord[]) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
180
+ }
162
181
  type AnyRelationQuery = {
163
182
  kind: RelationKind;
164
183
  applyEagerLoad(query: unknown, alias: string): void;
@@ -169,4 +188,4 @@ type AnyRelationQuery = {
169
188
  then?: (onfulfilled?: ((value: unknown) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null) => Promise<unknown>;
170
189
  };
171
190
  export type { AnyRelationQuery, RelatedModelClass, RelatedRecord, RelationHost };
172
- export { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, };
191
+ export { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, };
@@ -25,6 +25,16 @@ interface BelongsToManyRelation<TParent, TRelated, Pivot extends object, ParentK
25
25
  foreignPivotKey: ForeignPivotKey;
26
26
  relatedPivotKey: RelatedPivotKey;
27
27
  }
28
+ interface HasManyThroughRelation<TParent, TFar, LocalKey extends keyof TParent & string = keyof TParent & string, FirstKey extends string = string, SecondLocalKey extends string = string, SecondKey extends keyof TFar & string = keyof TFar & string> {
29
+ type: "hasManyThrough";
30
+ name: string;
31
+ throughTable: string;
32
+ localKey: LocalKey;
33
+ firstKey: FirstKey;
34
+ secondLocalKey: SecondLocalKey;
35
+ secondKey: SecondKey;
36
+ throughParentKey?: string;
37
+ }
28
38
  declare function hasMany<TParent, TChild, LocalKey extends keyof TParent & string = keyof TParent & string, ForeignKey extends keyof TChild & string = keyof TChild & string>(definition: {
29
39
  name: string;
30
40
  localKey: LocalKey;
@@ -40,6 +50,15 @@ declare function belongsTo<TChild, TParent, ForeignKey extends keyof TChild & st
40
50
  foreignKey: ForeignKey;
41
51
  ownerKey: OwnerKey;
42
52
  }): BelongsToRelation<TChild, TParent, ForeignKey, OwnerKey>;
53
+ declare function hasManyThrough<TParent, TFar, LocalKey extends keyof TParent & string = keyof TParent & string, FirstKey extends string = string, SecondLocalKey extends string = string, SecondKey extends keyof TFar & string = keyof TFar & string>(definition: {
54
+ name: string;
55
+ throughTable: string;
56
+ localKey: LocalKey;
57
+ firstKey: FirstKey;
58
+ secondLocalKey: SecondLocalKey;
59
+ secondKey: SecondKey;
60
+ throughParentKey?: string;
61
+ }): HasManyThroughRelation<TParent, TFar, LocalKey, FirstKey, SecondLocalKey, SecondKey>;
43
62
  declare function belongsToMany<TParent, TRelated, Pivot extends object, ParentKey extends keyof TParent & string = keyof TParent & string, RelatedKey extends keyof TRelated & string = keyof TRelated & string, ForeignPivotKey extends keyof Pivot & string = keyof Pivot & string, RelatedPivotKey extends keyof Pivot & string = keyof Pivot & string>(definition: {
44
63
  name: string;
45
64
  pivotTable: string;
@@ -97,6 +116,7 @@ declare function morphTo<TChild, MorphTypeKey extends keyof TChild & string = ke
97
116
  }): MorphToRelation<TChild, MorphTypeKey, MorphIdKey>;
98
117
  declare function indexMorphManyRelation<TParent, TChild, LocalKey extends keyof TParent & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string>(parents: readonly TParent[], children: readonly TChild[], relation: MorphManyRelation<TParent, TChild, LocalKey, MorphTypeKey, MorphIdKey>): Map<TParent[LocalKey], TChild[]>;
99
118
  declare function indexMorphOneRelation<TParent, TChild, LocalKey extends keyof TParent & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string>(parents: readonly TParent[], children: readonly TChild[], relation: MorphOneRelation<TParent, TChild, LocalKey, MorphTypeKey, MorphIdKey>): Map<TParent[LocalKey], TChild | undefined>;
119
+ declare function indexHasManyThroughRelation<TParent, TFar, LocalKey extends keyof TParent & string, FirstKey extends string, SecondLocalKey extends string, SecondKey extends keyof TFar & string>(parents: readonly TParent[], children: readonly (TFar & Record<string, unknown>)[], relation: HasManyThroughRelation<TParent, TFar, LocalKey, FirstKey, SecondLocalKey, SecondKey>): Map<TParent[LocalKey], TFar[]>;
100
120
  declare function indexMorphToRelation<TChild, TParent extends object, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, _OwnerKey extends keyof TParent & string = keyof TParent & string>(children: readonly TChild[], parentsByType: ReadonlyMap<string, ReadonlyMap<unknown, TParent>>, relation: MorphToRelation<TChild, MorphTypeKey, MorphIdKey>): Map<TChild[MorphIdKey], TParent>;
101
- export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, };
102
- export { belongsTo, belongsToMany, getByRelationKey, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, relationMatchKey, };
121
+ export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasManyThroughRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, };
122
+ export { belongsTo, belongsToMany, getByRelationKey, hasMany, hasManyThrough, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasManyThroughRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, relationMatchKey, };
@@ -1,6 +1,6 @@
1
1
  import type { PaginatedResult } from "../pagination/index.ts";
2
2
  import type BaseRepository from "./baseRepository.ts";
3
- import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, MorphManyRelation, MorphOneRelation, MorphToRelation } from "./relationships.ts";
3
+ import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasManyThroughRelation, MorphManyRelation, MorphOneRelation, MorphToRelation } from "./relationships.ts";
4
4
  import type { QueryOptions, QueryWhere } from "./types.ts";
5
5
  import { WhereBuilder } from "./whereBuilder.ts";
6
6
  type LoadedRow = Record<string, unknown>;
@@ -31,6 +31,9 @@ declare class RepositoryQuery<TEntity extends object, PrimaryKey extends keyof T
31
31
  withMorphOne<TChild extends object, LocalKey extends keyof TEntity & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, Alias extends string>(as: Alias, relation: MorphOneRelation<TEntity, TChild, LocalKey, MorphTypeKey, MorphIdKey>, childRepository: BaseRepository<TChild, keyof TChild & string>, options?: Omit<QueryOptions<TChild>, "where">): this;
32
32
  withMorphTo<TParent extends object, MorphTypeKey extends keyof TEntity & string, MorphIdKey extends keyof TEntity & string, Alias extends string>(as: Alias, relation: MorphToRelation<TEntity, MorphTypeKey, MorphIdKey>, repositoriesByType: ReadonlyMap<string, BaseRepository<TParent, keyof TParent & string>>, options?: Omit<QueryOptions<TParent>, "where">): this;
33
33
  withBelongsToMany<TRelated extends object, Pivot extends object, ParentKey extends keyof TEntity & string, RelatedKey extends keyof TRelated & string, ForeignPivotKey extends keyof Pivot & string, RelatedPivotKey extends keyof Pivot & string, Alias extends string>(as: Alias, relation: BelongsToManyRelation<TEntity, TRelated, Pivot, ParentKey, RelatedKey, ForeignPivotKey, RelatedPivotKey>, relatedRepository: BaseRepository<TRelated, RelatedKey>, options?: Omit<QueryOptions<TRelated>, "where">): this;
34
+ withHasManyThrough<TFar extends object, LocalKey extends keyof TEntity & string, SecondKey extends keyof TFar & string, Alias extends string>(as: Alias, relation: HasManyThroughRelation<TEntity, TFar, LocalKey, string, string, SecondKey>, farRepository: BaseRepository<TFar, keyof TFar & string>, options?: Omit<QueryOptions<TFar>, "where">): this;
35
+ withTrashed(): this;
36
+ onlyTrashed(): this;
34
37
  get(): Promise<Array<TEntity & LoadedRow>>;
35
38
  first(): Promise<(TEntity & LoadedRow) | null>;
36
39
  count(): Promise<number>;
@@ -42,5 +45,6 @@ declare class RepositoryQuery<TEntity extends object, PrimaryKey extends keyof T
42
45
  private buildOptions;
43
46
  private addJoin;
44
47
  private attach;
48
+ private hydrateEagerLoad;
45
49
  }
46
50
  export { RepositoryQuery };
@@ -0,0 +1,7 @@
1
+ import type { ActiveDatabaseHandle } from "./connectionContext.ts";
2
+ type SqliteConnection = ActiveDatabaseHandle & {
3
+ close(): void;
4
+ };
5
+ declare function createSqliteConnection(filename: string): SqliteConnection;
6
+ export type { SqliteConnection };
7
+ export { createSqliteConnection };
@@ -1,11 +1,14 @@
1
1
  import type { Middleware } from "./middleware";
2
2
  interface LoginThrottleOptions {
3
- redisUrl: string;
3
+ redisUrl?: string;
4
4
  maxAttempts: number;
5
5
  decaySeconds: number;
6
6
  keyPrefix?: string;
7
7
  }
8
8
  declare function resolveLoginIdentity(request: Request): string;
9
+ declare function resolveLoginEmail(request: Request): Promise<string>;
10
+ declare function createMemoryLoginThrottleMiddleware(options: LoginThrottleOptions): Middleware;
9
11
  declare function createLoginThrottleMiddleware(options: LoginThrottleOptions): Middleware;
12
+ declare function resetMemoryLoginThrottleForTests(): void;
10
13
  export type { LoginThrottleOptions };
11
- export { createLoginThrottleMiddleware, resolveLoginIdentity };
14
+ export { createLoginThrottleMiddleware, createMemoryLoginThrottleMiddleware, resetMemoryLoginThrottleForTests, resolveLoginEmail, resolveLoginIdentity, };
@@ -1,7 +1,7 @@
1
1
  declare function serializeDate(value: Date | string): string;
2
2
  declare function whenLoaded<T>(model: {
3
3
  loaded: (name: string) => unknown;
4
- }, relation: string, transform?: (value: unknown) => T): T | undefined;
4
+ }, relation: string, transform?: (value: unknown) => T): T | undefined | null;
5
5
  declare class JsonResource<T = unknown> {
6
6
  protected readonly resource: T;
7
7
  static wrap: string | null;
@@ -11,7 +11,7 @@ declare class JsonResource<T = unknown> {
11
11
  static collection<TResource>(items: readonly TResource[]): ResourceCollection<TResource>;
12
12
  additional(data: Record<string, unknown>): this;
13
13
  when<TValue>(condition: boolean, value: TValue): TValue | undefined;
14
- whenLoaded<TValue = unknown>(relation: string, transform?: (value: unknown) => TValue): TValue | undefined;
14
+ whenLoaded<TValue = unknown>(relation: string, transform?: (value: unknown) => TValue): TValue | undefined | null;
15
15
  toArray(): Record<string, unknown>;
16
16
  toResponse(): Record<string, unknown>;
17
17
  }
@@ -3,4 +3,5 @@ declare function createdResponse(data: unknown, init?: ResponseInit): Response;
3
3
  declare function noContentResponse(): Response;
4
4
  declare function errorResponse(error: unknown): Response;
5
5
  declare function withErrorHandling<TArgs extends unknown[]>(handler: (...args: TArgs) => Response | Promise<Response>): (...args: TArgs) => Promise<Response>;
6
- export { createdResponse, errorResponse, jsonResponse, noContentResponse, withErrorHandling };
6
+ declare function withJsonErrorHandling<TArgs extends unknown[]>(handler: (...args: TArgs) => Response | Promise<Response>): (...args: TArgs) => Promise<Response>;
7
+ export { createdResponse, errorResponse, jsonResponse, noContentResponse, withErrorHandling, withJsonErrorHandling, };
@@ -0,0 +1,8 @@
1
+ declare function authorizationScheme(request: Request): string;
2
+ declare function requestUsesHeaderCredentials(request: Request): boolean;
3
+ declare function readBearerToken(request: Request): string | null;
4
+ declare function readBasicCredentials(request: Request): {
5
+ username: string;
6
+ password: string;
7
+ } | null;
8
+ export { authorizationScheme, readBasicCredentials, readBearerToken, requestUsesHeaderCredentials };
@@ -0,0 +1,2 @@
1
+ declare function tooManyRequestsResponse(request: Request, message: string, decaySeconds: number): Promise<Response>;
2
+ export { tooManyRequestsResponse };
@@ -1,6 +1,14 @@
1
- type FrontendMode = "api" | "server-htmx" | "spa-react";
1
+ declare const FRONTEND_MODES: readonly ["api", "server-htmx", "spa-react", "hybrid"];
2
+ declare const DEFAULT_SPA_PREFIX = "/app";
3
+ type FrontendMode = (typeof FRONTEND_MODES)[number];
4
+ declare const FRONTEND_MODE_PATTERN: RegExp;
5
+ declare function parseFrontendMode(value: string | undefined): FrontendMode;
2
6
  declare function readFrontendMode(): FrontendMode;
7
+ declare function isViewsMode(mode: FrontendMode): boolean;
8
+ declare function isSpaMode(mode: FrontendMode): boolean;
3
9
  declare function isViewsEnabled(): boolean;
4
10
  declare function isSpaEnabled(): boolean;
11
+ declare function normalizeSpaPrefix(value: string | undefined): string;
12
+ declare function readSpaPrefix(): string;
5
13
  export type { FrontendMode };
6
- export { isSpaEnabled, isViewsEnabled, readFrontendMode };
14
+ export { DEFAULT_SPA_PREFIX, FRONTEND_MODE_PATTERN, FRONTEND_MODES, isSpaEnabled, isSpaMode, isViewsEnabled, isViewsMode, normalizeSpaPrefix, parseFrontendMode, readFrontendMode, readSpaPrefix, };
@@ -0,0 +1,137 @@
1
+ // @bun
2
+ // ../../src/core/contracts/serviceTokens.ts
3
+ var CORE_CONFIG_TOKEN = "core.config";
4
+ var CORE_CACHE_TOKEN = "core.cache";
5
+ var CORE_QUEUE_TOKEN = "core.queue";
6
+ var CORE_EVENT_BUS_TOKEN = "core.eventBus";
7
+ var CORE_POLICY_GATE_TOKEN = "core.policyGate";
8
+ var CORE_AUTH_TOKEN = "core.auth";
9
+ var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
10
+ var CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
11
+ var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
12
+ function isAbilityChecker(value) {
13
+ if (!value || typeof value !== "object") {
14
+ return false;
15
+ }
16
+ const candidate = value;
17
+ return typeof candidate.tokenCan === "function" && typeof candidate.requireAbility === "function";
18
+ }
19
+ function isAuthUserDirectory(value) {
20
+ if (!value || typeof value !== "object") {
21
+ return false;
22
+ }
23
+ return typeof value.findByIdOrThrow === "function";
24
+ }
25
+ function resolveAbilityChecker(container) {
26
+ if (container.has(CORE_ABILITY_CHECKER_TOKEN)) {
27
+ return container.resolve(CORE_ABILITY_CHECKER_TOKEN);
28
+ }
29
+ return container.resolve(CORE_TOKEN_SERVICE_TOKEN);
30
+ }
31
+ function resolveAuthUserDirectory(container) {
32
+ if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
33
+ const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
34
+ if (isAuthUserDirectory(directory)) {
35
+ return directory;
36
+ }
37
+ }
38
+ if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
39
+ const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
40
+ if (isAuthUserDirectory(tokenService)) {
41
+ return tokenService;
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+
47
+ // ../../src/core/http/statelessAuth.ts
48
+ function authorizationScheme(request) {
49
+ const header = request.headers.get("authorization")?.trim() ?? "";
50
+ const scheme = header.split(/\s+/, 1)[0];
51
+ return scheme ? scheme.toLowerCase() : "";
52
+ }
53
+ function requestUsesHeaderCredentials(request) {
54
+ const scheme = authorizationScheme(request);
55
+ return scheme === "bearer" || scheme === "basic";
56
+ }
57
+ function readBearerToken(request) {
58
+ const header = request.headers.get("authorization")?.trim() ?? "";
59
+ if (!header.toLowerCase().startsWith("bearer ")) {
60
+ return null;
61
+ }
62
+ const token = header.slice("Bearer ".length).trim();
63
+ return token.length > 0 ? token : null;
64
+ }
65
+ function readBasicCredentials(request) {
66
+ const header = request.headers.get("authorization")?.trim() ?? "";
67
+ if (!header.toLowerCase().startsWith("basic ")) {
68
+ return null;
69
+ }
70
+ const encoded = header.slice("Basic ".length).trim();
71
+ if (!encoded) {
72
+ return null;
73
+ }
74
+ try {
75
+ const decoded = Buffer.from(encoded, "base64").toString("utf8");
76
+ const separator = decoded.indexOf(":");
77
+ if (separator < 0) {
78
+ return null;
79
+ }
80
+ return {
81
+ username: decoded.slice(0, separator),
82
+ password: decoded.slice(separator + 1)
83
+ };
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ // ../../src/core/auth/password.ts
90
+ async function hashPassword(password) {
91
+ return await Bun.password.hash(password, {
92
+ algorithm: "bcrypt",
93
+ cost: 10
94
+ });
95
+ }
96
+ async function verifyPassword(password, passwordHash) {
97
+ return await Bun.password.verify(password, passwordHash);
98
+ }
99
+
100
+ // ../../src/core/auth/basicAuthGuard.ts
101
+ class BasicAuthGuard {
102
+ container;
103
+ constructor(container) {
104
+ this.container = container;
105
+ }
106
+ async resolve(request) {
107
+ const credentials = readBasicCredentials(request);
108
+ if (!credentials) {
109
+ return null;
110
+ }
111
+ const directory = resolveAuthUserDirectory(this.container);
112
+ if (!directory) {
113
+ return null;
114
+ }
115
+ if (typeof directory.verifyCredentials === "function") {
116
+ return await directory.verifyCredentials(credentials.username, credentials.password);
117
+ }
118
+ if (typeof directory.findByEmail !== "function") {
119
+ return null;
120
+ }
121
+ const record = await directory.findByEmail(credentials.username);
122
+ if (!record?.password) {
123
+ return null;
124
+ }
125
+ if (!await verifyPassword(credentials.password, record.password)) {
126
+ return null;
127
+ }
128
+ return {
129
+ id: record.id,
130
+ role: record.role,
131
+ emailVerifiedAt: record.email_verified_at ?? null
132
+ };
133
+ }
134
+ }
135
+ export {
136
+ BasicAuthGuard
137
+ };
@@ -0,0 +1,135 @@
1
+ // @bun
2
+ // ../../src/core/auth/jwt.ts
3
+ import { createHmac, timingSafeEqual } from "crypto";
4
+
5
+ // ../../src/core/runtime/appKeyPrefix.ts
6
+ function appKeyPrefix() {
7
+ return process.env.APP_KEY_PREFIX?.trim() || "strata";
8
+ }
9
+ function appCookieName(kind) {
10
+ return `${appKeyPrefix()}_${kind}`;
11
+ }
12
+ function appDevSecret(kind) {
13
+ return `${appKeyPrefix()}-dev-${kind}`;
14
+ }
15
+ function namespacedRedisKey(kind) {
16
+ return `${appKeyPrefix()}:${kind}`;
17
+ }
18
+ function smtpEhloHost() {
19
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
20
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
21
+ return safe || "strata.local";
22
+ }
23
+ function siemEventType() {
24
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
25
+ }
26
+ function appUserAgent() {
27
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
28
+ }
29
+ function otelServiceName() {
30
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
31
+ }
32
+ function webhookSignatureHeader() {
33
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
34
+ }
35
+ function appDisplayName() {
36
+ return process.env.APP_NAME?.trim() || "Strata";
37
+ }
38
+ function appEnv() {
39
+ return process.env.APP_ENV?.trim() || "local";
40
+ }
41
+ function appUrl() {
42
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
43
+ }
44
+ function apiPrefix() {
45
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
46
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
47
+ const trimmed = withSlash.replace(/\/+$/, "");
48
+ return trimmed || "/api/v1";
49
+ }
50
+ function sdkClientClassName() {
51
+ const override = process.env.APP_SDK_CLASS?.trim();
52
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
53
+ return override;
54
+ }
55
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
56
+ return fromName ? `${fromName}Client` : "AppClient";
57
+ }
58
+
59
+ // ../../src/core/auth/jwt.ts
60
+ function resolveJwtSecret(secret) {
61
+ return secret?.trim() || process.env.JWT_SECRET?.trim() || process.env.SESSION_SECRET?.trim() || appDevSecret("jwt-secret");
62
+ }
63
+ function jwtTtlSeconds(override) {
64
+ if (typeof override === "number" && Number.isInteger(override) && override > 0) {
65
+ return override;
66
+ }
67
+ const parsed = Number.parseInt(process.env.JWT_TTL_SECONDS ?? "", 10);
68
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : 3600;
69
+ }
70
+ function encodeJson(value) {
71
+ return Buffer.from(JSON.stringify(value)).toString("base64url");
72
+ }
73
+ function decodeJson(value) {
74
+ try {
75
+ return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+ function signPart(headerAndPayload, secret) {
81
+ return createHmac("sha256", secret).update(headerAndPayload).digest("base64url");
82
+ }
83
+ function signaturesMatch(left, right) {
84
+ const leftBuffer = Buffer.from(left);
85
+ const rightBuffer = Buffer.from(right);
86
+ if (leftBuffer.length !== rightBuffer.length) {
87
+ return false;
88
+ }
89
+ return timingSafeEqual(leftBuffer, rightBuffer);
90
+ }
91
+ function signJwt(payload, options = {}) {
92
+ const now = Math.floor(Date.now() / 1000);
93
+ const body = {
94
+ ...payload,
95
+ iat: now,
96
+ exp: now + jwtTtlSeconds(options.ttlSeconds)
97
+ };
98
+ const header = encodeJson({ alg: "HS256", typ: "JWT" });
99
+ const data = encodeJson(body);
100
+ const unsigned = `${header}.${data}`;
101
+ const signature = signPart(unsigned, resolveJwtSecret(options.secret));
102
+ return `${unsigned}.${signature}`;
103
+ }
104
+ function verifyJwt(token, secret) {
105
+ const parts = token.split(".");
106
+ if (parts.length !== 3) {
107
+ return null;
108
+ }
109
+ const [header, data, signature] = parts;
110
+ if (!header || !data || !signature) {
111
+ return null;
112
+ }
113
+ const expected = signPart(`${header}.${data}`, resolveJwtSecret(secret));
114
+ if (!signaturesMatch(signature, expected)) {
115
+ return null;
116
+ }
117
+ const parsedHeader = decodeJson(header);
118
+ if (parsedHeader?.alg !== "HS256") {
119
+ return null;
120
+ }
121
+ const payload = decodeJson(data);
122
+ if (!payload || payload.sub === undefined || payload.sub === null) {
123
+ return null;
124
+ }
125
+ if (typeof payload.exp === "number" && payload.exp * 1000 <= Date.now()) {
126
+ return null;
127
+ }
128
+ return payload;
129
+ }
130
+ export {
131
+ jwtTtlSeconds,
132
+ resolveJwtSecret,
133
+ signJwt,
134
+ verifyJwt
135
+ };