@sixthsummit/ship-api 0.0.0-stage → 4.1.1
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 +605 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +58 -2
- package/cli/commands/add.js +537 -0
- package/cli/commands/contract.js +2 -0
- package/cli/commands/create.js +250 -0
- package/cli/commands/dev.js +227 -0
- package/cli/commands/doctor.js +286 -0
- package/cli/commands/generate.js +937 -0
- package/cli/commands/graph.js +2 -0
- package/cli/commands/migrate-orm.js +1171 -0
- package/cli/commands/update.js +37 -0
- package/cli/ship-api.js +285 -0
- package/lib/app/auth/AuthProvider.d.ts +91 -0
- package/lib/app/auth/AuthProvider.d.ts.map +1 -0
- package/lib/app/auth/AuthProvider.js +13 -0
- package/lib/app/auth/AuthProvider.js.map +1 -0
- package/lib/app/auth/adapters/firebase.d.ts +79 -0
- package/lib/app/auth/adapters/firebase.d.ts.map +1 -0
- package/lib/app/auth/adapters/firebase.js +122 -0
- package/lib/app/auth/adapters/firebase.js.map +1 -0
- package/lib/app/auth/adapters/jwtCookie.d.ts +55 -0
- package/lib/app/auth/adapters/jwtCookie.d.ts.map +1 -0
- package/lib/app/auth/adapters/jwtCookie.js +120 -0
- package/lib/app/auth/adapters/jwtCookie.js.map +1 -0
- package/lib/app/auth/index.d.ts +8 -0
- package/lib/app/auth/index.d.ts.map +1 -0
- package/lib/app/auth/index.js +7 -0
- package/lib/app/auth/index.js.map +1 -0
- package/lib/app/auth/jwt/AuthFlow.d.ts +94 -0
- package/lib/app/auth/jwt/AuthFlow.d.ts.map +1 -0
- package/lib/app/auth/jwt/AuthFlow.js +127 -0
- package/lib/app/auth/jwt/AuthFlow.js.map +1 -0
- package/lib/app/auth/jwt/DrizzleRefreshTokenStore.d.ts +80 -0
- package/lib/app/auth/jwt/DrizzleRefreshTokenStore.d.ts.map +1 -0
- package/lib/app/auth/jwt/DrizzleRefreshTokenStore.js +134 -0
- package/lib/app/auth/jwt/DrizzleRefreshTokenStore.js.map +1 -0
- package/lib/app/auth/jwt/JwtSigner.d.ts +123 -0
- package/lib/app/auth/jwt/JwtSigner.d.ts.map +1 -0
- package/lib/app/auth/jwt/JwtSigner.js +118 -0
- package/lib/app/auth/jwt/JwtSigner.js.map +1 -0
- package/lib/app/auth/jwt/RefreshTokenStore.d.ts +97 -0
- package/lib/app/auth/jwt/RefreshTokenStore.d.ts.map +1 -0
- package/lib/app/auth/jwt/RefreshTokenStore.js +122 -0
- package/lib/app/auth/jwt/RefreshTokenStore.js.map +1 -0
- package/lib/app/auth/permissionDefinition.d.ts +20 -0
- package/lib/app/auth/permissionDefinition.d.ts.map +1 -0
- package/lib/app/auth/permissionDefinition.js +2 -0
- package/lib/app/auth/permissionDefinition.js.map +1 -0
- package/lib/app/context/RequestContext.d.ts +59 -0
- package/lib/app/context/RequestContext.d.ts.map +1 -0
- package/lib/app/context/RequestContext.js +25 -0
- package/lib/app/context/RequestContext.js.map +1 -0
- package/lib/app/db/QueryOptions.d.ts +82 -0
- package/lib/app/db/QueryOptions.d.ts.map +1 -0
- package/lib/app/db/QueryOptions.js +37 -0
- package/lib/app/db/QueryOptions.js.map +1 -0
- package/lib/app/db/baseFields.d.ts +60 -0
- package/lib/app/db/baseFields.d.ts.map +1 -0
- package/lib/app/db/baseFields.js +54 -0
- package/lib/app/db/baseFields.js.map +1 -0
- package/lib/app/db/buildWhere.d.ts +22 -0
- package/lib/app/db/buildWhere.d.ts.map +1 -0
- package/lib/app/db/buildWhere.js +75 -0
- package/lib/app/db/buildWhere.js.map +1 -0
- package/lib/app/db/client.d.ts +111 -0
- package/lib/app/db/client.d.ts.map +1 -0
- package/lib/app/db/client.js +99 -0
- package/lib/app/db/client.js.map +1 -0
- package/lib/app/db/tenantScoped.d.ts +59 -0
- package/lib/app/db/tenantScoped.d.ts.map +1 -0
- package/lib/app/db/tenantScoped.js +54 -0
- package/lib/app/db/tenantScoped.js.map +1 -0
- package/lib/app/db/transaction.d.ts +58 -0
- package/lib/app/db/transaction.d.ts.map +1 -0
- package/lib/app/db/transaction.js +106 -0
- package/lib/app/db/transaction.js.map +1 -0
- package/lib/app/health/HealthCheck.d.ts +121 -0
- package/lib/app/health/HealthCheck.d.ts.map +1 -0
- package/lib/app/health/HealthCheck.js +151 -0
- package/lib/app/health/HealthCheck.js.map +1 -0
- package/lib/app/health/redisHealthCheck.d.ts +14 -0
- package/lib/app/health/redisHealthCheck.d.ts.map +1 -0
- package/lib/app/health/redisHealthCheck.js +21 -0
- package/lib/app/health/redisHealthCheck.js.map +1 -0
- package/lib/app/lifecycle/Bootstrapper.d.ts +92 -0
- package/lib/app/lifecycle/Bootstrapper.d.ts.map +1 -0
- package/lib/app/lifecycle/Bootstrapper.js +99 -0
- package/lib/app/lifecycle/Bootstrapper.js.map +1 -0
- package/lib/app/registry/routes.d.ts +89 -0
- package/lib/app/registry/routes.d.ts.map +1 -0
- package/lib/app/registry/routes.js +72 -0
- package/lib/app/registry/routes.js.map +1 -0
- package/lib/app/services/DrizzleEntityService.d.ts +123 -0
- package/lib/app/services/DrizzleEntityService.d.ts.map +1 -0
- package/lib/app/services/DrizzleEntityService.js +342 -0
- package/lib/app/services/DrizzleEntityService.js.map +1 -0
- package/lib/app/services/RedisProvider.d.ts +52 -0
- package/lib/app/services/RedisProvider.d.ts.map +1 -0
- package/lib/app/services/RedisProvider.js +75 -0
- package/lib/app/services/RedisProvider.js.map +1 -0
- package/lib/app/services/cache/Cache.d.ts +70 -0
- package/lib/app/services/cache/Cache.d.ts.map +1 -0
- package/lib/app/services/cache/Cache.js +18 -0
- package/lib/app/services/cache/Cache.js.map +1 -0
- package/lib/app/services/cache/InMemoryCache.d.ts +35 -0
- package/lib/app/services/cache/InMemoryCache.d.ts.map +1 -0
- package/lib/app/services/cache/InMemoryCache.js +76 -0
- package/lib/app/services/cache/InMemoryCache.js.map +1 -0
- package/lib/app/services/cache/RedisCache.d.ts +99 -0
- package/lib/app/services/cache/RedisCache.d.ts.map +1 -0
- package/lib/app/services/cache/RedisCache.js +150 -0
- package/lib/app/services/cache/RedisCache.js.map +1 -0
- package/lib/app/services/cache/cached.d.ts +30 -0
- package/lib/app/services/cache/cached.d.ts.map +1 -0
- package/lib/app/services/cache/cached.js +38 -0
- package/lib/app/services/cache/cached.js.map +1 -0
- package/lib/app/services/cache/index.d.ts +5 -0
- package/lib/app/services/cache/index.d.ts.map +1 -0
- package/lib/app/services/cache/index.js +4 -0
- package/lib/app/services/cache/index.js.map +1 -0
- package/lib/app/services/cron/defineCron.d.ts +57 -0
- package/lib/app/services/cron/defineCron.d.ts.map +1 -0
- package/lib/app/services/cron/defineCron.js +15 -0
- package/lib/app/services/cron/defineCron.js.map +1 -0
- package/lib/app/services/cron/index.d.ts +2 -0
- package/lib/app/services/cron/index.d.ts.map +1 -0
- package/lib/app/services/cron/index.js +2 -0
- package/lib/app/services/cron/index.js.map +1 -0
- package/lib/app/services/hooks/LifecycleHooks.d.ts +61 -0
- package/lib/app/services/hooks/LifecycleHooks.d.ts.map +1 -0
- package/lib/app/services/hooks/LifecycleHooks.js +85 -0
- package/lib/app/services/hooks/LifecycleHooks.js.map +1 -0
- package/lib/app/services/jobs/JobsManager.d.ts +67 -0
- package/lib/app/services/jobs/JobsManager.d.ts.map +1 -0
- package/lib/app/services/jobs/JobsManager.js +121 -0
- package/lib/app/services/jobs/JobsManager.js.map +1 -0
- package/lib/app/services/jobs/bullBoard.d.ts +72 -0
- package/lib/app/services/jobs/bullBoard.d.ts.map +1 -0
- package/lib/app/services/jobs/bullBoard.js +96 -0
- package/lib/app/services/jobs/bullBoard.js.map +1 -0
- package/lib/app/services/jobs/defineJob.d.ts +81 -0
- package/lib/app/services/jobs/defineJob.d.ts.map +1 -0
- package/lib/app/services/jobs/defineJob.js +49 -0
- package/lib/app/services/jobs/defineJob.js.map +1 -0
- package/lib/app/services/jobs/index.d.ts +4 -0
- package/lib/app/services/jobs/index.d.ts.map +1 -0
- package/lib/app/services/jobs/index.js +4 -0
- package/lib/app/services/jobs/index.js.map +1 -0
- package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.d.ts +23 -0
- package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.d.ts.map +1 -0
- package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.js +51 -0
- package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.js.map +1 -0
- package/lib/app/services/rateLimit/RateLimitBackend.d.ts +41 -0
- package/lib/app/services/rateLimit/RateLimitBackend.d.ts.map +1 -0
- package/lib/app/services/rateLimit/RateLimitBackend.js +19 -0
- package/lib/app/services/rateLimit/RateLimitBackend.js.map +1 -0
- package/lib/app/services/rateLimit/RedisSlidingWindowBackend.d.ts +16 -0
- package/lib/app/services/rateLimit/RedisSlidingWindowBackend.d.ts.map +1 -0
- package/lib/app/services/rateLimit/RedisSlidingWindowBackend.js +80 -0
- package/lib/app/services/rateLimit/RedisSlidingWindowBackend.js.map +1 -0
- package/lib/app/services/rateLimit/index.d.ts +4 -0
- package/lib/app/services/rateLimit/index.d.ts.map +1 -0
- package/lib/app/services/rateLimit/index.js +3 -0
- package/lib/app/services/rateLimit/index.js.map +1 -0
- package/lib/app/services/storage/DiskStorageAdapter.d.ts +62 -0
- package/lib/app/services/storage/DiskStorageAdapter.d.ts.map +1 -0
- package/lib/app/services/storage/DiskStorageAdapter.js +295 -0
- package/lib/app/services/storage/DiskStorageAdapter.js.map +1 -0
- package/lib/app/services/storage/GCSStorageAdapter.d.ts +44 -0
- package/lib/app/services/storage/GCSStorageAdapter.d.ts.map +1 -0
- package/lib/app/services/storage/GCSStorageAdapter.js +133 -0
- package/lib/app/services/storage/GCSStorageAdapter.js.map +1 -0
- package/lib/app/services/storage/S3StorageAdapter.d.ts +57 -0
- package/lib/app/services/storage/S3StorageAdapter.d.ts.map +1 -0
- package/lib/app/services/storage/S3StorageAdapter.js +162 -0
- package/lib/app/services/storage/S3StorageAdapter.js.map +1 -0
- package/lib/app/services/storage/StorageService.d.ts +88 -0
- package/lib/app/services/storage/StorageService.d.ts.map +1 -0
- package/lib/app/services/storage/StorageService.js +18 -0
- package/lib/app/services/storage/StorageService.js.map +1 -0
- package/lib/app/services/storage/index.d.ts +5 -0
- package/lib/app/services/storage/index.d.ts.map +1 -0
- package/lib/app/services/storage/index.js +4 -0
- package/lib/app/services/storage/index.js.map +1 -0
- package/lib/app/utils/ContextValidators.d.ts +46 -0
- package/lib/app/utils/ContextValidators.d.ts.map +1 -0
- package/lib/app/utils/ContextValidators.js +81 -0
- package/lib/app/utils/ContextValidators.js.map +1 -0
- package/lib/app/utils/defineEnv.d.ts +80 -0
- package/lib/app/utils/defineEnv.d.ts.map +1 -0
- package/lib/app/utils/defineEnv.js +95 -0
- package/lib/app/utils/defineEnv.js.map +1 -0
- package/lib/client.d.ts +139 -0
- package/lib/client.d.ts.map +1 -0
- package/lib/client.js +98 -0
- package/lib/client.js.map +1 -0
- package/lib/contract/application.d.ts +3 -0
- package/lib/contract/application.d.ts.map +1 -0
- package/lib/contract/application.js +2 -0
- package/lib/contract/application.js.map +1 -0
- package/lib/contract/client.d.ts +3 -0
- package/lib/contract/client.d.ts.map +1 -0
- package/lib/contract/client.js +3 -0
- package/lib/contract/client.js.map +1 -0
- package/lib/contract/index.d.ts +3 -0
- package/lib/contract/index.d.ts.map +1 -0
- package/lib/contract/index.js +3 -0
- package/lib/contract/index.js.map +1 -0
- package/lib/contract/manifest.d.ts +3 -0
- package/lib/contract/manifest.d.ts.map +1 -0
- package/lib/contract/manifest.js +2 -0
- package/lib/contract/manifest.js.map +1 -0
- package/lib/contract/openapi.d.ts +3 -0
- package/lib/contract/openapi.d.ts.map +1 -0
- package/lib/contract/openapi.js +3 -0
- package/lib/contract/openapi.js.map +1 -0
- package/lib/contract/project.d.ts +3 -0
- package/lib/contract/project.d.ts.map +1 -0
- package/lib/contract/project.js +3 -0
- package/lib/contract/project.js.map +1 -0
- package/lib/contract/schema.d.ts +3 -0
- package/lib/contract/schema.d.ts.map +1 -0
- package/lib/contract/schema.js +3 -0
- package/lib/contract/schema.js.map +1 -0
- package/lib/drizzle/index.d.ts +3 -0
- package/lib/drizzle/index.d.ts.map +1 -0
- package/lib/drizzle/index.js +3 -0
- package/lib/drizzle/index.js.map +1 -0
- package/lib/hono/auth.d.ts +6 -0
- package/lib/hono/auth.d.ts.map +1 -0
- package/lib/hono/auth.js +54 -0
- package/lib/hono/auth.js.map +1 -0
- package/lib/hono/index.d.ts +4 -0
- package/lib/hono/index.d.ts.map +1 -0
- package/lib/hono/index.js +4 -0
- package/lib/hono/index.js.map +1 -0
- package/lib/http/app/createApp.d.ts +132 -0
- package/lib/http/app/createApp.d.ts.map +1 -0
- package/lib/http/app/createApp.js +215 -0
- package/lib/http/app/createApp.js.map +1 -0
- package/lib/http/app/devRoutes.d.ts +31 -0
- package/lib/http/app/devRoutes.d.ts.map +1 -0
- package/lib/http/app/devRoutes.js +145 -0
- package/lib/http/app/devRoutes.js.map +1 -0
- package/lib/http/app/errors.d.ts +45 -0
- package/lib/http/app/errors.d.ts.map +1 -0
- package/lib/http/app/errors.js +93 -0
- package/lib/http/app/errors.js.map +1 -0
- package/lib/http/app/health.d.ts +66 -0
- package/lib/http/app/health.d.ts.map +1 -0
- package/lib/http/app/health.js +104 -0
- package/lib/http/app/health.js.map +1 -0
- package/lib/http/errors/AppError.d.ts +10 -0
- package/lib/http/errors/AppError.d.ts.map +1 -0
- package/lib/http/errors/AppError.js +19 -0
- package/lib/http/errors/AppError.js.map +1 -0
- package/lib/http/errors/AuthenticationError.d.ts +8 -0
- package/lib/http/errors/AuthenticationError.d.ts.map +1 -0
- package/lib/http/errors/AuthenticationError.js +10 -0
- package/lib/http/errors/AuthenticationError.js.map +1 -0
- package/lib/http/errors/AuthorizationError.d.ts +8 -0
- package/lib/http/errors/AuthorizationError.d.ts.map +1 -0
- package/lib/http/errors/AuthorizationError.js +10 -0
- package/lib/http/errors/AuthorizationError.js.map +1 -0
- package/lib/http/errors/BadRequestError.d.ts +8 -0
- package/lib/http/errors/BadRequestError.d.ts.map +1 -0
- package/lib/http/errors/BadRequestError.js +10 -0
- package/lib/http/errors/BadRequestError.js.map +1 -0
- package/lib/http/errors/ConflictError.d.ts +8 -0
- package/lib/http/errors/ConflictError.d.ts.map +1 -0
- package/lib/http/errors/ConflictError.js +10 -0
- package/lib/http/errors/ConflictError.js.map +1 -0
- package/lib/http/errors/InternalServerError.d.ts +8 -0
- package/lib/http/errors/InternalServerError.d.ts.map +1 -0
- package/lib/http/errors/InternalServerError.js +10 -0
- package/lib/http/errors/InternalServerError.js.map +1 -0
- package/lib/http/errors/NotFoundError.d.ts +8 -0
- package/lib/http/errors/NotFoundError.d.ts.map +1 -0
- package/lib/http/errors/NotFoundError.js +10 -0
- package/lib/http/errors/NotFoundError.js.map +1 -0
- package/lib/http/errors/RateLimitError.d.ts +13 -0
- package/lib/http/errors/RateLimitError.d.ts.map +1 -0
- package/lib/http/errors/RateLimitError.js +16 -0
- package/lib/http/errors/RateLimitError.js.map +1 -0
- package/lib/http/errors/ServiceUnavailableError.d.ts +8 -0
- package/lib/http/errors/ServiceUnavailableError.d.ts.map +1 -0
- package/lib/http/errors/ServiceUnavailableError.js +10 -0
- package/lib/http/errors/ServiceUnavailableError.js.map +1 -0
- package/lib/http/errors/ValidationError.d.ts +9 -0
- package/lib/http/errors/ValidationError.d.ts.map +1 -0
- package/lib/http/errors/ValidationError.js +12 -0
- package/lib/http/errors/ValidationError.js.map +1 -0
- package/lib/http/errors/index.d.ts +11 -0
- package/lib/http/errors/index.d.ts.map +1 -0
- package/lib/http/errors/index.js +12 -0
- package/lib/http/errors/index.js.map +1 -0
- package/lib/http/middleware/apiKey.d.ts +9 -0
- package/lib/http/middleware/apiKey.d.ts.map +1 -0
- package/lib/http/middleware/apiKey.js +71 -0
- package/lib/http/middleware/apiKey.js.map +1 -0
- package/lib/http/middleware/csrf.d.ts +63 -0
- package/lib/http/middleware/csrf.d.ts.map +1 -0
- package/lib/http/middleware/csrf.js +169 -0
- package/lib/http/middleware/csrf.js.map +1 -0
- package/lib/http/middleware/httpLogger.d.ts +39 -0
- package/lib/http/middleware/httpLogger.d.ts.map +1 -0
- package/lib/http/middleware/httpLogger.js +118 -0
- package/lib/http/middleware/httpLogger.js.map +1 -0
- package/lib/http/middleware/idempotency.d.ts +48 -0
- package/lib/http/middleware/idempotency.d.ts.map +1 -0
- package/lib/http/middleware/idempotency.js +180 -0
- package/lib/http/middleware/idempotency.js.map +1 -0
- package/lib/http/middleware/rateLimit.d.ts +105 -0
- package/lib/http/middleware/rateLimit.d.ts.map +1 -0
- package/lib/http/middleware/rateLimit.js +84 -0
- package/lib/http/middleware/rateLimit.js.map +1 -0
- package/lib/http/middleware/requireAuth.d.ts +58 -0
- package/lib/http/middleware/requireAuth.d.ts.map +1 -0
- package/lib/http/middleware/requireAuth.js +63 -0
- package/lib/http/middleware/requireAuth.js.map +1 -0
- package/lib/http/middleware/requirePermission.d.ts +23 -0
- package/lib/http/middleware/requirePermission.d.ts.map +1 -0
- package/lib/http/middleware/requirePermission.js +50 -0
- package/lib/http/middleware/requirePermission.js.map +1 -0
- package/lib/http/middleware/resolveTenant.d.ts +41 -0
- package/lib/http/middleware/resolveTenant.d.ts.map +1 -0
- package/lib/http/middleware/resolveTenant.js +17 -0
- package/lib/http/middleware/resolveTenant.js.map +1 -0
- package/lib/http/streaming/sse.d.ts +31 -0
- package/lib/http/streaming/sse.d.ts.map +1 -0
- package/lib/http/streaming/sse.js +11 -0
- package/lib/http/streaming/sse.js.map +1 -0
- package/lib/http/streaming/streamLLM.d.ts +94 -0
- package/lib/http/streaming/streamLLM.d.ts.map +1 -0
- package/lib/http/streaming/streamLLM.js +94 -0
- package/lib/http/streaming/streamLLM.js.map +1 -0
- package/lib/http/utils/authCookies.d.ts +74 -0
- package/lib/http/utils/authCookies.d.ts.map +1 -0
- package/lib/http/utils/authCookies.js +102 -0
- package/lib/http/utils/authCookies.js.map +1 -0
- package/lib/http/utils/clientIp.d.ts +85 -0
- package/lib/http/utils/clientIp.d.ts.map +1 -0
- package/lib/http/utils/clientIp.js +249 -0
- package/lib/http/utils/clientIp.js.map +1 -0
- package/lib/http/utils/index.d.ts +4 -0
- package/lib/http/utils/index.d.ts.map +1 -0
- package/lib/http/utils/index.js +5 -0
- package/lib/http/utils/index.js.map +1 -0
- package/lib/http/utils/metaTags.d.ts +49 -0
- package/lib/http/utils/metaTags.d.ts.map +1 -0
- package/lib/http/utils/metaTags.js +32 -0
- package/lib/http/utils/metaTags.js.map +1 -0
- package/lib/http/utils/publicSuffix.d.ts +34 -0
- package/lib/http/utils/publicSuffix.d.ts.map +1 -0
- package/lib/http/utils/publicSuffix.js +276 -0
- package/lib/http/utils/publicSuffix.js.map +1 -0
- package/lib/http/utils/uploads.d.ts +92 -0
- package/lib/http/utils/uploads.d.ts.map +1 -0
- package/lib/http/utils/uploads.js +126 -0
- package/lib/http/utils/uploads.js.map +1 -0
- package/lib/http/utils/validate.d.ts +41 -0
- package/lib/http/utils/validate.d.ts.map +1 -0
- package/lib/http/utils/validate.js +55 -0
- package/lib/http/utils/validate.js.map +1 -0
- package/lib/http/utils/webhooks.d.ts +63 -0
- package/lib/http/utils/webhooks.d.ts.map +1 -0
- package/lib/http/utils/webhooks.js +133 -0
- package/lib/http/utils/webhooks.js.map +1 -0
- package/lib/http/ws/auth.d.ts +46 -0
- package/lib/http/ws/auth.d.ts.map +1 -0
- package/lib/http/ws/auth.js +123 -0
- package/lib/http/ws/auth.js.map +1 -0
- package/lib/index.d.ts +80 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +61 -0
- package/lib/index.js.map +1 -0
- package/lib/kernel/index.d.ts +3 -0
- package/lib/kernel/index.d.ts.map +1 -0
- package/lib/kernel/index.js +3 -0
- package/lib/kernel/index.js.map +1 -0
- package/lib/libraries/.gitkeep +0 -0
- package/lib/libraries/ai/CHANGELOG.md +17 -0
- package/lib/libraries/ai/README.md +164 -0
- package/lib/libraries/ai/adapters/AnthropicAdapter.ts +382 -0
- package/lib/libraries/ai/adapters/LLMProvider.ts +76 -0
- package/lib/libraries/ai/adapters/OpenAIAdapter.ts +466 -0
- package/lib/libraries/ai/experimental/index.ts +20 -0
- package/lib/libraries/ai/index.ts +128 -0
- package/lib/libraries/ai/library.json +19 -0
- package/lib/libraries/ai/loop/agentLoop.ts +377 -0
- package/lib/libraries/ai/prompts/definePrompt.ts +0 -0
- package/lib/libraries/ai/rag/retrieveAndAnswer.ts +177 -0
- package/lib/libraries/ai/streaming/streamLLM.ts +94 -0
- package/lib/libraries/ai/structured/completeStructured.ts +143 -0
- package/lib/libraries/ai/tokens/Tokenizer.ts +216 -0
- package/lib/libraries/ai/tools/defineTool.ts +208 -0
- package/lib/libraries/ai/types.ts +260 -0
- package/lib/libraries/chunking/ChunkingService.ts +103 -0
- package/lib/libraries/chunking/README.md +104 -0
- package/lib/libraries/chunking/docs/api-reference.md +549 -0
- package/lib/libraries/chunking/docs/best-practices.md +321 -0
- package/lib/libraries/chunking/docs/code-organization.md +538 -0
- package/lib/libraries/chunking/docs/configuration.md +418 -0
- package/lib/libraries/chunking/docs/index.md +236 -0
- package/lib/libraries/chunking/docs/quick-start.md +298 -0
- package/lib/libraries/chunking/docs/strategies.md +407 -0
- package/lib/libraries/chunking/docs/summary.md +258 -0
- package/lib/libraries/chunking/docs/troubleshooting.md +527 -0
- package/lib/libraries/chunking/index.ts +17 -0
- package/lib/libraries/chunking/library.json +16 -0
- package/lib/libraries/chunking/strategies/ChunkingStrategy.ts +13 -0
- package/lib/libraries/chunking/strategies/FixedLengthChunkingStrategy.ts +88 -0
- package/lib/libraries/chunking/strategies/SemanticChunkingStrategy.ts +389 -0
- package/lib/libraries/chunking/strategies/SlidingWindowChunkingStrategy.ts +258 -0
- package/lib/libraries/chunking/types/index.ts +63 -0
- package/lib/libraries/email/EmailService.ts +591 -0
- package/lib/libraries/email/README.md +777 -0
- package/lib/libraries/email/TemplateRegistry.ts +148 -0
- package/lib/libraries/email/TemplateRenderer.ts +273 -0
- package/lib/libraries/email/index.ts +4 -0
- package/lib/libraries/email/initializeEmailService.ts +60 -0
- package/lib/libraries/email/library.json +12 -0
- package/lib/libraries/email/types/EmailAddress.ts +4 -0
- package/lib/libraries/email/types/EmailAttachment.ts +11 -0
- package/lib/libraries/email/types/EmailTag.ts +9 -0
- package/lib/libraries/email/types/EmailTemplate.ts +124 -0
- package/lib/libraries/email/types/SendBatchEmailsOptions.ts +9 -0
- package/lib/libraries/email/types/SendBatchEmailsResult.ts +9 -0
- package/lib/libraries/email/types/SendEmailOptions.ts +33 -0
- package/lib/libraries/email/types/SendEmailResult.ts +9 -0
- package/lib/libraries/email/types/index.ts +8 -0
- package/lib/libraries/weaviate/QueryBuilder.ts +292 -0
- package/lib/libraries/weaviate/README.md +284 -0
- package/lib/libraries/weaviate/Weaviate.ts +1419 -0
- package/lib/libraries/weaviate/WeaviateConfig.ts +268 -0
- package/lib/libraries/weaviate/docs/.gitkeep +1 -0
- package/lib/libraries/weaviate/docs/API_REFERENCE.md +1067 -0
- package/lib/libraries/weaviate/docs/CONFIGURATION.md +320 -0
- package/lib/libraries/weaviate/docs/ERROR_HANDLING.md +271 -0
- package/lib/libraries/weaviate/docs/MULTI_TENANCY.md +348 -0
- package/lib/libraries/weaviate/docs/PERFORMANCE.md +602 -0
- package/lib/libraries/weaviate/docs/PLUGIN_DEVELOPMENT.md +547 -0
- package/lib/libraries/weaviate/docs/QUICK_START.md +443 -0
- package/lib/libraries/weaviate/docs/README.md +269 -0
- package/lib/libraries/weaviate/docs/SCHEMA_GUIDE.md +630 -0
- package/lib/libraries/weaviate/docs/WEAVIATE_README.md +520 -0
- package/lib/libraries/weaviate/errors/WeaviateError.ts +92 -0
- package/lib/libraries/weaviate/errors/index.ts +1 -0
- package/lib/libraries/weaviate/index.ts +35 -0
- package/lib/libraries/weaviate/library.json +26 -0
- package/lib/libraries/weaviate/plugins/index.ts +2 -0
- package/lib/libraries/weaviate/schemas/SchemaRegistry.ts +284 -0
- package/lib/libraries/weaviate/schemas/index.ts +2 -0
- package/lib/libraries/weaviate/types/BM25SearchOptions.ts +9 -0
- package/lib/libraries/weaviate/types/ExtendedWeaviateClient.ts +7 -0
- package/lib/libraries/weaviate/types/HybridSearchOptions.ts +11 -0
- package/lib/libraries/weaviate/types/MultiTenancy.ts +81 -0
- package/lib/libraries/weaviate/types/Plugin.ts +17 -0
- package/lib/libraries/weaviate/types/SchemaConfig.ts +25 -0
- package/lib/libraries/weaviate/types/SearchMethod.ts +8 -0
- package/lib/libraries/weaviate/types/SearchOptions.ts +16 -0
- package/lib/libraries/weaviate/types/SearchRequest.ts +19 -0
- package/lib/libraries/weaviate/types/SearchResult.ts +16 -0
- package/lib/libraries/weaviate/types/SearchableField.ts +87 -0
- package/lib/libraries/weaviate/types/VectorIndexConfig.ts +19 -0
- package/lib/libraries/weaviate/types/VectorSearchOptions.ts +18 -0
- package/lib/libraries/weaviate/types/VectorizerConfig.ts +8 -0
- package/lib/libraries/weaviate/types/index.ts +29 -0
- package/lib/libraries/weaviate/utils/TenantContextManager.ts +94 -0
- package/lib/modules/accounts/index.ts +47 -0
- package/lib/modules/accounts/module.json +15 -0
- package/lib/modules/accounts/permissions.ts +33 -0
- package/lib/modules/accounts/routes.ts +200 -0
- package/lib/modules/accounts/schema.ts +219 -0
- package/lib/modules/accounts/services/AccountService.ts +44 -0
- package/lib/modules/accounts/validation/schemas.ts +70 -0
- package/lib/modules/oauth/adapters/GmailOAuthAdapter.ts +35 -0
- package/lib/modules/oauth/adapters/GoogleOAuthAdapter.ts +278 -0
- package/lib/modules/oauth/adapters/MicrosoftOAuthAdapter.ts +269 -0
- package/lib/modules/oauth/adapters/MockOAuthAdapter.ts +138 -0
- package/lib/modules/oauth/adapters/OAuthAdapter.ts +211 -0
- package/lib/modules/oauth/adapters/OAuthAdapterRegistry.ts +77 -0
- package/lib/modules/oauth/adapters/OutlookOAuthAdapter.ts +38 -0
- package/lib/modules/oauth/index.ts +79 -0
- package/lib/modules/oauth/module.json +23 -0
- package/lib/modules/oauth/permissions.ts +39 -0
- package/lib/modules/oauth/routes.ts +497 -0
- package/lib/modules/oauth/schema.ts +127 -0
- package/lib/modules/oauth/services/OAuthConnectionService.ts +147 -0
- package/lib/modules/oauth/state.ts +234 -0
- package/lib/modules/oauth/stateJtiCache.ts +85 -0
- package/lib/modules/oauth/verifyIdToken.ts +178 -0
- package/lib/modules/outbox/EventEmitter.ts +106 -0
- package/lib/modules/outbox/OutboxDrainer.ts +163 -0
- package/lib/modules/outbox/handlers/EventHandler.ts +115 -0
- package/lib/modules/outbox/index.ts +43 -0
- package/lib/modules/outbox/module.json +15 -0
- package/lib/modules/outbox/permissions.ts +36 -0
- package/lib/modules/outbox/routes.ts +219 -0
- package/lib/modules/outbox/schema.ts +207 -0
- package/lib/modules/outbox/services/OutboxEventService.ts +223 -0
- package/lib/observability/logger.d.ts +30 -0
- package/lib/observability/logger.d.ts.map +1 -0
- package/lib/observability/logger.js +110 -0
- package/lib/observability/logger.js.map +1 -0
- package/lib/observability/metrics.d.ts +3 -0
- package/lib/observability/metrics.d.ts.map +1 -0
- package/lib/observability/metrics.js +3 -0
- package/lib/observability/metrics.js.map +1 -0
- package/lib/observability/otel.d.ts +3 -0
- package/lib/observability/otel.d.ts.map +1 -0
- package/lib/observability/otel.js +3 -0
- package/lib/observability/otel.js.map +1 -0
- package/lib/observability/safeErrorFields.d.ts +14 -0
- package/lib/observability/safeErrorFields.d.ts.map +1 -0
- package/lib/observability/safeErrorFields.js +14 -0
- package/lib/observability/safeErrorFields.js.map +1 -0
- package/lib/observability/tracing.d.ts +3 -0
- package/lib/observability/tracing.d.ts.map +1 -0
- package/lib/observability/tracing.js +3 -0
- package/lib/observability/tracing.js.map +1 -0
- package/lib/package.json +4 -0
- package/lib/testing.d.ts +3 -0
- package/lib/testing.d.ts.map +1 -0
- package/lib/testing.js +3 -0
- package/lib/testing.js.map +1 -0
- package/package.json +193 -3
- package/templates/default/.claude/agents/qa-brief.md +106 -0
- package/templates/default/.claude/commands/sherpa-brief.md +23 -0
- package/templates/default/.cursor/rules/api.mdc +80 -0
- package/templates/default/.cursor/rules/commits.mdc +88 -0
- package/templates/default/.cursor/rules/security.mdc +92 -0
- package/templates/default/.cursor/rules/testing.mdc +123 -0
- package/templates/default/.cursor/rules/typescript.mdc +86 -0
- package/templates/default/.env.example +79 -0
- package/templates/default/.github/workflows/sherpa-brief.yml +180 -0
- package/templates/default/AGENTS.md +166 -0
- package/templates/default/CLAUDE.md +17 -0
- package/templates/default/README.md +97 -0
- package/templates/default/_gitignore +27 -0
- package/templates/default/docker-compose.yml +83 -0
- package/templates/default/drizzle.config.ts +34 -0
- package/templates/default/eslint.config.mjs +106 -0
- package/templates/default/package.json +57 -0
- package/templates/default/ship-api.config.ts +21 -0
- package/templates/default/src/client.ts +34 -0
- package/templates/default/src/config/modules.ts +23 -0
- package/templates/default/src/db/schema.ts +26 -0
- package/templates/default/src/environment.ts +81 -0
- package/templates/default/src/index.test.ts +53 -0
- package/templates/default/src/index.ts +11 -0
- package/templates/default/src/instrumentation.ts +13 -0
- package/templates/default/src/server.ts +90 -0
- package/templates/default/tsconfig.json +24 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import type {
|
|
3
|
+
AuthenticatedAccount,
|
|
4
|
+
AuthProvider,
|
|
5
|
+
} from "@sixthsummit/ship-api/app/auth/AuthProvider";
|
|
6
|
+
import type { AppEnv } from "@sixthsummit/ship-api/http/app/createApp";
|
|
7
|
+
import { createModuleApp } from "@sixthsummit/ship-api/http/app/createApp";
|
|
8
|
+
import { BadRequestError } from "@sixthsummit/ship-api/http/errors/BadRequestError";
|
|
9
|
+
import { NotFoundError } from "@sixthsummit/ship-api/http/errors/NotFoundError";
|
|
10
|
+
import { requireAuth } from "@sixthsummit/ship-api/http/middleware/requireAuth";
|
|
11
|
+
import { requirePermission } from "@sixthsummit/ship-api/http/middleware/requirePermission";
|
|
12
|
+
import { validate } from "@sixthsummit/ship-api/http/utils/validate";
|
|
13
|
+
import type { Hono, Schema } from "hono";
|
|
14
|
+
import type { BlankSchema } from "hono/types";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
import type { OAuthAdapter, OAuthClientCredentials } from "./adapters/OAuthAdapter.js";
|
|
18
|
+
import type { OAuthAdapterRegistry } from "./adapters/OAuthAdapterRegistry.js";
|
|
19
|
+
import { OAUTH_PERMISSIONS } from "./permissions.js";
|
|
20
|
+
import { isActive, type OAuthConnection } from "./schema.js";
|
|
21
|
+
import type { OAuthConnectionService } from "./services/OAuthConnectionService.js";
|
|
22
|
+
import { InvalidStateError, type StateSecret, signState, verifyState } from "./state.js";
|
|
23
|
+
import { InMemoryStateJtiCache, type StateJtiCache } from "./stateJtiCache.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* v4 oauth routes — `defineOAuthRoutes(app, deps)`.
|
|
27
|
+
*
|
|
28
|
+
* Wires:
|
|
29
|
+
* - GET /oauth list current user's connections
|
|
30
|
+
* - POST /oauth/:providerCode/authorize build authorize URL (returns { url, state })
|
|
31
|
+
* - GET /oauth/:providerCode/callback receive code+state, exchange, upsert
|
|
32
|
+
* - DELETE /oauth/:providerCode disconnect (clear tokens; optionally revoke)
|
|
33
|
+
*
|
|
34
|
+
* The callback is GET because OAuth providers redirect via HTTP 302
|
|
35
|
+
* after the user grants access — the browser does the request, not
|
|
36
|
+
* application code.
|
|
37
|
+
*
|
|
38
|
+
* The authorize is POST and returns JSON `{ url, state }` rather than
|
|
39
|
+
* doing a 302 redirect itself. This is API-first: SPAs and mobile
|
|
40
|
+
* clients want the URL to load in a controlled way (new tab, in-app
|
|
41
|
+
* browser, etc.) rather than have the API server pick the navigation
|
|
42
|
+
* mode. The `Location`-redirect pattern is one `Response.redirect(url)`
|
|
43
|
+
* away if the consumer prefers — they can layer their own GET route
|
|
44
|
+
* that calls into the same flow.
|
|
45
|
+
*
|
|
46
|
+
* State management: framework-owned end-to-end. The authorize handler
|
|
47
|
+
* signs a `StatePayload` (accountId + providerCode + optional returnTo
|
|
48
|
+
* + optional extras) and embeds it in the URL the adapter builds.
|
|
49
|
+
* The callback handler verifies the state against the same secret,
|
|
50
|
+
* recovers the payload, and proceeds. Adapters never inspect state.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
export interface OAuthCredentialsResolver {
|
|
54
|
+
/**
|
|
55
|
+
* Look up the OAuth client credentials for a given provider. Returns
|
|
56
|
+
* null when the provider isn't configured (caller env var unset).
|
|
57
|
+
* Routes layer surfaces `null` as a 400 BadRequestError.
|
|
58
|
+
*/
|
|
59
|
+
(providerCode: string): OAuthClientCredentials | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface OAuthRoutesDeps<TAccount extends AuthenticatedAccount = AuthenticatedAccount> {
|
|
63
|
+
service: OAuthConnectionService;
|
|
64
|
+
provider: AuthProvider<TAccount>;
|
|
65
|
+
adapters: OAuthAdapterRegistry;
|
|
66
|
+
/**
|
|
67
|
+
* Resolve `OAuthClientCredentials` per `providerCode`. Typically a
|
|
68
|
+
* closure over the consumer's `defineEnv`-validated env config.
|
|
69
|
+
*/
|
|
70
|
+
credentials: OAuthCredentialsResolver;
|
|
71
|
+
/**
|
|
72
|
+
* HMAC-SHA256 secret(s) for state signing. Source from the consumer's
|
|
73
|
+
* env (e.g. `OAUTH_STATE_SECRET`). MUST be at least 32 bytes; this
|
|
74
|
+
* function trusts the caller — `defineEnv` is the boundary.
|
|
75
|
+
*
|
|
76
|
+
* Pass an array to enable zero-downtime rotation: the head signs new
|
|
77
|
+
* state, every entry is accepted on verify. See `StateSecret` for
|
|
78
|
+
* the rotation pattern.
|
|
79
|
+
*/
|
|
80
|
+
stateSecret: StateSecret;
|
|
81
|
+
/**
|
|
82
|
+
* Where the callback handler redirects on success when the
|
|
83
|
+
* authorize call didn't supply a `returnTo`. Defaults to "/".
|
|
84
|
+
*/
|
|
85
|
+
defaultRedirectUrl?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Resolve the framework's account-id from `c.get('user')`. The
|
|
88
|
+
* framework's `AuthProvider` returns whatever shape the consumer
|
|
89
|
+
* configured for `Account`; the routes layer doesn't know the
|
|
90
|
+
* shape. Defaults to reading `.id` (most common case).
|
|
91
|
+
*/
|
|
92
|
+
resolveAccountId?: (user: TAccount) => string;
|
|
93
|
+
/**
|
|
94
|
+
* Origins permitted as absolute-URL `returnTo` targets at the
|
|
95
|
+
* authorize endpoint. Each entry is an absolute URL whose `origin`
|
|
96
|
+
* (scheme + host + port) is the comparison key.
|
|
97
|
+
*
|
|
98
|
+
* Same-origin relative paths (`/dashboard`, `/`) are always
|
|
99
|
+
* accepted regardless of this list. Absolute URLs are rejected
|
|
100
|
+
* entirely when the list is omitted or empty — the framework
|
|
101
|
+
* defends against open-redirect chains originating on the
|
|
102
|
+
* consumer's trusted login domain.
|
|
103
|
+
*
|
|
104
|
+
* Schemes outside `http:`/`https:` (e.g. `javascript:`, `data:`)
|
|
105
|
+
* are rejected unconditionally. Protocol-relative `//host`,
|
|
106
|
+
* backslash-host (`/\host`), and CRLF/null-byte payloads are
|
|
107
|
+
* rejected unconditionally.
|
|
108
|
+
*/
|
|
109
|
+
returnToAllowlist?: readonly string[];
|
|
110
|
+
/**
|
|
111
|
+
* Single-use cache for OAuth state nonces (F16). Every successful
|
|
112
|
+
* callback records the verified state's nonce; a re-submit of the
|
|
113
|
+
* same state within the cache's TTL (default 60 s, well above any
|
|
114
|
+
* legitimate round-trip) is rejected as a replay.
|
|
115
|
+
*
|
|
116
|
+
* Defaults to an `InMemoryStateJtiCache` per `defineOAuthRoutes`
|
|
117
|
+
* call. Multi-instance deployments (behind a load balancer) need
|
|
118
|
+
* a shared backing to enforce single-use across nodes — pass a
|
|
119
|
+
* Redis-backed `StateJtiCache` implementation.
|
|
120
|
+
*/
|
|
121
|
+
stateJtiCache?: StateJtiCache;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const authorizeSchema = z
|
|
125
|
+
.object({
|
|
126
|
+
/** OAuth scopes to request. Provider-defined values. */
|
|
127
|
+
scopes: z.array(z.string()).default([]),
|
|
128
|
+
/** Where to redirect the user after the callback succeeds. */
|
|
129
|
+
returnTo: z.string().optional(),
|
|
130
|
+
/** Provider-specific options the adapter consults (`access_type`, etc.). */
|
|
131
|
+
providerOptions: z.record(z.string(), z.unknown()).optional(),
|
|
132
|
+
/**
|
|
133
|
+
* Free-form extras the framework round-trips through state — e.g.
|
|
134
|
+
* a wizard step ID or a feature flag the callback page should see.
|
|
135
|
+
* Not seen by the adapter; recovered intact on the callback.
|
|
136
|
+
*/
|
|
137
|
+
extras: z.record(z.string(), z.unknown()).optional(),
|
|
138
|
+
})
|
|
139
|
+
.strict();
|
|
140
|
+
|
|
141
|
+
const providerCodeParam = z.object({
|
|
142
|
+
providerCode: z.string().min(1).max(50),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const callbackQuerySchema = z
|
|
146
|
+
.object({
|
|
147
|
+
code: z.string().min(1).optional(),
|
|
148
|
+
state: z.string().min(1).optional(),
|
|
149
|
+
error: z.string().optional(),
|
|
150
|
+
error_description: z.string().optional(),
|
|
151
|
+
})
|
|
152
|
+
.passthrough();
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* ⚠️ FOOT-GUN: do NOT thread one shared `app` through several modules'
|
|
156
|
+
* `defineXxxRoutes` calls — it accumulates one giant Hono route-type and
|
|
157
|
+
* detonates `tsc` (TS2589) at scale. Prefer {@link buildOAuthApp} mounted via
|
|
158
|
+
* `root.route("/", buildOAuthApp(deps))`. This raw form is kept for advanced
|
|
159
|
+
* composition.
|
|
160
|
+
*/
|
|
161
|
+
// Generic over Hono's schema param `S` so an already-chained app keeps its
|
|
162
|
+
// previously registered route types — `Hono<AppEnv>` here would widen them
|
|
163
|
+
// away. See defineAccountsRoutes for the full rationale.
|
|
164
|
+
export function defineOAuthRoutes<
|
|
165
|
+
TAccount extends AuthenticatedAccount = AuthenticatedAccount,
|
|
166
|
+
S extends Schema = BlankSchema,
|
|
167
|
+
>(app: Hono<AppEnv, S>, deps: OAuthRoutesDeps<TAccount>) {
|
|
168
|
+
const auth = requireAuth({ provider: deps.provider });
|
|
169
|
+
const resolveAccountId = deps.resolveAccountId ?? ((u: TAccount) => (u as { id: string }).id);
|
|
170
|
+
const defaultRedirectUrl = deps.defaultRedirectUrl ?? "/";
|
|
171
|
+
const returnToAllowlist = deps.returnToAllowlist ?? [];
|
|
172
|
+
const stateJtiCache = deps.stateJtiCache ?? new InMemoryStateJtiCache();
|
|
173
|
+
|
|
174
|
+
return app
|
|
175
|
+
.get("/oauth", auth, requirePermission(OAUTH_PERMISSIONS.readSelf), async (c) => {
|
|
176
|
+
const user = c.get("user" as never) as TAccount;
|
|
177
|
+
const accountId = resolveAccountId(user);
|
|
178
|
+
const rows = (await deps.service.getAll({
|
|
179
|
+
where: { accountId },
|
|
180
|
+
})) as OAuthConnection[];
|
|
181
|
+
const summary = rows.map(summarizeConnection);
|
|
182
|
+
return c.json(summary, 200);
|
|
183
|
+
})
|
|
184
|
+
.post(
|
|
185
|
+
"/oauth/:providerCode/authorize",
|
|
186
|
+
auth,
|
|
187
|
+
requirePermission(OAUTH_PERMISSIONS.connect),
|
|
188
|
+
validate("param", providerCodeParam),
|
|
189
|
+
validate("json", authorizeSchema),
|
|
190
|
+
async (c) => {
|
|
191
|
+
const { providerCode } = c.req.valid("param");
|
|
192
|
+
const body = c.req.valid("json");
|
|
193
|
+
const adapter = requireAdapter(deps.adapters, providerCode);
|
|
194
|
+
const credentials = requireCredentials(deps.credentials, providerCode);
|
|
195
|
+
const user = c.get("user" as never) as TAccount;
|
|
196
|
+
const accountId = resolveAccountId(user);
|
|
197
|
+
const safeReturnTo = validateReturnTo(body.returnTo, returnToAllowlist);
|
|
198
|
+
|
|
199
|
+
// PKCE (F12): generate a 256-bit verifier and the SHA-256
|
|
200
|
+
// challenge. Verifier round-trips through the framework-
|
|
201
|
+
// signed state; challenge goes to the provider via the
|
|
202
|
+
// authorize URL. Defense-in-depth even for confidential
|
|
203
|
+
// clients (OAuth 2.1 mandates PKCE).
|
|
204
|
+
const codeVerifier = randomBytes(32).toString("base64url");
|
|
205
|
+
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
|
|
206
|
+
|
|
207
|
+
const state = signState(
|
|
208
|
+
{
|
|
209
|
+
accountId,
|
|
210
|
+
providerCode,
|
|
211
|
+
returnTo: safeReturnTo,
|
|
212
|
+
extras: body.extras as Record<string, unknown> | undefined,
|
|
213
|
+
codeVerifier,
|
|
214
|
+
},
|
|
215
|
+
deps.stateSecret,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const url = adapter.buildAuthorizationUrl(credentials, {
|
|
219
|
+
state,
|
|
220
|
+
scopes: body.scopes,
|
|
221
|
+
providerOptions: body.providerOptions,
|
|
222
|
+
codeChallenge,
|
|
223
|
+
codeChallengeMethod: "S256",
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return c.json({ url, state }, 200);
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
.get(
|
|
230
|
+
"/oauth/:providerCode/callback",
|
|
231
|
+
validate("param", providerCodeParam),
|
|
232
|
+
validate("query", callbackQuerySchema),
|
|
233
|
+
async (c) => {
|
|
234
|
+
const { providerCode } = c.req.valid("param");
|
|
235
|
+
const q = c.req.valid("query");
|
|
236
|
+
|
|
237
|
+
// Provider-error short-circuit: redirect with the error tag in
|
|
238
|
+
// the URL so the consumer's UI surfaces something actionable.
|
|
239
|
+
// F23: defensively truncate provider-supplied error strings to
|
|
240
|
+
// 256 chars before round-tripping through the URL. Pathological
|
|
241
|
+
// providers / malicious upstream proxies could otherwise emit
|
|
242
|
+
// multi-KB error blobs that overflow Location-header limits or
|
|
243
|
+
// bloat the consumer's UI.
|
|
244
|
+
if (q.error) {
|
|
245
|
+
return c.redirect(
|
|
246
|
+
withQuery(defaultRedirectUrl, {
|
|
247
|
+
oauth_error: q.error.slice(0, 256),
|
|
248
|
+
oauth_error_description: (q.error_description ?? "").slice(0, 256),
|
|
249
|
+
}),
|
|
250
|
+
302,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (!q.code || !q.state) {
|
|
254
|
+
throw new BadRequestError(
|
|
255
|
+
"OAuth callback missing required `code` or `state` query param",
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let payload;
|
|
260
|
+
try {
|
|
261
|
+
payload = verifyState(q.state, deps.stateSecret);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
if (err instanceof InvalidStateError) {
|
|
264
|
+
throw new BadRequestError(err.message);
|
|
265
|
+
}
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
268
|
+
if (payload.providerCode !== providerCode) {
|
|
269
|
+
throw new BadRequestError(
|
|
270
|
+
"OAuth callback `providerCode` does not match the state's bound provider",
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// F16: defend against state-replay within the signature
|
|
275
|
+
// window. `verifyState` already enforces the 10-minute
|
|
276
|
+
// expiry; this guards the (theoretical) window inside that
|
|
277
|
+
// expiry where a leaked + replayed state could be exchanged
|
|
278
|
+
// a second time if the upstream provider's auth-code
|
|
279
|
+
// invalidation lagged.
|
|
280
|
+
if (payload.nonce && stateJtiCache.seenAndRecord(payload.nonce)) {
|
|
281
|
+
throw new BadRequestError("OAuth state already used");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const adapter = requireAdapter(deps.adapters, providerCode);
|
|
285
|
+
const credentials = requireCredentials(deps.credentials, providerCode);
|
|
286
|
+
|
|
287
|
+
const tokens = await adapter.exchangeCodeForTokens(q.code, credentials, {
|
|
288
|
+
codeVerifier: payload.codeVerifier,
|
|
289
|
+
});
|
|
290
|
+
const providerAccountId = await adapter.getProviderAccountId(tokens, credentials);
|
|
291
|
+
|
|
292
|
+
// Defense in depth: re-validate the state's `returnTo` against
|
|
293
|
+
// the current allowlist before redirecting. A state signed under
|
|
294
|
+
// an older (permissive) configuration must fail closed once the
|
|
295
|
+
// operator tightens the allowlist. The validator throws
|
|
296
|
+
// `BadRequestError` on any disallowed value.
|
|
297
|
+
const safeTarget =
|
|
298
|
+
validateReturnTo(payload.returnTo, returnToAllowlist) ?? defaultRedirectUrl;
|
|
299
|
+
|
|
300
|
+
await deps.service.upsertTokens({
|
|
301
|
+
accountId: payload.accountId,
|
|
302
|
+
providerCode,
|
|
303
|
+
accessToken: tokens.accessToken,
|
|
304
|
+
refreshToken: tokens.refreshToken,
|
|
305
|
+
expiresAt: tokens.expiresAt,
|
|
306
|
+
scope: tokens.scope,
|
|
307
|
+
tokenType: tokens.tokenType,
|
|
308
|
+
providerAccountId,
|
|
309
|
+
metadata: tokens.metadata,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
return c.redirect(safeTarget, 302);
|
|
313
|
+
},
|
|
314
|
+
)
|
|
315
|
+
.delete(
|
|
316
|
+
"/oauth/:providerCode",
|
|
317
|
+
auth,
|
|
318
|
+
requirePermission(OAUTH_PERMISSIONS.disconnect),
|
|
319
|
+
validate("param", providerCodeParam),
|
|
320
|
+
async (c) => {
|
|
321
|
+
const { providerCode } = c.req.valid("param");
|
|
322
|
+
const user = c.get("user" as never) as TAccount;
|
|
323
|
+
const accountId = resolveAccountId(user);
|
|
324
|
+
|
|
325
|
+
const existing = await deps.service.findByAccountAndProvider(accountId, providerCode);
|
|
326
|
+
if (!existing) {
|
|
327
|
+
throw new NotFoundError(`No OAuth connection for provider "${providerCode}"`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Best-effort upstream revoke — failure doesn't abort the
|
|
331
|
+
// local clear (see adapter contract: revoke is "best-effort").
|
|
332
|
+
if (
|
|
333
|
+
existing.accessToken &&
|
|
334
|
+
deps.adapters.has(providerCode) &&
|
|
335
|
+
deps.adapters.get(providerCode)!.revokeAccess
|
|
336
|
+
) {
|
|
337
|
+
const credentials = deps.credentials(providerCode);
|
|
338
|
+
if (credentials) {
|
|
339
|
+
try {
|
|
340
|
+
await deps.adapters.require(providerCode).revokeAccess!(
|
|
341
|
+
existing.accessToken,
|
|
342
|
+
credentials,
|
|
343
|
+
);
|
|
344
|
+
} catch {
|
|
345
|
+
// swallow — local clear still happens.
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
await deps.service.clearTokens(existing.id);
|
|
351
|
+
return c.body(null, 204);
|
|
352
|
+
},
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Build the oauth module as its own sub-app, ready to mount on the root with
|
|
358
|
+
* `root.route("/", buildOAuthApp(deps))`. Canonical wiring — see
|
|
359
|
+
* {@link buildAccountsApp} for the rationale. Routes are tagged under the
|
|
360
|
+
* `oauth` module in `/dev/routes` / `list_routes`.
|
|
361
|
+
*/
|
|
362
|
+
export function buildOAuthApp<TAccount extends AuthenticatedAccount = AuthenticatedAccount>(
|
|
363
|
+
deps: OAuthRoutesDeps<TAccount>,
|
|
364
|
+
) {
|
|
365
|
+
return defineOAuthRoutes(createModuleApp("oauth"), deps);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function requireAdapter(registry: OAuthAdapterRegistry, providerCode: string): OAuthAdapter {
|
|
369
|
+
const adapter = registry.get(providerCode);
|
|
370
|
+
if (!adapter) {
|
|
371
|
+
throw new BadRequestError(`No OAuth adapter registered for provider "${providerCode}"`);
|
|
372
|
+
}
|
|
373
|
+
return adapter;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function requireCredentials(
|
|
377
|
+
resolver: OAuthCredentialsResolver,
|
|
378
|
+
providerCode: string,
|
|
379
|
+
): OAuthClientCredentials {
|
|
380
|
+
const credentials = resolver(providerCode);
|
|
381
|
+
if (!credentials) {
|
|
382
|
+
throw new BadRequestError(
|
|
383
|
+
`OAuth credentials for provider "${providerCode}" are not configured`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return credentials;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Strip secrets from a row before returning to the client. Tokens
|
|
391
|
+
* never leave the server; status booleans + metadata are safe.
|
|
392
|
+
*/
|
|
393
|
+
function summarizeConnection(row: OAuthConnection) {
|
|
394
|
+
return {
|
|
395
|
+
id: row.id,
|
|
396
|
+
providerCode: row.providerCode,
|
|
397
|
+
providerAccountId: row.providerAccountId,
|
|
398
|
+
scope: row.scope,
|
|
399
|
+
expiresAt: row.expiresAt,
|
|
400
|
+
isActive: isActive(row),
|
|
401
|
+
metadata: row.metadata,
|
|
402
|
+
createdAt: row.createdAt,
|
|
403
|
+
updatedAt: row.updatedAt,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Validate a user-supplied `returnTo` value before it is signed into
|
|
409
|
+
* state or used as a `Location` redirect. Closes the open-redirect
|
|
410
|
+
* chain that lets an anonymous attacker craft a phishing link pointing
|
|
411
|
+
* at the consumer's trusted authorize endpoint with a 3rd-party return
|
|
412
|
+
* URL. The framework-signed state defeats *tampering*, but the value
|
|
413
|
+
* itself is user input — it must be validated up front.
|
|
414
|
+
*
|
|
415
|
+
* Accepted shapes:
|
|
416
|
+
* - `undefined` / empty → returns `undefined` (caller falls back to
|
|
417
|
+
* `defaultRedirectUrl`).
|
|
418
|
+
* - Same-origin relative path: must start with a single `/` (not
|
|
419
|
+
* `//`, not `/\\`) and not contain CR / LF / null bytes (Location-
|
|
420
|
+
* header injection vectors). The full string is returned as-is.
|
|
421
|
+
* - Absolute URL whose `origin` matches an entry in `allowlist`. The
|
|
422
|
+
* allowlist entries are normalized by `new URL(entry).origin`.
|
|
423
|
+
*
|
|
424
|
+
* Everything else throws `BadRequestError`:
|
|
425
|
+
* - Non-http(s) schemes (`javascript:`, `data:`, `vbscript:`,
|
|
426
|
+
* `file:`, `ms-…:`, etc.).
|
|
427
|
+
* - Protocol-relative `//host` and backslash-host `/\\host`.
|
|
428
|
+
* - Absolute URLs whose origin is not in the allowlist (including
|
|
429
|
+
* all absolute URLs when the allowlist is empty).
|
|
430
|
+
* - Malformed URL strings.
|
|
431
|
+
*
|
|
432
|
+
* Exported (alongside the route factory) so consumers building their
|
|
433
|
+
* own redirect-bearing endpoints can apply the same defense.
|
|
434
|
+
*/
|
|
435
|
+
export function validateReturnTo(
|
|
436
|
+
returnTo: string | undefined,
|
|
437
|
+
allowlist: readonly string[],
|
|
438
|
+
): string | undefined {
|
|
439
|
+
if (returnTo == null || returnTo === "") return undefined;
|
|
440
|
+
|
|
441
|
+
// Reject header-injection shapes outright. Hono's `c.redirect()`
|
|
442
|
+
// sets the `Location` header — CR / LF / NUL would let an attacker
|
|
443
|
+
// forge response headers.
|
|
444
|
+
if (/[\r\n\0]/.test(returnTo)) {
|
|
445
|
+
throw new BadRequestError("Invalid returnTo: contains illegal characters");
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Same-origin relative paths. Reject "//host" (protocol-relative)
|
|
449
|
+
// and "/\\host" (some user-agents resolve the backslash as a host
|
|
450
|
+
// separator, escaping the origin).
|
|
451
|
+
if (returnTo.startsWith("/") && !returnTo.startsWith("//") && !returnTo.startsWith("/\\")) {
|
|
452
|
+
return returnTo;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Anything not starting with "/" must be an absolute URL parseable
|
|
456
|
+
// by `URL`, whose origin matches an allowlist entry.
|
|
457
|
+
let parsed: URL;
|
|
458
|
+
try {
|
|
459
|
+
parsed = new URL(returnTo);
|
|
460
|
+
} catch {
|
|
461
|
+
throw new BadRequestError("Invalid returnTo: not a valid URL");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Disallow non-http(s) schemes even if listed somehow.
|
|
465
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
466
|
+
throw new BadRequestError(`Invalid returnTo: scheme "${parsed.protocol}" not permitted`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (allowlist.length === 0) {
|
|
470
|
+
throw new BadRequestError("Invalid returnTo: absolute URLs require returnToAllowlist");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
for (const entry of allowlist) {
|
|
474
|
+
let entryOrigin: string;
|
|
475
|
+
try {
|
|
476
|
+
entryOrigin = new URL(entry).origin;
|
|
477
|
+
} catch {
|
|
478
|
+
// Malformed allowlist entry — skip it. (Operator misconfig; the
|
|
479
|
+
// request is still rejected if no other entry matches.)
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (parsed.origin === entryOrigin) {
|
|
483
|
+
return returnTo;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
throw new BadRequestError("Invalid returnTo: origin not in returnToAllowlist");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function withQuery(url: string, params: Record<string, string>): string {
|
|
491
|
+
// Preserves an existing query string by appending. Simple
|
|
492
|
+
// string-level concat — intentionally avoids the `URL` constructor
|
|
493
|
+
// since callers may pass relative paths (e.g. "/onboarding").
|
|
494
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
495
|
+
const qs = new URLSearchParams(Object.entries(params).filter(([, v]) => v !== "")).toString();
|
|
496
|
+
return qs ? `${url}${sep}${qs}` : url;
|
|
497
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { baseFields } from "@sixthsummit/ship-api/app/db/baseFields";
|
|
2
|
+
import { relations } from "drizzle-orm";
|
|
3
|
+
import {
|
|
4
|
+
index,
|
|
5
|
+
jsonb,
|
|
6
|
+
pgTable,
|
|
7
|
+
text,
|
|
8
|
+
timestamp,
|
|
9
|
+
uniqueIndex,
|
|
10
|
+
uuid,
|
|
11
|
+
varchar,
|
|
12
|
+
} from "drizzle-orm/pg-core";
|
|
13
|
+
import { accounts } from "../accounts/schema.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* v4 OAuth connection table — ports v3's `OAuthConnection` TypeORM entity.
|
|
17
|
+
*
|
|
18
|
+
* One row per (account, provider) pair: an account that has OAuth-linked
|
|
19
|
+
* Google + Microsoft has two rows. The unique composite on
|
|
20
|
+
* `(account_id, provider_code)` enforces this; the codemod surfaced the
|
|
21
|
+
* @Unique decorator as a TODO and we restore it here.
|
|
22
|
+
*
|
|
23
|
+
* Token storage is intentionally plain text columns (Postgres at-rest
|
|
24
|
+
* encryption is the deployment-level concern). Refresh tokens live in
|
|
25
|
+
* the same row as the access token; rotation overwrites in place
|
|
26
|
+
* (no token-history trail — when the upstream provider rotates the
|
|
27
|
+
* refresh token, the old one is gone).
|
|
28
|
+
*
|
|
29
|
+
* The `metadata` jsonb column carries provider-specific extras
|
|
30
|
+
* (Google `id_token`, Microsoft `tenant_id`, …) that don't fit a
|
|
31
|
+
* cross-provider schema. Adapters own the contract.
|
|
32
|
+
*/
|
|
33
|
+
export const oauthConnections = pgTable(
|
|
34
|
+
"oauth_connections",
|
|
35
|
+
{
|
|
36
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
37
|
+
|
|
38
|
+
accountId: uuid("account_id")
|
|
39
|
+
.notNull()
|
|
40
|
+
.references(() => accounts.id, { onDelete: "cascade" }),
|
|
41
|
+
|
|
42
|
+
/** Stable provider identifier (e.g. "google", "microsoft", "gmail"). */
|
|
43
|
+
providerCode: varchar("provider_code", { length: 50 }).notNull(),
|
|
44
|
+
|
|
45
|
+
/** Bearer-style access token. Plaintext in DB; rely on at-rest encryption. */
|
|
46
|
+
accessToken: text("access_token"),
|
|
47
|
+
|
|
48
|
+
/** Long-lived refresh token. Rotated in place on refresh. */
|
|
49
|
+
refreshToken: text("refresh_token"),
|
|
50
|
+
|
|
51
|
+
/** Access-token expiry. Null when the provider doesn't issue one. */
|
|
52
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
53
|
+
|
|
54
|
+
/** Granted OAuth scopes (space-separated, as the provider returned). */
|
|
55
|
+
scope: text("scope"),
|
|
56
|
+
|
|
57
|
+
/** Token type — typically "Bearer". */
|
|
58
|
+
tokenType: varchar("token_type", { length: 50 }),
|
|
59
|
+
|
|
60
|
+
/** Stable identifier for the user *at the upstream provider*. */
|
|
61
|
+
providerAccountId: varchar("provider_account_id", { length: 255 }),
|
|
62
|
+
|
|
63
|
+
/** Provider-specific extras the cross-provider schema doesn't model. */
|
|
64
|
+
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
|
|
65
|
+
|
|
66
|
+
...baseFields,
|
|
67
|
+
},
|
|
68
|
+
(table) => [
|
|
69
|
+
uniqueIndex("oauth_conn_account_provider_unique").on(table.accountId, table.providerCode),
|
|
70
|
+
index("oauth_conn_provider_ix").on(table.providerCode),
|
|
71
|
+
index("oauth_conn_expires_ix").on(table.expiresAt),
|
|
72
|
+
index("oauth_conn_provider_account_ix").on(table.providerCode, table.providerAccountId),
|
|
73
|
+
],
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
export const oauthConnectionsRelations = relations(oauthConnections, ({ one }) => ({
|
|
77
|
+
account: one(accounts, {
|
|
78
|
+
fields: [oauthConnections.accountId],
|
|
79
|
+
references: [accounts.id],
|
|
80
|
+
}),
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
export type OAuthConnection = typeof oauthConnections.$inferSelect;
|
|
84
|
+
export type NewOAuthConnection = typeof oauthConnections.$inferInsert;
|
|
85
|
+
|
|
86
|
+
// ─── Token-status helpers ──────────────────────────────────────────────
|
|
87
|
+
//
|
|
88
|
+
// v3 had these as instance methods on the TypeORM entity class. v4 rows
|
|
89
|
+
// are plain objects ($inferSelect produces a structural type, not a
|
|
90
|
+
// class), so the helpers move to module-level functions. Same semantics,
|
|
91
|
+
// no `this` — the service / adapter layer calls them on the row.
|
|
92
|
+
|
|
93
|
+
const FIVE_MINUTES_MS = 5 * 60 * 1000;
|
|
94
|
+
|
|
95
|
+
/** True when the access token has expired (or expires-at is in the past). */
|
|
96
|
+
export function isExpired(conn: Pick<OAuthConnection, "expiresAt">): boolean {
|
|
97
|
+
if (!conn.expiresAt) return false;
|
|
98
|
+
return Date.now() >= conn.expiresAt.getTime();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** True when the access token expires within the next 5 minutes. */
|
|
102
|
+
export function isExpiringSoon(conn: Pick<OAuthConnection, "expiresAt">): boolean {
|
|
103
|
+
if (!conn.expiresAt) return false;
|
|
104
|
+
return conn.expiresAt.getTime() - Date.now() <= FIVE_MINUTES_MS;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** True when the connection has both an access token and isn't expired. */
|
|
108
|
+
export function isActive(conn: Pick<OAuthConnection, "accessToken" | "expiresAt">): boolean {
|
|
109
|
+
return Boolean(conn.accessToken) && !isExpired(conn);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** True when refreshing the token is needed (expired or expiring soon). */
|
|
113
|
+
export function needsRefresh(conn: Pick<OAuthConnection, "expiresAt">): boolean {
|
|
114
|
+
return isExpired(conn) || isExpiringSoon(conn);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Build the `Authorization: <type> <token>` header value for upstream
|
|
119
|
+
* provider API calls. Returns null when there's no access token to send.
|
|
120
|
+
*/
|
|
121
|
+
export function authorizationHeader(
|
|
122
|
+
conn: Pick<OAuthConnection, "accessToken" | "tokenType">,
|
|
123
|
+
): string | null {
|
|
124
|
+
if (!conn.accessToken) return null;
|
|
125
|
+
const type = conn.tokenType ?? "Bearer";
|
|
126
|
+
return `${type} ${conn.accessToken}`;
|
|
127
|
+
}
|