@starci/hfs 5.0.8 → 5.2.0
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 +30 -0
- package/README.md +10 -1
- package/cache/import-graph.mjs +6 -61
- package/cache/lint-family.d.mts +10 -1
- package/cache/lint-family.mjs +12 -5
- package/codemod/directive-first.d.mts +48 -0
- package/codemod/directive-first.mjs +136 -0
- package/codemod/explicit-surface-edit.d.mts +14 -0
- package/codemod/explicit-surface-edit.mjs +57 -0
- package/codemod/explicit-surface-names.d.mts +30 -0
- package/codemod/explicit-surface-names.mjs +135 -0
- package/codemod/explicit-surface-render.d.mts +29 -0
- package/codemod/explicit-surface-render.mjs +197 -0
- package/codemod/explicit-surface.d.mts +50 -0
- package/codemod/explicit-surface.mjs +374 -0
- package/codemod/main.d.mts +7 -5
- package/codemod/main.mjs +37 -16
- package/codemod/source-tree.d.mts +8 -0
- package/codemod/source-tree.mjs +23 -0
- package/db/postgres.mjs +9 -1
- package/lint/eslint-list.cjs +29 -0
- package/lint/run.d.mts +19 -5
- package/lint/run.mjs +47 -14
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +31 -10
- package/runtime/knowledge/hfs/fe-stack.yaml +7 -7
- package/runtime/knowledge/hfs/fork-images.yaml +7 -0
- package/runtime/knowledge/hfs/rules.yaml +107 -8
- package/runtime/knowledge/hfs/slots.yaml +75 -24
- package/runtime/modules/kernel/failure-codes.yaml +62 -0
- package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
- package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
- package/runtime/scripts/hfs/architecture/test-world-files.d.mts +4 -3
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +26 -7
- package/runtime/scripts/hfs/architecture/transport-owner.d.mts +1 -2
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -6
- package/runtime/scripts/hfs/explicit-surface-rule.d.mts +58 -0
- package/runtime/scripts/hfs/explicit-surface-rule.mjs +59 -0
- package/runtime/scripts/hfs/manifest-shape.d.mts +1 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +3 -1
- package/runtime/scripts/hfs/rules/a15-findings.mjs +5 -0
- package/runtime/scripts/hfs/rules/stacks-local.d.mts +7 -0
- package/runtime/scripts/hfs/rules/stacks-local.mjs +71 -0
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +6 -1
- package/runtime/scripts/hfs/rules/stacks-service-images.d.mts +12 -0
- package/runtime/scripts/hfs/rules/stacks-service-images.mjs +120 -0
- package/runtime/scripts/hfs/secret-dev-init.d.mts +11 -0
- package/runtime/scripts/hfs/secret-dev-init.mjs +79 -0
- package/runtime/scripts/hfs/secret.mjs +10 -7
- package/runtime/scripts/hfs/slots.mjs +1 -1
- package/runtime/scripts/hfs/stack-local-config.d.mts +101 -0
- package/runtime/scripts/hfs/stack-local-config.mjs +320 -0
- package/runtime/scripts/hfs/stacks-gate.mjs +1 -1
- package/runtime/scripts/lib/declaration-surface.d.mts +35 -0
- package/runtime/scripts/lib/declaration-surface.mjs +112 -0
- package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
- package/runtime/scripts/lib/swarm-stack.mjs +2 -2
- package/scaffold/add-module.d.mts +16 -0
- package/scaffold/add-module.mjs +232 -0
- package/scaffold/add-queue-lite.mjs +49 -0
- package/scaffold/add.mjs +5 -0
- package/scaffold/app.mjs +7 -7
- package/scaffold/module-registry.d.mts +19 -0
- package/scaffold/module-registry.mjs +84 -0
- package/src/main.mjs +23 -2
- package/stack/cli.mjs +6 -1
- package/stack/context.d.mts +15 -0
- package/stack/context.mjs +1 -0
- package/stack/local-cli.d.mts +25 -0
- package/stack/local-cli.mjs +89 -0
- package/stack/local-config.d.mts +17 -0
- package/stack/local-config.mjs +37 -0
- package/stack/local-custody.d.mts +24 -0
- package/stack/local-custody.mjs +109 -0
- package/stack/local-doctor.d.mts +6 -0
- package/stack/local-doctor.mjs +49 -0
- package/stack/local-down.d.mts +8 -0
- package/stack/local-down.mjs +20 -0
- package/stack/local-host.d.mts +42 -0
- package/stack/local-host.mjs +163 -0
- package/stack/local-logs.d.mts +8 -0
- package/stack/local-logs.mjs +32 -0
- package/stack/local-ports.d.mts +7 -0
- package/stack/local-ports.mjs +39 -0
- package/stack/local-prepare.d.mts +22 -0
- package/stack/local-prepare.mjs +62 -0
- package/stack/local-run.d.mts +71 -0
- package/stack/local-run.mjs +200 -0
- package/stack/local-state.d.mts +35 -0
- package/stack/local-state.mjs +29 -0
- package/stack/local-status.d.mts +6 -0
- package/stack/local-status.mjs +36 -0
- package/stack/local-up.d.mts +11 -0
- package/stack/local-up.mjs +166 -0
- package/stack/local-verify.d.mts +6 -0
- package/stack/local-verify.mjs +21 -0
- package/sync/index.d.mts +13 -12
- package/sync/index.mjs +83 -28
- package/templates/app/module/jest.config.js.tpl +8 -0
- package/templates/app/module/src/index.ts.tpl +2 -0
- package/templates/app/module/src/manifest/declaration.spec.ts.tpl +11 -0
- package/templates/app/module/src/manifest/declaration.ts.tpl +13 -0
- package/templates/app/module/src/manifest/index.ts.tpl +1 -0
- package/templates/app/module/src/public/index.ts.tpl +2 -0
- package/templates/app/module/src/template/__name__.template.spec.ts.tpl +13 -0
- package/templates/app/module/src/template/__name__.template.ts.tpl +41 -0
- package/templates/app/module/src/template/index.ts.tpl +1 -0
- package/templates/app/module/src/workbench/components/__pascal__WorkbenchView.tsx.tpl +16 -0
- package/templates/app/module/src/workbench/index.ts.tpl +2 -0
- package/templates/app/package-scripts/package.json +1 -1
- package/templates/app/package-scripts-lite/package.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +76 -70
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +23 -13
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +25 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -26
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +1 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +2 -1
- package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +57 -14
- package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.query.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-begin.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-begin.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-complete.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-complete.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/login.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/login.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/logout.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/logout.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/refresh.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/refresh.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/session.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/session.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +13 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.options.ts +3 -0
- package/templates/be/auth-lite/src/features/api/auth/index.ts +2 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +13 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module.ts +9 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.options.ts +3 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +90 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/google-start.request.ts +17 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/login.request.ts +16 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +46 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +28 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +145 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +95 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +38 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +54 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts +18 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +34 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-session.entity.ts +30 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +17 -0
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +86 -0
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +45 -0
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +97 -0
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +81 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +78 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +87 -0
- package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +44 -0
- package/templates/be/inbox/delivery-recorded.event.ts.tpl +2 -2
- package/templates/be/inbox/entities.index.ts.tpl +1 -1
- package/templates/be/inbox/module-definition.ts.tpl +8 -2
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +10 -2
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +8 -2
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +3 -2
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -3
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +3 -2
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +7 -6
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/jobs/job.processor.ts.tpl +1 -1
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +5 -4
- package/templates/be/patterns/projections/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -3
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/reactors/consumer.ts.tpl +1 -1
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +1 -1
- package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +1 -1
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +9 -3
- package/templates/be/platform/graphql/graphql.module-definition.ts +8 -3
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +9 -3
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +3 -2
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +2 -1
- package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +8 -4
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/http-security/persistence/connection.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/http-security/persistence/rate-limit.sql.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +8 -3
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +6 -5
- package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +3 -2
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +10 -5
- package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +34 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +47 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +3 -1
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +8 -4
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts +29 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +6 -31
- package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +18 -9
- package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +35 -11
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.spec.ts +44 -8
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +40 -18
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +18 -6
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.spec.ts +73 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.ts +26 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +24 -53
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.registration.ts +31 -0
- package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/persistence/entities/index.ts +1 -1
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +7 -1
- package/templates/be/table/api/feature.module-definition.ts.tpl +9 -3
- package/templates/be/table/api/graphql.module-definition.ts.tpl +9 -3
- package/templates/be/table/entities.index.ts.tpl +1 -1
- package/templates/be/table/module-definition.ts.tpl +8 -3
- package/templates/be/test-world/e2e/serves-once-migrated.e2e-spec.ts.tpl +9 -9
- package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +4 -3
- package/templates/be/test-world/world/test-world.config.ts.tpl +8 -1
- package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
- package/templates/fe/common/app-layout.tsx.partial +1 -1
- package/templates/fe/common/site-shell.tsx.partial +3 -3
- package/templates/fe/skeleton/apps/__app__/src/components/providers/AppProviders/index.tsx +33 -10
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +2 -1
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignIn.ts +11 -3
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +8 -2
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useViewerId.ts +11 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +2 -1
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/hooks.ts +1 -1
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +18 -12
- package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/hooks.ts +3 -2
- package/templates/fe/skeleton/apps/__app__/src/redux/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +12 -4
- package/templates/fe/skeleton/apps/__app__/src/redux/store.ts +8 -3
- package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +1 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +2 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -1
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +2 -1
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +20 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +5 -1
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +4 -7
- package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/index.tsx +2 -3
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +4 -7
- package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/index.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +3 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +5 -1
- package/templates/fe/table/graphql/useMutateSwr.ts.tpl +2 -1
- package/templates/fe/table/graphql/useQuerySwr.ts.tpl +2 -1
- package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/clients/no-auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/attach-access-token.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/error.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/http.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/locale.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/refresh-token.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/retry.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/timeout.ts +1 -1
- package/templates/fe/units/page/index.tsx.tpl +3 -2
- package/templates/fe/units/slot/SlotView.tsx.tpl +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +0 -16
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +0 -11
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +0 -7
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +0 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +0 -8
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +0 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +0 -186
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +0 -22
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +0 -25
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts +0 -104
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +0 -34
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.spec.ts +0 -28
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +0 -11
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +0 -88
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +0 -69
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +0 -37
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -36
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +0 -63
- package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/classNames.ts +0 -4
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +0 -30
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +0 -50
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +0 -31
- package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/classNames.ts +0 -4
package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { Test } from "@nestjs/testing"
|
|
2
|
-
import { exportPKCS8, generateKeyPair, SignJWT } from "jose"
|
|
3
|
-
import { Secret } from "@modules/platform/config"
|
|
4
|
-
import { PassportErrorCode } from "./errors/passport.error"
|
|
5
|
-
import { createTokenKit, tokenKit } from "./passport.tokens"
|
|
6
|
-
|
|
7
|
-
const TOKENS: unique symbol = Symbol("passport.tokens")
|
|
8
|
-
|
|
9
|
-
type SigningKey = Awaited<ReturnType<typeof generateKeyPair>>["privateKey"]
|
|
10
|
-
|
|
11
|
-
const ISSUER = "https://api.example.test"
|
|
12
|
-
const AUDIENCE = "app"
|
|
13
|
-
|
|
14
|
-
const build = async () => {
|
|
15
|
-
const moduleRef = await Test.createTestingModule({
|
|
16
|
-
providers: [{ provide: TOKENS, useValue: { createTokenKit, tokenKit } }],
|
|
17
|
-
}).compile()
|
|
18
|
-
return moduleRef.get<{ createTokenKit: typeof createTokenKit; tokenKit: typeof tokenKit }>(TOKENS)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const keyPair = async () => {
|
|
22
|
-
const { privateKey } = await generateKeyPair("ES256", { extractable: true })
|
|
23
|
-
return { privateKey, options: { issuer: ISSUER, audience: AUDIENCE, privateKey: new Secret(await exportPKCS8(privateKey)), keyId: "k1", accessTtlSeconds: 60 } }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** A token the kit's own key signs with the given claims, so only the claim checks can refuse it. */
|
|
27
|
-
const ownSigned = (privateKey: SigningKey, claims: Record<string, unknown>, { subject = "user-1", expires = true } = {}) => {
|
|
28
|
-
const jwt = new SignJWT(claims).setProtectedHeader({ alg: "ES256", kid: "k1" }).setIssuer(ISSUER).setAudience(AUDIENCE).setIssuedAt()
|
|
29
|
-
if (subject !== "") jwt.setSubject(subject)
|
|
30
|
-
if (expires) jwt.setExpirationTime("60s")
|
|
31
|
-
return jwt.sign(privateKey)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe("createTokenKit", () => {
|
|
35
|
-
it("verifies its own token with an email against its own key set and publishes only the public key", async () => {
|
|
36
|
-
const subject = await build()
|
|
37
|
-
const { options } = await keyPair()
|
|
38
|
-
const kit = await subject.createTokenKit(options)
|
|
39
|
-
|
|
40
|
-
expect(await kit.verify(await kit.issue({ id: "user-1", email: "a@example.test" }))).toEqual({ kind: "ok", value: { id: "user-1", role: "authenticated", email: "a@example.test" } })
|
|
41
|
-
expect(kit.jwks.keys).toHaveLength(1)
|
|
42
|
-
expect(kit.jwks.keys[0]).toMatchObject({ kid: "k1", alg: "ES256", use: "sig", kty: "EC", crv: "P-256" })
|
|
43
|
-
expect(kit.jwks.keys[0]).not.toHaveProperty("d")
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it("issues a token without an email claim for a user who has none", async () => {
|
|
47
|
-
const subject = await build()
|
|
48
|
-
const { options } = await keyPair()
|
|
49
|
-
const kit = await subject.createTokenKit(options)
|
|
50
|
-
|
|
51
|
-
expect(await kit.verify(await kit.issue({ id: "user-2" }))).toEqual({ kind: "ok", value: { id: "user-2", role: "authenticated" } })
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it("refuses a token another key signed, naming the failed signature", async () => {
|
|
55
|
-
const subject = await build()
|
|
56
|
-
const kit = await subject.createTokenKit((await keyPair()).options)
|
|
57
|
-
const other = await subject.createTokenKit((await keyPair()).options)
|
|
58
|
-
|
|
59
|
-
expect(await kit.verify(await other.issue({ id: "user-1" }))).toEqual({
|
|
60
|
-
kind: "refused",
|
|
61
|
-
code: PassportErrorCode.AccessTokenInvalid,
|
|
62
|
-
params: { cause: "JWSSignatureVerificationFailed: signature verification failed" },
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it("refuses a correctly signed token whose subject, expiry or role is not the one it issues", async () => {
|
|
67
|
-
const subject = await build()
|
|
68
|
-
const { privateKey, options } = await keyPair()
|
|
69
|
-
const kit = await subject.createTokenKit(options)
|
|
70
|
-
const invalid = { kind: "refused", code: PassportErrorCode.AccessTokenInvalid }
|
|
71
|
-
|
|
72
|
-
expect(await kit.verify(await ownSigned(privateKey, { role: "authenticated" }, { subject: "" }))).toEqual(invalid)
|
|
73
|
-
expect(await kit.verify(await ownSigned(privateKey, { role: "authenticated" }, { expires: false }))).toEqual(invalid)
|
|
74
|
-
expect(await kit.verify(await ownSigned(privateKey, { role: "service_role" }))).toEqual(invalid)
|
|
75
|
-
expect(await kit.verify(await ownSigned(privateKey, { role: "authenticated", email: 7 }))).toEqual({ kind: "ok", value: { id: "user-1", role: "authenticated" } })
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
describe("tokenKit", () => {
|
|
80
|
-
it("signs, verifies and publishes through the one key import it starts", async () => {
|
|
81
|
-
const subject = await build()
|
|
82
|
-
const { options } = await keyPair()
|
|
83
|
-
const kit = subject.tokenKit(options)
|
|
84
|
-
|
|
85
|
-
expect(await kit.verify(await kit.issue({ id: "user-3", email: "c@example.test" }))).toEqual({ kind: "ok", value: { id: "user-3", role: "authenticated", email: "c@example.test" } })
|
|
86
|
-
expect((await kit.jwks()).keys.map((key) => key.kid)).toEqual(["k1"])
|
|
87
|
-
})
|
|
88
|
-
})
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { createLocalJWKSet, exportJWK, importPKCS8, jwtVerify, SignJWT } from "jose"
|
|
2
|
-
import type { JSONWebKeySet } from "jose"
|
|
3
|
-
import { ok, refused } from "@modules/platform/primitives"
|
|
4
|
-
import { PassportErrorCode } from "./errors/passport.error"
|
|
5
|
-
import type { IssueAccessToken, OwnPrincipal, TokenOptions, VerifyAccessToken } from "./passport.options"
|
|
6
|
-
|
|
7
|
-
const ALGORITHM = "ES256"
|
|
8
|
-
const ROLE = "authenticated"
|
|
9
|
-
|
|
10
|
-
/** The app's own token kit: the signer, the verifier over its own key set, and the public JWKS the API serves. */
|
|
11
|
-
export interface TokenKit {
|
|
12
|
-
/** Signs one access token. */
|
|
13
|
-
readonly issue: IssueAccessToken
|
|
14
|
-
/** Verifies one access token against the app's own keys. */
|
|
15
|
-
readonly verify: VerifyAccessToken
|
|
16
|
-
/** The public key set served at `/.well-known/jwks.json`. */
|
|
17
|
-
readonly jwks: JSONWebKeySet
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Builds the token kit from the ES256 private key; the verifier checks the app's own key set, never a remote one. */
|
|
21
|
-
export const createTokenKit = async (options: TokenOptions): Promise<TokenKit> => {
|
|
22
|
-
const privateKey = await importPKCS8(options.privateKey.reveal(), ALGORITHM, { extractable: true })
|
|
23
|
-
const { d: _private, ...publicJwk } = await exportJWK(privateKey)
|
|
24
|
-
const jwks: JSONWebKeySet = { keys: [{ ...publicJwk, kid: options.keyId, alg: ALGORITHM, use: "sig" }] }
|
|
25
|
-
const keys = createLocalJWKSet(jwks)
|
|
26
|
-
const issue: IssueAccessToken = (user) =>
|
|
27
|
-
new SignJWT({ role: ROLE, ...(user.email === undefined ? {} : { email: user.email }) })
|
|
28
|
-
.setProtectedHeader({ alg: ALGORITHM, kid: options.keyId })
|
|
29
|
-
.setSubject(user.id)
|
|
30
|
-
.setIssuer(options.issuer)
|
|
31
|
-
.setAudience(options.audience)
|
|
32
|
-
.setIssuedAt()
|
|
33
|
-
.setExpirationTime(`${String(options.accessTtlSeconds)}s`)
|
|
34
|
-
.sign(privateKey)
|
|
35
|
-
const verify: VerifyAccessToken = async (token) => {
|
|
36
|
-
try {
|
|
37
|
-
const { payload } = await jwtVerify(token, keys, {
|
|
38
|
-
algorithms: ["ES256"],
|
|
39
|
-
audience: options.audience,
|
|
40
|
-
issuer: options.issuer,
|
|
41
|
-
})
|
|
42
|
-
if (typeof payload.sub !== "string" || typeof payload.exp !== "number" || payload.role !== ROLE) {
|
|
43
|
-
return refused(PassportErrorCode.AccessTokenInvalid)
|
|
44
|
-
}
|
|
45
|
-
const principal: OwnPrincipal = {
|
|
46
|
-
id: payload.sub,
|
|
47
|
-
role: ROLE,
|
|
48
|
-
...(typeof payload.email === "string" ? { email: payload.email } : {}),
|
|
49
|
-
}
|
|
50
|
-
return ok(principal)
|
|
51
|
-
} catch (cause) {
|
|
52
|
-
return refused(PassportErrorCode.AccessTokenInvalid, { cause: String(cause) })
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return { issue, verify, jwks }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* The token kit of a composition root that registers synchronously: each call awaits the one key import started here, so a
|
|
60
|
-
* bad key fails the first verification instead of hiding behind a default.
|
|
61
|
-
*/
|
|
62
|
-
export const tokenKit = (options: TokenOptions): { readonly issue: IssueAccessToken; readonly verify: VerifyAccessToken; readonly jwks: () => Promise<JSONWebKeySet> } => {
|
|
63
|
-
const kit = createTokenKit(options)
|
|
64
|
-
return {
|
|
65
|
-
issue: async (user) => (await kit).issue(user),
|
|
66
|
-
verify: async (token) => (await kit).verify(token),
|
|
67
|
-
jwks: async () => (await kit).jwks,
|
|
68
|
-
}
|
|
69
|
-
}
|
package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Injectable } from "@nestjs/common"
|
|
2
|
-
import { PassportStrategy } from "@nestjs/passport"
|
|
3
|
-
import { Strategy } from "passport-google-oauth20"
|
|
4
|
-
import type { Profile } from "passport-google-oauth20"
|
|
5
|
-
import { InjectPassportOptions } from "../passport.decorators"
|
|
6
|
-
import type { PassportOptions } from "../passport.options"
|
|
7
|
-
import { CookieStateStore } from "./oauth-state.store"
|
|
8
|
-
|
|
9
|
-
/** The Google identity a callback proves: the provider subject and the verified email when Google states one. */
|
|
10
|
-
export interface GoogleIdentity {
|
|
11
|
-
/** Google's stable subject id (auth.identities.provider_id). */
|
|
12
|
-
readonly providerId: string
|
|
13
|
-
/** The verified email, when present. */
|
|
14
|
-
readonly email?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@Injectable()
|
|
18
|
-
/** Google OAuth 2.0 (authorization code with state and PKCE): it proves the Google identity and never signs the user in itself. */
|
|
19
|
-
export class GoogleStrategy extends PassportStrategy(Strategy, "google") {
|
|
20
|
-
constructor(@InjectPassportOptions() options: PassportOptions) {
|
|
21
|
-
super({
|
|
22
|
-
clientID: options.google.clientId,
|
|
23
|
-
clientSecret: options.google.clientSecret.reveal(),
|
|
24
|
-
callbackURL: options.google.callbackUrl,
|
|
25
|
-
scope: ["openid", "email"],
|
|
26
|
-
pkce: true,
|
|
27
|
-
state: true,
|
|
28
|
-
store: new CookieStateStore(new URL(options.google.callbackUrl).pathname, options.refresh.cookieDomain),
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** The identity of the callback; the identity domain links it to a user and issues the session. */
|
|
33
|
-
validate(_accessToken: string, _refreshToken: string, profile: Profile): GoogleIdentity {
|
|
34
|
-
const email = profile.emails?.find((entry) => entry.verified)?.value
|
|
35
|
-
return { providerId: profile.id, ...(email === undefined ? {} : { email }) }
|
|
36
|
-
}
|
|
37
|
-
}
|
package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { Injectable } from "@nestjs/common"
|
|
2
|
-
import { PassportStrategy } from "@nestjs/passport"
|
|
3
|
-
import { Strategy } from "passport-local"
|
|
4
|
-
import { PassportError, PassportErrorCode } from "../errors/passport.error"
|
|
5
|
-
import { InjectPasswordAccounts } from "../passport.decorators"
|
|
6
|
-
import { verifyPassword } from "../passport.password"
|
|
7
|
-
|
|
8
|
-
/** The account a password sign-in proves. */
|
|
9
|
-
export interface PasswordAccount {
|
|
10
|
-
/** The user id (auth.users.id). */
|
|
11
|
-
readonly id: string
|
|
12
|
-
/** The account email. */
|
|
13
|
-
readonly email: string
|
|
14
|
-
/** The stored bcrypt hash (auth.users.encrypted_password), null for an account without a password. */
|
|
15
|
-
readonly passwordHash: string | null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** The lookup the identity domain provides: the account of one email, or undefined. */
|
|
19
|
-
export type FindPasswordAccount = (email: string) => Promise<PasswordAccount | undefined>
|
|
20
|
-
|
|
21
|
-
@Injectable()
|
|
22
|
-
/** Email and password sign-in: bcrypt against the stored hash; a miss and a wrong password fail alike. */
|
|
23
|
-
export class LocalStrategy extends PassportStrategy(Strategy, "local") {
|
|
24
|
-
constructor(@InjectPasswordAccounts() private readonly findAccount: FindPasswordAccount) {
|
|
25
|
-
super({ usernameField: "email", passwordField: "password" })
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** The proven account; every refusal is the same error so an email's existence never leaks. */
|
|
29
|
-
async validate(email: string, password: string): Promise<{ readonly id: string; readonly email: string }> {
|
|
30
|
-
const account = await this.findAccount(email.trim().toLowerCase())
|
|
31
|
-
if (!(await verifyPassword(password, account?.passwordHash ?? null)) || account === undefined) {
|
|
32
|
-
throw new PassportError({ code: PassportErrorCode.CredentialsRefused })
|
|
33
|
-
}
|
|
34
|
-
return { id: account.id, email: account.email }
|
|
35
|
-
}
|
|
36
|
-
}
|
package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { randomBytes, timingSafeEqual } from "node:crypto"
|
|
2
|
-
import type { Request, Response } from "express"
|
|
3
|
-
import { domainAttribute } from "../passport.cookie"
|
|
4
|
-
|
|
5
|
-
const COOKIE = "oauth_state"
|
|
6
|
-
const MAX_AGE_MS = 600_000
|
|
7
|
-
|
|
8
|
-
type Stored = { readonly state: string; readonly verifier: string }
|
|
9
|
-
type StoreCallback = (error: Error | null, state?: string) => void
|
|
10
|
-
type VerifyCallback = (error: Error | null, verifier?: string | false, state?: string) => void
|
|
11
|
-
|
|
12
|
-
/** The response of the request a passport-oauth2 store is called with: passport keeps it on `req.res`. */
|
|
13
|
-
const responseOf = (request: Request): Response | undefined => request.res
|
|
14
|
-
|
|
15
|
-
/** The value of the state cookie in the request's Cookie header (no cookie middleware is installed). */
|
|
16
|
-
const cookieOf = (request: Request): string | undefined =>
|
|
17
|
-
request.headers.cookie
|
|
18
|
-
?.split(";")
|
|
19
|
-
.map((part) => part.trim())
|
|
20
|
-
.find((part) => part.startsWith(`${COOKIE}=`))
|
|
21
|
-
?.slice(COOKIE.length + 1)
|
|
22
|
-
|
|
23
|
-
const readStored = (request: Request): Stored | undefined => {
|
|
24
|
-
const raw = cookieOf(request)
|
|
25
|
-
if (raw === undefined) return undefined
|
|
26
|
-
const [state, verifier, extra] = raw.split(".")
|
|
27
|
-
return state && verifier && extra === undefined ? { state, verifier } : undefined
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const sameText = (left: string, right: string): boolean => {
|
|
31
|
-
const a = Buffer.from(left)
|
|
32
|
-
const b = Buffer.from(right)
|
|
33
|
-
return a.length === b.length && timingSafeEqual(a, b)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* The OAuth state and PKCE verifier store of the Google strategy without a server session: both live in one short-lived
|
|
38
|
-
* httpOnly, Secure, SameSite=Lax cookie scoped to the callback path, and the callback must present the same state.
|
|
39
|
-
*/
|
|
40
|
-
export class CookieStateStore {
|
|
41
|
-
constructor(
|
|
42
|
-
private readonly callbackPath: string,
|
|
43
|
-
private readonly cookieDomain?: string,
|
|
44
|
-
) {}
|
|
45
|
-
|
|
46
|
-
/** Stores a fresh state with the PKCE verifier and hands the state to the authorization redirect. */
|
|
47
|
-
store(request: Request, verifier: string, _state: unknown, _meta: unknown, callback: StoreCallback): void {
|
|
48
|
-
const state = randomBytes(24).toString("base64url")
|
|
49
|
-
responseOf(request)?.cookie(COOKIE, `${state}.${verifier}`, { httpOnly: true, secure: true, sameSite: "lax", path: this.callbackPath, maxAge: MAX_AGE_MS, ...domainAttribute(this.cookieDomain) })
|
|
50
|
-
callback(null, state)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Answers the stored verifier only when the callback's state equals the stored one; the cookie is cleared either way. */
|
|
54
|
-
verify(request: Request, provided: string, callback: VerifyCallback): void {
|
|
55
|
-
const stored = readStored(request)
|
|
56
|
-
responseOf(request)?.clearCookie(COOKIE, { httpOnly: true, secure: true, sameSite: "lax", path: this.callbackPath, ...domainAttribute(this.cookieDomain) })
|
|
57
|
-
if (stored === undefined || !sameText(stored.state, provided)) {
|
|
58
|
-
callback(null, false)
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
callback(null, stored.verifier, stored.state)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import axios from "axios"
|
|
2
|
-
import { apiUrl } from "@/modules/config"
|
|
3
|
-
|
|
4
|
-
/** What one auth door answered: its status and its body, unread (the client narrows it). */
|
|
5
|
-
export interface DoorAnswer {
|
|
6
|
-
readonly status: number
|
|
7
|
-
readonly body: unknown
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** The auth doors of the app's own back end; the refresh token rides in their httpOnly cookie, never in a body. */
|
|
11
|
-
export type AuthDoor = "sign-in" | "sign-up" | "refresh" | "sign-out" | "password-reset" | "resend-verification"
|
|
12
|
-
|
|
13
|
-
const TIMEOUT_MS = 10_000
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* The one auth door of the front end: a POST to `<api>/auth/<door>` with the cookies attached. A network failure answers
|
|
17
|
-
* status 0, so the client maps it to "unavailable" instead of throwing.
|
|
18
|
-
*/
|
|
19
|
-
export const callAuthDoor = async (door: AuthDoor, body?: Readonly<Record<string, string>>): Promise<DoorAnswer> => {
|
|
20
|
-
try {
|
|
21
|
-
const response = await axios.post<unknown>(`${apiUrl()}/auth/${door}`, body, {
|
|
22
|
-
withCredentials: true,
|
|
23
|
-
validateStatus: () => true,
|
|
24
|
-
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
25
|
-
})
|
|
26
|
-
return { status: response.status, body: response.data }
|
|
27
|
-
} catch {
|
|
28
|
-
return { status: 0, body: undefined }
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { callAuthDoor } from "./auth-door"
|
|
2
|
-
import type { DoorAnswer } from "./auth-door"
|
|
3
|
-
import type { AuthClient, AuthFailure, AuthSession } from "./contracts"
|
|
4
|
-
import { heldSession, heldToken, hold, listen } from "./token-store"
|
|
5
|
-
|
|
6
|
-
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> => typeof value === "object" && value !== null
|
|
7
|
-
|
|
8
|
-
/** The token and session an auth door issued, narrowed from its unread body; undefined when the body is not one. */
|
|
9
|
-
const issuedOf = (body: unknown): { readonly token: string; readonly session: AuthSession } | undefined => {
|
|
10
|
-
if (!isRecord(body) || typeof body.accessToken !== "string" || !isRecord(body.user) || typeof body.user.id !== "string") return undefined
|
|
11
|
-
const email = typeof body.user.email === "string" ? body.user.email : undefined
|
|
12
|
-
return { token: body.accessToken, session: { userId: body.user.id, ...(email === undefined ? {} : { email }) } }
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** The failure kind of a door's status: refused credentials, rejected input, or the API unavailable. */
|
|
16
|
-
const kindOf = (status: number): string => {
|
|
17
|
-
if (status === 400 || status === 401 || status === 403) return "refused"
|
|
18
|
-
return status === 422 ? "invalid" : "unavailable"
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const failureOf = (answer: DoorAnswer): AuthFailure => ({ code: `AUTH_${String(answer.status)}`, kind: kindOf(answer.status), message: "" })
|
|
22
|
-
|
|
23
|
-
const ok = (answer: DoorAnswer): boolean => answer.status >= 200 && answer.status < 300
|
|
24
|
-
|
|
25
|
-
/** Rotates the refresh cookie and holds the new access token; signs out when the API refuses the cookie. */
|
|
26
|
-
const refresh = async (): Promise<string | undefined> => {
|
|
27
|
-
const answer = await callAuthDoor("refresh")
|
|
28
|
-
hold(ok(answer) ? issuedOf(answer.body) : undefined)
|
|
29
|
-
return heldToken()
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** The lite session issuer's client: the app's own auth doors through `auth-door.ts`, the token in `token-store.ts`. */
|
|
33
|
-
export const authClient: AuthClient = {
|
|
34
|
-
session: async () => heldSession() ?? ((await refresh()) === undefined ? undefined : heldSession()),
|
|
35
|
-
accessToken: async () => heldToken() ?? refresh(),
|
|
36
|
-
refreshAccessToken: refresh,
|
|
37
|
-
signInWithPassword: async (email, password) => {
|
|
38
|
-
const answer = await callAuthDoor("sign-in", { email, password })
|
|
39
|
-
const issued = ok(answer) ? issuedOf(answer.body) : undefined
|
|
40
|
-
if (issued === undefined) return failureOf(answer)
|
|
41
|
-
hold(issued)
|
|
42
|
-
return undefined
|
|
43
|
-
},
|
|
44
|
-
signOut: async () => {
|
|
45
|
-
const answer = await callAuthDoor("sign-out")
|
|
46
|
-
hold(undefined)
|
|
47
|
-
return ok(answer) ? undefined : failureOf(answer)
|
|
48
|
-
},
|
|
49
|
-
onChange: listen,
|
|
50
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { AuthSession } from "./contracts"
|
|
2
|
-
|
|
3
|
-
/** The signed-in reader and the access token, in memory only; the refresh token never reaches the browser's script. */
|
|
4
|
-
interface Held {
|
|
5
|
-
readonly token: string
|
|
6
|
-
readonly session: AuthSession
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
let held: Held | undefined
|
|
10
|
-
const listeners = new Set<(session: AuthSession | undefined) => void>()
|
|
11
|
-
|
|
12
|
-
/** The held access token, or undefined when signed out. */
|
|
13
|
-
export const heldToken = (): string | undefined => held?.token
|
|
14
|
-
|
|
15
|
-
/** The held session, or undefined when signed out. */
|
|
16
|
-
export const heldSession = (): AuthSession | undefined => held?.session
|
|
17
|
-
|
|
18
|
-
/** Replaces the held token and session (undefined signs out) and tells every listener. */
|
|
19
|
-
export const hold = (next: Held | undefined): void => {
|
|
20
|
-
held = next
|
|
21
|
-
for (const listener of listeners) listener(held?.session)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Subscribes to session changes; the listener hears the current session first. */
|
|
25
|
-
export const listen = (listener: (session: AuthSession | undefined) => void): (() => void) => {
|
|
26
|
-
listeners.add(listener)
|
|
27
|
-
listener(held?.session)
|
|
28
|
-
return () => {
|
|
29
|
-
listeners.delete(listener)
|
|
30
|
-
}
|
|
31
|
-
}
|