@getstrata/bootstrap 0.2.30 → 0.2.31

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/index.js CHANGED
@@ -559,6 +559,7 @@ import {
559
559
  DatabaseTokenGuard,
560
560
  GuestGuard
561
561
  } from "@getstrata/core/auth/guard";
562
+ import { SessionGuard } from "@getstrata/core/auth/sessionGuard";
562
563
 
563
564
  // ../../src/config/auth.ts
564
565
  var authConfig = {
@@ -566,435 +567,6 @@ var authConfig = {
566
567
  tokenDefaultAbilities: ["*"]
567
568
  };
568
569
 
569
- // ../../src/domain/abilities.ts
570
- var MEMBER_ABILITIES = [
571
- "organizations:read",
572
- "projects:read",
573
- "projects:create",
574
- "tasks:read",
575
- "tasks:create",
576
- "comments:read",
577
- "comments:create",
578
- "attachments:read",
579
- "attachments:create",
580
- "auth:tokens:read",
581
- "auth:tokens:write"
582
- ];
583
- var ADMIN_ABILITIES = [
584
- ...MEMBER_ABILITIES,
585
- "organizations:create",
586
- "organizations:update",
587
- "organizations:delete",
588
- "projects:update",
589
- "projects:delete",
590
- "tasks:update",
591
- "tasks:delete",
592
- "comments:update",
593
- "comments:delete",
594
- "attachments:delete",
595
- "webhooks:read",
596
- "webhooks:write",
597
- "audit:read"
598
- ];
599
- var PLATFORM_ADMIN_ABILITIES = ["*"];
600
- function resolveAbilitiesForRole(role) {
601
- if (role === "admin") {
602
- return [...PLATFORM_ADMIN_ABILITIES];
603
- }
604
- return [...MEMBER_ABILITIES];
605
- }
606
-
607
- // ../../src/modules/user/provider.ts
608
- import { OidcProvider } from "@getstrata/core/auth/oauth/oidcProvider";
609
- import { GitHubOAuthProvider, MockOAuthProvider } from "@getstrata/core/auth/oauth/providers";
610
- import { SamlProvider } from "@getstrata/core/auth/oauth/samlProvider";
611
-
612
- // ../../src/modules/user/apiTokenRepository.ts
613
- import { BaseRepository } from "@getstrata/core/database";
614
-
615
- // ../../src/config/database.ts
616
- function readInteger(name, fallback) {
617
- const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
618
- return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
619
- }
620
- var databaseConfig = {
621
- url: process.env.DATABASE_URL ?? "",
622
- poolMax: readInteger("DB_POOL_MAX", 10),
623
- idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
624
- maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
625
- connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
626
- };
627
-
628
- // ../../src/core/runtime/asyncContextStore.ts
629
- import { AsyncLocalStorage } from "async_hooks";
630
- function createAsyncContextStore(key) {
631
- const symbol = Symbol.for(key);
632
- const globalRecord = globalThis;
633
- const existing = globalRecord[symbol];
634
- if (existing) {
635
- return existing;
636
- }
637
- const store = new AsyncLocalStorage;
638
- globalRecord[symbol] = store;
639
- return store;
640
- }
641
-
642
- // ../../src/core/database/connectionContext.ts
643
- var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
644
- function getActiveDatabaseConnection(fallback) {
645
- return activeConnection.getStore() ?? fallback;
646
- }
647
-
648
- // ../../src/core/database/queryProxy.ts
649
- var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
650
- function createDatabaseQueryProxy(pool) {
651
- function resolveDatabase() {
652
- return getActiveDatabaseConnection(pool);
653
- }
654
- function resolveDatabaseForProperty(property) {
655
- if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
656
- return pool;
657
- }
658
- return resolveDatabase();
659
- }
660
- return new Proxy(function database() {}, {
661
- apply(_target, _thisArg, args) {
662
- return resolveDatabase()(...args);
663
- },
664
- get(_target, property) {
665
- const connection = resolveDatabaseForProperty(property);
666
- const value = connection[property];
667
- return typeof value === "function" ? value.bind(connection) : value;
668
- }
669
- });
670
- }
671
-
672
- // ../../src/core/database/defaultConnection.ts
673
- var defaultPool = {
674
- connection: null
675
- };
676
- var defaultQuery = {
677
- connection: null
678
- };
679
- function registerDefaultDatabasePool(connection) {
680
- defaultPool.connection = connection;
681
- defaultQuery.connection = createDatabaseQueryProxy(connection);
682
- }
683
- function getDefaultDatabaseQuery() {
684
- if (!defaultQuery.connection) {
685
- throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
686
- }
687
- return defaultQuery.connection;
688
- }
689
-
690
- // ../../src/db/connection/createConnection.ts
691
- var {SQL } = globalThis.Bun;
692
- function createDatabaseConnection(config) {
693
- if (!config.url) {
694
- throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
695
- }
696
- return new SQL({
697
- url: config.url,
698
- max: config.poolMax,
699
- idleTimeout: config.idleTimeoutSeconds,
700
- maxLifetime: config.maxLifetimeSeconds,
701
- connectionTimeout: config.connectionTimeoutSeconds
702
- });
703
- }
704
-
705
- // ../../src/db/connection/index.ts
706
- var connectionHolder = {
707
- connection: null
708
- };
709
- function getDatabase() {
710
- if (!connectionHolder.connection) {
711
- connectionHolder.connection = createDatabaseConnection(databaseConfig);
712
- registerDefaultDatabasePool(connectionHolder.connection);
713
- }
714
- return connectionHolder.connection;
715
- }
716
- function getDb() {
717
- getDatabase();
718
- return getDefaultDatabaseQuery();
719
- }
720
- var db = new Proxy(function database() {}, {
721
- apply(_target, _thisArg, args) {
722
- return getDb()(...args);
723
- },
724
- get(_target, property) {
725
- const connection = getDb();
726
- const value = connection[property];
727
- return typeof value === "function" ? value.bind(connection) : value;
728
- }
729
- });
730
-
731
- // ../../src/modules/user/apiTokenTable.ts
732
- import { defineTable } from "@getstrata/core/database";
733
- var apiTokenTable = defineTable({
734
- name: "api_token",
735
- primaryKey: "id",
736
- columns: [
737
- "id",
738
- "user_id",
739
- "name",
740
- "token_hash",
741
- "abilities",
742
- "last_used_at",
743
- "expires_at",
744
- "created_at"
745
- ],
746
- defaultOrderBy: { column: "id", direction: "ASC" }
747
- });
748
-
749
- // ../../src/modules/user/authService.ts
750
- import { verifyPassword } from "@getstrata/core/auth/password";
751
- import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
752
- import { UnauthorizedError } from "@getstrata/core/errors/http";
753
- import { logSecurityEvent } from "@getstrata/core/security/securityEvents";
754
- import { resolveDefaultTokenExpiryDays } from "@getstrata/core/security/tokenExpiry";
755
- import { verifyTotp } from "@getstrata/core/security/totp";
756
- import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
757
- class AuthService {
758
- users;
759
- tokens;
760
- oauthIdentities;
761
- oauthProviders = new Map;
762
- constructor(users, tokens, oauthIdentities) {
763
- this.users = users;
764
- this.tokens = tokens;
765
- this.oauthIdentities = oauthIdentities;
766
- }
767
- registerOAuthProvider(provider) {
768
- this.oauthProviders.set(provider.name, provider);
769
- }
770
- getOAuthProvider(name) {
771
- return this.oauthProviders.get(name);
772
- }
773
- async loginWithPassword(email, password, options = {}) {
774
- const user = await this.users.findByEmail(email);
775
- if (!user?.password_hash) {
776
- logSecurityEvent("auth_login_failed", { reason: "unknown_user", email });
777
- throw new UnauthorizedError("Invalid credentials.");
778
- }
779
- const valid = await verifyPassword(password, user.password_hash);
780
- if (!valid) {
781
- logSecurityEvent("auth_login_failed", { reason: "invalid_password", user_id: user.id });
782
- throw new UnauthorizedError("Invalid credentials.");
783
- }
784
- if (isFeatureEnabled("emailVerification") && !user.email_verified_at) {
785
- logSecurityEvent("auth_login_blocked", { reason: "email_unverified", user_id: user.id });
786
- throw new UnauthorizedError("Email address is not verified.");
787
- }
788
- if (isFeatureEnabled("mfa") && user.mfa_enabled) {
789
- const mfaSecret = revealMfaSecret(user.mfa_secret);
790
- if (!mfaSecret || !options.mfaCode || !verifyTotp(mfaSecret, options.mfaCode)) {
791
- logSecurityEvent("auth_login_failed", { reason: "invalid_mfa", user_id: user.id });
792
- throw new UnauthorizedError("Invalid MFA code.");
793
- }
794
- }
795
- logSecurityEvent("auth_login_success", { user_id: user.id, method: "password" });
796
- return await this.tokens.createToken(user.id, {
797
- name: "password-login",
798
- abilities: resolveAbilitiesForRole(user.role),
799
- expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
800
- });
801
- }
802
- async loginWithOAuth(providerName, code) {
803
- const provider = this.oauthProviders.get(providerName);
804
- if (!provider) {
805
- throw new UnauthorizedError("Unsupported OAuth provider.");
806
- }
807
- const profile = await provider.exchangeCode(code);
808
- const user = await this.findOrCreateOAuthUser(providerName, profile);
809
- logSecurityEvent("auth_login_success", { user_id: user.id, method: `oauth:${providerName}` });
810
- return await this.tokens.createToken(user.id, {
811
- name: `${providerName}-oauth`,
812
- abilities: resolveAbilitiesForRole(user.role),
813
- expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
814
- });
815
- }
816
- buildOAuthAuthorizationUrl(providerName, state) {
817
- const provider = this.oauthProviders.get(providerName);
818
- if (!provider) {
819
- throw new UnauthorizedError("Unsupported OAuth provider.");
820
- }
821
- return provider.getAuthorizationUrl(state);
822
- }
823
- async findOrCreateOAuthUser(providerName, profile) {
824
- const existingIdentity = await this.oauthIdentities.findByProviderUser(providerName, profile.providerUserId);
825
- if (existingIdentity) {
826
- return await this.users.findByIdOrThrow(existingIdentity.user_id);
827
- }
828
- const existingUser = await this.users.findByEmail(profile.email);
829
- const user = existingUser ?? await this.users.create({
830
- name: profile.name,
831
- email: profile.email,
832
- role: "member",
833
- tenant_id: currentTenantId(),
834
- email_verified_at: new Date,
835
- created_at: new Date,
836
- updated_at: new Date
837
- });
838
- await this.oauthIdentities.create({
839
- user_id: user.id,
840
- provider: providerName,
841
- provider_user_id: profile.providerUserId,
842
- email: profile.email,
843
- created_at: new Date
844
- });
845
- return user;
846
- }
847
- }
848
-
849
- // ../../src/modules/user/notificationRepository.ts
850
- import { BaseRepository as BaseRepository2 } from "@getstrata/core/database";
851
-
852
- // ../../src/modules/user/notificationTable.ts
853
- import { defineTable as defineTable2 } from "@getstrata/core/database";
854
- var notificationTable = defineTable2({
855
- name: "notification",
856
- primaryKey: "id",
857
- columns: ["id", "user_id", "tenant_id", "type", "title", "body", "data", "read_at", "created_at"],
858
- defaultOrderBy: { column: "created_at", direction: "DESC" }
859
- });
860
-
861
- // ../../src/modules/user/notificationService.ts
862
- import { NotFoundError } from "@getstrata/core/errors/http";
863
-
864
- // ../../src/modules/user/oauthIdentityRepository.ts
865
- import { BaseRepository as BaseRepository3, defineTable as defineTable3 } from "@getstrata/core/database";
866
- var oauthIdentityTable = defineTable3({
867
- name: "oauth_identity",
868
- primaryKey: "id",
869
- columns: ["id", "user_id", "provider", "provider_user_id", "email", "created_at"]
870
- });
871
-
872
- // ../../src/modules/user/repository.ts
873
- import {
874
- emailLookupForQuery,
875
- protectEmail,
876
- revealEmail
877
- } from "@getstrata/core/crypto/fieldEncryption";
878
- import { revealMfaSecret as revealMfaSecret2 } from "@getstrata/core/crypto/mfaSecret";
879
- import { BaseRepository as BaseRepository4 } from "@getstrata/core/database";
880
- import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
881
-
882
- // ../../src/modules/user/table.ts
883
- import { defineTable as defineTable4 } from "@getstrata/core/database";
884
- var userTable = defineTable4({
885
- name: "users",
886
- primaryKey: "id",
887
- columns: [
888
- "id",
889
- "name",
890
- "email",
891
- "email_lookup",
892
- "role",
893
- "tenant_id",
894
- "password_hash",
895
- "email_verified_at",
896
- "mfa_secret",
897
- "mfa_enabled",
898
- "created_at",
899
- "updated_at"
900
- ],
901
- defaultOrderBy: { column: "id", direction: "ASC" }
902
- });
903
-
904
- // ../../src/modules/user/tokenService.ts
905
- import { hashApiToken } from "@getstrata/core/auth/tokenHash";
906
- import { ForbiddenError, NotFoundError as NotFoundError2 } from "@getstrata/core/errors/http";
907
- import { resolveDefaultTokenExpiryDays as resolveDefaultTokenExpiryDays2 } from "@getstrata/core/security/tokenExpiry";
908
-
909
- // ../../src/modules/user/provider.ts
910
- var tokenServiceToken = CORE_TOKEN_SERVICE_TOKEN;
911
-
912
- // ../../src/core/auth/sessionCookie.ts
913
- import { createHmac, timingSafeEqual } from "crypto";
914
- var SESSION_COOKIE = "workhub_session";
915
- var SESSION_TTL_SECONDS = 60 * 60 * 24 * 7;
916
- function resolveSessionSecret() {
917
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-session-secret";
918
- }
919
- function signSession(userId, issuedAt) {
920
- const payload = `${userId}.${issuedAt}`;
921
- const signature = createHmac("sha256", resolveSessionSecret()).update(payload).digest("hex");
922
- return `${payload}.${signature}`;
923
- }
924
- function readCookieValue(request, cookieName) {
925
- const cookieHeader = request.headers.get("cookie");
926
- if (!cookieHeader) {
927
- return null;
928
- }
929
- for (const part of cookieHeader.split(";")) {
930
- const [name, ...rest] = part.trim().split("=");
931
- if (name === cookieName) {
932
- return decodeURIComponent(rest.join("="));
933
- }
934
- }
935
- return null;
936
- }
937
- function readSessionUserId(request) {
938
- const cookieValue = readCookieValue(request, SESSION_COOKIE);
939
- if (!cookieValue) {
940
- return null;
941
- }
942
- const parts = cookieValue.split(".");
943
- if (parts.length !== 3) {
944
- return null;
945
- }
946
- const [userIdRaw, issuedAtRaw, cookieSignature] = parts;
947
- const userId = Number.parseInt(String(userIdRaw), 10);
948
- const issuedAt = Number.parseInt(String(issuedAtRaw), 10);
949
- if (!Number.isInteger(userId) || userId <= 0 || !Number.isFinite(issuedAt)) {
950
- return null;
951
- }
952
- if (Date.now() - issuedAt > SESSION_TTL_SECONDS * 1000) {
953
- return null;
954
- }
955
- const expectedSignature = signSession(userId, issuedAt).split(".").pop();
956
- if (!expectedSignature || !cookieSignature) {
957
- return null;
958
- }
959
- const expectedBuffer = Buffer.from(expectedSignature);
960
- const actualBuffer = Buffer.from(cookieSignature);
961
- if (expectedBuffer.length !== actualBuffer.length) {
962
- return null;
963
- }
964
- if (!timingSafeEqual(expectedBuffer, actualBuffer)) {
965
- return null;
966
- }
967
- return userId;
968
- }
969
-
970
- // ../../src/core/auth/sessionGuard.ts
971
- class SessionGuard {
972
- container;
973
- constructor(container) {
974
- this.container = container;
975
- }
976
- async resolve(request) {
977
- const userId = readSessionUserId(request);
978
- if (!userId) {
979
- return null;
980
- }
981
- if (!this.container.has(tokenServiceToken)) {
982
- return null;
983
- }
984
- const tokenService = this.container.resolve(tokenServiceToken);
985
- try {
986
- const user = await tokenService.findByIdOrThrow(userId);
987
- return {
988
- id: user.id,
989
- role: user.role,
990
- abilities: resolveAbilitiesForRole(user.role)
991
- };
992
- } catch {
993
- return null;
994
- }
995
- }
996
- }
997
-
998
570
  // ../../src/bootstrap/providers/auth.ts
999
571
  var authProvider = {
1000
572
  name: "core.auth",
@@ -1011,66 +583,7 @@ var auth_default = authProvider;
1011
583
 
1012
584
  // ../../src/bootstrap/providers/cache.ts
1013
585
  import { createCacheStore } from "@getstrata/core/cache/createCacheStore";
1014
-
1015
- // ../../src/core/cache/taggedCache.ts
1016
- class TaggedCache {
1017
- store;
1018
- tags;
1019
- constructor(store, tags) {
1020
- this.store = store;
1021
- this.tags = tags;
1022
- }
1023
- async remember(key, callback, ttlMs) {
1024
- const value = await this.store.getOrSet(key, callback, ttlMs);
1025
- await this.store.attachTags(key, this.tags);
1026
- return value;
1027
- }
1028
- async flush() {
1029
- return this.store.flushTags(this.tags);
1030
- }
1031
- }
1032
- var taggedCache_default = TaggedCache;
1033
-
1034
- // ../../src/core/cache/repository.ts
1035
- class CacheRepository {
1036
- store;
1037
- constructor(store) {
1038
- this.store = store;
1039
- }
1040
- async get(key) {
1041
- return this.store.get(key);
1042
- }
1043
- async remember(key, callback, ttlMs) {
1044
- return this.store.getOrSet(key, callback, ttlMs);
1045
- }
1046
- async forget(key) {
1047
- return this.store.invalidate(key);
1048
- }
1049
- async flush() {
1050
- await this.store.clear();
1051
- }
1052
- tags(...names) {
1053
- return new taggedCache_default(this.store, names);
1054
- }
1055
- async getOrSet(key, loader, ttlMs) {
1056
- return this.remember(key, loader, ttlMs);
1057
- }
1058
- async invalidate(key) {
1059
- return this.forget(key);
1060
- }
1061
- async invalidateByPrefix(prefix) {
1062
- return this.store.invalidateByPrefix(prefix);
1063
- }
1064
- async clear() {
1065
- await this.flush();
1066
- }
1067
- async size() {
1068
- return this.store.size();
1069
- }
1070
- }
1071
- var repository_default2 = CacheRepository;
1072
-
1073
- // ../../src/bootstrap/providers/cache.ts
586
+ import { CacheRepository } from "@getstrata/core/cache/repository";
1074
587
  var cacheProvider = {
1075
588
  name: "core.cache",
1076
589
  register({ container, config, dependencies }) {
@@ -1081,7 +594,7 @@ var cacheProvider = {
1081
594
  maxEntries: config.require(CACHE_MAX_ENTRIES_CONFIG_KEY),
1082
595
  redisUrl: config.get(REDIS_URL_CONFIG_KEY) || undefined
1083
596
  });
1084
- return new repository_default2(store);
597
+ return new CacheRepository(store);
1085
598
  });
1086
599
  dependencies.cache = container.resolve(CORE_CACHE_TOKEN);
1087
600
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/bootstrap",
3
- "version": "0.2.30",
3
+ "version": "0.2.31",
4
4
  "description": "Strata application bootstrap — HttpKernel, DI, web session helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",