json-rest-api 1.0.22 → 1.0.24
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/CLAUDE.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/README.md +22 -1
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +196 -38
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +111 -5
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +370 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/lib/enhanced-logger.js +1 -1
- package/package.json +6 -2
- package/plugins/core/bulk-operations-plugin.js +26 -34
- package/plugins/core/connectors/express-plugin.js +97 -190
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/busboy-detector.js +4 -4
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/anyapi/schema-utils.js +43 -4
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1111 -160
- package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -3
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +21 -76
- package/plugins/core/lib/querying/knex-relationship-includes.js +468 -215
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +20 -5
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -14
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
- package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
- package/plugins/core/lib/storage/canonical-storage-mapping.js +133 -0
- package/plugins/core/lib/storage/storage-adapter.js +82 -4
- package/plugins/core/lib/storage/storage-mapping.js +274 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
- package/plugins/core/rest-api-anyapi-knex-plugin.js +531 -312
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +271 -226
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +1 -0
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +148 -92
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get.js +29 -14
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
- package/plugins/core/rest-api-plugin-methods/patch.js +56 -12
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/post.js +21 -8
- package/plugins/core/rest-api-plugin-methods/put.js +73 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin-methods/release.js +0 -2
- package/plugins/core/rest-api-plugin.js +6 -0
- package/plugins/core/rest-api-positioning-plugin.js +2 -5
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -4
- package/plugins/storage/local-storage.js +1 -1
- package/plugins/storage/s3-storage.js +5 -2
- package/quickTest.js +1 -0
- package/scripts/run-docs.js +80 -84
- package/test-debug.js +1 -1
- package/tests/README.md +7 -0
- package/tests/anyapi-basic.test.js +3 -0
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +521 -0
- package/tests/db-table-operations.test.js +786 -0
- package/tests/fastify-plugin.test.js +156 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +278 -176
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/id-normalization.test.js +444 -0
- package/tests/logical-id-write-validation.test.js +151 -0
- package/tests/pagination-cursor-multifield.test.js +11 -11
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +259 -0
- package/audit/00-charter.md +0 -103
- package/audit/01-scope-map.md +0 -84
- package/audit/02-coverage-matrix.csv +0 -174
- package/audit/03-invariants.md +0 -123
- package/audit/04-standards-rubric.md +0 -56
- package/audit/05-competitor-rubric.md +0 -77
- package/audit/06-findings.md +0 -160
- package/audit/07-risk-register.csv +0 -10
- package/audit/08-remediation-roadmap.md +0 -177
- package/audit/09-signoff-check.md +0 -23
- package/audit/10-final-synthesis.md +0 -29
- package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
- package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
- package/audit/evidence/code/boundary-leak-checks.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
- package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
- package/audit/evidence/code/critical-pass1-summary.csv +0 -67
- package/audit/evidence/code/critical-pass2-summary.csv +0 -67
- package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
- package/audit/evidence/code/error-model-signals.txt +0 -98
- package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
- package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
- package/audit/evidence/code/hook-flow-signals.txt +0 -282
- package/audit/evidence/code/idproperty-signals.txt +0 -157
- package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
- package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
- package/audit/evidence/code/imports-json-rest-api.txt +0 -354
- package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
- package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
- package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
- package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
- package/audit/evidence/code/permission-signals.txt +0 -193
- package/audit/evidence/code/query-include-signals.txt +0 -687
- package/audit/evidence/code/transaction-signals.txt +0 -28
- package/audit/evidence/code/validation-signals.txt +0 -650
- package/audit/evidence/code/write-path-signals.txt +0 -61
- package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
- package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
- package/audit/evidence/external/corpus-freeze.md +0 -21
- package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
- package/audit/evidence/external/npm-download-signals.txt +0 -23
- package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
- package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
- package/audit/evidence/external/readme-fortune-signals.txt +0 -26
- package/audit/evidence/external/readme-fortune.md +0 -165
- package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
- package/audit/evidence/external/readme-json-api-serializer.md +0 -690
- package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
- package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
- package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
- package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
- package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
- package/audit/evidence/external/readme-jsonapi-server.md +0 -104
- package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
- package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
- package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
- package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
- package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
- package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
- package/audit/evidence/external/standards-sources.txt +0 -6
- package/audit/evidence/runtime/baseline-commits.txt +0 -2
- package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
- package/audit/evidence/runtime/environment.txt +0 -4
- package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
- package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
- package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
- package/audit/evidence/runtime/test-exitcodes.txt +0 -4
- package/audit/evidence/runtime/test-summary.md +0 -107
- package/audit/evidence/runtime/trace-anchors.txt +0 -61
- package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
- package/audit/evidence/tests/failing-tests-summary.txt +0 -25
- package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
- package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
- package/audit/evidence/tests/query-include-test-signals.txt +0 -948
- package/audit/evidence/tests/relationship-test-signals.txt +0 -223
- package/audit/evidence/tests/skipped-tests.txt +0 -10
- package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
- package/audit/evidence/tests/test-suite-list.txt +0 -32
- package/audit/subsystems/docs-drift.md +0 -49
- package/audit/subsystems/pass2-cross-review.md +0 -131
- package/audit/subsystems/plugins-access.md +0 -51
- package/audit/subsystems/plugins-bulk.md +0 -50
- package/audit/subsystems/plugins-cors.md +0 -48
- package/audit/subsystems/plugins-file.md +0 -53
- package/audit/subsystems/plugins-multihome.md +0 -50
- package/audit/subsystems/plugins-positioning.md +0 -54
- package/audit/subsystems/plugins-socketio.md +0 -50
- package/audit/subsystems/query-engine.md +0 -61
- package/audit/subsystems/relationships.md +0 -55
- package/audit/subsystems/rest-orchestration.md +0 -66
- package/audit/subsystems/schema-core.md +0 -64
- package/audit/subsystems/storage-knex.md +0 -57
- package/audit/subsystems/tests.md +0 -48
- package/audit/subsystems/transport-express.md +0 -55
- package/audit/traces/trace-read-get.md +0 -22
- package/audit/traces/trace-read-query.md +0 -25
- package/audit/traces/trace-relationship-endpoints.md +0 -31
- package/audit/traces/trace-write-delete.md +0 -17
- package/audit/traces/trace-write-patch.md +0 -26
- package/audit/traces/trace-write-post.md +0 -22
- package/audit/traces/trace-write-put.md +0 -23
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
# Authentication and Permissions Guide
|
|
2
|
-
|
|
3
|
-
This guide shows you how to add authentication to your REST API using the JWT Authentication Plugin. The plugin validates JWT tokens from any auth provider (Supabase, Auth0, Firebase, etc.) and enforces permissions on your resources.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
- [How It Works - The Architecture](#how-it-works---the-architecture)
|
|
7
|
-
- [Quick Start - Get Running in 5 Minutes](#quick-start---get-running-in-5-minutes)
|
|
8
|
-
- [Frontend: Getting Auth Tokens](#frontend-getting-auth-tokens)
|
|
9
|
-
- [Supabase Setup](#supabase-setup)
|
|
10
|
-
- [Auth0 Setup](#auth0-setup)
|
|
11
|
-
- [Google OAuth Login](#google-oauth-login)
|
|
12
|
-
- [Backend: Validating Tokens](#backend-validating-tokens)
|
|
13
|
-
- [Using Built-in Checkers](#using-built-in-checkers)
|
|
14
|
-
- [Progressive Examples](#progressive-examples)
|
|
15
|
-
- [Advanced: Custom Authorization](#advanced-custom-authorization)
|
|
16
|
-
- [Token Management](#token-management)
|
|
17
|
-
- [Migration Guide](#migration-guide)
|
|
18
|
-
|
|
19
|
-
## How It Works - The Architecture
|
|
20
|
-
|
|
21
|
-
The JWT plugin is a **backend/server-side** tool that validates tokens. Here's the complete flow:
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
|
|
25
|
-
│ Browser │ │ Supabase/ │ │ Your API │
|
|
26
|
-
│ (Frontend) │ ───> │ Auth0 │ │ (Backend) │
|
|
27
|
-
└─────────────┘ └─────────────┘ └──────────────┘
|
|
28
|
-
│ │ │
|
|
29
|
-
│ 1. Login with │ │
|
|
30
|
-
│ email/Google │ │
|
|
31
|
-
│ ─────────────────> │ │
|
|
32
|
-
│ │ │
|
|
33
|
-
│ 2. Get JWT token │ │
|
|
34
|
-
│ <───────────────── │ │
|
|
35
|
-
│ │ │
|
|
36
|
-
│ 3. API request with Bearer token │
|
|
37
|
-
│ ──────────────────────────────────────> │
|
|
38
|
-
│ │
|
|
39
|
-
│ 4. JWT Plugin validates
|
|
40
|
-
│ token with Supabase
|
|
41
|
-
│ │
|
|
42
|
-
│ 5. Response (allowed or denied) │
|
|
43
|
-
│ <────────────────────────────────────── │
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Key Points:**
|
|
47
|
-
- **Frontend**: Handles user login (email/password, Google, etc.) and gets JWT token
|
|
48
|
-
- **Auth Provider**: Issues and manages JWT tokens (Supabase, Auth0, etc.)
|
|
49
|
-
- **Backend (Your API)**: Uses JWT plugin to validate tokens and enforce permissions
|
|
50
|
-
- **JWT Plugin**: Does NOT handle login/signup - only validates existing tokens
|
|
51
|
-
|
|
52
|
-
## Quick Start - Get Running in 5 Minutes
|
|
53
|
-
|
|
54
|
-
### Step 1: Install the Plugin
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
npm install jose
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Step 2: Configure Your Backend API
|
|
61
|
-
|
|
62
|
-
```javascript
|
|
63
|
-
import { Api } from 'hooked-api';
|
|
64
|
-
import { RestApiPlugin, RestApiKnexPlugin } from 'json-rest-api';
|
|
65
|
-
import { JwtAuthPlugin } from 'json-rest-api/plugins/core/jwt-auth-plugin.js';
|
|
66
|
-
|
|
67
|
-
// Create your API
|
|
68
|
-
const api = new Api();
|
|
69
|
-
await api.use(RestApiPlugin);
|
|
70
|
-
await api.use(RestApiKnexPlugin, { knex });
|
|
71
|
-
|
|
72
|
-
// Add JWT authentication (example with Supabase)
|
|
73
|
-
await api.use(JwtAuthPlugin, {
|
|
74
|
-
jwksUrl: 'https://your-project.supabase.co/auth/v1/.well-known/jwks.json'
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Add a protected resource
|
|
78
|
-
await api.addResource('posts', {
|
|
79
|
-
schema: {
|
|
80
|
-
id: { type: 'id' },
|
|
81
|
-
title: { type: 'string', required: true },
|
|
82
|
-
content: { type: 'text' },
|
|
83
|
-
user_id: { type: 'string' } // Automatically set to current user
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
// Simple auth rules using built-in checkers
|
|
87
|
-
auth: {
|
|
88
|
-
query: ['public'], // Anyone can read posts
|
|
89
|
-
post: ['authenticated'], // Must be logged in to create
|
|
90
|
-
patch: ['owns'], // Can only edit your own posts
|
|
91
|
-
delete: ['owns'] // Can only delete your own posts
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Step 3: Test It
|
|
97
|
-
|
|
98
|
-
```javascript
|
|
99
|
-
// From your frontend (after user logs in)
|
|
100
|
-
const response = await fetch('http://localhost:3000/api/posts', {
|
|
101
|
-
method: 'POST',
|
|
102
|
-
headers: {
|
|
103
|
-
'Authorization': `Bearer ${token}`, // Token from Supabase/Auth0
|
|
104
|
-
'Content-Type': 'application/json'
|
|
105
|
-
},
|
|
106
|
-
body: JSON.stringify({
|
|
107
|
-
data: {
|
|
108
|
-
type: 'posts',
|
|
109
|
-
attributes: {
|
|
110
|
-
title: 'My First Post',
|
|
111
|
-
content: 'Hello world!'
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
});
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
That's it! You now have authenticated API endpoints.
|
|
119
|
-
|
|
120
|
-
## Frontend: Getting Auth Tokens
|
|
121
|
-
|
|
122
|
-
The JWT plugin validates tokens from any provider. Here's how to get tokens from popular services:
|
|
123
|
-
|
|
124
|
-
### Supabase Setup
|
|
125
|
-
|
|
126
|
-
#### 1. Create a Supabase Project
|
|
127
|
-
Go to [supabase.com](https://supabase.com) and create a new project. You'll get:
|
|
128
|
-
- Project URL: `https://your-project.supabase.co`
|
|
129
|
-
- Anon Key: `eyJhbGc...` (public key for frontend)
|
|
130
|
-
|
|
131
|
-
#### 2. Frontend: Email/Password Login
|
|
132
|
-
|
|
133
|
-
```javascript
|
|
134
|
-
// In your React/Vue/etc app
|
|
135
|
-
import { createClient } from '@supabase/supabase-js'
|
|
136
|
-
|
|
137
|
-
const supabase = createClient(
|
|
138
|
-
'https://your-project.supabase.co',
|
|
139
|
-
'your-anon-key'
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
// Sign up new user
|
|
143
|
-
async function signUp(email, password) {
|
|
144
|
-
const { data, error } = await supabase.auth.signUp({
|
|
145
|
-
email,
|
|
146
|
-
password
|
|
147
|
-
})
|
|
148
|
-
if (error) throw error
|
|
149
|
-
return data.session.access_token // This is your JWT!
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Sign in existing user
|
|
153
|
-
async function signIn(email, password) {
|
|
154
|
-
const { data, error } = await supabase.auth.signInWithPassword({
|
|
155
|
-
email,
|
|
156
|
-
password
|
|
157
|
-
})
|
|
158
|
-
if (error) throw error
|
|
159
|
-
return data.session.access_token // This is your JWT!
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Use the token for API calls
|
|
163
|
-
async function createPost(token, title, content) {
|
|
164
|
-
const response = await fetch('http://localhost:3000/api/posts', {
|
|
165
|
-
method: 'POST',
|
|
166
|
-
headers: {
|
|
167
|
-
'Authorization': `Bearer ${token}`,
|
|
168
|
-
'Content-Type': 'application/json'
|
|
169
|
-
},
|
|
170
|
-
body: JSON.stringify({
|
|
171
|
-
data: {
|
|
172
|
-
type: 'posts',
|
|
173
|
-
attributes: { title, content }
|
|
174
|
-
}
|
|
175
|
-
})
|
|
176
|
-
})
|
|
177
|
-
return response.json()
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
#### 3. Backend Configuration
|
|
182
|
-
|
|
183
|
-
```javascript
|
|
184
|
-
// Your API server
|
|
185
|
-
await api.use(JwtAuthPlugin, {
|
|
186
|
-
jwksUrl: 'https://your-project.supabase.co/auth/v1/.well-known/jwks.json'
|
|
187
|
-
})
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Auth0 Setup
|
|
191
|
-
|
|
192
|
-
#### 1. Create an Auth0 Application
|
|
193
|
-
Go to [auth0.com](https://auth0.com) and create a new Single Page Application.
|
|
194
|
-
|
|
195
|
-
#### 2. Frontend: Auth0 Login
|
|
196
|
-
|
|
197
|
-
```javascript
|
|
198
|
-
// In your React/Vue/etc app
|
|
199
|
-
import { createAuth0Client } from '@auth0/auth0-spa-js'
|
|
200
|
-
|
|
201
|
-
const auth0 = await createAuth0Client({
|
|
202
|
-
domain: 'your-domain.auth0.com',
|
|
203
|
-
clientId: 'your-client-id',
|
|
204
|
-
authorizationParams: {
|
|
205
|
-
redirect_uri: window.location.origin,
|
|
206
|
-
audience: 'https://your-api.com' // Your API identifier
|
|
207
|
-
}
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
// Login
|
|
211
|
-
async function login() {
|
|
212
|
-
await auth0.loginWithRedirect()
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// After redirect, get token
|
|
216
|
-
async function getToken() {
|
|
217
|
-
const token = await auth0.getAccessTokenSilently()
|
|
218
|
-
return token // Your JWT!
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
#### 3. Backend Configuration
|
|
223
|
-
|
|
224
|
-
```javascript
|
|
225
|
-
// Your API server
|
|
226
|
-
await api.use(JwtAuthPlugin, {
|
|
227
|
-
jwksUrl: 'https://your-domain.auth0.com/.well-known/jwks.json',
|
|
228
|
-
audience: 'https://your-api.com',
|
|
229
|
-
issuer: 'https://your-domain.auth0.com/'
|
|
230
|
-
})
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Google OAuth Login
|
|
234
|
-
|
|
235
|
-
Google login works through your auth provider (Supabase/Auth0), not directly. The user logs in with Google, but you still get a JWT from Supabase/Auth0.
|
|
236
|
-
|
|
237
|
-
#### Supabase + Google
|
|
238
|
-
|
|
239
|
-
##### 1. Enable Google in Supabase Dashboard
|
|
240
|
-
- Go to Authentication → Providers → Google
|
|
241
|
-
- Add your Google Client ID and Secret (from Google Cloud Console)
|
|
242
|
-
|
|
243
|
-
##### 2. Frontend: Google Login Button
|
|
244
|
-
|
|
245
|
-
```javascript
|
|
246
|
-
// React component example
|
|
247
|
-
function GoogleLoginButton() {
|
|
248
|
-
const handleGoogleLogin = async () => {
|
|
249
|
-
const { data, error } = await supabase.auth.signInWithOAuth({
|
|
250
|
-
provider: 'google',
|
|
251
|
-
options: {
|
|
252
|
-
redirectTo: 'http://localhost:3000/auth/callback'
|
|
253
|
-
}
|
|
254
|
-
})
|
|
255
|
-
if (error) throw error
|
|
256
|
-
// User will be redirected to Google, then back to your app
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return (
|
|
260
|
-
<button onClick={handleGoogleLogin}>
|
|
261
|
-
Sign in with Google
|
|
262
|
-
</button>
|
|
263
|
-
)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// After redirect, get the session
|
|
267
|
-
useEffect(() => {
|
|
268
|
-
supabase.auth.getSession().then(({ data: { session } }) => {
|
|
269
|
-
if (session) {
|
|
270
|
-
const token = session.access_token // JWT from Supabase!
|
|
271
|
-
// Use this token for API calls
|
|
272
|
-
}
|
|
273
|
-
})
|
|
274
|
-
}, [])
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
##### 3. Backend: Same Configuration!
|
|
278
|
-
|
|
279
|
-
```javascript
|
|
280
|
-
// No change needed - same as email/password
|
|
281
|
-
await api.use(JwtAuthPlugin, {
|
|
282
|
-
jwksUrl: 'https://your-project.supabase.co/auth/v1/.well-known/jwks.json'
|
|
283
|
-
})
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
The beauty is that your backend doesn't care HOW users logged in (email, Google, Facebook) - it just validates the JWT from Supabase.
|
|
287
|
-
|
|
288
|
-
## Backend: Validating Tokens
|
|
289
|
-
|
|
290
|
-
### Basic Configuration Options
|
|
291
|
-
|
|
292
|
-
```javascript
|
|
293
|
-
await api.use(JwtAuthPlugin, {
|
|
294
|
-
// Option 1: For external auth providers (Supabase, Auth0)
|
|
295
|
-
jwksUrl: 'https://.../.well-known/jwks.json',
|
|
296
|
-
|
|
297
|
-
// Option 2: For symmetric key (your own auth)
|
|
298
|
-
secret: process.env.JWT_SECRET,
|
|
299
|
-
|
|
300
|
-
// Option 3: For asymmetric key (your own auth)
|
|
301
|
-
publicKey: '-----BEGIN PUBLIC KEY-----...',
|
|
302
|
-
|
|
303
|
-
// Optional settings
|
|
304
|
-
audience: 'your-api-audience', // Required for Auth0
|
|
305
|
-
issuer: 'https://issuer.com', // Validate token issuer
|
|
306
|
-
userIdField: 'sub', // Where to find user ID (default: 'sub')
|
|
307
|
-
emailField: 'email', // Where to find email (default: 'email')
|
|
308
|
-
ownershipField: 'user_id' // Field for ownership checks (default: 'user_id')
|
|
309
|
-
})
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
### Understanding JWKS Configuration (Recommended)
|
|
313
|
-
|
|
314
|
-
When you use `jwksUrl` (the recommended approach for Supabase/Auth0), here's what actually happens behind the scenes:
|
|
315
|
-
|
|
316
|
-
#### What is JWKS?
|
|
317
|
-
JWKS (JSON Web Key Set) is a URL that provides public keys for verifying JWT signatures. Instead of hardcoding keys, your API fetches them dynamically.
|
|
318
|
-
|
|
319
|
-
#### The Automatic Process
|
|
320
|
-
```javascript
|
|
321
|
-
await api.use(JwtAuthPlugin, {
|
|
322
|
-
jwksUrl: 'https://your-project.supabase.co/auth/v1/.well-known/jwks.json'
|
|
323
|
-
});
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
This single line sets up:
|
|
327
|
-
|
|
328
|
-
1. **Automatic Key Fetching**: On first request, downloads Supabase's current public keys
|
|
329
|
-
2. **Smart Caching**: Caches keys for 10 minutes to avoid repeated downloads
|
|
330
|
-
3. **Key Rotation Handling**: When Supabase rotates keys, your API automatically gets the new ones
|
|
331
|
-
4. **Multiple Key Support**: Handles multiple keys during rotation periods
|
|
332
|
-
|
|
333
|
-
#### What the JWKS URL Returns
|
|
334
|
-
```json
|
|
335
|
-
{
|
|
336
|
-
"keys": [
|
|
337
|
-
{
|
|
338
|
-
"kid": "abc123", // Key ID - matches 'kid' in JWT header
|
|
339
|
-
"kty": "RSA", // Key type
|
|
340
|
-
"use": "sig", // Usage: signature verification
|
|
341
|
-
"n": "xGOr-H7A...", // RSA public key modulus
|
|
342
|
-
"e": "AQAB" // RSA public key exponent
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
"kid": "def456", // Another key (during rotation)
|
|
346
|
-
"n": "yKPq-J8B...",
|
|
347
|
-
"e": "AQAB"
|
|
348
|
-
}
|
|
349
|
-
]
|
|
350
|
-
}
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
#### Performance Impact
|
|
354
|
-
- **First request**: ~50-100ms (downloads keys from Supabase)
|
|
355
|
-
- **Next 10 minutes**: ~1-2ms (uses cached keys)
|
|
356
|
-
- **After cache expires**: Re-downloads automatically
|
|
357
|
-
- **On verification failure**: Attempts to refresh keys
|
|
358
|
-
|
|
359
|
-
#### Why This is Superior to Hardcoded Keys
|
|
360
|
-
- **Zero Maintenance**: Keys update automatically when provider rotates them
|
|
361
|
-
- **Always Secure**: You never handle or store private keys
|
|
362
|
-
- **No Downtime**: Key rotation happens seamlessly
|
|
363
|
-
- **No Code Changes**: Provider can change keys without you knowing
|
|
364
|
-
|
|
365
|
-
### What Happens During Validation
|
|
366
|
-
|
|
367
|
-
1. **Token Extraction**: Plugin extracts token from `Authorization: Bearer <token>` header
|
|
368
|
-
2. **Key Selection**: Matches token's `kid` (key ID) with cached JWKS keys
|
|
369
|
-
3. **Signature Verification**: Validates JWT signature using the matching public key
|
|
370
|
-
4. **Claims Validation**: Checks audience, issuer, expiration
|
|
371
|
-
5. **Context Population**: Sets `context.auth`:
|
|
372
|
-
```javascript
|
|
373
|
-
context.auth = {
|
|
374
|
-
userId: '123', // From 'sub' claim
|
|
375
|
-
email: 'user@example.com', // From 'email' claim
|
|
376
|
-
token: { /* full JWT */ }, // All token data
|
|
377
|
-
tokenId: 'jti-123' // For revocation
|
|
378
|
-
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
## Using Built-in Checkers
|
|
382
|
-
|
|
383
|
-
The plugin provides only THREE built-in authorization checkers:
|
|
384
|
-
|
|
385
|
-
### 1. `public` - No Authentication Required
|
|
386
|
-
|
|
387
|
-
```javascript
|
|
388
|
-
auth: {
|
|
389
|
-
query: ['public'], // Anyone can read, even without token
|
|
390
|
-
get: ['public']
|
|
391
|
-
}
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### 2. `authenticated` - Must Be Logged In
|
|
395
|
-
|
|
396
|
-
```javascript
|
|
397
|
-
auth: {
|
|
398
|
-
post: ['authenticated'], // Must have valid JWT token
|
|
399
|
-
patch: ['authenticated']
|
|
400
|
-
}
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
### 3. `owns` - Must Own the Resource
|
|
404
|
-
|
|
405
|
-
```javascript
|
|
406
|
-
auth: {
|
|
407
|
-
patch: ['owns'], // Can only edit records where user_id matches
|
|
408
|
-
delete: ['owns'] // Can only delete your own records
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
The `owns` checker compares the `user_id` field in the database record with the current user's ID from the token.
|
|
413
|
-
|
|
414
|
-
## Progressive Examples
|
|
415
|
-
|
|
416
|
-
Let's build up from simple to complex:
|
|
417
|
-
|
|
418
|
-
### Example 1: Public API (No Auth)
|
|
419
|
-
|
|
420
|
-
```javascript
|
|
421
|
-
await api.addResource('articles', {
|
|
422
|
-
schema: {
|
|
423
|
-
title: { type: 'string', required: true },
|
|
424
|
-
content: { type: 'text' }
|
|
425
|
-
}
|
|
426
|
-
// No auth property = completely open
|
|
427
|
-
})
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
### Example 2: Read-Only Public, Write Requires Login
|
|
431
|
-
|
|
432
|
-
```javascript
|
|
433
|
-
await api.addResource('articles', {
|
|
434
|
-
schema: {
|
|
435
|
-
title: { type: 'string', required: true },
|
|
436
|
-
content: { type: 'text' },
|
|
437
|
-
user_id: { type: 'string' }
|
|
438
|
-
},
|
|
439
|
-
|
|
440
|
-
auth: {
|
|
441
|
-
query: ['public'], // Anyone can read
|
|
442
|
-
get: ['public'],
|
|
443
|
-
post: ['authenticated'], // Must be logged in to create
|
|
444
|
-
patch: ['authenticated'],
|
|
445
|
-
delete: ['authenticated']
|
|
446
|
-
}
|
|
447
|
-
})
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
### Example 3: Users Can Only Edit Their Own Content
|
|
451
|
-
|
|
452
|
-
```javascript
|
|
453
|
-
await api.addResource('articles', {
|
|
454
|
-
schema: {
|
|
455
|
-
title: { type: 'string', required: true },
|
|
456
|
-
content: { type: 'text' },
|
|
457
|
-
user_id: { type: 'string' } // Auto-set on create
|
|
458
|
-
},
|
|
459
|
-
|
|
460
|
-
auth: {
|
|
461
|
-
query: ['public'],
|
|
462
|
-
get: ['public'],
|
|
463
|
-
post: ['authenticated'],
|
|
464
|
-
patch: ['owns'], // Only edit your own
|
|
465
|
-
delete: ['owns'] // Only delete your own
|
|
466
|
-
}
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
// Also enable auto-ownership
|
|
470
|
-
await api.use(JwtAuthPlugin, {
|
|
471
|
-
jwksUrl: '...',
|
|
472
|
-
autoOwnership: {
|
|
473
|
-
enabled: true, // Auto-set user_id on create
|
|
474
|
-
filterByOwner: true // Users only see their own records in queries
|
|
475
|
-
}
|
|
476
|
-
})
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
### Example 4: Adding Admin Override (Custom Checker)
|
|
480
|
-
|
|
481
|
-
```javascript
|
|
482
|
-
// Register a custom role checker
|
|
483
|
-
api.helpers.auth.registerChecker('role', async (context, { param }) => {
|
|
484
|
-
// In real app, query your users table
|
|
485
|
-
const user = await api.resources.users.get(context.auth.userId)
|
|
486
|
-
return user.role === param
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
await api.addResource('articles', {
|
|
490
|
-
schema: { /* ... */ },
|
|
491
|
-
|
|
492
|
-
auth: {
|
|
493
|
-
query: ['public'],
|
|
494
|
-
get: ['public'],
|
|
495
|
-
post: ['authenticated'],
|
|
496
|
-
patch: ['owns', 'role:admin'], // Owner OR admin
|
|
497
|
-
delete: ['owns', 'role:admin'] // Owner OR admin
|
|
498
|
-
}
|
|
499
|
-
})
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
## Advanced: Custom Authorization
|
|
503
|
-
|
|
504
|
-
The plugin is designed to be minimal. For complex authorization, you create custom checkers:
|
|
505
|
-
|
|
506
|
-
### Database-Driven Roles
|
|
507
|
-
|
|
508
|
-
```javascript
|
|
509
|
-
// Store roles in your database
|
|
510
|
-
await api.addResource('user_roles', {
|
|
511
|
-
schema: {
|
|
512
|
-
user_id: { type: 'string', required: true },
|
|
513
|
-
role: { type: 'string', required: true }
|
|
514
|
-
}
|
|
515
|
-
})
|
|
516
|
-
|
|
517
|
-
// Create role checker
|
|
518
|
-
api.helpers.auth.registerChecker('role', async (context, { param }) => {
|
|
519
|
-
const roles = await api.resources.user_roles.query({
|
|
520
|
-
filter: { user_id: context.auth.userId }
|
|
521
|
-
})
|
|
522
|
-
return roles.some(r => r.role === param)
|
|
523
|
-
})
|
|
524
|
-
|
|
525
|
-
// Use it
|
|
526
|
-
auth: {
|
|
527
|
-
delete: ['role:admin'] // Must have admin role in database
|
|
528
|
-
}
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
### Subscription Tiers
|
|
532
|
-
|
|
533
|
-
```javascript
|
|
534
|
-
api.helpers.auth.registerChecker('plan', async (context, { param }) => {
|
|
535
|
-
const user = await api.resources.users.get(context.auth.userId)
|
|
536
|
-
const plans = ['free', 'basic', 'pro', 'enterprise']
|
|
537
|
-
const requiredLevel = plans.indexOf(param)
|
|
538
|
-
const userLevel = plans.indexOf(user.subscription_plan)
|
|
539
|
-
return userLevel >= requiredLevel
|
|
540
|
-
})
|
|
541
|
-
|
|
542
|
-
// Premium features
|
|
543
|
-
auth: {
|
|
544
|
-
post: ['plan:pro'] // Need pro plan or higher
|
|
545
|
-
}
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
### Team Membership
|
|
549
|
-
|
|
550
|
-
```javascript
|
|
551
|
-
api.helpers.auth.registerChecker('team', async (context, { param }) => {
|
|
552
|
-
const membership = await api.resources.team_members.query({
|
|
553
|
-
filter: {
|
|
554
|
-
user_id: context.auth.userId,
|
|
555
|
-
team_id: param
|
|
556
|
-
}
|
|
557
|
-
})
|
|
558
|
-
return membership.length > 0
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
// Team resources
|
|
562
|
-
auth: {
|
|
563
|
-
query: ['team:engineering'] // Must be on engineering team
|
|
564
|
-
}
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
### Multi-Tenant Permissions
|
|
568
|
-
|
|
569
|
-
```javascript
|
|
570
|
-
api.helpers.auth.registerChecker('tenant_role', async (context, { param }) => {
|
|
571
|
-
const tenantId = context.request.headers['x-tenant-id']
|
|
572
|
-
const membership = await api.resources.tenant_members.query({
|
|
573
|
-
filter: {
|
|
574
|
-
tenant_id: tenantId,
|
|
575
|
-
user_id: context.auth.userId
|
|
576
|
-
}
|
|
577
|
-
})
|
|
578
|
-
return membership[0]?.role === param
|
|
579
|
-
})
|
|
580
|
-
|
|
581
|
-
// Tenant-specific permissions
|
|
582
|
-
auth: {
|
|
583
|
-
delete: ['tenant_role:admin'] // Admin in current tenant
|
|
584
|
-
}
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
## Token Management
|
|
588
|
-
|
|
589
|
-
### Accessing Token Data
|
|
590
|
-
|
|
591
|
-
```javascript
|
|
592
|
-
// After validation, you have access to:
|
|
593
|
-
context.auth = {
|
|
594
|
-
userId: '123', // From 'sub' claim
|
|
595
|
-
email: 'user@example.com', // From 'email' claim
|
|
596
|
-
token: { // Full JWT payload
|
|
597
|
-
sub: '123',
|
|
598
|
-
email: 'user@example.com',
|
|
599
|
-
iat: 1234567890,
|
|
600
|
-
exp: 1234567890,
|
|
601
|
-
// Any custom claims from your auth provider
|
|
602
|
-
app_metadata: { /* ... */ },
|
|
603
|
-
user_metadata: { /* ... */ }
|
|
604
|
-
},
|
|
605
|
-
tokenId: 'jti-123' // For revocation
|
|
606
|
-
}
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
### Token Revocation / Logout
|
|
610
|
-
|
|
611
|
-
```javascript
|
|
612
|
-
// Enable revocation
|
|
613
|
-
await api.use(JwtAuthPlugin, {
|
|
614
|
-
jwksUrl: '...',
|
|
615
|
-
revocation: {
|
|
616
|
-
enabled: true,
|
|
617
|
-
storage: 'database' // Survives restarts
|
|
618
|
-
},
|
|
619
|
-
endpoints: {
|
|
620
|
-
logout: '/auth/logout' // Add logout endpoint
|
|
621
|
-
}
|
|
622
|
-
})
|
|
623
|
-
|
|
624
|
-
// Frontend logout
|
|
625
|
-
async function logout(token) {
|
|
626
|
-
// 1. Revoke token on your API
|
|
627
|
-
await fetch('http://localhost:3000/auth/logout', {
|
|
628
|
-
method: 'POST',
|
|
629
|
-
headers: {
|
|
630
|
-
'Authorization': `Bearer ${token}`
|
|
631
|
-
}
|
|
632
|
-
})
|
|
633
|
-
|
|
634
|
-
// 2. Clear Supabase session
|
|
635
|
-
await supabase.auth.signOut()
|
|
636
|
-
}
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
### Direct API Usage (No HTTP)
|
|
640
|
-
|
|
641
|
-
For testing or server-side operations:
|
|
642
|
-
|
|
643
|
-
```javascript
|
|
644
|
-
// Create auth context manually
|
|
645
|
-
const authContext = {
|
|
646
|
-
auth: {
|
|
647
|
-
userId: 'user-123',
|
|
648
|
-
email: 'user@example.com'
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Use API methods directly
|
|
653
|
-
const posts = await api.resources.posts.query({
|
|
654
|
-
filter: { published: true }
|
|
655
|
-
}, authContext) // Pass context as second parameter
|
|
656
|
-
|
|
657
|
-
const newPost = await api.resources.posts.post({
|
|
658
|
-
inputRecord: { /* ... */ }
|
|
659
|
-
}, authContext)
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
## Common Issues and Solutions
|
|
663
|
-
|
|
664
|
-
### Issue: "Access denied" on all requests
|
|
665
|
-
**Solution**: Check that your token is being sent correctly:
|
|
666
|
-
```javascript
|
|
667
|
-
// Correct
|
|
668
|
-
headers: { 'Authorization': `Bearer ${token}` }
|
|
669
|
-
|
|
670
|
-
// Wrong
|
|
671
|
-
headers: { 'Authorization': token } // Missing "Bearer "
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
### Issue: Token validation fails
|
|
675
|
-
**Solution**: Ensure your JWKS URL matches your auth provider:
|
|
676
|
-
```javascript
|
|
677
|
-
// Supabase: Check your project URL
|
|
678
|
-
jwksUrl: 'https://YOUR-PROJECT.supabase.co/auth/v1/.well-known/jwks.json'
|
|
679
|
-
|
|
680
|
-
// Auth0: Check your domain
|
|
681
|
-
jwksUrl: 'https://YOUR-DOMAIN.auth0.com/.well-known/jwks.json'
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
### Issue: Google login token not working
|
|
685
|
-
**Solution**: Remember that Google login still goes through your auth provider:
|
|
686
|
-
- User logs in with Google → Supabase/Auth0 → Get Supabase/Auth0 JWT
|
|
687
|
-
- Your API validates the Supabase/Auth0 JWT, not Google's token
|
|
688
|
-
|
|
689
|
-
### Issue: Ownership not working
|
|
690
|
-
**Solution**: Ensure your schema has a `user_id` field:
|
|
691
|
-
```javascript
|
|
692
|
-
schema: {
|
|
693
|
-
user_id: { type: 'string' } // Required for 'owns' checker
|
|
694
|
-
}
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
## Summary
|
|
698
|
-
|
|
699
|
-
The JWT Authentication Plugin provides:
|
|
700
|
-
1. **Token validation** from any JWT provider (Supabase, Auth0, etc.)
|
|
701
|
-
2. **Three built-in checkers**: `public`, `authenticated`, `owns`
|
|
702
|
-
3. **Framework for custom authorization** via `checker:parameter` pattern
|
|
703
|
-
|
|
704
|
-
Remember:
|
|
705
|
-
- **Frontend**: Handles login (email, Google, etc.) and gets JWT
|
|
706
|
-
- **Backend**: Validates JWT and enforces permissions
|
|
707
|
-
- **Start simple**: Use built-in checkers, add custom logic as needed
|