sovrium 0.0.2
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/CHANGELOG.md +3497 -0
- package/LICENSE.md +147 -0
- package/LICENSE_EE.md +297 -0
- package/README.md +321 -0
- package/drizzle/0000_melted_kabuki.sql +163 -0
- package/drizzle/meta/0000_snapshot.json +1216 -0
- package/drizzle/meta/_journal.json +13 -0
- package/package.json +167 -0
- package/schemas/0.0.1/app.openapi.json +70 -0
- package/schemas/0.0.1/app.schema.json +7961 -0
- package/schemas/0.0.2/app.openapi.json +80 -0
- package/schemas/0.0.2/app.schema.json +8829 -0
- package/schemas/development/app.openapi.json +70 -0
- package/schemas/development/app.schema.json +7456 -0
- package/src/application/errors/app-validation-error.ts +14 -0
- package/src/application/errors/static-generation-error.ts +16 -0
- package/src/application/metadata/favicon-transformer.ts +127 -0
- package/src/application/models/server.ts +27 -0
- package/src/application/ports/models/user-metadata.ts +36 -0
- package/src/application/ports/models/user-session.ts +34 -0
- package/src/application/ports/repositories/activity-log-repository.ts +68 -0
- package/src/application/ports/repositories/activity-repository.ts +49 -0
- package/src/application/ports/repositories/analytics-repository.ts +164 -0
- package/src/application/ports/repositories/auth-repository.ts +33 -0
- package/src/application/ports/repositories/batch-repository.ts +86 -0
- package/src/application/ports/repositories/comment-repository.ts +150 -0
- package/src/application/ports/repositories/index.ts +41 -0
- package/src/application/ports/repositories/table-repository.ts +139 -0
- package/src/application/ports/services/css-compiler.ts +55 -0
- package/src/application/ports/services/index.ts +16 -0
- package/src/application/ports/services/page-renderer.ts +79 -0
- package/src/application/ports/services/server-factory.ts +80 -0
- package/src/application/ports/services/static-site-generator.ts +82 -0
- package/src/application/use-cases/activity/programs.ts +66 -0
- package/src/application/use-cases/analytics/collect-page-view.ts +114 -0
- package/src/application/use-cases/analytics/purge-old-data.ts +40 -0
- package/src/application/use-cases/analytics/query-campaigns.ts +43 -0
- package/src/application/use-cases/analytics/query-devices.ts +36 -0
- package/src/application/use-cases/analytics/query-overview.ts +50 -0
- package/src/application/use-cases/analytics/query-pages.ts +40 -0
- package/src/application/use-cases/analytics/query-referrers.ts +43 -0
- package/src/application/use-cases/analytics/ua-parser.ts +89 -0
- package/src/application/use-cases/analytics/visitor-hash.ts +77 -0
- package/src/application/use-cases/auth/bootstrap-admin.ts +270 -0
- package/src/application/use-cases/list-activity-logs.ts +123 -0
- package/src/application/use-cases/server/generate-static-helpers.ts +374 -0
- package/src/application/use-cases/server/generate-static.ts +287 -0
- package/src/application/use-cases/server/start-server.ts +118 -0
- package/src/application/use-cases/server/startup-error-handler.ts +69 -0
- package/src/application/use-cases/server/static-content-generators.ts +182 -0
- package/src/application/use-cases/server/static-language-generators.ts +181 -0
- package/src/application/use-cases/server/static-url-rewriter.ts +237 -0
- package/src/application/use-cases/server/translation-replacer.ts +164 -0
- package/src/application/use-cases/tables/activity-programs.ts +93 -0
- package/src/application/use-cases/tables/batch-operations.ts +156 -0
- package/src/application/use-cases/tables/comment-programs.ts +436 -0
- package/src/application/use-cases/tables/permissions/permissions.ts +25 -0
- package/src/application/use-cases/tables/programs.ts +435 -0
- package/src/application/use-cases/tables/table-operations.ts +412 -0
- package/src/application/use-cases/tables/user-role.ts +52 -0
- package/src/application/use-cases/tables/utils/display-formatter.ts +471 -0
- package/src/application/use-cases/tables/utils/field-read-filter.ts +189 -0
- package/src/application/use-cases/tables/utils/list-helpers.ts +122 -0
- package/src/application/use-cases/tables/utils/record-transformer.ts +319 -0
- package/src/cli.ts +370 -0
- package/src/domain/errors/create-tagged-error.ts +36 -0
- package/src/domain/errors/index.ts +78 -0
- package/src/domain/models/api/analytics.ts +179 -0
- package/src/domain/models/api/auth.ts +231 -0
- package/src/domain/models/api/common.ts +60 -0
- package/src/domain/models/api/error.ts +89 -0
- package/src/domain/models/api/health.ts +38 -0
- package/src/domain/models/api/index.ts +42 -0
- package/src/domain/models/api/request.ts +132 -0
- package/src/domain/models/api/tables.ts +444 -0
- package/src/domain/models/app/analytics/index.ts +129 -0
- package/src/domain/models/app/auth/config.ts +116 -0
- package/src/domain/models/app/auth/index.ts +230 -0
- package/src/domain/models/app/auth/methods/email-and-password.ts +67 -0
- package/src/domain/models/app/auth/methods/index.ts +11 -0
- package/src/domain/models/app/auth/methods/magic-link.ts +54 -0
- package/src/domain/models/app/auth/oauth/index.ts +8 -0
- package/src/domain/models/app/auth/oauth/providers.ts +105 -0
- package/src/domain/models/app/auth/plugins/admin.ts +130 -0
- package/src/domain/models/app/auth/plugins/index.ts +74 -0
- package/src/domain/models/app/auth/plugins/two-factor.ts +63 -0
- package/src/domain/models/app/auth/roles.ts +179 -0
- package/src/domain/models/app/auth/strategies.ts +191 -0
- package/src/domain/models/app/auth/validation.ts +127 -0
- package/src/domain/models/app/common/branded-ids.ts +200 -0
- package/src/domain/models/app/common/definitions.ts +187 -0
- package/src/domain/models/app/component/common/component-children.ts +119 -0
- package/src/domain/models/app/component/common/component-props.ts +89 -0
- package/src/domain/models/app/component/common/component-reference.ts +170 -0
- package/src/domain/models/app/component/component.ts +81 -0
- package/src/domain/models/app/components.ts +65 -0
- package/src/domain/models/app/description.ts +83 -0
- package/src/domain/models/app/index.ts +258 -0
- package/src/domain/models/app/language/language-config.ts +200 -0
- package/src/domain/models/app/languages.ts +205 -0
- package/src/domain/models/app/name.ts +66 -0
- package/src/domain/models/app/page/common/interactions/click-interaction.ts +116 -0
- package/src/domain/models/app/page/common/interactions/entrance-animation.ts +84 -0
- package/src/domain/models/app/page/common/interactions/hover-interaction.ts +144 -0
- package/src/domain/models/app/page/common/interactions/interactions.ts +64 -0
- package/src/domain/models/app/page/common/interactions/scroll-interaction.ts +93 -0
- package/src/domain/models/app/page/common/responsive.ts +114 -0
- package/src/domain/models/app/page/common/url.ts +35 -0
- package/src/domain/models/app/page/common/variable-reference.ts +53 -0
- package/src/domain/models/app/page/id.ts +44 -0
- package/src/domain/models/app/page/index.ts +270 -0
- package/src/domain/models/app/page/meta/analytics.ts +248 -0
- package/src/domain/models/app/page/meta/custom-elements.ts +180 -0
- package/src/domain/models/app/page/meta/dns-prefetch.ts +77 -0
- package/src/domain/models/app/page/meta/favicon-set.ts +203 -0
- package/src/domain/models/app/page/meta/favicon.ts +50 -0
- package/src/domain/models/app/page/meta/favicons-config.ts +73 -0
- package/src/domain/models/app/page/meta/index.ts +278 -0
- package/src/domain/models/app/page/meta/open-graph.ts +166 -0
- package/src/domain/models/app/page/meta/preload.ts +190 -0
- package/src/domain/models/app/page/meta/structured-data/article.ts +211 -0
- package/src/domain/models/app/page/meta/structured-data/breadcrumb.ts +115 -0
- package/src/domain/models/app/page/meta/structured-data/common-fields.ts +201 -0
- package/src/domain/models/app/page/meta/structured-data/education-event.ts +256 -0
- package/src/domain/models/app/page/meta/structured-data/faq-page.ts +127 -0
- package/src/domain/models/app/page/meta/structured-data/index.ts +95 -0
- package/src/domain/models/app/page/meta/structured-data/local-business.ts +247 -0
- package/src/domain/models/app/page/meta/structured-data/organization.ts +171 -0
- package/src/domain/models/app/page/meta/structured-data/person.ts +138 -0
- package/src/domain/models/app/page/meta/structured-data/postal-address.ts +106 -0
- package/src/domain/models/app/page/meta/structured-data/product.ts +214 -0
- package/src/domain/models/app/page/meta/twitter-card.ts +217 -0
- package/src/domain/models/app/page/name.ts +38 -0
- package/src/domain/models/app/page/path.ts +21 -0
- package/src/domain/models/app/page/scripts/external-scripts.ts +163 -0
- package/src/domain/models/app/page/scripts/features.ts +135 -0
- package/src/domain/models/app/page/scripts/inline-scripts.ts +114 -0
- package/src/domain/models/app/page/scripts/scripts.ts +102 -0
- package/src/domain/models/app/page/sections.ts +298 -0
- package/src/domain/models/app/pages.ts +61 -0
- package/src/domain/models/app/permissions/index.ts +61 -0
- package/src/domain/models/app/permissions/resource-action.ts +114 -0
- package/src/domain/models/app/permissions/roles.ts +120 -0
- package/src/domain/models/app/table/check-constraints.ts +105 -0
- package/src/domain/models/app/table/cycle-detection.ts +124 -0
- package/src/domain/models/app/table/database-identifier.ts +153 -0
- package/src/domain/models/app/table/field-name.ts +36 -0
- package/src/domain/models/app/table/field-types/advanced/array-field.ts +33 -0
- package/src/domain/models/app/table/field-types/advanced/autonumber-field.ts +54 -0
- package/src/domain/models/app/table/field-types/advanced/button-field.ts +56 -0
- package/src/domain/models/app/table/field-types/advanced/color-field.ts +57 -0
- package/src/domain/models/app/table/field-types/advanced/count-field.ts +54 -0
- package/src/domain/models/app/table/field-types/advanced/formula-field.ts +58 -0
- package/src/domain/models/app/table/field-types/advanced/geolocation-field.ts +49 -0
- package/src/domain/models/app/table/field-types/advanced/index.ts +16 -0
- package/src/domain/models/app/table/field-types/advanced/json-field.ts +25 -0
- package/src/domain/models/app/table/field-types/advanced/unknown-field.ts +85 -0
- package/src/domain/models/app/table/field-types/base-field.ts +42 -0
- package/src/domain/models/app/table/field-types/date-time/created-at-field.ts +49 -0
- package/src/domain/models/app/table/field-types/date-time/date-field.ts +95 -0
- package/src/domain/models/app/table/field-types/date-time/deleted-at-field.ts +56 -0
- package/src/domain/models/app/table/field-types/date-time/duration-field.ts +73 -0
- package/src/domain/models/app/table/field-types/date-time/index.ts +12 -0
- package/src/domain/models/app/table/field-types/date-time/updated-at-field.ts +50 -0
- package/src/domain/models/app/table/field-types/index.ts +19 -0
- package/src/domain/models/app/table/field-types/media/barcode-field.ts +58 -0
- package/src/domain/models/app/table/field-types/media/index.ts +10 -0
- package/src/domain/models/app/table/field-types/media/multiple-attachments-field.ts +80 -0
- package/src/domain/models/app/table/field-types/media/single-attachment-field.ts +81 -0
- package/src/domain/models/app/table/field-types/numeric/currency-field.ts +144 -0
- package/src/domain/models/app/table/field-types/numeric/decimal-field.ts +113 -0
- package/src/domain/models/app/table/field-types/numeric/index.ts +13 -0
- package/src/domain/models/app/table/field-types/numeric/integer-field.ts +98 -0
- package/src/domain/models/app/table/field-types/numeric/percentage-field.ts +115 -0
- package/src/domain/models/app/table/field-types/numeric/progress-field.ts +71 -0
- package/src/domain/models/app/table/field-types/numeric/rating-field.ts +74 -0
- package/src/domain/models/app/table/field-types/relational/index.ts +10 -0
- package/src/domain/models/app/table/field-types/relational/lookup-field.ts +46 -0
- package/src/domain/models/app/table/field-types/relational/relationship-field.ts +112 -0
- package/src/domain/models/app/table/field-types/relational/rollup-field.ts +58 -0
- package/src/domain/models/app/table/field-types/selection/checkbox-field.ts +51 -0
- package/src/domain/models/app/table/field-types/selection/index.ts +11 -0
- package/src/domain/models/app/table/field-types/selection/multi-select-field.ts +68 -0
- package/src/domain/models/app/table/field-types/selection/single-select-field.ts +54 -0
- package/src/domain/models/app/table/field-types/selection/status-field.ts +37 -0
- package/src/domain/models/app/table/field-types/text/email-field.ts +80 -0
- package/src/domain/models/app/table/field-types/text/index.ts +13 -0
- package/src/domain/models/app/table/field-types/text/long-text-field.ts +77 -0
- package/src/domain/models/app/table/field-types/text/phone-number-field.ts +82 -0
- package/src/domain/models/app/table/field-types/text/rich-text-field.ts +66 -0
- package/src/domain/models/app/table/field-types/text/single-line-text-field.ts +79 -0
- package/src/domain/models/app/table/field-types/text/url-field.ts +81 -0
- package/src/domain/models/app/table/field-types/user/created-by-field.ts +50 -0
- package/src/domain/models/app/table/field-types/user/deleted-by-field.ts +57 -0
- package/src/domain/models/app/table/field-types/user/index.ts +11 -0
- package/src/domain/models/app/table/field-types/user/updated-by-field.ts +51 -0
- package/src/domain/models/app/table/field-types/user/user-field.ts +52 -0
- package/src/domain/models/app/table/field-types/validation-utils.ts +166 -0
- package/src/domain/models/app/table/fields.ts +216 -0
- package/src/domain/models/app/table/foreign-keys.ts +111 -0
- package/src/domain/models/app/table/formula-keywords.ts +326 -0
- package/src/domain/models/app/table/id.ts +31 -0
- package/src/domain/models/app/table/index.ts +290 -0
- package/src/domain/models/app/table/indexes.ts +80 -0
- package/src/domain/models/app/table/name.ts +37 -0
- package/src/domain/models/app/table/permissions/field-permission.ts +83 -0
- package/src/domain/models/app/table/permissions/index.ts +167 -0
- package/src/domain/models/app/table/permissions/permission-evaluator.ts +372 -0
- package/src/domain/models/app/table/permissions/permission.ts +49 -0
- package/src/domain/models/app/table/primary-key.ts +62 -0
- package/src/domain/models/app/table/table-formula-validation.ts +168 -0
- package/src/domain/models/app/table/table-indexes-validation.ts +38 -0
- package/src/domain/models/app/table/table-permissions-validation.ts +77 -0
- package/src/domain/models/app/table/table-primary-key-validation.ts +49 -0
- package/src/domain/models/app/table/table-views-validation.ts +408 -0
- package/src/domain/models/app/table/unique-constraints.ts +79 -0
- package/src/domain/models/app/table/views/fields.ts +28 -0
- package/src/domain/models/app/table/views/filters.ts +162 -0
- package/src/domain/models/app/table/views/group-by.ts +32 -0
- package/src/domain/models/app/table/views/id.ts +50 -0
- package/src/domain/models/app/table/views/index.ts +177 -0
- package/src/domain/models/app/table/views/name.ts +32 -0
- package/src/domain/models/app/table/views/permissions.ts +98 -0
- package/src/domain/models/app/table/views/sorts.ts +31 -0
- package/src/domain/models/app/tables.ts +695 -0
- package/src/domain/models/app/theme/animations.ts +208 -0
- package/src/domain/models/app/theme/border-radius.ts +58 -0
- package/src/domain/models/app/theme/breakpoints.ts +62 -0
- package/src/domain/models/app/theme/colors.ts +110 -0
- package/src/domain/models/app/theme/fonts.ts +164 -0
- package/src/domain/models/app/theme/shadows.ts +61 -0
- package/src/domain/models/app/theme/spacing.ts +115 -0
- package/src/domain/models/app/theme.ts +66 -0
- package/src/domain/models/app/version.ts +87 -0
- package/src/domain/models/record-comment.ts +91 -0
- package/src/domain/utils/content-parsing.ts +49 -0
- package/src/domain/utils/format-detection.ts +69 -0
- package/src/domain/utils/index.ts +9 -0
- package/src/domain/utils/route-matcher.ts +184 -0
- package/src/domain/utils/translation-resolver.ts +170 -0
- package/src/index.ts +208 -0
- package/src/infrastructure/analytics/tracking-script.ts +48 -0
- package/src/infrastructure/auth/better-auth/auth.ts +216 -0
- package/src/infrastructure/auth/better-auth/email-handlers.ts +162 -0
- package/src/infrastructure/auth/better-auth/index.ts +16 -0
- package/src/infrastructure/auth/better-auth/layer.ts +97 -0
- package/src/infrastructure/auth/better-auth/plugins/admin.ts +56 -0
- package/src/infrastructure/auth/better-auth/plugins/magic-link.ts +31 -0
- package/src/infrastructure/auth/better-auth/plugins/two-factor.ts +19 -0
- package/src/infrastructure/auth/better-auth/schema.ts +152 -0
- package/src/infrastructure/auth/index.ts +27 -0
- package/src/infrastructure/css/cache/css-cache-service.ts +130 -0
- package/src/infrastructure/css/compiler.ts +210 -0
- package/src/infrastructure/css/css-compiler-live.ts +20 -0
- package/src/infrastructure/css/index.ts +25 -0
- package/src/infrastructure/css/styles/animation-styles-generator.ts +177 -0
- package/src/infrastructure/css/styles/click-animations.ts +147 -0
- package/src/infrastructure/css/styles/component-layer-generators.ts +147 -0
- package/src/infrastructure/css/theme/theme-generators.ts +130 -0
- package/src/infrastructure/css/theme/theme-layer-generators.ts +219 -0
- package/src/infrastructure/css/theme/theme-token-resolver.ts +76 -0
- package/src/infrastructure/database/activity-queries.ts +111 -0
- package/src/infrastructure/database/auth/auth-validation.ts +101 -0
- package/src/infrastructure/database/drizzle/db-bun.ts +17 -0
- package/src/infrastructure/database/drizzle/db.ts +17 -0
- package/src/infrastructure/database/drizzle/index.ts +16 -0
- package/src/infrastructure/database/drizzle/layer.ts +34 -0
- package/src/infrastructure/database/drizzle/migrate.ts +77 -0
- package/src/infrastructure/database/drizzle/schema/activity-log.ts +111 -0
- package/src/infrastructure/database/drizzle/schema/analytics-page-views.ts +116 -0
- package/src/infrastructure/database/drizzle/schema/migration-audit.ts +68 -0
- package/src/infrastructure/database/drizzle/schema/record-comments.ts +79 -0
- package/src/infrastructure/database/drizzle/schema.ts +12 -0
- package/src/infrastructure/database/field-utils.ts +87 -0
- package/src/infrastructure/database/filter-operators.ts +136 -0
- package/src/infrastructure/database/formula/formula-trigger-generators.ts +114 -0
- package/src/infrastructure/database/formula/formula-utils.ts +440 -0
- package/src/infrastructure/database/generators/index-generators.ts +152 -0
- package/src/infrastructure/database/generators/trigger-generators.ts +154 -0
- package/src/infrastructure/database/index.ts +35 -0
- package/src/infrastructure/database/lookup/lookup-expression-generators.ts +356 -0
- package/src/infrastructure/database/lookup/lookup-expressions.ts +116 -0
- package/src/infrastructure/database/lookup/lookup-view-generators.ts +403 -0
- package/src/infrastructure/database/lookup/lookup-view-helpers.ts +65 -0
- package/src/infrastructure/database/lookup/lookup-view-triggers.ts +121 -0
- package/src/infrastructure/database/migration-audit-trail.ts +375 -0
- package/src/infrastructure/database/repositories/activity-log-repository-live.ts +99 -0
- package/src/infrastructure/database/repositories/activity-repository-live.ts +21 -0
- package/src/infrastructure/database/repositories/analytics-repository-live.ts +316 -0
- package/src/infrastructure/database/repositories/auth-repository-live.ts +42 -0
- package/src/infrastructure/database/repositories/batch-repository-live.ts +29 -0
- package/src/infrastructure/database/repositories/comment-repository-live.ts +39 -0
- package/src/infrastructure/database/repositories/table-repository-live.ts +38 -0
- package/src/infrastructure/database/schema/schema-dependency-sorting.ts +142 -0
- package/src/infrastructure/database/schema/schema-initializer.ts +598 -0
- package/src/infrastructure/database/schema-migration/column-detection.ts +286 -0
- package/src/infrastructure/database/schema-migration/constants.ts +31 -0
- package/src/infrastructure/database/schema-migration/constraint-sync.ts +288 -0
- package/src/infrastructure/database/schema-migration/index-sync.ts +108 -0
- package/src/infrastructure/database/schema-migration/index.ts +66 -0
- package/src/infrastructure/database/schema-migration/migration-statements.ts +106 -0
- package/src/infrastructure/database/schema-migration/rename-detection.ts +87 -0
- package/src/infrastructure/database/schema-migration/table-operations.ts +65 -0
- package/src/infrastructure/database/schema-migration/type-utils.ts +98 -0
- package/src/infrastructure/database/schema-migration/types.ts +14 -0
- package/src/infrastructure/database/schema-migration-helpers.ts +53 -0
- package/src/infrastructure/database/session-context.ts +20 -0
- package/src/infrastructure/database/sql/sql-check-constraints.ts +252 -0
- package/src/infrastructure/database/sql/sql-column-generators.ts +174 -0
- package/src/infrastructure/database/sql/sql-execution.ts +245 -0
- package/src/infrastructure/database/sql/sql-field-predicates.ts +81 -0
- package/src/infrastructure/database/sql/sql-generators.ts +91 -0
- package/src/infrastructure/database/sql/sql-junction-tables.ts +79 -0
- package/src/infrastructure/database/sql/sql-key-constraints.ts +210 -0
- package/src/infrastructure/database/sql/sql-type-mappings.ts +106 -0
- package/src/infrastructure/database/sql/sql-utils.ts +53 -0
- package/src/infrastructure/database/table-live-layers.ts +30 -0
- package/src/infrastructure/database/table-operations/column-generators.ts +82 -0
- package/src/infrastructure/database/table-operations/create-table-sql.ts +81 -0
- package/src/infrastructure/database/table-operations/index.ts +55 -0
- package/src/infrastructure/database/table-operations/migration-utils.ts +157 -0
- package/src/infrastructure/database/table-operations/table-effects.ts +234 -0
- package/src/infrastructure/database/table-operations/table-features.ts +96 -0
- package/src/infrastructure/database/table-operations/type-compatibility.ts +58 -0
- package/src/infrastructure/database/table-operations.ts +47 -0
- package/src/infrastructure/database/table-queries/batch/batch-create.ts +80 -0
- package/src/infrastructure/database/table-queries/batch/batch-delete.ts +212 -0
- package/src/infrastructure/database/table-queries/batch/batch-helpers.ts +124 -0
- package/src/infrastructure/database/table-queries/batch/batch-restore.ts +161 -0
- package/src/infrastructure/database/table-queries/batch/batch-update.ts +146 -0
- package/src/infrastructure/database/table-queries/batch/batch-upsert.ts +357 -0
- package/src/infrastructure/database/table-queries/batch/batch.ts +14 -0
- package/src/infrastructure/database/table-queries/crud/crud-read.ts +351 -0
- package/src/infrastructure/database/table-queries/crud/crud-write.ts +399 -0
- package/src/infrastructure/database/table-queries/crud/crud.ts +16 -0
- package/src/infrastructure/database/table-queries/index.ts +11 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/authorship-helpers.ts +152 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/create-record-helpers.ts +90 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/delete-helpers.ts +163 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/record-fetch-helpers.ts +79 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/update-helpers.ts +74 -0
- package/src/infrastructure/database/table-queries/query-helpers/activity-log-helpers.ts +53 -0
- package/src/infrastructure/database/table-queries/query-helpers/activity-queries.ts +106 -0
- package/src/infrastructure/database/table-queries/query-helpers/aggregation-helpers.ts +314 -0
- package/src/infrastructure/database/table-queries/query-helpers/comment-queries.ts +414 -0
- package/src/infrastructure/database/table-queries/query-helpers/record-validation-queries.ts +126 -0
- package/src/infrastructure/database/table-queries/query-helpers/trash-helpers.ts +58 -0
- package/src/infrastructure/database/table-queries/shared/error-handling.ts +47 -0
- package/src/infrastructure/database/table-queries/shared/typed-execute.ts +27 -0
- package/src/infrastructure/database/table-queries/shared/user-join-helpers.ts +38 -0
- package/src/infrastructure/database/table-queries/shared/validation.ts +39 -0
- package/src/infrastructure/database/views/view-generators.ts +258 -0
- package/src/infrastructure/devtools/devtools-layer.ts +43 -0
- package/src/infrastructure/devtools/index.ts +8 -0
- package/src/infrastructure/email/email-config.ts +103 -0
- package/src/infrastructure/email/email-service.ts +152 -0
- package/src/infrastructure/email/index.ts +107 -0
- package/src/infrastructure/email/nodemailer.ts +125 -0
- package/src/infrastructure/email/templates.ts +244 -0
- package/src/infrastructure/errors/auth-config-required-error.ts +21 -0
- package/src/infrastructure/errors/auth-error.ts +16 -0
- package/src/infrastructure/errors/css-compilation-error.ts +14 -0
- package/src/infrastructure/errors/index.ts +26 -0
- package/src/infrastructure/errors/schema-initialization-error.ts +19 -0
- package/src/infrastructure/errors/server-creation-error.ts +14 -0
- package/src/infrastructure/filesystem/copy-directory.ts +136 -0
- package/src/infrastructure/layers/app-layer.ts +61 -0
- package/src/infrastructure/layers/page-renderer-layer.ts +41 -0
- package/src/infrastructure/logging/index.ts +8 -0
- package/src/infrastructure/logging/logger.ts +204 -0
- package/src/infrastructure/schema/file-loader.ts +53 -0
- package/src/infrastructure/schema/index.ts +15 -0
- package/src/infrastructure/schema/remote-loader.ts +48 -0
- package/src/infrastructure/server/index.ts +26 -0
- package/src/infrastructure/server/language-detection.ts +87 -0
- package/src/infrastructure/server/lifecycle.ts +67 -0
- package/src/infrastructure/server/route-setup/api-routes.ts +310 -0
- package/src/infrastructure/server/route-setup/auth-route-utils.ts +399 -0
- package/src/infrastructure/server/route-setup/auth-routes.ts +245 -0
- package/src/infrastructure/server/route-setup/openapi-routes.ts +45 -0
- package/src/infrastructure/server/route-setup/openapi-schema.ts +120 -0
- package/src/infrastructure/server/route-setup/page-routes.ts +219 -0
- package/src/infrastructure/server/route-setup/static-assets.ts +191 -0
- package/src/infrastructure/server/server-factory-live.ts +45 -0
- package/src/infrastructure/server/server.ts +275 -0
- package/src/infrastructure/server/ssg-adapter.ts +196 -0
- package/src/infrastructure/server/static-site-generator-live.ts +20 -0
- package/src/infrastructure/utils/accept-language-parser.ts +106 -0
- package/src/infrastructure/utils/glob-matcher.ts +50 -0
- package/src/presentation/api/client.ts +114 -0
- package/src/presentation/api/middleware/auth.ts +233 -0
- package/src/presentation/api/middleware/table.ts +155 -0
- package/src/presentation/api/middleware/validation.ts +88 -0
- package/src/presentation/api/routes/activity/get-activity-by-id-handler.ts +77 -0
- package/src/presentation/api/routes/activity/index.ts +28 -0
- package/src/presentation/api/routes/activity.ts +339 -0
- package/src/presentation/api/routes/analytics.ts +328 -0
- package/src/presentation/api/routes/auth.ts +169 -0
- package/src/presentation/api/routes/index.ts +11 -0
- package/src/presentation/api/routes/tables/activity-handlers.ts +57 -0
- package/src/presentation/api/routes/tables/batch-permission-helpers.ts +163 -0
- package/src/presentation/api/routes/tables/batch-routes.ts +355 -0
- package/src/presentation/api/routes/tables/comment-handlers.ts +377 -0
- package/src/presentation/api/routes/tables/create-record-helpers.ts +179 -0
- package/src/presentation/api/routes/tables/effect-runner.ts +58 -0
- package/src/presentation/api/routes/tables/error-handlers.ts +53 -0
- package/src/presentation/api/routes/tables/field-permission-validation.ts +167 -0
- package/src/presentation/api/routes/tables/filter-parser.ts +75 -0
- package/src/presentation/api/routes/tables/formula-parser.ts +118 -0
- package/src/presentation/api/routes/tables/index.ts +113 -0
- package/src/presentation/api/routes/tables/list-records-filter.ts +54 -0
- package/src/presentation/api/routes/tables/param-parsers.ts +59 -0
- package/src/presentation/api/routes/tables/record-handlers.ts +484 -0
- package/src/presentation/api/routes/tables/record-routes.ts +53 -0
- package/src/presentation/api/routes/tables/record-update-handler.ts +200 -0
- package/src/presentation/api/routes/tables/sort-validation.ts +85 -0
- package/src/presentation/api/routes/tables/table-routes.ts +76 -0
- package/src/presentation/api/routes/tables/timezone-validation.ts +41 -0
- package/src/presentation/api/routes/tables/upsert-helpers.ts +471 -0
- package/src/presentation/api/routes/tables/utils.ts +159 -0
- package/src/presentation/api/routes/tables/view-routes.ts +51 -0
- package/src/presentation/api/routes/tables.ts +9 -0
- package/src/presentation/api/utils/context-helpers.ts +43 -0
- package/src/presentation/api/utils/error-sanitizer.ts +235 -0
- package/src/presentation/api/utils/field-permission-validator.ts +53 -0
- package/src/presentation/api/utils/filter-field-validator.ts +90 -0
- package/src/presentation/api/utils/index.ts +13 -0
- package/src/presentation/api/utils/run-effect.ts +94 -0
- package/src/presentation/api/utils/validate-request.ts +89 -0
- package/src/presentation/api/validation/index.ts +29 -0
- package/src/presentation/api/validation/rules/field-rules.ts +158 -0
- package/src/presentation/api/validation/rules/record-rules.ts +73 -0
- package/src/presentation/cli/index.ts +19 -0
- package/src/presentation/cli/schema-loader.ts +172 -0
- package/src/presentation/hooks/use-breakpoint.ts +155 -0
- package/src/presentation/rendering/render-error-pages.tsx +60 -0
- package/src/presentation/rendering/render-homepage.tsx +137 -0
- package/src/presentation/scripts/script-renderers.ts +112 -0
- package/src/presentation/styling/animation-composer.ts +117 -0
- package/src/presentation/styling/index.ts +13 -0
- package/src/presentation/styling/parse-style.ts +243 -0
- package/src/presentation/styling/style-utils.ts +50 -0
- package/src/presentation/styling/theme-colors.ts +53 -0
- package/src/presentation/translations/component-utils.ts +54 -0
- package/src/presentation/translations/index.ts +16 -0
- package/src/presentation/translations/translation-resolver.ts +22 -0
- package/src/presentation/ui/languages/language-switcher.tsx +119 -0
- package/src/presentation/ui/metadata/analytics-builders.tsx +174 -0
- package/src/presentation/ui/metadata/analytics-head.tsx +39 -0
- package/src/presentation/ui/metadata/custom-elements-builders.tsx +157 -0
- package/src/presentation/ui/metadata/extract-component-meta.ts +108 -0
- package/src/presentation/ui/metadata/head-elements.tsx +164 -0
- package/src/presentation/ui/metadata/index.tsx +35 -0
- package/src/presentation/ui/metadata/meta-utils.tsx +42 -0
- package/src/presentation/ui/metadata/open-graph-meta.tsx +57 -0
- package/src/presentation/ui/metadata/structured-data-from-component.tsx +134 -0
- package/src/presentation/ui/metadata/structured-data.tsx +88 -0
- package/src/presentation/ui/metadata/twitter-card-meta.tsx +80 -0
- package/src/presentation/ui/pages/DefaultHomePage.tsx +43 -0
- package/src/presentation/ui/pages/DefaultPageConfigs.ts +220 -0
- package/src/presentation/ui/pages/DynamicPage.tsx +307 -0
- package/src/presentation/ui/pages/ErrorPage.tsx +25 -0
- package/src/presentation/ui/pages/NotFoundPage.tsx +25 -0
- package/src/presentation/ui/pages/PageBodyScripts.tsx +242 -0
- package/src/presentation/ui/pages/PageBodyStyles.ts +52 -0
- package/src/presentation/ui/pages/PageHead.tsx +380 -0
- package/src/presentation/ui/pages/PageLangResolver.ts +58 -0
- package/src/presentation/ui/pages/PageMain.tsx +58 -0
- package/src/presentation/ui/pages/PageMetadata.ts +168 -0
- package/src/presentation/ui/pages/PageMetadataI18n.ts +169 -0
- package/src/presentation/ui/pages/PageScripts.ts +78 -0
- package/src/presentation/ui/pages/SectionRenderer.tsx +67 -0
- package/src/presentation/ui/pages/SectionSpacing.tsx +131 -0
- package/src/presentation/ui/sections/component-renderer.tsx +426 -0
- package/src/presentation/ui/sections/component-renderer.types.ts +33 -0
- package/src/presentation/ui/sections/components/component-reference-handler.tsx +74 -0
- package/src/presentation/ui/sections/components/component-resolution.ts +65 -0
- package/src/presentation/ui/sections/components/index.ts +9 -0
- package/src/presentation/ui/sections/hero.tsx +394 -0
- package/src/presentation/ui/sections/props/component-builder.ts +183 -0
- package/src/presentation/ui/sections/props/element-props.ts +179 -0
- package/src/presentation/ui/sections/props/index.ts +9 -0
- package/src/presentation/ui/sections/props/prop-conversion.ts +171 -0
- package/src/presentation/ui/sections/props/props-builder-config.ts +42 -0
- package/src/presentation/ui/sections/props/props-builder.ts +296 -0
- package/src/presentation/ui/sections/renderers/element-renderers/html-element-renderer.tsx +124 -0
- package/src/presentation/ui/sections/renderers/element-renderers/index.ts +59 -0
- package/src/presentation/ui/sections/renderers/element-renderers/interactive-renderers.tsx +231 -0
- package/src/presentation/ui/sections/renderers/element-renderers/media-renderers.tsx +102 -0
- package/src/presentation/ui/sections/renderers/element-renderers/text-content-renderers.tsx +42 -0
- package/src/presentation/ui/sections/renderers/element-renderers.ts +53 -0
- package/src/presentation/ui/sections/renderers/html-element-helpers.ts +100 -0
- package/src/presentation/ui/sections/renderers/specialized-renderers.tsx +212 -0
- package/src/presentation/ui/sections/rendering/component-dispatch-config.ts +31 -0
- package/src/presentation/ui/sections/rendering/component-registry/index.ts +39 -0
- package/src/presentation/ui/sections/rendering/component-registry/interactive-components.ts +54 -0
- package/src/presentation/ui/sections/rendering/component-registry/media-components.ts +36 -0
- package/src/presentation/ui/sections/rendering/component-registry/special-components.tsx +153 -0
- package/src/presentation/ui/sections/rendering/component-registry/structural-components.ts +215 -0
- package/src/presentation/ui/sections/rendering/component-registry/text-components.ts +57 -0
- package/src/presentation/ui/sections/rendering/component-registry-helpers.tsx +29 -0
- package/src/presentation/ui/sections/rendering/component-registry.tsx +21 -0
- package/src/presentation/ui/sections/rendering/component-type-dispatcher.tsx +33 -0
- package/src/presentation/ui/sections/rendering/index.ts +9 -0
- package/src/presentation/ui/sections/responsive/responsive-children-builder.tsx +96 -0
- package/src/presentation/ui/sections/responsive/responsive-content-builder.tsx +95 -0
- package/src/presentation/ui/sections/responsive/responsive-props-merger.ts +195 -0
- package/src/presentation/ui/sections/responsive/responsive-resolver.ts +213 -0
- package/src/presentation/ui/sections/styling/animation-composer-wrapper.ts +65 -0
- package/src/presentation/ui/sections/styling/class-builders.ts +45 -0
- package/src/presentation/ui/sections/styling/color-resolver.ts +43 -0
- package/src/presentation/ui/sections/styling/hover-interaction-handler.ts +107 -0
- package/src/presentation/ui/sections/styling/index.ts +9 -0
- package/src/presentation/ui/sections/styling/interaction-props-builder.ts +55 -0
- package/src/presentation/ui/sections/styling/shadow-resolver.ts +83 -0
- package/src/presentation/ui/sections/styling/spacing-resolver.ts +104 -0
- package/src/presentation/ui/sections/styling/style-processor.ts +170 -0
- package/src/presentation/ui/sections/styling/theme-tokens.ts +184 -0
- package/src/presentation/ui/sections/translations/i18n-content-resolver.ts +198 -0
- package/src/presentation/ui/sections/translations/index.ts +9 -0
- package/src/presentation/ui/sections/translations/translation-handler.ts +143 -0
- package/src/presentation/ui/sections/translations/variable-substitution.ts +225 -0
- package/src/presentation/ui/sections/utils/time-parser.ts +82 -0
- package/src/presentation/utils/link-attributes.ts +50 -0
- package/src/presentation/utils/string-utils.ts +58 -0
- package/src/presentation/utils/styles.ts +50 -0
- package/tsconfig.json +46 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Rate limiting state for admin endpoints
|
|
10
|
+
* Maps IP addresses to request timestamps
|
|
11
|
+
* In production, this should use Redis or similar distributed storage
|
|
12
|
+
*/
|
|
13
|
+
const adminRateLimitState = new Map<string, number[]>()
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Rate limiting configuration constants
|
|
17
|
+
*/
|
|
18
|
+
const RATE_LIMIT_WINDOW_MS = 1000 // 1 second window
|
|
19
|
+
const RATE_LIMIT_MAX_REQUESTS = 10 // Maximum requests per window
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get rate limit window duration in milliseconds from environment variable
|
|
23
|
+
* Defaults to 60 seconds (production) if not set
|
|
24
|
+
* Tests should set RATE_LIMIT_WINDOW_SECONDS=5 for faster execution
|
|
25
|
+
*/
|
|
26
|
+
const getRateLimitWindowMs = (): number => {
|
|
27
|
+
const windowSeconds = process.env.RATE_LIMIT_WINDOW_SECONDS
|
|
28
|
+
return windowSeconds ? parseInt(windowSeconds, 10) * 1000 : 60 * 1000
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get recent requests within the rate limit window for an IP address
|
|
33
|
+
* Returns filtered array of timestamps within RATE_LIMIT_WINDOW_MS
|
|
34
|
+
* Single source of truth for request filtering (DRY principle)
|
|
35
|
+
*/
|
|
36
|
+
export const getRecentRequests = (ip: string): readonly number[] => {
|
|
37
|
+
const now = Date.now()
|
|
38
|
+
const requestHistory = adminRateLimitState.get(ip) ?? []
|
|
39
|
+
return requestHistory.filter((timestamp) => now - timestamp < RATE_LIMIT_WINDOW_MS)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check rate limit for an IP address
|
|
44
|
+
* Returns true if rate limit is exceeded, false otherwise
|
|
45
|
+
*/
|
|
46
|
+
export const isRateLimitExceeded = (ip: string): boolean => {
|
|
47
|
+
return getRecentRequests(ip).length >= RATE_LIMIT_MAX_REQUESTS
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Record a request for rate limiting
|
|
52
|
+
* Updates the request history for the given IP address
|
|
53
|
+
* Returns the updated request history for testing/debugging
|
|
54
|
+
*/
|
|
55
|
+
export const recordRateLimitRequest = (ip: string): readonly number[] => {
|
|
56
|
+
const recentRequests = getRecentRequests(ip)
|
|
57
|
+
const now = Date.now()
|
|
58
|
+
const updated = [...recentRequests, now]
|
|
59
|
+
// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data -- Rate limiting requires mutable state
|
|
60
|
+
adminRateLimitState.set(ip, updated)
|
|
61
|
+
return updated
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract IP address from request headers
|
|
66
|
+
* Returns the client IP from x-forwarded-for or defaults to localhost
|
|
67
|
+
*/
|
|
68
|
+
export const extractClientIp = (forwardedFor: string | undefined): string => {
|
|
69
|
+
return forwardedFor ? (forwardedFor.split(',')[0]?.trim() ?? '127.0.0.1') : '127.0.0.1'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Rate limiting configuration for authentication endpoints
|
|
74
|
+
* Security-critical endpoints to prevent brute force attacks
|
|
75
|
+
*/
|
|
76
|
+
interface EndpointRateLimitConfig {
|
|
77
|
+
readonly windowMs: number
|
|
78
|
+
readonly maxRequests: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get authentication rate limit configurations
|
|
83
|
+
* Uses configurable window from environment variable
|
|
84
|
+
*/
|
|
85
|
+
const getAuthRateLimitConfigs = (): Record<string, EndpointRateLimitConfig> => {
|
|
86
|
+
const windowMs = getRateLimitWindowMs()
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
'/api/auth/sign-in/email': {
|
|
90
|
+
windowMs,
|
|
91
|
+
maxRequests: 20, // 20 attempts per window (prevents brute force while allowing legitimate retries)
|
|
92
|
+
},
|
|
93
|
+
'/api/auth/sign-up/email': {
|
|
94
|
+
windowMs,
|
|
95
|
+
maxRequests: 20, // 20 signups per window (prevents abuse while allowing test scenarios)
|
|
96
|
+
},
|
|
97
|
+
'/api/auth/request-password-reset': {
|
|
98
|
+
windowMs,
|
|
99
|
+
maxRequests: 10, // 10 attempts per window (prevents enumeration while allowing legitimate use)
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const AUTH_RATE_LIMIT_CONFIGS: Record<string, EndpointRateLimitConfig> = getAuthRateLimitConfigs()
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Rate limiting state for authentication endpoints
|
|
108
|
+
* Maps endpoint + IP to request timestamps
|
|
109
|
+
*/
|
|
110
|
+
const authRateLimitState = new Map<string, number[]>()
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get rate limit key for endpoint + IP combination
|
|
114
|
+
*/
|
|
115
|
+
const getRateLimitKey = (endpoint: string, ip: string): string => {
|
|
116
|
+
return `${endpoint}:${ip}`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get recent requests within the rate limit window for an endpoint + IP
|
|
121
|
+
*/
|
|
122
|
+
export const getAuthRecentRequests = (endpoint: string, ip: string): readonly number[] => {
|
|
123
|
+
const config = AUTH_RATE_LIMIT_CONFIGS[endpoint]
|
|
124
|
+
if (!config) return []
|
|
125
|
+
|
|
126
|
+
const now = Date.now()
|
|
127
|
+
const key = getRateLimitKey(endpoint, ip)
|
|
128
|
+
const requestHistory = authRateLimitState.get(key) ?? []
|
|
129
|
+
return requestHistory.filter((timestamp) => now - timestamp < config.windowMs)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if auth endpoint rate limit is exceeded for an IP
|
|
134
|
+
*/
|
|
135
|
+
export const isAuthRateLimitExceeded = (endpoint: string, ip: string): boolean => {
|
|
136
|
+
const config = AUTH_RATE_LIMIT_CONFIGS[endpoint]
|
|
137
|
+
if (!config) return false
|
|
138
|
+
|
|
139
|
+
return getAuthRecentRequests(endpoint, ip).length >= config.maxRequests
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Record a request for auth endpoint rate limiting
|
|
144
|
+
*/
|
|
145
|
+
export const recordAuthRateLimitRequest = (endpoint: string, ip: string): readonly number[] => {
|
|
146
|
+
const recentRequests = getAuthRecentRequests(endpoint, ip)
|
|
147
|
+
const now = Date.now()
|
|
148
|
+
const key = getRateLimitKey(endpoint, ip)
|
|
149
|
+
const updated = [...recentRequests, now]
|
|
150
|
+
// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data -- Rate limiting requires mutable state
|
|
151
|
+
authRateLimitState.set(key, updated)
|
|
152
|
+
return updated
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Calculate seconds until auth rate limit window resets
|
|
157
|
+
*/
|
|
158
|
+
export const getAuthRateLimitRetryAfter = (endpoint: string, ip: string): number => {
|
|
159
|
+
const config = AUTH_RATE_LIMIT_CONFIGS[endpoint]
|
|
160
|
+
if (!config) return 0
|
|
161
|
+
|
|
162
|
+
const recentRequests = getAuthRecentRequests(endpoint, ip)
|
|
163
|
+
if (recentRequests.length === 0) return 0
|
|
164
|
+
|
|
165
|
+
const oldestRequest = Math.min(...recentRequests)
|
|
166
|
+
const resetTime = oldestRequest + config.windowMs
|
|
167
|
+
const now = Date.now()
|
|
168
|
+
const retryAfterMs = Math.max(0, resetTime - now)
|
|
169
|
+
|
|
170
|
+
return Math.ceil(retryAfterMs / 1000) // Convert to seconds
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Rate limiting configuration for table API endpoints
|
|
175
|
+
* Higher limits than auth endpoints since legitimate usage involves frequent data operations
|
|
176
|
+
*/
|
|
177
|
+
const getTablesRateLimitConfigs = (): Record<string, EndpointRateLimitConfig> => {
|
|
178
|
+
const windowMs = getRateLimitWindowMs()
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
// List tables endpoint
|
|
182
|
+
'GET:/api/tables': {
|
|
183
|
+
windowMs,
|
|
184
|
+
maxRequests: 100, // 100 requests per window (frequent polling for table lists)
|
|
185
|
+
},
|
|
186
|
+
// Get table records endpoint (read operations)
|
|
187
|
+
'GET:/api/tables/*': {
|
|
188
|
+
windowMs,
|
|
189
|
+
maxRequests: 100, // 100 requests per window (frequent data fetching)
|
|
190
|
+
},
|
|
191
|
+
// Create record endpoint (write operations - stricter limits)
|
|
192
|
+
'POST:/api/tables/*': {
|
|
193
|
+
windowMs,
|
|
194
|
+
maxRequests: 50, // 50 requests per window (prevent data flooding)
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const TABLES_RATE_LIMIT_CONFIGS: Record<string, EndpointRateLimitConfig> =
|
|
200
|
+
getTablesRateLimitConfigs()
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Rate limiting state for table endpoints
|
|
204
|
+
* Maps method:endpoint + IP to request timestamps
|
|
205
|
+
*/
|
|
206
|
+
const tablesRateLimitState = new Map<string, number[]>()
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get rate limit key for table endpoint (includes HTTP method)
|
|
210
|
+
*/
|
|
211
|
+
const getTablesRateLimitKey = (method: string, path: string, ip: string): string => {
|
|
212
|
+
// Match exact path or wildcard pattern
|
|
213
|
+
const configKey = TABLES_RATE_LIMIT_CONFIGS[`${method}:${path}`]
|
|
214
|
+
? `${method}:${path}`
|
|
215
|
+
: `${method}:/api/tables/*`
|
|
216
|
+
|
|
217
|
+
return `${configKey}:${ip}`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get recent requests for table endpoint
|
|
222
|
+
*/
|
|
223
|
+
export const getTablesRecentRequests = (
|
|
224
|
+
method: string,
|
|
225
|
+
path: string,
|
|
226
|
+
ip: string
|
|
227
|
+
): readonly number[] => {
|
|
228
|
+
const configKey = TABLES_RATE_LIMIT_CONFIGS[`${method}:${path}`]
|
|
229
|
+
? `${method}:${path}`
|
|
230
|
+
: `${method}:/api/tables/*`
|
|
231
|
+
|
|
232
|
+
const config = TABLES_RATE_LIMIT_CONFIGS[configKey]
|
|
233
|
+
if (!config) return []
|
|
234
|
+
|
|
235
|
+
const now = Date.now()
|
|
236
|
+
const key = getTablesRateLimitKey(method, path, ip)
|
|
237
|
+
const requestHistory = tablesRateLimitState.get(key) ?? []
|
|
238
|
+
return requestHistory.filter((timestamp) => now - timestamp < config.windowMs)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check if table endpoint rate limit is exceeded
|
|
243
|
+
*/
|
|
244
|
+
export const isTablesRateLimitExceeded = (method: string, path: string, ip: string): boolean => {
|
|
245
|
+
const configKey = TABLES_RATE_LIMIT_CONFIGS[`${method}:${path}`]
|
|
246
|
+
? `${method}:${path}`
|
|
247
|
+
: `${method}:/api/tables/*`
|
|
248
|
+
|
|
249
|
+
const config = TABLES_RATE_LIMIT_CONFIGS[configKey]
|
|
250
|
+
if (!config) return false
|
|
251
|
+
|
|
252
|
+
return getTablesRecentRequests(method, path, ip).length >= config.maxRequests
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Record a request for table endpoint rate limiting
|
|
257
|
+
*/
|
|
258
|
+
export const recordTablesRateLimitRequest = (
|
|
259
|
+
method: string,
|
|
260
|
+
path: string,
|
|
261
|
+
ip: string
|
|
262
|
+
): readonly number[] => {
|
|
263
|
+
const recentRequests = getTablesRecentRequests(method, path, ip)
|
|
264
|
+
const now = Date.now()
|
|
265
|
+
const key = getTablesRateLimitKey(method, path, ip)
|
|
266
|
+
const updated = [...recentRequests, now]
|
|
267
|
+
// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data -- Rate limiting requires mutable state
|
|
268
|
+
tablesRateLimitState.set(key, updated)
|
|
269
|
+
return updated
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Calculate seconds until rate limit window resets
|
|
274
|
+
*/
|
|
275
|
+
export const getTablesRateLimitRetryAfter = (method: string, path: string, ip: string): number => {
|
|
276
|
+
const recentRequests = getTablesRecentRequests(method, path, ip)
|
|
277
|
+
if (recentRequests.length === 0) return 0
|
|
278
|
+
|
|
279
|
+
const windowMs = getRateLimitWindowMs()
|
|
280
|
+
const oldestRequest = Math.min(...recentRequests)
|
|
281
|
+
const resetTime = oldestRequest + windowMs
|
|
282
|
+
const now = Date.now()
|
|
283
|
+
const retryAfterMs = Math.max(0, resetTime - now)
|
|
284
|
+
|
|
285
|
+
return Math.ceil(retryAfterMs / 1000) // Convert to seconds
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Rate limiting configuration for activity API endpoints
|
|
290
|
+
* Moderate limits since activity logs can be polled frequently for real-time updates
|
|
291
|
+
*/
|
|
292
|
+
const getActivityRateLimitConfigs = (): Record<string, EndpointRateLimitConfig> => {
|
|
293
|
+
const windowMs = getRateLimitWindowMs()
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
// List activity endpoint
|
|
297
|
+
'GET:/api/activity': {
|
|
298
|
+
windowMs,
|
|
299
|
+
maxRequests: 60, // 60 requests per window (polling for activity updates)
|
|
300
|
+
},
|
|
301
|
+
// Get activity detail endpoint
|
|
302
|
+
'GET:/api/activity/*': {
|
|
303
|
+
windowMs,
|
|
304
|
+
maxRequests: 60, // 60 requests per window (activity log detail fetching)
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const ACTIVITY_RATE_LIMIT_CONFIGS: Record<string, EndpointRateLimitConfig> =
|
|
310
|
+
getActivityRateLimitConfigs()
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Rate limiting state for activity endpoints
|
|
314
|
+
* Maps method:endpoint + IP to request timestamps
|
|
315
|
+
*/
|
|
316
|
+
const activityRateLimitState = new Map<string, number[]>()
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get rate limit key for activity endpoint (includes HTTP method)
|
|
320
|
+
*/
|
|
321
|
+
const getActivityRateLimitKey = (method: string, path: string, ip: string): string => {
|
|
322
|
+
const configKey = ACTIVITY_RATE_LIMIT_CONFIGS[`${method}:${path}`]
|
|
323
|
+
? `${method}:${path}`
|
|
324
|
+
: `${method}:/api/activity/*`
|
|
325
|
+
|
|
326
|
+
return `${configKey}:${ip}`
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get recent requests for activity endpoint
|
|
331
|
+
*/
|
|
332
|
+
export const getActivityRecentRequests = (
|
|
333
|
+
method: string,
|
|
334
|
+
path: string,
|
|
335
|
+
ip: string
|
|
336
|
+
): readonly number[] => {
|
|
337
|
+
const configKey = ACTIVITY_RATE_LIMIT_CONFIGS[`${method}:${path}`]
|
|
338
|
+
? `${method}:${path}`
|
|
339
|
+
: `${method}:/api/activity/*`
|
|
340
|
+
|
|
341
|
+
const config = ACTIVITY_RATE_LIMIT_CONFIGS[configKey]
|
|
342
|
+
if (!config) return []
|
|
343
|
+
|
|
344
|
+
const now = Date.now()
|
|
345
|
+
const key = getActivityRateLimitKey(method, path, ip)
|
|
346
|
+
const requestHistory = activityRateLimitState.get(key) ?? []
|
|
347
|
+
return requestHistory.filter((timestamp) => now - timestamp < config.windowMs)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if activity endpoint rate limit is exceeded
|
|
352
|
+
*/
|
|
353
|
+
export const isActivityRateLimitExceeded = (method: string, path: string, ip: string): boolean => {
|
|
354
|
+
const configKey = ACTIVITY_RATE_LIMIT_CONFIGS[`${method}:${path}`]
|
|
355
|
+
? `${method}:${path}`
|
|
356
|
+
: `${method}:/api/activity/*`
|
|
357
|
+
|
|
358
|
+
const config = ACTIVITY_RATE_LIMIT_CONFIGS[configKey]
|
|
359
|
+
if (!config) return false
|
|
360
|
+
|
|
361
|
+
return getActivityRecentRequests(method, path, ip).length >= config.maxRequests
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Record a request for activity endpoint rate limiting
|
|
366
|
+
*/
|
|
367
|
+
export const recordActivityRateLimitRequest = (
|
|
368
|
+
method: string,
|
|
369
|
+
path: string,
|
|
370
|
+
ip: string
|
|
371
|
+
): readonly number[] => {
|
|
372
|
+
const recentRequests = getActivityRecentRequests(method, path, ip)
|
|
373
|
+
const now = Date.now()
|
|
374
|
+
const key = getActivityRateLimitKey(method, path, ip)
|
|
375
|
+
const updated = [...recentRequests, now]
|
|
376
|
+
// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data -- Rate limiting requires mutable state
|
|
377
|
+
activityRateLimitState.set(key, updated)
|
|
378
|
+
return updated
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Calculate seconds until activity rate limit window resets
|
|
383
|
+
*/
|
|
384
|
+
export const getActivityRateLimitRetryAfter = (
|
|
385
|
+
method: string,
|
|
386
|
+
path: string,
|
|
387
|
+
ip: string
|
|
388
|
+
): number => {
|
|
389
|
+
const recentRequests = getActivityRecentRequests(method, path, ip)
|
|
390
|
+
if (recentRequests.length === 0) return 0
|
|
391
|
+
|
|
392
|
+
const windowMs = getRateLimitWindowMs()
|
|
393
|
+
const oldestRequest = Math.min(...recentRequests)
|
|
394
|
+
const resetTime = oldestRequest + windowMs
|
|
395
|
+
const now = Date.now()
|
|
396
|
+
const retryAfterMs = Math.max(0, resetTime - now)
|
|
397
|
+
|
|
398
|
+
return Math.ceil(retryAfterMs / 1000) // Convert to seconds
|
|
399
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type Hono } from 'hono'
|
|
9
|
+
import { cors } from 'hono/cors'
|
|
10
|
+
import { createAuthInstance } from '@/infrastructure/auth/better-auth/auth'
|
|
11
|
+
import { logError } from '@/infrastructure/logging/logger'
|
|
12
|
+
import {
|
|
13
|
+
isRateLimitExceeded,
|
|
14
|
+
recordRateLimitRequest,
|
|
15
|
+
extractClientIp,
|
|
16
|
+
isAuthRateLimitExceeded,
|
|
17
|
+
recordAuthRateLimitRequest,
|
|
18
|
+
getAuthRateLimitRetryAfter,
|
|
19
|
+
} from './auth-route-utils'
|
|
20
|
+
import type { App } from '@/domain/models/app'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Apply authentication check middleware for admin endpoints
|
|
24
|
+
*
|
|
25
|
+
* This middleware ensures authentication is checked BEFORE parameter validation,
|
|
26
|
+
* preventing information leakage through error responses (400/404/403 vs 401).
|
|
27
|
+
*
|
|
28
|
+
* Without this middleware, Better Auth's admin endpoints validate parameters first,
|
|
29
|
+
* allowing unauthenticated users to probe for valid user IDs by observing response codes.
|
|
30
|
+
*
|
|
31
|
+
* Returns a Hono app with authentication middleware applied
|
|
32
|
+
*/
|
|
33
|
+
const applyAuthCheckMiddleware = (
|
|
34
|
+
honoApp: Readonly<Hono>,
|
|
35
|
+
authInstance: Readonly<ReturnType<typeof createAuthInstance>>
|
|
36
|
+
): Readonly<Hono> => {
|
|
37
|
+
return honoApp.use('/api/auth/admin/*', async (c, next) => {
|
|
38
|
+
try {
|
|
39
|
+
// Check if request has a valid session cookie
|
|
40
|
+
const session = await authInstance.api.getSession({
|
|
41
|
+
headers: c.req.raw.headers,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Return 401 if no valid session (BEFORE any parameter validation)
|
|
45
|
+
if (!session) {
|
|
46
|
+
return c.json(
|
|
47
|
+
{ success: false, message: 'Authentication required', code: 'UNAUTHORIZED' },
|
|
48
|
+
401
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Session exists - proceed to next handler
|
|
53
|
+
// eslint-disable-next-line functional/no-expression-statements -- Hono middleware requires calling next()
|
|
54
|
+
await next()
|
|
55
|
+
} catch (error) {
|
|
56
|
+
// If session check fails, return 401 (assume unauthenticated)
|
|
57
|
+
logError('[Auth Middleware] Session check error', error)
|
|
58
|
+
return c.json(
|
|
59
|
+
{ success: false, message: 'Authentication required', code: 'UNAUTHORIZED' },
|
|
60
|
+
401
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Apply rate limiting middleware for admin endpoints
|
|
68
|
+
* Returns a Hono app with rate limiting middleware applied
|
|
69
|
+
*/
|
|
70
|
+
const applyRateLimitMiddleware = (honoApp: Readonly<Hono>): Readonly<Hono> => {
|
|
71
|
+
return honoApp.use('/api/auth/admin/*', async (c, next) => {
|
|
72
|
+
const ip = extractClientIp(c.req.header('x-forwarded-for'))
|
|
73
|
+
|
|
74
|
+
if (isRateLimitExceeded(ip)) {
|
|
75
|
+
return c.json(
|
|
76
|
+
{
|
|
77
|
+
success: false,
|
|
78
|
+
message: 'Too many requests. Please try again later.',
|
|
79
|
+
code: 'RATE_LIMITED',
|
|
80
|
+
},
|
|
81
|
+
429
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
recordRateLimitRequest(ip) // eslint-disable-line functional/no-expression-statements -- Rate limiting state update
|
|
86
|
+
|
|
87
|
+
// eslint-disable-next-line functional/no-expression-statements -- Hono middleware requires calling next()
|
|
88
|
+
await next()
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Apply rate limiting middleware for authentication endpoints
|
|
94
|
+
*
|
|
95
|
+
* Protects security-critical authentication endpoints from brute force attacks:
|
|
96
|
+
* - sign-in: 5 attempts per 60 seconds (prevent credential stuffing)
|
|
97
|
+
* - sign-up: 5 attempts per 60 seconds (prevent account creation abuse)
|
|
98
|
+
* - request-password-reset: 3 attempts per 60 seconds (prevent email enumeration)
|
|
99
|
+
*
|
|
100
|
+
* Returns a Hono app with rate limiting middleware applied
|
|
101
|
+
*/
|
|
102
|
+
const applyAuthRateLimitMiddleware = (honoApp: Readonly<Hono>): Readonly<Hono> => {
|
|
103
|
+
const endpoints = [
|
|
104
|
+
'/api/auth/sign-in/email',
|
|
105
|
+
'/api/auth/sign-up/email',
|
|
106
|
+
'/api/auth/request-password-reset',
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
const result = endpoints.reduce((app, endpoint) => {
|
|
110
|
+
return app.use(endpoint, async (c, next) => {
|
|
111
|
+
const ip = extractClientIp(c.req.header('x-forwarded-for'))
|
|
112
|
+
const { path } = c.req
|
|
113
|
+
|
|
114
|
+
if (isAuthRateLimitExceeded(path, ip)) {
|
|
115
|
+
const retryAfter = getAuthRateLimitRetryAfter(path, ip)
|
|
116
|
+
return c.json(
|
|
117
|
+
{
|
|
118
|
+
success: false,
|
|
119
|
+
message: 'Too many requests. Please try again later.',
|
|
120
|
+
code: 'RATE_LIMITED',
|
|
121
|
+
},
|
|
122
|
+
429,
|
|
123
|
+
{ 'Retry-After': retryAfter.toString() }
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
recordAuthRateLimitRequest(path, ip) // eslint-disable-line functional/no-expression-statements -- Rate limiting state update
|
|
128
|
+
|
|
129
|
+
// eslint-disable-next-line functional/no-expression-statements -- Hono middleware requires calling next()
|
|
130
|
+
await next()
|
|
131
|
+
})
|
|
132
|
+
}, honoApp)
|
|
133
|
+
|
|
134
|
+
return result
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Setup CORS middleware for Better Auth endpoints
|
|
139
|
+
*
|
|
140
|
+
* Configures CORS to allow:
|
|
141
|
+
* - All localhost origins for development/testing
|
|
142
|
+
* - Credentials for cookie-based authentication
|
|
143
|
+
* - Common headers and methods
|
|
144
|
+
*
|
|
145
|
+
* If no auth configuration is provided in the app, middleware is not applied.
|
|
146
|
+
*
|
|
147
|
+
* @param honoApp - Hono application instance
|
|
148
|
+
* @param app - Application configuration with auth settings
|
|
149
|
+
* @returns Hono app with CORS middleware configured (or unchanged if auth is disabled)
|
|
150
|
+
*/
|
|
151
|
+
export function setupAuthMiddleware(honoApp: Readonly<Hono>, app?: App): Readonly<Hono> {
|
|
152
|
+
// If no auth config is provided, don't apply CORS middleware
|
|
153
|
+
if (!app?.auth) {
|
|
154
|
+
return honoApp
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return honoApp.use(
|
|
158
|
+
'/api/auth/*',
|
|
159
|
+
cors({
|
|
160
|
+
origin: (origin) => {
|
|
161
|
+
// Allow all localhost origins for development and testing
|
|
162
|
+
if (origin.startsWith('http://localhost:') || origin.startsWith('http://127.0.0.1:')) {
|
|
163
|
+
return origin
|
|
164
|
+
}
|
|
165
|
+
// In production, this should be configured with specific allowed origins
|
|
166
|
+
return origin
|
|
167
|
+
},
|
|
168
|
+
allowHeaders: ['Content-Type', 'Authorization'],
|
|
169
|
+
allowMethods: ['POST', 'GET', 'OPTIONS'],
|
|
170
|
+
exposeHeaders: ['Content-Length'],
|
|
171
|
+
maxAge: 600,
|
|
172
|
+
credentials: true, // Required for cookie-based authentication
|
|
173
|
+
})
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Setup Better Auth routes with dynamic configuration
|
|
179
|
+
*
|
|
180
|
+
* Mounts Better Auth handler at /api/auth/* which provides all authentication endpoints.
|
|
181
|
+
*
|
|
182
|
+
* Creates a Better Auth instance dynamically based on the app's auth configuration,
|
|
183
|
+
* allowing features like requireEmailVerification to be controlled per app.
|
|
184
|
+
*
|
|
185
|
+
* IMPORTANT: Better Auth instance is created once per app configuration and reused
|
|
186
|
+
* across all requests to maintain internal state consistency.
|
|
187
|
+
*
|
|
188
|
+
* If no auth configuration is provided, no auth routes are registered and all
|
|
189
|
+
* /api/auth/* requests will return 404 Not Found.
|
|
190
|
+
*
|
|
191
|
+
* Better Auth natively provides:
|
|
192
|
+
* - Authentication: sign-up, sign-in, sign-out, verify-email, send-verification-email
|
|
193
|
+
* - Admin Plugin: list-users, get-user, set-role, ban-user, unban-user, impersonate-user, stop-impersonating
|
|
194
|
+
* - Organization Plugin: create-organization, list-organizations, get-organization, set-active-organization
|
|
195
|
+
* - Two-Factor: enable, disable, verify
|
|
196
|
+
* - Magic Link: send, verify
|
|
197
|
+
*
|
|
198
|
+
* Native Better Auth handles:
|
|
199
|
+
* - Banned user rejection (automatic in admin plugin)
|
|
200
|
+
* - Single-use verification tokens (automatic)
|
|
201
|
+
* - Admin role validation (via adminRoles/adminUserIds config)
|
|
202
|
+
* - Organization membership validation (automatic)
|
|
203
|
+
*
|
|
204
|
+
* @param honoApp - Hono application instance
|
|
205
|
+
* @param app - Application configuration with auth settings
|
|
206
|
+
* @returns Hono app with auth routes configured (or unchanged if auth is disabled)
|
|
207
|
+
*/
|
|
208
|
+
export function setupAuthRoutes(honoApp: Readonly<Hono>, app?: App): Readonly<Hono> {
|
|
209
|
+
// If no auth config is provided, don't register any auth routes
|
|
210
|
+
// This causes all /api/auth/* requests to return 404 (not found)
|
|
211
|
+
if (!app?.auth) {
|
|
212
|
+
return honoApp
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Create Better Auth instance with app-specific configuration (once per app startup)
|
|
216
|
+
// This instance is reused across all requests to maintain internal state
|
|
217
|
+
const authInstance = createAuthInstance(app.auth)
|
|
218
|
+
|
|
219
|
+
// Apply authentication check middleware (admin features always enabled when auth is configured)
|
|
220
|
+
// This ensures 401 is returned before any parameter validation, preventing information leakage
|
|
221
|
+
const appWithAuthCheck = applyAuthCheckMiddleware(honoApp, authInstance)
|
|
222
|
+
|
|
223
|
+
// Apply rate limiting middleware to admin routes
|
|
224
|
+
const appWithAdminRateLimit = applyRateLimitMiddleware(appWithAuthCheck)
|
|
225
|
+
|
|
226
|
+
// Apply rate limiting middleware to authentication endpoints (sign-in, sign-up, password reset)
|
|
227
|
+
const appWithAuthRateLimit = applyAuthRateLimitMiddleware(appWithAdminRateLimit)
|
|
228
|
+
|
|
229
|
+
// Mount Better Auth handler for all /api/auth/* routes
|
|
230
|
+
// Better Auth natively handles:
|
|
231
|
+
// - Authentication flows (sign-up, sign-in, sign-out, email verification)
|
|
232
|
+
// - Admin operations (list-users, get-user, set-role, ban-user, impersonation)
|
|
233
|
+
// - Organization management (create, list, get, set-active, invite members)
|
|
234
|
+
// - Two-factor authentication (enable, disable, verify)
|
|
235
|
+
// - Magic link authentication (send, verify)
|
|
236
|
+
// - Banned user rejection (automatic for admin plugin)
|
|
237
|
+
// - Single-use verification tokens (automatic)
|
|
238
|
+
// - Team operations (create-team, add-team-member, etc.) when teams plugin enabled
|
|
239
|
+
//
|
|
240
|
+
// IMPORTANT: Better Auth handles its own routing and expects the FULL request path
|
|
241
|
+
// including the /api/auth prefix. We pass the original request without modification.
|
|
242
|
+
return appWithAuthRateLimit.on(['POST', 'GET'], '/api/auth/*', async (c) => {
|
|
243
|
+
return authInstance.handler(c.req.raw)
|
|
244
|
+
})
|
|
245
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Scalar } from '@scalar/hono-api-reference'
|
|
9
|
+
import { type Hono } from 'hono'
|
|
10
|
+
import { auth } from '@/infrastructure/auth/better-auth/auth'
|
|
11
|
+
import { getOpenAPIDocument } from '@/infrastructure/server/route-setup/openapi-schema'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Setup OpenAPI documentation routes
|
|
15
|
+
*
|
|
16
|
+
* Mounts:
|
|
17
|
+
* - GET /api/openapi.json - Application API schema
|
|
18
|
+
* - GET /api/auth/openapi.json - Better Auth API schema
|
|
19
|
+
* - GET /api/scalar - Unified Scalar API documentation UI
|
|
20
|
+
*
|
|
21
|
+
* @param honoApp - Hono application instance
|
|
22
|
+
* @returns Hono app with OpenAPI routes configured
|
|
23
|
+
*/
|
|
24
|
+
export function setupOpenApiRoutes(honoApp: Readonly<Hono>): Readonly<Hono> {
|
|
25
|
+
return honoApp
|
|
26
|
+
.get('/api/openapi.json', (c) => {
|
|
27
|
+
const openApiDoc = getOpenAPIDocument()
|
|
28
|
+
return c.json(openApiDoc)
|
|
29
|
+
})
|
|
30
|
+
.get('/api/auth/openapi.json', async (c) => {
|
|
31
|
+
const authOpenApiDoc = await auth.api.generateOpenAPISchema()
|
|
32
|
+
return c.json(authOpenApiDoc)
|
|
33
|
+
})
|
|
34
|
+
.get(
|
|
35
|
+
'/api/scalar',
|
|
36
|
+
Scalar({
|
|
37
|
+
pageTitle: 'Sovrium API Documentation',
|
|
38
|
+
theme: 'default',
|
|
39
|
+
sources: [
|
|
40
|
+
{ url: '/api/openapi.json', title: 'API' },
|
|
41
|
+
{ url: '/api/auth/openapi.json', title: 'Auth' },
|
|
42
|
+
],
|
|
43
|
+
})
|
|
44
|
+
)
|
|
45
|
+
}
|