@mandujs/core 0.54.31 → 0.55.0-beta.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/README.md +654 -654
- package/package.json +106 -210
- package/scripts/postinstall-lock.ts +153 -153
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +15 -15
- package/src/a11y/types.ts +125 -125
- package/src/agent/__tests__/apply.test.ts +224 -0
- package/src/agent/__tests__/context.test.ts +97 -98
- package/src/agent/apply.ts +1031 -0
- package/src/agent/context.ts +552 -552
- package/src/agent/index.ts +4 -3
- package/src/agent/plan.ts +161 -243
- package/src/agent/repair.ts +239 -162
- package/src/agent/sync.ts +197 -198
- package/src/agent/types.ts +242 -94
- package/src/agent/verify.ts +55 -55
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +15 -5
- package/src/auth/__tests__/reset.test.ts +2 -2
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +14 -2
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/brain/doctor/analyzer.ts +7 -7
- package/src/bundler/__snapshots__/build.test.ts.snap +5 -5
- package/src/bundler/__tests__/build-runner.ts +166 -166
- package/src/bundler/__tests__/client-boundary-transform.test.ts +524 -524
- package/src/bundler/__tests__/cold-start.test.ts +60 -60
- package/src/bundler/__tests__/css.test.ts +49 -20
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +24 -24
- package/src/bundler/__tests__/generation.test.ts +447 -0
- package/src/bundler/__tests__/hdr.test.ts +24 -18
- package/src/bundler/__tests__/hmr-client.test.ts +62 -24
- package/src/bundler/__tests__/jsx-runtime-shim.test.ts +179 -140
- package/src/bundler/__tests__/manifest-schema.test.ts +305 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +42 -42
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/analyzer.ts +15 -15
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +898 -915
- package/src/bundler/build.ts +113 -24
- package/src/bundler/client-boundary-transform.ts +977 -977
- package/src/bundler/css.ts +65 -44
- package/src/bundler/dev.ts +105 -73
- package/src/bundler/fast-refresh-preamble.ts +47 -47
- package/src/bundler/generation.ts +602 -0
- package/src/bundler/index.ts +3 -3
- package/src/bundler/manifest-schema.ts +55 -40
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +13 -13
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +16 -15
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/prerender.ts +29 -9
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +1 -1
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +58 -56
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/props-serialization.test.ts +37 -37
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +2 -2
- package/src/client/index.ts +1 -1
- package/src/client/island.ts +79 -79
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/props-serialization.ts +233 -233
- package/src/client/runtime-entry.ts +598 -598
- package/src/client/runtime.ts +1 -1
- package/src/client/serialize.ts +50 -50
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image-compat.ts +3 -0
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +1 -1
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +9 -9
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/generate-types.ts +1 -1
- package/src/content/index.ts +2 -2
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +9 -9
- package/src/db/index.ts +2 -2
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +82 -82
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/smoke.test.ts +2 -2
- package/src/desktop/__tests__/webview-fallback.test.ts +12 -32
- package/src/desktop/__tests__/window.test.ts +20 -99
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/webview-fallback.ts +17 -4
- package/src/desktop/window.ts +17 -8
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +16 -16
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +1 -1
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/diagnose/__tests__/checks.test.ts +136 -136
- package/src/diagnose/checks.ts +185 -185
- package/src/diagnose/index.ts +17 -17
- package/src/diagnose/run.ts +10 -10
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +5 -1
- package/src/filling/auth.ts +308 -308
- package/src/filling/body-parse.test.ts +60 -60
- package/src/filling/cookie-codec.ts +5 -3
- package/src/filling/deps.ts +265 -265
- package/src/filling/head-method.test.ts +154 -154
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/generate.ts +30 -30
- package/src/generator/index.ts +3 -3
- package/src/generator/templates.test.ts +48 -48
- package/src/generator/templates.ts +219 -219
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/check.ts +39 -24
- package/src/guard/config-guard.ts +13 -13
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/fs-routes-policy.ts +51 -51
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/index.ts +11 -11
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/__tests__/id.test.ts +3 -3
- package/src/id/index.ts +105 -105
- package/src/index.ts +9 -48
- package/src/internal/client-boundary.ts +266 -266
- package/src/internal/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -779
- package/src/kitchen/api/bundle-inspector.ts +179 -0
- package/src/kitchen/api/errors-grouping.ts +126 -126
- package/src/kitchen/api/file-api.ts +11 -11
- package/src/kitchen/kitchen-handler.ts +36 -0
- package/src/kitchen/kitchen-ui.ts +456 -0
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/perf/user-marks.ts +1 -1
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +7 -7
- package/src/resource/__tests__/schema.test.ts +14 -14
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +165 -165
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +146 -146
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/generator-schema.ts +11 -11
- package/src/resource/generators/slot.ts +72 -72
- package/src/resource/schema.ts +21 -21
- package/src/router/client-entry.test.ts +227 -227
- package/src/router/client-entry.ts +218 -218
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.test.ts +532 -532
- package/src/router/fs-routes.ts +53 -53
- package/src/router/fs-scanner.ts +216 -216
- package/src/router/fs-types.ts +19 -19
- package/src/router/route-source-analyzer.ts +521 -521
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/devtools-adapter.test.ts +68 -68
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/inline-client-hydration.test.ts +237 -237
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/__tests__/observability-lifecycle.test.ts +103 -103
- package/src/runtime/__tests__/page-render-response.test.ts +517 -517
- package/src/runtime/__tests__/request-middleware.test.ts +70 -70
- package/src/runtime/__tests__/searchparams-page-props.test.ts +81 -81
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/devtools-adapter.ts +68 -68
- package/src/runtime/escape.ts +34 -34
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/fast-refresh-types.ts +1 -1
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +30 -11
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/observability-lifecycle.ts +290 -290
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/page-render-response.ts +287 -274
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/rate-limit.ts +1 -1
- package/src/runtime/redirect.ts +1 -1
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/request-middleware.ts +31 -31
- package/src/runtime/router.test.ts +4 -4
- package/src/runtime/router.ts +10 -10
- package/src/runtime/server.ts +39 -20
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/ssr.ts +43 -17
- package/src/runtime/static-files.ts +369 -289
- package/src/runtime/streaming-ssr.ts +219 -180
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/__tests__/scheduler.test.ts +12 -11
- package/src/scheduler/index.ts +11 -5
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/spec/schema.ts +35 -35
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/index.ts +9 -3
- package/src/testing/lcov.ts +192 -0
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/__tests__/watcher.test.ts +59 -59
- package/src/watcher/watcher.ts +61 -61
package/src/auth/index.ts
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @mandujs/core/auth — Unified auth barrel.
|
|
3
|
-
*
|
|
4
|
-
* One import surface for everything auth-related in Mandu:
|
|
5
|
-
* - Password hashing (argon2id / bcrypt) from `./password`
|
|
6
|
-
* - Session-backed login helpers from `./login`
|
|
7
|
-
* - Error classes and guards from `../filling/auth`
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* import {
|
|
12
|
-
* hashPassword,
|
|
13
|
-
* verifyPassword,
|
|
14
|
-
* loginUser,
|
|
15
|
-
* logoutUser,
|
|
16
|
-
* currentUserId,
|
|
17
|
-
* requireUser,
|
|
18
|
-
* AuthenticationError,
|
|
19
|
-
* } from "@mandujs/core/auth";
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* Fine-grained subpath imports remain available for tree-shaking-sensitive
|
|
23
|
-
* bundles:
|
|
24
|
-
* - `@mandujs/core/auth/password`
|
|
25
|
-
* - `@mandujs/core/auth/login`
|
|
26
|
-
*
|
|
27
|
-
* @module auth
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
// ── Password hashing (Phase 2.1) ──
|
|
31
|
-
export {
|
|
32
|
-
hashPassword,
|
|
33
|
-
verifyPassword,
|
|
34
|
-
type PasswordOptions,
|
|
35
|
-
} from "./password";
|
|
36
|
-
|
|
37
|
-
// ── Login / logout / read helpers (Phase 2.4) ──
|
|
38
|
-
export {
|
|
39
|
-
loginUser,
|
|
40
|
-
logoutUser,
|
|
41
|
-
currentUserId,
|
|
42
|
-
loggedAt,
|
|
43
|
-
type LoginOptions,
|
|
44
|
-
} from "./login";
|
|
45
|
-
|
|
46
|
-
// ── Email verification (Phase 5.3) ──
|
|
47
|
-
// The underlying token store (./tokens) is deliberately NOT re-exported —
|
|
48
|
-
// it's the shared plumbing for verification + reset, not a public primitive.
|
|
49
|
-
export {
|
|
50
|
-
createEmailVerification,
|
|
51
|
-
type VerificationFlow,
|
|
52
|
-
type VerificationFlowOptions,
|
|
53
|
-
} from "./verification";
|
|
54
|
-
|
|
55
|
-
// ── Password reset (Phase 5.3) ──
|
|
56
|
-
export {
|
|
57
|
-
createPasswordReset,
|
|
58
|
-
type ResetFlow,
|
|
59
|
-
type ResetFlowOptions,
|
|
60
|
-
} from "./reset";
|
|
61
|
-
|
|
62
|
-
// ── Error classes, guards, and user types (re-exported from filling/auth) ──
|
|
63
|
-
// We DO NOT re-export the factory functions (`createAuthGuard`, `createRoleGuard`)
|
|
64
|
-
// here — those are broader "beforeHandle" plumbing that lives on the filling
|
|
65
|
-
// surface. Users who need them still import from `@mandujs/core` directly.
|
|
66
|
-
export {
|
|
67
|
-
AuthenticationError,
|
|
68
|
-
AuthorizationError,
|
|
69
|
-
requireUser,
|
|
70
|
-
requireRole,
|
|
71
|
-
requireAnyRole,
|
|
72
|
-
requireAllRoles,
|
|
73
|
-
type BaseUser,
|
|
74
|
-
type UserWithRole,
|
|
75
|
-
type UserWithRoles,
|
|
76
|
-
} from "../filling/auth";
|
|
1
|
+
/**
|
|
2
|
+
* @mandujs/core/auth — Unified auth barrel.
|
|
3
|
+
*
|
|
4
|
+
* One import surface for everything auth-related in Mandu:
|
|
5
|
+
* - Password hashing (argon2id / bcrypt) from `./password`
|
|
6
|
+
* - Session-backed login helpers from `./login`
|
|
7
|
+
* - Error classes and guards from `../filling/auth`
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import {
|
|
12
|
+
* hashPassword,
|
|
13
|
+
* verifyPassword,
|
|
14
|
+
* loginUser,
|
|
15
|
+
* logoutUser,
|
|
16
|
+
* currentUserId,
|
|
17
|
+
* requireUser,
|
|
18
|
+
* AuthenticationError,
|
|
19
|
+
* } from "@mandujs/core/compat/auth/index";
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Fine-grained subpath imports remain available for tree-shaking-sensitive
|
|
23
|
+
* bundles:
|
|
24
|
+
* - `@mandujs/core/auth/password`
|
|
25
|
+
* - `@mandujs/core/auth/login`
|
|
26
|
+
*
|
|
27
|
+
* @module auth
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// ── Password hashing (Phase 2.1) ──
|
|
31
|
+
export {
|
|
32
|
+
hashPassword,
|
|
33
|
+
verifyPassword,
|
|
34
|
+
type PasswordOptions,
|
|
35
|
+
} from "./password";
|
|
36
|
+
|
|
37
|
+
// ── Login / logout / read helpers (Phase 2.4) ──
|
|
38
|
+
export {
|
|
39
|
+
loginUser,
|
|
40
|
+
logoutUser,
|
|
41
|
+
currentUserId,
|
|
42
|
+
loggedAt,
|
|
43
|
+
type LoginOptions,
|
|
44
|
+
} from "./login";
|
|
45
|
+
|
|
46
|
+
// ── Email verification (Phase 5.3) ──
|
|
47
|
+
// The underlying token store (./tokens) is deliberately NOT re-exported —
|
|
48
|
+
// it's the shared plumbing for verification + reset, not a public primitive.
|
|
49
|
+
export {
|
|
50
|
+
createEmailVerification,
|
|
51
|
+
type VerificationFlow,
|
|
52
|
+
type VerificationFlowOptions,
|
|
53
|
+
} from "./verification";
|
|
54
|
+
|
|
55
|
+
// ── Password reset (Phase 5.3) ──
|
|
56
|
+
export {
|
|
57
|
+
createPasswordReset,
|
|
58
|
+
type ResetFlow,
|
|
59
|
+
type ResetFlowOptions,
|
|
60
|
+
} from "./reset";
|
|
61
|
+
|
|
62
|
+
// ── Error classes, guards, and user types (re-exported from filling/auth) ──
|
|
63
|
+
// We DO NOT re-export the factory functions (`createAuthGuard`, `createRoleGuard`)
|
|
64
|
+
// here — those are broader "beforeHandle" plumbing that lives on the filling
|
|
65
|
+
// surface. Users who need them still import from `@mandujs/core` directly.
|
|
66
|
+
export {
|
|
67
|
+
AuthenticationError,
|
|
68
|
+
AuthorizationError,
|
|
69
|
+
requireUser,
|
|
70
|
+
requireRole,
|
|
71
|
+
requireAnyRole,
|
|
72
|
+
requireAllRoles,
|
|
73
|
+
type BaseUser,
|
|
74
|
+
type UserWithRole,
|
|
75
|
+
type UserWithRoles,
|
|
76
|
+
} from "../filling/auth";
|
package/src/auth/login.ts
CHANGED
|
@@ -1,225 +1,225 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @mandujs/core/auth/login
|
|
3
|
-
*
|
|
4
|
-
* Ergonomic helpers that bridge Phase 2.3's `session()` middleware with
|
|
5
|
-
* Mandu's existing auth types in `filling/auth.ts`.
|
|
6
|
-
*
|
|
7
|
-
* These helpers **absorb the ordering hazard** from `saveSession` / `destroySession`
|
|
8
|
-
* (see `middleware/session.ts` lines 99-102): callers must not build a Response
|
|
9
|
-
* before committing the session. `loginUser` / `logoutUser` call those helpers
|
|
10
|
-
* internally, so handler code collapses to:
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```ts
|
|
14
|
-
* import { loginUser, logoutUser, currentUserId } from "@mandujs/core/auth";
|
|
15
|
-
* import { hashPassword, verifyPassword } from "@mandujs/core/auth";
|
|
16
|
-
*
|
|
17
|
-
* // login route
|
|
18
|
-
* const ok = await verifyPassword(plaintext, storedHash);
|
|
19
|
-
* if (!ok) return ctx.unauthorized();
|
|
20
|
-
* await loginUser(ctx, user.id);
|
|
21
|
-
* return ctx.redirect("/dashboard");
|
|
22
|
-
*
|
|
23
|
-
* // logout route
|
|
24
|
-
* await logoutUser(ctx);
|
|
25
|
-
* return ctx.redirect("/");
|
|
26
|
-
*
|
|
27
|
-
* // read-side
|
|
28
|
-
* const uid = currentUserId(ctx); // null if not logged in
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* **Note on `ctx.set("user", ...)` vs `session.get("userId")`:** this module
|
|
32
|
-
* stores `userId` in the SESSION (persisted across requests via cookie).
|
|
33
|
-
* `requireUser` / `requireRole` from `filling/auth.ts` read from the
|
|
34
|
-
* REQUEST-SCOPED store at `ctx.get("user")`, which is a different location.
|
|
35
|
-
* A typical app bridges the two with a tiny middleware placed AFTER
|
|
36
|
-
* `session()`:
|
|
37
|
-
*
|
|
38
|
-
* ```ts
|
|
39
|
-
* .use(session({ storage }))
|
|
40
|
-
* .beforeHandle(async (ctx) => {
|
|
41
|
-
* const uid = currentUserId(ctx);
|
|
42
|
-
* if (uid) ctx.set("user", await db.users.findById(uid));
|
|
43
|
-
* })
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* @module auth/login
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
import type { ManduContext } from "../filling/context";
|
|
50
|
-
import type { Session } from "../filling/session";
|
|
51
|
-
import { saveSession, destroySession } from "../middleware/session";
|
|
52
|
-
import { AuthenticationError } from "../filling/auth";
|
|
53
|
-
|
|
54
|
-
// ========== Defaults ==========
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Session key under which `userId` is persisted. Kept short and stable so
|
|
58
|
-
* upgrading from `loginUser` to reading the raw session still works.
|
|
59
|
-
*/
|
|
60
|
-
const DEFAULT_USER_ID_KEY = "userId";
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Session key under which the login timestamp (ms since epoch) is persisted.
|
|
64
|
-
* Useful for session-age checks and re-authentication prompts.
|
|
65
|
-
*/
|
|
66
|
-
const DEFAULT_LOGGED_AT_KEY = "loginAt";
|
|
67
|
-
|
|
68
|
-
/** Default ctx key used by `session()` middleware. Mirrors `middleware/session.ts`. */
|
|
69
|
-
const DEFAULT_SESSION_ATTACH_KEY = "session";
|
|
70
|
-
|
|
71
|
-
// ========== Types ==========
|
|
72
|
-
|
|
73
|
-
/** Options passed when storing auth state in the session. */
|
|
74
|
-
export interface LoginOptions {
|
|
75
|
-
/** Session key for user id. Default: `"userId"`. */
|
|
76
|
-
userIdKey?: string;
|
|
77
|
-
/** Session key for "logged at" timestamp (ms). Default: `"loginAt"`. */
|
|
78
|
-
loggedAtKey?: string;
|
|
79
|
-
/**
|
|
80
|
-
* Extra session fields to set atomically with userId. Restricted to
|
|
81
|
-
* JSON-serializable primitives so the session cookie round-trips cleanly.
|
|
82
|
-
* If you need richer types, mutate the Session directly before calling
|
|
83
|
-
* `loginUser`, or after it (before the response).
|
|
84
|
-
*/
|
|
85
|
-
extras?: Record<string, string | number | boolean>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ========== Helpers ==========
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Mark the current session as authenticated for `userId`, commit it via
|
|
92
|
-
* `saveSession` (so the `Set-Cookie` header lands on the next response),
|
|
93
|
-
* and return. Must be called **before** returning a Response from the
|
|
94
|
-
* handler — `saveSession` attaches cookies via `ctx.cookies`, which
|
|
95
|
-
* `ctx.json` / `ctx.ok` / `ctx.redirect` snapshot at build time.
|
|
96
|
-
*
|
|
97
|
-
* Throws an {@link AuthenticationError} wrapping the underlying wiring
|
|
98
|
-
* error when the `session()` middleware is not installed on this request.
|
|
99
|
-
* Using `AuthenticationError` (and not raw `Error`) keeps the login path
|
|
100
|
-
* uniformly catchable — the same catch block that handles bad credentials
|
|
101
|
-
* can handle "no session middleware" as a generic auth failure.
|
|
102
|
-
*/
|
|
103
|
-
export async function loginUser(
|
|
104
|
-
ctx: ManduContext,
|
|
105
|
-
userId: string,
|
|
106
|
-
options?: LoginOptions,
|
|
107
|
-
): Promise<void> {
|
|
108
|
-
if (typeof userId !== "string" || userId.length === 0) {
|
|
109
|
-
throw new AuthenticationError("loginUser: userId must be a non-empty string");
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const userIdKey = options?.userIdKey ?? DEFAULT_USER_ID_KEY;
|
|
113
|
-
const loggedAtKey = options?.loggedAtKey ?? DEFAULT_LOGGED_AT_KEY;
|
|
114
|
-
|
|
115
|
-
const session = ctx.get<Session>(DEFAULT_SESSION_ATTACH_KEY);
|
|
116
|
-
if (!session) {
|
|
117
|
-
throw new AuthenticationError(
|
|
118
|
-
"Session middleware not installed: add `.use(session({ storage }))` before calling loginUser",
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// All three writes happen before the commit so they land atomically in the
|
|
123
|
-
// same Set-Cookie. If any write throws, we propagate without committing.
|
|
124
|
-
session.set(userIdKey, userId);
|
|
125
|
-
session.set(loggedAtKey, Date.now());
|
|
126
|
-
|
|
127
|
-
if (options?.extras) {
|
|
128
|
-
for (const [key, value] of Object.entries(options.extras)) {
|
|
129
|
-
session.set(key, value);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Commit — saveSession reads `ctx.get("session")` and attaches Set-Cookie.
|
|
134
|
-
// Forwards the same wiring-error from saveSession if storageKey was wired
|
|
135
|
-
// differently, though we already verified the session above.
|
|
136
|
-
try {
|
|
137
|
-
await saveSession(ctx);
|
|
138
|
-
} catch (cause) {
|
|
139
|
-
// Re-shape to AuthenticationError so callers have one error type on the
|
|
140
|
-
// login path. Preserve the original via the `cause` property (ES2022).
|
|
141
|
-
throw new AuthenticationError(
|
|
142
|
-
`loginUser: failed to commit session — ${(cause as Error)?.message ?? String(cause)}`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Clear the current session by invoking `destroySession`: wipes in-memory
|
|
149
|
-
* state and emits an expiring `Set-Cookie` (Max-Age=0) so the browser drops
|
|
150
|
-
* its copy.
|
|
151
|
-
*
|
|
152
|
-
* Idempotent: calling on a request with no session cookie, or calling twice
|
|
153
|
-
* in succession, is safe — `destroySession` always emits a fresh expiring
|
|
154
|
-
* cookie. Throws an {@link AuthenticationError} only when the session
|
|
155
|
-
* middleware itself was not installed (same rationale as {@link loginUser}).
|
|
156
|
-
*/
|
|
157
|
-
export async function logoutUser(
|
|
158
|
-
ctx: ManduContext,
|
|
159
|
-
options?: Pick<LoginOptions, "userIdKey">,
|
|
160
|
-
): Promise<void> {
|
|
161
|
-
const session = ctx.get<Session>(DEFAULT_SESSION_ATTACH_KEY);
|
|
162
|
-
if (!session) {
|
|
163
|
-
throw new AuthenticationError(
|
|
164
|
-
"Session middleware not installed: add `.use(session({ storage }))` before calling logoutUser",
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// `options.userIdKey` is accepted for symmetry with loginUser, but
|
|
169
|
-
// destroySession wipes the entire session so per-key handling is unneeded.
|
|
170
|
-
// We still `unset` it first to normalize dirty state in case a caller
|
|
171
|
-
// composes logoutUser with pre/post inspection.
|
|
172
|
-
const userIdKey = options?.userIdKey ?? DEFAULT_USER_ID_KEY;
|
|
173
|
-
if (session.has(userIdKey)) {
|
|
174
|
-
session.unset(userIdKey);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
await destroySession(ctx);
|
|
179
|
-
} catch (cause) {
|
|
180
|
-
throw new AuthenticationError(
|
|
181
|
-
`logoutUser: failed to destroy session — ${(cause as Error)?.message ?? String(cause)}`,
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Read the current `userId` from the session. Returns `null` when either:
|
|
188
|
-
* - The session middleware is not installed on this request
|
|
189
|
-
* - No `userId` key is present in the session
|
|
190
|
-
*
|
|
191
|
-
* **Never throws.** This is the read path; handlers call it to decide
|
|
192
|
-
* whether to redirect to login, so a throwing contract would force every
|
|
193
|
-
* caller to wrap in try/catch. Returning `null` is the ergonomic choice.
|
|
194
|
-
*/
|
|
195
|
-
export function currentUserId(
|
|
196
|
-
ctx: ManduContext,
|
|
197
|
-
options?: Pick<LoginOptions, "userIdKey">,
|
|
198
|
-
): string | null {
|
|
199
|
-
const session = ctx.get<Session>(DEFAULT_SESSION_ATTACH_KEY);
|
|
200
|
-
if (!session) return null;
|
|
201
|
-
|
|
202
|
-
const userIdKey = options?.userIdKey ?? DEFAULT_USER_ID_KEY;
|
|
203
|
-
const raw = session.get<unknown>(userIdKey);
|
|
204
|
-
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Read the login timestamp (ms since epoch) from the session. Returns `null`
|
|
209
|
-
* when the session middleware is absent, the key is unset, or the stored
|
|
210
|
-
* value is not a number (defensive against hand-edited / corrupted sessions).
|
|
211
|
-
*
|
|
212
|
-
* Useful for session-age checks ("re-authenticate after 30 min for sensitive
|
|
213
|
-
* actions"): `Date.now() - (loggedAt(ctx) ?? 0) > THIRTY_MIN`.
|
|
214
|
-
*/
|
|
215
|
-
export function loggedAt(
|
|
216
|
-
ctx: ManduContext,
|
|
217
|
-
options?: Pick<LoginOptions, "loggedAtKey">,
|
|
218
|
-
): number | null {
|
|
219
|
-
const session = ctx.get<Session>(DEFAULT_SESSION_ATTACH_KEY);
|
|
220
|
-
if (!session) return null;
|
|
221
|
-
|
|
222
|
-
const loggedAtKey = options?.loggedAtKey ?? DEFAULT_LOGGED_AT_KEY;
|
|
223
|
-
const raw = session.get<unknown>(loggedAtKey);
|
|
224
|
-
return typeof raw === "number" && Number.isFinite(raw) ? raw : null;
|
|
225
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @mandujs/core/auth/login
|
|
3
|
+
*
|
|
4
|
+
* Ergonomic helpers that bridge Phase 2.3's `session()` middleware with
|
|
5
|
+
* Mandu's existing auth types in `filling/auth.ts`.
|
|
6
|
+
*
|
|
7
|
+
* These helpers **absorb the ordering hazard** from `saveSession` / `destroySession`
|
|
8
|
+
* (see `middleware/session.ts` lines 99-102): callers must not build a Response
|
|
9
|
+
* before committing the session. `loginUser` / `logoutUser` call those helpers
|
|
10
|
+
* internally, so handler code collapses to:
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { loginUser, logoutUser, currentUserId } from "@mandujs/core/compat/auth/index";
|
|
15
|
+
* import { hashPassword, verifyPassword } from "@mandujs/core/compat/auth/index";
|
|
16
|
+
*
|
|
17
|
+
* // login route
|
|
18
|
+
* const ok = await verifyPassword(plaintext, storedHash);
|
|
19
|
+
* if (!ok) return ctx.unauthorized();
|
|
20
|
+
* await loginUser(ctx, user.id);
|
|
21
|
+
* return ctx.redirect("/dashboard");
|
|
22
|
+
*
|
|
23
|
+
* // logout route
|
|
24
|
+
* await logoutUser(ctx);
|
|
25
|
+
* return ctx.redirect("/");
|
|
26
|
+
*
|
|
27
|
+
* // read-side
|
|
28
|
+
* const uid = currentUserId(ctx); // null if not logged in
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* **Note on `ctx.set("user", ...)` vs `session.get("userId")`:** this module
|
|
32
|
+
* stores `userId` in the SESSION (persisted across requests via cookie).
|
|
33
|
+
* `requireUser` / `requireRole` from `filling/auth.ts` read from the
|
|
34
|
+
* REQUEST-SCOPED store at `ctx.get("user")`, which is a different location.
|
|
35
|
+
* A typical app bridges the two with a tiny middleware placed AFTER
|
|
36
|
+
* `session()`:
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* .use(session({ storage }))
|
|
40
|
+
* .beforeHandle(async (ctx) => {
|
|
41
|
+
* const uid = currentUserId(ctx);
|
|
42
|
+
* if (uid) ctx.set("user", await db.users.findById(uid));
|
|
43
|
+
* })
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @module auth/login
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import type { ManduContext } from "../filling/context";
|
|
50
|
+
import type { Session } from "../filling/session";
|
|
51
|
+
import { saveSession, destroySession } from "../middleware/session";
|
|
52
|
+
import { AuthenticationError } from "../filling/auth";
|
|
53
|
+
|
|
54
|
+
// ========== Defaults ==========
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Session key under which `userId` is persisted. Kept short and stable so
|
|
58
|
+
* upgrading from `loginUser` to reading the raw session still works.
|
|
59
|
+
*/
|
|
60
|
+
const DEFAULT_USER_ID_KEY = "userId";
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Session key under which the login timestamp (ms since epoch) is persisted.
|
|
64
|
+
* Useful for session-age checks and re-authentication prompts.
|
|
65
|
+
*/
|
|
66
|
+
const DEFAULT_LOGGED_AT_KEY = "loginAt";
|
|
67
|
+
|
|
68
|
+
/** Default ctx key used by `session()` middleware. Mirrors `middleware/session.ts`. */
|
|
69
|
+
const DEFAULT_SESSION_ATTACH_KEY = "session";
|
|
70
|
+
|
|
71
|
+
// ========== Types ==========
|
|
72
|
+
|
|
73
|
+
/** Options passed when storing auth state in the session. */
|
|
74
|
+
export interface LoginOptions {
|
|
75
|
+
/** Session key for user id. Default: `"userId"`. */
|
|
76
|
+
userIdKey?: string;
|
|
77
|
+
/** Session key for "logged at" timestamp (ms). Default: `"loginAt"`. */
|
|
78
|
+
loggedAtKey?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Extra session fields to set atomically with userId. Restricted to
|
|
81
|
+
* JSON-serializable primitives so the session cookie round-trips cleanly.
|
|
82
|
+
* If you need richer types, mutate the Session directly before calling
|
|
83
|
+
* `loginUser`, or after it (before the response).
|
|
84
|
+
*/
|
|
85
|
+
extras?: Record<string, string | number | boolean>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ========== Helpers ==========
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Mark the current session as authenticated for `userId`, commit it via
|
|
92
|
+
* `saveSession` (so the `Set-Cookie` header lands on the next response),
|
|
93
|
+
* and return. Must be called **before** returning a Response from the
|
|
94
|
+
* handler — `saveSession` attaches cookies via `ctx.cookies`, which
|
|
95
|
+
* `ctx.json` / `ctx.ok` / `ctx.redirect` snapshot at build time.
|
|
96
|
+
*
|
|
97
|
+
* Throws an {@link AuthenticationError} wrapping the underlying wiring
|
|
98
|
+
* error when the `session()` middleware is not installed on this request.
|
|
99
|
+
* Using `AuthenticationError` (and not raw `Error`) keeps the login path
|
|
100
|
+
* uniformly catchable — the same catch block that handles bad credentials
|
|
101
|
+
* can handle "no session middleware" as a generic auth failure.
|
|
102
|
+
*/
|
|
103
|
+
export async function loginUser(
|
|
104
|
+
ctx: ManduContext,
|
|
105
|
+
userId: string,
|
|
106
|
+
options?: LoginOptions,
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
if (typeof userId !== "string" || userId.length === 0) {
|
|
109
|
+
throw new AuthenticationError("loginUser: userId must be a non-empty string");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const userIdKey = options?.userIdKey ?? DEFAULT_USER_ID_KEY;
|
|
113
|
+
const loggedAtKey = options?.loggedAtKey ?? DEFAULT_LOGGED_AT_KEY;
|
|
114
|
+
|
|
115
|
+
const session = ctx.get<Session>(DEFAULT_SESSION_ATTACH_KEY);
|
|
116
|
+
if (!session) {
|
|
117
|
+
throw new AuthenticationError(
|
|
118
|
+
"Session middleware not installed: add `.use(session({ storage }))` before calling loginUser",
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// All three writes happen before the commit so they land atomically in the
|
|
123
|
+
// same Set-Cookie. If any write throws, we propagate without committing.
|
|
124
|
+
session.set(userIdKey, userId);
|
|
125
|
+
session.set(loggedAtKey, Date.now());
|
|
126
|
+
|
|
127
|
+
if (options?.extras) {
|
|
128
|
+
for (const [key, value] of Object.entries(options.extras)) {
|
|
129
|
+
session.set(key, value);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Commit — saveSession reads `ctx.get("session")` and attaches Set-Cookie.
|
|
134
|
+
// Forwards the same wiring-error from saveSession if storageKey was wired
|
|
135
|
+
// differently, though we already verified the session above.
|
|
136
|
+
try {
|
|
137
|
+
await saveSession(ctx);
|
|
138
|
+
} catch (cause) {
|
|
139
|
+
// Re-shape to AuthenticationError so callers have one error type on the
|
|
140
|
+
// login path. Preserve the original via the `cause` property (ES2022).
|
|
141
|
+
throw new AuthenticationError(
|
|
142
|
+
`loginUser: failed to commit session — ${(cause as Error)?.message ?? String(cause)}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Clear the current session by invoking `destroySession`: wipes in-memory
|
|
149
|
+
* state and emits an expiring `Set-Cookie` (Max-Age=0) so the browser drops
|
|
150
|
+
* its copy.
|
|
151
|
+
*
|
|
152
|
+
* Idempotent: calling on a request with no session cookie, or calling twice
|
|
153
|
+
* in succession, is safe — `destroySession` always emits a fresh expiring
|
|
154
|
+
* cookie. Throws an {@link AuthenticationError} only when the session
|
|
155
|
+
* middleware itself was not installed (same rationale as {@link loginUser}).
|
|
156
|
+
*/
|
|
157
|
+
export async function logoutUser(
|
|
158
|
+
ctx: ManduContext,
|
|
159
|
+
options?: Pick<LoginOptions, "userIdKey">,
|
|
160
|
+
): Promise<void> {
|
|
161
|
+
const session = ctx.get<Session>(DEFAULT_SESSION_ATTACH_KEY);
|
|
162
|
+
if (!session) {
|
|
163
|
+
throw new AuthenticationError(
|
|
164
|
+
"Session middleware not installed: add `.use(session({ storage }))` before calling logoutUser",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// `options.userIdKey` is accepted for symmetry with loginUser, but
|
|
169
|
+
// destroySession wipes the entire session so per-key handling is unneeded.
|
|
170
|
+
// We still `unset` it first to normalize dirty state in case a caller
|
|
171
|
+
// composes logoutUser with pre/post inspection.
|
|
172
|
+
const userIdKey = options?.userIdKey ?? DEFAULT_USER_ID_KEY;
|
|
173
|
+
if (session.has(userIdKey)) {
|
|
174
|
+
session.unset(userIdKey);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await destroySession(ctx);
|
|
179
|
+
} catch (cause) {
|
|
180
|
+
throw new AuthenticationError(
|
|
181
|
+
`logoutUser: failed to destroy session — ${(cause as Error)?.message ?? String(cause)}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Read the current `userId` from the session. Returns `null` when either:
|
|
188
|
+
* - The session middleware is not installed on this request
|
|
189
|
+
* - No `userId` key is present in the session
|
|
190
|
+
*
|
|
191
|
+
* **Never throws.** This is the read path; handlers call it to decide
|
|
192
|
+
* whether to redirect to login, so a throwing contract would force every
|
|
193
|
+
* caller to wrap in try/catch. Returning `null` is the ergonomic choice.
|
|
194
|
+
*/
|
|
195
|
+
export function currentUserId(
|
|
196
|
+
ctx: ManduContext,
|
|
197
|
+
options?: Pick<LoginOptions, "userIdKey">,
|
|
198
|
+
): string | null {
|
|
199
|
+
const session = ctx.get<Session>(DEFAULT_SESSION_ATTACH_KEY);
|
|
200
|
+
if (!session) return null;
|
|
201
|
+
|
|
202
|
+
const userIdKey = options?.userIdKey ?? DEFAULT_USER_ID_KEY;
|
|
203
|
+
const raw = session.get<unknown>(userIdKey);
|
|
204
|
+
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Read the login timestamp (ms since epoch) from the session. Returns `null`
|
|
209
|
+
* when the session middleware is absent, the key is unset, or the stored
|
|
210
|
+
* value is not a number (defensive against hand-edited / corrupted sessions).
|
|
211
|
+
*
|
|
212
|
+
* Useful for session-age checks ("re-authenticate after 30 min for sensitive
|
|
213
|
+
* actions"): `Date.now() - (loggedAt(ctx) ?? 0) > THIRTY_MIN`.
|
|
214
|
+
*/
|
|
215
|
+
export function loggedAt(
|
|
216
|
+
ctx: ManduContext,
|
|
217
|
+
options?: Pick<LoginOptions, "loggedAtKey">,
|
|
218
|
+
): number | null {
|
|
219
|
+
const session = ctx.get<Session>(DEFAULT_SESSION_ATTACH_KEY);
|
|
220
|
+
if (!session) return null;
|
|
221
|
+
|
|
222
|
+
const loggedAtKey = options?.loggedAtKey ?? DEFAULT_LOGGED_AT_KEY;
|
|
223
|
+
const raw = session.get<unknown>(loggedAtKey);
|
|
224
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : null;
|
|
225
|
+
}
|
package/src/auth/password.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* ```ts
|
|
16
|
-
* import { hashPassword, verifyPassword } from "@mandujs/core/auth/password";
|
|
16
|
+
* import { hashPassword, verifyPassword } from "@mandujs/core/compat/auth/password";
|
|
17
17
|
*
|
|
18
18
|
* const hash = await hashPassword("s3cret!");
|
|
19
19
|
* const ok = await verifyPassword("s3cret!", hash); // true
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* See https://en.wikipedia.org/wiki/Bcrypt#User_input
|
|
29
29
|
*/
|
|
30
30
|
const BCRYPT_MAX_BYTES = 72;
|
|
31
|
+
const ARGON2_MIN_MEMORY_COST = 8;
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Password hashing options. Mirrors the `Bun.password` option shape.
|
|
@@ -53,7 +54,7 @@ function getBunPassword(): BunPasswordApi {
|
|
|
53
54
|
const g = globalThis as unknown as { Bun?: { password?: BunPasswordApi } };
|
|
54
55
|
if (!g.Bun || !g.Bun.password) {
|
|
55
56
|
throw new Error(
|
|
56
|
-
"[@mandujs/core/auth/password] Bun.password is unavailable — this module requires the Bun runtime (>= 1.
|
|
57
|
+
"[@mandujs/core/auth/password] Bun.password is unavailable — this module requires the Bun runtime (>= 1.4.0).",
|
|
57
58
|
);
|
|
58
59
|
}
|
|
59
60
|
return g.Bun.password;
|
|
@@ -85,6 +86,17 @@ export async function hashPassword(
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
const algorithm = options?.algorithm ?? "argon2id";
|
|
90
|
+
if (
|
|
91
|
+
algorithm !== "bcrypt" &&
|
|
92
|
+
options?.memoryCost !== undefined &&
|
|
93
|
+
options.memoryCost < ARGON2_MIN_MEMORY_COST
|
|
94
|
+
) {
|
|
95
|
+
throw new RangeError(
|
|
96
|
+
`[@mandujs/core/auth/password] hashPassword: Argon2 memoryCost must be at least ${ARGON2_MIN_MEMORY_COST} on Bun 1.4.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
88
100
|
return await getBunPassword().hash(plain, options);
|
|
89
101
|
}
|
|
90
102
|
|